diff --git a/posthog-android/src/main/java/com/posthog/android/internal/PostHogSharedPreferences.kt b/posthog-android/src/main/java/com/posthog/android/internal/PostHogSharedPreferences.kt index f9223b50..380d2888 100644 --- a/posthog-android/src/main/java/com/posthog/android/internal/PostHogSharedPreferences.kt +++ b/posthog-android/src/main/java/com/posthog/android/internal/PostHogSharedPreferences.kt @@ -2,14 +2,25 @@ package com.posthog.android.internal import android.content.Context import android.content.Context.MODE_PRIVATE +import android.content.SharedPreferences import com.posthog.PostHogPreferences +import com.posthog.PostHogVisibleForTesting import com.posthog.android.PostHogAndroidConfig -internal class PostHogSharedPreferences(context: Context, config: PostHogAndroidConfig) : +/** + * Reads and writes to the SDKs shared preferences + * The shared pref is called "posthog-android-$apiKey" + * @property context the App Context + * @property config the Config + */ +internal class PostHogSharedPreferences( + private val context: Context, + private val config: PostHogAndroidConfig, + @PostHogVisibleForTesting + private val sharedPreferences: SharedPreferences = context.getSharedPreferences("posthog-android-${config.apiKey}", MODE_PRIVATE), +) : PostHogPreferences { - private val sharedPreferences = context.getSharedPreferences("posthog-android-${config.apiKey}", MODE_PRIVATE) - private val lock = Any() override fun getValue(key: String, defaultValue: Any?): Any? { @@ -44,11 +55,23 @@ internal class PostHogSharedPreferences(context: Context, config: PostHogAndroid is Int -> { edit.putInt(key, value) } - is Set<*> -> { + is Collection<*> -> { + @Suppress("UNCHECKED_CAST") + (value.toSet() as? Set)?.let { + edit.putStringSet(key, it) + } ?: run { + config.logger.log("Value type: ${value.javaClass.name} and value: $value isn't valid.") + } + } + is Array<*> -> { @Suppress("UNCHECKED_CAST") - (value as? Set)?.let { + (value.toSet() as? Set)?.let { edit.putStringSet(key, it) + } ?: run { + config.logger.log("Value type: ${value.javaClass.name} and value: $value isn't valid.") } + } else -> { + config.logger.log("Value type: ${value.javaClass.name} and value: $value isn't valid.") } } diff --git a/posthog-android/src/test/java/com/posthog/android/FakeSharedPreferences.kt b/posthog-android/src/test/java/com/posthog/android/FakeSharedPreferences.kt new file mode 100644 index 00000000..2c6986e6 --- /dev/null +++ b/posthog-android/src/test/java/com/posthog/android/FakeSharedPreferences.kt @@ -0,0 +1,51 @@ +package com.posthog.android + +import android.content.SharedPreferences + +internal class FakeSharedPreferences : SharedPreferences { + private val preferences = mutableMapOf() + override fun getAll(): MutableMap { + return preferences + } + + override fun getString(key: String, defValue: String?): String? { + return preferences[key] as? String ?: defValue + } + + override fun getStringSet(key: String, defValues: MutableSet?): MutableSet? { + @Suppress("UNCHECKED_CAST") + return preferences[key] as? MutableSet ?: defValues + } + + override fun getInt(key: String, defValue: Int): Int { + return preferences[key] as? Int ?: defValue + } + + override fun getLong(key: String, defValue: Long): Long { + return preferences[key] as? Long ?: defValue + } + + override fun getFloat(key: String, defValue: Float): Float { + return preferences[key] as? Float ?: defValue + } + + override fun getBoolean(key: String, defValue: Boolean): Boolean { + return preferences[key] as? Boolean ?: defValue + } + + override fun contains(key: String): Boolean { + return preferences.contains(key) + } + + override fun edit(): SharedPreferences.Editor { + return FakeSharedPreferencesEditor(preferences) + } + + override fun registerOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener?) { + TODO("Not yet implemented") + } + + override fun unregisterOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener?) { + TODO("Not yet implemented") + } +} diff --git a/posthog-android/src/test/java/com/posthog/android/FakeSharedPreferencesEditor.kt b/posthog-android/src/test/java/com/posthog/android/FakeSharedPreferencesEditor.kt new file mode 100644 index 00000000..c0e08b8d --- /dev/null +++ b/posthog-android/src/test/java/com/posthog/android/FakeSharedPreferencesEditor.kt @@ -0,0 +1,52 @@ +package com.posthog.android + +import android.content.SharedPreferences + +internal class FakeSharedPreferencesEditor(private val preferences: MutableMap) : SharedPreferences.Editor { + override fun putString(key: String, value: String?): SharedPreferences.Editor { + preferences[key] = value + return this + } + + override fun putStringSet(key: String, values: MutableSet?): SharedPreferences.Editor { + preferences[key] = values + return this + } + + override fun putInt(key: String, value: Int): SharedPreferences.Editor { + preferences[key] = value + return this + } + + override fun putLong(key: String, value: Long): SharedPreferences.Editor { + preferences[key] = value + return this + } + + override fun putFloat(key: String, value: Float): SharedPreferences.Editor { + preferences[key] = value + return this + } + + override fun putBoolean(key: String, value: Boolean): SharedPreferences.Editor { + preferences[key] = value + return this + } + + override fun remove(key: String): SharedPreferences.Editor { + preferences.remove(key) + return this + } + + override fun clear(): SharedPreferences.Editor { + preferences.clear() + return this + } + + override fun commit(): Boolean { + return true + } + + override fun apply() { + } +} diff --git a/posthog-android/src/test/java/com/posthog/android/Utils.kt b/posthog-android/src/test/java/com/posthog/android/Utils.kt index c4558ae9..d502b959 100644 --- a/posthog-android/src/test/java/com/posthog/android/Utils.kt +++ b/posthog-android/src/test/java/com/posthog/android/Utils.kt @@ -41,10 +41,10 @@ public fun mockScreenTitle(throws: Boolean): Activity { } @Suppress("DEPRECATION") -public fun mockPackageInfo(context: Context, name: String = "1.0.0", code: Int = 1) { +public fun Context.mockPackageInfo(name: String = "1.0.0", code: Int = 1) { val pm = mock() - whenever(context.packageManager).thenReturn(pm) - whenever(context.packageName).thenReturn("test") + whenever(packageManager).thenReturn(pm) + whenever(packageName).thenReturn("test") val pi = PackageInfo() pi.versionName = name pi.versionCode = code diff --git a/posthog-android/src/test/java/com/posthog/android/internal/PostHogAppInstallIntegrationTest.kt b/posthog-android/src/test/java/com/posthog/android/internal/PostHogAppInstallIntegrationTest.kt index 412727d9..aa8d1beb 100644 --- a/posthog-android/src/test/java/com/posthog/android/internal/PostHogAppInstallIntegrationTest.kt +++ b/posthog-android/src/test/java/com/posthog/android/internal/PostHogAppInstallIntegrationTest.kt @@ -35,7 +35,7 @@ internal class PostHogAppInstallIntegrationTest { fun `install captures app installed`() { val sut = getSut() - mockPackageInfo(context, "1.0.0", 1) + context.mockPackageInfo("1.0.0", 1) val fake = createPostHogFake() @@ -50,13 +50,13 @@ internal class PostHogAppInstallIntegrationTest { fun `install captures app updated`() { val sut = getSut() - mockPackageInfo(context, "1.0.0", 1) + context.mockPackageInfo("1.0.0", 1) val fake = createPostHogFake() sut.install() - mockPackageInfo(context, "2.0.0", 2) + context.mockPackageInfo("2.0.0", 2) sut.install() @@ -71,7 +71,7 @@ internal class PostHogAppInstallIntegrationTest { fun `install does not capture if not installed or updated`() { val sut = getSut() - mockPackageInfo(context, "1.0.0", 1) + context.mockPackageInfo("1.0.0", 1) val fake = createPostHogFake() diff --git a/posthog-android/src/test/java/com/posthog/android/internal/PostHogLifecycleObserverIntegrationTest.kt b/posthog-android/src/test/java/com/posthog/android/internal/PostHogLifecycleObserverIntegrationTest.kt index 5b4e1f6c..22386c8a 100644 --- a/posthog-android/src/test/java/com/posthog/android/internal/PostHogLifecycleObserverIntegrationTest.kt +++ b/posthog-android/src/test/java/com/posthog/android/internal/PostHogLifecycleObserverIntegrationTest.kt @@ -55,7 +55,7 @@ internal class PostHogLifecycleObserverIntegrationTest { val sut = getSut() val fake = createPostHogFake() - mockPackageInfo(context, "1.0.0", 1) + context.mockPackageInfo("1.0.0", 1) sut.onStart(ProcessLifecycleOwner.get()) @@ -70,7 +70,7 @@ internal class PostHogLifecycleObserverIntegrationTest { val sut = getSut() val fake = createPostHogFake() - mockPackageInfo(context, "1.0.0", 1) + context.mockPackageInfo("1.0.0", 1) sut.onStart(ProcessLifecycleOwner.get()) sut.onStart(ProcessLifecycleOwner.get()) diff --git a/posthog-android/src/test/java/com/posthog/android/internal/PostHogSharedPreferencesTests.kt b/posthog-android/src/test/java/com/posthog/android/internal/PostHogSharedPreferencesTests.kt new file mode 100644 index 00000000..24303a87 --- /dev/null +++ b/posthog-android/src/test/java/com/posthog/android/internal/PostHogSharedPreferencesTests.kt @@ -0,0 +1,148 @@ +package com.posthog.android.internal + +import android.content.Context +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.posthog.android.FakeSharedPreferences +import com.posthog.android.PostHogAndroidConfig +import com.posthog.android.apiKey +import org.junit.runner.RunWith +import org.mockito.kotlin.mock +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue + +@RunWith(AndroidJUnit4::class) +internal class PostHogSharedPreferencesTests { + private val context = mock() + + private fun getSut(): PostHogSharedPreferences { + val config = PostHogAndroidConfig(apiKey) + return PostHogSharedPreferences(context, config, sharedPreferences = FakeSharedPreferences()) + } + + @Test + fun `preferences set string`() { + val sut = getSut() + + sut.setValue("key", "value") + + assertEquals("value", sut.getValue("key")) + } + + @Test + fun `preferences set boolean`() { + val sut = getSut() + + sut.setValue("key", true) + + assertTrue(sut.getValue("key") as Boolean) + } + + @Test + fun `preferences set float`() { + val sut = getSut() + + sut.setValue("key", 1f) + + assertEquals(1f, sut.getValue("key")) + } + + @Test + fun `preferences set long`() { + val sut = getSut() + + sut.setValue("key", 1L) + + assertEquals(1L, sut.getValue("key")) + } + + @Test + fun `preferences set int`() { + val sut = getSut() + + sut.setValue("key", 1) + + assertEquals(1, sut.getValue("key")) + } + + @Test + fun `preferences set string set`() { + val sut = getSut() + + sut.setValue("key", setOf("1", "2")) + + assertEquals(setOf("1", "2"), sut.getValue("key")) + } + + @Test + fun `preferences set string list`() { + val sut = getSut() + + sut.setValue("key", listOf("1", "2")) + + assertEquals(setOf("1", "2"), sut.getValue("key")) + } + + @Test + fun `preferences set string array`() { + val sut = getSut() + + sut.setValue("key", arrayOf("1", "2")) + + assertEquals(setOf("1", "2"), sut.getValue("key")) + } + + @Test + fun `preferences does not set a non valid type`() { + val sut = getSut() + + sut.setValue("key", Any()) + + assertNull(sut.getValue("key")) + } + + @Test + fun `preferences clear all values`() { + val sut = getSut() + + sut.setValue("key", "1") + sut.clear(listOf()) + + assertTrue(sut.getAll().isEmpty()) + } + + @Test + fun `preferences clear all values but exceptions`() { + val sut = getSut() + + sut.setValue("key", "1") + sut.setValue("somethingElse", "1") + sut.clear(listOf("key")) + + assertEquals("1", sut.getValue("key")) + assertEquals(1, sut.getAll().size) + } + + @Test + fun `preferences removes item`() { + val sut = getSut() + + sut.setValue("key", "value") + sut.remove("key") + + assertNull(sut.getValue("key")) + } + + @Test + fun `preferences returns all items`() { + val sut = getSut() + + sut.setValue("key", "value") + sut.setValue("somethingElse", "value") + + assertEquals("value", sut.getValue("key") as String) + assertEquals("value", sut.getValue("somethingElse") as String) + assertEquals(2, sut.getAll().size) + } +}