Compare commits

...

2 Commits

Author SHA1 Message Date
GitHub Action
8dc3dda753 [automated] Apply ESLint and Oxfmt fixes 2026-04-16 06:11:12 +00:00
Kelly Yang
51c5318695 test: add unit tests for slotCalculations
Cover calculateInputSlotPosFromSlot and getSlotPosition legacy fallback
with behavioral unit tests for collapsed nodes, hard-coded positions,
default vertical layout ordering, widget slot exclusion, and slot index
out-of-range handling.

Closes #11106
2026-04-15 23:04:27 -07:00

View File

@@ -0,0 +1,196 @@
import { fromPartial } from '@total-typescript/shoehorn'
import { beforeEach, describe, expect, it, vi } from 'vitest'
const mockLiteGraph = vi.hoisted(() => ({
NODE_TITLE_HEIGHT: 30,
NODE_SLOT_HEIGHT: 20,
NODE_COLLAPSED_WIDTH: 80,
vueNodesMode: false
}))
vi.mock('@/lib/litegraph/src/litegraph', () => ({
LiteGraph: mockLiteGraph
}))
vi.mock('@/renderer/core/layout/slots/slotIdentifier', () => ({
getSlotKey: vi.fn()
}))
vi.mock('@/renderer/core/layout/store/layoutStore', () => ({
layoutStore: {
getSlotLayout: vi.fn().mockReturnValue(null),
getNodeLayoutRef: vi.fn().mockReturnValue({ value: null })
}
}))
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
import type {
INodeInputSlot,
INodeOutputSlot
} from '@/lib/litegraph/src/interfaces'
import {
calculateInputSlotPosFromSlot,
getSlotPosition
} from './slotCalculations'
import type { SlotPositionContext } from './slotCalculations'
const SLOT_HEIGHT = mockLiteGraph.NODE_SLOT_HEIGHT
const TITLE_HEIGHT = mockLiteGraph.NODE_TITLE_HEIGHT
function makeContext(
overrides: Partial<SlotPositionContext> = {}
): SlotPositionContext {
return {
nodeX: 100,
nodeY: 200,
nodeWidth: 180,
nodeHeight: 120,
collapsed: false,
inputs: [],
outputs: [],
...overrides
}
}
function makeInput(overrides: Partial<INodeInputSlot> = {}): INodeInputSlot {
return { name: 'input', type: 'INT', ...overrides } as INodeInputSlot
}
function makeOutput(overrides: Partial<INodeOutputSlot> = {}): INodeOutputSlot {
return { name: 'output', type: 'INT', ...overrides } as INodeOutputSlot
}
function makeNode(
overrides: Partial<{
inputs: INodeInputSlot[]
outputs: INodeOutputSlot[]
collapsed: boolean
}> = {}
): LGraphNode {
return fromPartial<LGraphNode>({
id: 1,
pos: [100, 200],
size: [180, 120],
flags: { collapsed: overrides.collapsed ?? false },
_collapsed_width: mockLiteGraph.NODE_COLLAPSED_WIDTH,
constructor: { slot_start_y: undefined },
inputs: overrides.inputs ?? [],
outputs: overrides.outputs ?? [],
widgets: []
})
}
describe('calculateInputSlotPosFromSlot', () => {
describe('collapsed node', () => {
it('returns node origin offset upward by half title height', () => {
const input = makeInput()
const ctx = makeContext({ collapsed: true })
const [x, y] = calculateInputSlotPosFromSlot(ctx, input)
expect(x).toBe(100)
expect(y).toBe(200 - TITLE_HEIGHT * 0.5)
})
})
describe('hard-coded slot position', () => {
it('returns node origin plus the hard-coded offset', () => {
const input = makeInput({ pos: [10, 25] })
const ctx = makeContext()
const [x, y] = calculateInputSlotPosFromSlot(ctx, input)
expect(x).toBe(110)
expect(y).toBe(225)
})
})
describe('default vertical layout', () => {
it('places the first input slot at the correct x and y', () => {
const input = makeInput()
const ctx = makeContext({ inputs: [input] })
const [x, y] = calculateInputSlotPosFromSlot(ctx, input)
expect(x).toBe(100 + SLOT_HEIGHT * 0.5)
expect(y).toBeCloseTo(200 + 0.7 * SLOT_HEIGHT)
})
it('places subsequent slots below the first', () => {
const first = makeInput({ name: 'a' })
const second = makeInput({ name: 'b' })
const ctx = makeContext({ inputs: [first, second] })
const [, y1] = calculateInputSlotPosFromSlot(ctx, first)
const [, y2] = calculateInputSlotPosFromSlot(ctx, second)
expect(y2).toBeCloseTo(y1 + SLOT_HEIGHT)
})
it('respects slotStartY offset', () => {
const input = makeInput()
const base = makeContext({ inputs: [input] })
const withOffset = makeContext({ inputs: [input], slotStartY: 40 })
const [, yBase] = calculateInputSlotPosFromSlot(base, input)
const [, yOffset] = calculateInputSlotPosFromSlot(withOffset, input)
expect(yOffset).toBeCloseTo(yBase + 40)
})
it('excludes widget input slots from vertical ordering', () => {
const widget = makeInput({ name: 'widget', widget: { name: 'widget' } })
const regular = makeInput({ name: 'regular' })
const ctx = makeContext({
inputs: [widget, regular],
widgets: [{ name: 'widget' }]
})
const [, yRegular] = calculateInputSlotPosFromSlot(ctx, regular)
expect(yRegular).toBeCloseTo(200 + 0.7 * SLOT_HEIGHT)
})
it('excludes slots with hard-coded positions from vertical ordering', () => {
const fixed = makeInput({ name: 'fixed', pos: [0, 50] })
const regular = makeInput({ name: 'regular' })
const ctx = makeContext({ inputs: [fixed, regular] })
const [, y] = calculateInputSlotPosFromSlot(ctx, regular)
expect(y).toBeCloseTo(200 + 0.7 * SLOT_HEIGHT)
})
})
})
describe('getSlotPosition — legacy fallback (vueNodesMode disabled)', () => {
beforeEach(() => {
mockLiteGraph.vueNodesMode = false
})
it('calculates input slot position from node.pos', () => {
const input = makeInput()
const node = makeNode({ inputs: [input] })
const [x, y] = getSlotPosition(node, 0, true)
expect(x).toBe(100 + SLOT_HEIGHT * 0.5)
expect(y).toBeCloseTo(200 + 0.7 * SLOT_HEIGHT)
})
it('calculates output slot position from node.pos', () => {
const output = makeOutput()
const node = makeNode({ outputs: [output] })
const [x, y] = getSlotPosition(node, 0, false)
expect(x).toBeCloseTo(100 + 180 + 1 - SLOT_HEIGHT * 0.5)
expect(y).toBeCloseTo(200 + 0.7 * SLOT_HEIGHT)
})
it('returns node origin offset upward when node is collapsed and requesting input', () => {
const input = makeInput()
const node = makeNode({ inputs: [input], collapsed: true })
const [x, y] = getSlotPosition(node, 0, true)
expect(x).toBe(100)
expect(y).toBe(200 - TITLE_HEIGHT * 0.5)
})
it('returns node origin offset right when node is collapsed and requesting output', () => {
const output = makeOutput()
const node = makeNode({ outputs: [output], collapsed: true })
const [x, y] = getSlotPosition(node, 0, false)
expect(x).toBe(100 + mockLiteGraph.NODE_COLLAPSED_WIDTH)
expect(y).toBe(200 - TITLE_HEIGHT * 0.5)
})
it('returns node origin for out-of-range input slot index', () => {
const node = makeNode()
const [x, y] = getSlotPosition(node, 5, true)
expect(x).toBe(100)
expect(y).toBe(200)
})
})