mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-09 23:20:04 +00:00
fix: stabilize nested subgraph promoted widget resolution (#9282)
## Summary Fix multiple issues with promoted widget resolution in nested subgraphs, ensuring correct value propagation, slot matching, and rendering for deeply nested promoted widgets. ## Changes - **What**: Stabilize nested subgraph promoted widget resolution chain - Use deep source keys for promoted widget values in Vue rendering mode - Resolve effective widget options from the source widget instead of the promoted view - Stabilize slot resolution for nested promoted widgets - Preserve combo value rendering for promoted subgraph widgets - Prevent subgraph definition deletion while other nodes still reference the same type - Clean up unused exported resolution types ## Review Focus - `resolveConcretePromotedWidget.ts` — new recursive resolution logic for deeply nested promoted widgets - `useGraphNodeManager.ts` — option extraction now uses `effectiveWidget` for promoted widgets - `SubgraphNode.ts` — unpack no longer force-deletes definitions referenced by other nodes ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9282-fix-stabilize-nested-subgraph-promoted-widget-resolution-3146d73d365081208a4fe931bb7569cf) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
116
src/components/graph/widgets/DomWidget.test.ts
Normal file
116
src/components/graph/widgets/DomWidget.test.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { reactive } from 'vue'
|
||||
|
||||
import { createMockLGraphNode } from '@/utils/__tests__/litegraphTestUtils'
|
||||
import type { BaseDOMWidget } from '@/scripts/domWidget'
|
||||
import type { DomWidgetState } from '@/stores/domWidgetStore'
|
||||
import { useDomWidgetStore } from '@/stores/domWidgetStore'
|
||||
|
||||
import DomWidget from './DomWidget.vue'
|
||||
|
||||
const mockUpdatePosition = vi.fn()
|
||||
const mockUpdateClipPath = vi.fn()
|
||||
const mockCanvasElement = document.createElement('canvas')
|
||||
const mockCanvasStore = {
|
||||
canvas: {
|
||||
graph: {
|
||||
getNodeById: vi.fn(() => true)
|
||||
},
|
||||
ds: {
|
||||
offset: [0, 0],
|
||||
scale: 1
|
||||
},
|
||||
canvas: mockCanvasElement,
|
||||
selected_nodes: {}
|
||||
},
|
||||
getCanvas: () => ({ canvas: mockCanvasElement }),
|
||||
linearMode: false
|
||||
}
|
||||
|
||||
vi.mock('@/composables/element/useAbsolutePosition', () => ({
|
||||
useAbsolutePosition: () => ({
|
||||
style: reactive<Record<string, string>>({}),
|
||||
updatePosition: mockUpdatePosition
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/element/useDomClipping', () => ({
|
||||
useDomClipping: () => ({
|
||||
style: reactive<Record<string, string>>({}),
|
||||
updateClipPath: mockUpdateClipPath
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/renderer/core/canvas/canvasStore', () => ({
|
||||
useCanvasStore: () => mockCanvasStore
|
||||
}))
|
||||
|
||||
vi.mock('@/platform/settings/settingStore', () => ({
|
||||
useSettingStore: () => ({
|
||||
get: vi.fn(() => false)
|
||||
})
|
||||
}))
|
||||
|
||||
function createWidgetState(overrideDisabled: boolean): DomWidgetState {
|
||||
const domWidgetStore = useDomWidgetStore()
|
||||
const node = createMockLGraphNode({
|
||||
id: 1,
|
||||
constructor: {
|
||||
nodeData: {}
|
||||
}
|
||||
})
|
||||
|
||||
const widget = {
|
||||
id: 'dom-widget-id',
|
||||
name: 'test_widget',
|
||||
type: 'custom',
|
||||
value: '',
|
||||
options: {},
|
||||
node,
|
||||
computedDisabled: false
|
||||
} as unknown as BaseDOMWidget<object | string>
|
||||
|
||||
domWidgetStore.registerWidget(widget)
|
||||
domWidgetStore.setPositionOverride(widget.id, {
|
||||
node: createMockLGraphNode({ id: 2 }),
|
||||
widget: { computedDisabled: overrideDisabled } as DomWidgetState['widget']
|
||||
})
|
||||
|
||||
const state = domWidgetStore.widgetStates.get(widget.id)
|
||||
if (!state) throw new Error('Expected registered DomWidgetState')
|
||||
|
||||
state.zIndex = 2
|
||||
state.size = [100, 40]
|
||||
|
||||
return reactive(state)
|
||||
}
|
||||
|
||||
describe('DomWidget disabled style', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
useDomWidgetStore().clear()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('uses disabled style when promoted override widget is computedDisabled', async () => {
|
||||
const widgetState = createWidgetState(true)
|
||||
const wrapper = mount(DomWidget, {
|
||||
props: {
|
||||
widgetState
|
||||
}
|
||||
})
|
||||
|
||||
widgetState.zIndex = 3
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const root = wrapper.get('.dom-widget').element as HTMLElement
|
||||
expect(root.style.pointerEvents).toBe('none')
|
||||
expect(root.style.opacity).toBe('0.5')
|
||||
})
|
||||
})
|
||||
@@ -110,13 +110,17 @@ watch(
|
||||
updateDomClipping()
|
||||
}
|
||||
|
||||
const override = widgetState.positionOverride
|
||||
const isDisabled = override
|
||||
? (override.widget.computedDisabled ?? widget.computedDisabled)
|
||||
: widget.computedDisabled
|
||||
|
||||
style.value = {
|
||||
...positionStyle.value,
|
||||
...(enableDomClipping.value ? clippingStyle.value : {}),
|
||||
zIndex: widgetState.zIndex,
|
||||
pointerEvents:
|
||||
widgetState.readonly || widget.computedDisabled ? 'none' : 'auto',
|
||||
opacity: widget.computedDisabled ? 0.5 : 1
|
||||
pointerEvents: widgetState.readonly || isDisabled ? 'none' : 'auto',
|
||||
opacity: isDisabled ? 0.5 : 1
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
|
||||
Reference in New Issue
Block a user