openpencil/tests/engine/hit-test/basic.test.ts
Danila Poyarkov 9e4d7bee5d chore(lint): fix unused test code
- Remove stale imports and unused locals from split engine and e2e tests
- Drop the targeted no-unused-vars test allowances
- Keep check and affected test suites warning-free
2026-05-06 02:53:19 +03:00

218 lines
5.2 KiB
TypeScript

import { describe, expect, test } from 'bun:test'
import { SceneGraph } from '@open-pencil/core'
function pageId(graph: SceneGraph) {
return graph.getPages()[0].id
}
describe('hitTest — hidden nodes', () => {
test('visible node at point is found', () => {
const graph = new SceneGraph()
const page = pageId(graph)
const rect = graph.createNode('RECTANGLE', page, {
name: 'R',
x: 100,
y: 100,
width: 50,
height: 50
})
const hit = graph.hitTest(125, 125)
expect(hit).not.toBeNull()
expect(hit?.id).toBe(rect.id)
})
test('hidden node (visible: false) is skipped', () => {
const graph = new SceneGraph()
const page = pageId(graph)
graph.createNode('RECTANGLE', page, {
name: 'R',
x: 100,
y: 100,
width: 50,
height: 50,
visible: false
})
const hit = graph.hitTest(125, 125)
expect(hit).toBeNull()
})
test('hidden child inside visible frame is skipped', () => {
const graph = new SceneGraph()
const page = pageId(graph)
const frame = graph.createNode('FRAME', page, {
name: 'F',
x: 0,
y: 0,
width: 200,
height: 200
})
graph.createNode('RECTANGLE', frame.id, {
name: 'R',
x: 50,
y: 50,
width: 50,
height: 50,
visible: false
})
const hit = graph.hitTest(75, 75)
// Frame itself has no visible fill/stroke, so it's not hit either
expect(hit).toBeNull()
})
})
describe('hitTest — locked nodes', () => {
test('locked container returns itself, does not recurse deeper', () => {
const graph = new SceneGraph()
const page = pageId(graph)
// A locked GROUP (transparent container) with a child at the same point
const group = graph.createNode('GROUP', page, {
name: 'G',
x: 0,
y: 0,
width: 200,
height: 200,
locked: true
})
graph.createNode('RECTANGLE', group.id, {
name: 'R',
x: 50,
y: 50,
width: 50,
height: 50
})
// The point (75, 75) is inside the child rectangle inside the locked group.
// Since the group is locked, hitTest should return the GROUP (not the child).
const hit = graph.hitTest(75, 75)
expect(hit).not.toBeNull()
expect(hit?.id).toBe(group.id)
})
test('unlocked container recurses to child', () => {
const graph = new SceneGraph()
const page = pageId(graph)
const frame = graph.createNode('FRAME', page, {
name: 'F',
x: 0,
y: 0,
width: 200,
height: 200
})
const rect = graph.createNode('RECTANGLE', frame.id, {
name: 'R',
x: 50,
y: 50,
width: 50,
height: 50
})
const hit = graph.hitTest(75, 75)
expect(hit).not.toBeNull()
expect(hit?.id).toBe(rect.id)
})
})
describe('hitTest — opacity', () => {
test('zero-opacity node is still hit-testable', () => {
const graph = new SceneGraph()
const page = pageId(graph)
const rect = graph.createNode('RECTANGLE', page, {
name: 'R',
x: 100,
y: 100,
width: 50,
height: 50,
opacity: 0
})
// Opacity is visual, not structural. Zero-opacity nodes should still be hit.
const hit = graph.hitTest(125, 125)
expect(hit).not.toBeNull()
expect(hit?.id).toBe(rect.id)
})
})
describe('hitTest — point outside all nodes', () => {
test('returns null for empty area', () => {
const graph = new SceneGraph()
const hit = graph.hitTest(500, 500)
expect(hit).toBeNull()
})
test('returns null for point outside node bounds', () => {
const graph = new SceneGraph()
const page = pageId(graph)
graph.createNode('RECTANGLE', page, {
name: 'R',
x: 100,
y: 100,
width: 50,
height: 50
})
const hit = graph.hitTest(200, 200)
expect(hit).toBeNull()
})
})
describe('hitTest — deep mode', () => {
test('hitTestDeep returns deep container for opaque containers', () => {
const graph = new SceneGraph()
const page = pageId(graph)
const component = graph.createNode('COMPONENT', page, {
name: 'C',
x: 0,
y: 0,
width: 200,
height: 200
})
graph.createNode('RECTANGLE', component.id, {
name: 'R',
x: 50,
y: 50,
width: 50,
height: 50
})
// Normal hitTest should return the component (opaque)
const normalHit = graph.hitTest(75, 75)
expect(normalHit?.id).toBe(component.id)
// Deep hitTest should return the rectangle inside the component
const deepHit = graph.hitTestDeep(75, 75)
expect(deepHit).not.toBeNull()
expect(deepHit?.type).toBe('RECTANGLE')
})
})
describe('hitTest — z-order (last child wins)', () => {
test('overlapping children: last in childIds array is hit', () => {
const graph = new SceneGraph()
const page = pageId(graph)
const rect1 = graph.createNode('RECTANGLE', page, {
name: 'R1',
x: 0,
y: 0,
width: 100,
height: 100
})
const rect2 = graph.createNode('RECTANGLE', page, {
name: 'R2',
x: 0,
y: 0,
width: 100,
height: 100
})
// Both overlap at (50, 50). Last child (rect2) should be hit.
const hit = graph.hitTest(50, 50)
expect(hit?.id).toBe(rect2.id)
expect(hit?.id).not.toBe(rect1.id)
})
})