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>
86 lines
2.9 KiB
TypeScript
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);
|
|
});
|
|
});
|