1 /** 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * ``` 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * ``` 10 * 11 * Unless required by applicable law or agreed to in writing, software distributed under the License 12 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 13 * or implied. See the License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 package com.android.healthconnect.controller.tests.dataaccess 17 18 import android.os.Bundle 19 import androidx.lifecycle.MutableLiveData 20 import androidx.test.espresso.Espresso.onView 21 import androidx.test.espresso.action.ViewActions.scrollTo 22 import androidx.test.espresso.assertion.ViewAssertions.doesNotExist 23 import androidx.test.espresso.assertion.ViewAssertions.matches 24 import androidx.test.espresso.matcher.ViewMatchers.isDisplayed 25 import androidx.test.espresso.matcher.ViewMatchers.withId 26 import androidx.test.espresso.matcher.ViewMatchers.withText 27 import com.android.healthconnect.controller.R 28 import com.android.healthconnect.controller.data.access.AccessViewModel 29 import com.android.healthconnect.controller.data.access.AccessViewModel.AccessScreenState 30 import com.android.healthconnect.controller.data.access.AccessViewModel.AccessScreenState.WithData 31 import com.android.healthconnect.controller.data.access.AppAccessMetadata 32 import com.android.healthconnect.controller.data.access.AppAccessState 33 import com.android.healthconnect.controller.dataaccess.HealthDataAccessFragment 34 import com.android.healthconnect.controller.permissions.data.FitnessPermissionType 35 import com.android.healthconnect.controller.permissiontypes.HealthPermissionTypesFragment.Companion.PERMISSION_TYPE_KEY 36 import com.android.healthconnect.controller.shared.app.AppMetadata 37 import com.android.healthconnect.controller.tests.utils.launchFragment 38 import com.android.healthconnect.controller.utils.logging.DataAccessElement 39 import com.android.healthconnect.controller.utils.logging.HealthConnectLogger 40 import com.android.healthconnect.controller.utils.logging.PageName 41 import dagger.hilt.android.testing.BindValue 42 import dagger.hilt.android.testing.HiltAndroidRule 43 import dagger.hilt.android.testing.HiltAndroidTest 44 import org.hamcrest.Matchers.not 45 import org.junit.After 46 import org.junit.Before 47 import org.junit.Rule 48 import org.junit.Test 49 import org.mockito.Mockito 50 import org.mockito.kotlin.atLeast 51 import org.mockito.kotlin.mock 52 import org.mockito.kotlin.reset 53 import org.mockito.kotlin.times 54 import org.mockito.kotlin.verify 55 import org.mockito.kotlin.whenever 56 57 @HiltAndroidTest 58 class HealthDataAccessFragmentTest { 59 60 @get:Rule val hiltRule = HiltAndroidRule(this) 61 62 @BindValue val viewModel: AccessViewModel = Mockito.mock(AccessViewModel::class.java) 63 @BindValue val healthConnectLogger: HealthConnectLogger = mock() 64 65 @Before setupnull66 fun setup() { 67 hiltRule.inject() 68 } 69 70 @After tearDownnull71 fun tearDown() { 72 reset(healthConnectLogger) 73 } 74 75 @Test dataAccessFragment_noSections_noneDisplayednull76 fun dataAccessFragment_noSections_noneDisplayed() { 77 whenever(viewModel.appMetadataMap).then { 78 MutableLiveData<AccessScreenState>(WithData(emptyMap())) 79 } 80 launchFragment<HealthDataAccessFragment>(distanceBundle()) 81 82 onView(withText("Can read distance")).check(doesNotExist()) 83 onView(withText("Can write distance")).check(doesNotExist()) 84 onView(withText("Inactive apps")).check(doesNotExist()) 85 onView( 86 withText( 87 "These apps can no longer read or write distance, but still have data stored in Health\u00A0Connect" 88 ) 89 ) 90 .check(doesNotExist()) 91 onView(withText("Manage data")).check(matches(isDisplayed())) 92 onView(withText("See all entries")).perform(scrollTo()).check(matches(isDisplayed())) 93 onView(withText("Delete this data")).perform(scrollTo()).check(matches(isDisplayed())) 94 } 95 96 @Test dataAccessFragment_readSection_isDisplayednull97 fun dataAccessFragment_readSection_isDisplayed() { 98 val map = 99 mapOf( 100 AppAccessState.Read to 101 listOf(AppAccessMetadata(AppMetadata("package1", "appName1", null))), 102 AppAccessState.Write to emptyList(), 103 AppAccessState.Inactive to emptyList(), 104 ) 105 whenever(viewModel.appMetadataMap).then { 106 MutableLiveData<AccessScreenState>(WithData(map)) 107 } 108 launchFragment<HealthDataAccessFragment>(distanceBundle()) 109 110 onView(withText("Can read distance")).check(matches(isDisplayed())) 111 onView(withText("Can write distance")).check(doesNotExist()) 112 onView(withText("Inactive apps")).check(doesNotExist()) 113 onView( 114 withText( 115 "These apps can no longer read or write distance, but still have data stored in Health\u00A0Connect" 116 ) 117 ) 118 .check(doesNotExist()) 119 onView(withText("Manage data")).check(matches(isDisplayed())) 120 onView(withText("See all entries")).perform(scrollTo()).check(matches(isDisplayed())) 121 onView(withText("Delete this data")).perform(scrollTo()).check(matches(isDisplayed())) 122 } 123 124 @Test dataAccessFragment_readAndWriteSections_isDisplayednull125 fun dataAccessFragment_readAndWriteSections_isDisplayed() { 126 val map = 127 mapOf( 128 AppAccessState.Read to 129 listOf(AppAccessMetadata(AppMetadata("package1", "appName1", null))), 130 AppAccessState.Write to 131 listOf(AppAccessMetadata(AppMetadata("package1", "appName1", null))), 132 AppAccessState.Inactive to emptyList(), 133 ) 134 whenever(viewModel.appMetadataMap).then { 135 MutableLiveData<AccessScreenState>(WithData(map)) 136 } 137 launchFragment<HealthDataAccessFragment>(distanceBundle()) 138 139 onView(withText("Can read distance")).check(matches(isDisplayed())) 140 onView(withText("Can write distance")).check(matches(isDisplayed())) 141 onView(withText("Inactive apps")).check(doesNotExist()) 142 onView( 143 withText( 144 "These apps can no longer read or write distance, but still have data stored in Health\u00A0Connect" 145 ) 146 ) 147 .check(doesNotExist()) 148 onView(withText("Manage data")).check(matches(isDisplayed())) 149 onView(withText("See all entries")).perform(scrollTo()).check(matches(isDisplayed())) 150 onView(withText("Delete this data")).perform(scrollTo()).check(matches(isDisplayed())) 151 152 verify(healthConnectLogger, atLeast(1)).setPageId(PageName.DATA_ACCESS_PAGE) 153 verify(healthConnectLogger).logPageImpression() 154 verify(healthConnectLogger, times(2)) 155 .logImpression(DataAccessElement.DATA_ACCESS_APP_BUTTON) 156 verify(healthConnectLogger).logImpression(DataAccessElement.DELETE_THIS_DATA_BUTTON) 157 verify(healthConnectLogger).logImpression(DataAccessElement.SEE_ALL_ENTRIES_BUTTON) 158 } 159 160 @Test dataAccessFragment_inactiveSection_isDisplayednull161 fun dataAccessFragment_inactiveSection_isDisplayed() { 162 val map = 163 mapOf( 164 AppAccessState.Read to emptyList(), 165 AppAccessState.Write to emptyList(), 166 AppAccessState.Inactive to 167 listOf(AppAccessMetadata(AppMetadata("package1", "appName1", null))), 168 ) 169 whenever(viewModel.appMetadataMap).then { 170 MutableLiveData<AccessScreenState>(WithData(map)) 171 } 172 launchFragment<HealthDataAccessFragment>(distanceBundle()) 173 174 onView(withText("Can read distance")).check(doesNotExist()) 175 onView(withText("Can write distance")).check(doesNotExist()) 176 onView(withText("Inactive apps")).check(matches(isDisplayed())) 177 onView( 178 withText( 179 "These apps can no longer read or write distance, but still have data stored in Health\u00A0Connect" 180 ) 181 ) 182 .check(matches(isDisplayed())) 183 onView(withText("Manage data")).check(matches(isDisplayed())) 184 onView(withText("See all entries")).perform(scrollTo()).check(matches(isDisplayed())) 185 onView(withText("Delete this data")).perform(scrollTo()).check(matches(isDisplayed())) 186 verify(healthConnectLogger).logImpression(DataAccessElement.DATA_ACCESS_INACTIVE_APP_BUTTON) 187 } 188 189 @Test dataAccessFragment_loadingState_showsLoadingnull190 fun dataAccessFragment_loadingState_showsLoading() { 191 whenever(viewModel.appMetadataMap).then { 192 MutableLiveData<AccessScreenState>(AccessScreenState.Loading) 193 } 194 launchFragment<HealthDataAccessFragment>(distanceBundle()) 195 onView(withId(R.id.progress_indicator)).check(matches(isDisplayed())) 196 } 197 198 @Test dataAccessFragment_withData_hidesLoadingnull199 fun dataAccessFragment_withData_hidesLoading() { 200 whenever(viewModel.appMetadataMap).then { 201 MutableLiveData<AccessScreenState>(WithData(emptyMap())) 202 } 203 launchFragment<HealthDataAccessFragment>(distanceBundle()) 204 onView(withId(R.id.progress_indicator)).check(matches(not(isDisplayed()))) 205 } 206 207 @Test dataAccessFragment_withError_showErrornull208 fun dataAccessFragment_withError_showError() { 209 whenever(viewModel.appMetadataMap).then { 210 MutableLiveData<AccessScreenState>(AccessScreenState.Error) 211 } 212 launchFragment<HealthDataAccessFragment>(distanceBundle()) 213 onView(withId(R.id.progress_indicator)).check(matches(not(isDisplayed()))) 214 onView(withId(R.id.error_view)).check(matches(isDisplayed())) 215 } 216 distanceBundlenull217 private fun distanceBundle(): Bundle { 218 val bundle = Bundle() 219 bundle.putSerializable(PERMISSION_TYPE_KEY, FitnessPermissionType.DISTANCE) 220 return bundle 221 } 222 } 223