Skip to content

Commit

Permalink
Update MultipleChoiceTaskFragmentTest.kt
Browse files Browse the repository at this point in the history
  • Loading branch information
shobhitagarwal1612 committed Dec 19, 2024
1 parent 459bee0 commit 256a339
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -50,7 +51,11 @@ fun MultipleChoiceItemView(
Row(modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
when (item.cardinality) {
MultipleChoice.Cardinality.SELECT_ONE -> {
RadioButton(selected = item.isSelected, onClick = { toggleItem(item) })
RadioButton(
modifier = Modifier.testTag(MultipleChoiceTestTags.SELECT_MULTIPLE_RADIO),
selected = item.isSelected,
onClick = { toggleItem(item) },
)
}

MultipleChoice.Cardinality.SELECT_MULTIPLE -> {
Expand All @@ -72,6 +77,7 @@ fun MultipleChoiceItemView(
value = item.otherText,
textStyle = MaterialTheme.typography.bodyLarge,
onValueChange = { otherValueChanged(it) },
modifier = Modifier.testTag(MultipleChoiceTestTags.OTHER_INPUT_TEXT),
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -49,7 +50,7 @@ class MultipleChoiceTaskFragment : AbstractTaskFragment<MultipleChoiceTaskViewMo
private fun ShowMultipleChoiceItems() {
val list by viewModel.itemsFlow.asLiveData().observeAsState()
list?.let { items ->
LazyColumn(Modifier.fillMaxSize()) {
LazyColumn(Modifier.fillMaxSize().testTag(MultipleChoiceTestTags.MULTIPLE_CHOICE_LIST)) {
items(items) { item ->
MultipleChoiceItemView(
item = item,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.ground.ui.datacollection.tasks.multiplechoice

object MultipleChoiceTestTags {

const val MULTIPLE_CHOICE_LIST = "multiple choice items test tag"
const val OTHER_INPUT_TEXT = "other input test tag"
const val SELECT_MULTIPLE_RADIO = "select multiple radio test tag"
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@
package com.google.android.ground.ui.datacollection.tasks.multiplechoice

import android.content.Context
import android.widget.RadioButton
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
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.google.android.ground.CustomViewActions
import com.google.android.ground.R
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotSelected
import androidx.compose.ui.test.assertIsSelected
import androidx.compose.ui.test.hasAnySibling
import androidx.compose.ui.test.hasTestTag
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.performTextClearance
import androidx.compose.ui.test.performTextInput
import com.google.android.ground.model.job.Job
import com.google.android.ground.model.submission.MultipleChoiceTaskData
import com.google.android.ground.model.task.MultipleChoice
Expand All @@ -37,15 +37,12 @@ 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
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
Expand Down Expand Up @@ -103,18 +100,20 @@ 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
fun `allows only one selection for SELECT_ONE cardinality`() = runWithTestDispatcher {
val multipleChoice = MultipleChoice(options, MultipleChoice.Cardinality.SELECT_ONE)
setupTaskFragment<MultipleChoiceTaskFragment>(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")
Expand All @@ -129,18 +128,20 @@ 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
fun `allows multiple selection for SELECT_MULTIPLE cardinality`() = runWithTestDispatcher {
val multipleChoice = MultipleChoice(options, MultipleChoice.Cardinality.SELECT_MULTIPLE)
setupTaskFragment<MultipleChoiceTaskFragment>(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")
Expand All @@ -155,11 +156,10 @@ 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))
composeTestRule.onNodeWithText("Other").performClick()
getOtherInputNode().assertIsDisplayed().performTextInput(userInput)

onView(withText("Other")).check(matches(isChecked()))
composeTestRule.onNode(hasText("Other")).assertIsDisplayed()
hasValue(MultipleChoiceTaskData(multipleChoice, listOf("[ $userInput ]")))
runner().assertButtonIsEnabled("Next")
}
Expand All @@ -169,13 +169,15 @@ class MultipleChoiceTaskFragmentTest :
runWithTestDispatcher {
val multipleChoice = MultipleChoice(options, MultipleChoice.Cardinality.SELECT_ONE, true)
setupTaskFragment<MultipleChoiceTaskFragment>(job, task.copy(multipleChoice = multipleChoice))
onView(withText("Option 1")).perform(click())
onView(withText("Other")).check(matches(isNotChecked()))

composeTestRule.onNodeWithText("Option 1").performClick()
getRadioButtonNode("Other").assertIsNotSelected()

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()))
getOtherInputNode().assertIsDisplayed().performTextInput(userInput)

getRadioButtonNode("Option 1").assertIsNotSelected()
getRadioButtonNode("Other").assertIsSelected()
hasValue(MultipleChoiceTaskData(multipleChoice, listOf("[ $userInput ]")))
}

Expand All @@ -187,33 +189,27 @@ class MultipleChoiceTaskFragmentTest :
task.copy(multipleChoice = multipleChoice, isRequired = true),
)

onView(withText("Other")).check(matches(isNotChecked()))
val userInput = "A"
onView(allOf(isDisplayed(), withId(R.id.user_response_text)))
.perform(CustomViewActions.forceTypeText(userInput))
onView(withText("Other")).check(matches(isChecked()))
getRadioButtonNode("Other").assertIsNotSelected()
getOtherInputNode().performTextInput("A")
getRadioButtonNode("Other").assertIsSelected()

onView(allOf(isDisplayed(), withId(R.id.user_response_text)))
.perform(CustomViewActions.clearText())
getOtherInputNode().performTextClearance()

onView(withText("Other")).check(matches(isNotChecked()))
getRadioButtonNode("Other").assertIsNotSelected()
}

@Test
fun `no deselection of other option on text clear when not required`() = runWithTestDispatcher {
val multipleChoice = MultipleChoice(options, MultipleChoice.Cardinality.SELECT_ONE, true)
setupTaskFragment<MultipleChoiceTaskFragment>(job, task.copy(multipleChoice = multipleChoice))

onView(withText("Other")).check(matches(isNotChecked()))
val userInput = "A"
onView(allOf(isDisplayed(), withId(R.id.user_response_text)))
.perform(CustomViewActions.forceTypeText(userInput))
onView(withText("Other")).check(matches(isChecked()))
getRadioButtonNode("Other").assertIsNotSelected()
getOtherInputNode().performTextInput("A")
getRadioButtonNode("Other").assertIsSelected()

onView(allOf(isDisplayed(), withId(R.id.user_response_text)))
.perform(CustomViewActions.clearText())
getOtherInputNode().performTextClearance()

onView(withText("Other")).check(matches(isChecked()))
getRadioButtonNode("Other").assertIsSelected()
}

@Test
Expand All @@ -224,18 +220,16 @@ class MultipleChoiceTaskFragmentTest :
task.copy(multipleChoice = multipleChoice, isRequired = true),
)

val userInput = "A"
onView(allOf(isDisplayed(), withId(R.id.user_response_text)))
.perform(CustomViewActions.forceTypeText(userInput))
onView(withText("Option 1")).perform(click())
onView(withText("Other")).check(matches(isNotChecked()))
onView(withText("Option 1")).check(matches(isChecked()))
getOtherInputNode().performTextInput("A")
composeTestRule.onNodeWithText("Option 1").performClick()

getRadioButtonNode("Other").assertIsNotSelected()
getRadioButtonNode("Option 1").assertIsSelected()

onView(allOf(isDisplayed(), withId(R.id.user_response_text)))
.perform(CustomViewActions.clearText())
getOtherInputNode().performTextClearance()

onView(withText("Option 1")).check(matches(isChecked()))
onView(withText("Other")).check(matches(isNotChecked()))
getRadioButtonNode("Option 1").assertIsSelected()
getRadioButtonNode("Other").assertIsNotSelected()
}

@Test
Expand Down Expand Up @@ -271,7 +265,7 @@ class MultipleChoiceTaskFragmentTest :
val multipleChoice = MultipleChoice(options, MultipleChoice.Cardinality.SELECT_ONE)
setupTaskFragment<MultipleChoiceTaskFragment>(job, task.copy(multipleChoice = multipleChoice))

onView(withText("Option 1")).perform(click())
composeTestRule.onNodeWithText("Option 1").performClick()

runner().assertButtonIsHidden("Skip")
}
Expand Down Expand Up @@ -317,9 +311,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")
Expand All @@ -334,13 +331,20 @@ class MultipleChoiceTaskFragmentTest :
task.copy(multipleChoice = multipleChoice, isRequired = true),
)

onView(withText("Option 1")).perform(click())
onView(withText("Option 2")).perform(click())
onView(withText("Other")).perform(click())
onView(allOf(isDisplayed(), withId(R.id.user_response_text)))
.perform(CustomViewActions.forceTypeText(""))
composeTestRule.onNodeWithText("Option 1").performClick()
composeTestRule.onNodeWithText("Option 2").performClick()
composeTestRule.onNodeWithText("Other").performClick()
getOtherInputNode().performTextClearance()

hasValue(null)
runner().assertButtonIsDisabled("Next")
}

private fun getRadioButtonNode(text: String) =
composeTestRule.onNode(
hasTestTag(MultipleChoiceTestTags.SELECT_MULTIPLE_RADIO) and hasAnySibling(hasText(text))
)

private fun getOtherInputNode() =
composeTestRule.onNodeWithTag(MultipleChoiceTestTags.OTHER_INPUT_TEXT)
}

0 comments on commit 256a339

Please sign in to comment.