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>
6.6 KiB
6.6 KiB
Unit Testing Guide
This guide covers patterns and examples for unit testing utilities, composables, and other non-component code in the ComfyUI Frontend codebase.
Table of Contents
- Testing Vue Composables with Reactivity
- Working with LiteGraph and Nodes
- Working with Workflow JSON Files
- Mocking the API Object
- Mocking Utility Functions
- Testing with Debounce and Throttle
- Mocking Node Definitions
Testing Vue Composables with Reactivity
Testing Vue composables requires handling reactivity correctly:
// Example from: tests-ui/tests/composables/useServerLogs.test.ts
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick } from 'vue'
import { useServerLogs } from '@/composables/useServerLogs'
// Mock dependencies
vi.mock('@/scripts/api', () => ({
api: {
subscribeLogs: vi.fn()
}
}))
describe('useServerLogs', () => {
it('should update reactive logs when receiving events', async () => {
const { logs, startListening } = useServerLogs()
await startListening()
// Simulate log event handler being called
const mockHandler = vi.mocked(useEventListener).mock.calls[0][2]
mockHandler(new CustomEvent('logs', {
detail: {
type: 'logs',
entries: [{ m: 'Log message' }]
}
}))
// Must wait for Vue reactivity to update
await nextTick()
expect(logs.value).toEqual(['Log message'])
})
})
Working with LiteGraph and Nodes
Testing LiteGraph-related functionality:
// Example from: tests-ui/tests/litegraph.test.ts
import { LGraph, LGraphNode, LiteGraph } from '@/lib/litegraph'
import { describe, expect, it } from 'vitest'
// Create dummy node for testing
class DummyNode extends LGraphNode {
constructor() {
super('dummy')
}
}
describe('LGraph', () => {
it('should serialize graph nodes', async () => {
// Register node type
LiteGraph.registerNodeType('dummy', DummyNode)
// Create graph with nodes
const graph = new LGraph()
const node = new DummyNode()
graph.add(node)
// Test serialization
const result = graph.serialize()
expect(result.nodes).toHaveLength(1)
expect(result.nodes[0].type).toBe('dummy')
})
})
Working with Workflow JSON Files
Testing with ComfyUI workflow files:
// Example from: tests-ui/tests/comfyWorkflow.test.ts
import { describe, expect, it } from 'vitest'
import { validateComfyWorkflow } from '@/domains/workflow/validation/schemas/workflowSchema'
import { defaultGraph } from '@/scripts/defaultGraph'
describe('workflow validation', () => {
it('should validate default workflow', async () => {
const validWorkflow = JSON.parse(JSON.stringify(defaultGraph))
// Validate workflow
const result = await validateComfyWorkflow(validWorkflow)
expect(result).not.toBeNull()
})
it('should handle position format conversion', async () => {
const workflow = JSON.parse(JSON.stringify(defaultGraph))
// Legacy position format as object
workflow.nodes[0].pos = { '0': 100, '1': 200 }
// Should convert to array format
const result = await validateComfyWorkflow(workflow)
expect(result.nodes[0].pos).toEqual([100, 200])
})
})
Mocking the API Object
Mocking the ComfyUI API object:
// Example from: tests-ui/tests/composables/useServerLogs.test.ts
import { describe, expect, it, vi } from 'vitest'
import { api } from '@/scripts/api'
// Mock the api object
vi.mock('@/scripts/api', () => ({
api: {
subscribeLogs: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn()
}
}))
it('should subscribe to logs API', () => {
// Call function that uses the API
startListening()
// Verify API was called correctly
expect(api.subscribeLogs).toHaveBeenCalledWith(true)
})
Mocking Lodash Functions
Mocking utility functions like debounce:
// Mock debounce to execute immediately
import { debounce } from 'es-toolkit/compat'
vi.mock('es-toolkit/compat', () => ({
debounce: vi.fn((fn) => {
// Return function that calls the input function immediately
const mockDebounced = (...args: any[]) => fn(...args)
// Add cancel method that debounced functions have
mockDebounced.cancel = vi.fn()
return mockDebounced
})
}))
describe('Function using debounce', () => {
it('calls debounced function immediately in tests', () => {
const mockFn = vi.fn()
const debouncedFn = debounce(mockFn, 1000)
debouncedFn()
// No need to wait - our mock makes it execute immediately
expect(mockFn).toHaveBeenCalled()
})
})
Testing with Debounce and Throttle
When you need to test real debounce/throttle behavior:
// Example from: tests-ui/tests/composables/useWorkflowAutoSave.test.ts
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
describe('debounced function', () => {
beforeEach(() => {
vi.useFakeTimers() // Use fake timers to control time
})
afterEach(() => {
vi.useRealTimers()
})
it('should debounce function calls', () => {
const mockFn = vi.fn()
const debouncedFn = debounce(mockFn, 1000)
// Call multiple times
debouncedFn()
debouncedFn()
debouncedFn()
// Function not called yet (debounced)
expect(mockFn).not.toHaveBeenCalled()
// Advance time just before debounce period
vi.advanceTimersByTime(999)
expect(mockFn).not.toHaveBeenCalled()
// Advance to debounce completion
vi.advanceTimersByTime(1)
expect(mockFn).toHaveBeenCalledTimes(1)
})
})
Mocking Node Definitions
Creating mock node definitions for testing:
// Example from: tests-ui/tests/apiTypes.test.ts
import { describe, expect, it } from 'vitest'
import { type ComfyNodeDef, validateComfyNodeDef } from '@/schemas/nodeDefSchema'
// Create a complete mock node definition
const EXAMPLE_NODE_DEF: ComfyNodeDef = {
input: {
required: {
ckpt_name: [['model1.safetensors', 'model2.ckpt'], {}]
}
},
output: ['MODEL', 'CLIP', 'VAE'],
output_is_list: [false, false, false],
output_name: ['MODEL', 'CLIP', 'VAE'],
name: 'CheckpointLoaderSimple',
display_name: 'Load Checkpoint',
description: '',
python_module: 'nodes',
category: 'loaders',
output_node: false,
experimental: false,
deprecated: false
}
it('should validate node definition', () => {
expect(validateComfyNodeDef(EXAMPLE_NODE_DEF)).not.toBeNull()
})