Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
150 changes: 149 additions & 1 deletion src/schemas/nodeDef/migration.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it } from 'vitest'
import { describe, expect, it, vi } from 'vitest'

import { transformNodeDefV1ToV2 } from '@/schemas/nodeDef/migration'
import type { ComfyNodeDef as ComfyNodeDefV1 } from '@/schemas/nodeDefSchema'
Expand Down Expand Up @@ -537,4 +537,152 @@ describe('ComfyNodeDefImpl', () => {
expect(result.api_node).toBe(expected)
}
)

describe('defaultInput migration', () => {
it('should not set forceInput on optional input with defaultInput', () => {
const nodeDef = new ComfyNodeDefImpl({
name: 'TestNode',
display_name: 'Test Node',
category: 'Test',
python_module: 'test_module',
description: 'A test node',
input: {
optional: {
seed_override: ['INT', { defaultInput: true, default: 0 }]
}
},
output: [],
output_is_list: [],
output_name: [],
output_node: false
} as ComfyNodeDefV1)

expect(nodeDef.inputs['seed_override']).toBeDefined()
expect(nodeDef.inputs['seed_override'].forceInput).toBeUndefined()
})

it('should preserve widget type on optional input with defaultInput', () => {
const nodeDef = new ComfyNodeDefImpl({
name: 'TestNode',
display_name: 'Test Node',
category: 'Test',
python_module: 'test_module',
description: 'A test node',
input: {
optional: {
my_param: [
'FLOAT',
{ defaultInput: true, default: 0.5, min: 0, max: 1 }
]
}
},
output: [],
output_is_list: [],
output_name: [],
output_node: false
} as ComfyNodeDefV1)

const inputSpec = nodeDef.inputs['my_param']
expect(inputSpec.type).toBe('FLOAT')
expect(inputSpec.isOptional).toBe(true)
expect(inputSpec.default).toBe(0.5)
expect(inputSpec.forceInput).toBeUndefined()
})

it('should not affect required inputs with defaultInput', () => {
const nodeDef = new ComfyNodeDefImpl({
name: 'TestNode',
display_name: 'Test Node',
category: 'Test',
python_module: 'test_module',
description: 'A test node',
input: {
required: {
value: ['INT', { defaultInput: true, default: 42 }]
}
},
output: [],
output_is_list: [],
output_name: [],
output_node: false
} as ComfyNodeDefV1)

expect(nodeDef.inputs['value']).toBeDefined()
expect(nodeDef.inputs['value'].forceInput).toBeUndefined()
})

it('should preserve explicit forceInput when set alongside defaultInput', () => {
const nodeDef = new ComfyNodeDefImpl({
name: 'TestNode',
display_name: 'Test Node',
category: 'Test',
python_module: 'test_module',
description: 'A test node',
input: {
optional: {
forced: ['INT', { forceInput: true, defaultInput: true }]
}
},
output: [],
output_is_list: [],
output_name: [],
output_node: false
} as ComfyNodeDefV1)

expect(nodeDef.inputs['forced'].forceInput).toBe(true)
})

it('should emit deprecation warning for optional input with defaultInput', () => {
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
try {
new ComfyNodeDefImpl({
name: 'TestNode',
display_name: 'Test Node',
category: 'Test',
python_module: 'test_module',
description: 'A test node',
input: {
optional: {
seed: ['INT', { defaultInput: true }]
}
},
output: [],
output_is_list: [],
output_name: [],
output_node: false
} as ComfyNodeDefV1)

expect(warnSpy).toHaveBeenCalledWith(
'Use of defaultInput on optional input test_module:TestNode:seed is deprecated and ignored. Remove defaultInput. Use forceInput only if you intentionally want a socket-only input.'
)
} finally {
warnSpy.mockRestore()
}
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.

it('should not mutate the original node definition', () => {
const originalDef = {
name: 'TestNode',
display_name: 'Test Node',
category: 'Test',
python_module: 'test_module',
description: 'A test node',
input: {
optional: {
param: ['INT', { defaultInput: true }]
}
},
output: [],
output_is_list: [],
output_name: [],
output_node: false
} as ComfyNodeDefV1

new ComfyNodeDefImpl(originalDef)

expect(
originalDef.input!.optional!['param'][1]!.forceInput
).toBeUndefined()
})
})
})
19 changes: 13 additions & 6 deletions src/stores/nodeDefStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,14 @@ export class ComfyNodeDefImpl

/**
* @internal
* Migrate default input options to forceInput.
* Migrate default input options. Since frontend version 1.16+, widget and
* input socket co-exist on every input, so `defaultInput` no longer needs
* special handling. We only emit deprecation warnings.
*
* Previously `defaultInput` on optional inputs was migrated to `forceInput`,
* which prevented the widget from being created at all and made it impossible
* for users to toggle back to widget mode after reload.
* See: https://github.com/Comfy-Org/ComfyUI_frontend/issues/1500
*/
private static _migrateDefaultInput(nodeDef: ComfyNodeDefV1): ComfyNodeDefV1 {
const def = _.cloneDeep(nodeDef)
Expand All @@ -118,16 +125,16 @@ export class ComfyNodeDefImpl
)
}
}
// For optional inputs, defaultInput is used to distinguish the null state.
// We migrate it to forceInput. One example is the "seed_override" input usage.
// User can connect the socket to override the seed.
// For optional inputs, defaultInput previously converted the widget into
// a socket-only input. Since 1.16+ widgets and sockets co-exist, we no
// longer need to set forceInput. The widget will be created alongside the
// socket, and users can choose how to provide the value.
for (const [name, spec] of Object.entries(def.input.optional ?? {})) {
const inputOptions = spec[1]
if (inputOptions && inputOptions.defaultInput) {
console.warn(
`Use of defaultInput on optional input ${nodeDef.python_module}:${nodeDef.name}:${name} is deprecated. Please use forceInput instead.`
`Use of defaultInput on optional input ${nodeDef.python_module}:${nodeDef.name}:${name} is deprecated and ignored. Remove defaultInput. Use forceInput only if you intentionally want a socket-only input.`
)
inputOptions.forceInput = true
}
}
return def
Expand Down
Loading