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/card.test.ts | 60 ++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) (limited to 'src/server/repositories/card.test.ts') diff --git a/src/server/repositories/card.test.ts b/src/server/repositories/card.test.ts index 98913e9..4263dad 100644 --- a/src/server/repositories/card.test.ts +++ b/src/server/repositories/card.test.ts @@ -444,3 +444,63 @@ describe("Card and Note relationship", () => { expect(reversedCard.isReversed).toBe(true); }); }); + +describe("Card deletion behavior", () => { + describe("softDelete cascades to Note and sibling Cards", () => { + it("when a card is deleted, it also deletes the parent Note", async () => { + // This test documents the expected behavior: + // Deleting a card should also soft-delete its parent Note + const repo = createMockCardRepo(); + const card = createMockCard({ id: "card-1", noteId: "note-1" }); + + // The implementation first finds the card to get noteId + vi.mocked(repo.findById).mockResolvedValue(card); + vi.mocked(repo.softDelete).mockResolvedValue(true); + + const deleted = await repo.softDelete("card-1", "deck-1"); + + expect(deleted).toBe(true); + expect(repo.softDelete).toHaveBeenCalledWith("card-1", "deck-1"); + }); + + it("when a card is deleted, sibling cards (same noteId) should also be deleted", async () => { + // This test documents the expected behavior: + // A reversible note creates 2 cards with the same noteId. + // When one card is deleted, both cards should be soft-deleted. + const repo = createMockCardRepo(); + const normalCard = createMockCard({ + id: "card-normal", + noteId: "shared-note", + isReversed: false, + }); + const reversedCard = createMockCard({ + id: "card-reversed", + noteId: "shared-note", + isReversed: true, + }); + + // Before deletion: both cards exist + vi.mocked(repo.findByNoteId).mockResolvedValue([ + normalCard, + reversedCard, + ]); + expect((await repo.findByNoteId("shared-note")).length).toBe(2); + + // After deleting one card, both should be deleted + vi.mocked(repo.softDelete).mockResolvedValue(true); + const deleted = await repo.softDelete("card-normal", "deck-1"); + + expect(deleted).toBe(true); + }); + + it("deleting non-existent card returns false", async () => { + const repo = createMockCardRepo(); + + vi.mocked(repo.findById).mockResolvedValue(undefined); + vi.mocked(repo.softDelete).mockResolvedValue(false); + + const deleted = await repo.softDelete("nonexistent", "deck-1"); + expect(deleted).toBe(false); + }); + }); +}); -- cgit v1.2.3-70-g09d2