import { describe, it, expect } from 'vitest'; import { readFileSync } from 'node:fs'; import { Dawg } from './dawg'; // The step-by-step DAWG cursor (root/final/next/arcs) is the primitive the move // generator walks. These fast unit tests pin it against a small committed sample // dictionary (backend/cmd/movegen); the full parity vs the Go solver lands with // the generator's conformance fixtures. const bytes = new Uint8Array(readFileSync(new URL('./testdata/sample_en.dawg', import.meta.url))); const fixture = JSON.parse( readFileSync(new URL('./testdata/sample_en.words.json', import.meta.url), 'utf8'), ) as { numAdded: number; words: string[]; indexes: number[][] }; const key = (w: number[]): string => w.join(','); // enumerateWords walks the whole automaton depth-first, collecting the index path // at every accepting node — i.e. every stored word. function enumerateWords(d: Dawg): number[][] { const out: number[][] = []; const path: number[] = []; const visit = (node: number): void => { d.arcs(node, (label, dest, final) => { path.push(label); if (final) out.push(path.slice()); visit(dest); path.pop(); return true; }); }; if (d.final(d.root())) out.push([]); visit(d.root()); return out; } describe('dawg cursor', () => { it('parses the sample fixture', () => { const d = new Dawg(bytes); expect(d.numAdded).toBe(fixture.numAdded); }); it('root is not an accepting state (the sample has no empty word)', () => { const d = new Dawg(bytes); expect(d.final(d.root())).toBe(false); }); it('enumerates exactly the stored words', () => { const d = new Dawg(bytes); const got = enumerateWords(d).map(key).sort(); const want = fixture.indexes.map(key).sort(); expect(got).toEqual(want); }); it('next walks a stored word to an accepting node and rejects a non-edge', () => { const d = new Dawg(bytes); let node = d.root(); for (const ch of [2, 0, 17, 4, 3]) { // "cared" node = d.next(node, ch); expect(node).toBeGreaterThanOrEqual(0); } expect(d.final(node)).toBe(true); // "care" (index [2,0,17,4]) is an internal accepting node on the way to "cared". const care = [2, 0, 17, 4].reduce((n, ch) => d.next(n, ch), d.root()); expect(d.final(care)).toBe(true); // No stored word starts with 'z' (index 25). expect(d.next(d.root(), 25)).toBe(-1); }); });