A try of overhauling the frontend tests, bailed after 24h+
This commit is contained in:
@@ -1,46 +1,108 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import TimeAdjuster from '../../src/components/tracker/TimeAdjuster';
|
||||
import { toLocalDatetimeInputValue } from '../../src/utils/time';
|
||||
|
||||
describe('TimeAdjuster', () => {
|
||||
it('renders with default value', () => {
|
||||
const { container } = render(<TimeAdjuster onTimeChange={() => {}} />);
|
||||
const input = container.querySelector('input');
|
||||
expect(input).toBeInTheDocument();
|
||||
const MOCK_NOW = new Date('2024-06-10T12:00:00Z');
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(MOCK_NOW);
|
||||
});
|
||||
|
||||
it('calls onTimeChange when value changes', () => {
|
||||
const handleChange = vi.fn();
|
||||
const { container } = render(<TimeAdjuster onTimeChange={handleChange} />);
|
||||
const input = container.querySelector('input');
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
vi.clearAllMocks();
|
||||
|
||||
fireEvent.change(input!, { target: { value: '2024-01-01T12:00' } });
|
||||
expect(handleChange).toHaveBeenCalledWith(new Date('2024-01-01T12:00').toISOString());
|
||||
});
|
||||
|
||||
it('resets to now when reset button is clicked', async () => {
|
||||
const handleChange = vi.fn();
|
||||
render(<TimeAdjuster onTimeChange={handleChange} />);
|
||||
it('initializes with the current time when no defaultValue is provided', () => {
|
||||
render(<TimeAdjuster onTimeChange={() => {}} />);
|
||||
const input = screen.getByLabelText(/Time/i) as HTMLInputElement;
|
||||
|
||||
// The value should be the local format of MOCK_NOW
|
||||
expect(input.value).toBe(toLocalDatetimeInputValue(MOCK_NOW));
|
||||
});
|
||||
|
||||
it('initializes with the provided defaultValue converted to local format', () => {
|
||||
const defaultValue = '2024-01-01T10:00:00Z';
|
||||
const expectedLocal = toLocalDatetimeInputValue(new Date(defaultValue));
|
||||
|
||||
render(<TimeAdjuster onTimeChange={() => {}} defaultValue={defaultValue} />);
|
||||
const input = screen.getByLabelText(/Time/i) as HTMLInputElement;
|
||||
|
||||
expect(input.value).toBe(expectedLocal);
|
||||
});
|
||||
|
||||
it('calls onTimeChange with UTC time when manual value is changed via input', async () => {
|
||||
const onTimeChange = vi.fn();
|
||||
render(<TimeAdjuster onTimeChange={onTimeChange} />);
|
||||
const input = screen.getByLabelText(/Time/i) as HTMLInputElement;
|
||||
|
||||
// Change to a specific local time
|
||||
const newLocalTime = '2024-06-11T15:45';
|
||||
fireEvent.change(input, { target: { value: newLocalTime } });
|
||||
|
||||
// onTimeChange should be called with the UTC equivalent
|
||||
// We need to be careful about how Date(newLocalTime) interprets it.
|
||||
// Without timezone, it's usually treated as local time.
|
||||
const expectedUTC = new Date(newLocalTime).toISOString();
|
||||
|
||||
// Since onTimeChange is in a useEffect, we might need to wait or use vi.runAllTimers()
|
||||
// But standard React state updates + useEffect should be synchronous enough for fireEvent in Vitest
|
||||
expect(onTimeChange).toHaveBeenCalledWith(expectedUTC);
|
||||
});
|
||||
|
||||
it('resets to current time when "Reset to now" button is clicked', () => {
|
||||
const onTimeChange = vi.fn();
|
||||
render(<TimeAdjuster onTimeChange={onTimeChange} />);
|
||||
const input = screen.getByLabelText(/Time/i) as HTMLInputElement;
|
||||
|
||||
// 1. Change value to something else
|
||||
const differentTime = '2023-01-01T00:00';
|
||||
fireEvent.change(input, { target: { value: differentTime } });
|
||||
expect(input.value).toBe(differentTime);
|
||||
|
||||
// 2. Click reset
|
||||
const resetButton = screen.getByText(/Reset to now/i);
|
||||
|
||||
fireEvent.click(resetButton);
|
||||
expect(handleChange).toHaveBeenCalled();
|
||||
|
||||
// 3. Verify input is back to MOCK_NOW's local format
|
||||
expect(input.value).toBe(toLocalDatetimeInputValue(MOCK_NOW));
|
||||
|
||||
// 4. Verify onTimeChange was called with MOCK_NOW's UTC format
|
||||
expect(onTimeChange).toHaveBeenLastCalledWith(MOCK_NOW.toISOString());
|
||||
});
|
||||
|
||||
it('handles invalid date input gracefully (returns empty string)', async () => {
|
||||
// Since we changed the implementation, it should no longer throw.
|
||||
// We can't easily test the useEffect error anymore because it doesn't throw,
|
||||
// but we can check if onTimeChange is called with "" for invalid input.
|
||||
const handleChange = vi.fn();
|
||||
const { container } = render(<TimeAdjuster onTimeChange={handleChange} />);
|
||||
const input = container.querySelector('input');
|
||||
it('handles invalid date input gracefully by calling onTimeChange with an empty string', async () => {
|
||||
const onTimeChange = vi.fn();
|
||||
render(<TimeAdjuster onTimeChange={onTimeChange} />);
|
||||
const input = screen.getByLabelText(/Time/i) as HTMLInputElement;
|
||||
|
||||
fireEvent.change(input!, { target: { value: '2026-06-0' } });
|
||||
// We might need to wait for the effect
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
expect(handleChange).toHaveBeenCalledWith("");
|
||||
// Simulate invalid input (though datetime-local usually prevents this via UI,
|
||||
// we test the logic in toUTCFromLocalInput)
|
||||
fireEvent.change(input, { target: { value: 'not-a-date' } });
|
||||
|
||||
// Wait for useEffect
|
||||
await vi.waitFor(() => {
|
||||
expect(onTimeChange).toHaveBeenCalledWith("");
|
||||
});
|
||||
});
|
||||
|
||||
it('does not call onTimeChange if the input value is same as current (initialization check)', () => {
|
||||
// The component has an effect on [value, onTimeChange].
|
||||
// On mount, it will call onTimeChange because value is set in useState.
|
||||
const onTimeChange = vi.fn();
|
||||
render(<TimeAdjuster onTimeChange={onTimeChange} />);
|
||||
|
||||
// Should be called once on mount due to useEffect [value]
|
||||
expect(onTimeChange).toHaveBeenCalledTimes(1);
|
||||
expect(onTimeChange).toHaveBeenCalledWith(MOCK_NOW.toISOString());
|
||||
});
|
||||
|
||||
it('does not call onTimeChange if it is not provided (edge case for coverage)', () => {
|
||||
// @ts-ignore - testing the branch where onTimeChange might be undefined
|
||||
render(<TimeAdjuster onTimeChange={undefined} />);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user