feat: add digital clock input and time converter integration

This commit is contained in:
Loic Coenen
2026-06-24 15:57:07 +02:00
parent 46168a70a8
commit 07b48a6490
5 changed files with 179 additions and 17 deletions

View File

@@ -8,7 +8,7 @@
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",
"test": "vitest run src/__tests__/BerlinClock.test.tsx",
"test": "vitest run src/__tests__/*",
"format": "eslint . --fix && prettier --write .",
"e2e": "cypress run",
"ci": "npm run format && npm run test && npm run e2e"

View File

@@ -1,8 +1,81 @@
import { render } from '@testing-library/react';
import { render, screen, fireEvent } from '@testing-library/react';
import { DigitalClock } from '../containers/DigitalClock';
import * as utils from '../utils';
vi.mock('../utils');
const mockedDateToDigital = vi.mocked(utils.dateToDigital);
const mockedDigitalToDate = vi.mocked(utils.digitalToDate);
describe('DigitalClock', () => {
it('renders when enabled', () => {
render(<DigitalClock enabled={true} />);
const onTimeChangeMock = vi.fn();
beforeEach(() => {
vi.clearAllMocks();
mockedDateToDigital.mockReturnValue('14:30:15');
mockedDigitalToDate.mockImplementation((str: string) => {
const parts = str.split(':');
if (parts.length !== 3) return new Date(NaN);
const [h, m, s] = parts.map(Number);
if (isNaN(h) || isNaN(m) || isNaN(s)) return new Date(NaN);
const d = new Date(2025, 0, 1, 14, 30, 15);
d.setHours(h, m, s, 0);
return d;
});
});
it('renders nothing when disabled', () => {
const { container } = render(
<DigitalClock
enabled={false}
currentTime={new Date(2025,0,1,14,30,15)}
onTimeChange={onTimeChangeMock}
/>,
);
expect(container.innerHTML).toBe('');
});
it('renders the digital time and input when enabled', () => {
render(
<DigitalClock
enabled={true}
currentTime={new Date(2025,0,1,14,30,15)}
onTimeChange={onTimeChangeMock}
/>,
);
expect(screen.getByTestId('digital-clock')).toHaveTextContent('14:30:15');
expect(screen.getByTestId('time-input')).toBeInTheDocument();
});
it('calls onTimeChange when a valid time is entered', () => {
render(
<DigitalClock
enabled={true}
currentTime={new Date(2025,0,1,14,30,15)}
onTimeChange={onTimeChangeMock}
/>,
);
const input = screen.getByTestId('time-input');
fireEvent.change(input, { target: { value: '12:00:00' } });
expect(onTimeChangeMock).toHaveBeenCalledTimes(1);
const expectedDate = new Date(2025, 0, 1, 12, 0, 0, 0);
expect(onTimeChangeMock).toHaveBeenCalledWith(expectedDate);
expect(input).toHaveValue('12:00:00');
});
it('does not call onTimeChange when an invalid time is entered', () => {
render(
<DigitalClock
enabled={true}
currentTime={new Date(2025,0,1,14,30,15)}
onTimeChange={onTimeChangeMock}
/>,
);
const input = screen.getByTestId('time-input');
fireEvent.change(input, { target: { value: 'notatime' } });
expect(onTimeChangeMock).not.toHaveBeenCalled();
expect(input).toHaveValue('notatime');
});
});

View File

@@ -1,13 +1,56 @@
import { render, screen, fireEvent } from '@testing-library/react';
import { TimeConverter } from '../containers/TimeConverter';
import { useCurrentTime } from '../hooks/useCurrentTime';
vi.mock('../hooks/useCurrentTime');
vi.mock('../containers/BerlinClock', () => ({
BerlinClock: ({ enabled }: { enabled: boolean }) => (
<div data-testid="berlin-clock">{enabled ? 'enabled' : 'disabled'}</div>
),
}));
vi.mock('../containers/DigitalClock', () => ({
DigitalClock: ({ enabled, currentTime, onTimeChange }: { enabled: boolean; currentTime: Date; onTimeChange: (d: Date) => void }) => (
<div data-testid="digital-clock-wrapper">
<span data-testid="digital-clock-time">{currentTime.toISOString()}</span>
<button data-testid="set-time" onClick={() => onTimeChange(new Date(2025,0,1,12,0,0))}>
Set time
</button>
{!enabled && <span data-testid="disabled-indicator">disabled</span>}
</div>
),
}));
const mockedUseCurrentTime = vi.mocked(useCurrentTime);
describe('TimeConverter', () => {
it('renders the toggle checkbox', () => {
const setCurrentTimeMock = vi.fn();
const baseDate = new Date(2025, 0, 1, 14, 30, 15);
beforeEach(() => {
vi.clearAllMocks();
mockedUseCurrentTime.mockReturnValue({
currentTime: baseDate,
setCurrentTime: setCurrentTimeMock,
});
});
it('renders the checkbox, BerlinClock, and DigitalClock', () => {
render(<TimeConverter />);
expect(screen.getByRole('checkbox')).toBeInTheDocument();
expect(screen.getByTestId('use-system-time')).toBeInTheDocument();
expect(screen.getByTestId('berlin-clock')).toHaveTextContent('enabled');
expect(screen.getByTestId('digital-clock-wrapper')).toBeInTheDocument();
});
it('disables clocks when checkbox is checked', () => {
it('disables both clocks when checkbox is checked', () => {
render(<TimeConverter />);
// TODO
fireEvent.click(screen.getByTestId('use-system-time'));
expect(screen.getByTestId('berlin-clock')).toHaveTextContent('disabled');
expect(screen.getByTestId('disabled-indicator')).toBeInTheDocument();
});
it('calls setCurrentTime when DigitalClock triggers onTimeChange', () => {
render(<TimeConverter />);
fireEvent.click(screen.getByTestId('set-time'));
expect(setCurrentTimeMock).toHaveBeenCalledWith(new Date(2025, 0, 1, 12, 0, 0));
});
});

View File

@@ -1,13 +1,36 @@
import { useState } from 'react';
import { useCurrentTime } from '../hooks/useCurrentTime';
import { dateToDigital } from '../utils';
import { dateToDigital, digitalToDate } from '../utils';
interface DigitalClockProps {
enabled: boolean;
currentTime: Date;
onTimeChange: (time: Date) => void;
}
export function DigitalClock({ enabled }: DigitalClockProps) {
const { currentTime } = useCurrentTime();
const digital = dateToDigital(currentTime);
return null;
export function DigitalClock({ enabled, currentTime, onTimeChange }: DigitalClockProps) {
const [inputValue, setInputValue] = useState(dateToDigital(currentTime));
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setInputValue(value);
const date = digitalToDate(value);
if (!isNaN(date.getTime())) {
onTimeChange(date);
}
};
if (!enabled) return null;
return (
<div>
<span data-testid="digital-clock">{dateToDigital(currentTime)}</span>
<input
data-testid="time-input"
type="text"
value={inputValue}
onChange={handleChange}
placeholder="HH:MM:SS"
/>
</div>
);
}

View File

@@ -4,7 +4,30 @@ import { DigitalClock } from './DigitalClock';
import { useCurrentTime } from '../hooks/useCurrentTime';
export function TimeConverter() {
const { currentTime, setCurrentTime } = useCurrentTime();
const [useSystemTime, setUseSystemTime] = useState(false);
// When useSystemTime is true, both BerlinClock and DigitalClock are disabled.
return null;
const handleTimeChange = (newTime: Date) => {
setCurrentTime(newTime);
};
return (
<div>
<label>
<input
type="checkbox"
data-testid="use-system-time"
checked={useSystemTime}
onChange={(e) => setUseSystemTime(e.target.checked)}
/>
Use system time
</label>
<BerlinClock enabled={!useSystemTime} />
<DigitalClock
enabled={!useSystemTime}
currentTime={currentTime}
onTimeChange={handleTimeChange}
/>
</div>
);
}