1import test from 'node:test';2import assert from 'node:assert/strict';3import { parseRules } from '../src/engine/rules.js';45const semanticProperties = new Set(['YOU', 'PUSH', 'STOP', 'WIN']);6const word = (text, x, y, id = `${text}-${x}-${y}`) => ({ kind: 'word', text, x, y, id });78test('pure rule parser preserves stacked sources and maximal physical spans', () => {9 const objects = [10 word('ROCK', 0, 0, 'h-rock'), word('IS', 1, 0, 'h-is'), word('STOP', 2, 0, 'h-stop'),11 word('ROCK', 0, 2, 'v-rock'), word('IS', 0, 3, 'v-is'), word('STOP', 0, 4, 'v-stop'),12 word('ROCK', 3, 0, 'and-rock'), word('AND', 4, 0, 'and-op'), word('FLAG', 5, 0, 'and-flag'),13 word('IS', 6, 0, 'and-is'), word('WIN', 7, 0, 'and-win'),14 ];15 const parsed = parseRules(objects, semanticProperties);16 assert.equal(parsed.rules.filter(rule => rule.text === 'ROCK IS STOP').length, 1);17 assert.deepEqual(parsed.sentences, [18 { ids: ['h-rock', 'h-is', 'h-stop'], axis: 'h' },19 { ids: ['and-rock', 'and-op', 'and-flag', 'and-is', 'and-win'], axis: 'h' },20 { ids: ['v-rock', 'v-is', 'v-stop'], axis: 'v' },21 ]);22});2324test('pure rule parser does not mutate its object input', () => {25 const objects = [word('MIAU', 0, 0), word('IS', 1, 0), word('YOU', 2, 0)];26 const before = structuredClone(objects);27 const parsed = parseRules(objects, semanticProperties);28 assert.deepEqual(objects, before);29 assert.deepEqual(parsed.rules.map(rule => rule.text), ['MIAU IS YOU']);30});