Skip to content

Commit

Permalink
Merge branch 'update_docs' of https://github.com/freeletics/FlowRedux
Browse files Browse the repository at this point in the history
…into update_docs
  • Loading branch information
sockeqwe committed Jul 27, 2023
2 parents 2514bc3 + c53f27b commit 5738eea
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class ConditionBuilderBlock<InputState : S, S : Any, A : Any> internal co
* Afterwards a the blocks are started again for the new `identity`.
*/
public fun untilIdentityChanges(
identity: (InputState) -> Any,
identity: (InputState) -> Any?,
block: IdentityBuilderBlock<InputState, S, A>.() -> Unit,
) {
sideEffectBuilders += IdentityBuilderBlock<InputState, S, A>(isInState, identity)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
@FlowReduxDsl
public class IdentityBuilderBlock<InputState : S, S : Any, A : Any> internal constructor(
override val isInState: SideEffectBuilder.IsInState<S>,
private val identity: (InputState) -> Any,
private val identity: (InputState) -> Any?,
) : BaseBuilderBlock<InputState, S, A>() {

@Suppress("UNCHECKED_CAST")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class InStateBuilderBlock<InputState : S, S : Any, A : Any> internal cons
* Afterwards a the blocks are started again for the new `identity`.
*/
public fun untilIdentityChanges(
identity: (InputState) -> Any,
identity: (InputState) -> Any?,
block: IdentityBuilderBlock<InputState, S, A>.() -> Unit,
) {
sideEffectBuilders += IdentityBuilderBlock<InputState, S, A>(isInState, identity)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ internal sealed class TestState {

data class GenericState(val aString: String, val anInt: Int) : TestState()

data class GenericNullableState(val aString: String?, val anInt: Int?) : TestState()

data class CounterState(val counter: Int) : TestState()
}
Original file line number Diff line number Diff line change
Expand Up @@ -215,4 +215,204 @@ internal class IdentityBlockTest {
assertEquals(1, cancellations[0].first)
assertIsNot<StateChangeCancellationException>(cancellations[0].second)
}

@Test
fun blockStartsWhenIdentityChangesBetweenNullAndNotNull() = runTest {
var counter = 0

val gs1 = TestState.GenericNullableState(null, null)

val sm = StateMachine {
inState<TestState.Initial> {
on<TestAction.A1> { _, state ->
state.override { gs1 }
}
}

inState<TestState.GenericNullableState> {
untilIdentityChanges({ it.anInt }) {
onEnterEffect {
counter++
}
}

on<TestAction.A1> { _, state ->
state.mutate { copy(anInt = (anInt ?: 0) + 1) }
}

on<TestAction.A2> { _, state ->
state.mutate { copy(anInt = null) }
}
}
}

sm.state.test {
assertEquals(TestState.Initial, awaitItem())
sm.dispatchAsync(TestAction.A1)
assertEquals(gs1, awaitItem())
sm.dispatchAsync(TestAction.A1)
assertEquals(gs1.copy(anInt = 1), awaitItem())
sm.dispatchAsync(TestAction.A2)
assertEquals(gs1, awaitItem())
}

assertEquals(3, counter)
}

@Test
fun blockDoesNotStartAgainIfIdentityStaysNull() = runTest {
var counter = 0

val gs1 = TestState.GenericNullableState(null, null)

val sm = StateMachine {
inState<TestState.Initial> {
on<TestAction.A1> { _, state ->
state.override { gs1 }
}
}

inState<TestState.GenericNullableState> {
untilIdentityChanges({ it.anInt }) {
onEnterEffect {
counter++
}
}

on<TestAction.A1> { _, state ->
state.mutate { copy(aString = aString + "1") }
}
}
}

sm.state.test {
assertEquals(TestState.Initial, awaitItem())
sm.dispatchAsync(TestAction.A1)
assertEquals(gs1, awaitItem())
sm.dispatchAsync(TestAction.A1)
assertEquals(gs1.copy(aString = "null1"), awaitItem())
sm.dispatchAsync(TestAction.A1)
assertEquals(gs1.copy(aString = "null11"), awaitItem())
sm.dispatchAsync(TestAction.A1)
assertEquals(gs1.copy(aString = "null111"), awaitItem())
sm.dispatchAsync(TestAction.A1)
assertEquals(gs1.copy(aString = "null1111"), awaitItem())
}

assertEquals(1, counter)
}

@Test
fun blockIsCancelledIfIdentityChangesBetweenNullAndNotNull() = runTest {
val cancellations = mutableListOf<Pair<Int?, Throwable>>()

val gs1 = TestState.GenericNullableState(null, null)

val sm = StateMachine {
inState<TestState.Initial> {
on<TestAction.A1> { _, state ->
state.override { gs1 }
}
}

inState<TestState.GenericNullableState> {
untilIdentityChanges({ it.anInt }) {
onEnter {
try {
awaitCancellation()
} catch (t: Throwable) {
cancellations.add(it.snapshot.anInt to t)
throw t
}
}
}

on<TestAction.A1> { _, state ->
state.mutate { copy(anInt = (anInt ?: 0) + 1) }
}
}
}

sm.state.test {
assertEquals(TestState.Initial, awaitItem())
sm.dispatchAsync(TestAction.A1)
assertEquals(gs1, awaitItem())
sm.dispatchAsync(TestAction.A1)
assertEquals(gs1.copy(anInt = 1), awaitItem())
sm.dispatchAsync(TestAction.A1)
assertEquals(gs1.copy(anInt = 2), awaitItem())
sm.dispatchAsync(TestAction.A1)
assertEquals(gs1.copy(anInt = 3), awaitItem())
sm.dispatchAsync(TestAction.A1)
assertEquals(gs1.copy(anInt = 4), awaitItem())

assertEquals(4, cancellations.size)
}

assertEquals(5, cancellations.size)
assertEquals(null, cancellations[0].first)
assertIs<StateChangeCancellationException>(cancellations[0].second)
assertEquals(1, cancellations[1].first)
assertIs<StateChangeCancellationException>(cancellations[1].second)
assertEquals(2, cancellations[2].first)
assertIs<StateChangeCancellationException>(cancellations[2].second)
assertEquals(3, cancellations[3].first)
assertIs<StateChangeCancellationException>(cancellations[3].second)
// this last cancellation comes when the state machine shuts down
assertEquals(4, cancellations[4].first)
assertIsNot<StateChangeCancellationException>(cancellations[4].second)
}

@Test
fun blockIsNotCancelledIfIdentityStaysNull() = runTest {
val cancellations = mutableListOf<Pair<Int?, Throwable>>()

val gs1 = TestState.GenericNullableState(null, null)

val sm = StateMachine {
inState<TestState.Initial> {
on<TestAction.A1> { _, state ->
state.override { gs1 }
}
}

inState<TestState.GenericNullableState> {
untilIdentityChanges({ it.anInt }) {
onEnter {
try {
awaitCancellation()
} catch (t: Throwable) {
cancellations.add(it.snapshot.anInt to t)
throw t
}
}
}

on<TestAction.A1> { _, state ->
state.mutate { copy(aString = aString + "1") }
}
}
}

sm.state.test {
assertEquals(TestState.Initial, awaitItem())
sm.dispatchAsync(TestAction.A1)
assertEquals(gs1, awaitItem())
sm.dispatchAsync(TestAction.A1)
assertEquals(gs1.copy(aString = "null1"), awaitItem())
sm.dispatchAsync(TestAction.A1)
assertEquals(gs1.copy(aString = "null11"), awaitItem())
sm.dispatchAsync(TestAction.A1)
assertEquals(gs1.copy(aString = "null111"), awaitItem())
sm.dispatchAsync(TestAction.A1)
assertEquals(gs1.copy(aString = "null1111"), awaitItem())

assertEquals(0, cancellations.size)
}

assertEquals(1, cancellations.size)
// this cancellation comes when the state machine shuts down
assertEquals(null, cancellations[0].first)
assertIsNot<StateChangeCancellationException>(cancellations[0].second)
}
}
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ android-target = "33"
android-compile = "33"
androidx-activity = "1.7.2"
androidx-appcompat = "1.6.1"
androidx-compose-compiler = "1.5.0"
androidx-compose-compiler = "1.5.1"
androidx-compose-runtime = "1.4.3"
androidx-compose-ui = "1.4.3"
androidx-compose-foundation = "1.4.3"
Expand All @@ -22,7 +22,7 @@ androidx-constraintlayout = "2.1.4"
androidx-core = "1.10.1"
androidx-lifecycle = "2.6.1"
androidx-fragment = "1.5.2"
androidx-recyclerview = "1.3.0"
androidx-recyclerview = "1.3.1"

material = "1.9.0"
adapterdelegates = "4.3.2"
Expand Down

0 comments on commit 5738eea

Please sign in to comment.