diff --git a/app/src/androidTest/java/org/dhis2/usescases/FlowTestsSuite.kt b/app/src/androidTest/java/org/dhis2/usescases/FlowTestsSuite.kt index 04d299aa61..22d11b012b 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/FlowTestsSuite.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/FlowTestsSuite.kt @@ -3,7 +3,6 @@ package org.dhis2.usescases import org.dhis2.usescases.flow.searchFlow.SearchFlowTest import org.dhis2.usescases.flow.syncFlow.SyncFlowTest import org.dhis2.usescases.flow.teiFlow.TeiFlowTest -import org.dhis2.usescases.form.FormTest import org.junit.runner.RunWith import org.junit.runners.Suite @@ -12,6 +11,5 @@ import org.junit.runners.Suite SearchFlowTest::class, SyncFlowTest::class, TeiFlowTest::class, - FormTest::class ) class FlowTestsSuite diff --git a/app/src/androidTest/java/org/dhis2/usescases/filters/FilterTest.kt b/app/src/androidTest/java/org/dhis2/usescases/filters/FilterTest.kt index 966881f270..62b3ab3a94 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/filters/FilterTest.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/filters/FilterTest.kt @@ -6,7 +6,6 @@ import androidx.test.rule.ActivityTestRule import org.dhis2.common.filters.filterRobotCommon import org.dhis2.usescases.BaseTest import org.dhis2.usescases.flow.syncFlow.robot.eventWithoutRegistrationRobot -import org.dhis2.usescases.form.formRobot import org.dhis2.usescases.main.AVOID_SYNC import org.dhis2.usescases.main.MainActivity import org.dhis2.usescases.main.homeRobot @@ -146,12 +145,13 @@ class FilterTest : BaseTest() { eventWithoutRegistrationRobot(composeTestRule) { clickOnEventAtPosition(0) } - formRobot(composeTestRule) { + // Commented because FormRobot class wah eliminated as FormTest was also eliminated + /*formRobot(composeTestRule) { clickOnSelectOption(1, 1) pressBack() pressBack() pressBack() - } + }*/ homeRobot { openFilters() } diff --git a/app/src/androidTest/java/org/dhis2/usescases/form/FormIntents.kt b/app/src/androidTest/java/org/dhis2/usescases/form/FormIntents.kt deleted file mode 100644 index c7c55ef64d..0000000000 --- a/app/src/androidTest/java/org/dhis2/usescases/form/FormIntents.kt +++ /dev/null @@ -1,20 +0,0 @@ -package org.dhis2.usescases.form - -import androidx.test.core.app.ApplicationProvider -import org.dhis2.LazyActivityScenarioRule -import org.dhis2.form.model.EventMode -import org.dhis2.usescases.eventsWithoutRegistration.eventCapture.EventCaptureActivity - -const val PROGRAM_UID = "PROGRAM_UID" -const val PROGRAM_XX_PROGRAM_RULES = "jIT6KcSZiAN" -const val EVENT_GAMMA = "MIZVQnTD4HW" - - -fun prepareIntentAndLaunchEventActivity(rule: LazyActivityScenarioRule) { - EventCaptureActivity.intent( - ApplicationProvider.getApplicationContext(), - EVENT_GAMMA, - PROGRAM_XX_PROGRAM_RULES, - EventMode.CHECK - ).also { rule.launch(it) } -} diff --git a/app/src/androidTest/java/org/dhis2/usescases/form/FormRobot.kt b/app/src/androidTest/java/org/dhis2/usescases/form/FormRobot.kt deleted file mode 100644 index e42dfdab16..0000000000 --- a/app/src/androidTest/java/org/dhis2/usescases/form/FormRobot.kt +++ /dev/null @@ -1,156 +0,0 @@ -package org.dhis2.usescases.form - -import android.app.Activity -import android.view.MenuItem -import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.junit4.ComposeTestRule -import androidx.compose.ui.test.onAllNodesWithTag -import androidx.compose.ui.test.onFirst -import androidx.compose.ui.test.onNodeWithText -import androidx.test.espresso.Espresso.onData -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.action.ViewActions.click -import androidx.test.espresso.assertion.ViewAssertions.doesNotExist -import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition -import androidx.test.espresso.matcher.RootMatchers.isPlatformPopup -import androidx.test.espresso.matcher.RootMatchers.withDecorView -import androidx.test.espresso.matcher.ViewMatchers.hasDescendant -import androidx.test.espresso.matcher.ViewMatchers.isDisplayed -import androidx.test.espresso.matcher.ViewMatchers.withId -import androidx.test.espresso.matcher.ViewMatchers.withText -import org.dhis2.R -import org.dhis2.common.BaseRobot -import org.dhis2.common.matchers.RecyclerviewMatchers.Companion.atPosition -import org.dhis2.common.matchers.RecyclerviewMatchers.Companion.hasItem -import org.dhis2.common.viewactions.clickChildViewWithId -import org.dhis2.common.viewactions.scrollToBottomRecyclerView -import org.dhis2.form.ui.FormViewHolder -import org.dhis2.usescases.form.FormTest.Companion.NO_ACTION_POSITION -import org.hamcrest.CoreMatchers.allOf -import org.hamcrest.CoreMatchers.anything -import org.hamcrest.CoreMatchers.instanceOf -import org.hamcrest.CoreMatchers.`is` -import org.hamcrest.CoreMatchers.not - - -fun formRobot( - composeTestRule: ComposeTestRule, - formRobot: FormRobot.() -> Unit -) { - FormRobot(composeTestRule).apply { - formRobot() - } -} - -class FormRobot(val composeTestRule: ComposeTestRule) : BaseRobot() { - - fun clickOnASpecificSection(sectionLabel: String) { - onView(withText(sectionLabel)).perform(click()) - } - - private fun clickOnSpinner(position: Int) { - onView(withId(R.id.recyclerView)) - .perform( - actionOnItemAtPosition( - position, clickChildViewWithId(R.id.inputEditText) - ) - ) - } - - private fun selectAction(position: Int) { - onData(anything()) - .inRoot(isPlatformPopup()) - .atPosition(position) - .perform(click()) - } - - fun resetToNoAction(position: Int) { - clickOnSpinner(position) - selectAction(NO_ACTION_POSITION) - } - - fun checkHiddenField(label: String) { - onView(withId(R.id.recyclerView)) - .check(matches(not(hasItem(withText(label))))) - } - - fun checkHiddenSection(label: String) { - onView(withId(R.id.recyclerView)) - .check(matches(not(hasItem(withText(label))))) - } - - fun checkValueWasAssigned(value: String) { - onView(withId(R.id.recyclerView)) - .check( - matches( - hasItem( - allOf( - hasDescendant(withId(R.id.input_editText)), - hasDescendant(withText(value)) - ) - ) - ) - ) - } - - fun checkWarningIsShown() { - onView(withId(R.id.recyclerView)) - .check(matches(hasItem(hasDescendant(withText("Warning with Current Event "))))) - } - - fun checkErrorIsShown() { - onView(withId(R.id.recyclerView)) - .check(matches(hasItem(hasDescendant(withText("Error with current event "))))) - } - - fun checkPopUpWithMessageOnCompleteIsShown(message: String, composeTestRule: ComposeTestRule) { - composeTestRule.onAllNodesWithTag(message).onFirst().assertExists() - } - - fun checkIndicatorIsDisplayed(name: String, value: String) { - composeTestRule.onNodeWithText(name).assertIsDisplayed() - composeTestRule.onNodeWithText(value).assertIsDisplayed() - } - - fun checkLabel(label: String, position: Int) { - onView(withId(R.id.recyclerView)) - .check(matches(atPosition(position, hasDescendant(withText(label))))) - } - - fun clickOnSaveForm() { - onView(withId(R.id.actionButton)).perform(click()) - } - - fun checkHiddenOption(label: String, position: Int) { - clickOnSpinner(position) - onView(allOf(instanceOf(MenuItem::class.java), hasDescendant(withText(label)))) - .check(doesNotExist()) - selectAction(0) - } - - fun checkDisplayedOption(label: String, position: Int, activity: Activity) { - clickOnSpinner(position) - onView(withText(label)) - .inRoot(withDecorView(not(`is`(activity.window.decorView)))) - .check(matches(isDisplayed())) - selectAction(0) - } - - fun clickOnSelectOption(position: Int, optionPosition: Int) { - clickOnSpinner(position) - selectAction(optionPosition) - } - - fun scrollToBottomForm() { - onView(withId(R.id.recyclerView)).perform(scrollToBottomRecyclerView()) - } - - fun goToAnalytics() { - onView(withId(R.id.navigation_analytics)).perform(click()) - } - - fun goToDataEntry() { - onView(withId(R.id.navigation_data_entry)).perform(click()) - } -} \ No newline at end of file diff --git a/app/src/androidTest/java/org/dhis2/usescases/form/FormTest.kt b/app/src/androidTest/java/org/dhis2/usescases/form/FormTest.kt deleted file mode 100644 index 06c74900a8..0000000000 --- a/app/src/androidTest/java/org/dhis2/usescases/form/FormTest.kt +++ /dev/null @@ -1,246 +0,0 @@ -package org.dhis2.usescases.form - -import android.util.Log -import androidx.compose.ui.test.junit4.createComposeRule -import org.dhis2.lazyActivityScenarioRule -import org.dhis2.usescases.BaseTest -import org.dhis2.usescases.eventsWithoutRegistration.eventCapture.EventCaptureActivity -import org.junit.After -import org.junit.Ignore -import org.junit.Rule -import org.junit.Test - -const val firstSectionPosition = 2 - -class FormTest : BaseTest() { - - @get:Rule - val ruleEvent = lazyActivityScenarioRule(launchActivity = false) - - - @get:Rule - val composeTestRule = createComposeRule() - - @After - override fun teardown() { - cleanLocalDatabase() - super.teardown() - } - - @Ignore("When added Event details section the test fails, is commented to be refactored with new form in a specific issue") - @Test - fun shouldApplyProgramRules() { - prepareIntentAndLaunchEventActivity(ruleEvent) - - formRobot(composeTestRule) { - clickOnASpecificSection("Gamma Rules A") - } - applyHideField() - applyHideSection() - applyShowWarning() - applyShowError() - applySetMandatoryField() - applyHideOption() - applyHideOptionGroup() - applyShowOptionGroup() - applyAssignValue() - applyDisplayText() - applyDisplayKeyValue() - applyWarningOnComplete() - applyErrorOnComplete() - } - - private fun applyHideField() { - formRobot(composeTestRule) { - clickOnSelectOption( - firstSectionPosition, - HIDE_FIELD_POSITION - ) - checkHiddenField("ZZ TEST LONGTEST") - } - } - - private fun applyHideSection() { - formRobot(composeTestRule) { - resetToNoAction(firstSectionPosition) - clickOnSelectOption( - firstSectionPosition, - HIDE_SECTION_POSITION - ) - checkHiddenSection("Gamma Rules A") - } - } - - private fun applyShowWarning() { - formRobot(composeTestRule) { - resetToNoAction(firstSectionPosition) - clickOnSelectOption( - firstSectionPosition, - SHOW_WARNING_POSITION - ) - checkWarningIsShown() - } - } - - private fun applyShowError() { - formRobot(composeTestRule) { - resetToNoAction(firstSectionPosition) - clickOnSelectOption( - firstSectionPosition, - SHOW_ERROR_POSITION - ) - checkErrorIsShown() - } - } - - private fun applySetMandatoryField() { - formRobot(composeTestRule) { - val nonMandatoryLabel = "ZZ TEST NUMBER" - val mandatoryLabel = "ZZ TEST NUMBER *" - val position = 5 - resetToNoAction(firstSectionPosition) - checkLabel(nonMandatoryLabel, position) - clickOnSelectOption( - firstSectionPosition, - MANDATORY_FIELD_POSITION - ) - checkLabel(mandatoryLabel, position) - } - } - - private fun applyHideOption() { - formRobot(composeTestRule) { - resetToNoAction(firstSectionPosition) - clickOnSelectOption( - firstSectionPosition, - HIDE_OPTION_POSITION - ) - checkHiddenOption("North", OPTION_SET_FIELD_POSITION) - } - } - - private fun applyHideOptionGroup() { - formRobot(composeTestRule) { - resetToNoAction(firstSectionPosition) - clickOnSelectOption( - firstSectionPosition, - HIDE_OPTION_GROUP_POSITION - ) - checkHiddenOption("North", OPTION_SET_FIELD_POSITION) - checkHiddenOption("West", OPTION_SET_FIELD_POSITION) - } - } - - private fun applyShowOptionGroup() { - formRobot(composeTestRule) { - resetToNoAction(firstSectionPosition) - clickOnSelectOption( - firstSectionPosition, - SHOW_OPTION_POSITION - ) - - val activity = waitForActivityScenario() - checkDisplayedOption("North", OPTION_SET_FIELD_POSITION, activity) - checkDisplayedOption("West", OPTION_SET_FIELD_POSITION, activity) - } - } - - private fun applyAssignValue() { - formRobot(composeTestRule) { - resetToNoAction(firstSectionPosition) - clickOnSelectOption( - firstSectionPosition, - ASSIGN_VALUE_POSITION - ) - checkValueWasAssigned(ASSIGNED_VALUE_TEXT) - } - } - - private fun applyDisplayText() { - formRobot(composeTestRule) { - resetToNoAction(firstSectionPosition) - clickOnSelectOption( - firstSectionPosition, - DISPLAY_TEXT_POSITION - ) - pressBack() - goToAnalytics() - checkIndicatorIsDisplayed("Info", "Current Option Selected: DT") - goToDataEntry() - } - } - - private fun applyDisplayKeyValue() { - formRobot(composeTestRule) { - resetToNoAction(firstSectionPosition) - clickOnSelectOption( - firstSectionPosition, - DISPLAY_KEY_POSITION - ) - pressBack() - goToAnalytics() - checkIndicatorIsDisplayed("Current Option", "DKVP") - goToDataEntry() - } - } - - private fun applyWarningOnComplete() { - formRobot(composeTestRule) { - resetToNoAction(firstSectionPosition) - clickOnSelectOption( - firstSectionPosition, - WARNING_COMPLETE_POSITION - ) - scrollToBottomForm() - waitToDebounce(1000) - clickOnSaveForm() - checkPopUpWithMessageOnCompleteIsShown("WARNING_ON_COMPLETE", composeTestRule) - pressBack() - } - } - - private fun applyErrorOnComplete() { - formRobot(composeTestRule) { - resetToNoAction(firstSectionPosition) - clickOnSelectOption( - firstSectionPosition, - ERROR_COMPLETE_POSITION - ) - scrollToBottomForm() - waitToDebounce(1000) - clickOnSaveForm() - checkPopUpWithMessageOnCompleteIsShown("ERROR_ON_COMPLETE", composeTestRule) - pressBack() - } - } - - private fun waitForActivityScenario(): EventCaptureActivity { - var activity: EventCaptureActivity? = null - ruleEvent.getScenario().onActivity { - activity = it - } - while (activity == null) { - Log.d("FormTest", "Waiting for activity to be initialized") - } - return activity!! - } - - companion object { - const val NO_ACTION_POSITION = 0 - const val HIDE_FIELD_POSITION = 1 - const val HIDE_SECTION_POSITION = 2 - const val HIDE_OPTION_POSITION = 3 - const val OPTION_SET_FIELD_POSITION = 6 - const val HIDE_OPTION_GROUP_POSITION = 4 - const val ASSIGN_VALUE_POSITION = 5 - const val ASSIGNED_VALUE_TEXT = "Result for current event" - const val SHOW_WARNING_POSITION = 6 - const val WARNING_COMPLETE_POSITION = 7 - const val SHOW_ERROR_POSITION = 8 - const val ERROR_COMPLETE_POSITION = 9 - const val MANDATORY_FIELD_POSITION = 10 - const val DISPLAY_TEXT_POSITION = 11 - const val DISPLAY_KEY_POSITION = 12 - const val SHOW_OPTION_POSITION = 14 - } -} \ No newline at end of file diff --git a/form/build.gradle.kts b/form/build.gradle.kts index f3c51e624a..9b7f7de06d 100644 --- a/form/build.gradle.kts +++ b/form/build.gradle.kts @@ -53,6 +53,12 @@ android { composeOptions { kotlinCompilerExtensionVersion = libs.versions.kotlinCompilerExtensionVersion.get() } + + testOptions { + unitTests { + isReturnDefaultValues = true + } + } } dependencies { diff --git a/form/src/test/java/org/dhis2/form/integration/ProgramRulesTest.kt b/form/src/test/java/org/dhis2/form/integration/ProgramRulesTest.kt new file mode 100644 index 0000000000..742c7e0b20 --- /dev/null +++ b/form/src/test/java/org/dhis2/form/integration/ProgramRulesTest.kt @@ -0,0 +1,535 @@ +package org.dhis2.form.integration + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.databinding.ObservableField +import io.reactivex.Flowable +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.dhis2.commons.prefs.PreferenceProvider +import org.dhis2.commons.viewmodel.DispatcherProvider +import org.dhis2.form.data.DataEntryRepository +import org.dhis2.form.data.FormRepository +import org.dhis2.form.data.FormRepositoryImpl +import org.dhis2.form.data.FormValueStore +import org.dhis2.form.data.GeometryController +import org.dhis2.form.data.OptionsRepository +import org.dhis2.form.data.RulesUtilsProvider +import org.dhis2.form.data.RulesUtilsProviderImpl +import org.dhis2.form.model.FieldUiModel +import org.dhis2.form.model.FieldUiModelImpl +import org.dhis2.form.model.OptionSetConfiguration +import org.dhis2.form.model.SectionUiModelImpl +import org.dhis2.form.model.StoreResult +import org.dhis2.form.model.ValueStoreResult +import org.dhis2.form.ui.FormViewModel +import org.dhis2.form.ui.intent.FormIntent +import org.dhis2.mobileProgramRules.RuleEngineHelper +import org.hisp.dhis.android.core.D2 +import org.hisp.dhis.android.core.common.ObjectWithUid +import org.hisp.dhis.android.core.common.ValueType +import org.hisp.dhis.android.core.option.Option +import org.hisp.dhis.android.core.program.ProgramRuleActionType +import org.hisp.dhis.rules.models.RuleAction +import org.hisp.dhis.rules.models.RuleEffect +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +class ProgramRulesTest { + + @get:Rule + val rule = InstantTaskExecutorRule() + + private val d2: D2 = mock() + private val optionRepository: OptionsRepository = mock() + private val formValueStore: FormValueStore = mock() + private val ruleEngineHelper: RuleEngineHelper = mock() + private val rulesUtilsProvider: RulesUtilsProvider = RulesUtilsProviderImpl(d2, optionRepository) + private val dataEntryRepository: DataEntryRepository = mock() + + private lateinit var repository: FormRepository + + private val preferenceProvider: PreferenceProvider = mock() + private val geometryController: GeometryController = mock() + + private lateinit var formViewModel: FormViewModel + + @OptIn(ExperimentalCoroutinesApi::class) + private val testingDispatcher = StandardTestDispatcher() + + @OptIn(ExperimentalCoroutinesApi::class) + @Before + fun setup() { + Dispatchers.setMain(testingDispatcher) + + whenever(dataEntryRepository.isEvent()) doReturn true + whenever(dataEntryRepository.disableCollapsableSections()) doReturn false + whenever(dataEntryRepository.list()) doReturn Flowable.just(provideItemList()) + whenever( + dataEntryRepository.updateField( + any(), + anyOrNull(), + any>(), + any>(), + any>(), + ), + ).thenAnswer { invocationOnMock -> + invocationOnMock.getArgument(0) as FieldUiModel + } + + whenever( + dataEntryRepository.updateSection( + any(), + any(), + any(), + any(), + any(), + any(), + ), + ).thenAnswer { invocationOnMock -> + invocationOnMock.getArgument(0) as FieldUiModel + } + + repository = FormRepositoryImpl( + formValueStore = formValueStore, + fieldErrorMessageProvider = mock(), + displayNameProvider = mock(), + dataEntryRepository = dataEntryRepository, + ruleEngineRepository = ruleEngineHelper, + rulesUtilsProvider = rulesUtilsProvider, + legendValueProvider = mock(), + useCompose = true, + ) + + whenever(repository.getDateFormatConfiguration()) doReturn "ddMMyyyy" + + formViewModel = FormViewModel( + repository, + object : DispatcherProvider { + override fun io(): CoroutineDispatcher { + return testingDispatcher + } + + override fun computation(): CoroutineDispatcher { + return testingDispatcher + } + + override fun ui(): CoroutineDispatcher { + return testingDispatcher + } + }, + geometryController, + preferenceProvider = preferenceProvider, + ) + + testingDispatcher.scheduler.advanceUntilIdle() + } + + @OptIn(ExperimentalCoroutinesApi::class) + @After + fun teardown() { + Dispatchers.resetMain() + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `Should assign a value`() = runTest { + whenever(ruleEngineHelper.evaluate()) doReturn listOf( + RuleEffect( + "", + RuleAction( + "assignedValue", + ProgramRuleActionType.ASSIGN.name, + mutableMapOf(Pair("field", "uid001")), + ), + "newValue", + ), + ) + + val intent = FormIntent.OnSave( + uid = "uid004", + value = "newValue04", + valueType = ValueType.TEXT, + ) + + formViewModel.submitIntent(intent) + advanceUntilIdle() + + val items = formViewModel.items.value ?: emptyList() + + assert(items.find { it.uid == "uid001" }?.value == "newValue") + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `Should hide field`() = runTest { + whenever(ruleEngineHelper.evaluate()) doReturn listOf( + RuleEffect( + "ruleUid", + RuleAction( + "data", + ProgramRuleActionType.HIDEFIELD.name, + mutableMapOf( + "content" to "content", + "field" to "uid001", + ), + ), + ), + ) + + val intent = FormIntent.OnSave( + uid = "uid004", + value = "newValue04", + valueType = ValueType.TEXT, + ) + + formViewModel.submitIntent(intent) + advanceUntilIdle() + + val items = formViewModel.items.value ?: emptyList() + + items.forEach { + assert(it.uid != "uid001") + } + assert(items.size == 6) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `Should hide section`() = runTest { + whenever(ruleEngineHelper.evaluate()) doReturn listOf( + RuleEffect( + "ruleUid", + RuleAction( + "data", + ProgramRuleActionType.HIDESECTION.name, + mutableMapOf( + "content" to "content", + "programStageSection" to "section1", + ), + ), + "data", + ), + ) + + val intent = FormIntent.OnSave( + uid = "uid004", + value = "newValue04", + valueType = ValueType.TEXT, + ) + + formViewModel.submitIntent(intent) + advanceUntilIdle() + + val items = formViewModel.items.value ?: emptyList() + + assertTrue(items.size == 4) + assertTrue(items[0].uid == "uid004") + assertTrue(items[1].uid == "uid005") + assertTrue(items[2].uid == "uid006") + assertTrue(items[3].uid == "uid007") + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `Should show warning and error message`() = runTest { + whenever(ruleEngineHelper.evaluate()) doReturn listOf( + RuleEffect( + "ruleUid", + RuleAction( + "data", + ProgramRuleActionType.SHOWWARNING.name, + mutableMapOf( + "content" to "content", + "field" to "uid002", + ), + ), + "warning message", + ), + RuleEffect( + "ruleUid2", + RuleAction( + "data", + ProgramRuleActionType.SHOWERROR.name, + mutableMapOf( + "content" to "content", + "field" to "uid005", + ), + ), + "error message", + ), + ) + + val intent = FormIntent.OnSave( + uid = "uid004", + value = "value04", + valueType = ValueType.TEXT, + ) + + formViewModel.submitIntent(intent) + advanceUntilIdle() + + val items = formViewModel.items.value ?: emptyList() + + items.forEach { + if (it.uid == "uid002") { + assertNotNull(it.warning) + assertEquals(it.warning, "content warning message") + } + if (it.uid == "uid005") { + assertNotNull(it.error) + assertEquals(it.error, "content error message") + } + } + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `Should set mandatory field`() = runTest { + whenever(ruleEngineHelper.evaluate()) doReturn listOf( + RuleEffect( + "ruleUid", + RuleAction( + "data", + ProgramRuleActionType.SETMANDATORYFIELD.name, + mutableMapOf( + "content" to "content", + "field" to "uid003", + ), + ), + "data", + ), + ) + + val intent = FormIntent.OnSave( + uid = "uid004", + value = "value04", + valueType = ValueType.TEXT, + ) + + formViewModel.submitIntent(intent) + advanceUntilIdle() + + val items = formViewModel.items.value ?: emptyList() + + items.forEach { + if (it.uid == "uid003") { + assertTrue(it.mandatory) + } + } + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `Should show option`() = runTest { + whenever(ruleEngineHelper.evaluate()) doReturn listOf( + RuleEffect( + "ruleUid", + RuleAction( + "data", + ProgramRuleActionType.SHOWOPTIONGROUP.name, + mutableMapOf( + "content" to "content", + "field" to "uid006", + "optionGroup" to "optionGroupId", + ), + ), + "data", + ), + ) + + val intent = FormIntent.OnSave( + uid = "uid004", + value = "value04", + valueType = ValueType.TEXT, + ) + + whenever(formValueStore.deleteOptionValueIfSelected(any(), any())) doReturn StoreResult( + "uid006", + ValueStoreResult.VALUE_CHANGED, + ) + + formViewModel.submitIntent(intent) + advanceUntilIdle() + + val items = formViewModel.items.value ?: emptyList() + + val optionsToDisplay: List