From 6546046e2d6183a3700c689db0d86e4f28d4096b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manu=20Mu=C3=B1oz?= Date: Tue, 7 Jan 2025 14:53:04 +0100 Subject: [PATCH] fix: [ANDROAPP-6705] allow future dates (#3929) --- .../teidashboard/robot/TeiDashboardRobot.kt | 3 + .../java/org/dhis2/form/ui/FormViewModel.kt | 21 ++++--- .../org/dhis2/form/ui/FormViewModelTest.kt | 59 ++++++++++++++++++- 3 files changed, 71 insertions(+), 12 deletions(-) diff --git a/app/src/androidTest/java/org/dhis2/usescases/teidashboard/robot/TeiDashboardRobot.kt b/app/src/androidTest/java/org/dhis2/usescases/teidashboard/robot/TeiDashboardRobot.kt index b9795c3f35..211d9cc787 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/teidashboard/robot/TeiDashboardRobot.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/teidashboard/robot/TeiDashboardRobot.kt @@ -1,6 +1,7 @@ package org.dhis2.usescases.teidashboard.robot import android.content.Context +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.hasAnySibling import androidx.compose.ui.test.hasTestTag @@ -97,7 +98,9 @@ class TeiDashboardRobot(val composeTestRule: ComposeTestRule) : BaseRobot() { composeTestRule.onNodeWithText("Enrollment cancelled").assertIsDisplayed() } + @OptIn(ExperimentalTestApi::class) fun clickOnEventWithTitle(title: String) { + composeTestRule.waitUntilExactlyOneExists(hasText(title)) composeTestRule.onNodeWithText(title).performClick() } diff --git a/form/src/main/java/org/dhis2/form/ui/FormViewModel.kt b/form/src/main/java/org/dhis2/form/ui/FormViewModel.kt index 50b9aab0b2..270f6e0760 100644 --- a/form/src/main/java/org/dhis2/form/ui/FormViewModel.kt +++ b/form/src/main/java/org/dhis2/form/ui/FormViewModel.kt @@ -343,11 +343,16 @@ class FormViewModel( private fun saveLastFocusedItem(rowAction: RowAction) = getLastFocusedTextItem()?.let { if (previousActionItem == null) previousActionItem = rowAction if (previousActionItem?.value != it.value && previousActionItem?.id == it.uid) { - val error = checkFieldError(it.valueType, it.value, it.fieldMask) - if (error != null) { - val action = rowActionFromIntent( - FormIntent.OnSave(it.uid, it.value, it.valueType, it.fieldMask), - ) + val action = rowActionFromIntent( + FormIntent.OnSave( + it.uid, + it.value, + it.valueType, + it.fieldMask, + it.allowFutureDates, + ), + ) + if (action.error != null) { repository.updateErrorList(action) StoreResult( rowAction.id, @@ -355,8 +360,6 @@ class FormViewModel( ) } else { checkAutoCompleteForLastFocusedItem(it) - val intent = FormIntent.OnSave(it.uid, it.value, it.valueType, it.fieldMask) - val action = rowActionFromIntent(intent) val result = repository.save(it.uid, it.value, action.extraData) repository.updateValueOnList(it.uid, it.value, it.valueType) repository.updateErrorList(action) @@ -809,13 +812,13 @@ class FormViewModel( fun discardChanges() { repository.backupOfChangedItems().forEach { - submitIntent(FormIntent.OnSave(it.uid, it.value, it.valueType, it.fieldMask)) + submitIntent(FormIntent.OnSave(it.uid, it.value, it.valueType, it.fieldMask, it.allowFutureDates)) } } fun saveDataEntry() { getLastFocusedTextItem()?.let { - submitIntent(FormIntent.OnSave(it.uid, it.value, it.valueType, it.fieldMask)) + submitIntent(FormIntent.OnSave(it.uid, it.value, it.valueType, it.fieldMask, it.allowFutureDates)) } submitIntent(FormIntent.OnFinish()) } diff --git a/form/src/test/java/org/dhis2/form/ui/FormViewModelTest.kt b/form/src/test/java/org/dhis2/form/ui/FormViewModelTest.kt index 64c30dae3c..9406438b81 100644 --- a/form/src/test/java/org/dhis2/form/ui/FormViewModelTest.kt +++ b/form/src/test/java/org/dhis2/form/ui/FormViewModelTest.kt @@ -4,7 +4,8 @@ import android.content.Intent import androidx.arch.core.executor.testing.InstantTaskExecutorRule import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import org.dhis2.commons.prefs.PreferenceProvider @@ -16,6 +17,7 @@ import org.dhis2.form.data.GeometryController import org.dhis2.form.data.MissingMandatoryResult import org.dhis2.form.data.SuccessfulResult import org.dhis2.form.model.ActionType +import org.dhis2.form.model.FieldUiModel import org.dhis2.form.model.RowAction import org.dhis2.form.model.StoreResult import org.dhis2.form.model.ValueStoreResult @@ -28,9 +30,13 @@ import org.junit.Before import org.junit.Ignore import org.junit.Rule import org.junit.Test +import org.mockito.kotlin.any import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock +import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import java.time.LocalDate +import java.time.format.DateTimeFormatter @ExperimentalCoroutinesApi class FormViewModelTest { @@ -39,8 +45,9 @@ class FormViewModelTest { val instantExecutorRule = InstantTaskExecutorRule() private val repository: FormRepository = mock() + private val testingDispatcher = StandardTestDispatcher() private val dispatcher: DispatcherProvider = mock { - on { io() } doReturn Dispatchers.IO + on { io() } doReturn testingDispatcher } private val preferenceProvider: PreferenceProvider = mock() private val geometryController: GeometryController = mock() @@ -49,13 +56,14 @@ class FormViewModelTest { @Before fun setUp() { - Dispatchers.setMain(UnconfinedTestDispatcher()) + Dispatchers.setMain(testingDispatcher) viewModel = FormViewModel( repository, dispatcher, geometryController, ) + whenever(repository.getDateFormatConfiguration()) doReturn "ddMMyyyy" } @Ignore("We need to update Kotlin version in order to test coroutines") @@ -161,4 +169,49 @@ class FormViewModelTest { assertTrue(viewModel.getUpdatedData(uiEvent).value == uiEvent.value) } + + @Test + fun `Should not save last focused item when is not allowed future dates`() = runTest { + val dateField = dateFieldNotAllowedFuture + whenever(repository.currentFocusedItem()) doReturn dateField + viewModel.previousActionItem = RowAction( + id = dateField.uid, + value = "2024-12-12", + type = ActionType.ON_FOCUS, + ) + viewModel.submitIntent(FormIntent.OnFocus("newField", null)) + advanceUntilIdle() + verify(repository).updateErrorList(any()) + } + + @Test + fun `Should save last focused item with future date when is allowed future dates`() = runTest { + val dateField = dateFieldFuture + whenever(repository.currentFocusedItem()) doReturn dateField + viewModel.previousActionItem = RowAction( + id = dateField.uid, + value = "2024-12-12", + type = ActionType.ON_FOCUS, + ) + viewModel.submitIntent(FormIntent.OnFocus("newField", null)) + advanceUntilIdle() + verify(repository).save(dateField.uid, dateField.value, null) + verify(repository).updateValueOnList(dateField.uid, dateField.value, dateField.valueType) + } + + private val futureDate: String = LocalDate.now().plusDays(1).format(DateTimeFormatter.ISO_DATE) + + private val dateFieldFuture: FieldUiModel = mock { + on { uid } doReturn "fieldUid" + on { valueType } doReturn ValueType.DATE + on { allowFutureDates } doReturn true + on { value } doReturn futureDate + } + + private val dateFieldNotAllowedFuture: FieldUiModel = mock { + on { uid } doReturn "fieldUid" + on { valueType } doReturn ValueType.DATE + on { allowFutureDates } doReturn false + on { value } doReturn futureDate + } }