Add toast notification system with sonner library
- Create Toaster component wrapping sonner at bottom-right position - Add showToast utility with success/error/info methods - Error toasts persist until dismissed, others auto-dismiss after 5s - Migrate error handling to toasts across all pages: - Dashboard (override toggle errors) - Settings (save/load success/error) - Garmin settings (connection success/error) - Calendar (load errors) - Period History (load/delete errors) - Add dark mode support for toast styling - Add Toaster provider to root layout - 27 new tests (23 toaster component + 4 integration) - Total: 977 unit tests passing P5.2 COMPLETE - All P0-P5 items now complete. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,16 @@ vi.mock("next/navigation", () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
// Mock showToast utility with vi.hoisted to avoid hoisting issues
|
||||
const mockShowToast = vi.hoisted(() => ({
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
info: vi.fn(),
|
||||
}));
|
||||
vi.mock("@/components/ui/toaster", () => ({
|
||||
showToast: mockShowToast,
|
||||
}));
|
||||
|
||||
// Mock fetch
|
||||
const mockFetch = vi.fn();
|
||||
global.fetch = mockFetch;
|
||||
@@ -30,6 +40,9 @@ describe("CalendarPage", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockShowToast.success.mockClear();
|
||||
mockShowToast.error.mockClear();
|
||||
mockShowToast.info.mockClear();
|
||||
mockFetch.mockResolvedValue({
|
||||
ok: true,
|
||||
json: () => Promise.resolve(mockUser),
|
||||
@@ -134,6 +147,21 @@ describe("CalendarPage", () => {
|
||||
expect(screen.getByRole("alert")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("shows error toast when fetching fails", async () => {
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
json: () => Promise.resolve({ error: "Network error" }),
|
||||
});
|
||||
|
||||
render(<CalendarPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockShowToast.error).toHaveBeenCalledWith(
|
||||
"Unable to fetch data. Retry?",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("month navigation", () => {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import { MonthView } from "@/components/calendar/month-view";
|
||||
import { showToast } from "@/components/ui/toaster";
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
@@ -30,12 +31,15 @@ export default function CalendarPage() {
|
||||
const res = await fetch("/api/user");
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
setError(data.error || "Failed to fetch user");
|
||||
const message = data.error || "Failed to fetch user";
|
||||
setError(message);
|
||||
showToast.error("Unable to fetch data. Retry?");
|
||||
return;
|
||||
}
|
||||
setUser(data);
|
||||
} catch {
|
||||
setError("Failed to fetch user data");
|
||||
showToast.error("Unable to fetch data. Retry?");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user