/** * Unit tests for LayerStats.compute() * Validates: Requirements 19.1, 19.2, 19.4, 19.5 */ import { describe, it, expect } from 'vitest'; // Extract the pure compute logic for testing (mirrors LayerStats.compute) function computeLayerStats(parsedModel) { const nodes = (parsedModel && parsedModel.graph && parsedModel.graph.nodes) ? parsedModel.graph.nodes : []; const edges = (parsedModel && parsedModel.graph && parsedModel.graph.edges) ? parsedModel.graph.edges : []; const initializers = (parsedModel && parsedModel.initializers) ? parsedModel.initializers : []; const totalNodes = nodes.length; const totalEdges = edges.length; const totalInitializers = initializers.length; const countMap = {}; nodes.forEach((node) => { const op = node.opType || 'Unknown'; countMap[op] = (countMap[op] || 0) + 1; }); const opTypeCounts = Object.entries(countMap) .map(([opType, count]) => ({ opType, count, percentage: totalNodes > 0 ? parseFloat(((count / totalNodes) * 100).toFixed(1)) : 0, })) .sort((a, b) => b.count - a.count); return { opTypeCounts, totalNodes, totalEdges, totalInitializers }; } describe('LayerStats.compute', () => { it('should count operator types correctly', () => { const model = { graph: { nodes: [ { id: '1', opType: 'Conv' }, { id: '2', opType: 'ReLU' }, { id: '3', opType: 'Conv' }, { id: '4', opType: 'Conv' }, { id: '5', opType: 'ReLU' }, ], edges: [{ source: '1', target: '2' }], }, initializers: [{ name: 'w1' }, { name: 'w2' }], }; const stats = computeLayerStats(model); expect(stats.totalNodes).toBe(5); expect(stats.totalEdges).toBe(1); expect(stats.totalInitializers).toBe(2); expect(stats.opTypeCounts).toHaveLength(2); // Sorted descending by count expect(stats.opTypeCounts[0]).toEqual({ opType: 'Conv', count: 3, percentage: 60 }); expect(stats.opTypeCounts[1]).toEqual({ opType: 'ReLU', count: 2, percentage: 40 }); }); it('should return sum of counts equal to totalNodes', () => { const model = { graph: { nodes: [ { id: '1', opType: 'Conv' }, { id: '2', opType: 'BatchNorm' }, { id: '3', opType: 'Conv' }, { id: '4', opType: 'Add' }, ], edges: [], }, initializers: [], }; const stats = computeLayerStats(model); const sumCounts = stats.opTypeCounts.reduce((s, e) => s + e.count, 0); expect(sumCounts).toBe(stats.totalNodes); }); it('should handle empty model gracefully', () => { const stats = computeLayerStats({ graph: { nodes: [], edges: [] }, initializers: [] }); expect(stats.totalNodes).toBe(0); expect(stats.totalEdges).toBe(0); expect(stats.totalInitializers).toBe(0); expect(stats.opTypeCounts).toHaveLength(0); }); it('should handle null/undefined model', () => { const stats = computeLayerStats(null); expect(stats.totalNodes).toBe(0); expect(stats.opTypeCounts).toHaveLength(0); }); it('should label nodes without opType as Unknown', () => { const model = { graph: { nodes: [{ id: '1' }, { id: '2', opType: '' }], edges: [] }, initializers: [], }; const stats = computeLayerStats(model); expect(stats.opTypeCounts).toHaveLength(1); expect(stats.opTypeCounts[0].opType).toBe('Unknown'); expect(stats.opTypeCounts[0].count).toBe(2); }); it('should sort by count descending', () => { const model = { graph: { nodes: [ { id: '1', opType: 'A' }, { id: '2', opType: 'B' }, { id: '3', opType: 'B' }, { id: '4', opType: 'C' }, { id: '5', opType: 'C' }, { id: '6', opType: 'C' }, ], edges: [], }, initializers: [], }; const stats = computeLayerStats(model); expect(stats.opTypeCounts[0].opType).toBe('C'); expect(stats.opTypeCounts[1].opType).toBe('B'); expect(stats.opTypeCounts[2].opType).toBe('A'); }); it('should compute percentages that sum close to 100', () => { const model = { graph: { nodes: [ { id: '1', opType: 'Conv' }, { id: '2', opType: 'Conv' }, { id: '3', opType: 'ReLU' }, ], edges: [], }, initializers: [], }; const stats = computeLayerStats(model); const sumPct = stats.opTypeCounts.reduce((s, e) => s + e.percentage, 0); // Allow small rounding tolerance expect(Math.abs(sumPct - 100)).toBeLessThan(1); }); });