geekdroid: add AccountsFlowTest
authorDa Risk <da_risk@geekorum.com>
Mon, 17 Feb 2025 21:58:32 -0400
changeset 84 b3dd237df1bd
parent 83 5ad573b6c84d
child 85 3b8739febbfe
geekdroid: add AccountsFlowTest
geekdroid/build.gradle.kts
geekdroid/src/main/java/com/geekorum/geekdroid/accounts/AccountsLiveData.kt
geekdroid/src/test/java/com/geekorum/geekdroid/accounts/AccountsFlowTest.kt
gradle/libs.versions.toml
--- a/geekdroid/build.gradle.kts	Tue Feb 18 18:08:10 2025 -0400
+++ b/geekdroid/build.gradle.kts	Mon Feb 17 21:58:32 2025 -0400
@@ -107,6 +107,9 @@
     implementation(libs.androidx.compose.material3)
     implementation(libs.androidx.activity.compose)
 
+    testImplementation(libs.kotlinx.coroutines.test)
+    testImplementation(libs.turbine)
+
 }
 
 
--- a/geekdroid/src/main/java/com/geekorum/geekdroid/accounts/AccountsLiveData.kt	Tue Feb 18 18:08:10 2025 -0400
+++ b/geekdroid/src/main/java/com/geekorum/geekdroid/accounts/AccountsLiveData.kt	Mon Feb 17 21:58:32 2025 -0400
@@ -27,10 +27,14 @@
 import android.annotation.SuppressLint
 import android.os.Build
 import androidx.lifecycle.LiveData
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
 
 /**
  * Allows to observe the list of [Account] for a specified Account type.
  */
+@Deprecated("Use AccountManager.accountsFlow()")
 class AccountsLiveData(
         private val accountManager: AccountManager,
         vararg accountType: String?
@@ -64,3 +68,43 @@
     }
 
 }
