import { render, screen, fireEvent, act } from '@testing-library/react'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import ClockPicker from '../../../../src/components/tracker/ClockPicker'; describe('ClockPicker', () => { const onChange = vi.fn(); beforeEach(() => { vi.clearAllMocks(); vi.useFakeTimers(); }); afterEach(() => { vi.useRealTimers(); }); it('renders initial hour and minute correctly', () => { render(); const hourButton = screen.getByRole('button', { name: /hour/i }); const minuteButton = screen.getByRole('button', { name: /minute/i }); expect(hourButton).toHaveClass('bg-white'); expect(minuteButton).toHaveClass('text-gray-500'); }); it('switches between hour and minute mode via buttons', () => { render(); const minuteButton = screen.getByRole('button', { name: /minute/i }); fireEvent.click(minuteButton); expect(minuteButton).toHaveClass('bg-white'); expect(screen.getByRole('button', { name: /hour/i })).toHaveClass('text-gray-500'); }); it('automatically switches to minute mode after selecting an hour', async () => { render(); const dial = screen.getByRole('button', { name: /hour/i }).parentElement?.nextElementSibling as HTMLElement; // Mock getBoundingClientRect to simulate a center point const rect = { left: 0, top: 0, width: 256, height: 256 }; vi.spyOn(dial, 'getBoundingClientRect').mockReturnValue(rect as DOMRect); // Click at the very top (should be 0:00) // Angle for 0 is 90 degrees in the component logic. // Center is (128, 128). Top point is (128, 0). // x = 0, y = -128. atan2(-128, 0) = -90 deg. + 90 = 0 deg. fireEvent.click(dial, { clientX: 128, clientY: 0 }); expect(onChange).toHaveBeenCalledWith(0, 30); // Fast-forward timers for the setTimeout in NumberDialPicker act(() => { vi.runAllTimers(); }); // Verify mode changed to minute expect(screen.getByRole('button', { name: /minute/i })).toHaveClass('bg-white'); act(() => { vi.runAllTimers(); }); // Verify mode changed to minute expect(screen.getByRole('button', { name: /minute/i })).toHaveClass('bg-white'); vi.runAllTimers(); // Verify mode changed to minute expect(screen.getByRole('button', { name: /minute/i })).toHaveClass('bg-white'); }); it('updates the correct value based on current mode', () => { const { rerender } = render(); const dial = screen.getByRole('button', { name: /hour/i }).parentElement?.nextElementSibling as HTMLElement; vi.spyOn(dial, 'getBoundingClientRect').mockReturnValue({ left: 0, top: 0, width: 256, height: 256 } as DOMRect); // 1. Test Hour Update (Mode is Hour) // Click at the right side (90 degrees in math). // Center is (128, 128). Right point is (256, 128). // x = 128, y = 0. atan2(0, 128) = 0 deg. + 90 = 90 deg. // Step for hours is 360/24 = 15. 90/15 = index 6. fireEvent.click(dial, { clientX: 256, clientY: 128 }); expect(onChange).toHaveBeenCalledWith(6, 30); vi.clearAllMocks(); // 2. Test Minute Update (Switch to minute mode) fireEvent.click(screen.getByRole('button', { name: /minute/i })); // Click at the bottom (180 degrees in math). // Center is (128, 128). Bottom point is (128, 256). // x = 0, y = 128. atan2(128, 0) = 90 deg. + 90 = 180 deg. // Step for minutes is 360/60 = 6. 180/6 = index 30. fireEvent.click(dial, { clientX: 128, clientY: 256 }); expect(onChange).toHaveBeenCalledWith(10, 30); // Wait, the call should be (hour, val) // Re-checking implementation: handleNumberChange calls handleTimeChange(hour, val) when mode is minute // Since initial hour was 10: expect(onChange).toHaveBeenLastCalledWith(10, 30); }); it('handles circular rotation correctly (angle < 0)', () => { render(); const dial = screen.getByRole('button', { name: /hour/i }).parentElement?.nextElementSibling as HTMLElement; vi.spyOn(dial, 'getBoundingClientRect').mockReturnValue({ left: 0, top: 0, width: 256, height: 256 } as DOMRect); // Click slightly "behind" the start (e.g., 350 degrees) // Center (128, 128). Point slightly above center-left. // x = -10, y = -128. atan2(-128, -10) is approx -94 deg. + 90 = -4 deg. // Code handles angle < 0 by adding 360 -> 356 deg. fireEvent.click(dial, { clientX: 118, clientY: 0 }); // Verify it didn't crash and called onChange with a valid index expect(onChange).toHaveBeenCalled(); const lastCall = vi.mocked(onChange).mock.calls[0][0]; expect(lastCall).toBeGreaterThanOrEqual(0); expect(lastCall).toBeLessThan(24); }); });