mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-05 05:32:02 +00:00
fix: 3D asset disappears when switching to image output in app mode (#9622)
## Summary Fix 3D asset disappearing when switching between 3D and image outputs in app mode — missing `onUnmounted` cleanup leaked WebGL contexts. ## Changes - **What**: Add `onUnmounted` hook to `Preview3d.vue` that calls `viewer.cleanup()`, releasing the WebGL context when Vue destroys the component via its v-if chain. Add unit tests covering init, cleanup on unmount, and remount behavior. ## Review Focus When switching outputs in app mode, Vue's v-if chain destroys and recreates `Preview3d`. Without `onUnmounted` cleanup, the old `Load3d` instance (WebGL context, RAF loop, ResizeObserver) leaks. After ~8-16 toggles, the browser's WebGL context limit is exhausted and new 3D viewers silently fail to render. <!-- Pipeline-Ticket: e36489d2-a9fb-47ca-9e27-88eb3170836b --> --------- Co-authored-by: Alexander Brown <drjkl@comfy.org>
This commit is contained in:
@@ -82,4 +82,71 @@ describe(flattenNodeOutput, () => {
|
||||
const result = flattenNodeOutput(['1', output])
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
|
||||
it('flattens non-standard output keys with ResultItem-like values', () => {
|
||||
const output = makeOutput({
|
||||
a_images: [{ filename: 'before.png', subfolder: '', type: 'output' }],
|
||||
b_images: [{ filename: 'after.png', subfolder: '', type: 'output' }]
|
||||
} as unknown as Partial<NodeExecutionOutput>)
|
||||
|
||||
const result = flattenNodeOutput(['10', output])
|
||||
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result.map((r) => r.filename)).toContain('before.png')
|
||||
expect(result.map((r) => r.filename)).toContain('after.png')
|
||||
})
|
||||
|
||||
it('excludes animated key', () => {
|
||||
const output = makeOutput({
|
||||
images: [{ filename: 'img.png', subfolder: '', type: 'output' }],
|
||||
animated: [true]
|
||||
})
|
||||
|
||||
const result = flattenNodeOutput(['1', output])
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].mediaType).toBe('images')
|
||||
})
|
||||
|
||||
it('excludes non-ResultItem array items', () => {
|
||||
const output = {
|
||||
images: [{ filename: 'img.png', subfolder: '', type: 'output' }],
|
||||
custom_data: [{ randomKey: 123 }]
|
||||
} as unknown as NodeExecutionOutput
|
||||
|
||||
const result = flattenNodeOutput(['1', output])
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].mediaType).toBe('images')
|
||||
})
|
||||
|
||||
it('accepts items with filename but no subfolder', () => {
|
||||
const output = {
|
||||
images: [
|
||||
{ filename: 'valid.png', subfolder: '', type: 'output' },
|
||||
{ filename: 'no-subfolder.png' }
|
||||
]
|
||||
} as unknown as NodeExecutionOutput
|
||||
|
||||
const result = flattenNodeOutput(['1', output])
|
||||
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0].filename).toBe('valid.png')
|
||||
expect(result[1].filename).toBe('no-subfolder.png')
|
||||
expect(result[1].subfolder).toBe('')
|
||||
})
|
||||
|
||||
it('excludes items missing filename', () => {
|
||||
const output = {
|
||||
images: [
|
||||
{ filename: 'valid.png', subfolder: '', type: 'output' },
|
||||
{ subfolder: '', type: 'output' }
|
||||
]
|
||||
} as unknown as NodeExecutionOutput
|
||||
|
||||
const result = flattenNodeOutput(['1', output])
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].filename).toBe('valid.png')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user