Bug 1878185 - Add ChangeDetectionMiddleware
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…
Reference in New Issue