From c1cb543875edee1aa9cc160a78b610e06ea139e7 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 14 Feb 2026 23:05:43 +0900 Subject: feat(study): add edit button to study session cards Allow editing note content directly from the study screen via a pen icon button or the E key shortcut. Keyboard shortcuts are disabled while the edit modal is open to prevent accidental card flips/ratings. Co-Authored-By: Claude Opus 4.6 --- src/client/pages/StudyPage.test.tsx | 221 ++++++++++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) (limited to 'src/client/pages/StudyPage.test.tsx') diff --git a/src/client/pages/StudyPage.test.tsx b/src/client/pages/StudyPage.test.tsx index 9c48083..860777a 100644 --- a/src/client/pages/StudyPage.test.tsx +++ b/src/client/pages/StudyPage.test.tsx @@ -27,6 +27,49 @@ vi.mock("../utils/shuffle", () => ({ shuffle: (array: T[]): T[] => [...array], })); +const mockEditNoteModalOnClose = vi.fn(); +const mockEditNoteModalOnNoteUpdated = vi.fn(); + +vi.mock("../components/EditNoteModal", () => ({ + EditNoteModal: ({ + isOpen, + deckId, + noteId, + onClose, + onNoteUpdated, + }: { + isOpen: boolean; + deckId: string; + noteId: string | null; + onClose: () => void; + onNoteUpdated: () => void; + }) => { + // Store callbacks so tests can call them + mockEditNoteModalOnClose.mockImplementation(onClose); + mockEditNoteModalOnNoteUpdated.mockImplementation(onNoteUpdated); + + if (!isOpen) return null; + return ( +
+ + +
+ ); + }, +})); + vi.mock("../api/client", () => ({ apiClient: { login: vi.fn(), @@ -935,4 +978,182 @@ describe("StudyPage", () => { ); }); }); + + describe("Edit Card", () => { + it("shows edit button on card", () => { + renderWithProviders({ + initialStudyData: { deck: mockDeck, cards: mockDueCards }, + }); + + expect(screen.getByTestId("edit-card-button")).toBeDefined(); + }); + + it("opens edit modal when edit button is clicked", async () => { + const user = userEvent.setup(); + + renderWithProviders({ + initialStudyData: { deck: mockDeck, cards: mockDueCards }, + }); + + expect(screen.queryByTestId("edit-note-modal")).toBeNull(); + + await user.click(screen.getByTestId("edit-card-button")); + + expect(screen.getByTestId("edit-note-modal")).toBeDefined(); + expect( + screen.getByTestId("edit-note-modal").getAttribute("data-note-id"), + ).toBe("note-1"); + expect( + screen.getByTestId("edit-note-modal").getAttribute("data-deck-id"), + ).toBe("deck-1"); + }); + + it("does not flip card when edit button is clicked", async () => { + const user = userEvent.setup(); + + renderWithProviders({ + initialStudyData: { deck: mockDeck, cards: mockDueCards }, + }); + + await user.click(screen.getByTestId("edit-card-button")); + + // Card should still show front, not back + expect(screen.getByTestId("card-front")).toBeDefined(); + expect(screen.queryByTestId("card-back")).toBeNull(); + }); + + it("closes edit modal when close button is clicked", async () => { + const user = userEvent.setup(); + + renderWithProviders({ + initialStudyData: { deck: mockDeck, cards: mockDueCards }, + }); + + await user.click(screen.getByTestId("edit-card-button")); + expect(screen.getByTestId("edit-note-modal")).toBeDefined(); + + await user.click(screen.getByTestId("edit-modal-close")); + + expect(screen.queryByTestId("edit-note-modal")).toBeNull(); + }); + + it("closes edit modal when save button is clicked", async () => { + const user = userEvent.setup(); + + renderWithProviders({ + initialStudyData: { deck: mockDeck, cards: mockDueCards }, + }); + + await user.click(screen.getByTestId("edit-card-button")); + expect(screen.getByTestId("edit-note-modal")).toBeDefined(); + + await user.click(screen.getByTestId("edit-modal-save")); + + expect(screen.queryByTestId("edit-note-modal")).toBeNull(); + }); + + it("opens edit modal with E key", async () => { + const user = userEvent.setup(); + + renderWithProviders({ + initialStudyData: { deck: mockDeck, cards: mockDueCards }, + }); + + await user.keyboard("e"); + + expect(screen.getByTestId("edit-note-modal")).toBeDefined(); + expect( + screen.getByTestId("edit-note-modal").getAttribute("data-note-id"), + ).toBe("note-1"); + }); + + it("opens edit modal with E key when card is flipped", async () => { + const user = userEvent.setup(); + + renderWithProviders({ + initialStudyData: { deck: mockDeck, cards: mockDueCards }, + }); + + // Flip card first + await user.keyboard(" "); + expect(screen.getByTestId("card-back")).toBeDefined(); + + await user.keyboard("e"); + + expect(screen.getByTestId("edit-note-modal")).toBeDefined(); + }); + + it("disables keyboard shortcuts while edit modal is open", async () => { + const user = userEvent.setup(); + + renderWithProviders({ + initialStudyData: { deck: mockDeck, cards: mockDueCards }, + }); + + // Open edit modal + await user.keyboard("e"); + expect(screen.getByTestId("edit-note-modal")).toBeDefined(); + + // Space should not flip the card + await user.keyboard(" "); + expect(screen.getByTestId("card-front")).toBeDefined(); + expect(screen.queryByTestId("card-back")).toBeNull(); + }); + + it("disables rating shortcuts while edit modal is open", async () => { + const user = userEvent.setup(); + + renderWithProviders({ + initialStudyData: { deck: mockDeck, cards: mockDueCards }, + }); + + // Flip card, then open edit modal via E key + await user.keyboard(" "); + expect(screen.getByTestId("card-back")).toBeDefined(); + + await user.keyboard("e"); + expect(screen.getByTestId("edit-note-modal")).toBeDefined(); + + // Number keys should not rate the card + await user.keyboard("3"); + + // Card should still be showing (not moved to next) + expect(screen.getByTestId("card-back")).toBeDefined(); + expect(screen.getByTestId("remaining-count").textContent).toBe( + "2 remaining", + ); + }); + + it("re-enables keyboard shortcuts after edit modal is closed", async () => { + const user = userEvent.setup(); + + renderWithProviders({ + initialStudyData: { deck: mockDeck, cards: mockDueCards }, + }); + + // Open and close edit modal + await user.keyboard("e"); + expect(screen.getByTestId("edit-note-modal")).toBeDefined(); + + await user.click(screen.getByTestId("edit-modal-close")); + expect(screen.queryByTestId("edit-note-modal")).toBeNull(); + + // Space should now flip the card + await user.keyboard(" "); + expect(screen.getByTestId("card-back")).toBeDefined(); + }); + + it("shows edit button when card is flipped", async () => { + const user = userEvent.setup(); + + renderWithProviders({ + initialStudyData: { deck: mockDeck, cards: mockDueCards }, + }); + + await user.click(screen.getByTestId("card-container")); + + expect(screen.getByTestId("card-back")).toBeDefined(); + expect(screen.getByTestId("edit-card-button")).toBeDefined(); + }); + }); }); -- cgit v1.3-1-g0d28