Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { fireEvent, render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";

// #6985: a real fetch failure used to render the same generic text as "still loading" — these tests
// pin the three render paths (loading / error / success) now that LoadingState/ErrorState replace it.
const { useApiResource } = vi.hoisted(() => ({ useApiResource: vi.fn() }));
vi.mock("@/lib/api/use-api-resource", () => ({
useApiResource: (...args: unknown[]) => useApiResource(...args),
}));

import { NotificationReadinessCard } from "./notification-readiness-card";

const notificationModelFixture = {
notificationModel: {
mode: "opt_in",
defaultState: "disabled",
channels: [
{
id: "browser_push",
transport: "web_push",
defaultEnabled: false,
purpose: "PR review updates",
},
],
privacyGuards: ["No content leaves the browser without consent."],
fallbackWhenUnavailable: "email digest",
},
pwa: { nativeDependency: false, manifestPath: "/manifest.json", serviceWorkerPath: "/sw.js" },
mobileReadyRoutes: [],
nativeMobileFuture: [],
};

describe("NotificationReadinessCard loading/error states (#6985)", () => {
it("shows a LoadingState (not the generic spinner-free text) while the model loads", () => {
useApiResource.mockReturnValue({
status: "loading",
data: null,
error: null,
loadedAt: null,
reload: () => {},
});

render(<NotificationReadinessCard />);

expect(screen.getByText("Loading notification model…")).toBeTruthy();
expect(screen.queryByText("Notification model unavailable.")).toBeNull();
});

it("shows an ErrorState with the real error message and a working retry, distinguishing it from loading", () => {
const reload = vi.fn();
useApiResource.mockReturnValue({
status: "error",
data: null,
error: "The server returned a 500.",
errorKind: "http",
loadedAt: null,
reload,
});

render(<NotificationReadinessCard />);

expect(screen.getByText("The server returned a 500.")).toBeTruthy();
expect(screen.queryByText("Loading notification model…")).toBeNull();
fireEvent.click(screen.getByRole("button", { name: "Try again" }));
expect(reload).toHaveBeenCalledTimes(1);
});

it("renders the network-specific ErrorState copy when errorKind indicates an unreachable server", () => {
useApiResource.mockReturnValue({
status: "error",
data: null,
error: "Failed to fetch",
errorKind: "network",
loadedAt: null,
reload: () => {},
});

render(<NotificationReadinessCard />);

expect(screen.getByText("Can't reach the server")).toBeTruthy();
});

it("still renders the privacy guards list unchanged on success", () => {
useApiResource.mockReturnValue({
status: "ready",
data: notificationModelFixture,
error: null,
loadedAt: Date.now(),
reload: () => {},
});

const { container } = render(<NotificationReadinessCard />);

expect(container.textContent).toContain("No content leaves the browser without consent.");
expect(screen.queryByText("Loading notification model…")).toBeNull();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Bell, BellOff } from "lucide-react";
import { useMemo, useState } from "react";

import { StatusPill } from "./control-primitives";
import { ErrorState, LoadingState } from "./state-views";
import { useApiResource } from "@/lib/api/use-api-resource";
import { useLocalStorage } from "@/lib/use-local-storage";

Expand Down Expand Up @@ -112,12 +113,15 @@ export function NotificationReadinessCard() {
))}
</ul>
</div>
) : model.status === "loading" ? (
<LoadingState className="mt-3" title="Loading notification model…" />
) : (
<p className="mt-3 text-token-sm text-muted-foreground">
{model.status === "loading"
? "Loading notification model…"
: "Notification model unavailable."}
</p>
<ErrorState
className="mt-3"
description={model.error}
errorKind={model.errorKind}
onRetry={model.reload}
/>
)}
</section>
);
Expand Down
Loading