blob: f552a620252b324df47ad6396c5bb62f70c8bcdf [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.keyboard_accessory.sheet_component;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.chromium.chrome.browser.keyboard_accessory.sheet_component.AccessorySheetProperties.ACTIVE_TAB_INDEX;
import static org.chromium.chrome.browser.keyboard_accessory.sheet_component.AccessorySheetProperties.HEIGHT;
import static org.chromium.chrome.browser.keyboard_accessory.sheet_component.AccessorySheetProperties.TABS;
import static org.chromium.chrome.browser.keyboard_accessory.sheet_component.AccessorySheetProperties.TOP_SHADOW_VISIBLE;
import static org.chromium.chrome.browser.keyboard_accessory.sheet_component.AccessorySheetProperties.VISIBLE;
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.test.ShadowRecordHistogram;
import org.chromium.base.task.test.CustomShadowAsyncTask;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.keyboard_accessory.AccessorySheetTrigger;
import org.chromium.chrome.browser.keyboard_accessory.ManualFillingMetricsRecorder;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.Tab;
import org.chromium.ui.modelutil.ListObservable;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyObservable;
import org.chromium.ui.test.util.modelutil.FakeViewProvider;
/**
* Controller tests for the keyboard accessory bottom sheet component.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE,
shadows = {CustomShadowAsyncTask.class, ShadowRecordHistogram.class})
public class AccessorySheetControllerTest {
@Mock
private PropertyObservable.PropertyObserver<PropertyKey> mMockPropertyObserver;
@Mock
private ListObservable.ListObserver<Void> mTabListObserver;
@Mock
private AccessorySheetView mMockView;
@Mock
private RecyclerView mMockRecyclerView;
private final Tab[] mTabs = new Tab[] {new Tab("Passwords", null, null, 0, 0, null),
new Tab("Passwords", null, null, 0, 0, null),
new Tab("Passwords", null, null, 0, 0, null),
new Tab("Passwords", null, null, 0, 0, null)};
private AccessorySheetCoordinator mCoordinator;
private AccessorySheetMediator mMediator;
private PropertyModel mModel;
@Before
public void setUp() {
ShadowRecordHistogram.reset();
MockitoAnnotations.initMocks(this);
when(mMockView.getLayoutParams()).thenReturn(new ViewGroup.LayoutParams(0, 0));
mCoordinator = new AccessorySheetCoordinator(new FakeViewProvider<>(mMockView));
mMediator = mCoordinator.getMediatorForTesting();
mModel = mMediator.getModelForTesting();
}
@Test
public void testCreatesValidSubComponents() {
assertThat(mCoordinator, is(notNullValue()));
assertThat(mMediator, is(notNullValue()));
assertThat(mModel, is(notNullValue()));
}
@Test
public void testModelNotifiesAboutVisibilityOncePerChange() {
mModel.addObserver(mMockPropertyObserver);
// Calling show on the mediator should make model propagate that it's visible.
mMediator.show();
verify(mMockPropertyObserver).onPropertyChanged(mModel, VISIBLE);
assertThat(mModel.get(VISIBLE), is(true));
// Calling show again does nothing.
mMediator.show();
verify(mMockPropertyObserver) // Still the same call and no new one added.
.onPropertyChanged(mModel, VISIBLE);
// Calling hide on the mediator should make model propagate that it's invisible.
mMediator.hide();
verify(mMockPropertyObserver, times(2)).onPropertyChanged(mModel, VISIBLE);
assertThat(mModel.get(VISIBLE), is(false));
}
@Test
public void testModelNotifiesHeightChanges() {
mModel.addObserver(mMockPropertyObserver);
// Setting height triggers the observer and changes the model.
mCoordinator.setHeight(123);
verify(mMockPropertyObserver).onPropertyChanged(mModel, HEIGHT);
assertThat(mModel.get(HEIGHT), is(123));
// Setting the same height doesn't trigger anything.
mCoordinator.setHeight(123);
verify(mMockPropertyObserver).onPropertyChanged(mModel, HEIGHT); // No 2nd call.
// Setting a different height triggers again.
mCoordinator.setHeight(234);
verify(mMockPropertyObserver, times(2)).onPropertyChanged(mModel, HEIGHT);
assertThat(mModel.get(HEIGHT), is(234));
}
@Test
public void testModelNotifiesChangesForNewSheet() {
mModel.addObserver(mMockPropertyObserver);
mModel.get(TABS).addObserver(mTabListObserver);
assertThat(mModel.get(TABS).size(), is(0));
mCoordinator.setTabs(new Tab[] {mTabs[0]});
verify(mTabListObserver).onItemRangeInserted(mModel.get(TABS), 0, 1);
assertThat(mModel.get(TABS).size(), is(1));
}
@Test
public void testDeletingFirstAndOnlyTabInvalidatesActiveTab() {
mCoordinator.setTabs(new Tab[] {mTabs[0]});
mCoordinator.setTabs(new Tab[0]);
assertThat(mModel.get(TABS).size(), is(0));
assertThat(mModel.get(ACTIVE_TAB_INDEX), is(AccessorySheetProperties.NO_ACTIVE_TAB));
}
@Test
public void testDoesntChangePositionOfActiveTabForDeletedSuccessors() {
mCoordinator.setTabs(mTabs);
mModel.set(ACTIVE_TAB_INDEX, 2);
mCoordinator.setTabs(new Tab[] {mTabs[0], mTabs[1], mTabs[2]});
assertThat(mModel.get(TABS).size(), is(3));
assertThat(mModel.get(ACTIVE_TAB_INDEX), is(2));
}
@Test
public void testScrollingChangesShadowVisibility() {
assertThat(mModel.get(TOP_SHADOW_VISIBLE), is(false));
// If the list was scrolled far enough that the top is hidden, show the top shadow.
when(mMockRecyclerView.canScrollVertically(eq(-1))).thenReturn(true);
mCoordinator.getScrollListener().onScrolled(mMockRecyclerView, 0, 10);
assertThat(mModel.get(TOP_SHADOW_VISIBLE), is(true));
// If the list was scrolled back to the top, hide the shadow again.
when(mMockRecyclerView.canScrollVertically(eq(-1))).thenReturn(false);
mCoordinator.getScrollListener().onScrolled(mMockRecyclerView, 0, -10);
assertThat(mModel.get(TOP_SHADOW_VISIBLE), is(false));
}
@Test
public void testRecordsSheetClosure() {
assertThat(RecordHistogram.getHistogramTotalCountForTesting(
ManualFillingMetricsRecorder.UMA_KEYBOARD_ACCESSORY_SHEET_TRIGGERED),
is(0));
// Although sheets must be opened manually as of now, don't assume that every opened sheet
// in the future will be manually opened. Closing is the only thing to be tested here.
mCoordinator.show();
mCoordinator.hide();
assertThat(getTriggerMetricsCount(AccessorySheetTrigger.ANY_CLOSE), is(1));
// Log closing every time it happens.
mCoordinator.show();
mCoordinator.hide();
assertThat(getTriggerMetricsCount(AccessorySheetTrigger.ANY_CLOSE), is(2));
}
private int getTriggerMetricsCount(@AccessorySheetTrigger int bucket) {
return RecordHistogram.getHistogramValueCountForTesting(
ManualFillingMetricsRecorder.UMA_KEYBOARD_ACCESSORY_SHEET_TRIGGERED, bucket);
}
}