Files
ComfyUI_frontend/src/utils/nodeDefUtil.test.ts
Kelly Yang 983789753e refactor: remove @ts-expect-error suppressions in test files (#11337)
## Summary
Part of #11092 — Phase 3: remove @ts-expect-error suppressions from test
files.
This phase targets 22 suppressions across two test files:
- `src/utils/nodeDefUtil.test.ts` (18)
- `src/platform/workflow/validation/schemas/workflowSchema.test.ts` (4)

## Changes                                                        
`nodeDefUtil.test.ts`: Each test already constrains the inputs to a
known subtype (`IntInputSpec`, `FloatInputSpec`, `ComboInputSpecV2`), so
casting result to the expected subtype at the declaration site is both
correct and self-documenting. For the one test that uses the base
`InputSpec` type, the options object is extracted with an inline
structural cast.
`workflowSchema.test.ts`: validateComfyWorkflow returns
ComfyWorkflowJSON | null. The tests were accessing .nodes[0].pos without
narrowing, causing "object is possibly null" errors. Fixed with explicit
expect(validatedWorkflow).not.toBeNull() assertions before each property
access, which also improves failure messages — previously a null result
would throw a TypeError rather than a readable assertion failure.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Test-only type-safety refactor with no runtime code changes; primary
risk is minor test assertion behavior changes if a helper unexpectedly
returns `null`.
> 
> **Overview**
> Removes `@ts-expect-error` suppressions from two test suites by making
nullability and return-type expectations explicit.
> 
> `workflowSchema.test.ts` now asserts `validateComfyWorkflow` results
are non-null before accessing `nodes[0]` fields, and
`nodeDefUtil.test.ts` casts `mergeInputSpec` results to the expected
spec subtype (or extracts typed options) so property assertions compile
cleanly under stricter TS settings.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
9f3829862b. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11337-refactor-remove-ts-expect-error-suppressions-in-test-files-3456d73d3650815aa2a2fca5a9332377)
by [Unito](https://www.unito.io)

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-04-21 13:34:04 -04:00

213 lines
7.0 KiB
TypeScript

import { fromAny } from '@total-typescript/shoehorn'
import { describe, expect, it } from 'vitest'
import type {
ComboInputSpec,
ComboInputSpecV2,
FloatInputSpec,
InputSpec,
IntInputSpec
} from '@/schemas/nodeDefSchema'
import { mergeInputSpec } from '@/utils/nodeDefUtil'
describe('nodeDefUtil', () => {
describe('mergeInputSpec', () => {
// Test numeric input specs (INT and FLOAT)
describe('numeric input specs', () => {
it('should merge INT specs with overlapping ranges', () => {
const spec1: IntInputSpec = ['INT', { min: 0, max: 10 }]
const spec2: IntInputSpec = ['INT', { min: 5, max: 15 }]
const result = mergeInputSpec(spec1, spec2) as IntInputSpec | null
expect(result).not.toBeNull()
expect(result?.[0]).toBe('INT')
expect(result?.[1]?.min).toBe(5)
expect(result?.[1]?.max).toBe(10)
})
it('should return null for INT specs with non-overlapping ranges', () => {
const spec1: IntInputSpec = ['INT', { min: 0, max: 5 }]
const spec2: IntInputSpec = ['INT', { min: 10, max: 15 }]
const result = mergeInputSpec(spec1, spec2)
expect(result).toBeNull()
})
it('should merge FLOAT specs with overlapping ranges', () => {
const spec1: FloatInputSpec = ['FLOAT', { min: 0.5, max: 10.5 }]
const spec2: FloatInputSpec = ['FLOAT', { min: 5.5, max: 15.5 }]
const result = mergeInputSpec(spec1, spec2) as FloatInputSpec | null
expect(result).not.toBeNull()
expect(result?.[0]).toBe('FLOAT')
expect(result?.[1]?.min).toBe(5.5)
expect(result?.[1]?.max).toBe(10.5)
})
it('should handle specs with undefined min/max values', () => {
const spec1: FloatInputSpec = ['FLOAT', { min: 0.5 }]
const spec2: FloatInputSpec = ['FLOAT', { max: 15.5 }]
const result = mergeInputSpec(spec1, spec2) as FloatInputSpec | null
expect(result).not.toBeNull()
expect(result?.[0]).toBe('FLOAT')
expect(result?.[1]?.min).toBe(0.5)
expect(result?.[1]?.max).toBe(15.5)
})
it('should merge step values using least common multiple', () => {
const spec1: IntInputSpec = ['INT', { min: 0, max: 10, step: 2 }]
const spec2: IntInputSpec = ['INT', { min: 0, max: 10, step: 3 }]
const result = mergeInputSpec(spec1, spec2) as IntInputSpec | null
expect(result).not.toBeNull()
expect(result?.[0]).toBe('INT')
expect(result?.[1]?.step).toBe(6) // LCM of 2 and 3 is 6
})
it('should use default step of 1 when step is not specified', () => {
const spec1: IntInputSpec = ['INT', { min: 0, max: 10 }]
const spec2: IntInputSpec = ['INT', { min: 0, max: 10, step: 4 }]
const result = mergeInputSpec(spec1, spec2) as IntInputSpec | null
expect(result).not.toBeNull()
expect(result?.[0]).toBe('INT')
expect(result?.[1]?.step).toBe(4) // LCM of 1 and 4 is 4
})
it('should handle step values for FLOAT specs', () => {
const spec1: FloatInputSpec = ['FLOAT', { min: 0, max: 10, step: 0.5 }]
const spec2: FloatInputSpec = ['FLOAT', { min: 0, max: 10, step: 0.25 }]
const result = mergeInputSpec(spec1, spec2) as FloatInputSpec | null
expect(result).not.toBeNull()
expect(result?.[0]).toBe('FLOAT')
expect(result?.[1]?.step).toBe(0.5)
})
})
// Test combo input specs
describe('combo input specs', () => {
it('should merge COMBO specs with overlapping options', () => {
const spec1: ComboInputSpecV2 = ['COMBO', { options: ['A', 'B', 'C'] }]
const spec2: ComboInputSpecV2 = ['COMBO', { options: ['B', 'C', 'D'] }]
const result = mergeInputSpec(spec1, spec2) as ComboInputSpecV2 | null
expect(result).not.toBeNull()
expect(result?.[0]).toBe('COMBO')
expect(result?.[1]?.options).toEqual(['B', 'C'])
})
it('should return null for COMBO specs with no overlapping options', () => {
const spec1: ComboInputSpecV2 = ['COMBO', { options: ['A', 'B'] }]
const spec2: ComboInputSpecV2 = ['COMBO', { options: ['C', 'D'] }]
const result = mergeInputSpec(spec1, spec2)
expect(result).toBeNull()
})
it('should handle COMBO specs with additional properties', () => {
const spec1: ComboInputSpecV2 = [
'COMBO',
{
options: ['A', 'B', 'C'],
default: 'A',
tooltip: 'Select an option'
}
]
const spec2: ComboInputSpecV2 = [
'COMBO',
{
options: ['B', 'C', 'D'],
default: 'B',
multiline: true
}
]
const result = mergeInputSpec(spec1, spec2) as ComboInputSpecV2 | null
expect(result).not.toBeNull()
expect(result?.[0]).toBe('COMBO')
expect(result?.[1]?.options).toEqual(['B', 'C'])
expect(result?.[1]?.default).toBe('B')
expect(result?.[1]?.tooltip).toBe('Select an option')
expect(result?.[1]?.multiline).toBe(true)
})
it('should handle v1 and v2 combo specs', () => {
const spec1: ComboInputSpec = [['A', 'B', 'C', 'D'], {}]
const spec2: ComboInputSpecV2 = ['COMBO', { options: ['C', 'D'] }]
const result = mergeInputSpec(spec1, spec2) as ComboInputSpecV2 | null
expect(result).not.toBeNull()
expect(result?.[0]).toBe('COMBO')
expect(result?.[1]?.options).toEqual(['C', 'D'])
})
})
// Test common input spec behavior
describe('common input spec behavior', () => {
it('should return null for specs with different types', () => {
const spec1: IntInputSpec = ['INT', { min: 0, max: 10 }]
const spec2: ComboInputSpecV2 = ['COMBO', { options: ['A', 'B'] }]
const result = mergeInputSpec(
spec1,
fromAny<IntInputSpec, unknown>(spec2)
)
expect(result).toBeNull()
})
it('should ignore specified keys when comparing specs', () => {
const spec1: InputSpec = [
'STRING',
{
default: 'value1',
tooltip: 'Tooltip 1',
step: 1
}
]
const spec2: InputSpec = [
'STRING',
{
default: 'value2',
tooltip: 'Tooltip 2',
step: 1
}
]
const result = mergeInputSpec(spec1, spec2)
expect(result).not.toBeNull()
expect(result?.[0]).toBe('STRING')
const options = result?.[1] as
| { default?: string; tooltip?: string; step?: number }
| undefined
expect(options?.default).toBe('value2')
expect(options?.tooltip).toBe('Tooltip 2')
expect(options?.step).toBe(1)
})
it('should return null if non-ignored properties differ', () => {
const spec1: InputSpec = ['STRING', { step: 1 }]
const spec2: InputSpec = ['STRING', { step: 2 }]
const result = mergeInputSpec(spec1, spec2)
expect(result).toBeNull()
})
})
})
})