/** * Tests for relation-specific proof generation. * * Verifies that generateProof produces rich, relation-specific failedPredicate * details instead of generic left/right metric comparisons. */ import { describe, it, beforeEach } from 'node:test' import assert from 'node:assert' import { generateProof, resetProofCounter, } from './proofs.js' import type { ClauseResult, ClauseDescriptor, GeometryWorld, } from './registry.js' // --------------------------------------------------------------------------- // Mock World // --------------------------------------------------------------------------- const world: GeometryWorld = { sceneId: 'scene_1', snapshotId: 'snap_1', env: { viewportWidth: 1280, viewportHeight: 800, deviceScaleFactor: 1, colorScheme: 'light', pointer: 'fine', hover: false, reducedMotion: false, locale: 'en', writingMode: 'horizontal-tb', }, strings: { values: [] }, subjects: { ids: [1, 2], domNodeId: [10, 20], subjectKind: [1, 1], primaryBoxId: [100, 200], firstFragmentId: [0, 0], fragmentCount: [0, 0], }, dom: { nodeId: [10, 20], parentNodeId: [0, 0], childCount: [0, 0], tagNameStringId: [0, 0], }, boxes: { boxId: [100, 200], subjectId: [1, 2], frameId: [1, 1], borderLeft: [0, 110], borderTop: [0, 50], borderRight: [100, 210], borderBottom: [40, 90], paddingLeft: [0, 0], paddingTop: [0, 0], paddingRight: [0, 0], paddingBottom: [0, 0], contentLeft: [0, 0], contentTop: [0, 0], contentRight: [0, 0], contentBottom: [0, 0], }, visualBoxes: { boxId: [], subjectId: [], frameId: [], borderLeft: [], borderTop: [], borderRight: [], borderBottom: [], paddingLeft: [], paddingTop: [], paddingRight: [], paddingBottom: [], contentLeft: [], contentTop: [], contentRight: [], contentBottom: [], }, transforms: { transformId: [], subjectId: [], matrixStart: [], matrixLength: [], originX: [], originY: [], }, matrices: { values: [] }, rects: { rectId: [], left: [], top: [], right: [], bottom: [] }, topology: { containingBlockOf: [0, 0], nearestPositionedAncestorOf: [0, 0], scrollContainerOf: [0, 0], stackingContextOf: [0, 0], formattingContextOf: [0, 0], clippingRootOf: [0, 0], paintOrderBucket: [0, 0], paintOrderIndex: [0, 0], }, scroll: { containerId: [], scrollLeft: [], scrollTop: [], scrollWidth: [], scrollHeight: [], clientWidth: [], clientHeight: [], }, clipping: { clipNodeId: [], subjectId: [], clipKind: [], clipLeft: [], clipTop: [], clipRight: [], clipBottom: [], parentClipNodeId: [], }, visibility: { subjectId: [], isRendered: [], isVisible: [], visibleArea: [], clippedArea: [], }, } function makeClause(kind: string): ClauseDescriptor { return { clauseId: 'clause_1', clauseKind: kind, version: 1, subjectRef: 1, referenceRef: 2, } } function makeResult(status: 'pass' | 'fail' | 'error', metrics?: Record): ClauseResult { return { clauseId: 'clause_1', status, truth: status === 'error' ? 'indeterminate' : 'determinate', metrics, witness: { subjectId: 1, referenceId: 2 }, } } // --------------------------------------------------------------------------- // Setup // --------------------------------------------------------------------------- beforeEach(() => { resetProofCounter() }) // --------------------------------------------------------------------------- // Relation-Specific Proof Tests // --------------------------------------------------------------------------- describe('relation-specific proofs', () => { it('leftOf proof includes measured gap and expected bounds', () => { const clause = makeClause('relation.leftOf') const result = makeResult('fail', { observedGap: -5, minGap: 0, maxGap: Infinity, subjectLeft: 0, subjectTop: 0, subjectRight: 100, subjectBottom: 40, refLeft: 90, refTop: 50, refRight: 190, refBottom: 90, }) const proof = generateProof(result, clause, world) assert.strictEqual(proof.outcome, 'fail') assert.ok(proof.failedPredicate) assert.strictEqual(proof.failedPredicate!.relationKind, 'leftOf') assert.strictEqual(proof.failedPredicate!.measuredGap, -5) assert.strictEqual(proof.failedPredicate!.expectedMinGap, 0) assert.ok(proof.failedPredicate!.subjectRect) assert.strictEqual(proof.failedPredicate!.subjectRect!.right, 100) assert.ok(proof.failedPredicate!.referenceRect) assert.strictEqual(proof.failedPredicate!.referenceRect!.left, 90) }) it('above proof includes vertical gap and positions', () => { const clause = makeClause('relation.above') const result = makeResult('fail', { observedGap: -3, minGap: 0, maxGap: Infinity, subjectLeft: 0, subjectTop: 0, subjectRight: 100, subjectBottom: 40, refLeft: 0, refTop: 35, refRight: 100, refBottom: 75, }) const proof = generateProof(result, clause, world) assert.strictEqual(proof.failedPredicate!.relationKind, 'above') assert.strictEqual(proof.failedPredicate!.measuredGap, -3) assert.ok(proof.failedPredicate!.subjectRect) assert.ok(proof.failedPredicate!.referenceRect) }) it('inside proof includes overflow edges', () => { const clause = makeClause('relation.inside') const result = makeResult('fail', { overflowLeft: 10, overflowTop: 0, overflowRight: -5, overflowBottom: 0, subjectLeft: 10, subjectTop: 0, subjectRight: 105, subjectBottom: 40, refLeft: 0, refTop: 0, refRight: 100, refBottom: 40, }) const proof = generateProof(result, clause, world) assert.strictEqual(proof.failedPredicate!.relationKind, 'inside') assert.ok(proof.failedPredicate!.overflowEdges) assert.strictEqual(proof.failedPredicate!.overflowEdges!.left, 10) assert.strictEqual(proof.failedPredicate!.overflowEdges!.right, -5) assert.ok(proof.failedPredicate!.subjectRect) assert.ok(proof.failedPredicate!.referenceRect) }) it('atLeast proof includes measured vs expected dimensions', () => { const clause = makeClause('size.atLeast') const result = makeResult('fail', { observed: 80, min: 100, }) const proof = generateProof(result, clause, world) assert.strictEqual(proof.failedPredicate!.relationKind, 'atLeast') assert.strictEqual(proof.failedPredicate!.measuredValue, 80) assert.strictEqual(proof.failedPredicate!.expectedMin, 100) }) it('atMost proof includes measured vs expected dimensions', () => { const clause = makeClause('size.atMost') const result = makeResult('fail', { observed: 120, max: 100, }) const proof = generateProof(result, clause, world) assert.strictEqual(proof.failedPredicate!.relationKind, 'atMost') assert.strictEqual(proof.failedPredicate!.measuredValue, 120) assert.strictEqual(proof.failedPredicate!.expectedMax, 100) }) it('alignedWith proof includes delta and tolerance', () => { const clause = makeClause('alignment.alignedWith') const result = makeResult('fail', { delta: 5, tolerance: 1, }) const proof = generateProof(result, clause, world) assert.strictEqual(proof.failedPredicate!.relationKind, 'alignedWith') assert.strictEqual(proof.failedPredicate!.measuredValue, 5) assert.strictEqual(proof.failedPredicate!.expectedMax, 1) }) it('centeredWithin proof includes deltaX, deltaY and tolerance', () => { const clause = makeClause('alignment.centeredWithin') const result = makeResult('fail', { deltaX: 3, deltaY: 4, tolerance: 2, }) const proof = generateProof(result, clause, world) assert.strictEqual(proof.failedPredicate!.relationKind, 'centeredWithin') assert.strictEqual(proof.failedPredicate!.measuredValue, 3) assert.strictEqual(proof.failedPredicate!.expectedMax, 2) }) it('omits failedPredicate on pass', () => { const clause = makeClause('relation.leftOf') const result = makeResult('pass', { observedGap: 10, minGap: 0 }) const proof = generateProof(result, clause, world) assert.strictEqual(proof.outcome, 'pass') assert.strictEqual(proof.failedPredicate, undefined) }) it('falls back to generic synthesis for unknown relation kinds', () => { const clause = makeClause('relation.unknownRelation') const result = makeResult('fail', { foo: 10, bar: 20 }) const proof = generateProof(result, clause, world) assert.ok(proof.failedPredicate) assert.strictEqual(proof.failedPredicate!.relationKind, 'unknownRelation') assert.strictEqual(proof.failedPredicate!.op, '<') assert.strictEqual(proof.failedPredicate!.left, 10) assert.strictEqual(proof.failedPredicate!.right, 20) }) })