glytch/fktry/src/ui/chipspec.test.ts
type-two a57729ce33 [ui] 57 tests for pagination, hit-testing, selection, keymap, chips, voice
Order asked for a minimum of ten under happy-dom. happy-dom is not actually
installed (not in package.json, not in node_modules) and root config is not
mine to edit, so these run in the default node environment instead — which is
why the logic was factored into pure modules first.

Covers everything the order named except the two things that genuinely need a
DOM: chip rendering (the arithmetic is covered) and attach()'s window binding
(the routing is covered).

Pins the invariants worth pinning: no build page ever exceeds nine slots at
any catalog size, syncRoster copies values rather than holding sim buffers
across frames, footprint hit-testing respects rotation, and an unknown jam
reason or machine kind degrades instead of throwing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 17:27:58 +10:00

86 lines
2.9 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import type { CommissionDef, EntityState, RecipeDef } from '../contracts';
import { commissionChips, commissionMet, intakeChips, outputChips } from './chipspec';
function ent(over: Partial<EntityState> = {}): EntityState {
return {
id: 1, def: 'demuxer', pos: { x: 0, y: 0 }, dir: 0, recipe: 'demux-ore',
progress: 0, inputBuf: {}, outputBuf: {}, jammed: null, heat: 0, ...over,
};
}
const DEMUX: RecipeDef = {
id: 'demux-ore', machine: 'demuxer',
inputs: { 'mdat-ore': 2 },
outputs: { 'luma-bars': 1, 'chroma-slurry': 1 },
ticks: 45, bandwidth: 3,
};
describe('intakeChips', () => {
it('reads have/need and marks a short intake', () => {
expect(intakeChips(DEMUX, ent({ inputBuf: { 'mdat-ore': 1 } }))).toEqual([
{ item: 'mdat-ore', count: '1/2', state: 'short' },
]);
});
it('marks intake met once the buffer covers the recipe', () => {
expect(intakeChips(DEMUX, ent({ inputBuf: { 'mdat-ore': 2 } }))[0].state).toBe('met');
});
it('treats a missing buffer entry as zero rather than NaN', () => {
expect(intakeChips(DEMUX, ent())[0].count).toBe('0/2');
});
});
describe('outputChips', () => {
it('lists recipe outputs even when the buffer is empty', () => {
expect(outputChips(DEMUX, ent()).map((c) => c.item))
.toEqual(['luma-bars', 'chroma-slurry']);
});
it('also surfaces buffer contents the recipe never mentions — that is the jam', () => {
const chips = outputChips(DEMUX, ent({ outputBuf: { 'luma-bars': 3, melt: 1 } }));
expect(chips).toContainEqual({ item: 'melt', count: '1' });
expect(chips).toContainEqual({ item: 'luma-bars', count: '3' });
});
it('does not duplicate an item present in both recipe and buffer', () => {
const ids = outputChips(DEMUX, ent({ outputBuf: { 'luma-bars': 2 } })).map((c) => c.item);
expect(ids.filter((i) => i === 'luma-bars')).toHaveLength(1);
});
});
describe('commissionChips', () => {
const c: CommissionDef = {
id: 'first-taste', flavor: '', wants: { melt: 40 }, rewardBandwidth: 50,
};
it('shows delivered against wanted', () => {
expect(commissionChips(c, { melt: 3 })).toEqual([
{ item: 'melt', count: '3/40', state: 'plain' },
]);
});
it('never reports more delivered than wanted', () => {
expect(commissionChips(c, { melt: 99 })[0].count).toBe('40/40');
});
it('marks met at exactly the wanted count', () => {
expect(commissionChips(c, { melt: 40 })[0].state).toBe('met');
});
});
describe('commissionMet', () => {
const two: CommissionDef = {
id: 'x', flavor: '', wants: { melt: 2, 'hf-dust': 1 }, rewardBandwidth: 0,
};
it('is false while any want is short', () => {
expect(commissionMet(two, { melt: 2 })).toBe(false);
});
it('is true only once every want is covered', () => {
expect(commissionMet(two, { melt: 2, 'hf-dust': 1 })).toBe(true);
});
});