testsAndMisc/reverse_survivors/src/render/draw.test.ts
Krzysztof Rudnicki a47191673a feat(reverse_survivors): migrate Reverse Survivors game into the monorepo
Moves the standalone React 19 + Vite 8 + TypeScript 6 game from
~/Downloads/reverse/reverse-survivors into reverse_survivors/, so it is
version-controlled, indexed, and covered by CI instead of sitting untracked
outside any repository.

- Relocates the project's nested .github/workflows/ci.yml to the repository
  root as .github/workflows/reverse-survivors-ci.yml. GitHub only reads
  workflows from the root, so the nested copy would have been inert — same
  treatment billsplit received. The workflow is path-scoped to
  reverse_survivors/** and runs npm ci / check / build on Node 22.
- Sets cache-dependency-path on actions/setup-node, since
  defaults.run.working-directory applies only to run: steps and not to
  actions; without it the npm cache looks for a lockfile at the root.
- Keeps npm and the existing package-lock.json. TypeScript stays pinned at
  6.0.3 deliberately: typescript-eslint 8.65 caps its peer range at <6.1.0
  and TS 7 drops the JS compiler API that typed linting needs.
- Adds a reverse_survivors/ row to the Repository Layout table in README.md
  and CLAUDE.md.

Verified in place: npm ci, npm run check (95 tests, 100% statements /
branches / functions / lines), npm run build, and a dev-server smoke check
serving /src/main.tsx.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 10:45:12 +02:00

64 lines
2.2 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { createInitialState, makeEnemy } from '../core/sim'
import type { GameState } from '../core/types'
import { makeCtxStub } from '../test/harness'
import { draw } from './draw'
const fresh = (): GameState => createInitialState(1)
// Empty state baseline: ritual circle + survivor halo + body + hp ring.
const BASE = { beginPath: 4, arc: 4, fill: 2, stroke: 2, fillRect: 1 }
describe('draw', () => {
it('paints the empty arena baseline', () => {
const ctx = makeCtxStub()
draw(ctx, fresh())
expect(ctx.fillRect).toHaveBeenCalledTimes(BASE.fillRect)
expect(ctx.beginPath).toHaveBeenCalledTimes(BASE.beginPath)
expect(ctx.arc).toHaveBeenCalledTimes(BASE.arc)
expect(ctx.fill).toHaveBeenCalledTimes(BASE.fill)
expect(ctx.stroke).toHaveBeenCalledTimes(BASE.stroke)
})
it('paints projectiles from both sides', () => {
const ctx = makeCtxStub()
const s = fresh()
s.projectiles.push(
{ id: 1, from: 'survivor', pos: { x: 10, y: 10 }, vel: { x: 0, y: 0 }, radius: 4, damage: 1, ttl: 1 },
{ id: 2, from: 'enemy', pos: { x: 20, y: 20 }, vel: { x: 0, y: 0 }, radius: 5, damage: 1, ttl: 1 },
)
draw(ctx, s)
expect(ctx.fill).toHaveBeenCalledTimes(BASE.fill + 2)
expect(ctx.arc).toHaveBeenCalledTimes(BASE.arc + 2)
})
it('a fresh grunt gets a body but no ring and no hp arc', () => {
const ctx = makeCtxStub()
const s = fresh()
makeEnemy(s, 'rusher', { x: 100, y: 100 })
draw(ctx, s)
expect(ctx.fill).toHaveBeenCalledTimes(BASE.fill + 1)
expect(ctx.stroke).toHaveBeenCalledTimes(BASE.stroke)
})
it('a damaged enemy shows an hp arc', () => {
const ctx = makeCtxStub()
const s = fresh()
const tank = makeEnemy(s, 'tank', { x: 100, y: 100 })
tank.hp = 60
draw(ctx, s)
expect(ctx.fill).toHaveBeenCalledTimes(BASE.fill + 1)
expect(ctx.stroke).toHaveBeenCalledTimes(BASE.stroke + 1)
})
it('bosses wear a blood ring', () => {
const ctx = makeCtxStub()
const s = fresh()
makeEnemy(s, 'colossus', { x: 100, y: 100 })
draw(ctx, s)
expect(ctx.fill).toHaveBeenCalledTimes(BASE.fill + 1)
expect(ctx.stroke).toHaveBeenCalledTimes(BASE.stroke + 1)
expect(ctx.arc).toHaveBeenCalledTimes(BASE.arc + 2)
})
})