test: add legacy mode tests and refactor shared assertion

- Add 4 legacy mode selection bounding box tests (expanded/collapsed
  x bottom-left/bottom-right)
- Extract assertSelectionEncompassesNodes shared helper
- Add boundingRect fallback for legacy nodes without DOM elements
- Rename Vue mode test describe for symmetry
This commit is contained in:
jaeone94
2026-03-31 20:53:26 +09:00
parent 87f394ac77
commit d434f770dc
2 changed files with 138 additions and 50 deletions

View File

@@ -31,10 +31,23 @@ export async function measureSelectionBounds(
let maxY = -Infinity
for (const item of selectedItems) {
const rect = item.boundingRect
// For collapsed nodes, use DOM element size (matches selectionBorder.ts
// which reads layoutStore.collapsedSize in Vue mode)
const id = 'id' in item ? String(item.id) : null
const isCollapsed =
'flags' in item &&
!!(item as { flags?: { collapsed?: boolean } }).flags?.collapsed
const el =
id && isCollapsed
? document.querySelector(`[data-node-id="${id}"]`)
: null
const w = el instanceof HTMLElement ? el.offsetWidth : rect[2]
const h = el instanceof HTMLElement ? el.offsetHeight : rect[3]
minX = Math.min(minX, rect[0])
minY = Math.min(minY, rect[1])
maxX = Math.max(maxX, rect[0] + rect[2])
maxY = Math.max(maxY, rect[1] + rect[3])
maxX = Math.max(maxX, rect[0] + w)
maxY = Math.max(maxY, rect[1] + h)
}
const selectionBounds =
selectedItems.size > 0
@@ -57,7 +70,23 @@ export async function measureSelectionBounds(
const nodeEl = document.querySelector(
`[data-node-id="${id}"]`
) as HTMLElement | null
if (!nodeEl) continue
// Legacy mode: no Vue DOM element, use boundingRect directly
if (!nodeEl) {
const node = window.app!.graph._nodes.find(
(n: { id: number | string }) => String(n.id) === id
)
if (node) {
const rect = node.boundingRect
nodeVisualBounds[id] = {
x: rect[0],
y: rect[1],
w: rect[2],
h: rect[3]
}
}
continue
}
const domRect = nodeEl.getBoundingClientRect()
const footerEls = nodeEl.querySelectorAll(

View File

@@ -1,5 +1,7 @@
import { expect } from '@playwright/test'
import type { Page } from '@playwright/test'
import type { ComfyPage } from '../fixtures/ComfyPage'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
import { measureSelectionBounds } from '../fixtures/helpers/boundsUtils'
@@ -25,68 +27,125 @@ function getRefId(type: NodeType): string {
return type === 'subgraph' ? REGULAR_ID : SUBGRAPH_ID
}
test.describe('Selection bounding box', { tag: ['@canvas', '@node'] }, () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
await comfyPage.workflow.loadWorkflow(WORKFLOW)
await comfyPage.vueNodes.waitForNodes()
})
async function assertSelectionEncompassesNodes(
page: Page,
comfyPage: ComfyPage,
nodeIds: string[]
) {
await comfyPage.canvas.press('Control+a')
await expect
.poll(() => comfyPage.nodeOps.getSelectedGraphNodesCount())
.toBe(2)
await comfyPage.nextFrame()
test.afterEach(async ({ comfyPage }) => {
await comfyPage.canvasOps.resetView()
})
const result = await measureSelectionBounds(page, nodeIds)
expect(result.selectionBounds).not.toBeNull()
const nodeTypes: NodeType[] = ['subgraph', 'regular']
const nodeStates: NodeState[] = ['expanded', 'collapsed']
const positions: Position[] = ['bottom-left', 'bottom-right']
const sel = result.selectionBounds!
const selRight = sel.x + sel.w
const selBottom = sel.y + sel.h
for (const nodeId of nodeIds) {
const vis = result.nodeVisualBounds[nodeId]
expect(vis).toBeDefined()
expect(sel.x).toBeLessThanOrEqual(vis.x)
expect(selRight).toBeGreaterThanOrEqual(vis.x + vis.w)
expect(sel.y).toBeLessThanOrEqual(vis.y)
expect(selBottom).toBeGreaterThanOrEqual(vis.y + vis.h)
}
}
test.describe(
'Selection bounding box (Vue mode)',
{ tag: ['@canvas', '@node'] },
() => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
await comfyPage.workflow.loadWorkflow(WORKFLOW)
await comfyPage.vueNodes.waitForNodes()
})
test.afterEach(async ({ comfyPage }) => {
await comfyPage.canvasOps.resetView()
})
const nodeTypes: NodeType[] = ['subgraph', 'regular']
const nodeStates: NodeState[] = ['expanded', 'collapsed']
const positions: Position[] = ['bottom-left', 'bottom-right']
for (const type of nodeTypes) {
for (const state of nodeStates) {
for (const pos of positions) {
test(`${type} node (${state}) at ${pos}: selection bounds encompass node`, async ({
comfyPage
}) => {
const targetId = getTargetId(type)
const refId = getRefId(type)
await comfyPage.nodeOps.repositionNodes({
[refId]: REF_POS,
[targetId]: TARGET_POSITIONS[pos]
})
await comfyPage.vueNodes.waitForNodes()
await comfyPage.vueNodes.getNodeLocator(targetId).waitFor()
await comfyPage.vueNodes.getNodeLocator(refId).waitFor()
if (state === 'collapsed') {
const nodeRef = await comfyPage.nodeOps.getNodeRefById(targetId)
await nodeRef.setCollapsed(true)
}
await assertSelectionEncompassesNodes(comfyPage.page, comfyPage, [
refId,
targetId
])
})
}
}
}
}
)
test.describe(
'Selection bounding box (legacy mode)',
{ tag: ['@canvas', '@node'] },
() => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', false)
await comfyPage.workflow.loadWorkflow(WORKFLOW)
await comfyPage.nextFrame()
})
test.afterEach(async ({ comfyPage }) => {
await comfyPage.canvasOps.resetView()
})
const nodeStates: NodeState[] = ['expanded', 'collapsed']
const positions: Position[] = ['bottom-left', 'bottom-right']
for (const type of nodeTypes) {
for (const state of nodeStates) {
for (const pos of positions) {
test(`${type} node (${state}) at ${pos}: selection bounds encompass node`, async ({
test(`legacy node (${state}) at ${pos}: selection bounds encompass node`, async ({
comfyPage
}) => {
const page = comfyPage.page
const targetId = getTargetId(type)
const refId = getRefId(type)
await comfyPage.nodeOps.repositionNodes({
[refId]: REF_POS,
[targetId]: TARGET_POSITIONS[pos]
[SUBGRAPH_ID]: REF_POS,
[REGULAR_ID]: TARGET_POSITIONS[pos]
})
await comfyPage.vueNodes.waitForNodes()
await comfyPage.vueNodes.getNodeLocator(targetId).waitFor()
await comfyPage.vueNodes.getNodeLocator(refId).waitFor()
await comfyPage.nextFrame()
if (state === 'collapsed') {
const nodeRef = await comfyPage.nodeOps.getNodeRefById(targetId)
const nodeRef = await comfyPage.nodeOps.getNodeRefById(REGULAR_ID)
await nodeRef.setCollapsed(true)
}
await comfyPage.canvas.press('Control+a')
await expect
.poll(() => comfyPage.nodeOps.getSelectedGraphNodesCount())
.toBe(2)
await comfyPage.nextFrame()
const result = await measureSelectionBounds(page, [refId, targetId])
expect(result.selectionBounds).not.toBeNull()
const sel = result.selectionBounds!
const selRight = sel.x + sel.w
const selBottom = sel.y + sel.h
for (const nodeId of [refId, targetId]) {
const vis = result.nodeVisualBounds[nodeId]
expect(vis).toBeDefined()
expect(sel.x).toBeLessThanOrEqual(vis.x)
expect(selRight).toBeGreaterThanOrEqual(vis.x + vis.w)
expect(sel.y).toBeLessThanOrEqual(vis.y)
expect(selBottom).toBeGreaterThanOrEqual(vis.y + vis.h)
}
await assertSelectionEncompassesNodes(comfyPage.page, comfyPage, [
SUBGRAPH_ID,
REGULAR_ID
])
})
}
}
}
})
)