mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-31 13:29:55 +00:00
* [refactor] Improve renderer architecture organization Building on PR #5388, this refines the renderer domain structure: **Key improvements:** - Group all transform utilities in `transform/` subdirectory for better cohesion - Move canvas state to dedicated `renderer/core/canvas/` domain - Consolidate coordinate system logic (TransformPane, useTransformState, sync utilities) **File organization:** - `renderer/core/canvas/canvasStore.ts` (was `stores/graphStore.ts`) - `renderer/core/layout/transform/` contains all coordinate system utilities - Transform sync utilities co-located with core transform logic This creates clearer domain boundaries and groups related functionality while building on the foundation established in PR #5388. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Clean up linter-modified files * Fix import paths and clean up unused imports after rebase - Update all remaining @/stores/graphStore references to @/renderer/core/canvas/canvasStore - Remove unused imports from selection toolbox components - All tests pass, only reka-ui upstream issue remains in typecheck 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * [auto-fix] Apply ESLint and Prettier fixes --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com>
241 lines
6.3 KiB
TypeScript
241 lines
6.3 KiB
TypeScript
import { mount } from '@vue/test-utils'
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { nextTick } from 'vue'
|
|
|
|
import { useCanvasTransformSync } from '@/renderer/core/layout/transform/useCanvasTransformSync'
|
|
|
|
import type { LGraphCanvas } from '../../../../src/lib/litegraph/src/litegraph'
|
|
|
|
// Mock LiteGraph canvas
|
|
const createMockCanvas = (): Partial<LGraphCanvas> => ({
|
|
canvas: document.createElement('canvas'),
|
|
ds: {
|
|
offset: [0, 0],
|
|
scale: 1
|
|
} as any // Mock the DragAndScale type
|
|
})
|
|
|
|
describe('useCanvasTransformSync', () => {
|
|
let mockCanvas: LGraphCanvas
|
|
let syncFn: ReturnType<typeof vi.fn>
|
|
let callbacks: {
|
|
onStart: ReturnType<typeof vi.fn>
|
|
onUpdate: ReturnType<typeof vi.fn>
|
|
onStop: ReturnType<typeof vi.fn>
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.useFakeTimers()
|
|
mockCanvas = createMockCanvas() as LGraphCanvas
|
|
syncFn = vi.fn()
|
|
callbacks = {
|
|
onStart: vi.fn(),
|
|
onUpdate: vi.fn(),
|
|
onStop: vi.fn()
|
|
}
|
|
|
|
// Mock requestAnimationFrame
|
|
global.requestAnimationFrame = vi.fn((cb) => {
|
|
setTimeout(cb, 16) // Simulate 60fps
|
|
return 1
|
|
})
|
|
global.cancelAnimationFrame = vi.fn()
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers()
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
it('should auto-start sync when canvas is provided', async () => {
|
|
const { isActive } = useCanvasTransformSync(mockCanvas, syncFn, callbacks)
|
|
|
|
await nextTick()
|
|
|
|
expect(isActive.value).toBe(true)
|
|
expect(callbacks.onStart).toHaveBeenCalledOnce()
|
|
expect(syncFn).toHaveBeenCalledWith(mockCanvas)
|
|
})
|
|
|
|
it('should not auto-start when autoStart is false', async () => {
|
|
const { isActive } = useCanvasTransformSync(mockCanvas, syncFn, callbacks, {
|
|
autoStart: false
|
|
})
|
|
|
|
await nextTick()
|
|
|
|
expect(isActive.value).toBe(false)
|
|
expect(callbacks.onStart).not.toHaveBeenCalled()
|
|
expect(syncFn).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should not start when canvas is null', async () => {
|
|
const { isActive } = useCanvasTransformSync(null, syncFn, callbacks)
|
|
|
|
await nextTick()
|
|
|
|
expect(isActive.value).toBe(false)
|
|
expect(callbacks.onStart).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should manually start and stop sync', async () => {
|
|
const { isActive, startSync, stopSync } = useCanvasTransformSync(
|
|
mockCanvas,
|
|
syncFn,
|
|
callbacks,
|
|
{ autoStart: false }
|
|
)
|
|
|
|
// Start manually
|
|
startSync()
|
|
await nextTick()
|
|
|
|
expect(isActive.value).toBe(true)
|
|
expect(callbacks.onStart).toHaveBeenCalledOnce()
|
|
|
|
// Stop manually
|
|
stopSync()
|
|
await nextTick()
|
|
|
|
expect(isActive.value).toBe(false)
|
|
expect(callbacks.onStop).toHaveBeenCalledOnce()
|
|
})
|
|
|
|
it('should call sync function on each frame', async () => {
|
|
useCanvasTransformSync(mockCanvas, syncFn, callbacks)
|
|
|
|
await nextTick()
|
|
|
|
// Advance timers to trigger additional frames (initial call + 3 more = 4 total)
|
|
vi.advanceTimersByTime(48) // 3 additional frames at 16ms each
|
|
await nextTick()
|
|
|
|
expect(syncFn).toHaveBeenCalledTimes(4) // Initial call + 3 timed calls
|
|
expect(syncFn).toHaveBeenCalledWith(mockCanvas)
|
|
})
|
|
|
|
it('should provide timing information in onUpdate callback', async () => {
|
|
// Mock performance.now to return predictable values
|
|
const mockNow = vi.spyOn(performance, 'now')
|
|
mockNow.mockReturnValueOnce(0).mockReturnValueOnce(5) // 5ms duration
|
|
|
|
useCanvasTransformSync(mockCanvas, syncFn, callbacks)
|
|
|
|
await nextTick()
|
|
|
|
expect(callbacks.onUpdate).toHaveBeenCalledWith(5)
|
|
})
|
|
|
|
it('should handle sync function that throws errors', async () => {
|
|
const errorSyncFn = vi.fn().mockImplementation(() => {
|
|
throw new Error('Sync failed')
|
|
})
|
|
|
|
// Creating the composable should not throw
|
|
expect(() => {
|
|
useCanvasTransformSync(mockCanvas, errorSyncFn, callbacks)
|
|
}).not.toThrow()
|
|
|
|
await nextTick()
|
|
|
|
// Even though sync function throws, the composable should handle it gracefully
|
|
expect(errorSyncFn).toHaveBeenCalled()
|
|
expect(callbacks.onStart).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should not start if already active', async () => {
|
|
const { startSync } = useCanvasTransformSync(mockCanvas, syncFn, callbacks)
|
|
|
|
await nextTick()
|
|
|
|
// Try to start again
|
|
startSync()
|
|
await nextTick()
|
|
|
|
// Should only be called once from auto-start
|
|
expect(callbacks.onStart).toHaveBeenCalledOnce()
|
|
})
|
|
|
|
it('should not stop if already inactive', async () => {
|
|
const { stopSync } = useCanvasTransformSync(mockCanvas, syncFn, callbacks, {
|
|
autoStart: false
|
|
})
|
|
|
|
// Try to stop when not started
|
|
stopSync()
|
|
await nextTick()
|
|
|
|
expect(callbacks.onStop).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should clean up on component unmount', async () => {
|
|
const TestComponent = {
|
|
setup() {
|
|
const { isActive } = useCanvasTransformSync(
|
|
mockCanvas,
|
|
syncFn,
|
|
callbacks
|
|
)
|
|
return { isActive }
|
|
},
|
|
template: '<div>{{ isActive }}</div>'
|
|
}
|
|
|
|
const wrapper = mount(TestComponent)
|
|
await nextTick()
|
|
|
|
expect(callbacks.onStart).toHaveBeenCalled()
|
|
|
|
// Unmount component
|
|
wrapper.unmount()
|
|
await nextTick()
|
|
|
|
expect(callbacks.onStop).toHaveBeenCalled()
|
|
expect(global.cancelAnimationFrame).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should work without callbacks', async () => {
|
|
const { isActive } = useCanvasTransformSync(mockCanvas, syncFn)
|
|
|
|
await nextTick()
|
|
|
|
expect(isActive.value).toBe(true)
|
|
expect(syncFn).toHaveBeenCalledWith(mockCanvas)
|
|
})
|
|
|
|
it('should stop sync when canvas becomes null during sync', async () => {
|
|
let currentCanvas: any = mockCanvas
|
|
const dynamicSyncFn = vi.fn(() => {
|
|
// Simulate canvas becoming null during sync
|
|
currentCanvas = null
|
|
})
|
|
|
|
const { isActive } = useCanvasTransformSync(
|
|
currentCanvas,
|
|
dynamicSyncFn,
|
|
callbacks
|
|
)
|
|
|
|
await nextTick()
|
|
|
|
expect(isActive.value).toBe(true)
|
|
|
|
// Advance time to trigger sync
|
|
vi.advanceTimersByTime(16)
|
|
await nextTick()
|
|
|
|
// Should handle null canvas gracefully
|
|
expect(dynamicSyncFn).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should use cancelAnimationFrame when stopping', async () => {
|
|
const { stopSync } = useCanvasTransformSync(mockCanvas, syncFn, callbacks)
|
|
|
|
await nextTick()
|
|
|
|
stopSync()
|
|
|
|
expect(global.cancelAnimationFrame).toHaveBeenCalledWith(1)
|
|
})
|
|
})
|