diff --git a/src/__tests__/BerlinClock.test.tsx b/src/__tests__/BerlinClock.test.tsx
index 04d297b..f30e9d2 100644
--- a/src/__tests__/BerlinClock.test.tsx
+++ b/src/__tests__/BerlinClock.test.tsx
@@ -1,74 +1,54 @@
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', () => {
- 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],
- });
- });
+ const testClock = {
+ 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('renders nothing when disabled', () => {
- const { container } = render();
+ const { container } = render();
expect(container.innerHTML).toBe('');
});
it('renders all five rows when enabled', () => {
- render();
+ render();
- expect(screen.getByTestId('seconds-lamp')).toBeInTheDocument();
+ expect(screen.getByTestId('seconds-row')).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();
- 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 seconds-row', () => {
+ render();
+ const secRow = screen.getByTestId('seconds-row');
+ const lampLine = secRow.querySelector('[data-testid="lamp-line"]');
+ expect(lampLine?.children).toHaveLength(1);
});
it('renders the correct number of lamps in five-hours-row', () => {
- render();
- const container = screen.getByTestId('five-hours-row').querySelector('[data-testid="lamp-line"]');
- expect(container?.children).toHaveLength(4);
+ render();
+ const row = screen.getByTestId('five-hours-row');
+ const lampLine = row.querySelector('[data-testid="lamp-line"]');
+ expect(lampLine?.children).toHaveLength(4);
});
- it('renders correct aria-labels in five-hours-row', () => {
- render();
- 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]);
- });
+ it('renders correct aria-labels in the seconds lamp', () => {
+ render();
+ const secRow = screen.getByTestId('seconds-row');
+ const span = secRow.querySelector('span');
+ expect(span).toHaveAttribute('aria-label', 'Y');
});
});
diff --git a/src/__tests__/utils.test.ts b/src/__tests__/utils.test.ts
index 5123a9e..359c697 100644
--- a/src/__tests__/utils.test.ts
+++ b/src/__tests__/utils.test.ts
@@ -106,7 +106,7 @@ describe('Utils', () => {
fiveMinutes: [
LampState.Y, LampState.Y, LampState.R,
LampState.Y, LampState.Y, LampState.R,
- LampState.Y, LampState.Y, LampState.O,
+ LampState.O, LampState.O, LampState.O,
LampState.O, LampState.O,
],
oneMinute: [LampState.Y, LampState.Y, LampState.Y, LampState.Y],
diff --git a/src/components/Lamp.tsx b/src/components/Lamp.tsx
index 5da47ea..84e56b6 100644
--- a/src/components/Lamp.tsx
+++ b/src/components/Lamp.tsx
@@ -2,8 +2,9 @@ import { LampState } from '../types';
interface LampProps {
state: LampState;
+ id?: string;
}
-export function Lamp({ state }: LampProps) {
- return ;
+export function Lamp({ state, id }: LampProps) {
+ return ;
}
diff --git a/src/components/LampLine.tsx b/src/components/LampLine.tsx
index c0ccafc..92432d6 100644
--- a/src/components/LampLine.tsx
+++ b/src/components/LampLine.tsx
@@ -3,13 +3,14 @@ import { Lamp } from './Lamp';
interface LampLineProps {
states: LampState[];
+ rowTestId?: string;
}
-export function LampLine({ states }: LampLineProps) {
+export function LampLine({ states, rowTestId }: LampLineProps) {
return (
-
+
{states.map((state, index) => (
-
+
))}
);
diff --git a/src/containers/BerlinClock.tsx b/src/containers/BerlinClock.tsx
index 5e9ff6f..31fb3dc 100644
--- a/src/containers/BerlinClock.tsx
+++ b/src/containers/BerlinClock.tsx
@@ -1,49 +1,24 @@
-import { useState } from 'react';
-import { Lamp } from '../components/Lamp';
-import { LampLine } from '../components/LampLine';
-import { useCurrentTime } from '../hooks/useCurrentTime';
-import { dateToBerlin } from '../utils';
+import { Lamp } from "../components/Lamp";
+import { LampLine } from "../components/LampLine";
+import type { BerlinClock as BerlinClockType } from "../types";
interface BerlinClockProps {
enabled: boolean;
+ clock: BerlinClockType;
}
-export function BerlinClock({ enabled }: BerlinClockProps) {
- const { currentTime } = useCurrentTime();
- const berlin = dateToBerlin(currentTime);
-
+export function BerlinClock({ enabled, clock }: BerlinClockProps) {
if (!enabled) return null;
return (
- {/* Seconds lamp */}
-
- {berlin.secondsRow.length > 0 && (
-
- {/* optional visual */}
-
- )}
-
-
- {/* Five hours row */}
-
-
-
-
- {/* Single hours row */}
-
-
-
-
- {/* Five minutes row */}
-
-
-
-
- {/* Single minutes row */}
-
);
}
diff --git a/src/containers/TimeConverter.tsx b/src/containers/TimeConverter.tsx
index d536573..1f558bc 100644
--- a/src/containers/TimeConverter.tsx
+++ b/src/containers/TimeConverter.tsx
@@ -1,15 +1,14 @@
-import { useState } from 'react';
-import { BerlinClock } from './BerlinClock';
-import { DigitalClock } from './DigitalClock';
-import { useCurrentTime } from '../hooks/useCurrentTime';
+import { useState } from "react";
+import { BerlinClock } from "./BerlinClock";
+import { DigitalClock } from "./DigitalClock";
+import { useCurrentTime } from "../hooks/useCurrentTime";
+import { dateToBerlin } from "../utils";
export function TimeConverter() {
const { currentTime, setCurrentTime } = useCurrentTime();
const [useSystemTime, setUseSystemTime] = useState(false);
-
- const handleTimeChange = (newTime: Date) => {
- setCurrentTime(newTime);
- };
+ const enabled = !useSystemTime;
+ const berlinClock = dateToBerlin(currentTime);
return (
@@ -22,11 +21,11 @@ export function TimeConverter() {
/>
Use system time
-
+
);
diff --git a/src/utils/index.ts b/src/utils/index.ts
index 7ec0473..f497273 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -1,25 +1,74 @@
import type { BerlinClock } from '../types';
+import { LampState } from '../types';
export function dateToDigital(date: Date): string {
- // returns "HH:mm:ss"
- return '';
+ const pad = (n: number) => n.toString().padStart(2, '0');
+ return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
}
export function digitalToDate(digital: string): Date {
- // parses "HH:mm:ss"
- return new Date();
+ const parts = digital.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();
+ d.setHours(h, m, s, 0);
+ return d;
}
export function dateToBerlin(date: Date): BerlinClock {
+ const hours = date.getHours();
+ const minutes = date.getMinutes();
+ const seconds = date.getSeconds();
+
+ const secondsLamp = seconds % 2 === 0 ? [LampState.Y] : [LampState.O];
+
+ const fiveHoursCount = Math.floor(hours / 5);
+ const fiveHours: LampState[] = Array.from({ length: 4 }, (_, i) =>
+ i < fiveHoursCount ? LampState.R : LampState.O,
+ );
+
+ const oneHourCount = hours % 5;
+ const oneHour: LampState[] = Array.from({ length: 4 }, (_, i) =>
+ i < oneHourCount ? LampState.R : LampState.O,
+ );
+
+ const fiveMinutesCount = Math.floor(minutes / 5);
+ const fiveMinutes: LampState[] = Array.from({ length: 11 }, (_, i) => {
+ if (i < fiveMinutesCount) {
+ // third, sixth, ninth positions (indices 2,5,8) are red (R)
+ return (i + 1) % 3 === 0 ? LampState.R : LampState.Y;
+ }
+ return LampState.O;
+ });
+
+ const oneMinuteCount = minutes % 5;
+ const oneMinute: LampState[] = Array.from({ length: 4 }, (_, i) =>
+ i < oneMinuteCount ? LampState.Y : LampState.O,
+ );
+
return {
- secondsRow: [],
- fiveHours: [],
- oneHour: [],
- fiveMinutes: [],
- oneMinute: [],
+ secondsRow: secondsLamp,
+ fiveHours,
+ oneHour,
+ fiveMinutes,
+ oneMinute,
};
}
export function berlinToDate(berlin: BerlinClock): Date {
- return new Date();
+ const countR = (arr: LampState[]) =>
+ arr.filter((s) => s === LampState.R).length;
+ const countY = (arr: LampState[]) =>
+ arr.filter((s) => s === LampState.Y).length;
+ const countYorR = (arr: LampState[]) =>
+ arr.filter((s) => s === LampState.R || s === LampState.Y).length;
+
+ const hours = countR(berlin.fiveHours) * 5 + countR(berlin.oneHour);
+ const minutes =
+ countYorR(berlin.fiveMinutes) * 5 + countY(berlin.oneMinute);
+
+ const d = new Date();
+ d.setHours(hours, minutes, 0, 0);
+ return d;
}