# Subgraph Testing Fixtures and Utilities This directory contains the testing infrastructure for LiteGraph's subgraph functionality. These utilities provide a consistent, easy-to-use API for writing subgraph tests. ## What is a Subgraph? A subgraph in LiteGraph is a graph-within-a-graph that can be reused as a single node. It has: - Input slots that map to an internal input node - Output slots that map to an internal output node - Internal nodes and connections - The ability to be instantiated multiple times as SubgraphNode instances ## Quick Start ```typescript // Import what you need import { createTestSubgraph, assertSubgraphStructure } from "./fixtures/subgraphHelpers" import { subgraphTest } from "./fixtures/subgraphFixtures" // Option 1: Create a subgraph manually it("should do something", () => { const subgraph = createTestSubgraph({ name: "My Test Subgraph", inputCount: 2, outputCount: 1 }) // Test your functionality expect(subgraph.inputs).toHaveLength(2) }) // Option 2: Use pre-configured fixtures subgraphTest("should handle events", ({ simpleSubgraph, eventCapture }) => { // simpleSubgraph comes pre-configured with 1 input, 1 output, and 2 nodes expect(simpleSubgraph.inputs).toHaveLength(1) // Your test logic here }) ``` ## Files Overview ### `subgraphHelpers.ts` - Core Helper Functions **Main Factory Functions:** - `createTestSubgraph(options?)` - Creates a fully configured Subgraph instance with root graph - `createTestSubgraphNode(subgraph, options?)` - Creates a SubgraphNode (instance of a subgraph) - `createNestedSubgraphs(options?)` - Creates nested subgraph hierarchies for testing deep structures **Assertion & Validation:** - `assertSubgraphStructure(subgraph, expected)` - Validates subgraph has expected inputs/outputs/nodes - `verifyEventSequence(events, expectedSequence)` - Ensures events fired in correct order - `logSubgraphStructure(subgraph, label?)` - Debug helper to print subgraph structure **Test Data & Events:** - `createTestSubgraphData(overrides?)` - Creates raw ExportedSubgraph data for serialization tests - `createComplexSubgraphData(nodeCount?)` - Generates complex subgraph with internal connections - `createEventCapture(eventTarget, eventTypes)` - Sets up event monitoring with automatic cleanup ### `subgraphFixtures.ts` - Vitest Fixtures Pre-configured test scenarios that automatically set up and tear down: **Basic Fixtures (`subgraphTest`):** - `emptySubgraph` - Minimal subgraph with no inputs/outputs/nodes - `simpleSubgraph` - 1 input ("input": number), 1 output ("output": number), 2 internal nodes - `complexSubgraph` - 3 inputs (data, control, text), 2 outputs (result, status), 5 nodes - `nestedSubgraph` - 3-level deep hierarchy with 2 nodes per level - `subgraphWithNode` - Complete setup: subgraph definition + SubgraphNode instance + parent graph - `eventCapture` - Subgraph with event monitoring for all I/O events **Edge Case Fixtures (`edgeCaseTest`):** - `circularSubgraph` - Two subgraphs set up for circular reference testing - `deeplyNestedSubgraph` - 50 levels deep for performance/limit testing - `maxIOSubgraph` - 20 inputs and 20 outputs for stress testing ### `testSubgraphs.json` - Sample Test Data Pre-defined subgraph configurations for consistent testing across different scenarios. **Note on Static UUIDs**: The hardcoded UUIDs in this file (e.g., "simple-subgraph-uuid", "complex-subgraph-uuid") are intentionally static to ensure test reproducibility and snapshot testing compatibility. ## Usage Examples ### Basic Test Creation ```typescript import { describe, expect, it } from "vitest" import { createTestSubgraph, assertSubgraphStructure } from "./fixtures/subgraphHelpers" describe("My Subgraph Feature", () => { it("should work correctly", () => { const subgraph = createTestSubgraph({ name: "My Test", inputCount: 2, outputCount: 1, nodeCount: 3 }) assertSubgraphStructure(subgraph, { inputCount: 2, outputCount: 1, nodeCount: 3, name: "My Test" }) // Your specific test logic... }) }) ``` ### Using Fixtures ```typescript import { subgraphTest } from "./fixtures/subgraphFixtures" subgraphTest("should handle events", ({ eventCapture }) => { const { subgraph, capture } = eventCapture subgraph.addInput("test", "number") expect(capture.events).toHaveLength(2) // adding-input, input-added }) ``` ### Event Testing ```typescript import { createEventCapture, verifyEventSequence } from "./fixtures/subgraphHelpers" it("should fire events in correct order", () => { const subgraph = createTestSubgraph() const capture = createEventCapture(subgraph.events, ["adding-input", "input-added"]) subgraph.addInput("test", "number") verifyEventSequence(capture.events, ["adding-input", "input-added"]) capture.cleanup() // Important: clean up listeners }) ``` ### Nested Structure Testing ```typescript import { createNestedSubgraphs } from "./fixtures/subgraphHelpers" it("should handle deep nesting", () => { const nested = createNestedSubgraphs({ depth: 5, nodesPerLevel: 2 }) expect(nested.subgraphs).toHaveLength(5) expect(nested.leafSubgraph.nodes).toHaveLength(2) }) ``` ## Common Patterns ### Testing SubgraphNode Instances ```typescript it("should create and configure a SubgraphNode", () => { // First create the subgraph definition const subgraph = createTestSubgraph({ inputs: [{ name: "value", type: "number" }], outputs: [{ name: "result", type: "number" }] }) // Then create an instance of it const subgraphNode = createTestSubgraphNode(subgraph, { pos: [100, 200], size: [180, 100] }) // The SubgraphNode will have matching slots expect(subgraphNode.inputs).toHaveLength(1) expect(subgraphNode.outputs).toHaveLength(1) expect(subgraphNode.subgraph).toBe(subgraph) }) ``` ### Complete Test with Parent Graph ```typescript subgraphTest("should work in a parent graph", ({ subgraphWithNode }) => { const { subgraph, subgraphNode, parentGraph } = subgraphWithNode // Everything is pre-configured and connected expect(parentGraph.nodes).toContain(subgraphNode) expect(subgraphNode.graph).toBe(parentGraph) expect(subgraphNode.subgraph).toBe(subgraph) }) ``` ## Configuration Options ### `createTestSubgraph(options)` ```typescript interface TestSubgraphOptions { id?: UUID // Custom UUID name?: string // Custom name nodeCount?: number // Number of internal nodes inputCount?: number // Number of inputs (uses generic types) outputCount?: number // Number of outputs (uses generic types) inputs?: Array<{ // Specific input definitions name: string type: ISlotType }> outputs?: Array<{ // Specific output definitions name: string type: ISlotType }> } ``` **Note**: Cannot specify both `inputs` array and `inputCount` (or `outputs` array and `outputCount`) - the function will throw an error with details. ### `createNestedSubgraphs(options)` ```typescript interface NestedSubgraphOptions { depth?: number // Nesting depth (default: 2) nodesPerLevel?: number // Nodes per subgraph (default: 2) inputsPerSubgraph?: number // Inputs per subgraph (default: 1) outputsPerSubgraph?: number // Outputs per subgraph (default: 1) } ``` ## Important Architecture Notes ### Subgraph vs SubgraphNode - **Subgraph**: The definition/template (like a class definition) - **SubgraphNode**: An instance of a subgraph placed in a graph (like a class instance) - One Subgraph can have many SubgraphNode instances ### Special Node IDs - Input node always has ID `-10` (SUBGRAPH_INPUT_ID) - Output node always has ID `-20` (SUBGRAPH_OUTPUT_ID) - These are virtual nodes that exist in every subgraph ### Common Pitfalls 1. **Array vs Index**: The `inputs` and `outputs` arrays don't have an `index` property on items. Use `indexOf()`: ```typescript // ❌ Wrong expect(input.index).toBe(0) // ✅ Correct expect(subgraph.inputs.indexOf(input)).toBe(0) ``` 2. **Graph vs Subgraph Property**: SubgraphInputNode/OutputNode have `subgraph`, not `graph`: ```typescript // ❌ Wrong expect(inputNode.graph).toBe(subgraph) // ✅ Correct expect(inputNode.subgraph).toBe(subgraph) ``` 3. **Event Detail Structure**: Events have specific detail structures: ```typescript // Input events "adding-input": { name: string, type: string } "input-added": { input: SubgraphInput, index: number } // Output events "adding-output": { name: string, type: string } "output-added": { output: SubgraphOutput, index: number } ``` 4. **Links are stored in a Map**: Use `.size` not `.length`: ```typescript // ❌ Wrong expect(subgraph.links.length).toBe(1) // ✅ Correct expect(subgraph.links.size).toBe(1) ``` ## Testing Best Practices - Always use helper functions instead of manual setup - Use fixtures for common scenarios to avoid repetitive code - Clean up event listeners with `capture.cleanup()` after event tests - Use `verifyEventSequence()` to test event ordering - Remember fixtures are created fresh for each test (no shared state) - Use `assertSubgraphStructure()` for comprehensive validation ## Debugging Tips - Use `logSubgraphStructure(subgraph)` to print subgraph details - Check `subgraph.rootGraph` to verify graph hierarchy - Event capture includes timestamps for debugging timing issues - All factory functions accept optional parameters for customization ## Adding New Test Utilities When extending the test infrastructure: 1. Add new helper functions to `subgraphHelpers.ts` 2. Add new fixtures to `subgraphFixtures.ts` 3. Update this README with usage examples 4. Follow existing patterns for consistency 5. Add TypeScript types for all parameters ## Performance Notes - Helper functions are optimized for test clarity, not performance - Use `structuredClone()` for deep copying test data - Event capture systems automatically clean up listeners - Fixtures are created fresh for each test to avoid state contamination