mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 10:59:53 +00:00
cleanup: remove useCanvasTransformSync composables. (#5742)
No I am not proud of the new placeholder arguments. ## Summary Small change to unify the two composables before updating the logic. Edit: Now unifies them both into the **void**. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5742-cleanup-unify-useCanvasTransformSync-composables-2776d73d36508147ad39d11de8588b2e) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -49,11 +49,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRafFn } from '@vueuse/core'
|
||||
import Button from 'primevue/button'
|
||||
import Popover from 'primevue/popover'
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
|
||||
import { useCanvasTransformSync } from '@/composables/canvas/useCanvasTransformSync'
|
||||
import {
|
||||
forceCloseMoreOptionsSignal,
|
||||
moreOptionsOpen,
|
||||
@@ -152,9 +152,7 @@ const repositionPopover = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const { startSync, stopSync } = useCanvasTransformSync(repositionPopover, {
|
||||
autoStart: false
|
||||
})
|
||||
const { resume: startSync, pause: stopSync } = useRafFn(repositionPopover)
|
||||
|
||||
function openPopover(triggerEvent?: Event): boolean {
|
||||
const el = getButtonEl()
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
import { onUnmounted, ref } from 'vue'
|
||||
|
||||
import type { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
|
||||
interface CanvasTransformSyncOptions {
|
||||
/**
|
||||
* Whether to automatically start syncing when canvas is available
|
||||
* @default true
|
||||
*/
|
||||
autoStart?: boolean
|
||||
/**
|
||||
* Called when sync starts
|
||||
*/
|
||||
onStart?: () => void
|
||||
/**
|
||||
* Called when sync stops
|
||||
*/
|
||||
onStop?: () => void
|
||||
}
|
||||
|
||||
interface CanvasTransform {
|
||||
scale: number
|
||||
offsetX: number
|
||||
offsetY: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages requestAnimationFrame-based synchronization with LiteGraph canvas transforms.
|
||||
*
|
||||
* This composable provides a clean way to sync Vue transform state with LiteGraph canvas
|
||||
* on every frame. It handles RAF lifecycle management, and ensures proper cleanup.
|
||||
*
|
||||
* The sync function typically reads canvas.ds properties like offset and scale to keep
|
||||
* Vue components aligned with the canvas coordinate system.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const syncWithCanvas = (canvas: LGraphCanvas) => {
|
||||
* canvas.ds.scale
|
||||
* canvas.ds.offset
|
||||
* }
|
||||
*
|
||||
* const { isActive, startSync, stopSync } = useCanvasTransformSync(
|
||||
* syncWithCanvas,
|
||||
* {
|
||||
* autoStart: false,
|
||||
* onStart: () => emit('rafStatusChange', true),
|
||||
* onStop: () => emit('rafStatusChange', false)
|
||||
* }
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
export function useCanvasTransformSync(
|
||||
syncFn: (canvas: LGraphCanvas) => void,
|
||||
options: CanvasTransformSyncOptions = {}
|
||||
) {
|
||||
const { onStart, onStop, autoStart = true } = options
|
||||
const { getCanvas } = useCanvasStore()
|
||||
|
||||
const isActive = ref(false)
|
||||
let rafId: number | null = null
|
||||
let lastTransform: CanvasTransform = {
|
||||
scale: 0,
|
||||
offsetX: 0,
|
||||
offsetY: 0
|
||||
}
|
||||
|
||||
const hasTransformChanged = (canvas: LGraphCanvas): boolean => {
|
||||
const ds = canvas.ds
|
||||
return (
|
||||
ds.scale !== lastTransform.scale ||
|
||||
ds.offset[0] !== lastTransform.offsetX ||
|
||||
ds.offset[1] !== lastTransform.offsetY
|
||||
)
|
||||
}
|
||||
|
||||
const sync = () => {
|
||||
if (!isActive.value) return
|
||||
|
||||
const canvas = getCanvas()
|
||||
if (!canvas) return
|
||||
|
||||
try {
|
||||
// Only run sync if transform actually changed
|
||||
if (hasTransformChanged(canvas)) {
|
||||
lastTransform = {
|
||||
scale: canvas.ds.scale,
|
||||
offsetX: canvas.ds.offset[0],
|
||||
offsetY: canvas.ds.offset[1]
|
||||
}
|
||||
|
||||
syncFn(canvas)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Canvas transform sync error:', error)
|
||||
}
|
||||
|
||||
rafId = requestAnimationFrame(sync)
|
||||
}
|
||||
|
||||
const startSync = () => {
|
||||
if (isActive.value) return
|
||||
|
||||
isActive.value = true
|
||||
onStart?.()
|
||||
|
||||
// Reset last transform to force initial sync
|
||||
lastTransform = { scale: 0, offsetX: 0, offsetY: 0 }
|
||||
|
||||
sync()
|
||||
}
|
||||
|
||||
const stopSync = () => {
|
||||
isActive.value = false
|
||||
|
||||
if (rafId !== null) {
|
||||
cancelAnimationFrame(rafId)
|
||||
rafId = null
|
||||
}
|
||||
|
||||
onStop?.()
|
||||
}
|
||||
|
||||
onUnmounted(stopSync)
|
||||
|
||||
if (autoStart) {
|
||||
startSync()
|
||||
}
|
||||
|
||||
return {
|
||||
isActive,
|
||||
startSync,
|
||||
stopSync
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useRafFn } from '@vueuse/core'
|
||||
import { computed, onUnmounted, ref, watch } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
import { useCanvasTransformSync } from '@/composables/canvas/useCanvasTransformSync'
|
||||
import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems'
|
||||
import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'
|
||||
import type { ReadOnlyRect } from '@/lib/litegraph/src/interfaces'
|
||||
@@ -128,9 +128,7 @@ export function useSelectionToolboxPosition(
|
||||
}
|
||||
|
||||
// Sync with canvas transform
|
||||
const { startSync, stopSync } = useCanvasTransformSync(updateTransform, {
|
||||
autoStart: false
|
||||
})
|
||||
const { resume: startSync, pause: stopSync } = useRafFn(updateTransform)
|
||||
|
||||
// Watch for selection changes
|
||||
watch(
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRafFn } from '@vueuse/core'
|
||||
import { computed, provide } from 'vue'
|
||||
|
||||
import type { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
|
||||
import { TransformStateKey } from '@/renderer/core/layout/injectionKeys'
|
||||
import { useCanvasTransformSync } from '@/renderer/core/layout/transform/useCanvasTransformSync'
|
||||
import { useTransformSettling } from '@/renderer/core/layout/transform/useTransformSettling'
|
||||
import { useTransformState } from '@/renderer/core/layout/transform/useTransformState'
|
||||
import { useLOD } from '@/renderer/extensions/vueNodes/lod/useLOD'
|
||||
@@ -68,14 +68,19 @@ const handlePointerDown = (event: PointerEvent) => {
|
||||
|
||||
const emit = defineEmits<{
|
||||
rafStatusChange: [active: boolean]
|
||||
transformUpdate: [time: number]
|
||||
transformUpdate: []
|
||||
}>()
|
||||
|
||||
useCanvasTransformSync(props.canvas, syncWithCanvas, {
|
||||
onStart: () => emit('rafStatusChange', true),
|
||||
onUpdate: (duration) => emit('transformUpdate', duration),
|
||||
onStop: () => emit('rafStatusChange', false)
|
||||
})
|
||||
useRafFn(
|
||||
() => {
|
||||
if (!props.canvas) {
|
||||
return
|
||||
}
|
||||
syncWithCanvas(props.canvas)
|
||||
emit('transformUpdate')
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
import { onUnmounted, ref } from 'vue'
|
||||
|
||||
import type { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
interface CanvasTransformSyncOptions {
|
||||
/**
|
||||
* Whether to automatically start syncing when canvas is available
|
||||
* @default true
|
||||
*/
|
||||
autoStart?: boolean
|
||||
}
|
||||
|
||||
interface CanvasTransformSyncCallbacks {
|
||||
/**
|
||||
* Called when sync starts
|
||||
*/
|
||||
onStart?: () => void
|
||||
/**
|
||||
* Called after each sync update with timing information
|
||||
*/
|
||||
onUpdate?: (duration: number) => void
|
||||
/**
|
||||
* Called when sync stops
|
||||
*/
|
||||
onStop?: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages requestAnimationFrame-based synchronization with LiteGraph canvas transforms.
|
||||
*
|
||||
* This composable provides a clean way to sync Vue transform state with LiteGraph canvas
|
||||
* on every frame. It handles RAF lifecycle management, provides performance timing,
|
||||
* and ensures proper cleanup.
|
||||
*
|
||||
* The sync function typically reads canvas.ds (draw state) properties like offset and scale
|
||||
* to keep Vue components aligned with the canvas coordinate system.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const { isActive, startSync, stopSync } = useCanvasTransformSync(
|
||||
* canvas,
|
||||
* (canvas) => syncWithCanvas(canvas),
|
||||
* {
|
||||
* onStart: () => emit('rafStatusChange', true),
|
||||
* onUpdate: (time) => emit('transformUpdate', time),
|
||||
* onStop: () => emit('rafStatusChange', false)
|
||||
* }
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
export function useCanvasTransformSync(
|
||||
canvas: LGraphCanvas | undefined | null,
|
||||
syncFn: (canvas: LGraphCanvas) => void,
|
||||
callbacks: CanvasTransformSyncCallbacks = {},
|
||||
options: CanvasTransformSyncOptions = {}
|
||||
) {
|
||||
const { autoStart = true } = options
|
||||
const { onStart, onUpdate, onStop } = callbacks
|
||||
|
||||
const isActive = ref(false)
|
||||
let rafId: number | null = null
|
||||
|
||||
const startSync = () => {
|
||||
if (isActive.value || !canvas) return
|
||||
|
||||
isActive.value = true
|
||||
onStart?.()
|
||||
|
||||
const sync = () => {
|
||||
if (!isActive.value || !canvas) return
|
||||
|
||||
try {
|
||||
const startTime = performance.now()
|
||||
syncFn(canvas)
|
||||
const endTime = performance.now()
|
||||
|
||||
onUpdate?.(endTime - startTime)
|
||||
} catch (error) {
|
||||
console.warn('Canvas transform sync error:', error)
|
||||
}
|
||||
|
||||
rafId = requestAnimationFrame(sync)
|
||||
}
|
||||
|
||||
sync()
|
||||
}
|
||||
|
||||
const stopSync = () => {
|
||||
if (!isActive.value) return
|
||||
|
||||
if (rafId !== null) {
|
||||
cancelAnimationFrame(rafId)
|
||||
rafId = null
|
||||
}
|
||||
|
||||
isActive.value = false
|
||||
onStop?.()
|
||||
}
|
||||
|
||||
// Auto-start if canvas is available and autoStart is enabled
|
||||
if (autoStart && canvas) {
|
||||
startSync()
|
||||
}
|
||||
|
||||
// Clean up on unmount
|
||||
onUnmounted(() => {
|
||||
stopSync()
|
||||
})
|
||||
|
||||
return {
|
||||
isActive,
|
||||
startSync,
|
||||
stopSync
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useRafFn } from '@vueuse/core'
|
||||
import { computed, ref } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
import { useCanvasTransformSync } from '@/composables/canvas/useCanvasTransformSync'
|
||||
import type { LGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import {
|
||||
calculateMinimapScale,
|
||||
@@ -124,9 +124,8 @@ export function useMinimapViewport(
|
||||
|
||||
c.setDirty(true, true)
|
||||
}
|
||||
|
||||
const { startSync: startViewportSync, stopSync: stopViewportSync } =
|
||||
useCanvasTransformSync(updateViewport, { autoStart: false })
|
||||
const { resume: startViewportSync, pause: stopViewportSync } =
|
||||
useRafFn(updateViewport)
|
||||
|
||||
return {
|
||||
bounds: computed(() => bounds.value),
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
import { useCanvasTransformSync } from '@/composables/canvas/useCanvasTransformSync'
|
||||
|
||||
// Mock canvas store
|
||||
let mockGetCanvas = vi.fn()
|
||||
vi.mock('@/renderer/core/canvas/canvasStore', () => ({
|
||||
useCanvasStore: vi.fn(() => ({
|
||||
getCanvas: mockGetCanvas
|
||||
}))
|
||||
}))
|
||||
|
||||
describe('useCanvasTransformSync', () => {
|
||||
let mockCanvas: { ds: { scale: number; offset: [number, number] } }
|
||||
let syncFn: ReturnType<typeof vi.fn>
|
||||
|
||||
beforeEach(() => {
|
||||
mockCanvas = {
|
||||
ds: {
|
||||
scale: 1,
|
||||
offset: [0, 0]
|
||||
}
|
||||
}
|
||||
syncFn = vi.fn()
|
||||
mockGetCanvas = vi.fn(() => mockCanvas)
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('should not call syncFn when transform has not changed', async () => {
|
||||
const { startSync } = useCanvasTransformSync(syncFn, { autoStart: false })
|
||||
|
||||
startSync()
|
||||
await nextTick()
|
||||
|
||||
// Should call once initially
|
||||
expect(syncFn).toHaveBeenCalledTimes(1)
|
||||
|
||||
// Wait for next RAF cycle
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve))
|
||||
|
||||
// Should not call again since transform didn't change
|
||||
expect(syncFn).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should call syncFn when scale changes', async () => {
|
||||
const { startSync } = useCanvasTransformSync(syncFn, { autoStart: false })
|
||||
|
||||
startSync()
|
||||
await nextTick()
|
||||
|
||||
expect(syncFn).toHaveBeenCalledTimes(1)
|
||||
|
||||
// Change scale
|
||||
mockCanvas.ds.scale = 2
|
||||
|
||||
// Wait for next RAF cycle
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve))
|
||||
|
||||
expect(syncFn).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('should call syncFn when offset changes', async () => {
|
||||
const { startSync } = useCanvasTransformSync(syncFn, { autoStart: false })
|
||||
|
||||
startSync()
|
||||
await nextTick()
|
||||
|
||||
expect(syncFn).toHaveBeenCalledTimes(1)
|
||||
|
||||
// Change offset
|
||||
mockCanvas.ds.offset = [10, 20]
|
||||
|
||||
// Wait for next RAF cycles
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve))
|
||||
|
||||
expect(syncFn).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('should stop calling syncFn after stopSync is called', async () => {
|
||||
const { startSync, stopSync } = useCanvasTransformSync(syncFn, {
|
||||
autoStart: false
|
||||
})
|
||||
|
||||
startSync()
|
||||
await nextTick()
|
||||
|
||||
expect(syncFn).toHaveBeenCalledTimes(1)
|
||||
|
||||
stopSync()
|
||||
|
||||
// Change transform after stopping
|
||||
mockCanvas.ds.scale = 2
|
||||
|
||||
// Wait for RAF cycle
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve))
|
||||
|
||||
// Should not call again
|
||||
expect(syncFn).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should handle null canvas gracefully', async () => {
|
||||
mockGetCanvas.mockReturnValue(null)
|
||||
const { startSync } = useCanvasTransformSync(syncFn, { autoStart: false })
|
||||
|
||||
startSync()
|
||||
await nextTick()
|
||||
|
||||
// Should not call syncFn with null canvas
|
||||
expect(syncFn).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should call onStart and onStop callbacks', () => {
|
||||
const onStart = vi.fn()
|
||||
const onStop = vi.fn()
|
||||
|
||||
const { startSync, stopSync } = useCanvasTransformSync(syncFn, {
|
||||
autoStart: false,
|
||||
onStart,
|
||||
onStop
|
||||
})
|
||||
|
||||
startSync()
|
||||
expect(onStart).toHaveBeenCalledTimes(1)
|
||||
|
||||
stopSync()
|
||||
expect(onStop).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
@@ -1,240 +0,0 @@
|
||||
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)
|
||||
})
|
||||
})
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useRafFn } from '@vueuse/core'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { useCanvasTransformSync } from '@/composables/canvas/useCanvasTransformSync'
|
||||
import type { LGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import { useMinimapViewport } from '@/renderer/extensions/minimap/composables/useMinimapViewport'
|
||||
import type { MinimapCanvas } from '@/renderer/extensions/minimap/types'
|
||||
|
||||
vi.mock('@/composables/canvas/useCanvasTransformSync')
|
||||
vi.mock('@vueuse/core')
|
||||
vi.mock('@/renderer/core/spatial/boundsCalculator', () => ({
|
||||
calculateNodeBounds: vi.fn(),
|
||||
calculateMinimapScale: vi.fn(),
|
||||
@@ -41,10 +41,10 @@ describe('useMinimapViewport', () => {
|
||||
]
|
||||
} as any
|
||||
|
||||
vi.mocked(useCanvasTransformSync).mockReturnValue({
|
||||
startSync: vi.fn(),
|
||||
stopSync: vi.fn()
|
||||
} as any)
|
||||
vi.mocked(useRafFn, { partial: true }).mockReturnValue({
|
||||
resume: vi.fn(),
|
||||
pause: vi.fn()
|
||||
})
|
||||
})
|
||||
|
||||
it('should initialize with default bounds', () => {
|
||||
@@ -206,10 +206,10 @@ describe('useMinimapViewport', () => {
|
||||
const startSyncMock = vi.fn()
|
||||
const stopSyncMock = vi.fn()
|
||||
|
||||
vi.mocked(useCanvasTransformSync).mockReturnValue({
|
||||
startSync: startSyncMock,
|
||||
stopSync: stopSyncMock
|
||||
} as any)
|
||||
vi.mocked(useRafFn, { partial: true }).mockReturnValue({
|
||||
resume: startSyncMock,
|
||||
pause: stopSyncMock
|
||||
})
|
||||
|
||||
const canvasRef = ref(mockCanvas as any)
|
||||
const graphRef = ref(mockGraph as any)
|
||||
|
||||
Reference in New Issue
Block a user