Skip to content

Commit da6c35b

Browse files
committed
Add Instrumented tests for data-modifying methods
1 parent 3fc0699 commit da6c35b

2 files changed

Lines changed: 165 additions & 2 deletions

File tree

AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/ContentProviderTest.kt

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ import android.database.Cursor
2424
import android.database.CursorWindow
2525
import android.net.Uri
2626
import anki.cards.FsrsMemoryState
27+
import anki.collection.OpChanges
2728
import anki.notetypes.StockNotetype
2829
import com.ichi2.anki.CollectionManager
2930
import com.ichi2.anki.FlashCardsContract
31+
import com.ichi2.anki.common.time.TimeManager
3032
import com.ichi2.anki.common.utils.annotation.KotlinCleanup
3133
import com.ichi2.anki.common.utils.emptyStringArray
3234
import com.ichi2.anki.libanki.Card
@@ -44,6 +46,7 @@ import com.ichi2.anki.libanki.backend.BackendUtils
4446
import com.ichi2.anki.libanki.exception.ConfirmModSchemaException
4547
import com.ichi2.anki.libanki.getStockNotetype
4648
import com.ichi2.anki.libanki.sched.Scheduler
49+
import com.ichi2.anki.observability.ChangeManager
4750
import com.ichi2.anki.provider.pureAnswer
4851
import com.ichi2.anki.testutil.DatabaseUtils.cursorFillWindow
4952
import com.ichi2.anki.testutil.GrantStoragePermission.storagePermission
@@ -70,6 +73,7 @@ import org.junit.Before
7073
import org.junit.Rule
7174
import org.junit.Test
7275
import timber.log.Timber
76+
import kotlin.test.assertFailsWith
7377
import kotlin.test.assertNotNull
7478
import kotlin.test.assertTrue
7579
import kotlin.test.junit.JUnitAsserter.assertNotNull
@@ -2462,6 +2466,166 @@ class ContentProviderTest : InstrumentedTest() {
24622466
return col
24632467
}
24642468

