mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-06 16:10:09 +00:00
* move ref initialization to the component * remove redundant init * [refactor] Move minimap to domain-driven renderer structure - Create new src/renderer/extensions/minimap/ structure following domain-driven design - Add composables: useMinimapGraph, useMinimapViewport, useMinimapRenderer, useMinimapInteraction, useMinimapSettings - Add minimapCanvasRenderer with efficient batched rendering - Add comprehensive type definitions in types.ts - Remove old src/composables/useMinimap.ts composable - Implement proper separation of concerns with dedicated composables for each domain The new structure provides cleaner APIs, better performance through batched rendering, and improved maintainability through domain separation. * [test] Fix minimap tests for new renderer structure - Update all test imports to use new renderer paths - Fix mock implementations to match new composable APIs - Add proper RAF mocking for throttled functions - Fix type assertions to handle strict TypeScript checks - Update test expectations for new implementation behavior - Fix viewport transform calculations in tests - Handle async/throttled behavior correctly in tests All 28 minimap tests now passing with new architecture. * [fix] Remove unused init import in MiniMap component * [refactor] Move useWorkflowThumbnail to renderer/thumbnail structure - Moved useWorkflowThumbnail from src/composables to src/renderer/thumbnail/composables - Updated all imports in components, stores and services - Moved test file to match new structure - This ensures all rendering-related composables live in the renderer directory * [test] Fix minimap canvas renderer test for connections - Fixed mock setup for graph links to match LiteGraph's hybrid Map/Object structure - LiteGraph expects links to be accessible both as a Map and as an object - Test now properly verifies connection rendering functionality
325 lines
8.4 KiB
TypeScript
325 lines
8.4 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import { LGraphEventMode } from '@/lib/litegraph/src/litegraph'
|
|
import type { LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
|
import { renderMinimapToCanvas } from '@/renderer/extensions/minimap/minimapCanvasRenderer'
|
|
import type { MinimapRenderContext } from '@/renderer/extensions/minimap/types'
|
|
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
|
import { adjustColor } from '@/utils/colorUtil'
|
|
|
|
vi.mock('@/stores/workspace/colorPaletteStore')
|
|
vi.mock('@/utils/colorUtil', () => ({
|
|
adjustColor: vi.fn((color: string) => color + '_adjusted')
|
|
}))
|
|
|
|
describe('minimapCanvasRenderer', () => {
|
|
let mockCanvas: HTMLCanvasElement
|
|
let mockContext: CanvasRenderingContext2D
|
|
let mockGraph: LGraph
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
|
|
mockContext = {
|
|
clearRect: vi.fn(),
|
|
fillRect: vi.fn(),
|
|
strokeRect: vi.fn(),
|
|
beginPath: vi.fn(),
|
|
moveTo: vi.fn(),
|
|
lineTo: vi.fn(),
|
|
stroke: vi.fn(),
|
|
arc: vi.fn(),
|
|
fill: vi.fn(),
|
|
fillStyle: '',
|
|
strokeStyle: '',
|
|
lineWidth: 1
|
|
} as any
|
|
|
|
mockCanvas = {
|
|
getContext: vi.fn().mockReturnValue(mockContext)
|
|
} as any
|
|
|
|
mockGraph = {
|
|
_nodes: [
|
|
{
|
|
id: '1',
|
|
pos: [100, 100],
|
|
size: [150, 80],
|
|
bgcolor: '#FF0000',
|
|
mode: LGraphEventMode.ALWAYS,
|
|
has_errors: false,
|
|
outputs: []
|
|
},
|
|
{
|
|
id: '2',
|
|
pos: [300, 200],
|
|
size: [120, 60],
|
|
bgcolor: '#00FF00',
|
|
mode: LGraphEventMode.BYPASS,
|
|
has_errors: true,
|
|
outputs: []
|
|
}
|
|
] as unknown as LGraphNode[],
|
|
_groups: [],
|
|
links: {},
|
|
getNodeById: vi.fn()
|
|
} as any
|
|
|
|
vi.mocked(useColorPaletteStore).mockReturnValue({
|
|
completedActivePalette: { light_theme: false }
|
|
} as any)
|
|
})
|
|
|
|
it('should clear canvas and render nodes', () => {
|
|
const context: MinimapRenderContext = {
|
|
bounds: { minX: 0, minY: 0, width: 500, height: 400 },
|
|
scale: 0.5,
|
|
settings: {
|
|
nodeColors: true,
|
|
showLinks: false,
|
|
showGroups: false,
|
|
renderBypass: true,
|
|
renderError: true
|
|
},
|
|
width: 250,
|
|
height: 200
|
|
}
|
|
|
|
renderMinimapToCanvas(mockCanvas, mockGraph, context)
|
|
|
|
// Should clear the canvas first
|
|
expect(mockContext.clearRect).toHaveBeenCalledWith(0, 0, 250, 200)
|
|
|
|
// Should render nodes (batch by color)
|
|
expect(mockContext.fillRect).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should handle empty graph', () => {
|
|
mockGraph._nodes = []
|
|
|
|
const context: MinimapRenderContext = {
|
|
bounds: { minX: 0, minY: 0, width: 500, height: 400 },
|
|
scale: 0.5,
|
|
settings: {
|
|
nodeColors: true,
|
|
showLinks: false,
|
|
showGroups: false,
|
|
renderBypass: false,
|
|
renderError: false
|
|
},
|
|
width: 250,
|
|
height: 200
|
|
}
|
|
|
|
renderMinimapToCanvas(mockCanvas, mockGraph, context)
|
|
|
|
expect(mockContext.clearRect).toHaveBeenCalledWith(0, 0, 250, 200)
|
|
expect(mockContext.fillRect).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should batch render nodes by color', () => {
|
|
const context: MinimapRenderContext = {
|
|
bounds: { minX: 0, minY: 0, width: 500, height: 400 },
|
|
scale: 0.5,
|
|
settings: {
|
|
nodeColors: true,
|
|
showLinks: false,
|
|
showGroups: false,
|
|
renderBypass: false,
|
|
renderError: false
|
|
},
|
|
width: 250,
|
|
height: 200
|
|
}
|
|
|
|
renderMinimapToCanvas(mockCanvas, mockGraph, context)
|
|
|
|
// Should set fill style for each color group
|
|
const fillStyleCalls = []
|
|
let currentStyle = ''
|
|
|
|
mockContext.fillStyle = ''
|
|
Object.defineProperty(mockContext, 'fillStyle', {
|
|
get: () => currentStyle,
|
|
set: (value) => {
|
|
currentStyle = value
|
|
fillStyleCalls.push(value)
|
|
}
|
|
})
|
|
|
|
renderMinimapToCanvas(mockCanvas, mockGraph, context)
|
|
|
|
// Different colors for different nodes
|
|
expect(fillStyleCalls.length).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('should render bypass nodes with special color', () => {
|
|
const context: MinimapRenderContext = {
|
|
bounds: { minX: 0, minY: 0, width: 500, height: 400 },
|
|
scale: 0.5,
|
|
settings: {
|
|
nodeColors: true,
|
|
showLinks: false,
|
|
showGroups: false,
|
|
renderBypass: true,
|
|
renderError: false
|
|
},
|
|
width: 250,
|
|
height: 200
|
|
}
|
|
|
|
renderMinimapToCanvas(mockCanvas, mockGraph, context)
|
|
|
|
// Node 2 is in bypass mode, should be rendered
|
|
expect(mockContext.fillRect).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should render error outlines when enabled', () => {
|
|
const context: MinimapRenderContext = {
|
|
bounds: { minX: 0, minY: 0, width: 500, height: 400 },
|
|
scale: 0.5,
|
|
settings: {
|
|
nodeColors: true,
|
|
showLinks: false,
|
|
showGroups: false,
|
|
renderBypass: false,
|
|
renderError: true
|
|
},
|
|
width: 250,
|
|
height: 200
|
|
}
|
|
|
|
renderMinimapToCanvas(mockCanvas, mockGraph, context)
|
|
|
|
// Should set stroke style for errors
|
|
expect(mockContext.strokeStyle).toBe('#FF0000')
|
|
expect(mockContext.strokeRect).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should render groups when enabled', () => {
|
|
mockGraph._groups = [
|
|
{
|
|
pos: [50, 50],
|
|
size: [400, 300],
|
|
color: '#0000FF'
|
|
}
|
|
] as any
|
|
|
|
const context: MinimapRenderContext = {
|
|
bounds: { minX: 0, minY: 0, width: 500, height: 400 },
|
|
scale: 0.5,
|
|
settings: {
|
|
nodeColors: true,
|
|
showLinks: false,
|
|
showGroups: true,
|
|
renderBypass: false,
|
|
renderError: false
|
|
},
|
|
width: 250,
|
|
height: 200
|
|
}
|
|
|
|
renderMinimapToCanvas(mockCanvas, mockGraph, context)
|
|
|
|
// Groups should be rendered before nodes
|
|
expect(mockContext.fillRect).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should render connections when enabled', () => {
|
|
const targetNode = {
|
|
id: '2',
|
|
pos: [300, 200],
|
|
size: [120, 60]
|
|
}
|
|
|
|
mockGraph._nodes[0].outputs = [
|
|
{
|
|
links: [1]
|
|
}
|
|
] as any
|
|
|
|
// Create a hybrid Map/Object for links as LiteGraph expects
|
|
const linksMap = new Map([[1, { id: 1, target_id: 2 }]])
|
|
const links = Object.assign(linksMap, {
|
|
1: { id: 1, target_id: 2 }
|
|
})
|
|
mockGraph.links = links as any
|
|
|
|
mockGraph.getNodeById = vi.fn().mockReturnValue(targetNode)
|
|
|
|
const context: MinimapRenderContext = {
|
|
bounds: { minX: 0, minY: 0, width: 500, height: 400 },
|
|
scale: 0.5,
|
|
settings: {
|
|
nodeColors: false,
|
|
showLinks: true,
|
|
showGroups: false,
|
|
renderBypass: false,
|
|
renderError: false
|
|
},
|
|
width: 250,
|
|
height: 200
|
|
}
|
|
|
|
renderMinimapToCanvas(mockCanvas, mockGraph, context)
|
|
|
|
// Should draw connection lines
|
|
expect(mockContext.beginPath).toHaveBeenCalled()
|
|
expect(mockContext.moveTo).toHaveBeenCalled()
|
|
expect(mockContext.lineTo).toHaveBeenCalled()
|
|
expect(mockContext.stroke).toHaveBeenCalled()
|
|
|
|
// Should draw connection slots
|
|
expect(mockContext.arc).toHaveBeenCalled()
|
|
expect(mockContext.fill).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should handle light theme colors', () => {
|
|
vi.mocked(useColorPaletteStore).mockReturnValue({
|
|
completedActivePalette: { light_theme: true }
|
|
} as any)
|
|
|
|
const context: MinimapRenderContext = {
|
|
bounds: { minX: 0, minY: 0, width: 500, height: 400 },
|
|
scale: 0.5,
|
|
settings: {
|
|
nodeColors: true,
|
|
showLinks: false,
|
|
showGroups: false,
|
|
renderBypass: false,
|
|
renderError: false
|
|
},
|
|
width: 250,
|
|
height: 200
|
|
}
|
|
|
|
renderMinimapToCanvas(mockCanvas, mockGraph, context)
|
|
|
|
// Color adjustment should be called for light theme
|
|
expect(adjustColor).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should calculate correct offsets for centering', () => {
|
|
const context: MinimapRenderContext = {
|
|
bounds: { minX: 0, minY: 0, width: 200, height: 100 },
|
|
scale: 0.5,
|
|
settings: {
|
|
nodeColors: false,
|
|
showLinks: false,
|
|
showGroups: false,
|
|
renderBypass: false,
|
|
renderError: false
|
|
},
|
|
width: 250,
|
|
height: 200
|
|
}
|
|
|
|
renderMinimapToCanvas(mockCanvas, mockGraph, context)
|
|
|
|
// With bounds 200x100 at scale 0.5 = 100x50
|
|
// Canvas is 250x200, so offset should be (250-100)/2 = 75, (200-50)/2 = 75
|
|
// This affects node positioning
|
|
expect(mockContext.fillRect).toHaveBeenCalled()
|
|
})
|
|
})
|