+
+/**
+ * Allows to observe the list of [Account] for a specified Account type.
+ */
+fun AccountManager.accountsFlow(vararg accountType: String): Flow<List<Account>> = callbackFlow {
+    val listener = object : OnAccountsUpdateListener {
+        override fun onAccountsUpdated(accounts: Array<out Account?>?) {
+            val forTypes = accounts?.filter { it?.type in accountType }?.filterNotNull() ?: emptyList()
+            trySend(forTypes)
+        }
+    }
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+        addOnAccountsUpdatedListener(
+            listener, null, true,
+            accountType
+        )
+    } else {
+        addOnAccountsUpdatedListener(listener, null, true)
+    }
+
+    awaitClose {
+        removeOnAccountsUpdatedListener(listener)
+    }
+}
+
+/**
+ * Allows to observe the list of [Account]
+ */
+fun AccountManager.accountsFlow(): Flow<List<Account>> = callbackFlow {
+    val listener = object : OnAccountsUpdateListener {
+        override fun onAccountsUpdated(accounts: Array<out Account?>?) {
+            trySend(accounts?.filterNotNull() ?: emptyList())
+        }
+    }
+    addOnAccountsUpdatedListener(listener, null, true)
+
+    awaitClose {
+        removeOnAccountsUpdatedListener(listener)
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/geekdroid/src/test/java/com/geekorum/geekdroid/accounts/AccountsFlowTest.kt	Mon Feb 17 21:58:32 2025 -0400
@@ -0,0 +1,110 @@
+/*
+ * Geekdroid is a utility library for development on the Android
+ * Platform.
+ *
+ * Copyright (C) 2017-2025 by Frederic-Charles Barthelery.
+ *
+ * This file is part of Geekdroid.
+ *
+ * Geekdroid is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Geekdroid is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Geekdroid.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.geekorum.geekdroid.accounts
+
+import android.accounts.Account
+import android.accounts.AccountManager
+import android.os.Build
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import app.cash.turbine.test
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+import org.robolectric.RuntimeEnvironment
+import org.robolectric.Shadows
+import org.robolectric.annotation.Config
+import org.robolectric.shadows.ShadowAccountManager
+import kotlin.test.BeforeTest
+import kotlin.test.Test
+
+@RunWith(AndroidJUnit4::class)
+@Config(shadows = [com.geekorum.geekdroid.shadows.ShadowAccountManager::class], sdk = [Build.VERSION_CODES.Q])
+class AccountsFlowTest {
+
+    private lateinit var shadowAccountManager: ShadowAccountManager
+    private lateinit var accountManager: AccountManager
+
+    private val TEST_ACCOUNT_TYPE: String = "test.account"
+    private val testAccounts = listOf(
+        Account("test1", TEST_ACCOUNT_TYPE),
+        Account("test2", TEST_ACCOUNT_TYPE),
+        Account("test3", TEST_ACCOUNT_TYPE),
+    )
+    private val testAccountMore = Account("test4 more", TEST_ACCOUNT_TYPE)
+
+
+    private val OTHER_ACCOUNT_TYPE: String = "other"
+    private val otherAccount = Account("test1", OTHER_ACCOUNT_TYPE)
+    private val otherAccount2 = Account("test2", OTHER_ACCOUNT_TYPE)
+
+    @BeforeTest
+    fun setUp() {
+        accountManager = AccountManager.get(RuntimeEnvironment.getApplication())
+        shadowAccountManager = Shadows.shadowOf(accountManager)
+        for (account in testAccounts) {
+            shadowAccountManager.addAccount(account)
+        }
+        shadowAccountManager.addAccount(otherAccount)
+
+    }
+
+    @Test
+    fun testWhenActiveGetTheCorrectData() = runTest {
+        accountManager.accountsFlow(TEST_ACCOUNT_TYPE)
+            .test {
+                assertThat(awaitItem()).isEqualTo(testAccounts)
+                cancelAndIgnoreRemainingEvents()
+            }
+    }
+
+    @Test
+    fun testThatWhenActiveGetCorrectUpdates() = runTest {
+        accountManager.accountsFlow(TEST_ACCOUNT_TYPE)
+            .test {
+                var expected = testAccounts
+                assertThat(awaitItem()).isEqualTo(expected)
+
+                // add some accounts
+                shadowAccountManager.addAccount(otherAccount2)
+                shadowAccountManager.addAccount(testAccountMore)
+                expected = testAccounts + testAccountMore
+
+                assertThat(awaitItem()).isEqualTo(expected)
+                cancelAndIgnoreRemainingEvents()
+            }
+    }
+
+    @Test
+    fun testThatWhenInactiveGetLaterUpdates() = runTest {
+        // add some accounts
+        shadowAccountManager.addAccount(testAccountMore)
+        shadowAccountManager.addAccount(otherAccount2)
+
+        accountManager.accountsFlow(TEST_ACCOUNT_TYPE)
+            .test {
+                var expected = testAccounts + testAccountMore
+
+                assertThat(awaitItem()).isEqualTo(expected)
+                cancelAndIgnoreRemainingEvents()
+            }
+    }
+}
\ No newline at end of file
--- a/gradle/libs.versions.toml	Tue Feb 18 18:08:10 2025 -0400
+++ b/gradle/libs.versions.toml	Mon Feb 17 21:58:32 2025 -0400
@@ -22,6 +22,7 @@
 recyclerview = "1.4.0"
 room = "2.6.1"
 timber = "5.0.1"
+turbine = "1.0.0"
 workmanager = "2.10.0"
 
 [plugins]
@@ -54,6 +55,7 @@
 kotlinx-coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version.ref = "kotlinx-coroutines-bom" }
 kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core" }
 kotlinx-coroutines-play-services = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-play-services" }
+kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test" }
 lifecycle-livedata-core-ktx = { module = "androidx.lifecycle:lifecycle-livedata-core-ktx", version.ref = "lifecycle" }
 lifecycle-viewmodel-savedstate = { module = "androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "lifecycle" }
 material = { module = "com.google.android.material:material", version.ref = "material" }
@@ -64,5 +66,6 @@
 recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" }
 room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
 timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
+turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" }
 work-runtime = { module = "androidx.work:work-runtime", version.ref = "workmanager" }