Skip to content

Commit

Permalink
Merge a667901 into acb6193
Browse files Browse the repository at this point in the history
  • Loading branch information
shobhitagarwal1612 authored Jan 5, 2025
2 parents acb6193 + a667901 commit 8618468
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ constructor(
val addLoiTaskId = deltas.indexOfFirst { it.taskId == addLoiTask.id }
if (addLoiTaskId < 0) error("Add LOI task response missing")
val addLoiValue = deltas.removeAt(addLoiTaskId).newTaskData
// TODO: Replace check for valid addLoiTask using task type instead of TaskValue's type.
// Issue URL: https://github.com/google/ground-android/issues/2981
if (addLoiValue !is GeometryTaskData) error("Invalid add LOI task response")
return locationOfInterestRepository.saveLoi(
addLoiValue.geometry,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em
import com.google.android.ground.R

const val LOI_NAME_TEXT_FIELD_TEST_TAG: String = "loi name text field test tag"

@Composable
fun LoiNameDialog(
textFieldValue: String,
Expand All @@ -50,7 +53,12 @@ fun LoiNameDialog(
Column {
Text(text = stringResource(R.string.loi_name_dialog_body))
Spacer(Modifier.height(16.dp))
TextField(value = textFieldValue, onValueChange = onTextFieldChange, singleLine = true)
TextField(
value = textFieldValue,
onValueChange = onTextFieldChange,
singleLine = true,
modifier = Modifier.testTag(LOI_NAME_TEXT_FIELD_TEST_TAG),
)
}
},
confirmButton = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ class DropPinTaskFragment @Inject constructor() : AbstractTaskFragment<DropPinTa
override fun onCreateActionButtons() {
addSkipButton()
addUndoButton()
// TODO: Disable the button is location is not available.
// Issue URL: https://github.com/google/ground-android/issues/2982
addButton(ButtonAction.DROP_PIN)
.setOnClickListener { viewModel.dropPin() }
.setOnValueChanged { button, value -> button.showIfTrue(value.isNullOrEmpty()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ constructor(
features.postValue(setOf())
}

fun updateResponse(point: Point) {
private fun updateResponse(point: Point) {
setValue(DropPinTaskData(point))
dropMarker(point)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@

package com.google.android.ground.ui.datacollection

import android.os.Bundle
import com.google.android.ground.BaseHiltTest
import com.google.android.ground.R
import com.google.android.ground.domain.usecases.survey.ActivateSurveyUseCase
import com.google.android.ground.launchFragmentWithNavController
import com.google.android.ground.model.geometry.Coordinates
import com.google.android.ground.model.geometry.Point
import com.google.android.ground.model.mutation.Mutation
import com.google.android.ground.model.mutation.SubmissionMutation
import com.google.android.ground.model.submission.DraftSubmission
import com.google.android.ground.model.submission.DropPinTaskData
import com.google.android.ground.model.submission.MultipleChoiceTaskData
import com.google.android.ground.model.submission.TextTaskData
import com.google.android.ground.model.submission.ValueDelta
Expand All @@ -33,15 +35,20 @@ import com.google.android.ground.model.task.MultipleChoice
import com.google.android.ground.model.task.Option
import com.google.android.ground.model.task.Task
import com.google.android.ground.persistence.local.room.converter.SubmissionDeltasConverter
import com.google.android.ground.persistence.sync.MutationSyncWorkManager
import com.google.android.ground.repository.LocationOfInterestRepository
import com.google.android.ground.repository.MutationRepository
import com.google.android.ground.repository.SubmissionRepository
import com.google.android.ground.repository.UserRepository
import com.google.android.ground.ui.datacollection.tasks.point.DropPinTaskViewModel
import com.google.android.ground.ui.map.CameraPosition
import com.google.common.truth.Truth.assertThat
import com.sharedtest.FakeData
import com.sharedtest.FakeData.LOCATION_OF_INTEREST
import com.sharedtest.FakeData.LOCATION_OF_INTEREST_NAME
import com.sharedtest.FakeData.USER
import com.sharedtest.persistence.remote.FakeRemoteDataStore
import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidTest
import java.util.Date
import javax.inject.Inject
Expand All @@ -51,6 +58,7 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.advanceUntilIdle
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.robolectric.RobolectricTestRunner
import org.robolectric.shadows.ShadowToast

Expand All @@ -61,10 +69,13 @@ class DataCollectionFragmentTest : BaseHiltTest() {

@Inject lateinit var activateSurvey: ActivateSurveyUseCase
@Inject lateinit var fakeRemoteDataStore: FakeRemoteDataStore
@Inject lateinit var loiRepository: LocationOfInterestRepository
@Inject lateinit var mutationRepository: MutationRepository
@Inject lateinit var submissionRepository: SubmissionRepository
@Inject lateinit var userRepository: UserRepository

@BindValue @Mock lateinit var mutationSyncWorkManager: MutationSyncWorkManager

lateinit var fragment: DataCollectionFragment

override fun setUp() = runBlocking {
Expand All @@ -81,13 +92,29 @@ class DataCollectionFragmentTest : BaseHiltTest() {
.validateTextIsDisplayed(requireNotNull(JOB.name))
}

@Test
fun `Only job name is displayed when LOI is not provided`() {
setupFragmentWithNoLoi()

runner()
.validateTextDoesNotExist("Unnamed point")
.validateTextIsDisplayed(requireNotNull(JOB.name))
}

@Test
fun `First task is loaded and is visible`() {
setupFragment()

runner().validateTextIsDisplayed(TASK_1_NAME).validateTextIsNotDisplayed(TASK_2_NAME)
}

@Test
fun `Add LOI task is loaded and is visible when LOI is not provided`() {
setupFragmentWithNoLoi()

runner().validateTextIsDisplayed(TASK_0_NAME).validateTextIsNotDisplayed(TASK_1_NAME)
}

@Test
fun `Next button is disabled when task doesn't have any value`() {
setupFragment()
Expand All @@ -109,6 +136,51 @@ class DataCollectionFragmentTest : BaseHiltTest() {
assertThat(ShadowToast.shownToastCount()).isEqualTo(0)
}

@Test
fun `Next button displays the LoiNameDialog when task has value when LOI is missing`() {
setupFragmentWithNoLoi()

runner().clickButton("Drop pin").clickNextButton().assertLoiNameDialogIsDisplayed()
}

@Test
fun `Entering loi name enables the save button in LoiNameDialog`() {
setupFragmentWithNoLoi()

runner()
.clickButton("Drop pin")
.clickNextButton()
.assertButtonIsDisabled("Save")
.inputLoiName("Custom Loi Name")
.assertButtonIsEnabled("Save")
}

@Test
fun `Clicking cancel hides the LoiNameDialog`() {
setupFragmentWithNoLoi()

runner()
.clickButton("Drop pin")
.clickNextButton()
.clickButton("Cancel")
.assertLoiNameDialogIsNotDisplayed()
}

@Test
fun `Clicking save in LoiNameDialog proceeds to next task`() {
setupFragmentWithNoLoi()

runner()
.clickButton("Drop pin")
.clickNextButton()
.inputLoiName("Custom Loi Name")
.clickButton("Save")
.validateTextIsDisplayed("Custom Loi Name")
.validateTextIsDisplayed(TASK_1_NAME)
.validateTextIsNotDisplayed(TASK_0_NAME)
.validateTextIsNotDisplayed(TASK_2_NAME)
}

@Test
fun `Previous button navigates back to first task`() {
setupFragment()
Expand Down Expand Up @@ -167,19 +239,7 @@ class DataCollectionFragmentTest : BaseHiltTest() {
// Issue URL: https://github.com/google/ground-android/issues/708
val expectedDeltas = listOf(TASK_1_VALUE_DELTA, TASK_2_VALUE_DELTA)

// Start the fragment with draft values
setupFragment(
DataCollectionFragmentArgs.Builder(
LOCATION_OF_INTEREST.id,
LOCATION_OF_INTEREST_NAME,
JOB.id,
true,
SubmissionDeltasConverter.toString(expectedDeltas),
"",
)
.build()
.toBundle()
)
setupFragmentWithDraft(expectedDeltas)

runner()
.assertInputTextDisplayed(TASK_1_RESPONSE)
Expand All @@ -202,6 +262,30 @@ class DataCollectionFragmentTest : BaseHiltTest() {
assertSubmissionSaved(listOf(TASK_1_VALUE_DELTA, TASK_2_VALUE_DELTA))
}

@Test
fun `Clicking done on final task saves the submission and LOI when LOI is not provided`() =
runWithTestDispatcher {
setupFragmentWithNoLoi()

runner()
.clickButton("Drop pin")
.clickNextButton()
.inputLoiName("Custom Loi Name")
.clickButton("Save")
.inputText(TASK_1_RESPONSE)
.clickNextButton()
.validateTextIsNotDisplayed(TASK_1_NAME)
.validateTextIsDisplayed(TASK_2_NAME)
.selectOption(TASK_2_OPTION_LABEL)
.clickDoneButton() // Click "done" on final task

assetLoiSaved(loiId = "TEST UUID", customId = "Custom Loi Name")
assertSubmissionSaved(
loiId = "TEST UUID",
valueDeltas = listOf(TASK_1_VALUE_DELTA, TASK_2_VALUE_DELTA),
)
}

@Test
fun `Clicking back button on first task clears the draft and returns false`() =
runWithTestDispatcher {
Expand Down Expand Up @@ -265,10 +349,24 @@ class DataCollectionFragmentTest : BaseHiltTest() {
assertSubmissionSaved(listOf(TASK_1_VALUE_DELTA, TASK_2_VALUE_DELTA))
}

private suspend fun assertSubmissionSaved(valueDeltas: List<ValueDelta>) {
assertNoDraftSaved()
private suspend fun assetLoiSaved(loiId: String, customId: String) {
val actualLoi = checkNotNull(loiRepository.getOfflineLoi(surveyId = SURVEY.id, loiId = loiId))

assertThat(actualLoi.id).isEqualTo(loiId)
assertThat(actualLoi.surveyId).isEqualTo(SURVEY.id)
assertThat(actualLoi.job).isEqualTo(JOB)
assertThat(actualLoi.customId).isEmpty()
assertThat(actualLoi.geometry).isEqualTo(TASK_0_VALUE.geometry)
assertThat(actualLoi.submissionCount).isEqualTo(0)
assertThat(actualLoi.properties).isEqualTo(mapOf("name" to customId))
assertThat(actualLoi.isPredefined).isFalse()
}

val loiId = LOCATION_OF_INTEREST.id
private suspend fun assertSubmissionSaved(
valueDeltas: List<ValueDelta>,
loiId: String = LOCATION_OF_INTEREST.id,
) {
assertNoDraftSaved()

// Exactly 1 submission should be saved.
assertThat(submissionRepository.getPendingCreateCount(loiId)).isEqualTo(1)
Expand Down Expand Up @@ -331,19 +429,38 @@ class DataCollectionFragmentTest : BaseHiltTest() {
advanceUntilIdle()
}

private fun setupFragment(fragmentArgs: Bundle? = null) {
private fun setupFragmentWithDraft(expectedValues: List<ValueDelta>) {
setupFragment(
shouldLoadFromDraft = true,
draftValues = SubmissionDeltasConverter.toString(expectedValues),
)
}

private fun setupFragmentWithNoLoi() {
setupFragment(loiId = null, loiName = null)

// Configured "isAddLoiTask" is of type DROP_PIN. Provide current location for it.
val viewModel = fragment.viewModel.getTaskViewModel(taskId = TASK_ID_0) as DropPinTaskViewModel
viewModel.updateCameraPosition(CameraPosition(TASK_0_RESPONSE))
}

private fun setupFragment(
loiId: String? = LOCATION_OF_INTEREST.id,
loiName: String? = LOCATION_OF_INTEREST_NAME,
shouldLoadFromDraft: Boolean = false,
draftValues: String? = null,
) {
val argsBundle =
fragmentArgs
?: DataCollectionFragmentArgs.Builder(
LOCATION_OF_INTEREST.id,
LOCATION_OF_INTEREST_NAME,
JOB.id,
false,
null,
"",
)
.build()
.toBundle()
DataCollectionFragmentArgs.Builder(
loiId,
loiName,
JOB.id,
shouldLoadFromDraft,
draftValues,
/* currentTaskId */ "",
)
.build()
.toBundle()

launchFragmentWithNavController<DataCollectionFragment>(
argsBundle,
Expand All @@ -356,6 +473,11 @@ class DataCollectionFragmentTest : BaseHiltTest() {
private fun runner() = TaskFragmentRunner(this, fragment)

companion object {
private const val TASK_ID_0 = "0"
const val TASK_0_NAME = "task 0"
private val TASK_0_RESPONSE = Coordinates(10.0, 20.0)
private val TASK_0_VALUE = DropPinTaskData(Point(TASK_0_RESPONSE))

private const val TASK_ID_1 = "1"
const val TASK_1_NAME = "task 1"
private const val TASK_1_RESPONSE = "response 1"
Expand Down Expand Up @@ -393,18 +515,19 @@ class DataCollectionFragmentTest : BaseHiltTest() {

private val TASKS =
listOf(
Task(TASK_ID_1, 0, Task.Type.TEXT, TASK_1_NAME, true),
Task(TASK_ID_0, 0, Task.Type.DROP_PIN, TASK_0_NAME, true, isAddLoiTask = true),
Task(TASK_ID_1, 1, Task.Type.TEXT, TASK_1_NAME, true),
Task(
TASK_ID_2,
1,
2,
Task.Type.MULTIPLE_CHOICE,
TASK_2_NAME,
true,
multipleChoice = TASK_2_MULTIPLE_CHOICE,
),
Task(
TASK_ID_CONDITIONAL,
2,
3,
Task.Type.TEXT,
TASK_CONDITIONAL_NAME,
true,
Expand Down
Loading

0 comments on commit 8618468

Please sign in to comment.