mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-04 05:02:17 +00:00
feat: Add more Storybook stories for UI components
This commit is contained in:
@@ -1,38 +1,14 @@
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { computed, toValue } from 'vue'
|
||||
import type { ComponentProps } from 'vue-component-type-helpers'
|
||||
import { computed, ref } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||
import { SelectedNodeIdsKey } from '@/renderer/core/canvas/injectionKeys'
|
||||
import LGraphNode from '@/renderer/extensions/vueNodes/components/LGraphNode.vue'
|
||||
import { useNodeEventHandlers } from '@/renderer/extensions/vueNodes/composables/useNodeEventHandlers'
|
||||
import { useVueElementTracking } from '@/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking'
|
||||
|
||||
const mockData = vi.hoisted(() => ({
|
||||
mockNodeIds: new Set<string>(),
|
||||
mockExecuting: false
|
||||
}))
|
||||
|
||||
vi.mock('@/renderer/core/canvas/canvasStore', () => {
|
||||
const getCanvas = vi.fn()
|
||||
const useCanvasStore = () => ({
|
||||
getCanvas,
|
||||
selectedNodeIds: computed(() => mockData.mockNodeIds)
|
||||
})
|
||||
return {
|
||||
useCanvasStore
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock(
|
||||
'@/renderer/extensions/vueNodes/composables/useNodeEventHandlers',
|
||||
() => {
|
||||
const handleNodeSelect = vi.fn()
|
||||
return { useNodeEventHandlers: () => ({ handleNodeSelect }) }
|
||||
}
|
||||
)
|
||||
import { useNodeExecutionState } from '@/renderer/extensions/vueNodes/execution/useNodeExecutionState'
|
||||
|
||||
vi.mock(
|
||||
'@/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking',
|
||||
@@ -50,7 +26,6 @@ vi.mock('@/composables/useErrorHandling', () => ({
|
||||
vi.mock('@/renderer/extensions/vueNodes/layout/useNodeLayout', () => ({
|
||||
useNodeLayout: () => ({
|
||||
position: { x: 100, y: 50 },
|
||||
size: { width: 200, height: 100 },
|
||||
startDrag: vi.fn(),
|
||||
handleDrag: vi.fn(),
|
||||
endDrag: vi.fn()
|
||||
@@ -72,7 +47,7 @@ vi.mock(
|
||||
'@/renderer/extensions/vueNodes/execution/useNodeExecutionState',
|
||||
() => ({
|
||||
useNodeExecutionState: vi.fn(() => ({
|
||||
executing: computed(() => mockData.mockExecuting),
|
||||
executing: computed(() => false),
|
||||
progress: computed(() => undefined),
|
||||
progressPercentage: computed(() => undefined),
|
||||
progressState: computed(() => undefined as any),
|
||||
@@ -81,13 +56,6 @@ vi.mock(
|
||||
})
|
||||
)
|
||||
|
||||
vi.mock('@/renderer/extensions/vueNodes/preview/useNodePreviewState', () => ({
|
||||
useNodePreviewState: vi.fn(() => ({
|
||||
latestPreviewUrl: computed(() => ''),
|
||||
shouldShowPreviewImg: computed(() => false)
|
||||
}))
|
||||
}))
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
@@ -97,56 +65,61 @@ const i18n = createI18n({
|
||||
}
|
||||
}
|
||||
})
|
||||
function mountLGraphNode(props: ComponentProps<typeof LGraphNode>) {
|
||||
return mount(LGraphNode, {
|
||||
props,
|
||||
global: {
|
||||
plugins: [
|
||||
createTestingPinia({
|
||||
createSpy: vi.fn
|
||||
}),
|
||||
i18n
|
||||
],
|
||||
stubs: {
|
||||
NodeHeader: true,
|
||||
NodeSlots: true,
|
||||
NodeWidgets: true,
|
||||
NodeContent: true,
|
||||
SlotConnectionDot: true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
const mockNodeData: VueNodeData = {
|
||||
id: 'test-node-123',
|
||||
title: 'Test Node',
|
||||
type: 'TestNode',
|
||||
mode: 0,
|
||||
flags: {},
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
widgets: [],
|
||||
selected: false,
|
||||
executing: false
|
||||
}
|
||||
|
||||
describe('LGraphNode', () => {
|
||||
const mockNodeData: VueNodeData = {
|
||||
id: 'test-node-123',
|
||||
title: 'Test Node',
|
||||
type: 'TestNode',
|
||||
mode: 0,
|
||||
flags: {},
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
widgets: [],
|
||||
selected: false,
|
||||
executing: false
|
||||
}
|
||||
|
||||
const mountLGraphNode = (props: any, selectedNodeIds = new Set()) => {
|
||||
return mount(LGraphNode, {
|
||||
props,
|
||||
global: {
|
||||
plugins: [
|
||||
createTestingPinia({
|
||||
createSpy: vi.fn
|
||||
}),
|
||||
i18n
|
||||
],
|
||||
provide: {
|
||||
[SelectedNodeIdsKey as symbol]: ref(selectedNodeIds)
|
||||
},
|
||||
stubs: {
|
||||
NodeHeader: true,
|
||||
NodeSlots: true,
|
||||
NodeWidgets: true,
|
||||
NodeContent: true,
|
||||
SlotConnectionDot: true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks()
|
||||
mockData.mockNodeIds = new Set()
|
||||
mockData.mockExecuting = false
|
||||
vi.clearAllMocks()
|
||||
// Reset to default mock
|
||||
vi.mocked(useNodeExecutionState).mockReturnValue({
|
||||
executing: computed(() => false),
|
||||
progress: computed(() => undefined),
|
||||
progressPercentage: computed(() => undefined),
|
||||
progressState: computed(() => undefined as any),
|
||||
executionState: computed(() => 'idle' as const)
|
||||
})
|
||||
})
|
||||
|
||||
it('should call resize tracking composable with node ID', () => {
|
||||
mountLGraphNode({ nodeData: mockNodeData })
|
||||
|
||||
expect(useVueElementTracking).toHaveBeenCalledWith(
|
||||
expect.any(Function),
|
||||
'node'
|
||||
)
|
||||
const idArg = vi.mocked(useVueElementTracking).mock.calls[0]?.[0]
|
||||
const id = toValue(idArg)
|
||||
expect(id).toEqual('test-node-123')
|
||||
expect(useVueElementTracking).toHaveBeenCalledWith('test-node-123', 'node')
|
||||
})
|
||||
|
||||
it('should render with data-node-id attribute', () => {
|
||||
@@ -166,6 +139,9 @@ describe('LGraphNode', () => {
|
||||
}),
|
||||
i18n
|
||||
],
|
||||
provide: {
|
||||
[SelectedNodeIdsKey as symbol]: ref(new Set())
|
||||
},
|
||||
stubs: {
|
||||
NodeSlots: true,
|
||||
NodeWidgets: true,
|
||||
@@ -179,15 +155,24 @@ describe('LGraphNode', () => {
|
||||
})
|
||||
|
||||
it('should apply selected styling when selected prop is true', () => {
|
||||
mockData.mockNodeIds = new Set(['test-node-123'])
|
||||
const wrapper = mountLGraphNode({ nodeData: mockNodeData })
|
||||
const wrapper = mountLGraphNode(
|
||||
{ nodeData: mockNodeData, selected: true },
|
||||
new Set(['test-node-123'])
|
||||
)
|
||||
expect(wrapper.classes()).toContain('outline-2')
|
||||
expect(wrapper.classes()).toContain('outline-black')
|
||||
expect(wrapper.classes()).toContain('dark-theme:outline-white')
|
||||
})
|
||||
|
||||
it('should apply executing animation when executing prop is true', () => {
|
||||
mockData.mockExecuting = true
|
||||
// Mock the execution state to return executing: true
|
||||
vi.mocked(useNodeExecutionState).mockReturnValue({
|
||||
executing: computed(() => true),
|
||||
progress: computed(() => undefined),
|
||||
progressPercentage: computed(() => undefined),
|
||||
progressState: computed(() => undefined as any),
|
||||
executionState: computed(() => 'running' as const)
|
||||
})
|
||||
|
||||
const wrapper = mountLGraphNode({ nodeData: mockNodeData })
|
||||
|
||||
@@ -195,16 +180,12 @@ describe('LGraphNode', () => {
|
||||
})
|
||||
|
||||
it('should emit node-click event on pointer up', async () => {
|
||||
const { handleNodeSelect } = useNodeEventHandlers()
|
||||
const wrapper = mountLGraphNode({ nodeData: mockNodeData })
|
||||
|
||||
await wrapper.trigger('pointerup')
|
||||
|
||||
expect(handleNodeSelect).toHaveBeenCalledOnce()
|
||||
expect(handleNodeSelect).toHaveBeenCalledWith(
|
||||
expect.any(PointerEvent),
|
||||
mockNodeData,
|
||||
expect.any(Boolean)
|
||||
)
|
||||
expect(wrapper.emitted('node-click')).toHaveLength(1)
|
||||
expect(wrapper.emitted('node-click')?.[0]).toHaveLength(3)
|
||||
expect(wrapper.emitted('node-click')?.[0][1]).toEqual(mockNodeData)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,223 +0,0 @@
|
||||
/**
|
||||
* Tests for NodeHeader subgraph functionality
|
||||
*/
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||
import NodeHeader from '@/renderer/extensions/vueNodes/components/NodeHeader.vue'
|
||||
import { getNodeByLocatorId } from '@/utils/graphTraversalUtil'
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('@/scripts/app', () => ({
|
||||
app: {
|
||||
graph: null as any
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/utils/graphTraversalUtil', () => ({
|
||||
getNodeByLocatorId: vi.fn(),
|
||||
getLocatorIdFromNodeData: vi.fn((nodeData) =>
|
||||
nodeData.subgraphId
|
||||
? `${nodeData.subgraphId}:${String(nodeData.id)}`
|
||||
: String(nodeData.id)
|
||||
)
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/useErrorHandling', () => ({
|
||||
useErrorHandling: () => ({
|
||||
toastErrorHandler: vi.fn()
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('vue-i18n', () => ({
|
||||
useI18n: () => ({
|
||||
t: vi.fn((key) => key)
|
||||
}),
|
||||
createI18n: vi.fn(() => ({
|
||||
global: {
|
||||
t: vi.fn((key) => key)
|
||||
}
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('@/i18n', () => ({
|
||||
st: vi.fn((key) => key),
|
||||
t: vi.fn((key) => key),
|
||||
i18n: {
|
||||
global: {
|
||||
t: vi.fn((key) => key)
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
describe('NodeHeader - Subgraph Functionality', () => {
|
||||
// Helper to setup common mocks
|
||||
const setupMocks = async (isSubgraph = true, hasGraph = true) => {
|
||||
const { app } = await import('@/scripts/app')
|
||||
|
||||
if (hasGraph) {
|
||||
;(app as any).graph = { rootGraph: {} }
|
||||
} else {
|
||||
;(app as any).graph = null
|
||||
}
|
||||
|
||||
vi.mocked(getNodeByLocatorId).mockReturnValue({
|
||||
isSubgraphNode: () => isSubgraph
|
||||
} as any)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
const createMockNodeData = (
|
||||
id: string,
|
||||
subgraphId?: string
|
||||
): VueNodeData => ({
|
||||
id,
|
||||
title: 'Test Node',
|
||||
type: 'TestNode',
|
||||
mode: 0,
|
||||
selected: false,
|
||||
executing: false,
|
||||
subgraphId,
|
||||
widgets: [],
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
hasErrors: false,
|
||||
flags: {}
|
||||
})
|
||||
|
||||
const createWrapper = (props = {}) => {
|
||||
return mount(NodeHeader, {
|
||||
props,
|
||||
global: {
|
||||
plugins: [createTestingPinia({ createSpy: vi.fn })],
|
||||
mocks: {
|
||||
$t: vi.fn((key: string) => key),
|
||||
$primevue: { config: {} }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
it('should show subgraph button for subgraph nodes', async () => {
|
||||
await setupMocks(true) // isSubgraph = true
|
||||
|
||||
const wrapper = createWrapper({
|
||||
nodeData: createMockNodeData('test-node-1'),
|
||||
readonly: false
|
||||
})
|
||||
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const subgraphButton = wrapper.find('[data-testid="subgraph-enter-button"]')
|
||||
expect(subgraphButton.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('should not show subgraph button for regular nodes', async () => {
|
||||
await setupMocks(false) // isSubgraph = false
|
||||
|
||||
const wrapper = createWrapper({
|
||||
nodeData: createMockNodeData('test-node-1'),
|
||||
readonly: false
|
||||
})
|
||||
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const subgraphButton = wrapper.find('[data-testid="subgraph-enter-button"]')
|
||||
expect(subgraphButton.exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('should not show subgraph button in readonly mode', async () => {
|
||||
await setupMocks(true) // isSubgraph = true
|
||||
|
||||
const wrapper = createWrapper({
|
||||
nodeData: createMockNodeData('test-node-1'),
|
||||
readonly: true
|
||||
})
|
||||
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const subgraphButton = wrapper.find('[data-testid="subgraph-enter-button"]')
|
||||
expect(subgraphButton.exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('should emit enter-subgraph event when button is clicked', async () => {
|
||||
await setupMocks(true) // isSubgraph = true
|
||||
|
||||
const wrapper = createWrapper({
|
||||
nodeData: createMockNodeData('test-node-1'),
|
||||
readonly: false
|
||||
})
|
||||
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const subgraphButton = wrapper.find('[data-testid="subgraph-enter-button"]')
|
||||
await subgraphButton.trigger('click')
|
||||
|
||||
expect(wrapper.emitted('enter-subgraph')).toBeTruthy()
|
||||
expect(wrapper.emitted('enter-subgraph')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('should handle subgraph context correctly', async () => {
|
||||
await setupMocks(true) // isSubgraph = true
|
||||
|
||||
const wrapper = createWrapper({
|
||||
nodeData: createMockNodeData('test-node-1', 'subgraph-id'),
|
||||
readonly: false
|
||||
})
|
||||
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
// Should call getNodeByLocatorId with correct locator ID
|
||||
expect(vi.mocked(getNodeByLocatorId)).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
'subgraph-id:test-node-1'
|
||||
)
|
||||
|
||||
const subgraphButton = wrapper.find('[data-testid="subgraph-enter-button"]')
|
||||
expect(subgraphButton.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('should handle missing graph gracefully', async () => {
|
||||
await setupMocks(true, false) // isSubgraph = true, hasGraph = false
|
||||
|
||||
const wrapper = createWrapper({
|
||||
nodeData: createMockNodeData('test-node-1'),
|
||||
readonly: false
|
||||
})
|
||||
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const subgraphButton = wrapper.find('[data-testid="subgraph-enter-button"]')
|
||||
expect(subgraphButton.exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('should prevent event propagation on double click', async () => {
|
||||
await setupMocks(true) // isSubgraph = true
|
||||
|
||||
const wrapper = createWrapper({
|
||||
nodeData: createMockNodeData('test-node-1'),
|
||||
readonly: false
|
||||
})
|
||||
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const subgraphButton = wrapper.find('[data-testid="subgraph-enter-button"]')
|
||||
|
||||
// Mock event object
|
||||
const mockEvent = {
|
||||
stopPropagation: vi.fn()
|
||||
}
|
||||
|
||||
// Trigger dblclick event
|
||||
await subgraphButton.trigger('dblclick', mockEvent)
|
||||
|
||||
// Should prevent propagation (handled by @dblclick.stop directive)
|
||||
// This is tested by ensuring the component doesn't error and renders correctly
|
||||
expect(subgraphButton.exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -1,82 +1,79 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { computed, shallowRef } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import {
|
||||
type GraphNodeManager,
|
||||
type VueNodeData,
|
||||
useGraphNodeManager
|
||||
} from '@/composables/graph/useGraphNodeManager'
|
||||
import { useVueNodeLifecycle } from '@/composables/graph/useVueNodeLifecycle'
|
||||
import type {
|
||||
LGraph,
|
||||
LGraphCanvas,
|
||||
LGraphNode
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||
import { useGraphNodeManager } from '@/composables/graph/useGraphNodeManager'
|
||||
import type { LGraphCanvas, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
|
||||
import { useNodeEventHandlers } from '@/renderer/extensions/vueNodes/composables/useNodeEventHandlers'
|
||||
|
||||
vi.mock('@/renderer/core/canvas/canvasStore', () => {
|
||||
const canvas: Partial<LGraphCanvas> = {
|
||||
vi.mock('@/renderer/core/canvas/canvasStore', () => ({
|
||||
useCanvasStore: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/renderer/core/layout/operations/layoutMutations', () => ({
|
||||
useLayoutMutations: vi.fn()
|
||||
}))
|
||||
|
||||
function createMockCanvas(): Pick<
|
||||
LGraphCanvas,
|
||||
'select' | 'deselect' | 'deselectAll'
|
||||
> {
|
||||
return {
|
||||
select: vi.fn(),
|
||||
deselect: vi.fn(),
|
||||
deselectAll: vi.fn()
|
||||
}
|
||||
const updateSelectedItems = vi.fn()
|
||||
}
|
||||
|
||||
function createMockNode(): Pick<LGraphNode, 'id' | 'selected' | 'flags'> {
|
||||
return {
|
||||
useCanvasStore: vi.fn(() => ({
|
||||
canvas: canvas as LGraphCanvas,
|
||||
updateSelectedItems,
|
||||
selectedItems: []
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/renderer/core/canvas/useCanvasInteractions', () => ({
|
||||
useCanvasInteractions: vi.fn(() => ({
|
||||
shouldHandleNodePointerEvents: computed(() => true) // Default to allowing pointer events
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('@/renderer/core/layout/operations/layoutMutations', () => {
|
||||
const setSource = vi.fn()
|
||||
const bringNodeToFront = vi.fn()
|
||||
return {
|
||||
useLayoutMutations: vi.fn(() => ({
|
||||
setSource,
|
||||
bringNodeToFront
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/composables/graph/useGraphNodeManager', () => {
|
||||
const mockNode = {
|
||||
id: 'node-1',
|
||||
selected: false,
|
||||
flags: { pinned: false }
|
||||
}
|
||||
const nodeManager = shallowRef({
|
||||
getNode: vi.fn(() => mockNode as Partial<LGraphNode> as LGraphNode)
|
||||
} as Partial<GraphNodeManager> as GraphNodeManager)
|
||||
return {
|
||||
useGraphNodeManager: vi.fn(() => nodeManager)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
vi.mock('@/composables/graph/useVueNodeLifecycle', () => {
|
||||
const nodeManager = useGraphNodeManager(undefined as unknown as LGraph)
|
||||
function createMockNodeManager(
|
||||
node: Pick<LGraphNode, 'id' | 'selected' | 'flags'>
|
||||
) {
|
||||
return {
|
||||
useVueNodeLifecycle: vi.fn(() => ({
|
||||
nodeManager
|
||||
}))
|
||||
getNode: vi.fn().mockReturnValue(node) as ReturnType<
|
||||
typeof useGraphNodeManager
|
||||
>['getNode']
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function createMockCanvasStore(
|
||||
canvas: Pick<LGraphCanvas, 'select' | 'deselect' | 'deselectAll'>
|
||||
): Pick<
|
||||
ReturnType<typeof useCanvasStore>,
|
||||
'canvas' | 'selectedItems' | 'updateSelectedItems'
|
||||
> {
|
||||
return {
|
||||
canvas: canvas as LGraphCanvas,
|
||||
selectedItems: [],
|
||||
updateSelectedItems: vi.fn()
|
||||
}
|
||||
}
|
||||
|
||||
function createMockLayoutMutations(): Pick<
|
||||
ReturnType<typeof useLayoutMutations>,
|
||||
'setSource' | 'bringNodeToFront'
|
||||
> {
|
||||
return {
|
||||
setSource: vi.fn(),
|
||||
bringNodeToFront: vi.fn()
|
||||
}
|
||||
}
|
||||
|
||||
describe('useNodeEventHandlers', () => {
|
||||
const { nodeManager: mockNodeManager } = useVueNodeLifecycle()
|
||||
|
||||
const mockNode = mockNodeManager.value!.getNode('fake_id')
|
||||
const mockLayoutMutations = useLayoutMutations()
|
||||
let mockCanvas: ReturnType<typeof createMockCanvas>
|
||||
let mockNode: ReturnType<typeof createMockNode>
|
||||
let mockNodeManager: ReturnType<typeof createMockNodeManager>
|
||||
let mockCanvasStore: ReturnType<typeof createMockCanvasStore>
|
||||
let mockLayoutMutations: ReturnType<typeof createMockLayoutMutations>
|
||||
|
||||
const testNodeData: VueNodeData = {
|
||||
id: 'node-1',
|
||||
@@ -88,13 +85,24 @@ describe('useNodeEventHandlers', () => {
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.restoreAllMocks()
|
||||
mockNode = createMockNode()
|
||||
mockCanvas = createMockCanvas()
|
||||
mockNodeManager = createMockNodeManager(mockNode)
|
||||
mockCanvasStore = createMockCanvasStore(mockCanvas)
|
||||
mockLayoutMutations = createMockLayoutMutations()
|
||||
|
||||
vi.mocked(useCanvasStore).mockReturnValue(
|
||||
mockCanvasStore as ReturnType<typeof useCanvasStore>
|
||||
)
|
||||
vi.mocked(useLayoutMutations).mockReturnValue(
|
||||
mockLayoutMutations as ReturnType<typeof useLayoutMutations>
|
||||
)
|
||||
})
|
||||
|
||||
describe('handleNodeSelect', () => {
|
||||
it('should select single node on regular click', () => {
|
||||
const { handleNodeSelect } = useNodeEventHandlers()
|
||||
const { canvas, updateSelectedItems } = useCanvasStore()
|
||||
const nodeManager = ref(mockNodeManager)
|
||||
const { handleNodeSelect } = useNodeEventHandlers(nodeManager)
|
||||
|
||||
const event = new PointerEvent('pointerdown', {
|
||||
bubbles: true,
|
||||
@@ -104,17 +112,17 @@ describe('useNodeEventHandlers', () => {
|
||||
|
||||
handleNodeSelect(event, testNodeData, false)
|
||||
|
||||
expect(canvas?.deselectAll).toHaveBeenCalledOnce()
|
||||
expect(canvas?.select).toHaveBeenCalledWith(mockNode)
|
||||
expect(updateSelectedItems).toHaveBeenCalledOnce()
|
||||
expect(mockCanvas.deselectAll).toHaveBeenCalledOnce()
|
||||
expect(mockCanvas.select).toHaveBeenCalledWith(mockNode)
|
||||
expect(mockCanvasStore.updateSelectedItems).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('should toggle selection on ctrl+click', () => {
|
||||
const { handleNodeSelect } = useNodeEventHandlers()
|
||||
const { canvas } = useCanvasStore()
|
||||
const nodeManager = ref(mockNodeManager)
|
||||
const { handleNodeSelect } = useNodeEventHandlers(nodeManager)
|
||||
|
||||
// Test selecting unselected node with ctrl
|
||||
mockNode!.selected = false
|
||||
mockNode.selected = false
|
||||
|
||||
const ctrlClickEvent = new PointerEvent('pointerdown', {
|
||||
bubbles: true,
|
||||
@@ -124,16 +132,16 @@ describe('useNodeEventHandlers', () => {
|
||||
|
||||
handleNodeSelect(ctrlClickEvent, testNodeData, false)
|
||||
|
||||
expect(canvas?.deselectAll).not.toHaveBeenCalled()
|
||||
expect(canvas?.select).toHaveBeenCalledWith(mockNode)
|
||||
expect(mockCanvas.deselectAll).not.toHaveBeenCalled()
|
||||
expect(mockCanvas.select).toHaveBeenCalledWith(mockNode)
|
||||
})
|
||||
|
||||
it('should deselect on ctrl+click of selected node', () => {
|
||||
const { handleNodeSelect } = useNodeEventHandlers()
|
||||
const { canvas } = useCanvasStore()
|
||||
const nodeManager = ref(mockNodeManager)
|
||||
const { handleNodeSelect } = useNodeEventHandlers(nodeManager)
|
||||
|
||||
// Test deselecting selected node with ctrl
|
||||
mockNode!.selected = true
|
||||
mockNode.selected = true
|
||||
|
||||
const ctrlClickEvent = new PointerEvent('pointerdown', {
|
||||
bubbles: true,
|
||||
@@ -143,15 +151,15 @@ describe('useNodeEventHandlers', () => {
|
||||
|
||||
handleNodeSelect(ctrlClickEvent, testNodeData, false)
|
||||
|
||||
expect(canvas?.deselect).toHaveBeenCalledWith(mockNode)
|
||||
expect(canvas?.select).not.toHaveBeenCalled()
|
||||
expect(mockCanvas.deselect).toHaveBeenCalledWith(mockNode)
|
||||
expect(mockCanvas.select).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should handle meta key (Cmd) on Mac', () => {
|
||||
const { handleNodeSelect } = useNodeEventHandlers()
|
||||
const { canvas } = useCanvasStore()
|
||||
const nodeManager = ref(mockNodeManager)
|
||||
const { handleNodeSelect } = useNodeEventHandlers(nodeManager)
|
||||
|
||||
mockNode!.selected = false
|
||||
mockNode.selected = false
|
||||
|
||||
const metaClickEvent = new PointerEvent('pointerdown', {
|
||||
bubbles: true,
|
||||
@@ -161,14 +169,15 @@ describe('useNodeEventHandlers', () => {
|
||||
|
||||
handleNodeSelect(metaClickEvent, testNodeData, false)
|
||||
|
||||
expect(canvas?.select).toHaveBeenCalledWith(mockNode)
|
||||
expect(canvas?.deselectAll).not.toHaveBeenCalled()
|
||||
expect(mockCanvas.select).toHaveBeenCalledWith(mockNode)
|
||||
expect(mockCanvas.deselectAll).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should bring node to front when not pinned', () => {
|
||||
const { handleNodeSelect } = useNodeEventHandlers()
|
||||
const nodeManager = ref(mockNodeManager)
|
||||
const { handleNodeSelect } = useNodeEventHandlers(nodeManager)
|
||||
|
||||
mockNode!.flags.pinned = false
|
||||
mockNode.flags.pinned = false
|
||||
|
||||
const event = new PointerEvent('pointerdown')
|
||||
handleNodeSelect(event, testNodeData, false)
|
||||
@@ -179,14 +188,49 @@ describe('useNodeEventHandlers', () => {
|
||||
})
|
||||
|
||||
it('should not bring pinned node to front', () => {
|
||||
const { handleNodeSelect } = useNodeEventHandlers()
|
||||
const nodeManager = ref(mockNodeManager)
|
||||
const { handleNodeSelect } = useNodeEventHandlers(nodeManager)
|
||||
|
||||
mockNode!.flags.pinned = true
|
||||
mockNode.flags.pinned = true
|
||||
|
||||
const event = new PointerEvent('pointerdown')
|
||||
handleNodeSelect(event, testNodeData, false)
|
||||
|
||||
expect(mockLayoutMutations.bringNodeToFront).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should handle missing canvas gracefully', () => {
|
||||
const nodeManager = ref(mockNodeManager)
|
||||
const { handleNodeSelect } = useNodeEventHandlers(nodeManager)
|
||||
|
||||
mockCanvasStore.canvas = null
|
||||
|
||||
const event = new PointerEvent('pointerdown')
|
||||
expect(() => {
|
||||
handleNodeSelect(event, testNodeData, false)
|
||||
}).not.toThrow()
|
||||
|
||||
expect(mockCanvas.select).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should handle missing node gracefully', () => {
|
||||
const nodeManager = ref(mockNodeManager)
|
||||
const { handleNodeSelect } = useNodeEventHandlers(nodeManager)
|
||||
|
||||
vi.mocked(mockNodeManager.getNode).mockReturnValue(undefined)
|
||||
|
||||
const event = new PointerEvent('pointerdown')
|
||||
const nodeData = {
|
||||
id: 'missing-node',
|
||||
title: 'Missing Node',
|
||||
type: 'test'
|
||||
} as any
|
||||
|
||||
expect(() => {
|
||||
handleNodeSelect(event, nodeData, false)
|
||||
}).not.toThrow()
|
||||
|
||||
expect(mockCanvas.select).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,7 +2,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
import { useAssetBrowserDialog } from '@/platform/assets/composables/useAssetBrowserDialog'
|
||||
import { assetService } from '@/platform/assets/services/assetService'
|
||||
import { useComboWidget } from '@/renderer/extensions/vueNodes/widgets/composables/useComboWidget'
|
||||
import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
@@ -30,25 +29,13 @@ vi.mock('@/platform/assets/services/assetService', () => ({
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/platform/assets/composables/useAssetBrowserDialog', () => {
|
||||
const mockAssetBrowserDialogShow = vi.fn()
|
||||
return {
|
||||
useAssetBrowserDialog: vi.fn(() => ({
|
||||
show: mockAssetBrowserDialogShow
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
// Test factory functions
|
||||
function createMockWidget(overrides: Partial<IBaseWidget> = {}): IBaseWidget {
|
||||
const mockCallback = vi.fn()
|
||||
return {
|
||||
type: 'combo',
|
||||
options: {},
|
||||
name: 'testWidget',
|
||||
value: undefined,
|
||||
callback: mockCallback,
|
||||
y: 0,
|
||||
...overrides
|
||||
} as IBaseWidget
|
||||
}
|
||||
@@ -58,16 +45,7 @@ function createMockNode(comfyClass = 'TestNode'): LGraphNode {
|
||||
node.comfyClass = comfyClass
|
||||
|
||||
// Spy on the addWidget method
|
||||
vi.spyOn(node, 'addWidget').mockImplementation(
|
||||
(type, name, value, callback) => {
|
||||
const widget = createMockWidget({ type, name, value })
|
||||
// Store the callback function on the widget for testing
|
||||
if (typeof callback === 'function') {
|
||||
widget.callback = callback
|
||||
}
|
||||
return widget
|
||||
}
|
||||
)
|
||||
vi.spyOn(node, 'addWidget').mockReturnValue(createMockWidget())
|
||||
|
||||
return node
|
||||
}
|
||||
@@ -83,9 +61,9 @@ function createMockInputSpec(overrides: Partial<InputSpec> = {}): InputSpec {
|
||||
describe('useComboWidget', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
// Reset to defaults
|
||||
mockSettingStoreGet.mockReturnValue(false)
|
||||
vi.mocked(assetService.isAssetBrowserEligible).mockReturnValue(false)
|
||||
vi.mocked(useAssetBrowserDialog).mockClear()
|
||||
})
|
||||
|
||||
it('should handle undefined spec', () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import axios from 'axios'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { useRemoteWidget } from '@/renderer/extensions/vueNodes/widgets/composables/useRemoteWidget'
|
||||
import type { RemoteWidgetConfig } from '@/schemas/nodeDefSchema'
|
||||
import { RemoteWidgetConfig } from '@/schemas/nodeDefSchema'
|
||||
|
||||
vi.mock('axios', () => {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user