From 6aa496b60879526b51fdf736c38f09aede283bde Mon Sep 17 00:00:00 2001
From: nsfisis <nsfisis@gmail.com>
Date: Wed, 31 Dec 2025 02:49:55 +0900
Subject: feat(client): add NoteTypeEditor component with field management
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Add a comprehensive editor for note types that allows users to:
- Edit note type name and templates
- Add, remove, and reorder fields
- Toggle the reversible card option

The editor fetches note type details including fields from the API,
enabling full CRUD operations for note type configuration.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---
 src/client/pages/NoteTypesPage.test.tsx | 90 +++++++++++++++++++++++++--------
 1 file changed, 69 insertions(+), 21 deletions(-)

(limited to 'src/client/pages/NoteTypesPage.test.tsx')

diff --git a/src/client/pages/NoteTypesPage.test.tsx b/src/client/pages/NoteTypesPage.test.tsx
index 0cf8615..c2df7f5 100644
--- a/src/client/pages/NoteTypesPage.test.tsx
+++ b/src/client/pages/NoteTypesPage.test.tsx
@@ -95,9 +95,7 @@ describe("NoteTypesPage", () => {
 
 		renderWithProviders();
 
-		expect(
-			screen.getByRole("heading", { name: "Note Types" }),
-		).toBeDefined();
+		expect(screen.getByRole("heading", { name: "Note Types" })).toBeDefined();
 		expect(screen.getByRole("link", { name: "Back to Home" })).toBeDefined();
 	});
 
@@ -137,9 +135,7 @@ describe("NoteTypesPage", () => {
 		renderWithProviders();
 
 		await waitFor(() => {
-			expect(
-				screen.getByRole("heading", { name: "Basic" }),
-			).toBeDefined();
+			expect(screen.getByRole("heading", { name: "Basic" })).toBeDefined();
 		});
 		expect(
 			screen.getByRole("heading", { name: "Basic (and reversed card)" }),
@@ -279,9 +275,7 @@ describe("NoteTypesPage", () => {
 				expect(screen.getByText("No note types yet")).toBeDefined();
 			});
 
-			await user.click(
-				screen.getByRole("button", { name: /New Note Type/i }),
-			);
+			await user.click(screen.getByRole("button", { name: /New Note Type/i }));
 
 			expect(screen.getByRole("dialog")).toBeDefined();
 			expect(
@@ -322,9 +316,7 @@ describe("NoteTypesPage", () => {
 			});
 
 			// Open modal
-			await user.click(
-				screen.getByRole("button", { name: /New Note Type/i }),
-			);
+			await user.click(screen.getByRole("button", { name: /New Note Type/i }));
 
 			// Fill in form
 			await user.type(screen.getByLabelText("Name"), "New Note Type");
@@ -367,10 +359,35 @@ describe("NoteTypesPage", () => {
 
 		it("opens edit modal when Edit button is clicked", async () => {
 			const user = userEvent.setup();
-			mockFetch.mockResolvedValue({
-				ok: true,
-				json: async () => ({ noteTypes: mockNoteTypes }),
-			});
+			const mockNoteTypeWithFields = {
+				...mockNoteTypes[0],
+				fields: [
+					{
+						id: "field-1",
+						noteTypeId: "note-type-1",
+						name: "Front",
+						order: 0,
+						fieldType: "text",
+					},
+					{
+						id: "field-2",
+						noteTypeId: "note-type-1",
+						name: "Back",
+						order: 1,
+						fieldType: "text",
+					},
+				],
+			};
+
+			mockFetch
+				.mockResolvedValueOnce({
+					ok: true,
+					json: async () => ({ noteTypes: mockNoteTypes }),
+				})
+				.mockResolvedValueOnce({
+					ok: true,
+					json: async () => ({ noteType: mockNoteTypeWithFields }),
+				});
 
 			renderWithProviders();
 
@@ -387,11 +404,33 @@ describe("NoteTypesPage", () => {
 			expect(
 				screen.getByRole("heading", { name: "Edit Note Type" }),
 			).toBeDefined();
-			expect(screen.getByLabelText("Name")).toHaveProperty("value", "Basic");
+
+			await waitFor(() => {
+				expect(screen.getByLabelText("Name")).toHaveProperty("value", "Basic");
+			});
 		});
 
 		it("edits note type and refreshes list", async () => {
 			const user = userEvent.setup();
+			const mockNoteTypeWithFields = {
+				...mockNoteTypes[0],
+				fields: [
+					{
+						id: "field-1",
+						noteTypeId: "note-type-1",
+						name: "Front",
+						order: 0,
+						fieldType: "text",
+					},
+					{
+						id: "field-2",
+						noteTypeId: "note-type-1",
+						name: "Back",
+						order: 1,
+						fieldType: "text",
+					},
+				],
+			};
 			const updatedNoteType = {
 				...mockNoteTypes[0],
 				name: "Updated Basic",
@@ -402,13 +441,19 @@ describe("NoteTypesPage", () => {
 					ok: true,
 					json: async () => ({ noteTypes: mockNoteTypes }),
 				})
+				.mockResolvedValueOnce({
+					ok: true,
+					json: async () => ({ noteType: mockNoteTypeWithFields }),
+				})
 				.mockResolvedValueOnce({
 					ok: true,
 					json: async () => ({ noteType: updatedNoteType }),
 				})
 				.mockResolvedValueOnce({
 					ok: true,
-					json: async () => ({ noteTypes: [updatedNoteType, mockNoteTypes[1]] }),
+					json: async () => ({
+						noteTypes: [updatedNoteType, mockNoteTypes[1]],
+					}),
 				});
 
 			renderWithProviders();
@@ -423,6 +468,11 @@ describe("NoteTypesPage", () => {
 			});
 			await user.click(editButtons.at(0) as HTMLElement);
 
+			// Wait for the editor to load
+			await waitFor(() => {
+				expect(screen.getByLabelText("Name")).toHaveProperty("value", "Basic");
+			});
+
 			// Update name
 			const nameInput = screen.getByLabelText("Name");
 			await user.clear(nameInput);
@@ -539,9 +589,7 @@ describe("NoteTypesPage", () => {
 
 			// Note type list should be refreshed without deleted note type
 			await waitFor(() => {
-				expect(
-					screen.queryByRole("heading", { name: "Basic" }),
-				).toBeNull();
+				expect(screen.queryByRole("heading", { name: "Basic" })).toBeNull();
 			});
 			expect(
 				screen.getByRole("heading", { name: "Basic (and reversed card)" }),
-- 
cgit v1.2.3-70-g09d2

