blob: a59d56584acd7a044410f7d66db762a479816a26 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/webid/account_selection_bubble_view.h"
#include <string>
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ui/views/controls/hover_button.h"
#include "chrome/browser/ui/views/webid/fake_delegate.h"
#include "chrome/browser/ui/views/webid/identity_provider_display_data.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/views/chrome_views_test_base.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/web_contents_tester.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/events/base_event_utils.h"
#include "ui/views/controls/button/checkbox.h"
#include "ui/views/controls/button/md_text_button.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/test/button_test_api.h"
#include "ui/views/view.h"
namespace {
const std::u16string kTopFrameETLDPlusOne = u"top-frame-example.com";
const std::u16string kIframeETLDPlusOne = u"iframe-example.com";
const std::u16string kIdpETLDPlusOne = u"idp-example.com";
const std::u16string kTitleSignIn =
u"Sign in to top-frame-example.com with idp-example.com";
const std::u16string kTitleSignInWithoutIdp =
u"Sign in to top-frame-example.com";
const std::u16string kTitleSigningIn = u"Verifying…";
const std::u16string kTitleSigningInWithAutoReauthn = u"Signing you in…";
constexpr char kIdBase[] = "id";
constexpr char kEmailBase[] = "email";
constexpr char kNameBase[] = "name";
constexpr char kGivenNameBase[] = "given_name";
const char kTermsOfServiceUrl[] = "htpps://terms-of-service.com";
const char kPrivacyPolicyUrl[] = "https://privacy-policy.com";
constexpr int kDesiredAvatarSize = 30;
content::IdentityRequestAccount CreateTestIdentityRequestAccount(
const std::string& account_suffix,
content::IdentityRequestAccount::LoginState login_state) {
return content::IdentityRequestAccount(
std::string(kIdBase) + account_suffix,
std::string(kEmailBase) + account_suffix,
std::string(kNameBase) + account_suffix,
std::string(kGivenNameBase) + account_suffix, GURL::EmptyGURL(),
std::vector<std::string>(), login_state);
}
std::vector<content::IdentityRequestAccount> CreateTestIdentityRequestAccounts(
const std::vector<std::string>& account_suffixes,
content::IdentityRequestAccount::LoginState login_state) {
std::vector<content::IdentityRequestAccount> accounts;
for (const std::string& account_suffix : account_suffixes) {
accounts.push_back(
CreateTestIdentityRequestAccount(account_suffix, login_state));
}
return accounts;
}
content::ClientMetadata CreateTestClientMetadata(
const std::string& terms_of_service_url) {
return content::ClientMetadata((GURL(terms_of_service_url)),
(GURL(kPrivacyPolicyUrl)));
}
std::vector<std::string> GetChildClassNames(views::View* parent) {
std::vector<std::string> child_class_names;
for (views::View* child_view : parent->children()) {
child_class_names.push_back(child_view->GetClassName());
}
return child_class_names;
}
views::View* GetViewWithClassName(views::View* parent,
const std::string& class_name) {
for (views::View* child_view : parent->children()) {
if (child_view->GetClassName() == class_name)
return child_view;
}
return nullptr;
}
} // namespace
class AccountSelectionBubbleViewTest : public ChromeViewsTestBase {
public:
AccountSelectionBubbleViewTest() = default;
protected:
void CreateAccountSelectionBubble(bool exclude_title,
bool exclude_iframe,
bool show_auto_reauthn_checkbox) {
views::Widget::InitParams params =
CreateParams(views::Widget::InitParams::TYPE_WINDOW);
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
anchor_widget_ = std::make_unique<views::Widget>();
anchor_widget_->Init(std::move(params));
anchor_widget_->Show();
absl::optional<std::u16string> title =
exclude_title ? absl::nullopt
: absl::make_optional<std::u16string>(kIdpETLDPlusOne);
absl::optional<std::u16string> iframe_etld_plus_one =
exclude_iframe
? absl::nullopt
: absl::make_optional<std::u16string>(kIframeETLDPlusOne);
dialog_ = new AccountSelectionBubbleView(
kTopFrameETLDPlusOne, iframe_etld_plus_one, title,
blink::mojom::RpContext::kSignIn, show_auto_reauthn_checkbox,
anchor_widget_->GetContentsView(), shared_url_loader_factory(),
/*observer=*/nullptr);
views::BubbleDialogDelegateView::CreateBubble(dialog_)->Show();
}
void CreateSingleAccountPicker(bool show_back_button,
const content::IdentityRequestAccount& account,
const std::string& terms_of_service_url,
bool show_auto_reauthn_checkbox = false,
bool exclude_iframe = true) {
CreateAccountSelectionBubble(/*exclude_title=*/false, exclude_iframe,
show_auto_reauthn_checkbox);
IdentityProviderDisplayData idp_data(
kIdpETLDPlusOne, content::IdentityProviderMetadata(),
CreateTestClientMetadata(terms_of_service_url), {account},
/*request_permission=*/true);
dialog_->ShowSingleAccountConfirmDialog(
kTopFrameETLDPlusOne,
exclude_iframe
? absl::nullopt
: absl::make_optional<std::u16string>(kIframeETLDPlusOne),
account, idp_data, show_back_button);
}
void CreateMultiIdpAccountPicker(
const std::vector<IdentityProviderDisplayData>& idp_data_list) {
CreateAccountSelectionBubble(/*exclude_title=*/true,
/*exclude_iframe=*/true,
/*show_auto_reauthn_checkbox=*/false);
dialog_->ShowMultiAccountPicker(idp_data_list);
}
void CheckAccountRow(views::View* row, const std::string& account_suffix) {
std::vector<views::View*> row_children = row->children();
ASSERT_EQ(row_children.size(), 2u);
// Check the image.
views::ImageView* image_view =
static_cast<views::ImageView*>(row_children[0]);
EXPECT_TRUE(image_view);
// Check the text shown.
views::View* text_view = row_children[1];
views::BoxLayout* layout_manager =
static_cast<views::BoxLayout*>(text_view->GetLayoutManager());
ASSERT_TRUE(layout_manager);
EXPECT_EQ(layout_manager->GetOrientation(),
views::BoxLayout::Orientation::kVertical);
std::vector<views::View*> text_view_children = text_view->children();
ASSERT_EQ(text_view_children.size(), 2u);
std::string expected_name(std::string(kNameBase) + account_suffix);
views::Label* name_view = static_cast<views::Label*>(text_view_children[0]);
ASSERT_TRUE(name_view);
EXPECT_EQ(name_view->GetText(), base::UTF8ToUTF16(expected_name));
std::string expected_email(std::string(kEmailBase) + account_suffix);
views::Label* email_view =
static_cast<views::Label*>(text_view_children[1]);
ASSERT_TRUE(email_view);
EXPECT_EQ(email_view->GetText(), base::UTF8ToUTF16(expected_email));
}
void PerformHeaderChecks(
views::View* header,
const std::u16string& expected_title,
const absl::optional<std::u16string>& expected_subtitle,
bool expect_idp_brand_icon_in_header) {
// Perform some basic dialog checks.
EXPECT_FALSE(dialog()->ShouldShowCloseButton());
EXPECT_FALSE(dialog()->ShouldShowWindowTitle());
EXPECT_FALSE(dialog()->GetOkButton());
EXPECT_FALSE(dialog()->GetCancelButton());
if (expected_subtitle.has_value()) {
EXPECT_THAT(GetChildClassNames(header),
testing::ElementsAreArray({"View", "Label"}));
ASSERT_EQ(header->children().size(), 2u);
// Check subtitle text.
views::Label* subtitle_view =
static_cast<views::Label*>(header->children()[1]);
ASSERT_TRUE(subtitle_view);
EXPECT_EQ(subtitle_view->GetText(), expected_subtitle.value());
header = header->children()[0];
}
// Order: Potentially hidden IDP brand icon, potentially hidden back button,
// title, close button.
std::vector<std::string> expected_class_names = {"ImageButton", "Label",
"ImageButton"};
if (expect_idp_brand_icon_in_header) {
expected_class_names.insert(expected_class_names.begin(), "ImageView");
}
EXPECT_THAT(GetChildClassNames(header),
testing::ElementsAreArray(expected_class_names));
// Check title text.
views::Label* title_view =
static_cast<views::Label*>(GetViewWithClassName(header, "Label"));
ASSERT_TRUE(title_view);
EXPECT_EQ(title_view->GetText(), expected_title);
// Check separator.
if (expected_title == kTitleSignIn ||
expected_title == kTitleSignInWithoutIdp) {
EXPECT_STREQ("Separator", dialog()->children()[1]->GetClassName());
} else if (expected_title == kTitleSigningIn) {
EXPECT_STREQ("ProgressBar", dialog()->children()[1]->GetClassName());
}
}
std::u16string GetAccountButtonTitle(HoverButton* account) {
return account->title()->GetText();
}
views::Label* GetAccountButtonSubtitle(HoverButton* account) {
return account->subtitle();
}
views::View* GetAccountButtonIconView(HoverButton* account) {
return account->icon_view();
}
void TestSingleAccount(const std::u16string expected_title,
const absl::optional<std::u16string> expected_subtitle,
bool expect_idp_brand_icon_in_header) {
const std::string kAccountSuffix = "suffix";
content::IdentityRequestAccount account(CreateTestIdentityRequestAccount(
kAccountSuffix, content::IdentityRequestAccount::LoginState::kSignUp));
CreateSingleAccountPicker(
/*show_back_button=*/false, account, kTermsOfServiceUrl);
std::vector<views::View*> children = dialog()->children();
ASSERT_EQ(children.size(), 3u);
PerformHeaderChecks(children[0], expected_title, expected_subtitle,
expect_idp_brand_icon_in_header);
views::View* single_account_chooser = children[2];
ASSERT_EQ(single_account_chooser->children().size(), 3u);
CheckAccountRow(single_account_chooser->children()[0], kAccountSuffix);
// Check the "Continue as" button.
views::MdTextButton* button = static_cast<views::MdTextButton*>(
single_account_chooser->children()[1]);
ASSERT_TRUE(button);
EXPECT_EQ(button->GetText(),
base::UTF8ToUTF16("Continue as " + std::string(kGivenNameBase) +
kAccountSuffix));
views::StyledLabel* disclosure_text =
static_cast<views::StyledLabel*>(single_account_chooser->children()[2]);
ASSERT_TRUE(disclosure_text);
EXPECT_EQ(disclosure_text->GetText(),
u"To continue, idp-example.com will share your name, email "
u"address, and profile picture with this site. See this site's "
u"privacy policy and terms of service.");
}
void TestMultipleAccounts(
const std::u16string& expected_title,
const absl::optional<std::u16string>& expected_subtitle,
bool expect_idp_brand_icon_in_header,
bool expect_idp_row) {
const std::vector<std::string> kAccountSuffixes = {"0", "1", "2"};
{
std::vector<content::IdentityRequestAccount> account_list =
CreateTestIdentityRequestAccounts(
kAccountSuffixes,
content::IdentityRequestAccount::LoginState::kSignUp);
CreateAccountSelectionBubble(/*exclude_title=*/false,
/*exclude_iframe=*/true,
/*show_auto_reauthn_checkbox=*/false);
std::vector<IdentityProviderDisplayData> idp_data;
idp_data.emplace_back(
kIdpETLDPlusOne, content::IdentityProviderMetadata(),
CreateTestClientMetadata(/*terms_of_service_url=*/""), account_list,
/*request_permission=*/true);
dialog_->ShowMultiAccountPicker(idp_data);
}
std::vector<views::View*> children = dialog()->children();
ASSERT_EQ(children.size(), 3u);
PerformHeaderChecks(children[0], expected_title, expected_subtitle,
expect_idp_brand_icon_in_header);
views::ScrollView* scroller = static_cast<views::ScrollView*>(children[2]);
ASSERT_FALSE(scroller->children().empty());
views::View* wrapper = scroller->children()[0];
ASSERT_FALSE(wrapper->children().empty());
views::View* contents = wrapper->children()[0];
views::BoxLayout* layout_manager =
static_cast<views::BoxLayout*>(contents->GetLayoutManager());
EXPECT_TRUE(layout_manager);
EXPECT_EQ(layout_manager->GetOrientation(),
views::BoxLayout::Orientation::kVertical);
std::vector<views::View*> accounts = contents->children();
size_t accounts_index = 0;
if (expect_idp_row) {
EXPECT_LT(0u, accounts.size());
CheckIdpRow(accounts[0u], u"idp-example.com");
++accounts_index;
}
// Check the text shown.
CheckAccountRows(accounts, kAccountSuffixes, accounts_index);
EXPECT_EQ(accounts_index, accounts.size());
}
void TestFailureDialog(const std::u16string expected_title,
const absl::optional<std::u16string> expected_subtitle,
bool expect_idp_brand_icon_in_header) {
const std::string kAccountSuffix = "suffix";
content::IdentityRequestAccount account = CreateTestIdentityRequestAccount(
kAccountSuffix, content::IdentityRequestAccount::LoginState::kSignIn);
CreateAccountSelectionBubble(
/*exclude_title=*/false,
/*exclude_iframe=*/!expected_subtitle.has_value(),
/*show_auto_reauthn_checkbox=*/false);
dialog_->ShowFailureDialog(
kTopFrameETLDPlusOne,
expected_subtitle.has_value()
? absl::make_optional<std::u16string>(kIframeETLDPlusOne)
: absl::nullopt,
kIdpETLDPlusOne, content::IdentityProviderMetadata());
const std::vector<views::View*> children = dialog()->children();
ASSERT_EQ(children.size(), 2u);
PerformHeaderChecks(children[0], expected_title, expected_subtitle,
expect_idp_brand_icon_in_header);
// Check the "Continue" button.
views::MdTextButton* button =
static_cast<views::MdTextButton*>(children[1]->children()[0]);
ASSERT_TRUE(button);
EXPECT_EQ(button->GetText(),
l10n_util::GetStringUTF16(
IDS_IDP_SIGNIN_STATUS_FAILURE_DIALOG_CONTINUE));
}
// Checks the account rows starting at `accounts[accounts_index]`. Updates
// `accounts_index` to the first unused index in `accounts`, or to
// `accounts.size()` if done.
void CheckAccountRows(const std::vector<views::View*>& accounts,
const std::vector<std::string>& account_suffixes,
size_t& accounts_index) {
EXPECT_GE(accounts.size(), account_suffixes.size());
for (size_t i = 0; i < std::size(account_suffixes); ++i) {
ASSERT_STREQ("HoverButton", accounts[accounts_index]->GetClassName());
HoverButton* account_row =
static_cast<HoverButton*>(accounts[accounts_index++]);
ASSERT_TRUE(account_row);
EXPECT_EQ(GetAccountButtonTitle(account_row),
base::UTF8ToUTF16(kNameBase + account_suffixes[i]));
EXPECT_EQ(
GetAccountButtonSubtitle(account_row)->GetText(),
base::UTF8ToUTF16(std::string(kEmailBase) + account_suffixes[i]));
// The subtitle has changed style, so AutoColorReadabilityEnabled should
// be set.
EXPECT_TRUE(GetAccountButtonSubtitle(account_row)
->GetAutoColorReadabilityEnabled());
views::View* icon_view = GetAccountButtonIconView(account_row);
EXPECT_TRUE(icon_view);
EXPECT_EQ(icon_view->size(),
gfx::Size(kDesiredAvatarSize, kDesiredAvatarSize));
}
}
void CheckIdpRow(views::View* idp_account,
const std::u16string& expected_idp) {
// Order: Brand icon, title.
EXPECT_THAT(GetChildClassNames(idp_account),
testing::ElementsAre("ImageView", "Label"));
views::Label* title_view =
static_cast<views::Label*>(idp_account->children()[1]);
EXPECT_EQ(title_view->GetText(), expected_idp);
}
void SetUp() override {
feature_list_.InitAndEnableFeature(features::kFedCm);
test_web_contents_ =
content::WebContentsTester::CreateTestWebContents(&profile_, nullptr);
delegate_ = std::make_unique<FakeDelegate>(test_web_contents_.get());
test_shared_url_loader_factory_ =
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&test_url_loader_factory_);
ChromeViewsTestBase::SetUp();
}
void TearDown() override {
anchor_widget_.reset();
feature_list_.Reset();
ChromeViewsTestBase::TearDown();
}
AccountSelectionBubbleView* dialog() { return dialog_; }
scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory()
const {
return test_shared_url_loader_factory_;
}
raw_ptr<AccountSelectionBubbleView> dialog_;
private:
base::test::ScopedFeatureList feature_list_;
TestingProfile profile_;
// This enables uses of TestWebContents.
content::RenderViewHostTestEnabler test_render_host_factories_;
std::unique_ptr<content::WebContents> test_web_contents_;
std::unique_ptr<views::Widget> anchor_widget_;
std::unique_ptr<FakeDelegate> delegate_;
scoped_refptr<network::SharedURLLoaderFactory>
test_shared_url_loader_factory_;
network::TestURLLoaderFactory test_url_loader_factory_;
};
TEST_F(AccountSelectionBubbleViewTest, SingleAccount) {
TestSingleAccount(kTitleSignIn, /*expected_subtitle=*/absl::nullopt,
/*expect_idp_brand_icon_in_header=*/true);
}
TEST_F(AccountSelectionBubbleViewTest, SingleAccountNoTermsOfService) {
const std::string kAccountSuffix = "suffix";
content::IdentityRequestAccount account = CreateTestIdentityRequestAccount(
kAccountSuffix, content::IdentityRequestAccount::LoginState::kSignUp);
CreateSingleAccountPicker(
/*show_back_button=*/false, account, /*terms_of_service_url=*/"");
std::vector<views::View*> children = dialog()->children();
ASSERT_EQ(children.size(), 3u);
PerformHeaderChecks(children[0], kTitleSignIn,
/*expected_subtitle=*/absl::nullopt,
/*expect_idp_brand_icon_in_header=*/true);
views::View* single_account_chooser = children[2];
ASSERT_EQ(single_account_chooser->children().size(), 3u);
// Check the "Continue as" button.
views::MdTextButton* button =
static_cast<views::MdTextButton*>(single_account_chooser->children()[1]);
ASSERT_TRUE(button);
EXPECT_EQ(button->GetText(),
base::UTF8ToUTF16("Continue as " + std::string(kGivenNameBase) +
kAccountSuffix));
views::StyledLabel* disclosure_text =
static_cast<views::StyledLabel*>(single_account_chooser->children()[2]);
ASSERT_TRUE(disclosure_text);
EXPECT_EQ(disclosure_text->GetText(),
u"To continue, idp-example.com will share your name, email "
u"address, and profile picture with this site. See this site's "
u"privacy policy.");
}
TEST_F(AccountSelectionBubbleViewTest, MultipleAccounts) {
TestMultipleAccounts(kTitleSignIn, /*expected_subtitle=*/absl::nullopt,
/*expect_idp_brand_icon_in_header=*/true,
/*expect_idp_row=*/false);
}
TEST_F(AccountSelectionBubbleViewTest, ReturningAccount) {
const std::string kAccountSuffix = "suffix";
content::IdentityRequestAccount account = CreateTestIdentityRequestAccount(
kAccountSuffix, content::IdentityRequestAccount::LoginState::kSignIn);
CreateSingleAccountPicker(
/*show_back_button=*/false, account, /*terms_of_service_url=*/"");
std::vector<views::View*> children = dialog()->children();
ASSERT_EQ(children.size(), 3u);
PerformHeaderChecks(children[0], kTitleSignIn,
/*expected_subtitle=*/absl::nullopt,
/*expect_idp_brand_icon_in_header=*/true);
views::View* single_account_chooser = children[2];
std::vector<views::View*> chooser_children =
single_account_chooser->children();
ASSERT_EQ(chooser_children.size(), 2u);
views::View* single_account_row = chooser_children[0];
CheckAccountRow(single_account_row, kAccountSuffix);
// Check the "Continue as" button.
views::MdTextButton* button =
static_cast<views::MdTextButton*>(chooser_children[1]);
EXPECT_EQ(button->GetText(),
base::UTF8ToUTF16("Continue as " + std::string(kGivenNameBase) +
kAccountSuffix));
}
TEST_F(AccountSelectionBubbleViewTest, Verifying) {
const std::string kAccountSuffix = "suffix";
content::IdentityRequestAccount account = CreateTestIdentityRequestAccount(
kAccountSuffix, content::IdentityRequestAccount::LoginState::kSignIn);
IdentityProviderDisplayData idp_data(
kIdpETLDPlusOne, content::IdentityProviderMetadata(),
content::ClientMetadata(GURL(), GURL()), {account},
/*request_permission=*/true);
CreateAccountSelectionBubble(/*exclude_title=*/false, /*exclude_iframe=*/true,
/*show_auto_reauthn_checkbox=*/false);
dialog_->ShowVerifyingSheet(
account, idp_data, l10n_util::GetStringUTF16(IDS_VERIFY_SHEET_TITLE));
const std::vector<views::View*> children = dialog()->children();
ASSERT_EQ(children.size(), 3u);
PerformHeaderChecks(children[0], kTitleSigningIn,
/*expected_subtitle=*/absl::nullopt,
/*expect_idp_brand_icon_in_header=*/true);
views::View* row_container = dialog()->children()[2];
ASSERT_EQ(row_container->children().size(), 1u);
CheckAccountRow(row_container->children()[0], kAccountSuffix);
}
TEST_F(AccountSelectionBubbleViewTest, VerifyingForAutoReauthn) {
const std::string kAccountSuffix = "suffix";
content::IdentityRequestAccount account = CreateTestIdentityRequestAccount(
kAccountSuffix, content::IdentityRequestAccount::LoginState::kSignIn);
IdentityProviderDisplayData idp_data(
kIdpETLDPlusOne, content::IdentityProviderMetadata(),
content::ClientMetadata(GURL(), GURL()), {account},
/*request_permission=*/true);
CreateAccountSelectionBubble(/*exclude_title=*/false, /*exclude_iframe=*/true,
/*show_auto_reauthn_checkbox=*/false);
const auto title =
l10n_util::GetStringUTF16(IDS_VERIFY_SHEET_TITLE_AUTO_REAUTHN);
dialog_->ShowVerifyingSheet(account, idp_data, title);
const std::vector<views::View*> children = dialog()->children();
ASSERT_EQ(children.size(), 3u);
PerformHeaderChecks(children[0], kTitleSigningInWithAutoReauthn,
/*expected_subtitle=*/absl::nullopt,
/*expect_idp_brand_icon_in_header=*/true);
views::View* row_container = dialog()->children()[2];
ASSERT_EQ(row_container->children().size(), 1u);
CheckAccountRow(row_container->children()[0], kAccountSuffix);
}
TEST_F(AccountSelectionBubbleViewTest, AutoReauthnCheckboxDisplayed) {
const std::string kAccountSuffix = "suffix";
content::IdentityRequestAccount account = CreateTestIdentityRequestAccount(
{kAccountSuffix}, content::IdentityRequestAccount::LoginState::kSignUp);
CreateSingleAccountPicker(
/*show_back_button=*/false, account, /*terms_of_service_url=*/"",
/*show_auto_reauthn_checkbox=*/true);
std::vector<views::View*> children = dialog()->children();
ASSERT_EQ(children.size(), 3u);
PerformHeaderChecks(children[0], kTitleSignIn,
/*expected_subtitle=*/absl::nullopt,
/*expect_idp_brand_icon_in_header=*/true);
views::View* single_account_chooser = children[2];
ASSERT_EQ(single_account_chooser->children().size(), 4u);
// Check the "Continue as" button.
views::MdTextButton* button =
static_cast<views::MdTextButton*>(single_account_chooser->children()[1]);
ASSERT_TRUE(button);
EXPECT_EQ(button->GetText(),
base::UTF8ToUTF16("Continue as " + std::string(kGivenNameBase) +
kAccountSuffix));
// Check the auto re-authn checkbox.
views::Checkbox* checkbox =
static_cast<views::Checkbox*>(single_account_chooser->children()[2]);
ASSERT_TRUE(checkbox->GetEnabled());
}
TEST_F(AccountSelectionBubbleViewTest, Failure) {
TestFailureDialog(
u"Failed signing in to top-frame-example.com with idp-example.com",
/*expected_subtitle=*/absl::nullopt,
/*expect_idp_brand_icon_in_header=*/true);
}
// Tests that when an iframe URL is provided, it is appropriately added to the
// header of an account picker.
TEST_F(AccountSelectionBubbleViewTest, SuccessIframeSubtitleInHeader) {
const std::string kAccountSuffix = "suffix";
content::IdentityRequestAccount account = CreateTestIdentityRequestAccount(
{kAccountSuffix}, content::IdentityRequestAccount::LoginState::kSignUp);
CreateSingleAccountPicker(
/*show_back_button=*/false, account, /*terms_of_service_url=*/"",
/*show_auto_reauthn_checkbox=*/false, /*exclude_iframe=*/false);
std::vector<views::View*> children = dialog()->children();
ASSERT_EQ(children.size(), 3u);
PerformHeaderChecks(
children[0], u"Sign in to iframe-example.com with idp-example.com",
u"on top-frame-example.com", /*expect_idp_brand_icon_in_header=*/true);
}
// Tests that when an iframe URL is provided, it is appropriately added to the
// header of a failure dialog.
TEST_F(AccountSelectionBubbleViewTest, FailureIframeSubtitleInHeader) {
TestFailureDialog(
u"Failed signing in to iframe-example.com with idp-example.com",
u"on top-frame-example.com",
/*expect_idp_brand_icon_in_header=*/true);
}
class MultipleIdpAccountSelectionBubbleViewTest
: public AccountSelectionBubbleViewTest {
public:
MultipleIdpAccountSelectionBubbleViewTest() = default;
protected:
void SetUp() override {
feature_list_.InitAndEnableFeature(
features::kFedCmMultipleIdentityProviders);
AccountSelectionBubbleViewTest::SetUp();
}
private:
base::test::ScopedFeatureList feature_list_;
};
// Tests that the single account case is the same with
// features::kFedCmMultipleIdentityProviders enabled. See
// AccountSelectionBubbleViewTest's SingleAccount test.
TEST_F(MultipleIdpAccountSelectionBubbleViewTest, SingleAccount) {
TestSingleAccount(kTitleSignIn, /*expected_subtitle=*/absl::nullopt,
/*expect_idp_brand_icon_in_header=*/true);
}
// Tests that when there is multiple accounts but only one IDP, the UI is
// exactly the same with features::kFedCmMultipleIdentityProviders enabled (see
// AccountSelectionBubbleViewTest's MultipleAccounts test).
TEST_F(MultipleIdpAccountSelectionBubbleViewTest, MultipleAccountsSingleIdp) {
TestMultipleAccounts(kTitleSignIn, /*expected_subtitle=*/absl::nullopt,
/*expect_idp_brand_icon_in_header=*/true,
/*expect_idp_row=*/false);
}
// Tests that the logo is visible with features::kFedCmMultipleIdentityProviders
// enabled and multiple IDPs.
TEST_F(MultipleIdpAccountSelectionBubbleViewTest,
MultipleAccountsMultipleIdps) {
const std::vector<std::string> kAccountSuffixes1 = {"1", "2"};
const std::vector<std::string> kAccountSuffixes2 = {"3", "4"};
std::vector<IdentityProviderDisplayData> idp_data;
std::vector<Account> accounts_first_idp = CreateTestIdentityRequestAccounts(
kAccountSuffixes1, content::IdentityRequestAccount::LoginState::kSignUp);
idp_data.emplace_back(kIdpETLDPlusOne, content::IdentityProviderMetadata(),
CreateTestClientMetadata(kTermsOfServiceUrl),
accounts_first_idp,
/*request_permission=*/true);
idp_data.emplace_back(
u"idp2.com", content::IdentityProviderMetadata(),
CreateTestClientMetadata("https://tos-2.com"),
CreateTestIdentityRequestAccounts(
kAccountSuffixes2,
content::IdentityRequestAccount::LoginState::kSignUp),
/*request_permission=*/true);
CreateMultiIdpAccountPicker(idp_data);
std::vector<views::View*> children = dialog()->children();
ASSERT_EQ(children.size(), 3u);
PerformHeaderChecks(children[0], kTitleSignInWithoutIdp,
/*expected_subtitle=*/absl::nullopt,
/*expect_idp_brand_icon_in_header=*/false);
views::ScrollView* scroller = static_cast<views::ScrollView*>(children[2]);
ASSERT_FALSE(scroller->children().empty());
views::View* wrapper = scroller->children()[0];
ASSERT_FALSE(wrapper->children().empty());
views::View* contents = wrapper->children()[0];
views::BoxLayout* layout_manager =
static_cast<views::BoxLayout*>(contents->GetLayoutManager());
EXPECT_TRUE(layout_manager);
EXPECT_EQ(layout_manager->GetOrientation(),
views::BoxLayout::Orientation::kVertical);
std::vector<views::View*> accounts = contents->children();
// There should be 6 rows: 3 for the first IDP, 3 for the second.
EXPECT_EQ(6u, accounts.size());
// Check the first IDP.
CheckIdpRow(accounts[0u], u"idp-example.com");
size_t accounts_index = 1;
CheckAccountRows(accounts, kAccountSuffixes1, accounts_index);
// Check the second IDP.
CheckIdpRow(accounts[accounts_index++], u"idp2.com");
CheckAccountRows(accounts, kAccountSuffixes2, accounts_index);
}