Skip to content

Commit 1b80448

Browse files
committed
cleanup: Remove JS private fields from LGraphNode, remove update requirement for selectedItems
1 parent b09ff3d commit 1b80448

File tree

16 files changed

+181
-159
lines changed

16 files changed

+181
-159
lines changed

src/components/graph/GraphCanvas.vue

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
<template #graph-canvas-panel>
4545
<GraphCanvasMenu v-if="canvasMenuEnabled" class="pointer-events-auto" />
4646
<MiniMap
47-
v-if="comfyAppReady && minimapEnabled"
47+
v-if="comfyAppReady && minimapEnabled && betaMenuEnabled"
4848
class="pointer-events-auto"
4949
/>
5050
</template>
@@ -119,7 +119,6 @@ import NodeSearchboxPopover from '@/components/searchbox/NodeSearchBoxPopover.vu
119119
import SideToolbar from '@/components/sidebar/SideToolbar.vue'
120120
import TopbarBadges from '@/components/topbar/TopbarBadges.vue'
121121
import WorkflowTabs from '@/components/topbar/WorkflowTabs.vue'
122-
import { useChainCallback } from '@/composables/functional/useChainCallback'
123122
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
124123
import { useVueNodeLifecycle } from '@/composables/graph/useVueNodeLifecycle'
125124
import { useNodeBadge } from '@/composables/node/useNodeBadge'
@@ -445,11 +444,6 @@ onMounted(async () => {
445444
446445
vueNodeLifecycle.setupEmptyGraphListener()
447446
448-
comfyApp.canvas.onSelectionChange = useChainCallback(
449-
comfyApp.canvas.onSelectionChange,
450-
() => canvasStore.updateSelectedItems()
451-
)
452-
453447
// Load color palette
454448
colorPaletteStore.customPalettes = settingStore.get(
455449
'Comfy.CustomColorPalettes'

src/components/graph/SelectionToolbox.test.ts

Lines changed: 47 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,32 @@
11
import { mount } from '@vue/test-utils'
2-
import { createPinia, setActivePinia } from 'pinia'
2+
import { setActivePinia } from 'pinia'
33
import PrimeVue from 'primevue/config'
44
import { beforeEach, describe, expect, it, vi } from 'vitest'
55
import { createI18n } from 'vue-i18n'
66

77
import SelectionToolbox from '@/components/graph/SelectionToolbox.vue'
8-
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
98
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
109
import { useExtensionService } from '@/services/extensionService'
10+
import type { Positionable } from '@/lib/litegraph/src/interfaces'
11+
import { ref } from 'vue'
12+
import { createTestingPinia } from '@pinia/testing'
13+
14+
const mockData = vi.hoisted(() => {
15+
return { canvas: undefined, selectedItems: [] as Positionable[] }
16+
})
17+
18+
vi.mock('@/renderer/core/canvas/canvasStore', () => ({
19+
useCanvasStore: vi.fn(() => ({
20+
canvas: {
21+
setDirty: vi.fn(),
22+
state: {
23+
selectionChanged: false
24+
}
25+
},
26+
selectedItems: ref(mockData.selectedItems)
27+
}))
28+
}))
1129

12-
// Mock the composables and services
1330
vi.mock('@/renderer/core/canvas/useCanvasInteractions', () => ({
1431
useCanvasInteractions: vi.fn(() => ({
1532
handleWheel: vi.fn()
@@ -84,8 +101,6 @@ vi.mock('@/stores/nodeDefStore', () => ({
84101
}))
85102

86103
describe('SelectionToolbox', () => {
87-
let canvasStore: ReturnType<typeof useCanvasStore>
88-
89104
const i18n = createI18n({
90105
legacy: false,
91106
locale: 'en',
@@ -108,16 +123,7 @@ describe('SelectionToolbox', () => {
108123
}
109124

110125
beforeEach(() => {
111-
setActivePinia(createPinia())
112-
canvasStore = useCanvasStore()
113-
114-
// Mock the canvas to avoid "getCanvas: canvas is null" errors
115-
canvasStore.canvas = {
116-
setDirty: vi.fn(),
117-
state: {
118-
selectionChanged: false
119-
}
120-
} as any
126+
setActivePinia(createTestingPinia({ createSpy: vi.fn }))
121127

122128
vi.resetAllMocks()
123129
})
@@ -191,12 +197,12 @@ describe('SelectionToolbox', () => {
191197

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

198204
// Multiple node selection
199-
canvasStore.selectedItems = [
205+
mockData.selectedItems = [
200206
{ type: 'TestNode1' },
201207
{ type: 'TestNode2' }
202208
] as any
@@ -206,7 +212,7 @@ describe('SelectionToolbox', () => {
206212
})
207213

208214
it('should not show info button when node definition is not found', () => {
209-
canvasStore.selectedItems = [{ type: 'TestNode' }] as any
215+
mockData.selectedItems = [{ type: 'TestNode' }] as any
210216
// mock nodedef and return null
211217
nodeDefMock = null
212218
// remount component
@@ -216,14 +222,14 @@ describe('SelectionToolbox', () => {
216222

217223
it('should show color picker for all selections', () => {
218224
// Single node selection
219-
canvasStore.selectedItems = [{ type: 'TestNode' }] as any
225+
mockData.selectedItems = [{ type: 'TestNode' }] as any
220226
const wrapper = mountComponent()
221227
expect(wrapper.find('[data-testid="color-picker-button"]').exists()).toBe(
222228
true
223229
)
224230

225231
// Multiple node selection
226-
canvasStore.selectedItems = [
232+
mockData.selectedItems = [
227233
{ type: 'TestNode1' },
228234
{ type: 'TestNode2' }
229235
] as any
@@ -236,12 +242,12 @@ describe('SelectionToolbox', () => {
236242

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

243249
// Multiple node selection
244-
canvasStore.selectedItems = [
250+
mockData.selectedItems = [
245251
{ type: 'TestNode1' },
246252
{ type: 'TestNode2' }
247253
] as any
@@ -252,12 +258,12 @@ describe('SelectionToolbox', () => {
252258

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

259265
// Multiple node selection
260-
canvasStore.selectedItems = [
266+
mockData.selectedItems = [
261267
{ type: 'TestNode1' },
262268
{ type: 'TestNode2' }
263269
] as any
@@ -267,7 +273,7 @@ describe('SelectionToolbox', () => {
267273
})
268274

269275
it('should show common buttons for all selections', () => {
270-
canvasStore.selectedItems = [{ type: 'TestNode' }] as any
276+
mockData.selectedItems = [{ type: 'TestNode' }] as any
271277
const wrapper = mountComponent()
272278

273279
expect(wrapper.find('[data-testid="delete-button"]').exists()).toBe(true)
@@ -285,13 +291,13 @@ describe('SelectionToolbox', () => {
285291

286292
// Single image node
287293
isImageNodeSpy.mockReturnValue(true)
288-
canvasStore.selectedItems = [{ type: 'ImageNode' }] as any
294+
mockData.selectedItems = [{ type: 'ImageNode' }] as any
289295
const wrapper = mountComponent()
290296
expect(wrapper.find('.mask-editor-button').exists()).toBe(true)
291297

292298
// Single non-image node
293299
isImageNodeSpy.mockReturnValue(false)
294-
canvasStore.selectedItems = [{ type: 'TestNode' }] as any
300+
mockData.selectedItems = [{ type: 'TestNode' }] as any
295301
wrapper.unmount()
296302
const wrapper2 = mountComponent()
297303
expect(wrapper2.find('.mask-editor-button').exists()).toBe(false)
@@ -303,13 +309,13 @@ describe('SelectionToolbox', () => {
303309

304310
// Single Load3D node
305311
isLoad3dNodeSpy.mockReturnValue(true)
306-
canvasStore.selectedItems = [{ type: 'Load3DNode' }] as any
312+
mockData.selectedItems = [{ type: 'Load3DNode' }] as any
307313
const wrapper = mountComponent()
308314
expect(wrapper.find('.load-3d-viewer-button').exists()).toBe(true)
309315

310316
// Single non-Load3D node
311317
isLoad3dNodeSpy.mockReturnValue(false)
312-
canvasStore.selectedItems = [{ type: 'TestNode' }] as any
318+
mockData.selectedItems = [{ type: 'TestNode' }] as any
313319
wrapper.unmount()
314320
const wrapper2 = mountComponent()
315321
expect(wrapper2.find('.load-3d-viewer-button').exists()).toBe(false)
@@ -326,7 +332,7 @@ describe('SelectionToolbox', () => {
326332
// With output node selected
327333
isOutputNodeSpy.mockReturnValue(true)
328334
filterOutputNodesSpy.mockReturnValue([{ type: 'SaveImage' }] as any)
329-
canvasStore.selectedItems = [
335+
mockData.selectedItems = [
330336
{ type: 'SaveImage', constructor: { nodeData: { output_node: true } } }
331337
] as any
332338
const wrapper = mountComponent()
@@ -335,13 +341,13 @@ describe('SelectionToolbox', () => {
335341
// Without output node selected
336342
isOutputNodeSpy.mockReturnValue(false)
337343
filterOutputNodesSpy.mockReturnValue([])
338-
canvasStore.selectedItems = [{ type: 'TestNode' }] as any
344+
mockData.selectedItems = [{ type: 'TestNode' }] as any
339345
wrapper.unmount()
340346
const wrapper2 = mountComponent()
341347
expect(wrapper2.find('.execute-button').exists()).toBe(false)
342348

343349
// No selection at all
344-
canvasStore.selectedItems = []
350+
mockData.selectedItems = []
345351
wrapper2.unmount()
346352
const wrapper3 = mountComponent()
347353
expect(wrapper3.find('.execute-button').exists()).toBe(false)
@@ -351,7 +357,7 @@ describe('SelectionToolbox', () => {
351357
describe('Divider Visibility Logic', () => {
352358
it('should show dividers between button groups when both groups have buttons', () => {
353359
// Setup single node to show info + other buttons
354-
canvasStore.selectedItems = [{ type: 'TestNode' }] as any
360+
mockData.selectedItems = [{ type: 'TestNode' }] as any
355361
const wrapper = mountComponent()
356362

357363
const dividers = wrapper.findAll('.vertical-divider')
@@ -360,7 +366,7 @@ describe('SelectionToolbox', () => {
360366

361367
it('should not show dividers when adjacent groups are empty', () => {
362368
// No selection should show minimal buttons and dividers
363-
canvasStore.selectedItems = []
369+
mockData.selectedItems = []
364370
const wrapper = mountComponent()
365371

366372
const buttons = wrapper.find('.panel').element.children
@@ -380,7 +386,7 @@ describe('SelectionToolbox', () => {
380386
invokeExtensions: vi.fn(() => ['test-command'])
381387
} as any)
382388

383-
canvasStore.selectedItems = [{ type: 'TestNode' }] as any
389+
mockData.selectedItems = [{ type: 'TestNode' }] as any
384390
const wrapper = mountComponent()
385391

386392
expect(wrapper.find('.extension-command-button').exists()).toBe(true)
@@ -393,7 +399,7 @@ describe('SelectionToolbox', () => {
393399
invokeExtensions: vi.fn(() => [])
394400
} as any)
395401

396-
canvasStore.selectedItems = [{ type: 'TestNode' }] as any
402+
mockData.selectedItems = [{ type: 'TestNode' }] as any
397403
const wrapper = mountComponent()
398404

399405
expect(wrapper.find('.extension-command-button').exists()).toBe(false)
@@ -408,7 +414,7 @@ describe('SelectionToolbox', () => {
408414
invokeExtensions: vi.fn(() => [])
409415
} as any)
410416

411-
canvasStore.selectedItems = [{ type: 'TestNode' }] as any
417+
mockData.selectedItems = [{ type: 'TestNode' }] as any
412418
const wrapper = mountComponent()
413419

414420
const panel = wrapper.find('.panel')
@@ -422,7 +428,7 @@ describe('SelectionToolbox', () => {
422428
invokeExtensions: vi.fn(() => [])
423429
} as any)
424430

425-
canvasStore.selectedItems = [{ type: 'TestNode' }] as any
431+
mockData.selectedItems = [{ type: 'TestNode' }] as any
426432
const wrapper = mountComponent()
427433

428434
const panel = wrapper.find('.panel')
@@ -439,7 +445,7 @@ describe('SelectionToolbox', () => {
439445
invokeExtensions: vi.fn(() => [])
440446
} as any)
441447

442-
canvasStore.selectedItems = [{ type: 'TestNode' }] as any
448+
mockData.selectedItems = [{ type: 'TestNode' }] as any
443449
const wrapper = mountComponent()
444450

445451
const panel = wrapper.find('.panel')
@@ -461,7 +467,7 @@ describe('SelectionToolbox', () => {
461467
invokeExtensions: vi.fn(() => [])
462468
} as any)
463469

464-
canvasStore.selectedItems = [{ type: 'TestNode' }] as any
470+
mockData.selectedItems = [{ type: 'TestNode' }] as any
465471
const wrapper = mountComponent()
466472

467473
const panel = wrapper.find('.panel')
@@ -481,7 +487,7 @@ describe('SelectionToolbox', () => {
481487
})
482488

483489
it('should hide most buttons when no items selected', () => {
484-
canvasStore.selectedItems = []
490+
mockData.selectedItems = []
485491
const wrapper = mountComponent()
486492

487493
expect(wrapper.find('.info-button').exists()).toBe(false)

src/components/graph/SelectionToolbox.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
</template>
4646

4747
<script setup lang="ts">
48+
import { storeToRefs } from 'pinia'
4849
import Panel from 'primevue/panel'
4950
import { computed, ref } from 'vue'
5051
@@ -80,9 +81,11 @@ const canvasInteractions = useCanvasInteractions()
8081
const toolboxRef = ref<HTMLElement | undefined>()
8182
const { visible } = useSelectionToolboxPosition(toolboxRef)
8283
84+
const { selectedItems } = storeToRefs(canvasStore)
85+
8386
const extensionToolboxCommands = computed<ComfyCommandImpl[]>(() => {
8487
const commandIds = new Set<string>(
85-
canvasStore.selectedItems
88+
selectedItems.value
8689
.map(
8790
(item) =>
8891
extensionService

0 commit comments

Comments
 (0)