TextListItemTest.java revision 32a32d76550734f2c35f5db5b53890d8a2589281
1/*
2 * Copyright 2017 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
17package androidx.car.widget;
18
19import static android.support.test.espresso.Espresso.onView;
20import static android.support.test.espresso.action.ViewActions.click;
21import static android.support.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition;
22import static android.support.test.espresso.contrib.RecyclerViewActions.scrollToPosition;
23import static android.support.test.espresso.matcher.ViewMatchers.withId;
24
25import static org.hamcrest.CoreMatchers.not;
26import static org.hamcrest.Matchers.greaterThanOrEqualTo;
27import static org.hamcrest.Matchers.lessThan;
28import static org.hamcrest.core.Is.is;
29import static org.hamcrest.core.IsEqual.equalTo;
30import static org.hamcrest.number.IsCloseTo.closeTo;
31
32import static org.junit.Assert.assertFalse;
33import static org.junit.Assert.assertThat;
34import static org.junit.Assert.assertTrue;
35
36import android.content.pm.PackageManager;
37import android.graphics.drawable.Drawable;
38import android.support.test.InstrumentationRegistry;
39import android.support.test.espresso.UiController;
40import android.support.test.espresso.ViewAction;
41import android.support.test.filters.SmallTest;
42import android.support.test.rule.ActivityTestRule;
43import android.support.test.runner.AndroidJUnit4;
44import android.text.InputFilter;
45import android.text.TextUtils;
46import android.view.View;
47import android.view.ViewGroup;
48import android.widget.RelativeLayout;
49
50import org.hamcrest.Matcher;
51import org.junit.Assume;
52import org.junit.Before;
53import org.junit.Rule;
54import org.junit.Test;
55import org.junit.runner.RunWith;
56
57import java.util.ArrayList;
58import java.util.Arrays;
59import java.util.List;
60import java.util.Locale;
61
62import androidx.car.test.R;
63import androidx.car.utils.CarUxRestrictionsTestUtils;
64import androidx.recyclerview.widget.LinearLayoutManager;
65
66/**
67* Tests the layout configuration in {@link TextListItem}.
68 */
69@RunWith(AndroidJUnit4.class)
70@SmallTest
71public class TextListItemTest {
72
73    @Rule
74    public ActivityTestRule<PagedListViewTestActivity> mActivityRule =
75            new ActivityTestRule<>(PagedListViewTestActivity.class);
76
77    private PagedListViewTestActivity mActivity;
78    private PagedListView mPagedListView;
79    private ListItemAdapter mAdapter;
80
81    private boolean isAutoDevice() {
82        PackageManager packageManager = mActivityRule.getActivity().getPackageManager();
83        return packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
84    }
85
86    @Before
87    public void setUp() {
88        Assume.assumeTrue(isAutoDevice());
89
90        mActivity = mActivityRule.getActivity();
91        mPagedListView = mActivity.findViewById(R.id.paged_list_view);
92    }
93
94    private void refreshUi() {
95        try {
96            mActivityRule.runOnUiThread(() -> {
97                mAdapter.notifyDataSetChanged();
98            });
99        } catch (Throwable throwable) {
100            throwable.printStackTrace();
101            throw new RuntimeException(throwable);
102        }
103        // Wait for paged list view to layout by using espresso to scroll to a position.
104        onView(withId(R.id.recycler_view)).perform(scrollToPosition(0));
105    }
106
107    private void setupPagedListView(List<TextListItem> items) {
108        ListItemProvider provider = new ListItemProvider.ListProvider(
109                new ArrayList<>(items));
110        try {
111            mAdapter = new ListItemAdapter(mActivity, provider);
112            mActivityRule.runOnUiThread(() -> {
113                mPagedListView.setAdapter(mAdapter);
114            });
115        } catch (Throwable throwable) {
116            throwable.printStackTrace();
117            throw new RuntimeException(throwable);
118        }
119
120        refreshUi();
121    }
122
123    private static void verifyViewIsHidden(View view) {
124        if (view instanceof ViewGroup) {
125            ViewGroup viewGroup = (ViewGroup) view;
126            final int childCount = viewGroup.getChildCount();
127            for (int i = 0; i < childCount; i++) {
128                verifyViewIsHidden(viewGroup.getChildAt(i));
129            }
130        } else {
131            assertThat(view.getVisibility(), is(equalTo(View.GONE)));
132        }
133    }
134
135    private TextListItem.ViewHolder getViewHolderAtPosition(int position) {
136        return (TextListItem.ViewHolder) mPagedListView.getRecyclerView()
137                .findViewHolderForAdapterPosition(
138                position);
139    }
140
141    @Test
142    public void testEmptyItemHidesAllViews() {
143        TextListItem item = new TextListItem(mActivity);
144        setupPagedListView(Arrays.asList(item));
145        verifyViewIsHidden(mPagedListView.getRecyclerView().getLayoutManager().getChildAt(0));
146    }
147
148    @Test
149    public void testPrimaryActionVisible() {
150        TextListItem item0 = new TextListItem(mActivity);
151        item0.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon, true);
152
153        TextListItem item1 = new TextListItem(mActivity);
154        item1.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false);
155
156        List<TextListItem> items = Arrays.asList(item0, item1);
157        setupPagedListView(items);
158
159        assertThat(getViewHolderAtPosition(0).getPrimaryIcon().getVisibility(),
160                is(equalTo(View.VISIBLE)));
161        assertThat(getViewHolderAtPosition(1).getPrimaryIcon().getVisibility(),
162                is(equalTo(View.VISIBLE)));
163    }
164
165    @Test
166    public void testTextVisible() {
167        TextListItem item0 = new TextListItem(mActivity);
168        item0.setTitle("title");
169
170        TextListItem item1 = new TextListItem(mActivity);
171        item1.setBody("body");
172
173        List<TextListItem> items = Arrays.asList(item0, item1);
174        setupPagedListView(items);
175
176        assertThat(getViewHolderAtPosition(0).getTitle().getVisibility(),
177                is(equalTo(View.VISIBLE)));
178        assertThat(getViewHolderAtPosition(1).getBody().getVisibility(),
179                is(equalTo(View.VISIBLE)));
180    }
181
182    @Test
183    public void testSupplementalActionVisible() {
184        TextListItem item0 = new TextListItem(mActivity);
185        item0.setSupplementalIcon(android.R.drawable.sym_def_app_icon, true);
186
187        TextListItem item1 = new TextListItem(mActivity);
188        item1.setAction("text", true, v -> { /* Do nothing. */ });
189
190
191        TextListItem item2 = new TextListItem(mActivity);
192        item2.setActions("text", true, v -> { /* Do nothing. */ },
193                        "text", true, v -> { /* Do nothing. */ });
194
195        List<TextListItem> items = Arrays.asList(item0, item1, item2);
196        setupPagedListView(items);
197
198        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
199        assertThat(viewHolder.getSupplementalIcon().getVisibility(), is(equalTo(View.VISIBLE)));
200        assertThat(viewHolder.getSupplementalIconDivider().getVisibility(),
201                is(equalTo(View.VISIBLE)));
202
203        viewHolder = getViewHolderAtPosition(1);
204        assertThat(viewHolder.getAction1().getVisibility(), is(equalTo(View.VISIBLE)));
205        assertThat(viewHolder.getAction1Divider().getVisibility(), is(equalTo(View.VISIBLE)));
206
207        viewHolder = getViewHolderAtPosition(2);
208        assertThat(viewHolder.getAction1().getVisibility(), is(equalTo(View.VISIBLE)));
209        assertThat(viewHolder.getAction1Divider().getVisibility(), is(equalTo(View.VISIBLE)));
210        assertThat(viewHolder.getAction2().getVisibility(), is(equalTo(View.VISIBLE)));
211        assertThat(viewHolder.getAction2Divider().getVisibility(), is(equalTo(View.VISIBLE)));
212    }
213
214    @Test
215    public void testSwitchVisibleAndCheckedState() {
216        TextListItem item0 = new TextListItem(mActivity);
217        item0.setSwitch(true, true, null);
218
219        TextListItem item1 = new TextListItem(mActivity);
220        item1.setSwitch(false, true, null);
221
222        List<TextListItem> items = Arrays.asList(item0, item1);
223        setupPagedListView(items);
224
225        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
226        assertThat(viewHolder.getSwitch().getVisibility(), is(equalTo(View.VISIBLE)));
227        assertThat(viewHolder.getSwitch().isChecked(), is(equalTo(true)));
228        assertThat(viewHolder.getSwitchDivider().getVisibility(), is(equalTo(View.VISIBLE)));
229
230        viewHolder = getViewHolderAtPosition(1);
231        assertThat(viewHolder.getSwitch().getVisibility(), is(equalTo(View.VISIBLE)));
232        assertThat(viewHolder.getSwitch().isChecked(), is(equalTo(false)));
233        assertThat(viewHolder.getSwitchDivider().getVisibility(), is(equalTo(View.VISIBLE)));
234    }
235
236    @Test
237    public void testSetSwitchState() {
238        TextListItem item0 = new TextListItem(mActivity);
239        item0.setSwitch(true, true, null);
240
241        setupPagedListView(Arrays.asList(item0));
242
243        item0.setSwitchState(false);
244
245        refreshUi();
246
247        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
248        assertThat(viewHolder.getSwitch().getVisibility(), is(equalTo(View.VISIBLE)));
249        assertThat(viewHolder.getSwitch().isChecked(), is(equalTo(false)));
250    }
251
252    @Test
253    public void testSetSwitchStateHasNoEffectIfSwitchIsNotEnabled() {
254        TextListItem item0 = new TextListItem(mActivity);
255        setupPagedListView(Arrays.asList(item0));
256
257        item0.setSwitchState(false);
258
259        refreshUi();
260
261        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
262        assertThat(viewHolder.getSwitch().getVisibility(), is(not(equalTo(View.VISIBLE))));
263    }
264
265    @Test
266    public void testDividersAreOptional() {
267        TextListItem item0 = new TextListItem(mActivity);
268        item0.setSupplementalIcon(android.R.drawable.sym_def_app_icon, false);
269
270        TextListItem item1 = new TextListItem(mActivity);
271        item1.setAction("text", false, v -> { /* Do nothing. */ });
272
273        TextListItem item2 = new TextListItem(mActivity);
274        item2.setActions("text", false, v -> { /* Do nothing. */ },
275                "text", false, v -> { /* Do nothing. */ });
276
277        TextListItem item3 = new TextListItem(mActivity);
278        item3.setSwitch(true, false, null);
279
280        List<TextListItem> items = Arrays.asList(item0, item1, item2, item3);
281        setupPagedListView(items);
282
283        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
284        assertThat(viewHolder.getSupplementalIcon().getVisibility(), is(equalTo(View.VISIBLE)));
285        assertThat(viewHolder.getSupplementalIconDivider().getVisibility(),
286                is(equalTo(View.GONE)));
287
288        viewHolder = getViewHolderAtPosition(1);
289        assertThat(viewHolder.getAction1().getVisibility(), is(equalTo(View.VISIBLE)));
290        assertThat(viewHolder.getAction1Divider().getVisibility(), is(equalTo(View.GONE)));
291
292        viewHolder = getViewHolderAtPosition(2);
293        assertThat(viewHolder.getAction1().getVisibility(), is(equalTo(View.VISIBLE)));
294        assertThat(viewHolder.getAction1Divider().getVisibility(), is(equalTo(View.GONE)));
295        assertThat(viewHolder.getAction2().getVisibility(), is(equalTo(View.VISIBLE)));
296        assertThat(viewHolder.getAction2Divider().getVisibility(), is(equalTo(View.GONE)));
297
298        viewHolder = getViewHolderAtPosition(3);
299        assertThat(viewHolder.getSwitch().getVisibility(), is(equalTo(View.VISIBLE)));
300        assertThat(viewHolder.getSwitchDivider().getVisibility(), is(equalTo(View.GONE)));
301    }
302
303    @Test
304    public void testTextStartMarginMatchesPrimaryActionType() {
305        TextListItem item0 = new TextListItem(mActivity);
306        item0.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon, true);
307
308        TextListItem item1 = new TextListItem(mActivity);
309        item1.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false);
310
311        TextListItem item2 = new TextListItem(mActivity);
312        item2.setPrimaryActionEmptyIcon();
313
314        TextListItem item3 = new TextListItem(mActivity);
315        item3.setPrimaryActionNoIcon();
316
317        List<TextListItem> items = Arrays.asList(item0, item1, item2, item3);
318        List<Integer> expectedStartMargin = Arrays.asList(R.dimen.car_keyline_4,
319                R.dimen.car_keyline_3, R.dimen.car_keyline_3, R.dimen.car_keyline_1);
320        setupPagedListView(items);
321
322        for (int i = 0; i < items.size(); i++) {
323            TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(i);
324
325            int expected = InstrumentationRegistry.getContext().getResources()
326                    .getDimensionPixelSize(expectedStartMargin.get(i));
327            assertThat(((ViewGroup.MarginLayoutParams) viewHolder.getTitle().getLayoutParams())
328                    .getMarginStart(), is(equalTo(expected)));
329            assertThat(((ViewGroup.MarginLayoutParams) viewHolder.getBody().getLayoutParams())
330                    .getMarginStart(), is(equalTo(expected)));
331        }
332    }
333
334    @Test
335    public void testItemWithOnlyTitleIsSingleLine() {
336        // Only space.
337        TextListItem item0 = new TextListItem(mActivity);
338        item0.setTitle(" ");
339
340        // Underscore.
341        TextListItem item1 = new TextListItem(mActivity);
342        item1.setTitle("______");
343
344        TextListItem item2 = new TextListItem(mActivity);
345        item2.setTitle("ALL UPPER CASE");
346
347        // String wouldn't fit in one line.
348        TextListItem item3 = new TextListItem(mActivity);
349        item3.setTitle(InstrumentationRegistry.getContext().getResources().getString(
350                R.string.over_uxr_text_length_limit));
351
352        List<TextListItem> items = Arrays.asList(item0, item1, item2, item3);
353        setupPagedListView(items);
354
355        double singleLineHeight = InstrumentationRegistry.getContext().getResources().getDimension(
356                R.dimen.car_single_line_list_item_height);
357
358        LinearLayoutManager layoutManager =
359                (LinearLayoutManager) mPagedListView.getRecyclerView().getLayoutManager();
360        for (int i = 0; i < items.size(); i++) {
361            assertThat((double) layoutManager.findViewByPosition(i).getHeight(),
362                    is(closeTo(singleLineHeight, 1.0d)));
363        }
364    }
365
366    @Test
367    public void testItemWithBodyTextIsAtLeastDoubleLine() {
368        // Only space.
369        TextListItem item0 = new TextListItem(mActivity);
370        item0.setBody(" ");
371
372        // Underscore.
373        TextListItem item1 = new TextListItem(mActivity);
374        item1.setBody("____");
375
376        // String wouldn't fit in one line.
377        TextListItem item2 = new TextListItem(mActivity);
378        item2.setBody(InstrumentationRegistry.getContext().getResources().getString(
379                R.string.over_uxr_text_length_limit));
380
381        List<TextListItem> items = Arrays.asList(item0, item1, item2);
382        setupPagedListView(items);
383
384        final int doubleLineHeight =
385                (int) InstrumentationRegistry.getContext().getResources().getDimension(
386                        R.dimen.car_double_line_list_item_height);
387
388        LinearLayoutManager layoutManager =
389                (LinearLayoutManager) mPagedListView.getRecyclerView().getLayoutManager();
390        for (int i = 0; i < items.size(); i++) {
391            assertThat(layoutManager.findViewByPosition(i).getHeight(),
392                    is(greaterThanOrEqualTo(doubleLineHeight)));
393        }
394    }
395
396    @Test
397    public void testPrimaryIconDrawable() {
398        Drawable drawable = InstrumentationRegistry.getContext().getResources().getDrawable(
399                android.R.drawable.sym_def_app_icon, null);
400
401        TextListItem item0 = new TextListItem(mActivity);
402        item0.setPrimaryActionIcon(drawable, true);
403
404        List<TextListItem> items = Arrays.asList(item0);
405        setupPagedListView(items);
406
407        assertTrue(getViewHolderAtPosition(0).getPrimaryIcon().getDrawable().getConstantState()
408                .equals(drawable.getConstantState()));
409    }
410
411    @Test
412    public void testLargePrimaryIconHasNoStartMargin() {
413        TextListItem item0 = new TextListItem(mActivity);
414        item0.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon, true);
415
416        List<TextListItem> items = Arrays.asList(item0);
417        setupPagedListView(items);
418
419        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
420        assertThat(((ViewGroup.MarginLayoutParams) viewHolder.getPrimaryIcon().getLayoutParams())
421                .getMarginStart(), is(equalTo(0)));
422    }
423
424    @Test
425    public void testSmallPrimaryIconStartMargin() {
426        TextListItem item0 = new TextListItem(mActivity);
427        item0.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false);
428
429        List<TextListItem> items = Arrays.asList(item0);
430        setupPagedListView(items);
431
432        int expected = InstrumentationRegistry.getContext().getResources().getDimensionPixelSize(
433                R.dimen.car_keyline_1);
434
435        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
436        assertThat(((ViewGroup.MarginLayoutParams) viewHolder.getPrimaryIcon().getLayoutParams())
437                .getMarginStart(), is(equalTo(expected)));
438    }
439
440    @Test
441    public void testSmallPrimaryIconTopMarginRemainsTheSameRegardlessOfTextLength() {
442        final String longText = InstrumentationRegistry.getContext().getResources().getString(
443                R.string.over_uxr_text_length_limit);
444
445        // Single line item.
446        TextListItem item0 = new TextListItem(mActivity);
447        item0.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false);
448        item0.setTitle("one line text");
449
450        // Double line item with one line text.
451        TextListItem item1 = new TextListItem(mActivity);
452        item1.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false);
453        item1.setTitle("one line text");
454        item1.setBody("one line text");
455
456        // Double line item with long text.
457        TextListItem item2 = new TextListItem(mActivity);
458        item2.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false);
459        item2.setTitle("one line text");
460        item2.setBody(longText);
461
462        // Body text only - long text.
463        TextListItem item3 = new TextListItem(mActivity);
464        item3.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false);
465        item3.setBody(longText);
466
467        // Body text only - one line text.
468        TextListItem item4 = new TextListItem(mActivity);
469        item4.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false);
470        item4.setBody("one line text");
471
472        List<TextListItem> items = Arrays.asList(item0, item1, item2, item3, item4);
473        setupPagedListView(items);
474
475        for (int i = 1; i < items.size(); i++) {
476            onView(withId(R.id.recycler_view)).perform(scrollToPosition(i));
477            // Implementation uses integer division so it may be off by 1 vs centered vertically.
478            assertThat((double) getViewHolderAtPosition(i - 1).getPrimaryIcon().getTop(),
479                    is(closeTo(
480                    (double) getViewHolderAtPosition(i).getPrimaryIcon().getTop(), 1.0d)));
481        }
482    }
483
484    @Test
485    public void testClickingPrimaryActionIsSeparateFromSupplementalAction() {
486        final boolean[] clicked = {false, false};
487
488        TextListItem item0 = new TextListItem(mActivity);
489        item0.setOnClickListener(v -> clicked[0] = true);
490        item0.setSupplementalIcon(android.R.drawable.sym_def_app_icon, true,
491                v -> clicked[1] = true);
492
493        List<TextListItem> items = Arrays.asList(item0);
494        setupPagedListView(items);
495
496        onView(withId(R.id.recycler_view)).perform(actionOnItemAtPosition(0, click()));
497        assertTrue(clicked[0]);
498        assertFalse(clicked[1]);
499
500        onView(withId(R.id.recycler_view)).perform(
501                actionOnItemAtPosition(0, clickChildViewWithId(R.id.supplemental_icon)));
502        assertTrue(clicked[1]);
503    }
504
505    @Test
506    public void testClickingSupplementalIcon() {
507        final boolean[] clicked = {false};
508
509        TextListItem item0 = new TextListItem(mActivity);
510        item0.setSupplementalIcon(android.R.drawable.sym_def_app_icon, true,
511                v -> clicked[0] = true);
512
513        List<TextListItem> items = Arrays.asList(item0);
514        setupPagedListView(items);
515
516        onView(withId(R.id.recycler_view)).perform(
517                actionOnItemAtPosition(0, clickChildViewWithId(R.id.supplemental_icon)));
518        assertTrue(clicked[0]);
519    }
520
521    @Test
522    public void testSupplementalIconWithoutClickListenerIsNotClickable() {
523        TextListItem item0 = new TextListItem(mActivity);
524        item0.setSupplementalIcon(android.R.drawable.sym_def_app_icon, true);
525
526        List<TextListItem> items = Arrays.asList(item0);
527        setupPagedListView(items);
528
529        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
530        assertFalse(viewHolder.getSupplementalIcon().isClickable());
531    }
532
533    @Test
534    public void testCheckingSwitch() {
535        final boolean[] clicked = {false, false};
536
537        TextListItem item0 = new TextListItem(mActivity);
538        item0.setSwitch(false, false, (button, isChecked) -> {
539            // Initial value is false.
540            assertTrue(isChecked);
541            clicked[0] = true;
542        });
543
544        TextListItem item1 = new TextListItem(mActivity);
545        item1.setSwitch(true, false, (button, isChecked) -> {
546            // Initial value is true.
547            assertFalse(isChecked);
548            clicked[1] = true;
549        });
550
551        List<TextListItem> items = Arrays.asList(item0, item1);
552        setupPagedListView(items);
553
554        onView(withId(R.id.recycler_view)).perform(
555                actionOnItemAtPosition(0, clickChildViewWithId(R.id.switch_widget)));
556        assertTrue(clicked[0]);
557
558        onView(withId(R.id.recycler_view)).perform(
559                actionOnItemAtPosition(1, clickChildViewWithId(R.id.switch_widget)));
560        assertTrue(clicked[1]);
561    }
562
563    @Test
564    public void testClickingSupplementalAction() {
565        final boolean[] clicked = {false};
566
567        TextListItem item0 = new TextListItem(mActivity);
568        item0.setAction("action", true, v -> clicked[0] = true);
569
570        List<TextListItem> items = Arrays.asList(item0);
571        setupPagedListView(items);
572
573        onView(withId(R.id.recycler_view)).perform(
574                actionOnItemAtPosition(0, clickChildViewWithId(R.id.action1)));
575        assertTrue(clicked[0]);
576    }
577
578    @Test
579    public void testClickingBothSupplementalActions() {
580        final boolean[] clicked = {false, false};
581
582        TextListItem item0 = new TextListItem(mActivity);
583        item0.setActions("action 1", true, v -> clicked[0] = true,
584                        "action 2", true, v -> clicked[1] = true);
585
586        List<TextListItem> items = Arrays.asList(item0);
587        setupPagedListView(items);
588
589        onView(withId(R.id.recycler_view)).perform(
590                actionOnItemAtPosition(0, clickChildViewWithId(R.id.action1)));
591        assertTrue(clicked[0]);
592        assertFalse(clicked[1]);
593
594        onView(withId(R.id.recycler_view)).perform(
595                actionOnItemAtPosition(0, clickChildViewWithId(R.id.action2)));
596        assertTrue(clicked[1]);
597    }
598
599    @Test
600    public void testCustomViewBinderBindsLast() {
601        final String updatedTitle = "updated title";
602
603        TextListItem item0 = new TextListItem(mActivity);
604        item0.setTitle("original title");
605        item0.addViewBinder((viewHolder) -> viewHolder.getTitle().setText(updatedTitle));
606
607        List<TextListItem> items = Arrays.asList(item0);
608        setupPagedListView(items);
609
610        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
611        assertThat(viewHolder.getTitle().getText(), is(equalTo(updatedTitle)));
612    }
613
614    @Test
615    public void testCustomViewBinderOnUnusedViewsHasNoEffect() {
616        TextListItem item0 = new TextListItem(mActivity);
617        item0.addViewBinder((viewHolder) -> viewHolder.getBody().setText("text"));
618
619        List<TextListItem> items = Arrays.asList(item0);
620        setupPagedListView(items);
621
622        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
623        assertThat(viewHolder.getBody().getVisibility(), is(equalTo(View.GONE)));
624        // Custom binder interacts with body but has no effect.
625        // Expect card height to remain single line.
626        assertThat((double) viewHolder.itemView.getHeight(), is(closeTo(
627                InstrumentationRegistry.getContext().getResources().getDimension(
628                        R.dimen.car_single_line_list_item_height), 1.0d)));
629    }
630
631    @Test
632    public void testRevertingViewBinder() throws Throwable {
633        TextListItem item0 = new TextListItem(mActivity);
634        item0.setBody("one item");
635        item0.addViewBinder(
636                (viewHolder) -> viewHolder.getBody().setEllipsize(TextUtils.TruncateAt.END),
637                (viewHolder -> viewHolder.getBody().setEllipsize(null)));
638
639        List<TextListItem> items = Arrays.asList(item0);
640        setupPagedListView(items);
641
642        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
643
644        // Bind view holder to a new item - the customization made by item0 should be reverted.
645        TextListItem item1 = new TextListItem(mActivity);
646        item1.setBody("new item");
647        mActivityRule.runOnUiThread(() -> item1.bind(viewHolder));
648
649        assertThat(viewHolder.getBody().getEllipsize(), is(equalTo(null)));
650    }
651
652    @Test
653    public void testRemovingViewBinder() {
654        TextListItem item0 = new TextListItem(mActivity);
655        item0.setBody("one item");
656        ListItem.ViewBinder<TextListItem.ViewHolder> binder =
657                (viewHolder) -> viewHolder.getTitle().setEllipsize(TextUtils.TruncateAt.END);
658        item0.addViewBinder(binder);
659
660        assertTrue(item0.removeViewBinder(binder));
661
662        List<TextListItem> items = Arrays.asList(item0);
663        setupPagedListView(items);
664
665        assertThat(getViewHolderAtPosition(0).getBody().getEllipsize(), is(equalTo(null)));
666    }
667
668    @Test
669    public void testSettingTitleOrBodyAsPrimaryText() {
670        // Create 2 items, one with Title as primary (default) and one with Body.
671        // The primary text, regardless of view, should have consistent look (as primary).
672        TextListItem item0 = new TextListItem(mActivity);
673        item0.setTitle("title");
674        item0.setBody("body");
675
676        TextListItem item1 = new TextListItem(mActivity);
677        item1.setTitle("title");
678        item1.setBody("body", true);
679
680        List<TextListItem> items = Arrays.asList(item0, item1);
681        setupPagedListView(items);
682
683        TextListItem.ViewHolder titlePrimary = getViewHolderAtPosition(0);
684        TextListItem.ViewHolder bodyPrimary = getViewHolderAtPosition(1);
685        assertThat(titlePrimary.getTitle().getTextSize(),
686                is(equalTo(bodyPrimary.getBody().getTextSize())));
687        assertThat(titlePrimary.getTitle().getTextColors(),
688                is(equalTo(bodyPrimary.getBody().getTextColors())));
689    }
690
691    @Test
692    public void testNoCarriedOverLayoutParamsForTextView() throws Throwable {
693        TextListItem singleLine = new TextListItem(mActivity);
694        singleLine.setTitle("title");
695
696        setupPagedListView(Arrays.asList(singleLine));
697
698        // Manually rebind the view holder of a single line item to a double line item.
699        TextListItem doubleLine = new TextListItem(mActivity);
700        doubleLine.setTitle("title");
701        doubleLine.setBody("body");
702        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
703        mActivityRule.runOnUiThread(() -> doubleLine.bind(viewHolder));
704
705        RelativeLayout.LayoutParams titleLayoutParams =
706                (RelativeLayout.LayoutParams) viewHolder.getTitle().getLayoutParams();
707        RelativeLayout.LayoutParams bodyLayoutParams =
708                (RelativeLayout.LayoutParams) viewHolder.getTitle().getLayoutParams();
709        assertThat(titleLayoutParams.getRule(RelativeLayout.CENTER_VERTICAL), is(equalTo(0)));
710        assertThat(bodyLayoutParams.getRule(RelativeLayout.CENTER_VERTICAL), is(equalTo(0)));
711    }
712
713    @Test
714    public void testNoCarriedOverLayoutParamsForPrimaryIcon() throws Throwable {
715        TextListItem smallIcon = new TextListItem(mActivity);
716        smallIcon.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false);
717        smallIcon.setBody("body");  // Small icon of items with body text should use top margin.
718        setupPagedListView(Arrays.asList(smallIcon));
719
720        // Manually rebind the view holder.
721        TextListItem largeIcon = new TextListItem(mActivity);
722        largeIcon.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon, true);
723        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
724        mActivityRule.runOnUiThread(() -> largeIcon.bind(viewHolder));
725
726        RelativeLayout.LayoutParams iconLayoutParams =
727                (RelativeLayout.LayoutParams) viewHolder.getPrimaryIcon().getLayoutParams();
728        assertThat(iconLayoutParams.getRule(RelativeLayout.CENTER_VERTICAL),
729                is(equalTo(RelativeLayout.TRUE)));
730        assertThat(iconLayoutParams.topMargin, is(equalTo(0)));
731    }
732
733    @Test
734    public void testNoCarriedOverOnClickListener() throws Throwable {
735        boolean[] clicked = new boolean[] {false};
736        TextListItem item0 = new TextListItem(mActivity);
737        item0.setOnClickListener(v -> clicked[0] = true);
738
739        setupPagedListView(Arrays.asList(item0));
740
741        onView(withId(R.id.recycler_view)).perform(actionOnItemAtPosition(0, click()));
742        assertTrue(clicked[0]);
743
744        // item1 does not have onClickListener.
745        TextListItem item1 = new TextListItem(mActivity);
746        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
747        // Manually rebind the view holder.
748        mActivityRule.runOnUiThread(() -> item1.bind(viewHolder));
749
750        // Reset for testing.
751        clicked[0] = false;
752        onView(withId(R.id.recycler_view)).perform(actionOnItemAtPosition(0, click()));
753        assertFalse(clicked[0]);
754    }
755
756    @Test
757    public void testUpdateItem() {
758        TextListItem item = new TextListItem(mActivity);
759        setupPagedListView(Arrays.asList(item));
760
761        String title = "updated title";
762        item.setTitle(title);
763
764        refreshUi();
765
766        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
767        assertThat(viewHolder.getTitle().getText(), is(equalTo(title)));
768    }
769
770    @Test
771    public void testUxRestrictionsChange() {
772        String longText = mActivity.getString(R.string.over_uxr_text_length_limit);
773        TextListItem item = new TextListItem(mActivity);
774        item.setBody(longText);
775
776        setupPagedListView(Arrays.asList(item));
777
778        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
779        // Default behavior without UXR is unrestricted.
780        assertThat(viewHolder.getBody().getText(), is(equalTo(longText)));
781
782        viewHolder.complyWithUxRestrictions(CarUxRestrictionsTestUtils.getFullyRestricted());
783        refreshUi();
784
785        // Verify that the body text length is limited.
786        assertThat(viewHolder.getBody().getText().length(), is(lessThan(longText.length())));
787    }
788
789    @Test
790    public void testUxRestrictionsChangesDoNotAlterExistingInputFilters() {
791        InputFilter filter = new InputFilter.AllCaps(Locale.US);
792        String bodyText = "bodytext";
793        TextListItem item = new TextListItem(mActivity);
794        item.setBody(bodyText);
795        item.addViewBinder(vh -> vh.getBody().setFilters(new InputFilter[] {filter}));
796
797        setupPagedListView(Arrays.asList(item));
798
799        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
800
801        // Toggle UX restrictions between fully restricted and unrestricted should not affect
802        // existing filters.
803        viewHolder.complyWithUxRestrictions(CarUxRestrictionsTestUtils.getFullyRestricted());
804        refreshUi();
805        assertTrue(Arrays.asList(viewHolder.getBody().getFilters()).contains(filter));
806
807        viewHolder.complyWithUxRestrictions(CarUxRestrictionsTestUtils.getBaseline());
808        refreshUi();
809        assertTrue(Arrays.asList(viewHolder.getBody().getFilters()).contains(filter));
810    }
811
812    private static ViewAction clickChildViewWithId(final int id) {
813        return new ViewAction() {
814            @Override
815            public Matcher<View> getConstraints() {
816                return null;
817            }
818
819            @Override
820            public String getDescription() {
821                return "Click on a child view with specific id.";
822            }
823
824            @Override
825            public void perform(UiController uiController, View view) {
826                View v = view.findViewById(id);
827                v.performClick();
828            }
829        };
830    }
831}
832