aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/client/pages/NoteTypesPage.test.tsx
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-01-01 23:44:50 +0900
committernsfisis <nsfisis@gmail.com>2026-01-01 23:47:21 +0900
commit2fb6471a685bec1433be3335f377a1a2313e4820 (patch)
tree328ddaeec0c591b06bf005d48b0242345c1336be /src/client/pages/NoteTypesPage.test.tsx
parentf30566e1c7126db4c6242ab38d07a9478f79d3db (diff)
downloadkioku-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/pages/NoteTypesPage.test.tsx')
-rw-r--r--src/client/pages/NoteTypesPage.test.tsx182
1 files changed, 61 insertions, 121 deletions
diff --git a/src/client/pages/NoteTypesPage.test.tsx b/src/client/pages/NoteTypesPage.test.tsx
index 8364d17..c0559f6 100644
--- a/src/client/pages/NoteTypesPage.test.tsx
+++ b/src/client/pages/NoteTypesPage.test.tsx
@@ -7,10 +7,16 @@ import userEvent from "@testing-library/user-event";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { Router } from "wouter";
import { memoryLocation } from "wouter/memory-location";
-import { apiClient } from "../api/client";
import { AuthProvider, SyncProvider } from "../stores";
import { NoteTypesPage } from "./NoteTypesPage";
+const mockNoteTypesGet = vi.fn();
+const mockNoteTypesPost = vi.fn();
+const mockNoteTypeGet = vi.fn();
+const mockNoteTypePut = vi.fn();
+const mockNoteTypeDelete = vi.fn();
+const mockHandleResponse = vi.fn();
+
vi.mock("../api/client", () => ({
apiClient: {
login: vi.fn(),
@@ -19,6 +25,20 @@ vi.mock("../api/client", () => ({
getTokens: vi.fn(),
getAuthHeader: vi.fn(),
onSessionExpired: vi.fn(() => vi.fn()),
+ rpc: {
+ api: {
+ "note-types": {
+ $get: () => mockNoteTypesGet(),
+ $post: (args: unknown) => mockNoteTypesPost(args),
+ ":id": {
+ $get: (args: unknown) => mockNoteTypeGet(args),
+ $put: (args: unknown) => mockNoteTypePut(args),
+ $delete: (args: unknown) => mockNoteTypeDelete(args),
+ },
+ },
+ },
+ },
+ handleResponse: (res: unknown) => mockHandleResponse(res),
},
ApiClientError: class ApiClientError extends Error {
constructor(
@@ -32,9 +52,7 @@ vi.mock("../api/client", () => ({
},
}));
-// Mock fetch globally
-const mockFetch = vi.fn();
-global.fetch = mockFetch;
+import { ApiClientError, apiClient } from "../api/client";
const mockNoteTypes = [
{
@@ -81,6 +99,9 @@ describe("NoteTypesPage", () => {
vi.mocked(apiClient.getAuthHeader).mockReturnValue({
Authorization: "Bearer access-token",
});
+
+ // handleResponse passes through whatever it receives
+ mockHandleResponse.mockImplementation((res) => Promise.resolve(res));
});
afterEach(() => {
@@ -89,10 +110,7 @@ describe("NoteTypesPage", () => {
});
it("renders page title and back button", async () => {
- mockFetch.mockResolvedValue({
- ok: true,
- json: async () => ({ noteTypes: [] }),
- });
+ mockNoteTypesGet.mockResolvedValue({ noteTypes: [] });
renderWithProviders();
@@ -101,7 +119,7 @@ describe("NoteTypesPage", () => {
});
it("shows loading state while fetching note types", async () => {
- mockFetch.mockImplementation(() => new Promise(() => {})); // Never resolves
+ mockNoteTypesGet.mockImplementation(() => new Promise(() => {})); // Never resolves
renderWithProviders();
@@ -110,10 +128,7 @@ describe("NoteTypesPage", () => {
});
it("displays empty state when no note types exist", async () => {
- mockFetch.mockResolvedValue({
- ok: true,
- json: async () => ({ noteTypes: [] }),
- });
+ mockNoteTypesGet.mockResolvedValue({ noteTypes: [] });
renderWithProviders();
@@ -128,10 +143,7 @@ describe("NoteTypesPage", () => {
});
it("displays list of note types", async () => {
- mockFetch.mockResolvedValue({
- ok: true,
- json: async () => ({ noteTypes: mockNoteTypes }),
- });
+ mockNoteTypesGet.mockResolvedValue({ noteTypes: mockNoteTypes });
renderWithProviders();
@@ -144,10 +156,7 @@ describe("NoteTypesPage", () => {
});
it("displays reversible badge for reversible note types", async () => {
- mockFetch.mockResolvedValue({
- ok: true,
- json: async () => ({ noteTypes: mockNoteTypes }),
- });
+ mockNoteTypesGet.mockResolvedValue({ noteTypes: mockNoteTypes });
renderWithProviders();
@@ -161,10 +170,7 @@ describe("NoteTypesPage", () => {
});
it("displays template info for each note type", async () => {
- mockFetch.mockResolvedValue({
- ok: true,
- json: async () => ({ noteTypes: mockNoteTypes }),
- });
+ mockNoteTypesGet.mockResolvedValue({ noteTypes: mockNoteTypes });
renderWithProviders();
@@ -177,11 +183,9 @@ describe("NoteTypesPage", () => {
});
it("displays error on API failure", async () => {
- mockFetch.mockResolvedValue({
- ok: false,
- status: 500,
- json: async () => ({ error: "Internal server error" }),
- });
+ mockNoteTypesGet.mockRejectedValue(
+ new ApiClientError("Internal server error", 500),
+ );
renderWithProviders();
@@ -193,7 +197,7 @@ describe("NoteTypesPage", () => {
});
it("displays generic error on unexpected failure", async () => {
- mockFetch.mockRejectedValue(new Error("Network error"));
+ mockNoteTypesGet.mockRejectedValue(new Error("Network error"));
renderWithProviders();
@@ -206,16 +210,9 @@ describe("NoteTypesPage", () => {
it("allows retry after error", async () => {
const user = userEvent.setup();
- mockFetch
- .mockResolvedValueOnce({
- ok: false,
- status: 500,
- json: async () => ({ error: "Server error" }),
- })
- .mockResolvedValueOnce({
- ok: true,
- json: async () => ({ noteTypes: mockNoteTypes }),
- });
+ mockNoteTypesGet
+ .mockRejectedValueOnce(new ApiClientError("Server error", 500))
+ .mockResolvedValueOnce({ noteTypes: mockNoteTypes });
renderWithProviders();
@@ -230,27 +227,19 @@ describe("NoteTypesPage", () => {
});
});
- it("passes auth header when fetching note types", async () => {
- mockFetch.mockResolvedValue({
- ok: true,
- json: async () => ({ noteTypes: [] }),
- });
+ it("calls correct RPC endpoint when fetching note types", async () => {
+ mockNoteTypesGet.mockResolvedValue({ noteTypes: [] });
renderWithProviders();
await waitFor(() => {
- expect(mockFetch).toHaveBeenCalledWith("/api/note-types", {
- headers: { Authorization: "Bearer access-token" },
- });
+ expect(mockNoteTypesGet).toHaveBeenCalled();
});
});
describe("Create Note Type", () => {
it("shows New Note Type button", async () => {
- mockFetch.mockResolvedValue({
- ok: true,
- json: async () => ({ noteTypes: [] }),
- });
+ mockNoteTypesGet.mockResolvedValue({ noteTypes: [] });
renderWithProviders();
@@ -265,10 +254,7 @@ describe("NoteTypesPage", () => {
it("opens modal when New Note Type button is clicked", async () => {
const user = userEvent.setup();
- mockFetch.mockResolvedValue({
- ok: true,
- json: async () => ({ noteTypes: [] }),
- });
+ mockNoteTypesGet.mockResolvedValue({ noteTypes: [] });
renderWithProviders();
@@ -296,19 +282,10 @@ describe("NoteTypesPage", () => {
updatedAt: "2024-01-03T00:00:00Z",
};
- mockFetch
- .mockResolvedValueOnce({
- ok: true,
- json: async () => ({ noteTypes: [] }),
- })
- .mockResolvedValueOnce({
- ok: true,
- json: async () => ({ noteType: newNoteType }),
- })
- .mockResolvedValueOnce({
- ok: true,
- json: async () => ({ noteTypes: [newNoteType] }),
- });
+ mockNoteTypesGet
+ .mockResolvedValueOnce({ noteTypes: [] })
+ .mockResolvedValueOnce({ noteTypes: [newNoteType] });
+ mockNoteTypesPost.mockResolvedValue({ noteType: newNoteType });
renderWithProviders();
@@ -341,10 +318,7 @@ describe("NoteTypesPage", () => {
describe("Edit Note Type", () => {
it("shows Edit button for each note type", async () => {
- mockFetch.mockResolvedValue({
- ok: true,
- json: async () => ({ noteTypes: mockNoteTypes }),
- });
+ mockNoteTypesGet.mockResolvedValue({ noteTypes: mockNoteTypes });
renderWithProviders();
@@ -380,15 +354,8 @@ describe("NoteTypesPage", () => {
],
};
- mockFetch
- .mockResolvedValueOnce({
- ok: true,
- json: async () => ({ noteTypes: mockNoteTypes }),
- })
- .mockResolvedValueOnce({
- ok: true,
- json: async () => ({ noteType: mockNoteTypeWithFields }),
- });
+ mockNoteTypesGet.mockResolvedValue({ noteTypes: mockNoteTypes });
+ mockNoteTypeGet.mockResolvedValue({ noteType: mockNoteTypeWithFields });
renderWithProviders();
@@ -437,25 +404,13 @@ describe("NoteTypesPage", () => {
name: "Updated Basic",
};
- mockFetch
- .mockResolvedValueOnce({
- ok: true,
- json: async () => ({ noteTypes: mockNoteTypes }),
- })
- .mockResolvedValueOnce({
- ok: true,
- json: async () => ({ noteType: mockNoteTypeWithFields }),
- })
+ mockNoteTypesGet
+ .mockResolvedValueOnce({ noteTypes: mockNoteTypes })
.mockResolvedValueOnce({
- ok: true,
- json: async () => ({ noteType: updatedNoteType }),
- })
- .mockResolvedValueOnce({
- ok: true,
- json: async () => ({
- noteTypes: [updatedNoteType, mockNoteTypes[1]],
- }),
+ noteTypes: [updatedNoteType, mockNoteTypes[1]],
});
+ mockNoteTypeGet.mockResolvedValue({ noteType: mockNoteTypeWithFields });
+ mockNoteTypePut.mockResolvedValue({ noteType: updatedNoteType });
renderWithProviders();
@@ -498,10 +453,7 @@ describe("NoteTypesPage", () => {
describe("Delete Note Type", () => {
it("shows Delete button for each note type", async () => {
- mockFetch.mockResolvedValue({
- ok: true,
- json: async () => ({ noteTypes: mockNoteTypes }),
- });
+ mockNoteTypesGet.mockResolvedValue({ noteTypes: mockNoteTypes });
renderWithProviders();
@@ -517,10 +469,7 @@ describe("NoteTypesPage", () => {
it("opens delete modal when Delete button is clicked", async () => {
const user = userEvent.setup();
- mockFetch.mockResolvedValue({
- ok: true,
- json: async () => ({ noteTypes: mockNoteTypes }),
- });
+ mockNoteTypesGet.mockResolvedValue({ noteTypes: mockNoteTypes });
renderWithProviders();
@@ -544,19 +493,10 @@ describe("NoteTypesPage", () => {
it("deletes note type and refreshes list", async () => {
const user = userEvent.setup();
- mockFetch
- .mockResolvedValueOnce({
- ok: true,
- json: async () => ({ noteTypes: mockNoteTypes }),
- })
- .mockResolvedValueOnce({
- ok: true,
- json: async () => ({ success: true }),
- })
- .mockResolvedValueOnce({
- ok: true,
- json: async () => ({ noteTypes: [mockNoteTypes[1]] }),
- });
+ mockNoteTypesGet
+ .mockResolvedValueOnce({ noteTypes: mockNoteTypes })
+ .mockResolvedValueOnce({ noteTypes: [mockNoteTypes[1]] });
+ mockNoteTypeDelete.mockResolvedValue({ success: true });
renderWithProviders();