Files
ComfyUI_frontend/tests-ui/tests/utils/spatial/QuadTree.test.ts
Christian Byrne 0854194aa1 [refactor] Refactor rendering-related files to DDD organization (#5388)
* refactor rendering-related files to DDD organization

* add to git ignore ignore revs
2025-09-06 02:47:37 -07:00

270 lines
6.7 KiB
TypeScript

import { beforeEach, describe, expect, it } from 'vitest'
import { type Bounds, QuadTree } from '@/renderer/core/spatial/QuadTree'
describe('QuadTree', () => {
let quadTree: QuadTree<string>
const worldBounds: Bounds = { x: 0, y: 0, width: 1000, height: 1000 }
beforeEach(() => {
quadTree = new QuadTree<string>(worldBounds, {
maxDepth: 4,
maxItemsPerNode: 4
})
})
describe('insertion', () => {
it('should insert items within bounds', () => {
const success = quadTree.insert(
'node1',
{ x: 100, y: 100, width: 50, height: 50 },
'node1'
)
expect(success).toBe(true)
expect(quadTree.size).toBe(1)
})
it('should reject items outside bounds', () => {
const success = quadTree.insert(
'node1',
{ x: -100, y: -100, width: 50, height: 50 },
'node1'
)
expect(success).toBe(false)
expect(quadTree.size).toBe(0)
})
it('should handle duplicate IDs by replacing', () => {
quadTree.insert(
'node1',
{ x: 100, y: 100, width: 50, height: 50 },
'data1'
)
quadTree.insert(
'node1',
{ x: 200, y: 200, width: 50, height: 50 },
'data2'
)
expect(quadTree.size).toBe(1)
const results = quadTree.query({
x: 150,
y: 150,
width: 100,
height: 100
})
expect(results).toContain('data2')
expect(results).not.toContain('data1')
})
})
describe('querying', () => {
beforeEach(() => {
// Insert test nodes in a grid pattern
for (let x = 0; x < 10; x++) {
for (let y = 0; y < 10; y++) {
const id = `node_${x}_${y}`
quadTree.insert(
id,
{
x: x * 100,
y: y * 100,
width: 50,
height: 50
},
id
)
}
}
})
it('should find nodes within query bounds', () => {
const results = quadTree.query({ x: 0, y: 0, width: 250, height: 250 })
expect(results.length).toBe(9) // 3x3 grid
})
it('should return empty array for out-of-bounds query', () => {
const results = quadTree.query({
x: 2000,
y: 2000,
width: 100,
height: 100
})
expect(results.length).toBe(0)
})
it('should handle partial overlaps', () => {
const results = quadTree.query({ x: 25, y: 25, width: 100, height: 100 })
expect(results.length).toBe(4) // 2x2 grid due to overlap
})
it('should handle large query areas efficiently', () => {
const startTime = performance.now()
const results = quadTree.query({ x: 0, y: 0, width: 1000, height: 1000 })
const queryTime = performance.now() - startTime
expect(results.length).toBe(100) // All nodes
expect(queryTime).toBeLessThan(5) // Should be fast
})
})
describe('removal', () => {
it('should remove existing items', () => {
quadTree.insert(
'node1',
{ x: 100, y: 100, width: 50, height: 50 },
'node1'
)
expect(quadTree.size).toBe(1)
const success = quadTree.remove('node1')
expect(success).toBe(true)
expect(quadTree.size).toBe(0)
})
it('should handle removal of non-existent items', () => {
const success = quadTree.remove('nonexistent')
expect(success).toBe(false)
})
})
describe('updating', () => {
it('should update item position', () => {
quadTree.insert(
'node1',
{ x: 100, y: 100, width: 50, height: 50 },
'node1'
)
const success = quadTree.update('node1', {
x: 200,
y: 200,
width: 50,
height: 50
})
expect(success).toBe(true)
// Should not find at old position
const oldResults = quadTree.query({
x: 75,
y: 75,
width: 100,
height: 100
})
expect(oldResults).not.toContain('node1')
// Should find at new position
const newResults = quadTree.query({
x: 175,
y: 175,
width: 100,
height: 100
})
expect(newResults).toContain('node1')
})
})
describe('subdivision', () => {
it('should subdivide when exceeding max items', () => {
// Insert 5 items (max is 4) to trigger subdivision
for (let i = 0; i < 5; i++) {
quadTree.insert(
`node${i}`,
{
x: i * 10,
y: i * 10,
width: 5,
height: 5
},
`node${i}`
)
}
expect(quadTree.size).toBe(5)
// Verify all items can still be found
const allResults = quadTree.query(worldBounds)
expect(allResults.length).toBe(5)
})
})
describe('performance', () => {
it('should handle 1000 nodes efficiently', () => {
const insertStart = performance.now()
// Insert 1000 nodes
for (let i = 0; i < 1000; i++) {
const x = Math.random() * 900
const y = Math.random() * 900
quadTree.insert(
`node${i}`,
{
x,
y,
width: 50,
height: 50
},
`node${i}`
)
}
const insertTime = performance.now() - insertStart
expect(insertTime).toBeLessThan(50) // Should be fast
// Query performance
const queryStart = performance.now()
const results = quadTree.query({
x: 400,
y: 400,
width: 200,
height: 200
})
const queryTime = performance.now() - queryStart
expect(queryTime).toBeLessThan(2) // Queries should be very fast
expect(results.length).toBeGreaterThan(0)
expect(results.length).toBeLessThan(1000) // Should cull most nodes
})
})
describe('edge cases', () => {
it('should handle zero-sized bounds', () => {
const success = quadTree.insert(
'point',
{ x: 100, y: 100, width: 0, height: 0 },
'point'
)
expect(success).toBe(true)
const results = quadTree.query({ x: 99, y: 99, width: 2, height: 2 })
expect(results).toContain('point')
})
it('should handle items spanning multiple quadrants', () => {
const success = quadTree.insert(
'large',
{
x: 400,
y: 400,
width: 200,
height: 200
},
'large'
)
expect(success).toBe(true)
// Should be found when querying any overlapping quadrant
const topLeft = quadTree.query({ x: 0, y: 0, width: 500, height: 500 })
const bottomRight = quadTree.query({
x: 500,
y: 500,
width: 500,
height: 500
})
expect(topLeft).toContain('large')
expect(bottomRight).toContain('large')
})
})
})