mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-03 04:31:58 +00:00
Update rh-test (as of 2025-10-11) (#6044)
## Summary Tested these changes and confirmed that: 1. Feedback button shows. 2. You can run workflows and switch out models. 3. You can use the mask editor. (thank you @ric-yu for helping me verify). ## Changes A lot, please see commits. Gets us up to date with `main` as of 10-11-2025. --------- Co-authored-by: Simula_r <18093452+simula-r@users.noreply.github.com> Co-authored-by: github-actions <github-actions@github.com> Co-authored-by: snomiao <snomiao@gmail.com> Co-authored-by: Christian Byrne <cbyrne@comfy.org> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DrJKL <DrJKL@users.noreply.github.com> Co-authored-by: Alexander Brown <drjkl@comfy.org> Co-authored-by: Marwan Ahmed <155799754+marawan206@users.noreply.github.com> Co-authored-by: DrJKL <DrJKL0424@gmail.com> Co-authored-by: Rizumu Ayaka <rizumu@ayaka.moe> Co-authored-by: Comfy Org PR Bot <snomiao+comfy-pr@gmail.com> Co-authored-by: AustinMroz <4284322+AustinMroz@users.noreply.github.com> Co-authored-by: Austin Mroz <austin@comfy.org> Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com> Co-authored-by: GitHub Action <action@github.com> Co-authored-by: filtered <176114999+webfiltered@users.noreply.github.com> Co-authored-by: Benjamin Lu <benceruleanlu@proton.me> Co-authored-by: Jin Yi <jin12cc@gmail.com> Co-authored-by: Robin Huang <robin.j.huang@gmail.com>
This commit is contained in:
200
tests-ui/tests/extensions/contextMenuExtension.test.ts
Normal file
200
tests-ui/tests/extensions/contextMenuExtension.test.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import type { IContextMenuValue } from '@/lib/litegraph/src/interfaces'
|
||||
import type { LGraphCanvas, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { useExtensionService } from '@/services/extensionService'
|
||||
import { useExtensionStore } from '@/stores/extensionStore'
|
||||
import type { ComfyExtension } from '@/types/comfy'
|
||||
|
||||
describe('Context Menu Extension API', () => {
|
||||
let mockCanvas: LGraphCanvas
|
||||
let mockNode: LGraphNode
|
||||
let extensionStore: ReturnType<typeof useExtensionStore>
|
||||
let extensionService: ReturnType<typeof useExtensionService>
|
||||
|
||||
// Mock menu items
|
||||
const canvasMenuItem1: IContextMenuValue = {
|
||||
content: 'Canvas Item 1',
|
||||
callback: () => {}
|
||||
}
|
||||
const canvasMenuItem2: IContextMenuValue = {
|
||||
content: 'Canvas Item 2',
|
||||
callback: () => {}
|
||||
}
|
||||
const nodeMenuItem1: IContextMenuValue = {
|
||||
content: 'Node Item 1',
|
||||
callback: () => {}
|
||||
}
|
||||
const nodeMenuItem2: IContextMenuValue = {
|
||||
content: 'Node Item 2',
|
||||
callback: () => {}
|
||||
}
|
||||
|
||||
// Mock extensions
|
||||
const createCanvasMenuExtension = (
|
||||
name: string,
|
||||
items: IContextMenuValue[]
|
||||
): ComfyExtension => ({
|
||||
name,
|
||||
getCanvasMenuItems: () => items
|
||||
})
|
||||
|
||||
const createNodeMenuExtension = (
|
||||
name: string,
|
||||
items: IContextMenuValue[]
|
||||
): ComfyExtension => ({
|
||||
name,
|
||||
getNodeMenuItems: () => items
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
extensionStore = useExtensionStore()
|
||||
extensionService = useExtensionService()
|
||||
|
||||
mockCanvas = {
|
||||
graph_mouse: [100, 100],
|
||||
selectedItems: new Set()
|
||||
} as unknown as LGraphCanvas
|
||||
|
||||
mockNode = {
|
||||
id: 1,
|
||||
type: 'TestNode',
|
||||
pos: [0, 0]
|
||||
} as unknown as LGraphNode
|
||||
})
|
||||
|
||||
describe('collectCanvasMenuItems', () => {
|
||||
it('should call getCanvasMenuItems and collect into flat array', () => {
|
||||
const ext1 = createCanvasMenuExtension('Extension 1', [canvasMenuItem1])
|
||||
const ext2 = createCanvasMenuExtension('Extension 2', [
|
||||
canvasMenuItem2,
|
||||
{ content: 'Item 3', callback: () => {} }
|
||||
])
|
||||
|
||||
extensionStore.registerExtension(ext1)
|
||||
extensionStore.registerExtension(ext2)
|
||||
|
||||
const items = extensionService
|
||||
.invokeExtensions('getCanvasMenuItems', mockCanvas)
|
||||
.flat() as IContextMenuValue[]
|
||||
|
||||
expect(items).toHaveLength(3)
|
||||
expect(items[0]).toMatchObject({ content: 'Canvas Item 1' })
|
||||
expect(items[1]).toMatchObject({ content: 'Canvas Item 2' })
|
||||
expect(items[2]).toMatchObject({ content: 'Item 3' })
|
||||
})
|
||||
|
||||
it('should support submenus and separators', () => {
|
||||
const extension = createCanvasMenuExtension('Test Extension', [
|
||||
{
|
||||
content: 'Menu with Submenu',
|
||||
has_submenu: true,
|
||||
submenu: {
|
||||
options: [
|
||||
{ content: 'Submenu Item 1', callback: () => {} },
|
||||
{ content: 'Submenu Item 2', callback: () => {} }
|
||||
]
|
||||
}
|
||||
},
|
||||
null as unknown as IContextMenuValue,
|
||||
{ content: 'After Separator', callback: () => {} }
|
||||
])
|
||||
|
||||
extensionStore.registerExtension(extension)
|
||||
|
||||
const items = extensionService
|
||||
.invokeExtensions('getCanvasMenuItems', mockCanvas)
|
||||
.flat() as IContextMenuValue[]
|
||||
|
||||
expect(items).toHaveLength(3)
|
||||
expect(items[0].content).toBe('Menu with Submenu')
|
||||
expect(items[0].submenu?.options).toHaveLength(2)
|
||||
expect(items[1]).toBeNull()
|
||||
expect(items[2].content).toBe('After Separator')
|
||||
})
|
||||
|
||||
it('should skip extensions without getCanvasMenuItems', () => {
|
||||
const canvasExtension = createCanvasMenuExtension('Canvas Ext', [
|
||||
canvasMenuItem1
|
||||
])
|
||||
const extensionWithoutCanvasMenu: ComfyExtension = {
|
||||
name: 'No Canvas Menu'
|
||||
}
|
||||
|
||||
extensionStore.registerExtension(canvasExtension)
|
||||
extensionStore.registerExtension(extensionWithoutCanvasMenu)
|
||||
|
||||
const items = extensionService
|
||||
.invokeExtensions('getCanvasMenuItems', mockCanvas)
|
||||
.flat() as IContextMenuValue[]
|
||||
|
||||
expect(items).toHaveLength(1)
|
||||
expect(items[0].content).toBe('Canvas Item 1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('collectNodeMenuItems', () => {
|
||||
it('should call getNodeMenuItems and collect into flat array', () => {
|
||||
const ext1 = createNodeMenuExtension('Extension 1', [nodeMenuItem1])
|
||||
const ext2 = createNodeMenuExtension('Extension 2', [
|
||||
nodeMenuItem2,
|
||||
{ content: 'Item 3', callback: () => {} }
|
||||
])
|
||||
|
||||
extensionStore.registerExtension(ext1)
|
||||
extensionStore.registerExtension(ext2)
|
||||
|
||||
const items = extensionService
|
||||
.invokeExtensions('getNodeMenuItems', mockNode)
|
||||
.flat() as IContextMenuValue[]
|
||||
|
||||
expect(items).toHaveLength(3)
|
||||
expect(items[0]).toMatchObject({ content: 'Node Item 1' })
|
||||
expect(items[1]).toMatchObject({ content: 'Node Item 2' })
|
||||
})
|
||||
|
||||
it('should support submenus', () => {
|
||||
const extension = createNodeMenuExtension('Submenu Extension', [
|
||||
{
|
||||
content: 'Node Menu with Submenu',
|
||||
has_submenu: true,
|
||||
submenu: {
|
||||
options: [
|
||||
{ content: 'Node Submenu 1', callback: () => {} },
|
||||
{ content: 'Node Submenu 2', callback: () => {} }
|
||||
]
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
extensionStore.registerExtension(extension)
|
||||
|
||||
const items = extensionService
|
||||
.invokeExtensions('getNodeMenuItems', mockNode)
|
||||
.flat() as IContextMenuValue[]
|
||||
|
||||
expect(items[0].content).toBe('Node Menu with Submenu')
|
||||
expect(items[0].submenu?.options).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('should skip extensions without getNodeMenuItems', () => {
|
||||
const nodeExtension = createNodeMenuExtension('Node Ext', [nodeMenuItem1])
|
||||
const extensionWithoutNodeMenu: ComfyExtension = {
|
||||
name: 'No Node Menu'
|
||||
}
|
||||
|
||||
extensionStore.registerExtension(nodeExtension)
|
||||
extensionStore.registerExtension(extensionWithoutNodeMenu)
|
||||
|
||||
const items = extensionService
|
||||
.invokeExtensions('getNodeMenuItems', mockNode)
|
||||
.flat() as IContextMenuValue[]
|
||||
|
||||
expect(items).toHaveLength(1)
|
||||
expect(items[0].content).toBe('Node Item 1')
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user