@@ -22,8 +22,13 @@ import com.google.android.ground.R
22
22
import com.google.android.ground.capture
23
23
import com.google.android.ground.domain.usecases.survey.ActivateSurveyUseCase
24
24
import com.google.android.ground.launchFragmentWithNavController
25
+ import com.google.android.ground.model.submission.MultipleChoiceTaskData
25
26
import com.google.android.ground.model.submission.TextTaskData
26
27
import com.google.android.ground.model.submission.ValueDelta
28
+ import com.google.android.ground.model.task.Condition
29
+ import com.google.android.ground.model.task.Expression
30
+ import com.google.android.ground.model.task.MultipleChoice
31
+ import com.google.android.ground.model.task.Option
27
32
import com.google.android.ground.model.task.Task
28
33
import com.google.android.ground.persistence.local.room.converter.SubmissionDeltasConverter
29
34
import com.google.android.ground.repository.SubmissionRepository
@@ -35,6 +40,7 @@ import com.sharedtest.persistence.remote.FakeRemoteDataStore
35
40
import dagger.hilt.android.testing.BindValue
36
41
import dagger.hilt.android.testing.HiltAndroidTest
37
42
import javax.inject.Inject
43
+ import kotlinx.collections.immutable.persistentListOf
38
44
import kotlinx.coroutines.ExperimentalCoroutinesApi
39
45
import kotlinx.coroutines.test.advanceUntilIdle
40
46
import org.junit.Test
@@ -134,7 +140,7 @@ class DataCollectionFragmentTest : BaseHiltTest() {
134
140
runner()
135
141
.inputText(TASK_1_RESPONSE )
136
142
.clickNextButton()
137
- .inputText( TASK_2_RESPONSE )
143
+ .selectMultipleChoiceOption( TASK_2_OPTION_LABEL )
138
144
.clickPreviousButton()
139
145
140
146
// Both deletion and creating happens twice as we do it on every previous/next step
@@ -187,7 +193,7 @@ class DataCollectionFragmentTest : BaseHiltTest() {
187
193
runner()
188
194
.validateTextIsDisplayed(TASK_1_RESPONSE )
189
195
.clickNextButton()
190
- .validateTextIsDisplayed(TASK_2_RESPONSE )
196
+ .validateTextIsDisplayed(TASK_2_OPTION_LABEL )
191
197
}
192
198
193
199
@Test
@@ -197,7 +203,7 @@ class DataCollectionFragmentTest : BaseHiltTest() {
197
203
.clickNextButton()
198
204
.validateTextIsNotDisplayed(TASK_1_NAME )
199
205
.validateTextIsDisplayed(TASK_2_NAME )
200
- .inputText( TASK_2_RESPONSE )
206
+ .selectMultipleChoiceOption( TASK_2_OPTION_LABEL )
201
207
.clickDoneButton() // Click "done" on final task
202
208
203
209
verify(submissionRepository)
@@ -216,6 +222,60 @@ class DataCollectionFragmentTest : BaseHiltTest() {
216
222
verify(submissionRepository, times(1 )).deleteDraftSubmission()
217
223
}
218
224
225
+ @Test
226
+ fun `Clicking done after triggering conditional task saves task data` () = runWithTestDispatcher {
227
+ runner()
228
+ .inputText(TASK_1_RESPONSE )
229
+ .clickNextButton()
230
+ .validateTextIsDisplayed(TASK_2_NAME )
231
+ // Select the option to unhide the conditional task.
232
+ .selectMultipleChoiceOption(TASK_2_OPTION_CONDITIONAL_LABEL )
233
+ // TODO(#2394): Next button should be rendered here.
234
+ .clickDoneButton()
235
+ // Conditional task is rendered.
236
+ .validateTextIsDisplayed(TASK_CONDITIONAL_NAME )
237
+ .inputText(TASK_CONDITIONAL_RESPONSE )
238
+ .clickNextButton()
239
+
240
+ verify(submissionRepository)
241
+ .saveSubmission(eq(SURVEY .id), eq(LOCATION_OF_INTEREST .id), capture(deltaCaptor))
242
+
243
+ // Conditional task data is submitted.
244
+ listOf (TASK_1_VALUE_DELTA , TASK_2_CONDITIONAL_VALUE_DELTA , TASK_CONDITIONAL_VALUE_DELTA )
245
+ .forEach { value -> assertThat(deltaCaptor.value).contains(value) }
246
+ }
247
+
248
+ @Test
249
+ fun `Clicking done after editing conditional task state doesn't save inputted conditional task` () =
250
+ runWithTestDispatcher {
251
+ runner()
252
+ .inputText(TASK_1_RESPONSE )
253
+ .clickNextButton()
254
+ .validateTextIsDisplayed(TASK_2_NAME )
255
+ // Select the option to unhide the conditional task.
256
+ .selectMultipleChoiceOption(TASK_2_OPTION_CONDITIONAL_LABEL )
257
+ // TODO(#2394): Next button should be rendered here.
258
+ .clickDoneButton()
259
+ .validateTextIsDisplayed(TASK_CONDITIONAL_NAME )
260
+ // Input a value, then go back to hide the task again.
261
+ .inputText(TASK_CONDITIONAL_RESPONSE )
262
+ .clickPreviousButton()
263
+ .validateTextIsDisplayed(TASK_2_NAME )
264
+ // Unselect the option to hide the conditional task.
265
+ .selectMultipleChoiceOption(TASK_2_OPTION_CONDITIONAL_LABEL )
266
+ .selectMultipleChoiceOption(TASK_2_OPTION_LABEL )
267
+ .clickDoneButton()
268
+ .validateTextIsNotDisplayed(TASK_CONDITIONAL_NAME )
269
+
270
+ verify(submissionRepository)
271
+ .saveSubmission(eq(SURVEY .id), eq(LOCATION_OF_INTEREST .id), capture(deltaCaptor))
272
+
273
+ // Conditional task data is not submitted.
274
+ listOf (TASK_1_VALUE_DELTA , TASK_2_VALUE_DELTA ).forEach { value ->
275
+ assertThat(deltaCaptor.value).contains(value)
276
+ }
277
+ }
278
+
219
279
private fun setupSubmission () = runWithTestDispatcher {
220
280
whenever(submissionRepository.createSubmission(SURVEY .id, LOCATION_OF_INTEREST .id))
221
281
.thenReturn(SUBMISSION )
@@ -258,14 +318,63 @@ class DataCollectionFragmentTest : BaseHiltTest() {
258
318
259
319
private const val TASK_ID_2 = " 2"
260
320
const val TASK_2_NAME = " task 2"
261
- private const val TASK_2_RESPONSE = " response 2"
262
- private val TASK_2_VALUE = TextTaskData .fromString(TASK_2_RESPONSE )
263
- private val TASK_2_VALUE_DELTA = ValueDelta (TASK_ID_2 , Task .Type .TEXT , TASK_2_VALUE )
321
+ private const val TASK_2_OPTION = " option 1"
322
+ private const val TASK_2_OPTION_LABEL = " Option 1"
323
+ private const val TASK_2_OPTION_CONDITIONAL = " option 2"
324
+ private const val TASK_2_OPTION_CONDITIONAL_LABEL = " Option 2"
325
+ private val TASK_2_MULTIPLE_CHOICE =
326
+ MultipleChoice (
327
+ persistentListOf(
328
+ Option (TASK_2_OPTION , " code1" , TASK_2_OPTION_LABEL ),
329
+ Option (TASK_2_OPTION_CONDITIONAL , " code2" , TASK_2_OPTION_CONDITIONAL_LABEL ),
330
+ ),
331
+ MultipleChoice .Cardinality .SELECT_MULTIPLE ,
332
+ )
333
+ private val TASK_2_VALUE =
334
+ MultipleChoiceTaskData .fromList(TASK_2_MULTIPLE_CHOICE , listOf (TASK_2_OPTION ))
335
+ private val TASK_2_CONDITIONAL_VALUE =
336
+ MultipleChoiceTaskData .fromList(TASK_2_MULTIPLE_CHOICE , listOf (TASK_2_OPTION_CONDITIONAL ))
337
+ private val TASK_2_VALUE_DELTA = ValueDelta (TASK_ID_2 , Task .Type .MULTIPLE_CHOICE , TASK_2_VALUE )
338
+ private val TASK_2_CONDITIONAL_VALUE_DELTA =
339
+ ValueDelta (TASK_ID_2 , Task .Type .MULTIPLE_CHOICE , TASK_2_CONDITIONAL_VALUE )
340
+
341
+ private const val TASK_ID_CONDITIONAL = " conditional"
342
+ const val TASK_CONDITIONAL_NAME = " conditional task"
343
+ private const val TASK_CONDITIONAL_RESPONSE = " conditional response"
344
+ private val TASK_CONDITIONAL_VALUE = TextTaskData .fromString(TASK_CONDITIONAL_RESPONSE )
345
+ private val TASK_CONDITIONAL_VALUE_DELTA =
346
+ ValueDelta (TASK_ID_CONDITIONAL , Task .Type .TEXT , TASK_CONDITIONAL_VALUE )
264
347
265
348
private val TASKS =
266
349
listOf (
267
350
Task (TASK_ID_1 , 0 , Task .Type .TEXT , TASK_1_NAME , true ),
268
- Task (TASK_ID_2 , 1 , Task .Type .TEXT , TASK_2_NAME , true ),
351
+ Task (
352
+ TASK_ID_2 ,
353
+ 1 ,
354
+ Task .Type .MULTIPLE_CHOICE ,
355
+ TASK_2_NAME ,
356
+ true ,
357
+ multipleChoice = TASK_2_MULTIPLE_CHOICE ,
358
+ ),
359
+ Task (
360
+ TASK_ID_CONDITIONAL ,
361
+ 2 ,
362
+ Task .Type .TEXT ,
363
+ TASK_CONDITIONAL_NAME ,
364
+ true ,
365
+ condition =
366
+ Condition (
367
+ Condition .MatchType .MATCH_ANY ,
368
+ expressions =
369
+ listOf (
370
+ Expression (
371
+ Expression .ExpressionType .ANY_OF_SELECTED ,
372
+ TASK_ID_2 ,
373
+ optionIds = setOf (TASK_2_OPTION_CONDITIONAL ),
374
+ )
375
+ ),
376
+ ),
377
+ ),
269
378
)
270
379
271
380
private val JOB = FakeData .JOB .copy(tasks = TASKS .associateBy { it.id })
0 commit comments