mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-21 07:14:11 +00:00
[test] Add subgraph units tests for events and i/o (#1126)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
458
test/subgraph/SubgraphEvents.test.ts
Normal file
458
test/subgraph/SubgraphEvents.test.ts
Normal file
@@ -0,0 +1,458 @@
|
||||
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),
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user