openpencil/tests/engine/geometry/clip-polygon.test.ts
Danila Poyarkov 86d6a63d0f fix(fig): restore strict parse validation
- Reject corrupt compressed Kiwi chunks instead of accepting raw garbage\n- Tolerate legacy source metadata without editedFields in renderer fallbacks\n- Align font, derived glyph, shared style, renderer mock, and ownership tests with current contracts\n- Restore clean quick and heavy unit quality gates
2026-07-18 03:58:18 +03:00

106 lines
3.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, expect, test } from 'bun:test'
import { clipBoundsToPolygon, clipPolygon, type Vector } from '@open-pencil/scene-graph'
describe('clipPolygon', () => {
test('returns the subject unchanged when the clip has fewer than 3 corners', () => {
const subject: Vector[] = [
{ x: 0, y: 0 },
{ x: 10, y: 0 },
{ x: 10, y: 10 },
{ x: 0, y: 10 }
]
expect(clipPolygon(subject, [])).toBe(subject)
expect(
clipPolygon(subject, [
{ x: 0, y: 0 },
{ x: 1, y: 1 }
])
).toBe(subject)
})
test('returns null when the subject is fully outside the clip', () => {
const subject: Vector[] = [
{ x: 100, y: 100 },
{ x: 110, y: 100 },
{ x: 110, y: 110 },
{ x: 100, y: 110 }
]
const clip: Vector[] = [
{ x: 0, y: 0 },
{ x: 10, y: 0 },
{ x: 10, y: 10 },
{ x: 0, y: 10 }
]
expect(clipPolygon(subject, clip)).toBeNull()
})
test('preserves the clipped polygon across a chain so a later clip excludes corners the inner clip removed', () => {
// Subject: the full [0,100]² square.
const subject: Vector[] = [
{ x: 0, y: 0 },
{ x: 100, y: 0 },
{ x: 100, y: 100 },
{ x: 0, y: 100 }
]
// Inner clip D1: the diamond inscribed in the square (corners at the edge
// midpoints). It removes the four square corners, including (0,0).
const d1: Vector[] = [
{ x: 50, y: 0 },
{ x: 100, y: 50 },
{ x: 50, y: 100 },
{ x: 0, y: 50 }
]
// Outer clip D2: a small square [0,30]² covering the (0,0) corner that D1
// already removed. If the chain collapsed D1's result to its AABB ([0,100]²,
// which re-adds the corner) before applying D2, the corner would survive.
// Preserving the polygon keeps it excluded: the only surviving region is the
// triangle where the diamond's x+y=50 edge meets [0,30]², i.e. AABB
// [20,30]×[20,30].
const d2: Vector[] = [
{ x: 0, y: 0 },
{ x: 30, y: 0 },
{ x: 30, y: 30 },
{ x: 0, y: 30 }
]
const afterD1 = clipPolygon(subject, d1)
expect(afterD1).not.toBeNull()
const clipped = afterD1 ? clipPolygon(afterD1, d2) : null
expect(clipped).not.toBeNull()
expect(clipped?.length).toBeGreaterThanOrEqual(3)
let minX = Infinity
let minY = Infinity
let maxX = -Infinity
let maxY = -Infinity
for (const p of clipped ?? []) {
if (p.x < minX) minX = p.x
if (p.y < minY) minY = p.y
if (p.x > maxX) maxX = p.x
if (p.y > maxY) maxY = p.y
}
// The corner (0,0) must stay excluded: the result does not reach below 20.
expect(minX).toBeGreaterThanOrEqual(20)
expect(minY).toBeGreaterThanOrEqual(20)
expect(maxX).toBeLessThanOrEqual(30)
expect(maxY).toBeLessThanOrEqual(30)
})
test('clipBoundsToPolygon agrees with clipPolygon for a single axis-aligned clip', () => {
const bounds = { minX: 0, minY: 0, maxX: 100, maxY: 100 }
const clip: Vector[] = [
{ x: 25, y: 25 },
{ x: 75, y: 25 },
{ x: 75, y: 75 },
{ x: 25, y: 75 }
]
const aabb = clipBoundsToPolygon(bounds, clip)
expect(aabb).toEqual({ minX: 25, minY: 25, maxX: 75, maxY: 75 })
})
})