fix: address review feedback for refreshComboInNode

- Distinguish 'no result' (undefined) from 'empty options' ([]) so a
  legitimate empty combo list propagates to the widget instead of being
  treated as a no-op.
- Fall back to input.name when the connected target's input has no
  widget locator, mirroring _onFirstConnection.
- Add regression tests for V2 combo specs, empty option propagation,
  and the widgetless-input fallback path.
This commit is contained in:
Glary-Bot
2026-05-02 02:10:37 +00:00
committed by bymyself
parent f5b3fc8d10
commit ae5aacb8f0
2 changed files with 62 additions and 9 deletions

View File

@@ -122,6 +122,18 @@ function defsWithCombo(
}
}
function defsWithV2Combo(
values: (string | number)[]
): Record<string, ComfyNodeDef> {
return {
[TARGET_NODE_TYPE]: fromPartial<ComfyNodeDef>({
input: {
required: { [TARGET_INPUT_NAME]: ['COMBO', { options: values }] }
}
})
}
}
describe('PrimitiveNode.refreshComboInNode', () => {
it('updates combo options from the freshly passed defs', () => {
const widget = createComboWidget('euler', ORIGINAL_OPTIONS)
@@ -196,6 +208,49 @@ describe('PrimitiveNode.refreshComboInNode', () => {
expect(widget.callback).not.toHaveBeenCalled()
})
it('updates combo options from V2 combo specs in defs', () => {
const widget = createComboWidget('euler', ORIGINAL_OPTIONS)
const stub = createPrimitiveStub({
widget,
slotWidget: createSlotWidget([ORIGINAL_OPTIONS, {}])
})
refreshOnStub(stub, defsWithV2Combo(FRESH_OPTIONS))
expect(widget.options.values).toEqual(FRESH_OPTIONS)
})
it('propagates an empty option list when defs return one', () => {
const widget = createComboWidget('euler', ORIGINAL_OPTIONS)
const stub = createPrimitiveStub({
widget,
slotWidget: createSlotWidget([ORIGINAL_OPTIONS, {}])
})
refreshOnStub(stub, defsWithCombo([]))
expect(widget.options.values).toEqual([])
expect(widget.value).toBeUndefined()
})
it('falls back to input.name when the target input has no widget locator', () => {
const widget = createComboWidget('euler', [])
const targetNode = fromPartial<TargetNode>({
id: 9,
type: TARGET_NODE_TYPE,
inputs: [
fromPartial<INodeInputSlot>({
name: TARGET_INPUT_NAME
})
]
})
const stub = createPrimitiveStub({ widget, targetNode })
refreshOnStub(stub, defsWithCombo(FRESH_OPTIONS))
expect(widget.options.values).toEqual(FRESH_OPTIONS)
})
it('skips non-combo widgets', () => {
const widget = fromPartial<IBaseWidget>({
type: 'string',

View File

@@ -64,7 +64,7 @@ export class PrimitiveNode extends LGraphNode {
if (widget?.type !== 'combo') return
const newValues = this._resolveComboValues(defs)
if (!newValues?.length) return
if (newValues === undefined) return
widget.options.values = newValues
@@ -78,17 +78,15 @@ export class PrimitiveNode extends LGraphNode {
defs?: Record<string, ComfyNodeDef>
): (string | number)[] | undefined {
const fromDefs = defs ? this._comboValuesFromDefs(defs) : undefined
if (fromDefs?.length) return fromDefs
if (fromDefs !== undefined) return fromDefs
const slotWidget = this.outputs?.[0]?.widget
const config = (
slotWidget?.[GET_CONFIG] as (() => InputSpec) | undefined
)?.()
if (!config) return undefined
if (!config || !isComboInputSpec(config)) return undefined
return isComboInputSpec(config)
? getComboSpecComboOptions(config)
: undefined
return getComboSpecComboOptions(config)
}
private _comboValuesFromDefs(
@@ -102,12 +100,12 @@ export class PrimitiveNode extends LGraphNode {
if (!targetType) return undefined
const targetInput = targetNode?.inputs?.[link.target_slot]
const widgetName = targetInput?.widget?.name
if (!widgetName) return undefined
const inputName = targetInput?.widget?.name ?? targetInput?.name
if (!inputName) return undefined
const def = defs[targetType]
const inputSpec =
def?.input?.required?.[widgetName] ?? def?.input?.optional?.[widgetName]
def?.input?.required?.[inputName] ?? def?.input?.optional?.[inputName]
if (!inputSpec || !isComboInputSpec(inputSpec)) return undefined
return getComboSpecComboOptions(inputSpec)