mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-11 16:34:06 +01:00
Split along actor lines, but not the obvious way. A plain sim/enemies cut
needs makeEnemy and grantXp going out and enemiesStep coming back -- a cycle.
Nothing here would have caught it: no eslint-plugin-import, and tsc and vitest
both pass on cyclic ES modules. It shows up as a TDZ error in the bundle.
So the graph is a tree instead:
simUnit.ts (42) leaf -- geometry, projectile constants, makeEnemy,
enemySpeed; imports only types and vec
simSurvivor.ts (132) createSurvivor, xp/upgrades, survivorStep
simThreats.ts (168) enemiesStep, projectilesStep, and enemy death
sim.ts (69) createInitialState and step, the only module that
reaches all three
Repointing director.ts's makeEnemy at the leaf also removes the sim <->
director cycle that was already there, surviving on const-arrow late binding.
npm run check and npm run build both pass -- 160 tests, coverage identical at
531/159/101/514, 32 modules bundled at 210.09 kB against 210.11 kB before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
91 lines
3.1 KiB
TypeScript
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/simUnit'
|
|
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()
|
|
})
|
|
})
|