test: replace fromAny with typed alternatives across litegraph test suite

Replace all fromAny() calls in 6 test files: use fromPartial<T> for partial
object construction, toLinkId for branded LinkId values, proper factory
functions with Partial<INodeInputSlot/OutputSlot> overrides for slot tests,
DOMMatrix for transform mocks, and direct undefined/null for defensively-
guarded inputs whose production signatures now accept undefined.
This commit is contained in:
huang47
2026-07-03 09:49:14 -07:00
parent d3d5a92175
commit d976c96381
7 changed files with 91 additions and 97 deletions

View File

@@ -1,7 +1,11 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { fromAny, fromPartial } from '@total-typescript/shoehorn'
import { fromPartial } from '@total-typescript/shoehorn'
import type { DefaultConnectionColors } from '@/lib/litegraph/src/interfaces'
import type {
DefaultConnectionColors,
INodeInputSlot,
INodeOutputSlot
} from '@/lib/litegraph/src/interfaces'
import { LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph'
import { SlotType } from '@/lib/litegraph/src/draw'
import {
@@ -29,33 +33,33 @@ function createNode(): LGraphNode {
}
function createInputSlot(
overrides: Partial<NodeInputSlot> = {},
overrides: Partial<INodeInputSlot> = {},
node = createNode()
): NodeInputSlot {
return new NodeInputSlot(
fromAny({
{
name: 'in',
type: 'STRING',
link: null,
boundingRect: [10, 20, 20, 20],
boundingRect: [10, 20, 20, 20] as const,
...overrides
}),
},
node
)
}
function createOutputSlot(
overrides: Partial<NodeOutputSlot> = {},
overrides: Partial<INodeOutputSlot> = {},
node = createNode()
): NodeOutputSlot {
return new NodeOutputSlot(
fromAny({
{
name: 'out',
type: 'STRING',
links: null,
boundingRect: [10, 20, 20, 20],
boundingRect: [10, 20, 20, 20] as const,
...overrides
}),
},
node
)
}
@@ -310,12 +314,12 @@ describe('NodeSlot rendering', () => {
})
it('falls back to the selected title colour, then text colour', () => {
LiteGraph.NODE_TEXT_HIGHLIGHT_COLOR = fromAny(undefined)
LiteGraph.NODE_TEXT_HIGHLIGHT_COLOR = undefined
expect(createInputSlot().highlightColor).toBe(
LiteGraph.NODE_SELECTED_TITLE_COLOR
)
LiteGraph.NODE_SELECTED_TITLE_COLOR = fromAny(undefined)
LiteGraph.NODE_SELECTED_TITLE_COLOR = undefined
expect(createInputSlot().highlightColor).toBe(LiteGraph.NODE_TEXT_COLOR)
})
})

View File

