test: fix broken unit tests, add missing fixtures, and stabilize CI pipeline

- Fix os module mock pattern for Vitest v4 in proxy-ide-config tests

- Fix enterprise oauth_client_key expectations in cloudHandler-sync and token-manager-parity tests

- Fix proxy-controller integration test argument expectations

- Add missing proxy-parity JSON fixtures (OpenAI tools, Anthropic tool-use, Gemini SSE)

- Add reasoning_content mapping to convertClaudeToOpenAIResponse

- Fix google-api service test env var cache leak with resetModules + dynamic imports

- Fix proxy-metrics timer tests by calling onModuleInit in beforeEach

- Fix locale-sensitive number formatting in StatCard tests

- Add missing test files to version control (components, hooks, ipc, mocks, server, utils)

- Fix CI workflows: testing.yaml (use vitest run, add type-check job, npm cache), lint.yaml and format.yaml (add npm cache, concurrency, fix job names)
This commit is contained in:
Gemini Nexus Bot committed 2026-04-23 16:57:58 -03:00
1 parent bf4bcf4f96
commit afe8859f16
47 files changed
+4341 -73

No files matched your search

@@ -0,0 +1,242 @@
import React from 'react';
import { screen, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderWithProviders } from '@/tests/unit/test-utils';
import { AccountCard } from '@/components/AccountCard';
import type { Account } from '@/types/account';
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string, options?: Record<string, unknown>) => {
if (options) {
const entries = Object.entries(options);
if (entries.length > 0) {
return `${key}:${JSON.stringify(options)}`;
}
}
return key;
},
i18n: { language: 'en' },
}),
}));
vi.mock('@/components/ui/dropdown-menu', () => ({
DropdownMenu: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
DropdownMenuTrigger: ({ children, asChild }: { children: React.ReactNode; asChild?: boolean }) => {
if (asChild) {
return <>{children}</>;
}
return <div data-testid="dropdown-trigger">{children}</div>;
},
DropdownMenuContent: ({ children }: { children: React.ReactNode }) => (
<div data-testid="dropdown-content">{children}</div>
),
DropdownMenuItem: ({
children,
onClick,
disabled,
}: {
children: React.ReactNode;
onClick?: (e: React.MouseEvent) => void;
disabled?: boolean;
}) => (
<button
data-testid="dropdown-item"
onClick={onClick}
disabled={disabled}
type="button"
>
{children}
</button>
),
}));
describe('AccountCard', () => {
const createMockAccount = (overrides?: Partial<Account>): Account => ({
id: 'acc-1',
name: 'Test User',
email: '[email protected]',
backup_file: '/path/to/backup',
avatar_url: 'https://example.com/avatar.png',
deviceProfile: {
machineId: 'machine-1',
macMachineId: 'mac-1',
devDeviceId: 'dev-1',
sqmId: 'sqm-1',
},
created_at: '2026-04-20T00:00:00Z',
last_used: '2026-04-23T00:00:00Z',
...overrides,
});
const defaultProps = {
account: createMockAccount(),
isCurrent: false,
onSwitch: vi.fn(),
onDelete: vi.fn(),
isSwitching: false,
isDeleting: false,
};
beforeEach(() => {
vi.clearAllMocks();
});
it('renders account name and email', () => {
renderWithProviders(<AccountCard {...defaultProps} />);
expect(screen.getByText('Test User')).toBeInTheDocument();
expect(screen.getByText('[email protected]')).toBeInTheDocument();
});
it('shows avatar image element when avatar_url is provided', () => {
renderWithProviders(<AccountCard {...defaultProps} />);
const avatarImage = document.querySelector('img');
if (avatarImage) {
expect(avatarImage).toHaveAttribute('src', 'https://example.com/avatar.png');
} else {
// Radix Avatar may render fallback before image loads; verify src attribute is present in DOM
expect(screen.getByText('TU')).toBeInTheDocument();
}
});
it('shows avatar fallback with initials when avatar_url is missing', () => {
renderWithProviders(
<AccountCard
{...defaultProps}
account={createMockAccount({ avatar_url: undefined })}
/>,
);
expect(screen.getByText('TU')).toBeInTheDocument();
});
it('shows email initial as fallback when name is empty', () => {
renderWithProviders(
<AccountCard
{...defaultProps}
account={createMockAccount({ name: '', avatar_url: undefined })}
/>,
);
expect(screen.getByText('T')).toBeInTheDocument();
});
it('displays current badge when isCurrent is true', () => {
renderWithProviders(<AccountCard {...defaultProps} isCurrent={true} />);
expect(screen.getByText('account.current')).toBeInTheDocument();
});
it('does not display current badge when isCurrent is false', () => {
renderWithProviders(<AccountCard {...defaultProps} isCurrent={false} />);
expect(screen.queryByText('account.current')).not.toBeInTheDocument();
});
it('handles delete action from dropdown menu', () => {
const onDelete = vi.fn();
renderWithProviders(<AccountCard {...defaultProps} onDelete={onDelete} />);
const deleteButton = screen.getByTestId('dropdown-item');
fireEvent.click(deleteButton);
expect(onDelete).toHaveBeenCalledTimes(1);
expect(onDelete).toHaveBeenCalledWith('acc-1');
});
it('handles switch action when card is clicked', () => {
const onSwitch = vi.fn();
renderWithProviders(<AccountCard {...defaultProps} onSwitch={onSwitch} />);
const card = document.querySelector('[class*="cursor-pointer"]');
expect(card).toBeInTheDocument();
if (card) {
fireEvent.click(card);
}
expect(onSwitch).toHaveBeenCalledTimes(1);
expect(onSwitch).toHaveBeenCalledWith('acc-1');
});
it('does not trigger switch when isCurrent is true', () => {
const onSwitch = vi.fn();
renderWithProviders(
<AccountCard {...defaultProps} isCurrent={true} onSwitch={onSwitch} />,
);
const card = document.querySelector('[class*="cursor-pointer"]');
expect(card).not.toBeInTheDocument();
expect(onSwitch).not.toHaveBeenCalled();
});
it('does not trigger switch when isSwitching is true', () => {
const onSwitch = vi.fn();
renderWithProviders(
<AccountCard {...defaultProps} isSwitching={true} onSwitch={onSwitch} />,
);
const card = document.querySelector('[class*="pointer-events-none"]');
expect(card).toBeInTheDocument();
});
it('does not trigger switch when isDeleting is true', () => {
const onSwitch = vi.fn();
renderWithProviders(
<AccountCard {...defaultProps} isDeleting={true} onSwitch={onSwitch} />,
);
const card = document.querySelector('[class*="pointer-events-none"]');
expect(card).toBeInTheDocument();
});
it('applies reduced opacity and pointer-events-none when switching', () => {
const { container } = renderWithProviders(
<AccountCard {...defaultProps} isSwitching={true} />,
);
const card = container.querySelector('[class*="opacity-60"]');
expect(card).toBeInTheDocument();
});
it('applies reduced opacity and pointer-events-none when deleting', () => {
const { container } = renderWithProviders(
<AccountCard {...defaultProps} isDeleting={true} />,
);
const card = container.querySelector('[class*="opacity-60"]');
expect(card).toBeInTheDocument();
});
it('applies primary border styling when isCurrent is true', () => {
const { container } = renderWithProviders(
<AccountCard {...defaultProps} isCurrent={true} />,
);
const card = container.querySelector('[class*="border-primary/50"]');
expect(card).toBeInTheDocument();
});
it('shows spinning refresh icon when isSwitching is true', () => {
renderWithProviders(<AccountCard {...defaultProps} isSwitching={true} />);
const spinIcon = document.querySelector('.animate-spin');
expect(spinIcon).toBeInTheDocument();
});
it('handles mouse move to update card transform', () => {
const { container } = renderWithProviders(<AccountCard {...defaultProps} />);
const wrapper = container.firstChild as HTMLElement;
fireEvent.mouseMove(wrapper, { clientX: 100, clientY: 50 });
expect(wrapper.style.transform).toContain('perspective(1000px)');
expect(wrapper.style.transform).toContain('rotateX');
expect(wrapper.style.transform).toContain('rotateY');
});
it('handles mouse leave to reset card transform', () => {
const { container } = renderWithProviders(<AccountCard {...defaultProps} />);
const wrapper = container.firstChild as HTMLElement;
fireEvent.mouseMove(wrapper, { clientX: 100, clientY: 50 });
expect(wrapper.style.transform).not.toContain('rotateX(0deg)');
fireEvent.mouseLeave(wrapper);
expect(wrapper.style.transform).toContain('rotateX(0deg)');
expect(wrapper.style.transform).toContain('rotateY(0deg)');
});
it('renders last used text', () => {
renderWithProviders(<AccountCard {...defaultProps} />);
expect(screen.getByText(/account\.lastUsed/)).toBeInTheDocument();
});
});
@@ -0,0 +1,140 @@
import React from 'react';
import { screen, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { renderWithProviders } from '@/tests/unit/test-utils';
import { ProviderGroup } from '@/components/ProviderGroup';
import type { ProviderStats } from '@/utils/provider-grouping';
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string, options?: Record<string, unknown>) => {
if (options && 'count' in options) {
return `${key} (${options.count})`;
}
return key;
},
}),
}));
describe('ProviderGroup', () => {
const createMockStats = (overrides?: Partial<ProviderStats>): ProviderStats => ({
providerKey: 'gemini-',
providerInfo: {
name: 'Gemini',
company: 'Google',
color: '#4285F4',
},
models: [
{ id: 'models/gemini-pro', percentage: 75, resetTime: '2026-04-24T00:00:00Z' },
{ id: 'models/gemini-ultra', percentage: 50, resetTime: '2026-04-24T00:00:00Z' },
],
visibleModels: [
{ id: 'models/gemini-pro', percentage: 75, resetTime: '2026-04-24T00:00:00Z' },
{ id: 'models/gemini-ultra', percentage: 50, resetTime: '2026-04-24T00:00:00Z' },
],
avgPercentage: 62.5,
earliestReset: '2026-04-24T00:00:00Z',
...overrides,
});
const defaultProps = {
stats: createMockStats(),
isCollapsed: false,
onToggleCollapse: vi.fn(),
getQuotaTextColorClass: (percentage: number) =>
percentage > 50 ? 'text-green-500' : 'text-yellow-500',
getQuotaBarColorClass: (percentage: number) =>
percentage > 50 ? 'bg-green-500' : 'bg-yellow-500',
formatQuotaLabel: (percentage: number) => `${percentage}%`,
formatResetTimeLabel: (resetTime?: string) => (resetTime ? '24h' : '—'),
formatResetTimeTitle: (resetTime?: string) =>
resetTime ? new Date(resetTime).toLocaleString() : undefined,
leftLabel: 'left',
};
it('renders provider name', () => {
renderWithProviders(<ProviderGroup {...defaultProps} />);
expect(screen.getByText('Gemini')).toBeInTheDocument();
});
it('displays model count', () => {
renderWithProviders(<ProviderGroup {...defaultProps} />);
expect(
screen.getByText(/settings\.providerGroupings\.models/),
).toBeInTheDocument();
});
it('shows quota bars and average percentage', () => {
renderWithProviders(<ProviderGroup {...defaultProps} />);
expect(screen.getByText('62.5%')).toBeInTheDocument();
expect(screen.getByText('settings.providerGroupings.avgLabel')).toBeInTheDocument();
});
it('shows reset time label', () => {
renderWithProviders(<ProviderGroup {...defaultProps} />);
expect(screen.getAllByText('24h').length).toBeGreaterThanOrEqual(1);
});
it('shows individual model rows when expanded', () => {
renderWithProviders(<ProviderGroup {...defaultProps} isCollapsed={false} />);
expect(screen.getByText('gemini-pro')).toBeInTheDocument();
expect(screen.getByText('gemini-ultra')).toBeInTheDocument();
});
it('hides individual model rows when collapsed', () => {
renderWithProviders(<ProviderGroup {...defaultProps} isCollapsed={true} />);
expect(screen.queryByText('gemini-pro')).not.toBeInTheDocument();
expect(screen.queryByText('gemini-ultra')).not.toBeInTheDocument();
});
it('calls onToggleCollapse when header button is clicked', () => {
const onToggleCollapse = vi.fn();
renderWithProviders(
<ProviderGroup {...defaultProps} onToggleCollapse={onToggleCollapse} />,
);
const button = screen.getByRole('button');
fireEvent.click(button);
expect(onToggleCollapse).toHaveBeenCalledTimes(1);
});
it('renders color dot with provider color', () => {
const { container } = renderWithProviders(
<ProviderGroup {...defaultProps} />,
);
const dot = container.querySelector('span[style*="background-color"]');
expect(dot).toHaveAttribute('style', expect.stringContaining('#4285F4'));
});
it('shows chevron right when collapsed', () => {
renderWithProviders(<ProviderGroup {...defaultProps} isCollapsed={true} />);
expect(document.querySelector('svg')).toBeInTheDocument();
});
it('shows chevron down when expanded', () => {
renderWithProviders(<ProviderGroup {...defaultProps} isCollapsed={false} />);
expect(document.querySelector('svg')).toBeInTheDocument();
});
it('renders nothing when visibleModels is empty', () => {
const { container } = renderWithProviders(
<ProviderGroup
{...defaultProps}
stats={createMockStats({ visibleModels: [] })}
/>,
);
expect(container.firstChild).toBeNull();
});
it('displays model-specific percentages in expanded rows', () => {
renderWithProviders(<ProviderGroup {...defaultProps} isCollapsed={false} />);
expect(screen.getByText('75%')).toBeInTheDocument();
expect(screen.getByText('50%')).toBeInTheDocument();
});
it('shows leftLabel next to model percentages when percentage > 0', () => {
renderWithProviders(<ProviderGroup {...defaultProps} isCollapsed={false} />);
const leftLabels = screen.getAllByText('left');
expect(leftLabels.length).toBe(2);
});
});
@@ -0,0 +1,72 @@
import React from 'react';
import { screen, fireEvent } from '@testing-library/react';
import { renderWithProviders } from '@/tests/unit/test-utils';
import { Button, buttonVariants } from '@/components/ui/button';
describe('Button', () => {
it('renders with default variant and size', () => {
renderWithProviders(<Button>Click me</Button>);
const button = screen.getByRole('button', { name: /click me/i });
expect(button).toBeInTheDocument();
expect(button).toHaveAttribute('data-slot', 'button');
});
it('renders all variant styles', () => {
const variants = ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link'] as const;
variants.forEach((variant) => {
const { container } = renderWithProviders(
<Button variant={variant}>{variant}</Button>,
);
expect(container.querySelector('button')).toBeInTheDocument();
});
});
it('renders all size styles', () => {
const sizes = ['default', 'sm', 'lg', 'icon'] as const;
sizes.forEach((size) => {
const { container } = renderWithProviders(
<Button size={size}>{size}</Button>,
);
expect(container.querySelector('button')).toBeInTheDocument();
});
});
it('handles click events', () => {
const handleClick = vi.fn();
renderWithProviders(<Button onClick={handleClick}>Click me</Button>);
fireEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('supports disabled state', () => {
renderWithProviders(<Button disabled>Disabled</Button>);
expect(screen.getByRole('button')).toBeDisabled();
});
it('renders as child element when asChild is true', () => {
renderWithProviders(
<Button asChild>
<a href="/test">Link Button</a>
</Button>,
);
const link = screen.getByRole('link', { name: /link button/i });
expect(link).toBeInTheDocument();
expect(link).toHaveAttribute('href', '/test');
});
it('applies custom className', () => {
renderWithProviders(<Button className="custom-class">Styled</Button>);
expect(screen.getByRole('button')).toHaveClass('custom-class');
});
it('buttonVariants function returns expected classes for default variant', () => {
const classes = buttonVariants({ variant: 'default', size: 'default' });
expect(classes).toContain('bg-primary');
expect(classes).toContain('text-primary-foreground');
});
it('buttonVariants function returns expected classes for destructive variant', () => {
const classes = buttonVariants({ variant: 'destructive' });
expect(classes).toContain('bg-destructive');
});
});
+107
View File
@@ -0,0 +1,107 @@
import React from 'react';
import { screen } from '@testing-library/react';
import { renderWithProviders } from '@/tests/unit/test-utils';
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
CardFooter,
} from '@/components/ui/card';
describe('Card', () => {
it('renders Card with content', () => {
renderWithProviders(
<Card data-testid="card">
<span>Card content</span>
</Card>,
);
expect(screen.getByTestId('card')).toBeInTheDocument();
expect(screen.getByText('Card content')).toBeInTheDocument();
});
it('applies custom className to Card', () => {
renderWithProviders(<Card className="my-card">Content</Card>);
expect(screen.getByText('Content')).toHaveClass('my-card');
});
it('forwards ref correctly', () => {
const ref = React.createRef<HTMLDivElement>();
renderWithProviders(<Card ref={ref}>Ref test</Card>);
expect(ref.current).toBeInstanceOf(HTMLDivElement);
});
});
describe('CardHeader', () => {
it('renders header with title and description', () => {
renderWithProviders(
<Card>
<CardHeader>
<CardTitle>Title</CardTitle>
<CardDescription>Description</CardDescription>
</CardHeader>
</Card>,
);
expect(screen.getByText('Title')).toBeInTheDocument();
expect(screen.getByText('Description')).toBeInTheDocument();
});
it('CardTitle renders as h3', () => {
renderWithProviders(<CardTitle>Heading</CardTitle>);
expect(screen.getByRole('heading', { level: 3 })).toHaveTextContent('Heading');
});
it('CardDescription has correct text styling classes', () => {
const { container } = renderWithProviders(<CardDescription>Desc</CardDescription>);
expect(container.querySelector('p')).toHaveClass('text-muted-foreground');
});
});
describe('CardContent', () => {
it('renders content area', () => {
renderWithProviders(
<Card>
<CardContent>Main content</CardContent>
</Card>,
);
expect(screen.getByText('Main content')).toBeInTheDocument();
});
});
describe('CardFooter', () => {
it('renders footer with actions', () => {
renderWithProviders(
<Card>
<CardFooter>
<button>Action</button>
</CardFooter>
</Card>,
);
expect(screen.getByRole('button')).toBeInTheDocument();
});
});
describe('Full Card composition', () => {
it('renders complete card with all sections', () => {
renderWithProviders(
<Card className="w-96">
<CardHeader>
<CardTitle>Account Stats</CardTitle>
<CardDescription>Overview of usage</CardDescription>
</CardHeader>
<CardContent>
<p>Tokens used: 1,234</p>
</CardContent>
<CardFooter>
<span>Last updated: now</span>
</CardFooter>
</Card>,
);
expect(screen.getByRole('heading', { name: /account stats/i })).toBeInTheDocument();
expect(screen.getByText(/overview of usage/i)).toBeInTheDocument();
expect(screen.getByText(/tokens used/i)).toBeInTheDocument();
expect(screen.getByText(/last updated/i)).toBeInTheDocument();
});
});
@@ -0,0 +1,66 @@
import React from 'react';
import { screen, fireEvent } from '@testing-library/react';
import { renderWithProviders } from '@/tests/unit/test-utils';
import { Input } from '@/components/ui/input';
describe('Input', () => {
it('renders an input element', () => {
renderWithProviders(<Input placeholder="Enter text" />);
expect(screen.getByPlaceholderText('Enter text')).toBeInTheDocument();
});
it('accepts and displays text value', () => {
renderWithProviders(<Input />);
const input = screen.getByRole('textbox');
fireEvent.change(input, { target: { value: 'Hello world' } });
expect(input).toHaveValue('Hello world');
});
it('supports different input types', () => {
const { rerender } = renderWithProviders(<Input type="text" />);
expect(screen.getByRole('textbox')).toHaveAttribute('type', 'text');
rerender(<Input type="password" data-testid="password-input" />);
expect(screen.getByTestId('password-input')).toHaveAttribute('type', 'password');
rerender(<Input type="email" data-testid="email-input" />);
expect(screen.getByTestId('email-input')).toHaveAttribute('type', 'email');
});
it('supports disabled state', () => {
renderWithProviders(<Input disabled />);
expect(screen.getByRole('textbox')).toBeDisabled();
});
it('forwards ref correctly', () => {
const ref = React.createRef<HTMLInputElement>();
renderWithProviders(<Input ref={ref} />);
expect(ref.current).toBeInstanceOf(HTMLInputElement);
});
it('applies custom className', () => {
renderWithProviders(<Input className="custom-input" />);
expect(screen.getByRole('textbox')).toHaveClass('custom-input');
});
it('handles focus and blur events', () => {
const onFocus = vi.fn();
const onBlur = vi.fn();
renderWithProviders(<Input onFocus={onFocus} onBlur={onBlur} />);
const input = screen.getByRole('textbox');
fireEvent.focus(input);
expect(onFocus).toHaveBeenCalledTimes(1);
fireEvent.blur(input);
expect(onBlur).toHaveBeenCalledTimes(1);
});
it('has expected base styling classes', () => {
const { container } = renderWithProviders(<Input />);
const input = container.querySelector('input');
expect(input).toHaveClass('rounded-md');
expect(input).toHaveClass('border');
expect(input).toHaveClass('bg-transparent');
});
});
@@ -0,0 +1,94 @@
import React from 'react';
import { screen, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { renderWithProviders } from '@/tests/unit/test-utils';
import { ChartCard } from '@/components/usage/ChartCard';
describe('ChartCard', () => {
const defaultProps = {
isLoading: false,
error: null,
isEmpty: false,
onRetry: vi.fn(),
title: 'Test Chart',
emptyTitle: 'No Data',
emptyDescription: 'There is no data to display.',
errorMessage: 'Failed to load chart.',
retryLabel: 'Retry',
children: <div data-testid="chart-content">Chart Content</div>,
};
it('renders loading state with skeleton', () => {
renderWithProviders(
<ChartCard {...defaultProps} isLoading={true} />,
);
expect(screen.getByText('Test Chart')).toBeInTheDocument();
expect(document.querySelector('.animate-pulse')).toBeInTheDocument();
expect(screen.queryByTestId('chart-content')).not.toBeInTheDocument();
});
it('renders error state with retry button and calls onRetry when clicked', () => {
const onRetry = vi.fn();
renderWithProviders(
<ChartCard
{...defaultProps}
error={new Error('Network error')}
onRetry={onRetry}
/>,
);
expect(screen.getByText('Failed to load chart.')).toBeInTheDocument();
const retryButton = screen.getByRole('button', { name: /retry/i });
expect(retryButton).toBeInTheDocument();
fireEvent.click(retryButton);
expect(onRetry).toHaveBeenCalledTimes(1);
});
it('renders empty state with title and description', () => {
renderWithProviders(
<ChartCard {...defaultProps} isEmpty={true} />,
);
expect(screen.getByText('No Data')).toBeInTheDocument();
expect(
screen.getByText('There is no data to display.'),
).toBeInTheDocument();
expect(screen.queryByTestId('chart-content')).not.toBeInTheDocument();
});
it('renders children when not loading, error, or empty', () => {
renderWithProviders(<ChartCard {...defaultProps} />);
expect(screen.getByTestId('chart-content')).toBeInTheDocument();
expect(screen.getByText('Chart Content')).toBeInTheDocument();
expect(document.querySelector('.animate-pulse')).not.toBeInTheDocument();
});
it('prioritizes loading over error state', () => {
renderWithProviders(
<ChartCard
{...defaultProps}
isLoading={true}
error={new Error('Some error')}
/>,
);
expect(document.querySelector('.animate-pulse')).toBeInTheDocument();
expect(screen.queryByText('Failed to load chart.')).not.toBeInTheDocument();
});
it('prioritizes error over empty state', () => {
renderWithProviders(
<ChartCard
{...defaultProps}
error={new Error('Some error')}
isEmpty={true}
/>,
);
expect(screen.getByText('Failed to load chart.')).toBeInTheDocument();
expect(screen.queryByText('No Data')).not.toBeInTheDocument();
});
});
@@ -0,0 +1,125 @@
import React from 'react';
import { screen } from '@testing-library/react';
import { renderWithProviders } from '@/tests/unit/test-utils';
import { StatCard } from '@/components/usage/StatCard';
import { Activity, TrendingUp, Users, Zap } from 'lucide-react';
import { vi, beforeAll, afterAll } from 'vitest';
describe('StatCard', () => {
beforeAll(() => {
vi.spyOn(Number.prototype, 'toLocaleString').mockImplementation(function (this: number) {
return this.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
});
});
afterAll(() => {
vi.restoreAllMocks();
});
it('renders label and value', () => {
renderWithProviders(
<StatCard label="Total Tokens" value={12345} icon={Activity} />,
);
expect(screen.getByText('Total Tokens')).toBeInTheDocument();
expect(screen.getByText('12,345')).toBeInTheDocument();
});
it('renders string values correctly', () => {
renderWithProviders(
<StatCard label="Status" value="Active" icon={Zap} />,
);
expect(screen.getByText('Active')).toBeInTheDocument();
});
it('formats number values with locale string', () => {
renderWithProviders(
<StatCard label="Requests" value={1000000} icon={TrendingUp} />,
);
expect(screen.getByText('1,000,000')).toBeInTheDocument();
});
it('shows skeleton when isLoading is true', () => {
renderWithProviders(
<StatCard label="Loading" value={0} icon={Users} isLoading />,
);
expect(screen.queryByText('0')).not.toBeInTheDocument();
expect(document.querySelector('.animate-pulse')).toBeInTheDocument();
});
it('does not show skeleton when isLoading is false', () => {
renderWithProviders(
<StatCard label="Loaded" value={42} icon={Activity} isLoading={false} />,
);
expect(screen.getByText('42')).toBeInTheDocument();
expect(document.querySelector('.animate-pulse')).not.toBeInTheDocument();
});
it('renders icon element', () => {
renderWithProviders(
<StatCard label="With Icon" value={100} icon={Zap} />,
);
expect(document.querySelector('svg')).toBeInTheDocument();
});
it('applies custom className', () => {
renderWithProviders(
<StatCard label="Styled" value={1} icon={Activity} className="my-stat" />,
);
expect(screen.getByText('Styled').closest('[class*="border-border"]')).toHaveClass('my-stat');
});
it('applies default slate accent when accent prop is not provided', () => {
const { container } = renderWithProviders(
<StatCard label="Default Accent" value={1} icon={Activity} />,
);
const iconContainer = container.querySelector('.rounded-md');
expect(iconContainer).toHaveClass('bg-slate-500/5');
});
it('applies blue accent', () => {
const { container } = renderWithProviders(
<StatCard label="Blue" value={1} icon={Activity} accent="blue" />,
);
const iconContainer = container.querySelector('.rounded-md');
expect(iconContainer).toHaveClass('bg-blue-500/5');
});
it('applies green accent', () => {
const { container } = renderWithProviders(
<StatCard label="Green" value={1} icon={Activity} accent="green" />,
);
const iconContainer = container.querySelector('.rounded-md');
expect(iconContainer).toHaveClass('bg-emerald-500/5');
});
it('applies amber accent', () => {
const { container } = renderWithProviders(
<StatCard label="Amber" value={1} icon={Activity} accent="amber" />,
);
const iconContainer = container.querySelector('.rounded-md');
expect(iconContainer).toHaveClass('bg-amber-500/5');
});
it('applies purple accent', () => {
const { container } = renderWithProviders(
<StatCard label="Purple" value={1} icon={Activity} accent="purple" />,
);
const iconContainer = container.querySelector('.rounded-md');
expect(iconContainer).toHaveClass('bg-purple-500/5');
});
it('has uppercase label styling', () => {
renderWithProviders(
<StatCard label="Uppercase Test" value={1} icon={Activity} />,
);
const label = screen.getByText('Uppercase Test');
expect(label).toHaveClass('uppercase');
});
it('has mono font for value', () => {
renderWithProviders(
<StatCard label="Mono" value={999} icon={Activity} />,
);
const value = screen.getByText('999');
expect(value).toHaveClass('font-mono');
});
});