- IncidentReportScene never stopped itself: the paperwork rendered on top of the roster after the FIRST night of every run. Looked like a freeze. - HaulDeskScene never reset per-night state: from night two the back office was a blank $0 desk that ate every input. Mine, from SOLO-26 — adding scene.stop() is exactly what makes Phaser hand back a dirty instance next time. - Dev routes (n/p/f/j/m) shipped unguarded on window; a stray F binned the shift. Now Ctrl+Shift in built games, and R routes home to the roster. - tests/sceneLifecycle.test.ts guards the class, verified to fail on the real bug. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
85 lines
3.9 KiB
TypeScript
85 lines
3.9 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
|
|
// The bug class this file exists for, hit FOUR times now:
|
|
// 1. DoorScene.busy stuck true after a late ruling (dead door for the run)
|
|
// 2. seenCards leaking across nights (every regular stamped SEEN TONIGHT)
|
|
// 3. IncidentReportScene never stopping (paperwork frozen over the roster)
|
|
// 4. HaulDeskScene never resetting (a blank $0 desk that eats every input)
|
|
//
|
|
// Phaser instantiates each configured scene ONCE and reuses that instance for
|
|
// the life of the game. So two rules hold for every scene that is entered more
|
|
// than once per session:
|
|
// - anything scoped to one night must be reset in create()/init(), never
|
|
// left to a class-field initialiser, and
|
|
// - a scene that hands control to a scene it does not own must stop itself,
|
|
// because ScenePlugin.start only queues a stop for its OWN key.
|
|
//
|
|
// Driving real Phaser here would need a canvas and a DOM; these are source
|
|
// assertions instead. Cheaper, and they fail for the right reason with a
|
|
// message that names the fix.
|
|
|
|
// Vite's glob import instead of node:fs — this suite runs under the app's
|
|
// tsconfig, which has no node types, and `?raw` gives us the source as a string
|
|
// without adding a dependency or loosening the config for one test.
|
|
const SOURCES = import.meta.glob('../src/**/*.ts', { query: '?raw', import: 'default', eager: true }) as Record<string, string>;
|
|
|
|
const src = (p: string): string => {
|
|
const hit = Object.entries(SOURCES).find(([k]) => k.endsWith(`/src/${p}`));
|
|
if (!hit) throw new Error(`sceneLifecycle: could not read src/${p}`);
|
|
return hit[1];
|
|
};
|
|
|
|
describe('scene lifecycle: state that must not survive the night', () => {
|
|
it('HaulDeskScene clears every per-night field in create()', () => {
|
|
const s = src('scenes/shared/HaulDeskScene.ts');
|
|
const body = s.slice(s.indexOf('create(): void {'));
|
|
for (const field of ['finished', 'idx', 'counted', 'phase', 'phaseMs', 'phaseFor', 'falling']) {
|
|
expect(body, `HaulDeskScene.create() must reset this.${field}`).toContain(`this.${field} =`);
|
|
}
|
|
});
|
|
|
|
it('IncidentReportScene resets its answers and index per night', () => {
|
|
const s = src('scenes/door/IncidentReportScene.ts');
|
|
const head = s.slice(0, s.indexOf('showQuestion'));
|
|
expect(head).toMatch(/this\.(answers|idx|index)\s*=/);
|
|
});
|
|
|
|
it('DoorScene clears the passback net and the busy latch every night', () => {
|
|
// Both of these have already shipped as bugs once.
|
|
const body = src('scenes/door/DoorScene.ts');
|
|
expect(body).toContain('this.seenCards.clear()');
|
|
expect(body).toContain('this.busy = false');
|
|
});
|
|
});
|
|
|
|
describe('scene lifecycle: a scene that hands over must stop itself', () => {
|
|
// Each of these hands control to a scene it does not own, via a `next`
|
|
// closure or a direct start of a sibling. Phaser will not stop them for us.
|
|
const HANDOVERS: Array<[string, string]> = [
|
|
['scenes/shared/HaulDeskScene.ts', 'the back office'],
|
|
['scenes/door/IncidentReportScene.ts', 'the paperwork'],
|
|
];
|
|
|
|
for (const [path, what] of HANDOVERS) {
|
|
it(`${what} stops itself on the way out`, () => {
|
|
const s = src(path);
|
|
expect(
|
|
s.includes('this.scene.stop()'),
|
|
`${path}: calls into another scene but never stops itself — it will keep ` +
|
|
`rendering underneath whatever comes next (ScenePlugin.start only stops its own key)`,
|
|
).toBe(true);
|
|
});
|
|
}
|
|
|
|
it('and the handover happens AFTER the stop, not before', () => {
|
|
// Stopping after the handover would tear down the scene that was just
|
|
// started in some orderings; stop first, then hand on.
|
|
const s = src('scenes/shared/HaulDeskScene.ts');
|
|
const stop = s.lastIndexOf('this.scene.stop()');
|
|
const next = s.indexOf('this.args.next()', stop);
|
|
expect(stop).toBeGreaterThan(-1);
|
|
expect(next, 'next() should follow scene.stop() in the exit handler').toBeGreaterThan(stop);
|
|
});
|
|
});
|