Files
ComfyUI_frontend/tests-ui/tests/litegraph/core/LGraphNode.titleButtons.test.ts
Alexander Brown b264685052 lint: add tsconfig for browser_tests, fix existing violations (#5633)
## Summary

See https://typescript-eslint.io/blog/project-service/ for context.
Creates a browser_tests specific tsconfig so that they can be linted.

Does not add a package.json script to do the linting yet, but `pnpm exec
eslint browser_tests` should work for now.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5633-lint-add-tsconfig-for-browser_tests-fix-existing-violations-2726d73d3650819d8ef2c4b0abc31e14)
by [Unito](https://www.unito.io)
2025-09-18 11:35:44 -07:00

320 lines
11 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest'
import { LGraphButton } from '@/lib/litegraph/src/litegraph'
import type { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
describe('LGraphNode Title Buttons', () => {
describe('addTitleButton', () => {
it('should add a title button to the node', () => {
const node = new LGraphNode('Test Node')
const button = node.addTitleButton({
name: 'test_button',
text: 'X',
fgColor: '#FF0000'
})
expect(button).toBeInstanceOf(LGraphButton)
expect(button.name).toBe('test_button')
expect(button.text).toBe('X')
expect(button.fgColor).toBe('#FF0000')
expect(node.title_buttons).toHaveLength(1)
expect(node.title_buttons[0]).toBe(button)
})
it('should add multiple title buttons', () => {
const node = new LGraphNode('Test Node')
const button1 = node.addTitleButton({ name: 'button1', text: 'A' })
const button2 = node.addTitleButton({ name: 'button2', text: 'B' })
const button3 = node.addTitleButton({ name: 'button3', text: 'C' })
expect(node.title_buttons).toHaveLength(3)
expect(node.title_buttons[0]).toBe(button1)
expect(node.title_buttons[1]).toBe(button2)
expect(node.title_buttons[2]).toBe(button3)
})
it('should create buttons with default options', () => {
const node = new LGraphNode('Test Node')
// @ts-expect-error TODO: Fix after merge - addTitleButton type issues
const button = node.addTitleButton({})
expect(button).toBeInstanceOf(LGraphButton)
expect(button.name).toBeUndefined()
expect(node.title_buttons).toHaveLength(1)
})
})
describe('title button handling via canvas', () => {
it('should handle click on title button through canvas processClick', () => {
const node = new LGraphNode('Test Node')
node.pos = [100, 200]
node.size = [180, 60]
const button = node.addTitleButton({
name: 'close_button',
text: 'X',
// @ts-expect-error TODO: Fix after merge - visible property not defined in type
visible: true
})
// Mock button methods
button.getWidth = vi.fn().mockReturnValue(20)
button.height = 16
button.isPointInside = vi.fn().mockReturnValue(true)
const canvas = {
ctx: {} as CanvasRenderingContext2D,
dispatch: vi.fn()
} as unknown as LGraphCanvas
// Mock the node's onTitleButtonClick method to verify it gets called
const onTitleButtonClickSpy = vi.spyOn(node, 'onTitleButtonClick')
// Calculate node-relative position for the click
const clickPosRelativeToNode: [number, number] = [
265 - node.pos[0], // 165
178 - node.pos[1] // -22
]
// Test the title button logic that's now in the canvas
// This simulates what happens in LGraphCanvas.processMouseDown
if (node.title_buttons?.length && !node.flags.collapsed) {
const nodeRelativeX = clickPosRelativeToNode[0]
const nodeRelativeY = clickPosRelativeToNode[1]
for (let i = 0; i < node.title_buttons.length; i++) {
const btn = node.title_buttons[i]
if (btn.visible && btn.isPointInside(nodeRelativeX, nodeRelativeY)) {
node.onTitleButtonClick(btn, canvas)
break
}
}
}
expect(button.isPointInside).toHaveBeenCalledWith(165, -22)
expect(onTitleButtonClickSpy).toHaveBeenCalledWith(button, canvas)
expect(canvas.dispatch).toHaveBeenCalledWith(
'litegraph:node-title-button-clicked',
{
node: node,
button: button
}
)
})
it('should not handle click outside title buttons', () => {
const node = new LGraphNode('Test Node')
node.pos = [100, 200]
node.size = [180, 60]
const button = node.addTitleButton({
name: 'test_button',
text: 'T',
// @ts-expect-error TODO: Fix after merge - visible property not defined in type
visible: true
})
button.getWidth = vi.fn().mockReturnValue(20)
button.height = 16
button.isPointInside = vi.fn().mockReturnValue(false)
const canvas = {
ctx: {} as CanvasRenderingContext2D,
dispatch: vi.fn()
} as unknown as LGraphCanvas
// Calculate node-relative position
const clickPosRelativeToNode: [number, number] = [
150 - node.pos[0], // 50
180 - node.pos[1] // -20
]
// Mock the node's onTitleButtonClick method to ensure it doesn't get called
const onTitleButtonClickSpy = vi.spyOn(node, 'onTitleButtonClick')
// Test the title button logic that's now in the canvas
let buttonClicked = false
if (node.title_buttons?.length && !node.flags.collapsed) {
const nodeRelativeX = clickPosRelativeToNode[0]
const nodeRelativeY = clickPosRelativeToNode[1]
for (let i = 0; i < node.title_buttons.length; i++) {
const btn = node.title_buttons[i]
if (btn.visible && btn.isPointInside(nodeRelativeX, nodeRelativeY)) {
node.onTitleButtonClick(btn, canvas)
buttonClicked = true
break
}
}
}
expect(button.isPointInside).toHaveBeenCalledWith(50, -20)
expect(onTitleButtonClickSpy).not.toHaveBeenCalled()
expect(canvas.dispatch).not.toHaveBeenCalled()
expect(buttonClicked).toBe(false)
})
it('should handle multiple buttons correctly', () => {
const node = new LGraphNode('Test Node')
node.pos = [100, 200]
node.size = [200, 60]
const button1 = node.addTitleButton({
name: 'button1',
text: 'A',
// @ts-expect-error TODO: Fix after merge - visible property not defined in type
visible: true
})
const button2 = node.addTitleButton({
name: 'button2',
text: 'B',
// @ts-expect-error TODO: Fix after merge - visible property not defined in type
visible: true
})
// Mock button methods
button1.getWidth = vi.fn().mockReturnValue(20)
button2.getWidth = vi.fn().mockReturnValue(20)
button1.height = button2.height = 16
button1.isPointInside = vi.fn().mockReturnValue(false)
button2.isPointInside = vi.fn().mockReturnValue(true)
const canvas = {
ctx: {} as CanvasRenderingContext2D,
dispatch: vi.fn()
} as unknown as LGraphCanvas
// Mock the node's onTitleButtonClick method
const onTitleButtonClickSpy = vi.spyOn(node, 'onTitleButtonClick')
// Click on second button
const titleY = 178 // node.pos[1] - NODE_TITLE_HEIGHT + 8 = 200 - 30 + 8 = 178
// Calculate node-relative position
const clickPosRelativeToNode: [number, number] = [
255 - node.pos[0], // 155
titleY - node.pos[1] // -22
]
// Test the title button logic that's now in the canvas
if (node.title_buttons?.length && !node.flags.collapsed) {
const nodeRelativeX = clickPosRelativeToNode[0]
const nodeRelativeY = clickPosRelativeToNode[1]
for (let i = 0; i < node.title_buttons.length; i++) {
const btn = node.title_buttons[i]
if (btn.visible && btn.isPointInside(nodeRelativeX, nodeRelativeY)) {
node.onTitleButtonClick(btn, canvas)
break
}
}
}
expect(button1.isPointInside).toHaveBeenCalledWith(155, -22)
expect(button2.isPointInside).toHaveBeenCalledWith(155, -22)
expect(onTitleButtonClickSpy).toHaveBeenCalledWith(button2, canvas)
expect(canvas.dispatch).toHaveBeenCalledWith(
'litegraph:node-title-button-clicked',
{
node: node,
button: button2
}
)
})
it('should skip invisible buttons', () => {
const node = new LGraphNode('Test Node')
node.pos = [100, 200]
node.size = [180, 60]
const button1 = node.addTitleButton({
name: 'invisible_button',
text: '' // Empty text makes it invisible
})
const button2 = node.addTitleButton({
name: 'visible_button',
text: 'V'
})
button1.getWidth = vi.fn().mockReturnValue(20)
button2.getWidth = vi.fn().mockReturnValue(20)
button1.height = button2.height = 16
// Set visibility - button1 is invisible (empty text), button2 is visible
button1.isPointInside = vi.fn().mockReturnValue(true) // Would be clicked if visible
button2.isPointInside = vi.fn().mockReturnValue(true)
const canvas = {
ctx: {} as CanvasRenderingContext2D,
dispatch: vi.fn()
} as unknown as LGraphCanvas
// Mock the node's onTitleButtonClick method
const onTitleButtonClickSpy = vi.spyOn(node, 'onTitleButtonClick')
// Click where both buttons would be positioned
const titleY = 178 // node.pos[1] - NODE_TITLE_HEIGHT + 8 = 200 - 30 + 8 = 178
// Calculate node-relative position
const clickPosRelativeToNode: [number, number] = [
265 - node.pos[0], // 165
titleY - node.pos[1] // -22
]
// Test the title button logic that's now in the canvas
if (node.title_buttons?.length && !node.flags.collapsed) {
const nodeRelativeX = clickPosRelativeToNode[0]
const nodeRelativeY = clickPosRelativeToNode[1]
for (let i = 0; i < node.title_buttons.length; i++) {
const btn = node.title_buttons[i]
// Only visible buttons are processed
if (btn.visible && btn.isPointInside(nodeRelativeX, nodeRelativeY)) {
node.onTitleButtonClick(btn, canvas)
break
}
}
}
// button1 should not be checked because it's not visible
expect(button1.isPointInside).not.toHaveBeenCalled()
// button2 should be checked and clicked because it's visible
expect(button2.isPointInside).toHaveBeenCalledWith(165, -22)
expect(onTitleButtonClickSpy).toHaveBeenCalledWith(button2, canvas)
expect(canvas.dispatch).toHaveBeenCalledWith(
'litegraph:node-title-button-clicked',
{
node: node,
button: button2 // Should click visible button, not invisible
}
)
})
})
describe('onTitleButtonClick', () => {
it('should dispatch litegraph:node-title-button-clicked event', () => {
const node = new LGraphNode('Test Node')
// @ts-expect-error TODO: Fix after merge - LGraphButton constructor type issues
const button = new LGraphButton({ name: 'test_button' })
const canvas = {
dispatch: vi.fn()
} as unknown as LGraphCanvas
node.onTitleButtonClick(button, canvas)
expect(canvas.dispatch).toHaveBeenCalledWith(
'litegraph:node-title-button-clicked',
{
node: node,
button: button
}
)
})
})
})