// TODO: Fix these tests after migration /** * SubgraphSerialization Tests * * Tests for saving, loading, and version compatibility of subgraphs. * This covers serialization, deserialization, data integrity, and migration scenarios. */ import { describe, expect, it } from 'vitest' import { LGraph, Subgraph } from '@/lib/litegraph/src/litegraph' import { createTestSubgraph, createTestSubgraphNode } from './fixtures/subgraphHelpers' describe.skip('SubgraphSerialization - Basic Serialization', () => { it('should save and load simple subgraphs', () => { const original = createTestSubgraph({ name: 'Simple Test', nodeCount: 2 }) original.addInput('in1', 'number') original.addInput('in2', 'string') original.addOutput('out', 'boolean') // Serialize const exported = original.asSerialisable() // Verify exported structure expect(exported).toHaveProperty('id', original.id) expect(exported).toHaveProperty('name', 'Simple Test') expect(exported).toHaveProperty('nodes') expect(exported).toHaveProperty('links') expect(exported).toHaveProperty('inputs') expect(exported).toHaveProperty('outputs') expect(exported).toHaveProperty('version') // Create new instance from serialized data const restored = new Subgraph(new LGraph(), exported) // Verify structure is preserved expect(restored.id).toBe(original.id) expect(restored.name).toBe(original.name) expect(restored.inputs.length).toBe(2) // Only added inputs, not original nodeCount expect(restored.outputs.length).toBe(1) // Note: nodes may not be restored if they're not registered types // This is expected behavior - serialization preserves I/O but nodes need valid types // Verify input details expect(restored.inputs[0].name).toBe('in1') expect(restored.inputs[0].type).toBe('number') expect(restored.inputs[1].name).toBe('in2') expect(restored.inputs[1].type).toBe('string') expect(restored.outputs[0].name).toBe('out') expect(restored.outputs[0].type).toBe('boolean') }) it('should verify all properties are preserved', () => { const original = createTestSubgraph({ name: 'Property Test', nodeCount: 3, inputs: [ { name: 'input1', type: 'number' }, { name: 'input2', type: 'string' } ], outputs: [ { name: 'output1', type: 'boolean' }, { name: 'output2', type: 'array' } ] }) const exported = original.asSerialisable() const restored = new Subgraph(new LGraph(), exported) // Verify core properties expect(restored.id).toBe(original.id) expect(restored.name).toBe(original.name) // @ts-expect-error description property not in type definition expect(restored.description).toBe(original.description) // Verify I/O structure expect(restored.inputs.length).toBe(original.inputs.length) expect(restored.outputs.length).toBe(original.outputs.length) // Nodes may not be restored if they don't have registered types // Verify I/O details match for (let i = 0; i < original.inputs.length; i++) { expect(restored.inputs[i].name).toBe(original.inputs[i].name) expect(restored.inputs[i].type).toBe(original.inputs[i].type) } for (let i = 0; i < original.outputs.length; i++) { expect(restored.outputs[i].name).toBe(original.outputs[i].name) expect(restored.outputs[i].type).toBe(original.outputs[i].type) } }) it('should test export() and configure() methods', () => { const subgraph = createTestSubgraph({ nodeCount: 1 }) subgraph.addInput('test_input', 'number') subgraph.addOutput('test_output', 'string') // Test export const exported = subgraph.asSerialisable() expect(exported).toHaveProperty('id') expect(exported).toHaveProperty('nodes') expect(exported).toHaveProperty('links') expect(exported).toHaveProperty('inputs') expect(exported).toHaveProperty('outputs') // Test configure with partial data const newSubgraph = createTestSubgraph({ nodeCount: 0 }) expect(() => { newSubgraph.configure(exported) }).not.toThrow() // Verify configuration applied expect(newSubgraph.inputs.length).toBe(1) expect(newSubgraph.outputs.length).toBe(1) expect(newSubgraph.inputs[0].name).toBe('test_input') expect(newSubgraph.outputs[0].name).toBe('test_output') }) }) describe.skip('SubgraphSerialization - Complex Serialization', () => { it('should serialize nested subgraphs with multiple levels', () => { // Create a nested structure const childSubgraph = createTestSubgraph({ name: 'Child', nodeCount: 2, inputs: [{ name: 'child_in', type: 'number' }], outputs: [{ name: 'child_out', type: 'string' }] }) const parentSubgraph = createTestSubgraph({ name: 'Parent', nodeCount: 1, inputs: [{ name: 'parent_in', type: 'boolean' }], outputs: [{ name: 'parent_out', type: 'array' }] }) // Add child to parent const childInstance = createTestSubgraphNode(childSubgraph, { id: 100 }) parentSubgraph.add(childInstance) // Serialize both const childExported = childSubgraph.asSerialisable() const parentExported = parentSubgraph.asSerialisable() // Verify both can be serialized expect(childExported).toHaveProperty('name', 'Child') expect(parentExported).toHaveProperty('name', 'Parent') expect(parentExported.nodes.length).toBe(2) // 1 original + 1 child subgraph // Restore and verify const restoredChild = new Subgraph(new LGraph(), childExported) const restoredParent = new Subgraph(new LGraph(), parentExported) expect(restoredChild.name).toBe('Child') expect(restoredParent.name).toBe('Parent') expect(restoredChild.inputs.length).toBe(1) expect(restoredParent.inputs.length).toBe(1) }) it('should serialize subgraphs with many nodes and connections', () => { const largeSubgraph = createTestSubgraph({ name: 'Large Subgraph', nodeCount: 10 // Many nodes }) // Add many I/O slots for (let i = 0; i < 5; i++) { largeSubgraph.addInput(`input_${i}`, 'number') largeSubgraph.addOutput(`output_${i}`, 'string') } const exported = largeSubgraph.asSerialisable() const restored = new Subgraph(new LGraph(), exported) // Verify I/O data preserved expect(restored.inputs.length).toBe(5) expect(restored.outputs.length).toBe(5) // Nodes may not be restored if they don't have registered types // Verify I/O naming preserved for (let i = 0; i < 5; i++) { expect(restored.inputs[i].name).toBe(`input_${i}`) expect(restored.outputs[i].name).toBe(`output_${i}`) } }) it('should preserve custom node data', () => { const subgraph = createTestSubgraph({ nodeCount: 2 }) // Add custom properties to nodes (if supported) const nodes = subgraph.nodes if (nodes.length > 0) { const firstNode = nodes[0] if (firstNode.properties) { firstNode.properties.customValue = 42 firstNode.properties.customString = 'test' } } const exported = subgraph.asSerialisable() const restored = new Subgraph(new LGraph(), exported) // Test nodes may not be restored if they don't have registered types // This is expected behavior // Custom properties preservation depends on node implementation // This test documents the expected behavior if (restored.nodes.length > 0 && restored.nodes[0].properties) { // Properties should be preserved if the node supports them expect(restored.nodes[0].properties).toBeDefined() } }) }) describe.skip('SubgraphSerialization - Version Compatibility', () => { it('should handle version field in exports', () => { const subgraph = createTestSubgraph({ nodeCount: 1 }) const exported = subgraph.asSerialisable() // Should have version field expect(exported).toHaveProperty('version') expect(typeof exported.version).toBe('number') }) it('should load version 1.0+ format', () => { const modernFormat = { version: 1, // Number as expected by current implementation id: 'test-modern-id', name: 'Modern Subgraph', nodes: [], links: {}, groups: [], config: {}, definitions: { subgraphs: [] }, inputs: [{ id: 'input-id', name: 'modern_input', type: 'number' }], outputs: [{ id: 'output-id', name: 'modern_output', type: 'string' }], inputNode: { id: -10, bounding: [0, 0, 120, 60] }, outputNode: { id: -20, bounding: [300, 0, 120, 60] }, widgets: [] } expect(() => { // @ts-expect-error Type mismatch in ExportedSubgraph format const subgraph = new Subgraph(new LGraph(), modernFormat) expect(subgraph.name).toBe('Modern Subgraph') expect(subgraph.inputs.length).toBe(1) expect(subgraph.outputs.length).toBe(1) }).not.toThrow() }) it('should handle missing fields gracefully', () => { const incompleteFormat = { version: 1, id: 'incomplete-id', name: 'Incomplete Subgraph', nodes: [], links: {}, groups: [], config: {}, definitions: { subgraphs: [] }, inputNode: { id: -10, bounding: [0, 0, 120, 60] }, outputNode: { id: -20, bounding: [300, 0, 120, 60] } // Missing optional: inputs, outputs, widgets } expect(() => { // @ts-expect-error Type mismatch in ExportedSubgraph format const subgraph = new Subgraph(new LGraph(), incompleteFormat) expect(subgraph.name).toBe('Incomplete Subgraph') // Should have default empty arrays expect(Array.isArray(subgraph.inputs)).toBe(true) expect(Array.isArray(subgraph.outputs)).toBe(true) }).not.toThrow() }) it('should consider future-proofing', () => { const futureFormat = { version: 2, // Future version (number) id: 'future-id', name: 'Future Subgraph', nodes: [], links: {}, groups: [], config: {}, definitions: { subgraphs: [] }, inputs: [], outputs: [], inputNode: { id: -10, bounding: [0, 0, 120, 60] }, outputNode: { id: -20, bounding: [300, 0, 120, 60] }, widgets: [], futureFeature: 'unknown_data' // Unknown future field } // Should handle future format gracefully expect(() => { // @ts-expect-error Type mismatch in ExportedSubgraph format const subgraph = new Subgraph(new LGraph(), futureFormat) expect(subgraph.name).toBe('Future Subgraph') }).not.toThrow() }) }) describe.skip('SubgraphSerialization - Data Integrity', () => { it('should pass round-trip testing (save → load → save → compare)', () => { const original = createTestSubgraph({ name: 'Round Trip Test', nodeCount: 3, inputs: [ { name: 'rt_input1', type: 'number' }, { name: 'rt_input2', type: 'string' } ], outputs: [{ name: 'rt_output1', type: 'boolean' }] }) // First round trip const exported1 = original.asSerialisable() const restored1 = new Subgraph(new LGraph(), exported1) // Second round trip const exported2 = restored1.asSerialisable() const restored2 = new Subgraph(new LGraph(), exported2) // Compare key properties expect(restored2.id).toBe(original.id) expect(restored2.name).toBe(original.name) expect(restored2.inputs.length).toBe(original.inputs.length) expect(restored2.outputs.length).toBe(original.outputs.length) // Nodes may not be restored if they don't have registered types // Compare I/O details for (let i = 0; i < original.inputs.length; i++) { expect(restored2.inputs[i].name).toBe(original.inputs[i].name) expect(restored2.inputs[i].type).toBe(original.inputs[i].type) } for (let i = 0; i < original.outputs.length; i++) { expect(restored2.outputs[i].name).toBe(original.outputs[i].name) expect(restored2.outputs[i].type).toBe(original.outputs[i].type) } }) it('should verify IDs remain unique', () => { const subgraph1 = createTestSubgraph({ name: 'Unique1', nodeCount: 2 }) const subgraph2 = createTestSubgraph({ name: 'Unique2', nodeCount: 2 }) const exported1 = subgraph1.asSerialisable() const exported2 = subgraph2.asSerialisable() // IDs should be unique expect(exported1.id).not.toBe(exported2.id) const restored1 = new Subgraph(new LGraph(), exported1) const restored2 = new Subgraph(new LGraph(), exported2) expect(restored1.id).not.toBe(restored2.id) expect(restored1.id).toBe(subgraph1.id) expect(restored2.id).toBe(subgraph2.id) }) it('should maintain connection integrity after load', () => { const subgraph = createTestSubgraph({ nodeCount: 2 }) subgraph.addInput('connection_test', 'number') subgraph.addOutput('connection_result', 'string') const exported = subgraph.asSerialisable() const restored = new Subgraph(new LGraph(), exported) // Verify I/O connections can be established expect(restored.inputs.length).toBe(1) expect(restored.outputs.length).toBe(1) expect(restored.inputs[0].name).toBe('connection_test') expect(restored.outputs[0].name).toBe('connection_result') // Verify subgraph can be instantiated const instance = createTestSubgraphNode(restored) expect(instance.inputs.length).toBe(1) expect(instance.outputs.length).toBe(1) }) it('should preserve node positions and properties', () => { const subgraph = createTestSubgraph({ nodeCount: 2 }) // Modify node positions if possible if (subgraph.nodes.length > 0) { const node = subgraph.nodes[0] if ('pos' in node) { node.pos = [100, 200] } if ('size' in node) { node.size = [150, 80] } } const exported = subgraph.asSerialisable() const restored = new Subgraph(new LGraph(), exported) // Test nodes may not be restored if they don't have registered types // This is expected behavior // Position/size preservation depends on node implementation // This test documents the expected behavior if (restored.nodes.length > 0) { const restoredNode = restored.nodes[0] expect(restoredNode).toBeDefined() // Properties should be preserved if supported if ('pos' in restoredNode && restoredNode.pos) { expect(Array.isArray(restoredNode.pos)).toBe(true) } } }) })