mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-24 14:45:36 +00:00
## Summary Stops the Shape submenu (and any other PrimeVue nested submenu) from being clipped behind the node context menu when the menu fits in the viewport. ## Changes - **What**: `constrainMenuHeight` in `NodeContextMenu.vue` now applies `max-height` + `overflow-y: auto` to the root `<ul>` only when `scrollHeight > availableHeight`. The common case keeps `overflow: visible`. - Added `browser_tests/tests/nodeContextMenuShapeSubmenu.spec.ts` regression spec. ## Review Focus Root cause: setting only `overflow-y: auto` on a `<ul>` coerces `overflow-x` to a non-visible value per CSS spec (`If one of overflow-x/overflow-y is visible and the other isn't, the visible value is computed as auto`). PrimeVue `ContextMenuSub` renders submenus in-tree as a nested `<ul>` with `position: absolute; left: 100%`, so the implicit horizontal clip hides them entirely. The pre-existing overflow scenario (#10824 / #10854) is unchanged — when the menu actually overflows, the clamp still applies and `nodeContextMenuOverflow.spec.ts` continues to verify scroll. Submenu clipping in that overflow case is a known limitation, not introduced by this PR. Fixes FE-570 ## screenshot ### AS IS <img width="788" height="505" alt="Screenshot 2026-05-07 at 12 43 26 PM" src="https://github.com/user-attachments/assets/36d34070-0c57-4385-a130-0394f22f282e" /> ### TO BE <img width="779" height="627" alt="Screenshot 2026-05-07 at 12 42 44 PM" src="https://github.com/user-attachments/assets/00956729-763b-4787-822f-209e8ea42331" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12035-fix-keep-node-context-menu-overflow-visible-when-content-fits-3586d73d365081ad9aaec82f220d401c) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com>
141 lines
4.6 KiB
TypeScript
141 lines
4.6 KiB
TypeScript
import { expect } from '@playwright/test'
|
|
|
|
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
|
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
|
|
import { openMoreOptionsMenu } from '@e2e/fixtures/utils/selectionToolboxMoreOptions'
|
|
|
|
test.beforeEach(async ({ comfyPage }) => {
|
|
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Disabled')
|
|
})
|
|
|
|
test.describe(
|
|
'Selection Toolbox - More Options Submenus',
|
|
{ tag: '@ui' },
|
|
() => {
|
|
test.beforeEach(async ({ comfyPage }) => {
|
|
await comfyPage.settings.setSetting('Comfy.Canvas.SelectionToolbox', true)
|
|
await comfyPage.workflow.loadWorkflow('nodes/single_ksampler')
|
|
await comfyPage.nodeOps.selectNodes(['KSampler'])
|
|
await comfyPage.nextFrame()
|
|
})
|
|
|
|
const openMoreOptions = (comfyPage: ComfyPage) =>
|
|
openMoreOptionsMenu(comfyPage, 'KSampler')
|
|
|
|
test('hides Node Info from More Options menu when the new menu is disabled', async ({
|
|
comfyPage
|
|
}) => {
|
|
await comfyPage.settings.setSetting('Comfy.NodeLibrary.NewDesign', false)
|
|
|
|
await openMoreOptions(comfyPage)
|
|
const nodeInfoButton = comfyPage.page.getByRole('menuitem', {
|
|
name: 'Node Info'
|
|
})
|
|
await expect(nodeInfoButton).toBeHidden()
|
|
})
|
|
|
|
test('changes node shape via Shape submenu', async ({ comfyPage }) => {
|
|
const nodeRef = (
|
|
await comfyPage.nodeOps.getNodeRefsByTitle('KSampler')
|
|
)[0]
|
|
|
|
await openMoreOptions(comfyPage)
|
|
// Shape now opens via body-appended popover (FE-570); a hover no
|
|
// longer reveals the submenu — match the Color flow and click.
|
|
await comfyPage.page.getByText('Shape', { exact: true }).click()
|
|
const shapePopover = comfyPage.page
|
|
.locator('.p-popover')
|
|
.filter({ hasText: 'Default' })
|
|
await expect(shapePopover.getByText('Box', { exact: true })).toBeVisible()
|
|
await shapePopover.getByText('Box', { exact: true }).click()
|
|
await comfyPage.nextFrame()
|
|
|
|
await expect.poll(() => nodeRef.getProperty<number>('shape')).toBe(1)
|
|
})
|
|
|
|
test('changes node color via Color submenu swatch', async ({
|
|
comfyPage
|
|
}) => {
|
|
const nodeRef = (
|
|
await comfyPage.nodeOps.getNodeRefsByTitle('KSampler')
|
|
)[0]
|
|
|
|
await openMoreOptions(comfyPage)
|
|
await comfyPage.page.getByText('Color', { exact: true }).click()
|
|
const blueSwatch = comfyPage.page.getByTitle('Blue')
|
|
await expect(blueSwatch.first()).toBeVisible()
|
|
await blueSwatch.first().click()
|
|
await comfyPage.nextFrame()
|
|
|
|
await expect
|
|
.poll(() => nodeRef.getProperty<string | undefined>('color'))
|
|
.toBe('#223')
|
|
})
|
|
|
|
test('renames a node using Rename action', async ({ comfyPage }) => {
|
|
const nodeRef = (
|
|
await comfyPage.nodeOps.getNodeRefsByTitle('KSampler')
|
|
)[0]
|
|
await openMoreOptions(comfyPage)
|
|
await comfyPage.page.getByText('Rename', { exact: true }).click()
|
|
const input = comfyPage.page.locator(
|
|
'.group-title-editor.node-title-editor .editable-text input'
|
|
)
|
|
await expect(input).toBeVisible()
|
|
await input.fill('RenamedNode')
|
|
await input.press('Enter')
|
|
await comfyPage.nextFrame()
|
|
await expect
|
|
.poll(() => nodeRef.getProperty<string>('title'))
|
|
.toBe('RenamedNode')
|
|
})
|
|
|
|
test('closes More Options menu when clicking outside', async ({
|
|
comfyPage
|
|
}) => {
|
|
await openMoreOptions(comfyPage)
|
|
const renameItem = comfyPage.page.getByText('Rename', { exact: true })
|
|
await expect(renameItem).toBeVisible()
|
|
|
|
// Wait for multiple frames to allow PrimeVue's outside click handler to initialize
|
|
for (let i = 0; i < 30; i++) {
|
|
await comfyPage.nextFrame()
|
|
}
|
|
|
|
await comfyPage.canvasOps.mouseClickAt({ x: 0, y: 50 })
|
|
await expect(
|
|
comfyPage.page.getByText('Rename', { exact: true })
|
|
).toBeHidden()
|
|
})
|
|
|
|
test('closes More Options menu when clicking the button again (toggle)', async ({
|
|
comfyPage
|
|
}) => {
|
|
await openMoreOptions(comfyPage)
|
|
await expect(
|
|
comfyPage.page.getByText('Rename', { exact: true })
|
|
).toBeVisible()
|
|
|
|
await comfyPage.page.evaluate(() => {
|
|
const btn = document.querySelector(
|
|
'[data-testid="more-options-button"]'
|
|
)
|
|
if (btn) {
|
|
const event = new MouseEvent('click', {
|
|
bubbles: true,
|
|
cancelable: true,
|
|
view: window,
|
|
detail: 1
|
|
})
|
|
btn.dispatchEvent(event)
|
|
}
|
|
})
|
|
await comfyPage.nextFrame()
|
|
|
|
await expect(
|
|
comfyPage.page.getByText('Rename', { exact: true })
|
|
).toBeHidden()
|
|
})
|
|
}
|
|
)
|