Files
ComfyUI_frontend/tests-ui/tests/nodeDef.test.ts

541 lines
15 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { transformNodeDefV1ToV2 } from '@/schemas/nodeDef/migration'
import type { ComfyNodeDef as ComfyNodeDefV1 } from '@/schemas/nodeDefSchema'
import { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
describe('NodeDef Migration', () => {
it('should transform a plain object to V2 format', () => {
const plainObject = {
required: {
intInput: ['INT', { min: 0, max: 100, default: 50 }],
stringInput: ['STRING', { default: 'Hello', multiline: true }]
},
optional: {
booleanInput: [
'BOOLEAN',
{ default: true, labelOn: 'Yes', labelOff: 'No' }
],
floatInput: ['FLOAT', { min: 0, max: 1, step: 0.1 }]
},
hidden: {
someHiddenValue: 42
}
} as ComfyNodeDefV1['input']
const nodeDef: ComfyNodeDefV1 = {
name: 'TestNode',
display_name: 'Test Node',
category: 'Testing',
python_module: 'test_module',
description: 'A test node',
input: plainObject,
output: ['INT'],
output_is_list: [false],
output_name: ['intOutput'],
output_node: false
}
const result = transformNodeDefV1ToV2(nodeDef)
expect(result).toBeDefined()
expect(result.inputs).toBeDefined()
expect(result.outputs).toBeDefined()
expect(result.hidden).toBeDefined()
})
it('should correctly transform required input specs', () => {
const plainObject = {
required: {
intInput: ['INT', { min: 0, max: 100, default: 50 }],
stringInput: ['STRING', { default: 'Hello', multiline: true }]
}
} as ComfyNodeDefV1['input']
const nodeDef: ComfyNodeDefV1 = {
name: 'TestNode',
display_name: 'Test Node',
category: 'Testing',
python_module: 'test_module',
description: 'A test node',
input: plainObject,
output: [],
output_is_list: [],
output_name: [],
output_node: false
}
const result = transformNodeDefV1ToV2(nodeDef)
const intInput = result.inputs['intInput']
const stringInput = result.inputs['stringInput']
expect(intInput.min).toBe(0)
expect(intInput.max).toBe(100)
expect(intInput.default).toBe(50)
expect(intInput.name).toBe('intInput')
expect(intInput.type).toBe('INT')
expect(stringInput.default).toBe('Hello')
expect(stringInput.multiline).toBe(true)
expect(stringInput.name).toBe('stringInput')
expect(stringInput.type).toBe('STRING')
})
it('should correctly transform optional input specs', () => {
const plainObject = {
optional: {
booleanInput: [
'BOOLEAN',
{ default: true, labelOn: 'Yes', labelOff: 'No' }
],
floatInput: ['FLOAT', { min: 0, max: 1, step: 0.1 }]
}
} as ComfyNodeDefV1['input']
const nodeDef: ComfyNodeDefV1 = {
name: 'TestNode',
display_name: 'Test Node',
category: 'Testing',
python_module: 'test_module',
description: 'A test node',
input: plainObject,
output: [],
output_is_list: [],
output_name: [],
output_node: false
}
const result = transformNodeDefV1ToV2(nodeDef)
const booleanInput = result.inputs['booleanInput']
const floatInput = result.inputs['floatInput']
expect(booleanInput.default).toBe(true)
expect(booleanInput.labelOn).toBe('Yes')
expect(booleanInput.labelOff).toBe('No')
expect(booleanInput.type).toBe('BOOLEAN')
expect(booleanInput.isOptional).toBe(true)
expect(floatInput.min).toBe(0)
expect(floatInput.max).toBe(1)
expect(floatInput.step).toBe(0.1)
expect(floatInput.type).toBe('FLOAT')
expect(floatInput.isOptional).toBe(true)
})
it('should handle combo input specs', () => {
const plainObject = {
optional: {
comboInput: [[1, 2, 3], { default: 2 }]
}
} as ComfyNodeDefV1['input']
const nodeDef: ComfyNodeDefV1 = {
name: 'TestNode',
display_name: 'Test Node',
category: 'Testing',
python_module: 'test_module',
description: 'A test node',
input: plainObject,
output: [],
output_is_list: [],
output_name: [],
output_node: false
}
const result = transformNodeDefV1ToV2(nodeDef)
expect(result.inputs['comboInput'].type).toBe('COMBO')
expect(result.inputs['comboInput'].default).toBe(2)
expect(result.inputs['comboInput'].options).toEqual([1, 2, 3])
})
it('should handle combo input specs (auto-default)', () => {
const plainObject = {
optional: {
comboInput: [[1, 2, 3], {}]
}
} as ComfyNodeDefV1['input']
const nodeDef: ComfyNodeDefV1 = {
name: 'TestNode',
display_name: 'Test Node',
category: 'Testing',
python_module: 'test_module',
description: 'A test node',
input: plainObject,
output: [],
output_is_list: [],
output_name: [],
output_node: false
}
const result = transformNodeDefV1ToV2(nodeDef)
expect(result.inputs['comboInput'].type).toBe('COMBO')
// Should pick the first choice as default
expect(result.inputs['comboInput'].options).toEqual([1, 2, 3])
})
it('should handle custom input specs', () => {
const plainObject = {
optional: {
customInput: ['CUSTOM_TYPE', { default: 'custom value' }]
}
} as ComfyNodeDefV1['input']
const nodeDef: ComfyNodeDefV1 = {
name: 'TestNode',
display_name: 'Test Node',
category: 'Testing',
python_module: 'test_module',
description: 'A test node',
input: plainObject,
output: [],
output_is_list: [],
output_name: [],
output_node: false
}
const result = transformNodeDefV1ToV2(nodeDef)
expect(result.inputs['customInput'].type).toBe('CUSTOM_TYPE')
expect(result.inputs['customInput'].default).toBe('custom value')
})
it('should not transform hidden fields', () => {
const plainObject = {
hidden: {
someHiddenValue: 42,
anotherHiddenValue: { nested: 'object' }
}
} as ComfyNodeDefV1['input']
const nodeDef: ComfyNodeDefV1 = {
name: 'TestNode',
display_name: 'Test Node',
category: 'Testing',
python_module: 'test_module',
description: 'A test node',
input: plainObject,
output: [],
output_is_list: [],
output_name: [],
output_node: false
}
const result = transformNodeDefV1ToV2(nodeDef)
// @ts-expect-error fixme ts strict error
expect(result.hidden).toEqual(plainObject.hidden)
expect(result.hidden?.someHiddenValue).toBe(42)
expect(result.hidden?.anotherHiddenValue).toEqual({ nested: 'object' })
})
it('should handle empty or undefined fields', () => {
const plainObject = {}
const nodeDef: ComfyNodeDefV1 = {
name: 'TestNode',
display_name: 'Test Node',
category: 'Testing',
python_module: 'test_module',
description: 'A test node',
input: plainObject,
output: [],
output_is_list: [],
output_name: [],
output_node: false
}
const result = transformNodeDefV1ToV2(nodeDef)
expect(result).toBeDefined()
expect(result.inputs).toEqual({})
expect(result.hidden).toBeUndefined()
})
})
describe('ComfyNodeDefImpl', () => {
it('should transform a basic node definition correctly', () => {
const plainObject = {
name: 'TestNode',
display_name: 'Test Node',
category: 'Testing',
python_module: 'test_module',
description: 'A test node',
input: {
required: {
intInput: ['INT', { min: 0, max: 100, default: 50 }]
}
},
output: ['INT'],
output_is_list: [false],
output_name: ['intOutput'],
output_node: false
} as ComfyNodeDefV1
const result = new ComfyNodeDefImpl(plainObject)
expect(result).toBeInstanceOf(ComfyNodeDefImpl)
expect(result.name).toBe('TestNode')
expect(result.display_name).toBe('Test Node')
expect(result.category).toBe('Testing')
expect(result.python_module).toBe('test_module')
expect(result.description).toBe('A test node')
expect(result.inputs).toBeDefined()
expect(result.outputs).toEqual([
{
index: 0,
name: 'intOutput',
type: 'INT',
is_list: false
}
])
expect(result.deprecated).toBe(false)
})
it('should transform a deprecated basic node definition', () => {
const plainObject = {
name: 'TestNode',
display_name: 'Test Node',
category: 'Testing',
python_module: 'test_module',
description: 'A test node',
input: {
required: {
intInput: ['INT', { min: 0, max: 100, default: 50 }]
}
},
output: ['INT'],
output_is_list: [false],
output_name: ['intOutput'],
deprecated: true,
output_node: false
} as ComfyNodeDefV1
const result = new ComfyNodeDefImpl(plainObject)
expect(result.deprecated).toBe(true)
})
// Legacy way of marking a node as deprecated
it('should mark deprecated with empty category', () => {
const plainObject = {
name: 'TestNode',
display_name: 'Test Node',
// Empty category should be treated as deprecated
category: '',
python_module: 'test_module',
description: 'A test node',
input: {
required: {
intInput: ['INT', { min: 0, max: 100, default: 50 }]
}
},
output: ['INT'],
output_is_list: [false],
output_name: ['intOutput'],
output_node: false
} as ComfyNodeDefV1
const result = new ComfyNodeDefImpl(plainObject)
expect(result.deprecated).toBe(true)
})
it('should handle multiple outputs including COMBO type', () => {
const plainObject = {
name: 'MultiOutputNode',
display_name: 'Multi Output Node',
category: 'Advanced',
python_module: 'advanced_module',
description: 'A node with multiple outputs',
input: {},
output: ['STRING', ['COMBO', 'option1', 'option2'], 'FLOAT'],
output_is_list: [true, false, false],
output_name: ['stringOutput', 'comboOutput', 'floatOutput'],
output_node: false
} as ComfyNodeDefV1
const result = new ComfyNodeDefImpl(plainObject)
expect(result.outputs).toEqual([
{
index: 0,
name: 'stringOutput',
type: 'STRING',
is_list: true
},
{
index: 1,
name: 'comboOutput',
type: 'COMBO',
is_list: false,
options: ['COMBO', 'option1', 'option2']
},
{
index: 2,
name: 'floatOutput',
type: 'FLOAT',
is_list: false
}
])
})
it('should use index for output names if matches type', () => {
const plainObject = {
name: 'MissingNamesNode',
display_name: 'Missing Names Node',
category: 'Test',
python_module: 'test_module',
description: 'A node with missing output names',
input: {},
output: ['INT', 'FLOAT', 'FLOAT'],
output_is_list: [false, true, true],
output_name: ['INT', 'FLOAT', 'FLOAT'],
output_node: false
} as ComfyNodeDefV1
const result = new ComfyNodeDefImpl(plainObject)
expect(result.outputs).toEqual([
{
index: 0,
name: 'INT',
type: 'INT',
is_list: false
},
{
index: 1,
name: 'FLOAT',
type: 'FLOAT',
is_list: true
},
{
index: 2,
name: 'FLOAT',
type: 'FLOAT',
is_list: true
}
])
})
it('should handle duplicate output names', () => {
const plainObject = {
name: 'DuplicateOutputNode',
display_name: 'Duplicate Output Node',
category: 'Test',
python_module: 'test_module',
description: 'A node with duplicate output names',
input: {},
output: ['INT', 'FLOAT', 'STRING'],
output_is_list: [false, false, false],
output_name: ['output', 'output', 'uniqueOutput'],
output_node: false
} as ComfyNodeDefV1
const result = new ComfyNodeDefImpl(plainObject)
expect(result.outputs).toEqual([
{
index: 0,
name: 'output',
type: 'INT',
is_list: false
},
{
index: 1,
name: 'output',
type: 'FLOAT',
is_list: false
},
{
index: 2,
name: 'uniqueOutput',
type: 'STRING',
is_list: false
}
])
})
it('should handle empty output', () => {
const plainObject = {
name: 'EmptyOutputNode',
display_name: 'Empty Output Node',
category: 'Test',
python_module: 'test_module',
description: 'A node with no outputs',
input: {},
output: [],
output_is_list: [],
output_name: [],
output_node: false
} as ComfyNodeDefV1
const result = new ComfyNodeDefImpl(plainObject)
expect(result.outputs).toEqual([])
})
it('should handle undefined fields', () => {
const plainObject = {
name: 'EmptyOutputNode',
display_name: 'Empty Output Node',
category: 'Test',
python_module: 'test_module',
description: 'A node with no outputs',
output_node: false
} as ComfyNodeDefV1
const result = new ComfyNodeDefImpl(plainObject)
expect(result.outputs).toEqual([])
expect(Object.keys(result.inputs)).toHaveLength(0)
})
it('should handle complex input specifications', () => {
const plainObject = {
name: 'ComplexInputNode',
display_name: 'Complex Input Node',
category: 'Advanced',
python_module: 'advanced_module',
description: 'A node with complex input',
input: {
required: {
intInput: ['INT', { min: 0, max: 100, default: 50 }],
stringInput: ['STRING', { multiline: true }]
},
optional: {
booleanInput: ['BOOLEAN', { default: true }],
floatInput: ['FLOAT', { step: 0.1 }]
}
},
output: ['INT'],
output_is_list: [false],
output_name: ['result'],
output_node: false
} as ComfyNodeDefV1
const result = new ComfyNodeDefImpl(plainObject)
expect(result.inputs).toBeDefined()
expect(Object.keys(result.inputs)).toHaveLength(4)
expect(result.inputs['intInput']).toBeDefined()
expect(result.inputs['stringInput']).toBeDefined()
expect(result.inputs['booleanInput']).toBeDefined()
expect(result.inputs['floatInput']).toBeDefined()
})
it.each([
{ api_node: true, expected: true },
{ api_node: false, expected: false },
{ api_node: undefined, expected: false }
] as { api_node: boolean | undefined; expected: boolean }[])(
'should handle api_node field: $api_node',
({ api_node, expected }) => {
const result = new ComfyNodeDefImpl({
name: 'ApiNode',
display_name: 'API Node',
category: 'Test',
python_module: 'test_module',
description: 'A node with API',
api_node
} as ComfyNodeDefV1)
expect(result.api_node).toBe(expected)
}
)
})