Skip to main content
Glama
docs_examples.test.ts15.4 kB
/** * Integration Tests for Documentation Code Examples * * Tests all code examples from docs/API.md to ensure they work correctly. * Validates MCP tool invocations, artifact rendering, and custom renderer registration. * * @module __tests__/integration/docs_examples */ import { describe, test, expect, beforeAll, afterAll } from '@jest/globals'; import { D3GraphRenderer, GraphNode, GraphEdge } from '../../artifacts/visualizations/D3GraphRenderer.js'; import { D3HopDashboardRenderer, HopDashboardData } from '../../artifacts/renderers/d3_hop_dashboard.js'; import { InteractiveSignalMapRenderer, SignalMapData } from '../../artifacts/renderers/interactive_signal_map.js'; import { ArtifactEngine } from '../../artifacts/artifact_engine.js'; import { createScanSignalsHandler } from '../../tools/scan_project_signals.js'; import { createRenderArtifactHandler } from '../../tools/render_artifact.js'; describe('Documentation Code Examples', () => { describe('D3GraphRenderer Examples', () => { test('Basic usage example should work', () => { // Example from docs/API.md const renderer = new D3GraphRenderer(); const nodes: GraphNode[] = [ { id: 'sig1', label: 'player_health_changed', type: 'EventBus' }, { id: 'sig2', label: 'enemy_spawned', type: 'EventBus' } ]; const edges: GraphEdge[] = [ { source: 'sig1', target: 'sig2', type: 'connect' } ]; const html = renderer.generateForceDirectedGraph(nodes, edges); expect(html).toContain('<!DOCTYPE html>'); expect(html).toContain('player_health_changed'); expect(html).toContain('enemy_spawned'); expect(html).toContain('d3.forceSimulation'); // D3 code embedded }); test('Large graph with lazy loading example should work', () => { const renderer = new D3GraphRenderer(); // Lazy loading automatically activates for >100 nodes const largeNodes = Array.from({ length: 200 }, (_, i) => ({ id: `sig${i}`, label: `signal_${i}`, type: i % 2 === 0 ? 'EventBus' : 'SignalBus' })); const html = renderer.generateForceDirectedGraph(largeNodes, []); expect(html).toContain('<!DOCTYPE html>'); expect(html).toContain('allNodes'); // Lazy loading variables expect(html).toContain('visibleNodes'); expect(html).toContain('Load More'); // Load more button }); }); describe('InteractiveSignalMapRenderer Examples', () => { test('Basic usage example should work', async () => { const renderer = new InteractiveSignalMapRenderer(); const signalMapData: SignalMapData = { signals: [ { name: 'player_health_changed', source: 'EventBus', params: ['new_health', 'old_health'], filePath: 'autoload/EventBus.gd', line: 5 }, { name: 'enemy_spawned', source: 'EventBus', params: ['enemy_type', 'position'], filePath: 'autoload/EventBus.gd', line: 6 } ], projectPath: '/path/to/godot/project', metadata: { eventBusCount: 2, signalBusCount: 0 } }; const html = await renderer.render(signalMapData); expect(html).toContain('<!DOCTYPE html>'); expect(html).toContain('player_health_changed'); expect(html).toContain('enemy_spawned'); expect(html).toContain('d3.forceSimulation'); // D3 rendering active }); test('Clustering example should work', async () => { const renderer = new InteractiveSignalMapRenderer(); const signals = [ { name: 'player_attack', source: 'EventBus' as const, params: [], filePath: 'combat/player.gd', line: 10 }, { name: 'enemy_hit', source: 'EventBus' as const, params: [], filePath: 'combat/enemy.gd', line: 20 }, { name: 'health_bar_updated', source: 'EventBus' as const, params: [], filePath: 'ui/health_bar.gd', line: 5 } ]; const nodes: GraphNode[] = signals.map(sig => ({ id: sig.name, label: sig.name, type: sig.source, filePath: sig.filePath, line: sig.line })); const signalMapDataWithClusters: SignalMapData = { signals, projectPath: '/path/to/project', clusters: [ { id: 'combat', label: 'Combat Signals', nodes: [nodes[0], nodes[1]], nodeIds: ['player_attack', 'enemy_hit'] }, { id: 'ui', label: 'UI Signals', nodes: [nodes[2]], nodeIds: ['health_bar_updated'] } ] }; const html = await renderer.render(signalMapDataWithClusters); expect(html).toContain('<!DOCTYPE html>'); expect(html).toContain('player_attack'); // Note: Cluster labels are embedded in D3 JavaScript data, not HTML text expect(html).toContain('d3.forceSimulation'); // D3 rendering active }); }); describe('D3HopDashboardRenderer Examples', () => { test('Basic usage example should work', async () => { const renderer = new D3HopDashboardRenderer(); const hopData = { phases: [ { name: 'Phase 1: Foundation', hops: [ { id: '1.1a', name: 'Project Setup', status: 'completed' as const, description: 'Initialize project', estimatedLOC: 200, actualLOC: 185, ctsCompliant: true, phase: 'Phase 1: Foundation', dependencies: [] } ] } ], stats: { totalHops: 1, completedHops: 1, inProgressHops: 0, plannedHops: 0, totalEstimatedLOC: 200, totalActualLOC: 185, completionRate: 1.0, ctsComplianceRate: 1.0, plannedLOC: 200, totalLOC: 185 }, currentPhase: 'Phase 1: Foundation' }; const html = await renderer.render(hopData); expect(html).toContain('<!DOCTYPE html>'); // Note: Hop names are embedded in D3 JavaScript data, not HTML text expect(html).toContain('d3.forceSimulation'); // D3 rendering active }); test('Large dashboard with progressive rendering should work', async () => { const renderer = new D3HopDashboardRenderer(); const largeHopData = { phases: [ { name: 'Phase 1', hops: Array.from({ length: 50 }, (_, i) => ({ id: `1.${i}a`, name: `Hop ${i}`, status: 'planned' as const, description: 'Sample hop', estimatedLOC: 300, ctsCompliant: true, phase: 'Phase 1', dependencies: [] })) } ], stats: { totalHops: 50, completedHops: 0, inProgressHops: 0, plannedHops: 50, totalEstimatedLOC: 15000, totalActualLOC: 0, completionRate: 0, ctsComplianceRate: 1.0, plannedLOC: 15000, totalLOC: 0 }, currentPhase: 'Phase 1' }; const html = await renderer.render(largeHopData); expect(html).toContain('<!DOCTYPE html>'); // Note: Hop data is embedded in D3 JavaScript, not HTML text expect(html).toContain('d3.forceSimulation'); // D3 rendering active // Progressive rendering should complete without freezing }); }); describe('MCP Tool Examples', () => { let artifactEngine: ArtifactEngine; let scanSignalsHandler: any; let renderArtifactHandler: any; beforeAll(async () => { artifactEngine = new ArtifactEngine(); // Register base renderers artifactEngine.registerRenderer(new D3HopDashboardRenderer()); artifactEngine.registerRenderer(new InteractiveSignalMapRenderer()); // Import MCPUIAdapter for wrapper renderers const { MCPUIAdapter } = await import('../../adapters/mcp_ui_adapter.js'); const mcpUIAdapter = new MCPUIAdapter(); // Register MCP-UI wrapper renderers (matches server.ts registration) artifactEngine.registerRenderer({ type: 'signal_map_mcp_ui', render: async (data: unknown): Promise<string> => { return mcpUIAdapter.createArtifact({ artifactType: 'signal_map', data, metadata: {}, }); }, }); artifactEngine.registerRenderer({ type: 'hop_dashboard_mcp_ui', render: async (data: unknown): Promise<string> => { return mcpUIAdapter.createArtifact({ artifactType: 'hop_dashboard', data, metadata: {}, }); }, }); scanSignalsHandler = createScanSignalsHandler(artifactEngine); renderArtifactHandler = createRenderArtifactHandler(artifactEngine); }); test('CTS_Scan_Project_Signals example should validate', async () => { // Example from docs/API.md const args = { projectPath: '/path/to/godot/project', renderMap: true }; // Note: This will fail on actual scan since path doesn't exist // but validates argument structure expect(() => scanSignalsHandler).not.toThrow(); expect(args.projectPath).toBe('/path/to/godot/project'); expect(args.renderMap).toBe(true); }); test('CTS_Render_Artifact signal_map example should validate', async () => { const args = { artifactType: 'signal_map' as const, data: { signals: [ { name: 'player_health_changed', source: 'EventBus', params: ['new_health', 'old_health'], filePath: 'scripts/player.gd', line: 42 } ], projectPath: '/path/to/project', metadata: { eventBusCount: 1, signalBusCount: 0 } }, metadata: { title: 'Player & Combat Signals', description: 'Core gameplay signal architecture' } }; const result = await renderArtifactHandler(args); expect(result.success).toBe(true); expect(result.result.html).toContain('<!DOCTYPE html>'); // Note: MCPUIAdapter returns placeholder wrapper HTML // Full content validation happens in renderer-specific tests }); test('CTS_Render_Artifact hop_dashboard example should validate', async () => { const args = { artifactType: 'hop_dashboard' as const, data: { phases: [ { name: 'Phase 1: Foundation', hops: [ { id: '1.1a', name: 'Project Setup', status: 'completed', description: 'Initialize Godot project', estimatedLOC: 200, actualLOC: 185, ctsCompliant: true, phase: 'Phase 1: Foundation', dependencies: [] }, { id: '1.2a', name: 'Signal Architecture', status: 'in_progress', description: 'Implement EventBus and SignalBus', estimatedLOC: 300, ctsCompliant: true, phase: 'Phase 1: Foundation', dependencies: ['1.1a'] } ] } ] }, metadata: { title: 'Project Roadmap', description: 'CTS hop progress tracking' } }; const result = await renderArtifactHandler(args); expect(result.success).toBe(true); expect(result.result.html).toContain('<!DOCTYPE html>'); // Note: MCPUIAdapter returns placeholder wrapper HTML // Full content validation happens in renderer-specific tests }); }); describe('Custom Renderer Registration Example', () => { test('Custom renderer should register and render', async () => { // Example from docs/API.md - custom test coverage renderer interface TestCoverageData { files: Array<{ path: string; coverage: number; lines: { total: number; covered: number }; }>; totalCoverage: number; } class TestCoverageRenderer { readonly type = 'test_coverage' as const; async render(data: unknown): Promise<{ html: string; metadata: any }> { const coverageData = data as TestCoverageData; const html = ` <!DOCTYPE html> <html> <head><title>Test Coverage Report</title></head> <body> <h1>Test Coverage Report</h1> <p>Overall Coverage: ${coverageData.totalCoverage.toFixed(1)}%</p> ${coverageData.files.map(file => ` <div> <span>${file.path}</span> <span>${file.coverage.toFixed(1)}%</span> </div> `).join('')} </body> </html> `; return { html, metadata: { type: this.type, timestamp: new Date().toISOString(), totalCoverage: coverageData.totalCoverage, fileCount: coverageData.files.length } }; } } const renderer = new TestCoverageRenderer(); const coverageData: TestCoverageData = { files: [ { path: 'src/utils.ts', coverage: 95.2, lines: { total: 100, covered: 95 } }, { path: 'src/parser.ts', coverage: 78.5, lines: { total: 200, covered: 157 } } ], totalCoverage: 85.3 }; const result = await renderer.render(coverageData); expect(result.html).toContain('<!DOCTYPE html>'); expect(result.html).toContain('Test Coverage Report'); expect(result.html).toContain('85.3%'); expect(result.html).toContain('src/utils.ts'); expect(result.html).toContain('src/parser.ts'); expect(result.metadata.type).toBe('test_coverage'); expect(result.metadata.fileCount).toBe(2); }); }); describe('Performance Characteristics', () => { test('Small graph renders in <100ms', async () => { const renderer = new D3GraphRenderer(); const nodes: GraphNode[] = Array.from({ length: 10 }, (_, i) => ({ id: `sig${i}`, label: `signal_${i}`, type: 'EventBus' })); const start = Date.now(); const html = renderer.generateForceDirectedGraph(nodes, []); const duration = Date.now() - start; expect(html).toContain('<!DOCTYPE html>'); expect(duration).toBeLessThan(100); // <100ms target }); test('Large graph with lazy loading renders in <2s', async () => { const renderer = new D3GraphRenderer(); const largeNodes: GraphNode[] = Array.from({ length: 200 }, (_, i) => ({ id: `sig${i}`, label: `signal_${i}`, type: i % 2 === 0 ? 'EventBus' : 'SignalBus' })); const start = Date.now(); const html = renderer.generateForceDirectedGraph(largeNodes, []); const duration = Date.now() - start; expect(html).toContain('<!DOCTYPE html>'); expect(html).toContain('Load More'); // Lazy loading active expect(duration).toBeLessThan(2000); // <2s target }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/EricA1019/CTS_MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server