[FedCM] Split button mode and widget mode UI on Android
This patch changes the view initialized depending on the RP mode passed. The new view's layout is account_selection_button_mode_sheet.xml. Note that this is a copy of account_selection_sheet.xml with an additional drag handlebar at the top, the UI will be changed more in future patches. This patch is mainly to split the UI to use different layouts for different RP modes and set up test suites. In case it helps, there is a design section in this document briefly outlining what should be changed. https://docs.google.com/document/d/1ZRVb6-5_S4APS4joLzYUu2YOQDLnWCxXH3WwGiEpJ1Y/edit?resourcekey=0-eJQ6WHnMBH37PjMssVC1jg&tab=t.0#heading=h.82v445ybapt Screenshot of layout account_selection_button_mode_sheet.xml https://issues.chromium.org/action/issues/327273595/attachments/57246729?download=false Bug: 327273595 Change-Id: I54deea9c1743d4f4b0b5f684fdf0d1b62457033b Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5618272 Reviewed-by: Nicolás Peña <npm@chromium.org> Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com> Commit-Queue: Zachary Tan <tanzachary@chromium.org> Cr-Commit-Position: refs/heads/main@{#1321205}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
2550b3d4f7
commit
78a8a52f44
chrome/browser/ui/android/webid
@ -10,6 +10,7 @@
|
||||
#include "base/android/jni_string.h"
|
||||
#include "chrome/browser/ui/webid/account_selection_view.h"
|
||||
#include "content/public/browser/identity_request_dialog_controller.h"
|
||||
#include "third_party/blink/public/mojom/webid/federated_auth_request.mojom-shared.h"
|
||||
#include "third_party/blink/public/mojom/webid/federated_auth_request.mojom.h"
|
||||
#include "ui/android/color_utils_android.h"
|
||||
#include "ui/android/window_android.h"
|
||||
@ -169,8 +170,7 @@ bool AccountSelectionViewAndroid::Show(
|
||||
Account::SignInMode sign_in_mode,
|
||||
blink::mojom::RpMode rp_mode,
|
||||
const std::optional<content::IdentityProviderData>& new_account_idp) {
|
||||
// TODO(crbug.com/41491333): Use rp_mode for button flows on Android.
|
||||
if (!MaybeCreateJavaObject()) {
|
||||
if (!MaybeCreateJavaObject(rp_mode)) {
|
||||
// It's possible that the constructor cannot access the bottom sheet clank
|
||||
// component. That case may be temporary but we can't let users in a
|
||||
// waiting state so report that AccountSelectionView is dismissed instead.
|
||||
@ -213,8 +213,11 @@ bool AccountSelectionViewAndroid::ShowFailureDialog(
|
||||
blink::mojom::RpContext rp_context,
|
||||
blink::mojom::RpMode rp_mode,
|
||||
const content::IdentityProviderMetadata& idp_metadata) {
|
||||
// TODO(crbug.com/41491333): Use rp_mode for button flows on Android.
|
||||
if (!MaybeCreateJavaObject()) {
|
||||
// ShowFailureDialog is never called in button mode.
|
||||
// TODO(crbug.com/347736746): Remove rp_mode from this method.
|
||||
CHECK(rp_mode == blink::mojom::RpMode::kWidget);
|
||||
|
||||
if (!MaybeCreateJavaObject(rp_mode)) {
|
||||
// It's possible that the constructor cannot access the bottom sheet clank
|
||||
// component. That case may be temporary but we can't let users in a
|
||||
// waiting state so report that AccountSelectionView is dismissed instead.
|
||||
@ -241,8 +244,9 @@ bool AccountSelectionViewAndroid::ShowErrorDialog(
|
||||
blink::mojom::RpMode rp_mode,
|
||||
const content::IdentityProviderMetadata& idp_metadata,
|
||||
const std::optional<TokenError>& error) {
|
||||
// TODO(crbug.com/41491333): Use rp_mode for button flows on Android.
|
||||
if (!MaybeCreateJavaObject()) {
|
||||
// TODO(crbug.com/347117752): Implement button mode error dialog.
|
||||
if (rp_mode == blink::mojom::RpMode::kButton ||
|
||||
!MaybeCreateJavaObject(rp_mode)) {
|
||||
// It's possible that the constructor cannot access the bottom sheet clank
|
||||
// component. That case may be temporary but we can't let users in a
|
||||
// waiting state so report that AccountSelectionView is dismissed instead.
|
||||
@ -267,8 +271,15 @@ bool AccountSelectionViewAndroid::ShowLoadingDialog(
|
||||
const std::string& idp_for_display,
|
||||
blink::mojom::RpContext rp_context,
|
||||
blink::mojom::RpMode rp_mode) {
|
||||
// TODO(crbug.com/327273595): Prototype button flow on Android.
|
||||
return false;
|
||||
if (!MaybeCreateJavaObject(rp_mode)) {
|
||||
// It's possible that the constructor cannot access the bottom sheet clank
|
||||
// component. That case may be temporary but we can't let users in a
|
||||
// waiting state so report that AccountSelectionView is dismissed instead.
|
||||
delegate_->OnDismiss(DismissReason::kOther);
|
||||
return false;
|
||||
}
|
||||
// TODO(crbug.com/327273595): Add button flow loading dialog on Android.
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string AccountSelectionViewAndroid::GetTitle() const {
|
||||
@ -358,7 +369,8 @@ void AccountSelectionViewAndroid::OnAccountsDisplayed(JNIEnv* env) {
|
||||
delegate_->OnAccountsDisplayed();
|
||||
}
|
||||
|
||||
bool AccountSelectionViewAndroid::MaybeCreateJavaObject() {
|
||||
bool AccountSelectionViewAndroid::MaybeCreateJavaObject(
|
||||
blink::mojom::RpMode rp_mode) {
|
||||
if (delegate_->GetNativeView() == nullptr ||
|
||||
delegate_->GetNativeView()->GetWindowAndroid() == nullptr) {
|
||||
return false; // No window attached (yet or anymore).
|
||||
@ -366,10 +378,12 @@ bool AccountSelectionViewAndroid::MaybeCreateJavaObject() {
|
||||
if (java_object_internal_) {
|
||||
return true;
|
||||
}
|
||||
JNIEnv* env = AttachCurrentThread();
|
||||
java_object_internal_ = Java_AccountSelectionBridge_create(
|
||||
AttachCurrentThread(), reinterpret_cast<intptr_t>(this),
|
||||
env, reinterpret_cast<intptr_t>(this),
|
||||
delegate_->GetWebContents()->GetJavaWebContents(),
|
||||
delegate_->GetNativeView()->GetWindowAndroid()->GetJavaObject());
|
||||
delegate_->GetNativeView()->GetWindowAndroid()->GetJavaObject(),
|
||||
static_cast<jint>(rp_mode));
|
||||
return !!java_object_internal_;
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "base/functional/callback.h"
|
||||
#include "chrome/browser/ui/webid/account_selection_view.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
#include "third_party/blink/public/mojom/webid/federated_auth_request.mojom-shared.h"
|
||||
|
||||
using TokenError = content::IdentityCredentialTokenError;
|
||||
|
||||
@ -71,7 +72,8 @@ class AccountSelectionViewAndroid : public AccountSelectionView {
|
||||
private:
|
||||
// Returns either true if the java counterpart of this bridge is initialized
|
||||
// successfully or false if the creation failed.
|
||||
bool MaybeCreateJavaObject();
|
||||
bool MaybeCreateJavaObject(
|
||||
blink::mojom::RpMode rp_mode = blink::mojom::RpMode::kWidget);
|
||||
|
||||
base::android::ScopedJavaGlobalRef<jobject> java_object_internal_;
|
||||
};
|
||||
|
@ -38,6 +38,7 @@ android_library("java") {
|
||||
"//third_party/androidx:androidx_annotation_annotation_java",
|
||||
"//third_party/androidx:androidx_browser_browser_java",
|
||||
"//third_party/androidx:androidx_recyclerview_recyclerview_java",
|
||||
"//third_party/blink/public/mojom:mojom_platform_java",
|
||||
"//third_party/jni_zero:jni_zero_java",
|
||||
"//ui/android:ui_java",
|
||||
"//url:gurl_java",
|
||||
@ -64,6 +65,7 @@ android_resources("java_resources") {
|
||||
]
|
||||
sources = [
|
||||
"java/res/layout/account_selection_account_item.xml",
|
||||
"java/res/layout/account_selection_button_mode_sheet.xml",
|
||||
"java/res/layout/account_selection_continue_button.xml",
|
||||
"java/res/layout/account_selection_data_sharing_consent_item.xml",
|
||||
"java/res/layout/account_selection_header_item.xml",
|
||||
@ -78,6 +80,7 @@ android_resources("java_resources") {
|
||||
robolectric_library("junit") {
|
||||
resources_package = "org.chromium.chrome.browser.ui.android.webid"
|
||||
sources = [
|
||||
"java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionButtonModeViewTest.java",
|
||||
"java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionControllerTest.java",
|
||||
"java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionViewTest.java",
|
||||
]
|
||||
@ -101,6 +104,7 @@ robolectric_library("junit") {
|
||||
"//third_party/androidx:androidx_recyclerview_recyclerview_java",
|
||||
"//third_party/androidx:androidx_test_core_java",
|
||||
"//third_party/androidx:androidx_test_ext_junit_java",
|
||||
"//third_party/blink/public/mojom:mojom_platform_java",
|
||||
"//third_party/junit",
|
||||
"//third_party/mockito:mockito_java",
|
||||
"//ui/android:ui_full_java",
|
||||
@ -144,6 +148,7 @@ android_library("javatests") {
|
||||
"//third_party/androidx:androidx_recyclerview_recyclerview_java",
|
||||
"//third_party/androidx:androidx_test_monitor_java",
|
||||
"//third_party/androidx:androidx_test_runner_java",
|
||||
"//third_party/blink/public/mojom:mojom_platform_java",
|
||||
"//third_party/hamcrest:hamcrest_core_java",
|
||||
"//third_party/hamcrest:hamcrest_library_java",
|
||||
"//third_party/junit",
|
||||
|
67
chrome/browser/ui/android/webid/internal/java/res/layout/account_selection_button_mode_sheet.xml
Normal file
67
chrome/browser/ui/android/webid/internal/java/res/layout/account_selection_button_mode_sheet.xml
Normal file
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2024 The Chromium Authors
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file.
|
||||
-->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:paddingBottom="8dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/drag_handlebar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:importantForAccessibility="no"
|
||||
app:srcCompat="@drawable/drag_handlebar" />
|
||||
|
||||
<include layout="@layout/account_selection_header_item"
|
||||
android:id="@+id/header_view_item" />
|
||||
<FrameLayout
|
||||
android:id="@+id/sheet_item_list_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/sheet_item_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:divider="@null"
|
||||
tools:listitem="@layout/account_selection_account_item"/>
|
||||
</FrameLayout>
|
||||
|
||||
<include layout="@layout/idp_signin_text_item"
|
||||
android:id="@+id/idp_signin"
|
||||
android:visibility="gone"/>
|
||||
<LinearLayout
|
||||
android:id="@+id/error_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/account_selection_sheet_horizontal_margin"
|
||||
android:orientation="vertical"
|
||||
android:layoutDirection="locale"
|
||||
android:textDirection="locale"
|
||||
android:visibility="gone">
|
||||
<include layout="@layout/error_summary_text_item"
|
||||
android:id="@+id/error_summary"/>
|
||||
<include layout="@layout/error_description_text_item"
|
||||
android:id="@+id/error_description"/>
|
||||
</LinearLayout>
|
||||
<include layout="@layout/account_selection_continue_button"
|
||||
android:id="@+id/account_selection_continue_btn"
|
||||
android:visibility="gone"/>
|
||||
<include layout="@layout/account_selection_data_sharing_consent_item"
|
||||
android:id="@+id/user_data_sharing_consent"
|
||||
android:visibility="gone"/>
|
||||
</LinearLayout>
|
@ -12,6 +12,7 @@ import org.jni_zero.CalledByNative;
|
||||
import org.jni_zero.NativeMethods;
|
||||
|
||||
import org.chromium.base.ContextUtils;
|
||||
import org.chromium.blink.mojom.RpMode;
|
||||
import org.chromium.chrome.browser.tab.Tab;
|
||||
import org.chromium.chrome.browser.tab.TabUtils;
|
||||
import org.chromium.chrome.browser.ui.android.webid.data.Account;
|
||||
@ -46,10 +47,12 @@ class AccountSelectionBridge implements AccountSelectionComponent.Delegate {
|
||||
long nativeView,
|
||||
Tab tab,
|
||||
WindowAndroid windowAndroid,
|
||||
BottomSheetController bottomSheetController) {
|
||||
BottomSheetController bottomSheetController,
|
||||
@RpMode.EnumType int rpMode) {
|
||||
mNativeView = nativeView;
|
||||
mAccountSelectionComponent =
|
||||
new AccountSelectionCoordinator(tab, windowAndroid, bottomSheetController, this);
|
||||
new AccountSelectionCoordinator(
|
||||
tab, windowAndroid, bottomSheetController, rpMode, this);
|
||||
}
|
||||
|
||||
@CalledByNative
|
||||
@ -72,12 +75,16 @@ class AccountSelectionBridge implements AccountSelectionComponent.Delegate {
|
||||
|
||||
@CalledByNative
|
||||
private static @Nullable AccountSelectionBridge create(
|
||||
long nativeView, WebContents webContents, WindowAndroid windowAndroid) {
|
||||
long nativeView,
|
||||
WebContents webContents,
|
||||
WindowAndroid windowAndroid,
|
||||
@RpMode.EnumType int rpMode) {
|
||||
BottomSheetController bottomSheetController =
|
||||
BottomSheetControllerProvider.from(windowAndroid);
|
||||
if (bottomSheetController == null) return null;
|
||||
Tab tab = TabUtils.fromWebContents(webContents);
|
||||
return new AccountSelectionBridge(nativeView, tab, windowAndroid, bottomSheetController);
|
||||
return new AccountSelectionBridge(
|
||||
nativeView, tab, windowAndroid, bottomSheetController, rpMode);
|
||||
}
|
||||
|
||||
@CalledByNative
|
||||
|
70
chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionButtonModeViewTest.java
Normal file
70
chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionButtonModeViewTest.java
Normal file
@ -0,0 +1,70 @@
|
||||
// Copyright 2024 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.ui.android.webid;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import org.chromium.base.test.BaseRobolectricTestRunner;
|
||||
import org.chromium.blink.mojom.RpMode;
|
||||
import org.chromium.ui.base.TestActivity;
|
||||
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
|
||||
import org.chromium.ui.modelutil.PropertyModel;
|
||||
|
||||
/**
|
||||
* View tests for the Account Selection Button Mode component ensure that model changes are
|
||||
* reflected in the sheet.
|
||||
*/
|
||||
@RunWith(BaseRobolectricTestRunner.class)
|
||||
public class AccountSelectionButtonModeViewTest {
|
||||
@Rule
|
||||
public ActivityScenarioRule<TestActivity> mActivityScenarioRule =
|
||||
new ActivityScenarioRule<>(TestActivity.class);
|
||||
|
||||
private PropertyModel mModel;
|
||||
private ModelList mSheetAccountItems;
|
||||
private View mContentView;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mActivityScenarioRule
|
||||
.getScenario()
|
||||
.onActivity(
|
||||
activity -> {
|
||||
mModel =
|
||||
new PropertyModel.Builder(
|
||||
AccountSelectionProperties.ItemProperties
|
||||
.ALL_KEYS)
|
||||
.build();
|
||||
mSheetAccountItems = new ModelList();
|
||||
mContentView =
|
||||
AccountSelectionCoordinator.setupContentView(
|
||||
activity,
|
||||
mModel,
|
||||
mSheetAccountItems,
|
||||
/* rpMode= */ RpMode.BUTTON);
|
||||
activity.setContentView(mContentView);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDragHandlebarShown() {
|
||||
assertEquals(View.VISIBLE, mContentView.getVisibility());
|
||||
View handlebar = mContentView.findViewById(R.id.drag_handlebar);
|
||||
assertTrue(handlebar.isShown());
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.chromium.base.IntentUtils;
|
||||
import org.chromium.blink.mojom.RpMode;
|
||||
import org.chromium.chrome.browser.IntentHandler;
|
||||
import org.chromium.chrome.browser.LaunchIntentDispatcher;
|
||||
import org.chromium.chrome.browser.app.ChromeActivity;
|
||||
@ -82,6 +83,7 @@ public class AccountSelectionCoordinator
|
||||
Tab tab,
|
||||
WindowAndroid windowAndroid,
|
||||
BottomSheetController sheetController,
|
||||
@RpMode.EnumType int rpMode,
|
||||
AccountSelectionComponent.Delegate delegate) {
|
||||
mBottomSheetController = sheetController;
|
||||
mWindowAndroid = windowAndroid;
|
||||
@ -93,7 +95,7 @@ public class AccountSelectionCoordinator
|
||||
.build();
|
||||
// Construct view and its related adaptor to be displayed in the bottom sheet.
|
||||
ModelList sheetItems = new ModelList();
|
||||
View contentView = setupContentView(context, model, sheetItems);
|
||||
View contentView = setupContentView(context, model, sheetItems, rpMode);
|
||||
mSheetItemListView = contentView.findViewById(R.id.sheet_item_list);
|
||||
|
||||
// Setup the bottom sheet content view.
|
||||
@ -124,11 +126,18 @@ public class AccountSelectionCoordinator
|
||||
avatarSize);
|
||||
}
|
||||
|
||||
static View setupContentView(Context context, PropertyModel model, ModelList sheetItems) {
|
||||
static View setupContentView(
|
||||
Context context,
|
||||
PropertyModel model,
|
||||
ModelList sheetItems,
|
||||
@RpMode.EnumType int rpMode) {
|
||||
int accountSelectionSheetLayout =
|
||||
rpMode == RpMode.BUTTON
|
||||
? R.layout.account_selection_button_mode_sheet
|
||||
: R.layout.account_selection_sheet;
|
||||
View contentView =
|
||||
(LinearLayout)
|
||||
LayoutInflater.from(context)
|
||||
.inflate(R.layout.account_selection_sheet, null);
|
||||
LayoutInflater.from(context).inflate(accountSelectionSheetLayout, null);
|
||||
|
||||
PropertyModelChangeProcessor.create(
|
||||
model, contentView, AccountSelectionViewBinder::bindContentView);
|
||||
|
@ -49,12 +49,16 @@ import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import org.chromium.base.test.params.ParameterAnnotations;
|
||||
import org.chromium.base.test.params.ParameterSet;
|
||||
import org.chromium.base.test.params.ParameterizedRunner;
|
||||
import org.chromium.base.test.util.ApplicationTestUtils;
|
||||
import org.chromium.base.test.util.Batch;
|
||||
import org.chromium.base.test.util.CommandLineFlags;
|
||||
import org.chromium.base.test.util.Criteria;
|
||||
import org.chromium.base.test.util.CriteriaHelper;
|
||||
import org.chromium.base.test.util.ScalableTimeout;
|
||||
import org.chromium.blink.mojom.RpMode;
|
||||
import org.chromium.chrome.browser.IntentHandler;
|
||||
import org.chromium.chrome.browser.customtabs.CustomTabActivity;
|
||||
import org.chromium.chrome.browser.flags.ChromeSwitches;
|
||||
@ -62,7 +66,7 @@ import org.chromium.chrome.browser.ui.android.webid.data.Account;
|
||||
import org.chromium.chrome.browser.ui.android.webid.data.ClientIdMetadata;
|
||||
import org.chromium.chrome.browser.ui.android.webid.data.IdentityCredentialTokenError;
|
||||
import org.chromium.chrome.browser.ui.android.webid.data.IdentityProviderMetadata;
|
||||
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
|
||||
import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
|
||||
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
|
||||
import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
|
||||
import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
|
||||
@ -74,15 +78,28 @@ import org.chromium.url.GURL;
|
||||
import org.chromium.url.JUnitTestGURLs;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Integration tests for the Account Selection component check that the calls to the Account
|
||||
* Selection API end up rendering a View.
|
||||
* Selection API end up rendering a View. This class is parameterized to run all tests for each RP
|
||||
* mode.
|
||||
*/
|
||||
@RunWith(ChromeJUnit4ClassRunner.class)
|
||||
@RunWith(ParameterizedRunner.class)
|
||||
@ParameterAnnotations.UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
|
||||
@Batch(Batch.PER_CLASS)
|
||||
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
|
||||
public class AccountSelectionIntegrationTest {
|
||||
@ParameterAnnotations.ClassParameter
|
||||
private static List<ParameterSet> sClassParams =
|
||||
Arrays.asList(
|
||||
new ParameterSet().value(RpMode.WIDGET).name("widget"),
|
||||
new ParameterSet().value(RpMode.BUTTON).name("button"));
|
||||
|
||||
public AccountSelectionIntegrationTest(@RpMode.EnumType int rpMode) {
|
||||
mRpMode = rpMode;
|
||||
}
|
||||
|
||||
private static final String EXAMPLE_ETLD_PLUS_ONE = "example.com";
|
||||
private static final String TEST_ETLD_PLUS_ONE_1 = "one.com";
|
||||
private static final String TEST_ETLD_PLUS_ONE_2 = "two.com";
|
||||
@ -127,6 +144,7 @@ public class AccountSelectionIntegrationTest {
|
||||
private String mTestUrlTermsOfService;
|
||||
private String mTestUrlPrivacyPolicy;
|
||||
private ClientIdMetadata mClientIdMetadata;
|
||||
private @RpMode.EnumType int mRpMode;
|
||||
|
||||
@Before
|
||||
public void setUp() throws InterruptedException {
|
||||
@ -142,6 +160,7 @@ public class AccountSelectionIntegrationTest {
|
||||
mActivityTestRule.getActivity().getActivityTab(),
|
||||
mActivityTestRule.getActivity().getWindowAndroid(),
|
||||
mBottomSheetController,
|
||||
mRpMode,
|
||||
mMockBridge);
|
||||
});
|
||||
|
||||
@ -374,6 +393,7 @@ public class AccountSelectionIntegrationTest {
|
||||
activity.getActivityTab(),
|
||||
activity.getWindowAndroid(),
|
||||
customTabController,
|
||||
mRpMode,
|
||||
mMockBridge);
|
||||
customTabComponent.closeModalDialog();
|
||||
});
|
||||
|
@ -30,12 +30,16 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner;
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner.Parameter;
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner.Parameters;
|
||||
import org.robolectric.shadows.ShadowLooper;
|
||||
|
||||
import org.chromium.base.Callback;
|
||||
import org.chromium.base.test.BaseRobolectricTestRunner;
|
||||
import org.chromium.base.test.BaseRobolectricTestRule;
|
||||
import org.chromium.base.test.util.CriteriaHelper;
|
||||
import org.chromium.base.test.util.ScalableTimeout;
|
||||
import org.chromium.blink.mojom.RpMode;
|
||||
import org.chromium.chrome.browser.ui.android.webid.AccountSelectionProperties.AccountProperties;
|
||||
import org.chromium.chrome.browser.ui.android.webid.AccountSelectionProperties.ContinueButtonProperties;
|
||||
import org.chromium.chrome.browser.ui.android.webid.AccountSelectionProperties.DataSharingConsentProperties;
|
||||
@ -53,47 +57,48 @@ import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
|
||||
import org.chromium.ui.modelutil.PropertyModel;
|
||||
import org.chromium.ui.widget.ButtonCompat;
|
||||
import org.chromium.url.GURL;
|
||||
import org.chromium.url.JUnitTestGURLs;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* View tests for the Account Selection component ensure that model changes are reflected in the
|
||||
* sheet.
|
||||
* sheet. This class is parameterized to run all tests for each RP mode.
|
||||
*/
|
||||
@RunWith(BaseRobolectricTestRunner.class)
|
||||
@RunWith(ParameterizedRobolectricTestRunner.class)
|
||||
public class AccountSelectionViewTest {
|
||||
@Parameter(0)
|
||||
public @RpMode.EnumType int mRpMode;
|
||||
|
||||
@Parameters
|
||||
public static Collection<Object> data() {
|
||||
return Arrays.asList(new Object[] {RpMode.WIDGET, RpMode.BUTTON});
|
||||
}
|
||||
|
||||
@Rule(order = -2)
|
||||
public BaseRobolectricTestRule mBaseRule = new BaseRobolectricTestRule();
|
||||
|
||||
// Note that these are not actual ETLD+1 values, but this is irrelevant for the purposes of this
|
||||
// test.
|
||||
private static final String TEST_IDP_ETLD_PLUS_ONE = JUnitTestGURLs.EXAMPLE_URL.getSpec();
|
||||
private static final String TEST_RP_ETLD_PLUS_ONE = JUnitTestGURLs.URL_1.getSpec();
|
||||
private static final GURL TEST_PROFILE_PIC = JUnitTestGURLs.EXAMPLE_URL;
|
||||
private static final GURL TEST_CONFIG_URL = JUnitTestGURLs.URL_1;
|
||||
private static final GURL TEST_LOGIN_URL = JUnitTestGURLs.URL_2;
|
||||
|
||||
private static final Account ANA =
|
||||
new Account("Ana", "ana@email.example", "Ana Doe", "Ana", TEST_PROFILE_PIC, null, true);
|
||||
private static final Account NO_ONE =
|
||||
new Account("", "", "No Subject", "", TEST_PROFILE_PIC, null, true);
|
||||
private static final Account BOB =
|
||||
new Account("Bob", "", "Bob", "", TEST_PROFILE_PIC, null, true);
|
||||
|
||||
private static final GURL TEST_ERROR_URL = JUnitTestGURLs.URL_1;
|
||||
private static final GURL TEST_EMPTY_ERROR_URL = new GURL("");
|
||||
private static final String TEST_IDP_ETLD_PLUS_ONE = "https://idp.com";
|
||||
private static final String TEST_RP_ETLD_PLUS_ONE = "https://rp.com";
|
||||
|
||||
// Android chrome strings may have link tags which needs to be removed before comparing with the
|
||||
// actual text on the dialog.
|
||||
private static final String LINK_TAG_REGEX = "<[^>]*>";
|
||||
|
||||
private static final IdentityProviderMetadata TEST_IDP_METADATA =
|
||||
new IdentityProviderMetadata(
|
||||
Color.BLUE,
|
||||
Color.GREEN,
|
||||
"https://icon-url.example",
|
||||
TEST_CONFIG_URL,
|
||||
TEST_LOGIN_URL,
|
||||
false);
|
||||
// Constants but can only be initialized after parameterized test runner setup.
|
||||
private GURL mTestProfilePicUrl;
|
||||
private GURL mTestConfigUrl;
|
||||
private GURL mTestLoginUrl;
|
||||
private GURL mTestErrorUrl;
|
||||
private GURL mTestEmptyErrorUrl;
|
||||
private Account mAnaAccount;
|
||||
private Account mNoOneAccount;
|
||||
private Account mBobAccount;
|
||||
private IdentityProviderMetadata mTestIdpMetadata;
|
||||
|
||||
private class RpContext {
|
||||
public String mValue;
|
||||
@ -178,7 +183,7 @@ public class AccountSelectionViewTest {
|
||||
private final String appendExtraDescription(String code, GURL url) {
|
||||
String initialDescription = mCodeToDescription.get(code);
|
||||
if (AccountSelectionViewBinder.GENERIC.equals(code)) {
|
||||
if (TEST_EMPTY_ERROR_URL.equals(url)) {
|
||||
if (mTestEmptyErrorUrl.equals(url)) {
|
||||
return initialDescription;
|
||||
}
|
||||
return initialDescription
|
||||
@ -188,7 +193,7 @@ public class AccountSelectionViewTest {
|
||||
.replaceAll(LINK_TAG_REGEX, "");
|
||||
}
|
||||
|
||||
if (TEST_EMPTY_ERROR_URL.equals(url)) {
|
||||
if (mTestEmptyErrorUrl.equals(url)) {
|
||||
return initialDescription
|
||||
+ " "
|
||||
+ mResources.getString(
|
||||
@ -224,6 +229,49 @@ public class AccountSelectionViewTest {
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mTestProfilePicUrl = new GURL("https://profile-picture.com");
|
||||
mTestConfigUrl = new GURL("https://idp.com/fedcm.json");
|
||||
mTestLoginUrl = new GURL("https://idp.com/login");
|
||||
mTestErrorUrl = new GURL("https://idp.com/error");
|
||||
mTestEmptyErrorUrl = new GURL("");
|
||||
|
||||
mAnaAccount =
|
||||
new Account(
|
||||
"Ana",
|
||||
"ana@email.example",
|
||||
"Ana Doe",
|
||||
"Ana",
|
||||
mTestProfilePicUrl,
|
||||
/* pictureBitmap= */ null,
|
||||
/* isSignIn= */ true);
|
||||
mNoOneAccount =
|
||||
new Account(
|
||||
"",
|
||||
"",
|
||||
"No Subject",
|
||||
"",
|
||||
mTestProfilePicUrl,
|
||||
/* pictureBitmap= */ null,
|
||||
/* isSignIn= */ true);
|
||||
mBobAccount =
|
||||
new Account(
|
||||
"Bob",
|
||||
"",
|
||||
"Bob",
|
||||
"",
|
||||
mTestProfilePicUrl,
|
||||
/* pictureBitmap= */ null,
|
||||
/* isSignIn= */ true);
|
||||
|
||||
mTestIdpMetadata =
|
||||
new IdentityProviderMetadata(
|
||||
Color.BLUE,
|
||||
Color.GREEN,
|
||||
"https://icon-url.example",
|
||||
mTestConfigUrl,
|
||||
mTestLoginUrl,
|
||||
false);
|
||||
|
||||
mActivityScenarioRule
|
||||
.getScenario()
|
||||
.onActivity(
|
||||
@ -236,7 +284,7 @@ public class AccountSelectionViewTest {
|
||||
mSheetAccountItems = new ModelList();
|
||||
mContentView =
|
||||
AccountSelectionCoordinator.setupContentView(
|
||||
activity, mModel, mSheetAccountItems);
|
||||
activity, mModel, mSheetAccountItems, mRpMode);
|
||||
activity.setContentView(mContentView);
|
||||
mResources = activity.getResources();
|
||||
});
|
||||
@ -341,22 +389,25 @@ public class AccountSelectionViewTest {
|
||||
@Test
|
||||
public void testAccountsChangedByModel() {
|
||||
mSheetAccountItems.addAll(
|
||||
asList(buildAccountItem(ANA), buildAccountItem(NO_ONE), buildAccountItem(BOB)));
|
||||
asList(
|
||||
buildAccountItem(mAnaAccount),
|
||||
buildAccountItem(mNoOneAccount),
|
||||
buildAccountItem(mBobAccount)));
|
||||
ShadowLooper.shadowMainLooper().idle();
|
||||
|
||||
assertEquals(View.VISIBLE, mContentView.getVisibility());
|
||||
assertEquals("Incorrect account count", 3, getAccounts().getChildCount());
|
||||
assertEquals("Incorrect name", ANA.getName(), getAccountNameAt(0).getText());
|
||||
assertEquals("Incorrect email", ANA.getEmail(), getAccountEmailAt(0).getText());
|
||||
assertEquals("Incorrect name", NO_ONE.getName(), getAccountNameAt(1).getText());
|
||||
assertEquals("Incorrect email", NO_ONE.getEmail(), getAccountEmailAt(1).getText());
|
||||
assertEquals("Incorrect name", BOB.getName(), getAccountNameAt(2).getText());
|
||||
assertEquals("Incorrect email", BOB.getEmail(), getAccountEmailAt(2).getText());
|
||||
assertEquals("Incorrect name", mAnaAccount.getName(), getAccountNameAt(0).getText());
|
||||
assertEquals("Incorrect email", mAnaAccount.getEmail(), getAccountEmailAt(0).getText());
|
||||
assertEquals("Incorrect name", mNoOneAccount.getName(), getAccountNameAt(1).getText());
|
||||
assertEquals("Incorrect email", mNoOneAccount.getEmail(), getAccountEmailAt(1).getText());
|
||||
assertEquals("Incorrect name", mBobAccount.getName(), getAccountNameAt(2).getText());
|
||||
assertEquals("Incorrect email", mBobAccount.getEmail(), getAccountEmailAt(2).getText());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAccountsAreClickable() {
|
||||
mSheetAccountItems.addAll(Collections.singletonList(buildAccountItem(ANA)));
|
||||
mSheetAccountItems.addAll(Collections.singletonList(buildAccountItem(mAnaAccount)));
|
||||
ShadowLooper.shadowMainLooper().idle();
|
||||
|
||||
assertEquals(View.VISIBLE, mContentView.getVisibility());
|
||||
@ -365,7 +416,7 @@ public class AccountSelectionViewTest {
|
||||
|
||||
getAccounts().getChildAt(0).performClick();
|
||||
|
||||
waitForEvent(mAccountCallback).onResult(eq(ANA));
|
||||
waitForEvent(mAccountCallback).onResult(eq(mAnaAccount));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -376,14 +427,14 @@ public class AccountSelectionViewTest {
|
||||
new MVCListAdapter.ListItem(
|
||||
AccountSelectionProperties.ITEM_TYPE_ACCOUNT,
|
||||
new PropertyModel.Builder(AccountProperties.ALL_KEYS)
|
||||
.with(AccountProperties.ACCOUNT, ANA)
|
||||
.with(AccountProperties.ACCOUNT, mAnaAccount)
|
||||
.with(AccountProperties.ON_CLICK_LISTENER, null)
|
||||
.build()));
|
||||
ShadowLooper.shadowMainLooper().idle();
|
||||
|
||||
mModel.set(
|
||||
ItemProperties.CONTINUE_BUTTON,
|
||||
buildContinueButton(ANA, TEST_IDP_METADATA, HeaderType.SIGN_IN));
|
||||
buildContinueButton(mAnaAccount, mTestIdpMetadata, HeaderType.SIGN_IN));
|
||||
assertEquals(View.VISIBLE, mContentView.getVisibility());
|
||||
|
||||
assertNotNull(getAccounts().getChildAt(0));
|
||||
@ -392,7 +443,7 @@ public class AccountSelectionViewTest {
|
||||
assertTrue(continueButton.isShown());
|
||||
continueButton.performClick();
|
||||
|
||||
waitForEvent(mAccountCallback).onResult(eq(ANA));
|
||||
waitForEvent(mAccountCallback).onResult(eq(mAnaAccount));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -427,13 +478,13 @@ public class AccountSelectionViewTest {
|
||||
expectedTextColor,
|
||||
/* brandBackgroundColor= */ Color.GREEN,
|
||||
"https://icon-url.example",
|
||||
TEST_CONFIG_URL,
|
||||
TEST_LOGIN_URL,
|
||||
mTestConfigUrl,
|
||||
mTestLoginUrl,
|
||||
false);
|
||||
|
||||
mModel.set(
|
||||
ItemProperties.CONTINUE_BUTTON,
|
||||
buildContinueButton(ANA, idpMetadata, HeaderType.SIGN_IN));
|
||||
buildContinueButton(mAnaAccount, idpMetadata, HeaderType.SIGN_IN));
|
||||
|
||||
assertEquals(View.VISIBLE, mContentView.getVisibility());
|
||||
|
||||
@ -513,7 +564,7 @@ public class AccountSelectionViewTest {
|
||||
|
||||
mModel.set(
|
||||
ItemProperties.CONTINUE_BUTTON,
|
||||
buildContinueButton(null, TEST_IDP_METADATA, HeaderType.SIGN_IN_TO_IDP_STATIC));
|
||||
buildContinueButton(null, mTestIdpMetadata, HeaderType.SIGN_IN_TO_IDP_STATIC));
|
||||
ButtonCompat continueButton =
|
||||
mContentView.findViewById(R.id.account_selection_continue_btn);
|
||||
assertTrue(continueButton.isShown());
|
||||
@ -527,23 +578,21 @@ public class AccountSelectionViewTest {
|
||||
public void testErrorDisplayed() {
|
||||
final TokenError[] mErrors =
|
||||
new TokenError[] {
|
||||
new TokenError(AccountSelectionViewBinder.GENERIC, TEST_EMPTY_ERROR_URL),
|
||||
new TokenError(AccountSelectionViewBinder.GENERIC, TEST_ERROR_URL),
|
||||
new TokenError(AccountSelectionViewBinder.GENERIC, mTestEmptyErrorUrl),
|
||||
new TokenError(AccountSelectionViewBinder.GENERIC, mTestErrorUrl),
|
||||
new TokenError(AccountSelectionViewBinder.INVALID_REQUEST, mTestEmptyErrorUrl),
|
||||
new TokenError(AccountSelectionViewBinder.INVALID_REQUEST, mTestErrorUrl),
|
||||
new TokenError(
|
||||
AccountSelectionViewBinder.INVALID_REQUEST, TEST_EMPTY_ERROR_URL),
|
||||
new TokenError(AccountSelectionViewBinder.INVALID_REQUEST, TEST_ERROR_URL),
|
||||
AccountSelectionViewBinder.UNAUTHORIZED_CLIENT, mTestEmptyErrorUrl),
|
||||
new TokenError(AccountSelectionViewBinder.UNAUTHORIZED_CLIENT, mTestErrorUrl),
|
||||
new TokenError(AccountSelectionViewBinder.ACCESS_DENIED, mTestEmptyErrorUrl),
|
||||
new TokenError(AccountSelectionViewBinder.ACCESS_DENIED, mTestErrorUrl),
|
||||
new TokenError(
|
||||
AccountSelectionViewBinder.UNAUTHORIZED_CLIENT, TEST_EMPTY_ERROR_URL),
|
||||
new TokenError(AccountSelectionViewBinder.UNAUTHORIZED_CLIENT, TEST_ERROR_URL),
|
||||
new TokenError(AccountSelectionViewBinder.ACCESS_DENIED, TEST_EMPTY_ERROR_URL),
|
||||
new TokenError(AccountSelectionViewBinder.ACCESS_DENIED, TEST_ERROR_URL),
|
||||
AccountSelectionViewBinder.TEMPORARILY_UNAVAILABLE, mTestEmptyErrorUrl),
|
||||
new TokenError(
|
||||
AccountSelectionViewBinder.TEMPORARILY_UNAVAILABLE,
|
||||
TEST_EMPTY_ERROR_URL),
|
||||
new TokenError(
|
||||
AccountSelectionViewBinder.TEMPORARILY_UNAVAILABLE, TEST_ERROR_URL),
|
||||
new TokenError(AccountSelectionViewBinder.SERVER_ERROR, TEST_EMPTY_ERROR_URL),
|
||||
new TokenError(AccountSelectionViewBinder.SERVER_ERROR, TEST_ERROR_URL)
|
||||
AccountSelectionViewBinder.TEMPORARILY_UNAVAILABLE, mTestErrorUrl),
|
||||
new TokenError(AccountSelectionViewBinder.SERVER_ERROR, mTestEmptyErrorUrl),
|
||||
new TokenError(AccountSelectionViewBinder.SERVER_ERROR, mTestErrorUrl)
|
||||
};
|
||||
|
||||
for (TokenError error : mErrors) {
|
||||
|
Reference in New Issue
Block a user