From c4f4a452a0067ab368605aac5299380484df0f2a Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 22:32:26 +0000 Subject: [PATCH] CodeRabbit Generated Unit Tests: Add injectionKeys and boundsCalculator unit tests --- .../element/useTransformState.test.ts | 46 +++++++++ .../core/layout/injectionKeys.test.ts | 59 ++++++++++++ .../core/spatial/boundsCalculator.test.ts | 95 +++++++++++++++++++ .../composables/useNodeEventHandlers.test.ts | 80 ++++++++++++++++ 4 files changed, 280 insertions(+) create mode 100644 tests-ui/tests/renderer/core/layout/injectionKeys.test.ts create mode 100644 tests-ui/tests/renderer/core/spatial/boundsCalculator.test.ts diff --git a/tests-ui/tests/composables/element/useTransformState.test.ts b/tests-ui/tests/composables/element/useTransformState.test.ts index 457b2fdd2f..47878cb8fa 100644 --- a/tests-ui/tests/composables/element/useTransformState.test.ts +++ b/tests-ui/tests/composables/element/useTransformState.test.ts @@ -349,3 +349,49 @@ describe('useTransformState', () => { }) }) }) + +describe('useTransformState - Non-Shared Behavior', () => { + it('should create independent instances for multiple calls', () => { + const state1 = useTransformState() + const state2 = useTransformState() + + // Modify state1 + const mockCanvas1 = { + ds: { offset: [100, 200], scale: 1.5 } + } as any + state1.syncWithCanvas(mockCanvas1) + + // Modify state2 differently + const mockCanvas2 = { + ds: { offset: [300, 400], scale: 0.5 } + } as any + state2.syncWithCanvas(mockCanvas2) + + // Each instance should have its own camera state + expect(state1.camera.x).toBe(100) + expect(state1.camera.z).toBe(1.5) + expect(state2.camera.x).toBe(300) + expect(state2.camera.z).toBe(0.5) + }) + + it('should handle ArrayLike parameters correctly', () => { + const { isNodeInViewport } = useTransformState() + + const viewport = { width: 800, height: 600 } + + // Test with regular arrays + const arrayPos: number[] = [100, 200] + const arraySize: number[] = [150, 100] + expect(() => isNodeInViewport(arrayPos, arraySize, viewport)).not.toThrow() + + // Test with TypedArrays + const typedPos = new Float32Array([100, 200]) + const typedSize = new Float64Array([150, 100]) + expect(() => isNodeInViewport(typedPos, typedSize, viewport)).not.toThrow() + + // Test with tuples + const tuplePos: [number, number] = [100, 200] + const tupleSize: [number, number] = [150, 100] + expect(() => isNodeInViewport(tuplePos, tupleSize, viewport)).not.toThrow() + }) +}) diff --git a/tests-ui/tests/renderer/core/layout/injectionKeys.test.ts b/tests-ui/tests/renderer/core/layout/injectionKeys.test.ts new file mode 100644 index 0000000000..84049e8502 --- /dev/null +++ b/tests-ui/tests/renderer/core/layout/injectionKeys.test.ts @@ -0,0 +1,59 @@ +import { describe, expect, it } from 'vitest' +import { inject, provide } from 'vue' + +import { + TransformStateKey, + type TransformState +} from '@/renderer/core/layout/injectionKeys' + +describe('injectionKeys', () => { + describe('TransformStateKey', () => { + it('should be a valid Symbol injection key', () => { + expect(TransformStateKey).toBeDefined() + expect(typeof TransformStateKey).toBe('symbol') + expect(TransformStateKey.toString()).toContain('transformState') + }) + + it('should work with Vue provide/inject', () => { + const mockTransformState: TransformState = { + camera: { x: 0, y: 0, z: 1 }, + screenToCanvas: () => ({ x: 0, y: 0 }), + canvasToScreen: () => ({ x: 0, y: 0 }), + isNodeInViewport: () => true + } + + // Simulate provide + provide(TransformStateKey, mockTransformState) + + // Simulate inject + const injected = inject(TransformStateKey) + expect(injected).toBe(mockTransformState) + }) + + it('should enforce correct TransformState interface structure', () => { + const validState: TransformState = { + camera: { x: 10, y: 20, z: 1.5 }, + screenToCanvas: (point: { x: number; y: number }) => ({ + x: point.x / 1.5 - 10, + y: point.y / 1.5 - 20 + }), + canvasToScreen: (point: { x: number; y: number }) => ({ + x: (point.x + 10) * 1.5, + y: (point.y + 20) * 1.5 + }), + isNodeInViewport: ( + nodePos: ArrayLike, + nodeSize: ArrayLike, + viewport: { width: number; height: number }, + margin?: number + ) => { + return true + } + } + + // Type check - should compile without errors + const _check: TransformState = validState + expect(_check).toBeDefined() + }) + }) +}) \ No newline at end of file diff --git a/tests-ui/tests/renderer/core/spatial/boundsCalculator.test.ts b/tests-ui/tests/renderer/core/spatial/boundsCalculator.test.ts new file mode 100644 index 0000000000..05a31a6217 --- /dev/null +++ b/tests-ui/tests/renderer/core/spatial/boundsCalculator.test.ts @@ -0,0 +1,95 @@ +import { describe, expect, it } from 'vitest' + +import { calculateNodeBounds } from '@/renderer/core/spatial/boundsCalculator' + +describe('boundsCalculator', () => { + describe('calculateNodeBounds', () => { + it('should calculate bounds for single node', () => { + const nodes = [{ pos: [100, 200], size: [150, 100] }] + + const bounds = calculateNodeBounds(nodes) + + expect(bounds).toEqual({ + minX: 100, + minY: 200, + maxX: 250, + maxY: 300, + width: 150, + height: 100 + }) + }) + + it('should calculate bounds for multiple nodes', () => { + const nodes = [ + { pos: [100, 200], size: [150, 100] }, + { pos: [300, 400], size: [200, 150] }, + { pos: [50, 100], size: [100, 80] } + ] + + const bounds = calculateNodeBounds(nodes) + + expect(bounds).toEqual({ + minX: 50, + minY: 100, + maxX: 500, + maxY: 550, + width: 450, + height: 450 + }) + }) + + it('should return null for empty array', () => { + const nodes: Array<{ pos: ArrayLike; size: ArrayLike }> = + [] + + const bounds = calculateNodeBounds(nodes) + + expect(bounds).toBeNull() + }) + + it('should handle nodes with negative coordinates', () => { + const nodes = [ + { pos: [-100, -200], size: [150, 100] }, + { pos: [50, 100], size: [100, 80] } + ] + + const bounds = calculateNodeBounds(nodes) + + expect(bounds).toEqual({ + minX: -100, + minY: -200, + maxX: 150, + maxY: 180, + width: 250, + height: 380 + }) + }) + + it('should accept TypedArray for position and size', () => { + const nodes = [ + { pos: new Float32Array([100, 200]), size: [150, 100] }, + { pos: new Float64Array([300, 400]), size: new Float32Array([200, 150]) } + ] + + const bounds = calculateNodeBounds(nodes) + + expect(bounds).not.toBeNull() + expect(bounds!.minX).toBe(100) + expect(bounds!.minY).toBe(200) + }) + + it('should handle large number of nodes efficiently', () => { + const nodes = Array.from({ length: 10000 }, (_, i) => ({ + pos: [i * 10, i * 10], + size: [100, 100] + })) + + const start = performance.now() + const bounds = calculateNodeBounds(nodes) + const end = performance.now() + + expect(bounds).not.toBeNull() + expect(end - start).toBeLessThan(100) + }) + }) +}) \ No newline at end of file diff --git a/tests-ui/tests/renderer/extensions/vueNodes/composables/useNodeEventHandlers.test.ts b/tests-ui/tests/renderer/extensions/vueNodes/composables/useNodeEventHandlers.test.ts index c5d5032262..9eb8b1eec6 100644 --- a/tests-ui/tests/renderer/extensions/vueNodes/composables/useNodeEventHandlers.test.ts +++ b/tests-ui/tests/renderer/extensions/vueNodes/composables/useNodeEventHandlers.test.ts @@ -399,3 +399,83 @@ describe('useNodeEventHandlers', () => { }) }) }) + +describe('useNodeEventHandlers - New Batch Operations', () => { + describe('selectNodes', () => { + it('should select multiple nodes at once', () => { + const { selectNodes } = useNodeEventHandlers() + + const mockNodes = [ + { id: 'node1', selected: false }, + { id: 'node2', selected: false }, + { id: 'node3', selected: false } + ] + + mockNodeManager.value!.getNode = vi.fn((id: string) => + mockNodes.find((n) => n.id === id) + ) as any + + const canvasStore = useCanvasStore() + canvasStore.canvas!.select = vi.fn() + canvasStore.canvas!.deselectAll = vi.fn() + + selectNodes(['node1', 'node2', 'node3'], false) + + expect(canvasStore.canvas!.deselectAll).toHaveBeenCalled() + expect(canvasStore.canvas!.select).toHaveBeenCalledTimes(3) + }) + + it('should skip non-existent nodes', () => { + const { selectNodes } = useNodeEventHandlers() + + mockNodeManager.value!.getNode = vi.fn(() => null) + + const canvasStore = useCanvasStore() + canvasStore.canvas!.select = vi.fn() + + selectNodes(['missing-node'], false) + + expect(canvasStore.canvas!.select).not.toHaveBeenCalled() + }) + }) + + describe('deselectNodes', () => { + it('should deselect multiple nodes', () => { + const { deselectNodes } = useNodeEventHandlers() + + const mockNodes = [ + { id: 'node1', selected: true }, + { id: 'node2', selected: true } + ] + + mockNodeManager.value!.getNode = vi.fn((id: string) => + mockNodes.find((n) => n.id === id) + ) as any + + const canvasStore = useCanvasStore() + canvasStore.canvas!.deselect = vi.fn() + + deselectNodes(['node1', 'node2']) + + expect(canvasStore.canvas!.deselect).toHaveBeenCalledTimes(2) + }) + }) + + describe('deselectNode', () => { + it('should deselect a single node', () => { + const { deselectNode } = useNodeEventHandlers() + + const mockNodeToDeselect = { id: 'test-node', selected: true } + mockNodeManager.value!.getNode = vi.fn(() => mockNodeToDeselect as any) + + const canvasStore = useCanvasStore() + canvasStore.canvas!.deselect = vi.fn() + + deselectNode('test-node') + + expect(canvasStore.canvas!.deselect).toHaveBeenCalledWith( + mockNodeToDeselect + ) + }) + }) +})