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