Each section of the user settings page (Profile, Account, Notifications, API Access, Data & Privacy) is now a real, linkable path (/settings/profile, /settings/account, etc.) instead of local toggle state, so sections can be bookmarked, shared, opened in a new tab, and reached via the browser back/forward buttons. - Route now matches an optional segment (settings/:section?), keeping the single route so the shared loader/action and form submissions are unchanged. An unknown section redirects back to /settings. - The shared settings nav components render proper <Link>s when given a buildHref/backHref, and keep their button/onClick behaviour for the league settings page (which has unsaved-changes guards). - The settings page derives the active section and mobile grid/section view from the URL param instead of useState. - Notifications "Account settings" link and the Discord OAuth callback now point at /settings/account. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jt8Hhsio4bHGMaDZy7ftBs
93 lines
2.9 KiB
TypeScript
93 lines
2.9 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
|
import { AccountSection } from "../AccountSection";
|
|
|
|
vi.mock("react-router", () => ({
|
|
Link: ({ children, to }: { children: React.ReactNode; to: string }) => (
|
|
<a href={to}>{children}</a>
|
|
),
|
|
}));
|
|
|
|
const mockLinkSocial = vi.fn();
|
|
vi.mock("~/lib/auth-client", () => ({
|
|
authClient: {
|
|
linkSocial: (...args: unknown[]) => mockLinkSocial(...args),
|
|
},
|
|
}));
|
|
|
|
describe("AccountSection", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockLinkSocial.mockResolvedValue({});
|
|
});
|
|
|
|
it("shows Connect Discord button when Discord is not linked", () => {
|
|
render(
|
|
<AccountSection
|
|
email="user@example.com"
|
|
linkedAccounts={[{ id: "1", providerId: "credential" }]}
|
|
/>
|
|
);
|
|
expect(screen.getByRole("button", { name: /connect discord/i })).toBeInTheDocument();
|
|
});
|
|
|
|
it("does not show Connect Discord button when Discord is already linked", () => {
|
|
render(
|
|
<AccountSection
|
|
email="user@example.com"
|
|
linkedAccounts={[
|
|
{ id: "1", providerId: "credential" },
|
|
{ id: "2", providerId: "discord" },
|
|
]}
|
|
/>
|
|
);
|
|
expect(screen.queryByRole("button", { name: /connect discord/i })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("calls authClient.linkSocial with discord provider on button click", async () => {
|
|
render(
|
|
<AccountSection
|
|
email="user@example.com"
|
|
linkedAccounts={[{ id: "1", providerId: "credential" }]}
|
|
/>
|
|
);
|
|
fireEvent.click(screen.getByRole("button", { name: /connect discord/i }));
|
|
await waitFor(() => {
|
|
expect(mockLinkSocial).toHaveBeenCalledWith({
|
|
provider: "discord",
|
|
callbackURL: "/settings/account",
|
|
});
|
|
});
|
|
});
|
|
|
|
it("shows Redirecting… and disables the button while linking", async () => {
|
|
// Never resolves so we can inspect the in-flight state
|
|
mockLinkSocial.mockReturnValue(new Promise(() => {}));
|
|
render(
|
|
<AccountSection
|
|
email="user@example.com"
|
|
linkedAccounts={[{ id: "1", providerId: "credential" }]}
|
|
/>
|
|
);
|
|
fireEvent.click(screen.getByRole("button", { name: /connect discord/i }));
|
|
await waitFor(() => {
|
|
const btn = screen.getByRole("button", { name: /redirecting/i });
|
|
expect(btn).toBeDisabled();
|
|
});
|
|
});
|
|
|
|
it("re-enables the button and shows an error message when linkSocial throws", async () => {
|
|
mockLinkSocial.mockRejectedValue(new Error("OAuth failed"));
|
|
render(
|
|
<AccountSection
|
|
email="user@example.com"
|
|
linkedAccounts={[{ id: "1", providerId: "credential" }]}
|
|
/>
|
|
);
|
|
fireEvent.click(screen.getByRole("button", { name: /connect discord/i }));
|
|
await waitFor(() => {
|
|
expect(screen.getByRole("button", { name: /connect discord/i })).not.toBeDisabled();
|
|
expect(screen.getByText(/failed to connect discord/i)).toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|