feat(auth): AuthProvider context + useAuth hook

This commit is contained in:
Nora
2026-06-22 15:11:02 -06:00
parent 55a298f05f
commit d895854dcc
3 changed files with 192 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
// @vitest-environment happy-dom
import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderHook, act, waitFor } from "@testing-library/react";
vi.mock("./api", () => ({
authApi: {
me: vi.fn(),
login: vi.fn(),
logout: vi.fn(),
},
}));
import { authApi } from "./api";
import { AuthProvider, useAuth } from "./AuthProvider";
const wrapper = ({ children }: { children: React.ReactNode }) => (
<AuthProvider>{children}</AuthProvider>
);
describe("AuthProvider", () => {
beforeEach(() => {
vi.resetAllMocks();
});
it("loads user on mount", async () => {
(authApi.me as unknown as ReturnType<typeof vi.fn>).mockResolvedValue({
id: 1,
username: "alice",
role: "user",
});
const { result } = renderHook(() => useAuth(), { wrapper });
await waitFor(() => expect(result.current.status).toBe("authenticated"));
expect(result.current.user?.username).toBe("alice");
});
it("status is unauthenticated when /me fails", async () => {
(authApi.me as unknown as ReturnType<typeof vi.fn>).mockRejectedValue(
new Error("401")
);
const { result } = renderHook(() => useAuth(), { wrapper });
await waitFor(() => expect(result.current.status).toBe("unauthenticated"));
expect(result.current.user).toBeNull();
});
it("login() updates state on success", async () => {
(authApi.me as unknown as ReturnType<typeof vi.fn>).mockRejectedValue(
new Error("401")
);
(
authApi.login as unknown as ReturnType<typeof vi.fn>
).mockResolvedValue({ id: 1, username: "alice", role: "admin" });
const { result } = renderHook(() => useAuth(), { wrapper });
await waitFor(() => expect(result.current.status).toBe("unauthenticated"));
await act(async () => {
await result.current.login("alice", "pw");
});
expect(result.current.user?.username).toBe("alice");
expect(result.current.status).toBe("authenticated");
});
it("logout() clears state", async () => {
(authApi.me as unknown as ReturnType<typeof vi.fn>).mockResolvedValue({
id: 1,
username: "alice",
role: "user",
});
(
authApi.logout as unknown as ReturnType<typeof vi.fn>
).mockResolvedValue(undefined);
const { result } = renderHook(() => useAuth(), { wrapper });
await waitFor(() => expect(result.current.status).toBe("authenticated"));
await act(async () => {
await result.current.logout();
});
expect(result.current.user).toBeNull();
expect(result.current.status).toBe("unauthenticated");
});
});