diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditorFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditorFragment.kt index 366d9ee231ff..8b173b51a9aa 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditorFragment.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditorFragment.kt @@ -328,40 +328,46 @@ class NoteEditorFragment : private val requestTemplateEditLauncher = registerForActivityResult( ActivityResultContracts.StartActivityForResult(), - NoteEditorActivityResultCallback { - // Note type can change regardless of exit type - update ourselves and CardBrowser - reloadRequired = true - editorNote!!.notetype = getColUnsafe.notetypes.get(editorNote!!.noteTypeId)!! - if (currentEditedCard == null || - !editorNote!! - .cardIds(getColUnsafe) - .contains(currentEditedCard!!.id) - ) { - if (!addNote) { - /* This can occur, for example, if the - * card type was deleted or if the note - * type was changed without moving this - * card to another type. */ - Timber.d("onActivityResult() template edit return - current card is gone, close note editor") - showSnackbar(getString(R.string.template_for_current_card_deleted)) - closeNoteEditor() - } else { - Timber.d("onActivityResult() template edit return, in add mode, just re-display") - updateCards(editorNote!!.notetype) - } - } else { - Timber.d("onActivityResult() template edit return - current card exists") - // reload current card - the template ordinals are possibly different post-edit - currentEditedCard = getColUnsafe.getCard(currentEditedCard!!.id) - @NeedsTest("#17282 returning from template editor saves further made changes") - // make sure the card's note is available going forward - currentEditedCard!!.note(getColUnsafe) - editorNote = currentEditedCard!!.note // update the NoteEditor's working note reference - updateCards(editorNote!!.notetype) - } - }, + NoteEditorActivityResultCallback { onTemplateEditorResult() }, ) + /** Handles a return from the [CardTemplateEditor] */ + @VisibleForTesting + internal fun onTemplateEditorResult() { + // Note type can change regardless of exit type - update ourselves and CardBrowser + reloadRequired = true + editorNote!!.notetype = getColUnsafe.notetypes.get(editorNote!!.noteTypeId)!! + if (currentEditedCard == null || + !editorNote!! + .cardIds(getColUnsafe) + .contains(currentEditedCard!!.id) + ) { + if (!addNote) { + /* This can occur, for example, if the + * card type was deleted or if the note + * type was changed without moving this + * card to another type. */ + Timber.d("onActivityResult() template edit return - current card is gone, close note editor") + showSnackbar(getString(R.string.template_for_current_card_deleted)) + closeNoteEditor() + } else { + Timber.d("onActivityResult() template edit return, in add mode, just re-display") + // the fields may have changed, so rebuild them + refreshNoteData(FieldChangeType.changeFieldCount(shouldReplaceNewlines())) + } + } else { + Timber.d("onActivityResult() template edit return - current card exists") + // reload current card - the template ordinals are possibly different post-edit + currentEditedCard = getColUnsafe.getCard(currentEditedCard!!.id) + @NeedsTest("#17282 returning from template editor saves further made changes") + // make sure the card's note is available going forward + currentEditedCard!!.note(getColUnsafe) + editorNote = currentEditedCard!!.note // update the NoteEditor's working note reference + // the fields may have changed, so rebuild them + setNote(editorNote, FieldChangeType.changeFieldCount(shouldReplaceNewlines())) + } + } + private val ioEditorLauncher = registerForActivityResult( ActivityResultContracts.GetContent(), diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/NoteEditorTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/NoteEditorTest.kt index 5e64b85d57cc..a9c50cff934c 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/NoteEditorTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/anki/NoteEditorTest.kt @@ -4,6 +4,7 @@ package com.ichi2.anki +import android.annotation.SuppressLint import android.app.Activity import android.content.ClipData import android.content.Intent @@ -19,6 +20,8 @@ import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.ext.junit.runners.AndroidJUnit4 import anki.config.ConfigKey +import anki.notetypes.StockNotetype +import anki.notetypes.notetypeId import com.ichi2.anki.NoteEditorTest.FromScreen.DECK_LIST import com.ichi2.anki.NoteEditorTest.FromScreen.REVIEWER import com.ichi2.anki.api.AddContentApi.Companion.DEFAULT_DECK_ID @@ -65,6 +68,49 @@ class NoteEditorTest : RobolectricTest() { ) } + @Test // #20510 + fun `editing - fields are refreshed after the template editor restores the note type to default`() { + val basic = col.notetypes.byName("Basic")!! + col.notetypes.addFieldLegacy(basic, col.notetypes.newField("ThirdField")) + + val editor = getNoteEditorEditingExistingBasicNote("Hello", "World", REVIEWER) + assertThat("field count before restore", editor.editFields!!.size, equalTo(3)) + + // simulate 'Restore to Default' being performed inside the CardTemplateEditor + @SuppressLint("CheckResult") + col.notetypes.restoreNotetypeToStock( + notetypeId { ntid = basic.id }, + StockNotetype.Kind.KIND_BASIC, + ) + editor.onTemplateEditorResult() + + assertThat("field count after restore", editor.editFields!!.size, equalTo(2)) + assertThat(editor.currentFieldStrings.toList(), contains("Hello", "World")) + } + + @Test // #20510 + fun `adding - fields are refreshed after the template editor restores the note type to default`() { + val basic = col.notetypes.byName("Basic")!! + col.notetypes.addFieldLegacy(basic, col.notetypes.newField("ThirdField")) + + val editor = + getNoteEditorAdding(NoteType.BASIC) + .withFirstField("Hello") + .build() + assertThat("field count before restore", editor.editFields!!.size, equalTo(3)) + + // simulate 'Restore to Default' being performed inside the CardTemplateEditor + @SuppressLint("CheckResult") + col.notetypes.restoreNotetypeToStock( + notetypeId { ntid = basic.id }, + StockNotetype.Kind.KIND_BASIC, + ) + editor.onTemplateEditorResult() + + assertThat("field count after restore", editor.editFields!!.size, equalTo(2)) + assertThat("typed text is preserved", editor.currentFieldStrings[0], equalTo("Hello")) + } + @Test fun errorSavingNoteWithNoFirstFieldDisplaysNoFirstField() = runTest { diff --git a/libanki/src/main/java/com/ichi2/anki/libanki/Collection.kt b/libanki/src/main/java/com/ichi2/anki/libanki/Collection.kt index e8131fbcec4a..2a38871aae7a 100644 --- a/libanki/src/main/java/com/ichi2/anki/libanki/Collection.kt +++ b/libanki/src/main/java/com/ichi2/anki/libanki/Collection.kt @@ -1185,14 +1185,26 @@ class Collection( * directly mutating the database). */ @LibAnkiAlias("undo") - fun undo(): OpChangesAfterUndo = backend.undo() + fun undo(): OpChangesAfterUndo { + val out = backend.undo() + if (out.changes.notetype) { + notetypes.clearCache() + } + return out + } /** * Returns result of backend redo operation, or throws UndoEmpty. */ @RustCleanup("document exception") @LibAnkiAlias("redo") - fun redo(): OpChangesAfterUndo = backend.redo() + fun redo(): OpChangesAfterUndo { + val out = backend.redo() + if (out.changes.notetype) { + notetypes.clearCache() + } + return out + } @Deprecated("Not implemented") @LibAnkiAlias("op_made_changes") diff --git a/libanki/src/test/java/com/ichi2/anki/libanki/NotetypesTest.kt b/libanki/src/test/java/com/ichi2/anki/libanki/NotetypesTest.kt index 8c48dda5a80c..0550b677777d 100644 --- a/libanki/src/test/java/com/ichi2/anki/libanki/NotetypesTest.kt +++ b/libanki/src/test/java/com/ichi2/anki/libanki/NotetypesTest.kt @@ -16,6 +16,9 @@ package com.ichi2.anki.libanki +import android.annotation.SuppressLint +import anki.notetypes.StockNotetype +import anki.notetypes.notetypeId import com.ichi2.anki.libanki.testutils.InMemoryAnkiTest import net.ankiweb.rsdroid.exceptions.BackendInvalidInputException import net.ankiweb.rsdroid.exceptions.BackendNotFoundException @@ -25,6 +28,32 @@ import org.junit.Test import kotlin.test.assertFailsWith class NotetypesTest : InMemoryAnkiTest() { + @Test // #20510 + fun `notetype cache is invalidated on undo and redo`() { + val notetype = col.notetypes.basic + + fun fieldCount() = + col.notetypes + .get(notetype.id)!! + .fields.size + + col.notetypes.addFieldLegacy(notetype, col.notetypes.newField("NewField")) + assertThat("field added", fieldCount(), equalTo(3)) + + @SuppressLint("CheckResult") + col.notetypes.restoreNotetypeToStock( + notetypeId { ntid = notetype.id }, + forceKind = StockNotetype.Kind.KIND_BASIC, + ) + assertThat("fields after restore", fieldCount(), equalTo(2)) + + col.undo() + assertThat("fields after undo", fieldCount(), equalTo(3)) + + col.redo() + assertThat("fields after redo", fieldCount(), equalTo(2)) + } + @Test fun `getSingleNotetypeOfNotes - multiple`() { val notes = addNotes(2)