/** * Unit tests for TensorShapeInspector * Validates: Requirements 21.1, 21.2, 21.3, 21.4, 21.5 */ import { describe, it, expect, beforeEach } from 'vitest'; /** * Extract the pure tensor lookup logic for testing (mirrors TensorShapeInspector._buildTensorInfoMap + lookupTensor). * @param {Object} parsedModel * @returns {{ lookup: (name: string) => Object|null, size: number }} */ function buildTensorLookup(parsedModel) { const map = new Map(); // 1. Inputs if (Array.isArray(parsedModel.inputs)) { for (const inp of parsedModel.inputs) { if (inp.name) { map.set(inp.name, { name: inp.name, shape: inp.shape || [], dataType: inp.dataType || 'UNKNOWN' }); } } } // 2. Outputs if (Array.isArray(parsedModel.outputs)) { for (const out of parsedModel.outputs) { if (out.name) { map.set(out.name, { name: out.name, shape: out.shape || [], dataType: out.dataType || 'UNKNOWN' }); } } } // 3. value_info (intermediate tensors) const valueInfo = parsedModel.graph && parsedModel.graph.valueInfo; if (valueInfo && typeof valueInfo === 'object') { const entries = Object.entries(valueInfo); for (const [key, vi] of entries) { if (key && !map.has(key)) { map.set(key, { name: vi.name || key, shape: vi.shape || [], dataType: vi.dataType || 'UNKNOWN' }); } } } return { lookup: (name) => map.get(name) || null, size: map.size }; } /** * Build tooltip text (mirrors TensorShapeInspector._buildTooltipHTML logic, text-only). */ function buildTooltipText(tensorName, tensorInfo) { const displayName = tensorName || 'Unnamed tensor'; if (tensorInfo) { const shapeStr = tensorInfo.shape && tensorInfo.shape.length > 0 ? '[' + tensorInfo.shape.join(', ') + ']' : 'unknown'; return { name: displayName, shape: shapeStr, dataType: tensorInfo.dataType }; } return { name: displayName, shape: 'unknown', dataType: null }; } describe('TensorShapeInspector - Tensor Lookup', () => { let model; beforeEach(() => { model = { inputs: [ { name: 'input_0', shape: [1, 3, 224, 224], dataType: 'FLOAT' } ], outputs: [ { name: 'output_0', shape: [1, 1000], dataType: 'FLOAT' } ], graph: { nodes: [], edges: [], valueInfo: { 'conv1_out': { name: 'conv1_out', shape: [1, 64, 112, 112], dataType: 'FLOAT' }, 'relu1_out': { name: 'relu1_out', shape: [1, 64, 112, 112], dataType: 'FLOAT' } } }, initializers: [] }; }); it('should find input tensors by name', () => { const { lookup } = buildTensorLookup(model); const info = lookup('input_0'); expect(info).not.toBeNull(); expect(info.name).toBe('input_0'); expect(info.shape).toEqual([1, 3, 224, 224]); expect(info.dataType).toBe('FLOAT'); }); it('should find output tensors by name', () => { const { lookup } = buildTensorLookup(model); const info = lookup('output_0'); expect(info).not.toBeNull(); expect(info.shape).toEqual([1, 1000]); expect(info.dataType).toBe('FLOAT'); }); it('should find intermediate tensors from valueInfo', () => { const { lookup } = buildTensorLookup(model); const info = lookup('conv1_out'); expect(info).not.toBeNull(); expect(info.shape).toEqual([1, 64, 112, 112]); expect(info.dataType).toBe('FLOAT'); }); it('should return null for unknown tensor names', () => { const { lookup } = buildTensorLookup(model); expect(lookup('nonexistent_tensor')).toBeNull(); }); it('should return null for empty/null name', () => { const { lookup } = buildTensorLookup(model); expect(lookup('')).toBeNull(); expect(lookup(null)).toBeNull(); expect(lookup(undefined)).toBeNull(); }); it('should count all tensors from all sources', () => { const { size } = buildTensorLookup(model); // 1 input + 1 output + 2 valueInfo = 4 expect(size).toBe(4); }); it('should handle model with no valueInfo', () => { model.graph.valueInfo = {}; const { lookup, size } = buildTensorLookup(model); expect(size).toBe(2); // only input + output expect(lookup('conv1_out')).toBeNull(); }); it('should handle model with no inputs or outputs', () => { const emptyModel = { inputs: [], outputs: [], graph: { valueInfo: {} }, initializers: [] }; const { size } = buildTensorLookup(emptyModel); expect(size).toBe(0); }); it('should prioritize inputs/outputs over valueInfo for same name', () => { model.graph.valueInfo['input_0'] = { name: 'input_0', shape: [999], dataType: 'INT32' }; const { lookup } = buildTensorLookup(model); const info = lookup('input_0'); // Input should take priority expect(info.shape).toEqual([1, 3, 224, 224]); expect(info.dataType).toBe('FLOAT'); }); it('should handle tensors with missing shape', () => { model.graph.valueInfo['no_shape'] = { name: 'no_shape', dataType: 'FLOAT' }; const { lookup } = buildTensorLookup(model); const info = lookup('no_shape'); expect(info).not.toBeNull(); expect(info.shape).toEqual([]); }); it('should handle tensors with missing dataType', () => { model.graph.valueInfo['no_dtype'] = { name: 'no_dtype', shape: [1, 10] }; const { lookup } = buildTensorLookup(model); const info = lookup('no_dtype'); expect(info.dataType).toBe('UNKNOWN'); }); }); describe('TensorShapeInspector - Tooltip Content', () => { it('should show shape and type for known tensor', () => { const tensorInfo = { name: 'conv1_out', shape: [1, 64, 112, 112], dataType: 'FLOAT' }; const result = buildTooltipText('conv1_out', tensorInfo); expect(result.name).toBe('conv1_out'); expect(result.shape).toBe('[1, 64, 112, 112]'); expect(result.dataType).toBe('FLOAT'); }); it('should show "Shape: unknown" when tensor info is null', () => { const result = buildTooltipText('mystery_tensor', null); expect(result.name).toBe('mystery_tensor'); expect(result.shape).toBe('unknown'); expect(result.dataType).toBeNull(); }); it('should show "Shape: unknown" when tensor has empty shape', () => { const tensorInfo = { name: 'empty', shape: [], dataType: 'FLOAT' }; const result = buildTooltipText('empty', tensorInfo); expect(result.shape).toBe('unknown'); }); it('should handle dynamic dimensions in shape', () => { const tensorInfo = { name: 'dynamic', shape: ['batch', 3, 224, 224], dataType: 'FLOAT' }; const result = buildTooltipText('dynamic', tensorInfo); expect(result.shape).toBe('[batch, 3, 224, 224]'); }); it('should show "Unnamed tensor" when name is empty', () => { const result = buildTooltipText('', null); expect(result.name).toBe('Unnamed tensor'); }); });