Files
ComfyUI_frontend/tests-ui/ensureCorrectLayoutScale.test.ts

1115 lines
31 KiB
TypeScript

import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'
import { ensureCorrectLayoutScale } from '../src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale'
import type { LGraph, RendererType } from '../src/lib/litegraph/src/LGraph'
import { LiteGraph } from '../src/lib/litegraph/src/litegraph'
// Mock dependencies
vi.mock('../src/composables/useVueFeatureFlags', () => ({
useVueFeatureFlags: vi.fn()
}))
vi.mock('../src/platform/settings/settingStore', () => ({
useSettingStore: vi.fn()
}))
vi.mock('../src/stores/layoutStore', () => ({
layoutStore: {
batchUpdateNodeBounds: vi.fn()
}
}))
vi.mock('../src/stores/layoutMutations', () => ({
useLayoutMutations: vi.fn()
}))
vi.mock('../src/scripts/app', () => ({
comfyApp: {
canvas: null
}
}))
vi.mock('../src/lib/litegraph/src/measure', () => ({
createBounds: vi.fn()
}))
import { useVueFeatureFlags } from '../src/composables/useVueFeatureFlags'
import { useSettingStore } from '../src/platform/settings/settingStore'
import { layoutStore } from '../src/stores/layoutStore'
import { useLayoutMutations } from '../src/stores/layoutMutations'
import { comfyApp } from '../src/scripts/app'
import { createBounds } from '../src/lib/litegraph/src/measure'
describe('ensureCorrectLayoutScale', () => {
let mockGraph: LGraph
let mockCanvas: any
let mockSettingStore: any
let mockLayoutMutations: any
beforeEach(() => {
// Reset all mocks
vi.clearAllMocks()
// Create a complete mock graph with all required properties
mockGraph = {
nodes: [],
reroutes: new Map(),
groups: [],
extra: {
workflowRendererVersion: undefined
},
inputNode: null,
outputNode: null
} as any
// Mock canvas
mockCanvas = {
graph: mockGraph,
ds: {
scale: 1.0,
convertOffsetToCanvas: vi.fn((pos) => pos),
changeScale: vi.fn()
}
}
// Mock setting store
mockSettingStore = {
get: vi.fn().mockReturnValue(true)
}
vi.mocked(useSettingStore).mockReturnValue(mockSettingStore)
// Mock layout mutations
mockLayoutMutations = {
moveReroute: vi.fn()
}
vi.mocked(useLayoutMutations).mockReturnValue(mockLayoutMutations)
// Mock Vue feature flags
vi.mocked(useVueFeatureFlags).mockReturnValue({
shouldRenderVueNodes: { value: false }
} as any)
// Set comfyApp canvas
;(comfyApp as any).canvas = mockCanvas
// Mock createBounds
vi.mocked(createBounds).mockReturnValue([0, 0, 100, 100])
})
afterEach(() => {
;(comfyApp as any).canvas = null
})
describe('autoScaleLayoutSetting disabled', () => {
it('should return early when autoScaleLayoutSetting is false', () => {
mockSettingStore.get.mockReturnValue(false)
ensureCorrectLayoutScale('LG', mockGraph)
expect(createBounds).not.toHaveBeenCalled()
expect(mockGraph.extra.workflowRendererVersion).toBeUndefined()
})
it('should return early when autoScaleLayoutSetting is undefined', () => {
mockSettingStore.get.mockReturnValue(undefined)
ensureCorrectLayoutScale('LG', mockGraph)
expect(createBounds).not.toHaveBeenCalled()
})
it('should return early when autoScaleLayoutSetting is null', () => {
mockSettingStore.get.mockReturnValue(null)
ensureCorrectLayoutScale('LG', mockGraph)
expect(createBounds).not.toHaveBeenCalled()
})
})
describe('invalid graph scenarios', () => {
it('should return early when graph is null', () => {
ensureCorrectLayoutScale('LG', null as any)
expect(createBounds).not.toHaveBeenCalled()
})
it('should return early when graph is undefined', () => {
ensureCorrectLayoutScale('LG', undefined as any)
expect(createBounds).not.toHaveBeenCalled()
})
it('should return early when graph has no nodes', () => {
mockGraph.nodes = null as any
ensureCorrectLayoutScale('LG', mockGraph)
expect(createBounds).not.toHaveBeenCalled()
})
it('should return early when graph has empty nodes array', () => {
mockGraph.nodes = []
ensureCorrectLayoutScale('LG', mockGraph)
expect(createBounds).toHaveBeenCalled()
expect(createBounds).toHaveReturnedWith(null)
})
it('should return early when createBounds returns null', () => {
mockGraph.nodes = [
{ id: 1, pos: [10, 10], size: [100, 50], width: 100, height: 50 }
] as any
vi.mocked(createBounds).mockReturnValue(null)
ensureCorrectLayoutScale('LG', mockGraph)
expect(layoutStore.batchUpdateNodeBounds).not.toHaveBeenCalled()
})
})
describe('default renderer parameter', () => {
it('should default to LG when no renderer is provided', () => {
mockGraph.nodes = [
{ id: 1, pos: [10, 10], size: [100, 50], width: 100, height: 50 }
] as any
ensureCorrectLayoutScale(undefined as any, mockGraph)
expect(mockGraph.extra.workflowRendererVersion).toBe('LG')
})
it('should use provided renderer type when specified', () => {
mockGraph.nodes = [
{ id: 1, pos: [10, 10], size: [100, 50], width: 100, height: 50 }
] as any
vi.mocked(useVueFeatureFlags).mockReturnValue({
shouldRenderVueNodes: { value: true }
} as any)
ensureCorrectLayoutScale('Vue', mockGraph)
expect(mockGraph.extra.workflowRendererVersion).toBe('Vue')
})
})
describe('no scaling needed scenarios', () => {
it('should set workflowRendererVersion when LG renderer and Vue nodes disabled', () => {
mockGraph.nodes = [
{ id: 1, pos: [10, 10], size: [100, 50], width: 100, height: 50 }
] as any
vi.mocked(useVueFeatureFlags).mockReturnValue({
shouldRenderVueNodes: { value: false }
} as any)
ensureCorrectLayoutScale('LG', mockGraph)
expect(mockGraph.extra.workflowRendererVersion).toBe('LG')
expect(layoutStore.batchUpdateNodeBounds).not.toHaveBeenCalled()
})
it('should set workflowRendererVersion when Vue renderer and Vue nodes enabled', () => {
mockGraph.nodes = [
{ id: 1, pos: [10, 10], size: [100, 50], width: 100, height: 50 }
] as any
vi.mocked(useVueFeatureFlags).mockReturnValue({
shouldRenderVueNodes: { value: true }
} as any)
ensureCorrectLayoutScale('Vue', mockGraph)
expect(mockGraph.extra.workflowRendererVersion).toBe('Vue')
expect(layoutStore.batchUpdateNodeBounds).not.toHaveBeenCalled()
})
it('should not overwrite existing workflowRendererVersion when no scaling needed', () => {
mockGraph.nodes = [
{ id: 1, pos: [10, 10], size: [100, 50], width: 100, height: 50 }
] as any
mockGraph.extra.workflowRendererVersion = 'Vue'
vi.mocked(useVueFeatureFlags).mockReturnValue({
shouldRenderVueNodes: { value: true }
} as any)
ensureCorrectLayoutScale('Vue', mockGraph)
expect(mockGraph.extra.workflowRendererVersion).toBe('Vue')
})
})
describe('upscaling from LG to Vue', () => {
beforeEach(() => {
vi.mocked(useVueFeatureFlags).mockReturnValue({
shouldRenderVueNodes: { value: true }
} as any)
vi.mocked(createBounds).mockReturnValue([0, 0, 100, 100])
})
it('should scale node positions and sizes by SCALE_FACTOR (1.2)', () => {
const node = {
id: 1,
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
mockGraph.nodes = [node] as any
ensureCorrectLayoutScale('LG', mockGraph)
// Original position (100, 100) relative to origin (0, 0)
// Scaled by 1.2: (120, 120)
expect(node.pos[0]).toBeCloseTo(120, 5)
expect(node.pos[1]).toBeCloseTo(120, 5)
// Original size (200, 150) scaled by 1.2: (240, 180)
expect(node.size[0]).toBeCloseTo(240, 5)
expect(node.size[1]).toBeCloseTo(180, 5)
})
it('should adjust Y position accounting for NODE_TITLE_HEIGHT during upscaling', () => {
const titleHeight = LiteGraph.NODE_TITLE_HEIGHT
const node = {
id: 1,
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
mockGraph.nodes = [node] as any
ensureCorrectLayoutScale('LG', mockGraph)
// Y adjustment: (100 - titleHeight) scaled, then no additional adjustment
const expectedY = (100 - titleHeight) * 1.2
expect(node.pos[1]).toBeCloseTo(expectedY, 5)
})
it('should handle multiple nodes correctly', () => {
const nodes = [
{ id: 1, pos: [0, 0], size: [100, 100], width: 100, height: 100 },
{ id: 2, pos: [200, 200], size: [150, 100], width: 150, height: 100 },
{ id: 3, pos: [400, 100], size: [120, 80], width: 120, height: 80 }
]
mockGraph.nodes = nodes as any
vi.mocked(createBounds).mockReturnValue([0, 0, 520, 300])
ensureCorrectLayoutScale('LG', mockGraph)
// Verify all nodes were scaled
nodes.forEach((node) => {
expect(node.size[0]).toBeGreaterThan(100)
expect(node.size[1]).toBeGreaterThan(0)
})
})
it('should update layout store with batched node bounds for active graph', () => {
const node = {
id: 1,
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
mockGraph.nodes = [node] as any
mockCanvas.graph = mockGraph
ensureCorrectLayoutScale('LG', mockGraph)
expect(layoutStore.batchUpdateNodeBounds).toHaveBeenCalledTimes(1)
expect(layoutStore.batchUpdateNodeBounds).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({
nodeId: '1',
bounds: expect.objectContaining({
x: expect.any(Number),
y: expect.any(Number),
width: expect.any(Number),
height: expect.any(Number)
})
})
])
)
})
it('should not update layout store for non-active graphs', () => {
const node = {
id: 1,
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
const targetGraph = {
nodes: [node],
reroutes: new Map(),
groups: [],
extra: {}
} as any
ensureCorrectLayoutScale('LG', targetGraph)
expect(layoutStore.batchUpdateNodeBounds).not.toHaveBeenCalled()
})
it('should set workflowRendererVersion to Vue after upscaling', () => {
const node = {
id: 1,
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
mockGraph.nodes = [node] as any
ensureCorrectLayoutScale('LG', mockGraph)
expect(mockGraph.extra.workflowRendererVersion).toBe('Vue')
})
})
describe('downscaling from Vue to LG', () => {
beforeEach(() => {
vi.mocked(useVueFeatureFlags).mockReturnValue({
shouldRenderVueNodes: { value: false }
} as any)
vi.mocked(createBounds).mockReturnValue([0, 0, 120, 120])
})
it('should scale node positions and sizes by 1/SCALE_FACTOR (1/1.2)', () => {
const node = {
id: 1,
pos: [120, 120],
size: [240, 180],
width: 240,
height: 180
}
mockGraph.nodes = [node] as any
ensureCorrectLayoutScale('Vue', mockGraph)
// Scaled by 1/1.2: approximately (100, 100)
expect(node.pos[0]).toBeCloseTo(100, 5)
expect(node.pos[1]).toBeCloseTo(100, 5)
// Size scaled by 1/1.2 and adjusted for title height
expect(node.size[0]).toBeCloseTo(200, 5)
expect(node.size[1]).toBeCloseTo(150 - LiteGraph.NODE_TITLE_HEIGHT, 5)
})
it('should adjust Y position accounting for NODE_TITLE_HEIGHT during downscaling', () => {
const titleHeight = LiteGraph.NODE_TITLE_HEIGHT
const node = {
id: 1,
pos: [120, 120],
size: [240, 180],
width: 240,
height: 180
}
mockGraph.nodes = [node] as any
ensureCorrectLayoutScale('Vue', mockGraph)
// Y should be scaled down and then adjusted by title height
const expectedY = (120 / 1.2) + titleHeight
expect(node.pos[1]).toBeCloseTo(expectedY, 5)
})
it('should reduce height by NODE_TITLE_HEIGHT during downscaling', () => {
const titleHeight = LiteGraph.NODE_TITLE_HEIGHT
const node = {
id: 1,
pos: [120, 120],
size: [240, 180],
width: 240,
height: 180
}
mockGraph.nodes = [node] as any
ensureCorrectLayoutScale('Vue', mockGraph)
const expectedHeight = (180 / 1.2) - titleHeight
expect(node.size[1]).toBeCloseTo(expectedHeight, 5)
})
it('should set workflowRendererVersion to LG after downscaling', () => {
const node = {
id: 1,
pos: [120, 120],
size: [240, 180],
width: 240,
height: 180
}
mockGraph.nodes = [node] as any
ensureCorrectLayoutScale('Vue', mockGraph)
expect(mockGraph.extra.workflowRendererVersion).toBe('LG')
})
})
describe('reroute handling', () => {
beforeEach(() => {
vi.mocked(useVueFeatureFlags).mockReturnValue({
shouldRenderVueNodes: { value: true }
} as any)
})
it('should scale reroute positions during upscaling', () => {
const node = {
id: 1,
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
const reroute = { id: 'r1', pos: [150, 150] }
mockGraph.nodes = [node] as any
mockGraph.reroutes = new Map([['r1', reroute]]) as any
ensureCorrectLayoutScale('LG', mockGraph)
// Reroute position should be scaled: (150 * 1.2, 150 * 1.2) = (180, 180)
expect(reroute.pos[0]).toBeCloseTo(180, 5)
expect(reroute.pos[1]).toBeCloseTo(180, 5)
})
it('should call moveReroute for active graph with Vue nodes enabled', () => {
const node = {
id: 1,
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
const reroute = { id: 'r1', pos: [150, 150] }
mockGraph.nodes = [node] as any
mockGraph.reroutes = new Map([['r1', reroute]]) as any
mockCanvas.graph = mockGraph
ensureCorrectLayoutScale('LG', mockGraph)
expect(mockLayoutMutations.moveReroute).toHaveBeenCalledWith(
'r1',
expect.objectContaining({ x: expect.any(Number), y: expect.any(Number) }),
expect.objectContaining({ x: 150, y: 150 })
)
})
it('should not call moveReroute for non-active graph', () => {
const node = {
id: 1,
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
const reroute = { id: 'r1', pos: [150, 150] }
const targetGraph = {
nodes: [node],
reroutes: new Map([['r1', reroute]]),
groups: [],
extra: {}
} as any
ensureCorrectLayoutScale('LG', targetGraph)
expect(mockLayoutMutations.moveReroute).not.toHaveBeenCalled()
})
it('should not call moveReroute when Vue nodes are disabled', () => {
vi.mocked(useVueFeatureFlags).mockReturnValue({
shouldRenderVueNodes: { value: false }
} as any)
const node = {
id: 1,
pos: [120, 120],
size: [240, 180],
width: 240,
height: 180
}
const reroute = { id: 'r1', pos: [180, 180] }
mockGraph.nodes = [node] as any
mockGraph.reroutes = new Map([['r1', reroute]]) as any
mockCanvas.graph = mockGraph
ensureCorrectLayoutScale('Vue', mockGraph)
expect(mockLayoutMutations.moveReroute).not.toHaveBeenCalled()
})
it('should handle multiple reroutes correctly', () => {
const node = {
id: 1,
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
const reroutes = new Map([
['r1', { id: 'r1', pos: [150, 150] }],
['r2', { id: 'r2', pos: [200, 200] }],
['r3', { id: 'r3', pos: [250, 250] }]
])
mockGraph.nodes = [node] as any
mockGraph.reroutes = reroutes as any
ensureCorrectLayoutScale('LG', mockGraph)
// Verify all reroutes were scaled
reroutes.forEach((reroute) => {
expect(reroute.pos[0]).toBeGreaterThan(150)
expect(reroute.pos[1]).toBeGreaterThan(150)
})
})
})
describe('subgraph IO node handling', () => {
beforeEach(() => {
vi.mocked(useVueFeatureFlags).mockReturnValue({
shouldRenderVueNodes: { value: true }
} as any)
})
it('should scale input node position and size', () => {
const node = {
id: 1,
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
const inputNode = {
pos: [50, 50],
size: [100, 80]
}
mockGraph.nodes = [node] as any
mockGraph.inputNode = inputNode as any
ensureCorrectLayoutScale('LG', mockGraph)
expect(inputNode.pos[0]).toBeCloseTo(60, 5)
expect(inputNode.pos[1]).toBeCloseTo(60, 5)
expect(inputNode.size[0]).toBeCloseTo(120, 5)
expect(inputNode.size[1]).toBeCloseTo(96, 5)
})
it('should scale output node position and size', () => {
const node = {
id: 1,
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
const outputNode = {
pos: [300, 300],
size: [100, 80]
}
mockGraph.nodes = [node] as any
mockGraph.outputNode = outputNode as any
ensureCorrectLayoutScale('LG', mockGraph)
expect(outputNode.pos[0]).toBeCloseTo(360, 5)
expect(outputNode.pos[1]).toBeCloseTo(360, 5)
expect(outputNode.size[0]).toBeCloseTo(120, 5)
expect(outputNode.size[1]).toBeCloseTo(96, 5)
})
it('should scale both input and output nodes when present', () => {
const node = {
id: 1,
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
const inputNode = {
pos: [50, 50],
size: [100, 80]
}
const outputNode = {
pos: [300, 300],
size: [100, 80]
}
mockGraph.nodes = [node] as any
mockGraph.inputNode = inputNode as any
mockGraph.outputNode = outputNode as any
ensureCorrectLayoutScale('LG', mockGraph)
expect(inputNode.pos[0]).toBeCloseTo(60, 5)
expect(outputNode.pos[0]).toBeCloseTo(360, 5)
})
it('should handle missing input node gracefully', () => {
const node = {
id: 1,
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
const outputNode = {
pos: [300, 300],
size: [100, 80]
}
mockGraph.nodes = [node] as any
mockGraph.inputNode = null
mockGraph.outputNode = outputNode as any
expect(() => ensureCorrectLayoutScale('LG', mockGraph)).not.toThrow()
expect(outputNode.pos[0]).toBeCloseTo(360, 5)
})
it('should handle missing output node gracefully', () => {
const node = {
id: 1,
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
const inputNode = {
pos: [50, 50],
size: [100, 80]
}
mockGraph.nodes = [node] as any
mockGraph.inputNode = inputNode as any
mockGraph.outputNode = null
expect(() => ensureCorrectLayoutScale('LG', mockGraph)).not.toThrow()
expect(inputNode.pos[0]).toBeCloseTo(60, 5)
})
})
describe('group handling', () => {
beforeEach(() => {
vi.mocked(useVueFeatureFlags).mockReturnValue({
shouldRenderVueNodes: { value: true }
} as any)
})
it('should scale group positions and sizes during upscaling', () => {
const node = {
id: 1,
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
const group = {
pos: [80, 80],
size: [250, 200]
}
mockGraph.nodes = [node] as any
mockGraph.groups = [group] as any
ensureCorrectLayoutScale('LG', mockGraph)
expect(group.pos[0]).toBeCloseTo(96, 5)
expect(group.size[0]).toBeCloseTo(300, 5)
expect(group.size[1]).toBeCloseTo(240, 5)
})
it('should adjust group Y position for NODE_TITLE_HEIGHT during upscaling', () => {
const titleHeight = LiteGraph.NODE_TITLE_HEIGHT
const node = {
id: 1,
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
const group = {
pos: [80, 100],
size: [250, 200]
}
mockGraph.nodes = [node] as any
mockGraph.groups = [group] as any
ensureCorrectLayoutScale('LG', mockGraph)
// Y adjustment: (100 - titleHeight) scaled, then no additional offset
const expectedY = (100 - titleHeight) * 1.2
expect(group.pos[1]).toBeCloseTo(expectedY, 5)
})
it('should scale multiple groups correctly', () => {
const node = {
id: 1,
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
const groups = [
{ pos: [0, 0], size: [300, 300] },
{ pos: [200, 200], size: [150, 150] },
{ pos: [400, 100], size: [200, 250] }
]
mockGraph.nodes = [node] as any
mockGraph.groups = groups as any
ensureCorrectLayoutScale('LG', mockGraph)
// Verify all groups were scaled
groups.forEach((group) => {
expect(group.size[0]).toBeGreaterThan(150)
expect(group.size[1]).toBeGreaterThan(150)
})
})
it('should handle empty groups array', () => {
const node = {
id: 1,
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
mockGraph.nodes = [node] as any
mockGraph.groups = []
expect(() => ensureCorrectLayoutScale('LG', mockGraph)).not.toThrow()
})
it('should adjust group positions during downscaling', () => {
vi.mocked(useVueFeatureFlags).mockReturnValue({
shouldRenderVueNodes: { value: false }
} as any)
const node = {
id: 1,
pos: [120, 120],
size: [240, 180],
width: 240,
height: 180
}
const group = {
pos: [96, 96],
size: [300, 240]
}
mockGraph.nodes = [node] as any
mockGraph.groups = [group] as any
ensureCorrectLayoutScale('Vue', mockGraph)
expect(group.pos[0]).toBeCloseTo(80, 5)
expect(group.size[0]).toBeCloseTo(250, 5)
})
})
describe('canvas scale adjustment', () => {
beforeEach(() => {
vi.mocked(useVueFeatureFlags).mockReturnValue({
shouldRenderVueNodes: { value: true }
} as any)
})
it('should call changeScale on canvas during upscaling', () => {
const node = {
id: 1,
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
mockGraph.nodes = [node] as any
mockCanvas.graph = mockGraph
mockCanvas.ds.scale = 1.0
mockCanvas.ds.convertOffsetToCanvas.mockReturnValue([0, 0])
ensureCorrectLayoutScale('LG', mockGraph)
expect(mockCanvas.ds.changeScale).toHaveBeenCalledWith(
1.0 / 1.2,
[0, 0]
)
})
it('should call changeScale on canvas during downscaling', () => {
vi.mocked(useVueFeatureFlags).mockReturnValue({
shouldRenderVueNodes: { value: false }
} as any)
const node = {
id: 1,
pos: [120, 120],
size: [240, 180],
width: 240,
height: 180
}
mockGraph.nodes = [node] as any
mockCanvas.graph = mockGraph
mockCanvas.ds.scale = 0.833333
mockCanvas.ds.convertOffsetToCanvas.mockReturnValue([0, 0])
ensureCorrectLayoutScale('Vue', mockGraph)
expect(mockCanvas.ds.changeScale).toHaveBeenCalledWith(
expect.any(Number),
[0, 0]
)
})
it('should not call changeScale for non-active graph', () => {
const node = {
id: 1,
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
const targetGraph = {
nodes: [node],
reroutes: new Map(),
groups: [],
extra: {}
} as any
ensureCorrectLayoutScale('LG', targetGraph)
expect(mockCanvas.ds.changeScale).not.toHaveBeenCalled()
})
it('should not call changeScale when canvas is null', () => {
;(comfyApp as any).canvas = null
const node = {
id: 1,
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
mockGraph.nodes = [node] as any
expect(() => ensureCorrectLayoutScale('LG', mockGraph)).not.toThrow()
})
it('should use correct origin screen position for scale adjustment', () => {
const node = {
id: 1,
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
mockGraph.nodes = [node] as any
mockCanvas.graph = mockGraph
vi.mocked(createBounds).mockReturnValue([50, 60, 300, 250])
mockCanvas.ds.convertOffsetToCanvas.mockReturnValue([100, 120])
ensureCorrectLayoutScale('LG', mockGraph)
expect(mockCanvas.ds.convertOffsetToCanvas).toHaveBeenCalledWith([50, 60])
expect(mockCanvas.ds.changeScale).toHaveBeenCalledWith(
expect.any(Number),
[100, 120]
)
})
})
describe('edge cases and boundary conditions', () => {
beforeEach(() => {
vi.mocked(useVueFeatureFlags).mockReturnValue({
shouldRenderVueNodes: { value: true }
} as any)
})
it('should handle nodes at origin (0, 0)', () => {
const node = {
id: 1,
pos: [0, 0],
size: [100, 100],
width: 100,
height: 100
}
mockGraph.nodes = [node] as any
vi.mocked(createBounds).mockReturnValue([0, 0, 100, 100])
ensureCorrectLayoutScale('LG', mockGraph)
expect(node.pos[0]).toBeCloseTo(0, 5)
expect(node.size[0]).toBeCloseTo(120, 5)
})
it('should handle negative positions', () => {
const node = {
id: 1,
pos: [-100, -100],
size: [200, 150],
width: 200,
height: 150
}
mockGraph.nodes = [node] as any
vi.mocked(createBounds).mockReturnValue([-100, -100, 100, 50])
ensureCorrectLayoutScale('LG', mockGraph)
expect(node.pos[0]).toBeLessThan(0)
expect(node.pos[1]).toBeLessThan(0)
})
it('should handle very small node sizes', () => {
const node = {
id: 1,
pos: [100, 100],
size: [1, 1],
width: 1,
height: 1
}
mockGraph.nodes = [node] as any
ensureCorrectLayoutScale('LG', mockGraph)
expect(node.size[0]).toBeCloseTo(1.2, 5)
expect(node.size[1]).toBeCloseTo(1.2, 5)
})
it('should handle very large node sizes', () => {
const node = {
id: 1,
pos: [100, 100],
size: [10000, 5000],
width: 10000,
height: 5000
}
mockGraph.nodes = [node] as any
ensureCorrectLayoutScale('LG', mockGraph)
expect(node.size[0]).toBeCloseTo(12000, 5)
expect(node.size[1]).toBeCloseTo(6000, 5)
})
it('should handle nodes with fractional positions', () => {
const node = {
id: 1,
pos: [100.5, 100.7],
size: [200.3, 150.9],
width: 200.3,
height: 150.9
}
mockGraph.nodes = [node] as any
ensureCorrectLayoutScale('LG', mockGraph)
expect(node.pos[0]).toBeCloseTo(120.6, 5)
expect(node.size[0]).toBeCloseTo(240.36, 5)
})
it('should handle node with id of 0', () => {
const node = {
id: 0,
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
mockGraph.nodes = [node] as any
expect(() => ensureCorrectLayoutScale('LG', mockGraph)).not.toThrow()
expect(layoutStore.batchUpdateNodeBounds).toHaveBeenCalled()
})
it('should handle string node IDs', () => {
const node = {
id: 'node-123',
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
mockGraph.nodes = [node] as any
expect(() => ensureCorrectLayoutScale('LG', mockGraph)).not.toThrow()
})
})
describe('RendererType type usage', () => {
it('should accept valid RendererType values', () => {
const node = {
id: 1,
pos: [100, 100],
size: [200, 150],
width: 200,
height: 150
}
mockGraph.nodes = [node] as any
const lgRenderer: RendererType = 'LG'
const vueRenderer: RendererType = 'Vue'
expect(() => ensureCorrectLayoutScale(lgRenderer, mockGraph)).not.toThrow()
expect(() => ensureCorrectLayoutScale(vueRenderer, mockGraph)).not.toThrow()
})
})
describe('integration scenarios', () => {
it('should handle a complete workflow upscaling', () => {
vi.mocked(useVueFeatureFlags).mockReturnValue({
shouldRenderVueNodes: { value: true }
} as any)
const nodes = [
{ id: 1, pos: [100, 100], size: [200, 150], width: 200, height: 150 },
{ id: 2, pos: [400, 100], size: [150, 100], width: 150, height: 100 }
]
const reroutes = new Map([
['r1', { id: 'r1', pos: [300, 150] }]
])
const groups = [{ pos: [50, 50], size: [500, 300] }]
const inputNode = { pos: [0, 100], size: [100, 80] }
mockGraph.nodes = nodes as any
mockGraph.reroutes = reroutes as any
mockGraph.groups = groups as any
mockGraph.inputNode = inputNode as any
mockCanvas.graph = mockGraph
ensureCorrectLayoutScale('LG', mockGraph)
// Verify all elements were scaled
expect(nodes[0].size[0]).toBeCloseTo(240, 5)
expect(nodes[1].size[0]).toBeCloseTo(180, 5)
expect(reroutes.get('r1')?.pos[0]).toBeCloseTo(360, 5)
expect(groups[0].size[0]).toBeCloseTo(600, 5)
expect(inputNode.size[0]).toBeCloseTo(120, 5)
expect(mockGraph.extra.workflowRendererVersion).toBe('Vue')
expect(layoutStore.batchUpdateNodeBounds).toHaveBeenCalled()
expect(mockCanvas.ds.changeScale).toHaveBeenCalled()
})
it('should handle a complete workflow downscaling', () => {
vi.mocked(useVueFeatureFlags).mockReturnValue({
shouldRenderVueNodes: { value: false }
} as any)
const nodes = [
{ id: 1, pos: [120, 120], size: [240, 180], width: 240, height: 180 },
{ id: 2, pos: [480, 120], size: [180, 120], width: 180, height: 120 }
]
const reroutes = new Map([
['r1', { id: 'r1', pos: [360, 180] }]
])
const groups = [{ pos: [60, 60], size: [600, 360] }]
mockGraph.nodes = nodes as any
mockGraph.reroutes = reroutes as any
mockGraph.groups = groups as any
mockCanvas.graph = mockGraph
ensureCorrectLayoutScale('Vue', mockGraph)
// Verify all elements were scaled down
expect(nodes[0].size[0]).toBeCloseTo(200, 5)
expect(nodes[1].size[0]).toBeCloseTo(150, 5)
expect(reroutes.get('r1')?.pos[0]).toBeCloseTo(300, 5)
expect(groups[0].size[0]).toBeCloseTo(500, 5)
expect(mockGraph.extra.workflowRendererVersion).toBe('LG')
})
})
})