Add period date setup modal for new users
All checks were successful
Deploy / deploy (push) Successful in 2m27s
All checks were successful
Deploy / deploy (push) Successful in 2m27s
Users without a lastPeriodDate can now set it via a modal opened from the onboarding banner. The dashboard now fetches user data independently so the banner shows even when /api/today fails due to missing period date. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -500,11 +500,16 @@ describe("Dashboard", () => {
|
||||
|
||||
describe("error handling", () => {
|
||||
it("shows error message when /api/today fails", async () => {
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 500,
|
||||
json: () => Promise.resolve({ error: "Internal server error" }),
|
||||
});
|
||||
mockFetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 500,
|
||||
json: () => Promise.resolve({ error: "Internal server error" }),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () => Promise.resolve(mockUserResponse),
|
||||
});
|
||||
|
||||
render(<Dashboard />);
|
||||
|
||||
@@ -515,20 +520,31 @@ describe("Dashboard", () => {
|
||||
});
|
||||
|
||||
it("shows setup message when user has no lastPeriodDate", async () => {
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 400,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
error:
|
||||
"User has no lastPeriodDate set. Please log your period start date first.",
|
||||
}),
|
||||
});
|
||||
mockFetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 400,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
error:
|
||||
"User has no lastPeriodDate set. Please log your period start date first.",
|
||||
}),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
...mockUserResponse,
|
||||
lastPeriodDate: null,
|
||||
}),
|
||||
});
|
||||
|
||||
render(<Dashboard />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("alert")).toBeInTheDocument();
|
||||
// Multiple alerts may be present (error alert + onboarding banner)
|
||||
const alerts = screen.getAllByRole("alert");
|
||||
expect(alerts.length).toBeGreaterThan(0);
|
||||
// Check for the specific help text about getting started
|
||||
expect(
|
||||
screen.getByText(/please log your period start date to get started/i),
|
||||
@@ -723,4 +739,295 @@ describe("Dashboard", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("period date modal flow", () => {
|
||||
it("shows onboarding banner when todayData fails but userData shows no lastPeriodDate", async () => {
|
||||
mockFetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 400,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
error:
|
||||
"User has no lastPeriodDate set. Please log your period start date first.",
|
||||
}),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
...mockUserResponse,
|
||||
lastPeriodDate: null,
|
||||
}),
|
||||
});
|
||||
|
||||
render(<Dashboard />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(/Set your last period date for accurate tracking/i),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("button", { name: /set date/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("opens period date modal when clicking Set date button", async () => {
|
||||
mockFetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 400,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
error:
|
||||
"User has no lastPeriodDate set. Please log your period start date first.",
|
||||
}),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
...mockUserResponse,
|
||||
lastPeriodDate: null,
|
||||
}),
|
||||
});
|
||||
|
||||
render(<Dashboard />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole("button", { name: /set date/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /set date/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole("dialog", { name: /set period date/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("calls POST /api/cycle/period when submitting date in modal", async () => {
|
||||
mockFetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 400,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
error:
|
||||
"User has no lastPeriodDate set. Please log your period start date first.",
|
||||
}),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
...mockUserResponse,
|
||||
lastPeriodDate: null,
|
||||
}),
|
||||
});
|
||||
|
||||
render(<Dashboard />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole("button", { name: /set date/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /set date/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole("dialog", { name: /set period date/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Set up mock for the period POST and subsequent refetch
|
||||
mockFetch.mockClear();
|
||||
mockFetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
message: "Period start date logged successfully",
|
||||
lastPeriodDate: "2024-01-15",
|
||||
cycleDay: 1,
|
||||
phase: "MENSTRUAL",
|
||||
}),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () => Promise.resolve(mockTodayResponse),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
...mockUserResponse,
|
||||
lastPeriodDate: "2024-01-15",
|
||||
}),
|
||||
});
|
||||
|
||||
const dateInput = screen.getByLabelText(
|
||||
/when did your last period start/i,
|
||||
);
|
||||
fireEvent.change(dateInput, { target: { value: "2024-01-15" } });
|
||||
fireEvent.click(screen.getByRole("button", { name: /save/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockFetch).toHaveBeenCalledWith("/api/cycle/period", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ startDate: "2024-01-15" }),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("closes modal and refetches data after successful submission", async () => {
|
||||
mockFetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 400,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
error:
|
||||
"User has no lastPeriodDate set. Please log your period start date first.",
|
||||
}),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
...mockUserResponse,
|
||||
lastPeriodDate: null,
|
||||
}),
|
||||
});
|
||||
|
||||
render(<Dashboard />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole("button", { name: /set date/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /set date/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole("dialog", { name: /set period date/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Set up mock for the period POST and successful refetch
|
||||
mockFetch.mockClear();
|
||||
mockFetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
message: "Period start date logged successfully",
|
||||
lastPeriodDate: "2024-01-15",
|
||||
cycleDay: 1,
|
||||
phase: "MENSTRUAL",
|
||||
}),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () => Promise.resolve(mockTodayResponse),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
...mockUserResponse,
|
||||
lastPeriodDate: "2024-01-15",
|
||||
}),
|
||||
});
|
||||
|
||||
const dateInput = screen.getByLabelText(
|
||||
/when did your last period start/i,
|
||||
);
|
||||
fireEvent.change(dateInput, { target: { value: "2024-01-15" } });
|
||||
fireEvent.click(screen.getByRole("button", { name: /save/i }));
|
||||
|
||||
// Modal should close and dashboard should show normal content
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByRole("dialog", { name: /set period date/i }),
|
||||
).not.toBeInTheDocument();
|
||||
// Dashboard should now show the decision card
|
||||
expect(screen.getByText("TRAIN")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("shows error in modal when API call fails", async () => {
|
||||
mockFetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 400,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
error:
|
||||
"User has no lastPeriodDate set. Please log your period start date first.",
|
||||
}),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
...mockUserResponse,
|
||||
lastPeriodDate: null,
|
||||
}),
|
||||
});
|
||||
|
||||
render(<Dashboard />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole("button", { name: /set date/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /set date/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole("dialog", { name: /set period date/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Set up mock for failed API call
|
||||
mockFetch.mockClear();
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 500,
|
||||
json: () => Promise.resolve({ error: "Failed to update period date" }),
|
||||
});
|
||||
|
||||
const dateInput = screen.getByLabelText(
|
||||
/when did your last period start/i,
|
||||
);
|
||||
fireEvent.change(dateInput, { target: { value: "2024-01-15" } });
|
||||
fireEvent.click(screen.getByRole("button", { name: /save/i }));
|
||||
|
||||
// Error should appear in modal (there may be multiple alerts - dashboard error + modal error)
|
||||
await waitFor(() => {
|
||||
const alerts = screen.getAllByRole("alert");
|
||||
const modalError = alerts.find((alert) =>
|
||||
alert.textContent?.includes("Failed to update period date"),
|
||||
);
|
||||
expect(modalError).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Modal should still be open
|
||||
expect(
|
||||
screen.getByRole("dialog", { name: /set period date/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user