/** * Tests for the FOL compiler's cross-package compatibility. * * Verifies that duck-type checks replace instanceof so that assertions * compile correctly when packages are symlinked or duplicated. */ import { describe, it } from 'node:test' import assert from 'node:assert' import { compileAssertionsToFOL } from './fol-compiler.js' describe('fol-compiler duck-type compilation', () => { it('compiles a plain object resembling FluentRelation', () => { const plainRelation = { relation: 'leftOf', assertion: { getSubject: () => '.a', getQuantifier: () => 'all', }, referenceSelector: '.b', options: { minGap: 8 }, } const formula = compileAssertionsToFOL([plainRelation as any]) assert.ok(formula !== null, 'Plain object FluentRelation should compile to a formula') assert.strictEqual(formula?.kind, 'forall') }) it('compiles a plain object resembling FluentQuantifier', () => { const plainQuantifier = { bindings: [{ selector: '.item' }], toFormula: () => ({ type: 'FormulaNode', kind: 'forall', bindings: [], body: { type: 'FormulaNode', kind: 'predicate', predicate: 'true', args: [] }, }), } const formula = compileAssertionsToFOL([plainQuantifier as any]) assert.ok(formula !== null, 'Plain object FluentQuantifier should compile to a formula') }) it('ignores objects that do not duck-type as FluentRelation or FluentQuantifier', () => { const plainObj = { foo: 'bar' } const formula = compileAssertionsToFOL([plainObj as any]) assert.strictEqual(formula, null) }) it('rejects null and primitive values', () => { assert.strictEqual(compileAssertionsToFOL([null as any]), null) assert.strictEqual(compileAssertionsToFOL([42 as any]), null) assert.strictEqual(compileAssertionsToFOL(['string' as any]), null) }) it('adapts grammar.ts ForAll shape to logic-ast.ts shape', () => { const grammarForAll = { bindings: [{ selector: '.item' }], toFormula: () => ({ type: 'ForAll', variable: { type: 'VariableRef', name: '$x' }, domain: { type: 'DomainRef', kind: 'elements', selector: '.item' }, body: { type: 'PredicateCall', name: 'leftOf', args: [{ type: 'VariableRef', name: '$x' }], }, }), } const formula = compileAssertionsToFOL([grammarForAll as any]) assert.ok(formula !== null) assert.strictEqual(formula!.kind, 'forall') assert.strictEqual((formula as any).bindings[0].variables[0], '$x') assert.strictEqual((formula as any).bindings[0].domain.domain, 'elements') assert.strictEqual((formula as any).body.kind, 'predicate') assert.strictEqual((formula as any).body.predicate, 'leftOf') }) it('adapts grammar.ts Exists shape to logic-ast.ts shape', () => { const grammarExists = { bindings: [{ selector: '.item' }], toFormula: () => ({ type: 'Exists', variable: { type: 'VariableRef', name: '$y' }, domain: { type: 'DomainRef', kind: 'elements', selector: '.item' }, body: { type: 'PredicateCall', name: 'above', args: [{ type: 'VariableRef', name: '$y' }], }, }), } const formula = compileAssertionsToFOL([grammarExists as any]) assert.ok(formula !== null) assert.strictEqual(formula!.kind, 'exists') }) it('adapts grammar.ts And shape to logic-ast.ts shape', () => { const grammarAnd = { bindings: [{ selector: '.item' }], toFormula: () => ({ type: 'And', left: { type: 'PredicateCall', name: 'leftOf', args: [{ type: 'VariableRef', name: '$x' }], }, right: { type: 'PredicateCall', name: 'above', args: [{ type: 'VariableRef', name: '$x' }], }, }), } const formula = compileAssertionsToFOL([grammarAnd as any]) assert.ok(formula !== null) assert.strictEqual(formula!.kind, 'and') assert.strictEqual((formula as any).left.kind, 'predicate') assert.strictEqual((formula as any).right.kind, 'predicate') }) it('adapts grammar.ts Not shape to logic-ast.ts shape', () => { const grammarNot = { bindings: [{ selector: '.item' }], toFormula: () => ({ type: 'Not', operand: { type: 'PredicateCall', name: 'overlaps', args: [{ type: 'VariableRef', name: '$x' }], }, }), } const formula = compileAssertionsToFOL([grammarNot as any]) assert.ok(formula !== null) assert.strictEqual(formula!.kind, 'not') assert.strictEqual((formula as any).operand.kind, 'predicate') }) it('adapts grammar.ts Implies shape to logic-ast.ts shape', () => { const grammarImplies = { bindings: [{ selector: '.item' }], toFormula: () => ({ type: 'Implies', left: { type: 'PredicateCall', name: 'inside', args: [{ type: 'VariableRef', name: '$x' }], }, right: { type: 'PredicateCall', name: 'alignedWith', args: [{ type: 'VariableRef', name: '$x' }], }, }), } const formula = compileAssertionsToFOL([grammarImplies as any]) assert.ok(formula !== null) assert.strictEqual(formula!.kind, 'implies') assert.strictEqual((formula as any).antecedent.kind, 'predicate') assert.strictEqual((formula as any).consequent.kind, 'predicate') }) it('adapts deeply nested grammar.ts shape', () => { const grammarNested = { bindings: [{ selector: '.item' }], toFormula: () => ({ type: 'ForAll', variable: { type: 'VariableRef', name: '$x' }, domain: { type: 'DomainRef', kind: 'elements', selector: '.item' }, body: { type: 'And', left: { type: 'Not', operand: { type: 'PredicateCall', name: 'overlaps', args: [{ type: 'VariableRef', name: '$x' }], }, }, right: { type: 'Exists', variable: { type: 'VariableRef', name: '$y' }, domain: { type: 'DomainRef', kind: 'elements', selector: '.ref' }, body: { type: 'PredicateCall', name: 'leftOf', args: [ { type: 'VariableRef', name: '$x' }, { type: 'VariableRef', name: '$y' }, ], }, }, }, }), } const formula = compileAssertionsToFOL([grammarNested as any]) assert.ok(formula !== null) assert.strictEqual(formula!.kind, 'forall') const body = (formula as any).body assert.strictEqual(body.kind, 'and') assert.strictEqual(body.left.kind, 'not') assert.strictEqual(body.right.kind, 'exists') assert.strictEqual(body.right.body.kind, 'predicate') assert.strictEqual(body.right.body.args.length, 2) }) it('passes through already-correct logic-ast.ts shape', () => { const logicAstForAll = { bindings: [{ selector: '.item' }], toFormula: () => ({ type: 'FormulaNode', kind: 'forall', bindings: [ { type: 'TupleBinding', variables: ['$x'], domain: { type: 'DomainRef', domain: 'elements', selector: '.item' }, }, ], body: { type: 'FormulaNode', kind: 'predicate', predicate: 'true', args: [] }, }), } const formula = compileAssertionsToFOL([logicAstForAll as any]) assert.ok(formula !== null) assert.strictEqual(formula!.kind, 'forall') }) it('compiles mixed arrays of plain objects', () => { const plainRelation = { relation: 'above', assertion: { getSubject: () => '.c', getQuantifier: () => 'all', }, referenceSelector: '.d', options: { minGap: 4 }, } const plainQuantifier = { bindings: [{ selector: '.x' }], toFormula: () => ({ type: 'FormulaNode', kind: 'exists', bindings: [], body: { type: 'FormulaNode', kind: 'predicate', predicate: 'true', args: [] }, }), } const formula = compileAssertionsToFOL([plainRelation as any, plainQuantifier as any]) assert.ok(formula !== null) assert.strictEqual(formula?.kind, 'and') }) it('propagates assertion layout space to simple relations', () => { const relation = { relation: 'leftOf', assertion: { getSubject: () => '.a', getQuantifier: () => 'all', getSpace: () => 'layout', }, referenceSelector: '.b', options: {}, } const formula = compileAssertionsToFOL([relation as any]) as any const predicate = formula.body assert.strictEqual(predicate.kind, 'predicate') assert.strictEqual(predicate.options.space, 'layout') }) it('propagates assertion visual space to compound relations', () => { const relation = { relation: 'above', assertion: { getSubject: () => '.a', getQuantifier: () => 'all', getSpace: () => 'visual', }, referenceSelector: '.c', options: {}, _compoundOperator: 'and', _compoundParts: [ { relation: 'leftOf', referenceSelector: '.b', options: {} }, { relation: 'above', referenceSelector: '.c', options: {} }, ], } const formula = compileAssertionsToFOL([relation as any]) as any assert.strictEqual(formula.kind, 'forall') const andBody = formula.body assert.strictEqual(andBody.kind, 'and') assert.strictEqual(andBody.left.body.options.space, 'visual') assert.strictEqual(andBody.right.body.options.space, 'visual') }) })