mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-07-11 09:42:22 +00:00
## Summary Adds an app mode validation warning so users can see when a workflow has errors before running and jump directly back to graph mode to review them. ## Changes - **What**: Adds a reusable app mode warning banner above the Run button when the execution error store reports workflow errors, including validation and missing asset states. - **What**: Reuses the existing graph-error navigation flow so the warning action switches out of app mode and opens the Errors panel in graph mode. - **What**: Updates the app mode Run button icon and accessible label in the warning state while keeping the Run action non-blocking. - **What**: Adds unit coverage for the warning render/accessibility state and an E2E flow that triggers a validation failure, dismisses the overlay, and opens graph errors from the app mode warning. - **Breaking**: None. - **Dependencies**: None. ## Review Focus The warning intentionally mirrors graph mode behavior: it surfaces the error state but does not prevent the user from clicking Run. This avoids turning display-level validation signals into hard execution blockers. The warning is driven by the existing `hasAnyError` aggregate, so missing nodes, missing models, and missing media are included alongside prompt/node/execution errors. ## Tests - `pnpm format` - `pnpm lint` - `pnpm typecheck` - `pnpm test:unit` - `pnpm knip` - `pnpm test:browser:local browser_tests/tests/appModeValidationWarning.spec.ts` ## Screenshots <img width="461" height="994" alt="스크린샷 2026-06-25 오후 7 00 55" src="https://github.com/user-attachments/assets/f8fc20bf-d572-46b5-9fa4-312e7c4c8076" />
106 lines
3.2 KiB
TypeScript
106 lines
3.2 KiB
TypeScript
import { createPinia, setActivePinia } from 'pinia'
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
|
import { useExecutionErrorStore } from '@/stores/executionErrorStore'
|
|
import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'
|
|
import { LGraph, LGraphCanvas, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
|
import { createMockCanvasRenderingContext2D } from '@/utils/__tests__/litegraphTestUtils'
|
|
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
|
|
|
import { useViewErrorsInGraph } from './useViewErrorsInGraph'
|
|
|
|
const apiMock = vi.hoisted(() => ({
|
|
getSettings: vi.fn(),
|
|
storeSetting: vi.fn(),
|
|
storeSettings: vi.fn()
|
|
}))
|
|
|
|
vi.mock('@/scripts/api', () => ({
|
|
api: apiMock
|
|
}))
|
|
|
|
const appMock = vi.hoisted(() => ({
|
|
ui: {
|
|
settings: {
|
|
dispatchChange: vi.fn()
|
|
}
|
|
},
|
|
rootGraph: {
|
|
events: new EventTarget(),
|
|
nodes: []
|
|
}
|
|
}))
|
|
|
|
vi.mock('@/scripts/app', () => ({
|
|
app: appMock
|
|
}))
|
|
|
|
function createSelectedCanvas() {
|
|
const graph = new LGraph()
|
|
const canvasElement = document.createElement('canvas')
|
|
canvasElement.width = 800
|
|
canvasElement.height = 600
|
|
canvasElement.getContext = vi
|
|
.fn()
|
|
.mockReturnValue(createMockCanvasRenderingContext2D())
|
|
|
|
const canvas = new LGraphCanvas(canvasElement, graph, {
|
|
skip_events: true,
|
|
skip_render: true
|
|
})
|
|
const node = new LGraphNode('Selected Node')
|
|
graph.add(node)
|
|
canvas.selectedItems.add(node)
|
|
node.selected = true
|
|
|
|
return { canvas, node }
|
|
}
|
|
|
|
describe('useViewErrorsInGraph', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
setActivePinia(createPinia())
|
|
apiMock.getSettings.mockResolvedValue({})
|
|
apiMock.storeSetting.mockResolvedValue(undefined)
|
|
apiMock.storeSettings.mockResolvedValue(undefined)
|
|
})
|
|
|
|
it('opens graph errors and clears app-mode error UI state', () => {
|
|
const canvasStore = useCanvasStore()
|
|
const executionErrorStore = useExecutionErrorStore()
|
|
const rightSidePanelStore = useRightSidePanelStore()
|
|
const workflowStore = useWorkflowStore()
|
|
const { canvas, node } = createSelectedCanvas()
|
|
workflowStore.activeWorkflow = {
|
|
activeMode: 'app'
|
|
} as typeof workflowStore.activeWorkflow
|
|
canvasStore.canvas = canvas
|
|
canvasStore.selectedItems = [node]
|
|
executionErrorStore.showErrorOverlay()
|
|
|
|
useViewErrorsInGraph().viewErrorsInGraph()
|
|
|
|
expect(node.selected).toBe(false)
|
|
expect(canvasStore.linearMode).toBe(false)
|
|
expect(canvasStore.selectedItems).toEqual([])
|
|
expect(rightSidePanelStore.activeTab).toBe('errors')
|
|
expect(rightSidePanelStore.isOpen).toBe(true)
|
|
expect(executionErrorStore.isErrorOverlayOpen).toBe(false)
|
|
})
|
|
|
|
it('opens graph errors when the canvas is not initialized', () => {
|
|
const canvasStore = useCanvasStore()
|
|
const executionErrorStore = useExecutionErrorStore()
|
|
const rightSidePanelStore = useRightSidePanelStore()
|
|
canvasStore.canvas = null
|
|
executionErrorStore.showErrorOverlay()
|
|
|
|
expect(() => useViewErrorsInGraph().viewErrorsInGraph()).not.toThrow()
|
|
|
|
expect(rightSidePanelStore.activeTab).toBe('errors')
|
|
expect(rightSidePanelStore.isOpen).toBe(true)
|
|
expect(executionErrorStore.isErrorOverlayOpen).toBe(false)
|
|
})
|
|
})
|