not-tonight/tests/haul.test.ts
type-two 8bd219796a The back office: tip the night into a coin sorter and watch it cope
Between the swept floor and the paperwork. Cash counts, the phone slides into a
lost-property shoebox, curios JAM the machine, and the bag stops it dead with a
different line depending on whether you pocketed it hours ago. Ledger builds
down the left as a receipt for the night.

Sequence runs off update(), not tween callbacks — a dropped callback would have
stranded the player short of the paperwork. Desk also stops itself on handoff.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 16:09:18 +10:00

77 lines
3.1 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { REFUSE_QUIP, REFUSE_BINNED_QUIP, tallyHaul, sorterVerdict } from '../src/rules/haul';
import { rollDrops } from '../src/rules/floorScore';
import { SeededRNG } from '../src/core/SeededRNG';
import type { Drop } from '../src/rules/floorScore';
const d = (kind: Drop['kind'], value = 0, label = kind): Drop => ({ kind, value, label });
describe('the coin sorter', () => {
it('counts money and nothing else', () => {
const t = tallyHaul([d('note', 20), d('coins', 7), d('curio'), d('phone'), d('baggie')], true);
expect(t.counted).toBe(27);
});
it('jams on the objects, and that is the whole joke', () => {
const t = tallyHaul([d('curio'), d('curio'), d('note', 5)], false);
expect(t.jams).toBe(2);
expect(t.lines.filter((l) => l.fate === 'jammed')).toHaveLength(2);
expect(t.lines.find((l) => l.fate === 'counted')?.value).toBe(5);
});
it('the phone goes sideways into lost property, never into the total', () => {
const t = tallyHaul([d('phone')], false);
expect(t.lines[0]!.fate).toBe('rejected');
expect(t.counted).toBe(0);
});
it('the bag gets a different line depending on what you did with it', () => {
expect(tallyHaul([d('baggie')], true).lines[0]!.quip).toBe(REFUSE_QUIP);
expect(tallyHaul([d('baggie')], false).lines[0]!.quip).toBe(REFUSE_BINNED_QUIP);
});
it('keeps the order you picked things up in — the desk replays your night', () => {
const haul: Drop[] = [
{ kind: 'note', value: 10, label: 'a' },
{ kind: 'curio', value: 0, label: 'b' },
{ kind: 'coins', value: 3, label: 'c' },
];
expect(tallyHaul(haul, false).lines.map((l) => l.label)).toEqual(['a', 'b', 'c']);
});
it('is deterministic — the same night tells the same joke', () => {
const haul = rollDrops(new SeededRNG(4207).stream('sweep'), 40);
expect(tallyHaul(haul, true)).toEqual(tallyHaul(haul, true));
});
it('every line has words, whatever the machine made of it', () => {
const haul = rollDrops(new SeededRNG(9).stream('sweep'), 60);
for (const line of tallyHaul(haul, true).lines) {
expect(line.quip.length).toBeGreaterThan(5);
expect(line.label.length).toBeGreaterThan(0);
}
});
it('an empty haul is handled, and says so', () => {
const t = tallyHaul([], false);
expect(t).toMatchObject({ counted: 0, jams: 0 });
expect(t.lines).toEqual([]);
expect(sorterVerdict(t)).toContain('nothing in the pockets');
});
it('the sign-off escalates with how badly you treated the machine', () => {
const clean = sorterVerdict(tallyHaul([d('note', 5)], false));
const rough = sorterVerdict(tallyHaul(Array(5).fill(d('curio')), false));
expect(clean).not.toBe(rough);
expect(rough).toContain('looked at');
});
it('a real swept night always gives the desk something to do', () => {
for (const seed of [4207, 4208, 99]) {
const t = tallyHaul(rollDrops(new SeededRNG(seed).stream('sweep'), 40), true);
expect(t.lines.length).toBeGreaterThan(0);
expect(t.counted).toBeGreaterThan(0);
}
});
});