Repository: st3v3nmw/obsidian-spaced-repetition
Pull request #775 (2023-10-24 10:18:02)

Bug fix: questions now removed from postponement list; etc
Fixes: https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/774

1. Questions with only a single card are not added to the postponement list.
2. Questions should be removed from the postponement list the following day.



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1bed66b..1cc39a6 120000
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,1 +1,1 @@
-docs/changelog.md
\ No newline at end of file
+docs/changelog.md
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 651dc17..9815d5b 120000
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,1 +1,1 @@
-docs/en/contributing.md
\ No newline at end of file
+docs/en/contributing.md
diff --git a/docs/changelog.md b/docs/changelog.md
index 6dbd41b..8d9704e 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -4,10 +4,9 @@ All notable changes to this project will be documented in this file. Dates are d
 
 Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
 
-#### [1.10.3](https://github.com/st3v3nmw/obsidian-spaced-repetition/compare/1.10.2...1.10.3)
+#### Unreleased
 
--   doc: update note about the reversed card [`#699`](https://github.com/st3v3nmw/obsidian-spaced-repetition/pull/699)
--   Fix bugs regarding bury sibling cards [`#763`](https://github.com/st3v3nmw/obsidian-spaced-repetition/pull/763)
+-   Fix: Questions not removed from the card postponement list [`#774`](https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/774)
 
 #### [1.10.2](https://github.com/st3v3nmw/obsidian-spaced-repetition/compare/1.10.1...1.10.2)
 
diff --git a/src/Deck.ts b/src/Deck.ts
index fceecb5..fe42fb1 100644
--- a/src/Deck.ts
+++ b/src/Deck.ts
@@ -17,6 +17,14 @@ export class Deck {
     public subdecks: Deck[];
     public parent: Deck | null;
 
+    constructor(deckName: string, parent: Deck | null) {
+        this.deckName = deckName;
+        this.newFlashcards = [];
+        this.dueFlashcards = [];
+        this.subdecks = [];
+        this.parent = parent;
+    }
+
     public getCardCount(cardListType: CardListType, includeSubdeckCounts: boolean): number {
         let result: number = 0;
         if (cardListType == CardListType.NewCard || cardListType == CardListType.All)
@@ -32,12 +40,24 @@ export class Deck {
         return result;
     }
 
-    constructor(deckName: string, parent: Deck | null) {
-        this.deckName = deckName;
-        this.newFlashcards = [];
-        this.dueFlashcards = [];
-        this.subdecks = [];
-        this.parent = parent;
+    //
+    // Returns a count of the number of this question's cards are present in this deck.
+    // (The returned value would be <= question.cards.length)
+    //
+    public getQuestionCardCount(question: Question): number {
+        let result: number = 0;
+        result += this.getQuestionCardCountForCardListType(question, this.newFlashcards);
+        result += this.getQuestionCardCountForCardListType(question, this.dueFlashcards);
+        return result;
+    }
+
+    private getQuestionCardCountForCardListType(question: Question, cards: Card[]): number {
+        let result: number = 0;
+        for (let i = 0; i < cards.length; i++) {
+            const card = cards[i];
+            if (Object.is(question, cards[i].question)) result++;
+        }
+        return result;
     }
 
     static get emptyDeck(): Deck {
@@ -124,12 +144,6 @@ export class Deck {
         cardList.splice(index, 1);
     }
 
-    deleteAllCardsForQuestion(question: Question): void {
-        for (let idx = question.cards.length - 1; idx >= 0; idx--) {
-            this.deleteCardAtIndex(idx, question.cards[idx].cardListType);
-        }
-    }
-
     toDeckArray(): Deck[] {
         const result: Deck[] = [];
         result.push(this);
diff --git a/src/FlashcardReviewSequencer.ts b/src/FlashcardReviewSequencer.ts
index 871c72b..072e43f 100644
--- a/src/FlashcardReviewSequencer.ts
+++ b/src/FlashcardReviewSequencer.ts
@@ -144,8 +144,7 @@ export class FlashcardReviewSequencer implements IFlashcardReviewSequencer {
             this.cardSequencer.nextCard();
         } else {
             if (this.settings.burySiblingCards) {
-                this.questionPostponementList.add(this.currentQuestion);
-                await this.questionPostponementList.write();
+                await this.burySiblingCards();
                 this.cardSequencer.deleteCurrentQuestion();
             } else {
                 this.deleteCurrentCard();
@@ -153,6 +152,17 @@ export class FlashcardReviewSequencer implements IFlashcardReviewSequencer {
         }
     }
 
+    private async burySiblingCards(): Promise<void> {
+        // We check if there are any sibling cards still in the deck,
+        // We do this because otherwise we would be adding every reviewed card to the postponement list, even for a
+        // question with a single card. That isn't consistent with the 1.10.1 behavior
+        const remaining = this.currentDeck.getQuestionCardCount(this.currentQuestion);
+        if (remaining > 1) {
+            this.questionPostponementList.add(this.currentQuestion);
+            await this.questionPostponementList.write();
+        }
+    }
+
     async processReview_CramMode(response: ReviewResponse): Promise<void> {
         if (response == ReviewResponse.Easy) this.deleteCurrentCard();
         else {
diff --git a/src/QuestionPostponementList.ts b/src/QuestionPostponementList.ts
index 5438616..cc0164c 100644
--- a/src/QuestionPostponementList.ts
+++ b/src/QuestionPostponementList.ts
@@ -21,7 +21,7 @@ export class QuestionPostponementList implements IQuestionPostponementList {
     }
 
     clear(): void {
-        this.list = [];
+        this.list.splice(0);
     }
 
     add(question: Question): void {
diff --git a/src/main.ts b/src/main.ts
index 80c4c6d..bf580b6 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -336,6 +336,9 @@ export default class SRPlugin extends Plugin {
         if (todayDate !== this.data.buryDate) {
             this.data.buryDate = todayDate;
             this.questionPostponementList.clear();
+
+            // The following isn't needed for plug-in functionality; but can aid during debugging
+            await this.savePluginData();
         }
 
         const notes: TFile[] = this.app.vault.getMarkdownFiles();


Test patch:
diff --git a/tests/unit/FlashcardReviewSequencer.test.ts b/tests/unit/FlashcardReviewSequencer.test.ts
index 923afdf..dad014d 100644
--- a/tests/unit/FlashcardReviewSequencer.test.ts
+++ b/tests/unit/FlashcardReviewSequencer.test.ts
@@ -534,7 +534,7 @@ Q1::A1
         });
 
         describe("Checking postponement list (after card reviewed, burySiblingCards=true)", () => {
-            test("reviewed question added to postponement list; sibling cards are buried", async () => {
+            test("Question with multiple cards; reviewed question added to postponement list; sibling cards are buried", async () => {
                 let settings: SRSettings = { ...DEFAULT_SETTINGS };
                 settings.burySiblingCards = true;
 
@@ -567,7 +567,7 @@ Q1::A1
                 checkQuestionPostponementListCount(c, 1);
             });
 
-            test("card reviewed as hard, after restarting the review process, that question skipped and next question is shown", async () => {
+            test("Question with multiple cards; card reviewed as hard, after restarting the review process, that whole question skipped and next question is shown", async () => {
                 let settings: SRSettings = { ...DEFAULT_SETTINGS };
                 settings.burySiblingCards = true;
 
@@ -632,6 +632,31 @@ Q1::A1
                 );
                 expect(c.reviewSequencer.currentCard.front).toEqual("Q1");
             });
+
+            test("Question with single cards; card reviewed as hard, the question is NOT added to the postponement list", async () => {
+                let settings: SRSettings = { ...DEFAULT_SETTINGS };
+                settings.burySiblingCards = true;
+
+                // Question with a single card
+                let text: string = `#flashcards Q1::A1`;
+
+                // Create the test context
+                setupStaticDateProvider_OriginDatePlusDays(0);
+                const c: TestContext = TestContext.Create(
+                    order_DueFirst_Sequential,
+                    FlashcardReviewMode.Review,
+                    settings,
+                    text,
+                );
+                await c.setSequencerDeckTreeFromOriginalText();
+                expect(c.reviewSequencer.currentCard.front).toEqual("Q1");
+
+                // Review the card
+                await c.reviewSequencer.processReview(ReviewResponse.Hard);
+
+                // Check that there are no questions on the postponement list
+                checkQuestionPostponementListCount(c, 0);
+            });
         });
     });