blob: 637b2af17456827eb1b7e8c0fff3dd5f8aebd92a [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 "components/user_education/webui/help_bubble_handler.h"
#include <memory>
#include <vector>
#include "base/callback_list.h"
#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "components/user_education/common/help_bubble_factory_registry.h"
#include "components/user_education/common/help_bubble_params.h"
#include "components/user_education/test/test_help_bubble.h"
#include "components/user_education/webui/help_bubble_webui.h"
#include "components/user_education/webui/tracked_element_webui.h"
#include "components/vector_icons/vector_icons.h"
#include "content/public/browser/web_ui_controller.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/base/interaction/expect_call_in_scope.h"
#include "ui/webui/resources/cr_components/help_bubble/help_bubble.mojom-forward.h"
#include "ui/webui/resources/cr_components/help_bubble/help_bubble.mojom-shared.h"
#include "ui/webui/resources/cr_components/help_bubble/help_bubble.mojom.h"
namespace user_education {
namespace {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kHelpBubbleHandlerTestElementIdentifier);
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kHelpBubbleHandlerTestElementIdentifier2);
constexpr gfx::RectF kElementBounds{50, 51, 51, 53};
// Mock version of the help bubble client so we don't need a remote (while being
// able to know when the remote methods would have been called).
class MockHelpBubbleClient : public help_bubble::mojom::HelpBubbleClient {
public:
MockHelpBubbleClient() = default;
~MockHelpBubbleClient() override = default;
MOCK_METHOD(void,
ShowHelpBubble,
(help_bubble::mojom::HelpBubbleParamsPtr data),
(override));
MOCK_METHOD(void,
ToggleFocusForAccessibility,
(const std::string& native_identifier),
(override));
MOCK_METHOD(void,
HideHelpBubble,
(const std::string& native_identifier),
(override));
MOCK_METHOD(void,
ExternalHelpBubbleUpdated,
(const std::string& native_identifier, bool shown),
(override));
};
// Handler that mocks the remote connection to the web side of the component.
// The mock is a strict mock and can be retrieved by calling the `mock()`
// method.
class TestHelpBubbleHandler : public HelpBubbleHandlerBase {
public:
explicit TestHelpBubbleHandler(
const std::vector<ui::ElementIdentifier>& identifiers,
std::unique_ptr<VisibilityProvider> visibility_provider)
: HelpBubbleHandlerBase(std::make_unique<ClientProvider>(),
std::move(visibility_provider),
identifiers,
ui::ElementContext(this)) {}
~TestHelpBubbleHandler() override = default;
// Provides direct access to the mock client for use with EXPECT_CALL*
// macros.
testing::StrictMock<MockHelpBubbleClient>& mock() {
return static_cast<ClientProvider*>(client_provider())->client_;
}
content::WebUIController* GetController() override {
NOTREACHED() << "Should not call this in tests.";
return nullptr;
}
class MockVisibilityProvider
: public HelpBubbleHandlerBase::VisibilityProvider {
public:
MockVisibilityProvider() = default;
~MockVisibilityProvider() override = default;
using HelpBubbleHandlerBase::VisibilityProvider::SetLastKnownVisibility;
MOCK_METHOD(absl::optional<bool>, CheckIsVisible, (), (override));
};
private:
// Provides a mock client.
class ClientProvider : public HelpBubbleHandlerBase::ClientProvider {
public:
ClientProvider() = default;
~ClientProvider() override = default;
help_bubble::mojom::HelpBubbleClient* GetClient() override {
return &client_;
}
private:
friend class TestHelpBubbleHandler;
testing::StrictMock<MockHelpBubbleClient> client_;
};
};
MATCHER_P(MatchesHelpBubbleParams, expected, "") {
EXPECT_EQ(expected->body_text, arg->body_text);
EXPECT_EQ(expected->close_button_alt_text, arg->close_button_alt_text);
EXPECT_EQ(expected->timeout, arg->timeout);
EXPECT_EQ(expected->body_icon_name, arg->body_icon_name);
EXPECT_EQ(expected->body_icon_alt_text, arg->body_icon_alt_text);
EXPECT_EQ(expected->native_identifier, arg->native_identifier);
EXPECT_EQ(expected->position, arg->position);
EXPECT_EQ(expected->title_text, arg->title_text);
EXPECT_EQ(!!expected->progress, !!arg->progress);
if (expected->progress && arg->progress) {
EXPECT_EQ(expected->progress->current, arg->progress->current);
EXPECT_EQ(expected->progress->total, arg->progress->total);
}
EXPECT_EQ(expected->buttons.size(), arg->buttons.size());
if (expected->buttons.size() == arg->buttons.size()) {
for (size_t i = 0; i < expected->buttons.size(); ++i) {
EXPECT_EQ(expected->buttons[i]->text, arg->buttons[i]->text);
EXPECT_EQ(expected->buttons[i]->is_default, arg->buttons[i]->is_default);
}
}
return true;
}
} // namespace
// Tests the interaction of HelpBubbleHandler, HelpBubbleWebUI, and
// TrackedElementWebUI. The three form a single system that all work together.
class HelpBubbleHandlerTest : public testing::Test {
public:
HelpBubbleHandlerTest() {
help_bubble_factory_registry_.MaybeRegister<HelpBubbleFactoryWebUI>();
}
~HelpBubbleHandlerTest() override = default;
void SetUp() override {
auto visibility_provider =
std::make_unique<TestHelpBubbleHandler::MockVisibilityProvider>();
EXPECT_CALL(*visibility_provider.get(), CheckIsVisible)
.WillRepeatedly(testing::Return(true));
visibility_provider_ = visibility_provider.get();
test_handler_ = std::make_unique<TestHelpBubbleHandler>(
std::vector{kHelpBubbleHandlerTestElementIdentifier,
kHelpBubbleHandlerTestElementIdentifier2},
std::move(visibility_provider));
}
void TearDown() override { visibility_provider_ = nullptr; }
protected:
help_bubble::mojom::HelpBubbleHandler* handler() {
return test_handler_.get();
}
raw_ptr<TestHelpBubbleHandler::MockVisibilityProvider, DanglingUntriaged>
visibility_provider_ = nullptr;
std::unique_ptr<TestHelpBubbleHandler> test_handler_;
HelpBubbleFactoryRegistry help_bubble_factory_registry_;
};
TEST_F(HelpBubbleHandlerTest, StartsWithNoElement) {
EXPECT_FALSE(ui::ElementTracker::GetElementTracker()->GetElementInAnyContext(
kHelpBubbleHandlerTestElementIdentifier));
EXPECT_FALSE(ui::ElementTracker::GetElementTracker()->GetElementInAnyContext(
kHelpBubbleHandlerTestElementIdentifier2));
}
TEST_F(HelpBubbleHandlerTest, ElementCreatedOnEvent) {
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), true, kElementBounds);
EXPECT_TRUE(ui::ElementTracker::GetElementTracker()->GetElementInAnyContext(
kHelpBubbleHandlerTestElementIdentifier));
EXPECT_FALSE(ui::ElementTracker::GetElementTracker()->GetElementInAnyContext(
kHelpBubbleHandlerTestElementIdentifier2));
// Verify that we don't leave elements dangling if the handler is destroyed.
test_handler_.reset();
EXPECT_FALSE(ui::ElementTracker::GetElementTracker()->GetElementInAnyContext(
kHelpBubbleHandlerTestElementIdentifier));
EXPECT_FALSE(ui::ElementTracker::GetElementTracker()->GetElementInAnyContext(
kHelpBubbleHandlerTestElementIdentifier2));
}
TEST_F(HelpBubbleHandlerTest, ElementHiddenOnEvent) {
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), true, kElementBounds);
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), false, gfx::RectF());
EXPECT_FALSE(ui::ElementTracker::GetElementTracker()->GetElementInAnyContext(
kHelpBubbleHandlerTestElementIdentifier));
EXPECT_FALSE(ui::ElementTracker::GetElementTracker()->GetElementInAnyContext(
kHelpBubbleHandlerTestElementIdentifier2));
}
TEST_F(HelpBubbleHandlerTest, ElementActivatedOnEvent) {
UNCALLED_MOCK_CALLBACK(ui::ElementTracker::Callback, activated);
const std::string name = kHelpBubbleHandlerTestElementIdentifier.GetName();
handler()->HelpBubbleAnchorVisibilityChanged(name, true, kElementBounds);
auto* const tracker = ui::ElementTracker::GetElementTracker();
auto* const element =
tracker->GetElementInAnyContext(kHelpBubbleHandlerTestElementIdentifier);
auto subscription =
ui::ElementTracker::GetElementTracker()->AddElementActivatedCallback(
element->identifier(), element->context(), activated.Get());
EXPECT_CALL_IN_SCOPE(activated, Run(element),
handler()->HelpBubbleAnchorActivated(name));
}
TEST_F(HelpBubbleHandlerTest, ElementCustomEventOnEvent) {
DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(kCustomEvent);
const std::string event_name = kCustomEvent.GetName();
const std::string element_name =
kHelpBubbleHandlerTestElementIdentifier.GetName();
UNCALLED_MOCK_CALLBACK(ui::ElementTracker::Callback, custom_event);
handler()->HelpBubbleAnchorVisibilityChanged(element_name, true,
kElementBounds);
auto* const tracker = ui::ElementTracker::GetElementTracker();
auto* const element =
tracker->GetElementInAnyContext(kHelpBubbleHandlerTestElementIdentifier);
auto subscription =
ui::ElementTracker::GetElementTracker()->AddCustomEventCallback(
kCustomEvent, element->context(), custom_event.Get());
EXPECT_CALL_IN_SCOPE(
custom_event, Run(element),
handler()->HelpBubbleAnchorCustomEvent(element_name, event_name));
}
TEST_F(HelpBubbleHandlerTest, MultipleIdentifiers) {
// Show two elements.
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), true, kElementBounds);
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier2.GetName(), true, kElementBounds);
EXPECT_TRUE(ui::ElementTracker::GetElementTracker()->GetElementInAnyContext(
kHelpBubbleHandlerTestElementIdentifier));
EXPECT_TRUE(ui::ElementTracker::GetElementTracker()->GetElementInAnyContext(
kHelpBubbleHandlerTestElementIdentifier2));
// Hide one element.
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), false, gfx::RectF());
EXPECT_FALSE(ui::ElementTracker::GetElementTracker()->GetElementInAnyContext(
kHelpBubbleHandlerTestElementIdentifier));
EXPECT_TRUE(ui::ElementTracker::GetElementTracker()->GetElementInAnyContext(
kHelpBubbleHandlerTestElementIdentifier2));
// Hide the other element.
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier2.GetName(), false, gfx::RectF());
EXPECT_FALSE(ui::ElementTracker::GetElementTracker()->GetElementInAnyContext(
kHelpBubbleHandlerTestElementIdentifier));
EXPECT_FALSE(ui::ElementTracker::GetElementTracker()->GetElementInAnyContext(
kHelpBubbleHandlerTestElementIdentifier2));
// Re-show an element.
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), true, kElementBounds);
EXPECT_TRUE(ui::ElementTracker::GetElementTracker()->GetElementInAnyContext(
kHelpBubbleHandlerTestElementIdentifier));
EXPECT_FALSE(ui::ElementTracker::GetElementTracker()->GetElementInAnyContext(
kHelpBubbleHandlerTestElementIdentifier2));
}
TEST_F(HelpBubbleHandlerTest, ShowHelpBubble) {
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), true, kElementBounds);
auto* const element =
ui::ElementTracker::GetElementTracker()->GetUniqueElement(
kHelpBubbleHandlerTestElementIdentifier, test_handler_->context());
ASSERT_NE(nullptr, element);
HelpBubbleParams params;
params.body_text = u"Help bubble body.";
params.close_button_alt_text = u"Close button alt text.";
params.body_icon = &vector_icons::kCelebrationIcon;
params.body_icon_alt_text = u"Celebration";
params.arrow = HelpBubbleArrow::kTopCenter;
// Check the parameters passed to the ShowHelpBubble mojo method.
help_bubble::mojom::HelpBubbleParamsPtr expected =
help_bubble::mojom::HelpBubbleParams::New();
expected->native_identifier = element->identifier().GetName();
expected->body_text = base::UTF16ToUTF8(params.body_text);
expected->close_button_alt_text =
base::UTF16ToUTF8(params.close_button_alt_text);
expected->body_icon_name = "celebration";
expected->body_icon_alt_text = "Celebration";
expected->position = help_bubble::mojom::HelpBubbleArrowPosition::TOP_CENTER;
expected->timeout = base::Seconds(10);
EXPECT_CALL(test_handler_->mock(),
ShowHelpBubble(MatchesHelpBubbleParams(expected.get())));
auto help_bubble = help_bubble_factory_registry_.CreateHelpBubble(
element, std::move(params));
EXPECT_CALL(test_handler_->mock(), ShowHelpBubble(testing::_)).Times(0);
EXPECT_TRUE(help_bubble);
EXPECT_TRUE(help_bubble->is_open());
EXPECT_CALL(
test_handler_->mock(),
HideHelpBubble(kHelpBubbleHandlerTestElementIdentifier.GetName()));
EXPECT_TRUE(help_bubble->Close());
EXPECT_CALL(test_handler_->mock(), HideHelpBubble).Times(0);
EXPECT_FALSE(help_bubble->is_open());
}
// Regression test for possible cause of crbug.com/1474307.
TEST_F(HelpBubbleHandlerTest, ShowHelpBubbleTwice) {
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), true, kElementBounds);
auto* const element =
ui::ElementTracker::GetElementTracker()->GetUniqueElement(
kHelpBubbleHandlerTestElementIdentifier, test_handler_->context());
ASSERT_NE(nullptr, element);
auto get_params = []() {
HelpBubbleParams params;
params.body_text = u"Help bubble body.";
params.close_button_alt_text = u"Close button alt text.";
params.body_icon = &vector_icons::kCelebrationIcon;
params.body_icon_alt_text = u"Celebration";
params.arrow = HelpBubbleArrow::kTopCenter;
return params;
};
EXPECT_CALL(test_handler_->mock(), ShowHelpBubble(testing::_));
auto help_bubble =
help_bubble_factory_registry_.CreateHelpBubble(element, get_params());
EXPECT_CALL(test_handler_->mock(), HideHelpBubble(testing::_));
EXPECT_CALL(test_handler_->mock(), ShowHelpBubble(testing::_));
auto help_bubble2 =
help_bubble_factory_registry_.CreateHelpBubble(element, get_params());
EXPECT_CALL(test_handler_->mock(), ShowHelpBubble(testing::_)).Times(0);
EXPECT_CALL(test_handler_->mock(), HideHelpBubble(testing::_));
EXPECT_FALSE(help_bubble->is_open());
EXPECT_TRUE(help_bubble2->is_open());
}
TEST_F(HelpBubbleHandlerTest, ShowHelpBubbleWithButtonsAndProgress) {
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), true, kElementBounds);
auto* const element =
ui::ElementTracker::GetElementTracker()->GetUniqueElement(
kHelpBubbleHandlerTestElementIdentifier, test_handler_->context());
ASSERT_NE(nullptr, element);
HelpBubbleParams params;
params.body_text = u"Help bubble body.";
params.close_button_alt_text = u"Close button alt text.";
params.body_icon = &vector_icons::kLightbulbOutlineIcon;
params.body_icon_alt_text = u"Body icon alt text.";
params.arrow = HelpBubbleArrow::kTopCenter;
params.progress = std::make_pair(1, 3);
HelpBubbleButtonParams button;
button.is_default = true;
button.text = u"button1";
params.buttons.emplace_back(std::move(button));
// Check the parameters passed to the ShowHelpBubble mojo method.
help_bubble::mojom::HelpBubbleParamsPtr expected =
help_bubble::mojom::HelpBubbleParams::New();
expected->native_identifier = element->identifier().GetName();
expected->body_text = base::UTF16ToUTF8(params.body_text);
expected->close_button_alt_text =
base::UTF16ToUTF8(params.close_button_alt_text);
expected->body_icon_name = "lightbulb_outline";
expected->body_icon_alt_text = "Body icon alt text.";
expected->position = help_bubble::mojom::HelpBubbleArrowPosition::TOP_CENTER;
auto expected_button = help_bubble::mojom::HelpBubbleButtonParams::New();
expected_button->text = "button1";
expected_button->is_default = true;
expected->buttons.emplace_back(std::move(expected_button));
auto expected_progress = help_bubble::mojom::Progress::New();
expected_progress->current = 1;
expected_progress->total = 3;
expected->progress = std::move(expected_progress);
EXPECT_CALL(test_handler_->mock(),
ShowHelpBubble(MatchesHelpBubbleParams(expected.get())));
auto help_bubble = help_bubble_factory_registry_.CreateHelpBubble(
element, std::move(params));
EXPECT_TRUE(help_bubble);
EXPECT_TRUE(help_bubble->is_open());
EXPECT_CALL(
test_handler_->mock(),
HideHelpBubble(kHelpBubbleHandlerTestElementIdentifier.GetName()));
EXPECT_TRUE(help_bubble->Close());
EXPECT_FALSE(help_bubble->is_open());
}
TEST_F(HelpBubbleHandlerTest, FocusHelpBubble) {
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), true, kElementBounds);
auto* const element =
ui::ElementTracker::GetElementTracker()->GetUniqueElement(
kHelpBubbleHandlerTestElementIdentifier, test_handler_->context());
ASSERT_NE(nullptr, element);
HelpBubbleParams params;
params.body_text = u"Help bubble body.";
params.arrow = HelpBubbleArrow::kTopCenter;
EXPECT_CALL(test_handler_->mock(), ShowHelpBubble(testing::_));
auto help_bubble = help_bubble_factory_registry_.CreateHelpBubble(
element, std::move(params));
EXPECT_CALL(test_handler_->mock(),
ToggleFocusForAccessibility(
kHelpBubbleHandlerTestElementIdentifier.GetName()));
help_bubble_factory_registry_.ToggleFocusForAccessibility(
test_handler_->context());
EXPECT_CALL(test_handler_->mock(), ToggleFocusForAccessibility).Times(0);
EXPECT_CALL(
test_handler_->mock(),
HideHelpBubble(kHelpBubbleHandlerTestElementIdentifier.GetName()));
EXPECT_TRUE(help_bubble->Close());
}
TEST_F(HelpBubbleHandlerTest, ExternalHelpBubbleUpdated) {
// Generate and retrieve a tracked element.
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), true, kElementBounds);
auto* const element =
ui::ElementTracker::GetElementTracker()->GetUniqueElement(
kHelpBubbleHandlerTestElementIdentifier, test_handler_->context());
// Create a dummy external help bubble.
HelpBubbleParams params;
params.body_text = u"Help bubble body.";
params.close_button_alt_text = u"Close button alt text.";
HelpBubbleButtonParams button_params;
button_params.is_default = true;
button_params.text = u"Button";
params.buttons.emplace_back(std::move(button_params));
std::unique_ptr<HelpBubble> help_bubble =
std::make_unique<test::TestHelpBubble>(element, std::move(params));
// Call the floating help bubble created method and ensure that the correct
// message is sent over to the client.
EXPECT_CALL(test_handler_->mock(),
ExternalHelpBubbleUpdated(element->identifier().GetName(), true));
test_handler_->OnFloatingHelpBubbleCreated(element->identifier(),
help_bubble.get());
// Close the bubble and verify that the correct message is sent, this that
// there is no longer a bubble.
EXPECT_CALL(
test_handler_->mock(),
ExternalHelpBubbleUpdated(element->identifier().GetName(), false));
EXPECT_TRUE(help_bubble->Close());
}
TEST_F(HelpBubbleHandlerTest, HelpBubbleClosedWhenVisibilityChanges) {
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), true, kElementBounds);
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier2.GetName(), true, kElementBounds);
auto* const element =
ui::ElementTracker::GetElementTracker()->GetUniqueElement(
kHelpBubbleHandlerTestElementIdentifier, test_handler_->context());
ASSERT_NE(nullptr, element);
HelpBubbleParams params;
params.body_text = u"Help bubble body.";
params.arrow = HelpBubbleArrow::kTopCenter;
EXPECT_CALL(test_handler_->mock(), ShowHelpBubble(testing::_));
auto help_bubble = help_bubble_factory_registry_.CreateHelpBubble(
element, std::move(params));
EXPECT_TRUE(
test_handler_->IsHelpBubbleShowingForTesting(element->identifier()));
// This should have no effect since it's the wrong element.
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier2.GetName(), false, gfx::RectF());
EXPECT_TRUE(help_bubble->is_open());
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), false, gfx::RectF());
EXPECT_FALSE(help_bubble->is_open());
EXPECT_FALSE(
test_handler_->IsHelpBubbleShowingForTesting(element->identifier()));
}
TEST_F(HelpBubbleHandlerTest, HelpBubbleClosedWhenClosedRemotely) {
UNCALLED_MOCK_CALLBACK(HelpBubble::ClosedCallback, closed);
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), true, kElementBounds);
auto* const element =
ui::ElementTracker::GetElementTracker()->GetUniqueElement(
kHelpBubbleHandlerTestElementIdentifier, test_handler_->context());
ASSERT_NE(nullptr, element);
HelpBubbleParams params;
params.body_text = u"Help bubble body.";
params.arrow = HelpBubbleArrow::kTopCenter;
EXPECT_CALL(test_handler_->mock(), ShowHelpBubble(testing::_));
auto help_bubble = help_bubble_factory_registry_.CreateHelpBubble(
element, std::move(params));
auto subscription = help_bubble->AddOnCloseCallback(closed.Get());
EXPECT_TRUE(
test_handler_->IsHelpBubbleShowingForTesting(element->identifier()));
EXPECT_CALL_IN_SCOPE(
closed, Run,
handler()->HelpBubbleClosed(
kHelpBubbleHandlerTestElementIdentifier.GetName(),
help_bubble::mojom::HelpBubbleClosedReason::kPageChanged));
EXPECT_FALSE(help_bubble->is_open());
EXPECT_FALSE(
test_handler_->IsHelpBubbleShowingForTesting(element->identifier()));
}
TEST_F(HelpBubbleHandlerTest, DestroyHandlerCleansUpElement) {
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), true, kElementBounds);
const ui::ElementContext context = test_handler_->context();
EXPECT_TRUE(ui::ElementTracker::GetElementTracker()->IsElementVisible(
kHelpBubbleHandlerTestElementIdentifier, context));
test_handler_.reset();
EXPECT_FALSE(ui::ElementTracker::GetElementTracker()->IsElementVisible(
kHelpBubbleHandlerTestElementIdentifier, context));
}
// Asserts that closing the HelpBubble handle to a bubble instance destroys
// the bubble.
TEST_F(HelpBubbleHandlerTest, DestroyBubbleWrapperClosesHelpBubble) {
UNCALLED_MOCK_CALLBACK(HelpBubble::ClosedCallback, closed);
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), true, kElementBounds);
auto* const element =
ui::ElementTracker::GetElementTracker()->GetUniqueElement(
kHelpBubbleHandlerTestElementIdentifier, test_handler_->context());
ASSERT_NE(nullptr, element);
HelpBubbleParams params;
params.body_text = u"Help bubble body.";
params.arrow = HelpBubbleArrow::kTopCenter;
EXPECT_CALL(test_handler_->mock(), ShowHelpBubble(testing::_));
auto help_bubble = help_bubble_factory_registry_.CreateHelpBubble(
element, std::move(params));
auto subscription = help_bubble->AddOnCloseCallback(closed.Get());
EXPECT_CALL(
test_handler_->mock(),
HideHelpBubble(kHelpBubbleHandlerTestElementIdentifier.GetName()));
EXPECT_CALL_IN_SCOPE(closed, Run, help_bubble.reset());
}
TEST_F(HelpBubbleHandlerTest, HelpBubbleClosedWhenClosedByUserCallsDismiss) {
UNCALLED_MOCK_CALLBACK(base::OnceClosure, dismissed);
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), true, kElementBounds);
auto* const element =
ui::ElementTracker::GetElementTracker()->GetUniqueElement(
kHelpBubbleHandlerTestElementIdentifier, test_handler_->context());
ASSERT_NE(nullptr, element);
HelpBubbleParams params;
params.body_text = u"Help bubble body.";
params.arrow = HelpBubbleArrow::kTopCenter;
params.dismiss_callback = dismissed.Get();
EXPECT_CALL(test_handler_->mock(), ShowHelpBubble(testing::_));
auto help_bubble = help_bubble_factory_registry_.CreateHelpBubble(
element, std::move(params));
EXPECT_TRUE(
test_handler_->IsHelpBubbleShowingForTesting(element->identifier()));
EXPECT_CALL_IN_SCOPE(
dismissed, Run,
handler()->HelpBubbleClosed(
kHelpBubbleHandlerTestElementIdentifier.GetName(),
help_bubble::mojom::HelpBubbleClosedReason::kDismissedByUser));
EXPECT_FALSE(help_bubble->is_open());
EXPECT_FALSE(
test_handler_->IsHelpBubbleShowingForTesting(element->identifier()));
}
TEST_F(HelpBubbleHandlerTest, ButtonPressedCallsCallback) {
UNCALLED_MOCK_CALLBACK(base::OnceClosure, button1_pressed);
UNCALLED_MOCK_CALLBACK(base::OnceClosure, button2_pressed);
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), true, kElementBounds);
auto* const element =
ui::ElementTracker::GetElementTracker()->GetUniqueElement(
kHelpBubbleHandlerTestElementIdentifier, test_handler_->context());
ASSERT_NE(nullptr, element);
HelpBubbleParams params;
params.body_text = u"Help bubble body.";
params.arrow = HelpBubbleArrow::kTopCenter;
HelpBubbleButtonParams button1;
button1.is_default = true;
button1.text = u"button1";
button1.callback = button1_pressed.Get();
params.buttons.emplace_back(std::move(button1));
HelpBubbleButtonParams button2;
button2.is_default = false;
button2.text = u"button2";
button2.callback = button2_pressed.Get();
params.buttons.emplace_back(std::move(button2));
EXPECT_CALL(test_handler_->mock(), ShowHelpBubble(testing::_));
auto help_bubble = help_bubble_factory_registry_.CreateHelpBubble(
element, std::move(params));
EXPECT_TRUE(
test_handler_->IsHelpBubbleShowingForTesting(element->identifier()));
EXPECT_CALL_IN_SCOPE(
button2_pressed, Run,
handler()->HelpBubbleButtonPressed(
kHelpBubbleHandlerTestElementIdentifier.GetName(), 1));
EXPECT_FALSE(help_bubble->is_open());
EXPECT_FALSE(
test_handler_->IsHelpBubbleShowingForTesting(element->identifier()));
}
TEST_F(HelpBubbleHandlerTest, ShowMultipleBubblesAndCloseOneViaVisibility) {
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), true, kElementBounds);
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier2.GetName(), true, kElementBounds);
auto* const element =
ui::ElementTracker::GetElementTracker()->GetUniqueElement(
kHelpBubbleHandlerTestElementIdentifier, test_handler_->context());
auto* const element2 =
ui::ElementTracker::GetElementTracker()->GetUniqueElement(
kHelpBubbleHandlerTestElementIdentifier2, test_handler_->context());
ASSERT_NE(nullptr, element);
ASSERT_NE(nullptr, element2);
HelpBubbleParams params;
params.body_text = u"Help bubble body.";
params.arrow = HelpBubbleArrow::kTopCenter;
EXPECT_CALL(test_handler_->mock(), ShowHelpBubble(testing::_));
auto help_bubble = help_bubble_factory_registry_.CreateHelpBubble(
element, std::move(params));
EXPECT_TRUE(
test_handler_->IsHelpBubbleShowingForTesting(element->identifier()));
HelpBubbleParams params2;
params2.body_text = u"Help bubble body 2.";
params2.arrow = HelpBubbleArrow::kBottomLeft;
EXPECT_CALL(test_handler_->mock(), ShowHelpBubble(testing::_));
auto help_bubble2 = help_bubble_factory_registry_.CreateHelpBubble(
element2, std::move(params2));
EXPECT_TRUE(
test_handler_->IsHelpBubbleShowingForTesting(element2->identifier()));
EXPECT_TRUE(help_bubble->is_open());
EXPECT_TRUE(help_bubble2->is_open());
// Close one bubble without closing the other.
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), false, gfx::RectF());
EXPECT_FALSE(help_bubble->is_open());
EXPECT_TRUE(help_bubble2->is_open());
EXPECT_FALSE(
test_handler_->IsHelpBubbleShowingForTesting(element->identifier()));
EXPECT_TRUE(
test_handler_->IsHelpBubbleShowingForTesting(element2->identifier()));
// When the second bubble goes away, it will attempt to close the bubble on
// the remote.
EXPECT_CALL(
test_handler_->mock(),
HideHelpBubble(kHelpBubbleHandlerTestElementIdentifier2.GetName()));
}
TEST_F(HelpBubbleHandlerTest, ShowMultipleBubblesAndCloseOneViaCallback) {
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), true, kElementBounds);
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier2.GetName(), true, kElementBounds);
auto* const element =
ui::ElementTracker::GetElementTracker()->GetUniqueElement(
kHelpBubbleHandlerTestElementIdentifier, test_handler_->context());
auto* const element2 =
ui::ElementTracker::GetElementTracker()->GetUniqueElement(
kHelpBubbleHandlerTestElementIdentifier2, test_handler_->context());
ASSERT_NE(nullptr, element);
ASSERT_NE(nullptr, element2);
HelpBubbleParams params;
params.body_text = u"Help bubble body.";
params.arrow = HelpBubbleArrow::kTopCenter;
EXPECT_CALL(test_handler_->mock(), ShowHelpBubble(testing::_));
auto help_bubble = help_bubble_factory_registry_.CreateHelpBubble(
element, std::move(params));
EXPECT_TRUE(
test_handler_->IsHelpBubbleShowingForTesting(element->identifier()));
HelpBubbleParams params2;
params2.body_text = u"Help bubble body 2.";
params2.arrow = HelpBubbleArrow::kBottomLeft;
EXPECT_CALL(test_handler_->mock(), ShowHelpBubble(testing::_));
auto help_bubble2 = help_bubble_factory_registry_.CreateHelpBubble(
element2, std::move(params2));
EXPECT_TRUE(
test_handler_->IsHelpBubbleShowingForTesting(element2->identifier()));
EXPECT_TRUE(help_bubble->is_open());
EXPECT_TRUE(help_bubble2->is_open());
// Close one bubble without closing the other.
handler()->HelpBubbleClosed(
kHelpBubbleHandlerTestElementIdentifier.GetName(),
help_bubble::mojom::HelpBubbleClosedReason::kPageChanged);
EXPECT_FALSE(help_bubble->is_open());
EXPECT_TRUE(help_bubble2->is_open());
EXPECT_FALSE(
test_handler_->IsHelpBubbleShowingForTesting(element->identifier()));
EXPECT_TRUE(
test_handler_->IsHelpBubbleShowingForTesting(element2->identifier()));
// When the second bubble goes away, it will attempt to close the bubble on
// the remote.
EXPECT_CALL(
test_handler_->mock(),
HideHelpBubble(kHelpBubbleHandlerTestElementIdentifier2.GetName()));
}
// The following tests check that the WebContents visibility logic.
TEST_F(HelpBubbleHandlerTest, WebContentsNotVisibleResultsInNoElement) {
UNCALLED_MOCK_CALLBACK(ui::ElementTracker::Callback, element_shown);
const auto subscription =
ui::ElementTracker::GetElementTracker()
->AddElementShownInAnyContextCallback(
kHelpBubbleHandlerTestElementIdentifier, element_shown.Get());
EXPECT_CALL(*visibility_provider_, CheckIsVisible)
.WillOnce(testing::Return(false));
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), true, kElementBounds);
}
TEST_F(HelpBubbleHandlerTest, WebContentsVisibilityNotAvailable) {
UNCALLED_MOCK_CALLBACK(ui::ElementTracker::Callback, element_shown);
const auto subscription =
ui::ElementTracker::GetElementTracker()
->AddElementShownInAnyContextCallback(
kHelpBubbleHandlerTestElementIdentifier, element_shown.Get());
EXPECT_CALL(*visibility_provider_, CheckIsVisible)
.WillOnce(testing::Return(absl::nullopt));
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), true, kElementBounds);
}
TEST_F(HelpBubbleHandlerTest, ElementShownOnmWebContentsBecomingVisible) {
UNCALLED_MOCK_CALLBACK(ui::ElementTracker::Callback, element_shown);
const auto subscription =
ui::ElementTracker::GetElementTracker()
->AddElementShownInAnyContextCallback(
kHelpBubbleHandlerTestElementIdentifier, element_shown.Get());
EXPECT_CALL(*visibility_provider_, CheckIsVisible)
.WillOnce(testing::Return(absl::nullopt));
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), true, kElementBounds);
EXPECT_CALL_IN_SCOPE(element_shown, Run,
visibility_provider_->SetLastKnownVisibility(true));
}
TEST_F(HelpBubbleHandlerTest, ElementHiddenWebContentsBecomingInvisible) {
UNCALLED_MOCK_CALLBACK(ui::ElementTracker::Callback, element_hidden);
ui::TrackedElement* el = nullptr;
const auto sub1 = ui::ElementTracker::GetElementTracker()
->AddElementShownInAnyContextCallback(
kHelpBubbleHandlerTestElementIdentifier,
base::BindLambdaForTesting(
[&el](ui::TrackedElement* el_) { el = el_; }));
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), true, kElementBounds);
const auto sub2 =
ui::ElementTracker::GetElementTracker()->AddElementHiddenCallback(
el->identifier(), el->context(), element_hidden.Get());
EXPECT_CALL_IN_SCOPE(element_hidden, Run,
visibility_provider_->SetLastKnownVisibility(false));
}
TEST_F(HelpBubbleHandlerTest, ElementHiddenWebContentsBecomingUnknown) {
UNCALLED_MOCK_CALLBACK(ui::ElementTracker::Callback, element_shown);
UNCALLED_MOCK_CALLBACK(ui::ElementTracker::Callback, element_hidden);
const base::CallbackListSubscription sub1 =
ui::ElementTracker::GetElementTracker()
->AddElementShownInAnyContextCallback(
kHelpBubbleHandlerTestElementIdentifier, element_shown.Get());
base::CallbackListSubscription sub2;
EXPECT_CALL(element_shown, Run).WillOnce([&](ui::TrackedElement* el) {
sub2 = ui::ElementTracker::GetElementTracker()->AddElementHiddenCallback(
el->identifier(), el->context(), element_hidden.Get());
});
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), true, kElementBounds);
EXPECT_CALL_IN_SCOPE(
element_hidden, Run,
visibility_provider_->SetLastKnownVisibility(absl::nullopt));
}
TEST_F(HelpBubbleHandlerTest, RepeatedlyQueriesVisibility) {
UNCALLED_MOCK_CALLBACK(ui::ElementTracker::Callback, element_shown);
const auto subscription =
ui::ElementTracker::GetElementTracker()
->AddElementShownInAnyContextCallback(
kHelpBubbleHandlerTestElementIdentifier, element_shown.Get());
EXPECT_CALL(*visibility_provider_, CheckIsVisible)
.Times(2)
.WillRepeatedly(testing::Return(absl::nullopt));
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), true, kElementBounds);
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier2.GetName(), true, kElementBounds);
}
TEST_F(HelpBubbleHandlerTest, WebContentsVisibilityCanChangeMultipleTimes) {
UNCALLED_MOCK_CALLBACK(ui::ElementTracker::Callback, element_shown);
const auto subscription =
ui::ElementTracker::GetElementTracker()
->AddElementShownInAnyContextCallback(
kHelpBubbleHandlerTestElementIdentifier, element_shown.Get());
EXPECT_CALL_IN_SCOPE(element_shown, Run,
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(),
true, kElementBounds));
visibility_provider_->SetLastKnownVisibility(false);
EXPECT_CALL_IN_SCOPE(element_shown, Run,
visibility_provider_->SetLastKnownVisibility(true));
visibility_provider_->SetLastKnownVisibility(absl::nullopt);
EXPECT_CALL_IN_SCOPE(element_shown, Run,
visibility_provider_->SetLastKnownVisibility(true));
}
TEST_F(HelpBubbleHandlerTest, DestroyHandlerDuringCallback) {
UNCALLED_MOCK_CALLBACK(ui::ElementTracker::Callback, element_shown);
const auto subscription =
ui::ElementTracker::GetElementTracker()
->AddElementShownInAnyContextCallback(
kHelpBubbleHandlerTestElementIdentifier, element_shown.Get());
EXPECT_CALL_IN_SCOPE(element_shown, Run,
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(),
true, kElementBounds));
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier2.GetName(), true, kElementBounds);
visibility_provider_->SetLastKnownVisibility(false);
EXPECT_CALL(element_shown, Run).WillOnce([&]() { test_handler_.reset(); });
visibility_provider_->SetLastKnownVisibility(true);
}
TEST_F(HelpBubbleHandlerTest,
WebUIHelpBubblePreventsHideWhenVisibilityChanges) {
UNCALLED_MOCK_CALLBACK(ui::ElementTracker::Callback, element_hidden);
handler()->HelpBubbleAnchorVisibilityChanged(
kHelpBubbleHandlerTestElementIdentifier.GetName(), true, kElementBounds);
auto* const element =
ui::ElementTracker::GetElementTracker()->GetUniqueElement(
kHelpBubbleHandlerTestElementIdentifier, test_handler_->context());
ASSERT_NE(nullptr, element);
const auto subscription =
ui::ElementTracker::GetElementTracker()->AddElementHiddenCallback(
element->identifier(), element->context(), element_hidden.Get());
HelpBubbleParams params;
params.body_text = u"Help bubble body.";
params.arrow = HelpBubbleArrow::kTopCenter;
EXPECT_CALL(test_handler_->mock(), ShowHelpBubble(testing::_));
auto help_bubble = help_bubble_factory_registry_.CreateHelpBubble(
element, std::move(params));
visibility_provider_->SetLastKnownVisibility(false);
EXPECT_TRUE(help_bubble->is_open());
EXPECT_CALL(test_handler_->mock(), HideHelpBubble(testing::_));
EXPECT_CALL_IN_SCOPE(element_hidden, Run, help_bubble->Close());
}
} // namespace user_education