fix: update reactive ref after merge in imagePreviewStore (#8479)

## Summary
Fix for COM-14110: Preview image does not display new outputs in
vue-nodes.

## Problem
The merge logic in `setOutputsByLocatorId` updated `app.nodeOutputs` but
returned early without updating the reactive `nodeOutputs.value` ref.
This caused Vue components to never receive merged output updates
because only the non-reactive `app.nodeOutputs` was being updated.

## Solution
Added `nodeOutputs.value[nodeLocatorId] = existingOutput` after the
merge loop, before the return statement.

## Testing
- Added 2 unit tests covering the merge behavior
- All 4076 existing unit tests pass
- Typechecks pass
- Lint passes

## Notes
- Related open PRs touching same files: #8143, #8366 - potential minor
conflicts possible

Fixes COM-14110

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8479-fix-update-reactive-ref-after-merge-in-imagePreviewStore-2f86d73d365081f1a145fa5a9782515f)
by [Unito](https://www.unito.io)

Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Christian Byrne
2026-01-30 16:49:57 -08:00
committed by GitHub
parent a64c561a5f
commit c4e5fc8dbf
2 changed files with 60 additions and 1 deletions

View File

@@ -14,7 +14,9 @@ vi.mock('@/utils/litegraphUtil', () => ({
vi.mock('@/scripts/app', () => ({
app: {
getPreviewFormatParam: vi.fn(() => '&format=test_webp')
getPreviewFormatParam: vi.fn(() => '&format=test_webp'),
nodeOutputs: {} as Record<string, unknown>,
nodePreviewImages: {} as Record<string, string[]>
}
}))
@@ -29,6 +31,62 @@ const createMockOutputs = (
images?: ExecutedWsMessage['output']['images']
): ExecutedWsMessage['output'] => ({ images })
vi.mock('@/stores/executionStore', () => ({
useExecutionStore: vi.fn(() => ({
executionIdToNodeLocatorId: vi.fn((id: string) => id)
}))
}))
vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({
useWorkflowStore: vi.fn(() => ({
nodeIdToNodeLocatorId: vi.fn((id: string | number) => String(id)),
nodeToNodeLocatorId: vi.fn((node: { id: number }) => String(node.id))
}))
}))
describe('imagePreviewStore setNodeOutputsByExecutionId with merge', () => {
beforeEach(() => {
setActivePinia(createTestingPinia({ stubActions: false }))
vi.clearAllMocks()
app.nodeOutputs = {}
app.nodePreviewImages = {}
})
it('should update reactive nodeOutputs.value when merging outputs', () => {
const store = useNodeOutputStore()
const executionId = '1'
const initialOutput = createMockOutputs([{ filename: 'a.png' }])
store.setNodeOutputsByExecutionId(executionId, initialOutput)
expect(app.nodeOutputs[executionId]?.images).toHaveLength(1)
expect(store.nodeOutputs[executionId]?.images).toHaveLength(1)
const newOutput = createMockOutputs([{ filename: 'b.png' }])
store.setNodeOutputsByExecutionId(executionId, newOutput, { merge: true })
expect(app.nodeOutputs[executionId]?.images).toHaveLength(2)
expect(store.nodeOutputs[executionId]?.images).toHaveLength(2)
})
it('should assign to reactive ref after merge for Vue reactivity', () => {
const store = useNodeOutputStore()
const executionId = '1'
const initialOutput = createMockOutputs([{ filename: 'a.png' }])
store.setNodeOutputsByExecutionId(executionId, initialOutput)
const newOutput = createMockOutputs([{ filename: 'b.png' }])
store.setNodeOutputsByExecutionId(executionId, newOutput, { merge: true })
expect(store.nodeOutputs[executionId]).toStrictEqual(
app.nodeOutputs[executionId]
)
expect(store.nodeOutputs[executionId]?.images).toHaveLength(2)
})
})
describe('imagePreviewStore getPreviewParam', () => {
beforeEach(() => {
setActivePinia(createTestingPinia({ stubActions: false }))

View File

@@ -148,6 +148,7 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => {
existingOutput[k] = newValue
}
}
nodeOutputs.value[nodeLocatorId] = existingOutput
return
}
}