feat: add testing library dependencies and implement component tests

This commit is contained in:
Loic Coenen
2026-06-23 16:42:49 +02:00
committed by Loic Coenen (aider)
parent 826069e8dd
commit 46168a70a8
10 changed files with 859 additions and 14 deletions

View File

@@ -1,11 +1,74 @@
import { render } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import { BerlinClock } from '../containers/BerlinClock';
import { LampState } from '../types';
import { useCurrentTime } from '../hooks/useCurrentTime';
import * as utils from '../utils';
// Mock the hook and the converter
vi.mock('../hooks/useCurrentTime');
vi.mock('../utils');
const mockedUseCurrentTime = vi.mocked(useCurrentTime);
const mockedDateToBerlin = vi.mocked(utils.dateToBerlin);
describe('BerlinClock', () => {
it('renders when enabled', () => {
render(<BerlinClock enabled={true} />);
beforeEach(() => {
// Set a fixed date for the hook
mockedUseCurrentTime.mockReturnValue({
currentTime: new Date(2025, 0, 1, 14, 30, 0),
setCurrentTime: vi.fn(),
});
// Provide a controlled BerlinClock output for that time
mockedDateToBerlin.mockReturnValue({
secondsRow: [LampState.Y],
fiveHours: [LampState.R, LampState.R, LampState.R, LampState.O],
oneHour: [LampState.R, LampState.R, LampState.R, LampState.R],
fiveMinutes: [
LampState.Y, LampState.Y, LampState.R,
LampState.Y, LampState.Y, LampState.R,
LampState.O, LampState.O, LampState.O,
LampState.O, LampState.O,
],
oneMinute: [LampState.O, LampState.O, LampState.O, LampState.O],
});
});
it('does not render when disabled', () => {
render(<BerlinClock enabled={false} />);
it('renders nothing when disabled', () => {
const { container } = render(<BerlinClock enabled={false} />);
expect(container.innerHTML).toBe('');
});
it('renders all five rows when enabled', () => {
render(<BerlinClock enabled={true} />);
expect(screen.getByTestId('seconds-lamp')).toBeInTheDocument();
expect(screen.getByTestId('five-hours-row')).toBeInTheDocument();
expect(screen.getByTestId('single-hours-row')).toBeInTheDocument();
expect(screen.getByTestId('five-minutes-row')).toBeInTheDocument();
expect(screen.getByTestId('single-minutes-row')).toBeInTheDocument();
});
it('renders the seconds lamp with correct aria-label', () => {
render(<BerlinClock enabled={true} />);
const secLamp = screen.getByTestId('seconds-lamp').querySelector('[data-testid="lamp-0"]');
expect(secLamp).toHaveAttribute('aria-label', 'Y');
});
it('renders the correct number of lamps in five-hours-row', () => {
render(<BerlinClock enabled={true} />);
const container = screen.getByTestId('five-hours-row').querySelector('[data-testid="lamp-line"]');
expect(container?.children).toHaveLength(4);
});
it('renders correct aria-labels in five-hours-row', () => {
render(<BerlinClock enabled={true} />);
const container = screen.getByTestId('five-hours-row').querySelector('[data-testid="lamp-line"]');
const spans = container?.querySelectorAll('span');
expect(spans).toHaveLength(4);
const expected = ['R', 'R', 'R', 'O'];
spans?.forEach((span, i) => {
expect(span).toHaveAttribute('aria-label', expected[i]);
});
});
});

View File

@@ -1,9 +1,22 @@
import { render, screen } from '@testing-library/react';
import { render } from '@testing-library/react';
import { Lamp } from '../components/Lamp';
import { LampState } from '../types';
describe('Lamp', () => {
it('renders without crashing', () => {
render(<Lamp state={LampState.Y} />);
// If it doesn't throw, we're good.
});
it.each([
['Y' as LampState, 'Y'],
['R' as LampState, 'R'],
['O' as LampState, 'O'],
['N' as LampState, 'N'],
])('displays aria-label "%s" for state %s', (state, expectedLabel) => {
const { container } = render(<Lamp state={state} />);
// The root element should have the correct aria-label
const lampElement = container.firstChild as HTMLElement;
expect(lampElement).toHaveAttribute('aria-label', expectedLabel);
});
});

View File

@@ -1,11 +1,33 @@
import { render } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import { LampLine } from '../components/LampLine';
import { LampState } from '../types';
describe('LampLine', () => {
it('renders correct number of lamps', () => {
it('renders without crashing', () => {
render(<LampLine states={[]} />);
});
it('renders the correct number of lamps', () => {
const states = [LampState.Y, LampState.R, LampState.O];
render(<LampLine states={states} />);
// TODO: assert number of child lamps
const container = screen.getByTestId('lamp-line');
expect(container.children).toHaveLength(states.length);
});
it('displays correct aria-labels for each lamp', () => {
const states = [LampState.Y, LampState.R, LampState.O, LampState.N];
render(<LampLine states={states} />);
const container = screen.getByTestId('lamp-line');
const lampSpans = container.querySelectorAll('span');
expect(lampSpans).toHaveLength(states.length);
lampSpans.forEach((span, index) => {
expect(span).toHaveAttribute('aria-label', states[index]);
});
});
it('renders no lamps when given an empty array', () => {
render(<LampLine states={[]} />);
const container = screen.getByTestId('lamp-line');
expect(container.children).toHaveLength(0);
});
});