@@ -24,9 +24,11 @@ import android.database.Cursor
2424import android.database.CursorWindow
2525import android.net.Uri
2626import anki.cards.FsrsMemoryState
27+ import anki.collection.OpChanges
2728import anki.notetypes.StockNotetype
2829import com.ichi2.anki.CollectionManager
2930import com.ichi2.anki.FlashCardsContract
31+ import com.ichi2.anki.common.time.TimeManager
3032import com.ichi2.anki.common.utils.annotation.KotlinCleanup
3133import com.ichi2.anki.common.utils.emptyStringArray
3234import com.ichi2.anki.libanki.Card
@@ -44,6 +46,7 @@ import com.ichi2.anki.libanki.backend.BackendUtils
4446import com.ichi2.anki.libanki.exception.ConfirmModSchemaException
4547import com.ichi2.anki.libanki.getStockNotetype
4648import com.ichi2.anki.libanki.sched.Scheduler
49+ import com.ichi2.anki.observability.ChangeManager
4750import com.ichi2.anki.provider.pureAnswer
4851import com.ichi2.anki.testutil.DatabaseUtils.cursorFillWindow
4952import com.ichi2.anki.testutil.GrantStoragePermission.storagePermission
@@ -70,6 +73,7 @@ import org.junit.Before
7073import org.junit.Rule
7174import org.junit.Test
7275import timber.log.Timber
76+ import kotlin.test.assertFailsWith
7377import kotlin.test.assertNotNull
7478import kotlin.test.assertTrue
7579import kotlin.test.junit.JUnitAsserter.assertNotNull
@@ -2462,6 +2466,160 @@ class ContentProviderTest : InstrumentedTest() {
24622466 return col
24632467 }
24642468
2469+ @Test
2470+ fun testInsertNotifiesUI () {
2471+ val counter = TestSubscriber ()
2472+ ChangeManager .subscribe(counter)
2473+ try {
2474+ val mid = noteTypeId
2475+ val noteType = col.notetypes.get(mid)!!
2476+ val fieldCount = noteType.fields.length()
2477+ val emptyFields = Array (fieldCount) { " " }.joinToString(separator = " \u001f " )
2478+
2479+ val values =
2480+ ContentValues ().apply {
2481+ put(FlashCardsContract .Note .MID , mid)
2482+ put(FlashCardsContract .Note .FLDS , emptyFields)
2483+ }
2484+
2485+ contentResolver.insert(FlashCardsContract .Note .CONTENT_URI , values)
2486+ assertNotificationReceived(counter)
2487+ } finally {
2488+ ChangeManager .unsubscribe(counter)
2489+ }
2490+ }
2491+
2492+ @Test
2493+ fun testUpdateNotifiesUI () {
2494+ val noteId = createdNotes.first().lastPathSegment!! .toLong()
2495+ val uri = Uri .withAppendedPath(FlashCardsContract .Note .CONTENT_URI , noteId.toString())
2496+ val values =
2497+ ContentValues ().apply {
2498+ put(FlashCardsContract .Note .TAGS , " new_tag" )
2499+ }
2500+ val counter = TestSubscriber ()
2501+ ChangeManager .subscribe(counter)
2502+ try {
2503+ contentResolver.update(uri, values, null , null )
2504+ assertNotificationReceived(counter)
2505+ } finally {
2506+ ChangeManager .unsubscribe(counter)
2507+ }
2508+ }
2509+
2510+ @Test
2511+ fun testUpdateNonExistentNoteDoesNotNotifyUI () {
2512+ val counter = TestSubscriber ()
2513+ ChangeManager .subscribe(counter)
2514+ try {
2515+ val values =
2516+ ContentValues ().apply {
2517+ put(FlashCardsContract .Note .TAGS , " new_tag" )
2518+ }
2519+ val uri = Uri .withAppendedPath(FlashCardsContract .Note .CONTENT_URI , " 999999" )
2520+
2521+ assertFailsWith<Exception > {
2522+ contentResolver.update(uri, values, null , null )
2523+ }
2524+
2525+ Thread .sleep(1000 )
2526+ assertEquals(" UI should not be notified if update is failed" , 0 , counter.count)
2527+ } finally {
2528+ ChangeManager .unsubscribe(counter)
2529+ }
2530+ }
2531+
2532+ @Test
2533+ fun testDeleteNotifiesUI () {
2534+ val noteId = createdNotes.first().lastPathSegment!! .toLong()
2535+ val uri = Uri .withAppendedPath(FlashCardsContract .Note .CONTENT_URI , noteId.toString())
2536+ val counter = TestSubscriber ()
2537+ ChangeManager .subscribe(counter)
2538+ try {
2539+ contentResolver.delete(uri, null , null )
2540+ assertNotificationReceived(counter)
2541+ } finally {
2542+ ChangeManager .unsubscribe(counter)
2543+ }
2544+ }
2545+
2546+ @Test
2547+ fun testDeleteNonExistentNoteDoesNotNotifyUI () {
2548+ val counter = TestSubscriber ()
2549+ ChangeManager .subscribe(counter)
2550+ try {
2551+ val uri = Uri .withAppendedPath(FlashCardsContract .Note .CONTENT_URI , " 999999" )
2552+ val deletedCount = contentResolver.delete(uri, null , null )
2553+ assertEquals(" It should return 0 for non-existent note" , 0 , deletedCount)
2554+
2555+ Thread .sleep(1000 )
2556+ assertEquals(" UI should not be notify if nothing was deleted" , 0 , counter.count)
2557+ } finally {
2558+ ChangeManager .unsubscribe(counter)
2559+ }
2560+ }
2561+
2562+ @Test
2563+ fun testBulkInsertNotifiesUI () {
2564+ val counter = TestSubscriber ()
2565+ ChangeManager .subscribe(counter)
2566+ try {
2567+ val mid = noteTypeId
2568+ val noteType = col.notetypes.get(mid)!!
2569+ val fieldCount = noteType.fields.length()
2570+ val emptyFields = Array (fieldCount) { " " }.joinToString(separator = " \u001f " )
2571+
2572+ val values =
2573+ arrayOf(
2574+ ContentValues ().apply {
2575+ put(FlashCardsContract .Note .MID , mid)
2576+ put(FlashCardsContract .Note .FLDS , emptyFields)
2577+ },
2578+ )
2579+
2580+ contentResolver.bulkInsert(FlashCardsContract .Note .CONTENT_URI , values)
2581+ assertNotificationReceived(counter)
2582+ } finally {
2583+ ChangeManager .unsubscribe(counter)
2584+ }
2585+ }
2586+
2587+ @Test
2588+ fun testBulkInsertEmptyListDoesNotNotifyUI () {
2589+ val counter = TestSubscriber ()
2590+ ChangeManager .subscribe(counter)
2591+ try {
2592+ contentResolver.bulkInsert(FlashCardsContract .Note .CONTENT_URI , emptyArray())
2593+
2594+ Thread .sleep(1000 )
2595+ assertEquals(" UI should not be notified for empty bulk insert" , 0 , counter.count)
2596+ } finally {
2597+ ChangeManager .unsubscribe(counter)
2598+ }
2599+ }
2600+
2601+ // TODO: PERF: use TestChangeSubscriber once we've moved to testFixtures
2602+ private class TestSubscriber : ChangeManager .Subscriber {
2603+ var count = 0
2604+
2605+ override fun opExecuted (
2606+ changes : OpChanges ,
2607+ handler : Any? ,
2608+ ) {
2609+ count++
2610+ }
2611+ }
2612+
2613+ private fun assertNotificationReceived (subscriber : TestSubscriber ) {
2614+ val timeout = 5000L
2615+ val startTime = TimeManager .time.intTimeMS()
2616+ while (subscriber.count == 0 && TimeManager .time.intTimeMS() - startTime < timeout) {
2617+ Thread .sleep(100 )
2618+ }
2619+
2620+ assertTrue(" UI should be notified of the change" , subscriber.count > 0 )
2621+ }
2622+
24652623 private val contentResolver: ContentResolver
24662624 get() = testContext.contentResolver
24672625
0 commit comments