mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 14:30:41 +00:00
fix: PromotedWidgetSlot label setter, callback property, sourceNodeId passthrough
- Add label setter to PromotedWidgetSlot so renameWidget() doesn't throw - Move callback from class method to constructor property for safe reassignment - Pass sourceNodeId from PromotedWidgetSlot to getSharedWidgetEnhancements - Add tests for all three fixes Amp-Thread-ID: https://ampcode.com/threads/T-019c5105-89d8-717d-a12e-fcecfe27f947 Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -3,8 +3,12 @@ import { createTestingPinia } from '@pinia/testing'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { computed, nextTick, watch } from 'vue'
|
||||
|
||||
import { useGraphNodeManager } from '@/composables/graph/useGraphNodeManager'
|
||||
import {
|
||||
getSharedWidgetEnhancements,
|
||||
useGraphNodeManager
|
||||
} from '@/composables/graph/useGraphNodeManager'
|
||||
import { BaseWidget, LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
import { NodeSlotType } from '@/lib/litegraph/src/types/globalEnums'
|
||||
import { useWidgetValueStore } from '@/stores/widgetValueStore'
|
||||
|
||||
@@ -74,3 +78,38 @@ describe('Node Reactivity', () => {
|
||||
expect(widgetValue.value).toBe(99)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getSharedWidgetEnhancements', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
})
|
||||
|
||||
it('returns nodeType when sourceNodeId is provided for a subgraph node', () => {
|
||||
const subgraph = new LGraph()
|
||||
const interiorNode = new LGraphNode('KSampler', 'KSampler')
|
||||
subgraph.add(interiorNode)
|
||||
|
||||
const subgraphNode = new LGraphNode('graph/subgraph')
|
||||
;(subgraphNode as unknown as { subgraph: LGraph }).subgraph = subgraph
|
||||
vi.spyOn(subgraphNode, 'isSubgraphNode').mockReturnValue(true)
|
||||
|
||||
const widget = { name: 'seed', type: 'number', value: 0 } as IBaseWidget
|
||||
const result = getSharedWidgetEnhancements(
|
||||
subgraphNode,
|
||||
widget,
|
||||
String(interiorNode.id)
|
||||
)
|
||||
|
||||
expect(result.nodeType).toBe('KSampler')
|
||||
})
|
||||
|
||||
it('returns undefined nodeType when sourceNodeId is omitted', () => {
|
||||
const subgraphNode = new LGraphNode('graph/subgraph')
|
||||
vi.spyOn(subgraphNode, 'isSubgraphNode').mockReturnValue(true)
|
||||
|
||||
const widget = { name: 'seed', type: 'number', value: 0 } as IBaseWidget
|
||||
const result = getSharedWidgetEnhancements(subgraphNode, widget)
|
||||
|
||||
expect(result.nodeType).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -197,7 +197,13 @@ function safeWidgetMapper(
|
||||
return function (widget) {
|
||||
try {
|
||||
// Get shared enhancements (controlWidget, spec, nodeType)
|
||||
const sharedEnhancements = getSharedWidgetEnhancements(node, widget)
|
||||
const sourceNodeId =
|
||||
'sourceNodeId' in widget ? (widget.sourceNodeId as string) : undefined
|
||||
const sharedEnhancements = getSharedWidgetEnhancements(
|
||||
node,
|
||||
widget,
|
||||
sourceNodeId
|
||||
)
|
||||
const slotInfo = slotMetadata.get(widget.name)
|
||||
|
||||
// Wrapper callback specific to Nodes 2.0 rendering
|
||||
|
||||
@@ -185,6 +185,44 @@ describe('PromotedWidgetSlot', () => {
|
||||
const slot = new PromotedWidgetSlot(subNode, '5', 'seed')
|
||||
expect(slot.label).toBe('5: seed')
|
||||
})
|
||||
|
||||
it('writes label to interior widget', () => {
|
||||
const interiorWidget = createMockWidget()
|
||||
const interiorNode = {
|
||||
id: '5',
|
||||
widgets: [interiorWidget]
|
||||
} as unknown as LGraphNode
|
||||
|
||||
const subNode = createMockSubgraphNode({ '5': interiorNode })
|
||||
const slot = new PromotedWidgetSlot(subNode, '5', 'seed')
|
||||
slot.label = 'Renamed'
|
||||
|
||||
expect(interiorWidget.label).toBe('Renamed')
|
||||
})
|
||||
|
||||
it('clears label on interior widget when set to undefined', () => {
|
||||
const interiorWidget = createMockWidget()
|
||||
interiorWidget.label = 'Old Label'
|
||||
const interiorNode = {
|
||||
id: '5',
|
||||
widgets: [interiorWidget]
|
||||
} as unknown as LGraphNode
|
||||
|
||||
const subNode = createMockSubgraphNode({ '5': interiorNode })
|
||||
const slot = new PromotedWidgetSlot(subNode, '5', 'seed')
|
||||
slot.label = undefined
|
||||
|
||||
expect(interiorWidget.label).toBeUndefined()
|
||||
})
|
||||
|
||||
it('does not throw when setting label while disconnected', () => {
|
||||
const subNode = createMockSubgraphNode()
|
||||
const slot = new PromotedWidgetSlot(subNode, '5', 'seed')
|
||||
|
||||
expect(() => {
|
||||
slot.label = 'Renamed'
|
||||
}).not.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
describe('options', () => {
|
||||
@@ -260,6 +298,47 @@ describe('PromotedWidgetSlot', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('callback', () => {
|
||||
it('delegates to interior widget callback', () => {
|
||||
const interiorCallback = vi.fn()
|
||||
const interiorWidget = createMockWidget({ callback: interiorCallback })
|
||||
const interiorNode = {
|
||||
id: '5',
|
||||
widgets: [interiorWidget]
|
||||
} as unknown as LGraphNode
|
||||
|
||||
const subNode = createMockSubgraphNode({ '5': interiorNode })
|
||||
const slot = new PromotedWidgetSlot(subNode, '5', 'seed')
|
||||
slot.callback?.(42)
|
||||
|
||||
expect(interiorCallback).toHaveBeenCalledWith(
|
||||
42,
|
||||
undefined,
|
||||
interiorNode,
|
||||
undefined,
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
it('does not throw when disconnected', () => {
|
||||
const subNode = createMockSubgraphNode()
|
||||
const slot = new PromotedWidgetSlot(subNode, '5', 'seed')
|
||||
|
||||
expect(() => slot.callback?.(42)).not.toThrow()
|
||||
})
|
||||
|
||||
it('can be reassigned as a property', () => {
|
||||
const subNode = createMockSubgraphNode()
|
||||
const slot = new PromotedWidgetSlot(subNode, '5', 'seed')
|
||||
const customCallback = vi.fn()
|
||||
|
||||
slot.callback = customCallback
|
||||
slot.callback?.(99)
|
||||
|
||||
expect(customCallback).toHaveBeenCalledWith(99)
|
||||
})
|
||||
})
|
||||
|
||||
describe('_displayValue', () => {
|
||||
it('returns string representation of value', () => {
|
||||
const store = useWidgetValueStore()
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import type { LGraphNode, NodeId } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { Point } from '@/lib/litegraph/src/interfaces'
|
||||
import type { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import type { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
|
||||
import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
import type {
|
||||
DrawWidgetOptions,
|
||||
@@ -63,6 +60,12 @@ export class PromotedWidgetSlot
|
||||
// our getters are used.
|
||||
delete (this as Record<string, unknown>).type
|
||||
delete (this as Record<string, unknown>).options
|
||||
|
||||
this.callback = (value, canvas, _node, pos, e) => {
|
||||
const resolved = this.resolve()
|
||||
if (!resolved) return
|
||||
resolved.widget.callback?.(value, canvas, resolved.node, pos, e)
|
||||
}
|
||||
}
|
||||
|
||||
private resolve(): {
|
||||
@@ -113,6 +116,13 @@ export class PromotedWidgetSlot
|
||||
return state?.label ?? this.name
|
||||
}
|
||||
|
||||
override set label(v: string | undefined) {
|
||||
const resolved = this.resolve()
|
||||
if (resolved) {
|
||||
resolved.widget.label = v
|
||||
}
|
||||
}
|
||||
|
||||
override get promoted(): boolean {
|
||||
return true
|
||||
}
|
||||
@@ -179,18 +189,6 @@ export class PromotedWidgetSlot
|
||||
const concrete = toConcreteWidget(resolved.widget, resolved.node, false)
|
||||
concrete?.onClick(options)
|
||||
}
|
||||
|
||||
override callback(
|
||||
value: WidgetValue,
|
||||
canvas?: LGraphCanvas,
|
||||
_node?: LGraphNode,
|
||||
pos?: Point,
|
||||
e?: CanvasPointerEvent
|
||||
): void {
|
||||
const resolved = this.resolve()
|
||||
if (!resolved) return
|
||||
resolved.widget.callback?.(value, canvas, resolved.node, pos, e)
|
||||
}
|
||||
}
|
||||
|
||||
// Install dynamic getters via defineProperty on the prototype.
|
||||
|
||||
Reference in New Issue
Block a user