diff --git a/library-test/src/main/java/com/alexstyl/contactstore/test/StoredContact.kt b/library-test/src/main/java/com/alexstyl/contactstore/test/StoredContact.kt index 3aaf115a..3e9e5003 100644 --- a/library-test/src/main/java/com/alexstyl/contactstore/test/StoredContact.kt +++ b/library-test/src/main/java/com/alexstyl/contactstore/test/StoredContact.kt @@ -1,38 +1,52 @@ package com.alexstyl.contactstore.test import android.provider.ContactsContract +import com.alexstyl.contactstore.Contact +import com.alexstyl.contactstore.ContactColumn import com.alexstyl.contactstore.EventDate import com.alexstyl.contactstore.GroupMembership +import com.alexstyl.contactstore.ImAddress import com.alexstyl.contactstore.ImageData import com.alexstyl.contactstore.LabeledValue +import com.alexstyl.contactstore.LinkedAccountValue +import com.alexstyl.contactstore.LookupKey import com.alexstyl.contactstore.MailAddress import com.alexstyl.contactstore.Note import com.alexstyl.contactstore.PhoneNumber import com.alexstyl.contactstore.PostalAddress +import com.alexstyl.contactstore.Relation +import com.alexstyl.contactstore.SipAddress import com.alexstyl.contactstore.WebAddress public data class StoredContact( - val contactId: Long, - val isStarred: Boolean = false, - val prefix: String? = null, - val firstName: String? = null, - val middleName: String? = null, - val lastName: String? = null, - val suffix: String? = null, - val phoneticFirstName: String? = null, - val phoneticMiddleName: String? = null, - val phoneticLastName: String? = null, - val imageData: ImageData? = null, - val organization: String? = null, - val jobTitle: String? = null, - val webAddresses: List> = emptyList(), - val phones: List> = emptyList(), - val mails: List> = emptyList(), - val events: List> = emptyList(), - val postalAddresses: List> = emptyList(), - val note: Note? = null, - val nickname: String? = null, - val fullNameStyle: Int = ContactsContract.FullNameStyle.UNDEFINED, - val phoneticNameStyle: Int = ContactsContract.PhoneticNameStyle.UNDEFINED, - val groups: List = emptyList() -) \ No newline at end of file + override val contactId: Long, + override val isStarred: Boolean = false, + override val prefix: String? = null, + override val firstName: String? = null, + override val middleName: String? = null, + override val lastName: String? = null, + override val suffix: String? = null, + override val phoneticFirstName: String? = null, + override val phoneticMiddleName: String? = null, + override val phoneticLastName: String? = null, + override val imageData: ImageData? = null, + override val organization: String? = null, + override val jobTitle: String? = null, + override val webAddresses: List> = emptyList(), + override val phones: List> = emptyList(), + override val mails: List> = emptyList(), + override val events: List> = emptyList(), + override val postalAddresses: List> = emptyList(), + override val note: Note? = null, + override val nickname: String? = null, + override val fullNameStyle: Int = ContactsContract.FullNameStyle.UNDEFINED, + override val phoneticNameStyle: Int = ContactsContract.PhoneticNameStyle.UNDEFINED, + override val groups: List = emptyList(), + override val displayName: String? = null, + override val lookupKey: LookupKey? = null, + override val sipAddresses: List> = emptyList(), + override val linkedAccountValues: List = emptyList(), + override val imAddresses: List> = emptyList(), + override val relations: List> = emptyList(), + override val columns: List = emptyList() +) : Contact diff --git a/library-test/src/main/java/com/alexstyl/contactstore/test/StoredContactGroup.kt b/library-test/src/main/java/com/alexstyl/contactstore/test/StoredContactGroup.kt new file mode 100644 index 00000000..83979fef --- /dev/null +++ b/library-test/src/main/java/com/alexstyl/contactstore/test/StoredContactGroup.kt @@ -0,0 +1,8 @@ +package com.alexstyl.contactstore.test + +public data class StoredContactGroup( + val groupId: Long, + val title: String = "", + val note: String? = null, + val isDeleted : Boolean = false +) \ No newline at end of file diff --git a/library-test/src/main/java/com/alexstyl/contactstore/test/TestContactStore.kt b/library-test/src/main/java/com/alexstyl/contactstore/test/TestContactStore.kt index 7686571f..553c4e8a 100644 --- a/library-test/src/main/java/com/alexstyl/contactstore/test/TestContactStore.kt +++ b/library-test/src/main/java/com/alexstyl/contactstore/test/TestContactStore.kt @@ -4,17 +4,26 @@ import android.provider.ContactsContract import com.alexstyl.contactstore.Contact import com.alexstyl.contactstore.ContactColumn import com.alexstyl.contactstore.ContactGroup -import com.alexstyl.contactstore.ContactOperation +import com.alexstyl.contactstore.ContactOperation.Delete +import com.alexstyl.contactstore.ContactOperation.DeleteGroup +import com.alexstyl.contactstore.ContactOperation.Insert +import com.alexstyl.contactstore.ContactOperation.InsertGroup +import com.alexstyl.contactstore.ContactOperation.Update +import com.alexstyl.contactstore.ContactOperation.UpdateGroup import com.alexstyl.contactstore.ContactPredicate import com.alexstyl.contactstore.ContactStore import com.alexstyl.contactstore.DisplayNameStyle import com.alexstyl.contactstore.ExperimentalContactStoreApi +import com.alexstyl.contactstore.GroupsPredicate +import com.alexstyl.contactstore.ImmutableContactGroup import com.alexstyl.contactstore.MutableContact +import com.alexstyl.contactstore.MutableContactGroup import com.alexstyl.contactstore.PartialContact import com.alexstyl.contactstore.SaveRequest import com.alexstyl.contactstore.containsColumn import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map /** @@ -31,41 +40,81 @@ import kotlinx.coroutines.flow.map */ @ExperimentalContactStoreApi public class TestContactStore( - contactsSnapshot: List = emptyList() + contactsSnapshot: List = emptyList(), + contactGroupsSnapshot: List = emptyList(), ) : ContactStore { - private val snapshot: MutableStateFlow> = MutableStateFlow(contactsSnapshot) + private val contacts: MutableStateFlow> = MutableStateFlow(contactsSnapshot) + private val contactGroups: MutableStateFlow> = + MutableStateFlow(contactGroupsSnapshot) + + override suspend fun execute(request: SaveRequest.() -> Unit) { + val saveRequest = SaveRequest().apply(request) + executeInternal(saveRequest) + } override suspend fun execute(request: SaveRequest) { - request.requests.forEach { operation -> - when (operation) { - is ContactOperation.Delete -> deleteContact(withId = operation.contactId) - is ContactOperation.Insert -> insertContact(operation.contact) - is ContactOperation.Update -> updateContact(operation.contact) - } - } + executeInternal(request) } - override suspend fun execute(request: SaveRequest.() -> Unit) { - val saveRequest = SaveRequest().apply(request) + private suspend fun executeInternal(saveRequest: SaveRequest) { saveRequest.requests.forEach { operation -> when (operation) { - is ContactOperation.Delete -> deleteContact(withId = operation.contactId) - is ContactOperation.Insert -> insertContact(operation.contact) - is ContactOperation.Update -> updateContact(operation.contact) + is Delete -> deleteContact(withId = operation.contactId) + is Insert -> insertContact(operation.contact) + is Update -> updateContact(operation.contact) + is DeleteGroup -> deleteContactGroup(operation.groupId) + is InsertGroup -> insertGroup(operation.group) + is UpdateGroup -> updateGroup(operation.group) } } } + private suspend fun updateGroup(group: MutableContactGroup) { + val currentGroup = contactGroups.value + .find { it.groupId == group.groupId } ?: return + val updatedGroup = currentGroup.copy( + title = group.title, + note = group.note + ) + val newList = contactGroups.value.toMutableList() + .replace(updatedGroup) { + it.groupId == group.groupId + } + .toList() + contactGroups.emit(newList) + } + private suspend fun deleteContact(withId: Long) { - snapshot.emit( - snapshot.value.dropWhile { it.contactId == withId } + contacts.emit( + contacts.value.dropWhile { it.contactId == withId } + ) + } + + private suspend fun deleteContactGroup(withId: Long) { + contactGroups.emit( + contactGroups.value.dropWhile { it.groupId == withId } + ) + } + + private suspend fun insertGroup(group: MutableContactGroup) { + val current = contactGroups.value + contactGroups.emit( + current.toMutableList().apply { + add( + StoredContactGroup( + groupId = group.groupId, + title = group.title, + note = group.note + ) + ) + } ) } private suspend fun insertContact(contact: MutableContact) { - val current = snapshot.value - snapshot.emit( + val current = contacts.value + contacts.emit( current.toMutableList() .apply { add( @@ -118,7 +167,7 @@ public class TestContactStore( } private suspend fun updateContact(contact: MutableContact) { - val currentContact = snapshot.value + val currentContact = contacts.value .find { it.contactId == contact.contactId } ?: return val updatedContact = currentContact.copy( firstName = contact.takeIfContains(ContactColumn.Names) { contact.firstName } @@ -156,7 +205,8 @@ public class TestContactStore( ?: currentContact.events, postalAddresses = contact.takeIfContains(ContactColumn.PostalAddresses) { contact.postalAddresses } ?: currentContact.postalAddresses, - note = contact.takeIfContains(ContactColumn.Note) { contact.note } ?: currentContact.note, + note = contact.takeIfContains(ContactColumn.Note) { contact.note } + ?: currentContact.note, nickname = contact.takeIfContains(ContactColumn.Nickname) { contact.nickname } ?: currentContact.nickname, groups = contact @@ -165,12 +215,12 @@ public class TestContactStore( fullNameStyle = contact.takeIfContains(ContactColumn.Names) { contact.fullNameStyle } ?: currentContact.fullNameStyle ) - val newList = snapshot.value.toMutableList() + val newList = contacts.value.toMutableList() .replace(updatedContact) { it.contactId == contact.contactId } .toList() - snapshot.emit(newList) + contacts.emit(newList) } private fun List.replace(newValue: T, block: (T) -> Boolean): List { @@ -184,7 +234,7 @@ public class TestContactStore( columnsToFetch: List, displayNameStyle: DisplayNameStyle ): Flow> { - return snapshot + return contacts .map { contacts -> contacts.filter { current -> matchesPredicate(contact = current, predicate) @@ -192,8 +242,24 @@ public class TestContactStore( } } - override fun fetchContactGroups(): Flow> { - TODO("Not yet implemented") + override fun fetchContactGroups(predicate: GroupsPredicate?): Flow> { + return combine(contactGroups.map { groups -> + groups.filter { group -> + matchesPredicate(group, predicate) + } + }, contacts) { groups, contacts -> + groups + .map { group -> + ImmutableContactGroup( + groupId = group.groupId, + title = group.title, + note = group.note, + contactCount = contacts.count { contact -> + contact.groups.any { membership -> membership.groupId == group.groupId } + } + ) + } + } } private fun matchesPredicate( @@ -212,6 +278,26 @@ public class TestContactStore( } } + private fun matchesPredicate( + group: StoredContactGroup, + predicate: GroupsPredicate? + ): Boolean { + if (predicate == null) return group.isDeleted.not() + return when (predicate) { + is GroupsPredicate.GroupLookup -> { + val passesIdCheck = predicate.inGroupIds == null || predicate.inGroupIds.orEmpty() + .contains(group.groupId) + val passedDeletedCheck = if (predicate.includeDeleted) { + true + } else { + group.isDeleted.not() + } + passesIdCheck && passedDeletedCheck + } + } + } + + private fun matchesContact( predicate: ContactPredicate.ContactLookup, contact: StoredContact diff --git a/library-test/src/test/java/com/alexstyl/contactstore/Fixtures.kt b/library-test/src/test/java/com/alexstyl/contactstore/Fixtures.kt deleted file mode 100644 index fbd385e8..00000000 --- a/library-test/src/test/java/com/alexstyl/contactstore/Fixtures.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.alexstyl.contactstore - -import android.net.Uri -import com.alexstyl.contactstore.test.StoredContact - -internal object SnapshotFixtures { - val KIM_CLAY = StoredContact( - contactId = 1, - firstName = "Kim", - lastName = "Clay", - isStarred = false - ) - val PAOLO_MELENDEZ = StoredContact( - contactId = 0L, - isStarred = false, - firstName = "Paolo", - lastName = "Melendez", - phones = listOf( - LabeledValue(PhoneNumber("555-15"), Label.PhoneNumberMobile) - ), - mails = listOf( - LabeledValue(MailAddress("hi@mail.com"), Label.LocationHome) - ), - postalAddresses = listOf( - LabeledValue(PostalAddress("SomeStreet 55"), Label.LocationHome) - ), - imageData = ImageData("imagedata".toByteArray()), - organization = "Organization", - jobTitle = "Job Title", - webAddresses = listOf( - LabeledValue(WebAddress(Uri.parse("www.web.com")), Label.WebsiteHomePage) - ), - events = listOf( - LabeledValue(EventDate(1, 1, 2021), Label.DateBirthday) - ), - note = Note("note"), - prefix = "Prefix", - middleName = "Mid", - suffix = "Suffix", - nickname = "Nickname", - groups = listOf(GroupMembership(groupId = 10)) - ) -} - -internal object ContactFixtures { - val PAOLO_MELENDEZ = PartialContact( - contactId = 0L, - displayName = "Prefix Paolo Mid Melendez, Suffix", - columns = standardColumns(), - isStarred = false, - firstName = "Paolo", - lastName = "Melendez", - phones = listOf( - LabeledValue(PhoneNumber("555-15"), Label.PhoneNumberMobile) - ), - mails = listOf( - LabeledValue(MailAddress("hi@mail.com"), Label.LocationHome) - ), - postalAddresses = listOf( - LabeledValue(PostalAddress("SomeStreet 55"), Label.LocationHome) - ), - imageData = ImageData("imagedata".toByteArray()), - organization = "Organization", - jobTitle = "Job Title", - webAddresses = listOf( - LabeledValue(WebAddress(Uri.parse("www.web.com")), Label.WebsiteHomePage) - ), - events = listOf( - LabeledValue(EventDate(1, 1, 2021), Label.DateBirthday) - ), - note = Note("note"), - prefix = "Prefix", - middleName = "Mid", - suffix = "Suffix", - nickname = "Nickname", - groups = listOf(GroupMembership(groupId = 10)), - lookupKey = null, - ) -} diff --git a/library-test/src/test/java/com/alexstyl/contactstore/ColumnTestContactStoreTest.kt b/library-test/src/test/java/com/alexstyl/contactstore/test/ColumnTestContactStoreTest.kt similarity index 89% rename from library-test/src/test/java/com/alexstyl/contactstore/ColumnTestContactStoreTest.kt rename to library-test/src/test/java/com/alexstyl/contactstore/test/ColumnTestContactStoreTest.kt index 78ca74fa..a95eac44 100644 --- a/library-test/src/test/java/com/alexstyl/contactstore/ColumnTestContactStoreTest.kt +++ b/library-test/src/test/java/com/alexstyl/contactstore/test/ColumnTestContactStoreTest.kt @@ -1,10 +1,21 @@ -package com.alexstyl.contactstore +package com.alexstyl.contactstore.test import android.net.Uri -import com.alexstyl.contactstore.test.TestContactStore +import com.alexstyl.contactstore.ContactColumn +import com.alexstyl.contactstore.ExperimentalContactStoreApi +import com.alexstyl.contactstore.GroupMembership +import com.alexstyl.contactstore.ImageData +import com.alexstyl.contactstore.Label +import com.alexstyl.contactstore.LabeledValue +import com.alexstyl.contactstore.MailAddress +import com.alexstyl.contactstore.Note +import com.alexstyl.contactstore.PartialContact +import com.alexstyl.contactstore.PostalAddress +import com.alexstyl.contactstore.WebAddress import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import org.assertj.core.api.Assertions +import org.assertj.core.api.Assertions.assertThat import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner @@ -22,7 +33,7 @@ internal class ColumnTestContactStoreTest { val actual = store.fetchContacts().first() - Assertions.assertThat(actual).containsOnly( + assertThat(actual).containsOnly( PartialContact( contactId = ContactFixtures.PAOLO_MELENDEZ.contactId, displayName = ContactFixtures.PAOLO_MELENDEZ.displayName, @@ -45,7 +56,7 @@ internal class ColumnTestContactStoreTest { columnsToFetch = listOf(ContactColumn.Names) ).first() - Assertions.assertThat(actual).containsOnly( + assertThat(actual).containsOnly( PartialContact( contactId = ContactFixtures.PAOLO_MELENDEZ.contactId, displayName = ContactFixtures.PAOLO_MELENDEZ.displayName, @@ -73,7 +84,7 @@ internal class ColumnTestContactStoreTest { columnsToFetch = listOf(ContactColumn.Phones) ).first() - Assertions.assertThat(actual).containsOnly( + assertThat(actual).containsOnly( PartialContact( contactId = ContactFixtures.PAOLO_MELENDEZ.contactId, displayName = ContactFixtures.PAOLO_MELENDEZ.displayName, @@ -97,7 +108,7 @@ internal class ColumnTestContactStoreTest { columnsToFetch = listOf(ContactColumn.Mails) ).first() - Assertions.assertThat(actual).containsOnly( + assertThat(actual).containsOnly( PartialContact( contactId = ContactFixtures.PAOLO_MELENDEZ.contactId, displayName = ContactFixtures.PAOLO_MELENDEZ.displayName, @@ -123,7 +134,7 @@ internal class ColumnTestContactStoreTest { columnsToFetch = listOf(ContactColumn.Organization) ).first() - Assertions.assertThat(actual).containsOnly( + assertThat(actual).containsOnly( PartialContact( contactId = ContactFixtures.PAOLO_MELENDEZ.contactId, displayName = ContactFixtures.PAOLO_MELENDEZ.displayName, @@ -148,7 +159,7 @@ internal class ColumnTestContactStoreTest { columnsToFetch = listOf(ContactColumn.Image) ).first() - Assertions.assertThat(actual).containsOnly( + assertThat(actual).containsOnly( PartialContact( contactId = ContactFixtures.PAOLO_MELENDEZ.contactId, displayName = ContactFixtures.PAOLO_MELENDEZ.displayName, @@ -172,7 +183,7 @@ internal class ColumnTestContactStoreTest { columnsToFetch = listOf(ContactColumn.Note) ).first() - Assertions.assertThat(actual).containsOnly( + assertThat(actual).containsOnly( PartialContact( contactId = ContactFixtures.PAOLO_MELENDEZ.contactId, displayName = ContactFixtures.PAOLO_MELENDEZ.displayName, @@ -196,7 +207,7 @@ internal class ColumnTestContactStoreTest { columnsToFetch = listOf(ContactColumn.PostalAddresses) ).first() - Assertions.assertThat(actual).containsOnly( + assertThat(actual).containsOnly( PartialContact( contactId = ContactFixtures.PAOLO_MELENDEZ.contactId, displayName = ContactFixtures.PAOLO_MELENDEZ.displayName, @@ -222,7 +233,7 @@ internal class ColumnTestContactStoreTest { columnsToFetch = listOf(ContactColumn.Nickname) ).first() - Assertions.assertThat(actual).containsOnly( + assertThat(actual).containsOnly( PartialContact( contactId = ContactFixtures.PAOLO_MELENDEZ.contactId, displayName = ContactFixtures.PAOLO_MELENDEZ.displayName, @@ -246,7 +257,7 @@ internal class ColumnTestContactStoreTest { columnsToFetch = listOf(ContactColumn.WebAddresses) ).first() - Assertions.assertThat(actual).containsOnly( + assertThat(actual).containsOnly( PartialContact( contactId = ContactFixtures.PAOLO_MELENDEZ.contactId, displayName = ContactFixtures.PAOLO_MELENDEZ.displayName, @@ -272,7 +283,7 @@ internal class ColumnTestContactStoreTest { columnsToFetch = listOf(ContactColumn.GroupMemberships) ).first() - Assertions.assertThat(actual).containsOnly( + assertThat(actual).containsOnly( PartialContact( contactId = ContactFixtures.PAOLO_MELENDEZ.contactId, displayName = ContactFixtures.PAOLO_MELENDEZ.displayName, diff --git a/library-test/src/test/java/com/alexstyl/contactstore/test/ContactFixtures.kt b/library-test/src/test/java/com/alexstyl/contactstore/test/ContactFixtures.kt new file mode 100644 index 00000000..d2532dbc --- /dev/null +++ b/library-test/src/test/java/com/alexstyl/contactstore/test/ContactFixtures.kt @@ -0,0 +1,51 @@ +package com.alexstyl.contactstore.test + +import android.net.Uri +import com.alexstyl.contactstore.EventDate +import com.alexstyl.contactstore.GroupMembership +import com.alexstyl.contactstore.ImageData +import com.alexstyl.contactstore.Label +import com.alexstyl.contactstore.LabeledValue +import com.alexstyl.contactstore.MailAddress +import com.alexstyl.contactstore.Note +import com.alexstyl.contactstore.PartialContact +import com.alexstyl.contactstore.PhoneNumber +import com.alexstyl.contactstore.PostalAddress +import com.alexstyl.contactstore.WebAddress +import com.alexstyl.contactstore.standardColumns + +internal object ContactFixtures { + val PAOLO_MELENDEZ = PartialContact( + contactId = 0L, + displayName = "Prefix Paolo Mid Melendez, Suffix", + columns = standardColumns(), + isStarred = false, + firstName = "Paolo", + lastName = "Melendez", + phones = listOf( + LabeledValue(PhoneNumber("555-15"), Label.PhoneNumberMobile) + ), + mails = listOf( + LabeledValue(MailAddress("hi@mail.com"), Label.LocationHome) + ), + postalAddresses = listOf( + LabeledValue(PostalAddress("SomeStreet 55"), Label.LocationHome) + ), + imageData = ImageData("imagedata".toByteArray()), + organization = "Organization", + jobTitle = "Job Title", + webAddresses = listOf( + LabeledValue(WebAddress(Uri.parse("www.web.com")), Label.WebsiteHomePage) + ), + events = listOf( + LabeledValue(EventDate(1, 1, 2021), Label.DateBirthday) + ), + note = Note("note"), + prefix = "Prefix", + middleName = "Mid", + suffix = "Suffix", + nickname = "Nickname", + groups = listOf(GroupMembership(groupId = 10)), + lookupKey = null, + ) +} diff --git a/library-test/src/test/java/com/alexstyl/contactstore/ExecuteTestContactStoreTest.kt b/library-test/src/test/java/com/alexstyl/contactstore/test/ExecuteTestContactStoreTest.kt similarity index 84% rename from library-test/src/test/java/com/alexstyl/contactstore/ExecuteTestContactStoreTest.kt rename to library-test/src/test/java/com/alexstyl/contactstore/test/ExecuteTestContactStoreTest.kt index 528dc639..60b4d1ef 100644 --- a/library-test/src/test/java/com/alexstyl/contactstore/ExecuteTestContactStoreTest.kt +++ b/library-test/src/test/java/com/alexstyl/contactstore/test/ExecuteTestContactStoreTest.kt @@ -1,9 +1,12 @@ -package com.alexstyl.contactstore +package com.alexstyl.contactstore.test -import com.alexstyl.contactstore.test.TestContactStore +import com.alexstyl.contactstore.ExperimentalContactStoreApi +import com.alexstyl.contactstore.PartialContact +import com.alexstyl.contactstore.mutableCopy +import com.alexstyl.contactstore.standardColumns import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking -import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner @@ -23,7 +26,7 @@ internal class ExecuteTestContactStoreTest { val actual = store.fetchContacts( columnsToFetch = standardColumns() ).first() - assertThat(actual).containsOnly( + Assertions.assertThat(actual).containsOnly( ContactFixtures.PAOLO_MELENDEZ ) } @@ -41,9 +44,7 @@ internal class ExecuteTestContactStoreTest { delete(paoloMelendez().contactId) } val actual = store.fetchContacts().first() - assertThat(actual).containsOnly( - kimClay() - ) + Assertions.assertThat(actual).containsOnly(kimClay()) } @Test @@ -65,7 +66,7 @@ internal class ExecuteTestContactStoreTest { } val actual = store.fetchContacts().first() - assertThat(actual).containsOnly( + Assertions.assertThat(actual).containsOnly( PartialContact( contactId = SNAPSHOT_PAOLO.contactId, displayName = "Prefix Peter Mid Melendez, Suffix", @@ -97,4 +98,4 @@ internal class ExecuteTestContactStoreTest { columns = emptyList(), lookupKey = null, ) -} +} \ No newline at end of file diff --git a/library-test/src/test/java/com/alexstyl/contactstore/test/FetchGroupTestStoreTest.kt b/library-test/src/test/java/com/alexstyl/contactstore/test/FetchGroupTestStoreTest.kt new file mode 100644 index 00000000..6378b87a --- /dev/null +++ b/library-test/src/test/java/com/alexstyl/contactstore/test/FetchGroupTestStoreTest.kt @@ -0,0 +1,173 @@ +package com.alexstyl.contactstore.test + +import com.alexstyl.contactstore.ExperimentalContactStoreApi +import com.alexstyl.contactstore.GroupMembership +import com.alexstyl.contactstore.GroupsPredicate +import com.alexstyl.contactstore.ImmutableContactGroup +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test + +@OptIn(ExperimentalContactStoreApi::class) +internal class FetchGroupTestStoreTest { + + @Test + fun returnsAllGroups(): Unit = runBlocking { + val store = TestContactStore( + contactGroupsSnapshot = listOf( + StoredContactGroup( + groupId = 0, + title = "GroupA", + note = "Note" + ), + StoredContactGroup( + groupId = 1, + title = "GroupB", + ) + ) + ) + val actual = store.fetchContactGroups().first() + assertThat(actual).containsExactly( + ImmutableContactGroup( + groupId = 0, + title = "GroupA", + note = "Note", + contactCount = 0 + ), + ImmutableContactGroup( + groupId = 1, + title = "GroupB", + contactCount = 0, + note = null + ) + ) + } + + @Test + fun doesNotReturnDeleted(): Unit = runBlocking { + val store = TestContactStore( + contactGroupsSnapshot = listOf( + StoredContactGroup( + groupId = 0, + title = "GroupA", + note = "Note" + ), + StoredContactGroup( + groupId = 1, + title = "GroupB", + isDeleted = true + ) + ) + ) + val actual = store.fetchContactGroups().first() + assertThat(actual).containsExactly( + ImmutableContactGroup( + groupId = 0, + title = "GroupA", + note = "Note", + contactCount = 0 + ) + ) + } + + @Test + fun countsContactsInGroup(): Unit = runBlocking { + val store = TestContactStore( + contactGroupsSnapshot = listOf( + StoredContactGroup( + groupId = 0, + title = "GroupA", + note = "Note" + ) + ), + contactsSnapshot = listOf( + StoredContact( + contactId = 0, + groups = listOf(GroupMembership(0)) + ), + StoredContact( + contactId = 1, + groups = listOf(GroupMembership(0)) + ) + ) + ) + val actual = store.fetchContactGroups().first() + assertThat(actual).containsExactly( + ImmutableContactGroup( + groupId = 0, + title = "GroupA", + note = "Note", + contactCount = 2 + ), + ) + } + + @Test + fun fetchesAccordingToIdLookupPredicate(): Unit = runBlocking { + val store = TestContactStore( + contactGroupsSnapshot = listOf( + StoredContactGroup( + groupId = 0, + title = "GroupA", + note = "Note" + ), + StoredContactGroup( + groupId = 1, + title = "GroupB", + ) + ) + ) + val actual = store.fetchContactGroups( + predicate = GroupsPredicate.GroupLookup( + inGroupIds = listOf(0) + ) + ).first() + assertThat(actual).containsExactly( + ImmutableContactGroup( + groupId = 0, + title = "GroupA", + note = "Note", + contactCount = 0 + ), + ) + } + + @Test + fun fetchesAccordingToDeletedPredicate(): Unit = runBlocking { + val store = TestContactStore( + contactGroupsSnapshot = listOf( + StoredContactGroup( + groupId = 0, + title = "GroupA", + note = "Note" + ), + StoredContactGroup( + groupId = 1, + title = "GroupB", + isDeleted = true + ) + ) + ) + val actual = store.fetchContactGroups( + predicate = GroupsPredicate.GroupLookup( + includeDeleted = true + ) + ).first() + assertThat(actual).containsExactly( + ImmutableContactGroup( + groupId = 0, + title = "GroupA", + note = "Note", + contactCount = 0 + ), + ImmutableContactGroup( + groupId = 1, + title = "GroupB", + contactCount = 0, + note = null + ) + ) + } + +} diff --git a/library-test/src/test/java/com/alexstyl/contactstore/PredicateTestContactStoreTest.kt b/library-test/src/test/java/com/alexstyl/contactstore/test/PredicateTestContactStoreTest.kt similarity index 91% rename from library-test/src/test/java/com/alexstyl/contactstore/PredicateTestContactStoreTest.kt rename to library-test/src/test/java/com/alexstyl/contactstore/test/PredicateTestContactStoreTest.kt index 82155b69..fc5b136f 100644 --- a/library-test/src/test/java/com/alexstyl/contactstore/PredicateTestContactStoreTest.kt +++ b/library-test/src/test/java/com/alexstyl/contactstore/test/PredicateTestContactStoreTest.kt @@ -1,10 +1,13 @@ -package com.alexstyl.contactstore +package com.alexstyl.contactstore.test import com.alexstyl.contactstore.ContactPredicate.ContactLookup import com.alexstyl.contactstore.ContactPredicate.MailLookup import com.alexstyl.contactstore.ContactPredicate.NameLookup import com.alexstyl.contactstore.ContactPredicate.PhoneLookup -import com.alexstyl.contactstore.test.TestContactStore +import com.alexstyl.contactstore.ExperimentalContactStoreApi +import com.alexstyl.contactstore.MailAddress +import com.alexstyl.contactstore.PartialContact +import com.alexstyl.contactstore.PhoneNumber import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import org.assertj.core.api.Assertions.assertThat diff --git a/library-test/src/test/java/com/alexstyl/contactstore/test/SnapshotFixtures.kt b/library-test/src/test/java/com/alexstyl/contactstore/test/SnapshotFixtures.kt new file mode 100644 index 00000000..b5a7e70e --- /dev/null +++ b/library-test/src/test/java/com/alexstyl/contactstore/test/SnapshotFixtures.kt @@ -0,0 +1,52 @@ +package com.alexstyl.contactstore.test + +import android.net.Uri +import com.alexstyl.contactstore.EventDate +import com.alexstyl.contactstore.GroupMembership +import com.alexstyl.contactstore.ImageData +import com.alexstyl.contactstore.Label +import com.alexstyl.contactstore.LabeledValue +import com.alexstyl.contactstore.MailAddress +import com.alexstyl.contactstore.Note +import com.alexstyl.contactstore.PhoneNumber +import com.alexstyl.contactstore.PostalAddress +import com.alexstyl.contactstore.WebAddress + +internal object SnapshotFixtures { + val KIM_CLAY = StoredContact( + contactId = 1, + firstName = "Kim", + lastName = "Clay", + isStarred = false + ) + val PAOLO_MELENDEZ = StoredContact( + contactId = 0L, + isStarred = false, + firstName = "Paolo", + lastName = "Melendez", + phones = listOf( + LabeledValue(PhoneNumber("555-15"), Label.PhoneNumberMobile) + ), + mails = listOf( + LabeledValue(MailAddress("hi@mail.com"), Label.LocationHome) + ), + postalAddresses = listOf( + LabeledValue(PostalAddress("SomeStreet 55"), Label.LocationHome) + ), + imageData = ImageData("imagedata".toByteArray()), + organization = "Organization", + jobTitle = "Job Title", + webAddresses = listOf( + LabeledValue(WebAddress(Uri.parse("www.web.com")), Label.WebsiteHomePage) + ), + events = listOf( + LabeledValue(EventDate(1, 1, 2021), Label.DateBirthday) + ), + note = Note("note"), + prefix = "Prefix", + middleName = "Mid", + suffix = "Suffix", + nickname = "Nickname", + groups = listOf(GroupMembership(groupId = 10)) + ) +} \ No newline at end of file diff --git a/library/src/main/java/com/alexstyl/contactstore/ContactGroup.kt b/library/src/main/java/com/alexstyl/contactstore/ContactGroup.kt index da678fb1..65c72619 100644 --- a/library/src/main/java/com/alexstyl/contactstore/ContactGroup.kt +++ b/library/src/main/java/com/alexstyl/contactstore/ContactGroup.kt @@ -5,6 +5,26 @@ public interface ContactGroup { public val title: String public val contactCount: Int public val note: String? + + public fun equalsGroup(other: Any?): Boolean { + if (this === other) return true + if (other !is ContactGroup) return false + + if (groupId != other.groupId) return false + if (title != other.title) return false + if (contactCount != other.contactCount) return false + if (note != other.note) return false + + return true + } + + public fun hashCodeGroup(): Int { + var result = groupId.hashCode() + result = 31 * result + title.hashCode() + result = 31 * result + contactCount + result = 31 * result + (note?.hashCode() ?: 0) + return result + } } public data class MutableContactGroup internal constructor( @@ -13,12 +33,16 @@ public data class MutableContactGroup internal constructor( override val contactCount: Int, override var note: String? ) : ContactGroup { + public constructor() : this( groupId = -1L, title = "", contactCount = 0, note = null ) + + override fun equals(other: Any?): Boolean = equalsGroup(other) + override fun hashCode(): Int = hashCodeGroup() } public data class ImmutableContactGroup( @@ -26,7 +50,10 @@ public data class ImmutableContactGroup( override val title: String, override val contactCount: Int, override val note: String?, -) : ContactGroup +) : ContactGroup { + override fun equals(other: Any?): Boolean = equalsGroup(other) + override fun hashCode(): Int = hashCodeGroup() +} public fun ContactGroup.mutableCopy(): MutableContactGroup { diff --git a/library/src/main/java/com/alexstyl/contactstore/ContactGroupQueries.kt b/library/src/main/java/com/alexstyl/contactstore/ContactGroupQueries.kt index 9f43799d..e69b2f7a 100644 --- a/library/src/main/java/com/alexstyl/contactstore/ContactGroupQueries.kt +++ b/library/src/main/java/com/alexstyl/contactstore/ContactGroupQueries.kt @@ -49,7 +49,7 @@ internal class ContactGroupQueries( val buildString = buildString { val groupLookup = from as? GroupsPredicate.GroupLookup - val deleted = intOf(groupLookup?.deleted ?: false) + val deleted = intOf(groupLookup?.includeDeleted ?: false) append("${Groups.DELETED} = $deleted") append(" AND ${Groups.GROUP_IS_READ_ONLY} = 0") diff --git a/library/src/main/java/com/alexstyl/contactstore/GroupsPredicate.kt b/library/src/main/java/com/alexstyl/contactstore/GroupsPredicate.kt index e699358f..cf11aa84 100644 --- a/library/src/main/java/com/alexstyl/contactstore/GroupsPredicate.kt +++ b/library/src/main/java/com/alexstyl/contactstore/GroupsPredicate.kt @@ -3,6 +3,6 @@ package com.alexstyl.contactstore public sealed class GroupsPredicate { public data class GroupLookup( val inGroupIds: List? = null, - val deleted: Boolean = false, + val includeDeleted: Boolean = false, ) : GroupsPredicate() } \ No newline at end of file diff --git a/library/src/main/java/com/alexstyl/contactstore/PartialContact.kt b/library/src/main/java/com/alexstyl/contactstore/PartialContact.kt index 43293546..7e637dd0 100644 --- a/library/src/main/java/com/alexstyl/contactstore/PartialContact.kt +++ b/library/src/main/java/com/alexstyl/contactstore/PartialContact.kt @@ -52,7 +52,10 @@ public class PartialContact constructor( override val firstName: String? by requireColumn(Names, firstName) override val middleName: String? by requireColumn(Names, middleName) override val lastName: String? by requireColumn(Names, lastName) - override val imAddresses: List> by requireColumn(ImAddresses, imAddresses) + override val imAddresses: List> by requireColumn( + ImAddresses, + imAddresses + ) override val suffix: String? by requireColumn(Names, suffix) override val phoneticFirstName: String? by requireColumn(Names, phoneticFirstName) override val phoneticMiddleName: String? by requireColumn(Names, phoneticMiddleName) @@ -64,13 +67,24 @@ public class PartialContact constructor( override val phones: List> by requireColumn(Phones, phones) override val mails: List> by requireColumn(Mails, mails) override val events: List> by requireColumn(Events, events) - override val postalAddresses: List> by requireColumn(PostalAddresses, postalAddresses) + override val postalAddresses: List> by requireColumn( + PostalAddresses, + postalAddresses + ) override val note: com.alexstyl.contactstore.Note? by requireColumn(Note, note) - override val webAddresses: List> by requireColumn(WebAddresses, webAddresses) - override val sipAddresses: List> by requireColumn(SipAddresses, sipAddresses) + override val webAddresses: List> by requireColumn( + WebAddresses, + webAddresses + ) + override val sipAddresses: List> by requireColumn( + SipAddresses, + sipAddresses + ) override val organization: String? by requireColumn(Organization, organization) override val jobTitle: String? by requireColumn(Organization, jobTitle) - override val linkedAccountValues: List by requireAnyLinkedAccountColumn(linkedAccountValues) + override val linkedAccountValues: List by requireAnyLinkedAccountColumn( + linkedAccountValues + ) override val groups: List by requireColumn(GroupMemberships, groups) override val relations: List> by requireColumn(Relations, relations) diff --git a/library/src/test/java/com/alexstyl/contactstore/ContactStoreDSLKtTest.kt b/library/src/test/java/com/alexstyl/contactstore/ContactStoreDSLKtTest.kt index ebb3fa66..e29aa7ad 100644 --- a/library/src/test/java/com/alexstyl/contactstore/ContactStoreDSLKtTest.kt +++ b/library/src/test/java/com/alexstyl/contactstore/ContactStoreDSLKtTest.kt @@ -137,5 +137,9 @@ internal class ContactStoreDSLKtTest { ): Flow> { return emptyFlow() } + + override fun fetchContactGroups(predicate: GroupsPredicate?): Flow> { + return emptyFlow() + } } }