testsAndMisc/reverse_survivors/src/App.test.tsx
Krzysztof kuhy Rudnicki 5d0caa9725
Some checks failed
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
reverse-survivors CI / check (push) Has been cancelled
reverse_survivors: difficulty tiers
Haunting, Normal and Crusade scale enemy hp and speed, the survivor's
health, your income, and the length of the night. Normal is exactly 1.0 on
every multiplier — it is the identity tier, which is what lets every
existing raw-value assertion keep passing untouched.

Enemy speed now derives from a single helper, so difficulty and frenzy
compose in one place rather than fighting at three call sites.

Also fixes a bug this surfaced: handleRestart ignored the selected tier, so
restarting after choosing Crusade silently reverted to Normal. The
react-hooks purity rule flagged Date.now() in the new handler, and
extracting restartSeed() exposed the omission.

Endless mode is deliberately left out — fmtTime(Infinity) renders
Infinity:NaN, which would cost a guard plus tests for a mode whose only
content is the same game but longer.

README rewritten for all four features, including the reversal of its old
placement promise.

160 tests, 100% coverage.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VFyXexNjMQG9V7K8de17fc
2026-08-09 23:17:07 +02:00

91 lines
3.1 KiB
TypeScript

import { act, fireEvent, render, screen } from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { App } from './App'
import { makeEnemy } from './core/sim'
import type { RafHarness } from './test/harness'
import { installRaf, makeCtxStub } from './test/harness'
afterEach(() => {
vi.unstubAllGlobals()
vi.restoreAllMocks()
})
const pump = (raf: RafHarness, ts: number): void => {
act(() => {
raf.pump(ts)
})
}
describe('App', () => {
it('runs the loop and accrues souls even without a canvas context', () => {
const raf = installRaf()
render(<App />)
expect(screen.getByText('30 / 400')).toBeInTheDocument()
pump(raf, 0)
for (let i = 1; i <= 20; i += 1) {
pump(raf, i * 1000)
}
expect(screen.getByText('42 / 400')).toBeInTheDocument()
expect(screen.getByText(/0:02 \/ 5:00/)).toBeInTheDocument()
})
it('spends souls on a spawn and draws the horde', () => {
const raf = installRaf()
const stub = makeCtxStub()
vi.spyOn(HTMLCanvasElement.prototype, 'getContext')
.mockImplementation(() => stub as unknown as CanvasRenderingContext2D)
render(<App seed={5} />)
fireEvent.click(screen.getByRole('button', { name: /rusher/i }))
pump(raf, 0)
expect(screen.getByText('20 / 400')).toBeInTheDocument()
const arcsWithHorde = stub.arc.mock.calls.length
expect(arcsWithHorde).toBeGreaterThan(4)
})
it('shows the director victory overlay and restarts fresh', () => {
const raf = installRaf()
render(
<App
seed={9}
boot={(state) => {
state.survivor.hp = 5
makeEnemy(state, 'rusher', { x: state.survivor.pos.x, y: state.survivor.pos.y })
}}
/>,
)
pump(raf, 0)
expect(screen.getByText('The intruder falls')).toBeInTheDocument()
fireEvent.click(screen.getByRole('button', { name: /open the gates/i }))
expect(screen.queryByText('The intruder falls')).not.toBeInTheDocument()
expect(screen.getByText('30 / 400')).toBeInTheDocument()
})
it('restarts on a new tier when the difficulty is changed', () => {
const raf = installRaf()
render(<App seed={9} />)
pump(raf, 0)
// Normal runs 5:00; Crusade runs 6:00, so the clock proves the tier took.
expect(screen.getByText(/\/ 5:00/)).toBeInTheDocument()
fireEvent.click(screen.getByRole('button', { name: 'Crusade' }))
expect(screen.getByText(/\/ 6:00/)).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Crusade' })).toHaveClass('diff-on')
})
it('dispatches a director power from the bar', () => {
const raf = installRaf()
render(<App seed={9} boot={(state) => { state.director.energy = 400 }} />)
pump(raf, 0)
fireEvent.click(screen.getByRole('button', { name: /frenzy/i }))
pump(raf, 16)
expect(screen.getByText(/frenzied/i)).toBeInTheDocument()
})
it('shows the dawn overlay when the survivor outlasts the horde', () => {
const raf = installRaf()
render(<App boot={(state) => { state.duration = 0.05 }} />)
pump(raf, 0)
pump(raf, 100)
expect(screen.getByText('Dawn breaks')).toBeInTheDocument()
})
})