Files
ComfyUI_frontend/tests-ui/tests/store/subgraphNavigationStore.viewport.test.ts
Christian Byrne ca312fd1ea [refactor] Improve workflow domain organization (#5584)
* [refactor] move workflow domain to its own folder

* [refactor] Fix workflow platform architecture organization

- Move workflow rendering functionality to renderer/thumbnail domain
- Rename ui folder to management for better semantic clarity
- Update all import paths to reflect proper domain boundaries
- Fix test imports to use new structure

Architecture improvements:
- rendering → renderer/thumbnail (belongs with other rendering logic)
- ui → management (better name for state management and UI integration)

This ensures proper separation of concerns and domain boundaries.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* [fix] Resolve circular dependency between nodeDefStore and subgraphStore

* [fix] Update browser test imports to use new workflow platform paths

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-15 02:22:37 -07:00

255 lines
7.4 KiB
TypeScript

import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick } from 'vue'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
import type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'
import { app } from '@/scripts/app'
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
vi.mock('@/scripts/app', () => {
const mockCanvas = {
subgraph: null,
ds: {
scale: 1,
offset: [0, 0],
state: {
scale: 1,
offset: [0, 0]
}
},
setDirty: vi.fn()
}
return {
app: {
graph: {
_nodes: [],
nodes: [],
subgraphs: new Map(),
getNodeById: vi.fn()
},
canvas: mockCanvas
}
}
})
// Mock canvasStore
vi.mock('@/renderer/core/canvas/canvasStore', () => ({
useCanvasStore: () => ({
getCanvas: () => (app as any).canvas
})
}))
// Get reference to mock canvas
const mockCanvas = app.canvas as any
describe('useSubgraphNavigationStore - Viewport Persistence', () => {
beforeEach(() => {
setActivePinia(createPinia())
// Reset canvas state
mockCanvas.ds.scale = 1
mockCanvas.ds.offset = [0, 0]
mockCanvas.ds.state.scale = 1
mockCanvas.ds.state.offset = [0, 0]
mockCanvas.setDirty.mockClear()
})
describe('saveViewport', () => {
it('should save viewport state for root graph', () => {
const navigationStore = useSubgraphNavigationStore()
// Set viewport state
mockCanvas.ds.state.scale = 2
mockCanvas.ds.state.offset = [100, 200]
// Save viewport for root
navigationStore.saveViewport('root')
// Check it was saved
const saved = navigationStore.viewportCache.get('root')
expect(saved).toEqual({
scale: 2,
offset: [100, 200]
})
})
it('should save viewport state for subgraph', () => {
const navigationStore = useSubgraphNavigationStore()
// Set viewport state
mockCanvas.ds.state.scale = 1.5
mockCanvas.ds.state.offset = [50, 75]
// Save viewport for subgraph
navigationStore.saveViewport('subgraph-123')
// Check it was saved
const saved = navigationStore.viewportCache.get('subgraph-123')
expect(saved).toEqual({
scale: 1.5,
offset: [50, 75]
})
})
it('should save viewport for current context when no ID provided', () => {
const navigationStore = useSubgraphNavigationStore()
const workflowStore = useWorkflowStore()
// Mock being in a subgraph
const mockSubgraph = { id: 'sub-456' }
workflowStore.activeSubgraph = mockSubgraph as any
// Set viewport state
mockCanvas.ds.state.scale = 3
mockCanvas.ds.state.offset = [10, 20]
// Save viewport without ID (should default to root since activeSubgraph is not tracked by navigation store)
navigationStore.saveViewport('sub-456')
// Should save for the specified subgraph
const saved = navigationStore.viewportCache.get('sub-456')
expect(saved).toEqual({
scale: 3,
offset: [10, 20]
})
})
})
describe('restoreViewport', () => {
it('should restore viewport state for root graph', () => {
const navigationStore = useSubgraphNavigationStore()
// Save a viewport state
navigationStore.viewportCache.set('root', {
scale: 2.5,
offset: [150, 250]
})
// Restore it
navigationStore.restoreViewport('root')
// Check canvas was updated
expect(mockCanvas.ds.scale).toBe(2.5)
expect(mockCanvas.ds.offset).toEqual([150, 250])
expect(mockCanvas.setDirty).toHaveBeenCalledWith(true, true)
})
it('should restore viewport state for subgraph', () => {
const navigationStore = useSubgraphNavigationStore()
// Save a viewport state
navigationStore.viewportCache.set('sub-789', {
scale: 0.75,
offset: [-50, -100]
})
// Restore it
navigationStore.restoreViewport('sub-789')
// Check canvas was updated
expect(mockCanvas.ds.scale).toBe(0.75)
expect(mockCanvas.ds.offset).toEqual([-50, -100])
})
it('should do nothing if no saved viewport exists', () => {
const navigationStore = useSubgraphNavigationStore()
// Reset canvas
mockCanvas.ds.scale = 1
mockCanvas.ds.offset = [0, 0]
mockCanvas.setDirty.mockClear()
// Try to restore non-existent viewport
navigationStore.restoreViewport('non-existent')
// Canvas should not change
expect(mockCanvas.ds.scale).toBe(1)
expect(mockCanvas.ds.offset).toEqual([0, 0])
expect(mockCanvas.setDirty).not.toHaveBeenCalled()
})
})
describe('navigation integration', () => {
it('should save and restore viewport when navigating between subgraphs', async () => {
const navigationStore = useSubgraphNavigationStore()
const workflowStore = useWorkflowStore()
// Create mock subgraph with both _nodes and nodes properties
const mockRootGraph = {
_nodes: [],
nodes: [],
subgraphs: new Map(),
getNodeById: vi.fn()
}
const subgraph1 = {
id: 'sub1',
rootGraph: mockRootGraph,
_nodes: [],
nodes: []
}
// Start at root with custom viewport
mockCanvas.ds.state.scale = 2
mockCanvas.ds.state.offset = [100, 100]
// Navigate to subgraph
workflowStore.activeSubgraph = subgraph1 as any
await nextTick()
// Root viewport should have been saved automatically
const rootViewport = navigationStore.viewportCache.get('root')
expect(rootViewport).toBeDefined()
expect(rootViewport?.scale).toBe(2)
expect(rootViewport?.offset).toEqual([100, 100])
// Change viewport in subgraph
mockCanvas.ds.state.scale = 0.5
mockCanvas.ds.state.offset = [-50, -50]
// Navigate back to root
workflowStore.activeSubgraph = undefined
await nextTick()
// Subgraph viewport should have been saved automatically
const sub1Viewport = navigationStore.viewportCache.get('sub1')
expect(sub1Viewport).toBeDefined()
expect(sub1Viewport?.scale).toBe(0.5)
expect(sub1Viewport?.offset).toEqual([-50, -50])
// Root viewport should be restored automatically
expect(mockCanvas.ds.scale).toBe(2)
expect(mockCanvas.ds.offset).toEqual([100, 100])
})
it('should preserve viewport cache when switching workflows', async () => {
const navigationStore = useSubgraphNavigationStore()
const workflowStore = useWorkflowStore()
// Add some viewport states
navigationStore.viewportCache.set('root', { scale: 2, offset: [0, 0] })
navigationStore.viewportCache.set('sub1', {
scale: 1.5,
offset: [10, 10]
})
expect(navigationStore.viewportCache.size).toBe(2)
// Switch workflows
const workflow1 = { path: 'workflow1.json' } as ComfyWorkflow
const workflow2 = { path: 'workflow2.json' } as ComfyWorkflow
workflowStore.activeWorkflow = workflow1 as any
await nextTick()
workflowStore.activeWorkflow = workflow2 as any
await nextTick()
// Cache should be preserved (LRU will manage memory)
expect(navigationStore.viewportCache.size).toBe(2)
expect(navigationStore.viewportCache.has('root')).toBe(true)
expect(navigationStore.viewportCache.has('sub1')).toBe(true)
})
})
})