Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 56 additions & 36 deletions .beads/issues.jsonl

Large diffs are not rendered by default.

33 changes: 29 additions & 4 deletions web-ui/__tests__/components/AgentCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,31 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';

// Mock Hugeicons - must be before component import
jest.mock('@hugeicons/react', () => {
const createMockIcon = (name: string, testId: string) => {
const Icon = ({ className }: { className?: string }) => (
<svg className={className} data-testid={testId} aria-hidden="true" />
);
Icon.displayName = name;
return Icon;
};

return {
// Agent type icons
Settings01Icon: createMockIcon('Settings01Icon', 'settings-icon'),
PaintBrush01Icon: createMockIcon('PaintBrush01Icon', 'paint-brush-icon'),
TestTube01Icon: createMockIcon('TestTube01Icon', 'test-tube-icon'),
BotIcon: createMockIcon('BotIcon', 'bot-icon'),
// Maturity level icons
SunriseIcon: createMockIcon('SunriseIcon', 'sunrise-icon'),
BookOpen01Icon: createMockIcon('BookOpen01Icon', 'book-icon'),
FlashIcon: createMockIcon('FlashIcon', 'flash-icon'),
Award01Icon: createMockIcon('Award01Icon', 'award-icon'),
};
});

import AgentCard, { Agent } from '@/components/AgentCard';

describe('AgentCard Component', () => {
Expand Down Expand Up @@ -212,7 +237,7 @@ describe('AgentCard Component', () => {

const badge = screen.getByText('Backend Worker').parentElement;
expect(badge).toHaveClass('bg-primary/10', 'text-primary-foreground');
expect(screen.getByText('βš™οΈ')).toBeInTheDocument();
expect(screen.getByTestId('settings-icon')).toBeInTheDocument();
});

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

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

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

const badge = screen.getByText('Test Engineer').parentElement;
expect(badge).toHaveClass('bg-secondary', 'text-secondary-foreground');
expect(screen.getByText('πŸ§ͺ')).toBeInTheDocument();
expect(screen.getByTestId('test-tube-icon')).toBeInTheDocument();
});

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

const badge = screen.getByText('Custom Agent').parentElement;
expect(badge).toHaveClass('bg-muted', 'text-foreground');
expect(screen.getByText('πŸ€–')).toBeInTheDocument();
expect(screen.getByTestId('bot-icon')).toBeInTheDocument();
});
});

Expand Down
30 changes: 29 additions & 1 deletion web-ui/__tests__/components/AgentList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,34 @@
import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import { SWRConfig } from 'swr';

// Mock Hugeicons - must be before component import
// Include all icons used by AgentList and its child components (AgentAssignmentCard)
jest.mock('@hugeicons/react', () => {
const createMockIcon = (name: string, testId: string) => {
const Icon = ({ className }: { className?: string }) => (
<svg className={className} data-testid={testId} aria-hidden="true" />
);
Icon.displayName = name;
return Icon;
};

return {
// AgentList icons
BotIcon: createMockIcon('BotIcon', 'bot-icon'),
CheckListIcon: createMockIcon('CheckListIcon', 'checklist-icon'),
Alert02Icon: createMockIcon('Alert02Icon', 'alert-icon'),
RefreshIcon: createMockIcon('RefreshIcon', 'refresh-icon'),
// AgentAssignmentCard icons
CrownIcon: createMockIcon('CrownIcon', 'crown-icon'),
Settings01Icon: createMockIcon('Settings01Icon', 'settings-icon'),
PaintBrush01Icon: createMockIcon('PaintBrush01Icon', 'paint-brush-icon'),
TestTube01Icon: createMockIcon('TestTube01Icon', 'test-tube-icon'),
Search01Icon: createMockIcon('Search01Icon', 'search-icon'),
CheckmarkCircle01Icon: createMockIcon('CheckmarkCircle01Icon', 'checkmark-icon'),
};
});

import { AgentList } from '../../src/components/AgentList';
import * as agentAssignmentApi from '../../src/api/agentAssignment';
import type { IssuesResponse } from '../../src/types/api';
Expand Down Expand Up @@ -227,7 +255,7 @@ describe('AgentList', () => {
);

await waitFor(() => {
// Check for BotIcon (Hugeicons adds data-testid automatically)
// Check for BotIcon (data-testid added explicitly in component source)
expect(screen.getByTestId('bot-icon')).toBeInTheDocument();
});
});
Expand Down
36 changes: 25 additions & 11 deletions web-ui/__tests__/components/BlockerBadge.test.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
/**
* BlockerBadge Component Tests
* Tests for blocker type badge display (049-human-in-loop, T016)
* Updated for emoji-to-Hugeicons migration: icons now use Hugeicons components
*/

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

// Mock Hugeicons
jest.mock('@hugeicons/react', () => {
const React = require('react');
return {
Alert02Icon: ({ className }: { className?: string }) => (
<svg data-testid="Alert02Icon" className={className} aria-hidden="true" />
),
Idea01Icon: ({ className }: { className?: string }) => (
<svg data-testid="Idea01Icon" className={className} aria-hidden="true" />
),
};
});

