blob: 5f5682c13cb8650c2fd5c1d1f977dddeb79dec79 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.design.widget;
import static android.support.design.testutils.TestUtilsActions.setLayoutDirection;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.swipeLeft;
import static android.support.test.espresso.action.ViewActions.swipeRight;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
import static android.support.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.core.AllOf.allOf;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.content.res.Resources;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.design.test.R;
import android.support.design.testutils.SnackbarUtils;
import android.support.test.espresso.ViewAction;
import android.support.test.espresso.ViewInteraction;
import android.support.test.espresso.action.CoordinatesProvider;
import android.support.test.espresso.action.GeneralSwipeAction;
import android.support.test.espresso.action.Press;
import android.support.test.espresso.action.Swipe;
import android.support.test.filters.MediumTest;
import android.support.v4.view.ViewCompat;
import android.text.TextUtils;
import android.view.View;
import org.junit.Before;
import org.junit.Test;
@MediumTest
public class SnackbarTest extends BaseInstrumentationTestCase<SnackbarActivity> {
private static final String MESSAGE_TEXT = "Test Message";
private static final @StringRes int MESSAGE_ID = R.string.snackbar_text;
private static final String ACTION_TEXT = "Action";
private static final @StringRes int ACTION_ID = R.string.snackbar_action;
private CoordinatorLayout mCoordinatorLayout;
private interface DismissAction {
void dismiss(Snackbar snackbar);
}
public SnackbarTest() {
super(SnackbarActivity.class);
}
@Before
public void setup() {
mCoordinatorLayout =
(CoordinatorLayout) mActivityTestRule.getActivity().findViewById(R.id.col);
}
private void verifySnackbarContent(final Snackbar snackbar, final String expectedMessage,
final String expectedAction) throws Throwable {
// Show the snackbar
SnackbarUtils.showTransientBottomBarAndWaitUntilFullyShown(snackbar);
// Verify that we're showing the message
withText(expectedMessage).matches(allOf(
isDescendantOfA(isAssignableFrom(Snackbar.SnackbarLayout.class)),
isCompletelyDisplayed()));
// If the action is not empty, verify that we're showing it
if (!TextUtils.isEmpty(expectedAction)) {
withText(expectedAction).matches(allOf(
isDescendantOfA(isAssignableFrom(Snackbar.SnackbarLayout.class)),
isCompletelyDisplayed()));
}
// Dismiss the snackbar
SnackbarUtils.dismissTransientBottomBarAndWaitUntilFullyDismissed(snackbar);
}
@Test
public void testBasicContent() throws Throwable {
// Verify different combinations of snackbar content (message and action) and duration
final Resources res = mActivityTestRule.getActivity().getResources();
final String resolvedMessage = res.getString(MESSAGE_ID);
final String resolvedAction = res.getString(ACTION_ID);
// String message and no action
verifySnackbarContent(
Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_SHORT),
MESSAGE_TEXT, null);
// Resource message and no action
verifySnackbarContent(
Snackbar.make(mCoordinatorLayout, MESSAGE_ID, Snackbar.LENGTH_LONG),
resolvedMessage, null);
// String message and string action
verifySnackbarContent(
Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_INDEFINITE)
.setAction(ACTION_TEXT, mock(View.OnClickListener.class)),
MESSAGE_TEXT, ACTION_TEXT);
// String message and resource action
verifySnackbarContent(
Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_SHORT)
.setAction(ACTION_ID, mock(View.OnClickListener.class)),
MESSAGE_TEXT, resolvedAction);
// Resource message and resource action
verifySnackbarContent(
Snackbar.make(mCoordinatorLayout, MESSAGE_ID, Snackbar.LENGTH_LONG)
.setAction(ACTION_ID, mock(View.OnClickListener.class)),
resolvedMessage, resolvedAction);
}
private void verifyDismissCallback(final ViewInteraction interaction,
final @Nullable ViewAction action, final @Nullable DismissAction dismissAction,
final int length, @Snackbar.Callback.DismissEvent final int expectedEvent)
throws Throwable {
final BaseTransientBottomBar.BaseCallback mockCallback =
mock(BaseTransientBottomBar.BaseCallback.class);
final Snackbar snackbar = Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, length)
.setAction(ACTION_TEXT, mock(View.OnClickListener.class))
.addCallback(mockCallback);
// Show the snackbar
SnackbarUtils.showTransientBottomBarAndWaitUntilFullyShown(snackbar);
// Verify that our onShown has been called
verify(mockCallback, times(1)).onShown(snackbar);
// and that the snackbar is either shown or queued to be shown
assertTrue(snackbar.isShownOrQueued());
// and also check that we have the intended message / action displayed somewhere in
// our hierarchy
onView(withText(MESSAGE_TEXT)).check(matches(isCompletelyDisplayed()));
onView(withText(ACTION_TEXT)).check(matches(isCompletelyDisplayed()));
// Now perform the UI interaction
SnackbarUtils.performActionAndWaitUntilFullyDismissed(snackbar,
new SnackbarUtils.TransientBottomBarAction() {
@Override
public void perform() throws Throwable {
if (action != null) {
interaction.perform(action);
} else if (dismissAction != null) {
mActivityTestRule.runOnUiThread(new Runnable() {
@Override
public void run() {
dismissAction.dismiss(snackbar);
}
});
}
}
});
// Verify that our onDismissed has been called
verify(mockCallback, times(1)).onDismissed(snackbar, expectedEvent);
verifyNoMoreInteractions(mockCallback);
// and that the snackbar is neither shown nor queued to be shown
assertFalse(snackbar.isShownOrQueued());
}
@Test
public void testDismissViaActionClick() throws Throwable {
verifyDismissCallback(
onView(withId(R.id.snackbar_action)),
click(),
null,
Snackbar.LENGTH_LONG,
Snackbar.Callback.DISMISS_EVENT_ACTION);
}
@Test
public void testDismissViaSwipe() throws Throwable {
verifyDismissCallback(
onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
swipeRight(),
null,
Snackbar.LENGTH_LONG,
Snackbar.Callback.DISMISS_EVENT_SWIPE);
}
@Test
public void testDismissViaSwipeRtl() throws Throwable {
onView(withId(R.id.col)).perform(setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));
if (ViewCompat.getLayoutDirection(mCoordinatorLayout) == ViewCompat.LAYOUT_DIRECTION_RTL) {
// On devices that support RTL layout, the start-to-end dismiss swipe is done
// with swipeLeft() action
verifyDismissCallback(
onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
swipeLeft(),
null,
Snackbar.LENGTH_LONG,
Snackbar.Callback.DISMISS_EVENT_SWIPE);
}
}
@Test
public void testDismissViaApi() throws Throwable {
verifyDismissCallback(
onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
null,
new DismissAction() {
@Override
public void dismiss(Snackbar snackbar) {
snackbar.dismiss();
}
},
Snackbar.LENGTH_LONG,
Snackbar.Callback.DISMISS_EVENT_MANUAL);
}
@Test
public void testDismissViaTimeout() throws Throwable {
verifyDismissCallback(
onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
null,
null,
Snackbar.LENGTH_LONG,
Snackbar.Callback.DISMISS_EVENT_TIMEOUT);
}
@Test
public void testSwipeUpDismissesViaTimeout() throws Throwable {
verifyDismissCallback(
onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
// This is a swipe up, from the middle center of the view, to above the view
// (outside the bounds)
new GeneralSwipeAction(
Swipe.SLOW,
new CoordinatesProvider() {
@Override
public float[] calculateCoordinates(View view) {
final int[] loc = new int[2];
view.getLocationOnScreen(loc);
return new float[]{
loc[0] + view.getWidth() / 2,
loc[1] + view.getHeight() / 2};
}
},
new CoordinatesProvider() {
@Override
public float[] calculateCoordinates(View view) {
final int[] loc = new int[2];
view.getLocationOnScreen(loc);
return new float[]{
loc[0] + view.getWidth() / 2,
loc[1] - view.getHeight()};
}
},
Press.FINGER
),
null,
Snackbar.LENGTH_SHORT,
Snackbar.Callback.DISMISS_EVENT_TIMEOUT);
}
@Test
public void testDismissViaAnotherSnackbar() throws Throwable {
final Snackbar anotherSnackbar =
Snackbar.make(mCoordinatorLayout, "A different message", Snackbar.LENGTH_SHORT);
// Our dismiss action is to show another snackbar (and verify that the original snackbar
// is now dismissed with CONSECUTIVE event)
verifyDismissCallback(
onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
null,
new DismissAction() {
@Override
public void dismiss(Snackbar snackbar) {
anotherSnackbar.show();
}
},
Snackbar.LENGTH_LONG,
Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE);
// And dismiss the second snackbar to get back to clean state
SnackbarUtils.dismissTransientBottomBarAndWaitUntilFullyDismissed(anotherSnackbar);
}
@Test
public void testActionClickListener() {
final View.OnClickListener mockClickListener = mock(View.OnClickListener.class);
final Snackbar snackbar =
Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_SHORT)
.setAction(ACTION_TEXT, mockClickListener);
// Show the snackbar
SnackbarUtils.showTransientBottomBarAndWaitUntilFullyShown(snackbar);
// perform the action click
onView(withId(R.id.snackbar_action)).perform(click());
// and verify that our click listener has been called
verify(mockClickListener, times(1)).onClick(any(View.class));
}
@Test
public void testSetCallback() throws Throwable {
final Snackbar snackbar =
Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_INDEFINITE)
.setAction(ACTION_TEXT, mock(View.OnClickListener.class));
final Snackbar.Callback mockCallback = spy(new Snackbar.Callback());
snackbar.setCallback(mockCallback);
SnackbarUtils.showTransientBottomBarAndWaitUntilFullyShown(snackbar);
verify(mockCallback, times(1)).onShown(snackbar);
SnackbarUtils.dismissTransientBottomBarAndWaitUntilFullyDismissed(snackbar);
verify(mockCallback, times(1)).onDismissed(snackbar,
BaseTransientBottomBar.BaseCallback.DISMISS_EVENT_MANUAL);
}
@Test
public void testSingleCallback() throws Throwable {
final Snackbar snackbar =
Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_INDEFINITE)
.setAction(ACTION_TEXT, mock(View.OnClickListener.class));
final BaseTransientBottomBar.BaseCallback mockCallback1 =
mock(BaseTransientBottomBar.BaseCallback.class);
final BaseTransientBottomBar.BaseCallback mockCallback2 =
mock(BaseTransientBottomBar.BaseCallback.class);
snackbar.addCallback(mockCallback1);
SnackbarUtils.showTransientBottomBarAndWaitUntilFullyShown(snackbar);
verify(mockCallback1, times(1)).onShown(snackbar);
verify(mockCallback2, never()).onShown(snackbar);
SnackbarUtils.dismissTransientBottomBarAndWaitUntilFullyDismissed(snackbar);
verify(mockCallback1, times(1)).onDismissed(snackbar,
BaseTransientBottomBar.BaseCallback.DISMISS_EVENT_MANUAL);
verify(mockCallback2, never()).onDismissed(snackbar,
BaseTransientBottomBar.BaseCallback.DISMISS_EVENT_MANUAL);
}
@Test
public void testMultipleCallbacks() throws Throwable {
final Snackbar snackbar =
Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_INDEFINITE)
.setAction(ACTION_TEXT, mock(View.OnClickListener.class));
final BaseTransientBottomBar.BaseCallback mockCallback1 =
mock(BaseTransientBottomBar.BaseCallback.class);
final BaseTransientBottomBar.BaseCallback mockCallback2 =
mock(BaseTransientBottomBar.BaseCallback.class);
snackbar.addCallback(mockCallback1);
snackbar.addCallback(mockCallback2);
SnackbarUtils.showTransientBottomBarAndWaitUntilFullyShown(snackbar);
verify(mockCallback1, times(1)).onShown(snackbar);
verify(mockCallback2, times(1)).onShown(snackbar);
SnackbarUtils.dismissTransientBottomBarAndWaitUntilFullyDismissed(snackbar);
verify(mockCallback1, times(1)).onDismissed(snackbar,
BaseTransientBottomBar.BaseCallback.DISMISS_EVENT_MANUAL);
verify(mockCallback2, times(1)).onDismissed(snackbar,
BaseTransientBottomBar.BaseCallback.DISMISS_EVENT_MANUAL);
}
@Test
public void testMultipleCallbacksWithRemoval() throws Throwable {
final Snackbar snackbar =
Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_INDEFINITE)
.setAction(ACTION_TEXT, mock(View.OnClickListener.class));
final BaseTransientBottomBar.BaseCallback mockCallback1 =
mock(BaseTransientBottomBar.BaseCallback.class);
final BaseTransientBottomBar.BaseCallback mockCallback2 =
mock(BaseTransientBottomBar.BaseCallback.class);
snackbar.addCallback(mockCallback1);
snackbar.addCallback(mockCallback2);
SnackbarUtils.showTransientBottomBarAndWaitUntilFullyShown(snackbar);
verify(mockCallback1, times(1)).onShown(snackbar);
verify(mockCallback2, times(1)).onShown(snackbar);
snackbar.removeCallback(mockCallback2);
SnackbarUtils.dismissTransientBottomBarAndWaitUntilFullyDismissed(snackbar);
verify(mockCallback1, times(1)).onDismissed(snackbar,
BaseTransientBottomBar.BaseCallback.DISMISS_EVENT_MANUAL);
verify(mockCallback2, never()).onDismissed(snackbar,
BaseTransientBottomBar.BaseCallback.DISMISS_EVENT_MANUAL);
}
}