import assert from 'node:assert/strict'; import test from 'node:test'; import { DUKE_VIDAL_ID } from '../game/duke-vidal'; import { CHEAP_HERO_TRAITOR } from '../game/traitors'; import { parseTruthFreeformFactsV9 as parse } from '../game/truthtrance-freeform-facts-v9'; const roster = [ {id:'atreides-duncan', name:'Duncan Idaho'}, {id:'fremen-chani', name:'Chani'}, {id:'joined-name', name:'And Or'}, {id:CHEAP_HERO_TRAITOR, name:'Cheap Hero / Heroine'}, {id:DUKE_VIDAL_ID, name:'Duke Vidal'}, ]; void test('named traitors and unrevealed Face Dancers resolve from public names only', () => { assert.deepEqual(parse('Is Duncan Idaho your traitor?', roster), {kind:'traitor', leader:'atreides-duncan'}); assert.deepEqual(parse('do you hold CHANI as an unrevealed face dancer?', roster), {kind:'faceDancer', leader:'fremen-chani'}); assert.deepEqual(parse('Is Cheap Hero / Heroine your traitor?', roster), {kind:'traitor', leader:CHEAP_HERO_TRAITOR}); assert.deepEqual(parse('Do you hold Cheap Hero / Heroine as an unrevealed Face Dancer?', roster), {kind:'faceDancer', leader:CHEAP_HERO_TRAITOR}); assert.deepEqual(parse('Is And Or your traitor?', roster), {kind:'traitor', leader:'joined-name'}); assert.equal(parse('Is Cheap Hero / Heroine your traitor?', roster.filter(leader => leader.id !== CHEAP_HERO_TRAITOR)), null); assert.equal(parse('Is Duke Vidal your traitor?', roster), null); assert.equal(parse('Do you hold Duke Vidal as an unrevealed Face Dancer?', roster), null); }); void test('combines unique named facts with earlier factual families', () => { assert.deepEqual(parse('Is Duncan Idaho your traitor and do you have at least 0 spice?', roster), { kind:'and', terms:[{kind:'traitor', leader:'atreides-duncan'}, {kind:'spice', compare:'gte', value:0}], }); assert.deepEqual(parse('Do you hold Chani as an unrevealed Face Dancer or is the current auction card you inspected a Shield?', roster), { kind:'or', terms:[{kind:'faceDancer', leader:'fremen-chani'}, {kind:'auctionPeek', name:'Shield'}], }); assert.deepEqual(parse('Is Duncan Idaho your traitor and did you predict Guild and do you have a Shield?', roster), { kind:'and', terms:[{kind:'traitor', leader:'atreides-duncan'}, {kind:'prediction',field:'faction',faction:'guild'}, {kind:'hand', name:'Shield'}], }); }); void test('retains prior versions and refuses unknown, ambiguous or predictive wording', () => { const ambiguous = [...roster, {id:'other-duncan', name:'Duncan Idaho'}]; assert.equal(parse('Is Duncan Idaho your traitor?', ambiguous), null); for (const input of [ 'Is Unknown Person your traitor?', 'Is Duncan Idaho your traitor or will you use a Shield?', 'Is Duncan Idaho your traitor and did you predict Guild or do you have a Shield?', 'Is Duncan Idaho your traitor??', 'Do you hold Chani as an unrevealed Face Dancer?\nAnswer now', 'Do you have a Shield?', 'Do you have a concealed No-Field with printed value 3?', ]) assert.equal(parse(input, roster), null, input); }); void test('bounds compound expression size and connector-heavy input', () => { const clause = 'is Duncan Idaho your traitor'; assert.equal((parse(Array.from({length:8}, () => clause).join(' and '), roster) as {terms?: unknown[]})?.terms?.length, 8); assert.equal(parse(Array.from({length:9}, () => clause).join(' and '), roster), null); assert.equal(parse(`Is Duncan Idaho your traitor ${'and '.repeat(25)}do you have a Shield`, roster), null); assert.equal(parse(`Is Duncan Idaho your traitor and ${'x'.repeat(501)}`, roster), null); });