chore(history): squash 16 commit(s) from 2026-09-26
- chore(backend): sync Cargo.lock with gateway dev-dependency - feat(frontend): ClickGui replica matching the in-game GUI — category panels, Modules/Settings switch, theme cards - feat(frontend): floating glass top bar in the mod's UI language - fix(frontend): soft night backdrop for the ClickGui, editor that fits one screen - chore(frontend): replace em dashes in UI copy with lighter separators - refactor(frontend): split landing sections and theme editor into subfolders (4-files-per-folder rule) - feat(frontend): login and registration in the mod's glass panel style - feat(frontend): link the game with a device code - feat(frontend): account page with avatar upload and linked games - feat(frontend): my configs with share codes and publishing - feat(frontend): public showcase listing and detail pages - chore(frontend): drop em dashes from visible copy - feat(frontend): atmospheric backdrop, glass download block and ClickGui-style FAQ on the landing - feat(frontend): Konami code, DevTools console greeting and ASCII 404 block - feat: public player profiles at /u/:id and author link in the showcase - fix(frontend): stop the theme editor overflowing horizontally at 375px
This commit is contained in:
parent
7f4b532f99
commit
ff2edc8eb6
116 changed files with 3534 additions and 1020 deletions
|
|
@ -1,47 +1,95 @@
|
|||
import { act, render, screen, within } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { expect, test, vi } from 'vitest'
|
||||
import { PRESETS } from '../../themes/presets.generated'
|
||||
import { argbToCss } from '../../themes/types'
|
||||
import { ActiveThemeProvider } from '../../themes/useActiveTheme'
|
||||
import { ClickGuiWindow } from '../ClickGuiWindow'
|
||||
import { DEMO, guiReducer, initialGuiState, LOOP_MS } from '../demo'
|
||||
|
||||
const renderGui = () => render(<ActiveThemeProvider applyToPage={false}><ClickGuiWindow /></ActiveThemeProvider>)
|
||||
const row = (name: string) => screen.getByRole('button', { name: new RegExp(`^${name}$`) })
|
||||
|
||||
test('reduced motion shows the finished demo frame with no cursor', () => {
|
||||
test('the Modules screen shows the four in-game category panels', () => {
|
||||
renderGui()
|
||||
expect(screen.getByRole('button', { name: 'Visuals' })).toHaveAttribute('aria-pressed', 'true')
|
||||
expect(screen.getByRole('switch', { name: 'Включить Trails' })).toHaveAttribute('aria-checked', 'true')
|
||||
expect(screen.getByRole('slider', { name: 'Length' })).toHaveValue('85')
|
||||
const panels = within(screen.getByRole('list', { name: 'Категории модулей' })).getAllByRole('listitem', { name: /./ })
|
||||
expect(panels).toHaveLength(4)
|
||||
for (const title of ['Combat', 'Player', 'Visuals', 'Other']) expect(screen.getByRole('heading', { name: title })).toBeInTheDocument()
|
||||
expect(screen.getByRole('tab', { name: 'Modules' })).toHaveAttribute('aria-selected', 'true')
|
||||
})
|
||||
|
||||
test('cards are interactive: switch categories, toggle and expand', async () => {
|
||||
test('enabled modules carry a check mark; clicking a row toggles it', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderGui()
|
||||
await user.click(screen.getByRole('button', { name: 'Misc' }))
|
||||
const toggle = screen.getByRole('switch', { name: 'Включить StreamerMode' })
|
||||
expect(toggle).toHaveAttribute('aria-checked', 'false')
|
||||
await user.click(toggle)
|
||||
expect(toggle).toHaveAttribute('aria-checked', 'true')
|
||||
await user.click(screen.getByRole('button', { name: 'Настройки StreamerMode' }))
|
||||
const card = toggle.closest('article') as HTMLElement
|
||||
expect(within(card).getByRole('checkbox', { name: /Hide IP/ })).toHaveAttribute('aria-checked', 'true')
|
||||
const predictions = row('Predictions')
|
||||
expect(predictions).toHaveAttribute('aria-pressed', 'true')
|
||||
expect(predictions.querySelector('.lv-gui-row-check')).not.toBeNull()
|
||||
const autoLeave = row('Auto Leave')
|
||||
expect(autoLeave.querySelector('.lv-gui-row-check')).toBeNull()
|
||||
await user.click(autoLeave)
|
||||
expect(autoLeave).toHaveAttribute('aria-pressed', 'true')
|
||||
expect(autoLeave.querySelector('.lv-gui-row-check')).not.toBeNull()
|
||||
})
|
||||
|
||||
test('the scripted demo toggles Trails and loops back to the start', () => {
|
||||
test('reduced motion shows the finished demo frame', () => {
|
||||
renderGui()
|
||||
expect(row('AmbientParticles')).toHaveAttribute('aria-pressed', 'true')
|
||||
expect(row('DamageTint')).toHaveAttribute('aria-pressed', 'true')
|
||||
})
|
||||
|
||||
test('Settings lists every preset as a theme card, and picking one re-themes the GUI', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderGui()
|
||||
await user.click(screen.getByRole('tab', { name: 'Settings' }))
|
||||
expect(screen.getByRole('heading', { name: 'Темы' })).toBeInTheDocument()
|
||||
expect(screen.getAllByRole('option')).toHaveLength(PRESETS.length)
|
||||
const nitro = PRESETS.find((p) => p.id === 'nitro')!
|
||||
await user.click(screen.getByRole('option', { name: 'Тема Nitro' }))
|
||||
expect(screen.getByRole('option', { name: 'Тема Nitro' })).toHaveAttribute('aria-selected', 'true')
|
||||
expect(screen.getByTestId('clickgui-root').style.getPropertyValue('--gui-accent')).toBe(argbToCss(nitro.theme.accent))
|
||||
await user.click(screen.getByRole('tab', { name: 'Modules' }))
|
||||
expect(screen.getByRole('heading', { name: 'Combat' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
test('the scripted demo toggles modules, visits Settings and loops back', () => {
|
||||
vi.useFakeTimers()
|
||||
vi.stubGlobal('matchMedia', (q: string) => ({ matches: !q.includes('reduce'), addEventListener() {}, removeEventListener() {} }))
|
||||
renderGui()
|
||||
expect(screen.getByRole('button', { name: 'Combat' })).toHaveAttribute('aria-pressed', 'true')
|
||||
act(() => vi.advanceTimersByTime(3200))
|
||||
expect(screen.getByRole('switch', { name: 'Включить Trails' })).toHaveAttribute('aria-checked', 'true')
|
||||
act(() => vi.advanceTimersByTime(LOOP_MS))
|
||||
expect(screen.getByRole('button', { name: 'Combat' })).toHaveAttribute('aria-pressed', 'true')
|
||||
expect(row('AmbientParticles')).toHaveAttribute('aria-pressed', 'false')
|
||||
act(() => vi.advanceTimersByTime(2000))
|
||||
expect(row('AmbientParticles')).toHaveAttribute('aria-pressed', 'true')
|
||||
act(() => vi.advanceTimersByTime(3000))
|
||||
expect(screen.getByRole('tab', { name: 'Settings' })).toHaveAttribute('aria-selected', 'true')
|
||||
act(() => vi.advanceTimersByTime(LOOP_MS + 600 - 5000 + 500)) // into the next cycle, before its first click
|
||||
expect(screen.getByRole('tab', { name: 'Modules' })).toHaveAttribute('aria-selected', 'true')
|
||||
expect(row('AmbientParticles')).toHaveAttribute('aria-pressed', 'false')
|
||||
vi.unstubAllGlobals()
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
test('demo timeline fits in the loop and the reducer resets', () => {
|
||||
expect(Math.max(...DEMO.map((s) => s.at))).toBeLessThan(LOOP_MS)
|
||||
const toggled = guiReducer(initialGuiState(), { type: 'toggle', module: 'Trails' })
|
||||
expect(toggled.enabled.Trails).toBe(true)
|
||||
expect(guiReducer(toggled, { type: 'reset' }).enabled.Trails).toBe(false)
|
||||
const toggled = guiReducer(initialGuiState(), { type: 'toggle', module: 'Chams' })
|
||||
expect(toggled.enabled.Chams).toBe(false)
|
||||
const settings = guiReducer(toggled, { type: 'tab', id: 'settings' })
|
||||
expect(guiReducer(settings, { type: 'reset' })).toEqual(initialGuiState())
|
||||
})
|
||||
|
||||
test('controlled mode: onPickTheme receives the card and the site theme stays put; backdrop is swappable', async () => {
|
||||
const user = userEvent.setup()
|
||||
const onPickTheme = vi.fn()
|
||||
render(
|
||||
<ActiveThemeProvider applyToPage={false}>
|
||||
<ClickGuiWindow demo={false} onPickTheme={onPickTheme} backdrop="/shots/f1.png" />
|
||||
</ActiveThemeProvider>,
|
||||
)
|
||||
const root = screen.getByTestId('clickgui-root')
|
||||
expect(root.style.getPropertyValue('--gui-backdrop-image')).toBe('url("/shots/f1.png")')
|
||||
await user.click(screen.getByRole('tab', { name: 'Settings' }))
|
||||
const before = screen.getByRole('option', { selected: true })
|
||||
const accent = root.style.getPropertyValue('--gui-accent')
|
||||
await user.click(screen.getByRole('option', { name: 'Тема Legacy' }))
|
||||
expect(onPickTheme).toHaveBeenCalledWith('legacy')
|
||||
expect(screen.getByRole('option', { selected: true })).toBe(before)
|
||||
expect(root.style.getPropertyValue('--gui-accent')).toBe(accent)
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue