CustomSnackbarTest.java revision fa0f82f629bf95681c14ed559922f77a3030aa18
1/*
2 * Copyright (C) 2016 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 android.support.design.widget;
18
19import static android.support.design.testutils.TestUtilsActions.setLayoutDirection;
20import static android.support.test.espresso.Espresso.onView;
21import static android.support.test.espresso.action.ViewActions.swipeLeft;
22import static android.support.test.espresso.action.ViewActions.swipeRight;
23import static android.support.test.espresso.assertion.ViewAssertions.matches;
24import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
25import static android.support.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
26import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA;
27import static android.support.test.espresso.matcher.ViewMatchers.withId;
28import static android.support.test.espresso.matcher.ViewMatchers.withText;
29
30import static org.hamcrest.core.AllOf.allOf;
31import static org.junit.Assert.assertFalse;
32import static org.junit.Assert.assertTrue;
33import static org.mockito.Mockito.mock;
34import static org.mockito.Mockito.times;
35import static org.mockito.Mockito.verify;
36import static org.mockito.Mockito.verifyNoMoreInteractions;
37
38import android.support.annotation.Nullable;
39import android.support.design.test.R;
40import android.support.design.testutils.SnackbarUtils;
41import android.support.test.espresso.ViewAction;
42import android.support.test.espresso.ViewInteraction;
43import android.support.test.filters.MediumTest;
44import android.support.test.filters.SmallTest;
45import android.support.v4.view.ViewCompat;
46import android.view.LayoutInflater;
47
48import org.junit.Before;
49import org.junit.Test;
50
51public class CustomSnackbarTest extends BaseInstrumentationTestCase<SnackbarActivity> {
52    private static final String TITLE_TEXT = "Test title";
53    private static final String SUBTITLE_TEXT = "Test subtitle";
54
55    private CoordinatorLayout mCoordinatorLayout;
56
57    private interface DismissAction {
58        void dismiss(CustomSnackbar snackbar);
59    }
60
61    public CustomSnackbarTest() {
62        super(SnackbarActivity.class);
63    }
64
65    @Before
66    public void setup() {
67        mCoordinatorLayout =
68                (CoordinatorLayout) mActivityTestRule.getActivity().findViewById(R.id.col);
69    }
70
71    private void verifySnackbarContent(final CustomSnackbar snackbar, final String expectedTitle,
72            final String expectedSubtitle) throws Throwable {
73        // Show the snackbar
74        SnackbarUtils.showTransientBottomBarAndWaitUntilFullyShown(snackbar);
75
76        // Verify that we're showing the title
77        withText(expectedTitle).matches(allOf(
78                isDescendantOfA(isAssignableFrom(Snackbar.SnackbarLayout.class)),
79                isDescendantOfA(isAssignableFrom(CustomSnackbarMainContent.class)),
80                isCompletelyDisplayed()));
81
82        // Verify that we're showing the subtitle
83        withText(expectedSubtitle).matches(allOf(
84                isDescendantOfA(isAssignableFrom(Snackbar.SnackbarLayout.class)),
85                isDescendantOfA(isAssignableFrom(CustomSnackbarMainContent.class)),
86                isCompletelyDisplayed()));
87
88        // Dismiss the snackbar
89        SnackbarUtils.dismissTransientBottomBarAndWaitUntilFullyDismissed(snackbar);
90    }
91
92    private CustomSnackbar makeCustomSnackbar() {
93        final LayoutInflater inflater = LayoutInflater.from(mCoordinatorLayout.getContext());
94        final CustomSnackbarMainContent content =
95                (CustomSnackbarMainContent) inflater.inflate(
96                        R.layout.custom_snackbar_include, mCoordinatorLayout, false);
97        final BaseTransientBottomBar.ContentViewCallback contentViewCallback =
98                new BaseTransientBottomBar.ContentViewCallback() {
99                    @Override
100                    public void animateContentIn(int delay, int duration) {
101                        content.setAlpha(0f);
102                        content.animate().alpha(1f).setDuration(duration)
103                                .setStartDelay(delay).start();
104                    }
105
106                    @Override
107                    public void animateContentOut(int delay, int duration) {
108                        content.setAlpha(1f);
109                        content.animate().alpha(0f).setDuration(duration)
110                                .setStartDelay(delay).start();
111                    }
112                };
113        return new CustomSnackbar(mCoordinatorLayout, content, contentViewCallback);
114    }
115
116    @Test
117    @SmallTest
118    public void testBasicContent() throws Throwable {
119        // Verify different combinations of snackbar content (title / subtitle and action)
120        // and duration
121
122        // Short duration
123        verifySnackbarContent(
124                makeCustomSnackbar().setTitle(TITLE_TEXT)
125                        .setSubtitle(SUBTITLE_TEXT).setDuration(Snackbar.LENGTH_SHORT),
126                TITLE_TEXT, SUBTITLE_TEXT);
127
128        // Long duration
129        verifySnackbarContent(
130                makeCustomSnackbar().setTitle(TITLE_TEXT)
131                        .setSubtitle(SUBTITLE_TEXT).setDuration(Snackbar.LENGTH_LONG),
132                TITLE_TEXT, SUBTITLE_TEXT);
133
134        // Indefinite duration
135        verifySnackbarContent(
136                makeCustomSnackbar().setTitle(TITLE_TEXT)
137                        .setSubtitle(SUBTITLE_TEXT).setDuration(Snackbar.LENGTH_INDEFINITE),
138                TITLE_TEXT, SUBTITLE_TEXT);
139    }
140
141    private void verifyDismissCallback(final ViewInteraction interaction,
142            @Nullable final ViewAction action, @Nullable final DismissAction dismissAction,
143            final int length, @Snackbar.Callback.DismissEvent final int expectedEvent)
144            throws Throwable {
145        final BaseTransientBottomBar.BaseCallback mockCallback =
146                mock(BaseTransientBottomBar.BaseCallback.class);
147        final CustomSnackbar snackbar = makeCustomSnackbar().setTitle(TITLE_TEXT)
148                .setSubtitle(SUBTITLE_TEXT).setDuration(length)
149                .addCallback(mockCallback);
150
151        // Show the snackbar
152        SnackbarUtils.showTransientBottomBarAndWaitUntilFullyShown(snackbar);
153        // Verify that our onShown has been called
154        verify(mockCallback, times(1)).onShown(snackbar);
155        // and that the snackbar is either shown or queued to be shown
156        assertTrue(snackbar.isShownOrQueued());
157        // and also check that we have the intended title / subtitle displayed somewhere in
158        // our hierarchy
159        onView(withText(TITLE_TEXT)).check(matches(isCompletelyDisplayed()));
160        onView(withText(SUBTITLE_TEXT)).check(matches(isCompletelyDisplayed()));
161
162        // Now perform the UI interaction
163        SnackbarUtils.performActionAndWaitUntilFullyDismissed(snackbar,
164                new SnackbarUtils.TransientBottomBarAction() {
165                    @Override
166                    public void perform() throws Throwable {
167                        if (action != null) {
168                            interaction.perform(action);
169                        } else if (dismissAction != null) {
170                            mActivityTestRule.runOnUiThread(new Runnable() {
171                                @Override
172                                public void run() {
173                                    dismissAction.dismiss(snackbar);
174                                }
175                            });
176                        }
177                    }
178                });
179
180        // Verify that our onDismissed has been called
181        verify(mockCallback, times(1)).onDismissed(snackbar, expectedEvent);
182        verifyNoMoreInteractions(mockCallback);
183        // and that the snackbar is neither shown nor queued to be shown
184        assertFalse(snackbar.isShownOrQueued());
185    }
186
187    @Test
188    @MediumTest
189    public void testDismissViaSwipe() throws Throwable {
190        verifyDismissCallback(
191                onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
192                swipeRight(),
193                null,
194                Snackbar.LENGTH_LONG,
195                Snackbar.Callback.DISMISS_EVENT_SWIPE);
196    }
197
198    @Test
199    @MediumTest
200    public void testDismissViaSwipeRtl() throws Throwable {
201        onView(withId(R.id.col)).perform(setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));
202        if (ViewCompat.getLayoutDirection(mCoordinatorLayout) == ViewCompat.LAYOUT_DIRECTION_RTL) {
203            // On devices that support RTL layout, the start-to-end dismiss swipe is done
204            // with swipeLeft() action
205            verifyDismissCallback(
206                    onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
207                    swipeLeft(),
208                    null,
209                    Snackbar.LENGTH_LONG,
210                    Snackbar.Callback.DISMISS_EVENT_SWIPE);
211        }
212    }
213
214    @Test
215    @MediumTest
216    public void testDismissViaApi() throws Throwable {
217        verifyDismissCallback(
218                onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
219                null,
220                new DismissAction() {
221                    @Override
222                    public void dismiss(CustomSnackbar snackbar) {
223                        snackbar.dismiss();
224                    }
225                },
226                Snackbar.LENGTH_LONG,
227                Snackbar.Callback.DISMISS_EVENT_MANUAL);
228    }
229
230    @Test
231    @MediumTest
232    public void testDismissViaTimeout() throws Throwable {
233        verifyDismissCallback(
234                onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
235                null,
236                null,
237                Snackbar.LENGTH_LONG,
238                Snackbar.Callback.DISMISS_EVENT_TIMEOUT);
239    }
240
241    @Test
242    @MediumTest
243    public void testDismissViaAnotherSnackbar() throws Throwable {
244        final CustomSnackbar anotherSnackbar =
245                makeCustomSnackbar().setTitle("Different title")
246                        .setSubtitle("Different subtitle").setDuration(Snackbar.LENGTH_SHORT);
247
248        // Our dismiss action is to show another snackbar (and verify that the original snackbar
249        // is now dismissed with CONSECUTIVE event)
250        verifyDismissCallback(
251                onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
252                null,
253                new DismissAction() {
254                    @Override
255                    public void dismiss(CustomSnackbar snackbar) {
256                        anotherSnackbar.show();
257                    }
258                },
259                Snackbar.LENGTH_LONG,
260                Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE);
261
262        // And dismiss the second snackbar to get back to clean state
263        SnackbarUtils.dismissTransientBottomBarAndWaitUntilFullyDismissed(anotherSnackbar);
264    }
265
266    @Test
267    @MediumTest
268    public void testMultipleCallbacks() throws Throwable {
269        final CustomSnackbar snackbar = makeCustomSnackbar().setTitle(TITLE_TEXT)
270                .setSubtitle(SUBTITLE_TEXT).setDuration(Snackbar.LENGTH_INDEFINITE);
271        final BaseTransientBottomBar.BaseCallback mockCallback1 =
272                mock(BaseTransientBottomBar.BaseCallback.class);
273        final BaseTransientBottomBar.BaseCallback mockCallback2 =
274                mock(BaseTransientBottomBar.BaseCallback.class);
275        snackbar.addCallback(mockCallback1);
276        snackbar.addCallback(mockCallback2);
277
278        SnackbarUtils.showTransientBottomBarAndWaitUntilFullyShown(snackbar);
279        verify(mockCallback1, times(1)).onShown(snackbar);
280        verify(mockCallback2, times(1)).onShown(snackbar);
281
282        SnackbarUtils.dismissTransientBottomBarAndWaitUntilFullyDismissed(snackbar);
283        verify(mockCallback1, times(1)).onDismissed(snackbar,
284                BaseTransientBottomBar.BaseCallback.DISMISS_EVENT_MANUAL);
285        verify(mockCallback2, times(1)).onDismissed(snackbar,
286                BaseTransientBottomBar.BaseCallback.DISMISS_EVENT_MANUAL);
287    }
288}
289