Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8c2250f
refactor: eliminate unsafe type assertions from Group 2 test files
Myestery Jan 23, 2026
6c566cf
[automated] Apply ESLint and Oxfmt fixes
actions-user Jan 23, 2026
4056f78
Updates: Model Management (#8248)
DrJKL Jan 23, 2026
8776790
Further number widget fixes (#8251)
AustinMroz Jan 23, 2026
dcaca49
refactor: improve TypeScript patterns in test files (Group 1/8) (#8253)
Myestery Jan 23, 2026
bea8138
feat: Add search_aliases to node search (#8223)
christian-byrne Jan 23, 2026
98b0d84
feat: active jobs context menu (#8216)
benceruleanlu Jan 23, 2026
f49e60f
Merge branch 'main' into refactor/cleanup-any-part8-group2
Myestery Jan 23, 2026
cf9be05
test: fix invalid type annotation in SelectionToolbox test
Myestery Jan 23, 2026
e09507e
test: make partial mock explicit in SelectionToolbox test
Myestery Jan 23, 2026
99a0600
test: use factory function for mock isolation in BypassButton test
Myestery Jan 23, 2026
710b22d
test: make partial mock explicit in ColorPickerButton test
Myestery Jan 23, 2026
6876627
test: remove misleading MockCommandStore type alias
Myestery Jan 23, 2026
d81fa7c
refactor: convert useChainCallback to function declaration
Myestery Jan 23, 2026
b357c78
test: replace unknown types with explicit mock interfaces
Myestery Jan 23, 2026
b3c3701
test: align nodeDefStoreMock with ComfyNodeDefImpl type
Myestery Jan 23, 2026
7676e83
test: refactor store mocks to use @pinia/testing
Myestery Jan 23, 2026
faa151a
test: remove unsafe null! assertions in useCanvasTools.test.ts
Myestery Jan 23, 2026
cc3968d
test: extract duplicated mock into createMockSettingStore helper
Myestery Jan 23, 2026
bae1f9a
test: make partial mock intent explicit in litegraphTestUtils
Myestery Jan 23, 2026
b8a142a
remove useless typing
Myestery Jan 23, 2026
2419950
test: use @pinia/testing in ExecuteButton.test.ts
Myestery Jan 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 75 additions & 72 deletions src/components/graph/SelectionToolbox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,30 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import { createI18n } from 'vue-i18n'

import SelectionToolbox from '@/components/graph/SelectionToolbox.vue'
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
import { useExtensionService } from '@/services/extensionService'
import { createMockPositionable } from '@/utils/__tests__/litegraphTestUtils'

function createMockCanvas(): typeof useCanvasStore.prototype.canvas {
return {
setDirty: vi.fn(),
state: {
selectionChanged: false
}
} as typeof useCanvasStore.prototype.canvas
}

function createMockExtensionService(): ReturnType<typeof useExtensionService> {
return {
extensionCommands: { value: new Map() },
loadExtensions: vi.fn(),
registerExtension: vi.fn(),
invokeExtensions: vi.fn(() => []),
invokeExtensionsAsync: vi.fn()
} as ReturnType<typeof useExtensionService>
}

// Mock the composables and services
vi.mock('@/renderer/core/canvas/useCanvasInteractions', () => ({
Expand Down Expand Up @@ -112,12 +133,7 @@ describe('SelectionToolbox', () => {
canvasStore = useCanvasStore()

// Mock the canvas to avoid "getCanvas: canvas is null" errors
canvasStore.canvas = {
setDirty: vi.fn(),
state: {
selectionChanged: false
}
} as any
canvasStore.canvas = createMockCanvas()

vi.resetAllMocks()
})
Expand Down Expand Up @@ -184,30 +200,27 @@ describe('SelectionToolbox', () => {
describe('Button Visibility Logic', () => {
beforeEach(() => {
const mockExtensionService = vi.mocked(useExtensionService)
mockExtensionService.mockReturnValue({
extensionCommands: { value: new Map() },
invokeExtensions: vi.fn(() => [])
} as any)
mockExtensionService.mockReturnValue(createMockExtensionService())
})

it('should show info button only for single selections', () => {
// Single node selection
canvasStore.selectedItems = [{ type: 'TestNode' }] as any
canvasStore.selectedItems = [createMockPositionable()]
const wrapper = mountComponent()
expect(wrapper.find('.info-button').exists()).toBe(true)

// Multiple node selection
canvasStore.selectedItems = [
{ type: 'TestNode1' },
{ type: 'TestNode2' }
] as any
createMockPositionable(),
createMockPositionable()
]
wrapper.unmount()
const wrapper2 = mountComponent()
expect(wrapper2.find('.info-button').exists()).toBe(false)
})

it('should not show info button when node definition is not found', () => {
canvasStore.selectedItems = [{ type: 'TestNode' }] as any
canvasStore.selectedItems = [createMockPositionable()]
// mock nodedef and return null
nodeDefMock = null
// remount component
Expand All @@ -217,17 +230,17 @@ describe('SelectionToolbox', () => {

it('should show color picker for all selections', () => {
// Single node selection
canvasStore.selectedItems = [{ type: 'TestNode' }] as any
canvasStore.selectedItems = [createMockPositionable()]
const wrapper = mountComponent()
expect(wrapper.find('[data-testid="color-picker-button"]').exists()).toBe(
true
)

// Multiple node selection
canvasStore.selectedItems = [
{ type: 'TestNode1' },
{ type: 'TestNode2' }
] as any
createMockPositionable(),
createMockPositionable()
]
wrapper.unmount()
const wrapper2 = mountComponent()
expect(
Expand All @@ -237,38 +250,38 @@ describe('SelectionToolbox', () => {

it('should show frame nodes only for multiple selections', () => {
// Single node selection
canvasStore.selectedItems = [{ type: 'TestNode' }] as any
canvasStore.selectedItems = [createMockPositionable()]
const wrapper = mountComponent()
expect(wrapper.find('.frame-nodes').exists()).toBe(false)

// Multiple node selection
canvasStore.selectedItems = [
{ type: 'TestNode1' },
{ type: 'TestNode2' }
] as any
createMockPositionable(),
createMockPositionable()
]
wrapper.unmount()
const wrapper2 = mountComponent()
expect(wrapper2.find('.frame-nodes').exists()).toBe(true)
})

it('should show bypass button for appropriate selections', () => {
// Single node selection
canvasStore.selectedItems = [{ type: 'TestNode' }] as any
canvasStore.selectedItems = [createMockPositionable()]
const wrapper = mountComponent()
expect(wrapper.find('[data-testid="bypass-button"]').exists()).toBe(true)

// Multiple node selection
canvasStore.selectedItems = [
{ type: 'TestNode1' },
{ type: 'TestNode2' }
] as any
createMockPositionable(),
createMockPositionable()
]
wrapper.unmount()
const wrapper2 = mountComponent()
expect(wrapper2.find('[data-testid="bypass-button"]').exists()).toBe(true)
})

it('should show common buttons for all selections', () => {
canvasStore.selectedItems = [{ type: 'TestNode' }] as any
canvasStore.selectedItems = [createMockPositionable()]
const wrapper = mountComponent()

expect(wrapper.find('[data-testid="delete-button"]').exists()).toBe(true)
Expand All @@ -286,13 +299,13 @@ describe('SelectionToolbox', () => {

// Single image node
isImageNodeSpy.mockReturnValue(true)
canvasStore.selectedItems = [{ type: 'ImageNode' }] as any
canvasStore.selectedItems = [createMockPositionable()]
const wrapper = mountComponent()
expect(wrapper.find('.mask-editor-button').exists()).toBe(true)

// Single non-image node
isImageNodeSpy.mockReturnValue(false)
canvasStore.selectedItems = [{ type: 'TestNode' }] as any
canvasStore.selectedItems = [createMockPositionable()]
wrapper.unmount()
const wrapper2 = mountComponent()
expect(wrapper2.find('.mask-editor-button').exists()).toBe(false)
Expand All @@ -304,13 +317,13 @@ describe('SelectionToolbox', () => {

// Single Load3D node
isLoad3dNodeSpy.mockReturnValue(true)
canvasStore.selectedItems = [{ type: 'Load3DNode' }] as any
canvasStore.selectedItems = [createMockPositionable()]
const wrapper = mountComponent()
expect(wrapper.find('.load-3d-viewer-button').exists()).toBe(true)

// Single non-Load3D node
isLoad3dNodeSpy.mockReturnValue(false)
canvasStore.selectedItems = [{ type: 'TestNode' }] as any
canvasStore.selectedItems = [createMockPositionable()]
wrapper.unmount()
const wrapper2 = mountComponent()
expect(wrapper2.find('.load-3d-viewer-button').exists()).toBe(false)
Expand All @@ -326,17 +339,17 @@ describe('SelectionToolbox', () => {

// With output node selected
isOutputNodeSpy.mockReturnValue(true)
filterOutputNodesSpy.mockReturnValue([{ type: 'SaveImage' }] as any)
canvasStore.selectedItems = [
{ type: 'SaveImage', constructor: { nodeData: { output_node: true } } }
] as any
filterOutputNodesSpy.mockReturnValue([
{ type: 'SaveImage' }
] as LGraphNode[])
canvasStore.selectedItems = [createMockPositionable()]
const wrapper = mountComponent()
expect(wrapper.find('.execute-button').exists()).toBe(true)

// Without output node selected
isOutputNodeSpy.mockReturnValue(false)
filterOutputNodesSpy.mockReturnValue([])
canvasStore.selectedItems = [{ type: 'TestNode' }] as any
canvasStore.selectedItems = [createMockPositionable()]
wrapper.unmount()
const wrapper2 = mountComponent()
expect(wrapper2.find('.execute-button').exists()).toBe(false)
Expand All @@ -352,7 +365,7 @@ describe('SelectionToolbox', () => {
describe('Divider Visibility Logic', () => {
it('should show dividers between button groups when both groups have buttons', () => {
// Setup single node to show info + other buttons
canvasStore.selectedItems = [{ type: 'TestNode' }] as any
canvasStore.selectedItems = [createMockPositionable()]
const wrapper = mountComponent()

const dividers = wrapper.findAll('.vertical-divider')
Expand All @@ -378,23 +391,23 @@ describe('SelectionToolbox', () => {
['test-command', { id: 'test-command', title: 'Test Command' }]
])
},
invokeExtensions: vi.fn(() => ['test-command'])
} as any)
loadExtensions: vi.fn(),
registerExtension: vi.fn(),
invokeExtensions: vi.fn(() => ['test-command']),
invokeExtensionsAsync: vi.fn()
} as ReturnType<typeof useExtensionService>)

canvasStore.selectedItems = [{ type: 'TestNode' }] as any
canvasStore.selectedItems = [createMockPositionable()]
const wrapper = mountComponent()

expect(wrapper.find('.extension-command-button').exists()).toBe(true)
})

it('should not render extension commands when none available', () => {
const mockExtensionService = vi.mocked(useExtensionService)
mockExtensionService.mockReturnValue({
extensionCommands: { value: new Map() },
invokeExtensions: vi.fn(() => [])
} as any)
mockExtensionService.mockReturnValue(createMockExtensionService())

canvasStore.selectedItems = [{ type: 'TestNode' }] as any
canvasStore.selectedItems = [createMockPositionable()]
const wrapper = mountComponent()

expect(wrapper.find('.extension-command-button').exists()).toBe(false)
Expand All @@ -404,12 +417,9 @@ describe('SelectionToolbox', () => {
describe('Container Styling', () => {
it('should apply minimap container styles', () => {
const mockExtensionService = vi.mocked(useExtensionService)
mockExtensionService.mockReturnValue({
extensionCommands: { value: new Map() },
invokeExtensions: vi.fn(() => [])
} as any)
mockExtensionService.mockReturnValue(createMockExtensionService())

canvasStore.selectedItems = [{ type: 'TestNode' }] as any
canvasStore.selectedItems = [createMockPositionable()]
const wrapper = mountComponent()

const panel = wrapper.find('.panel')
Expand All @@ -418,12 +428,9 @@ describe('SelectionToolbox', () => {

it('should have correct CSS classes', () => {
const mockExtensionService = vi.mocked(useExtensionService)
mockExtensionService.mockReturnValue({
extensionCommands: { value: new Map() },
invokeExtensions: vi.fn(() => [])
} as any)
mockExtensionService.mockReturnValue(createMockExtensionService())

canvasStore.selectedItems = [{ type: 'TestNode' }] as any
canvasStore.selectedItems = [createMockPositionable()]
const wrapper = mountComponent()

const panel = wrapper.find('.panel')
Expand All @@ -435,12 +442,9 @@ describe('SelectionToolbox', () => {

it('should handle animation class conditionally', () => {
const mockExtensionService = vi.mocked(useExtensionService)
mockExtensionService.mockReturnValue({
extensionCommands: { value: new Map() },
invokeExtensions: vi.fn(() => [])
} as any)
mockExtensionService.mockReturnValue(createMockExtensionService())

canvasStore.selectedItems = [{ type: 'TestNode' }] as any
canvasStore.selectedItems = [createMockPositionable()]
const wrapper = mountComponent()

const panel = wrapper.find('.panel')
Expand All @@ -453,16 +457,18 @@ describe('SelectionToolbox', () => {
const mockCanvasInteractions = vi.mocked(useCanvasInteractions)
const forwardEventToCanvasSpy = vi.fn()
mockCanvasInteractions.mockReturnValue({
forwardEventToCanvas: forwardEventToCanvasSpy
} as any)
handleWheel: vi.fn(),
handlePointer: vi.fn(),
forwardEventToCanvas: forwardEventToCanvasSpy,
shouldHandleNodePointerEvents: { value: true } as ReturnType<
typeof useCanvasInteractions
>['shouldHandleNodePointerEvents']
} as ReturnType<typeof useCanvasInteractions>)

const mockExtensionService = vi.mocked(useExtensionService)
mockExtensionService.mockReturnValue({
extensionCommands: { value: new Map() },
invokeExtensions: vi.fn(() => [])
} as any)
mockExtensionService.mockReturnValue(createMockExtensionService())

canvasStore.selectedItems = [{ type: 'TestNode' }] as any
canvasStore.selectedItems = [createMockPositionable()]
const wrapper = mountComponent()

const panel = wrapper.find('.panel')
Expand All @@ -475,10 +481,7 @@ describe('SelectionToolbox', () => {
describe('No Selection State', () => {
beforeEach(() => {
const mockExtensionService = vi.mocked(useExtensionService)
mockExtensionService.mockReturnValue({
extensionCommands: { value: new Map() },
invokeExtensions: vi.fn(() => [])
} as any)
mockExtensionService.mockReturnValue(createMockExtensionService())
})

it('should hide most buttons when no items selected', () => {
Expand Down
23 changes: 12 additions & 11 deletions src/components/graph/selectionToolbox/BypassButton.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import { createI18n } from 'vue-i18n'

import BypassButton from '@/components/graph/selectionToolbox/BypassButton.vue'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { LGraphEventMode } from '@/lib/litegraph/src/litegraph'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useCommandStore } from '@/stores/commandStore'
import { createMockLGraphNode } from '@/utils/__tests__/litegraphTestUtils'

const mockLGraphNode = {
type: 'TestNode',
title: 'Test Node',
mode: LGraphEventMode.ALWAYS
}
const mockLGraphNode = createMockLGraphNode({ type: 'TestNode' })

vi.mock('@/utils/litegraphUtil', () => ({
isLGraphNode: vi.fn(() => true)
Expand Down Expand Up @@ -59,21 +57,21 @@ describe('BypassButton', () => {
}

it('should render bypass button', () => {
canvasStore.selectedItems = [mockLGraphNode] as any
canvasStore.selectedItems = [mockLGraphNode]
const wrapper = mountComponent()
const button = wrapper.find('button')
expect(button.exists()).toBe(true)
})

it('should have correct test id', () => {
canvasStore.selectedItems = [mockLGraphNode] as any
canvasStore.selectedItems = [mockLGraphNode]
const wrapper = mountComponent()
const button = wrapper.find('[data-testid="bypass-button"]')
expect(button.exists()).toBe(true)
})

it('should execute bypass command when clicked', async () => {
canvasStore.selectedItems = [mockLGraphNode] as any
canvasStore.selectedItems = [mockLGraphNode]
const executeSpy = vi.spyOn(commandStore, 'execute').mockResolvedValue()

const wrapper = mountComponent()
Expand All @@ -85,8 +83,11 @@ describe('BypassButton', () => {
})

it('should show bypassed styling when node is bypassed', async () => {
const bypassedNode = { ...mockLGraphNode, mode: LGraphEventMode.BYPASS }
canvasStore.selectedItems = [bypassedNode] as any
const bypassedNode: Partial<LGraphNode> = {
...mockLGraphNode,
mode: LGraphEventMode.BYPASS
}
canvasStore.selectedItems = [bypassedNode as LGraphNode]
vi.spyOn(commandStore, 'execute').mockResolvedValue()
const wrapper = mountComponent()

Expand All @@ -100,7 +101,7 @@ describe('BypassButton', () => {

it('should handle multiple selected items', () => {
vi.spyOn(commandStore, 'execute').mockResolvedValue()
canvasStore.selectedItems = [mockLGraphNode, mockLGraphNode] as any
canvasStore.selectedItems = [mockLGraphNode, mockLGraphNode]
const wrapper = mountComponent()
const button = wrapper.find('button')
expect(button.exists()).toBe(true)
Expand Down
Loading