mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-05 13:10:24 +00:00
## Summary Change app mode changes to be written directly to the workflow on change instead of requiring explicit save via builder. Temporary: Adds `.app.json` file extension to app files for identification since we don't currently have a way to identify them with metadata Removes app builder save dialog and replaces it with default mode selection ## Changes - **What**: - ensure all save locations handle app mode - remove dirtyLinearData and flushing - **Breaking**: - if people are relying on workflow names and are converting to/from app mode in the same workflow, they will gain/lose the `.app` part of the extension ## Screenshots (if applicable) <img width="689" height="84" alt="image" src="https://github.com/user-attachments/assets/335596ee-dce9-4e3a-a7b5-f0715c294e41" /> <img width="421" height="324" alt="image" src="https://github.com/user-attachments/assets/ad3cd33c-e9f0-4c30-8874-d4507892fc6b" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9338-feat-App-mode-saving-rework-3176d73d3650813f9ae1f6c5a234da8c) by [Unito](https://www.unito.io)
164 lines
4.8 KiB
TypeScript
164 lines
4.8 KiB
TypeScript
import { createTestingPinia } from '@pinia/testing'
|
|
import { setActivePinia } from 'pinia'
|
|
import { nextTick } from 'vue'
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import type { LoadedComfyWorkflow } from '@/platform/workflow/management/stores/comfyWorkflow'
|
|
import { ComfyWorkflow as ComfyWorkflowClass } from '@/platform/workflow/management/stores/comfyWorkflow'
|
|
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
|
import { app } from '@/scripts/app'
|
|
import { createMockChangeTracker } from '@/utils/__tests__/litegraphTestUtils'
|
|
|
|
vi.mock('@/scripts/app', () => ({
|
|
app: {
|
|
rootGraph: { extra: {} }
|
|
}
|
|
}))
|
|
|
|
vi.mock('@/renderer/core/canvas/canvasStore', () => ({
|
|
useCanvasStore: () => ({
|
|
getCanvas: () => ({ read_only: false })
|
|
})
|
|
}))
|
|
|
|
import { useAppModeStore } from './appModeStore'
|
|
|
|
function createBuilderWorkflow(
|
|
activeMode: string = 'builder:select'
|
|
): LoadedComfyWorkflow {
|
|
const workflow = new ComfyWorkflowClass({
|
|
path: 'workflows/test.json',
|
|
modified: Date.now(),
|
|
size: 100
|
|
})
|
|
workflow.changeTracker = createMockChangeTracker()
|
|
workflow.content = '{}'
|
|
workflow.originalContent = '{}'
|
|
workflow.activeMode = activeMode as LoadedComfyWorkflow['activeMode']
|
|
return workflow as LoadedComfyWorkflow
|
|
}
|
|
|
|
describe('appModeStore', () => {
|
|
beforeEach(() => {
|
|
setActivePinia(createTestingPinia({ stubActions: false }))
|
|
vi.clearAllMocks()
|
|
vi.mocked(app.rootGraph).extra = {}
|
|
})
|
|
|
|
describe('enterBuilder', () => {
|
|
it('navigates to builder:arrange when in app mode with outputs', () => {
|
|
const workflowStore = useWorkflowStore()
|
|
workflowStore.activeWorkflow = createBuilderWorkflow('app')
|
|
|
|
const store = useAppModeStore()
|
|
store.selectedOutputs.push(1)
|
|
|
|
store.enterBuilder()
|
|
|
|
expect(workflowStore.activeWorkflow!.activeMode).toBe('builder:arrange')
|
|
})
|
|
|
|
it('navigates to builder:select when in app mode without outputs', () => {
|
|
const workflowStore = useWorkflowStore()
|
|
workflowStore.activeWorkflow = createBuilderWorkflow('app')
|
|
|
|
const store = useAppModeStore()
|
|
|
|
store.enterBuilder()
|
|
|
|
expect(workflowStore.activeWorkflow!.activeMode).toBe('builder:select')
|
|
})
|
|
|
|
it('navigates to builder:select when in graph mode with outputs', () => {
|
|
const workflowStore = useWorkflowStore()
|
|
workflowStore.activeWorkflow = createBuilderWorkflow('graph')
|
|
|
|
const store = useAppModeStore()
|
|
store.selectedOutputs.push(1)
|
|
|
|
store.enterBuilder()
|
|
|
|
expect(workflowStore.activeWorkflow!.activeMode).toBe('builder:select')
|
|
})
|
|
|
|
it('navigates to builder:select when in graph mode without outputs', () => {
|
|
const workflowStore = useWorkflowStore()
|
|
workflowStore.activeWorkflow = createBuilderWorkflow('graph')
|
|
|
|
const store = useAppModeStore()
|
|
|
|
store.enterBuilder()
|
|
|
|
expect(workflowStore.activeWorkflow!.activeMode).toBe('builder:select')
|
|
})
|
|
})
|
|
|
|
describe('linearData sync watcher', () => {
|
|
it('writes linearData to rootGraph.extra when in builder mode', async () => {
|
|
const workflowStore = useWorkflowStore()
|
|
const store = useAppModeStore()
|
|
|
|
workflowStore.activeWorkflow = createBuilderWorkflow()
|
|
await nextTick()
|
|
|
|
store.selectedOutputs.push(1)
|
|
await nextTick()
|
|
|
|
expect(app.rootGraph.extra.linearData).toEqual({
|
|
inputs: [],
|
|
outputs: [1]
|
|
})
|
|
})
|
|
|
|
it('does not write linearData when not in builder mode', async () => {
|
|
const workflowStore = useWorkflowStore()
|
|
const store = useAppModeStore()
|
|
|
|
const workflow = createBuilderWorkflow()
|
|
workflow.activeMode = 'graph'
|
|
workflowStore.activeWorkflow = workflow
|
|
await nextTick()
|
|
|
|
store.selectedOutputs.push(1)
|
|
await nextTick()
|
|
|
|
expect(app.rootGraph.extra.linearData).toBeUndefined()
|
|
})
|
|
|
|
it('does not write when rootGraph is null', async () => {
|
|
const workflowStore = useWorkflowStore()
|
|
const store = useAppModeStore()
|
|
|
|
workflowStore.activeWorkflow = createBuilderWorkflow()
|
|
await nextTick()
|
|
|
|
const originalRootGraph = app.rootGraph
|
|
Object.defineProperty(app, 'rootGraph', { value: null, writable: true })
|
|
|
|
store.selectedOutputs.push(1)
|
|
await nextTick()
|
|
|
|
Object.defineProperty(app, 'rootGraph', {
|
|
value: originalRootGraph,
|
|
writable: true
|
|
})
|
|
})
|
|
|
|
it('reflects input changes in linearData', async () => {
|
|
const workflowStore = useWorkflowStore()
|
|
const store = useAppModeStore()
|
|
|
|
workflowStore.activeWorkflow = createBuilderWorkflow()
|
|
await nextTick()
|
|
|
|
store.selectedInputs.push([42, 'prompt'])
|
|
await nextTick()
|
|
|
|
expect(app.rootGraph.extra.linearData).toEqual({
|
|
inputs: [[42, 'prompt']],
|
|
outputs: []
|
|
})
|
|
})
|
|
})
|
|
})
|