blob: 77402aea48dd281658d70d179a534b50f81084fc [file] [log] [blame]
// Copyright 2021 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/common/tutorial.h"
#include <string>
#include "base/test/bind.h"
#include "base/test/gtest_util.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "components/strings/grit/components_strings.h"
#include "components/user_education/common/help_bubble_factory_registry.h"
#include "components/user_education/common/help_bubble_params.h"
#include "components/user_education/common/tutorial_description.h"
#include "components/user_education/common/tutorial_identifier.h"
#include "components/user_education/common/tutorial_registry.h"
#include "components/user_education/common/tutorial_service.h"
#include "components/user_education/test/test_help_bubble.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_test_util.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/base/interaction/expect_call_in_scope.h"
#include "ui/base/interaction/interaction_sequence.h"
#include "ui/base/l10n/l10n_util.h"
namespace user_education {
namespace {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestIdentifier1);
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestIdentifier2);
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestIdentifier3);
DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(kCustomEventType1);
const char kTestElementName1[] = "ELEMENT_NAME_1";
const ui::ElementContext kTestContext1(1);
const TutorialIdentifier kTestTutorial1{"kTestTutorial1"};
const TutorialIdentifier kTestTutorial2{"kTestTutorial2"};
const TutorialIdentifier kTestTutorial3{"kTestTutorial3"};
const char kHistogramName1[] = "histogram 1";
const char kHistogramName2[] = "histogram 2";
class TestTutorialService : public TutorialService {
public:
TestTutorialService(TutorialRegistry* tutorial_registry,
HelpBubbleFactoryRegistry* help_bubble_factory_registry)
: TutorialService(tutorial_registry, help_bubble_factory_registry) {}
~TestTutorialService() override = default;
std::u16string GetBodyIconAltText(bool is_last_step) const override {
return std::u16string();
}
};
std::unique_ptr<HelpBubbleFactoryRegistry>
CreateTestTutorialBubbleFactoryRegistry() {
auto bubble_factory_registry = std::make_unique<HelpBubbleFactoryRegistry>();
bubble_factory_registry->MaybeRegister<test::TestHelpBubbleFactory>();
return bubble_factory_registry;
}
void ClickDismissButton(HelpBubble* bubble) {
auto* const help_bubble = static_cast<test::TestHelpBubble*>(bubble);
help_bubble->SimulateDismiss();
}
void ClickCloseButton(HelpBubble* bubble) {
auto* const help_bubble = static_cast<test::TestHelpBubble*>(bubble);
int button_index = help_bubble->GetIndexOfButtonWithText(
l10n_util::GetStringUTF16(IDS_TUTORIAL_CLOSE_TUTORIAL));
EXPECT_TRUE(button_index != test::TestHelpBubble::kNoButtonWithTextIndex);
help_bubble->SimulateButtonPress(button_index);
}
void ClickNextButton(HelpBubble* bubble) {
auto* const help_bubble = static_cast<test::TestHelpBubble*>(bubble);
int button_index = help_bubble->GetIndexOfButtonWithText(
l10n_util::GetStringUTF16(IDS_TUTORIAL_NEXT_BUTTON));
EXPECT_TRUE(button_index != test::TestHelpBubble::kNoButtonWithTextIndex);
help_bubble->SimulateButtonPress(button_index);
}
void ClickRestartButton(HelpBubble* bubble) {
auto* const help_bubble = static_cast<test::TestHelpBubble*>(bubble);
int button_index = help_bubble->GetIndexOfButtonWithText(
l10n_util::GetStringUTF16(IDS_TUTORIAL_RESTART_TUTORIAL));
EXPECT_TRUE(button_index != test::TestHelpBubble::kNoButtonWithTextIndex);
help_bubble->SimulateButtonPress(button_index);
}
bool HasButtonWithText(HelpBubble* bubble, int message_id) {
return static_cast<test::TestHelpBubble*>(bubble)->GetIndexOfButtonWithText(
l10n_util::GetStringUTF16(message_id)) !=
test::TestHelpBubble::kNoButtonWithTextIndex;
}
} // namespace
class TutorialTest : public testing::Test {
public:
TutorialTest() = default;
~TutorialTest() override = default;
protected:
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
};
TEST_F(TutorialTest, TutorialBuilder) {
const auto bubble_factory_registry =
CreateTestTutorialBubbleFactoryRegistry();
TutorialRegistry registry;
TestTutorialService service(&registry, bubble_factory_registry.get());
Tutorial::Builder builder;
// build a step with an ElementID
std::unique_ptr<ui::InteractionSequence::Step> step1 =
Tutorial::StepBuilder()
.SetAnchorElementID(kTestIdentifier1)
.Build(&service);
// build a step that names an element
std::unique_ptr<ui::InteractionSequence::Step> step2 =
Tutorial::StepBuilder()
.SetAnchorElementID(kTestIdentifier1)
.SetNameElementsCallback(
base::BindRepeating([](ui::InteractionSequence* sequence,
ui::TrackedElement* element) {
sequence->NameElement(element, "TEST ELEMENT");
return true;
}))
.Build(&service);
// build a step with a named element
std::unique_ptr<ui::InteractionSequence::Step> step3 =
Tutorial::StepBuilder()
.SetAnchorElementName(std::string(kTestElementName1))
.Build(&service);
// transition event
std::unique_ptr<ui::InteractionSequence::Step> step4 =
Tutorial::StepBuilder()
.SetAnchorElementID(kTestIdentifier1)
.SetTransitionOnlyOnEvent(true)
.Build(&service);
builder.SetContext(kTestContext1)
.AddStep(std::move(step1))
.AddStep(std::move(step2))
.AddStep(std::move(step3))
.AddStep(std::move(step4))
.Build();
}
TEST_F(TutorialTest, RegisterTutorial) {
std::unique_ptr<TutorialRegistry> registry =
std::make_unique<TutorialRegistry>();
{
TutorialDescription description;
description.steps.emplace_back(
0, IDS_OK, ui::InteractionSequence::StepType::kShown, kTestIdentifier1,
std::string(), HelpBubbleArrow::kNone);
description.can_be_restarted = true;
registry->AddTutorial(kTestTutorial1, std::move(description));
}
std::unique_ptr<HelpBubbleFactoryRegistry> bubble_factory_registry =
std::make_unique<HelpBubbleFactoryRegistry>();
registry->GetTutorialIdentifiers();
}
TEST_F(TutorialTest, RegisterMultipleTutorials) {
std::unique_ptr<TutorialRegistry> registry =
std::make_unique<TutorialRegistry>();
TutorialDescription::Step step(
0, IDS_OK, ui::InteractionSequence::StepType::kShown, kTestIdentifier1,
std::string(), HelpBubbleArrow::kNone);
TutorialDescription description1;
description1.steps.push_back(step);
description1.histograms = MakeTutorialHistograms<kHistogramName1>(1);
TutorialDescription description2;
description2.steps.push_back(step);
description2.histograms = MakeTutorialHistograms<kHistogramName2>(1);
registry->AddTutorial(kTestTutorial1, std::move(description1));
registry->AddTutorial(kTestTutorial2, std::move(description2));
EXPECT_TRUE(registry->IsTutorialRegistered(kTestTutorial1));
EXPECT_TRUE(registry->IsTutorialRegistered(kTestTutorial2));
}
TEST_F(TutorialTest, RegisterSameTutorialTwice) {
std::unique_ptr<TutorialRegistry> registry =
std::make_unique<TutorialRegistry>();
TutorialDescription::Step step(
0, IDS_OK, ui::InteractionSequence::StepType::kShown, kTestIdentifier1,
std::string(), HelpBubbleArrow::kNone);
TutorialDescription description1;
description1.steps.push_back(step);
description1.histograms = MakeTutorialHistograms<kHistogramName1>(1);
TutorialDescription description2;
description2.steps.push_back(step);
description2.histograms = MakeTutorialHistograms<kHistogramName1>(1);
registry->AddTutorial(kTestTutorial1, std::move(description1));
registry->AddTutorial(kTestTutorial1, std::move(description2));
EXPECT_TRUE(registry->IsTutorialRegistered(kTestTutorial1));
}
TEST_F(TutorialTest, RegisterTutorialsWithAndWithoutHistograms) {
std::unique_ptr<TutorialRegistry> registry =
std::make_unique<TutorialRegistry>();
TutorialDescription::Step step(
0, IDS_OK, ui::InteractionSequence::StepType::kShown, kTestIdentifier1,
std::string(), HelpBubbleArrow::kNone);
TutorialDescription description1;
description1.steps.push_back(step);
TutorialDescription description2;
description2.steps.push_back(step);
description2.histograms = MakeTutorialHistograms<kHistogramName2>(1);
TutorialDescription description3;
description2.steps.push_back(step);
registry->AddTutorial(kTestTutorial1, std::move(description1));
registry->AddTutorial(kTestTutorial2, std::move(description2));
registry->AddTutorial(kTestTutorial3, std::move(description3));
EXPECT_TRUE(registry->IsTutorialRegistered(kTestTutorial1));
EXPECT_TRUE(registry->IsTutorialRegistered(kTestTutorial2));
EXPECT_TRUE(registry->IsTutorialRegistered(kTestTutorial3));
}
#if DCHECK_IS_ON()
#define MAYBE_RegisterDifferentTutorialsWithSameHistogram \
RegisterDifferentTutorialsWithSameHistogram
#else
#define MAYBE_RegisterDifferentTutorialsWithSameHistogram \
DISABLED_RegisterDifferentTutorialsWithSameHistogram
#endif
TEST_F(TutorialTest, MAYBE_RegisterDifferentTutorialsWithSameHistogram) {
std::unique_ptr<TutorialRegistry> registry =
std::make_unique<TutorialRegistry>();
TutorialDescription::Step step(
0, IDS_OK, ui::InteractionSequence::StepType::kShown, kTestIdentifier1,
std::string(), HelpBubbleArrow::kNone);
TutorialDescription description1;
description1.steps.push_back(step);
description1.histograms = MakeTutorialHistograms<kHistogramName1>(1);
TutorialDescription description2;
description2.steps.push_back(step);
description2.histograms = MakeTutorialHistograms<kHistogramName1>(1);
registry->AddTutorial(kTestTutorial1, std::move(description1));
EXPECT_DCHECK_DEATH(
registry->AddTutorial(kTestTutorial2, std::move(description2)));
}
TEST_F(TutorialTest, RegisterSameTutorialInMultipleRegistries) {
std::unique_ptr<TutorialRegistry> registry1 =
std::make_unique<TutorialRegistry>();
std::unique_ptr<TutorialRegistry> registry2 =
std::make_unique<TutorialRegistry>();
TutorialDescription::Step step(
0, IDS_OK, ui::InteractionSequence::StepType::kShown, kTestIdentifier1,
std::string(), HelpBubbleArrow::kNone);
TutorialDescription description1;
description1.steps.push_back(step);
description1.histograms = MakeTutorialHistograms<kHistogramName1>(1);
TutorialDescription description2;
description2.steps.push_back(step);
description2.histograms = MakeTutorialHistograms<kHistogramName1>(1);
registry1->AddTutorial(kTestTutorial1, std::move(description1));
registry2->AddTutorial(kTestTutorial1, std::move(description2));
EXPECT_TRUE(registry1->IsTutorialRegistered(kTestTutorial1));
EXPECT_TRUE(registry2->IsTutorialRegistered(kTestTutorial1));
}
TEST_F(TutorialTest, SingleInteractionTutorialRuns) {
UNCALLED_MOCK_CALLBACK(TutorialService::CompletedCallback, completed);
const auto bubble_factory_registry =
CreateTestTutorialBubbleFactoryRegistry();
TutorialRegistry registry;
TestTutorialService service(&registry, bubble_factory_registry.get());
// build elements and keep them for triggering show/hide
ui::test::TestElement element_1(kTestIdentifier1, kTestContext1);
element_1.Show();
// Build the tutorial Description
TutorialDescription description;
description.steps.emplace_back(IDS_OK, IDS_OK,
ui::InteractionSequence::StepType::kShown,
kTestIdentifier1, "", HelpBubbleArrow::kNone);
registry.AddTutorial(kTestTutorial1, std::move(description));
service.StartTutorial(kTestTutorial1, element_1.context(), completed.Get());
EXPECT_TRUE(service.currently_displayed_bubble_for_testing());
EXPECT_CALL_IN_SCOPE(
completed, Run,
ClickCloseButton(service.currently_displayed_bubble_for_testing()));
}
TEST_F(TutorialTest, MultipleInteractionTutorialRuns) {
UNCALLED_MOCK_CALLBACK(TutorialDescription::NextButtonCallback, next);
UNCALLED_MOCK_CALLBACK(TutorialService::CompletedCallback, completed);
const auto bubble_factory_registry =
CreateTestTutorialBubbleFactoryRegistry();
TutorialRegistry registry;
TestTutorialService service(&registry, bubble_factory_registry.get());
// Build and show test elements.
ui::test::TestElement element_1(kTestIdentifier1, kTestContext1);
ui::test::TestElement element_2(kTestIdentifier2, kTestContext1);
ui::test::TestElement element_3(kTestIdentifier3, kTestContext1);
element_1.Show();
element_2.Show();
element_3.Show();
// Configure `next` callback to trigger `kCustomEventType1` on current anchor.
ON_CALL(next, Run).WillByDefault(
testing::Invoke([](ui::TrackedElement* current_anchor) {
ui::ElementTracker::GetFrameworkDelegate()->NotifyCustomEvent(
current_anchor, kCustomEventType1);
}));
// Build the tutorial `description`.
TutorialDescription description;
description.steps.emplace_back(
TutorialDescription::BubbleStep(kTestIdentifier1)
.SetBubbleBodyText(IDS_OK)
.AddDefaultNextButton());
description.steps.emplace_back(TutorialDescription::EventStep(
kHelpBubbleNextButtonClickedEvent, kTestIdentifier1));
description.steps.emplace_back(
TutorialDescription::BubbleStep(kTestIdentifier2)
.SetBubbleBodyText(IDS_OK)
.AddCustomNextButton(next.Get()));
description.steps.emplace_back(
TutorialDescription::EventStep(kCustomEventType1, kTestIdentifier2));
description.steps.emplace_back(
TutorialDescription::BubbleStep(kTestIdentifier3)
.SetBubbleBodyText(IDS_OK)
// Should no-op for last step.
.AddCustomNextButton(base::DoNothing())
.AddDefaultNextButton());
// Register and start the tutorial.
registry.AddTutorial(kTestTutorial1, std::move(description));
service.StartTutorial(kTestTutorial1, element_1.context(), completed.Get());
// Verify only the default next button is visible and click it.
auto* bubble = service.currently_displayed_bubble_for_testing();
ASSERT_TRUE(bubble);
EXPECT_FALSE(HasButtonWithText(bubble, IDS_TUTORIAL_CLOSE_TUTORIAL));
EXPECT_TRUE(HasButtonWithText(bubble, IDS_TUTORIAL_NEXT_BUTTON));
ClickNextButton(bubble);
// Verify only the custom next button is visible and click it.
bubble = service.currently_displayed_bubble_for_testing();
ASSERT_TRUE(bubble);
EXPECT_FALSE(HasButtonWithText(bubble, IDS_TUTORIAL_CLOSE_TUTORIAL));
EXPECT_TRUE(HasButtonWithText(bubble, IDS_TUTORIAL_NEXT_BUTTON));
EXPECT_CALL_IN_SCOPE(next, Run, ClickNextButton(bubble));
// Verify only the close button is visible and click it.
bubble = service.currently_displayed_bubble_for_testing();
ASSERT_TRUE(bubble);
EXPECT_TRUE(HasButtonWithText(bubble, IDS_TUTORIAL_CLOSE_TUTORIAL));
EXPECT_FALSE(HasButtonWithText(bubble, IDS_TUTORIAL_NEXT_BUTTON));
EXPECT_CALL_IN_SCOPE(completed, Run, ClickCloseButton(bubble));
}
TEST_F(TutorialTest, StartTutorialAbortsExistingTutorial) {
UNCALLED_MOCK_CALLBACK(TutorialService::CompletedCallback, completed);
UNCALLED_MOCK_CALLBACK(TutorialService::CompletedCallback, aborted);
const auto bubble_factory_registry =
CreateTestTutorialBubbleFactoryRegistry();
TutorialRegistry registry;
TestTutorialService service(&registry, bubble_factory_registry.get());
// build elements and keep them for triggering show/hide
ui::test::TestElement element_1(kTestIdentifier1, kTestContext1);
element_1.Show();
// Build the tutorial Description. This has two steps, the second of which
// will not
TutorialDescription description;
description.steps.emplace_back(IDS_OK, IDS_OK,
ui::InteractionSequence::StepType::kShown,
kTestIdentifier1, "", HelpBubbleArrow::kNone);
description.steps.emplace_back(IDS_OK, IDS_OK,
ui::InteractionSequence::StepType::kShown,
kTestIdentifier2, "", HelpBubbleArrow::kNone);
registry.AddTutorial(kTestTutorial1, std::move(description));
service.StartTutorial(kTestTutorial1, element_1.context(), completed.Get(),
aborted.Get());
EXPECT_CALL_IN_SCOPE(
aborted, Run, service.StartTutorial(kTestTutorial1, element_1.context()));
EXPECT_TRUE(service.IsRunningTutorial());
}
TEST_F(TutorialTest, StartTutorialCompletesExistingTutorial) {
UNCALLED_MOCK_CALLBACK(TutorialService::CompletedCallback, completed);
UNCALLED_MOCK_CALLBACK(TutorialService::CompletedCallback, aborted);
const auto bubble_factory_registry =
CreateTestTutorialBubbleFactoryRegistry();
TutorialRegistry registry;
TestTutorialService service(&registry, bubble_factory_registry.get());
// build elements and keep them for triggering show/hide
ui::test::TestElement element_1(kTestIdentifier1, kTestContext1);
element_1.Show();
// Build the tutorial Description. This has two steps, the second of which
// will not
TutorialDescription description;
description.steps.emplace_back(IDS_OK, IDS_OK,
ui::InteractionSequence::StepType::kShown,
kTestIdentifier1, "", HelpBubbleArrow::kNone);
registry.AddTutorial(kTestTutorial1, std::move(description));
service.StartTutorial(kTestTutorial1, element_1.context(), completed.Get(),
aborted.Get());
EXPECT_CALL_IN_SCOPE(
completed, Run,
service.StartTutorial(kTestTutorial1, element_1.context()));
EXPECT_TRUE(service.IsRunningTutorial());
}
TEST_F(TutorialTest, TutorialWithCustomEvent) {
UNCALLED_MOCK_CALLBACK(TutorialService::CompletedCallback, completed);
const auto bubble_factory_registry =
CreateTestTutorialBubbleFactoryRegistry();
TutorialRegistry registry;
TestTutorialService service(&registry, bubble_factory_registry.get());
// build elements and keep them for triggering show/hide
ui::test::TestElement element_1(kTestIdentifier1, kTestContext1);
element_1.Show();
// Build the tutorial Description
TutorialDescription description;
description.steps.emplace_back(
IDS_OK, IDS_OK, ui::InteractionSequence::StepType::kCustomEvent,
kTestIdentifier1, "", HelpBubbleArrow::kNone, kCustomEventType1);
registry.AddTutorial(kTestTutorial1, std::move(description));
service.StartTutorial(kTestTutorial1, element_1.context(), completed.Get());
ui::ElementTracker::GetFrameworkDelegate()->NotifyCustomEvent(
&element_1, kCustomEventType1);
EXPECT_CALL_IN_SCOPE(
completed, Run,
ClickCloseButton(service.currently_displayed_bubble_for_testing()));
}
TEST_F(TutorialTest, TutorialWithNamedElement) {
UNCALLED_MOCK_CALLBACK(TutorialService::CompletedCallback, completed);
static constexpr char kElementName1[] = "Element Name 1";
static constexpr char kElementName2[] = "Element Name 2";
static constexpr char kElementName3[] = "Element Name 3";
const auto bubble_factory_registry =
CreateTestTutorialBubbleFactoryRegistry();
TutorialRegistry registry;
TestTutorialService service(&registry, bubble_factory_registry.get());
// Build elements and keep them for triggering show/hide.
ui::test::TestElement element_1(kTestIdentifier1, kTestContext1);
element_1.Show();
// Build the tutorial description.
TutorialDescription description;
description.steps.emplace_back(
IDS_OK, IDS_OK, ui::InteractionSequence::StepType::kShown,
kTestIdentifier1, std::string(), HelpBubbleArrow::kNone,
ui::CustomElementEventType(),
/* must_remain_visible =*/true,
/* transition_only_on_event =*/false,
base::BindLambdaForTesting(
[](ui::InteractionSequence* sequence, ui::TrackedElement* element) {
sequence->NameElement(element, base::StringPiece(kElementName1));
return true;
}));
description.steps.emplace_back(
TutorialDescription::HiddenStep::WaitForShown(kElementName1)
.NameElement(kElementName2));
description.steps.emplace_back(
TutorialDescription::HiddenStep::WaitForShown(kElementName2)
.NameElements(base::BindRepeating(
[](ui::InteractionSequence* sequence, ui::TrackedElement* el) {
sequence->NameElement(el, base::StringPiece(kElementName3));
return true;
})));
description.steps.emplace_back(
IDS_OK, IDS_OK, ui::InteractionSequence::StepType::kShown,
ui::ElementIdentifier(), kElementName3, HelpBubbleArrow::kNone);
registry.AddTutorial(kTestTutorial1, std::move(description));
service.StartTutorial(kTestTutorial1, element_1.context(), completed.Get());
EXPECT_CALL_IN_SCOPE(
completed, Run,
ClickCloseButton(service.currently_displayed_bubble_for_testing()));
}
TEST_F(TutorialTest, SingleStepRestartTutorial) {
UNCALLED_MOCK_CALLBACK(TutorialService::CompletedCallback, completed);
const auto bubble_factory_registry =
CreateTestTutorialBubbleFactoryRegistry();
TutorialRegistry registry;
TestTutorialService service(&registry, bubble_factory_registry.get());
// build elements and keep them for triggering show/hide
ui::test::TestElement element_1(kTestIdentifier1, kTestContext1);
element_1.Show();
// Build the tutorial Description
TutorialDescription description;
description.steps.emplace_back(IDS_OK, IDS_OK,
ui::InteractionSequence::StepType::kShown,
kTestIdentifier1, "", HelpBubbleArrow::kNone);
description.can_be_restarted = true;
registry.AddTutorial(kTestTutorial1, std::move(description));
service.StartTutorial(kTestTutorial1, element_1.context(), completed.Get());
ClickRestartButton(service.currently_displayed_bubble_for_testing());
EXPECT_CALL_IN_SCOPE(
completed, Run,
ClickCloseButton(service.currently_displayed_bubble_for_testing()));
}
// Starts a tutorial with 3 steps, completes steps, then clicks restart tutorial
// then completes the tutorial again and closes it from the close button.
// Expects to call the completed callback.
TEST_F(TutorialTest, MultiStepRestartTutorialWithCloseOnComplete) {
UNCALLED_MOCK_CALLBACK(TutorialService::CompletedCallback, completed);
const auto bubble_factory_registry =
CreateTestTutorialBubbleFactoryRegistry();
TutorialRegistry registry;
TestTutorialService service(&registry, bubble_factory_registry.get());
// build elements and keep them for triggering show/hide
ui::test::TestElement element_1(kTestIdentifier1, kTestContext1);
ui::test::TestElement element_2(kTestIdentifier2, kTestContext1);
ui::test::TestElement element_3(kTestIdentifier3, kTestContext1);
element_1.Show();
// Build the tutorial Description
TutorialDescription description;
description.steps.emplace_back(IDS_OK, IDS_OK,
ui::InteractionSequence::StepType::kShown,
kTestIdentifier1, "", HelpBubbleArrow::kNone);
description.steps.emplace_back(IDS_OK, IDS_OK,
ui::InteractionSequence::StepType::kShown,
kTestIdentifier2, "", HelpBubbleArrow::kNone);
description.steps.emplace_back(IDS_OK, IDS_OK,
ui::InteractionSequence::StepType::kShown,
kTestIdentifier3, "", HelpBubbleArrow::kNone);
description.can_be_restarted = true;
registry.AddTutorial(kTestTutorial1, std::move(description));
service.StartTutorial(kTestTutorial1, element_1.context(), completed.Get());
element_2.Show();
element_3.Show();
element_2.Hide();
ClickRestartButton(service.currently_displayed_bubble_for_testing());
EXPECT_TRUE(service.IsRunningTutorial());
element_2.Show();
EXPECT_CALL_IN_SCOPE(
completed, Run,
ClickCloseButton(service.currently_displayed_bubble_for_testing()));
}
// Starts a tutorial with 3 steps, completes steps, then clicks restart tutorial
// then closes the tutorial on the first step. Expects to call the completed
// callback.
TEST_F(TutorialTest, MultiStepRestartTutorialWithDismissAfterRestart) {
UNCALLED_MOCK_CALLBACK(TutorialService::CompletedCallback, completed);
const auto bubble_factory_registry =
CreateTestTutorialBubbleFactoryRegistry();
TutorialRegistry registry;
TestTutorialService service(&registry, bubble_factory_registry.get());
// build elements and keep them for triggering show/hide
ui::test::TestElement element_1(kTestIdentifier1, kTestContext1);
ui::test::TestElement element_2(kTestIdentifier2, kTestContext1);
ui::test::TestElement element_3(kTestIdentifier3, kTestContext1);
element_1.Show();
// Build the tutorial Description
TutorialDescription description;
description.steps.emplace_back(IDS_OK, IDS_OK,
ui::InteractionSequence::StepType::kShown,
kTestIdentifier1, "", HelpBubbleArrow::kNone);
description.steps.emplace_back(IDS_OK, IDS_OK,
ui::InteractionSequence::StepType::kShown,
kTestIdentifier2, "", HelpBubbleArrow::kNone);
description.steps.emplace_back(IDS_OK, IDS_OK,
ui::InteractionSequence::StepType::kShown,
kTestIdentifier3, "", HelpBubbleArrow::kNone);
description.can_be_restarted = true;
registry.AddTutorial(kTestTutorial1, std::move(description));
service.StartTutorial(kTestTutorial1, element_1.context(), completed.Get());
element_2.Show();
element_3.Show();
element_2.Hide();
ClickRestartButton(service.currently_displayed_bubble_for_testing());
EXPECT_TRUE(service.IsRunningTutorial());
EXPECT_TRUE(service.currently_displayed_bubble_for_testing() != nullptr);
EXPECT_CALL_IN_SCOPE(
completed, Run,
ClickDismissButton(service.currently_displayed_bubble_for_testing()));
}
// Verify that when the final bubble of a tutorial is forced to close without
// being dismissed by the user (e.g. because its anchor element disappears, or
// it's programmatically closed) the tutorial ends.
TEST_F(TutorialTest, BubbleClosingProgrammaticallyOnlyEndsTutorialOnLastStep) {
UNCALLED_MOCK_CALLBACK(TutorialService::CompletedCallback, completed);
UNCALLED_MOCK_CALLBACK(TutorialService::AbortedCallback, aborted);
const auto bubble_factory_registry =
CreateTestTutorialBubbleFactoryRegistry();
TutorialRegistry registry;
TestTutorialService service(&registry, bubble_factory_registry.get());
// build elements and keep them for triggering show/hide
ui::test::TestElement element_1(kTestIdentifier1, kTestContext1);
ui::test::TestElement element_2(kTestIdentifier2, kTestContext1);
element_1.Show();
// Build the tutorial Description
TutorialDescription description;
description.steps.emplace_back(IDS_OK, IDS_OK,
ui::InteractionSequence::StepType::kShown,
kTestIdentifier1, "", HelpBubbleArrow::kNone);
description.steps.emplace_back(IDS_OK, IDS_OK,
ui::InteractionSequence::StepType::kShown,
kTestIdentifier2, "", HelpBubbleArrow::kNone);
description.can_be_restarted = true;
registry.AddTutorial(kTestTutorial1, std::move(description));
service.StartTutorial(kTestTutorial1, element_1.context(), completed.Get(),
aborted.Get());
service.currently_displayed_bubble_for_testing()->Close();
element_2.Show();
EXPECT_CALL_IN_SCOPE(
completed, Run,
service.currently_displayed_bubble_for_testing()->Close());
}
TEST_F(TutorialTest, TimeoutBeforeFirstBubble) {
UNCALLED_MOCK_CALLBACK(TutorialService::CompletedCallback, completed);
UNCALLED_MOCK_CALLBACK(TutorialService::AbortedCallback, aborted);
const auto bubble_factory_registry =
CreateTestTutorialBubbleFactoryRegistry();
TutorialRegistry registry;
TestTutorialService service(&registry, bubble_factory_registry.get());
ui::test::TestElement el(kTestIdentifier1, kTestContext1);
TutorialDescription description;
description.steps.emplace_back(IDS_OK, IDS_OK,
ui::InteractionSequence::StepType::kShown,
kTestIdentifier1, "", HelpBubbleArrow::kNone);
registry.AddTutorial(kTestTutorial1, std::move(description));
service.StartTutorial(kTestTutorial1, el.context(), completed.Get(),
aborted.Get());
EXPECT_FALSE(service.currently_displayed_bubble_for_testing());
EXPECT_CALL_IN_SCOPE(aborted, Run,
task_environment_.FastForwardUntilNoTasksRemain());
}
TEST_F(TutorialTest, TimeoutBetweenBubbles) {
UNCALLED_MOCK_CALLBACK(TutorialService::CompletedCallback, completed);
UNCALLED_MOCK_CALLBACK(TutorialService::AbortedCallback, aborted);
const auto bubble_factory_registry =
CreateTestTutorialBubbleFactoryRegistry();
TutorialRegistry registry;
TestTutorialService service(&registry, bubble_factory_registry.get());
ui::test::TestElement el1(kTestIdentifier1, kTestContext1);
ui::test::TestElement el2(kTestIdentifier1, kTestContext1);
el1.Show();
TutorialDescription description;
description.steps.emplace_back(
IDS_OK, IDS_OK, ui::InteractionSequence::StepType::kShown,
kTestIdentifier1, "", HelpBubbleArrow::kNone,
ui::CustomElementEventType(), /* must_remain_visible */ false);
description.steps.emplace_back(IDS_OK, IDS_OK,
ui::InteractionSequence::StepType::kShown,
kTestIdentifier2, "", HelpBubbleArrow::kNone);
registry.AddTutorial(kTestTutorial1, std::move(description));
service.StartTutorial(kTestTutorial1, el1.context(), completed.Get(),
aborted.Get());
// This closes the bubble but does not advance the tutorial.
el1.Hide();
EXPECT_FALSE(service.currently_displayed_bubble_for_testing());
EXPECT_CALL_IN_SCOPE(aborted, Run,
task_environment_.FastForwardUntilNoTasksRemain());
}
TEST_F(TutorialTest, NoTimeoutIfBubbleShowing) {
UNCALLED_MOCK_CALLBACK(TutorialService::CompletedCallback, completed);
UNCALLED_MOCK_CALLBACK(TutorialService::AbortedCallback, aborted);
const auto bubble_factory_registry =
CreateTestTutorialBubbleFactoryRegistry();
TutorialRegistry registry;
TestTutorialService service(&registry, bubble_factory_registry.get());
ui::test::TestElement el1(kTestIdentifier1, kTestContext1);
ui::test::TestElement el2(kTestIdentifier1, kTestContext1);
el1.Show();
TutorialDescription description;
description.steps.emplace_back(IDS_OK, IDS_OK,
ui::InteractionSequence::StepType::kShown,
kTestIdentifier1, "", HelpBubbleArrow::kNone);
description.steps.emplace_back(IDS_OK, IDS_OK,
ui::InteractionSequence::StepType::kShown,
kTestIdentifier2, "", HelpBubbleArrow::kNone);
registry.AddTutorial(kTestTutorial1, std::move(description));
service.StartTutorial(kTestTutorial1, el1.context(), completed.Get(),
aborted.Get());
// Since there is a bubble, there is no timeout.
EXPECT_TRUE(service.currently_displayed_bubble_for_testing());
task_environment_.FastForwardUntilNoTasksRemain();
// When we exit and destroy the service, the callback will be called.
EXPECT_CALL(aborted, Run).Times(1);
}
TEST_F(TutorialTest, RegisterTutorialWithCreate) {
std::unique_ptr<TutorialRegistry> registry =
std::make_unique<TutorialRegistry>();
{
auto description = TutorialDescription::Create<kHistogramName1>(
TutorialDescription::BubbleStep(kTestIdentifier1)
.SetBubbleBodyText(IDS_OK));
description.can_be_restarted = true;
EXPECT_TRUE(description.steps.size() == 1);
registry->AddTutorial(kTestTutorial1, std::move(description));
}
std::unique_ptr<HelpBubbleFactoryRegistry> bubble_factory_registry =
std::make_unique<HelpBubbleFactoryRegistry>();
EXPECT_TRUE(registry->IsTutorialRegistered(kTestTutorial1));
}
TEST_F(TutorialTest, RegisterTutorialWithCreateFromVector) {
std::unique_ptr<TutorialRegistry> registry =
std::make_unique<TutorialRegistry>();
{
TutorialDescription::Step first_step =
TutorialDescription::BubbleStep(kTestIdentifier1)
.SetBubbleBodyText(IDS_OK);
std::vector<TutorialDescription::Step> next_steps = {
TutorialDescription::BubbleStep(kTestIdentifier2)
.SetBubbleBodyText(IDS_OK),
TutorialDescription::BubbleStep(kTestIdentifier3)
.SetBubbleBodyText(IDS_OK)};
auto description =
TutorialDescription::Create<kHistogramName1>(first_step, next_steps);
description.can_be_restarted = true;
EXPECT_TRUE(description.steps.size() == 3);
registry->AddTutorial(kTestTutorial1, std::move(description));
}
std::unique_ptr<HelpBubbleFactoryRegistry> bubble_factory_registry =
std::make_unique<HelpBubbleFactoryRegistry>();
EXPECT_TRUE(registry->IsTutorialRegistered(kTestTutorial1));
}
} // namespace user_education