diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-01-01 23:44:50 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-01-01 23:47:21 +0900 |
| commit | 2fb6471a685bec1433be3335f377a1a2313e4820 (patch) | |
| tree | 328ddaeec0c591b06bf005d48b0242345c1336be /src/client/components/EditDeckModal.test.tsx | |
| parent | f30566e1c7126db4c6242ab38d07a9478f79d3db (diff) | |
| download | kioku-2fb6471a685bec1433be3335f377a1a2313e4820.tar.gz kioku-2fb6471a685bec1433be3335f377a1a2313e4820.tar.zst kioku-2fb6471a685bec1433be3335f377a1a2313e4820.zip | |
refactor(client): migrate API calls to typed RPC client
Replace raw fetch() calls with apiClient.rpc typed client across all
modal and page components. This provides better type safety and
eliminates manual auth header handling.
- Make handleResponse public for component usage
- Update all component tests to mock RPC methods instead of fetch
- Change POSTGRES_HOST default to kioku-db for Docker compatibility
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'src/client/components/EditDeckModal.test.tsx')
| -rw-r--r-- | src/client/components/EditDeckModal.test.tsx | 143 |
1 files changed, 48 insertions, 95 deletions
diff --git a/src/client/components/EditDeckModal.test.tsx b/src/client/components/EditDeckModal.test.tsx index c627dd5..fce17f6 100644 --- a/src/client/components/EditDeckModal.test.tsx +++ b/src/client/components/EditDeckModal.test.tsx @@ -4,11 +4,22 @@ import { cleanup, render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { apiClient } from "../api/client"; + +const mockPut = vi.fn(); +const mockHandleResponse = vi.fn(); vi.mock("../api/client", () => ({ apiClient: { - getAuthHeader: vi.fn(), + rpc: { + api: { + decks: { + ":id": { + $put: (args: unknown) => mockPut(args), + }, + }, + }, + }, + handleResponse: (res: unknown) => mockHandleResponse(res), }, ApiClientError: class ApiClientError extends Error { constructor( @@ -22,13 +33,10 @@ vi.mock("../api/client", () => ({ }, })); +import { ApiClientError } from "../api/client"; // Import after mock is set up import { EditDeckModal } from "./EditDeckModal"; -// Mock fetch globally -const mockFetch = vi.fn(); -global.fetch = mockFetch; - describe("EditDeckModal", () => { const mockDeck = { id: "deck-123", @@ -46,8 +54,14 @@ describe("EditDeckModal", () => { beforeEach(() => { vi.clearAllMocks(); - vi.mocked(apiClient.getAuthHeader).mockReturnValue({ - Authorization: "Bearer access-token", + mockPut.mockResolvedValue({ ok: true }); + mockHandleResponse.mockResolvedValue({ + deck: { + id: "deck-123", + name: "Test Deck", + description: "Test description", + newCardsPerDay: 20, + }, }); }); @@ -155,18 +169,6 @@ describe("EditDeckModal", () => { const onClose = vi.fn(); const onDeckUpdated = vi.fn(); - mockFetch.mockResolvedValue({ - ok: true, - json: async () => ({ - deck: { - id: "deck-123", - name: "Updated Deck", - description: "Test description", - newCardsPerDay: 20, - }, - }), - }); - render( <EditDeckModal isOpen={true} @@ -182,16 +184,12 @@ describe("EditDeckModal", () => { await user.click(screen.getByRole("button", { name: "Save Changes" })); await waitFor(() => { - expect(mockFetch).toHaveBeenCalledWith("/api/decks/deck-123", { - method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer access-token", - }, - body: JSON.stringify({ + expect(mockPut).toHaveBeenCalledWith({ + param: { id: "deck-123" }, + json: { name: "Updated Deck", description: "Test description", - }), + }, }); }); @@ -204,18 +202,6 @@ describe("EditDeckModal", () => { const onClose = vi.fn(); const onDeckUpdated = vi.fn(); - mockFetch.mockResolvedValue({ - ok: true, - json: async () => ({ - deck: { - id: "deck-123", - name: "Test Deck", - description: "New description", - newCardsPerDay: 20, - }, - }), - }); - render( <EditDeckModal isOpen={true} @@ -231,16 +217,12 @@ describe("EditDeckModal", () => { await user.click(screen.getByRole("button", { name: "Save Changes" })); await waitFor(() => { - expect(mockFetch).toHaveBeenCalledWith("/api/decks/deck-123", { - method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer access-token", - }, - body: JSON.stringify({ + expect(mockPut).toHaveBeenCalledWith({ + param: { id: "deck-123" }, + json: { name: "Test Deck", description: "New description", - }), + }, }); }); @@ -251,18 +233,6 @@ describe("EditDeckModal", () => { it("clears description when input is emptied", async () => { const user = userEvent.setup(); - mockFetch.mockResolvedValue({ - ok: true, - json: async () => ({ - deck: { - id: "deck-123", - name: "Test Deck", - description: null, - newCardsPerDay: 20, - }, - }), - }); - render(<EditDeckModal {...defaultProps} />); const descInput = screen.getByLabelText("Description (optional)"); @@ -270,16 +240,12 @@ describe("EditDeckModal", () => { await user.click(screen.getByRole("button", { name: "Save Changes" })); await waitFor(() => { - expect(mockFetch).toHaveBeenCalledWith("/api/decks/deck-123", { - method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer access-token", - }, - body: JSON.stringify({ + expect(mockPut).toHaveBeenCalledWith({ + param: { id: "deck-123" }, + json: { name: "Test Deck", description: null, - }), + }, }); }); }); @@ -287,11 +253,6 @@ describe("EditDeckModal", () => { it("trims whitespace from name and description", async () => { const user = userEvent.setup(); - mockFetch.mockResolvedValue({ - ok: true, - json: async () => ({ deck: { id: "deck-123" } }), - }); - const deckWithWhitespace = { ...mockDeck, name: " Deck ", @@ -302,16 +263,12 @@ describe("EditDeckModal", () => { await user.click(screen.getByRole("button", { name: "Save Changes" })); await waitFor(() => { - expect(mockFetch).toHaveBeenCalledWith("/api/decks/deck-123", { - method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer access-token", - }, - body: JSON.stringify({ + expect(mockPut).toHaveBeenCalledWith({ + param: { id: "deck-123" }, + json: { name: "Deck", description: "Description", - }), + }, }); }); }); @@ -319,7 +276,7 @@ describe("EditDeckModal", () => { it("shows loading state during submission", async () => { const user = userEvent.setup(); - mockFetch.mockImplementation(() => new Promise(() => {})); // Never resolves + mockPut.mockImplementation(() => new Promise(() => {})); // Never resolves render(<EditDeckModal {...defaultProps} />); @@ -344,11 +301,9 @@ describe("EditDeckModal", () => { it("displays API error message", async () => { const user = userEvent.setup(); - mockFetch.mockResolvedValue({ - ok: false, - status: 400, - json: async () => ({ error: "Deck name already exists" }), - }); + mockHandleResponse.mockRejectedValue( + new ApiClientError("Deck name already exists", 400), + ); render(<EditDeckModal {...defaultProps} />); @@ -364,7 +319,7 @@ describe("EditDeckModal", () => { it("displays generic error on unexpected failure", async () => { const user = userEvent.setup(); - mockFetch.mockRejectedValue(new Error("Network error")); + mockPut.mockRejectedValue(new Error("Network error")); render(<EditDeckModal {...defaultProps} />); @@ -377,10 +332,12 @@ describe("EditDeckModal", () => { }); }); - it("displays error when not authenticated", async () => { + it("displays error when handleResponse throws", async () => { const user = userEvent.setup(); - vi.mocked(apiClient.getAuthHeader).mockReturnValue(undefined); + mockHandleResponse.mockRejectedValue( + new ApiClientError("Not authenticated", 401), + ); render(<EditDeckModal {...defaultProps} />); @@ -419,11 +376,7 @@ describe("EditDeckModal", () => { const user = userEvent.setup(); const onClose = vi.fn(); - mockFetch.mockResolvedValue({ - ok: false, - status: 400, - json: async () => ({ error: "Some error" }), - }); + mockHandleResponse.mockRejectedValue(new ApiClientError("Some error", 400)); const { rerender } = render( <EditDeckModal {...defaultProps} onClose={onClose} />, |
