From 38b8fc0e9927c4146b4c8b309b2bcc644abd63d0 Mon Sep 17 00:00:00 2001
From: nsfisis <nsfisis@gmail.com>
Date: Wed, 25 Feb 2026 23:02:35 +0900
Subject: feat(decks): add default note type setting per deck

Allow each deck to specify a default note type that is auto-selected
when creating new notes. Includes DB schema migration, server API
updates, sync layer support, and UI for editing the default in the
deck settings modal.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---
 src/client/components/EditDeckModal.test.tsx | 58 +++++++++++++++++++++-------
 1 file changed, 45 insertions(+), 13 deletions(-)

(limited to 'src/client/components/EditDeckModal.test.tsx')

diff --git a/src/client/components/EditDeckModal.test.tsx b/src/client/components/EditDeckModal.test.tsx
index b22cb1d..248c74f 100644
--- a/src/client/components/EditDeckModal.test.tsx
+++ b/src/client/components/EditDeckModal.test.tsx
@@ -8,6 +8,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
 const mockPut = vi.fn();
 const mockHandleResponse = vi.fn();
 
+const mockGetNoteTypes = vi.fn();
+
 vi.mock("../api/client", () => ({
 	apiClient: {
 		rpc: {
@@ -17,6 +19,9 @@ vi.mock("../api/client", () => ({
 						$put: (args: unknown) => mockPut(args),
 					},
 				},
+				"note-types": {
+					$get: () => mockGetNoteTypes(),
+				},
 			},
 		},
 		handleResponse: (res: unknown) => mockHandleResponse(res),
@@ -42,6 +47,7 @@ describe("EditDeckModal", () => {
 		id: "deck-123",
 		name: "Test Deck",
 		description: "Test description",
+		defaultNoteTypeId: null as string | null,
 	};
 
 	const defaultProps = {
@@ -51,15 +57,25 @@ describe("EditDeckModal", () => {
 		onDeckUpdated: vi.fn(),
 	};
 
+	const noteTypesResponse = { ok: true, _type: "noteTypes" };
+	const putResponse = { ok: true, _type: "put" };
+
 	beforeEach(() => {
 		vi.clearAllMocks();
-		mockPut.mockResolvedValue({ ok: true });
-		mockHandleResponse.mockResolvedValue({
-			deck: {
-				id: "deck-123",
-				name: "Test Deck",
-				description: "Test description",
-			},
+		mockPut.mockResolvedValue(putResponse);
+		mockGetNoteTypes.mockResolvedValue(noteTypesResponse);
+		mockHandleResponse.mockImplementation((res: unknown) => {
+			if (res === noteTypesResponse) {
+				return Promise.resolve({ noteTypes: [] });
+			}
+			return Promise.resolve({
+				deck: {
+					id: "deck-123",
+					name: "Test Deck",
+					description: "Test description",
+					defaultNoteTypeId: null,
+				},
+			});
 		});
 	});
 
@@ -187,6 +203,7 @@ describe("EditDeckModal", () => {
 				json: {
 					name: "Updated Deck",
 					description: "Test description",
+					defaultNoteTypeId: null,
 				},
 			});
 		});
@@ -220,6 +237,7 @@ describe("EditDeckModal", () => {
 				json: {
 					name: "Test Deck",
 					description: "New description",
+					defaultNoteTypeId: null,
 				},
 			});
 		});
@@ -243,6 +261,7 @@ describe("EditDeckModal", () => {
 				json: {
 					name: "Test Deck",
 					description: null,
+					defaultNoteTypeId: null,
 				},
 			});
 		});
@@ -266,6 +285,7 @@ describe("EditDeckModal", () => {
 				json: {
 					name: "Deck",
 					description: "Description",
+					defaultNoteTypeId: null,
 				},
 			});
 		});
@@ -299,12 +319,16 @@ describe("EditDeckModal", () => {
 	it("displays API error message", async () => {
 		const user = userEvent.setup();
 
+		render(<EditDeckModal {...defaultProps} />);
+
+		// Wait for note types to load, then override handleResponse for the PUT
+		await waitFor(() => {
+			expect(mockGetNoteTypes).toHaveBeenCalled();
+		});
 		mockHandleResponse.mockRejectedValue(
 			new ApiClientError("Deck name already exists", 400),
 		);
 
-		render(<EditDeckModal {...defaultProps} />);
-
 		await user.click(screen.getByRole("button", { name: "Save Changes" }));
 
 		await waitFor(() => {
@@ -333,12 +357,16 @@ describe("EditDeckModal", () => {
 	it("displays error when handleResponse throws", async () => {
 		const user = userEvent.setup();
 
+		render(<EditDeckModal {...defaultProps} />);
+
+		// Wait for note types to load, then override handleResponse for the PUT
+		await waitFor(() => {
+			expect(mockGetNoteTypes).toHaveBeenCalled();
+		});
 		mockHandleResponse.mockRejectedValue(
 			new ApiClientError("Not authenticated", 401),
 		);
 
-		render(<EditDeckModal {...defaultProps} />);
-
 		await user.click(screen.getByRole("button", { name: "Save Changes" }));
 
 		await waitFor(() => {
@@ -374,12 +402,16 @@ describe("EditDeckModal", () => {
 		const user = userEvent.setup();
 		const onClose = vi.fn();
 
-		mockHandleResponse.mockRejectedValue(new ApiClientError("Some error", 400));
-
 		const { rerender } = render(
 			<EditDeckModal {...defaultProps} onClose={onClose} />,
 		);
 
+		// Wait for note types to load, then override handleResponse for the PUT
+		await waitFor(() => {
+			expect(mockGetNoteTypes).toHaveBeenCalled();
+		});
+		mockHandleResponse.mockRejectedValue(new ApiClientError("Some error", 400));
+
 		// Trigger error
 		await user.click(screen.getByRole("button", { name: "Save Changes" }));
 		await waitFor(() => {
-- 
cgit v1.3-1-g0d28

