mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
* [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>
8.0 KiB
8.0 KiB
Pinia Store Testing Guide
This guide covers patterns and examples for testing Pinia stores in the ComfyUI Frontend codebase.
Table of Contents
- Setting Up Store Tests
- Testing Store State
- Testing Store Actions
- Testing Store Getters
- Mocking Dependencies in Stores
- Testing Store Watchers
- Testing Store Integration
Setting Up Store Tests
Basic setup for testing Pinia stores:
// Example from: tests-ui/tests/store/workflowStore.test.ts
import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { useWorkflowStore } from '@/domains/workflow/ui/stores/workflowStore'
describe('useWorkflowStore', () => {
let store: ReturnType<typeof useWorkflowStore>
beforeEach(() => {
// Create a fresh pinia and activate it for each test
setActivePinia(createPinia())
// Initialize the store
store = useWorkflowStore()
// Clear any mocks
vi.clearAllMocks()
})
it('should initialize with default state', () => {
expect(store.workflows).toEqual([])
expect(store.activeWorkflow).toBeUndefined()
expect(store.openWorkflows).toEqual([])
})
})
Testing Store State
Testing store state changes:
// Example from: tests-ui/tests/store/workflowStore.test.ts
it('should create a temporary workflow with a unique path', () => {
const workflow = store.createTemporary()
expect(workflow.path).toBe('workflows/Unsaved Workflow.json')
const workflow2 = store.createTemporary()
expect(workflow2.path).toBe('workflows/Unsaved Workflow (2).json')
})
it('should create a temporary workflow not clashing with persisted workflows', async () => {
await syncRemoteWorkflows(['a.json'])
const workflow = store.createTemporary('a.json')
expect(workflow.path).toBe('workflows/a (2).json')
})
Testing Store Actions
Testing store actions:
// Example from: tests-ui/tests/store/workflowStore.test.ts
describe('openWorkflow', () => {
it('should load and open a temporary workflow', async () => {
// Create a test workflow
const workflow = store.createTemporary('test.json')
const mockWorkflowData = { nodes: [], links: [] }
// Mock the load response
vi.spyOn(workflow, 'load').mockImplementation(async () => {
workflow.changeTracker = { activeState: mockWorkflowData } as any
return workflow as LoadedComfyWorkflow
})
// Open the workflow
await store.openWorkflow(workflow)
// Verify the workflow is now active
expect(store.activeWorkflow?.path).toBe(workflow.path)
// Verify the workflow is in the open workflows list
expect(store.isOpen(workflow)).toBe(true)
})
it('should not reload an already active workflow', async () => {
const workflow = await store.createTemporary('test.json').load()
vi.spyOn(workflow, 'load')
// Set as active workflow
store.activeWorkflow = workflow
await store.openWorkflow(workflow)
// Verify load was not called
expect(workflow.load).not.toHaveBeenCalled()
})
})
Testing Store Getters
Testing store getters:
// Example from: tests-ui/tests/store/modelStore.test.ts
describe('getters', () => {
beforeEach(() => {
setActivePinia(createPinia())
store = useModelStore()
// Set up test data
store.models = {
checkpoints: [
{ name: 'model1.safetensors', path: 'models/checkpoints/model1.safetensors' },
{ name: 'model2.ckpt', path: 'models/checkpoints/model2.ckpt' }
],
loras: [
{ name: 'lora1.safetensors', path: 'models/loras/lora1.safetensors' }
]
}
// Mock API
vi.mocked(api.getModelInfo).mockImplementation(async (modelName) => {
if (modelName.includes('model1')) {
return { info: { resolution: 768 } }
}
return { info: { resolution: 512 } }
})
})
it('should return models grouped by type', () => {
expect(store.modelsByType.checkpoints.length).toBe(2)
expect(store.modelsByType.loras.length).toBe(1)
})
it('should filter models by name', () => {
store.searchTerm = 'model1'
expect(store.filteredModels.checkpoints.length).toBe(1)
expect(store.filteredModels.checkpoints[0].name).toBe('model1.safetensors')
})
})
Mocking Dependencies in Stores
Mocking API and other dependencies:
// Example from: tests-ui/tests/store/workflowStore.test.ts
// Add mock for api at the top of the file
vi.mock('@/scripts/api', () => ({
api: {
getUserData: vi.fn(),
storeUserData: vi.fn(),
listUserDataFullInfo: vi.fn(),
apiURL: vi.fn(),
addEventListener: vi.fn()
}
}))
// Mock comfyApp globally for the store setup
vi.mock('@/scripts/app', () => ({
app: {
canvas: null // Start with canvas potentially undefined or null
}
}))
describe('syncWorkflows', () => {
const syncRemoteWorkflows = async (filenames: string[]) => {
vi.mocked(api.listUserDataFullInfo).mockResolvedValue(
filenames.map((filename) => ({
path: filename,
modified: new Date().getTime(),
size: 1 // size !== -1 for remote workflows
}))
)
return await store.syncWorkflows()
}
it('should sync workflows', async () => {
await syncRemoteWorkflows(['a.json', 'b.json'])
expect(store.workflows.length).toBe(2)
})
})
Testing Store Watchers
Testing store watchers and reactive behavior:
// Example from: tests-ui/tests/store/workflowStore.test.ts
import { nextTick } from 'vue'
describe('Subgraphs', () => {
it('should update automatically when activeWorkflow changes', async () => {
// Arrange: Set initial canvas state
const initialSubgraph = {
name: 'Initial Subgraph',
pathToRootGraph: [{ name: 'Root' }, { name: 'Initial Subgraph' }],
isRootGraph: false
}
vi.mocked(comfyApp.canvas).subgraph = initialSubgraph as any
// Trigger initial update
store.updateActiveGraph()
await nextTick()
// Verify initial state
expect(store.isSubgraphActive).toBe(true)
expect(store.subgraphNamePath).toEqual(['Initial Subgraph'])
// Act: Change the active workflow and canvas state
const workflow2 = store.createTemporary('workflow2.json')
vi.spyOn(workflow2, 'load').mockImplementation(async () => {
workflow2.changeTracker = { activeState: {} } as any
workflow2.originalContent = '{}'
workflow2.content = '{}'
return workflow2 as LoadedComfyWorkflow
})
// Change canvas state
vi.mocked(comfyApp.canvas).subgraph = undefined
await store.openWorkflow(workflow2)
await nextTick() // Allow watcher to trigger
// Assert: Check state was updated by the watcher
expect(store.isSubgraphActive).toBe(false)
expect(store.subgraphNamePath).toEqual([])
})
})
Testing Store Integration
Testing store integration with other parts of the application:
// Example from: tests-ui/tests/store/workflowStore.test.ts
describe('renameWorkflow', () => {
it('should rename workflow and update bookmarks', async () => {
const workflow = store.createTemporary('dir/test.json')
const bookmarkStore = useWorkflowBookmarkStore()
// Set up initial bookmark
expect(workflow.path).toBe('workflows/dir/test.json')
await bookmarkStore.setBookmarked(workflow.path, true)
expect(bookmarkStore.isBookmarked(workflow.path)).toBe(true)
// Mock super.rename
vi.spyOn(Object.getPrototypeOf(workflow), 'rename').mockImplementation(
async function (this: any, newPath: string) {
this.path = newPath
return this
} as any
)
// Perform rename
const newPath = 'workflows/dir/renamed.json'
await store.renameWorkflow(workflow, newPath)
// Check that bookmark was transferred
expect(bookmarkStore.isBookmarked(newPath)).toBe(true)
expect(bookmarkStore.isBookmarked('workflows/dir/test.json')).toBe(false)
})
})