/*
* 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.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.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.support.annotation.Nullable;
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.filters.MediumTest;
import android.support.test.filters.SmallTest;
import android.support.v4.view.ViewCompat;
import android.view.LayoutInflater;
import org.junit.Before;
import org.junit.Test;
public class CustomSnackbarTest extends BaseInstrumentationTestCase<SnackbarActivity> {
private static final String TITLE_TEXT = "Test title";
private static final String SUBTITLE_TEXT = "Test subtitle";
private CoordinatorLayout mCoordinatorLayout;
private interface DismissAction {
void dismiss(CustomSnackbar snackbar);
}
public CustomSnackbarTest() {
super(SnackbarActivity.class);
}
@Before
public void setup() {
mCoordinatorLayout =
(CoordinatorLayout) mActivityTestRule.getActivity().findViewById(R.id.col);
}
private void verifySnackbarContent(final CustomSnackbar snackbar, final String expectedTitle,
final String expectedSubtitle) throws Throwable {
// Show the snackbar
SnackbarUtils.showTransientBottomBarAndWaitUntilFullyShown(snackbar);
// Verify that we're showing the title
withText(expectedTitle).matches(allOf(
isDescendantOfA(isAssignableFrom(Snackbar.SnackbarLayout.class)),
isDescendantOfA(isAssignableFrom(CustomSnackbarMainContent.class)),
isCompletelyDisplayed()));
// Verify that we're showing the subtitle
withText(expectedSubtitle).matches(allOf(
isDescendantOfA(isAssignableFrom(Snackbar.SnackbarLayout.class)),
isDescendantOfA(isAssignableFrom(CustomSnackbarMainContent.class)),
isCompletelyDisplayed()));
// Dismiss the snackbar
SnackbarUtils.dismissTransientBottomBarAndWaitUntilFullyDismissed(snackbar);
}
private CustomSnackbar makeCustomSnackbar() {
final LayoutInflater inflater = LayoutInflater.from(mCoordinatorLayout.getContext());
final CustomSnackbarMainContent content =
(CustomSnackbarMainContent) inflater.inflate(
R.layout.custom_snackbar_include, mCoordinatorLayout, false);
final BaseTransientBottomBar.ContentViewCallback contentViewCallback =
new BaseTransientBottomBar.ContentViewCallback() {
@Override
public void animateContentIn(int delay, int duration) {
ViewCompat.setAlpha(content, 0f);
ViewCompat.animate(content).alpha(1f).setDuration(duration)
.setStartDelay(delay).start();
}
@Override
public void animateContentOut(int delay, int duration) {
ViewCompat.setAlpha(content, 1f);
ViewCompat.animate(content).alpha(0f).setDuration(duration)
.setStartDelay(delay).start();
}
};
return new CustomSnackbar(mCoordinatorLayout, content, contentViewCallback);
}
@Test
@SmallTest
public void testBasicContent() throws Throwable {
// Verify different combinations of snackbar content (title / subtitle and action)
// and duration
// Short duration
verifySnackbarContent(
makeCustomSnackbar().setTitle(TITLE_TEXT)
.setSubtitle(SUBTITLE_TEXT).setDuration(Snackbar.LENGTH_SHORT),
TITLE_TEXT, SUBTITLE_TEXT);
// Long duration
verifySnackbarContent(
makeCustomSnackbar().setTitle(TITLE_TEXT)
.setSubtitle(SUBTITLE_TEXT).setDuration(Snackbar.LENGTH_LONG),
TITLE_TEXT, SUBTITLE_TEXT);
// Indefinite duration
verifySnackbarContent(
makeCustomSnackbar().setTitle(TITLE_TEXT)
.setSubtitle(SUBTITLE_TEXT).setDuration(Snackbar.LENGTH_INDEFINITE),
TITLE_TEXT, SUBTITLE_TEXT);
}
private void verifyDismissCallback(final ViewInteraction interaction,
@Nullable final ViewAction action, @Nullable final DismissAction dismissAction,
final int length, @Snackbar.Callback.DismissEvent final int expectedEvent)
throws Throwable {
final BaseTransientBottomBar.BaseCallback mockCallback =
mock(BaseTransientBottomBar.BaseCallback.class);
final CustomSnackbar snackbar = makeCustomSnackbar().setTitle(TITLE_TEXT)
.setSubtitle(SUBTITLE_TEXT).setDuration(length)
.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 title / subtitle displayed somewhere in
// our hierarchy
onView(withText(TITLE_TEXT)).check(matches(isCompletelyDisplayed()));
onView(withText(SUBTITLE_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
@MediumTest
public void testDismissViaSwipe() throws Throwable {
verifyDismissCallback(
onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
swipeRight(),
null,
Snackbar.LENGTH_LONG,
Snackbar.Callback.DISMISS_EVENT_SWIPE);
}
@Test
@MediumTest
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
@MediumTest
public void testDismissViaApi() throws Throwable {
verifyDismissCallback(
onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
null,
new DismissAction() {
@Override
public void dismiss(CustomSnackbar snackbar) {
snackbar.dismiss();
}
},
Snackbar.LENGTH_LONG,
Snackbar.Callback.DISMISS_EVENT_MANUAL);
}
@Test
@MediumTest
public void testDismissViaTimeout() throws Throwable {
verifyDismissCallback(
onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
null,
null,
Snackbar.LENGTH_LONG,
Snackbar.Callback.DISMISS_EVENT_TIMEOUT);
}
@Test
@MediumTest
public void testDismissViaAnotherSnackbar() throws Throwable {
final CustomSnackbar anotherSnackbar =
makeCustomSnackbar().setTitle("Different title")
.setSubtitle("Different subtitle").setDuration(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(CustomSnackbar 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
@MediumTest
public void testMultipleCallbacks() throws Throwable {
final CustomSnackbar snackbar = makeCustomSnackbar().setTitle(TITLE_TEXT)
.setSubtitle(SUBTITLE_TEXT).setDuration(Snackbar.LENGTH_INDEFINITE);
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);
}
}