// Copyright 2019 The Chromium Authors
// 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_tabs;
import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.IdRes;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.filters.MediumTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.chrome.browser.autofill.helpers.FaviconHelper;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.keyboard_accessory.AccessoryTabType;
import org.chromium.chrome.browser.keyboard_accessory.R;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.PlusAddressInfo;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.UserInfo;
import org.chromium.chrome.browser.keyboard_accessory.data.UserInfoField;
import org.chromium.chrome.browser.keyboard_accessory.sheet_component.AccessorySheetCoordinator;
import org.chromium.chrome.browser.keyboard_accessory.sheet_tabs.AccessorySheetTabItemsModel.AccessorySheetDataPiece;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.components.browser_ui.widget.chips.ChipView;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
/** View tests for the address accessory sheet. */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class AddressAccessorySheetViewTest {
private AccessorySheetTabItemsModel mModel;
private AtomicReference<RecyclerView> mView = new AtomicReference<>();
@Rule
public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
@Before
public void setUp() throws InterruptedException {
mActivityTestRule.startMainActivityOnBlankPage();
ThreadUtils.runOnUiThreadBlocking(
() -> {
mModel = new AccessorySheetTabItemsModel();
AccessorySheetCoordinator accessorySheet =
new AccessorySheetCoordinator(
mActivityTestRule
.getActivity()
.findViewById(R.id.keyboard_accessory_sheet_stub),
null);
accessorySheet.setTabs(
new KeyboardAccessoryData.Tab[] {
new KeyboardAccessoryData.Tab(
"Addresses",
null,
null,
R.layout.address_accessory_sheet,
AccessoryTabType.ADDRESSES,
new KeyboardAccessoryData.Tab.Listener() {
@Override
public void onTabCreated(ViewGroup view) {
mView.set((RecyclerView) view);
AccessorySheetTabViewBinder.initializeView(
mView.get(), null);
AddressAccessorySheetViewBinder.initializeView(
mView.get(),
mModel,
FaviconHelper.create(
view.getContext(),
mActivityTestRule.getProfile(
false)));
}
@Override
public void onTabShown() {}
})
});
accessorySheet.setHeight(
mActivityTestRule
.getActivity()
.getResources()
.getDimensionPixelSize(
R.dimen.keyboard_accessory_sheet_height));
accessorySheet.show();
});
CriteriaHelper.pollUiThread(() -> Criteria.checkThat(mView.get(), notNullValue()));
}
@After
public void tearDown() {
mView.set(null);
}
@Test
@MediumTest
public void testAddingCaptionsToTheModelRendersThem() {
assertThat(mView.get().getChildCount(), is(0));
ThreadUtils.runOnUiThreadBlocking(
() -> {
mModel.add(
new AccessorySheetDataPiece(
"Addresses", AccessorySheetDataPiece.Type.TITLE));
});
CriteriaHelper.pollUiThread(() -> Criteria.checkThat(mView.get().getChildCount(), is(1)));
View title = mView.get().findViewById(R.id.tab_title);
assertThat(title, is(not(nullValue())));
assertThat(title, instanceOf(TextView.class));
assertThat(((TextView) title).getText(), is("Addresses"));
}
@Test
@MediumTest
public void testAddingUserInfoToTheModelRendersClickableActions() throws ExecutionException {
final AtomicBoolean clicked = new AtomicBoolean();
assertThat(mView.get().getChildCount(), is(0));
ThreadUtils.runOnUiThreadBlocking(
() -> {
mModel.add(
new AccessorySheetDataPiece(
createInfo(
/* nameFull= */ "Maya J. Park",
/* companyName= */ "",
/* addressHomeLine1= */ "100 Test Str.",
/* addressHomeLine2= */ "",
/* addressHomeZip= */ "14482",
/* addressHomeCity= */ "Potsdam",
/* addressHomeState= */ "CO",
/* addressHomeCountry= */ "France",
/* phoneHomeWholeNumber= */ "+4917802048383",
/* emailAddress= */ "[email protected]",
clicked),
AccessorySheetDataPiece.Type.ADDRESS_INFO));
mModel.add(
new AccessorySheetDataPiece(
new KeyboardAccessoryData.FooterCommand(
"Manage addresses", null),
AccessorySheetDataPiece.Type.FOOTER_COMMAND));
});
// Wait until at least one element is rendered. Test devices with small screens will cause
// the footer to not be created. Instantiating a footer still covers potential crashes.
CriteriaHelper.pollUiThread(
() -> Criteria.checkThat(mView.get().getChildCount(), greaterThan(0)));
// Check that the titles are correct:
assertThat(getChipText(R.id.name_full), is("Maya J. Park"));
assertThat(getChipText(R.id.company_name), is(""));
assertThat(getChipText(R.id.address_home_line_1), is("100 Test Str."));
assertThat(getChipText(R.id.address_home_line_2), is(""));
assertThat(getChipText(R.id.address_home_zip), is("14482"));
assertThat(getChipText(R.id.address_home_city), is("Potsdam"));
assertThat(getChipText(R.id.address_home_state), is("CO"));
assertThat(getChipText(R.id.address_home_country), is("France"));
assertThat(getChipText(R.id.phone_home_whole_number), is("+4917802048383"));
assertThat(getChipText(R.id.email_address), is("[email protected]"));
// Chips without content shouldn't be visible:
assertThat(findChipView(R.id.company_name).isShown(), is(false));
// Chips are clickable:
ThreadUtils.runOnUiThreadBlocking(findChipView(R.id.name_full)::performClick);
assertThat(clicked.get(), is(true));
clicked.set(false);
ThreadUtils.runOnUiThreadBlocking(findChipView(R.id.email_address)::performClick);
assertThat(clicked.get(), is(true));
}
@Test
@MediumTest
public void testAddingPlusAddressInfoToTheModelRendersClickableActions()
throws ExecutionException {
final AtomicBoolean clicked = new AtomicBoolean();
ThreadUtils.runOnUiThreadBlocking(
() -> {
mModel.add(
new AccessorySheetDataPiece(
new PlusAddressInfo(
/* origin= */ "google.com",
new UserInfoField.Builder()
.setDisplayText("[email protected]")
.setTextToFill("[email protected]")
.setIsObfuscated(false)
.setCallback(unused -> clicked.set(true))
.build()),
AccessorySheetDataPiece.Type.PLUS_ADDRESS_SECTION));
});
CriteriaHelper.pollUiThread(
() -> Criteria.checkThat(mView.get().getChildCount(), greaterThan(0)));
assertThat(getChipText(R.id.plus_address), is("[email protected]"));
// Plus address chip is clickable:
ThreadUtils.runOnUiThreadBlocking(findChipView(R.id.plus_address)::performClick);
assertThat(clicked.get(), is(true));
}
private UserInfo createInfo(
String nameFull,
String companyName,
String addressHomeLine1,
String addressHomeLine2,
String addressHomeZip,
String addressHomeCity,
String addressHomeState,
String addressHomeCountry,
String phoneHomeWholeNumber,
String emailAddress,
AtomicBoolean clickRecorder) {
UserInfo info = new UserInfo("", false);
info.addField(
new UserInfoField(nameFull, nameFull, "", false, item -> clickRecorder.set(true)));
info.addField(
new UserInfoField(
companyName, companyName, "", false, item -> clickRecorder.set(true)));
info.addField(
new UserInfoField(
addressHomeLine1,
addressHomeLine1,
"",
false,
item -> clickRecorder.set(true)));
info.addField(
new UserInfoField(
addressHomeLine2,
addressHomeLine2,
"",
false,
item -> clickRecorder.set(true)));
info.addField(
new UserInfoField(
addressHomeZip,
addressHomeZip,
"",
false,
item -> clickRecorder.set(true)));
info.addField(
new UserInfoField(
addressHomeCity,
addressHomeCity,
"",
false,
item -> clickRecorder.set(true)));
info.addField(
new UserInfoField(
addressHomeState,
addressHomeState,
"",
false,
item -> clickRecorder.set(true)));
info.addField(
new UserInfoField(
addressHomeCountry,
addressHomeCountry,
"",
false,
item -> clickRecorder.set(true)));
info.addField(
new UserInfoField(
phoneHomeWholeNumber,
phoneHomeWholeNumber,
"",
false,
item -> clickRecorder.set(true)));
info.addField(
new UserInfoField(
emailAddress, emailAddress, "", false, item -> clickRecorder.set(true)));
return info;
}
private ChipView findChipView(@IdRes int id) {
assertThat(mView.get().getChildAt(0), instanceOf(LinearLayout.class));
LinearLayout layout = (LinearLayout) mView.get().getChildAt(0);
View view = layout.findViewById(id);
assertThat(view, is(not(nullValue())));
assertThat(view, instanceOf(ChipView.class));
return ((ChipView) view);
}
private CharSequence getChipText(@IdRes int id) {
return findChipView(id).getPrimaryTextView().getText();
}
}