style(design): apply Minimal Dark Mode design system\n\n- Switched to complete Dark Mode (OLED-friendly)\n- Established standard color palette matching modern technical aesthetics\n- Changed font to Plus Jakarta Sans for increased legibility\n- Enforced dark mode as default in App.tsx\n- Fixed several React cascading render (setState in useEffect) warnings
This commit is contained in:
1 parent
b80531f8a8
commit
bac7b5591e
738 files changed
+138513
-138123
No files matched your search
@@ -1,93 +1,93 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -1,124 +1,124 @@
|
||||
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');
|
||||
});
|
||||
});
|
||||
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');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user