// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string>
#include "ash/shell.h"
#include "ash/system/notification_center/ash_message_popup_collection.h"
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "chrome/browser/notifications/notification_display_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/chromeos/read_write_cards/read_write_cards_ui_controller.h"
#include "chrome/browser/ui/quick_answers/quick_answers_browsertest_base.h"
#include "chrome/browser/ui/quick_answers/quick_answers_controller_impl.h"
#include "chrome/browser/ui/quick_answers/quick_answers_ui_controller.h"
#include "chrome/browser/ui/quick_answers/ui/quick_answers_view.h"
#include "chrome/browser/ui/quick_answers/ui/rich_answers_view.h"
#include "chrome/browser/ui/quick_answers/ui/user_consent_view.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chromeos/components/magic_boost/public/cpp/magic_boost_state.h"
#include "chromeos/components/quick_answers/public/cpp/controller/quick_answers_controller.h"
#include "chromeos/components/quick_answers/public/cpp/quick_answers_prefs.h"
#include "chromeos/components/quick_answers/public/cpp/quick_answers_state.h"
#include "chromeos/components/quick_answers/quick_answers_model.h"
#include "chromeos/constants/chromeos_features.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "components/omnibox/browser/vector_icons.h"
#include "components/prefs/pref_service.h"
#include "content/public/test/browser_test.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/color/color_id.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/public/cpp/notification_delegate.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/widget/any_widget_observer.h"
#include "ui/views/widget/widget.h"
#include "url/gurl.h"
namespace quick_answers {
namespace {
constexpr char kTestQuery[] = "test";
constexpr int kCursorXToOverlapWithANotification = 630;
constexpr int kCursorYToOverlapWithANotification = 400;
constexpr char kTestNotificationId[] = "id";
constexpr char16_t kTestNotificationTitle[] = u"title";
constexpr char16_t kTestNotificationMessage[] = u"message";
constexpr char16_t kTestNotificationDisplaySource[] = u"display-source";
constexpr char kTestNotificationOriginUrl[] = "https://example.com/";
// Definition result.
constexpr char16_t kQueryText[] = u"indefinite";
constexpr char16_t kPhoneticsText[] = u"inˈdef(ə)nət";
constexpr char kPhoneticsAudioUrlWithProtocol[] = "https://example.com/audio";
constexpr char kDefinitionText[] =
"lasting for an unknown or unstated length of time.";
constexpr char kWordClassText[] = "adjective";
constexpr char kSampleSentenceText[] = "they may face indefinite detention";
constexpr char kFirstSynonymText[] = "unknown";
constexpr char kSecondSynonymText[] = "indeterminate";
constexpr char kThirdSynonymText[] = "unspecified";
constexpr char kSubsenseDefinitionText[] =
"not clearly expressed or defined; vague.";
constexpr char kSubsenseSampleSentenceText[] =
"their status remains indefinite";
constexpr char kSubsenseSynonymText[] = "vague";
// Translation result.
constexpr char16_t kSourceText[] = u"prodotto";
constexpr char16_t kTranslatedText[] = u"product";
// Unit conversion result.
constexpr char16_t kSourceValueText[] = u"20";
constexpr char16_t kSourceUnitText[] = u"feet";
constexpr char kConversionSourceText[] = "20 feet";
constexpr char kConversionResultText[] = "6.667 yards";
constexpr char kConversionCategoryText[] = "Length";
constexpr double kConversionSourceAmount = 20;
constexpr char kSourceRuleUnitText[] = "Foot";
constexpr double kSourceRuleTermA = 0.3048;
constexpr char kDestRuleUnitText[] = "Yard";
constexpr double kDestRuleTermA = 0.9144;
constexpr char kAlternativeDestRuleUnitText[] = "Inch";
constexpr double kAlternativeDestRuleTermA = 0.0254;
constexpr int kFakeImageWidth = 300;
constexpr int kFakeImageHeight = 300;
constexpr int kAnimationCompletionPollingInterval = 50; // milliseconds
constexpr int kQuickAnswersResultTypeIconSizeDip = 12;
constexpr int kRichAnswersResultTypeIconSizeDip = 16;
gfx::Image CreateFakeImage() {
SkBitmap bitmap;
bitmap.allocN32Pixels(kFakeImageWidth, kFakeImageHeight);
return gfx::Image::CreateFrom1xBitmap(bitmap);
}
void CheckAnimationEnded(views::Widget* widget,
gfx::Rect* rect,
base::RunLoop* run_loop) {
gfx::Rect current_rect = widget->GetWindowBoundsInScreen();
if (current_rect == *rect) {
run_loop->Quit();
return;
}
*rect = current_rect;
}
void WaitAnimationCompletion(views::Widget* widget) {
base::RunLoop run_loop;
base::RepeatingTimer timer;
gfx::Rect rect;
timer.Start(
FROM_HERE, base::Milliseconds(kAnimationCompletionPollingInterval),
base::BindRepeating(&CheckAnimationEnded, widget, &rect, &run_loop));
run_loop.Run();
}
// Simulate a valid QuickAnswer definition response.
std::unique_ptr<QuickAnswersSession> CreateQuickAnswerDefinitionResponse() {
std::unique_ptr<quick_answers::QuickAnswer> quick_answer =
std::make_unique<quick_answers::QuickAnswer>();
quick_answer->result_type = ResultType::kDefinitionResult;
quick_answer->title.push_back(
std::make_unique<quick_answers::QuickAnswerText>(
l10n_util::GetStringFUTF8(IDS_QUICK_ANSWERS_DEFINITION_TITLE_TEXT,
kQueryText, kPhoneticsText)));
quick_answer->first_answer_row.push_back(
std::make_unique<quick_answers::QuickAnswerResultText>(kDefinitionText));
std::unique_ptr<DefinitionResult> definition_result =
std::make_unique<DefinitionResult>();
definition_result->word = base::UTF16ToUTF8(kQueryText);
definition_result->word_class = kWordClassText;
PhoneticsInfo phonetics_info;
phonetics_info.text = base::UTF16ToUTF8(kPhoneticsText);
phonetics_info.phonetics_audio = GURL(kPhoneticsAudioUrlWithProtocol);
phonetics_info.locale = "en";
definition_result->phonetics_info = phonetics_info;
Sense sense;
sense.definition = kDefinitionText;
sense.sample_sentence = kSampleSentenceText;
std::vector<std::string> synonyms_list{kFirstSynonymText, kSecondSynonymText,
kThirdSynonymText};
sense.synonyms_list = synonyms_list;
definition_result->sense = sense;
std::vector<Sense> subsenses_list;
Sense subsense;
subsense.definition = kSubsenseDefinitionText;
subsense.sample_sentence = kSubsenseSampleSentenceText;
std::vector<std::string> subsense_synonyms_list{kSubsenseSynonymText};
subsense.synonyms_list = subsense_synonyms_list;
subsenses_list.push_back(subsense);
definition_result->subsenses_list = subsenses_list;
std::unique_ptr<QuickAnswersSession> quick_answers_session =
std::make_unique<QuickAnswersSession>();
quick_answers_session->quick_answer = std::move(quick_answer);
quick_answers_session->structured_result =
std::make_unique<StructuredResult>();
quick_answers_session->structured_result->definition_result =
std::move(definition_result);
return quick_answers_session;
}
// Simulate a valid QuickAnswer translation response.
std::unique_ptr<QuickAnswersSession> CreateQuickAnswerTranslationResponse() {
std::unique_ptr<quick_answers::QuickAnswer> quick_answer =
std::make_unique<quick_answers::QuickAnswer>();
quick_answer->result_type = ResultType::kTranslationResult;
quick_answer->title.push_back(
std::make_unique<quick_answers::QuickAnswerText>(
l10n_util::GetStringFUTF8(IDS_QUICK_ANSWERS_TRANSLATION_TITLE_TEXT,
kSourceText, u"Italian")));
quick_answer->first_answer_row.push_back(
std::make_unique<quick_answers::QuickAnswerResultText>(
base::UTF16ToUTF8(kTranslatedText)));
std::unique_ptr<TranslationResult> translation_result =
std::make_unique<TranslationResult>();
translation_result->text_to_translate = base::UTF16ToUTF8(kSourceText);
translation_result->translated_text = base::UTF16ToUTF8(kTranslatedText);
translation_result->target_locale = "en";
translation_result->source_locale = "it";
std::unique_ptr<QuickAnswersSession> quick_answers_session =
std::make_unique<QuickAnswersSession>();
quick_answers_session->quick_answer = std::move(quick_answer);
quick_answers_session->structured_result =
std::make_unique<StructuredResult>();
quick_answers_session->structured_result->translation_result =
std::move(translation_result);
return quick_answers_session;
}
// Simulate a valid QuickAnswer unit conversion response.
std::unique_ptr<QuickAnswersSession> CreateQuickAnswerUnitConversionResponse() {
std::unique_ptr<quick_answers::QuickAnswer> quick_answer =
std::make_unique<quick_answers::QuickAnswer>();
quick_answer->result_type = ResultType::kUnitConversionResult;
quick_answer->title.push_back(
std::make_unique<quick_answers::QuickAnswerText>(
l10n_util::GetStringFUTF8(
IDS_QUICK_ANSWERS_UNIT_CONVERSION_RESULT_TEXT, kSourceValueText,
kSourceUnitText)));
quick_answer->first_answer_row.push_back(
std::make_unique<quick_answers::QuickAnswerResultText>(
kConversionResultText));
std::unique_ptr<UnitConversionResult> unit_conversion_result =
std::make_unique<UnitConversionResult>();
unit_conversion_result->source_text = kConversionSourceText;
unit_conversion_result->result_text = kConversionResultText;
unit_conversion_result->category = kConversionCategoryText;
unit_conversion_result->source_amount = kConversionSourceAmount;
std::optional<ConversionRule> source_rule = ConversionRule::Create(
kConversionCategoryText, kSourceRuleUnitText, kSourceRuleTermA,
/*term_b=*/std::nullopt, /*term_c=*/std::nullopt);
std::optional<ConversionRule> dest_rule = ConversionRule::Create(
kConversionCategoryText, kDestRuleUnitText, kDestRuleTermA,
/*term_b=*/std::nullopt, /*term_c=*/std::nullopt);
std::optional<UnitConversion> unit_conversion =
UnitConversion::Create(source_rule.value(), dest_rule.value());
unit_conversion_result->source_to_dest_unit_conversion =
unit_conversion.value();
std::vector<UnitConversion> alternative_unit_conversions_list;
std::optional<ConversionRule> alternative_dest_rule = ConversionRule::Create(
kConversionCategoryText, kAlternativeDestRuleUnitText,
kAlternativeDestRuleTermA, /*term_b=*/std::nullopt,
/*term_c=*/std::nullopt);
std::optional<UnitConversion> alternative_unit_conversion =
UnitConversion::Create(source_rule.value(),
alternative_dest_rule.value());
alternative_unit_conversions_list.push_back(
alternative_unit_conversion.value());
unit_conversion_result->alternative_unit_conversions_list =
alternative_unit_conversions_list;
std::unique_ptr<QuickAnswersSession> quick_answers_session =
std::make_unique<QuickAnswersSession>();
quick_answers_session->quick_answer = std::move(quick_answer);
quick_answers_session->structured_result =
std::make_unique<StructuredResult>();
quick_answers_session->structured_result->unit_conversion_result =
std::move(unit_conversion_result);
return quick_answers_session;
}
} // namespace
class QuickAnswersBrowserTest : public QuickAnswersBrowserTestBase {
protected:
void SetQuickAnswersEnabled(bool enabled) {
if (IsMagicBoostEnabled()) {
// Approve HMRConsentStatus to bypass opt-in flow.
chromeos::MagicBoostState::Get()->AsyncWriteConsentStatus(
chromeos::HMRConsentStatus::kApproved);
chromeos::MagicBoostState::Get()->AsyncWriteHMREnabled(true);
} else {
// This simulates a behavior where a user enables QuickAnswers from
// Settings.
chrome_test_utils::GetProfile(this)->GetPrefs()->SetBoolean(
prefs::kQuickAnswersEnabled, enabled);
}
}
void SendTestImageNotification() {
message_center::RichNotificationData rich_notification_data;
rich_notification_data.image = CreateFakeImage();
rich_notification_data.never_timeout = true;
message_center::Notification notification(
message_center::NotificationType::NOTIFICATION_TYPE_IMAGE,
kTestNotificationId, kTestNotificationTitle, kTestNotificationMessage,
/*icon=*/ui::ImageModel(), kTestNotificationDisplaySource,
GURL(kTestNotificationOriginUrl), message_center::NotifierId(),
rich_notification_data,
base::MakeRefCounted<message_center::NotificationDelegate>());
NotificationDisplayService::GetForProfile(
chrome_test_utils::GetProfile(this))
->Display(NotificationHandler::Type::TRANSIENT, notification,
/*metadata=*/nullptr);
}
// Trigger the Quick Answers widget and wait until it appears.
views::Widget* ShowQuickAnswersWidget() {
views::NamedWidgetShownWaiter quick_answers_view_widget_waiter(
views::test::AnyWidgetTestPasskey(),
chromeos::ReadWriteCardsUiController::kWidgetName);
ShowMenuParams params;
params.selected_text = kTestQuery;
params.x = kCursorXToOverlapWithANotification;
params.y = kCursorYToOverlapWithANotification;
ShowMenu(params);
return quick_answers_view_widget_waiter.WaitIfNeededAndGet();
}
void FakeControllerTimeTick() {
CHECK(fake_time_tick_.is_null()) << "Fake is already enabled.";
fake_time_tick_ = base::TimeTicks::Now();
static_cast<QuickAnswersControllerImpl*>(controller())
->OverrideTimeTickNowForTesting(base::BindRepeating(
&QuickAnswersBrowserTest::FakeTimeTickNow, base::Unretained(this)));
}
void FastForwardBy(base::TimeDelta delta) {
CHECK(!fake_time_tick_.is_null()) << "Fake is not enabled.";
fake_time_tick_ += delta;
}
base::TimeTicks FakeTimeTickNow() { return fake_time_tick_; }
UserConsentView* GetUserConsentView() {
return static_cast<QuickAnswersControllerImpl*>(controller())
->quick_answers_ui_controller()
->user_consent_view();
}
QuickAnswersView* GetQuickAnswersView() {
return static_cast<QuickAnswersControllerImpl*>(controller())
->quick_answers_ui_controller()
->quick_answers_view();
}
private:
base::TimeTicks fake_time_tick_;
};
IN_PROC_BROWSER_TEST_P(QuickAnswersBrowserTest,
QuickAnswersViewAboveNotification) {
SetQuickAnswersEnabled(true);
views::Widget* quick_answers_view_widget = ShowQuickAnswersWidget();
ASSERT_TRUE(quick_answers_view_widget != nullptr);
views::NamedWidgetShownWaiter message_popup_widget_waiter(
views::test::AnyWidgetTestPasskey(),
ash::AshMessagePopupCollection::kMessagePopupWidgetName);
SendTestImageNotification();
views::Widget* message_popup_widget =
message_popup_widget_waiter.WaitIfNeededAndGet();
ASSERT_TRUE(message_popup_widget != nullptr);
// The notification is animating. Wait the animation completion before
// checking the bounds.
WaitAnimationCompletion(message_popup_widget);
// Make sure that `QuickAnswersView` overlaps with the notification.
EXPECT_FALSE(
gfx::IntersectRects(message_popup_widget->GetWindowBoundsInScreen(),
quick_answers_view_widget->GetWindowBoundsInScreen())
.IsEmpty());
// TODO(b/239716419): Quick answers UI should be above the notification.
EXPECT_TRUE(message_popup_widget->IsStackedAbove(
quick_answers_view_widget->GetNativeView()));
}
IN_PROC_BROWSER_TEST_P(QuickAnswersBrowserTest,
UserConsentViewAboveNotification) {
if (IsMagicBoostEnabled()) {
GTEST_SKIP() << "This test only applies when Magic Boost is disabled.";
}
// User consent view is stored within the `ReadWriteCardsUiController`'s
// widget.
views::NamedWidgetShownWaiter user_consent_view_widget_waiter(
views::test::AnyWidgetTestPasskey(),
chromeos::ReadWriteCardsUiController::kWidgetName);
ShowMenuParams params;
params.selected_text = kTestQuery;
params.x = kCursorXToOverlapWithANotification;
params.y = kCursorYToOverlapWithANotification;
ShowMenu(params);
views::Widget* user_consent_view_widget =
user_consent_view_widget_waiter.WaitIfNeededAndGet();
ASSERT_TRUE(user_consent_view_widget != nullptr);
views::NamedWidgetShownWaiter message_popup_widget_waiter(
views::test::AnyWidgetTestPasskey(),
ash::AshMessagePopupCollection::kMessagePopupWidgetName);
SendTestImageNotification();
views::Widget* message_popup_widget =
message_popup_widget_waiter.WaitIfNeededAndGet();
ASSERT_TRUE(message_popup_widget != nullptr);
// The notification is animating. Wait the animation completion before
// checking the bounds.
WaitAnimationCompletion(message_popup_widget);
// Make sure that `UserConsentView` overlaps with the notification.
EXPECT_FALSE(
gfx::IntersectRects(message_popup_widget->GetWindowBoundsInScreen(),
user_consent_view_widget->GetWindowBoundsInScreen())
.IsEmpty());
// TODO(b/239716419): Quick answers UI should be above the notification.
EXPECT_TRUE(message_popup_widget->IsStackedAbove(
user_consent_view_widget->GetNativeView()));
}
IN_PROC_BROWSER_TEST_P(QuickAnswersBrowserTest, UserConsentViewImpressionCap) {
if (IsMagicBoostEnabled()) {
GTEST_SKIP() << "This test only applies when Magic Boost is disabled.";
}
FakeControllerTimeTick();
for (int i = 0; i < kConsentImpressionCap; ++i) {
ShowQuickAnswersWidget();
ASSERT_EQ(QuickAnswersVisibility::kUserConsentVisible,
controller()->GetQuickAnswersVisibility());
FastForwardBy(base::Seconds(kConsentImpressionMinimumDuration));
ui::test::EventGenerator event_generator(
ash::Shell::GetPrimaryRootWindow());
event_generator.PressAndReleaseKey(ui::KeyboardCode::VKEY_ESCAPE);
base::RunLoop run_loop;
chrome_test_utils::GetProfile(this)->GetPrefs()->CommitPendingWrite(
run_loop.QuitClosure());
run_loop.Run();
EXPECT_FALSE(chrome_test_utils::GetProfile(this)->GetPrefs()->GetBoolean(
prefs::kQuickAnswersEnabled));
EXPECT_EQ(chrome_test_utils::GetProfile(this)->GetPrefs()->GetInteger(
prefs::kQuickAnswersConsentStatus),
i == kConsentImpressionCap - 1
? quick_answers::prefs::ConsentStatus::kRejected
: quick_answers::prefs::ConsentStatus::kUnknown)
<< "Consent status is set to kRejected once it reaches the impression "
"cap.";
}
}
IN_PROC_BROWSER_TEST_P(QuickAnswersBrowserTest, ClickAllowOnUserConsentView) {
if (IsMagicBoostEnabled()) {
GTEST_SKIP() << "This test only applies when Magic Boost is disabled.";
}
// User consent view is stored within the `ReadWriteCardsUiController`'s
// widget.
views::NamedWidgetShownWaiter user_consent_view_widget_waiter(
views::test::AnyWidgetTestPasskey(),
chromeos::ReadWriteCardsUiController::kWidgetName);
ShowMenuParams params;
params.selected_text = kTestQuery;
params.x = kCursorXToOverlapWithANotification;
params.y = kCursorYToOverlapWithANotification;
ShowMenu(params);
views::Widget* user_consent_view_widget =
user_consent_view_widget_waiter.WaitIfNeededAndGet();
ASSERT_TRUE(user_consent_view_widget);
ASSERT_EQ(QuickAnswersVisibility::kUserConsentVisible,
controller()->GetQuickAnswersVisibility());
ASSERT_FALSE(chrome_test_utils::GetProfile(this)->GetPrefs()->GetBoolean(
prefs::kQuickAnswersEnabled));
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
generator.MoveMouseTo(GetUserConsentView()
->allow_button_for_test()
->GetBoundsInScreen()
.CenterPoint());
generator.ClickLeftButton();
// Prefs should be set and quick answers view should be shown.
EXPECT_TRUE(chrome_test_utils::GetProfile(this)->GetPrefs()->GetBoolean(
prefs::kQuickAnswersEnabled));
EXPECT_EQ(QuickAnswersVisibility::kQuickAnswersVisible,
controller()->GetQuickAnswersVisibility());
}
IN_PROC_BROWSER_TEST_P(QuickAnswersBrowserTest,
ClickNoThanksOnUserConsentView) {
if (IsMagicBoostEnabled()) {
GTEST_SKIP() << "This test only applies when Magic Boost is disabled.";
}
// User consent view is stored within the `ReadWriteCardsUiController`'s
// widget.
views::NamedWidgetShownWaiter user_consent_view_widget_waiter(
views::test::AnyWidgetTestPasskey(),
chromeos::ReadWriteCardsUiController::kWidgetName);
ShowMenuParams params;
params.selected_text = kTestQuery;
params.x = kCursorXToOverlapWithANotification;
params.y = kCursorYToOverlapWithANotification;
ShowMenu(params);
views::Widget* user_consent_view_widget =
user_consent_view_widget_waiter.WaitIfNeededAndGet();
ASSERT_TRUE(user_consent_view_widget);
ASSERT_EQ(QuickAnswersVisibility::kUserConsentVisible,
controller()->GetQuickAnswersVisibility());
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
generator.MoveMouseTo(GetUserConsentView()
->no_thanks_button_for_test()
->GetBoundsInScreen()
.CenterPoint());
generator.ClickLeftButton();
// Prefs should not be set and no views should be shown.
EXPECT_FALSE(chrome_test_utils::GetProfile(this)->GetPrefs()->GetBoolean(
prefs::kQuickAnswersEnabled));
EXPECT_EQ(QuickAnswersVisibility::kClosed,
controller()->GetQuickAnswersVisibility());
}
INSTANTIATE_TEST_SUITE_P(
/* no prefix */,
QuickAnswersBrowserTest,
::testing::Bool());
class RichAnswersBrowserTest : public QuickAnswersBrowserTest {
protected:
void SetUpOnMainThread() override {
QuickAnswersBrowserTestBase::SetUpOnMainThread();
SetQuickAnswersEnabled(true);
event_generator_.emplace(ash::Shell::GetPrimaryRootWindow());
}
// Trigger the Rich Answers widget by clicking on |quick_answers_view_widget|,
// and wait until it appears.
views::Widget* ShowRichAnswersWidget(
views::Widget* quick_answers_view_widget) {
views::NamedWidgetShownWaiter rich_answers_view_widget_waiter(
views::test::AnyWidgetTestPasskey(), RichAnswersView::kWidgetName);
event_generator_->MoveMouseTo(
quick_answers_view_widget->GetWindowBoundsInScreen().CenterPoint());
event_generator_->ClickLeftButton();
views::Widget* rich_answers_view_widget =
rich_answers_view_widget_waiter.WaitIfNeededAndGet();
return rich_answers_view_widget;
}
std::optional<ui::test::EventGenerator> event_generator_;
private:
base::test::ScopedFeatureList feature_list_{
chromeos::features::kQuickAnswersRichCard};
};
IN_PROC_BROWSER_TEST_P(RichAnswersBrowserTest,
RichAnswersNotTriggeredOnInvalidResult) {
views::Widget* quick_answers_view_widget = ShowQuickAnswersWidget();
// Click on the Quick Answers widget. This should *not* trigger the
// Rich Answers widget since no valid QuickAnswer result is provided.
views::NamedWidgetShownWaiter rich_answers_view_widget_waiter(
views::test::AnyWidgetTestPasskey(), RichAnswersView::kWidgetName);
event_generator_->MoveMouseTo(
quick_answers_view_widget->GetWindowBoundsInScreen().CenterPoint());
event_generator_->ClickLeftButton();
// Check that all quick answers views are closed.
EXPECT_TRUE(quick_answers_view_widget->IsClosed());
EXPECT_TRUE(controller()->GetQuickAnswersVisibility() ==
QuickAnswersVisibility::kClosed);
}
IN_PROC_BROWSER_TEST_P(RichAnswersBrowserTest,
RichAnswersTriggeredAndDismissedOnValidResult) {
views::Widget* quick_answers_view_widget = ShowQuickAnswersWidget();
// Simulate having received a valid QuickAnswer response.
controller()->GetQuickAnswersDelegate()->OnQuickAnswerReceived(
CreateQuickAnswerTranslationResponse());
views::Widget* rich_answers_view_widget =
ShowRichAnswersWidget(quick_answers_view_widget);
ASSERT_TRUE(rich_answers_view_widget != nullptr);
// Check that the quick answers view closes when the rich answers view shows.
EXPECT_TRUE(quick_answers_view_widget->IsClosed());
EXPECT_TRUE(controller()->GetQuickAnswersVisibility() ==
QuickAnswersVisibility::kRichAnswersVisible);
// Click outside the rich answers view window bounds to dismiss it.
gfx::Rect rich_answers_bounds =
rich_answers_view_widget->GetWindowBoundsInScreen();
event_generator_->MoveMouseTo(
gfx::Point(rich_answers_bounds.x() / 2, rich_answers_bounds.y() / 2));
event_generator_->ClickLeftButton();
// Check that the rich answers view is dismissed.
EXPECT_TRUE(rich_answers_view_widget->IsClosed());
EXPECT_TRUE(controller()->GetQuickAnswersVisibility() ==
QuickAnswersVisibility::kClosed);
}
IN_PROC_BROWSER_TEST_P(RichAnswersBrowserTest,
DefinitionResultCardContentsCorrectlyShown) {
views::Widget* quick_answers_view_widget = ShowQuickAnswersWidget();
// Simulate having received a valid QuickAnswer definition response.
controller()->GetQuickAnswersDelegate()->OnQuickAnswerReceived(
CreateQuickAnswerDefinitionResponse());
// Check that the shown result type icon on the QuickAnswersView
// correctly corresponds to the definition result type.
ui::ImageModel expected_image_model = ui::ImageModel::FromVectorIcon(
omnibox::kAnswerDictionaryIcon, ui::kColorSysBaseContainerElevated,
/*icon_size=*/kQuickAnswersResultTypeIconSizeDip);
views::Widget* rich_answers_view_widget =
ShowRichAnswersWidget(quick_answers_view_widget);
// Check that the shown result type icon on the RichAnswersView
// correctly corresponds to the definition result type.
RichAnswersView* rich_answers_view = static_cast<RichAnswersView*>(
rich_answers_view_widget->GetContentsView());
expected_image_model = ui::ImageModel::FromVectorIcon(
omnibox::kAnswerDictionaryIcon, ui::kColorSysBaseContainerElevated,
/*icon_size=*/kRichAnswersResultTypeIconSizeDip);
EXPECT_TRUE(rich_answers_view->GetIconImageModelForTesting() ==
expected_image_model);
// TODO(b/326370198): Add checks for other card contents.
}
IN_PROC_BROWSER_TEST_P(RichAnswersBrowserTest,
TranslationResultCardContentsCorrectlyShown) {
views::Widget* quick_answers_view_widget = ShowQuickAnswersWidget();
// Simulate having received a valid QuickAnswer translation response.
controller()->GetQuickAnswersDelegate()->OnQuickAnswerReceived(
CreateQuickAnswerTranslationResponse());
// Check that the shown result type icon on the QuickAnswersView
// correctly corresponds to the translation result type.
ui::ImageModel expected_image_model = ui::ImageModel::FromVectorIcon(
omnibox::kAnswerTranslationIcon, ui::kColorSysBaseContainerElevated,
/*icon_size=*/kQuickAnswersResultTypeIconSizeDip);
views::Widget* rich_answers_view_widget =
ShowRichAnswersWidget(quick_answers_view_widget);
// Check that the shown result type icon on the RichAnswersView
// correctly corresponds to the translation result type.
RichAnswersView* rich_answers_view = static_cast<RichAnswersView*>(
rich_answers_view_widget->GetContentsView());
expected_image_model = ui::ImageModel::FromVectorIcon(
omnibox::kAnswerTranslationIcon, ui::kColorSysBaseContainerElevated,
/*icon_size=*/kRichAnswersResultTypeIconSizeDip);
EXPECT_TRUE(rich_answers_view->GetIconImageModelForTesting() ==
expected_image_model);
// TODO(b/326370198): Add checks for other card contents.
}
IN_PROC_BROWSER_TEST_P(RichAnswersBrowserTest,
UnitConversionResultCardContentsCorrectlyShown) {
views::Widget* quick_answers_view_widget = ShowQuickAnswersWidget();
// Simulate having received a valid QuickAnswer unit conversion response.
controller()->GetQuickAnswersDelegate()->OnQuickAnswerReceived(
CreateQuickAnswerUnitConversionResponse());
// Check that the shown result type icon on the QuickAnswersView
// correctly corresponds to the unit conversion result type.
ui::ImageModel expected_image_model = ui::ImageModel::FromVectorIcon(
omnibox::kAnswerCalculatorIcon, ui::kColorSysBaseContainerElevated,
/*icon_size=*/kQuickAnswersResultTypeIconSizeDip);
views::Widget* rich_answers_view_widget =
ShowRichAnswersWidget(quick_answers_view_widget);
// Check that the shown result type icon on the RichAnswersView
// correctly corresponds to the unit conversion result type.
RichAnswersView* rich_answers_view = static_cast<RichAnswersView*>(
rich_answers_view_widget->GetContentsView());
expected_image_model = ui::ImageModel::FromVectorIcon(
omnibox::kAnswerCalculatorIcon, ui::kColorSysBaseContainerElevated,
/*icon_size=*/kRichAnswersResultTypeIconSizeDip);
EXPECT_TRUE(rich_answers_view->GetIconImageModelForTesting() ==
expected_image_model);
// TODO(b/326370198): Add checks for other card contents.
}
IN_PROC_BROWSER_TEST_P(RichAnswersBrowserTest, AccessibleProperties) {
views::Widget* quick_answers_view_widget = ShowQuickAnswersWidget();
controller()->GetQuickAnswersDelegate()->OnQuickAnswerReceived(
CreateQuickAnswerUnitConversionResponse());
RichAnswersView* rich_answers_view = static_cast<RichAnswersView*>(
ShowRichAnswersWidget(quick_answers_view_widget)->GetContentsView());
ui::AXNodeData data;
ASSERT_TRUE(rich_answers_view);
rich_answers_view->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(data.role, ax::mojom::Role::kDialog);
EXPECT_EQ(data.GetStringAttribute(ax::mojom::StringAttribute::kName),
l10n_util::GetStringUTF8(IDS_RICH_ANSWERS_VIEW_A11Y_NAME_TEXT));
}
INSTANTIATE_TEST_SUITE_P(
/* no prefix */,
RichAnswersBrowserTest,
::testing::Bool());
} // namespace quick_answers