Compare commits

...

2 Commits

Author SHA1 Message Date
Glary-Bot
d3a1997604 test(e2e): assert More Options menu hides LiteGraph Resize/Collapse/Expand
Add an E2E test that verifies the Vue 'More Options' selection toolbox
menu shows 'Minimize Node' but no longer surfaces the duplicate
LiteGraph 'Resize', 'Collapse', or 'Expand' entries, locking in the
behavior introduced by the previous commit.
2026-05-13 19:41:27 +00:00
Glary-Bot
6f748be56f fix: hide duplicate LiteGraph Resize/Collapse/Expand entries from Vue node menu
The Vue right-click 'More Options' node menu currently shows duplicate
entries for collapse/expand functionality:
- Vue source: 'Minimize Node' / 'Expand Node' (works)
- LiteGraph source: 'Resize', 'Collapse', 'Expand' (Resize works on
  legacy canvas only; Collapse/Expand silently no-op because the
  converter wrapper invokes 'LGraphCanvas.onMenuNodeCollapse' without
  the node argument it expects)

Suppress the LiteGraph duplicates in 'convertContextMenuToOptions' by
matching the built-in callback identity (LGraphCanvas.onMenuResizeNode,
LGraphCanvas.onMenuNodeCollapse), not the raw label. Matching by
identity avoids accidentally hiding extension-provided items that
happen to share those labels.

The entries remain in 'LGraphCanvas.getNodeMenuOptions' so the legacy
right-click menu (when 'Comfy.UseNewMenu' is disabled) and Load3D's
context menu, which both consume the raw LiteGraph option array, are
unaffected.

Also align CORE_MENU_ITEMS / MENU_ORDER on the Vue label 'Expand Node'
so the toggled Minimize/Expand pair sorts correctly, and renumber the
section-boundary comment / indices to match the shorter list.
2026-05-12 05:40:48 +00:00
3 changed files with 74 additions and 12 deletions

View File

@@ -56,6 +56,30 @@ test.describe('Selection Toolbox - More Options', { tag: '@ui' }, () => {
await expect(nodeRef).not.toBeCollapsed()
})
test('More Options menu does not surface duplicate LiteGraph Resize / Collapse / Expand entries', async ({
comfyPage
}) => {
const nodeRef = (
await comfyPage.nodeOps.getNodeRefsByTitle('KSampler')
)[0]
await comfyPage.nodeOps.selectNodeWithPan(nodeRef)
const menu = await openMoreOptions(comfyPage)
await expect(
menu.getByText('Minimize Node', { exact: true })
).toBeVisible()
await expect(
menu.getByRole('menuitem', { name: 'Resize', exact: true })
).toHaveCount(0)
await expect(
menu.getByRole('menuitem', { name: 'Collapse', exact: true })
).toHaveCount(0)
await expect(
menu.getByRole('menuitem', { name: 'Expand', exact: true })
).toHaveCount(0)
})
test('copy via More Options menu', async ({ comfyPage }) => {
const nodeRef = (
await comfyPage.nodeOps.getNodeRefsByTitle('KSampler')

View File

@@ -1,5 +1,7 @@
import { describe, it, expect } from 'vitest'
import { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
import type { MenuOption } from './useMoreOptionsMenu'
import {
buildStructuredMenu,
@@ -175,6 +177,28 @@ describe('contextMenuConverter', () => {
expect(result.find((opt) => opt.label === 'Properties')).toBeUndefined()
})
it.for([
{ label: 'Resize', callback: LGraphCanvas.onMenuResizeNode },
{ label: 'Collapse', callback: LGraphCanvas.onMenuNodeCollapse },
{ label: 'Expand', callback: LGraphCanvas.onMenuNodeCollapse }
])(
'should skip built-in LiteGraph $label entry by callback identity',
({ label, callback }) => {
const items = [{ content: label, callback }]
const result = convertContextMenuToOptions(items, undefined, false)
expect(result.find((opt) => opt.label === label)).toBeUndefined()
}
)
it.for(['Resize', 'Collapse', 'Expand'])(
'should keep extension-provided %s entries (different callback identity)',
(label) => {
const items = [{ content: label, callback: () => {} }]
const result = convertContextMenuToOptions(items, undefined, false)
expect(result.find((opt) => opt.label === label)).toBeDefined()
}
)
it('should convert basic menu items with content', () => {
const items = [{ content: 'Test Item', callback: () => {} }]
const result = convertContextMenuToOptions(items, undefined, false)

View File

@@ -1,6 +1,6 @@
import { default as DOMPurify } from 'dompurify'
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { LGraphCanvas, LiteGraph } from '@/lib/litegraph/src/litegraph'
import type {
IContextMenuValue,
LGraphNode,
@@ -24,6 +24,17 @@ const HARD_BLACKLIST = new Set([
'Copy (Clipspace)'
])
/**
* Callbacks of built-in LiteGraph node menu items that are superseded by
* the Vue node menu (Minimize Node / Expand Node) or have no working
* Vue-side equivalent. Matched by callback identity so that extensions
* providing their own items with the same labels are not affected.
*/
const SUPPRESSED_LITEGRAPH_CALLBACKS = new Set<unknown>([
LGraphCanvas.onMenuResizeNode,
LGraphCanvas.onMenuNodeCollapse
])
/**
* Core menu items - items that should appear in the main menu, not under Extensions
* Includes both LiteGraph base menu items and ComfyUI built-in functionality
@@ -46,11 +57,9 @@ const CORE_MENU_ITEMS = new Set([
'Frame selection',
'Frame Nodes',
'Minimize Node',
'Expand',
'Collapse',
'Expand Node',
// Info and adjustments
'Node Info',
'Resize',
'Title',
'Properties Panel',
'Adjust Size',
@@ -231,9 +240,7 @@ const MENU_ORDER: string[] = [
'Frame selection',
'Frame Nodes',
'Minimize Node',
'Expand',
'Collapse',
'Resize',
'Expand Node',
'Clone',
// Section 4: Node properties
'Node Info',
@@ -303,14 +310,14 @@ export function buildStructuredMenu(options: MenuOption[]): MenuOption[] {
// Section boundaries based on MENU_ORDER indices
// Section 1: 0-2 (Rename, Copy, Duplicate)
// Section 2: 3-8 (Run Branch, Pin, Unpin, Bypass, Remove Bypass, Mute)
// Section 3: 9-15 (Convert to Subgraph, Frame selection, Minimize Node, Expand, Collapse, Resize, Clone)
// Section 4: 16-17 (Node Info, Color)
// Section 5: 18+ (Image operations and fallback items)
// Section 3: 9-14 (Convert to Subgraph, Frame selection, Frame Nodes, Minimize Node, Expand Node, Clone)
// Section 4: 15-16 (Node Info, Color)
// Section 5: 17+ (Image operations and fallback items)
const getSectionNumber = (index: number): number => {
if (index <= 2) return 1
if (index <= 8) return 2
if (index <= 15) return 3
if (index <= 17) return 4
if (index <= 14) return 3
if (index <= 16) return 4
return 5
}
@@ -391,6 +398,13 @@ export function convertContextMenuToOptions(
continue
}
// Skip built-in LiteGraph items that the Vue menu replaces.
// Matched by callback identity, not label, to avoid suppressing
// extension-provided items that happen to share a label.
if (item.callback && SUPPRESSED_LITEGRAPH_CALLBACKS.has(item.callback)) {
continue
}
// Skip if a similar item already exists in results
if (isDuplicateItem(item.content, result)) {
continue