2469+
@Test
2470+
fun testInsertNotifiesUI() {
2471+
// TODO: PERF: use TestChangeSubscriber once we've moved to testFixtures
2472+
val counter = TestSubscriber()
2473+
ChangeManager.subscribe(counter)
2474+
try {
2475+
val mid = noteTypeId
2476+
val noteType = col.notetypes.get(mid)!!
2477+
val fieldCount = noteType.fields.length()
2478+
val emptyFields = Array(fieldCount) { "" }.joinToString(separator = "\u001f")
2479+
2480+
val values =
2481+
ContentValues().apply {
2482+
put(FlashCardsContract.Note.MID, mid)
2483+
put(FlashCardsContract.Note.FLDS, emptyFields)
2484+
}
2485+
2486+
contentResolver.insert(FlashCardsContract.Note.CONTENT_URI, values)
2487+
assertNotificationReceived(counter)
2488+
} finally {
2489+
ChangeManager.unsubscribe(counter)
2490+
}
2491+
}
2492+
2493+
@Test
2494+
fun testUpdateNotifiesUI() {
2495+
// TODO: PERF: use TestChangeSubscriber once we've moved to testFixtures
2496+
val noteId = createdNotes.first().lastPathSegment!!.toLong()
2497+
val uri = Uri.withAppendedPath(FlashCardsContract.Note.CONTENT_URI, noteId.toString())
2498+
val values =
2499+
ContentValues().apply {
2500+
put(FlashCardsContract.Note.TAGS, "new_tag")
2501+
}
2502+
val counter = TestSubscriber()
2503+
ChangeManager.subscribe(counter)
2504+
try {
2505+
contentResolver.update(uri, values, null, null)
2506+
assertNotificationReceived(counter)
2507+
} finally {
2508+
ChangeManager.unsubscribe(counter)
2509+
}
2510+
}
2511+
2512+
@Test
2513+
fun testUpdateNonExistentNoteDoesNotNotifyUI() {
2514+
// TODO: PERF: use TestChangeSubscriber once we've moved to testFixtures
2515+
val counter = TestSubscriber()
2516+
ChangeManager.subscribe(counter)
2517+
try {
2518+
val values =
2519+
ContentValues().apply {
2520+
put(FlashCardsContract.Note.TAGS, "new_tag")
2521+
}
2522+
val uri = Uri.withAppendedPath(FlashCardsContract.Note.CONTENT_URI, "999999")
2523+
2524+
assertFailsWith<Exception> {
2525+
contentResolver.update(uri, values, null, null)
2526+
}
2527+
2528+
Thread.sleep(1000)
2529+
assertEquals("UI should not be notified if update is failed", 0, counter.count)
2530+
} finally {
2531+
ChangeManager.unsubscribe(counter)
2532+
}
2533+
}
2534+
2535+
@Test
2536+
fun testDeleteNotifiesUI() {
2537+
// TODO: PERF: use TestChangeSubscriber once we've moved to testFixtures
2538+
val noteId = createdNotes.first().lastPathSegment!!.toLong()
2539+
val uri = Uri.withAppendedPath(FlashCardsContract.Note.CONTENT_URI, noteId.toString())
2540+
val counter = TestSubscriber()
2541+
ChangeManager.subscribe(counter)
2542+
try {
2543+
contentResolver.delete(uri, null, null)
2544+
assertNotificationReceived(counter)
2545+
} finally {
2546+
ChangeManager.unsubscribe(counter)
2547+
}
2548+
}
2549+
2550+
@Test
2551+
fun testDeleteNonExistentNoteDoesNotNotifyUI() {
2552+
// TODO: PERF: use TestChangeSubscriber once we've moved to testFixtures
2553+
val counter = TestSubscriber()
2554+
ChangeManager.subscribe(counter)
2555+
try {
2556+
val uri = Uri.withAppendedPath(FlashCardsContract.Note.CONTENT_URI, "999999")
2557+
val deletedCount = contentResolver.delete(uri, null, null)
2558+
assertEquals("It should return 0 for non-existent note", 0, deletedCount)
2559+
2560+
Thread.sleep(1000)
2561+
assertEquals("UI should not be notify if nothing was deleted", 0, counter.count)
2562+
} finally {
2563+
ChangeManager.unsubscribe(counter)
2564+
}
2565+
}
2566+
2567+
@Test
2568+
fun testBulkInsertNotifiesUI() {
2569+
// TODO: PERF: use TestChangeSubscriber once we've moved to testFixtures
2570+
val counter = TestSubscriber()
2571+
ChangeManager.subscribe(counter)
2572+
try {
2573+
val mid = noteTypeId
2574+
val noteType = col.notetypes.get(mid)!!
2575+
val fieldCount = noteType.fields.length()
2576+
val emptyFields = Array(fieldCount) { "" }.joinToString(separator = "\u001f")
2577+
2578+
val values =
2579+
arrayOf(
2580+
ContentValues().apply {
2581+
put(FlashCardsContract.Note.MID, mid)
2582+
put(FlashCardsContract.Note.FLDS, emptyFields)
2583+
},
2584+
)
2585+
2586+
contentResolver.bulkInsert(FlashCardsContract.Note.CONTENT_URI, values)
2587+
assertNotificationReceived(counter)
2588+
} finally {
2589+
ChangeManager.unsubscribe(counter)
2590+
}
2591+
}
2592+
2593+
@Test
2594+
fun testBulkInsertEmptyListDoesNotNotifyUI() {
2595+
// TODO: PERF: use TestChangeSubscriber once we've moved to testFixtures -
2596+
val counter = TestSubscriber()
2597+
ChangeManager.subscribe(counter)
2598+
try {
2599+
contentResolver.bulkInsert(FlashCardsContract.Note.CONTENT_URI, emptyArray())
2600+
2601+
Thread.sleep(1000)
2602+
assertEquals("UI should not be notified for empty bulk insert", 0, counter.count)
2603+
} finally {
2604+
ChangeManager.unsubscribe(counter)
2605+
}
2606+
}
2607+
2608+
private class TestSubscriber : ChangeManager.Subscriber {
2609+
var count = 0
2610+
2611+
override fun opExecuted(
2612+
changes: OpChanges,
2613+
handler: Any?,
2614+
) {
2615+
count++
2616+
}
2617+
}
2618+
2619+
private fun assertNotificationReceived(subscriber: TestSubscriber) {
2620+
val timeout = 5000L
2621+
val startTime = TimeManager.time.intTimeMS()
2622+
while (subscriber.count == 0 && TimeManager.time.intTimeMS() - startTime < timeout) {
2623+
Thread.sleep(100)
2624+
}
2625+
2626+
assertTrue("UI should be notified of the change", subscriber.count > 0)
2627+
}
2628+
24652629
private val contentResolver: ContentResolver
24662630
get() = testContext.contentResolver
24672631

AnkiDroid/src/main/java/com/ichi2/anki/provider/CardContentProvider.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -754,8 +754,7 @@ class CardContentProvider : ContentProvider() {
754754
val deletedCount =
755755
when (sUriMatcher.match(uri)) {
756756
NOTES_ID -> {
757-
col.removeNotes(noteIds = listOf(uri.pathSegments[1].toLong()))
758-
1
757+
col.removeNotes(noteIds = listOf(uri.pathSegments[1].toLong())).count
759758
}
760759
NOTE_TYPES_ID_EMPTY_CARDS -> {
761760
val noteType = col.notetypes.get(getNoteTypeIdFromUri(uri, col)) ?: return -1

0 commit comments

Comments
 (0)