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:
Connor Byrne
2026-05-13 18:27:47 -07:00
committed by bymyself
parent 9c4fab20d9
commit 9adfa9efc2
26 changed files with 96 additions and 82 deletions

View File

@@ -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]) {

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()

View File

@@ -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()
})
})

View File

@@ -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()
})

View File

@@ -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])

View File

@@ -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) =>

View File

@@ -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

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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)
}

View File

@@ -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',

View File

@@ -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>.

View File

@@ -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
}
}
}

View File

@@ -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

View File

@@ -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')
})
})

View File

@@ -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' }]

View File

@@ -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: [] } })

View File

@@ -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' }

View File

@@ -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
}

View File

@@ -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

View File

@@ -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 = [
{

View File

@@ -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',

View File

@@ -53,6 +53,7 @@ interface WidgetHandle {
interface AppEvents {
domWidgetCreated: WidgetHandle
[key: string]: unknown
}
function makeWidget(overrides: Partial<WidgetHandle> = {}): WidgetHandle {

View File

@@ -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.