mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-02 14:27:40 +00:00
459 lines
14 KiB
TypeScript
459 lines
14 KiB
TypeScript
import { describe, expect, vi } from "vitest"
|
|
|
|
import { subgraphTest } from "./fixtures/subgraphFixtures"
|
|
import { verifyEventSequence } from "./fixtures/subgraphHelpers"
|
|
|
|
describe("SubgraphEvents - Event Payload Verification", () => {
|
|
subgraphTest("dispatches input-added with correct payload", ({ eventCapture }) => {
|
|
const { subgraph, capture } = eventCapture
|
|
|
|
const input = subgraph.addInput("test_input", "number")
|
|
|
|
const addedEvents = capture.getEventsByType("input-added")
|
|
expect(addedEvents).toHaveLength(1)
|
|
|
|
expect(addedEvents[0].detail).toEqual({
|
|
input: expect.objectContaining({
|
|
name: "test_input",
|
|
type: "number",
|
|
}),
|
|
})
|
|
|
|
expect(addedEvents[0].detail.input).toBe(input)
|
|
})
|
|
|
|
subgraphTest("dispatches output-added with correct payload", ({ eventCapture }) => {
|
|
const { subgraph, capture } = eventCapture
|
|
|
|
const output = subgraph.addOutput("test_output", "string")
|
|
|
|
const addedEvents = capture.getEventsByType("output-added")
|
|
expect(addedEvents).toHaveLength(1)
|
|
|
|
expect(addedEvents[0].detail).toEqual({
|
|
output: expect.objectContaining({
|
|
name: "test_output",
|
|
type: "string",
|
|
}),
|
|
})
|
|
|
|
expect(addedEvents[0].detail.output).toBe(output)
|
|
})
|
|
|
|
subgraphTest("dispatches removing-input with correct payload", ({ eventCapture }) => {
|
|
const { subgraph, capture } = eventCapture
|
|
|
|
const input = subgraph.addInput("to_remove", "boolean")
|
|
|
|
capture.clear()
|
|
|
|
subgraph.removeInput(input)
|
|
|
|
const removingEvents = capture.getEventsByType("removing-input")
|
|
expect(removingEvents).toHaveLength(1)
|
|
|
|
expect(removingEvents[0].detail).toEqual({
|
|
input: expect.objectContaining({
|
|
name: "to_remove",
|
|
type: "boolean",
|
|
}),
|
|
index: 0,
|
|
})
|
|
|
|
expect(removingEvents[0].detail.input).toBe(input)
|
|
})
|
|
|
|
subgraphTest("dispatches removing-output with correct payload", ({ eventCapture }) => {
|
|
const { subgraph, capture } = eventCapture
|
|
|
|
const output = subgraph.addOutput("to_remove", "number")
|
|
|
|
capture.clear()
|
|
|
|
subgraph.removeOutput(output)
|
|
|
|
const removingEvents = capture.getEventsByType("removing-output")
|
|
expect(removingEvents).toHaveLength(1)
|
|
|
|
expect(removingEvents[0].detail).toEqual({
|
|
output: expect.objectContaining({
|
|
name: "to_remove",
|
|
type: "number",
|
|
}),
|
|
index: 0,
|
|
})
|
|
|
|
expect(removingEvents[0].detail.output).toBe(output)
|
|
})
|
|
|
|
subgraphTest("dispatches renaming-input with correct payload", ({ eventCapture }) => {
|
|
const { subgraph, capture } = eventCapture
|
|
|
|
const input = subgraph.addInput("old_name", "string")
|
|
|
|
capture.clear()
|
|
|
|
subgraph.renameInput(input, "new_name")
|
|
|
|
const renamingEvents = capture.getEventsByType("renaming-input")
|
|
expect(renamingEvents).toHaveLength(1)
|
|
|
|
expect(renamingEvents[0].detail).toEqual({
|
|
input: expect.objectContaining({
|
|
type: "string",
|
|
}),
|
|
index: 0,
|
|
oldName: "old_name",
|
|
newName: "new_name",
|
|
})
|
|
|
|
expect(renamingEvents[0].detail.input).toBe(input)
|
|
|
|
// Verify the label was updated after the event (renameInput sets label, not name)
|
|
expect(input.label).toBe("new_name")
|
|
expect(input.displayName).toBe("new_name")
|
|
expect(input.name).toBe("old_name")
|
|
})
|
|
|
|
subgraphTest("dispatches renaming-output with correct payload", ({ eventCapture }) => {
|
|
const { subgraph, capture } = eventCapture
|
|
|
|
const output = subgraph.addOutput("old_name", "number")
|
|
|
|
capture.clear()
|
|
|
|
subgraph.renameOutput(output, "new_name")
|
|
|
|
const renamingEvents = capture.getEventsByType("renaming-output")
|
|
expect(renamingEvents).toHaveLength(1)
|
|
|
|
expect(renamingEvents[0].detail).toEqual({
|
|
output: expect.objectContaining({
|
|
name: "old_name", // Should still have the old name when event is dispatched
|
|
type: "number",
|
|
}),
|
|
index: 0,
|
|
oldName: "old_name",
|
|
newName: "new_name",
|
|
})
|
|
|
|
expect(renamingEvents[0].detail.output).toBe(output)
|
|
|
|
// Verify the label was updated after the event
|
|
expect(output.label).toBe("new_name")
|
|
expect(output.displayName).toBe("new_name")
|
|
expect(output.name).toBe("old_name")
|
|
})
|
|
|
|
subgraphTest("dispatches adding-input with correct payload", ({ eventCapture }) => {
|
|
const { subgraph, capture } = eventCapture
|
|
|
|
subgraph.addInput("test_input", "number")
|
|
|
|
const addingEvents = capture.getEventsByType("adding-input")
|
|
expect(addingEvents).toHaveLength(1)
|
|
|
|
expect(addingEvents[0].detail).toEqual({
|
|
name: "test_input",
|
|
type: "number",
|
|
})
|
|
})
|
|
|
|
subgraphTest("dispatches adding-output with correct payload", ({ eventCapture }) => {
|
|
const { subgraph, capture } = eventCapture
|
|
|
|
subgraph.addOutput("test_output", "string")
|
|
|
|
const addingEvents = capture.getEventsByType("adding-output")
|
|
expect(addingEvents).toHaveLength(1)
|
|
|
|
expect(addingEvents[0].detail).toEqual({
|
|
name: "test_output",
|
|
type: "string",
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("SubgraphEvents - Event Handler Isolation", () => {
|
|
subgraphTest("continues dispatching if handler throws", ({ emptySubgraph }) => {
|
|
const handler1 = vi.fn(() => {
|
|
throw new Error("Handler 1 error")
|
|
})
|
|
const handler2 = vi.fn()
|
|
const handler3 = vi.fn()
|
|
|
|
emptySubgraph.events.addEventListener("input-added", handler1)
|
|
emptySubgraph.events.addEventListener("input-added", handler2)
|
|
emptySubgraph.events.addEventListener("input-added", handler3)
|
|
|
|
// The operation itself should not throw (error is isolated)
|
|
expect(() => {
|
|
emptySubgraph.addInput("test", "number")
|
|
}).not.toThrow()
|
|
|
|
// Verify all handlers were called despite the first one throwing
|
|
expect(handler1).toHaveBeenCalled()
|
|
expect(handler2).toHaveBeenCalled()
|
|
expect(handler3).toHaveBeenCalled()
|
|
|
|
// Verify the throwing handler actually received the event
|
|
expect(handler1).toHaveBeenCalledWith(expect.objectContaining({
|
|
type: "input-added",
|
|
}))
|
|
|
|
// Verify other handlers received correct event data
|
|
expect(handler2).toHaveBeenCalledWith(expect.objectContaining({
|
|
type: "input-added",
|
|
detail: expect.objectContaining({
|
|
input: expect.objectContaining({
|
|
name: "test",
|
|
type: "number",
|
|
}),
|
|
}),
|
|
}))
|
|
expect(handler3).toHaveBeenCalledWith(expect.objectContaining({
|
|
type: "input-added",
|
|
}))
|
|
})
|
|
|
|
subgraphTest("maintains handler execution order", ({ emptySubgraph }) => {
|
|
const executionOrder: number[] = []
|
|
|
|
const handler1 = vi.fn(() => executionOrder.push(1))
|
|
const handler2 = vi.fn(() => executionOrder.push(2))
|
|
const handler3 = vi.fn(() => executionOrder.push(3))
|
|
|
|
emptySubgraph.events.addEventListener("input-added", handler1)
|
|
emptySubgraph.events.addEventListener("input-added", handler2)
|
|
emptySubgraph.events.addEventListener("input-added", handler3)
|
|
|
|
emptySubgraph.addInput("test", "number")
|
|
|
|
expect(executionOrder).toEqual([1, 2, 3])
|
|
})
|
|
|
|
subgraphTest("prevents handler accumulation with proper cleanup", ({ emptySubgraph }) => {
|
|
const handler = vi.fn()
|
|
|
|
for (let i = 0; i < 5; i++) {
|
|
emptySubgraph.events.addEventListener("input-added", handler)
|
|
emptySubgraph.events.removeEventListener("input-added", handler)
|
|
}
|
|
|
|
emptySubgraph.events.addEventListener("input-added", handler)
|
|
|
|
emptySubgraph.addInput("test", "number")
|
|
|
|
expect(handler).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
subgraphTest("supports AbortController cleanup patterns", ({ emptySubgraph }) => {
|
|
const abortController = new AbortController()
|
|
const { signal } = abortController
|
|
|
|
const handler = vi.fn()
|
|
|
|
emptySubgraph.events.addEventListener("input-added", handler, { signal })
|
|
|
|
emptySubgraph.addInput("test1", "number")
|
|
expect(handler).toHaveBeenCalledTimes(1)
|
|
|
|
abortController.abort()
|
|
|
|
emptySubgraph.addInput("test2", "number")
|
|
expect(handler).toHaveBeenCalledTimes(1)
|
|
})
|
|
})
|
|
|
|
describe("SubgraphEvents - Event Sequence Testing", () => {
|
|
subgraphTest("maintains correct event sequence for inputs", ({ eventCapture }) => {
|
|
const { subgraph, capture } = eventCapture
|
|
|
|
subgraph.addInput("input1", "number")
|
|
|
|
verifyEventSequence(capture.events, [
|
|
"adding-input",
|
|
"input-added",
|
|
])
|
|
})
|
|
|
|
subgraphTest("maintains correct event sequence for outputs", ({ eventCapture }) => {
|
|
const { subgraph, capture } = eventCapture
|
|
|
|
subgraph.addOutput("output1", "string")
|
|
|
|
verifyEventSequence(capture.events, [
|
|
"adding-output",
|
|
"output-added",
|
|
])
|
|
})
|
|
|
|
subgraphTest("maintains correct event sequence for rapid operations", ({ eventCapture }) => {
|
|
const { subgraph, capture } = eventCapture
|
|
|
|
subgraph.addInput("input1", "number")
|
|
subgraph.addInput("input2", "string")
|
|
subgraph.addOutput("output1", "boolean")
|
|
subgraph.addOutput("output2", "number")
|
|
|
|
verifyEventSequence(capture.events, [
|
|
"adding-input",
|
|
"input-added",
|
|
"adding-input",
|
|
"input-added",
|
|
"adding-output",
|
|
"output-added",
|
|
"adding-output",
|
|
"output-added",
|
|
])
|
|
})
|
|
|
|
subgraphTest("handles concurrent event handling", ({ eventCapture }) => {
|
|
const { subgraph, capture } = eventCapture
|
|
|
|
const handler1 = vi.fn(() => {
|
|
return new Promise(resolve => setTimeout(resolve, 1))
|
|
})
|
|
|
|
const handler2 = vi.fn()
|
|
const handler3 = vi.fn()
|
|
|
|
subgraph.events.addEventListener("input-added", handler1)
|
|
subgraph.events.addEventListener("input-added", handler2)
|
|
subgraph.events.addEventListener("input-added", handler3)
|
|
|
|
subgraph.addInput("test", "number")
|
|
|
|
expect(handler1).toHaveBeenCalled()
|
|
expect(handler2).toHaveBeenCalled()
|
|
expect(handler3).toHaveBeenCalled()
|
|
|
|
const addedEvents = capture.getEventsByType("input-added")
|
|
expect(addedEvents).toHaveLength(1)
|
|
})
|
|
|
|
subgraphTest("validates event timestamps are properly ordered", ({ eventCapture }) => {
|
|
const { subgraph, capture } = eventCapture
|
|
|
|
subgraph.addInput("input1", "number")
|
|
subgraph.addInput("input2", "string")
|
|
subgraph.addOutput("output1", "boolean")
|
|
|
|
for (let i = 1; i < capture.events.length; i++) {
|
|
expect(capture.events[i].timestamp).toBeGreaterThanOrEqual(
|
|
capture.events[i - 1].timestamp,
|
|
)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe("SubgraphEvents - Event Cancellation", () => {
|
|
subgraphTest("supports preventDefault() for cancellable events", ({ emptySubgraph }) => {
|
|
const preventHandler = vi.fn((event: Event) => {
|
|
event.preventDefault()
|
|
})
|
|
|
|
emptySubgraph.events.addEventListener("removing-input", preventHandler)
|
|
|
|
const input = emptySubgraph.addInput("test", "number")
|
|
|
|
emptySubgraph.removeInput(input)
|
|
|
|
expect(emptySubgraph.inputs).toContain(input)
|
|
expect(preventHandler).toHaveBeenCalled()
|
|
})
|
|
|
|
subgraphTest("supports preventDefault() for output removal", ({ emptySubgraph }) => {
|
|
const preventHandler = vi.fn((event: Event) => {
|
|
event.preventDefault()
|
|
})
|
|
|
|
emptySubgraph.events.addEventListener("removing-output", preventHandler)
|
|
|
|
const output = emptySubgraph.addOutput("test", "number")
|
|
|
|
emptySubgraph.removeOutput(output)
|
|
|
|
expect(emptySubgraph.outputs).toContain(output)
|
|
expect(preventHandler).toHaveBeenCalled()
|
|
})
|
|
|
|
subgraphTest("allows removal when not prevented", ({ emptySubgraph }) => {
|
|
const allowHandler = vi.fn()
|
|
|
|
emptySubgraph.events.addEventListener("removing-input", allowHandler)
|
|
|
|
const input = emptySubgraph.addInput("test", "number")
|
|
|
|
emptySubgraph.removeInput(input)
|
|
|
|
expect(emptySubgraph.inputs).not.toContain(input)
|
|
expect(emptySubgraph.inputs).toHaveLength(0)
|
|
expect(allowHandler).toHaveBeenCalled()
|
|
})
|
|
})
|
|
|
|
describe("SubgraphEvents - Event Detail Structure Validation", () => {
|
|
subgraphTest("validates all event detail structures match TypeScript types", ({ eventCapture }) => {
|
|
const { subgraph, capture } = eventCapture
|
|
|
|
const input = subgraph.addInput("test_input", "number")
|
|
subgraph.renameInput(input, "renamed_input")
|
|
subgraph.removeInput(input)
|
|
|
|
const output = subgraph.addOutput("test_output", "string")
|
|
subgraph.renameOutput(output, "renamed_output")
|
|
subgraph.removeOutput(output)
|
|
|
|
const addingInputEvent = capture.getEventsByType("adding-input")[0]
|
|
expect(addingInputEvent.detail).toEqual({
|
|
name: expect.any(String),
|
|
type: expect.any(String),
|
|
})
|
|
|
|
const inputAddedEvent = capture.getEventsByType("input-added")[0]
|
|
expect(inputAddedEvent.detail).toEqual({
|
|
input: expect.any(Object),
|
|
})
|
|
|
|
const renamingInputEvent = capture.getEventsByType("renaming-input")[0]
|
|
expect(renamingInputEvent.detail).toEqual({
|
|
input: expect.any(Object),
|
|
index: expect.any(Number),
|
|
oldName: expect.any(String),
|
|
newName: expect.any(String),
|
|
})
|
|
|
|
const removingInputEvent = capture.getEventsByType("removing-input")[0]
|
|
expect(removingInputEvent.detail).toEqual({
|
|
input: expect.any(Object),
|
|
index: expect.any(Number),
|
|
})
|
|
|
|
const addingOutputEvent = capture.getEventsByType("adding-output")[0]
|
|
expect(addingOutputEvent.detail).toEqual({
|
|
name: expect.any(String),
|
|
type: expect.any(String),
|
|
})
|
|
|
|
const outputAddedEvent = capture.getEventsByType("output-added")[0]
|
|
expect(outputAddedEvent.detail).toEqual({
|
|
output: expect.any(Object),
|
|
})
|
|
|
|
const renamingOutputEvent = capture.getEventsByType("renaming-output")[0]
|
|
expect(renamingOutputEvent.detail).toEqual({
|
|
output: expect.any(Object),
|
|
index: expect.any(Number),
|
|
oldName: expect.any(String),
|
|
newName: expect.any(String),
|
|
})
|
|
|
|
const removingOutputEvent = capture.getEventsByType("removing-output")[0]
|
|
expect(removingOutputEvent.detail).toEqual({
|
|
output: expect.any(Object),
|
|
index: expect.any(Number),
|
|
})
|
|
})
|
|
})
|