A try of overhauling the frontend tests, bailed after 24h+

This commit is contained in:
2026-06-11 09:09:02 +02:00
parent a9e1e96622
commit bb58389675
33 changed files with 3257 additions and 275 deletions
+91 -29
View File
@@ -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} />);
});
});