mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-14 01:20:03 +00:00
## Summary Added visual indicators (colored borders) to the MiniMap to display the real-time execution status (running, executed, or error) of nodes. ## Changes - **What**: Added visual feedback to the MiniMap to show node execution states (green for running/executed, red for errors) by integrating with `useExecutionStore` and updating the canvas renderer. ## Review Focus Confirmed that relying on the array `.includes()` check for `executingNodeIds` in the data sources avoids unnecessary `Set` allocations during frequent redraws. ## Screenshots <img width="540" height="446" alt="14949d48035db5c64cceb11f7f7f94a3" src="https://github.com/user-attachments/assets/cac53a80-9882-43fd-a725-7003fe3fd21a" /> <img width="562" height="464" alt="7e922f54dea2cea4e6b66202d2ad0dd3" src="https://github.com/user-attachments/assets/e178b981-3af0-417f-8e21-a706f192fabf" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9187-feat-minimap-add-node-execution-status-visualization-3126d73d3650816eb7b3ca415cf6a8f1) by [Unito](https://www.unito.io)
183 lines
5.4 KiB
TypeScript
183 lines
5.4 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest'
|
|
import { computed } from 'vue'
|
|
import type { ComputedRef } from 'vue'
|
|
|
|
import type { NodeId } from '@/lib/litegraph/src/LGraphNode'
|
|
import type { LGraph, LGraphNode, LLink } from '@/lib/litegraph/src/litegraph'
|
|
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
|
|
import type { NodeLayout } from '@/renderer/core/layout/types'
|
|
import { MinimapDataSourceFactory } from '@/renderer/extensions/minimap/data/MinimapDataSourceFactory'
|
|
|
|
// Mock layoutStore
|
|
vi.mock('@/renderer/core/layout/store/layoutStore', () => ({
|
|
layoutStore: {
|
|
getAllNodes: vi.fn()
|
|
}
|
|
}))
|
|
|
|
// Mock useExecutionStore
|
|
vi.mock('@/stores/executionStore', () => ({
|
|
useExecutionStore: vi.fn().mockReturnValue({
|
|
nodeProgressStates: {}
|
|
})
|
|
}))
|
|
|
|
// Helper to create mock links that satisfy LGraph['links'] type
|
|
function createMockLinks(): LGraph['links'] {
|
|
const map = new Map<number, LLink>()
|
|
return Object.assign(map, {}) as LGraph['links']
|
|
}
|
|
|
|
describe('MinimapDataSource', () => {
|
|
describe('MinimapDataSourceFactory', () => {
|
|
it('should create LayoutStoreDataSource when LayoutStore has data', () => {
|
|
// Arrange
|
|
const mockNodes = new Map<string, NodeLayout>([
|
|
[
|
|
'node1',
|
|
{
|
|
id: 'node1',
|
|
position: { x: 0, y: 0 },
|
|
size: { width: 100, height: 50 },
|
|
zIndex: 0,
|
|
visible: true,
|
|
bounds: { x: 0, y: 0, width: 100, height: 50 }
|
|
}
|
|
]
|
|
])
|
|
|
|
// Create a computed ref that returns the map
|
|
const computedNodes: ComputedRef<ReadonlyMap<string, NodeLayout>> =
|
|
computed(() => mockNodes)
|
|
vi.mocked(layoutStore.getAllNodes).mockReturnValue(computedNodes)
|
|
|
|
const mockGraph: Pick<LGraph, '_nodes' | '_groups' | 'links'> = {
|
|
_nodes: [],
|
|
_groups: [],
|
|
links: createMockLinks()
|
|
}
|
|
|
|
// Act
|
|
const dataSource = MinimapDataSourceFactory.create(mockGraph as LGraph)
|
|
|
|
// Assert
|
|
expect(dataSource).toBeDefined()
|
|
expect(dataSource.hasData()).toBe(true)
|
|
expect(dataSource.getNodeCount()).toBe(1)
|
|
})
|
|
|
|
it('should create LiteGraphDataSource when LayoutStore is empty', () => {
|
|
// Arrange
|
|
const emptyMap = new Map<string, NodeLayout>()
|
|
const computedEmpty: ComputedRef<ReadonlyMap<string, NodeLayout>> =
|
|
computed(() => emptyMap)
|
|
vi.mocked(layoutStore.getAllNodes).mockReturnValue(computedEmpty)
|
|
|
|
const mockNode: Pick<
|
|
LGraphNode,
|
|
'id' | 'pos' | 'size' | 'bgcolor' | 'mode' | 'has_errors' | 'outputs'
|
|
> = {
|
|
id: 'node1' as NodeId,
|
|
pos: [0, 0],
|
|
size: [100, 50],
|
|
bgcolor: '#fff',
|
|
mode: 0,
|
|
has_errors: false,
|
|
outputs: []
|
|
}
|
|
|
|
const mockGraph: Pick<LGraph, '_nodes' | '_groups' | 'links'> = {
|
|
_nodes: [mockNode as LGraphNode],
|
|
_groups: [],
|
|
links: createMockLinks()
|
|
}
|
|
|
|
// Act
|
|
const dataSource = MinimapDataSourceFactory.create(mockGraph as LGraph)
|
|
|
|
// Assert
|
|
expect(dataSource).toBeDefined()
|
|
expect(dataSource.hasData()).toBe(true)
|
|
expect(dataSource.getNodeCount()).toBe(1)
|
|
|
|
const nodes = dataSource.getNodes()
|
|
expect(nodes).toHaveLength(1)
|
|
expect(nodes[0]).toMatchObject({
|
|
id: 'node1',
|
|
x: 0,
|
|
y: 0,
|
|
width: 100,
|
|
height: 50
|
|
})
|
|
})
|
|
|
|
it('should handle empty graph correctly', () => {
|
|
// Arrange
|
|
const emptyMap = new Map<string, NodeLayout>()
|
|
const computedEmpty: ComputedRef<ReadonlyMap<string, NodeLayout>> =
|
|
computed(() => emptyMap)
|
|
vi.mocked(layoutStore.getAllNodes).mockReturnValue(computedEmpty)
|
|
|
|
const mockGraph: Pick<LGraph, '_nodes' | '_groups' | 'links'> = {
|
|
_nodes: [],
|
|
_groups: [],
|
|
links: createMockLinks()
|
|
}
|
|
|
|
// Act
|
|
const dataSource = MinimapDataSourceFactory.create(mockGraph as LGraph)
|
|
|
|
// Assert
|
|
expect(dataSource.hasData()).toBe(false)
|
|
expect(dataSource.getNodeCount()).toBe(0)
|
|
expect(dataSource.getNodes()).toEqual([])
|
|
expect(dataSource.getLinks()).toEqual([])
|
|
expect(dataSource.getGroups()).toEqual([])
|
|
})
|
|
})
|
|
|
|
describe('Bounds calculation', () => {
|
|
it('should calculate correct bounds from nodes', () => {
|
|
// Arrange
|
|
const emptyMap = new Map<string, NodeLayout>()
|
|
const computedEmpty: ComputedRef<ReadonlyMap<string, NodeLayout>> =
|
|
computed(() => emptyMap)
|
|
vi.mocked(layoutStore.getAllNodes).mockReturnValue(computedEmpty)
|
|
|
|
const mockNode1: Pick<LGraphNode, 'id' | 'pos' | 'size' | 'outputs'> = {
|
|
id: 'node1' as NodeId,
|
|
pos: [0, 0],
|
|
size: [100, 50],
|
|
outputs: []
|
|
}
|
|
|
|
const mockNode2: Pick<LGraphNode, 'id' | 'pos' | 'size' | 'outputs'> = {
|
|
id: 'node2' as NodeId,
|
|
pos: [200, 100],
|
|
size: [150, 75],
|
|
outputs: []
|
|
}
|
|
|
|
const mockGraph: Pick<LGraph, '_nodes' | '_groups' | 'links'> = {
|
|
_nodes: [mockNode1 as LGraphNode, mockNode2 as LGraphNode],
|
|
_groups: [],
|
|
links: createMockLinks()
|
|
}
|
|
|
|
// Act
|
|
const dataSource = MinimapDataSourceFactory.create(mockGraph as LGraph)
|
|
const bounds = dataSource.getBounds()
|
|
|
|
// Assert
|
|
expect(bounds).toEqual({
|
|
minX: 0,
|
|
minY: 0,
|
|
maxX: 350,
|
|
maxY: 175,
|
|
width: 350,
|
|
height: 175
|
|
})
|
|
})
|
|
})
|
|
})
|