Bug 1878185 - Add ChangeDetectionMiddleware

fenix/125.0
Matthew Tighe 4 months ago committed by mergify[bot]
parent 0c7c7a7333
commit 63ba218cd1

@ -0,0 +1,40 @@
/* 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 org.mozilla.fenix.components
import mozilla.components.lib.state.Action
import mozilla.components.lib.state.Middleware
import mozilla.components.lib.state.MiddlewareContext
import mozilla.components.lib.state.State
/**
* A Middleware for detecting changes to a state property, and offering a callback that captures the action that changed
* the property, the property before the change, and the property after the change.
*
* For example, this can be useful for debugging:
* ```
* val selectedTabChangedMiddleware: Middleware<BrowserState, BrowserAction> = ChangeDetectionMiddleware(
* val selector = { it.selectedTabId }
* val onChange = { actionThatCausedResult, preResult, postResult ->
* logger.debug("$actionThatCausedResult changed selectedTabId from $preResult to $postResult")
* }
* ```
*
* @param selector A function to map from the State to the properties that are being inspected.
* @param onChange A callback to react to changes to the properties defined by [selector].
*/
class ChangeDetectionMiddleware<S : State, A : Action, T>(
private val selector: (S) -> T,
private val onChange: (A, pre: T, post: T) -> Unit,
) : Middleware<S, A> {
override fun invoke(context: MiddlewareContext<S, A>, next: (A) -> Unit, action: A) {
val pre = selector(context.store.state)
next(action)
val post = selector(context.store.state)
if (pre != post) {
onChange(action, pre, post)
}
}
}

@ -0,0 +1,102 @@
/* 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 org.mozilla.fenix.components
import mozilla.components.lib.state.Action
import mozilla.components.lib.state.Middleware
import mozilla.components.lib.state.Reducer
import mozilla.components.lib.state.State
import mozilla.components.lib.state.Store
import mozilla.components.support.test.ext.joinBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
class ChangeDetectionMiddlewareTest {
@Test
fun `GIVEN single state property change WHEN action changes that state THEN callback is invoked`() {
var capturedAction: TestAction? = null
var preCount = 0
var postCount = 0
val middleware: Middleware<TestState, TestAction> = ChangeDetectionMiddleware(
selector = { it.counter },
onChange = { action, pre, post ->
capturedAction = action
preCount = pre
postCount = post
},
)
val store = TestStore(
TestState(counter = preCount, enabled = false),
::reducer,
listOf(middleware),
)
store.dispatch(TestAction.IncrementAction).joinBlocking()
assertTrue(capturedAction is TestAction.IncrementAction)
assertEquals(0, preCount)
assertEquals(1, postCount)
store.dispatch(TestAction.DecrementAction).joinBlocking()
assertTrue(capturedAction is TestAction.DecrementAction)
assertEquals(1, preCount)
assertEquals(0, postCount)
}
@Test
fun `GIVEN multiple state property change WHEN action changes any state THEN callback is invoked`() {
var capturedAction: TestAction? = null
var preState = listOf<Any>()
var postState = listOf<Any>()
val middleware: Middleware<TestState, TestAction> = ChangeDetectionMiddleware(
selector = { listOf(it.counter, it.enabled) },
onChange = { action, pre, post ->
capturedAction = action
preState = pre
postState = post
},
)
val store = TestStore(
TestState(counter = 0, enabled = false),
::reducer,
listOf(middleware),
)
store.dispatch(TestAction.SetEnabled(true)).joinBlocking()
assertTrue(capturedAction is TestAction.SetEnabled)
assertEquals(false, preState[1])
assertEquals(true, postState[1])
store.dispatch(TestAction.SetEnabled(false)).joinBlocking()
assertTrue(capturedAction is TestAction.SetEnabled)
assertEquals(true, preState[1])
assertEquals(false, postState[1])
}
private class TestStore(
initialState: TestState,
reducer: Reducer<TestState, TestAction>,
middleware: List<Middleware<TestState, TestAction>>,
) : Store<TestState, TestAction>(initialState, reducer, middleware)
private data class TestState(
val counter: Int,
val enabled: Boolean,
) : State
private sealed class TestAction : Action {
object IncrementAction : TestAction()
object DecrementAction : TestAction()
data class SetEnabled(val enabled: Boolean) : TestAction()
}
private 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)
is TestAction.SetEnabled -> state.copy(enabled = action.enabled)
}
}
Loading…
Cancel
Save