diff --git a/gitnexus-web/src/components/FileTreePanel.tsx b/gitnexus-web/src/components/FileTreePanel.tsx index 2007e54d4c..4f55f43be4 100644 --- a/gitnexus-web/src/components/FileTreePanel.tsx +++ b/gitnexus-web/src/components/FileTreePanel.tsx @@ -14,6 +14,9 @@ import { Variable, Hash, Target, + List, + AtSign, + Type, } from '@/lib/lucide-icons'; import { useAppState } from '../hooks/useAppState'; import { FILTERABLE_LABELS, NODE_COLORS, ALL_EDGE_TYPES, EDGE_INFO, type EdgeType } from '../lib/constants'; @@ -187,7 +190,11 @@ const getNodeTypeIcon = (label: NodeLabel) => { case 'Function': return Braces; case 'Method': return Braces; case 'Interface': return Hash; + case 'Enum': return List; + case 'Type': return Type; + case 'Decorator': return AtSign; case 'Import': return FileCode; + case 'Variable': return Variable; default: return Variable; } }; @@ -501,7 +508,7 @@ export const FileTreePanel = ({ onFocusNode }: FileTreePanelProps) => { Color Legend
- {(['Folder', 'File', 'Class', 'Function', 'Interface', 'Method'] as NodeLabel[]).map(label => ( + {(['Folder', 'File', 'Class', 'Interface', 'Enum', 'Type', 'Function', 'Method', 'Variable', 'Decorator'] as NodeLabel[]).map(label => (
{ }); }); +describe('FILTERABLE_LABELS', () => { + it('includes all newly added node types', () => { + expect(FILTERABLE_LABELS).toContain('Enum'); + expect(FILTERABLE_LABELS).toContain('Type'); + expect(FILTERABLE_LABELS).toContain('Decorator'); + expect(FILTERABLE_LABELS).toContain('Variable'); + }); + + it('every filterable label has a defined color in NODE_COLORS', () => { + for (const label of FILTERABLE_LABELS) { + expect(NODE_COLORS).toHaveProperty(label); + expect(NODE_COLORS[label as keyof typeof NODE_COLORS]).toMatch(/^#[0-9a-f]{6}$/i); + } + }); + + it('every filterable label has a defined size in NODE_SIZES', () => { + for (const label of FILTERABLE_LABELS) { + expect(NODE_SIZES).toHaveProperty(label); + expect(NODE_SIZES[label as keyof typeof NODE_SIZES]).toBeGreaterThan(0); + } + }); + + it('has no duplicate entries', () => { + const unique = new Set(FILTERABLE_LABELS); + expect(unique.size).toBe(FILTERABLE_LABELS.length); + }); + + it('is a subset of DEFAULT_VISIBLE_LABELS plus togglable labels', () => { + const allKnown = new Set(Object.keys(NODE_COLORS)); + for (const label of FILTERABLE_LABELS) { + expect(allKnown.has(label)).toBe(true); + } + }); +}); + describe('edge types', () => { it('ALL_EDGE_TYPES contains all EDGE_INFO keys', () => { const edgeInfoKeys = Object.keys(EDGE_INFO).sort(); diff --git a/gitnexus-web/test/unit/filter-panel.test.ts b/gitnexus-web/test/unit/filter-panel.test.ts new file mode 100644 index 0000000000..19d20fb020 --- /dev/null +++ b/gitnexus-web/test/unit/filter-panel.test.ts @@ -0,0 +1,81 @@ +import { describe, expect, it } from 'vitest'; +import { FILTERABLE_LABELS, NODE_COLORS } from '../../src/lib/constants'; +import type { NodeLabel } from '../../src/core/graph/types'; +import * as lucideIcons from '../../src/lib/lucide-icons'; + +const LEGEND_LABELS: NodeLabel[] = [ + 'Folder', 'File', 'Class', 'Interface', 'Enum', 'Type', + 'Function', 'Method', 'Variable', 'Decorator', +]; + +const ICON_MAP: Record = { + Folder: 'Folder', + File: 'FileCode', + Class: 'Box', + Function: 'Braces', + Method: 'Braces', + Interface: 'Hash', + Enum: 'List', + Type: 'Type', + Decorator: 'AtSign', + Import: 'FileCode', + Variable: 'Variable', +}; + +describe('filter panel icon mappings', () => { + it('every filterable label has a mapped icon', () => { + for (const label of FILTERABLE_LABELS) { + expect(ICON_MAP).toHaveProperty(label); + } + }); + + it('every mapped icon is exported from lucide-icons', () => { + const exportedNames = new Set(Object.keys(lucideIcons)); + const requiredIcons = new Set(Object.values(ICON_MAP)); + for (const iconName of requiredIcons) { + expect(exportedNames.has(iconName), `${iconName} should be exported from lucide-icons`).toBe(true); + } + }); + + it('newly added node types have distinct icons', () => { + expect(ICON_MAP.Enum).toBe('List'); + expect(ICON_MAP.Type).toBe('Type'); + expect(ICON_MAP.Decorator).toBe('AtSign'); + }); +}); + +describe('color legend', () => { + it('includes all newly added node types', () => { + expect(LEGEND_LABELS).toContain('Enum'); + expect(LEGEND_LABELS).toContain('Type'); + expect(LEGEND_LABELS).toContain('Decorator'); + expect(LEGEND_LABELS).toContain('Variable'); + }); + + it('every legend label has a color defined', () => { + for (const label of LEGEND_LABELS) { + expect(NODE_COLORS).toHaveProperty(label); + expect(NODE_COLORS[label]).toMatch(/^#[0-9a-f]{6}$/i); + } + }); + + it('legend labels match the order used in FileTreePanel', () => { + const expected: NodeLabel[] = [ + 'Folder', 'File', 'Class', 'Interface', 'Enum', 'Type', + 'Function', 'Method', 'Variable', 'Decorator', + ]; + expect(LEGEND_LABELS).toEqual(expected); + }); + + it('legend labels are a subset of filterable labels plus Import', () => { + const filterableSet = new Set(FILTERABLE_LABELS); + for (const label of LEGEND_LABELS) { + expect(filterableSet.has(label), `${label} should be in FILTERABLE_LABELS`).toBe(true); + } + }); + + it('has no duplicate entries', () => { + const unique = new Set(LEGEND_LABELS); + expect(unique.size).toBe(LEGEND_LABELS.length); + }); +});