From 357e2cd5c8377941472b1978ce52492332a070b6 Mon Sep 17 00:00:00 2001 From: Alok Silswal Date: Thu, 5 Mar 2026 19:50:40 +0530 Subject: [PATCH] fix(instant-card): set cloze number when existing clozes are present If the initially input text had cloze deletions, the next cloze started at `{{c1::`, rather than the next ordinal in the text Added a unit test case for cloze numbering with existing clozes Added more unit test cases Amended logic w.r.t. feedback and added more unit tests Fixes 20350 --- .../InstantEditorViewModel.kt | 16 +++++++ .../InstantEditorViewModelTest.kt | 46 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/instantnoteeditor/InstantEditorViewModel.kt b/AnkiDroid/src/main/java/com/ichi2/anki/instantnoteeditor/InstantEditorViewModel.kt index 4d058abfa141..e83cc6f0bb66 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/instantnoteeditor/InstantEditorViewModel.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/instantnoteeditor/InstantEditorViewModel.kt @@ -17,6 +17,7 @@ package com.ichi2.anki.instantnoteeditor +import androidx.annotation.CheckResult import androidx.annotation.VisibleForTesting import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -26,6 +27,7 @@ import com.ichi2.anki.CollectionManager.withCol import com.ichi2.anki.NoteFieldsCheckResult import com.ichi2.anki.OnErrorListener import com.ichi2.anki.checkNoteFieldsResponse +import com.ichi2.anki.common.utils.ext.replaceWith import com.ichi2.anki.instantnoteeditor.InstantNoteEditorActivity.DialogType import com.ichi2.anki.libanki.DeckId import com.ichi2.anki.libanki.Note @@ -180,6 +182,18 @@ class InstantEditorViewModel : } } + /** + * Extracts the cloze ordinals from text (if any). + * + * `"{{c2::text}} {{c1::more}}"` => `[2, 1]` + */ + @CheckResult + private fun getClozeOrdinals(text: String): List = + clozePattern + .findAll(text) + .mapNotNull { it.groups[2]?.value?.toIntOrNull() } + .toList() + /** * Retrieves all cloze text fields from the current editor note's note type. * @@ -206,6 +220,8 @@ class InstantEditorViewModel : fun setClozeFieldText(text: String?) { _actualClozeFieldText.value = text + intClozeList.replaceWith(getClozeOrdinals(text ?: "")) + _currentClozeNumber.value = (intClozeList.maxOrNull() ?: 0) + 1 } /** diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/instanteditor/InstantEditorViewModelTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/instanteditor/InstantEditorViewModelTest.kt index 2e9604d12322..c62da3f09522 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/instanteditor/InstantEditorViewModelTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/anki/instanteditor/InstantEditorViewModelTest.kt @@ -213,6 +213,52 @@ class InstantEditorViewModelTest : RobolectricTest() { assertEquals(word, cleanWord) } + @Test + fun `cloze numbering continues from existing clozes`() = + runViewModelTest { + val text = + "This is the {{c1::first cloze}}. " + + "This should be the {{c2::second cloze}}. " + + "Similarly, this one as {{c3::third cloze}}." + + setClozeFieldText(text) + + val result = buildClozeText("fourth") + + assertEquals("{{c4::fourth}}", result) + } + + @Test + fun `removing a cloze does not reset numbering`() = + runViewModelTest { + val text = "{{c1::first}} {{c2::second}} {{c3::third}}" + + setClozeFieldText(text) + + val words = getWordsFromFieldText().toMutableList() + + words[1] = buildClozeText(words[1]) // remove c2 + + val result = buildClozeText("fourth") + + assertEquals("{{c4::fourth}}", result) + } + + @Test + fun `toggling cloze mode does not reset numbering`() = + runViewModelTest { + val text = "{{c1::first}} {{c2::second}} {{c3::third}}" + + setClozeFieldText(text) + + toggleClozeMode() // switch to NO_INCREMENT mode + toggleClozeMode() // switch back to INCREMENT mode + + val result = buildClozeText("fourth") + + assertEquals("{{c4::fourth}}", result) + } + private fun runViewModelTest( initViewModel: () -> InstantEditorViewModel = { InstantEditorViewModel() }, testBody: suspend InstantEditorViewModel.() -> Unit,