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:
loki5512344 2026-09-26 15:26:37 +02:00
parent 7f4b532f99
commit ff2edc8eb6
116 changed files with 3534 additions and 1020 deletions

View file

@ -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)
})