mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
## Summary - Convert `fromAny` → `fromPartial` in 7 test files where object literals or interfaces are passed - `fromPartial` type-checks the provided fields, unlike `fromAny` which bypasses all checking (same as `as unknown as`) - Class-based types (`LGraphNode`, `LGraph`) remain `fromAny` due to shoehorn's `PartialDeep` incompatibility with class constructors ## Changes - **Pure conversions** (all `fromAny` → `fromPartial`): `domWidgetZIndex`, `matchPromotedInput`, `promotionUtils`, `subgraphNavigationStore` - **Mixed** (some converted, some kept): `promotedWidgetView`, `widgetUtil` - **Cleanup**: `nodeOutputStore` type param normalization Follows up on #10761. ## Test plan - [x] `pnpm typecheck` passes - [x] `pnpm vitest run` on all 7 changed files — 169 tests pass - [x] `pnpm lint` passes ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10788-test-migrate-fromAny-to-fromPartial-for-type-checked-test-mocks-3356d73d365081f7bf61d48a47af530c) by [Unito](https://www.unito.io)
161 lines
4.8 KiB
TypeScript
161 lines
4.8 KiB
TypeScript
import { fromAny, fromPartial } from '@total-typescript/shoehorn'
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
|
import type { INodeInputSlot } from '@/lib/litegraph/src/interfaces'
|
|
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
|
import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
|
import { getWidgetDefaultValue, renameWidget } from '@/utils/widgetUtil'
|
|
|
|
vi.mock('@/core/graph/subgraph/resolvePromotedWidgetSource', () => ({
|
|
resolvePromotedWidgetSource: vi.fn()
|
|
}))
|
|
|
|
import { resolvePromotedWidgetSource } from '@/core/graph/subgraph/resolvePromotedWidgetSource'
|
|
|
|
const mockedResolve = vi.mocked(resolvePromotedWidgetSource)
|
|
|
|
describe('getWidgetDefaultValue', () => {
|
|
it('returns undefined for undefined spec', () => {
|
|
expect(getWidgetDefaultValue(undefined)).toBeUndefined()
|
|
})
|
|
|
|
it('returns explicit default when provided', () => {
|
|
const spec = { type: 'INT', default: 42 } as InputSpec
|
|
expect(getWidgetDefaultValue(spec)).toBe(42)
|
|
})
|
|
|
|
it.for([
|
|
{ type: 'INT', expected: 0 },
|
|
{ type: 'FLOAT', expected: 0 },
|
|
{ type: 'BOOLEAN', expected: false },
|
|
{ type: 'STRING', expected: '' }
|
|
])(
|
|
'returns $expected for $type type without default',
|
|
({ type, expected }) => {
|
|
const spec = { type } as InputSpec
|
|
expect(getWidgetDefaultValue(spec)).toBe(expected)
|
|
}
|
|
)
|
|
|
|
it('returns first option for array options without default', () => {
|
|
const spec = { type: 'COMBO', options: ['a', 'b', 'c'] } as InputSpec
|
|
expect(getWidgetDefaultValue(spec)).toBe('a')
|
|
})
|
|
|
|
it('returns undefined for unknown type without options', () => {
|
|
const spec = { type: 'CUSTOM' } as InputSpec
|
|
expect(getWidgetDefaultValue(spec)).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
function makeWidget(overrides: Record<string, unknown> = {}): IBaseWidget {
|
|
return fromPartial<IBaseWidget>({
|
|
name: 'myWidget',
|
|
type: 'number',
|
|
value: 0,
|
|
label: undefined,
|
|
options: {},
|
|
...overrides
|
|
})
|
|
}
|
|
|
|
function makeNode({
|
|
isSubgraph = false,
|
|
inputs = [] as INodeInputSlot[]
|
|
}: {
|
|
isSubgraph?: boolean
|
|
inputs?: INodeInputSlot[]
|
|
} = {}): LGraphNode {
|
|
return fromAny<LGraphNode, unknown>({
|
|
id: 1,
|
|
inputs,
|
|
isSubgraphNode: () => isSubgraph
|
|
})
|
|
}
|
|
|
|
describe('renameWidget', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
it('renames a regular widget and its matching input', () => {
|
|
const widget = makeWidget({ name: 'seed' })
|
|
const input = { name: 'seed', widget: { name: 'seed' } } as INodeInputSlot
|
|
const node = makeNode({ inputs: [input] })
|
|
|
|
const result = renameWidget(widget, node, 'My Seed')
|
|
|
|
expect(result).toBe(true)
|
|
expect(widget.label).toBe('My Seed')
|
|
expect(input.label).toBe('My Seed')
|
|
})
|
|
|
|
it('clears label when given empty string', () => {
|
|
const widget = makeWidget({ name: 'seed', label: 'Old Label' })
|
|
const node = makeNode()
|
|
|
|
renameWidget(widget, node, '')
|
|
|
|
expect(widget.label).toBeUndefined()
|
|
})
|
|
|
|
it('renames promoted widget source when node is a subgraph without explicit parents', () => {
|
|
const sourceWidget = makeWidget({ name: 'innerSeed' })
|
|
const interiorInput = {
|
|
name: 'innerSeed',
|
|
widget: { name: 'innerSeed' }
|
|
} as INodeInputSlot
|
|
const interiorNode = makeNode({ inputs: [interiorInput] })
|
|
|
|
mockedResolve.mockReturnValue({
|
|
widget: sourceWidget,
|
|
node: interiorNode
|
|
})
|
|
|
|
const promotedWidget = makeWidget({
|
|
name: 'seed',
|
|
sourceNodeId: '5',
|
|
sourceWidgetName: 'innerSeed'
|
|
})
|
|
const subgraphNode = makeNode({ isSubgraph: true })
|
|
|
|
const result = renameWidget(promotedWidget, subgraphNode, 'Renamed')
|
|
|
|
expect(result).toBe(true)
|
|
expect(sourceWidget.label).toBe('Renamed')
|
|
expect(interiorInput.label).toBe('Renamed')
|
|
expect(promotedWidget.label).toBe('Renamed')
|
|
})
|
|
|
|
it('updates _subgraphSlot.label when input has a subgraph slot', () => {
|
|
const widget = makeWidget({ name: 'seed' })
|
|
const subgraphSlot = { label: undefined as string | undefined }
|
|
const input = fromAny<INodeInputSlot, unknown>({
|
|
name: 'seed',
|
|
widget: { name: 'seed' },
|
|
_subgraphSlot: subgraphSlot
|
|
})
|
|
const node = makeNode({ inputs: [input] })
|
|
|
|
renameWidget(widget, node, 'New Label')
|
|
|
|
expect(subgraphSlot.label).toBe('New Label')
|
|
})
|
|
|
|
it('does not resolve promoted widget source for non-subgraph node without parents', () => {
|
|
const promotedWidget = makeWidget({
|
|
name: 'seed',
|
|
sourceNodeId: '5',
|
|
sourceWidgetName: 'innerSeed'
|
|
})
|
|
const node = makeNode({ isSubgraph: false })
|
|
|
|
const result = renameWidget(promotedWidget, node, 'Renamed')
|
|
|
|
expect(result).toBe(true)
|
|
expect(mockedResolve).not.toHaveBeenCalled()
|
|
expect(promotedWidget.label).toBe('Renamed')
|
|
})
|
|
})
|