describe('BlockerBadge', () => {
describe('SYNC blocker badge', () => {
it('renders with correct label', () => {
render(<BlockerBadge type="SYNC" />);
expect(screen.getByText('CRITICAL')).toBeInTheDocument();
});

it('displays alert icon', () => {
it('displays Alert02Icon', () => {
render(<BlockerBadge type="SYNC" />);
expect(screen.getByText('🚨')).toBeInTheDocument();
expect(screen.getByTestId('Alert02Icon')).toBeInTheDocument();
});

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

it('displays lightbulb icon', () => {
it('displays Idea01Icon', () => {
render(<BlockerBadge type="ASYNC" />);
expect(screen.getByText('πŸ’‘')).toBeInTheDocument();
expect(screen.getByTestId('Idea01Icon')).toBeInTheDocument();
});

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

describe('icon rendering', () => {
it('renders icon with correct size class', () => {
const { container } = render(<BlockerBadge type="SYNC" />);
const icon = screen.getByText('🚨');
expect(icon).toHaveClass('text-sm');
it('renders SYNC icon with correct size class', () => {
render(<BlockerBadge type="SYNC" />);
const icon = screen.getByTestId('Alert02Icon');
expect(icon).toHaveClass('h-3.5', 'w-3.5');
});

it('renders ASYNC icon with correct size class', () => {
const { container } = render(<BlockerBadge type="ASYNC" />);
const icon = screen.getByText('πŸ’‘');
expect(icon).toHaveClass('text-sm');
render(<BlockerBadge type="ASYNC" />);
const icon = screen.getByTestId('Idea01Icon');
expect(icon).toHaveClass('h-3.5', 'w-3.5');
});
});
});
25 changes: 21 additions & 4 deletions web-ui/__tests__/components/BlockerPanel.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
* BlockerPanel Component Tests
* Tests for blocker list display and sorting (049-human-in-loop, T017)
* Updated for emoji-to-Hugeicons migration: icons now use Hugeicons components
*/

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

// Mock Hugeicons
jest.mock('@hugeicons/react', () => {
const React = require('react');
const createMockIcon = (name: string) => {
const Icon = ({ className }: { className?: string }) => (
<svg data-testid={name} className={className} aria-hidden="true" />
);
Icon.displayName = name;
return Icon;
};
return {
CheckmarkCircle01Icon: createMockIcon('CheckmarkCircle01Icon'),
BotIcon: createMockIcon('BotIcon'),
};
});

// Mock BlockerBadge component
jest.mock('@/components/BlockerBadge', () => ({
BlockerBadge: ({ type }: { type: string }) => (
Expand All @@ -31,7 +48,7 @@ describe('BlockerPanel', () => {
it('renders empty state when blockers array is empty', () => {
render(<BlockerPanel blockers={mockEmptyBlockersList} />);
expect(screen.getByText('No blockers - agents are running smoothly!')).toBeInTheDocument();
expect(screen.getByText('βœ…')).toBeInTheDocument();
expect(screen.getByTestId('CheckmarkCircle01Icon')).toBeInTheDocument();
});

it('displays (0) count in empty state', () => {
Expand Down Expand Up @@ -246,12 +263,12 @@ describe('BlockerPanel', () => {
// Skip first 3 filter buttons, get the first blocker button
const blockerButton = buttons[3];
// Should not have the 'β€’' separator for task
expect(blockerButton.textContent).not.toMatch(/πŸ€–.*β€’.*Implement/);
expect(blockerButton.textContent).not.toMatch(/β€’.*Implement/);
});

it('displays robot emoji for agent', () => {
it('displays BotIcon for agent', () => {
render(<BlockerPanel blockers={[mockSyncBlocker]} />);
expect(screen.getByText('πŸ€–')).toBeInTheDocument();
expect(screen.getByTestId('BotIcon')).toBeInTheDocument();
});
});

Expand Down
13 changes: 13 additions & 0 deletions web-ui/__tests__/components/Dashboard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,19 @@ jest.mock('@hugeicons/react', () => {
// Other icons
Download01Icon: createMockIcon('download-icon'),
AlertDiamondIcon: createMockIcon('alert-diamond-icon'),
// Quality gate icons
ChartBarLineIcon: createMockIcon('chart-bar-line-icon'),
FileEditIcon: createMockIcon('file-edit-icon'),
SparklesIcon: createMockIcon('sparkles-icon'),
Settings01Icon: createMockIcon('settings-icon'),
PauseIcon: createMockIcon('pause-icon'),
RefreshIcon: createMockIcon('refresh-icon'),
InformationCircleIcon: createMockIcon('info-circle-icon'),
// AgentCard icons (agent type and maturity badges)
PaintBrush01Icon: createMockIcon('paint-brush-icon'),
SunriseIcon: createMockIcon('sunrise-icon'),
BookOpen01Icon: createMockIcon('book-open-icon'),
FlashIcon: createMockIcon('flash-icon'),
};
});

Expand Down
35 changes: 27 additions & 8 deletions web-ui/__tests__/components/DiscoveryProgress.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,35 @@
*/

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

// Mock Hugeicons
jest.mock('@hugeicons/react', () => ({
Cancel01Icon: ({ className }: { className?: string }) => <span className={className} data-testid="cancel-icon" />,
CheckmarkCircle01Icon: ({ className }: { className?: string }) => <span className={className} data-testid="checkmark-icon" />,
Alert02Icon: ({ className }: { className?: string }) => <span className={className} data-testid="alert-icon" />,
}));
// Mock Hugeicons - must be before component imports
jest.mock('@hugeicons/react', () => {
const createMockIcon = (name: string, testId: string) => {
const Icon = ({ className }: { className?: string }) => (
<svg className={className} data-testid={testId} aria-hidden="true" />
);
Icon.displayName = name;
return Icon;
};

return {
Cancel01Icon: createMockIcon('Cancel01Icon', 'cancel-icon'),
CheckmarkCircle01Icon: createMockIcon('CheckmarkCircle01Icon', 'checkmark-icon'),
Alert02Icon: createMockIcon('Alert02Icon', 'alert-icon'),
AlertDiamondIcon: createMockIcon('AlertDiamondIcon', 'alert-diamond-icon'),
Idea01Icon: createMockIcon('Idea01Icon', 'idea-icon'),
// Dialog component icons
Tick01Icon: createMockIcon('Tick01Icon', 'tick-icon'),
ArrowDown01Icon: createMockIcon('ArrowDown01Icon', 'arrow-down-icon'),
ArrowUp01Icon: createMockIcon('ArrowUp01Icon', 'arrow-up-icon'),
CheckmarkSquare01Icon: createMockIcon('CheckmarkSquare01Icon', 'checkmark-square-icon'),
CircleIcon: createMockIcon('CircleIcon', 'circle-icon'),
};
});

import DiscoveryProgress from '@/components/DiscoveryProgress';
import { projectsApi, tasksApi } from '@/lib/api';

// Mock the API
const mockStartProject = jest.fn();
Expand Down
31 changes: 25 additions & 6 deletions web-ui/__tests__/components/DiscoveryProgress.testutils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,31 @@ export const simulateWsMessage = (message: Record<string, unknown>) => {
// Mock Modules Setup
// =============================================================================

// Mock Hugeicons
jest.mock('@hugeicons/react', () => ({
Cancel01Icon: ({ className }: { className?: string }) => <span className={className} data-testid="cancel-icon" />,
CheckmarkCircle01Icon: ({ className }: { className?: string }) => <span className={className} data-testid="checkmark-icon" />,
Alert02Icon: ({ className }: { className?: string }) => <span className={className} data-testid="alert-icon" />,
}));
// Mock Hugeicons - comprehensive mock for all icons used in DiscoveryProgress and shadcn/ui components
jest.mock('@hugeicons/react', () => {
const createMockIcon = (name: string, testId: string) => {
const Icon = ({ className }: { className?: string }) => (
<svg className={className} data-testid={testId} aria-hidden="true" />
);
Icon.displayName = name;
return Icon;
};

return {
// DiscoveryProgress icons
Cancel01Icon: createMockIcon('Cancel01Icon', 'cancel-icon'),
CheckmarkCircle01Icon: createMockIcon('CheckmarkCircle01Icon', 'checkmark-icon'),
Alert02Icon: createMockIcon('Alert02Icon', 'alert-icon'),
AlertDiamondIcon: createMockIcon('AlertDiamondIcon', 'alert-diamond-icon'),
Idea01Icon: createMockIcon('Idea01Icon', 'idea-icon'),
// shadcn/ui Dialog component icons
Tick01Icon: createMockIcon('Tick01Icon', 'tick-icon'),
ArrowDown01Icon: createMockIcon('ArrowDown01Icon', 'arrow-down-icon'),
ArrowUp01Icon: createMockIcon('ArrowUp01Icon', 'arrow-up-icon'),
CheckmarkSquare01Icon: createMockIcon('CheckmarkSquare01Icon', 'checkmark-square-icon'),
CircleIcon: createMockIcon('CircleIcon', 'circle-icon'),
};
});

// Mock the API
jest.mock('@/lib/api', () => ({
Expand Down
14 changes: 12 additions & 2 deletions web-ui/__tests__/components/ErrorBoundary.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import ErrorBoundary from '@/components/ErrorBoundary';
import { ReactNode } from 'react';

// Mock Hugeicons
jest.mock('@hugeicons/react', () => {
const React = require('react');
return {
Alert02Icon: ({ className }: { className?: string }) => (
<svg data-testid="Alert02Icon" className={className} aria-hidden="true" />
),
};
});

/**
* Helper to set NODE_ENV for testing (process.env.NODE_ENV is read-only)
*/
Expand Down Expand Up @@ -204,8 +214,8 @@ describe('ErrorBoundary', () => {
</ErrorBoundary>
);

// Check for warning emoji
expect(screen.getByText('⚠')).toBeInTheDocument();
// Check for Alert02Icon
expect(screen.getByTestId('Alert02Icon')).toBeInTheDocument();
});

it('should display error details in development mode', () => {
Expand Down
Loading
Loading