A try of overhauling the frontend tests, bailed after 24h+
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
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(<ClockPicker hour={10} minute={30} onChange={onChange} />);
|
||||
|
||||
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(<ClockPicker hour={10} minute={30} onChange={onChange} />);
|
||||
|
||||
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(<ClockPicker hour={10} minute={30} onChange={onChange} />);
|
||||
|
||||
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(<ClockPicker hour={10} minute={30} onChange={onChange} />);
|
||||
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(<ClockPicker hour={10} minute={30} onChange={onChange} />);
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import TimeAdjuster from '../../../../src/components/tracker/TimeAdjuster';
|
||||
|
||||
// Mock time utils to avoid timezone issues in tests
|
||||
vi.mock('../../../src/utils/time', () => ({
|
||||
toLocalDatetimeInputValue: vi.fn((d) => d.toISOString().slice(0, 16)),
|
||||
toUTCFromLocalInput: vi.fn((val) => val), // simple identity for testing
|
||||
}));
|
||||
|
||||
describe('TimeAdjuster', () => {
|
||||
const onChange = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders with default value if provided', () => {
|
||||
const defaultValue = '2024-01-01T12:00';
|
||||
render(<TimeAdjuster onTimeChange={onChange} defaultValue={defaultValue} />);
|
||||
|
||||
const input = screen.getByLabelText(/Time/i) as HTMLInputElement;
|
||||
expect(input.value).toBe(defaultValue);
|
||||
});
|
||||
|
||||
it('calls onTimeChange when input value changes', async () => {
|
||||
render(<TimeAdjuster onTimeChange={onChange} />);
|
||||
|
||||
const input = screen.getByLabelText(/Time/i) as HTMLInputElement;
|
||||
fireEvent.change(input, { target: { value: '2024-01-01T13:00' } });
|
||||
|
||||
expect(onChange).toHaveBeenLastCalledWith('2024-01-01T12:00:00.000Z');
|
||||
});
|
||||
|
||||
it('resets to now when reset button is clicked', () => {
|
||||
const mockNow = new Date('2024-01-01T10:00:00');
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(mockNow);
|
||||
|
||||
render(<TimeAdjuster onTimeChange={onChange} />);
|
||||
|
||||
const resetButton = screen.getByText(/Reset to now/i);
|
||||
fireEvent.click(resetButton);
|
||||
|
||||
const input = screen.getByLabelText(/Time/i) as HTMLInputElement;
|
||||
// The component calls toLocalDatetimeInputValue(new Date())
|
||||
// Our mock returns the iso string slice.
|
||||
expect(input.value).toBe('2024-01-01T10:00');
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user