import { createPinia, setActivePinia } from 'pinia' import { beforeEach, describe, expect, it, vi } from 'vitest' import { ref } from 'vue' import { useCoreCommands } from '@/composables/useCoreCommands' import type { LGraphNode } from '@/lib/litegraph/src/litegraph' import { useSettingStore } from '@/platform/settings/settingStore' import { api } from '@/scripts/api' import { app } from '@/scripts/app' import { createMockLGraphNode } from '@/utils/__tests__/litegraphTestUtils' // Mock vue-i18n for useExternalLink const mockLocale = ref('en') vi.mock('vue-i18n', async () => { const actual = await vi.importActual('vue-i18n') return { ...actual, useI18n: vi.fn(() => ({ locale: mockLocale })) } }) vi.mock('@/scripts/app', () => { const mockGraphClear = vi.fn() const mockCanvas = { subgraph: undefined } return { app: { clean: vi.fn(() => { // Simulate app.clean() calling graph.clear() only when not in subgraph if (!mockCanvas.subgraph) { mockGraphClear() } }), canvas: mockCanvas, rootGraph: { clear: mockGraphClear } } } }) vi.mock('@/scripts/api', () => ({ api: { dispatchCustomEvent: vi.fn(), apiURL: vi.fn(() => 'http://localhost:8188') } })) vi.mock('@/platform/settings/settingStore') vi.mock('@/stores/firebaseAuthStore', () => ({ useFirebaseAuthStore: vi.fn(() => ({})) })) vi.mock('@/composables/auth/useFirebaseAuth', () => ({ useFirebaseAuth: vi.fn(() => null) })) vi.mock('firebase/auth', () => ({ setPersistence: vi.fn(), browserLocalPersistence: {}, onAuthStateChanged: vi.fn() })) vi.mock('@/platform/workflow/core/services/workflowService', () => ({ useWorkflowService: vi.fn(() => ({})) })) vi.mock('@/services/dialogService', () => ({ useDialogService: vi.fn(() => ({})) })) vi.mock('@/services/litegraphService', () => ({ useLitegraphService: vi.fn(() => ({})) })) vi.mock('@/stores/executionStore', () => ({ useExecutionStore: vi.fn(() => ({})) })) vi.mock('@/stores/toastStore', () => ({ useToastStore: vi.fn(() => ({})) })) vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({ useWorkflowStore: vi.fn(() => ({})) })) vi.mock('@/stores/subgraphStore', () => ({ useSubgraphStore: vi.fn(() => ({})) })) vi.mock('@/stores/workspace/colorPaletteStore', () => ({ useColorPaletteStore: vi.fn(() => ({})) })) vi.mock('@/composables/auth/useFirebaseAuthActions', () => ({ useFirebaseAuthActions: vi.fn(() => ({})) })) vi.mock('@/platform/cloud/subscription/composables/useSubscription', () => ({ useSubscription: vi.fn(() => ({ isActiveSubscription: vi.fn().mockReturnValue(true), showSubscriptionDialog: vi.fn() })) })) describe('useCoreCommands', () => { const createMockNode = (id: number, comfyClass: string): LGraphNode => { const baseNode = createMockLGraphNode({ id }) return Object.assign(baseNode, { constructor: { ...baseNode.constructor, comfyClass } }) } const createMockSubgraph = () => { const mockNodes = [ // Mock input node createMockNode(1, 'SubgraphInputNode'), // Mock output node createMockNode(2, 'SubgraphOutputNode'), // Mock user node createMockNode(3, 'SomeUserNode'), // Another mock user node createMockNode(4, 'AnotherUserNode') ] return { nodes: mockNodes, remove: vi.fn(), events: { dispatch: vi.fn(), addEventListener: vi.fn(), removeEventListener: vi.fn(), dispatchEvent: vi.fn() }, name: 'test-subgraph', inputNode: undefined, outputNode: undefined, add: vi.fn(), clear: vi.fn(), serialize: vi.fn(), configure: vi.fn(), start: vi.fn(), stop: vi.fn(), runStep: vi.fn(), findNodeByTitle: vi.fn(), findNodesByTitle: vi.fn(), findNodesByType: vi.fn(), findNodeById: vi.fn(), getNodeById: vi.fn(), setDirtyCanvas: vi.fn(), sendActionToCanvas: vi.fn() } as Partial as typeof app.canvas.subgraph } const mockSubgraph = createMockSubgraph() function createMockSettingStore( getReturnValue: boolean ): ReturnType { return { get: vi.fn().mockReturnValue(getReturnValue), addSetting: vi.fn(), load: vi.fn(), set: vi.fn(), exists: vi.fn(), getDefaultValue: vi.fn(), isReady: true, isLoading: false, error: undefined, settingValues: {}, settingsById: {}, $id: 'setting', $state: { settingValues: {}, settingsById: {}, isReady: true, isLoading: false, error: undefined }, $patch: vi.fn(), $reset: vi.fn(), $subscribe: vi.fn(), $onAction: vi.fn(), $dispose: vi.fn(), _customProperties: new Set() } satisfies ReturnType } beforeEach(() => { vi.clearAllMocks() // Set up Pinia setActivePinia(createPinia()) // Reset app state app.canvas.subgraph = undefined // Mock settings store vi.mocked(useSettingStore).mockReturnValue(createMockSettingStore(false)) // Mock global confirm global.confirm = vi.fn().mockReturnValue(true) }) describe('ClearWorkflow command', () => { it('should clear main graph when not in subgraph', async () => { const commands = useCoreCommands() const clearCommand = commands.find( (cmd) => cmd.id === 'Comfy.ClearWorkflow' )! // Execute the command await clearCommand.function() expect(app.clean).toHaveBeenCalled() expect(app.rootGraph.clear).toHaveBeenCalled() expect(api.dispatchCustomEvent).toHaveBeenCalledWith('graphCleared') }) it('should preserve input/output nodes when clearing subgraph', async () => { // Set up subgraph context app.canvas.subgraph = mockSubgraph const commands = useCoreCommands() const clearCommand = commands.find( (cmd) => cmd.id === 'Comfy.ClearWorkflow' )! // Execute the command await clearCommand.function() expect(app.clean).toHaveBeenCalled() expect(app.rootGraph.clear).not.toHaveBeenCalled() // Should only remove user nodes, not input/output nodes const subgraph = app.canvas.subgraph! expect(subgraph.remove).toHaveBeenCalledTimes(2) expect(subgraph.remove).toHaveBeenCalledWith(subgraph.nodes[2]) // user1 expect(subgraph.remove).toHaveBeenCalledWith(subgraph.nodes[3]) // user2 expect(subgraph.remove).not.toHaveBeenCalledWith(subgraph.nodes[0]) // input1 expect(subgraph.remove).not.toHaveBeenCalledWith(subgraph.nodes[1]) // output1 expect(api.dispatchCustomEvent).toHaveBeenCalledWith('graphCleared') }) it('should respect confirmation setting', async () => { // Mock confirmation required vi.mocked(useSettingStore).mockReturnValue(createMockSettingStore(true)) global.confirm = vi.fn().mockReturnValue(false) // User cancels const commands = useCoreCommands() const clearCommand = commands.find( (cmd) => cmd.id === 'Comfy.ClearWorkflow' )! // Execute the command await clearCommand.function() // Should not clear anything when user cancels expect(app.clean).not.toHaveBeenCalled() expect(app.rootGraph.clear).not.toHaveBeenCalled() expect(api.dispatchCustomEvent).not.toHaveBeenCalled() }) }) })