From eda29a2d34606ca261548720dfb558dbb230aad8 Mon Sep 17 00:00:00 2001 From: Shobhit Agarwal Date: Sun, 5 Jan 2025 20:10:15 +0530 Subject: [PATCH 1/8] Overload setupFragment for loading from draft --- .../DataCollectionFragmentTest.kt | 42 +++++++------------ 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt b/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt index 6a7e6287a4..3404ee1763 100644 --- a/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt +++ b/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt @@ -16,7 +16,6 @@ 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 @@ -167,19 +166,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) @@ -331,19 +318,22 @@ class DataCollectionFragmentTest : BaseHiltTest() { advanceUntilIdle() } - private fun setupFragment(fragmentArgs: Bundle? = null) { + private fun setupFragmentWithDraft(expectedValues: List) { + setupFragment(true, SubmissionDeltasConverter.toString(expectedValues)) + } + + private fun setupFragment(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( + LOCATION_OF_INTEREST.id, + LOCATION_OF_INTEREST_NAME, + JOB.id, + shouldLoadFromDraft, + draftValues, + /* currentTaskId */ "", + ) + .build() + .toBundle() launchFragmentWithNavController( argsBundle, From 018b0f4f075df63285e06a3e12cd11488f5e91b1 Mon Sep 17 00:00:00 2001 From: Shobhit Agarwal Date: Sun, 5 Jan 2025 20:38:59 +0530 Subject: [PATCH 2/8] Add tests for Job/LOI name display status when LOI is missing --- .../DataCollectionFragmentTest.kt | 49 ++++++++++++++++--- .../ui/datacollection/TaskFragmentRunner.kt | 6 +++ 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt b/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt index 3404ee1763..c93997d4a1 100644 --- a/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt +++ b/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt @@ -80,6 +80,15 @@ 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() @@ -87,6 +96,13 @@ class DataCollectionFragmentTest : BaseHiltTest() { 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() @@ -319,14 +335,26 @@ class DataCollectionFragmentTest : BaseHiltTest() { } private fun setupFragmentWithDraft(expectedValues: List) { - setupFragment(true, SubmissionDeltasConverter.toString(expectedValues)) + setupFragment( + shouldLoadFromDraft = true, + draftValues = SubmissionDeltasConverter.toString(expectedValues), + ) + } + + private fun setupFragmentWithNoLoi() { + setupFragment(loiId = null, loiName = null) } - private fun setupFragment(shouldLoadFromDraft: Boolean = false, draftValues: String? = null) { + private fun setupFragment( + loiId: String? = LOCATION_OF_INTEREST.id, + loiName: String? = LOCATION_OF_INTEREST_NAME, + shouldLoadFromDraft: Boolean = false, + draftValues: String? = null, + ) { val argsBundle = DataCollectionFragmentArgs.Builder( - LOCATION_OF_INTEREST.id, - LOCATION_OF_INTEREST_NAME, + loiId, + loiName, JOB.id, shouldLoadFromDraft, draftValues, @@ -346,6 +374,12 @@ 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 const val TASK_0_RESPONSE = "response 0" + private val TASK_0_VALUE = TextTaskData.fromString(TASK_0_RESPONSE) + private val TASK_0_VALUE_DELTA = ValueDelta(TASK_ID_0, Task.Type.TEXT, TASK_0_VALUE) + private const val TASK_ID_1 = "1" const val TASK_1_NAME = "task 1" private const val TASK_1_RESPONSE = "response 1" @@ -383,10 +417,11 @@ 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.TEXT, 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, @@ -394,7 +429,7 @@ class DataCollectionFragmentTest : BaseHiltTest() { ), Task( TASK_ID_CONDITIONAL, - 2, + 3, Task.Type.TEXT, TASK_CONDITIONAL_NAME, true, diff --git a/ground/src/test/java/com/google/android/ground/ui/datacollection/TaskFragmentRunner.kt b/ground/src/test/java/com/google/android/ground/ui/datacollection/TaskFragmentRunner.kt index 1a2333ecb5..cb8e5ffe2e 100644 --- a/ground/src/test/java/com/google/android/ground/ui/datacollection/TaskFragmentRunner.kt +++ b/ground/src/test/java/com/google/android/ground/ui/datacollection/TaskFragmentRunner.kt @@ -35,6 +35,7 @@ import androidx.compose.ui.test.performScrollToNode import androidx.compose.ui.test.performTextClearance import androidx.compose.ui.test.performTextInput import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.assertion.ViewAssertions.doesNotExist import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withId @@ -159,6 +160,11 @@ class TaskFragmentRunner( return this } + internal fun validateTextDoesNotExist(text: String): TaskFragmentRunner { + onView(withText(text)).check(doesNotExist()) + return this + } + internal fun assertInfoCardHidden(): TaskFragmentRunner { onView(withId(R.id.infoCard)).check(matches(not(isDisplayed()))) return this From 98d47a005bebceaf93fa75356b0205950bbc7bb0 Mon Sep 17 00:00:00 2001 From: Shobhit Agarwal Date: Sun, 5 Jan 2025 21:10:14 +0530 Subject: [PATCH 3/8] Add tests for LoiNameDialog --- .../components/LoiNameDialog.kt | 10 ++++- .../DataCollectionFragmentTest.kt | 45 +++++++++++++++++++ .../ui/datacollection/TaskFragmentRunner.kt | 28 ++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/ground/src/main/java/com/google/android/ground/ui/datacollection/components/LoiNameDialog.kt b/ground/src/main/java/com/google/android/ground/ui/datacollection/components/LoiNameDialog.kt index 39187cc7dc..ac4ff670ee 100644 --- a/ground/src/main/java/com/google/android/ground/ui/datacollection/components/LoiNameDialog.kt +++ b/ground/src/main/java/com/google/android/ground/ui/datacollection/components/LoiNameDialog.kt @@ -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, @@ -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 = { diff --git a/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt b/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt index c93997d4a1..4a23e61eb7 100644 --- a/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt +++ b/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt @@ -124,6 +124,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().inputText(TASK_0_RESPONSE).clickNextButton().assertLoiNameDialogIsDisplayed() + } + + @Test + fun `Entering loi name enables the save button in LoiNameDialog`() { + setupFragmentWithNoLoi() + + runner() + .inputText(TASK_0_RESPONSE) + .clickNextButton() + .assertButtonIsDisabled("Save") + .inputLoiName("Custom Loi Name") + .assertButtonIsEnabled("Save") + } + + @Test + fun `Clicking cancel hides the LoiNameDialog`() { + setupFragmentWithNoLoi() + + runner() + .inputText(TASK_0_RESPONSE) + .clickNextButton() + .clickButton("Cancel") + .assertLoiNameDialogIsNotDisplayed() + } + + @Test + fun `Clicking save in LoiNameDialog proceeds to next task`() { + setupFragmentWithNoLoi() + + runner() + .inputText(TASK_0_RESPONSE) + .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() diff --git a/ground/src/test/java/com/google/android/ground/ui/datacollection/TaskFragmentRunner.kt b/ground/src/test/java/com/google/android/ground/ui/datacollection/TaskFragmentRunner.kt index cb8e5ffe2e..c2555e97fa 100644 --- a/ground/src/test/java/com/google/android/ground/ui/datacollection/TaskFragmentRunner.kt +++ b/ground/src/test/java/com/google/android/ground/ui/datacollection/TaskFragmentRunner.kt @@ -26,7 +26,9 @@ import androidx.compose.ui.test.hasAnySibling import androidx.compose.ui.test.hasContentDescription import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.isDisplayed import androidx.compose.ui.test.isEnabled +import androidx.compose.ui.test.isNotDisplayed import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText @@ -42,6 +44,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText import com.google.android.ground.BaseHiltTest import com.google.android.ground.R +import com.google.android.ground.ui.datacollection.components.LOI_NAME_TEXT_FIELD_TEST_TAG import com.google.android.ground.ui.datacollection.tasks.multiplechoice.MULTIPLE_CHOICE_LIST_TEST_TAG import com.google.android.ground.ui.datacollection.tasks.multiplechoice.OTHER_INPUT_TEXT_TEST_TAG import com.google.android.ground.ui.datacollection.tasks.multiplechoice.SELECT_MULTIPLE_RADIO_TEST_TAG @@ -234,6 +237,31 @@ class TaskFragmentRunner( return this } + internal fun assertLoiNameDialogIsDisplayed(): TaskFragmentRunner { + with(baseHiltTest.composeTestRule) { + val resources = fragment?.resources ?: error("Fragment not found") + onNodeWithText(resources.getString(R.string.loi_name_dialog_title)).isDisplayed() + onNodeWithText(resources.getString(R.string.loi_name_dialog_body)).isDisplayed() + } + return this + } + + internal fun assertLoiNameDialogIsNotDisplayed(): TaskFragmentRunner { + with(baseHiltTest.composeTestRule) { + val resources = fragment?.resources ?: error("Fragment not found") + onNodeWithText(resources.getString(R.string.loi_name_dialog_title)).isNotDisplayed() + onNodeWithText(resources.getString(R.string.loi_name_dialog_body)).isNotDisplayed() + } + return this + } + + internal fun inputLoiName(loiName: String): TaskFragmentRunner { + baseHiltTest.composeTestRule + .onNodeWithTag(LOI_NAME_TEXT_FIELD_TEST_TAG) + .performTextInput(loiName) + return this + } + @OptIn(ExperimentalCoroutinesApi::class) private fun waitUntilDone(testBody: suspend () -> Unit) { baseHiltTest.runWithTestDispatcher { From 4f88613afc7319636c6ea3ab5bf69d7b6706dc2d Mon Sep 17 00:00:00 2001 From: Shobhit Agarwal Date: Sun, 5 Jan 2025 22:55:11 +0530 Subject: [PATCH 4/8] Add test for LOI and Submission saving add AddLoiTask flow --- .../usecases/submission/SubmitDataUseCase.kt | 1 + .../tasks/point/DropPinTaskFragment.kt | 1 + .../tasks/point/DropPinTaskViewModel.kt | 2 +- .../DataCollectionFragmentTest.kt | 79 ++++++++++++++++--- 4 files changed, 69 insertions(+), 14 deletions(-) diff --git a/ground/src/main/java/com/google/android/ground/domain/usecases/submission/SubmitDataUseCase.kt b/ground/src/main/java/com/google/android/ground/domain/usecases/submission/SubmitDataUseCase.kt index cf57ab9994..58c90723c9 100644 --- a/ground/src/main/java/com/google/android/ground/domain/usecases/submission/SubmitDataUseCase.kt +++ b/ground/src/main/java/com/google/android/ground/domain/usecases/submission/SubmitDataUseCase.kt @@ -68,6 +68,7 @@ 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. if (addLoiValue !is GeometryTaskData) error("Invalid add LOI task response") return locationOfInterestRepository.saveLoi( addLoiValue.geometry, diff --git a/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/point/DropPinTaskFragment.kt b/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/point/DropPinTaskFragment.kt index a13344faff..677e585941 100644 --- a/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/point/DropPinTaskFragment.kt +++ b/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/point/DropPinTaskFragment.kt @@ -62,6 +62,7 @@ class DropPinTaskFragment @Inject constructor() : AbstractTaskFragment button.showIfTrue(value.isNullOrEmpty()) } diff --git a/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/point/DropPinTaskViewModel.kt b/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/point/DropPinTaskViewModel.kt index 4a31ab29ce..3ff75f768b 100644 --- a/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/point/DropPinTaskViewModel.kt +++ b/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/point/DropPinTaskViewModel.kt @@ -62,7 +62,7 @@ constructor( features.postValue(setOf()) } - fun updateResponse(point: Point) { + private fun updateResponse(point: Point) { setValue(DropPinTaskData(point)) dropMarker(point) } diff --git a/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt b/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt index 4a23e61eb7..8a5e6c187a 100644 --- a/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt +++ b/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt @@ -20,9 +20,12 @@ 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 @@ -32,26 +35,32 @@ 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 import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.ExperimentalCoroutinesApi 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 +import java.util.Date +import javax.inject.Inject @OptIn(ExperimentalCoroutinesApi::class) @HiltAndroidTest @@ -60,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 { @@ -128,7 +140,7 @@ class DataCollectionFragmentTest : BaseHiltTest() { fun `Next button displays the LoiNameDialog when task has value when LOI is missing`() { setupFragmentWithNoLoi() - runner().inputText(TASK_0_RESPONSE).clickNextButton().assertLoiNameDialogIsDisplayed() + runner().clickButton("Drop pin").clickNextButton().assertLoiNameDialogIsDisplayed() } @Test @@ -136,7 +148,7 @@ class DataCollectionFragmentTest : BaseHiltTest() { setupFragmentWithNoLoi() runner() - .inputText(TASK_0_RESPONSE) + .clickButton("Drop pin") .clickNextButton() .assertButtonIsDisabled("Save") .inputLoiName("Custom Loi Name") @@ -148,7 +160,7 @@ class DataCollectionFragmentTest : BaseHiltTest() { setupFragmentWithNoLoi() runner() - .inputText(TASK_0_RESPONSE) + .clickButton("Drop pin") .clickNextButton() .clickButton("Cancel") .assertLoiNameDialogIsNotDisplayed() @@ -159,7 +171,7 @@ class DataCollectionFragmentTest : BaseHiltTest() { setupFragmentWithNoLoi() runner() - .inputText(TASK_0_RESPONSE) + .clickButton("Drop pin") .clickNextButton() .inputLoiName("Custom Loi Name") .clickButton("Save") @@ -250,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 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 { @@ -313,10 +349,24 @@ class DataCollectionFragmentTest : BaseHiltTest() { assertSubmissionSaved(listOf(TASK_1_VALUE_DELTA, TASK_2_VALUE_DELTA)) } - private suspend fun assertSubmissionSaved(valueDeltas: List) { - 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, + loiId: String = LOCATION_OF_INTEREST.id, + ) { + assertNoDraftSaved() // Exactly 1 submission should be saved. assertThat(submissionRepository.getPendingCreateCount(loiId)).isEqualTo(1) @@ -388,6 +438,10 @@ class DataCollectionFragmentTest : BaseHiltTest() { 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( @@ -421,9 +475,8 @@ class DataCollectionFragmentTest : BaseHiltTest() { companion object { private const val TASK_ID_0 = "0" const val TASK_0_NAME = "task 0" - private const val TASK_0_RESPONSE = "response 0" - private val TASK_0_VALUE = TextTaskData.fromString(TASK_0_RESPONSE) - private val TASK_0_VALUE_DELTA = ValueDelta(TASK_ID_0, Task.Type.TEXT, TASK_0_VALUE) + 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" @@ -462,7 +515,7 @@ class DataCollectionFragmentTest : BaseHiltTest() { private val TASKS = listOf( - Task(TASK_ID_0, 0, Task.Type.TEXT, TASK_0_NAME, true, isAddLoiTask = 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, From eaa0bd7212023a1550f5653cb71e0bf34a1b5955 Mon Sep 17 00:00:00 2001 From: Shobhit Agarwal Date: Sun, 5 Jan 2025 22:55:24 +0530 Subject: [PATCH 5/8] Fix import order --- .../ground/ui/datacollection/DataCollectionFragmentTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt b/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt index 8a5e6c187a..9d2f8cddfb 100644 --- a/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt +++ b/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt @@ -50,6 +50,8 @@ 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 import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking @@ -59,8 +61,6 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.robolectric.RobolectricTestRunner import org.robolectric.shadows.ShadowToast -import java.util.Date -import javax.inject.Inject @OptIn(ExperimentalCoroutinesApi::class) @HiltAndroidTest From 2b93523841f0f663c20e5bf894f01105ae202c2d Mon Sep 17 00:00:00 2001 From: shobhitagarwal1612 Date: Sun, 5 Jan 2025 17:28:38 +0000 Subject: [PATCH 6/8] Automatically added GitHub issue links to TODOs --- .../ground/domain/usecases/submission/SubmitDataUseCase.kt | 1 + .../ground/ui/datacollection/tasks/point/DropPinTaskFragment.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/ground/src/main/java/com/google/android/ground/domain/usecases/submission/SubmitDataUseCase.kt b/ground/src/main/java/com/google/android/ground/domain/usecases/submission/SubmitDataUseCase.kt index 58c90723c9..52f8d5ba97 100644 --- a/ground/src/main/java/com/google/android/ground/domain/usecases/submission/SubmitDataUseCase.kt +++ b/ground/src/main/java/com/google/android/ground/domain/usecases/submission/SubmitDataUseCase.kt @@ -69,6 +69,7 @@ constructor( 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, diff --git a/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/point/DropPinTaskFragment.kt b/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/point/DropPinTaskFragment.kt index 677e585941..f7c56071ea 100644 --- a/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/point/DropPinTaskFragment.kt +++ b/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/point/DropPinTaskFragment.kt @@ -63,6 +63,7 @@ class DropPinTaskFragment @Inject constructor() : AbstractTaskFragment button.showIfTrue(value.isNullOrEmpty()) } From 682ff3996e6b03c86b9c84e9b421a7e3426a4ea6 Mon Sep 17 00:00:00 2001 From: Shobhit Agarwal Date: Sun, 5 Jan 2025 23:00:30 +0530 Subject: [PATCH 7/8] Fix test name --- .../ground/ui/datacollection/DataCollectionFragmentTest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt b/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt index 9d2f8cddfb..b147f29ef3 100644 --- a/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt +++ b/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt @@ -50,8 +50,6 @@ 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 import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking @@ -61,6 +59,8 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.robolectric.RobolectricTestRunner import org.robolectric.shadows.ShadowToast +import java.util.Date +import javax.inject.Inject @OptIn(ExperimentalCoroutinesApi::class) @HiltAndroidTest @@ -263,7 +263,7 @@ class DataCollectionFragmentTest : BaseHiltTest() { } @Test - fun `Clicking done on final task saves the submission when LOI is not provided`() = + fun `Clicking done on final task saves the submission and LOI when LOI is not provided`() = runWithTestDispatcher { setupFragmentWithNoLoi() From a6679012f42dfd571747825645d5109465dbbb23 Mon Sep 17 00:00:00 2001 From: Shobhit Agarwal Date: Sun, 5 Jan 2025 23:05:45 +0530 Subject: [PATCH 8/8] Fix import order --- .../ground/ui/datacollection/DataCollectionFragmentTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt b/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt index b147f29ef3..eb26f38faa 100644 --- a/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt +++ b/ground/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt @@ -50,6 +50,8 @@ 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 import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking @@ -59,8 +61,6 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.robolectric.RobolectricTestRunner import org.robolectric.shadows.ShadowToast -import java.util.Date -import javax.inject.Inject @OptIn(ExperimentalCoroutinesApi::class) @HiltAndroidTest