mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-22 13:32:11 +00:00
fix(v2-tests): resolve type errors in BC pattern tests
- Fix Size type usage (tuple [width, height] not object) - Fix entity ID branded types (strings not numbers) - Fix WidgetValueChangeEvent<unknown> generics throughout - Fix vi.fn() mock typing for argument counts - Fix EventListener cast through unknown - Fix NodeBeforeSerializeEvent.replace callable issue - Fix VirtualNode.isVirtualNode prototype assignment - Fix NodeExecutedEvent.output property access casts - Update handler types in mock interfaces Remaining 14 items are unused variable warnings (TS6133/TS6196), not blocking compilation. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -84,8 +84,8 @@ describe('BC.01 v1 contract — node lifecycle: creation', () => {
|
||||
const fakeNode = { id: 3, type: 'VAEDecode' }
|
||||
const callOrder: string[] = []
|
||||
|
||||
const extA = { nodeCreated: vi.fn(() => callOrder.push('A')) }
|
||||
const extB = { nodeCreated: vi.fn(() => callOrder.push('B')) }
|
||||
const extA = { nodeCreated: vi.fn((_node: unknown) => callOrder.push('A')) }
|
||||
const extB = { nodeCreated: vi.fn((_node: unknown) => callOrder.push('B')) }
|
||||
|
||||
// Simulate the app dispatching nodeCreated to all registered extensions
|
||||
for (const ext of [extA, extB]) {
|
||||
|
||||
@@ -131,7 +131,7 @@ describe('BC.02 migration — node lifecycle: teardown', () => {
|
||||
const v1Ticks = vi.fn()
|
||||
const v2Ticks = vi.fn()
|
||||
|
||||
let v2Handle: ReturnType<typeof setInterval> | undefined
|
||||
let v2Handle: number | undefined
|
||||
|
||||
// v1 pattern: manual tracking
|
||||
const v1Handle = setInterval(v1Ticks, 100)
|
||||
|
||||
@@ -96,7 +96,7 @@ describe('BC.02 v2 contract — node lifecycle: teardown', () => {
|
||||
|
||||
it('cleanup fires for every node when world.clear() triggers unmount of all nodes', () => {
|
||||
const world = createHarnessWorld()
|
||||
const cleanups: (() => void)[] = []
|
||||
const _cleanups: (() => void)[] = []
|
||||
|
||||
// Mount 3 nodes, collect their unmount handles
|
||||
const handles = [
|
||||
@@ -142,7 +142,7 @@ describe('BC.02 v2 contract — node lifecycle: teardown', () => {
|
||||
it('interval cleared in onScopeDispose does not fire after unmount', () => {
|
||||
vi.useFakeTimers()
|
||||
const intervalCallback = vi.fn()
|
||||
let handle: ReturnType<typeof setInterval> | undefined
|
||||
let handle: number | undefined
|
||||
|
||||
const { unmount } = mountNode(() => {
|
||||
handle = setInterval(intervalCallback, 100)
|
||||
|
||||
@@ -154,7 +154,7 @@ describe('BC.03 migration — node lifecycle: hydration from saved workflows', (
|
||||
|
||||
// Simulate fresh creation: runtime does NOT call onConfigure / loadedGraphNode.
|
||||
// (Only nodeCreated / onNodeCreated fire for fresh nodes.)
|
||||
const _freshNodeId = createHarnessWorld().addNode({ type: 'KSampler' })
|
||||
const ___freshNodeId = createHarnessWorld().addNode({ type: 'KSampler' })
|
||||
|
||||
// Neither function called — fresh creation path.
|
||||
expect(v1ConfigureFn).not.toHaveBeenCalled()
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// mouseDown + selected/deselected migration tests are Phase B (API not yet present).
|
||||
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { NodeSizeChangedEvent } from '@/extension-api/node'
|
||||
import type { NodeSizeChangedEvent, Size } from '@/extension-api/node'
|
||||
import type { Unsubscribe } from '@/extension-api/events'
|
||||
|
||||
// ── Shared mock ───────────────────────────────────────────────────────────────
|
||||
@@ -22,7 +22,7 @@ interface MockNode {
|
||||
event: 'sizeChanged',
|
||||
handler: (e: NodeSizeChangedEvent) => void
|
||||
): Unsubscribe
|
||||
_emitSizeChanged(size: { width: number; height: number }): void
|
||||
_emitSizeChanged(size: Size): void
|
||||
}
|
||||
|
||||
function createMockNode(): MockNode {
|
||||
@@ -38,7 +38,7 @@ function createMockNode(): MockNode {
|
||||
if (idx !== -1) listeners.splice(idx, 1)
|
||||
}
|
||||
},
|
||||
_emitSizeChanged(size) {
|
||||
_emitSizeChanged(size: Size) {
|
||||
const event: NodeSizeChangedEvent = { size }
|
||||
for (const fn of [...listeners]) fn(event)
|
||||
}
|
||||
@@ -51,22 +51,22 @@ describe('BC.04 migration — node interaction: pointer, selection, resize', ()
|
||||
describe('resize parity: v1 onResize([w,h]) ↔ v2 on("sizeChanged", { size }) (S2.N19)', () => {
|
||||
it('v2 sizeChanged handler receives same dimensions that v1 onResize received', () => {
|
||||
const node = createMockNode()
|
||||
const v2Sizes: { width: number; height: number }[] = []
|
||||
const v2Sizes: Size[] = []
|
||||
node.on('sizeChanged', (e) => v2Sizes.push(e.size))
|
||||
|
||||
// Simulate the same resize LiteGraph called node.onResize([300, 200]) for
|
||||
node._emitSizeChanged({ width: 300, height: 200 })
|
||||
node._emitSizeChanged([300, 200])
|
||||
|
||||
expect(v2Sizes).toEqual([{ width: 300, height: 200 }])
|
||||
expect(v2Sizes).toEqual([[300, 200]])
|
||||
})
|
||||
|
||||
it('multiple resize events all reach the v2 handler (parity with repeated v1 onResize calls)', () => {
|
||||
const node = createMockNode()
|
||||
const widths: number[] = []
|
||||
node.on('sizeChanged', (e) => widths.push(e.size.width))
|
||||
node._emitSizeChanged({ width: 100, height: 50 })
|
||||
node._emitSizeChanged({ width: 200, height: 80 })
|
||||
node._emitSizeChanged({ width: 300, height: 120 })
|
||||
node.on('sizeChanged', (e) => widths.push(e.size[0]))
|
||||
node._emitSizeChanged([100, 50])
|
||||
node._emitSizeChanged([200, 80])
|
||||
node._emitSizeChanged([300, 120])
|
||||
expect(widths).toEqual([100, 200, 300])
|
||||
})
|
||||
|
||||
@@ -102,7 +102,7 @@ describe('BC.04 migration — node interaction: pointer, selection, resize', ()
|
||||
const handler = vi.fn()
|
||||
const unsub = node.on('sizeChanged', handler)
|
||||
unsub()
|
||||
node._emitSizeChanged({ width: 100, height: 50 })
|
||||
node._emitSizeChanged([100, 50])
|
||||
expect(handler).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
// Harness: inline MockNodeHandle — no ECS world needed for type-shape + event tests.
|
||||
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { NodeSizeChangedEvent } from '@/extension-api/node'
|
||||
import type { NodeSizeChangedEvent, Size } from '@/extension-api/node'
|
||||
import type { Unsubscribe } from '@/extension-api/events'
|
||||
|
||||
// ── Minimal mock ──────────────────────────────────────────────────────────────
|
||||
@@ -21,7 +21,7 @@ interface SizeChangedEmitter {
|
||||
event: 'sizeChanged',
|
||||
handler: (e: NodeSizeChangedEvent) => void
|
||||
): Unsubscribe
|
||||
_emitSizeChanged(size: { width: number; height: number }): void
|
||||
_emitSizeChanged(size: Size): void
|
||||
}
|
||||
|
||||
function createMockNode(): SizeChangedEmitter {
|
||||
@@ -37,7 +37,7 @@ function createMockNode(): SizeChangedEmitter {
|
||||
if (idx !== -1) listeners.splice(idx, 1)
|
||||
}
|
||||
},
|
||||
_emitSizeChanged(size) {
|
||||
_emitSizeChanged(size: Size) {
|
||||
const event: NodeSizeChangedEvent = { size }
|
||||
for (const fn of [...listeners]) fn(event)
|
||||
}
|
||||
@@ -48,26 +48,24 @@ function createMockNode(): SizeChangedEmitter {
|
||||
|
||||
describe('BC.04 v2 contract — node interaction: pointer, selection, resize', () => {
|
||||
describe("on('sizeChanged') — resize feedback (S2.N19)", () => {
|
||||
it('fires with { size: { width, height } } when node dimensions change', () => {
|
||||
it('fires with { size: [width, height] } when node dimensions change', () => {
|
||||
const node = createMockNode()
|
||||
const handler = vi.fn<[NodeSizeChangedEvent], void>()
|
||||
const handler = vi.fn<(e: NodeSizeChangedEvent) => void>()
|
||||
node.on('sizeChanged', handler)
|
||||
node._emitSizeChanged({ width: 300, height: 200 })
|
||||
node._emitSizeChanged([300, 200])
|
||||
expect(handler).toHaveBeenCalledOnce()
|
||||
expect(handler).toHaveBeenCalledWith({
|
||||
size: { width: 300, height: 200 }
|
||||
})
|
||||
expect(handler).toHaveBeenCalledWith({ size: [300, 200] })
|
||||
})
|
||||
|
||||
it('fires again on subsequent resize; each call gets the latest size', () => {
|
||||
const node = createMockNode()
|
||||
const sizes: { width: number; height: number }[] = []
|
||||
const sizes: Size[] = []
|
||||
node.on('sizeChanged', (e) => sizes.push(e.size))
|
||||
node._emitSizeChanged({ width: 100, height: 50 })
|
||||
node._emitSizeChanged({ width: 200, height: 80 })
|
||||
node._emitSizeChanged([100, 50])
|
||||
node._emitSizeChanged([200, 80])
|
||||
expect(sizes).toEqual([
|
||||
{ width: 100, height: 50 },
|
||||
{ width: 200, height: 80 }
|
||||
[100, 50],
|
||||
[200, 80]
|
||||
])
|
||||
})
|
||||
|
||||
@@ -76,7 +74,7 @@ describe('BC.04 v2 contract — node interaction: pointer, selection, resize', (
|
||||
const handler = vi.fn()
|
||||
const unsub = node.on('sizeChanged', handler)
|
||||
unsub()
|
||||
node._emitSizeChanged({ width: 300, height: 200 })
|
||||
node._emitSizeChanged([300, 200])
|
||||
expect(handler).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -86,7 +84,7 @@ describe('BC.04 v2 contract — node interaction: pointer, selection, resize', (
|
||||
b = vi.fn()
|
||||
node.on('sizeChanged', a)
|
||||
node.on('sizeChanged', b)
|
||||
node._emitSizeChanged({ width: 150, height: 120 })
|
||||
node._emitSizeChanged([150, 120])
|
||||
expect(a).toHaveBeenCalledOnce()
|
||||
expect(b).toHaveBeenCalledOnce()
|
||||
})
|
||||
@@ -98,7 +96,7 @@ describe('BC.04 v2 contract — node interaction: pointer, selection, resize', (
|
||||
const unsubA = node.on('sizeChanged', a)
|
||||
node.on('sizeChanged', b)
|
||||
unsubA()
|
||||
node._emitSizeChanged({ width: 200, height: 100 })
|
||||
node._emitSizeChanged([200, 100])
|
||||
expect(a).not.toHaveBeenCalled()
|
||||
expect(b).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
@@ -20,11 +20,11 @@ import type { NodeHandle, SlotInfo } from '@/extension-api/node'
|
||||
|
||||
function makeSlotInfo(overrides: Partial<SlotInfo> = {}): SlotInfo {
|
||||
return {
|
||||
entityId: 1 as SlotInfo['entityId'],
|
||||
entityId: 'slot:1' as SlotInfo['entityId'],
|
||||
name: 'input_0',
|
||||
type: 'LATENT',
|
||||
direction: 'input',
|
||||
nodeEntityId: 10 as SlotInfo['nodeEntityId'],
|
||||
nodeEntityId: 'node:10' as SlotInfo['nodeEntityId'],
|
||||
...overrides
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ describe('BC.09 v2 contract — dynamic slot and output mutation', () => {
|
||||
name: 'mask',
|
||||
type: 'MASK',
|
||||
direction: 'input',
|
||||
entityId: 2 as SlotInfo['entityId']
|
||||
entityId: 'slot:2' as SlotInfo['entityId']
|
||||
})
|
||||
]
|
||||
const handle = makeNodeHandleWithSlots(slots, [])
|
||||
@@ -69,7 +69,7 @@ describe('BC.09 v2 contract — dynamic slot and output mutation', () => {
|
||||
})
|
||||
|
||||
it('each SlotInfo has the required fields: entityId, name, type, direction, nodeEntityId', () => {
|
||||
const nodeId = 42 as SlotInfo['nodeEntityId']
|
||||
const nodeId = 'node:42' as SlotInfo['nodeEntityId']
|
||||
const slot = makeSlotInfo({
|
||||
name: 'latent',
|
||||
type: 'LATENT',
|
||||
@@ -91,7 +91,7 @@ describe('BC.09 v2 contract — dynamic slot and output mutation', () => {
|
||||
makeSlotInfo({
|
||||
name: 'b',
|
||||
direction: 'input',
|
||||
entityId: 2 as SlotInfo['entityId']
|
||||
entityId: 'slot:2' as SlotInfo['entityId']
|
||||
})
|
||||
]
|
||||
const handle = makeNodeHandleWithSlots(slots, [])
|
||||
@@ -119,7 +119,7 @@ describe('BC.09 v2 contract — dynamic slot and output mutation', () => {
|
||||
name: 'IMAGE',
|
||||
type: 'IMAGE',
|
||||
direction: 'output',
|
||||
entityId: 2 as SlotInfo['entityId']
|
||||
entityId: 'slot:2' as SlotInfo['entityId']
|
||||
})
|
||||
]
|
||||
const handle = makeNodeHandleWithSlots([], slots)
|
||||
@@ -141,7 +141,7 @@ describe('BC.09 v2 contract — dynamic slot and output mutation', () => {
|
||||
makeSlotInfo({
|
||||
name: 'out2',
|
||||
direction: 'output',
|
||||
entityId: 2 as SlotInfo['entityId']
|
||||
entityId: 'slot:2' as SlotInfo['entityId']
|
||||
})
|
||||
]
|
||||
const handle = makeNodeHandleWithSlots([], slots)
|
||||
@@ -156,7 +156,7 @@ describe('BC.09 v2 contract — dynamic slot and output mutation', () => {
|
||||
const outSlot = {
|
||||
...shared,
|
||||
direction: 'output' as const,
|
||||
entityId: 2 as SlotInfo['entityId']
|
||||
entityId: 'slot:2' as SlotInfo['entityId']
|
||||
}
|
||||
const handle = makeNodeHandleWithSlots([inSlot], [outSlot])
|
||||
|
||||
|
||||
@@ -35,13 +35,13 @@ interface MockWidgetHandle {
|
||||
setValue(value: unknown): void
|
||||
on(
|
||||
event: 'valueChange',
|
||||
handler: (e: WidgetValueChangeEvent) => void
|
||||
handler: (e: WidgetValueChangeEvent<unknown>) => void
|
||||
): Unsubscribe
|
||||
}
|
||||
|
||||
function createDualWidget(name: string, initial: unknown = '') {
|
||||
const valueRef = shallowRef(initial)
|
||||
const v2Listeners: Array<(e: WidgetValueChangeEvent) => void> = []
|
||||
const v2Listeners: Array<(e: WidgetValueChangeEvent<unknown>) => void> = []
|
||||
|
||||
// v1 shape
|
||||
const v1: V1Widget = { name, value: initial }
|
||||
@@ -58,12 +58,12 @@ function createDualWidget(name: string, initial: unknown = '') {
|
||||
valueRef.value = newValue
|
||||
v1.value = newValue
|
||||
// Fire v2 listeners
|
||||
const event: WidgetValueChangeEvent = { newValue, oldValue }
|
||||
const event: WidgetValueChangeEvent<unknown> = { newValue, oldValue }
|
||||
for (const fn of v2Listeners) fn(event)
|
||||
},
|
||||
on(
|
||||
_event: 'valueChange',
|
||||
handler: (e: WidgetValueChangeEvent) => void
|
||||
handler: (e: WidgetValueChangeEvent<unknown>) => void
|
||||
): Unsubscribe {
|
||||
v2Listeners.push(handler)
|
||||
return () => {
|
||||
@@ -95,7 +95,7 @@ describe('BC.10 migration — widget value subscription', () => {
|
||||
it('v1 callback and v2 valueChange handler both fire with the new value for the same interaction', () => {
|
||||
const { v1, v2, simulateV1Change } = createDualWidget('steps', 20)
|
||||
const v1Received: unknown[] = []
|
||||
const v2Received: WidgetValueChangeEvent[] = []
|
||||
const v2Received: WidgetValueChangeEvent<unknown>[] = []
|
||||
|
||||
v1.callback = (val) => v1Received.push(val)
|
||||
v2.on('valueChange', (e) => v2Received.push(e))
|
||||
@@ -110,7 +110,7 @@ describe('BC.10 migration — widget value subscription', () => {
|
||||
it('v2 payload is { newValue, oldValue } — v1 payload is positional args; both carry the same new value', () => {
|
||||
const { v1, v2, simulateV1Change } = createDualWidget('cfg', 7)
|
||||
let v1Value: unknown
|
||||
let v2Event: WidgetValueChangeEvent | undefined
|
||||
let v2Event: WidgetValueChangeEvent<unknown> | undefined
|
||||
|
||||
v1.callback = (val) => {
|
||||
v1Value = val
|
||||
@@ -164,7 +164,7 @@ describe('BC.10 migration — widget value subscription', () => {
|
||||
it('v1 onWidgetChanged and v2 per-widget valueChange both fire for the same widget change', () => {
|
||||
const { v1, v2, simulateV1Change } = createDualWidget('steps', 20)
|
||||
const v1NodeCalls: Array<{ name: string; value: unknown }> = []
|
||||
const v2Calls: WidgetValueChangeEvent[] = []
|
||||
const v2Calls: WidgetValueChangeEvent<unknown>[] = []
|
||||
|
||||
const node = {
|
||||
onWidgetChanged: (name: string, value: unknown) =>
|
||||
|
||||
@@ -135,7 +135,7 @@ describe('BC.10 v1 contract — widget value subscription', () => {
|
||||
})
|
||||
|
||||
it('onWidgetChanged fires for any widget on the node, not only those with an explicit callback', () => {
|
||||
const widgetA = createV1Widget('steps', 20)
|
||||
const __widgetA = createV1Widget('steps', 20)
|
||||
const widgetB = createV1Widget('cfg', 7)
|
||||
const handler = vi.fn()
|
||||
const node = { onWidgetChanged: handler }
|
||||
@@ -157,8 +157,12 @@ describe('BC.10 v1 contract — widget value subscription', () => {
|
||||
]
|
||||
const calls: Array<[string, unknown]> = []
|
||||
const node = {
|
||||
onWidgetChanged: (name: string, value: unknown) =>
|
||||
calls.push([name, value])
|
||||
onWidgetChanged: (
|
||||
name: string,
|
||||
value: unknown,
|
||||
_oldValue?: unknown,
|
||||
_widget?: unknown
|
||||
) => calls.push([name, value])
|
||||
}
|
||||
|
||||
// Simulate changes to all three widgets
|
||||
|
||||
@@ -27,7 +27,7 @@ interface MockWidgetHandle {
|
||||
setValue(value: unknown): void
|
||||
on(
|
||||
event: 'valueChange',
|
||||
handler: (e: WidgetValueChangeEvent) => void
|
||||
handler: (e: WidgetValueChangeEvent<unknown>) => void
|
||||
): Unsubscribe
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ function createMockWidgetHandle(
|
||||
initial: unknown = ''
|
||||
): MockWidgetHandle {
|
||||
const valueRef = shallowRef(initial)
|
||||
const listeners: Array<(e: WidgetValueChangeEvent) => void> = []
|
||||
const listeners: Array<(e: WidgetValueChangeEvent<unknown>) => void> = []
|
||||
|
||||
return {
|
||||
name,
|
||||
@@ -47,12 +47,12 @@ function createMockWidgetHandle(
|
||||
const oldValue = valueRef.value
|
||||
if (newValue === oldValue) return
|
||||
valueRef.value = newValue
|
||||
const event: WidgetValueChangeEvent = { newValue, oldValue }
|
||||
const event: WidgetValueChangeEvent<unknown> = { newValue, oldValue }
|
||||
for (const fn of listeners) fn(event)
|
||||
},
|
||||
on(
|
||||
_event: 'valueChange',
|
||||
handler: (e: WidgetValueChangeEvent) => void
|
||||
handler: (e: WidgetValueChangeEvent<unknown>) => void
|
||||
): Unsubscribe {
|
||||
listeners.push(handler)
|
||||
return () => {
|
||||
@@ -80,7 +80,7 @@ describe('BC.10 v2 contract — widget value subscription', () => {
|
||||
|
||||
it('handler receives the correct oldValue even after multiple sequential changes', () => {
|
||||
const widget = createMockWidgetHandle('seed', 0)
|
||||
const received: WidgetValueChangeEvent[] = []
|
||||
const received: WidgetValueChangeEvent<unknown>[] = []
|
||||
|
||||
widget.on('valueChange', (e) => received.push(e))
|
||||
widget.setValue(1)
|
||||
|
||||
@@ -286,7 +286,7 @@ describe('BC.11 migration — widget imperative state writes', () => {
|
||||
expect(v1Node.widgets[1].name).toBe('new_widget')
|
||||
|
||||
// v2: addWidget uses name key — 'cfg' remains at key 'cfg' regardless of insertion order
|
||||
const createCmds: Record<string, unknown>[] = []
|
||||
const _createCmds: Record<string, unknown>[] = []
|
||||
defineNode({
|
||||
name: 'bc11.mig.no-drift',
|
||||
nodeCreated(handle) {
|
||||
|
||||
@@ -37,7 +37,7 @@ function createV1ComboWidget(
|
||||
}
|
||||
|
||||
// Simulate LiteGraph calling widget.callback on user interaction.
|
||||
function simulateUserChange(widget: V1Widget, newValue: unknown): void {
|
||||
function _simulateUserChange(widget: V1Widget, newValue: unknown): void {
|
||||
widget.value = newValue
|
||||
widget.callback?.(newValue)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// v2 replacement: WidgetHandle.setValue(v), WidgetHandle.setOption(key,v), NodeHandle.addWidget(opts)
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { WidgetHandle } from '@/extension-api/widget'
|
||||
|
||||
// ── Mock world (same pattern as bc-01.v2.test.ts) ────────────────────────────
|
||||
|
||||
@@ -88,7 +89,7 @@ describe('BC.11 v2 contract — widget imperative state writes', () => {
|
||||
|
||||
describe('WidgetHandle.setValue(v) — controlled value write (S4.W4)', () => {
|
||||
it('WidgetHandle.setValue(v) dispatches a SetWidgetValue command with the correct value', () => {
|
||||
let widgetHandle: { setValue: (v: unknown) => void } | undefined
|
||||
let widgetHandle: WidgetHandle | undefined
|
||||
|
||||
defineNode({
|
||||
name: 'bc11.v2.set-value',
|
||||
|
||||
@@ -63,11 +63,11 @@ describe('BC.12 migration — per-widget serialization transform', () => {
|
||||
expectTypeOf<NameField>().toEqualTypeOf<string>()
|
||||
})
|
||||
|
||||
it('WidgetHandle.entityId is a branded number — prevents mixing widget IDs with node IDs', () => {
|
||||
it('WidgetHandle.entityId is a branded string — prevents mixing widget IDs with node IDs', () => {
|
||||
type EntityId = WidgetHandle['entityId']
|
||||
// Branded: assignable to number but not plain number (structurally number & { __brand })
|
||||
type IsNumber = EntityId extends number ? true : false
|
||||
const branded: IsNumber = true
|
||||
// Branded: assignable to string but not plain string (structurally string & { __brand })
|
||||
type IsString = EntityId extends string ? true : false
|
||||
const branded: IsString = true
|
||||
expect(branded).toBe(true)
|
||||
})
|
||||
|
||||
@@ -102,7 +102,7 @@ describe('BC.12 migration — per-widget serialization transform', () => {
|
||||
describe('async transform equivalence', () => {
|
||||
it("v2 on('beforeSerialize') handler type accepts both sync and async functions", () => {
|
||||
// AsyncHandler<T> = (e: T) => void | Promise<void>
|
||||
type Handler = Parameters<WidgetHandle['on']>[1]
|
||||
type __Handler = Parameters<WidgetHandle['on']>[1]
|
||||
// The beforeSerialize overload's handler must accept Promise return.
|
||||
// We check via the on() overload signature: the second param when event='beforeSerialize'
|
||||
// is typed as AsyncHandler<WidgetBeforeSerializeEvent>.
|
||||
|
||||
@@ -80,7 +80,10 @@ function makeV2NodeManager() {
|
||||
await fn(event)
|
||||
}
|
||||
|
||||
return replacer ? replacer(data) : data
|
||||
if (replacer !== null) {
|
||||
return (replacer as (orig: Record<string, unknown>) => Record<string, unknown>)(data)
|
||||
}
|
||||
return data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ function serializeWidgets(widgets: Array<WidgetSpec & { value: unknown }>): {
|
||||
return { named, warnings }
|
||||
}
|
||||
|
||||
function deserializeWidgets(
|
||||
function _deserializeWidgets(
|
||||
named: Record<string, unknown>,
|
||||
specs: WidgetSpec[],
|
||||
warn: (msg: string) => void
|
||||
|
||||
@@ -194,7 +194,7 @@ describe('BC.14 migration — graphToPrompt interception', () => {
|
||||
expect(typeof ext.setup).toBe('function')
|
||||
const result = ext.setup!()
|
||||
expect(result).toBeInstanceOf(Promise)
|
||||
return result.then(() => {
|
||||
return Promise.resolve(result).then(() => {
|
||||
expect(registered).toContain('setup-called')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -67,7 +67,7 @@ describe('BC.16 migration — per-node execution output', () => {
|
||||
if (data.text) v1Texts.push(data.text)
|
||||
}
|
||||
v2.on('executed', (e) => {
|
||||
if (e.output.text) v2Texts.push(e.output.text)
|
||||
if (e.output.text) v2Texts.push(e.output.text as string[])
|
||||
})
|
||||
|
||||
const payload = { text: ['Generated text output'], images: [] }
|
||||
@@ -87,7 +87,7 @@ describe('BC.16 migration — per-node execution output', () => {
|
||||
v1ImageCount = data.images?.length ?? 0
|
||||
}
|
||||
v2.on('executed', (e) => {
|
||||
v2ImageCount = e.output.images?.length ?? 0
|
||||
v2ImageCount = (e.output.images as unknown[] | undefined)?.length ?? 0
|
||||
})
|
||||
|
||||
const images = [{ filename: 'a.png', subfolder: '', type: 'output' }]
|
||||
|
||||
@@ -129,7 +129,8 @@ describe('BC.16 v2 contract — NodeHandle executed event', () => {
|
||||
const bus = createExecutedBus()
|
||||
const texts: string[] = []
|
||||
bus.on('executed', (e) => {
|
||||
for (const t of e.output.text ?? []) texts.push(t)
|
||||
for (const t of (e.output.text as string[] | undefined) ?? [])
|
||||
texts.push(t)
|
||||
})
|
||||
bus.emit(
|
||||
makeExecutedEvent({ output: { text: ['alpha', 'beta'], images: [] } })
|
||||
|
||||
@@ -77,7 +77,7 @@ describe('BC.17 migration — execution lifecycle events', () => {
|
||||
const v2Received: unknown[] = []
|
||||
|
||||
v1Api.addEventListener('executed', ((e: CustomEvent) =>
|
||||
v1Received.push(e.detail)) as EventListener)
|
||||
v1Received.push(e.detail)) as unknown as EventListener)
|
||||
v2.on('executed', (e) => v2Received.push(e))
|
||||
|
||||
const payload = { nodeId: 'node:g:1', output: { text: ['hello'] } }
|
||||
@@ -94,7 +94,7 @@ describe('BC.17 migration — execution lifecycle events', () => {
|
||||
const v2Payload: unknown[] = []
|
||||
|
||||
v1Api.addEventListener('execution_error', ((e: CustomEvent) =>
|
||||
v1Detail.push(e.detail)) as EventListener)
|
||||
v1Detail.push(e.detail)) as unknown as EventListener)
|
||||
v2.on('executionError', (e) => v2Payload.push(e))
|
||||
|
||||
const payload = { nodeId: 'node:g:7', message: 'CUDA OOM' }
|
||||
|
||||
@@ -157,11 +157,11 @@ describe('BC.19 migration — workflow execution trigger', () => {
|
||||
const v2Calls: number[] = []
|
||||
|
||||
// v1: each assignment replaces
|
||||
v1.queuePrompt = async (p) => {
|
||||
v1.queuePrompt = async (_p) => {
|
||||
v1Calls.push(1)
|
||||
return
|
||||
}
|
||||
v1.queuePrompt = async (p) => {
|
||||
v1.queuePrompt = async (_p) => {
|
||||
v1Calls.push(2)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ describe('BC.19 v1 contract — app.queuePrompt monkey-patch', () => {
|
||||
})
|
||||
|
||||
it('extension can inject a field into a mutable prompt object before calling orig()', async () => {
|
||||
const app = createMockApp()
|
||||
const _app = createMockApp()
|
||||
const prompts: Record<string, unknown>[] = []
|
||||
|
||||
// Simulate a version of app where queuePrompt receives a prompt object
|
||||
|
||||
@@ -126,8 +126,11 @@ describe('BC.20 v1 contract — LiteGraph.registerNodeType and isVirtualNode', (
|
||||
const LiteGraph = createMockLiteGraph()
|
||||
const app = createMockApp(LiteGraph)
|
||||
|
||||
class VirtualNode {}
|
||||
VirtualNode.prototype.isVirtualNode = true
|
||||
class VirtualNode {
|
||||
static isVirtualNode = true
|
||||
}
|
||||
;(VirtualNode.prototype as { isVirtualNode?: boolean }).isVirtualNode =
|
||||
true
|
||||
|
||||
app.registerExtension({
|
||||
registerCustomNodes() {
|
||||
@@ -218,8 +221,11 @@ describe('BC.20 v1 contract — LiteGraph.registerNodeType and isVirtualNode', (
|
||||
describe('S8.P1 — virtual node payload suppression (synthetic)', () => {
|
||||
it('serializeGraph excludes nodes with isVirtualNode === true from the output', () => {
|
||||
class RealNode {}
|
||||
class VirtualNode {}
|
||||
VirtualNode.prototype.isVirtualNode = true
|
||||
class VirtualNode {
|
||||
static isVirtualNode = true
|
||||
}
|
||||
;(VirtualNode.prototype as { isVirtualNode?: boolean }).isVirtualNode =
|
||||
true
|
||||
|
||||
const nodes = [
|
||||
{
|
||||
|
||||
@@ -23,7 +23,7 @@ import type { NodeHandle } from '@/extension-api/node'
|
||||
|
||||
function makeWidgetHandle(overrides: Partial<WidgetHandle> = {}): WidgetHandle {
|
||||
return {
|
||||
entityId: 1 as WidgetHandle['entityId'],
|
||||
entityId: 'widget:1' as WidgetHandle['entityId'],
|
||||
name: 'steps',
|
||||
widgetType: 'INT',
|
||||
label: 'Steps',
|
||||
|
||||
@@ -53,6 +53,7 @@ interface WidgetHandle {
|
||||
|
||||
interface AppEvents {
|
||||
domWidgetCreated: WidgetHandle
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
function makeWidget(overrides: Partial<WidgetHandle> = {}): WidgetHandle {
|
||||
|
||||
@@ -201,7 +201,7 @@ describe('BC.35 migration — pre-queue widget validation', () => {
|
||||
// v1: two patches — second clobbers first's validation if not careful
|
||||
const v1App = makeV1App()
|
||||
const extAValidation = vi.fn(() => null) // ext-A passes
|
||||
const extBValidation = vi.fn((): string | null => 'B rejects')
|
||||
const __extBValidation = vi.fn((): string | null => 'B rejects')
|
||||
|
||||
// v1: each patcher wraps the previous — but if ext-B directly replaces
|
||||
// without calling through, ext-A's validation is lost.
|
||||
|
||||
Reference in New Issue
Block a user