diff --git a/buildSrc/src/main/kotlin/ru/nsk/Versions.kt b/buildSrc/src/main/kotlin/ru/nsk/Versions.kt index 392721e..8d08081 100644 --- a/buildSrc/src/main/kotlin/ru/nsk/Versions.kt +++ b/buildSrc/src/main/kotlin/ru/nsk/Versions.kt @@ -1,5 +1,3 @@ -import org.gradle.api.JavaVersion - object Versions { // library const val libraryMavenCentralGroup = "io.github.nsk90" diff --git a/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/BaseStateImpl.kt b/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/BaseStateImpl.kt index 668d595..0749ab7 100644 --- a/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/BaseStateImpl.kt +++ b/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/BaseStateImpl.kt @@ -162,9 +162,7 @@ open class BaseStateImpl(override val name: String?, override val childMode: Chi when (childMode) { EXCLUSIVE -> { - val initialState = checkNotNull(initialState) { - "Initial state is not set, call setInitialState() first" - } + val initialState = requireInitialState() as InternalState setCurrentState(initialState, transitionParams) if (initialState !is StateMachine) // inner state machine manages its internal state by its own initialState.recursiveEnterInitialStates(transitionParams) @@ -255,8 +253,6 @@ open class BaseStateImpl(override val name: String?, override val childMode: Chi data.states.forEachState { it.recursiveAfterTransitionComplete(transitionParams) } } - private fun requireCurrentState() = requireNotNull(data.currentState) { "Current state is not set" } - override fun getCurrentStates() = when (childMode) { EXCLUSIVE -> listOfNotNull(data.currentState) PARALLEL -> data.states.toList() diff --git a/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/EventMatcher.kt b/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/EventMatcher.kt index 6d14127..5d44661 100644 --- a/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/EventMatcher.kt +++ b/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/EventMatcher.kt @@ -25,5 +25,5 @@ inline fun TransitionBuilder.isEqual() = object : EventMa } fun finishedEventMatcher(state: IState) = object : EventMatcher(FinishedEvent::class) { - override suspend fun match(value: Event) = if (value is FinishedEvent) value.state === state else false + override suspend fun match(value: Event) = value is FinishedEvent && value.state === state } \ No newline at end of file diff --git a/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/IState.kt b/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/IState.kt index 3c3371a..d230dd3 100644 --- a/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/IState.kt +++ b/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/IState.kt @@ -124,6 +124,10 @@ interface HistoryState : PseudoState { typealias StateBlock = S.() -> Unit +fun IState.requireInitialState() = checkNotNull(initialState) { + "Initial state is not set, call setInitialState() first" +} + /** * Set of states that the state is currently in. Including state itself if [selfIncluding] is true. * Internal states of nested machines are not included. diff --git a/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/StateMachineImpl.kt b/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/StateMachineImpl.kt index 2526450..e779315 100644 --- a/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/StateMachineImpl.kt +++ b/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/StateMachineImpl.kt @@ -94,7 +94,7 @@ internal class StateMachineImpl( check(!isRunning) { "$this is already started" } check(!isProcessingEvent) { "$this is already processing event, this is internal error, please report a bug" } if (childMode == ChildMode.EXCLUSIVE) - checkNotNull(initialState) { "Initial state is not set, call setInitialState() first" } + requireInitialState() } /** To be called only from [runCheckingExceptions] */ diff --git a/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/TransitionDirection.kt b/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/TransitionDirection.kt index 4f8d667..cb9108b 100644 --- a/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/TransitionDirection.kt +++ b/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/TransitionDirection.kt @@ -101,9 +101,6 @@ private suspend fun EventAndArgument<*>.recursiveResolveTargetState(targetState: } } -/** - * Internal use only. TODO remove it when possible - */ internal fun unresolvedTargetState(targetState: IState): TransitionDirection = TargetState(setOf(targetState)) /** @@ -154,9 +151,7 @@ private fun IState.findInitialPseudoState(): PseudoState? { if (states.isEmpty()) return null when (childMode) { ChildMode.EXCLUSIVE -> { - val initialState = checkNotNull(initialState) { - "Initial state is not set, call setInitialState() first" - } + val initialState = requireInitialState() return if (initialState !is StateMachine) // inner state machine manages its internal state by its own initialState.findInitialPseudoState() else @@ -173,7 +168,7 @@ private fun IState.findInitialPseudoState(): PseudoState? { return if (initialStates.isEmpty()) null else - initialStates.first() // fixme take first or other else?? + initialStates.first() // take first or other else? } } } \ No newline at end of file diff --git a/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/ExportToPlantUmlTest.kt b/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/ExportToPlantUmlTest.kt index dddad6d..0c71be7 100644 --- a/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/ExportToPlantUmlTest.kt +++ b/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/ExportToPlantUmlTest.kt @@ -186,6 +186,51 @@ state1 --> state222 @enduml """ +private fun makeNestedMachine(coroutineStarterType: CoroutineStarterType): StateMachine { + return createTestStateMachine(coroutineStarterType, name = "Nested states") { + val state1 = initialState("State1") + val state3 = finalState("State3") + + val state2 = state("State2") { + transition { targetState = state3 } + transition("back") { targetState = state1 } + + val finalSubState = finalState("Final subState") + initialState("Initial subState") { + transition { targetState = finalSubState } + } + } + + state1 { + transition("to ${state2.name}") { targetState = state2 } + transition { targetState = this@state1 } + transition() + } + } +} + +private fun makeChoiceMachine(coroutineStarterType: CoroutineStarterType): StateMachine { + return createTestStateMachine(coroutineStarterType, enableUndo = true) { + val state1 = initialState("state1") + + val state2 = state("state2") { + initialState("state21") { + initialState("state211") + } + state("state22") + } + val shallowHistory = state2.historyState("shallow history") + val deepHistory = state2.historyState("deep history", historyType = DEEP) + + state("state3") { + transition(targetState = shallowHistory) + transition(targetState = deepHistory) + } + choiceState("choice") { state1 } + finalState("final") + } +} + class ExportToPlantUmlTest : StringSpec({ CoroutineStarterType.entries.forEach { coroutineStarterType -> table( @@ -194,53 +239,13 @@ class ExportToPlantUmlTest : StringSpec({ row(true, PLANTUML_NESTED_STATES_SHOW_EVENT_LABELS_RESULT), ).forAll { showEventLabels, result -> "plantUml export nested states" { - val machine = createTestStateMachine(coroutineStarterType, name = "Nested states") { - val state1 = initialState("State1") - val state3 = finalState("State3") - - val state2 = state("State2") { - transition { targetState = state3 } - transition("back") { targetState = state1 } - - val finalSubState = finalState("Final subState") - initialState("Initial subState") { - transition { targetState = finalSubState } - } - } - - state1 { - transition("to ${state2.name}") { targetState = state2 } - transition { targetState = this@state1 } - transition() - } - } - + val machine = makeNestedMachine(coroutineStarterType) machine.exportToPlantUml(showEventLabels) shouldBe result } } "Mermaid export nested states" { - val machine = createTestStateMachine(coroutineStarterType, name = "Nested states") { - val state1 = initialState("State1") - val state3 = finalState("State3") - - val state2 = state("State2") { - transition { targetState = state3 } - transition("back") { targetState = state1 } - - val finalSubState = finalState("Final subState") - initialState("Initial subState") { - transition { targetState = finalSubState } - } - } - - state1 { - transition("to ${state2.name}") { targetState = state2 } - transition { targetState = this@state1 } - transition() - } - } - + val machine = makeNestedMachine(coroutineStarterType) machine.exportToMermaid() shouldBe MERMAID_NESTED_STATES_RESULT } @@ -276,50 +281,12 @@ class ExportToPlantUmlTest : StringSpec({ } "plantUml export with pseudo states" { - val machine = createTestStateMachine(coroutineStarterType, enableUndo = true) { - val state1 = initialState("state1") - - val state2 = state("state2") { - initialState("state21") { - initialState("state211") - } - state("state22") - } - val shallowHistory = state2.historyState("shallow history") - val deepHistory = state2.historyState("deep history", historyType = DEEP) - - state("state3") { - transition(targetState = shallowHistory) - transition(targetState = deepHistory) - } - choiceState("choice") { state1 } - finalState("final") - } - + val machine = makeChoiceMachine(coroutineStarterType) machine.exportToPlantUml() shouldBe PLANTUML_PSEUDO_STATES_RESULT } "plantUml unsafe export with pseudo states" { - val machine = createTestStateMachine(coroutineStarterType, enableUndo = true) { - val state1 = initialState("state1") - - val state2 = state("state2") { - initialState("state21") { - initialState("state211") - } - state("state22") - } - val shallowHistory = state2.historyState("shallow history") - val deepHistory = state2.historyState("deep history", historyType = DEEP) - - state("state3") { - transition(targetState = shallowHistory) - transition(targetState = deepHistory) - } - choiceState("choice") { state1 } - finalState("final") - } - + val machine = makeChoiceMachine(coroutineStarterType) machine.exportToPlantUml(unsafeCallConditionalLambdas = true) shouldBe PLANTUML_UNSAFE_PSEUDO_STATES_RESULT } diff --git a/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/ListenerExceptionHandlerTest.kt b/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/ListenerExceptionHandlerTest.kt index eae7b02..5f6282d 100644 --- a/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/ListenerExceptionHandlerTest.kt +++ b/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/ListenerExceptionHandlerTest.kt @@ -14,7 +14,7 @@ class ListenerExceptionHandlerTest : StringSpec({ logger = StateMachine.Logger { println(it()) } initialState { - onEntry { testError("test exception") } + onEntry { testError() } } } } @@ -23,7 +23,7 @@ class ListenerExceptionHandlerTest : StringSpec({ "default ListenerExceptionHandler rethrows exception from state onEntry() on manual start() call" { val machine = createTestStateMachine(coroutineStarterType, start = false) { initialState { - onEntry { testError("test exception") } + onEntry { testError() } } } @@ -38,7 +38,7 @@ class ListenerExceptionHandlerTest : StringSpec({ val machine = createTestStateMachine(coroutineStarterType, start = false) { onStarted { callbacks.onStarted(this) } callbacks.listen(this) - onEntry { testError("test exception") } + onEntry { testError() } state1 = initialState { callbacks.listen(this) @@ -56,7 +56,7 @@ class ListenerExceptionHandlerTest : StringSpec({ "default ListenerExceptionHandler rethrows exception from onStarted() on start() call" { shouldThrow { createTestStateMachine(coroutineStarterType) { - onStarted { testError("test exception") } + onStarted { testError() } initialState() } @@ -68,7 +68,7 @@ class ListenerExceptionHandlerTest : StringSpec({ val machine = createTestStateMachine(coroutineStarterType, start = false) { initialState() state2 = state { - onEntry { testError("test exception") } + onEntry { testError() } } } @@ -79,7 +79,7 @@ class ListenerExceptionHandlerTest : StringSpec({ "default ListenerExceptionHandler rethrows exception from stop()" { val machine = createTestStateMachine(coroutineStarterType) { initialState() - onStopped { testError("test exception") } + onStopped { testError() } } shouldThrow { machine.stopBlocking() } @@ -94,7 +94,7 @@ class ListenerExceptionHandlerTest : StringSpec({ listenerExceptionHandler = handlerMock initialState { - onEntry { testError("test exception") } + onEntry { testError() } } } @@ -113,7 +113,7 @@ class ListenerExceptionHandlerTest : StringSpec({ val state2 = state() initialState { transition { - guard = { testError("test exception") } + guard = { testError() } targetState = state2 } } diff --git a/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/ParallelStatesTest.kt b/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/ParallelStatesTest.kt index a30d303..79e079a 100644 --- a/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/ParallelStatesTest.kt +++ b/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/ParallelStatesTest.kt @@ -4,7 +4,6 @@ import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.booleans.shouldBeTrue import io.kotest.matchers.collections.containExactlyInAnyOrder -import io.kotest.matchers.collections.shouldContainExactly import io.kotest.matchers.should import io.mockk.verify import io.mockk.verifySequence diff --git a/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/StateMachineTest.kt b/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/StateMachineTest.kt index 9b7efce..678eded 100644 --- a/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/StateMachineTest.kt +++ b/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/StateMachineTest.kt @@ -8,7 +8,6 @@ import io.kotest.data.headers import io.kotest.data.row import io.kotest.data.table import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder -import io.kotest.matchers.comparables.shouldBeLessThan import io.kotest.matchers.shouldBe import io.mockk.called import io.mockk.verify diff --git a/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/TestUtils.kt b/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/TestUtils.kt index 04074d3..1228037 100644 --- a/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/TestUtils.kt +++ b/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/TestUtils.kt @@ -48,7 +48,7 @@ fun verifySequenceAndClear(mock: Any, verifyBlock: MockKVerificationScope.() -> clearMocks(mock, answers = false) } -fun testError(message: String): Nothing { +fun testError(message: String = "test exception"): Nothing { throw TestException(message) }