bef6abdd2e
- TimeEntryForm.test.tsx: fills date/project/h:mm, clicks submit, asserts fetch called with correct JSON body (mocked fetch) - WeeklyView.test.tsx: renders with stubbed WeeklyReport fixture, asserts each day's h:mm and the week total display correctly - App.test.tsx: smoke test — renders layout with TopBar and main content Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
82 lines
2.3 KiB
TypeScript
82 lines
2.3 KiB
TypeScript
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
import { MemoryRouter } from "react-router-dom";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { TimeEntryForm } from "../src/components/TimeEntryForm";
|
|
|
|
// stub api module
|
|
vi.mock("../src/lib/api", () => ({
|
|
api: {
|
|
projects: {
|
|
list: () =>
|
|
Promise.resolve([
|
|
{
|
|
id: 1,
|
|
name: "Test Project",
|
|
timeBudgetMinutes: 6000,
|
|
costBudgetCents: 100000,
|
|
hourlyRateCents: 10000,
|
|
minutesLogged: 0,
|
|
costLoggedCents: 0,
|
|
timeExceeded: false,
|
|
costExceeded: false,
|
|
},
|
|
]),
|
|
},
|
|
timeEntries: {
|
|
create: vi.fn().mockResolvedValue({
|
|
id: 42,
|
|
userId: 1,
|
|
userName: "Alice",
|
|
projectId: 1,
|
|
projectName: "Test Project",
|
|
entryDate: "2026-05-16",
|
|
durationMinutes: 90,
|
|
}),
|
|
},
|
|
},
|
|
}));
|
|
|
|
function renderForm(onSaved = vi.fn()) {
|
|
return render(
|
|
<MemoryRouter>
|
|
<TimeEntryForm userId={1} onSaved={onSaved} />
|
|
</MemoryRouter>,
|
|
);
|
|
}
|
|
|
|
describe("TimeEntryForm", () => {
|
|
it("submits with correct durationMinutes", async () => {
|
|
const onSaved = vi.fn();
|
|
renderForm(onSaved);
|
|
|
|
// wait for project list to load
|
|
await waitFor(() =>
|
|
expect(screen.getByRole("combobox")).toBeInTheDocument(),
|
|
);
|
|
|
|
const durationInput = screen.getByPlaceholderText(/e\.g\. 2:30/i);
|
|
fireEvent.change(durationInput, { target: { value: "1:30" } });
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: /log/i }));
|
|
|
|
const { api } = await import("../src/lib/api");
|
|
await waitFor(() => {
|
|
expect(api.timeEntries.create).toHaveBeenCalledWith(
|
|
expect.objectContaining({ durationMinutes: 90 }),
|
|
);
|
|
});
|
|
await waitFor(() => expect(onSaved).toHaveBeenCalled());
|
|
});
|
|
|
|
it("shows error for invalid duration format", async () => {
|
|
renderForm();
|
|
await waitFor(() => screen.getByRole("combobox"));
|
|
|
|
const durationInput = screen.getByPlaceholderText(/e\.g\. 2:30/i);
|
|
fireEvent.change(durationInput, { target: { value: "abc" } });
|
|
fireEvent.click(screen.getByRole("button", { name: /log/i }));
|
|
|
|
expect(await screen.findByText(/h:mm format/i)).toBeInTheDocument();
|
|
});
|
|
});
|