mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-19 22:09:37 +00:00
*PR Created by the Glary-Bot Agent* --- ## Summary - Replace all `as unknown as Type` assertions in 59 unit test files with type-safe `@total-typescript/shoehorn` functions - Use `fromPartial<Type>()` for partial mock objects where deep-partial type-checks (21 files) - Use `fromAny<Type>()` for fundamentally incompatible types: null, undefined, primitives, variables, class expressions, and mocks with test-specific extra properties that `PartialDeepObject` rejects (remaining files) - All explicit type parameters preserved so TypeScript return types are correct - Browser test `.spec.ts` files excluded (shoehorn unavailable in `page.evaluate` browser context) ## Verification - `pnpm typecheck` ✅ - `pnpm lint` ✅ - `pnpm format` ✅ - Pre-commit hooks passed (format + oxlint + eslint + typecheck) - Migrated test files verified passing (ran representative subset) - No test behavior changes — only type assertion syntax changed - No UI changes — screenshots not applicable ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10761-test-migrate-as-unknown-as-to-total-typescript-shoehorn-3336d73d365081f6b8adc44db5dcc380) by [Unito](https://www.unito.io) --------- Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> Co-authored-by: Amp <amp@ampcode.com>
195 lines
5.9 KiB
TypeScript
195 lines
5.9 KiB
TypeScript
import { createTestingPinia } from '@pinia/testing'
|
|
import { fromPartial } from '@total-typescript/shoehorn'
|
|
import { setActivePinia } from 'pinia'
|
|
import { beforeEach, describe, expect, test, vi } from 'vitest'
|
|
|
|
import { resolveSubgraphInputLink } from '@/core/graph/subgraph/resolveSubgraphInputLink'
|
|
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
|
import {
|
|
createTestSubgraph,
|
|
createTestSubgraphNode,
|
|
resetSubgraphFixtureState
|
|
} from '@/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers'
|
|
import type { Subgraph } from '@/lib/litegraph/src/subgraph/Subgraph'
|
|
import type { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
|
|
|
|
vi.mock('@/renderer/core/canvas/canvasStore', () => ({
|
|
useCanvasStore: () => ({})
|
|
}))
|
|
vi.mock('@/stores/domWidgetStore', () => ({
|
|
useDomWidgetStore: () => ({ widgetStates: new Map() })
|
|
}))
|
|
vi.mock('@/services/litegraphService', () => ({
|
|
useLitegraphService: () => ({ updatePreviews: () => ({}) })
|
|
}))
|
|
|
|
function createSubgraphSetup(inputName: string): {
|
|
subgraph: Subgraph
|
|
subgraphNode: SubgraphNode
|
|
} {
|
|
const subgraph = createTestSubgraph({
|
|
inputs: [{ name: inputName, type: '*' }]
|
|
})
|
|
const subgraphNode = createTestSubgraphNode(subgraph, { id: 1 })
|
|
return { subgraph, subgraphNode }
|
|
}
|
|
|
|
function addLinkedInteriorInput(
|
|
subgraph: Subgraph,
|
|
inputName: string,
|
|
linkedInputName: string,
|
|
widgetName: string
|
|
): {
|
|
node: LGraphNode
|
|
linkId: number
|
|
} {
|
|
const inputSlot = subgraph.inputNode.slots.find(
|
|
(slot) => slot.name === inputName
|
|
)
|
|
if (!inputSlot) throw new Error(`Missing subgraph input slot: ${inputName}`)
|
|
|
|
const node = new LGraphNode(`Interior-${linkedInputName}`)
|
|
const input = node.addInput(linkedInputName, '*')
|
|
node.addWidget('text', widgetName, '', () => undefined)
|
|
input.widget = { name: widgetName }
|
|
subgraph.add(node)
|
|
inputSlot.connect(input, node)
|
|
|
|
if (input.link == null)
|
|
throw new Error(`Expected link to be created for input ${linkedInputName}`)
|
|
|
|
return { node, linkId: input.link }
|
|
}
|
|
|
|
beforeEach(() => {
|
|
setActivePinia(createTestingPinia({ stubActions: false }))
|
|
resetSubgraphFixtureState()
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
describe('resolveSubgraphInputLink', () => {
|
|
test('returns undefined for non-subgraph nodes', () => {
|
|
const node = new LGraphNode('plain-node')
|
|
|
|
const result = resolveSubgraphInputLink(node, 'missing', () => 'resolved')
|
|
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
test('returns undefined when input slot is missing', () => {
|
|
const { subgraphNode } = createSubgraphSetup('existing')
|
|
|
|
const result = resolveSubgraphInputLink(
|
|
subgraphNode,
|
|
'missing',
|
|
() => 'resolved'
|
|
)
|
|
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
test('skips stale links where inputNode.inputs is unavailable', () => {
|
|
const { subgraph, subgraphNode } = createSubgraphSetup('prompt')
|
|
addLinkedInteriorInput(subgraph, 'prompt', 'seed_input', 'seed')
|
|
const stale = addLinkedInteriorInput(
|
|
subgraph,
|
|
'prompt',
|
|
'stale_input',
|
|
'stale'
|
|
)
|
|
|
|
const originalGetLink = subgraph.getLink.bind(subgraph)
|
|
vi.spyOn(subgraph, 'getLink').mockImplementation((linkId) => {
|
|
if (typeof linkId !== 'number') return originalGetLink(linkId)
|
|
if (linkId === stale.linkId) {
|
|
return fromPartial<ReturnType<typeof subgraph.getLink>>({
|
|
resolve: () => ({
|
|
inputNode: {
|
|
inputs: undefined,
|
|
getWidgetFromSlot: () => ({ name: 'ignored' })
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
return originalGetLink(linkId)
|
|
})
|
|
|
|
const result = resolveSubgraphInputLink(
|
|
subgraphNode,
|
|
'prompt',
|
|
({ targetInput }) => targetInput.name
|
|
)
|
|
|
|
expect(result).toBe('seed_input')
|
|
})
|
|
|
|
test('resolves the first connected link when multiple links exist', () => {
|
|
const { subgraph, subgraphNode } = createSubgraphSetup('prompt')
|
|
addLinkedInteriorInput(subgraph, 'prompt', 'first_input', 'firstWidget')
|
|
addLinkedInteriorInput(subgraph, 'prompt', 'second_input', 'secondWidget')
|
|
|
|
const result = resolveSubgraphInputLink(
|
|
subgraphNode,
|
|
'prompt',
|
|
({ targetInput }) => targetInput.name
|
|
)
|
|
|
|
// First connected wins — consistent with SubgraphNode._resolveLinkedPromotionBySubgraphInput
|
|
expect(result).toBe('first_input')
|
|
})
|
|
|
|
test('caches getTargetWidget result within the same callback evaluation', () => {
|
|
const { subgraph, subgraphNode } = createSubgraphSetup('model')
|
|
const linked = addLinkedInteriorInput(
|
|
subgraph,
|
|
'model',
|
|
'model_input',
|
|
'modelWidget'
|
|
)
|
|
const getWidgetFromSlot = vi.spyOn(linked.node, 'getWidgetFromSlot')
|
|
|
|
const result = resolveSubgraphInputLink(
|
|
subgraphNode,
|
|
'model',
|
|
({ getTargetWidget }) => {
|
|
expect(getTargetWidget()?.name).toBe('modelWidget')
|
|
expect(getTargetWidget()?.name).toBe('modelWidget')
|
|
return 'ok'
|
|
}
|
|
)
|
|
|
|
expect(result).toBe('ok')
|
|
expect(getWidgetFromSlot).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
test('returns first link result with 3+ links connected', () => {
|
|
const { subgraph, subgraphNode } = createSubgraphSetup('prompt')
|
|
addLinkedInteriorInput(subgraph, 'prompt', 'first_input', 'firstWidget')
|
|
addLinkedInteriorInput(subgraph, 'prompt', 'second_input', 'secondWidget')
|
|
addLinkedInteriorInput(subgraph, 'prompt', 'third_input', 'thirdWidget')
|
|
|
|
const result = resolveSubgraphInputLink(
|
|
subgraphNode,
|
|
'prompt',
|
|
({ targetInput }) => targetInput.name
|
|
)
|
|
|
|
expect(result).toBe('first_input')
|
|
})
|
|
|
|
test('returns undefined when all links fail to resolve', () => {
|
|
const { subgraph, subgraphNode } = createSubgraphSetup('prompt')
|
|
addLinkedInteriorInput(subgraph, 'prompt', 'first_input', 'firstWidget')
|
|
addLinkedInteriorInput(subgraph, 'prompt', 'second_input', 'secondWidget')
|
|
|
|
const result = resolveSubgraphInputLink(
|
|
subgraphNode,
|
|
'prompt',
|
|
() => undefined
|
|
)
|
|
|
|
expect(result).toBeUndefined()
|
|
})
|
|
})
|