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