1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  *
17  */
18 
19 /**
20  * Copyright (C) 2022 The Android Open Source Project
21  *
22  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
23  * in compliance with the License. You may obtain a copy of the License at
24  *
25  *      http://www.apache.org/licenses/LICENSE-2.0
26  *
27  * Unless required by applicable law or agreed to in writing, software distributed under the License
28  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
29  * or implied. See the License for the specific language governing permissions and limitations under
30  * the License.
31  */
32 package com.android.healthconnect.controller.tests.entrydetails
33 
34 import android.content.Context
35 import android.health.connect.datatypes.ExerciseCompletionGoal
36 import android.health.connect.datatypes.ExercisePerformanceGoal
37 import android.health.connect.datatypes.ExerciseRoute
38 import android.health.connect.datatypes.ExerciseSegmentType
39 import android.health.connect.datatypes.PlannedExerciseStep
40 import android.health.connect.datatypes.units.Length
41 import android.health.connect.datatypes.units.Velocity
42 import androidx.lifecycle.MutableLiveData
43 import androidx.test.espresso.Espresso.onView
44 import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
45 import androidx.test.espresso.assertion.ViewAssertions.matches
46 import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
47 import androidx.test.espresso.matcher.ViewMatchers.withId
48 import androidx.test.espresso.matcher.ViewMatchers.withText
49 import androidx.test.platform.app.InstrumentationRegistry
50 import com.android.healthconnect.controller.R
51 import com.android.healthconnect.controller.data.entries.FormattedEntry
52 import com.android.healthconnect.controller.data.entries.FormattedEntry.ExercisePerformanceGoalEntry
53 import com.android.healthconnect.controller.data.entries.FormattedEntry.ExerciseSessionEntry
54 import com.android.healthconnect.controller.data.entries.FormattedEntry.FormattedSectionContent
55 import com.android.healthconnect.controller.data.entries.FormattedEntry.PlannedExerciseBlockEntry
56 import com.android.healthconnect.controller.data.entries.FormattedEntry.PlannedExerciseSessionEntry
57 import com.android.healthconnect.controller.data.entries.FormattedEntry.PlannedExerciseStepEntry
58 import com.android.healthconnect.controller.data.entries.FormattedEntry.SeriesDataEntry
59 import com.android.healthconnect.controller.data.entries.FormattedEntry.SessionHeader
60 import com.android.healthconnect.controller.data.entries.FormattedEntry.SleepSessionEntry
61 import com.android.healthconnect.controller.entrydetails.DataEntryDetailsFragment
62 import com.android.healthconnect.controller.entrydetails.DataEntryDetailsViewModel
63 import com.android.healthconnect.controller.entrydetails.DataEntryDetailsViewModel.DateEntryFragmentState.Loading
64 import com.android.healthconnect.controller.entrydetails.DataEntryDetailsViewModel.DateEntryFragmentState.LoadingFailed
65 import com.android.healthconnect.controller.entrydetails.DataEntryDetailsViewModel.DateEntryFragmentState.WithData
66 import com.android.healthconnect.controller.permissions.data.HealthPermissionType.EXERCISE
67 import com.android.healthconnect.controller.permissions.data.HealthPermissionType.HEART_RATE
68 import com.android.healthconnect.controller.permissions.data.HealthPermissionType.PLANNED_EXERCISE
69 import com.android.healthconnect.controller.permissions.data.HealthPermissionType.SKIN_TEMPERATURE
70 import com.android.healthconnect.controller.permissions.data.HealthPermissionType.SLEEP
71 import com.android.healthconnect.controller.shared.DataType
72 import com.android.healthconnect.controller.tests.utils.TestData.WARSAW_ROUTE
73 import com.android.healthconnect.controller.tests.utils.getPlannedExerciseBlock
74 import com.android.healthconnect.controller.tests.utils.getPlannedExerciseStep
75 import com.android.healthconnect.controller.tests.utils.launchFragment
76 import com.android.healthconnect.controller.tests.utils.setLocale
77 import com.android.healthconnect.controller.utils.logging.DataEntriesElement
78 import com.android.healthconnect.controller.utils.logging.EntryDetailsElement
79 import com.android.healthconnect.controller.utils.logging.HealthConnectLogger
80 import com.android.healthconnect.controller.utils.logging.PageName
81 import dagger.hilt.android.testing.BindValue
82 import dagger.hilt.android.testing.HiltAndroidRule
83 import dagger.hilt.android.testing.HiltAndroidTest
84 import java.time.ZoneId
85 import java.util.Locale
86 import java.util.TimeZone
87 import org.hamcrest.Matchers.not
88 import org.junit.After
89 import org.junit.Before
90 import org.junit.Rule
91 import org.junit.Test
92 import org.mockito.Mockito.mock
93 import org.mockito.Mockito.`when` as whenever
94 import org.mockito.kotlin.atLeast
95 import org.mockito.kotlin.atMost
96 import org.mockito.kotlin.reset
97 import org.mockito.kotlin.times
98 import org.mockito.kotlin.verify
99 
100 @HiltAndroidTest
101 class DataEntryDetailsFragmentTest {
102     @get:Rule val hiltRule = HiltAndroidRule(this)
103 
104     @BindValue
105     val viewModel: DataEntryDetailsViewModel = mock(DataEntryDetailsViewModel::class.java)
106     private lateinit var context: Context
107     @BindValue val healthConnectLogger: HealthConnectLogger = org.mockito.kotlin.mock()
108 
109     @Before
setupnull110     fun setup() {
111         hiltRule.inject()
112         context = InstrumentationRegistry.getInstrumentation().context
113         context.setLocale(Locale.UK)
114         TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("UTC")))
115     }
116 
117     @After
tearDownnull118     fun tearDown() {
119         reset(healthConnectLogger)
120     }
121 
122     @Test
dataEntriesDetailsInit_showsFragmentnull123     fun dataEntriesDetailsInit_showsFragment() {
124         whenever(viewModel.sessionData).thenReturn(MutableLiveData(WithData(emptyList())))
125 
126         launchFragment<DataEntryDetailsFragment>(
127             DataEntryDetailsFragment.createBundle(
128                 permissionType = SLEEP, entryId = "1", showDataOrigin = true))
129 
130         onView(withId(R.id.loading)).check(matches(not(isDisplayed())))
131     }
132 
133     @Test
dataEntriesDetailsInit_error_showsErrornull134     fun dataEntriesDetailsInit_error_showsError() {
135         whenever(viewModel.sessionData).thenReturn(MutableLiveData(LoadingFailed))
136 
137         launchFragment<DataEntryDetailsFragment>(
138             DataEntryDetailsFragment.createBundle(
139                 permissionType = SLEEP, entryId = "1", showDataOrigin = true))
140 
141         onView(withId(R.id.error_view)).check(matches(isDisplayed()))
142     }
143 
144     @Test
dataEntriesDetailsInit_loading_showsLoadingnull145     fun dataEntriesDetailsInit_loading_showsLoading() {
146         whenever(viewModel.sessionData).thenReturn(MutableLiveData(Loading))
147 
148         launchFragment<DataEntryDetailsFragment>(
149             DataEntryDetailsFragment.createBundle(
150                 permissionType = SLEEP, entryId = "1", showDataOrigin = true))
151 
152         onView(withId(R.id.loading)).check(matches(isDisplayed()))
153     }
154 
155     @Test
dataEntriesDetailsInit_withData_showsItemnull156     fun dataEntriesDetailsInit_withData_showsItem() {
157         whenever(viewModel.sessionData)
158             .thenReturn(
159                 MutableLiveData(
160                     WithData(
161                         listOf(
162                             SleepSessionEntry(
163                                 uuid = "1",
164                                 header = "07:06 • TEST_APP_NAME",
165                                 headerA11y = "07:06 • TEST_APP_NAME",
166                                 title = "12 hour sleeping",
167                                 titleA11y = "12 hour sleeping",
168                                 dataType = DataType.SLEEP,
169                                 notes = "notes")))))
170 
171         launchFragment<DataEntryDetailsFragment>(
172             DataEntryDetailsFragment.createBundle(
173                 permissionType = SLEEP, entryId = "1", showDataOrigin = true))
174 
175         onView(withText("07:06 • TEST_APP_NAME")).check(matches(isDisplayed()))
176         onView(withText("12 hour sleeping")).check(matches(isDisplayed()))
177         onView(withText("notes")).check(matches(isDisplayed()))
178 
179         verify(healthConnectLogger, atLeast(1)).setPageId(PageName.ENTRY_DETAILS_PAGE)
180         verify(healthConnectLogger).logPageImpression()
181         verify(healthConnectLogger).logImpression(DataEntriesElement.SLEEP_SESSION_ENTRY_BUTTON)
182     }
183 
184     @Test
dataEntriesDetailsInit_withDetails_showsItem_showsDetailsnull185     fun dataEntriesDetailsInit_withDetails_showsItem_showsDetails() {
186         val list = buildList {
187             add(getFormattedSleepSession())
188             addAll(getSleepStages())
189         }
190         whenever(viewModel.sessionData).thenReturn(MutableLiveData(WithData(list)))
191 
192         launchFragment<DataEntryDetailsFragment>(
193             DataEntryDetailsFragment.createBundle(
194                 permissionType = SLEEP, entryId = "1", showDataOrigin = true))
195 
196         onView(withText("12 hour sleeping")).check(matches(isDisplayed()))
197         onView(withText("6 hour light sleeping")).check(matches(isDisplayed()))
198         onView(withText("6 hour deep sleeping")).check(matches(isDisplayed()))
199     }
200 
201     @Test
dataEntriesDetailsInit_withHeartRate_showsItem_showsDetailsnull202     fun dataEntriesDetailsInit_withHeartRate_showsItem_showsDetails() {
203         val list = buildList { add(getFormattedSeriesData()) }
204 
205         whenever(viewModel.sessionData).thenReturn(MutableLiveData(WithData(list)))
206 
207         launchFragment<DataEntryDetailsFragment>(
208             DataEntryDetailsFragment.createBundle(
209                 permissionType = HEART_RATE, entryId = "1", showDataOrigin = true))
210 
211         onView(withText("07:06 - 8:06 • TEST_APP_NAME")).check(matches(isDisplayed()))
212         onView(withText("100 bpm")).check(matches(isDisplayed()))
213         verify(healthConnectLogger).logImpression(DataEntriesElement.DATA_ENTRY_VIEW)
214     }
215 
216     @Test
dataEntriesInit_withSkinTemperature_showsItem_showsDetailsnull217     fun dataEntriesInit_withSkinTemperature_showsItem_showsDetails() {
218         val list = buildList {
219             add(getSkinTemperatureEntry())
220             addAll(getSkinTemperatureDeltas(includeBaseline = true, includeLocation = true))
221         }
222         whenever(viewModel.sessionData).thenReturn(MutableLiveData(WithData(list)))
223 
224         launchFragment<DataEntryDetailsFragment>(
225             DataEntryDetailsFragment.createBundle(
226                 permissionType = SKIN_TEMPERATURE, entryId = "1", showDataOrigin = true))
227 
228         onView(withText("+0.5℃ (avg variation)")).check(matches(isDisplayed()))
229         onView(withText("Measurement location")).check(matches(isDisplayed()))
230         onView(withText("Toe")).check(matches(isDisplayed()))
231         onView(withText("Baseline")).check(matches(isDisplayed()))
232         onView(withText("25℃")).check(matches(isDisplayed()))
233         onView(withText("Variation from baseline")).check(matches(isDisplayed()))
234         verify(healthConnectLogger, times(2))
235             .logImpression((EntryDetailsElement.REVERSE_SESSION_DETAIL_ENTRY_VIEW))
236         verify(healthConnectLogger)
237             .logImpression((EntryDetailsElement.FORMATTED_SECTION_TITLE_VIEW))
238     }
239 
240     @Test
dataEntriesInit_withSkinTemperature_locationUnknown_hidesLocationDetailnull241     fun dataEntriesInit_withSkinTemperature_locationUnknown_hidesLocationDetail() {
242         val list = buildList {
243             add(getSkinTemperatureEntry())
244             addAll(getSkinTemperatureDeltas(includeBaseline = true, includeLocation = false))
245         }
246         whenever(viewModel.sessionData).thenReturn(MutableLiveData(WithData(list)))
247 
248         launchFragment<DataEntryDetailsFragment>(
249             DataEntryDetailsFragment.createBundle(
250                 permissionType = SKIN_TEMPERATURE, entryId = "1", showDataOrigin = true))
251 
252         onView(withText("+0.5℃ (avg variation)")).check(matches(isDisplayed()))
253         onView(withText("Measurement location")).check(doesNotExist())
254         onView(withText("Toe")).check(doesNotExist())
255         onView(withText("Baseline")).check(matches(isDisplayed()))
256         onView(withText("25℃")).check(matches(isDisplayed()))
257         onView(withText("Variation from baseline")).check(matches(isDisplayed()))
258         verify(healthConnectLogger, atMost(1))
259             .logImpression((EntryDetailsElement.REVERSE_SESSION_DETAIL_ENTRY_VIEW))
260         verify(healthConnectLogger)
261             .logImpression((EntryDetailsElement.FORMATTED_SECTION_TITLE_VIEW))
262     }
263 
264     @Test
dataEntriesInit_withSkinTemperature_baselineUnknown_hidesBaselineDetailnull265     fun dataEntriesInit_withSkinTemperature_baselineUnknown_hidesBaselineDetail() {
266         val list = buildList {
267             add(getSkinTemperatureEntry())
268             addAll(getSkinTemperatureDeltas(includeBaseline = false, includeLocation = true))
269         }
270         whenever(viewModel.sessionData).thenReturn(MutableLiveData(WithData(list)))
271 
272         launchFragment<DataEntryDetailsFragment>(
273             DataEntryDetailsFragment.createBundle(
274                 permissionType = SKIN_TEMPERATURE, entryId = "1", showDataOrigin = true))
275 
276         onView(withText("+0.5℃ (avg variation)")).check(matches(isDisplayed()))
277         onView(withText("Measurement location")).check(matches(isDisplayed()))
278         onView(withText("Toe")).check(matches(isDisplayed()))
279         onView(withText("Baseline")).check(doesNotExist())
280         onView(withText("25℃")).check(doesNotExist())
281         onView(withText("Variation from baseline")).check(matches(isDisplayed()))
282         verify(healthConnectLogger, atMost(1))
283             .logImpression((EntryDetailsElement.REVERSE_SESSION_DETAIL_ENTRY_VIEW))
284         verify(healthConnectLogger)
285             .logImpression((EntryDetailsElement.FORMATTED_SECTION_TITLE_VIEW))
286     }
287 
288     @Test
dataEntriesDetailsInit_withRouteDetails_showsMapViewnull289     fun dataEntriesDetailsInit_withRouteDetails_showsMapView() {
290         val list = buildList { add(getFormattedExerciseSession(showSession = true)) }
291         whenever(viewModel.sessionData).thenReturn(MutableLiveData(WithData(list)))
292         launchFragment<DataEntryDetailsFragment>(
293             DataEntryDetailsFragment.createBundle(
294                 permissionType = EXERCISE, entryId = "1", showDataOrigin = true))
295 
296         onView(withText("12 hour running")).check(matches(isDisplayed()))
297         onView(withId(R.id.map_view)).check(matches(isDisplayed()))
298         verify(healthConnectLogger).logImpression(DataEntriesElement.EXERCISE_SESSION_ENTRY_BUTTON)
299         verify(healthConnectLogger).logImpression(DataEntriesElement.EXERCISE_SESSION_MAP_VIEW)
300     }
301 
302     @Test
dataEntriesDetailsInit_noRouteDetails_hidesMapViewnull303     fun dataEntriesDetailsInit_noRouteDetails_hidesMapView() {
304         val list = buildList { add(getFormattedExerciseSession(showSession = false)) }
305         whenever(viewModel.sessionData).thenReturn(MutableLiveData(WithData(list)))
306         launchFragment<DataEntryDetailsFragment>(
307             DataEntryDetailsFragment.createBundle(
308                 permissionType = EXERCISE, entryId = "1", showDataOrigin = true))
309 
310         onView(withText("12 hour running")).check(matches(isDisplayed()))
311         onView(withId(R.id.map_view)).check(matches(not(isDisplayed())))
312     }
313 
314     @Test
dataEntriesDetailsInit_withPlannedExerciseSession_showsItem_showsDetailsnull315     fun dataEntriesDetailsInit_withPlannedExerciseSession_showsItem_showsDetails() {
316         val list = buildList {
317             add(
318                 PlannedExerciseSessionEntry(
319                     uuid = "test_id",
320                     header = "07:06 - 08:06 • Health Connect test app",
321                     headerA11y = "from 07:06 to 08:06 • Health Connect test app",
322                     title = "Running • Morning Run",
323                     titleA11y = "Running • Morning Run",
324                     dataType = DataType.PLANNED_EXERCISE,
325                     notes = "Morning quick run by the park"))
326             add(
327                 PlannedExerciseBlockEntry(
328                     block =
329                         getPlannedExerciseBlock(
330                             1,
331                             "Warm up",
332                             listOf(
333                                 getPlannedExerciseStep(
334                                     exerciseSegmentType =
335                                         ExerciseSegmentType.EXERCISE_SEGMENT_TYPE_RUNNING,
336                                     completionGoal =
337                                         ExerciseCompletionGoal.DistanceGoal(
338                                             Length.fromMeters(1000.0)),
339                                     performanceGoals =
340                                         listOf(
341                                             ExercisePerformanceGoal.HeartRateGoal(100, 150),
342                                             ExercisePerformanceGoal.SpeedGoal(
343                                                 Velocity.fromMetersPerSecond(25.0),
344                                                 Velocity.fromMetersPerSecond(15.0)))))),
345                     title = "Warm up: 1 time",
346                     titleA11y = "Warm up 1 time"))
347             add(SessionHeader("Notes"))
348             add(FormattedSectionContent("Morning quick run by the park"))
349             add(
350                 PlannedExerciseStepEntry(
351                     step =
352                         getPlannedExerciseStepBuilder()
353                             .setPerformanceGoals(
354                                 listOf(
355                                     ExercisePerformanceGoal.HeartRateGoal(150, 180),
356                                     ExercisePerformanceGoal.SpeedGoal(
357                                         Velocity.fromMetersPerSecond(25.0),
358                                         Velocity.fromMetersPerSecond(15.0))))
359                             .build(),
360                     title = "4 km Running",
361                     titleA11y = "4 kilometres Running"))
362             add(FormattedSectionContent(title = "This is a test exercise step", bulleted = true))
363             add(
364                 ExercisePerformanceGoalEntry(
365                     goal = ExercisePerformanceGoal.HeartRateGoal(150, 180),
366                     title = "150 bpm - 180 bpm",
367                     titleA11y = "150 beats per minute - 180 beats per minute"))
368             add(
369                 ExercisePerformanceGoalEntry(
370                     goal =
371                         ExercisePerformanceGoal.SpeedGoal(
372                             Velocity.fromMetersPerSecond(180.0),
373                             Velocity.fromMetersPerSecond(90.0)),
374                     title = "180 km/h - 90 km/h",
375                     titleA11y = "180 kilometres per hour - 90 kilometres per hour"))
376         }
377 
378         whenever(viewModel.sessionData).thenReturn(MutableLiveData(WithData(list)))
379 
380         launchFragment<DataEntryDetailsFragment>(
381             DataEntryDetailsFragment.createBundle(
382                 permissionType = PLANNED_EXERCISE, entryId = "1", showDataOrigin = true))
383 
384         onView(withText("07:06 - 08:06 • Health Connect test app")).check(matches(isDisplayed()))
385         onView(withText("Running • Morning Run")).check(matches(isDisplayed()))
386         onView(withText("Notes")).check(matches(isDisplayed()))
387         onView(withText("Morning quick run by the park")).check(matches(isDisplayed()))
388         onView(withText("Warm up: 1 time")).check(matches(isDisplayed()))
389         onView(withText("4 km Running")).check(matches(isDisplayed()))
390         onView(withText("• This is a test exercise step")).check(matches(isDisplayed()))
391         onView(withText("• 150 bpm - 180 bpm")).check(matches(isDisplayed()))
392         onView(withText("• 180 km/h - 90 km/h")).check(matches(isDisplayed()))
393         verify(healthConnectLogger, times(2))
394             .logImpression((EntryDetailsElement.FORMATTED_SECTION_CONTENT_VIEW))
395         verify(healthConnectLogger)
396             .logImpression((EntryDetailsElement.PLANNED_EXERCISE_BLOCK_ENTRY_VIEW))
397         verify(healthConnectLogger)
398             .logImpression((EntryDetailsElement.PLANNED_EXERCISE_STEP_ENTRY_VIEW))
399         verify(healthConnectLogger).logImpression((EntryDetailsElement.SESSION_DETAIL_HEADER_VIEW))
400     }
401 
getSleepStagesnull402     private fun getSleepStages(): List<FormattedEntry> {
403         return listOf(
404             FormattedEntry.SessionHeader(header = "Stages"),
405             FormattedEntry.FormattedSessionDetail(
406                 uuid = "1",
407                 header = "07:06 • TEST_APP_NAME",
408                 headerA11y = "07:06 • TEST_APP_NAME",
409                 title = "6 hour light sleeping",
410                 titleA11y = "6 hour light sleeping",
411             ),
412             FormattedEntry.FormattedSessionDetail(
413                 uuid = "1",
414                 header = "07:06 • TEST_APP_NAME",
415                 headerA11y = "07:06 • TEST_APP_NAME",
416                 title = "6 hour deep sleeping",
417                 titleA11y = "6 hour deep sleeping",
418             ))
419     }
420 
getSkinTemperatureEntrynull421     private fun getSkinTemperatureEntry(): FormattedEntry {
422         return SeriesDataEntry(
423             uuid = "1",
424             header = "16:00 - 17:00 • TEST_APP_NAME",
425             headerA11y = "16:00 - 17:00 • TEST_APP_NAME",
426             title = "+0.5℃ (avg variation)",
427             titleA11y = "+0.5 degrees Celsius (average variation)",
428             dataType = DataType.SKIN_TEMPERATURE)
429     }
430 
getSkinTemperatureDeltasnull431     private fun getSkinTemperatureDeltas(
432         includeBaseline: Boolean,
433         includeLocation: Boolean
434     ): List<FormattedEntry> {
435         val locationDefinedFormattedEntry: FormattedEntry.ReverseSessionDetail =
436             FormattedEntry.ReverseSessionDetail(
437                 uuid = "1",
438                 header = "Measurement location",
439                 headerA11y = "Measurement location",
440                 title = "Toe",
441                 titleA11y = "Toe",
442             )
443 
444         val baselineDefinedFormattedEntry: FormattedEntry.ReverseSessionDetail =
445             FormattedEntry.ReverseSessionDetail(
446                 uuid = "1",
447                 header = "Baseline",
448                 headerA11y = "Baseline",
449                 title = "25℃",
450                 titleA11y = "25 degrees Celsius",
451             )
452 
453         val deltaFormattedEntries: List<FormattedEntry> =
454             listOf(
455                 FormattedEntry.FormattedSectionTitle(title = "Variation from baseline"),
456                 FormattedEntry.FormattedSessionDetail(
457                     uuid = "1",
458                     header = "16:10 AM",
459                     headerA11y = "16:10 AM",
460                     title = "+1.5℃",
461                     titleA11y = "+1.5 degrees Celsius",
462                 ),
463                 FormattedEntry.FormattedSessionDetail(
464                     uuid = "1",
465                     header = "16:40 AM",
466                     headerA11y = "16:40 AM",
467                     title = "-0.5℃",
468                     titleA11y = "-0.5 degrees Celsius",
469                 ))
470 
471         return if (includeBaseline && includeLocation) {
472             listOf(locationDefinedFormattedEntry, baselineDefinedFormattedEntry)
473                 .plus(deltaFormattedEntries)
474         } else if (includeBaseline) {
475             listOf(baselineDefinedFormattedEntry).plus(deltaFormattedEntries)
476         } else if (includeLocation) {
477             listOf(locationDefinedFormattedEntry).plus(deltaFormattedEntries)
478         } else {
479             deltaFormattedEntries
480         }
481     }
482 
getFormattedSleepSessionnull483     private fun getFormattedSleepSession(): SleepSessionEntry {
484         return SleepSessionEntry(
485             uuid = "1",
486             header = "07:06 • TEST_APP_NAME",
487             headerA11y = "07:06 • TEST_APP_NAME",
488             title = "12 hour sleeping",
489             titleA11y = "12 hour sleeping",
490             dataType = DataType.SLEEP,
491             notes = "notes")
492     }
493 
getFormattedExerciseSessionnull494     private fun getFormattedExerciseSession(showSession: Boolean): ExerciseSessionEntry {
495         return ExerciseSessionEntry(
496             uuid = "1",
497             header = "07:06 • TEST_APP_NAME",
498             headerA11y = "07:06 • TEST_APP_NAME",
499             title = "12 hour running",
500             titleA11y = "12 hour running",
501             dataType = DataType.EXERCISE,
502             notes = "notes",
503             route =
504                 if (showSession) {
505                     ExerciseRoute(WARSAW_ROUTE)
506                 } else {
507                     null
508                 })
509     }
510 
getFormattedSeriesDatanull511     private fun getFormattedSeriesData(): SeriesDataEntry {
512         return SeriesDataEntry(
513             uuid = "1",
514             header = "07:06 - 8:06 • TEST_APP_NAME",
515             headerA11y = "07:06 - 8:06 • TEST_APP_NAME",
516             title = "100 bpm",
517             titleA11y = "100 beats per minute",
518             dataType = DataType.HEART_RATE)
519     }
520 
getPlannedExerciseStepBuildernull521     private fun getPlannedExerciseStepBuilder(): PlannedExerciseStep.Builder {
522         return PlannedExerciseStep.Builder(
523             ExerciseSegmentType.EXERCISE_SEGMENT_TYPE_RUNNING,
524             PlannedExerciseStep.EXERCISE_CATEGORY_ACTIVE,
525             ExerciseCompletionGoal.DistanceGoal(Length.fromMeters(1000.0)))
526     }
527 }
528