chore: fill CSS contain test gaps + unskip clippedBy e2e test

- Add 4 unit tests in predicates.test.ts for new contain metrics:
  inside hasClippedOverflow (with/without clipping)
  clippedBy clipKind (contain:paint=1, overflow=2)
- Unskip and implement clippedBy e2e test with overflow:hidden container
  in e2e-edge.test.ts (was stale skipped with 'not yet implemented')
This commit is contained in:
John Dvorak
2026-05-21 14:44:26 -07:00
parent 4ff56d61c2
commit de12e93cf8
2 changed files with 165 additions and 5 deletions
@@ -477,4 +477,125 @@ describe('spatial alias predicates', () => {
assert.strictEqual(result.metrics?.max, 540);
});
});
// --- CSS contain metrics tests ------------------------------------------
describe('CSS contain metrics', () => {
beforeEach(() => {
clearPredicateRegistry();
registerDefaultPredicates();
});
it('inside reports hasClippedOverflow when reference clips and subject overflows', () => {
const evaluator = getPredicateEvaluator('inside')!;
// Subject 1 (10,10)-(90,30) is NOT fully inside Reference 2 (0,0)-(50,50)
// overflowLeft=0, overflowTop=0, overflowRight=40, overflowBottom=0
// Reference 2 clips (overflow:hidden → clipKind=1)
const world = makeWorld({
boxes: {
boxId: [100, 200],
subjectId: [1, 2],
frameId: [1, 1],
borderLeft: [10, 0],
borderTop: [10, 0],
borderRight: [90, 50],
borderBottom: [30, 50],
paddingLeft: [0, 0], paddingTop: [0, 0], paddingRight: [0, 0], paddingBottom: [0, 0],
contentLeft: [10, 0], contentTop: [10, 0], contentRight: [90, 50], contentBottom: [30, 50],
},
visualBoxes: {
boxId: [100, 200],
subjectId: [1, 2],
frameId: [1, 1],
borderLeft: [10, 0], borderTop: [10, 0], borderRight: [90, 50], borderBottom: [30, 50],
paddingLeft: [0, 0], paddingTop: [0, 0], paddingRight: [0, 0], paddingBottom: [0, 0],
contentLeft: [10, 0], contentTop: [10, 0], contentRight: [90, 50], contentBottom: [30, 50],
},
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] },
clipping: {
clipNodeId: [0],
subjectId: [2], // reference 2 is a clipping container (overflow:hidden)
clipKind: [1],
clipLeft: [0], clipTop: [0], clipRight: [50], clipBottom: [50],
parentClipNodeId: [0],
},
});
const result = evaluator.evaluateTuple(world, [1, 2]);
assert.strictEqual(result.truth, 'false');
assert.strictEqual(result.metrics?.overflowRight, 40);
assert.strictEqual(result.metrics?.hasClippedOverflow, 1);
});
it('inside does not report hasClippedOverflow when reference does not clip', () => {
const evaluator = getPredicateEvaluator('inside')!;
// Reference 2 does NOT clip (no clipping entry)
const world = makeWorld({
boxes: {
boxId: [100, 200],
subjectId: [1, 2],
frameId: [1, 1],
borderLeft: [10, 0],
borderTop: [10, 0],
borderRight: [90, 50],
borderBottom: [30, 50],
paddingLeft: [0, 0], paddingTop: [0, 0], paddingRight: [0, 0], paddingBottom: [0, 0],
contentLeft: [10, 0], contentTop: [10, 0], contentRight: [90, 50], contentBottom: [30, 50],
},
visualBoxes: {
boxId: [100, 200],
subjectId: [1, 2],
frameId: [1, 1],
borderLeft: [10, 0], borderTop: [10, 0], borderRight: [90, 50], borderBottom: [30, 50],
paddingLeft: [0, 0], paddingTop: [0, 0], paddingRight: [0, 0], paddingBottom: [0, 0],
contentLeft: [10, 0], contentTop: [10, 0], contentRight: [90, 50], contentBottom: [30, 50],
},
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] },
});
const result = evaluator.evaluateTuple(world, [1, 2]);
assert.strictEqual(result.truth, 'false');
assert.strictEqual(result.metrics?.overflowRight, 40);
assert.strictEqual(result.metrics?.hasClippedOverflow, undefined);
});
it('clippedBy reports clipKind=1 for contain:paint clip', () => {
const evaluator = getPredicateEvaluator('clippedBy')!;
// Subject 1 is clipped by Reference 2 (contain:paint → clipKind=5)
const world = makeWorld({
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] },
topology: { clippingRootOf: [2], stackingContextOf: [], scrollContainerOf: [], containingBlockOf: [], formattingContextOf: [], nearestPositionedAncestorOf: [], paintOrderBucket: [], paintOrderIndex: [] },
clipping: {
clipNodeId: [0],
subjectId: [2],
clipKind: [5], // Contain=5
clipLeft: [0], clipTop: [0], clipRight: [100], clipBottom: [100],
parentClipNodeId: [0],
},
});
const result = evaluator.evaluateTuple(world, [1, 2]);
assert.strictEqual(result.truth, 'true');
assert.strictEqual(result.metrics?.clipKind, 1); // 1=contain:paint in predicate encoding
});
it('clippedBy reports clipKind=2 for overflow:hidden clip', () => {
const evaluator = getPredicateEvaluator('clippedBy')!;
const world = makeWorld({
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] },
topology: { clippingRootOf: [2], stackingContextOf: [], scrollContainerOf: [], containingBlockOf: [], formattingContextOf: [], nearestPositionedAncestorOf: [], paintOrderBucket: [], paintOrderIndex: [] },
clipping: {
clipNodeId: [0],
subjectId: [2],
clipKind: [1], // Overflow=1
clipLeft: [0], clipTop: [0], clipRight: [100], clipBottom: [100],
parentClipNodeId: [0],
},
});
const result = evaluator.evaluateTuple(world, [1, 2]);
assert.strictEqual(result.truth, 'true');
assert.strictEqual(result.metrics?.clipKind, 2); // 2=overflow in predicate encoding
});
});
});