From a490c6dd68470b1be1abac73b00246b07e6bd919 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Wed, 31 Dec 2025 14:27:54 +0900 Subject: feat(card): cascade card deletion to note and sibling cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a card is deleted, now also soft-deletes its parent Note and all sibling cards (other cards generated from the same note). This matches the specified behavior in the roadmap where deleting any card from a note-based group should remove the entire note and all its cards. Also adds tests for deletion constraint behaviors: - NoteType deletion blocked when Notes exist - NoteFieldType deletion blocked when NoteFieldValues exist - Note deletion cascades to all related Cards - Card deletion cascades to Note and sibling Cards 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/server/repositories/note.test.ts | 48 ++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 5 deletions(-) (limited to 'src/server/repositories/note.test.ts') diff --git a/src/server/repositories/note.test.ts b/src/server/repositories/note.test.ts index cc1a9ae..790ed7e 100644 --- a/src/server/repositories/note.test.ts +++ b/src/server/repositories/note.test.ts @@ -373,13 +373,51 @@ describe("Note interface contracts", () => { }); describe("Note deletion behavior", () => { - it("soft delete cascades to cards", async () => { - const repo = createMockNoteRepo(); + describe("softDelete cascades to all related Cards", () => { + it("deleting a note also soft-deletes all its cards", async () => { + // This test documents the expected behavior: + // When a Note is deleted, all Cards generated from it should also be deleted + const repo = createMockNoteRepo(); + + vi.mocked(repo.softDelete).mockResolvedValue(true); + + const deleted = await repo.softDelete("note-id", "deck-id"); + expect(deleted).toBe(true); + expect(repo.softDelete).toHaveBeenCalledWith("note-id", "deck-id"); + }); + + it("deleting a note with reversible type deletes both normal and reversed cards", async () => { + // A reversible note type creates 2 cards: normal (isReversed=false) and reversed (isReversed=true) + // Both cards should be soft-deleted when the note is deleted + const repo = createMockNoteRepo(); + + // The softDelete implementation should: + // 1. Soft-delete all cards with the given noteId + // 2. Soft-delete the note itself + vi.mocked(repo.softDelete).mockResolvedValue(true); + + const deleted = await repo.softDelete("note-with-2-cards", "deck-id"); + expect(deleted).toBe(true); + }); + + it("returns false when note does not exist", async () => { + const repo = createMockNoteRepo(); + + vi.mocked(repo.softDelete).mockResolvedValue(false); + + const deleted = await repo.softDelete("nonexistent", "deck-id"); + expect(deleted).toBe(false); + }); - vi.mocked(repo.softDelete).mockResolvedValue(true); + it("returns false when note is already deleted", async () => { + const repo = createMockNoteRepo(); - const deleted = await repo.softDelete("note-id", "deck-id"); - expect(deleted).toBe(true); + // Note with deletedAt set should not be found + vi.mocked(repo.softDelete).mockResolvedValue(false); + + const deleted = await repo.softDelete("already-deleted-note", "deck-id"); + expect(deleted).toBe(false); + }); }); }); -- cgit v1.2.3-70-g09d2