Merge pull request #566 from vector-im/feature/fga/update-rust-sdk-0.1.16
Feature/fga/update rust sdk 0.1.16
This commit is contained in:
commit
511b26b2ab
16 changed files with 251 additions and 666 deletions
|
|
@ -40,32 +40,32 @@ interface SessionVerificationService {
|
|||
/**
|
||||
* Request verification of the current session.
|
||||
*/
|
||||
fun requestVerification()
|
||||
suspend fun requestVerification()
|
||||
|
||||
/**
|
||||
* Cancels the current verification attempt.
|
||||
*/
|
||||
fun cancelVerification()
|
||||
suspend fun cancelVerification()
|
||||
|
||||
/**
|
||||
* Approves the current verification. This must happen on both devices to successfully verify a session.
|
||||
*/
|
||||
fun approveVerification()
|
||||
suspend fun approveVerification()
|
||||
|
||||
/**
|
||||
* Declines the verification attempt because the user could not verify or does not trust the other side of the verification.
|
||||
*/
|
||||
fun declineVerification()
|
||||
suspend fun declineVerification()
|
||||
|
||||
/**
|
||||
* Starts the verification of the unverified session from another device.
|
||||
*/
|
||||
fun startVerification()
|
||||
suspend fun startVerification()
|
||||
|
||||
/**
|
||||
* Returns the verification service state to the initial step.
|
||||
*/
|
||||
fun reset()
|
||||
suspend fun reset()
|
||||
}
|
||||
|
||||
/** Verification status of the current session. */
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ import org.matrix.rustcomponents.sdk.SlidingSyncList
|
|||
import org.matrix.rustcomponents.sdk.SlidingSyncListBuilder
|
||||
import org.matrix.rustcomponents.sdk.SlidingSyncListOnceBuilt
|
||||
import org.matrix.rustcomponents.sdk.SlidingSyncRequestListFilters
|
||||
import org.matrix.rustcomponents.sdk.SlidingSyncSelectiveModeBuilder
|
||||
import org.matrix.rustcomponents.sdk.TaskHandle
|
||||
import org.matrix.rustcomponents.sdk.use
|
||||
import timber.log.Timber
|
||||
|
|
@ -124,8 +125,7 @@ class RustMatrixClient constructor(
|
|||
)
|
||||
)
|
||||
.filters(visibleRoomsSlidingSyncFilters)
|
||||
.syncModeSelective()
|
||||
.addRange(0u, 20u)
|
||||
.syncModeSelective(SlidingSyncSelectiveModeBuilder().addRange(0u, 20u))
|
||||
.onceBuilt(object : SlidingSyncListOnceBuilt {
|
||||
override fun updateList(list: SlidingSyncList): SlidingSyncList {
|
||||
visibleRoomsSlidingSyncList.tryEmit(list)
|
||||
|
|
@ -146,8 +146,7 @@ class RustMatrixClient constructor(
|
|||
)
|
||||
)
|
||||
.filters(invitesSlidingSyncFilters)
|
||||
.syncModeSelective()
|
||||
.addRange(0u, 20u)
|
||||
.syncModeSelective(SlidingSyncSelectiveModeBuilder().addRange(0u, 20u))
|
||||
.onceBuilt(object : SlidingSyncListOnceBuilt {
|
||||
override fun updateList(list: SlidingSyncList): SlidingSyncList {
|
||||
invitesSlidingSyncList.tryEmit(list)
|
||||
|
|
@ -156,10 +155,9 @@ class RustMatrixClient constructor(
|
|||
})
|
||||
|
||||
private val slidingSync = client
|
||||
.slidingSync()
|
||||
.slidingSync("ElementX")
|
||||
// .homeserver("https://slidingsync.lab.matrix.org")
|
||||
.withCommonExtensions()
|
||||
.storageKey("ElementX")
|
||||
.addList(visibleRoomsSlidingSyncListBuilder)
|
||||
.addList(invitesSlidingSyncListBuilder)
|
||||
.use {
|
||||
|
|
|
|||
|
|
@ -75,8 +75,7 @@ class RustMediaLoader(
|
|||
mediaSource = mediaSource,
|
||||
body = body,
|
||||
mimeType = mimeType ?: "application/octet-stream",
|
||||
//TODO uncomment when rust api will be merged
|
||||
//tempDir = cacheDirectory.path,
|
||||
tempDir = cacheDirectory.path,
|
||||
)
|
||||
RustMediaFile(mediaFile)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import org.matrix.rustcomponents.sdk.RoomListEntry
|
|||
import org.matrix.rustcomponents.sdk.SlidingSync
|
||||
import org.matrix.rustcomponents.sdk.SlidingSyncList
|
||||
import org.matrix.rustcomponents.sdk.SlidingSyncListRoomsListDiff
|
||||
import org.matrix.rustcomponents.sdk.SlidingSyncSelectiveModeBuilder
|
||||
import org.matrix.rustcomponents.sdk.SlidingSyncState
|
||||
import org.matrix.rustcomponents.sdk.UpdateSummary
|
||||
import timber.log.Timber
|
||||
|
|
@ -98,7 +99,9 @@ internal class RustRoomSummaryDataSource(
|
|||
override fun setSlidingSyncRange(range: IntRange) {
|
||||
Timber.v("setVisibleRange=$range")
|
||||
coroutineScope.launch {
|
||||
slidingSyncListFlow.first().setRange(range.first.toUInt(), range.last.toUInt())
|
||||
val slidingSyncMode = SlidingSyncSelectiveModeBuilder()
|
||||
.addRange(range.first.toUInt(), range.last.toUInt())
|
||||
slidingSyncListFlow.first().setSyncMode(slidingSyncMode)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ class RustMatrixTimeline(
|
|||
),
|
||||
timelineLimit = null
|
||||
)
|
||||
listenerTokens += slidingSyncRoom.subscribeToRoom(settings)
|
||||
slidingSyncRoom.subscribeToRoom(settings)
|
||||
val result = slidingSyncRoom.addTimelineListener(timelineListener)
|
||||
launch {
|
||||
fetchMembers()
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ package io.element.android.libraries.matrix.impl.verification
|
|||
|
||||
import io.element.android.libraries.core.data.tryOrNull
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationFlowState
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationEmoji
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationFlowState
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
|
@ -52,21 +52,21 @@ class RustSessionVerificationService @Inject constructor() : SessionVerification
|
|||
private val _sessionVerifiedStatus = MutableStateFlow<SessionVerifiedStatus>(SessionVerifiedStatus.Unknown)
|
||||
override val sessionVerifiedStatus: StateFlow<SessionVerifiedStatus> = _sessionVerifiedStatus.asStateFlow()
|
||||
|
||||
override fun requestVerification() = tryOrFail {
|
||||
override suspend fun requestVerification() = tryOrFail {
|
||||
verificationController?.requestVerification()
|
||||
}
|
||||
|
||||
override fun cancelVerification() = tryOrFail { verificationController?.cancelVerification() }
|
||||
override suspend fun cancelVerification() = tryOrFail { verificationController?.cancelVerification() }
|
||||
|
||||
override fun approveVerification() = tryOrFail { verificationController?.approveVerification() }
|
||||
override suspend fun approveVerification() = tryOrFail { verificationController?.approveVerification() }
|
||||
|
||||
override fun declineVerification() = tryOrFail { verificationController?.declineVerification() }
|
||||
override suspend fun declineVerification() = tryOrFail { verificationController?.declineVerification() }
|
||||
|
||||
override fun startVerification() = tryOrFail {
|
||||
override suspend fun startVerification() = tryOrFail {
|
||||
verificationController?.startSasVerification()
|
||||
}
|
||||
|
||||
private fun tryOrFail(block: () -> Unit) {
|
||||
private suspend fun tryOrFail(block: suspend () -> Unit) {
|
||||
runCatching {
|
||||
block()
|
||||
}.onFailure { didFail() }
|
||||
|
|
@ -107,7 +107,7 @@ class RustSessionVerificationService @Inject constructor() : SessionVerification
|
|||
|
||||
// end-region
|
||||
|
||||
override fun reset() {
|
||||
override suspend fun reset() {
|
||||
if (isReady.value) {
|
||||
// Cancel any pending verification attempt
|
||||
tryOrNull { verificationController?.cancelVerification() }
|
||||
|
|
|
|||
|
|
@ -37,17 +37,15 @@ class FakeSessionVerificationService : SessionVerificationService {
|
|||
|
||||
override val isReady: StateFlow<Boolean> = _isReady
|
||||
|
||||
override fun requestVerification() {
|
||||
override suspend fun requestVerification() {
|
||||
_verificationFlowState.value = VerificationFlowState.AcceptedVerificationRequest
|
||||
_verificationFlowState.value = VerificationFlowState.StartedSasVerification
|
||||
_verificationFlowState.value = VerificationFlowState.ReceivedVerificationData(emojiList)
|
||||
}
|
||||
|
||||
override fun cancelVerification() {
|
||||
override suspend fun cancelVerification() {
|
||||
_verificationFlowState.value = VerificationFlowState.Canceled
|
||||
}
|
||||
|
||||
override fun approveVerification() {
|
||||
override suspend fun approveVerification() {
|
||||
if (!shouldFail) {
|
||||
_verificationFlowState.value = VerificationFlowState.Finished
|
||||
} else {
|
||||
|
|
@ -55,7 +53,7 @@ class FakeSessionVerificationService : SessionVerificationService {
|
|||
}
|
||||
}
|
||||
|
||||
override fun declineVerification() {
|
||||
override suspend fun declineVerification() {
|
||||
if (!shouldFail) {
|
||||
_verificationFlowState.value = VerificationFlowState.Canceled
|
||||
} else {
|
||||
|
|
@ -63,11 +61,14 @@ class FakeSessionVerificationService : SessionVerificationService {
|
|||
}
|
||||
}
|
||||
|
||||
override fun startVerification() {
|
||||
_verificationFlowState.value = VerificationFlowState.StartedSasVerification
|
||||
fun triggerReceiveVerificationData() {
|
||||
_verificationFlowState.value = VerificationFlowState.ReceivedVerificationData(emojiList)
|
||||
}
|
||||
|
||||
override suspend fun startVerification() {
|
||||
_verificationFlowState.value = VerificationFlowState.StartedSasVerification
|
||||
}
|
||||
|
||||
fun givenVerifiedStatus(status: SessionVerifiedStatus) {
|
||||
_sessionVerifiedStatus.value = status
|
||||
}
|
||||
|
|
@ -84,7 +85,7 @@ class FakeSessionVerificationService : SessionVerificationService {
|
|||
this.emojiList = emojis
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
override suspend fun reset() {
|
||||
_verificationFlowState.value = VerificationFlowState.Initial
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("java-library")
|
||||
id("com.android.lint")
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.coroutines.core)
|
||||
|
||||
testImplementation(libs.test.junit)
|
||||
testImplementation(libs.test.truth)
|
||||
}
|
||||
|
|
@ -1,192 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.statemachine
|
||||
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
fun <Event : Any, State : Any> createStateMachine(
|
||||
config: StateMachineBuilder<Event, State>.() -> Unit
|
||||
): StateMachine<Event, State> {
|
||||
val builder = StateMachineBuilder<Event, State>()
|
||||
config(builder)
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
class StateMachine<Event : Any, State : Any>(
|
||||
val initialState: State,
|
||||
private val stateConfigs: Map<Class<*>, StateConfig<*>>,
|
||||
private val routes: List<StateMachineRoute<*, *, *>>,
|
||||
) {
|
||||
|
||||
private val _stateFlow = MutableStateFlow(initialState)
|
||||
val stateFlow = _stateFlow.asStateFlow()
|
||||
val currentState: State get() = stateFlow.value
|
||||
|
||||
var transitionHandler: ((State, Event, State) -> Unit)? = null
|
||||
|
||||
init {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val initialStateConfig = stateConfigs[initialState::class.java] as StateConfig<State>
|
||||
initialStateConfig.onEnter?.invoke(initialState)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <E : Event> process(event: E) {
|
||||
val route = findMatchingRoute(event) ?: error("No route found for state $currentState on event $event")
|
||||
|
||||
val lastStateConfig: StateConfig<State>? = stateConfigs[currentState::class.java] as? StateConfig<State>
|
||||
lastStateConfig?.onExit?.invoke(currentState)
|
||||
|
||||
val nextState = route.toState(event, currentState)
|
||||
transitionHandler?.invoke(currentState, event, nextState)
|
||||
_stateFlow.value = nextState
|
||||
|
||||
val currentStateConfig = stateConfigs[nextState::class.java] as? StateConfig<State>
|
||||
currentStateConfig?.onEnter?.invoke(nextState)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun <E : Event> findMatchingRoute(event: E): StateMachineRoute<E, State, State>? {
|
||||
val routesForEvent = routes.filter { it.eventType.isInstance(event) }
|
||||
|
||||
return (routesForEvent.firstOrNull { it.fromState?.isInstance(currentState) == true }
|
||||
?: routesForEvent.firstOrNull { it.fromState == null }) as? StateMachineRoute<E, State, State>
|
||||
}
|
||||
|
||||
fun restart() {
|
||||
_stateFlow.value = initialState
|
||||
}
|
||||
}
|
||||
|
||||
class StateMachineBuilder<Event : Any, State : Any>(
|
||||
val routes: MutableList<StateMachineRoute<out Event, out State, out State>> = mutableListOf(),
|
||||
) {
|
||||
|
||||
lateinit var initialState: State
|
||||
var stateConfigs = mutableMapOf<Class<out State>, StateConfig<out State>>()
|
||||
|
||||
inline fun <reified S : State> addState(block: StateRegistrationBuilder<Event, State, S>.() -> Unit = {}) {
|
||||
val config = StateConfig(S::class.java)
|
||||
val registrationBuilder = StateRegistrationBuilder<Event, State, S>(config)
|
||||
block(registrationBuilder)
|
||||
|
||||
verifyRoutesAreUnique(S::class.java, routes, registrationBuilder.routes)
|
||||
|
||||
if (stateConfigs.contains(S::class.java)) {
|
||||
error("Duplicate registration for state ${S::class.java.name}")
|
||||
}
|
||||
stateConfigs[S::class.java] = config
|
||||
routes.addAll(registrationBuilder.routes)
|
||||
}
|
||||
|
||||
inline fun <reified S : State> addInitialState(state: S, config: StateRegistrationBuilder<Event, State, S>.() -> Unit = {}) {
|
||||
initialState = state
|
||||
addState(block = config)
|
||||
}
|
||||
|
||||
inline fun <reified E : Event, reified S : State> on(noinline configuration: (E, State) -> S) {
|
||||
val builder = RouteBuilder<E, State, S>(E::class.java, null)
|
||||
builder.toState = configuration
|
||||
val newRoute = builder.build()
|
||||
verifyRoutesAreUnique(S::class.java, routes, listOf(newRoute))
|
||||
routes.add(newRoute)
|
||||
}
|
||||
|
||||
inline fun <reified E : Event> on(newState: State) {
|
||||
val builder = RouteBuilder<E, State, State>(E::class.java, null)
|
||||
builder.toState = { _, _ -> newState }
|
||||
val newRoute = builder.build()
|
||||
verifyRoutesAreUnique(null, routes, listOf(newRoute))
|
||||
routes.add(newRoute)
|
||||
}
|
||||
|
||||
fun build(): StateMachine<Event, State> {
|
||||
if (::initialState.isInitialized) {
|
||||
return StateMachine(initialState, stateConfigs.toMap(), routes)
|
||||
} else {
|
||||
error("The state machine has no initial state")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun verifyRoutesAreUnique(
|
||||
state: Class<*>?,
|
||||
oldRoutes: List<StateMachineRoute<*, *, *>>,
|
||||
newRoutes: List<StateMachineRoute<*, *, *>>,
|
||||
) {
|
||||
val oldEvents = oldRoutes.filter { it.fromState == state }.map { it.eventType }
|
||||
val newEvents = newRoutes.filter { it.fromState == state }.map { it.eventType }
|
||||
val intersection = oldEvents.intersect(newEvents)
|
||||
if (intersection.isNotEmpty()) {
|
||||
val duplicates = intersection.joinToString(", ") { it.name }
|
||||
error("Duplicate registration in state ${state?.name} for events: $duplicates")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class StateRegistrationBuilder<Event : Any, BaseState : Any, State : BaseState>(
|
||||
val fromState: StateConfig<State>,
|
||||
val routes: MutableList<StateMachineRoute<out Event, out State, out BaseState>> = mutableListOf(),
|
||||
) {
|
||||
|
||||
fun onEnter(enter: (State) -> Unit) {
|
||||
fromState.onEnter = enter
|
||||
}
|
||||
|
||||
fun onExit(exit: (State) -> Unit) {
|
||||
fromState.onExit = exit
|
||||
}
|
||||
|
||||
inline fun <reified E : Event> on(noinline configuration: (E, State) -> BaseState) {
|
||||
val builder = RouteBuilder<E, State, BaseState>(E::class.java, fromState.state)
|
||||
builder.toState = configuration
|
||||
val newRoute = builder.build()
|
||||
StateMachineBuilder.verifyRoutesAreUnique(fromState.state, routes, listOf(newRoute))
|
||||
routes.add(newRoute)
|
||||
}
|
||||
|
||||
inline fun <reified E : Event> on(newState: BaseState) {
|
||||
val builder = RouteBuilder<E, State, BaseState>(E::class.java, fromState.state)
|
||||
builder.toState = { _, _ -> newState }
|
||||
val newRoute = builder.build()
|
||||
StateMachineBuilder.verifyRoutesAreUnique(fromState.state, routes, listOf(newRoute))
|
||||
routes.add(newRoute)
|
||||
}
|
||||
}
|
||||
|
||||
class RouteBuilder<Event : Any, FromState : Any, ToState : Any>(
|
||||
val eventType: Class<out Event>,
|
||||
val fromState: Class<out FromState>?,
|
||||
) {
|
||||
lateinit var toState: (Event, FromState) -> ToState
|
||||
|
||||
fun build() = StateMachineRoute(eventType, fromState, toState)
|
||||
}
|
||||
|
||||
data class StateMachineRoute<Event : Any, FromState : Any, ToState : Any>(
|
||||
val eventType: Class<out Event>,
|
||||
val fromState: Class<out FromState>?,
|
||||
val toState: (Event, FromState) -> ToState,
|
||||
)
|
||||
|
||||
data class StateConfig<State : Any>(
|
||||
val state: Class<State>,
|
||||
var onEnter: ((State) -> Unit)? = null,
|
||||
var onExit: ((State) -> Unit)? = null,
|
||||
)
|
||||
|
|
@ -1,202 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.statemachine
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Assert.fail
|
||||
import org.junit.Test
|
||||
|
||||
class StateMachineTests {
|
||||
|
||||
sealed interface Events {
|
||||
data class GoToSecond(val string: String) : Events
|
||||
|
||||
object GoToThird : Events
|
||||
|
||||
object GoToFourth : Events
|
||||
|
||||
object Cancel : Events
|
||||
}
|
||||
|
||||
sealed interface States {
|
||||
object First : States
|
||||
|
||||
data class Second(val string: String) : States
|
||||
|
||||
object Third : States
|
||||
|
||||
object Fourth : States
|
||||
object Canceled : States
|
||||
}
|
||||
|
||||
private var enteredSecondState = false
|
||||
private var exitedFirstState = false
|
||||
private var transitionHandlerParams: Triple<States, Events, States>? = null
|
||||
private fun aStateMachine() = createStateMachine<Events, States> {
|
||||
addInitialState(States.First) {
|
||||
onExit { exitedFirstState = true }
|
||||
on<Events.GoToSecond> { first, _ ->
|
||||
States.Second(first.string)
|
||||
}
|
||||
}
|
||||
addState<States.Second> {
|
||||
onEnter { enteredSecondState = true }
|
||||
on<Events.GoToThird>(States.Third)
|
||||
}
|
||||
|
||||
addState<States.Fourth>()
|
||||
|
||||
on<Events.GoToFourth, States.Fourth> { _, _ -> States.Fourth }
|
||||
on<Events.Cancel>(States.Canceled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `process - moves to next state given an event if the route exists`() = aStateMachine().run {
|
||||
process(Events.GoToSecond("Hello"))
|
||||
assertThat(currentState).isEqualTo(States.Second("Hello"))
|
||||
process(Events.GoToThird)
|
||||
assertThat(currentState).isEqualTo(States.Third)
|
||||
process(Events.GoToFourth)
|
||||
assertThat(currentState).isEqualTo(States.Fourth)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `process - throws exception if there is no route for an event in a state`() = aStateMachine().run {
|
||||
runCatching {
|
||||
process(Events.GoToThird)
|
||||
}.onSuccess {
|
||||
fail("It should have thrown an error")
|
||||
}.onFailure {
|
||||
assertThat(it.message).startsWith("No route found for state")
|
||||
}
|
||||
Unit
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `process - calls onEnter and onExit callbacks when moving through states`() = aStateMachine().run {
|
||||
process(Events.GoToSecond("Hello"))
|
||||
assertThat(currentState).isEqualTo(States.Second("Hello"))
|
||||
|
||||
assertThat(exitedFirstState).isTrue()
|
||||
assertThat(enteredSecondState).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `process - if an Event route is registered inside a state and outside it, the internal registration takes precedence`() {
|
||||
val customStateMachine = createStateMachine {
|
||||
addInitialState(States.First) {
|
||||
on<Events.Cancel>(States.Canceled)
|
||||
}
|
||||
on<Events.Cancel>(States.Fourth)
|
||||
}
|
||||
customStateMachine.process(Events.Cancel)
|
||||
assertThat(customStateMachine.currentState).isEqualTo(States.Canceled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `transitionHandler - is called when moving from a state to another`() = aStateMachine().run {
|
||||
transitionHandler = { from, event, to ->
|
||||
transitionHandlerParams = Triple(from, event, to)
|
||||
}
|
||||
|
||||
process(Events.GoToSecond("Hello"))
|
||||
|
||||
assertThat(transitionHandlerParams).isEqualTo(
|
||||
Triple(
|
||||
States.First,
|
||||
Events.GoToSecond("Hello"),
|
||||
States.Second("Hello"),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `restart - sets the state machine to its initial state`() {
|
||||
val customStateMachine = createStateMachine {
|
||||
addInitialState(States.First)
|
||||
on<Events.GoToFourth>(States.Fourth)
|
||||
}
|
||||
customStateMachine.process(Events.GoToFourth)
|
||||
assertThat(customStateMachine.currentState).isEqualTo(States.Fourth)
|
||||
|
||||
customStateMachine.restart()
|
||||
assertThat(customStateMachine.currentState).isEqualTo(customStateMachine.initialState)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `init - the state machine must have registered a initial state`() {
|
||||
runCatching {
|
||||
createStateMachine<Events, States> {
|
||||
addState<States.Second>()
|
||||
on<Events.Cancel>(States.Canceled)
|
||||
}
|
||||
}.onSuccess {
|
||||
fail("It should have thrown an error")
|
||||
}.onFailure { error ->
|
||||
assertThat(error.message).isEqualTo("The state machine has no initial state")
|
||||
}
|
||||
Unit
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `init - the state machine having duplicate registrations for a state throws an error`() {
|
||||
runCatching {
|
||||
createStateMachine<Events, States> {
|
||||
addInitialState(States.First)
|
||||
addState<States.First>()
|
||||
}
|
||||
}.onSuccess {
|
||||
fail("It should have thrown an error")
|
||||
}.onFailure { error ->
|
||||
assertThat(error.message).startsWith("Duplicate registration for state ")
|
||||
}
|
||||
Unit
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `init - the state machine having duplicate registrations for an event inside a state throws an error`() {
|
||||
runCatching {
|
||||
createStateMachine<Events, States> {
|
||||
addInitialState(States.First) {
|
||||
on<Events.GoToThird>(States.Third)
|
||||
on<Events.GoToThird> { _, _ -> States.Third }
|
||||
}
|
||||
}
|
||||
}.onSuccess {
|
||||
fail("It should have thrown an error")
|
||||
}.onFailure { error ->
|
||||
assertThat(error.message).startsWith("Duplicate registration in state")
|
||||
}
|
||||
Unit
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `init - the state machine having duplicate registrations for an event at the root level throws an error`() {
|
||||
runCatching {
|
||||
createStateMachine<Events, States> {
|
||||
addInitialState(States.First)
|
||||
on<Events.GoToThird>(States.Third)
|
||||
on<Events.GoToThird>(States.Third)
|
||||
}
|
||||
}.onSuccess {
|
||||
fail("It should have thrown an error")
|
||||
}.onFailure { error ->
|
||||
assertThat(error.message).startsWith("Duplicate registration in state")
|
||||
}
|
||||
Unit
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue