diff --git a/AnkiDroid/src/main/java/com/ichi2/widget/cardanalysis/CardAnalysisWidgetConfig.kt b/AnkiDroid/src/main/java/com/ichi2/widget/cardanalysis/CardAnalysisWidgetConfig.kt index 6bc18e2dd57f..b9bcabafae87 100644 --- a/AnkiDroid/src/main/java/com/ichi2/widget/cardanalysis/CardAnalysisWidgetConfig.kt +++ b/AnkiDroid/src/main/java/com/ichi2/widget/cardanalysis/CardAnalysisWidgetConfig.kt @@ -27,6 +27,7 @@ import android.view.View import android.widget.Button import androidx.activity.OnBackPressedCallback import androidx.annotation.StringRes +import androidx.annotation.VisibleForTesting import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager @@ -46,6 +47,7 @@ import com.ichi2.anki.snackbar.SnackbarBuilder import com.ichi2.anki.snackbar.showSnackbar import com.ichi2.widget.WidgetConfigScreenAdapter import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import timber.log.Timber @@ -67,6 +69,10 @@ class CardAnalysisWidgetConfig : AnkiActivity(), DeckSelectionListener, BaseSnac private lateinit var onBackPressedCallback: OnBackPressedCallback private val EXTRA_SELECTED_DECK_IDS = "card_analysis_widget_selected_deck_ids" + /** Tracks coroutine running [initializeUIComponents]: must be run on a non-empty collection */ + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + internal lateinit var initTask: Job + override fun onCreate(savedInstanceState: Bundle?) { if (showedActivityFailedScreen(savedInstanceState)) { return @@ -94,7 +100,7 @@ class CardAnalysisWidgetConfig : AnkiActivity(), DeckSelectionListener, BaseSnac } // Check if the collection is empty before proceeding and if the collection is empty, show a toast instead of the configuration view. - lifecycleScope.launch { + this.initTask = lifecycleScope.launch { if (isCollectionEmpty()) { Timber.w("Closing: Collection is empty") showThemedToast( @@ -126,7 +132,7 @@ class CardAnalysisWidgetConfig : AnkiActivity(), DeckSelectionListener, BaseSnac showSnackbar(getString(messageResId)) } - fun initializeUIComponents() { + private fun initializeUIComponents() { deckAdapter = WidgetConfigScreenAdapter { deck, _ -> deckAdapter.removeDeck(deck.deckId) showSnackbar(R.string.deck_removed_from_widget) diff --git a/AnkiDroid/src/main/java/com/ichi2/widget/deckpicker/DeckPickerWidgetConfig.kt b/AnkiDroid/src/main/java/com/ichi2/widget/deckpicker/DeckPickerWidgetConfig.kt index d980345f709b..033f44a93d42 100644 --- a/AnkiDroid/src/main/java/com/ichi2/widget/deckpicker/DeckPickerWidgetConfig.kt +++ b/AnkiDroid/src/main/java/com/ichi2/widget/deckpicker/DeckPickerWidgetConfig.kt @@ -26,6 +26,7 @@ import android.os.Bundle import android.view.View import android.widget.Button import androidx.activity.OnBackPressedCallback +import androidx.annotation.VisibleForTesting import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.ItemTouchHelper @@ -47,6 +48,7 @@ import com.ichi2.anki.snackbar.SnackbarBuilder import com.ichi2.anki.snackbar.showSnackbar import com.ichi2.widget.WidgetConfigScreenAdapter import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import timber.log.Timber @@ -71,6 +73,10 @@ class DeckPickerWidgetConfig : AnkiActivity(), DeckSelectionListener, BaseSnackb private var isAdapterObserverRegistered = false private lateinit var onBackPressedCallback: OnBackPressedCallback + /** Tracks coroutine running [initializeUIComponents]: must be run on a non-empty collection */ + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + internal lateinit var initTask: Job + override fun onCreate(savedInstanceState: Bundle?) { if (showedActivityFailedScreen(savedInstanceState)) { return @@ -98,7 +104,7 @@ class DeckPickerWidgetConfig : AnkiActivity(), DeckSelectionListener, BaseSnackb } // Check if the collection is empty before proceeding and if the collection is empty, show a toast instead of the configuration view. - lifecycleScope.launch { + this.initTask = lifecycleScope.launch { if (isCollectionEmpty()) { Timber.w("Closing: Collection is empty") showThemedToast( @@ -125,7 +131,7 @@ class DeckPickerWidgetConfig : AnkiActivity(), DeckSelectionListener, BaseSnackb showSnackbar(getString(messageResId)) } - fun initializeUIComponents() { + private fun initializeUIComponents() { deckAdapter = WidgetConfigScreenAdapter { deck, position -> deckAdapter.removeDeck(deck.deckId) showSnackbar(R.string.deck_removed_from_widget) diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/widget/cardanalysis/CardAnalysisWidgetConfigTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/widget/cardanalysis/CardAnalysisWidgetConfigTest.kt index 31e780b054ca..cc338d11eccc 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/widget/cardanalysis/CardAnalysisWidgetConfigTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/anki/widget/cardanalysis/CardAnalysisWidgetConfigTest.kt @@ -26,6 +26,7 @@ import com.ichi2.anki.RobolectricTest import com.ichi2.anki.dialogs.DeckSelectionDialog import com.ichi2.widget.cardanalysis.CardAnalysisWidgetConfig import com.ichi2.widget.cardanalysis.CardAnalysisWidgetPreferences +import kotlinx.coroutines.runBlocking import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.equalTo import org.junit.Before @@ -47,6 +48,8 @@ class CardAnalysisWidgetConfigTest : RobolectricTest() { @Before override fun setUp() { super.setUp() + ensureNonEmptyCollection() + val intent = Intent(targetContext, CardAnalysisWidgetConfig::class.java).apply { putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 1) } @@ -54,7 +57,7 @@ class CardAnalysisWidgetConfigTest : RobolectricTest() { activity = startActivityNormallyOpenCollectionWithIntent(CardAnalysisWidgetConfig::class.java, intent) // Ensure deckAdapter is initialized - activity.initializeUIComponents() + runBlocking { activity.initTask.join() } } /** diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/widget/deckpicker/DeckPickerWidgetConfigTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/widget/deckpicker/DeckPickerWidgetConfigTest.kt index 74730384093e..5757d6791736 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/widget/deckpicker/DeckPickerWidgetConfigTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/anki/widget/deckpicker/DeckPickerWidgetConfigTest.kt @@ -26,6 +26,7 @@ import com.ichi2.anki.RobolectricTest import com.ichi2.anki.dialogs.DeckSelectionDialog import com.ichi2.widget.deckpicker.DeckPickerWidgetConfig import com.ichi2.widget.deckpicker.DeckPickerWidgetPreferences +import kotlinx.coroutines.runBlocking import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.equalTo import org.junit.Before @@ -47,6 +48,8 @@ class DeckPickerWidgetConfigTest : RobolectricTest() { @Before override fun setUp() { super.setUp() + ensureNonEmptyCollection() + val intent = Intent(targetContext, DeckPickerWidgetConfig::class.java).apply { putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 1) } @@ -54,7 +57,7 @@ class DeckPickerWidgetConfigTest : RobolectricTest() { activity = startActivityNormallyOpenCollectionWithIntent(DeckPickerWidgetConfig::class.java, intent) // Ensure deckAdapter is initialized - activity.initializeUIComponents() + runBlocking { activity.initTask.join() } } /** diff --git a/AnkiDroid/src/test/java/com/ichi2/testutils/TestClass.kt b/AnkiDroid/src/test/java/com/ichi2/testutils/TestClass.kt index 205cbafd3169..a14eee7c81ec 100644 --- a/AnkiDroid/src/test/java/com/ichi2/testutils/TestClass.kt +++ b/AnkiDroid/src/test/java/com/ichi2/testutils/TestClass.kt @@ -18,6 +18,7 @@ package com.ichi2.testutils import com.ichi2.anki.CollectionManager import com.ichi2.anki.ioDispatcher +import com.ichi2.anki.isCollectionEmpty import com.ichi2.libanki.Card import com.ichi2.libanki.Collection import com.ichi2.libanki.Consts @@ -144,6 +145,11 @@ interface TestClass { } } + /** Ensures [isCollectionEmpty] returns `false` */ + fun ensureNonEmptyCollection() { + addNotes(1) + } + fun selectDefaultDeck() { col.decks.select(Consts.DEFAULT_DECK_ID) }