diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
commit | d8bbc7858622b6d9c278469aab701ca0b609cddf (patch) | |
tree | eff41dc61d9f714852212739e6b3738b82a2af87 /mobile/android/android-components/components/support/test-libstate | |
parent | Releasing progress-linux version 125.0.3-1~progress7.99u1. (diff) | |
download | firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.tar.xz firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.zip |
Merging upstream version 126.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'mobile/android/android-components/components/support/test-libstate')
6 files changed, 225 insertions, 0 deletions
diff --git a/mobile/android/android-components/components/support/test-libstate/README.md b/mobile/android/android-components/components/support/test-libstate/README.md new file mode 100644 index 0000000000..2756befdfc --- /dev/null +++ b/mobile/android/android-components/components/support/test-libstate/README.md @@ -0,0 +1,19 @@ +# [Android Components](../../../README.md) > Support > Test + +A collection of helpers for testing functionality that relies on the lib-state component in local unit tests (`src/test`). + +## Usage + +### Setting up the dependency + +Use Gradle to download the library from [maven.mozilla.org](https://maven.mozilla.org/) ([Setup repository](../../../README.md#maven-repository)): + +```Groovy +implementation "org.mozilla.components:support-test-libstate:{latest-version}" +``` + +## License + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/ diff --git a/mobile/android/android-components/components/support/test-libstate/build.gradle b/mobile/android/android-components/components/support/test-libstate/build.gradle new file mode 100644 index 0000000000..b67bf66a7a --- /dev/null +++ b/mobile/android/android-components/components/support/test-libstate/build.gradle @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + defaultConfig { + minSdkVersion config.minSdkVersion + compileSdk config.compileSdkVersion + targetSdkVersion config.targetSdkVersion + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt') + } + } + + lint { + // Disabled since this caused issues with Android Gradle Plugin 3.2.1+ (NullPointerException:InvalidPackageDetector) + tasks.lint.enabled = false + + lintConfig file("lint.xml") + } + + namespace 'mozilla.components.support.test.libstate' +} + +dependencies { + implementation ComponentsDependencies.kotlin_coroutines + implementation project(':lib-state') + + testImplementation ComponentsDependencies.androidx_core + testImplementation ComponentsDependencies.androidx_test_junit +} + +apply from: '../../../android-lint.gradle' +apply from: '../../../publish.gradle' +ext.configurePublish(config.componentsGroupId, archivesBaseName, project.ext.description) + diff --git a/mobile/android/android-components/components/support/test-libstate/src/main/AndroidManifest.xml b/mobile/android/android-components/components/support/test-libstate/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..41078a7325 --- /dev/null +++ b/mobile/android/android-components/components/support/test-libstate/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this file, + - You can obtain one at http://mozilla.org/MPL/2.0/. --> +<manifest /> diff --git a/mobile/android/android-components/components/support/test-libstate/src/main/java/mozilla/components/support/test/libstate/ext/Store.kt b/mobile/android/android-components/components/support/test-libstate/src/main/java/mozilla/components/support/test/libstate/ext/Store.kt new file mode 100644 index 0000000000..8620ca6e90 --- /dev/null +++ b/mobile/android/android-components/components/support/test-libstate/src/main/java/mozilla/components/support/test/libstate/ext/Store.kt @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.support.test.libstate.ext + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.runBlocking +import mozilla.components.lib.state.Action +import mozilla.components.lib.state.State +import mozilla.components.lib.state.Store + +/** + * Blocks and returns once all dispatched actions have been processed + * i.e. the reducers have run and all observers have been notified of + * state changes. + */ +fun <S : State, A : Action> Store<S, A>.waitUntilIdle() { + val scopeField = Store::class.java.getDeclaredField("scope") + scopeField.isAccessible = true + val scope = scopeField.get(this) as CoroutineScope + runBlocking { + scope.coroutineContext[Job]?.children?.forEach { it.join() } + } +} diff --git a/mobile/android/android-components/components/support/test-libstate/src/main/java/mozilla/components/support/test/middleware/CaptureActionsMiddleware.kt b/mobile/android/android-components/components/support/test-libstate/src/main/java/mozilla/components/support/test/middleware/CaptureActionsMiddleware.kt new file mode 100644 index 0000000000..55670446d0 --- /dev/null +++ b/mobile/android/android-components/components/support/test-libstate/src/main/java/mozilla/components/support/test/middleware/CaptureActionsMiddleware.kt @@ -0,0 +1,88 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.support.test.middleware + +import mozilla.components.lib.state.Action +import mozilla.components.lib.state.Middleware +import mozilla.components.lib.state.MiddlewareContext +import mozilla.components.lib.state.State +import kotlin.reflect.KClass + +/** + * A [Middleware] implementation for unit tests that want to inspect actions dispatched on a `Store` + */ +class CaptureActionsMiddleware<S : State, A : Action> : Middleware<S, A> { + private val capturedActions = mutableListOf<A>() + + @Synchronized + override fun invoke(context: MiddlewareContext<S, A>, next: (A) -> Unit, action: A) { + capturedActions.add(action) + next(action) + } + + /** + * Returns the first action of type [clazz] that was dispatched on the store. Throws + * [AssertionError] if no such action was dispatched. + */ + @Synchronized + @Suppress("UNCHECKED_CAST") + fun <X : A> findFirstAction(clazz: KClass<X>): X { + return capturedActions.firstOrNull { it.javaClass == clazz.java } as? X + ?: throw AssertionError("No action of type $clazz found") + } + + /** + * Returns the last action of type [clazz] that was dispatched on the store. Throws + * [AssertionError] if no such action was dispatched. + */ + @Synchronized + @Suppress("UNCHECKED_CAST") + fun <X : A> findLastAction(clazz: KClass<X>): X { + return capturedActions.lastOrNull { it.javaClass == clazz.java } as? X + ?: throw AssertionError("No action of type $clazz found") + } + + /** + * Asserts that an action of type [clazz] was dispatched and optionally executes a given [block] + * with the first action of type [clazz] that was dispatched on the store. Throws [AssertionError] + * if no such action was dispatched. + */ + @Synchronized + fun <X : A> assertFirstAction(clazz: KClass<X>, block: (X) -> Unit = {}) { + val action = findFirstAction(clazz) + block(action) + } + + /** + * Executes the given [block] with the last action of type [clazz] that was dispatched on the + * store. Throws [AssertionError] if no such action was dispatched. + */ + @Synchronized + fun <X : A> assertLastAction(clazz: KClass<X>, block: (X) -> Unit) { + val action = findLastAction(clazz) + block(action) + } + + /** + * Asserts that no action of type [clazz] was dispatched. Throws [AssertionError] if a matching + * action was found. + */ + @Synchronized + fun <X : A> assertNotDispatched(clazz: KClass<X>) { + if (!capturedActions.none { it.javaClass == clazz.java }) { + throw AssertionError("Action of type $clazz was dispatched: ${findFirstAction(clazz)}") + } + } + + /** + * Resets the remembered list of actions. + * + * Usually this is called between test runs to avoid verifying actions of a previous test methods. + */ + @Synchronized + fun reset() { + capturedActions.clear() + } +} diff --git a/mobile/android/android-components/components/support/test-libstate/src/test/java/mozilla/components/support/test/libstate/ext/StoreTest.kt b/mobile/android/android-components/components/support/test-libstate/src/test/java/mozilla/components/support/test/libstate/ext/StoreTest.kt new file mode 100644 index 0000000000..516206215e --- /dev/null +++ b/mobile/android/android-components/components/support/test-libstate/src/test/java/mozilla/components/support/test/libstate/ext/StoreTest.kt @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.support.test.libstate.ext + +import mozilla.components.lib.state.Action +import mozilla.components.lib.state.State +import mozilla.components.lib.state.Store +import org.junit.Assert.assertEquals +import org.junit.Test + +class StoreTest { + + @Test + fun `waitUntilIdle blocks and returns once reducers were executed`() { + val store = Store( + TestState(counter = 23), + ::reducer, + ) + + store.dispatch(TestAction.IncrementAction) + store.waitUntilIdle() + assertEquals(24, store.state.counter) + + store.dispatch(TestAction.DecrementAction) + store.dispatch(TestAction.DecrementAction) + store.waitUntilIdle() + assertEquals(22, store.state.counter) + } +} + +fun reducer(state: TestState, action: TestAction): TestState = when (action) { + is TestAction.IncrementAction -> state.copy(counter = state.counter + 1) + is TestAction.DecrementAction -> state.copy(counter = state.counter - 1) +} + +data class TestState(val counter: Int) : State + +sealed class TestAction : Action { + object IncrementAction : TestAction() + object DecrementAction : TestAction() +} |