TextListItemTest.java revision 65ec7f1338516c11ea1442912a22d50033fe8fb8
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 testSetSupplementalActionWithDrawable() {
216        Drawable drawable = mActivity.getDrawable(android.R.drawable.sym_def_app_icon);
217        TextListItem item = new TextListItem(mActivity);
218        item.setSupplementalIcon(drawable, true);
219
220        setupPagedListView(Arrays.asList(item));
221
222        assertThat(getViewHolderAtPosition(0).getSupplementalIcon().getDrawable(),
223                is(equalTo(drawable)));
224    }
225
226    @Test
227    public void testSwitchVisibleAndCheckedState() {
228        TextListItem item0 = new TextListItem(mActivity);
229        item0.setSwitch(true, true, null);
230
231        TextListItem item1 = new TextListItem(mActivity);
232        item1.setSwitch(false, true, null);
233
234        List<TextListItem> items = Arrays.asList(item0, item1);
235        setupPagedListView(items);
236
237        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
238        assertThat(viewHolder.getSwitch().getVisibility(), is(equalTo(View.VISIBLE)));
239        assertThat(viewHolder.getSwitch().isChecked(), is(equalTo(true)));
240        assertThat(viewHolder.getSwitchDivider().getVisibility(), is(equalTo(View.VISIBLE)));
241
242        viewHolder = getViewHolderAtPosition(1);
243        assertThat(viewHolder.getSwitch().getVisibility(), is(equalTo(View.VISIBLE)));
244        assertThat(viewHolder.getSwitch().isChecked(), is(equalTo(false)));
245        assertThat(viewHolder.getSwitchDivider().getVisibility(), is(equalTo(View.VISIBLE)));
246    }
247
248    @Test
249    public void testSetSwitchState() {
250        TextListItem item0 = new TextListItem(mActivity);
251        item0.setSwitch(true, true, null);
252
253        setupPagedListView(Arrays.asList(item0));
254
255        item0.setSwitchState(false);
256
257        refreshUi();
258
259        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
260        assertThat(viewHolder.getSwitch().getVisibility(), is(equalTo(View.VISIBLE)));
261        assertThat(viewHolder.getSwitch().isChecked(), is(equalTo(false)));
262    }
263
264    @Test
265    public void testSetSwitchStateHasNoEffectIfSwitchIsNotEnabled() {
266        TextListItem item0 = new TextListItem(mActivity);
267        setupPagedListView(Arrays.asList(item0));
268
269        item0.setSwitchState(false);
270
271        refreshUi();
272
273        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
274        assertThat(viewHolder.getSwitch().getVisibility(), is(not(equalTo(View.VISIBLE))));
275    }
276
277    @Test
278    public void testDividersAreOptional() {
279        TextListItem item0 = new TextListItem(mActivity);
280        item0.setSupplementalIcon(android.R.drawable.sym_def_app_icon, false);
281
282        TextListItem item1 = new TextListItem(mActivity);
283        item1.setAction("text", false, v -> { /* Do nothing. */ });
284
285        TextListItem item2 = new TextListItem(mActivity);
286        item2.setActions("text", false, v -> { /* Do nothing. */ },
287                "text", false, v -> { /* Do nothing. */ });
288
289        TextListItem item3 = new TextListItem(mActivity);
290        item3.setSwitch(true, false, null);
291
292        List<TextListItem> items = Arrays.asList(item0, item1, item2, item3);
293        setupPagedListView(items);
294
295        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
296        assertThat(viewHolder.getSupplementalIcon().getVisibility(), is(equalTo(View.VISIBLE)));
297        assertThat(viewHolder.getSupplementalIconDivider().getVisibility(),
298                is(equalTo(View.GONE)));
299
300        viewHolder = getViewHolderAtPosition(1);
301        assertThat(viewHolder.getAction1().getVisibility(), is(equalTo(View.VISIBLE)));
302        assertThat(viewHolder.getAction1Divider().getVisibility(), is(equalTo(View.GONE)));
303
304        viewHolder = getViewHolderAtPosition(2);
305        assertThat(viewHolder.getAction1().getVisibility(), is(equalTo(View.VISIBLE)));
306        assertThat(viewHolder.getAction1Divider().getVisibility(), is(equalTo(View.GONE)));
307        assertThat(viewHolder.getAction2().getVisibility(), is(equalTo(View.VISIBLE)));
308        assertThat(viewHolder.getAction2Divider().getVisibility(), is(equalTo(View.GONE)));
309
310        viewHolder = getViewHolderAtPosition(3);
311        assertThat(viewHolder.getSwitch().getVisibility(), is(equalTo(View.VISIBLE)));
312        assertThat(viewHolder.getSwitchDivider().getVisibility(), is(equalTo(View.GONE)));
313    }
314
315    @Test
316    public void testTextStartMarginMatchesPrimaryActionType() {
317        TextListItem item0 = new TextListItem(mActivity);
318        item0.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon, true);
319
320        TextListItem item1 = new TextListItem(mActivity);
321        item1.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false);
322
323        TextListItem item2 = new TextListItem(mActivity);
324        item2.setPrimaryActionEmptyIcon();
325
326        TextListItem item3 = new TextListItem(mActivity);
327        item3.setPrimaryActionNoIcon();
328
329        List<TextListItem> items = Arrays.asList(item0, item1, item2, item3);
330        List<Integer> expectedStartMargin = Arrays.asList(R.dimen.car_keyline_4,
331                R.dimen.car_keyline_3, R.dimen.car_keyline_3, R.dimen.car_keyline_1);
332        setupPagedListView(items);
333
334        for (int i = 0; i < items.size(); i++) {
335            TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(i);
336
337            int expected = InstrumentationRegistry.getContext().getResources()
338                    .getDimensionPixelSize(expectedStartMargin.get(i));
339            assertThat(((ViewGroup.MarginLayoutParams) viewHolder.getTitle().getLayoutParams())
340                    .getMarginStart(), is(equalTo(expected)));
341            assertThat(((ViewGroup.MarginLayoutParams) viewHolder.getBody().getLayoutParams())
342                    .getMarginStart(), is(equalTo(expected)));
343        }
344    }
345
346    @Test
347    public void testItemWithOnlyTitleIsSingleLine() {
348        // Only space.
349        TextListItem item0 = new TextListItem(mActivity);
350        item0.setTitle(" ");
351
352        // Underscore.
353        TextListItem item1 = new TextListItem(mActivity);
354        item1.setTitle("______");
355
356        TextListItem item2 = new TextListItem(mActivity);
357        item2.setTitle("ALL UPPER CASE");
358
359        // String wouldn't fit in one line.
360        TextListItem item3 = new TextListItem(mActivity);
361        item3.setTitle(InstrumentationRegistry.getContext().getResources().getString(
362                R.string.over_uxr_text_length_limit));
363
364        List<TextListItem> items = Arrays.asList(item0, item1, item2, item3);
365        setupPagedListView(items);
366
367        double singleLineHeight = InstrumentationRegistry.getContext().getResources().getDimension(
368                R.dimen.car_single_line_list_item_height);
369
370        LinearLayoutManager layoutManager =
371                (LinearLayoutManager) mPagedListView.getRecyclerView().getLayoutManager();
372        for (int i = 0; i < items.size(); i++) {
373            assertThat((double) layoutManager.findViewByPosition(i).getHeight(),
374                    is(closeTo(singleLineHeight, 1.0d)));
375        }
376    }
377
378    @Test
379    public void testItemWithBodyTextIsAtLeastDoubleLine() {
380        // Only space.
381        TextListItem item0 = new TextListItem(mActivity);
382        item0.setBody(" ");
383
384        // Underscore.
385        TextListItem item1 = new TextListItem(mActivity);
386        item1.setBody("____");
387
388        // String wouldn't fit in one line.
389        TextListItem item2 = new TextListItem(mActivity);
390        item2.setBody(InstrumentationRegistry.getContext().getResources().getString(
391                R.string.over_uxr_text_length_limit));
392
393        List<TextListItem> items = Arrays.asList(item0, item1, item2);
394        setupPagedListView(items);
395
396        final int doubleLineHeight =
397                (int) InstrumentationRegistry.getContext().getResources().getDimension(
398                        R.dimen.car_double_line_list_item_height);
399
400        LinearLayoutManager layoutManager =
401                (LinearLayoutManager) mPagedListView.getRecyclerView().getLayoutManager();
402        for (int i = 0; i < items.size(); i++) {
403            assertThat(layoutManager.findViewByPosition(i).getHeight(),
404                    is(greaterThanOrEqualTo(doubleLineHeight)));
405        }
406    }
407
408    @Test
409    public void testPrimaryIconDrawable() {
410        Drawable drawable = InstrumentationRegistry.getContext().getResources().getDrawable(
411                android.R.drawable.sym_def_app_icon, null);
412
413        TextListItem item0 = new TextListItem(mActivity);
414        item0.setPrimaryActionIcon(drawable, true);
415
416        List<TextListItem> items = Arrays.asList(item0);
417        setupPagedListView(items);
418
419        assertTrue(getViewHolderAtPosition(0).getPrimaryIcon().getDrawable().getConstantState()
420                .equals(drawable.getConstantState()));
421    }
422
423    @Test
424    public void testLargePrimaryIconHasNoStartMargin() {
425        TextListItem item0 = new TextListItem(mActivity);
426        item0.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon, true);
427
428        List<TextListItem> items = Arrays.asList(item0);
429        setupPagedListView(items);
430
431        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
432        assertThat(((ViewGroup.MarginLayoutParams) viewHolder.getPrimaryIcon().getLayoutParams())
433                .getMarginStart(), is(equalTo(0)));
434    }
435
436    @Test
437    public void testSmallPrimaryIconStartMargin() {
438        TextListItem item0 = new TextListItem(mActivity);
439        item0.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false);
440
441        List<TextListItem> items = Arrays.asList(item0);
442        setupPagedListView(items);
443
444        int expected = InstrumentationRegistry.getContext().getResources().getDimensionPixelSize(
445                R.dimen.car_keyline_1);
446
447        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
448        assertThat(((ViewGroup.MarginLayoutParams) viewHolder.getPrimaryIcon().getLayoutParams())
449                .getMarginStart(), is(equalTo(expected)));
450    }
451
452    @Test
453    public void testSmallPrimaryIconTopMarginRemainsTheSameRegardlessOfTextLength() {
454        final String longText = InstrumentationRegistry.getContext().getResources().getString(
455                R.string.over_uxr_text_length_limit);
456
457        // Single line item.
458        TextListItem item0 = new TextListItem(mActivity);
459        item0.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false);
460        item0.setTitle("one line text");
461
462        // Double line item with one line text.
463        TextListItem item1 = new TextListItem(mActivity);
464        item1.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false);
465        item1.setTitle("one line text");
466        item1.setBody("one line text");
467
468        // Double line item with long text.
469        TextListItem item2 = new TextListItem(mActivity);
470        item2.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false);
471        item2.setTitle("one line text");
472        item2.setBody(longText);
473
474        // Body text only - long text.
475        TextListItem item3 = new TextListItem(mActivity);
476        item3.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false);
477        item3.setBody(longText);
478
479        // Body text only - one line text.
480        TextListItem item4 = new TextListItem(mActivity);
481        item4.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false);
482        item4.setBody("one line text");
483
484        List<TextListItem> items = Arrays.asList(item0, item1, item2, item3, item4);
485        setupPagedListView(items);
486
487        for (int i = 1; i < items.size(); i++) {
488            onView(withId(R.id.recycler_view)).perform(scrollToPosition(i));
489            // Implementation uses integer division so it may be off by 1 vs centered vertically.
490            assertThat((double) getViewHolderAtPosition(i - 1).getPrimaryIcon().getTop(),
491                    is(closeTo(
492                    (double) getViewHolderAtPosition(i).getPrimaryIcon().getTop(), 1.0d)));
493        }
494    }
495
496    @Test
497    public void testClickingPrimaryActionIsSeparateFromSupplementalAction() {
498        final boolean[] clicked = {false, false};
499
500        TextListItem item0 = new TextListItem(mActivity);
501        item0.setOnClickListener(v -> clicked[0] = true);
502        item0.setSupplementalIcon(android.R.drawable.sym_def_app_icon, true,
503                v -> clicked[1] = true);
504
505        List<TextListItem> items = Arrays.asList(item0);
506        setupPagedListView(items);
507
508        onView(withId(R.id.recycler_view)).perform(actionOnItemAtPosition(0, click()));
509        assertTrue(clicked[0]);
510        assertFalse(clicked[1]);
511
512        onView(withId(R.id.recycler_view)).perform(
513                actionOnItemAtPosition(0, clickChildViewWithId(R.id.supplemental_icon)));
514        assertTrue(clicked[1]);
515    }
516
517    @Test
518    public void testClickingSupplementalIcon() {
519        final boolean[] clicked = {false};
520
521        TextListItem item0 = new TextListItem(mActivity);
522        item0.setSupplementalIcon(android.R.drawable.sym_def_app_icon, true,
523                v -> clicked[0] = true);
524
525        List<TextListItem> items = Arrays.asList(item0);
526        setupPagedListView(items);
527
528        onView(withId(R.id.recycler_view)).perform(
529                actionOnItemAtPosition(0, clickChildViewWithId(R.id.supplemental_icon)));
530        assertTrue(clicked[0]);
531    }
532
533    @Test
534    public void testSupplementalIconWithoutClickListenerIsNotClickable() {
535        TextListItem item0 = new TextListItem(mActivity);
536        item0.setSupplementalIcon(android.R.drawable.sym_def_app_icon, true);
537
538        List<TextListItem> items = Arrays.asList(item0);
539        setupPagedListView(items);
540
541        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
542        assertFalse(viewHolder.getSupplementalIcon().isClickable());
543    }
544
545    @Test
546    public void testCheckingSwitch() {
547        final boolean[] clicked = {false, false};
548
549        TextListItem item0 = new TextListItem(mActivity);
550        item0.setSwitch(false, false, (button, isChecked) -> {
551            // Initial value is false.
552            assertTrue(isChecked);
553            clicked[0] = true;
554        });
555
556        TextListItem item1 = new TextListItem(mActivity);
557        item1.setSwitch(true, false, (button, isChecked) -> {
558            // Initial value is true.
559            assertFalse(isChecked);
560            clicked[1] = true;
561        });
562
563        List<TextListItem> items = Arrays.asList(item0, item1);
564        setupPagedListView(items);
565
566        onView(withId(R.id.recycler_view)).perform(
567                actionOnItemAtPosition(0, clickChildViewWithId(R.id.switch_widget)));
568        assertTrue(clicked[0]);
569
570        onView(withId(R.id.recycler_view)).perform(
571                actionOnItemAtPosition(1, clickChildViewWithId(R.id.switch_widget)));
572        assertTrue(clicked[1]);
573    }
574
575    @Test
576    public void testClickingSupplementalAction() {
577        final boolean[] clicked = {false};
578
579        TextListItem item0 = new TextListItem(mActivity);
580        item0.setAction("action", true, v -> clicked[0] = true);
581
582        List<TextListItem> items = Arrays.asList(item0);
583        setupPagedListView(items);
584
585        onView(withId(R.id.recycler_view)).perform(
586                actionOnItemAtPosition(0, clickChildViewWithId(R.id.action1)));
587        assertTrue(clicked[0]);
588    }
589
590    @Test
591    public void testClickingBothSupplementalActions() {
592        final boolean[] clicked = {false, false};
593
594        TextListItem item0 = new TextListItem(mActivity);
595        item0.setActions("action 1", true, v -> clicked[0] = true,
596                        "action 2", true, v -> clicked[1] = true);
597
598        List<TextListItem> items = Arrays.asList(item0);
599        setupPagedListView(items);
600
601        onView(withId(R.id.recycler_view)).perform(
602                actionOnItemAtPosition(0, clickChildViewWithId(R.id.action1)));
603        assertTrue(clicked[0]);
604        assertFalse(clicked[1]);
605
606        onView(withId(R.id.recycler_view)).perform(
607                actionOnItemAtPosition(0, clickChildViewWithId(R.id.action2)));
608        assertTrue(clicked[1]);
609    }
610
611    @Test
612    public void testCustomViewBinderBindsLast() {
613        final String updatedTitle = "updated title";
614
615        TextListItem item0 = new TextListItem(mActivity);
616        item0.setTitle("original title");
617        item0.addViewBinder((viewHolder) -> viewHolder.getTitle().setText(updatedTitle));
618
619        List<TextListItem> items = Arrays.asList(item0);
620        setupPagedListView(items);
621
622        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
623        assertThat(viewHolder.getTitle().getText(), is(equalTo(updatedTitle)));
624    }
625
626    @Test
627    public void testCustomViewBinderOnUnusedViewsHasNoEffect() {
628        TextListItem item0 = new TextListItem(mActivity);
629        item0.addViewBinder((viewHolder) -> viewHolder.getBody().setText("text"));
630
631        List<TextListItem> items = Arrays.asList(item0);
632        setupPagedListView(items);
633
634        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
635        assertThat(viewHolder.getBody().getVisibility(), is(equalTo(View.GONE)));
636        // Custom binder interacts with body but has no effect.
637        // Expect card height to remain single line.
638        assertThat((double) viewHolder.itemView.getHeight(), is(closeTo(
639                InstrumentationRegistry.getContext().getResources().getDimension(
640                        R.dimen.car_single_line_list_item_height), 1.0d)));
641    }
642
643    @Test
644    public void testRevertingViewBinder() throws Throwable {
645        TextListItem item0 = new TextListItem(mActivity);
646        item0.setBody("one item");
647        item0.addViewBinder(
648                (viewHolder) -> viewHolder.getBody().setEllipsize(TextUtils.TruncateAt.END),
649                (viewHolder -> viewHolder.getBody().setEllipsize(null)));
650
651        List<TextListItem> items = Arrays.asList(item0);
652        setupPagedListView(items);
653
654        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
655
656        // Bind view holder to a new item - the customization made by item0 should be reverted.
657        TextListItem item1 = new TextListItem(mActivity);
658        item1.setBody("new item");
659        mActivityRule.runOnUiThread(() -> item1.bind(viewHolder));
660
661        assertThat(viewHolder.getBody().getEllipsize(), is(equalTo(null)));
662    }
663
664    @Test
665    public void testRemovingViewBinder() {
666        TextListItem item0 = new TextListItem(mActivity);
667        item0.setBody("one item");
668        ListItem.ViewBinder<TextListItem.ViewHolder> binder =
669                (viewHolder) -> viewHolder.getTitle().setEllipsize(TextUtils.TruncateAt.END);
670        item0.addViewBinder(binder);
671
672        assertTrue(item0.removeViewBinder(binder));
673
674        List<TextListItem> items = Arrays.asList(item0);
675        setupPagedListView(items);
676
677        assertThat(getViewHolderAtPosition(0).getBody().getEllipsize(), is(equalTo(null)));
678    }
679
680    @Test
681    public void testSettingTitleOrBodyAsPrimaryText() {
682        // Create 2 items, one with Title as primary (default) and one with Body.
683        // The primary text, regardless of view, should have consistent look (as primary).
684        TextListItem item0 = new TextListItem(mActivity);
685        item0.setTitle("title");
686        item0.setBody("body");
687
688        TextListItem item1 = new TextListItem(mActivity);
689        item1.setTitle("title");
690        item1.setBody("body", true);
691
692        List<TextListItem> items = Arrays.asList(item0, item1);
693        setupPagedListView(items);
694
695        TextListItem.ViewHolder titlePrimary = getViewHolderAtPosition(0);
696        TextListItem.ViewHolder bodyPrimary = getViewHolderAtPosition(1);
697        assertThat(titlePrimary.getTitle().getTextSize(),
698                is(equalTo(bodyPrimary.getBody().getTextSize())));
699        assertThat(titlePrimary.getTitle().getTextColors(),
700                is(equalTo(bodyPrimary.getBody().getTextColors())));
701    }
702
703    @Test
704    public void testNoCarriedOverLayoutParamsForTextView() throws Throwable {
705        TextListItem singleLine = new TextListItem(mActivity);
706        singleLine.setTitle("title");
707
708        setupPagedListView(Arrays.asList(singleLine));
709
710        // Manually rebind the view holder of a single line item to a double line item.
711        TextListItem doubleLine = new TextListItem(mActivity);
712        doubleLine.setTitle("title");
713        doubleLine.setBody("body");
714        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
715        mActivityRule.runOnUiThread(() -> doubleLine.bind(viewHolder));
716
717        RelativeLayout.LayoutParams titleLayoutParams =
718                (RelativeLayout.LayoutParams) viewHolder.getTitle().getLayoutParams();
719        RelativeLayout.LayoutParams bodyLayoutParams =
720                (RelativeLayout.LayoutParams) viewHolder.getTitle().getLayoutParams();
721        assertThat(titleLayoutParams.getRule(RelativeLayout.CENTER_VERTICAL), is(equalTo(0)));
722        assertThat(bodyLayoutParams.getRule(RelativeLayout.CENTER_VERTICAL), is(equalTo(0)));
723    }
724
725    @Test
726    public void testNoCarriedOverLayoutParamsForPrimaryIcon() throws Throwable {
727        TextListItem smallIcon = new TextListItem(mActivity);
728        smallIcon.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false);
729        smallIcon.setBody("body");  // Small icon of items with body text should use top margin.
730        setupPagedListView(Arrays.asList(smallIcon));
731
732        // Manually rebind the view holder.
733        TextListItem largeIcon = new TextListItem(mActivity);
734        largeIcon.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon, true);
735        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
736        mActivityRule.runOnUiThread(() -> largeIcon.bind(viewHolder));
737
738        RelativeLayout.LayoutParams iconLayoutParams =
739                (RelativeLayout.LayoutParams) viewHolder.getPrimaryIcon().getLayoutParams();
740        assertThat(iconLayoutParams.getRule(RelativeLayout.CENTER_VERTICAL),
741                is(equalTo(RelativeLayout.TRUE)));
742        assertThat(iconLayoutParams.topMargin, is(equalTo(0)));
743    }
744
745    @Test
746    public void testNoCarriedOverOnClickListener() throws Throwable {
747        boolean[] clicked = new boolean[] {false};
748        TextListItem item0 = new TextListItem(mActivity);
749        item0.setOnClickListener(v -> clicked[0] = true);
750
751        setupPagedListView(Arrays.asList(item0));
752
753        onView(withId(R.id.recycler_view)).perform(actionOnItemAtPosition(0, click()));
754        assertTrue(clicked[0]);
755
756        // item1 does not have onClickListener.
757        TextListItem item1 = new TextListItem(mActivity);
758        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
759        // Manually rebind the view holder.
760        mActivityRule.runOnUiThread(() -> item1.bind(viewHolder));
761
762        // Reset for testing.
763        clicked[0] = false;
764        onView(withId(R.id.recycler_view)).perform(actionOnItemAtPosition(0, click()));
765        assertFalse(clicked[0]);
766    }
767
768    @Test
769    public void testUpdateItem() {
770        TextListItem item = new TextListItem(mActivity);
771        setupPagedListView(Arrays.asList(item));
772
773        String title = "updated title";
774        item.setTitle(title);
775
776        refreshUi();
777
778        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
779        assertThat(viewHolder.getTitle().getText(), is(equalTo(title)));
780    }
781
782    @Test
783    public void testUxRestrictionsChange() {
784        String longText = mActivity.getString(R.string.over_uxr_text_length_limit);
785        TextListItem item = new TextListItem(mActivity);
786        item.setBody(longText);
787
788        setupPagedListView(Arrays.asList(item));
789
790        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
791        // Default behavior without UXR is unrestricted.
792        assertThat(viewHolder.getBody().getText(), is(equalTo(longText)));
793
794        viewHolder.complyWithUxRestrictions(CarUxRestrictionsTestUtils.getFullyRestricted());
795        refreshUi();
796
797        // Verify that the body text length is limited.
798        assertThat(viewHolder.getBody().getText().length(), is(lessThan(longText.length())));
799    }
800
801    @Test
802    public void testUxRestrictionsChangesDoNotAlterExistingInputFilters() {
803        InputFilter filter = new InputFilter.AllCaps(Locale.US);
804        String bodyText = "bodytext";
805        TextListItem item = new TextListItem(mActivity);
806        item.setBody(bodyText);
807        item.addViewBinder(vh -> vh.getBody().setFilters(new InputFilter[] {filter}));
808
809        setupPagedListView(Arrays.asList(item));
810
811        TextListItem.ViewHolder viewHolder = getViewHolderAtPosition(0);
812
813        // Toggle UX restrictions between fully restricted and unrestricted should not affect
814        // existing filters.
815        viewHolder.complyWithUxRestrictions(CarUxRestrictionsTestUtils.getFullyRestricted());
816        refreshUi();
817        assertTrue(Arrays.asList(viewHolder.getBody().getFilters()).contains(filter));
818
819        viewHolder.complyWithUxRestrictions(CarUxRestrictionsTestUtils.getBaseline());
820        refreshUi();
821        assertTrue(Arrays.asList(viewHolder.getBody().getFilters()).contains(filter));
822    }
823
824    private static ViewAction clickChildViewWithId(final int id) {
825        return new ViewAction() {
826            @Override
827            public Matcher<View> getConstraints() {
828                return null;
829            }
830
831            @Override
832            public String getDescription() {
833                return "Click on a child view with specific id.";
834            }
835
836            @Override
837            public void perform(UiController uiController, View view) {
838                View v = view.findViewById(id);
839                v.performClick();
840            }
841        };
842    }
843}
844