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 package com.android.intentresolver.emptystate 18 19 import android.view.LayoutInflater 20 import android.view.View 21 import android.view.ViewGroup 22 import android.widget.FrameLayout 23 import android.widget.TextView 24 import androidx.test.platform.app.InstrumentationRegistry 25 import com.google.common.truth.Truth.assertThat 26 import java.util.Optional 27 import java.util.function.Supplier 28 import org.junit.Before 29 import org.junit.Test 30 import org.mockito.kotlin.any 31 import org.mockito.kotlin.mock 32 import org.mockito.kotlin.never 33 import org.mockito.kotlin.verify 34 35 class EmptyStateUiHelperTest { 36 private val context = InstrumentationRegistry.getInstrumentation().getContext() 37 38 var shouldOverrideContainerPadding = false 39 val containerPaddingSupplier = <lambda>null40 Supplier<Optional<Int>> { 41 Optional.ofNullable(if (shouldOverrideContainerPadding) 42 else null) 42 } 43 44 lateinit var rootContainer: ViewGroup 45 lateinit var mainListView: View // Visible when no empty state is showing. 46 lateinit var emptyStateTitleView: TextView 47 lateinit var emptyStateSubtitleView: TextView 48 lateinit var emptyStateButtonView: View 49 lateinit var emptyStateProgressView: View 50 lateinit var emptyStateDefaultTextView: View 51 lateinit var emptyStateContainerView: View 52 lateinit var emptyStateRootView: View 53 lateinit var emptyStateUiHelper: EmptyStateUiHelper 54 55 @Before setupnull56 fun setup() { 57 rootContainer = FrameLayout(context) 58 LayoutInflater.from(context) 59 .inflate( 60 com.android.intentresolver.R.layout.resolver_list_per_profile, 61 rootContainer, 62 true 63 ) 64 mainListView = rootContainer.requireViewById(com.android.internal.R.id.resolver_list) 65 emptyStateRootView = 66 rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state) 67 emptyStateTitleView = 68 rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state_title) 69 emptyStateSubtitleView = 70 rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state_subtitle) 71 emptyStateButtonView = 72 rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state_button) 73 emptyStateProgressView = 74 rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state_progress) 75 emptyStateDefaultTextView = rootContainer.requireViewById(com.android.internal.R.id.empty) 76 emptyStateContainerView = 77 rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state_container) 78 emptyStateUiHelper = 79 EmptyStateUiHelper( 80 rootContainer, 81 com.android.internal.R.id.resolver_list, 82 containerPaddingSupplier 83 ) 84 } 85 86 @Test testResetViewVisibilitiesnull87 fun testResetViewVisibilities() { 88 // First set each view's visibility to differ from the expected "reset" state so we can then 89 // assert that they're all reset afterward. 90 // TODO: for historic reasons "reset" doesn't cover `emptyStateContainerView`; should it? 91 emptyStateRootView.visibility = View.GONE 92 emptyStateTitleView.visibility = View.GONE 93 emptyStateSubtitleView.visibility = View.GONE 94 emptyStateButtonView.visibility = View.VISIBLE 95 emptyStateProgressView.visibility = View.VISIBLE 96 emptyStateDefaultTextView.visibility = View.VISIBLE 97 98 emptyStateUiHelper.resetViewVisibilities() 99 100 assertThat(emptyStateRootView.visibility).isEqualTo(View.VISIBLE) 101 assertThat(emptyStateTitleView.visibility).isEqualTo(View.VISIBLE) 102 assertThat(emptyStateSubtitleView.visibility).isEqualTo(View.VISIBLE) 103 assertThat(emptyStateButtonView.visibility).isEqualTo(View.INVISIBLE) 104 assertThat(emptyStateProgressView.visibility).isEqualTo(View.GONE) 105 assertThat(emptyStateDefaultTextView.visibility).isEqualTo(View.GONE) 106 } 107 108 @Test testShowSpinnernull109 fun testShowSpinner() { 110 emptyStateTitleView.visibility = View.VISIBLE 111 emptyStateButtonView.visibility = View.VISIBLE 112 emptyStateProgressView.visibility = View.GONE 113 emptyStateDefaultTextView.visibility = View.VISIBLE 114 115 emptyStateUiHelper.showSpinner() 116 117 // TODO: should this cover any other views? Subtitle? 118 assertThat(emptyStateTitleView.visibility).isEqualTo(View.INVISIBLE) 119 assertThat(emptyStateButtonView.visibility).isEqualTo(View.INVISIBLE) 120 assertThat(emptyStateProgressView.visibility).isEqualTo(View.VISIBLE) 121 assertThat(emptyStateDefaultTextView.visibility).isEqualTo(View.GONE) 122 } 123 124 @Test testHidenull125 fun testHide() { 126 emptyStateRootView.visibility = View.VISIBLE 127 mainListView.visibility = View.GONE 128 129 emptyStateUiHelper.hide() 130 131 assertThat(emptyStateRootView.visibility).isEqualTo(View.GONE) 132 assertThat(mainListView.visibility).isEqualTo(View.VISIBLE) 133 } 134 135 @Test testBottomPaddingDelegate_defaultnull136 fun testBottomPaddingDelegate_default() { 137 shouldOverrideContainerPadding = false 138 emptyStateContainerView.setPadding(1, 2, 3, 4) 139 140 emptyStateUiHelper.setupContainerPadding() 141 142 assertThat(emptyStateContainerView.paddingLeft).isEqualTo(1) 143 assertThat(emptyStateContainerView.paddingTop).isEqualTo(2) 144 assertThat(emptyStateContainerView.paddingRight).isEqualTo(3) 145 assertThat(emptyStateContainerView.paddingBottom).isEqualTo(4) 146 } 147 148 @Test testBottomPaddingDelegate_overridenull149 fun testBottomPaddingDelegate_override() { 150 shouldOverrideContainerPadding = true // Set bottom padding to 42. 151 emptyStateContainerView.setPadding(1, 2, 3, 4) 152 153 emptyStateUiHelper.setupContainerPadding() 154 155 assertThat(emptyStateContainerView.paddingLeft).isEqualTo(1) 156 assertThat(emptyStateContainerView.paddingTop).isEqualTo(2) 157 assertThat(emptyStateContainerView.paddingRight).isEqualTo(3) 158 assertThat(emptyStateContainerView.paddingBottom).isEqualTo(42) 159 } 160 161 @Test testShowEmptyState_noOnClickHandlernull162 fun testShowEmptyState_noOnClickHandler() { 163 mainListView.visibility = View.VISIBLE 164 165 // Note: an `EmptyState.ClickListener` isn't invoked directly by the UI helper; it has to be 166 // built into the "on-click handler" that's injected to implement the button-press. We won't 167 // display the button without a click "handler," even if it *does* have a `ClickListener`. 168 val clickListener = mock<EmptyState.ClickListener>() 169 170 val emptyState = 171 object : EmptyState { 172 override fun getTitle() = "Test title" 173 override fun getSubtitle() = "Test subtitle" 174 175 override fun getButtonClickListener() = clickListener 176 } 177 emptyStateUiHelper.showEmptyState(emptyState, null) 178 179 assertThat(mainListView.visibility).isEqualTo(View.GONE) 180 assertThat(emptyStateRootView.visibility).isEqualTo(View.VISIBLE) 181 assertThat(emptyStateTitleView.visibility).isEqualTo(View.VISIBLE) 182 assertThat(emptyStateSubtitleView.visibility).isEqualTo(View.VISIBLE) 183 assertThat(emptyStateButtonView.visibility).isEqualTo(View.GONE) 184 assertThat(emptyStateProgressView.visibility).isEqualTo(View.GONE) 185 assertThat(emptyStateDefaultTextView.visibility).isEqualTo(View.GONE) 186 187 assertThat(emptyStateTitleView.text).isEqualTo("Test title") 188 assertThat(emptyStateSubtitleView.text).isEqualTo("Test subtitle") 189 190 verify(clickListener, never()).onClick(any()) 191 } 192 193 @Test testShowEmptyState_withOnClickHandlerAndClickListenernull194 fun testShowEmptyState_withOnClickHandlerAndClickListener() { 195 mainListView.visibility = View.VISIBLE 196 197 val clickListener = mock<EmptyState.ClickListener>() 198 val onClickHandler = mock<View.OnClickListener>() 199 200 val emptyState = 201 object : EmptyState { 202 override fun getTitle() = "Test title" 203 override fun getSubtitle() = "Test subtitle" 204 205 override fun getButtonClickListener() = clickListener 206 } 207 emptyStateUiHelper.showEmptyState(emptyState, onClickHandler) 208 209 assertThat(mainListView.visibility).isEqualTo(View.GONE) 210 assertThat(emptyStateRootView.visibility).isEqualTo(View.VISIBLE) 211 assertThat(emptyStateTitleView.visibility).isEqualTo(View.VISIBLE) 212 assertThat(emptyStateSubtitleView.visibility).isEqualTo(View.VISIBLE) 213 assertThat(emptyStateButtonView.visibility).isEqualTo(View.VISIBLE) // Now shown. 214 assertThat(emptyStateProgressView.visibility).isEqualTo(View.GONE) 215 assertThat(emptyStateDefaultTextView.visibility).isEqualTo(View.GONE) 216 217 assertThat(emptyStateTitleView.text).isEqualTo("Test title") 218 assertThat(emptyStateSubtitleView.text).isEqualTo("Test subtitle") 219 220 emptyStateButtonView.performClick() 221 222 verify(onClickHandler).onClick(emptyStateButtonView) 223 // The test didn't explicitly configure its `OnClickListener` to relay the click event on 224 // to the `EmptyState.ClickListener`, so it still won't have fired here. 225 verify(clickListener, never()).onClick(any()) 226 } 227 } 228