diff --git a/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt b/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt index bf53b35d6d..09545d4782 100644 --- a/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt +++ b/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt @@ -28,6 +28,7 @@ import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.tooling.preview.Preview @@ -46,7 +47,7 @@ fun MultipleChoiceItemView( toggleItem: (item: MultipleChoiceItem) -> Unit = {}, otherValueChanged: (text: String) -> Unit = {}, ) { - Column { + Column(modifier = modifier.testTag(MultipleChoiceTestTags.MULTIPLE_CHOICE_ITEM)) { Row(modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { when (item.cardinality) { MultipleChoice.Cardinality.SELECT_ONE -> { @@ -54,7 +55,11 @@ fun MultipleChoiceItemView( } MultipleChoice.Cardinality.SELECT_MULTIPLE -> { - Checkbox(checked = item.isSelected, onCheckedChange = { toggleItem(item) }) + Checkbox( + checked = item.isSelected, + onCheckedChange = { toggleItem(item) }, + modifier = Modifier.testTag(MultipleChoiceTestTags.SELECT_MULTIPLE_CHECKBOX), + ) } } @@ -72,6 +77,7 @@ fun MultipleChoiceItemView( value = item.otherText, textStyle = MaterialTheme.typography.bodyLarge, onValueChange = { otherValueChanged(it) }, + modifier = Modifier.testTag(MultipleChoiceTestTags.OTHER_INPUT_TEXT), ) } } diff --git a/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceTaskFragment.kt b/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceTaskFragment.kt index 7abf8fcde8..0bae82fd24 100644 --- a/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceTaskFragment.kt +++ b/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceTaskFragment.kt @@ -25,6 +25,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.testTag import androidx.lifecycle.asLiveData import com.google.android.ground.ui.datacollection.components.TaskView import com.google.android.ground.ui.datacollection.components.TaskViewFactory @@ -49,7 +50,7 @@ class MultipleChoiceTaskFragment : AbstractTaskFragment - LazyColumn(Modifier.fillMaxSize()) { + LazyColumn(Modifier.fillMaxSize().testTag(MultipleChoiceTestTags.MULTIPLE_CHOICE_LIST)) { items(items) { item -> MultipleChoiceItemView( item = item, diff --git a/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceTestTags.kt b/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceTestTags.kt new file mode 100644 index 0000000000..1cc996eb81 --- /dev/null +++ b/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceTestTags.kt @@ -0,0 +1,9 @@ +package com.google.android.ground.ui.datacollection.tasks.multiplechoice + +object MultipleChoiceTestTags { + + const val MULTIPLE_CHOICE_LIST = "multiple choice items test tag" + const val MULTIPLE_CHOICE_ITEM = "multiple choice item test tag" + const val OTHER_INPUT_TEXT = "other input test tag" + const val SELECT_MULTIPLE_CHECKBOX = "select multiple test tag" +} diff --git a/ground/src/test/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceTaskFragmentTest.kt b/ground/src/test/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceTaskFragmentTest.kt index b3a9943606..d1361eb2d5 100644 --- a/ground/src/test/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceTaskFragmentTest.kt +++ b/ground/src/test/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceTaskFragmentTest.kt @@ -16,11 +16,22 @@ package com.google.android.ground.ui.datacollection.tasks.multiplechoice import android.content.Context -import android.widget.RadioButton +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.SemanticsProperties +import androidx.compose.ui.semantics.getOrNull +import androidx.compose.ui.test.SemanticsMatcher +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsNotSelected +import androidx.compose.ui.test.assertIsSelected +import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performScrollToNode +import androidx.compose.ui.test.performTextInput import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.matcher.ViewMatchers.hasChildCount import androidx.test.espresso.matcher.ViewMatchers.isChecked import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isNotChecked @@ -37,7 +48,6 @@ import com.google.android.ground.ui.common.ViewModelFactory import com.google.android.ground.ui.datacollection.DataCollectionViewModel import com.google.android.ground.ui.datacollection.components.ButtonAction import com.google.android.ground.ui.datacollection.tasks.BaseTaskFragmentTest -import com.google.android.material.checkbox.MaterialCheckBox import com.google.common.truth.Truth.assertThat import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.testing.BindValue @@ -45,7 +55,6 @@ import dagger.hilt.android.testing.HiltAndroidTest import javax.inject.Inject import kotlinx.collections.immutable.persistentListOf import org.hamcrest.Matchers.allOf -import org.hamcrest.Matchers.instanceOf import org.junit.Assert.assertThrows import org.junit.Test import org.junit.runner.RunWith @@ -103,9 +112,11 @@ class MultipleChoiceTaskFragmentTest : task.copy(multipleChoice = MultipleChoice(options, MultipleChoice.Cardinality.SELECT_ONE)), ) - onView(withId(R.id.select_option_list)).check(matches(allOf(isDisplayed(), hasChildCount(2)))) - onView(withText("Option 1")) - .check(matches(allOf(isDisplayed(), instanceOf(RadioButton::class.java)))) + composeTestRule + .onNodeWithTag(MultipleChoiceTestTags.MULTIPLE_CHOICE_LIST) + .performScrollToNode(hasText("Option 1")) + .performScrollToNode(hasText("Option 2")) + .assertIsDisplayed() } @Test @@ -113,8 +124,8 @@ class MultipleChoiceTaskFragmentTest : val multipleChoice = MultipleChoice(options, MultipleChoice.Cardinality.SELECT_ONE) setupTaskFragment(job, task.copy(multipleChoice = multipleChoice)) - onView(withText("Option 1")).perform(click()) - onView(withText("Option 2")).perform(click()) + composeTestRule.onNodeWithText("Option 1").performClick() + composeTestRule.onNodeWithText("Option 2").performClick() hasValue(MultipleChoiceTaskData(multipleChoice, listOf("option id 2"))) runner().assertButtonIsEnabled("Next") @@ -129,9 +140,11 @@ class MultipleChoiceTaskFragmentTest : ), ) - onView(withId(R.id.select_option_list)).check(matches(allOf(isDisplayed(), hasChildCount(2)))) - onView(withText("Option 1")) - .check(matches(allOf(isDisplayed(), instanceOf(MaterialCheckBox::class.java)))) + composeTestRule + .onNodeWithTag(MultipleChoiceTestTags.MULTIPLE_CHOICE_LIST) + .performScrollToNode(hasText("Option 1")) + .performScrollToNode(hasText("Option 2")) + .assertIsDisplayed() } @Test @@ -139,8 +152,8 @@ class MultipleChoiceTaskFragmentTest : val multipleChoice = MultipleChoice(options, MultipleChoice.Cardinality.SELECT_MULTIPLE) setupTaskFragment(job, task.copy(multipleChoice = multipleChoice)) - onView(withText("Option 1")).perform(click()) - onView(withText("Option 2")).perform(click()) + composeTestRule.onNodeWithText("Option 1").performClick() + composeTestRule.onNodeWithText("Option 2").performClick() hasValue(MultipleChoiceTaskData(multipleChoice, listOf("option id 1", "option id 2"))) runner().assertButtonIsEnabled("Next") @@ -155,27 +168,71 @@ class MultipleChoiceTaskFragmentTest : ) val userInput = "User inputted text" - onView(withText("Other")).perform(click()) - onView(allOf(isDisplayed(), withId(R.id.user_response_text))) - .perform(CustomViewActions.forceTypeText(userInput)) - - onView(withText("Other")).check(matches(isChecked())) + composeTestRule.onNodeWithText("Other").performClick() + composeTestRule + .onNodeWithTag(MultipleChoiceTestTags.OTHER_INPUT_TEXT) + .assertIsDisplayed() + .performTextInput(userInput) + // onView(allOf(isDisplayed(), withId(R.id.user_response_text))) + // .perform(CustomViewActions.forceTypeText(userInput)) + + // val string = + // + // error( + // + // composeTestRule.onNodeWithTag(MultipleChoiceTestTags.MULTIPLE_CHOICE_LIST).printToString() + // ) + + // composeTestRule.onRoot().printToLog("hello") + + // composeTestRule.onNode(isSelectable()).assertIsSelected() + + // composeTestRule.onNode(withRole(Role.Checkbox).and(hasText("Other"))).assertIsDisplayed() + composeTestRule.onNode(hasText("Other")).assertIsDisplayed() + // .onSibling() + + // .onNodeWithTag(MultipleChoiceTestTags.MULTIPLE_CHOICE_LIST) + // .performScrollToNode(hasText("Other")) + // .assertIsDisplayed() + // .onChild() + // .assertIsSelected() + // .onNode(hasText("Other") and + // hasTestTag(MultipleChoiceTestTags.SELECT_MULTIPLE_CHECKBOX)) + // .assertIsDisplayed() + // .assertIsSelected() + // composeTestRule.onNodeWithText("Other").assertIsSelected() + // onView(withText("Other")).check(matches(isChecked())) hasValue(MultipleChoiceTaskData(multipleChoice, listOf("[ $userInput ]"))) runner().assertButtonIsEnabled("Next") } + fun withRole(role: Role) = + SemanticsMatcher("${SemanticsProperties.Role.name} contains '$role'") { + val roleProperty = it.config.getOrNull(SemanticsProperties.Role) ?: false + roleProperty == role + } + @Test fun `selects other option on text input and deselects other radio inputs`() = runWithTestDispatcher { val multipleChoice = MultipleChoice(options, MultipleChoice.Cardinality.SELECT_ONE, true) setupTaskFragment(job, task.copy(multipleChoice = multipleChoice)) - onView(withText("Option 1")).perform(click()) - onView(withText("Other")).check(matches(isNotChecked())) + + composeTestRule.onNodeWithText("Option 1").performClick() + composeTestRule.onNodeWithText("Other").assertIsNotSelected() + // onView(withText("Option 1")).perform(click()) + // onView(withText("Other")).check(matches(isNotChecked())) val userInput = "A" - onView(allOf(isDisplayed(), withId(R.id.user_response_text))) - .perform(CustomViewActions.forceTypeText(userInput)) - onView(withText("Option 1")).check(matches(isNotChecked())) - onView(withText("Other")).check(matches(isChecked())) + composeTestRule + .onNodeWithTag(MultipleChoiceTestTags.OTHER_INPUT_TEXT) + .assertIsDisplayed() + .performTextInput(userInput) + // onView(allOf(isDisplayed(), withId(R.id.user_response_text))) + // .perform(CustomViewActions.forceTypeText(userInput)) + composeTestRule.onNodeWithText("Option 1").assertIsNotSelected() + // onView(withText("Option 1")).check(matches(isNotChecked())) + composeTestRule.onNodeWithText("Other").assertIsSelected() + // onView(withText("Other")).check(matches(isChecked())) hasValue(MultipleChoiceTaskData(multipleChoice, listOf("[ $userInput ]"))) } @@ -317,9 +374,12 @@ class MultipleChoiceTaskFragmentTest : task.copy(multipleChoice = multipleChoice, isRequired = true), ) - onView(withText("Other")).perform(click()) - onView(allOf(isDisplayed(), withId(R.id.user_response_text))) - .perform(CustomViewActions.forceTypeText("")) + composeTestRule.onNodeWithText("Other").performClick() + + composeTestRule + .onNodeWithTag(MultipleChoiceTestTags.OTHER_INPUT_TEXT) + .assertIsDisplayed() + .performTextInput("") hasValue(null) runner().assertButtonIsDisabled("Next")