Skip to content

Commit bcfa684

Browse files
committed
Add unit tests for data-modifying methods
1 parent 3fc0699 commit bcfa684

2 files changed

Lines changed: 141 additions & 2 deletions

File tree

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
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// SPDX-FileCopyrightText: 2026 Vedant Kakade <vedantkakade05@gmail.com>
2+
// SPDX-License-Identifier: GPL-3.0-or-later
3+
4+
package com.ichi2.anki.provider
5+
6+
import android.content.ContentValues
7+
import android.net.Uri
8+
import androidx.test.ext.junit.runners.AndroidJUnit4
9+
import com.ichi2.anki.FlashCardsContract
10+
import com.ichi2.anki.RobolectricTest
11+
import com.ichi2.testutils.grantPermissions
12+
import com.ichi2.testutils.subscriberChangeCounter
13+
import org.hamcrest.MatcherAssert.assertThat
14+
import org.hamcrest.Matchers.equalTo
15+
import org.junit.Before
16+
import org.junit.Test
17+
import org.junit.runner.RunWith
18+
19+
@RunWith(AndroidJUnit4::class)
20+
class CardContentProviderTest : RobolectricTest() {
21+
@Before
22+
override fun setUp() {
23+
super.setUp()
24+
25+
grantPermissions(FlashCardsContract.READ_WRITE_PERMISSION)
26+
}
27+
28+
@Test
29+
fun `insert notifies UI`() {
30+
val counter = subscriberChangeCounter()
31+
val mid = col.notetypes.all()[0].id
32+
33+
val noteType = col.notetypes.get(mid)!!
34+
val fieldCount = noteType.fields.length()
35+
val emptyFields = Array(fieldCount) { "" }.joinToString(separator = "\u001f")
36+
37+
val values =
38+
ContentValues().apply {
39+
put(FlashCardsContract.Note.MID, mid)
40+
put(FlashCardsContract.Note.FLDS, emptyFields)
41+
}
42+
43+
targetContext.contentResolver.insert(FlashCardsContract.Note.CONTENT_URI, values)
44+
45+
advanceRobolectricLooper()
46+
assertThat("Insert should notify UI", counter.hasChanges, equalTo(true))
47+
}
48+
49+
@Test
50+
fun `update notifies UI`() {
51+
val note = addBasicNote("f1", "b1")
52+
val counter = subscriberChangeCounter()
53+
val values =
54+
ContentValues().apply {
55+
put(FlashCardsContract.Note.TAGS, "new_tag")
56+
}
57+
58+
val uri = Uri.withAppendedPath(FlashCardsContract.Note.CONTENT_URI, note.id.toString())
59+
60+
targetContext.contentResolver.update(uri, values, null, null)
61+
62+
advanceRobolectricLooper()
63+
assertThat("Update should notify UI", counter.hasChanges, equalTo(true))
64+
}
65+
66+
@Test
67+
fun `update non-existent note does not notify UI`() {
68+
val counter = subscriberChangeCounter()
69+
val values =
70+
ContentValues().apply {
71+
put(FlashCardsContract.Note.TAGS, "new_tag")
72+
}
73+
val uri = Uri.withAppendedPath(FlashCardsContract.Note.CONTENT_URI, "999999")
74+
75+
try {
76+
targetContext.contentResolver.update(uri, values, null, null)
77+
} catch (e: Exception) {
78+
// BackendNotException is expected
79+
}
80+
81+
advanceRobolectricLooper()
82+
assertThat("Update should not notify UI if update is failed", counter.hasChanges, equalTo(false))
83+
}
84+
85+
@Test
86+
fun `delete notifies UI`() {
87+
val note = addBasicNote("f1", "b1")
88+
val counter = subscriberChangeCounter()
89+
90+
val uri = Uri.withAppendedPath(FlashCardsContract.Note.CONTENT_URI, note.id.toString())
91+
92+
targetContext.contentResolver.delete(uri, null, null)
93+
94+
advanceRobolectricLooper()
95+
assertThat("Delete should notify UI", counter.hasChanges, equalTo(true))
96+
}
97+
98+
@Test
99+
fun `delete non-existent note does not notify UI`() {
100+
val counter = subscriberChangeCounter()
101+
val uri = Uri.withAppendedPath(FlashCardsContract.Note.CONTENT_URI, "999999")
102+
103+
targetContext.contentResolver.delete(uri, null, null)
104+
105+
advanceRobolectricLooper()
106+
assertThat("Delete should not notify UI if nothing was deleted", counter.hasChanges, equalTo(false))
107+
}
108+
109+
@Test
110+
fun `bulkInsert notifies UI`() {
111+
val counter = subscriberChangeCounter()
112+
val mid = col.notetypes.all()[0].id
113+
114+
val noteType = col.notetypes.get(mid)!!
115+
val fieldCount = noteType.fields.length()
116+
val emptyFields = Array(fieldCount) { "" }.joinToString(separator = "\u001f")
117+
118+
val values =
119+
arrayOf(
120+
ContentValues().apply {
121+
put(FlashCardsContract.Note.MID, mid)
122+
put(FlashCardsContract.Note.FLDS, emptyFields)
123+
},
124+
)
125+
126+
targetContext.contentResolver.bulkInsert(FlashCardsContract.Note.CONTENT_URI, values)
127+
128+
advanceRobolectricLooper()
129+
assertThat(" Bulk insert should notify UI", counter.hasChanges, equalTo(true))
130+
}
131+
132+
@Test
133+
fun `bulkInsert with empty list does not notify UI`() {
134+
val counter = subscriberChangeCounter()
135+
136+
targetContext.contentResolver.bulkInsert(FlashCardsContract.Note.CONTENT_URI, emptyArray())
137+
138+
assertThat("Empty bulk insert should not notify UI", counter.hasChanges, equalTo(false))
139+
}
140+
}

0 commit comments

Comments
 (0)