diff --git a/desktop/adapters/src/main/kotlin/com/soyle/stories/storyevent/time/reschedule/RescheduleStoryEventController.kt b/desktop/adapters/src/main/kotlin/com/soyle/stories/storyevent/time/reschedule/RescheduleStoryEventController.kt index c3cda236e..2117e06fe 100644 --- a/desktop/adapters/src/main/kotlin/com/soyle/stories/storyevent/time/reschedule/RescheduleStoryEventController.kt +++ b/desktop/adapters/src/main/kotlin/com/soyle/stories/storyevent/time/reschedule/RescheduleStoryEventController.kt @@ -2,33 +2,51 @@ package com.soyle.stories.storyevent.time.reschedule import com.soyle.stories.common.ThreadTransformer import com.soyle.stories.domain.storyevent.StoryEvent +import com.soyle.stories.storyevent.time.NormalizationPrompt +import com.soyle.stories.usecase.storyevent.StoryEventRepository import com.soyle.stories.usecase.storyevent.time.reschedule.RescheduleStoryEvent +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlin.coroutines.CoroutineContext interface RescheduleStoryEventController { - fun requestToRescheduleStoryEvent(storyEventId: StoryEvent.Id, currentTime: Long) - fun rescheduleStoryEvent(storyEventId: StoryEvent.Id, time: Long): Job + fun rescheduleStoryEvent(storyEventId: StoryEvent.Id): Job companion object { + fun Implementation( + guiContext: CoroutineContext, + asyncContext: CoroutineContext, - operator fun invoke( - threadTransformer: ThreadTransformer, - rescheduleStoryEvent: RescheduleStoryEvent, - rescheduleStoryEventOutput: RescheduleStoryEvent.OutputPort, + newTimePrompt: RescheduleStoryEventPrompt, + normalizationPrompt: NormalizationPrompt, - newTimePrompt: RescheduleStoryEventPrompt - ): RescheduleStoryEventController = object : RescheduleStoryEventController { + storyEventRepository: StoryEventRepository, - override fun requestToRescheduleStoryEvent(storyEventId: StoryEvent.Id, currentTime: Long) { - newTimePrompt.promptForNewTime(storyEventId, currentTime) + rescheduleStoryEvent: RescheduleStoryEvent, + rescheduleStoryEventOutput: RescheduleStoryEvent.OutputPort, + ): RescheduleStoryEventController = object : RescheduleStoryEventController, CoroutineScope by CoroutineScope(guiContext) { + override fun rescheduleStoryEvent(storyEventId: StoryEvent.Id): Job = launch { + val storyEvent = storyEventRepository.getStoryEventOrError(storyEventId) + newTimePrompt.use { + rescheduleStoryEvent(storyEventId, storyEvent.time.toLong()) + } } - override fun rescheduleStoryEvent(storyEventId: StoryEvent.Id, time: Long): Job { - return threadTransformer.async { - rescheduleStoryEvent.invoke(storyEventId, time, rescheduleStoryEventOutput) + private tailrec suspend fun rescheduleStoryEvent(storyEventId: StoryEvent.Id, currentTime: Long) { + val newTime = newTimePrompt.requestNewTime(currentTime) ?: return + + if (newTime > 0 || normalizationPrompt.confirmNormalization()) { + withContext(asyncContext) { + rescheduleStoryEvent(storyEventId, newTime, rescheduleStoryEventOutput) + } + return } + rescheduleStoryEvent(storyEventId, currentTime) } + } } diff --git a/desktop/adapters/src/main/kotlin/com/soyle/stories/storyevent/time/reschedule/RescheduleStoryEventPrompt.kt b/desktop/adapters/src/main/kotlin/com/soyle/stories/storyevent/time/reschedule/RescheduleStoryEventPrompt.kt index 81bce60c1..8e2aa2b59 100644 --- a/desktop/adapters/src/main/kotlin/com/soyle/stories/storyevent/time/reschedule/RescheduleStoryEventPrompt.kt +++ b/desktop/adapters/src/main/kotlin/com/soyle/stories/storyevent/time/reschedule/RescheduleStoryEventPrompt.kt @@ -2,6 +2,6 @@ package com.soyle.stories.storyevent.time.reschedule import com.soyle.stories.domain.storyevent.StoryEvent -interface RescheduleStoryEventPrompt { - fun promptForNewTime(storyEventId: StoryEvent.Id, currentTime: Long) +interface RescheduleStoryEventPrompt : AutoCloseable { + suspend fun requestNewTime(currentTime: Long): Long? } \ No newline at end of file diff --git a/desktop/adapters/src/testFixtures/kotlin/storyevent/RescheduleStoryEventControllerDouble.kt b/desktop/adapters/src/testFixtures/kotlin/storyevent/RescheduleStoryEventControllerDouble.kt index 8f680130f..61d8fc8f0 100644 --- a/desktop/adapters/src/testFixtures/kotlin/storyevent/RescheduleStoryEventControllerDouble.kt +++ b/desktop/adapters/src/testFixtures/kotlin/storyevent/RescheduleStoryEventControllerDouble.kt @@ -2,16 +2,8 @@ package com.soyle.stories.storyevent.time.reschedule import com.soyle.stories.domain.storyevent.StoryEvent +import io.mockk.spyk import kotlinx.coroutines.Job -class RescheduleStoryEventControllerDouble : RescheduleStoryEventController { - - override fun requestToRescheduleStoryEvent(storyEventId: StoryEvent.Id, currentTime: Long) { - - } - - override fun rescheduleStoryEvent(storyEventId: StoryEvent.Id, time: Long): Job { - return Job() - } -} \ No newline at end of file +class RescheduleStoryEventControllerDouble : RescheduleStoryEventController by spyk() \ No newline at end of file diff --git a/desktop/src/main/kotlin/storyevent/Presentation.kt b/desktop/src/main/kotlin/storyevent/Presentation.kt index e8ebd0af9..8fe988a19 100644 --- a/desktop/src/main/kotlin/storyevent/Presentation.kt +++ b/desktop/src/main/kotlin/storyevent/Presentation.kt @@ -21,7 +21,7 @@ import com.soyle.stories.storyevent.rename.* import com.soyle.stories.storyevent.time.* import com.soyle.stories.storyevent.time.adjust.AdjustStoryEventsTimeController import com.soyle.stories.storyevent.time.adjust.AdjustStoryEventsTimePrompt -import com.soyle.stories.storyevent.time.adjust.StoryEventTimeChangePromptPresenter +import com.soyle.stories.storyevent.time.StoryEventTimeChangePromptPresenter import com.soyle.stories.storyevent.time.normalization.NormalizationPromptPresenter import com.soyle.stories.storyevent.time.reschedule.RescheduleStoryEventController import com.soyle.stories.storyevent.time.reschedule.RescheduleStoryEventPrompt @@ -43,7 +43,6 @@ import com.soyle.stories.storyevent.timeline.viewport.ruler.label.TimeSpanLabel import com.soyle.stories.storyevent.timeline.viewport.ruler.label.TimeSpanLabelComponent import com.soyle.stories.storyevent.timeline.viewport.ruler.label.menu.TimelineRulerLabelMenu import com.soyle.stories.storyevent.timeline.viewport.ruler.label.menu.TimelineRulerLabelMenuComponent -import com.soyle.stories.usecase.storyevent.create.CreateStoryEvent import javafx.beans.property.BooleanProperty import javafx.collections.ObservableList import javafx.scene.Node @@ -136,35 +135,10 @@ object Presentation { } } - provide { + provide(RescheduleStoryEventPrompt::class, AdjustStoryEventsTimePrompt::class) { StoryEventTimeChangePromptPresenter(get()::currentStage) } - provide(RescheduleStoryEventPrompt::class) { - object : RescheduleStoryEventPrompt { - - private val presenterBuilder by lazy { - TimeAdjustmentPromptPresenter( - get(), - get(), - applicationScope.get() - ) - } - - override fun promptForNewTime(storyEventId: StoryEvent.Id, currentTime: Long) { - val presenter = presenterBuilder(storyEventId, currentTime) - - val stage = TimeAdjustmentPromptView( - presenter, - presenter.viewModel - ).openModal(owner = get().root.scene?.window)!! - presenter.viewModel.isCompleted.onChangeUntil({ it == true }) { - if (it == true) stage.hide() - } - } - } - } - provide { object : RemoveStoryEventConfirmation { override fun requestDeleteStoryEventConfirmation(storyEventIds: Set) { diff --git a/desktop/src/main/kotlin/storyevent/UseCases.kt b/desktop/src/main/kotlin/storyevent/UseCases.kt index f44176be7..a7a91e16b 100644 --- a/desktop/src/main/kotlin/storyevent/UseCases.kt +++ b/desktop/src/main/kotlin/storyevent/UseCases.kt @@ -61,8 +61,11 @@ object UseCases { provide { RescheduleStoryEventUseCase(get()) } provide { RescheduleStoryEventOutput(get()) } provide { - RescheduleStoryEventController( - applicationScope.get(), + RescheduleStoryEventController.Implementation( + applicationScope.get().guiContext, + applicationScope.get().asyncContext, + get(), + get(), get(), get(), get() diff --git a/desktop/src/test/kotlin/drivers/storyevent/Reschedule Story Event Dialog Robot.kt b/desktop/src/test/kotlin/drivers/storyevent/Reschedule Story Event Dialog Robot.kt index d73d73be7..376460157 100644 --- a/desktop/src/test/kotlin/drivers/storyevent/Reschedule Story Event Dialog Robot.kt +++ b/desktop/src/test/kotlin/drivers/storyevent/Reschedule Story Event Dialog Robot.kt @@ -3,17 +3,17 @@ package com.soyle.stories.desktop.config.drivers.storyevent import com.soyle.stories.desktop.config.drivers.robot import com.soyle.stories.desktop.view.project.workbench.getOpenDialog import com.soyle.stories.project.WorkBench -import com.soyle.stories.storyevent.time.TimeAdjustmentPromptView +import com.soyle.stories.storyevent.time.StoryEventTimeChangeView import javafx.scene.control.Spinner @Suppress("unused") -fun WorkBench.getOpenStoryEventTimeAdjustmentDialog(): TimeAdjustmentPromptView? = +fun WorkBench.getOpenStoryEventTimeAdjustmentDialog(): StoryEventTimeChangeView? = robot.getOpenDialog() -fun WorkBench.getOpenStoryEventTimeAdjustmentDialogOrError(): TimeAdjustmentPromptView = +fun WorkBench.getOpenStoryEventTimeAdjustmentDialogOrError(): StoryEventTimeChangeView = getOpenStoryEventTimeAdjustmentDialog() ?: error("Reschedule Story Event Dialog is not open") -fun TimeAdjustmentPromptView.reschedule(to: Long) { +fun StoryEventTimeChangeView.reschedule(to: Long) { robot.interact { val timeInput = robot.from(root).lookup("#time").query>() timeInput.editor.text = to.toString() @@ -22,7 +22,7 @@ fun TimeAdjustmentPromptView.reschedule(to: Long) { } } -fun TimeAdjustmentPromptView.adjustTime(by: Long) { +fun StoryEventTimeChangeView.adjustTime(by: Long) { robot.interact { val timeInput = robot.from(root).lookup("#time").query>() timeInput.editor.text = by.toString() diff --git a/desktop/views/src/design/kotlin/storyevent/Time Adjustment Prompt Design.kt b/desktop/views/src/design/kotlin/storyevent/Time Adjustment Prompt Design.kt index aa113248a..9e4e8cf8d 100644 --- a/desktop/views/src/design/kotlin/storyevent/Time Adjustment Prompt Design.kt +++ b/desktop/views/src/design/kotlin/storyevent/Time Adjustment Prompt Design.kt @@ -1,22 +1,18 @@ package com.soyle.stories.desktop.view.storyevent import com.soyle.stories.desktop.view.testframework.DesignTest -import com.soyle.stories.storyevent.time.TimeAdjustmentPromptView -import com.soyle.stories.storyevent.time.TimeAdjustmentPromptViewActions -import com.soyle.stories.storyevent.time.TimeAdjustmentPromptViewModel +import com.soyle.stories.storyevent.time.StoryEventTimeChangeView +import com.soyle.stories.storyevent.time.StoryEventTimeChangeViewModel +import com.soyle.stories.storyevent.time.adjust.AdjustTimePromptViewModel +import com.soyle.stories.storyevent.time.reschedule.ReschedulePromptViewModel import javafx.scene.Node import org.junit.jupiter.api.Test class `Time Adjustment Prompt Design` : DesignTest() { - private val actions = object : TimeAdjustmentPromptViewActions { - override fun submit() = Unit - override fun cancel() = Unit - } - - private var viewModel: TimeAdjustmentPromptViewModel = TimeAdjustmentPromptViewModel.adjustment() + private var viewModel: StoryEventTimeChangeViewModel = AdjustTimePromptViewModel() override val node: Node - get() = TimeAdjustmentPromptView(actions, viewModel).root + get() = StoryEventTimeChangeView(viewModel).root @Test fun `created without current time`() { @@ -25,13 +21,13 @@ class `Time Adjustment Prompt Design` : DesignTest() { @Test fun `created with current time`() { - viewModel = TimeAdjustmentPromptViewModel.reschedule(9L) + viewModel = ReschedulePromptViewModel(9L) verifyDesign() } @Test fun submitting() { - viewModel = TimeAdjustmentPromptViewModel.reschedule(4) + viewModel = ReschedulePromptViewModel(4) viewModel.submitting() verifyDesign() } diff --git a/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/item/Menu.kt b/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/item/Menu.kt index c9e373d80..7dff8d571 100644 --- a/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/item/Menu.kt +++ b/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/item/Menu.kt @@ -59,7 +59,7 @@ class StoryEventItemMenuPresenter( override fun rescheduleSelectedItem() { singleSelection { - dependencies.rescheduleStoryEventController.requestToRescheduleStoryEvent(it.storyEventId, it.time) + dependencies.rescheduleStoryEventController.rescheduleStoryEvent(it.storyEventId) } } diff --git a/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/list/StoryEventListPresenter.kt b/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/list/StoryEventListPresenter.kt index c072b3d8c..3789cdad6 100644 --- a/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/list/StoryEventListPresenter.kt +++ b/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/list/StoryEventListPresenter.kt @@ -135,9 +135,8 @@ class StoryEventListPresenter( override fun rescheduleSelectedItem() { selectedItem { - rescheduleStoryEventController.requestToRescheduleStoryEvent( - it.id, - it.timeProperty.value + rescheduleStoryEventController.rescheduleStoryEvent( + it.id ) } } diff --git a/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/StoryEventTimeChangePromptPresenter.kt b/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/StoryEventTimeChangePromptPresenter.kt new file mode 100644 index 000000000..9f46706a3 --- /dev/null +++ b/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/StoryEventTimeChangePromptPresenter.kt @@ -0,0 +1,90 @@ +package com.soyle.stories.storyevent.time + +import com.soyle.stories.storyevent.time.adjust.AdjustStoryEventsTimePrompt +import com.soyle.stories.storyevent.time.adjust.AdjustTimePromptViewModel +import com.soyle.stories.storyevent.time.reschedule.ReschedulePromptViewModel +import com.soyle.stories.storyevent.time.reschedule.RescheduleStoryEventPrompt +import javafx.beans.binding.BooleanExpression +import javafx.stage.Modality +import javafx.stage.Window +import kotlinx.coroutines.CompletableDeferred +import tornadofx.booleanBinding +import tornadofx.booleanProperty + +class StoryEventTimeChangePromptPresenter( + private val getOwnerWindow: () -> Window? +) : AdjustStoryEventsTimePrompt, RescheduleStoryEventPrompt { + + private var viewModel: StoryEventTimeChangeViewModel? = null + private var view: StoryEventTimeChangeView? = null + + override suspend fun requestAdjustmentAmount(): Long? { + val deferred = CompletableDeferred() + val viewModel = adjustmentViewModel() + openWindowIfNotAlready(deferred) + viewModel.endSubmission() + + viewModel.setOnSubmit { + if (! deferred.isCompleted) deferred.complete(viewModel.time) + } + return deferred.await() + } + + override suspend fun confirmAdjustmentAmount(amount: Long): Long? { + val deferred = CompletableDeferred() + val viewModel = adjustmentViewModel() + openWindowIfNotAlready(deferred) + viewModel.endSubmission() + viewModel.time = amount + viewModel.setOnSubmit { + if (! deferred.isCompleted) deferred.complete(viewModel.time) + } + return deferred.await() + } + + override suspend fun requestNewTime(currentTime: Long): Long? { + val deferred = CompletableDeferred() + val viewModel = rescheduleViewModel(currentTime) + openWindowIfNotAlready(deferred) + viewModel.endSubmission() + viewModel.setOnSubmit { + if (! deferred.isCompleted) deferred.complete(viewModel.time) + } + return deferred.await() + } + + private fun adjustmentViewModel(): StoryEventTimeChangeViewModel { + var viewModel = viewModel as? AdjustTimePromptViewModel + if (viewModel == null) { + viewModel = AdjustTimePromptViewModel() + view = StoryEventTimeChangeView(viewModel) + this.viewModel = viewModel + } + return viewModel + } + + private fun rescheduleViewModel(currentTime: Long): StoryEventTimeChangeViewModel { + var viewModel = viewModel as? ReschedulePromptViewModel + if (viewModel == null) { + viewModel = ReschedulePromptViewModel(currentTime) + view = StoryEventTimeChangeView(viewModel) + this.viewModel = viewModel + } + return viewModel + } + + private fun openWindowIfNotAlready(deferred: CompletableDeferred) { + if (view?.currentStage?.isShowing != true) view?.openModal(modality = Modality.APPLICATION_MODAL, owner = getOwnerWindow()) + view?.currentStage?.setOnHidden { + if (! deferred.isCompleted) deferred.complete(null) + + viewModel = null + view = null + } + } + + override fun close() { + view?.currentStage?.close() + } + +} \ No newline at end of file diff --git a/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/adjust/StoryEventTimeChangeView.kt b/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/StoryEventTimeChangeView.kt similarity index 94% rename from desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/adjust/StoryEventTimeChangeView.kt rename to desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/StoryEventTimeChangeView.kt index 0fe4d4a4a..936e8b5bc 100644 --- a/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/adjust/StoryEventTimeChangeView.kt +++ b/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/StoryEventTimeChangeView.kt @@ -1,4 +1,4 @@ -package com.soyle.stories.storyevent.time.adjust +package com.soyle.stories.storyevent.time import com.soyle.stories.common.components.ComponentsStyles import com.soyle.stories.common.components.text.FieldLabel.Companion.fieldLabel @@ -25,7 +25,7 @@ class StoryEventTimeChangeView( id = "time" valueFactory = NullableLongSpinnerValueFactory() isEditable = true - viewModel.adjustment().bindBidirectional(editor.textProperty()) + viewModel.timeText().bindBidirectional(editor.textProperty()) disableWhen(viewModel.submitting()) editor.action(viewModel::submit) } diff --git a/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/adjust/StoryEventTimeChangeViewModel.kt b/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/StoryEventTimeChangeViewModel.kt similarity index 55% rename from desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/adjust/StoryEventTimeChangeViewModel.kt rename to desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/StoryEventTimeChangeViewModel.kt index 66139eedd..68b9a3ed7 100644 --- a/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/adjust/StoryEventTimeChangeViewModel.kt +++ b/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/StoryEventTimeChangeViewModel.kt @@ -1,33 +1,33 @@ -package com.soyle.stories.storyevent.time.adjust +package com.soyle.stories.storyevent.time import javafx.beans.binding.BooleanExpression import javafx.beans.property.ReadOnlyBooleanWrapper import javafx.beans.property.StringProperty -import tornadofx.booleanBinding -import tornadofx.stringProperty -import tornadofx.getValue -import tornadofx.objectProperty +import tornadofx.* -class StoryEventTimeChangeViewModel { +abstract class StoryEventTimeChangeViewModel { - private val adjustmentProperty = stringProperty() - fun adjustment(): StringProperty = adjustmentProperty - var adjustment: Long? + private val timeTextProperty = stringProperty("") + fun timeText(): StringProperty = timeTextProperty + var time: Long? get() { - val adjustmentText = adjustmentProperty.get() - if (adjustmentText.isBlank()) return 0 + val adjustmentText = timeTextProperty.get() + if (adjustmentText.isEmpty()) return 0 else return adjustmentText.toLongOrNull() } set(value) { - adjustmentProperty.set(value.toString()) + timeTextProperty.set(value.toString()) } - private val canSubmitExpression = booleanBinding(adjustmentProperty) { adjustment != null && adjustment != 0L } + protected abstract val canSubmitExpression: BooleanExpression fun canSubmit(): BooleanExpression = canSubmitExpression - val canSubmit: Boolean by canSubmit() + val canSubmit: Boolean + get() = canSubmitExpression.get() private val submittingExpression = ReadOnlyBooleanWrapper(false) fun submitting(): BooleanExpression = submittingExpression.readOnlyProperty + val isSubmitting: Boolean + get() = submittingExpression.get() private val onSubmitProperty = objectProperty<() -> Unit> {} fun setOnSubmit(block: () -> Unit) { diff --git a/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/TimeAdjustmentPromptPresenter.kt b/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/TimeAdjustmentPromptPresenter.kt deleted file mode 100644 index b37dac5ae..000000000 --- a/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/TimeAdjustmentPromptPresenter.kt +++ /dev/null @@ -1,83 +0,0 @@ -package com.soyle.stories.storyevent.time - -import com.soyle.stories.common.ThreadTransformer -import com.soyle.stories.domain.storyevent.StoryEvent -import com.soyle.stories.storyevent.time.adjust.AdjustStoryEventsTimeController -import com.soyle.stories.storyevent.time.reschedule.RescheduleStoryEventController -import kotlinx.coroutines.Job - -class TimeAdjustmentPromptPresenter private constructor( - private val storyEventIds: Set, - val viewModel: TimeAdjustmentPromptViewModel, - private val rescheduleStoryEventController: RescheduleStoryEventController, - private val adjustStoryEventsTimeController: AdjustStoryEventsTimeController, - private val threadTransformer: ThreadTransformer -) : TimeAdjustmentPromptViewActions { - - interface Builder { - operator fun invoke(storyEventIds: Set): TimeAdjustmentPromptPresenter - operator fun invoke(storyEventId: StoryEvent.Id, currentTime: Long): TimeAdjustmentPromptPresenter - operator fun invoke(storyEventIds: Set, initialAdjustment: Long): TimeAdjustmentPromptPresenter - } - - companion object { - - operator fun invoke( - rescheduleStoryEventController: RescheduleStoryEventController, - adjustStoryEventsTimeController: AdjustStoryEventsTimeController, - threadTransformer: ThreadTransformer - ): Builder = object : Builder { - override fun invoke(storyEventIds: Set): TimeAdjustmentPromptPresenter { - return TimeAdjustmentPromptPresenter(storyEventIds, TimeAdjustmentPromptViewModel.adjustment(), rescheduleStoryEventController, adjustStoryEventsTimeController, threadTransformer) - } - - override fun invoke(storyEventId: StoryEvent.Id, currentTime: Long): TimeAdjustmentPromptPresenter { - return TimeAdjustmentPromptPresenter(setOf(storyEventId), TimeAdjustmentPromptViewModel.reschedule(currentTime), rescheduleStoryEventController, adjustStoryEventsTimeController, threadTransformer) - } - - override fun invoke(storyEventIds: Set, initialAdjustment: Long): TimeAdjustmentPromptPresenter { - return TimeAdjustmentPromptPresenter(storyEventIds, TimeAdjustmentPromptViewModel.adjustment(initialAdjustment), rescheduleStoryEventController, adjustStoryEventsTimeController, threadTransformer) - } - } - - } - - //val viewModel = TimeAdjustmentPromptViewModel.reschedule(currentTime ?: 0) - - private fun canSubmit() = viewModel.canSubmit.value - - override fun submit() { - if (!canSubmit()) return - val time = viewModel.time.value.toLongOrNull() ?: return - startSubmission(time) - } - - override fun cancel() { - viewModel.success() - } - - private fun startSubmission(time: Long) { - viewModel.submitting() - val job = getSubmissionJob(time) - job.invokeOnCompletion(::endSubmission) - } - - private fun getSubmissionJob(time: Long): Job { - return if (viewModel.adjustment) { - adjustStoryEventsTimeController.adjustTimesBy(storyEventIds, time) - } else { - rescheduleStoryEventController.rescheduleStoryEvent(storyEventIds.single(), time) - } - } - - private fun endSubmission(potentialFailure: Throwable?) { - threadTransformer.gui { - if (potentialFailure != null) viewModel.failed() - else viewModel.success() - } - } - - @Suppress("NOTHING_TO_INLINE") - private inline fun Collection<*>.onlyHasOneItem(): Boolean = size == 1 - -} \ No newline at end of file diff --git a/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/TimeAdjustmentPromptView.kt b/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/TimeAdjustmentPromptView.kt deleted file mode 100644 index 16cdab6ae..000000000 --- a/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/TimeAdjustmentPromptView.kt +++ /dev/null @@ -1,92 +0,0 @@ -package com.soyle.stories.storyevent.time - -import com.soyle.stories.common.components.ComponentsStyles.Companion.filled -import com.soyle.stories.common.components.ComponentsStyles.Companion.outlined -import com.soyle.stories.common.components.ComponentsStyles.Companion.primary -import com.soyle.stories.common.components.ComponentsStyles.Companion.secondary -import com.soyle.stories.common.components.text.FieldLabel.Companion.fieldLabel -import com.soyle.stories.storyevent.NullableLongSpinnerValueFactory -import javafx.application.Platform -import javafx.geometry.Pos -import javafx.scene.Parent -import tornadofx.* -import tornadofx.Stylesheet.Companion.buttonBar -import tornadofx.Stylesheet.Companion.form - -class TimeAdjustmentPromptView( - private val actions: TimeAdjustmentPromptViewActions, - private val viewModel: TimeAdjustmentPromptViewModel -) : Fragment() { - - init { - title = "Reschedule Story Event" - } - - override val root: Parent = vbox { - addClass(Styles.timeAdjustmentPrompt) - vbox { - addClass(form) - fieldLabel("Time").labelFor = spinner { - id = "time" - valueFactory = NullableLongSpinnerValueFactory() - editor.text = viewModel.time.value - isEditable = true - viewModel.time.bind(editor.textProperty()) - disableWhen(viewModel.submitting) - editor.action(actions::submit) - } - } - hbox { - addClass(buttonBar) - button("RESCHEDULE") { - id = "save" - addClass(primary, filled) - enableWhen(viewModel.canSubmit) - action(actions::submit) - } - button("CANCEL") { - addClass(secondary, outlined) - action(actions::cancel) - } - } - } - - init { - root.properties[UI_COMPONENT_PROPERTY] = this - } - - class Styles : Stylesheet() { - - companion object { - - val timeAdjustmentPrompt by cssclass() - - init { - if (Platform.isFxApplicationThread()) importStylesheet() - else runLater { importStylesheet() } - } - - } - - init { - timeAdjustmentPrompt { - fillWidth = true - form { - fillWidth = true - padding = box(12.px) - spinner { - maxWidth = Double.MAX_VALUE.px - } - } - buttonBar { - alignment = Pos.CENTER_RIGHT - padding = box(12.px) - spacing = 8.px - - } - } - } - - } - -} \ No newline at end of file diff --git a/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/TimeAdjustmentPromptViewActions.kt b/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/TimeAdjustmentPromptViewActions.kt deleted file mode 100644 index e1d5b9163..000000000 --- a/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/TimeAdjustmentPromptViewActions.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.soyle.stories.storyevent.time - -interface TimeAdjustmentPromptViewActions { - - fun submit() - fun cancel() - -} \ No newline at end of file diff --git a/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/TimeAdjustmentPromptViewModel.kt b/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/TimeAdjustmentPromptViewModel.kt deleted file mode 100644 index 3125c483e..000000000 --- a/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/TimeAdjustmentPromptViewModel.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.soyle.stories.storyevent.time - -import javafx.beans.binding.BooleanBinding -import javafx.beans.property.ReadOnlyBooleanProperty -import javafx.beans.property.ReadOnlyBooleanWrapper -import javafx.beans.property.StringProperty -import tornadofx.booleanBinding -import tornadofx.stringProperty - -class TimeAdjustmentPromptViewModel private constructor( - val time: StringProperty, - private val timeShouldNotEqual: Long, - val adjustment: Boolean -) { - - companion object { - fun adjustment(initialValue: Long = 0L) = TimeAdjustmentPromptViewModel( - stringProperty(initialValue.toString()), - 0, - true - ) - fun reschedule(currentTime: Long) = TimeAdjustmentPromptViewModel( - stringProperty(currentTime.toString()), - currentTime, - false - ) - } - - private val _submitting = ReadOnlyBooleanWrapper() - val submitting: ReadOnlyBooleanProperty = _submitting.readOnlyProperty - val canSubmit: BooleanBinding = booleanBinding(time) { - val timeAsLong = time.get().toLongOrNull() ?: return@booleanBinding false - timeAsLong != timeShouldNotEqual - }.and(submitting.not()) - private val _isCompleted = ReadOnlyBooleanWrapper() - val isCompleted: ReadOnlyBooleanProperty = _isCompleted.readOnlyProperty - - fun submitting() { - _submitting.set(true) - } - - fun failed() { - _submitting.set(false) - } - - fun success() { - _submitting.set(false) - _isCompleted.set(true) - } - -} \ No newline at end of file diff --git a/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/adjust/AdjustTimePromptViewModel.kt b/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/adjust/AdjustTimePromptViewModel.kt new file mode 100644 index 000000000..25bd90cb3 --- /dev/null +++ b/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/adjust/AdjustTimePromptViewModel.kt @@ -0,0 +1,13 @@ +package com.soyle.stories.storyevent.time.adjust + +import com.soyle.stories.storyevent.time.StoryEventTimeChangeViewModel +import javafx.beans.binding.BooleanExpression +import tornadofx.booleanBinding + +class AdjustTimePromptViewModel : StoryEventTimeChangeViewModel() { + + override val canSubmitExpression: BooleanExpression = booleanBinding(timeText(), submitting()) { + !isSubmitting && time != null && time != 0L + } + +} \ No newline at end of file diff --git a/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/adjust/StoryEventTimeChangePromptPresenter.kt b/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/adjust/StoryEventTimeChangePromptPresenter.kt deleted file mode 100644 index f3bd84cd1..000000000 --- a/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/adjust/StoryEventTimeChangePromptPresenter.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.soyle.stories.storyevent.time.adjust - -import javafx.stage.Modality -import javafx.stage.Window -import kotlinx.coroutines.CompletableDeferred - -class StoryEventTimeChangePromptPresenter( - private val getOwnerWindow: () -> Window? -) : AdjustStoryEventsTimePrompt { - - private var viewModel = StoryEventTimeChangeViewModel() - private var view = StoryEventTimeChangeView(viewModel) - - override suspend fun requestAdjustmentAmount(): Long? { - val deferred = CompletableDeferred() - openWindowIfNotAlready(deferred) - viewModel.endSubmission() - - viewModel.setOnSubmit { - if (! deferred.isCompleted) deferred.complete(viewModel.adjustment) - } - return deferred.await() - } - - override suspend fun confirmAdjustmentAmount(amount: Long): Long? { - val deferred = CompletableDeferred() - openWindowIfNotAlready(deferred) - viewModel.endSubmission() - viewModel.adjustment = amount - viewModel.setOnSubmit { - if (! deferred.isCompleted) deferred.complete(viewModel.adjustment) - } - return deferred.await() - } - - private fun openWindowIfNotAlready(deferred: CompletableDeferred) { - if (view.currentStage?.isShowing != true) view.openModal(modality = Modality.APPLICATION_MODAL, owner = getOwnerWindow()) - view.currentStage?.setOnHidden { - if (! deferred.isCompleted) deferred.complete(null) - - viewModel = StoryEventTimeChangeViewModel() - view = StoryEventTimeChangeView(viewModel) - } - } - - override fun close() { - view.currentStage?.close() - } - -} \ No newline at end of file diff --git a/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/insert/InsertTimeForm.kt b/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/insert/InsertTimeForm.kt deleted file mode 100644 index d12c2c2c4..000000000 --- a/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/insert/InsertTimeForm.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.soyle.stories.storyevent.time.insert - -class InsertTimeForm { -} \ No newline at end of file diff --git a/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/reschedule/ReschedulePromptViewModel.kt b/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/reschedule/ReschedulePromptViewModel.kt new file mode 100644 index 000000000..2a1233f67 --- /dev/null +++ b/desktop/views/src/main/kotlin/com/soyle/stories/storyevent/time/reschedule/ReschedulePromptViewModel.kt @@ -0,0 +1,17 @@ +package com.soyle.stories.storyevent.time.reschedule + +import com.soyle.stories.storyevent.time.StoryEventTimeChangeViewModel +import javafx.beans.binding.BooleanExpression +import tornadofx.booleanBinding + +class ReschedulePromptViewModel(private val currentTime: Long) : StoryEventTimeChangeViewModel() { + + override val canSubmitExpression: BooleanExpression = booleanBinding(timeText(), submitting()) { + !isSubmitting && time != null && time != currentTime + } + + init { + time = currentTime + } + +} \ No newline at end of file diff --git a/desktop/views/src/test/kotlin/storyevent/list/Story Event List Presenter Test.kt b/desktop/views/src/test/kotlin/storyevent/list/Story Event List Presenter Test.kt index bbcdb0a9a..292109a78 100644 --- a/desktop/views/src/test/kotlin/storyevent/list/Story Event List Presenter Test.kt +++ b/desktop/views/src/test/kotlin/storyevent/list/Story Event List Presenter Test.kt @@ -27,6 +27,7 @@ import com.soyle.stories.storyevent.rename.StoryEventRenamedNotifier import com.soyle.stories.storyevent.time.StoryEventRescheduledNotifier import com.soyle.stories.storyevent.time.adjust.AdjustStoryEventsTimeController import com.soyle.stories.storyevent.time.reschedule.RescheduleStoryEventController +import com.soyle.stories.storyevent.time.reschedule.RescheduleStoryEventControllerDouble import com.soyle.stories.theme.themeOppositionWebs.Styles.Companion.selectedItem import com.soyle.stories.usecase.storyevent.StoryEventItem import com.soyle.stories.usecase.storyevent.create.CreateStoryEvent @@ -96,26 +97,9 @@ class `Story Event List Presenter Test` { private val storyEventRenamedNotifier = StoryEventRenamedNotifier() - private val rescheduleStoryEventController = object : RescheduleStoryEventController { - var requestedId: StoryEvent.Id? = null - var requestedTime: Long? = null - override fun requestToRescheduleStoryEvent(storyEventId: StoryEvent.Id, currentTime: Long) { - requestedId = storyEventId - requestedTime = currentTime - } - - override fun rescheduleStoryEvent(storyEventId: StoryEvent.Id, time: Long): Job { - fail("Should not be called by story event list") - } - } + private val rescheduleStoryEventController = RescheduleStoryEventControllerDouble() - private val adjustStoryEventsTimeController = - object : AdjustStoryEventsTimeController by AdjustStoryEventsTimeControllerDouble() { - var requestedIds: Set? = null - override fun requestToAdjustStoryEventsTimes(storyEventIds: Set) { - requestedIds = storyEventIds - } - } + private val adjustStoryEventsTimeController = AdjustStoryEventsTimeControllerDouble() private val storyEventRescheduledNotifier = StoryEventRescheduledNotifier() @@ -360,8 +344,9 @@ class `Story Event List Presenter Test` { fun `should request to reschedule the selected story event`() { presenter.rescheduleSelectedItem() - assertThat(rescheduleStoryEventController.requestedId).isEqualTo(selectedItem.id) - assertThat(rescheduleStoryEventController.requestedTime).isEqualTo(selectedItem.timeProperty.value) + verify { + rescheduleStoryEventController.rescheduleStoryEvent(selectedItem.id) + } } } @@ -395,7 +380,9 @@ class `Story Event List Presenter Test` { fun `cannot reschedule multiple story events`() { presenter.rescheduleSelectedItem() - assertThat(rescheduleStoryEventController.requestedId).isNull() + verify { + rescheduleStoryEventController.rescheduleStoryEvent(any()) wasNot Called + } } @Test @@ -417,8 +404,9 @@ class `Story Event List Presenter Test` { fun `should request to adjust the times of all selected items`() { presenter.adjustTimesOfSelectedItems() - assertThat(adjustStoryEventsTimeController.requestedIds) - .containsExactlyInAnyOrderElementsOf(selectedItems.map { it.id }) + verify { + adjustStoryEventsTimeController.adjustTimes(selectedItems.map { it.id }.toSet()) + } } } diff --git a/desktop/views/src/test/kotlin/storyevent/time/Time Adjustment Prompt Presenter Test.kt b/desktop/views/src/test/kotlin/storyevent/time/Time Adjustment Prompt Presenter Test.kt deleted file mode 100644 index 1263313dc..000000000 --- a/desktop/views/src/test/kotlin/storyevent/time/Time Adjustment Prompt Presenter Test.kt +++ /dev/null @@ -1,312 +0,0 @@ -package com.soyle.stories.desktop.view.storyevent.time - -import com.soyle.stories.common.ThreadTransformer -import com.soyle.stories.domain.storyevent.StoryEvent -import com.soyle.stories.storyevent.time.TimeAdjustmentPromptPresenter -import com.soyle.stories.storyevent.time.adjust.AdjustStoryEventsTimeController -import com.soyle.stories.storyevent.time.reschedule.RescheduleStoryEventController -import kotlinx.coroutines.CompletableJob -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.fail -import org.testfx.assertions.api.Assertions.assertThat -import tornadofx.onChange - -class `Time Adjustment Prompt Presenter Test` { - - private val storyEventId = StoryEvent.Id() - - private val threadTransformer = object : ThreadTransformer { - - override fun isGuiThread(): Boolean = _isGuiThread - - var _isGuiThread = true - private set - - fun falseAsync(block: () -> Unit) { - _isGuiThread = false - block() - _isGuiThread = true - } - - override fun async(task: suspend CoroutineScope.() -> Unit): Job = - fail("Should not be called by presenter") - - override fun gui(update: suspend CoroutineScope.() -> Unit) { - _isGuiThread = true - runBlocking { update() } - _isGuiThread = false - } - } - - private val rescheduleStoryEventController = object : RescheduleStoryEventController { - var requestedStoryEvent: StoryEvent.Id? = null - var requestedTime: Long? = null - - private var job: CompletableJob = Job() - - fun completeJob(failure: Throwable? = null) { - threadTransformer.falseAsync { - if (failure != null) job.completeExceptionally(failure) - else job.complete() - } - } - - override fun rescheduleStoryEvent(storyEventId: StoryEvent.Id, time: Long): Job { - requestedStoryEvent = storyEventId - requestedTime = time - job = Job() - return job - } - - override fun requestToRescheduleStoryEvent(storyEventId: StoryEvent.Id, currentTime: Long) { - fail("Should not be called from prompt") - } - } - - private val adjustStoryEventsTimeController = object : AdjustStoryEventsTimeController { - var requestedStoryEventIds: Set? = null - var requestedAmount: Long? = null - private var job: CompletableJob = Job() - - fun completeJob(failure: Throwable? = null) { - threadTransformer.falseAsync { - if (failure != null) job.completeExceptionally(failure) - else job.complete() - } - } - - override fun adjustStoryEventsTime(storyEventIds: Set, amount: Long): Job { - requestedStoryEventIds = storyEventIds - requestedAmount = amount - job = Job() - return job - } - - override fun requestToAdjustStoryEventsTimes(storyEventIds: Set) { - fail("Should not be called from prompt") - } - - override fun requestToAdjustStoryEventsTimes(storyEventIds: Set, amount: Long) { - - } - } - - private val timeAdjustmentPromptPresenter = object : TimeAdjustmentPromptPresenter.Builder { - private val builder = TimeAdjustmentPromptPresenter( - rescheduleStoryEventController, - adjustStoryEventsTimeController, - threadTransformer - ) - - override fun invoke(storyEventIds: Set): TimeAdjustmentPromptPresenter = - builder(storyEventIds).apply(::enforceGUIUpdates) - - override fun invoke( - storyEventIds: Set, - initialAdjustment: Long - ): TimeAdjustmentPromptPresenter = builder(storyEventIds, initialAdjustment).apply(::enforceGUIUpdates) - - override fun invoke(storyEventId: StoryEvent.Id, currentTime: Long): TimeAdjustmentPromptPresenter = - builder(storyEventId, currentTime).apply(::enforceGUIUpdates) - } - - private fun enforceGUIUpdates(presenter: TimeAdjustmentPromptPresenter) { - presenter.apply { - viewModel.submitting.onChange { if (!threadTransformer.isGuiThread()) fail("Not on GUI thread") } - viewModel.isCompleted.onChange { if (!threadTransformer.isGuiThread()) fail("Not on GUI thread") } - } - } - - @Test - fun `should provide current time to view model`() { - timeAdjustmentPromptPresenter(storyEventId, 9).let { - assertThat(it.viewModel.time.value).isEqualTo("9") - } - timeAdjustmentPromptPresenter(setOf()).let { - assertThat(it.viewModel.time.value).isEqualTo("0") - } - timeAdjustmentPromptPresenter(setOf(), 9).let { - assertThat(it.viewModel.time.value).isEqualTo("9") - } - } - - @Test - fun `cannot submit if view model is not in valid state`() { - val presenter = timeAdjustmentPromptPresenter(setOf()) - - presenter.submit() - - assertThat(presenter.viewModel.submitting.value).isFalse - } - - @Test - fun `can submit if view model is in valid state`() { - val allPresenters = listOf( - timeAdjustmentPromptPresenter(setOf(storyEventId)).apply { viewModel.time.set("4") }, - timeAdjustmentPromptPresenter(setOf(), 6), - timeAdjustmentPromptPresenter(storyEventId, 8).apply { viewModel.time.set("4") }, - ) - - allPresenters.onEach { it.submit() } - - assertThat(allPresenters) - .allMatch { it.viewModel.submitting.value } - } - - @Nested - inner class `Given View Model is in Valid State` { - - @Test - fun `submit should update view model`() { - val presenter = timeAdjustmentPromptPresenter(storyEventId, 7) - - presenter.viewModel.time.set("5") - presenter.submit() - - assertThat(presenter.viewModel.submitting.value).isTrue - } - - @Nested - inner class `Given Rescheduling a Story Event` { - - val presenter = timeAdjustmentPromptPresenter(storyEventId, 7) - - @Test - fun `submitting should send request to reschedule controller`() { - presenter.viewModel.time.set("5") - presenter.submit() - - assertThat(rescheduleStoryEventController.requestedStoryEvent).isEqualTo(storyEventId) - assertThat(rescheduleStoryEventController.requestedTime).isEqualTo(5L) - } - - @Test - fun `if only one story event provided - should still reschedule time`() { - val presenter = timeAdjustmentPromptPresenter(storyEventId, 9) - - presenter.viewModel.time.set("5") - presenter.submit() - - assertThat(adjustStoryEventsTimeController.requestedStoryEventIds).isNull() - assertThat(adjustStoryEventsTimeController.requestedAmount).isNull() - - assertThat(rescheduleStoryEventController.requestedStoryEvent).isEqualTo(storyEventId) - assertThat(rescheduleStoryEventController.requestedTime).isEqualTo(5L) - - } - - } - - @Nested - inner class `Given Adjust Time of Potentially Many Story Events` { - - private val storyEventIds = List(5) { StoryEvent.Id() }.toSet() - private val presenter = timeAdjustmentPromptPresenter(storyEventIds) - - @Test - fun `submitting should send request to adjustment controller`() { - presenter.viewModel.time.set("5") - presenter.submit() - - assertThat(adjustStoryEventsTimeController.requestedStoryEventIds).isEqualTo(storyEventIds) - assertThat(adjustStoryEventsTimeController.requestedAmount).isEqualTo(5L) - } - - @Test - fun `if only one story event provided - should still adjust time`() { - val presenter = timeAdjustmentPromptPresenter(setOf(storyEventId)) - - presenter.viewModel.time.set("5") - presenter.submit() - - assertThat(adjustStoryEventsTimeController.requestedStoryEventIds).isEqualTo(setOf(storyEventId)) - assertThat(adjustStoryEventsTimeController.requestedAmount).isEqualTo(5L) - - assertThat(rescheduleStoryEventController.requestedStoryEvent).isNull() - assertThat(rescheduleStoryEventController.requestedTime).isNull() - - } - - } - - } - - @Nested - inner class `Given Submitting` { - - private fun given(presenter: TimeAdjustmentPromptPresenter) { - presenter.viewModel.time.set("5") - presenter.submit() - } - - @Nested - inner class `Given Rescheduling a Story Event` { - - @Test - fun `when submission fails - should notify view model`() { - val presenter = timeAdjustmentPromptPresenter(storyEventId, 9) - given(presenter) - - rescheduleStoryEventController.completeJob(Error("Intentional Error")) - - assertThat(presenter.viewModel.submitting.value).isFalse - assertThat(presenter.viewModel.isCompleted.value).isFalse - } - - @Test - fun `when submission succeeds - should notify view model`() { - val presenter = timeAdjustmentPromptPresenter(storyEventId, 9) - given(presenter) - - rescheduleStoryEventController.completeJob() - - assertThat(presenter.viewModel.submitting.value).isFalse - assertThat(presenter.viewModel.isCompleted.value).isTrue - } - - } - - @Nested - inner class `Given Adjust Time of Potentially Many Story Events` { - - @Test - fun `when submission fails - should notify view model`() { - val presenter = timeAdjustmentPromptPresenter(setOf()) - given(presenter) - - adjustStoryEventsTimeController.completeJob(Error("Intentional Error")) - - assertThat(presenter.viewModel.submitting.value).isFalse - assertThat(presenter.viewModel.isCompleted.value).isFalse - } - - @Test - fun `when submission succeeds - should notify view model`() { - val presenter = timeAdjustmentPromptPresenter(setOf()) - given(presenter) - - adjustStoryEventsTimeController.completeJob() - - assertThat(presenter.viewModel.submitting.value).isFalse - assertThat(presenter.viewModel.isCompleted.value).isTrue - } - - } - - } - - @Test - fun `cancelling should complete the view model`() { - val presenter = timeAdjustmentPromptPresenter(setOf()) - - presenter.cancel() - - assertThat(presenter.viewModel.isCompleted.value).isTrue - } - - -} \ No newline at end of file diff --git a/desktop/views/src/test/kotlin/storyevent/time/Time Adjustment Prompt ViewModel Test.kt b/desktop/views/src/test/kotlin/storyevent/time/Time Adjustment Prompt ViewModel Test.kt index b1afb5b09..7468336df 100644 --- a/desktop/views/src/test/kotlin/storyevent/time/Time Adjustment Prompt ViewModel Test.kt +++ b/desktop/views/src/test/kotlin/storyevent/time/Time Adjustment Prompt ViewModel Test.kt @@ -1,6 +1,7 @@ package com.soyle.stories.desktop.view.storyevent.time -import com.soyle.stories.storyevent.time.TimeAdjustmentPromptViewModel +import com.soyle.stories.storyevent.time.adjust.AdjustTimePromptViewModel +import com.soyle.stories.storyevent.time.reschedule.ReschedulePromptViewModel import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Nested @@ -14,23 +15,19 @@ class `Time Adjustment Prompt ViewModel Test` { @Nested inner class `Given Created to Adjust Time` { - val viewModel = TimeAdjustmentPromptViewModel.adjustment() + val viewModel = AdjustTimePromptViewModel() @Test fun `time adjustment should equal zero`() { - assertThat(viewModel.time.value).isEqualTo("0") + assertThat(viewModel.timeText().value).isEqualTo("") + assertThat(viewModel.time).isEqualTo(0L) } @Test fun `when initial adjustment is provided, should display initial value`() { - val viewModel = TimeAdjustmentPromptViewModel.adjustment(9L) - assertThat(viewModel.time.value).isEqualTo("9") - } - - @Test - fun `should be in adjusting state`() { - assertTrue(TimeAdjustmentPromptViewModel.adjustment().adjustment) - assertTrue(TimeAdjustmentPromptViewModel.adjustment(9L).adjustment) + val viewModel = AdjustTimePromptViewModel() + viewModel.time = 9L + assertThat(viewModel.timeText().value).isEqualTo("9") } @Nested @@ -39,23 +36,24 @@ class `Time Adjustment Prompt ViewModel Test` { @ParameterizedTest @ValueSource(strings = [" ", "banana", "0"]) fun `should not be able to submit change`(timeValue: String) { - viewModel.time.set(timeValue) + viewModel.timeText().set(timeValue) - assertThat(viewModel.canSubmit.value).isFalse + assertThat(viewModel.canSubmit).isFalse } @Test fun `when a valid number - should be able to submit change`() { - viewModel.time.set("14") + viewModel.timeText().set("14") - assertThat(viewModel.canSubmit.value).isTrue + assertThat(viewModel.canSubmit).isTrue } @Test fun `when created with an initial value - should be able to submit change`() { - val viewModel = TimeAdjustmentPromptViewModel.adjustment(9L) + val viewModel = AdjustTimePromptViewModel() + viewModel.time = 9L - assertThat(viewModel.canSubmit.value).isTrue + assertThat(viewModel.canSubmit).isTrue } } @@ -64,15 +62,15 @@ class `Time Adjustment Prompt ViewModel Test` { inner class `Given Change Can be Submitted` { init { - viewModel.time.set("14") + viewModel.timeText().set("14") } @Test fun `when change is submitted - should not be able to submit change`() { - viewModel.submitting() + viewModel.submit() - assertThat(viewModel.submitting.value).isTrue - assertThat(viewModel.canSubmit.value).isFalse + assertThat(viewModel.isSubmitting).isTrue + assertThat(viewModel.canSubmit).isFalse } } @@ -81,24 +79,15 @@ class `Time Adjustment Prompt ViewModel Test` { inner class `Given Change has been Submitted` { init { - viewModel.time.set("14") - viewModel.submitting() + viewModel.timeText().set("14") + viewModel.submit() } @Test fun `when change failed - should be able to submit change`() { - viewModel.failed() + viewModel.endSubmission() - assertThat(viewModel.canSubmit.value).isTrue - assertThat(viewModel.isCompleted.value).isFalse - } - - @Test - fun `when change succeeded - should be completed`() { - viewModel.success() - - assertThat(viewModel.canSubmit.value).isTrue - assertThat(viewModel.isCompleted.value).isTrue + assertThat(viewModel.canSubmit).isTrue } } @@ -109,16 +98,11 @@ class `Time Adjustment Prompt ViewModel Test` { inner class `Given Created to Reschedule` { private val currentTime = 9L - val viewModel = TimeAdjustmentPromptViewModel.reschedule(currentTime) + val viewModel = ReschedulePromptViewModel(currentTime) @Test fun `time should be equal to the current time`() { - assertThat(viewModel.time.value).isEqualTo("9") - } - - @Test - fun `should be in rescheduling state`() { - assertFalse(TimeAdjustmentPromptViewModel.reschedule(currentTime).adjustment) + assertThat(viewModel.timeText().value).isEqualTo("9") } @Nested @@ -127,25 +111,16 @@ class `Time Adjustment Prompt ViewModel Test` { @ParameterizedTest @ValueSource(strings = [" ", "banana", "9"]) fun `should not be able to submit change`(timeValue: String) { - viewModel.time.set(timeValue) + viewModel.timeText().set(timeValue) - assertThat(viewModel.canSubmit.value).isFalse + assertThat(viewModel.canSubmit).isFalse } @Test fun `when a different number - should be able to submit change`() { - viewModel.time.set("14") + viewModel.timeText().set("14") - assertThat(viewModel.canSubmit.value).isTrue - } - - } - - @Nested - inner class `Given Time is Valid` { - - init { - viewModel.time.set("14") + assertThat(viewModel.canSubmit).isTrue } } @@ -154,14 +129,14 @@ class `Time Adjustment Prompt ViewModel Test` { inner class `Given Change Can be Submitted` { init { - viewModel.time.set("14") + viewModel.timeText().set("14") } @Test fun `when change is submitted - should not be able to submit change`() { - viewModel.submitting() + viewModel.submit() - assertThat(viewModel.canSubmit.value).isFalse + assertThat(viewModel.canSubmit).isFalse } } @@ -170,22 +145,15 @@ class `Time Adjustment Prompt ViewModel Test` { inner class `Given Change has been Submitted` { init { - viewModel.time.set("14") - viewModel.submitting() + viewModel.timeText().set("14") + viewModel.submit() } @Test fun `when change failed - should be able to submit change`() { - viewModel.failed() - - assertThat(viewModel.canSubmit.value).isTrue - } - - @Test - fun `when change succeeded - should be completed`() { - viewModel.success() + viewModel.endSubmission() - assertThat(viewModel.isCompleted.value).isTrue + assertThat(viewModel.canSubmit).isTrue } } diff --git a/desktop/views/src/test/kotlin/storyevent/timeline/Timeline Ruler Label Menu Unit Test.kt b/desktop/views/src/test/kotlin/storyevent/timeline/Timeline Ruler Label Menu Unit Test.kt index b9c5251b7..b451eec4a 100644 --- a/desktop/views/src/test/kotlin/storyevent/timeline/Timeline Ruler Label Menu Unit Test.kt +++ b/desktop/views/src/test/kotlin/storyevent/timeline/Timeline Ruler Label Menu Unit Test.kt @@ -73,7 +73,7 @@ class `Timeline Ruler Label Menu Unit Test` { menu.access().insertBeforeOption!!.fire() verify { - dependencies.adjustStoryEventsTimeController.requestToAdjustStoryEventsTimes( + dependencies.adjustStoryEventsTimeController.adjustTimesBy( storyEventItems.drop(1).map { it.storyEventId }.toSet(), 5L ) @@ -106,7 +106,7 @@ class `Timeline Ruler Label Menu Unit Test` { menu.access().insertAfterOption!!.fire() verify { - dependencies.adjustStoryEventsTimeController.requestToAdjustStoryEventsTimes( + dependencies.adjustStoryEventsTimeController.adjustTimesBy( storyEventItems.drop(3).map { it.storyEventId }.toSet(), 5L ) @@ -139,7 +139,7 @@ class `Timeline Ruler Label Menu Unit Test` { menu.access().removeTimeOption!!.fire() verify { - dependencies.adjustStoryEventsTimeController.requestToAdjustStoryEventsTimes( + dependencies.adjustStoryEventsTimeController.adjustTimesBy( storyEventItems.drop(1).map { it.storyEventId }.toSet(), -5L ) diff --git a/desktop/views/src/test/kotlin/storyevent/timeline/Timeline ViewPort Unit Test.kt b/desktop/views/src/test/kotlin/storyevent/timeline/Timeline ViewPort Unit Test.kt index b5254db3d..13dce1849 100644 --- a/desktop/views/src/test/kotlin/storyevent/timeline/Timeline ViewPort Unit Test.kt +++ b/desktop/views/src/test/kotlin/storyevent/timeline/Timeline ViewPort Unit Test.kt @@ -341,7 +341,7 @@ class `Timeline ViewPort Unit Test` : viewPort.fireEvent(mouseReleasedEvent()) verify { - component.dependencies.adjustStoryEventsTimeController.adjustStoryEventsTime( + component.dependencies.adjustStoryEventsTimeController.adjustTimesBy( any(), expectedAdjustment ) @@ -353,7 +353,7 @@ class `Timeline ViewPort Unit Test` : viewPort.fireEvent(mouseReleasedEvent()) verify { - component.dependencies.adjustStoryEventsTimeController.adjustStoryEventsTime( + component.dependencies.adjustStoryEventsTimeController.adjustTimesBy( (additionalLabels.take(3) + label).map { it.storyEventId }.toSet(), any() ) }