feat(frontend): breakable grass block on the 404 page

Implements the last unbuilt easter egg (PLAN Task 11 #5, TODO «Пасхалки» #5): the
404 used to show static ASCII art; now a grass block drops into a dusk world,
cracks over three clicks, then bursts into square debris and respawns. Keyboard
accessible (a real button), and under prefers-reduced-motion the fall and burst
are skipped while the crack stages still work. Replaces the NOT_FOUND_ART pane;
i18n adds notFound.blockAria/blockHint (ru+en).
This commit is contained in:
loki5512344 2026-09-28 13:51:00 +02:00
parent 07b3f55bee
commit 904c724c9f
Signed by: boba
GPG key ID: 253067914055423B
6 changed files with 201 additions and 4 deletions

View file

@ -0,0 +1,36 @@
import { screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { expect, test, vi } from 'vitest'
import { BreakableBlock } from '../BreakableBlock'
import { renderApp } from '../../../test/render'
// motion's useReducedMotion reads matchMedia, which jsdom lacks.
vi.stubGlobal('matchMedia', (query: string) => ({
matches: !query.includes('reduce'),
media: query,
onchange: null,
addEventListener() {},
removeEventListener() {},
addListener() {},
removeListener() {},
dispatchEvent: () => false,
}))
test('a block drops in and cracks over three clicks, then bursts and respawns', async () => {
const user = userEvent.setup()
renderApp([{ path: '/', element: <BreakableBlock /> }])
// The block remounts (new key) on respawn, so re-query it before each click.
const block = () => screen.getByRole('button', { name: 'Сломать блок' })
expect(block().querySelector('.lv-block-crack')).toBeNull()
await user.click(block())
expect(block().querySelector('.lv-block-crack')).toHaveAttribute('data-stage', '1')
await user.click(block())
expect(block().querySelector('.lv-block-crack')).toHaveAttribute('data-stage', '2')
// Third click shatters: cracks clear (respawn) and debris spawns.
await user.click(block())
expect(block().querySelector('.lv-block-crack')).toBeNull()
expect(document.querySelectorAll('.lv-block-particle').length).toBeGreaterThan(0)
})