@@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest'
import type { INodeOutputSlot } from '@/lib/litegraph/src/interfaces'
import type { IWidget } from '@/lib/litegraph/src/litegraph'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { INumericWidget } from '@/lib/litegraph/src/types/widgets'
import { toLinkId } from '@/types/linkId'
import { outputAsSerialisable } from './slotUtils'
@@ -37,7 +38,12 @@ describe('outputAsSerialisable', () => {
it('serialises only the widget name for outputs with widgets', () => {
const node = new LGraphNode('test')
const output = node.addOutput('out', 'number') as OutputSlotParam
output.widget = { name: 'my-widget', type: 'number' } as IWidget
output.widget = node.addWidget(
'number',
'my-widget',
0,
null
) as INumericWidget
const serialised = outputAsSerialisable(output)
expect(serialised.widget).toEqual({ name: 'my-widget' })

View File

@@ -1,5 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { fromAny, fromPartial } from '@total-typescript/shoehorn'
import { fromPartial } from '@total-typescript/shoehorn'
import type { CanvasPointer } from '@/lib/litegraph/src/CanvasPointer'
import { LGraphCanvas } from '@/lib/litegraph/src/LGraphCanvas'
@@ -11,6 +11,7 @@ import type {
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events'
import { CanvasItem } from '@/lib/litegraph/src/types/globalEnums'
import { toLinkId } from '@/types/linkId'
import {
createTestSubgraph,
@@ -131,7 +132,7 @@ describe('SubgraphIONodeBase', () => {
beforeEach(() => {
captured = undefined
LiteGraph.ContextMenu = fromAny(
LiteGraph.ContextMenu = fromPartial<typeof LiteGraph.ContextMenu>(
class {
constructor(
options: (IContextMenuValue | null)[],
@@ -150,7 +151,7 @@ describe('SubgraphIONodeBase', () => {
it('offers disconnect, rename, and remove for connected slots', () => {
const { subgraph, inputNode } = createArrangedInputNode()
const slot = inputNode.slots[0]
slot.linkIds.push(fromAny(1))
slot.linkIds.push(toLinkId(1))
const [slotX, slotY] = slotCentre(slot)
inputNode.onPointerDown(
@@ -168,21 +169,11 @@ describe('SubgraphIONodeBase', () => {
])
// Disconnect action clears the slot's links.
void captured?.opts.callback?.(
fromPartial({ value: 'disconnect' }),
fromAny({}),
fromAny({}),
fromAny({})
)
void captured?.opts.callback?.(fromPartial({ value: 'disconnect' }))
expect(slot.linkIds).toHaveLength(0)
// Remove action deletes the slot from the subgraph.
void captured?.opts.callback?.(
fromPartial({ value: 'remove' }),
fromAny({}),
fromAny({}),
fromAny({})
)
void captured?.opts.callback?.(fromPartial({ value: 'remove' }))
expect(subgraph.inputs).toHaveLength(0)
})
@@ -208,12 +199,7 @@ describe('SubgraphIONodeBase', () => {
fromPartial<CanvasPointer>({}),
fromPartial<LinkConnector>({})
)
void captured?.opts.callback?.(
fromPartial({ value: 'rename' }),
fromAny({}),
fromAny({}),
fromAny({})
)
void captured?.opts.callback?.(fromPartial({ value: 'rename' }))
expect(prompt).toHaveBeenCalledWith(
'Slot name',
@@ -269,14 +255,14 @@ describe('SubgraphIONodeBase', () => {
linkConnector
)
pointer.onDragStart?.(fromAny({}))
pointer.onDragStart?.(pointer)
expect(linkConnector.dragNewFromSubgraphInput).toHaveBeenCalledWith(
subgraph,
inputNode,
slot
)
pointer.onDragEnd?.(fromAny({}))
pointer.onDragEnd?.(fromPartial<CanvasPointerEvent>({}))
expect(linkConnector.dropLinks).toHaveBeenCalled()
pointer.finally?.()
@@ -296,7 +282,7 @@ describe('SubgraphIONodeBase', () => {
pointer,
fromPartial<LinkConnector>({})
)
pointer.onDoubleClick?.(fromAny({}))
pointer.onDoubleClick?.(fromPartial<CanvasPointerEvent>({}))
expect(prompt).toHaveBeenCalled()
})
@@ -313,7 +299,7 @@ describe('SubgraphIONodeBase', () => {
pointer,
fromPartial<LinkConnector>({})
)
pointer.onDoubleClick?.(fromAny({}))
pointer.onDoubleClick?.(fromPartial<CanvasPointerEvent>({}))
expect(prompt).not.toHaveBeenCalled()
})
@@ -363,7 +349,7 @@ describe('SubgraphIONodeBase', () => {
const { inputNode } = createArrangedInputNode()
const strokeStyles: unknown[] = []
const ctx = fromPartial<CanvasRenderingContext2D>({
getTransform: vi.fn(() => fromAny({})),
getTransform: vi.fn(() => new DOMMatrix()),
setTransform: vi.fn(),
translate: vi.fn(),
beginPath: vi.fn(),

View File

@@ -1,17 +1,19 @@
import { createTestingPinia } from '@pinia/testing'
import { setActivePinia } from 'pinia'
import { fromAny, fromPartial } from '@total-typescript/shoehorn'
import { fromPartial } from '@total-typescript/shoehorn'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { CanvasPointer } from '@/lib/litegraph/src/CanvasPointer'
import type { LinkConnector } from '@/lib/litegraph/src/canvas/LinkConnector'
import type { Subgraph } from '@/lib/litegraph/src/LGraph'
import type {
INodeInputSlot,
INodeOutputSlot
INodeOutputSlot,
Point
} from '@/lib/litegraph/src/interfaces'
import { RenderShape } from '@/lib/litegraph/src/types/globalEnums'
import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events'
import type { SubgraphIO } from '@/lib/litegraph/src/types/serialisation'
import type { Subgraph } from '@/lib/litegraph/src/litegraph'
import { LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph'
import { toLinkId } from '@/types/linkId'
import { createMockCanvasRenderingContext2D } from '@/utils/__tests__/litegraphTestUtils'
@@ -260,7 +262,7 @@ describe('SubgraphInput.getConnectedWidgets', () => {
expect(subgraph.inputs[0].getConnectedWidgets()).toEqual([])
expect(warn).toHaveBeenCalledWith('Widget not found', { name: 'missing' })
input.widget = fromAny({ name: '' })
input.widget = { name: '' }
expect(subgraph.inputs[0].getConnectedWidgets()).toEqual([])
expect(warn).toHaveBeenCalledWith('Invalid widget name', { name: '' })
})
@@ -292,7 +294,7 @@ describe('SubgraphSlot base behaviour', () => {
const slot = subgraph.inputs[0]
slot.pos = [3, 4]
slot.pos = fromAny([5])
slot.pos = fromPartial<Point>([5])
expect([slot.pos[0], slot.pos[1]]).toEqual([3, 4])
})
@@ -300,7 +302,7 @@ describe('SubgraphSlot base behaviour', () => {
it('generates an id when the serialised slot has none', () => {
const subgraph = createIoSubgraph()
const slot = new SubgraphInput(
fromAny({ name: 'anon', type: 'STRING', linkIds: [] }),
fromPartial<SubgraphIO>({ name: 'anon', type: 'STRING', linkIds: [] }),
subgraph.inputNode
)
@@ -392,7 +394,7 @@ describe('SubgraphSlot base behaviour', () => {
it('falls back to the default label colour when unset', () => {
const originalColor = LiteGraph.NODE_TEXT_COLOR
try {
LiteGraph.NODE_TEXT_COLOR = fromAny('')
LiteGraph.NODE_TEXT_COLOR = ''
const subgraph = createIoSubgraph()
const fillStyles: unknown[] = []
const ctx = createMockCanvasRenderingContext2D({
@@ -442,13 +444,13 @@ describe('SubgraphOutputNode interaction', () => {
linkConnector
)
pointer.onDragStart?.(fromAny({}))
pointer.onDragStart?.(pointer)
expect(linkConnector.dragNewFromSubgraphOutput).toHaveBeenCalledWith(
subgraph,
outputNode,
slot
)
pointer.onDragEnd?.(fromAny({}))
pointer.onDragEnd?.(fromPartial<CanvasPointerEvent>({}))
expect(linkConnector.dropLinks).toHaveBeenCalled()
pointer.finally?.()
expect(linkConnector.reset).toHaveBeenCalledWith(true)
@@ -458,7 +460,7 @@ describe('SubgraphOutputNode interaction', () => {
const { outputNode } = createArrangedOutputNode()
const OriginalContextMenu = LiteGraph.ContextMenu
let constructed = false
LiteGraph.ContextMenu = fromAny(
LiteGraph.ContextMenu = fromPartial<typeof LiteGraph.ContextMenu>(
class {
constructor() {
constructed = true
@@ -515,7 +517,7 @@ describe('SubgraphInputNode connections', () => {
expect(() =>
subgraph.inputNode.connectSlots(
fromAny({}),
fromPartial<SubgraphInput>({}),
node,
node.inputs[0],
undefined
@@ -541,7 +543,7 @@ describe('SubgraphInputNode connections', () => {
it('falls back to the subgraph slot type for untyped inputs', () => {
const subgraph = createIoSubgraph()
const node = new LGraphNode('Untyped')
node.addInput('in', fromAny(''))
node.addInput('in', '')
subgraph.add(node)
const link = subgraph.inputNode.connectSlots(

View File

@@ -1,9 +1,13 @@
import { createTestingPinia } from '@pinia/testing'
import { setActivePinia } from 'pinia'
import { fromAny, fromPartial } from '@total-typescript/shoehorn'
import { fromPartial } from '@total-typescript/shoehorn'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { Positionable } from '@/lib/litegraph/src/interfaces'
import type {
INodeInputSlot,
INodeOutputSlot,
Positionable
} from '@/lib/litegraph/src/interfaces'
import {
LGraph,
LGraphGroup,
@@ -18,8 +22,11 @@ import type { ResolvedConnection } from '@/lib/litegraph/src/LLink'
import type { Reroute } from '@/lib/litegraph/src/Reroute'
import type { SerialisableLLink } from '@/lib/litegraph/src/types/serialisation'
import { toRerouteId } from '@/types/rerouteId'
import { toLinkId } from '@/types/linkId'
import { createMockPositionable } from '@/utils/__tests__/litegraphTestUtils'
import type { SubgraphInput } from './SubgraphInput'
import {
getBoundaryLinks,
groupResolvedByOutput,
@@ -346,7 +353,7 @@ describe('subgraphUtils', () => {
const first = fromPartial<ResolvedConnection>({ output })
const second = fromPartial<ResolvedConnection>({ output })
const bySubgraphInput = fromPartial<ResolvedConnection>({
subgraphInput: fromAny({ name: 'sub' })
subgraphInput: fromPartial<SubgraphInput>({ name: 'sub' })
})
const unresolvable = fromPartial<ResolvedConnection>({})
@@ -358,7 +365,7 @@ describe('subgraphUtils', () => {
])
expect(grouped.size).toBe(3)
expect(grouped.get(fromAny(output))).toEqual([first, second])
expect(grouped.get(output!)).toEqual([first, second])
})
})
@@ -367,18 +374,15 @@ describe('subgraphUtils', () => {
linkId: number,
inputOverrides: Record<string, unknown> = {}
): { resolved: ResolvedConnection; link: LLink } {
const link = new LLink(
fromAny(linkId),
'number',
fromAny(1),
0,
fromAny(2),
0
)
const link = new LLink(toLinkId(linkId), 'number', 1, 0, 2, 0)
const resolved = fromPartial<ResolvedConnection>({
link,
input: fromAny({ name: 'in', type: 'number', ...inputOverrides }),
output: fromAny({ name: 'out', type: 'number' })
input: fromPartial<INodeInputSlot>({
name: 'in',
type: 'number',
...inputOverrides
}),
output: fromPartial<INodeOutputSlot>({ name: 'out', type: 'number' })
})
return { resolved, link }
}
@@ -412,10 +416,10 @@ describe('subgraphUtils', () => {
})
it('skips connections without a resolved input', () => {
const link = new LLink(fromAny(1), 'number', fromAny(1), 0, fromAny(2), 0)
const link = new LLink(toLinkId(1), 'number', 1, 0, 2, 0)
const resolved = fromPartial<ResolvedConnection>({
link,
output: fromAny({ name: 'out', type: 'number' })
output: fromPartial<INodeOutputSlot>({ name: 'out', type: 'number' })
})
const inputs = mapSubgraphInputsAndLinks([resolved], [], new Map())
@@ -444,18 +448,15 @@ describe('subgraphUtils', () => {
linkId: number,
outputOverrides: Record<string, unknown> = {}
): { resolved: ResolvedConnection; link: LLink } {
const link = new LLink(
fromAny(linkId),
'number',
fromAny(1),
0,
fromAny(2),
0
)
const link = new LLink(toLinkId(linkId), 'number', 1, 0, 2, 0)
const resolved = fromPartial<ResolvedConnection>({
link,
input: fromAny({ name: 'in', type: 'number' }),
output: fromAny({ name: 'out', type: 'number', ...outputOverrides })
input: fromPartial<INodeInputSlot>({ name: 'in', type: 'number' }),
output: fromPartial<INodeOutputSlot>({
name: 'out',
type: 'number',
...outputOverrides
})
})
return { resolved, link }
}
@@ -488,10 +489,10 @@ describe('subgraphUtils', () => {
})
it('skips connections without a resolved output', () => {
const link = new LLink(fromAny(1), 'number', fromAny(1), 0, fromAny(2), 0)
const link = new LLink(toLinkId(1), 'number', 1, 0, 2, 0)
const resolved = fromPartial<ResolvedConnection>({
link,
input: fromAny({ name: 'in', type: 'number' })
input: fromPartial<INodeInputSlot>({ name: 'in', type: 'number' })
})
const outputs = mapSubgraphOutputsAndLinks([resolved], [], new Map())
@@ -502,9 +503,12 @@ describe('subgraphUtils', () => {
describe('reorderSubgraphInputs', () => {
it('returns silently when the node has no subgraph', () => {
expect(() =>
reorderSubgraphInputs(fromAny({ subgraph: undefined }), [])
).not.toThrow()
const subgraphNode = createTestSubgraphNode(createTestSubgraph())
Object.defineProperty(subgraphNode, 'subgraph', {
value: undefined,
configurable: true
})
expect(() => reorderSubgraphInputs(subgraphNode, [])).not.toThrow()
})
it('rejects indices that are not a permutation', () => {

View File

@@ -1,5 +1,4 @@
import { describe, expect, it } from 'vitest'
import { fromAny } from '@total-typescript/shoehorn'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { Direction } from '@/lib/litegraph/src/interfaces'
@@ -16,11 +15,11 @@ function createNode(x: number, y: number, width: number, height: number) {
describe('getBoundaryNodes', () => {
it('returns null when no nodes are supplied', () => {
expect(getBoundaryNodes([])).toBeNull()
expect(getBoundaryNodes(fromAny(undefined))).toBeNull()
expect(getBoundaryNodes(undefined)).toBeNull()
})
it('returns null when all nodes are falsy', () => {
expect(getBoundaryNodes(fromAny([undefined, null]))).toBeNull()
expect(getBoundaryNodes([undefined, null])).toBeNull()
})
it('returns the same node for all edges with a single node', () => {
@@ -51,7 +50,7 @@ describe('getBoundaryNodes', () => {
const node = createNode(5, 5, 10, 10)
const other = createNode(50, 50, 10, 10)
const boundary = getBoundaryNodes(fromAny([node, undefined, other]))
const boundary = getBoundaryNodes([node, undefined, other])
expect(boundary?.left).toBe(node)
expect(boundary?.right).toBe(other)
@@ -62,7 +61,7 @@ describe('distributeNodes', () => {
it('returns an empty array when fewer than two nodes are supplied', () => {
expect(distributeNodes([])).toEqual([])
expect(distributeNodes([createNode(0, 0, 10, 10)])).toEqual([])
expect(distributeNodes(fromAny(undefined))).toEqual([])
expect(distributeNodes(undefined)).toEqual([])
})
it('distributes nodes evenly along the horizontal plane', () => {
@@ -97,7 +96,7 @@ describe('distributeNodes', () => {
describe('alignNodes', () => {
it('returns an empty array when nodes are not supplied', () => {
expect(alignNodes(fromAny(undefined), 'left')).toEqual([])
expect(alignNodes(undefined, 'left')).toEqual([])
})
it('returns an empty array when boundary nodes cannot be determined', () => {

View File

@@ -1,5 +1,4 @@
import { describe, expect, it } from 'vitest'
import { fromAny } from '@total-typescript/shoehorn'
import type { Positionable } from '@/lib/litegraph/src/interfaces'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
@@ -13,7 +12,7 @@ import {
describe('getAllNestedItems', () => {
it('returns an empty set when items are not supplied', () => {
expect(getAllNestedItems(fromAny(undefined)).size).toBe(0)
expect(getAllNestedItems(undefined).size).toBe(0)
})
it('excludes pinned items', () => {
@@ -77,13 +76,7 @@ describe('findFreeSlotOfType', () => {
it('returns undefined when no slots are supplied', () => {
expect(findFreeSlotOfType([], 'A', hasNoLinks)).toBeUndefined()
expect(
findFreeSlotOfType(
fromAny<TestSlot[], undefined>(undefined),
'A',
hasNoLinks
)
).toBeUndefined()
expect(findFreeSlotOfType(undefined, 'A', hasNoLinks)).toBeUndefined()
})
it('returns the first free slot with an exact type match', () => {