MeshiTrack/packages/web/tests/components/ui.test.tsx

139 lines
4.3 KiB
TypeScript
Raw Normal View History

2026-05-14 14:47:23 +09:00
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
2026-05-19 11:06:03 +09:00
import { Avatar } from '../../src/components/ui/Avatar';
import { IconButton } from '../../src/components/ui/IconButton';
import { Ring } from '../../src/components/ui/Ring';
import { SparkBars } from '../../src/components/ui/SparkBars';
import { SupplyBar } from '../../src/components/ui/SupplyBar';
import { Card, CardHeader, CardBody } from '../../src/components/ui/Card';
2026-05-19 14:09:03 +09:00
import { Icon } from '../../src/components/ui/Icon';
2026-05-14 14:47:23 +09:00
describe('Avatar', () => {
it('renders initial from name', () => {
render(<Avatar name="Alice" />);
expect(screen.getByLabelText('Alice')).toBeInTheDocument();
expect(screen.getByText('A')).toBeInTheDocument();
});
it('uses custom size', () => {
render(<Avatar name="Bob" size={48} />);
const el = screen.getByLabelText('Bob');
expect(el).toHaveStyle({ width: '48px', height: '48px' });
});
2026-05-19 14:09:03 +09:00
it('falls back to ? for empty name', () => {
render(<Avatar name="" />);
expect(screen.getByText('?')).toBeInTheDocument();
});
2026-05-14 14:47:23 +09:00
});
describe('IconButton', () => {
it('renders with label and icon', () => {
render(<IconButton icon="settings" label="Settings" />);
expect(screen.getByLabelText('Settings')).toBeInTheDocument();
});
it('renders notification dot when dot=true', () => {
render(<IconButton icon="bell" label="Notifications" dot />);
// dot is a span inside the button
const btn = screen.getByLabelText('Notifications');
expect(btn.querySelectorAll('span').length).toBeGreaterThanOrEqual(1);
});
});
describe('Ring', () => {
it('renders value and total', () => {
render(<Ring value={5} total={10} />);
expect(screen.getByLabelText('5 of 10 doses taken')).toBeInTheDocument();
expect(screen.getByText('5')).toBeInTheDocument();
});
it('handles zero total', () => {
render(<Ring value={0} total={0} />);
expect(screen.getByText('0')).toBeInTheDocument();
});
});
describe('SparkBars', () => {
it('renders bars with labels', () => {
const data = [
{ label: 'Jan', amount: 100 },
{ label: 'Feb', amount: 200 },
];
render(<SparkBars data={data} />);
expect(screen.getByText('Jan')).toBeInTheDocument();
expect(screen.getByText('Feb')).toBeInTheDocument();
});
it('renders formatted amounts', () => {
const data = [{ label: 'Mar', amount: 5000 }];
render(<SparkBars data={data} />);
expect(screen.getByText('5k')).toBeInTheDocument();
});
});
describe('SupplyBar', () => {
it('renders days value', () => {
render(<SupplyBar days={30} />);
expect(screen.getByText('30')).toBeInTheDocument();
expect(screen.getByText('d')).toBeInTheDocument();
});
it('renders for critical days', () => {
render(<SupplyBar days={5} />);
expect(screen.getByText('5')).toBeInTheDocument();
});
it('renders for medium days', () => {
render(<SupplyBar days={10} />);
expect(screen.getByText('10')).toBeInTheDocument();
});
});
2026-05-19 10:13:40 +09:00
describe('Card', () => {
it('renders children and applies custom styles', () => {
render(
<Card className="custom-card" style={{ margin: '20px' }}>
<div>Card Content</div>
</Card>
);
expect(screen.getByText('Card Content')).toBeInTheDocument();
});
it('renders header with title, subtitle and action', () => {
render(
<CardHeader
title="My Card"
subtitle="My Subtitle"
action={<button>Click Me</button>}
/>
);
expect(screen.getByText('My Card')).toBeInTheDocument();
expect(screen.getByText('My Subtitle')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Click Me' })).toBeInTheDocument();
});
it('renders body with children', () => {
render(
<CardBody>
<div>Body Content</div>
</CardBody>
);
expect(screen.getByText('Body Content')).toBeInTheDocument();
});
});
2026-05-19 14:09:03 +09:00
describe('Icon', () => {
it('renders paths for valid icon', () => {
const { container } = render(<Icon name="bell" />);
expect(container.querySelector('svg')).toBeInTheDocument();
});
it('renders null for invalid icon name', () => {
2026-05-19 16:15:15 +09:00
const { container } = render(<Icon name={"nonexistent" as any} />);
2026-05-19 14:09:03 +09:00
const svg = container.querySelector('svg');
expect(svg).toBeInTheDocument();
expect(svg?.childNodes.length).toBe(0);
});
});