Skip to content

Commit 589ae17

Browse files
frankbriaTest User
andauthored
refactor(ui): migrate from emojis to Hugeicons for consistent icon system (#286)
* refactor(ui): migrate from emojis to Hugeicons for consistent icon system Replace all emoji characters with Hugeicons React components across the web-ui codebase. This ensures consistency with the Nova design system and provides proper accessibility attributes (aria-hidden) for icons. Key changes: - Replace emoji literals (🔒, ⚠️, ✅, etc.) with Hugeicons components - Rename utility files to .tsx for JSX return types - Add getCategoryIcon() and getStatusIcon() functions for type-safe icons - Update all test files with comprehensive Hugeicons mocks - Update jest.setup.js with global icon mocks Components updated: AgentCard, BlockerBadge, ErrorBoundary, TaskTreeView, QualityGateStatus, ReviewFindings, ReviewSummary, DiscoveryProgress, and more Test results: 1805 tests passing, build successful * fix(ui): address CodeRabbit review feedback for icon migration - Remove duplicate warning icon in CheckpointRestore (inline SVG + emoji) - Update stale .bg-blue-50 test selectors to .bg-muted (Nova palette) - Correct misleading comment about Hugeicons data-testid in AgentList test - Update CheckpointRestore test to match separated emoji/text structure * fix(ui): complete Hugeicons migration and fix redundant className - Replace success message inline SVG with CheckmarkCircle01Icon - Remove redundant text-2xl wrapper (icons already sized via h-5 w-5) - Fix redundant iconProps className in getStatusIcon utility - Update test mock to include CheckmarkCircle01Icon --------- Co-authored-by: Test User <test@example.com>
1 parent 81fdeee commit 589ae17

43 files changed

Lines changed: 782 additions & 309 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.beads/issues.jsonl

Lines changed: 56 additions & 36 deletions
Large diffs are not rendered by default.

web-ui/__tests__/components/AgentCard.test.tsx

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,31 @@
88
import React from 'react';
99
import { render, screen, fireEvent } from '@testing-library/react';
1010
import '@testing-library/jest-dom';
11+
12+
// Mock Hugeicons - must be before component import
13+
jest.mock('@hugeicons/react', () => {
14+
const createMockIcon = (name: string, testId: string) => {
15+
const Icon = ({ className }: { className?: string }) => (
16+
<svg className={className} data-testid={testId} aria-hidden="true" />
17+
);
18+
Icon.displayName = name;
19+
return Icon;
20+
};
21+
22+
return {
23+
// Agent type icons
24+
Settings01Icon: createMockIcon('Settings01Icon', 'settings-icon'),
25+
PaintBrush01Icon: createMockIcon('PaintBrush01Icon', 'paint-brush-icon'),
26+
TestTube01Icon: createMockIcon('TestTube01Icon', 'test-tube-icon'),
27+
BotIcon: createMockIcon('BotIcon', 'bot-icon'),
28+
// Maturity level icons
29+
SunriseIcon: createMockIcon('SunriseIcon', 'sunrise-icon'),
30+
BookOpen01Icon: createMockIcon('BookOpen01Icon', 'book-icon'),
31+
FlashIcon: createMockIcon('FlashIcon', 'flash-icon'),
32+
Award01Icon: createMockIcon('Award01Icon', 'award-icon'),
33+
};
34+
});
35+
1136
import AgentCard, { Agent } from '@/components/AgentCard';
1237

1338
describe('AgentCard Component', () => {
@@ -212,7 +237,7 @@ describe('AgentCard Component', () => {
212237

213238
const badge = screen.getByText('Backend Worker').parentElement;
214239
expect(badge).toHaveClass('bg-primary/10', 'text-primary-foreground');
215-
expect(screen.getByText('⚙️')).toBeInTheDocument();
240+
expect(screen.getByTestId('settings-icon')).toBeInTheDocument();
216241
});
217242

218243
it('should show frontend badge with correct icon', () => {
@@ -227,7 +252,7 @@ describe('AgentCard Component', () => {
227252

228253
const badge = screen.getByText('Frontend Specialist').parentElement;
229254
expect(badge).toHaveClass('bg-secondary', 'text-secondary-foreground');
230-
expect(screen.getByText('🎨')).toBeInTheDocument();
255+
expect(screen.getByTestId('paint-brush-icon')).toBeInTheDocument();
231256
});
232257

233258
it('should show test badge with correct icon', () => {
@@ -242,7 +267,7 @@ describe('AgentCard Component', () => {
242267

243268
const badge = screen.getByText('Test Engineer').parentElement;
244269
expect(badge).toHaveClass('bg-secondary', 'text-secondary-foreground');
245-
expect(screen.getByText('🧪')).toBeInTheDocument();
270+
expect(screen.getByTestId('test-tube-icon')).toBeInTheDocument();
246271
});
247272

248273
it('should show default badge for unknown agent type', () => {
@@ -257,7 +282,7 @@ describe('AgentCard Component', () => {
257282

258283
const badge = screen.getByText('Custom Agent').parentElement;
259284
expect(badge).toHaveClass('bg-muted', 'text-foreground');
260-
expect(screen.getByText('🤖')).toBeInTheDocument();
285+
expect(screen.getByTestId('bot-icon')).toBeInTheDocument();
261286
});
262287
});
263288

web-ui/__tests__/components/AgentList.test.tsx

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,34 @@
1111
import React from 'react';
1212
import { render, screen, waitFor } from '@testing-library/react';
1313
import { SWRConfig } from 'swr';
14+
15+
// Mock Hugeicons - must be before component import
16+
// Include all icons used by AgentList and its child components (AgentAssignmentCard)
17+
jest.mock('@hugeicons/react', () => {
18+
const createMockIcon = (name: string, testId: string) => {
19+
const Icon = ({ className }: { className?: string }) => (
20+
<svg className={className} data-testid={testId} aria-hidden="true" />
21+
);
22+
Icon.displayName = name;
23+
return Icon;
24+
};
25+
26+
return {
27+
// AgentList icons
28+
BotIcon: createMockIcon('BotIcon', 'bot-icon'),
29+
CheckListIcon: createMockIcon('CheckListIcon', 'checklist-icon'),
30+
Alert02Icon: createMockIcon('Alert02Icon', 'alert-icon'),
31+
RefreshIcon: createMockIcon('RefreshIcon', 'refresh-icon'),
32+
// AgentAssignmentCard icons
33+
CrownIcon: createMockIcon('CrownIcon', 'crown-icon'),
34+
Settings01Icon: createMockIcon('Settings01Icon', 'settings-icon'),
35+
PaintBrush01Icon: createMockIcon('PaintBrush01Icon', 'paint-brush-icon'),
36+
TestTube01Icon: createMockIcon('TestTube01Icon', 'test-tube-icon'),
37+
Search01Icon: createMockIcon('Search01Icon', 'search-icon'),
38+
CheckmarkCircle01Icon: createMockIcon('CheckmarkCircle01Icon', 'checkmark-icon'),
39+
};
40+
});
41+
1442
import { AgentList } from '../../src/components/AgentList';
1543
import * as agentAssignmentApi from '../../src/api/agentAssignment';
1644
import type { IssuesResponse } from '../../src/types/api';
@@ -227,7 +255,7 @@ describe('AgentList', () => {
227255
);
228256

229257
await waitFor(() => {
230-
// Check for BotIcon (Hugeicons adds data-testid automatically)
258+
// Check for BotIcon (data-testid added explicitly in component source)
231259
expect(screen.getByTestId('bot-icon')).toBeInTheDocument();
232260
});
233261
});

web-ui/__tests__/components/BlockerBadge.test.tsx

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,35 @@
11
/**
22
* BlockerBadge Component Tests
33
* Tests for blocker type badge display (049-human-in-loop, T016)
4+
* Updated for emoji-to-Hugeicons migration: icons now use Hugeicons components
45
*/
56

67
import { render, screen } from '@testing-library/react';
78
import { BlockerBadge } from '@/components/BlockerBadge';
89

10+
// Mock Hugeicons
11+
jest.mock('@hugeicons/react', () => {
12+
const React = require('react');
13+
return {
14+
Alert02Icon: ({ className }: { className?: string }) => (
15+
<svg data-testid="Alert02Icon" className={className} aria-hidden="true" />
16+
),
17+
Idea01Icon: ({ className }: { className?: string }) => (
18+
<svg data-testid="Idea01Icon" className={className} aria-hidden="true" />
19+
),
20+
};
21+
});
22+
923
describe('BlockerBadge', () => {
1024
describe('SYNC blocker badge', () => {
1125
it('renders with correct label', () => {
1226
render(<BlockerBadge type="SYNC" />);
1327
expect(screen.getByText('CRITICAL')).toBeInTheDocument();
1428
});
1529

16-
it('displays alert icon', () => {
30+
it('displays Alert02Icon', () => {
1731
render(<BlockerBadge type="SYNC" />);
18-
expect(screen.getByText('🚨')).toBeInTheDocument();
32+
expect(screen.getByTestId('Alert02Icon')).toBeInTheDocument();
1933
});
2034

2135
it('has red background and text colors', () => {
@@ -51,9 +65,9 @@ describe('BlockerBadge', () => {
5165
expect(screen.getByText('INFO')).toBeInTheDocument();
5266
});
5367

54-
it('displays lightbulb icon', () => {
68+
it('displays Idea01Icon', () => {
5569
render(<BlockerBadge type="ASYNC" />);
56-
expect(screen.getByText('💡')).toBeInTheDocument();
70+
expect(screen.getByTestId('Idea01Icon')).toBeInTheDocument();
5771
});
5872

5973
it('has yellow background and text colors', () => {
@@ -102,16 +116,16 @@ describe('BlockerBadge', () => {
102116
});
103117

104118
describe('icon rendering', () => {
105-
it('renders icon with correct size class', () => {
106-
const { container } = render(<BlockerBadge type="SYNC" />);
107-
const icon = screen.getByText('🚨');
108-
expect(icon).toHaveClass('text-sm');
119+
it('renders SYNC icon with correct size class', () => {
120+
render(<BlockerBadge type="SYNC" />);
121+
const icon = screen.getByTestId('Alert02Icon');
122+
expect(icon).toHaveClass('h-3.5', 'w-3.5');
109123
});
110124

111125
it('renders ASYNC icon with correct size class', () => {
112-
const { container } = render(<BlockerBadge type="ASYNC" />);
113-
const icon = screen.getByText('💡');
114-
expect(icon).toHaveClass('text-sm');
126+
render(<BlockerBadge type="ASYNC" />);
127+
const icon = screen.getByTestId('Idea01Icon');
128+
expect(icon).toHaveClass('h-3.5', 'w-3.5');
115129
});
116130
});
117131
});

web-ui/__tests__/components/BlockerPanel.test.tsx

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/**
22
* BlockerPanel Component Tests
33
* Tests for blocker list display and sorting (049-human-in-loop, T017)
4+
* Updated for emoji-to-Hugeicons migration: icons now use Hugeicons components
45
*/
56

67
import { render, screen, fireEvent } from '@testing-library/react';
@@ -19,6 +20,22 @@ import {
1920
mockMultipleAsyncBlockers,
2021
} from '../fixtures/blockers';
2122

23+
// Mock Hugeicons
24+
jest.mock('@hugeicons/react', () => {
25+
const React = require('react');
26+
const createMockIcon = (name: string) => {
27+
const Icon = ({ className }: { className?: string }) => (
28+
<svg data-testid={name} className={className} aria-hidden="true" />
29+
);
30+
Icon.displayName = name;
31+
return Icon;
32+
};
33+
return {
34+
CheckmarkCircle01Icon: createMockIcon('CheckmarkCircle01Icon'),
35+
BotIcon: createMockIcon('BotIcon'),
36+
};
37+
});
38+
2239
// Mock BlockerBadge component
2340
jest.mock('@/components/BlockerBadge', () => ({
2441
BlockerBadge: ({ type }: { type: string }) => (
@@ -31,7 +48,7 @@ describe('BlockerPanel', () => {
3148
it('renders empty state when blockers array is empty', () => {
3249
render(<BlockerPanel blockers={mockEmptyBlockersList} />);
3350
expect(screen.getByText('No blockers - agents are running smoothly!')).toBeInTheDocument();
34-
expect(screen.getByText('✅')).toBeInTheDocument();
51+
expect(screen.getByTestId('CheckmarkCircle01Icon')).toBeInTheDocument();
3552
});
3653

3754
it('displays (0) count in empty state', () => {
@@ -246,12 +263,12 @@ describe('BlockerPanel', () => {
246263
// Skip first 3 filter buttons, get the first blocker button
247264
const blockerButton = buttons[3];
248265
// Should not have the '•' separator for task
249-
expect(blockerButton.textContent).not.toMatch(/🤖.*.*Implement/);
266+
expect(blockerButton.textContent).not.toMatch(/.*Implement/);
250267
});
251268

252-
it('displays robot emoji for agent', () => {
269+
it('displays BotIcon for agent', () => {
253270
render(<BlockerPanel blockers={[mockSyncBlocker]} />);
254-
expect(screen.getByText('🤖')).toBeInTheDocument();
271+
expect(screen.getByTestId('BotIcon')).toBeInTheDocument();
255272
});
256273
});
257274

web-ui/__tests__/components/Dashboard.test.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,19 @@ jest.mock('@hugeicons/react', () => {
6666
// Other icons
6767
Download01Icon: createMockIcon('download-icon'),
6868
AlertDiamondIcon: createMockIcon('alert-diamond-icon'),
69+
// Quality gate icons
70+
ChartBarLineIcon: createMockIcon('chart-bar-line-icon'),
71+
FileEditIcon: createMockIcon('file-edit-icon'),
72+
SparklesIcon: createMockIcon('sparkles-icon'),
73+
Settings01Icon: createMockIcon('settings-icon'),
74+
PauseIcon: createMockIcon('pause-icon'),
75+
RefreshIcon: createMockIcon('refresh-icon'),
76+
InformationCircleIcon: createMockIcon('info-circle-icon'),
77+
// AgentCard icons (agent type and maturity badges)
78+
PaintBrush01Icon: createMockIcon('paint-brush-icon'),
79+
SunriseIcon: createMockIcon('sunrise-icon'),
80+
BookOpen01Icon: createMockIcon('book-open-icon'),
81+
FlashIcon: createMockIcon('flash-icon'),
6982
};
7083
});
7184

web-ui/__tests__/components/DiscoveryProgress.test.tsx

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,35 @@
44
*/
55

66
import { render, screen, waitFor, fireEvent, act } from '@testing-library/react';
7-
import DiscoveryProgress from '@/components/DiscoveryProgress';
8-
import { projectsApi, tasksApi } from '@/lib/api';
97
import type { DiscoveryProgressResponse } from '@/types/api';
108

11-
// Mock Hugeicons
12-
jest.mock('@hugeicons/react', () => ({
13-
Cancel01Icon: ({ className }: { className?: string }) => <span className={className} data-testid="cancel-icon" />,
14-
CheckmarkCircle01Icon: ({ className }: { className?: string }) => <span className={className} data-testid="checkmark-icon" />,
15-
Alert02Icon: ({ className }: { className?: string }) => <span className={className} data-testid="alert-icon" />,
16-
}));
9+
// Mock Hugeicons - must be before component imports
10+
jest.mock('@hugeicons/react', () => {
11+
const createMockIcon = (name: string, testId: string) => {
12+
const Icon = ({ className }: { className?: string }) => (
13+
<svg className={className} data-testid={testId} aria-hidden="true" />
14+
);
15+
Icon.displayName = name;
16+
return Icon;
17+
};
18+
19+
return {
20+
Cancel01Icon: createMockIcon('Cancel01Icon', 'cancel-icon'),
21+
CheckmarkCircle01Icon: createMockIcon('CheckmarkCircle01Icon', 'checkmark-icon'),
22+
Alert02Icon: createMockIcon('Alert02Icon', 'alert-icon'),
23+
AlertDiamondIcon: createMockIcon('AlertDiamondIcon', 'alert-diamond-icon'),
24+
Idea01Icon: createMockIcon('Idea01Icon', 'idea-icon'),
25+
// Dialog component icons
26+
Tick01Icon: createMockIcon('Tick01Icon', 'tick-icon'),
27+
ArrowDown01Icon: createMockIcon('ArrowDown01Icon', 'arrow-down-icon'),
28+
ArrowUp01Icon: createMockIcon('ArrowUp01Icon', 'arrow-up-icon'),
29+
CheckmarkSquare01Icon: createMockIcon('CheckmarkSquare01Icon', 'checkmark-square-icon'),
30+
CircleIcon: createMockIcon('CircleIcon', 'circle-icon'),
31+
};
32+
});
33+
34+
import DiscoveryProgress from '@/components/DiscoveryProgress';
35+
import { projectsApi, tasksApi } from '@/lib/api';
1736

1837
// Mock the API
1938
const mockStartProject = jest.fn();

web-ui/__tests__/components/DiscoveryProgress.testutils.tsx

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,31 @@ export const simulateWsMessage = (message: Record<string, unknown>) => {
5454
// Mock Modules Setup
5555
// =============================================================================
5656

57-
// Mock Hugeicons
58-
jest.mock('@hugeicons/react', () => ({
59-
Cancel01Icon: ({ className }: { className?: string }) => <span className={className} data-testid="cancel-icon" />,
60-
CheckmarkCircle01Icon: ({ className }: { className?: string }) => <span className={className} data-testid="checkmark-icon" />,
61-
Alert02Icon: ({ className }: { className?: string }) => <span className={className} data-testid="alert-icon" />,
62-
}));
57+
// Mock Hugeicons - comprehensive mock for all icons used in DiscoveryProgress and shadcn/ui components
58+
jest.mock('@hugeicons/react', () => {
59+
const createMockIcon = (name: string, testId: string) => {
60+
const Icon = ({ className }: { className?: string }) => (
61+
<svg className={className} data-testid={testId} aria-hidden="true" />
62+
);
63+
Icon.displayName = name;
64+
return Icon;
65+
};
66+
67+
return {
68+
// DiscoveryProgress icons
69+
Cancel01Icon: createMockIcon('Cancel01Icon', 'cancel-icon'),
70+
CheckmarkCircle01Icon: createMockIcon('CheckmarkCircle01Icon', 'checkmark-icon'),
71+
Alert02Icon: createMockIcon('Alert02Icon', 'alert-icon'),
72+
AlertDiamondIcon: createMockIcon('AlertDiamondIcon', 'alert-diamond-icon'),
73+
Idea01Icon: createMockIcon('Idea01Icon', 'idea-icon'),
74+
// shadcn/ui Dialog component icons
75+
Tick01Icon: createMockIcon('Tick01Icon', 'tick-icon'),
76+
ArrowDown01Icon: createMockIcon('ArrowDown01Icon', 'arrow-down-icon'),
77+
ArrowUp01Icon: createMockIcon('ArrowUp01Icon', 'arrow-up-icon'),
78+
CheckmarkSquare01Icon: createMockIcon('CheckmarkSquare01Icon', 'checkmark-square-icon'),
79+
CircleIcon: createMockIcon('CircleIcon', 'circle-icon'),
80+
};
81+
});
6382

6483
// Mock the API
6584
jest.mock('@/lib/api', () => ({

web-ui/__tests__/components/ErrorBoundary.test.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
1313
import ErrorBoundary from '@/components/ErrorBoundary';
1414
import { ReactNode } from 'react';
1515

16+
// Mock Hugeicons
17+
jest.mock('@hugeicons/react', () => {
18+
const React = require('react');
19+
return {
20+
Alert02Icon: ({ className }: { className?: string }) => (
21+
<svg data-testid="Alert02Icon" className={className} aria-hidden="true" />
22+
),
23+
};
24+
});
25+
1626
/**
1727
* Helper to set NODE_ENV for testing (process.env.NODE_ENV is read-only)
1828
*/
@@ -204,8 +214,8 @@ describe('ErrorBoundary', () => {
204214
</ErrorBoundary>
205215
);
206216

207-
// Check for warning emoji
208-
expect(screen.getByText('⚠')).toBeInTheDocument();
217+
// Check for Alert02Icon
218+
expect(screen.getByTestId('Alert02Icon')).toBeInTheDocument();
209219
});
210220

211221
it('should display error details in development mode', () => {

0 commit comments

Comments
 (0)