1 /*
2  * Copyright (C) 2018 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.settings.widget;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.ArgumentMatchers.any;
22 import static org.mockito.ArgumentMatchers.anyInt;
23 import static org.mockito.ArgumentMatchers.anyString;
24 import static org.mockito.ArgumentMatchers.eq;
25 import static org.mockito.Mockito.mock;
26 import static org.mockito.Mockito.spy;
27 import static org.mockito.Mockito.times;
28 import static org.mockito.Mockito.verify;
29 import static org.mockito.Mockito.verifyZeroInteractions;
30 import static org.mockito.Mockito.when;
31 
32 import android.content.Context;
33 import android.graphics.drawable.ColorDrawable;
34 import android.os.Bundle;
35 import android.view.View;
36 
37 import androidx.preference.Preference;
38 import androidx.preference.PreferenceCategory;
39 import androidx.preference.PreferenceScreen;
40 import androidx.preference.PreferenceViewHolder;
41 import androidx.recyclerview.widget.RecyclerView;
42 
43 import com.android.settings.R;
44 import com.android.settings.SettingsActivity;
45 import com.android.settings.SettingsPreferenceFragment;
46 
47 import com.google.android.material.appbar.AppBarLayout;
48 
49 import org.junit.Before;
50 import org.junit.Test;
51 import org.junit.runner.RunWith;
52 import org.mockito.Mock;
53 import org.mockito.MockitoAnnotations;
54 import org.robolectric.RobolectricTestRunner;
55 import org.robolectric.RuntimeEnvironment;
56 import org.robolectric.util.ReflectionHelpers;
57 
58 @RunWith(RobolectricTestRunner.class)
59 public class HighlightablePreferenceGroupAdapterTest {
60 
61     private static final String TEST_KEY = "key";
62 
63     @Mock
64     private View mRoot;
65     @Mock
66     private PreferenceCategory mPreferenceCatetory;
67     @Mock
68     private SettingsPreferenceFragment mFragment;
69 
70     private Context mContext;
71     private HighlightablePreferenceGroupAdapter mAdapter;
72     private PreferenceViewHolder mViewHolder;
73     private Preference mPreference;
74 
75     @Before
setUp()76     public void setUp() {
77         MockitoAnnotations.initMocks(this);
78         mContext = RuntimeEnvironment.application;
79         mPreference = new Preference(mContext);
80         mPreference.setKey(TEST_KEY);
81         when(mPreferenceCatetory.getContext()).thenReturn(mContext);
82         mAdapter = spy(new HighlightablePreferenceGroupAdapter(mPreferenceCatetory, TEST_KEY,
83                 false /* highlighted*/));
84         when(mAdapter.getItem(anyInt())).thenReturn(mPreference);
85         mViewHolder = PreferenceViewHolder.createInstanceForTests(
86                 View.inflate(mContext, R.layout.app_preference_item, null));
87     }
88 
89     @Test
requestHighlight_hasKey_notHighlightedBefore_shouldRequest()90     public void requestHighlight_hasKey_notHighlightedBefore_shouldRequest() {
91         when(mAdapter.getPreferenceAdapterPosition(anyString())).thenReturn(1);
92         mAdapter.requestHighlight(mRoot, mock(RecyclerView.class), mock(AppBarLayout.class));
93 
94         verify(mRoot).postDelayed(any(),
95                 eq(HighlightablePreferenceGroupAdapter.DELAY_COLLAPSE_DURATION_MILLIS));
96         verify(mRoot).postDelayed(any(),
97                 eq(HighlightablePreferenceGroupAdapter.DELAY_HIGHLIGHT_DURATION_MILLIS));
98     }
99 
100     @Test
requestHighlight_noKey_highlightedBefore_noRecyclerView_shouldNotRequest()101     public void requestHighlight_noKey_highlightedBefore_noRecyclerView_shouldNotRequest() {
102         ReflectionHelpers.setField(mAdapter, "mHighlightKey", null);
103         ReflectionHelpers.setField(mAdapter, "mHighlightRequested", false);
104         mAdapter.requestHighlight(mRoot, mock(RecyclerView.class),  mock(AppBarLayout.class));
105 
106         ReflectionHelpers.setField(mAdapter, "mHighlightKey", TEST_KEY);
107         ReflectionHelpers.setField(mAdapter, "mHighlightRequested", true);
108         mAdapter.requestHighlight(mRoot, mock(RecyclerView.class), mock(AppBarLayout.class));
109 
110         ReflectionHelpers.setField(mAdapter, "mHighlightKey", TEST_KEY);
111         ReflectionHelpers.setField(mAdapter, "mHighlightRequested", false);
112         mAdapter.requestHighlight(mRoot, null /* recyclerView */,  mock(AppBarLayout.class));
113 
114         verifyZeroInteractions(mRoot);
115     }
116 
117     @Test
adjustInitialExpandedChildCount_invalidInput_shouldNotAdjust()118     public void adjustInitialExpandedChildCount_invalidInput_shouldNotAdjust() {
119         HighlightablePreferenceGroupAdapter.adjustInitialExpandedChildCount(null /* host */);
120         HighlightablePreferenceGroupAdapter.adjustInitialExpandedChildCount(mFragment);
121         final Bundle args = new Bundle();
122         when(mFragment.getArguments()).thenReturn(args);
123         HighlightablePreferenceGroupAdapter.adjustInitialExpandedChildCount(mFragment);
124         final PreferenceScreen screen = mock(PreferenceScreen.class);
125         when(mFragment.getArguments()).thenReturn(null);
126         when(mFragment.getPreferenceScreen()).thenReturn(screen);
127         HighlightablePreferenceGroupAdapter.adjustInitialExpandedChildCount(mFragment);
128         verifyZeroInteractions(screen);
129     }
130 
131     @Test
adjustInitialExpandedChildCount_hasHightlightKey_shouldExpandAllChildren()132     public void adjustInitialExpandedChildCount_hasHightlightKey_shouldExpandAllChildren() {
133         final Bundle args = new Bundle();
134         when(mFragment.getArguments()).thenReturn(args);
135         args.putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, "testkey");
136         final PreferenceScreen screen = mock(PreferenceScreen.class);
137         when(mFragment.getPreferenceScreen()).thenReturn(screen);
138         HighlightablePreferenceGroupAdapter.adjustInitialExpandedChildCount(mFragment);
139 
140         verify(screen).setInitialExpandedChildrenCount(Integer.MAX_VALUE);
141     }
142 
143     @Test
adjustInitialExpandedChildCount_noKeyOrChildCountOverride_shouldDoNothing()144     public void adjustInitialExpandedChildCount_noKeyOrChildCountOverride_shouldDoNothing() {
145         final Bundle args = new Bundle();
146         when(mFragment.getArguments()).thenReturn(args);
147         when(mFragment.getInitialExpandedChildCount()).thenReturn(-1);
148         final PreferenceScreen screen = mock(PreferenceScreen.class);
149         when(mFragment.getPreferenceScreen()).thenReturn(screen);
150         HighlightablePreferenceGroupAdapter.adjustInitialExpandedChildCount(mFragment);
151 
152         verify(mFragment).getInitialExpandedChildCount();
153         verifyZeroInteractions(screen);
154     }
155 
156     @Test
adjustInitialExpandedChildCount_hasCountOverride_shouldDoNothing()157     public void adjustInitialExpandedChildCount_hasCountOverride_shouldDoNothing() {
158         when(mFragment.getInitialExpandedChildCount()).thenReturn(10);
159         final PreferenceScreen screen = mock(PreferenceScreen.class);
160         when(mFragment.getPreferenceScreen()).thenReturn(screen);
161         HighlightablePreferenceGroupAdapter.adjustInitialExpandedChildCount(mFragment);
162 
163         verify(mFragment).getInitialExpandedChildCount();
164 
165         verify(screen).setInitialExpandedChildrenCount(10);
166     }
167 
168     @Test
updateBackground_notHighlightedRow_shouldNotSetHighlightedTag()169     public void updateBackground_notHighlightedRow_shouldNotSetHighlightedTag() {
170         ReflectionHelpers.setField(mAdapter, "mHighlightPosition", 10);
171 
172         mAdapter.updateBackground(mViewHolder, 0);
173 
174         assertThat(mViewHolder.itemView.getTag(R.id.preference_highlighted)).isNull();
175     }
176 
177     @Test
updateBackground_highlight_shouldAnimateBackgroundAndSetHighlightedTag()178     public void updateBackground_highlight_shouldAnimateBackgroundAndSetHighlightedTag() {
179         ReflectionHelpers.setField(mAdapter, "mHighlightPosition", 10);
180         assertThat(mAdapter.mFadeInAnimated).isFalse();
181 
182         mAdapter.updateBackground(mViewHolder, 10);
183 
184         assertThat(mAdapter.mFadeInAnimated).isTrue();
185         assertThat(mViewHolder.itemView.getBackground()).isInstanceOf(ColorDrawable.class);
186         assertThat(mViewHolder.itemView.getTag(R.id.preference_highlighted)).isEqualTo(true);
187         verify(mAdapter).requestRemoveHighlightDelayed(mViewHolder);
188     }
189 
190     @Test
updateBackgroundTwice_highlight_shouldAnimateOnce()191     public void updateBackgroundTwice_highlight_shouldAnimateOnce() {
192         ReflectionHelpers.setField(mAdapter, "mHighlightPosition", 10);
193         ReflectionHelpers.setField(mViewHolder, "itemView", spy(mViewHolder.itemView));
194         assertThat(mAdapter.mFadeInAnimated).isFalse();
195         mAdapter.updateBackground(mViewHolder, 10);
196         // mFadeInAnimated change from false to true - indicating background change is scheduled
197         // through animation.
198         assertThat(mAdapter.mFadeInAnimated).isTrue();
199         // remove highlight should be requested.
200         verify(mAdapter).requestRemoveHighlightDelayed(mViewHolder);
201 
202         ReflectionHelpers.setField(mAdapter, "mHighlightPosition", 10);
203         mAdapter.updateBackground(mViewHolder, 10);
204         // only sets background color once - if it's animation this would be called many times
205         verify(mViewHolder.itemView).setBackgroundColor(mAdapter.mHighlightColor);
206         // remove highlight should be requested.
207         verify(mAdapter, times(2)).requestRemoveHighlightDelayed(mViewHolder);
208     }
209 
210     @Test
updateBackground_reuseHightlightedRowForNormalRow_shouldResetBackgroundAndTag()211     public void updateBackground_reuseHightlightedRowForNormalRow_shouldResetBackgroundAndTag() {
212         ReflectionHelpers.setField(mAdapter, "mHighlightPosition", 10);
213         mViewHolder.itemView.setTag(R.id.preference_highlighted, true);
214 
215         mAdapter.updateBackground(mViewHolder, 0);
216 
217         assertThat(mViewHolder.itemView.getBackground()).isNotInstanceOf(ColorDrawable.class);
218         assertThat(mViewHolder.itemView.getTag(R.id.preference_highlighted)).isEqualTo(false);
219     }
220 }
221