| // 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 "ui/base/interaction/interactive_test.h" |
| |
| #include <functional> |
| #include <list> |
| #include <memory> |
| #include <string> |
| |
| #include "base/functional/callback_forward.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/observer_list.h" |
| #include "base/observer_list_types.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/test/bind.h" |
| #include "base/test/task_environment.h" |
| #include "base/time/time.h" |
| #include "base/types/pass_key.h" |
| #include "build/build_config.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/interaction/interactive_test_internal.h" |
| #include "ui/base/interaction/state_observer.h" |
| |
| #if !BUILDFLAG(IS_IOS) |
| #include "ui/base/accelerators/accelerator.h" |
| #endif |
| |
| namespace ui::test { |
| |
| namespace { |
| |
| enum class ActionType { |
| kPressButton, |
| kSelectMenuItem, |
| kDoDefaultAction, |
| kSelectTab, |
| kSelectDropdownItem, |
| kEnterText, |
| kActivateSurface, |
| kFocusElement, |
| kSendAccelerator, |
| kSendKeyPress, |
| kConfirm |
| }; |
| |
| using ActionRecord = std::tuple<ActionType, |
| ElementIdentifier, |
| ElementContext, |
| InteractionTestUtil::InputType>; |
| |
| constexpr ui::ElementContext kTestContext1 = |
| ui::ElementContext::CreateFakeContextForTesting(1); |
| constexpr ui::ElementContext kTestContext2 = |
| ui::ElementContext::CreateFakeContextForTesting(2); |
| |
| DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestId1); |
| DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestId2); |
| DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestId3); |
| DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestId4); |
| DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(kTestEvent1); |
| DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(kTestEvent2); |
| |
| constexpr char kSetOnIncompatibleActionMessage[] = |
| "Explicitly testing incompatibility-handling."; |
| |
| class TestSimulator : public InteractionTestUtil::Simulator { |
| public: |
| TestSimulator() = default; |
| ~TestSimulator() override = default; |
| |
| void set_result(ActionResult result) { result_ = result; } |
| |
| ActionResult PressButton(TrackedElement* element, |
| InputType input_type) override { |
| DoAction(ActionType::kPressButton, element, input_type); |
| return result_; |
| } |
| |
| ActionResult SelectMenuItem(TrackedElement* element, |
| InputType input_type) override { |
| DoAction(ActionType::kSelectMenuItem, element, input_type); |
| return result_; |
| } |
| |
| ActionResult DoDefaultAction(TrackedElement* element, |
| InputType input_type) override { |
| DoAction(ActionType::kDoDefaultAction, element, input_type); |
| return result_; |
| } |
| |
| ActionResult SelectTab( |
| TrackedElement* tab_collection, |
| size_t index, |
| InputType input_type, |
| std::optional<size_t> expected_index_after_selection) override { |
| DoAction(ActionType::kSelectTab, tab_collection, input_type); |
| return result_; |
| } |
| |
| ActionResult SelectDropdownItem(TrackedElement* collection, |
| size_t item, |
| InputType input_type) override { |
| DoAction(ActionType::kSelectDropdownItem, collection, input_type); |
| return result_; |
| } |
| |
| ActionResult EnterText(TrackedElement* element, |
| std::u16string text, |
| TextEntryMode mode) override { |
| DoAction(ActionType::kEnterText, element, InputType::kKeyboard); |
| return result_; |
| } |
| |
| ActionResult ActivateSurface(TrackedElement* element) override { |
| DoAction(ActionType::kActivateSurface, element, InputType::kMouse); |
| return result_; |
| } |
| |
| ActionResult FocusElement(TrackedElement* element) override { |
| DoAction(ActionType::kFocusElement, element, InputType::kMouse); |
| return result_; |
| } |
| |
| #if !BUILDFLAG(IS_IOS) |
| |
| ActionResult SendAccelerator(TrackedElement* element, |
| Accelerator accel) override { |
| DoAction(ActionType::kSendAccelerator, element, InputType::kKeyboard); |
| return result_; |
| } |
| |
| ActionResult SendKeyPress(TrackedElement* element, |
| KeyboardCode key, |
| int flags) override { |
| DoAction(ActionType::kSendKeyPress, element, InputType::kKeyboard); |
| return result_; |
| } |
| |
| #endif // !BUILDFLAG(IS_IOS) |
| |
| ActionResult Confirm(TrackedElement* element) override { |
| DoAction(ActionType::kConfirm, element, InputType::kDontCare); |
| return result_; |
| } |
| |
| const std::vector<ActionRecord>& records() const { return records_; } |
| |
| private: |
| void DoAction(ActionType action_type, |
| TrackedElement* element, |
| InputType input_type) { |
| records_.emplace_back(action_type, element->identifier(), |
| element->context(), input_type); |
| } |
| |
| ActionResult result_ = ActionResult::kSucceeded; |
| std::vector<ActionRecord> records_; |
| }; |
| |
| void DoFunction() { |
| LOG(INFO) << "In normal function."; |
| } |
| |
| const TrackedElement* CheckElementFunction(const TrackedElement* el) { |
| return el; |
| } |
| |
| int ValueGeneratingFunction() { |
| return 5; |
| } |
| |
| struct CallableObject { |
| bool operator()() const { return i != 0; } |
| int i = 0; |
| }; |
| |
| struct MutableCallableObject { |
| bool operator()() { return i != 0; } |
| int i = 0; |
| }; |
| |
| struct EmptyCallableObject { |
| void operator()() const {} |
| }; |
| |
| } // namespace |
| |
| class InteractiveTestTest : public InteractiveTestMixin<testing::Test> { |
| public: |
| InteractiveTestTest() { |
| auto simulator = std::make_unique<TestSimulator>(); |
| simulator_ = simulator.get(); |
| test_util().AddSimulator(std::move(simulator)); |
| internal::InteractiveTestPrivate::set_interactive_test_verbs_allowed( |
| base::PassKey<InteractiveTestTest>()); |
| } |
| |
| protected: |
| TestSimulator* simulator() { return simulator_.get(); } |
| |
| // Posts the given `actions`, spaced out in time a bit so they don't flood the |
| // test faster than it can process the steps being tested. |
| // |
| // In a real test, each action would be triggered by the step before it, but |
| // since this test suite is testing the low-level primitives, the events must |
| // be simulated *and* cannot be tied to the sequence itself. |
| // |
| // This is in general not a great way to test things, as there is technically |
| // still a race condition. |
| template <typename... C> |
| void QueueActions(C&&... actions) { |
| (queued_actions_.emplace_back( |
| internal::MaybeBind(std::forward<C>(actions))), |
| ...); |
| MaybePostQueuedAction(); |
| } |
| |
| const auto& state_observers() { |
| return private_test_impl().state_observer_elements_; |
| } |
| |
| raw_ptr<TestSimulator> simulator_ = nullptr; |
| |
| private: |
| void MaybePostQueuedAction() { |
| if (queued_actions_.empty()) { |
| return; |
| } |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&InteractiveTestTest::RunQueuedAction, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::Milliseconds(100)); |
| } |
| |
| void RunQueuedAction() { |
| if (queued_actions_.empty()) { |
| return; |
| } |
| std::move(queued_actions_.front()).Run(); |
| queued_actions_.pop_front(); |
| MaybePostQueuedAction(); |
| } |
| |
| std::list<base::OnceClosure> queued_actions_; |
| base::test::SingleThreadTaskEnvironment task_environment_{ |
| base::test::SingleThreadTaskEnvironment::MainThreadType::UI}; |
| base::WeakPtrFactory<InteractiveTestTest> weak_ptr_factory_{this}; |
| }; |
| |
| TEST_F(InteractiveTestTest, StepsConstructsMultiStep) { |
| auto result = |
| Steps(StepBuilder(), Steps(StepBuilder(), StepBuilder()), StepBuilder()); |
| |
| EXPECT_EQ(4U, result.size()); |
| } |
| |
| TEST_F(InteractiveTestTest, RunTestSequenceInContext) { |
| TestElement el(kTestId1, kTestContext1); |
| el.Show(); |
| EXPECT_TRUE(RunTestSequenceInContext(kTestContext1, WaitForShow(kTestId1))); |
| } |
| |
| TEST_F(InteractiveTestTest, WaitInAnyContext) { |
| TestElement e1(kTestId1, kTestContext2); |
| TestElement e2(kTestId2, kTestContext2); |
| |
| QueueActions([&]() { e1.Show(); }, [&]() { e2.Show(); }, |
| [&]() { e2.SendCustomEvent(kTestEvent1); }, |
| [&]() { e1.Hide(); }); |
| |
| RunTestSequenceInContext( |
| kTestContext1, |
| InAnyContext(WaitForShow(kTestId1), WaitForShow(kTestId2), |
| WaitForEvent(kTestId2, kTestEvent1), WaitForHide(kTestId1))); |
| } |
| |
| TEST_F(InteractiveTestTest, FlushInAnyContext) { |
| TestElement e1(kTestId1, kTestContext2); |
| TestElement e2(kTestId2, kTestContext2); |
| e1.Show(); |
| e2.Show(); |
| |
| RunTestSequenceInContext(kTestContext1, InAnyContext(WaitForShow(kTestId1), |
| WaitForShow(kTestId2))); |
| } |
| |
| TEST_F(InteractiveTestTest, InteractionVerbs) { |
| TestElement e1(kTestId1, kTestContext1); |
| TestElement e2(kTestId2, kTestContext1); |
| TestElement e3(kTestId3, kTestContext1); |
| TestElement e4(kTestId4, kTestContext1); |
| e1.Show(); |
| e2.Show(); |
| e3.Show(); |
| e4.Show(); |
| RunTestSequenceInContext( |
| kTestContext1, PressButton(kTestId1, InputType::kDontCare), |
| SelectMenuItem(kTestId2, InputType::kKeyboard), |
| DoDefaultAction(kTestId3, InputType::kMouse), |
| SelectTab(kTestId4, 3U, InputType::kTouch), |
| SelectDropdownItem(kTestId1, 2U, InputType::kDontCare), |
| EnterText(kTestId2, u"The quick brown fox.", TextEntryMode::kAppend), |
| ActivateSurface(kTestId3), FocusElement(kTestId1), |
| #if !BUILDFLAG(IS_IOS) |
| SendAccelerator(kTestId4, Accelerator()), |
| SendKeyPress(kTestId2, KeyboardCode::VKEY_A, EF_NONE), |
| #endif |
| Confirm(kTestId1)); |
| |
| EXPECT_THAT(simulator()->records(), |
| testing::ElementsAre( |
| ActionRecord{ActionType::kPressButton, kTestId1, |
| kTestContext1, InputType::kDontCare}, |
| ActionRecord{ActionType::kSelectMenuItem, kTestId2, |
| kTestContext1, InputType::kKeyboard}, |
| ActionRecord{ActionType::kDoDefaultAction, kTestId3, |
| kTestContext1, InputType::kMouse}, |
| ActionRecord{ActionType::kSelectTab, kTestId4, kTestContext1, |
| InputType::kTouch}, |
| ActionRecord{ActionType::kSelectDropdownItem, kTestId1, |
| kTestContext1, InputType::kDontCare}, |
| ActionRecord{ActionType::kEnterText, kTestId2, kTestContext1, |
| InputType::kKeyboard}, |
| ActionRecord{ActionType::kActivateSurface, kTestId3, |
| kTestContext1, InputType::kMouse}, |
| ActionRecord{ActionType::kFocusElement, kTestId1, |
| kTestContext1, InputType::kMouse}, |
| #if !BUILDFLAG(IS_IOS) |
| ActionRecord{ActionType::kSendAccelerator, kTestId4, |
| kTestContext1, InputType::kKeyboard}, |
| ActionRecord{ActionType::kSendKeyPress, kTestId2, |
| kTestContext1, InputType::kKeyboard}, |
| #endif |
| ActionRecord{ActionType::kConfirm, kTestId1, kTestContext1, |
| InputType::kDontCare})); |
| } |
| |
| TEST_F(InteractiveTestTest, InteractionVerbsInAnyContext) { |
| TestElement e1(kTestId1, kTestContext1); |
| TestElement e2(kTestId2, kTestContext1); |
| TestElement e3(kTestId3, kTestContext1); |
| TestElement e4(kTestId4, kTestContext1); |
| e1.Show(); |
| e2.Show(); |
| e3.Show(); |
| e4.Show(); |
| RunTestSequenceInContext( |
| kTestContext2, InAnyContext(PressButton(kTestId1, InputType::kDontCare)), |
| InAnyContext(SelectMenuItem(kTestId2, InputType::kKeyboard)), |
| InAnyContext(DoDefaultAction(kTestId3, InputType::kMouse)), |
| InAnyContext(SelectTab(kTestId4, 3U, InputType::kTouch), |
| SelectDropdownItem(kTestId1, 2U, InputType::kDontCare), |
| EnterText(kTestId2, u"The quick brown fox."), |
| ActivateSurface(kTestId3), FocusElement(kTestId1), |
| #if !BUILDFLAG(IS_IOS) |
| SendAccelerator(kTestId4, Accelerator()), |
| SendKeyPress(kTestId2, VKEY_A, EF_NONE), |
| #endif |
| Confirm(kTestId1))); |
| |
| EXPECT_THAT(simulator()->records(), |
| testing::ElementsAre( |
| ActionRecord{ActionType::kPressButton, kTestId1, |
| kTestContext1, InputType::kDontCare}, |
| ActionRecord{ActionType::kSelectMenuItem, kTestId2, |
| kTestContext1, InputType::kKeyboard}, |
| ActionRecord{ActionType::kDoDefaultAction, kTestId3, |
| kTestContext1, InputType::kMouse}, |
| ActionRecord{ActionType::kSelectTab, kTestId4, kTestContext1, |
| InputType::kTouch}, |
| ActionRecord{ActionType::kSelectDropdownItem, kTestId1, |
| kTestContext1, InputType::kDontCare}, |
| ActionRecord{ActionType::kEnterText, kTestId2, kTestContext1, |
| InputType::kKeyboard}, |
| ActionRecord{ActionType::kActivateSurface, kTestId3, |
| kTestContext1, InputType::kMouse}, |
| ActionRecord{ActionType::kFocusElement, kTestId1, |
| kTestContext1, InputType::kMouse}, |
| #if !BUILDFLAG(IS_IOS) |
| ActionRecord{ActionType::kSendAccelerator, kTestId4, |
| kTestContext1, InputType::kKeyboard}, |
| ActionRecord{ActionType::kSendKeyPress, kTestId2, |
| kTestContext1, InputType::kKeyboard}, |
| #endif |
| ActionRecord{ActionType::kConfirm, kTestId1, kTestContext1, |
| InputType::kDontCare})); |
| } |
| |
| TEST_F(InteractiveTestTest, InteractionVerbsInSameContext) { |
| TestElement e1(kTestId1, kTestContext1); |
| TestElement e2(kTestId2, kTestContext1); |
| TestElement e3(kTestId3, kTestContext1); |
| TestElement e4(kTestId4, kTestContext1); |
| e1.Show(); |
| e2.Show(); |
| e3.Show(); |
| e4.Show(); |
| RunTestSequenceInContext( |
| kTestContext2, InAnyContext(PressButton(kTestId1, InputType::kDontCare)), |
| InSameContext(SelectMenuItem(kTestId2, InputType::kKeyboard)), |
| InSameContext(DoDefaultAction(kTestId3, InputType::kMouse)), |
| InSameContext(SelectTab(kTestId4, 3U, InputType::kTouch), |
| SelectDropdownItem(kTestId1, 2U, InputType::kDontCare), |
| EnterText(kTestId2, u"The quick brown fox."), |
| ActivateSurface(kTestId3), |
| #if !BUILDFLAG(IS_IOS) |
| SendAccelerator(kTestId4, Accelerator()), |
| #endif |
| Confirm(kTestId1))); |
| |
| EXPECT_THAT(simulator()->records(), |
| testing::ElementsAre( |
| ActionRecord{ActionType::kPressButton, kTestId1, |
| kTestContext1, InputType::kDontCare}, |
| ActionRecord{ActionType::kSelectMenuItem, kTestId2, |
| kTestContext1, InputType::kKeyboard}, |
| ActionRecord{ActionType::kDoDefaultAction, kTestId3, |
| kTestContext1, InputType::kMouse}, |
| ActionRecord{ActionType::kSelectTab, kTestId4, kTestContext1, |
| InputType::kTouch}, |
| ActionRecord{ActionType::kSelectDropdownItem, kTestId1, |
| kTestContext1, InputType::kDontCare}, |
| ActionRecord{ActionType::kEnterText, kTestId2, kTestContext1, |
| InputType::kKeyboard}, |
| ActionRecord{ActionType::kActivateSurface, kTestId3, |
| kTestContext1, InputType::kMouse}, |
| #if !BUILDFLAG(IS_IOS) |
| ActionRecord{ActionType::kSendAccelerator, kTestId4, |
| kTestContext1, InputType::kKeyboard}, |
| #endif |
| ActionRecord{ActionType::kConfirm, kTestId1, kTestContext1, |
| InputType::kDontCare})); |
| } |
| |
| TEST_F(InteractiveTestTest, InteractionVerbsInSameContextAs) { |
| constexpr char kElementName[] = "name"; |
| TestElement e1(kTestId1, kTestContext1); |
| TestElement e2(kTestId2, kTestContext1); |
| TestElement e3(kTestId3, kTestContext1); |
| TestElement e4(kTestId4, kTestContext1); |
| e1.Show(); |
| e2.Show(); |
| e3.Show(); |
| e4.Show(); |
| RunTestSequenceInContext( |
| kTestContext2, |
| // Name element 1. |
| InAnyContext(NameElementRelative( |
| kTestId1, kElementName, [](ui::TrackedElement* el) { return el; })), |
| // Use the named element to find an element. |
| InSameContextAs(kElementName, |
| SelectMenuItem(kTestId2, InputType::kKeyboard)), |
| // Use the element ID instead as it is unique. |
| InSameContextAs(kTestId1, DoDefaultAction(kTestId3, InputType::kMouse)), |
| // Ensure that we handle groups of steps with a named element as well. |
| InSameContextAs( |
| kElementName, |
| Steps(SelectTab(kTestId4, 3U, InputType::kTouch), |
| SelectDropdownItem(kTestId1, 2U, InputType::kDontCare), |
| EnterText(kTestId2, u"The quick brown fox."))), |
| // Ensure that we handle groups of steps with a unique element ID. |
| InSameContextAs(kTestId1, Steps(ActivateSurface(kTestId3), |
| #if !BUILDFLAG(IS_IOS) |
| SendAccelerator(kTestId4, Accelerator()), |
| #endif |
| Confirm(kTestId1)))); |
| |
| EXPECT_THAT(simulator()->records(), |
| testing::ElementsAre( |
| ActionRecord{ActionType::kSelectMenuItem, kTestId2, |
| kTestContext1, InputType::kKeyboard}, |
| ActionRecord{ActionType::kDoDefaultAction, kTestId3, |
| kTestContext1, InputType::kMouse}, |
| ActionRecord{ActionType::kSelectTab, kTestId4, kTestContext1, |
| InputType::kTouch}, |
| ActionRecord{ActionType::kSelectDropdownItem, kTestId1, |
| kTestContext1, InputType::kDontCare}, |
| ActionRecord{ActionType::kEnterText, kTestId2, kTestContext1, |
| InputType::kKeyboard}, |
| ActionRecord{ActionType::kActivateSurface, kTestId3, |
| kTestContext1, InputType::kMouse}, |
| #if !BUILDFLAG(IS_IOS) |
| ActionRecord{ActionType::kSendAccelerator, kTestId4, |
| kTestContext1, InputType::kKeyboard}, |
| #endif |
| ActionRecord{ActionType::kConfirm, kTestId1, kTestContext1, |
| InputType::kDontCare})); |
| } |
| |
| TEST_F(InteractiveTestTest, Do) { |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, f); |
| EXPECT_CALL_IN_SCOPE(f, Run, |
| RunTestSequenceInContext(kTestContext1, Do(f.Get()))); |
| } |
| |
| TEST_F(InteractiveTestTest, Check) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<bool()>, check); |
| |
| EXPECT_CALL(check, Run).WillOnce([]() { return true; }); |
| RunTestSequenceInContext(kTestContext1, Check(check.Get())); |
| } |
| |
| TEST_F(InteractiveTestTest, CheckFails) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<bool()>, check); |
| |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| |
| EXPECT_CALL(check, Run).WillOnce([]() { return false; }); |
| EXPECT_CALL_IN_SCOPE(aborted, Run, { |
| EXPECT_FALSE(RunTestSequenceInContext(kTestContext1, Check(check.Get()))); |
| }); |
| } |
| |
| TEST_F(InteractiveTestTest, CheckResult) { |
| UNCALLED_MOCK_CALLBACK(base::RepeatingCallback<int()>, f); |
| EXPECT_CALL(f, Run).WillOnce([]() { return 2; }).WillOnce([]() { return 3; }); |
| |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<std::string()>, f2); |
| const char kString[] = "a string"; |
| EXPECT_CALL(f2, Run).WillOnce([=]() { return std::string(kString); }); |
| |
| RunTestSequenceInContext(kTestContext1, CheckResult(f.Get(), 2), |
| CheckResult(f.Get(), testing::Gt(2)), |
| CheckResult(f2.Get(), kString)); |
| } |
| |
| TEST_F(InteractiveTestTest, CheckResultFails) { |
| UNCALLED_MOCK_CALLBACK(base::RepeatingCallback<int()>, f); |
| EXPECT_CALL(f, Run).WillOnce([]() { return 2; }); |
| |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| |
| EXPECT_CALL_IN_SCOPE(aborted, Run, { |
| EXPECT_FALSE( |
| RunTestSequenceInContext(kTestContext1, CheckResult(f.Get(), 3))); |
| }); |
| } |
| |
| TEST_F(InteractiveTestTest, CheckVariable) { |
| int x = 0; |
| const int y = 0; |
| const char* text = "foo"; |
| constexpr char kNewValue[] = "bar"; |
| |
| RunTestSequenceInContext(kTestContext1, CheckVariable(y, 0), Do([&]() { |
| x = 1; |
| text = kNewValue; |
| }), |
| CheckVariable(x, testing::Gt(0)), |
| CheckVariable(text, kNewValue)); |
| } |
| |
| TEST_F(InteractiveTestTest, CheckVariableFails) { |
| int x = 0; |
| |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| |
| EXPECT_CALL_IN_SCOPE(aborted, Run, { |
| EXPECT_FALSE(RunTestSequenceInContext(kTestContext1, Do([&]() { x = 1; }), |
| CheckVariable(x, 0))); |
| }); |
| } |
| |
| TEST_F(InteractiveTestTest, CheckElement) { |
| TestElement e1(kTestId1, kTestContext1); |
| TestElement e2(kTestId2, kTestContext1); |
| |
| UNCALLED_MOCK_CALLBACK(base::RepeatingCallback<bool(TrackedElement * el)>, |
| cb1); |
| EXPECT_CALL(cb1, Run).WillRepeatedly( |
| [&e1](TrackedElement* el) { return el == &e1; }); |
| |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<bool(TrackedElement * el)>, cb2); |
| EXPECT_CALL(cb2, Run).WillOnce( |
| [&e2](TrackedElement* el) { return el == &e2; }); |
| |
| e1.Show(); |
| e2.Show(); |
| |
| RunTestSequenceInContext(kTestContext1, CheckElement(kTestId1, cb1.Get()), |
| CheckElement(kTestId2, cb2.Get()), |
| CheckElement(kTestId1, cb1.Get())); |
| } |
| |
| TEST_F(InteractiveTestTest, CheckElementFails) { |
| TestElement e1(kTestId1, kTestContext1); |
| |
| UNCALLED_MOCK_CALLBACK(base::RepeatingCallback<bool(TrackedElement * el)>, |
| cb1); |
| EXPECT_CALL(cb1, Run).WillRepeatedly( |
| [&e1](TrackedElement* el) { return el != &e1; }); |
| |
| e1.Show(); |
| |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| |
| EXPECT_CALL_IN_SCOPE(aborted, Run, { |
| EXPECT_FALSE(RunTestSequenceInContext(kTestContext1, |
| CheckElement(kTestId1, cb1.Get()))); |
| }); |
| } |
| |
| TEST_F(InteractiveTestTest, CheckElementWithMatcher) { |
| TestElement e1(kTestId1, kTestContext1); |
| TestElement e2(kTestId2, kTestContext1); |
| |
| UNCALLED_MOCK_CALLBACK( |
| base::RepeatingCallback<TrackedElement*(TrackedElement * el)>, cb1); |
| EXPECT_CALL(cb1, Run).WillRepeatedly([](TrackedElement* el) { return el; }); |
| |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<int(TrackedElement * el)>, cb2); |
| EXPECT_CALL(cb2, Run).WillOnce( |
| [&e1](TrackedElement* el) { return el == &e1 ? 1 : 2; }); |
| |
| e1.Show(); |
| e2.Show(); |
| |
| RunTestSequenceInContext(kTestContext1, |
| // Implicitly create testing::Eq from value. |
| CheckElement(kTestId1, cb1.Get(), &e1), |
| // Test explicit matchers. |
| CheckElement(kTestId2, cb2.Get(), testing::Gt(1)), |
| CheckElement(kTestId2, cb1.Get(), testing::Ne(&e1))); |
| } |
| |
| TEST_F(InteractiveTestTest, CheckElementWithMatcherFails) { |
| TestElement e1(kTestId1, kTestContext1); |
| |
| UNCALLED_MOCK_CALLBACK(base::RepeatingCallback<int(TrackedElement * el)>, |
| cb1); |
| EXPECT_CALL(cb1, Run).WillRepeatedly( |
| [&e1](TrackedElement* el) { return el == &e1 ? 1 : 2; }); |
| |
| e1.Show(); |
| |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| |
| EXPECT_CALL_IN_SCOPE(aborted, Run, { |
| EXPECT_FALSE(RunTestSequenceInContext( |
| kTestContext1, CheckElement(kTestId1, cb1.Get(), 2))); |
| }); |
| } |
| |
| TEST_F(InteractiveTestTest, After) { |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, cb1); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, cb2); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, cb3); |
| TestElement el(kTestId1, kTestContext1); |
| |
| testing::InSequence in_sequence; |
| EXPECT_CALL(cb1, Run); |
| EXPECT_CALL(cb2, Run); |
| EXPECT_CALL(cb3, Run); |
| |
| QueueActions([&]() { el.Show(); }, [&]() { el.SendCustomEvent(kTestEvent1); }, |
| [&]() { el.SendCustomEvent(kTestEvent2); }, |
| [&]() { el.Hide(); }); |
| |
| RunTestSequenceInContext(kTestContext1, AfterShow(kTestId1, cb1.Get()), |
| AfterEvent(kTestId1, kTestEvent2, cb2.Get()), |
| AfterHide(kTestId1, cb3.Get())); |
| } |
| |
| TEST_F(InteractiveTestTest, WaitFor) { |
| TestElement e1(kTestId1, kTestContext1); |
| TestElement e2(kTestId2, kTestContext1); |
| |
| QueueActions( |
| // Already in step 1, this triggers step 2. |
| [&]() { e2.Show(); }, |
| // Hide before moving to step 3. |
| [&]() { e1.Hide(); }, |
| // This should transition both 3 and 4. |
| [&]() { e2.SendCustomEvent(kTestEvent1); }, |
| // This should transition step 5. |
| [&]() { e2.Hide(); }); |
| |
| e1.Show(); |
| |
| RunTestSequenceInContext( |
| kTestContext1, WaitForShow(kTestId1), |
| WaitForShow(kTestId2, /* transition_only_on_event =*/true), |
| WaitForEvent(kTestId2, kTestEvent1), WaitForHide(kTestId1), |
| WaitForHide(kTestId2, /* transition_only_on_event =*/true)); |
| } |
| |
| TEST_F(InteractiveTestTest, PresentOrNotPresentInAnyContext) { |
| TestElement e1(kTestId1, kTestContext1); |
| TestElement e2(kTestId2, kTestContext2); |
| e1.Show(); |
| e2.Show(); |
| |
| RunTestSequenceInContext(kTestContext1, EnsurePresent(kTestId1), |
| // Not present in the current context. |
| EnsureNotPresent(kTestId2), |
| InAnyContext(EnsureNotPresent(kTestId3)), |
| InAnyContext(EnsurePresent(kTestId2))); |
| } |
| |
| TEST_F(InteractiveTestTest, WithElementFails) { |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, callback); |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| |
| EXPECT_CALL_IN_SCOPE(aborted, Run, { |
| EXPECT_FALSE(RunTestSequenceInContext( |
| kTestContext1, WithElement(kTestId1, callback.Get()))); |
| }); |
| } |
| |
| TEST_F(InteractiveTestTest, EnsureNotPresentFails) { |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| |
| EXPECT_CALL_IN_SCOPE(aborted, Run, { |
| EXPECT_FALSE( |
| RunTestSequenceInContext(kTestContext1, EnsureNotPresent(kTestId1))); |
| }); |
| } |
| |
| TEST_F(InteractiveTestTest, EnsureNotPresentInAnyContextFails) { |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| |
| EXPECT_CALL_IN_SCOPE(aborted, Run, { |
| EXPECT_FALSE(RunTestSequenceInContext( |
| kTestContext2, InAnyContext(EnsureNotPresent(kTestId1)))); |
| }); |
| } |
| |
| TEST_F(InteractiveTestTest, EnsurePresentFails) { |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| EXPECT_CALL_IN_SCOPE(aborted, Run, { |
| EXPECT_FALSE(RunTestSequenceInContext( |
| kTestContext2, InAnyContext(EnsurePresent(kTestId2)))); |
| }); |
| } |
| |
| TEST_F(InteractiveTestTest, NameElementWithPointer) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<void(TrackedElement*)>, cb); |
| |
| TestElement el(kTestId1, kTestContext1); |
| el.Show(); |
| constexpr char kName[] = "name"; |
| |
| EXPECT_CALL_IN_SCOPE( |
| cb, Run(&el), |
| RunTestSequenceInContext(kTestContext1, NameElement(kName, &el), |
| WithElement(kName, cb.Get()))); |
| } |
| |
| TEST_F(InteractiveTestTest, NameElementWithReference) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<void(TrackedElement*)>, cb); |
| |
| TestElement el(kTestId1, kTestContext1); |
| el.Show(); |
| constexpr char kName[] = "name"; |
| |
| TrackedElement* ptr = nullptr; |
| |
| EXPECT_CALL_IN_SCOPE( |
| cb, Run(&el), |
| RunTestSequenceInContext(kTestContext1, Do([&]() { ptr = ⪙ }), |
| NameElement(kName, std::ref(ptr)), |
| WithElement(kName, cb.Get()))); |
| } |
| |
| TEST_F(InteractiveTestTest, NameElementWithCallback) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<void(TrackedElement*)>, cb); |
| |
| TestElement el(kTestId1, kTestContext1); |
| el.Show(); |
| constexpr char kName[] = "name"; |
| |
| EXPECT_CALL_IN_SCOPE( |
| cb, Run(&el), |
| RunTestSequenceInContext( |
| kTestContext1, |
| NameElement(kName, base::BindLambdaForTesting( |
| [&]() -> TrackedElement* { return ⪙ })), |
| WithElement(kName, cb.Get()))); |
| } |
| |
| TEST_F(InteractiveTestTest, NameElementWithContext) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<void(TrackedElement*)>, cb); |
| |
| TestElement el(kTestId1, kTestContext2); |
| el.Show(); |
| constexpr char kName[] = "name"; |
| |
| EXPECT_CALL_IN_SCOPE( |
| cb, Run(&el), |
| RunTestSequenceInContext( |
| kTestContext1, |
| InContext(kTestContext2, |
| NameElement( |
| kName, |
| base::BindLambdaForTesting([&](ElementContext context) { |
| return ElementTracker::GetElementTracker() |
| ->GetUniqueElement(kTestId1, context); |
| }))), |
| WithElement(kName, cb.Get()))); |
| } |
| |
| TEST_F(InteractiveTestTest, SimulatorSucceeds_SkipOnUnsupported) { |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| RunTestSequenceInContext( |
| kTestContext1, |
| SetOnIncompatibleAction(OnIncompatibleAction::kSkipTest, |
| kSetOnIncompatibleActionMessage), |
| PressButton(kTestId1)); |
| EXPECT_FALSE(private_test_impl().sequence_skipped()); |
| } |
| |
| TEST_F(InteractiveTestTest, SimulatorSucceeds_IgnoreOnUnsupported) { |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| RunTestSequenceInContext( |
| kTestContext1, |
| SetOnIncompatibleAction(OnIncompatibleAction::kIgnoreAndContinue, |
| kSetOnIncompatibleActionMessage), |
| PressButton(kTestId1)); |
| EXPECT_FALSE(private_test_impl().sequence_skipped()); |
| } |
| |
| TEST_F(InteractiveTestTest, SimulatorFailureFails) { |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| simulator_->set_result(ActionResult::kFailed); |
| EXPECT_CALL_IN_SCOPE( |
| aborted, Run, |
| RunTestSequenceInContext(kTestContext1, PressButton(kTestId1))); |
| EXPECT_FALSE(private_test_impl().sequence_skipped()); |
| } |
| |
| TEST_F(InteractiveTestTest, SimulatorFailureFails_SkipOnUnsupported) { |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| simulator_->set_result(ActionResult::kFailed); |
| EXPECT_CALL_IN_SCOPE( |
| aborted, Run, |
| RunTestSequenceInContext( |
| kTestContext1, |
| SetOnIncompatibleAction(OnIncompatibleAction::kSkipTest, |
| kSetOnIncompatibleActionMessage), |
| PressButton(kTestId1))); |
| EXPECT_FALSE(private_test_impl().sequence_skipped()); |
| } |
| |
| TEST_F(InteractiveTestTest, SimulatorFailureFails_IgnoreOnUnsupported) { |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| simulator_->set_result(ActionResult::kFailed); |
| EXPECT_CALL_IN_SCOPE( |
| aborted, Run, |
| RunTestSequenceInContext( |
| kTestContext1, |
| SetOnIncompatibleAction(OnIncompatibleAction::kIgnoreAndContinue, |
| kSetOnIncompatibleActionMessage), |
| PressButton(kTestId1))); |
| EXPECT_FALSE(private_test_impl().sequence_skipped()); |
| } |
| |
| TEST_F(InteractiveTestTest, SimulatorCannotSimulateFails) { |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| simulator_->set_result(ActionResult::kNotAttempted); |
| EXPECT_CALL_IN_SCOPE( |
| aborted, Run, |
| RunTestSequenceInContext(kTestContext1, PressButton(kTestId1))); |
| EXPECT_FALSE(private_test_impl().sequence_skipped()); |
| } |
| |
| TEST_F(InteractiveTestTest, SimulatorCannotSimulateFails_SkipOnUnsupported) { |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| simulator_->set_result(ActionResult::kNotAttempted); |
| EXPECT_CALL_IN_SCOPE( |
| aborted, Run, |
| RunTestSequenceInContext( |
| kTestContext1, |
| SetOnIncompatibleAction(OnIncompatibleAction::kSkipTest, |
| kSetOnIncompatibleActionMessage), |
| PressButton(kTestId1))); |
| EXPECT_FALSE(private_test_impl().sequence_skipped()); |
| } |
| |
| TEST_F(InteractiveTestTest, SimulatorCannotSimulateFails_IgnoreOnUnsupported) { |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| simulator_->set_result(ActionResult::kNotAttempted); |
| EXPECT_CALL_IN_SCOPE( |
| aborted, Run, |
| RunTestSequenceInContext( |
| kTestContext1, |
| SetOnIncompatibleAction(OnIncompatibleAction::kIgnoreAndContinue, |
| kSetOnIncompatibleActionMessage), |
| PressButton(kTestId1))); |
| EXPECT_FALSE(private_test_impl().sequence_skipped()); |
| } |
| |
| TEST_F(InteractiveTestTest, SimulatorNotSupportedFails) { |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| simulator_->set_result(ActionResult::kKnownIncompatible); |
| EXPECT_CALL_IN_SCOPE( |
| aborted, Run, |
| RunTestSequenceInContext(kTestContext1, PressButton(kTestId1))); |
| EXPECT_FALSE(private_test_impl().sequence_skipped()); |
| } |
| |
| TEST_F(InteractiveTestTest, SimulatorNotSupportedSkipsOnUnsupported) { |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| simulator_->set_result(ActionResult::kKnownIncompatible); |
| EXPECT_CALL_IN_SCOPE( |
| aborted, Run, |
| RunTestSequenceInContext( |
| kTestContext1, |
| SetOnIncompatibleAction(OnIncompatibleAction::kSkipTest, |
| kSetOnIncompatibleActionMessage), |
| PressButton(kTestId1))); |
| EXPECT_TRUE(private_test_impl().sequence_skipped()); |
| } |
| |
| TEST_F(InteractiveTestTest, SimulatorNotSupportedContinuesOnUnsupported) { |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| bool result = false; |
| |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| simulator_->set_result(ActionResult::kKnownIncompatible); |
| RunTestSequenceInContext( |
| kTestContext1, |
| SetOnIncompatibleAction(OnIncompatibleAction::kIgnoreAndContinue, |
| kSetOnIncompatibleActionMessage), |
| PressButton(kTestId1), Do([&result]() { result = true; })); |
| EXPECT_TRUE(result); |
| } |
| |
| TEST_F(InteractiveTestTest, CanChangeOnIncompatibleAction) { |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| simulator_->set_result(ActionResult::kKnownIncompatible); |
| EXPECT_CALL_IN_SCOPE( |
| aborted, Run, |
| RunTestSequenceInContext( |
| kTestContext1, |
| // Based on previous tests, this will fall through to the next step. |
| SetOnIncompatibleAction(OnIncompatibleAction::kIgnoreAndContinue, |
| kSetOnIncompatibleActionMessage), |
| PressButton(kTestId1), |
| // By changing the incompatible mode, the step after this one should |
| // fail. |
| SetOnIncompatibleAction(OnIncompatibleAction::kFailTest, ""), |
| PressButton(kTestId1))); |
| } |
| |
| TEST_F(InteractiveTestTest, SimulatorNotSupportedHaltAndSucceedOnUnsupported) { |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| bool result = false; |
| |
| simulator_->set_result(ActionResult::kKnownIncompatible); |
| RunTestSequenceInContext( |
| kTestContext1, |
| SetOnIncompatibleAction(OnIncompatibleAction::kHaltTest, |
| kSetOnIncompatibleActionMessage), |
| PressButton(kTestId1), Do([&result]() { result = true; })); |
| EXPECT_FALSE(result); |
| } |
| |
| TEST_F(InteractiveTestTest, ActuallySkipsTestOnSimulatorFailure) { |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| simulator_->set_result(ActionResult::kKnownIncompatible); |
| RunTestSequenceInContext( |
| kTestContext1, |
| SetOnIncompatibleAction(OnIncompatibleAction::kSkipTest, |
| kSetOnIncompatibleActionMessage), |
| PressButton(kTestId1)); |
| |
| // Note: this test will either be marked as skipped or failed, but never |
| // succeeded. The important thing is that it does not fail. |
| if (!testing::Test::IsSkipped()) { |
| GTEST_FAIL(); |
| } |
| } |
| |
| TEST_F(InteractiveTestTest, IfTrue) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<bool(void)>, condition); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, step); |
| |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| EXPECT_CALL(condition, Run).WillOnce(testing::Return(true)); |
| EXPECT_CALL(step, Run); |
| RunTestSequenceInContext(e1.context(), |
| If(condition.Get(), Then(Do(step.Get())))); |
| } |
| |
| TEST_F(InteractiveTestTest, IfFalse) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<bool(void)>, condition); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, step); |
| |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| EXPECT_CALL(condition, Run).WillOnce(testing::Return(false)); |
| RunTestSequenceInContext(e1.context(), |
| If(condition.Get(), Then(Do(step.Get())))); |
| } |
| |
| TEST_F(InteractiveTestTest, IfMatcherTrue) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<int(void)>, condition); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, step); |
| |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| EXPECT_CALL(condition, Run).WillOnce(testing::Return(1)); |
| EXPECT_CALL(step, Run); |
| RunTestSequenceInContext( |
| e1.context(), |
| IfMatches(condition.Get(), testing::Eq(1), Then(Do(step.Get())))); |
| } |
| |
| TEST_F(InteractiveTestTest, IfMatcherFalse) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<int(void)>, condition); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, step); |
| |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| EXPECT_CALL(condition, Run).WillOnce(testing::Return(0)); |
| RunTestSequenceInContext( |
| e1.context(), |
| IfMatches(condition.Get(), testing::Eq(1), Then(Do(step.Get())))); |
| } |
| |
| TEST_F(InteractiveTestTest, IfImplicitMatcherTrue) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<int(void)>, condition); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, step); |
| |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| EXPECT_CALL(condition, Run).WillOnce(testing::Return(1)); |
| EXPECT_CALL(step, Run); |
| RunTestSequenceInContext(e1.context(), |
| IfMatches(condition.Get(), 1, Then(Do(step.Get())))); |
| } |
| |
| TEST_F(InteractiveTestTest, IfImplicitMatcherFalse) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<int(void)>, condition); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, step); |
| |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| EXPECT_CALL(condition, Run).WillOnce(testing::Return(0)); |
| RunTestSequenceInContext(e1.context(), |
| IfMatches(condition.Get(), 1, Then(Do(step.Get())))); |
| } |
| |
| TEST_F(InteractiveTestTest, IfWithMultiStep) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<bool(void)>, condition); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, step1); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, step2); |
| |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| EXPECT_CALL(condition, Run).WillOnce(testing::Return(true)); |
| EXPECT_CALL(step1, Run); |
| EXPECT_CALL(step2, Run); |
| RunTestSequenceInContext( |
| e1.context(), |
| If(condition.Get(), Then(Do(step1.Get()), Do(step2.Get())))); |
| } |
| |
| TEST_F(InteractiveTestTest, IfElementTrue) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<bool(const TrackedElement*)>, |
| condition); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, step); |
| |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| EXPECT_CALL(condition, Run(&e1)).WillOnce(testing::Return(true)); |
| EXPECT_CALL(step, Run); |
| RunTestSequenceInContext( |
| e1.context(), |
| IfElement(e1.identifier(), condition.Get(), Then(Do(step.Get())))); |
| } |
| |
| TEST_F(InteractiveTestTest, IfElementFalse) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<bool(const TrackedElement*)>, |
| condition); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, step); |
| |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| EXPECT_CALL(condition, Run(&e1)).WillOnce(testing::Return(false)); |
| RunTestSequenceInContext( |
| e1.context(), |
| IfElement(e1.identifier(), condition.Get(), Then(Do(step.Get())))); |
| } |
| |
| TEST_F(InteractiveTestTest, IfElementMatchesTrue) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<std::string(const TrackedElement*)>, |
| condition); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, step); |
| |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| EXPECT_CALL(condition, Run(&e1)) |
| .WillOnce(testing::Return(std::string("foo"))); |
| EXPECT_CALL(step, Run); |
| RunTestSequenceInContext( |
| e1.context(), IfElementMatches(e1.identifier(), condition.Get(), "foo", |
| Then(Do(step.Get())))); |
| } |
| |
| TEST_F(InteractiveTestTest, IfElementMatchesFalse) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<std::string(const TrackedElement*)>, |
| condition); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, step); |
| |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| EXPECT_CALL(condition, Run(&e1)) |
| .WillOnce(testing::Return(std::string("bar"))); |
| RunTestSequenceInContext( |
| e1.context(), IfElementMatches(e1.identifier(), condition.Get(), "foo", |
| Then(Do(step.Get())))); |
| } |
| |
| TEST_F(InteractiveTestTest, IfElementWithMultiStep) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<bool(const TrackedElement*)>, |
| condition); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, step1); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, step2); |
| |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| EXPECT_CALL(condition, Run(&e1)).WillOnce(testing::Return(true)); |
| EXPECT_CALL(step1, Run); |
| EXPECT_CALL(step2, Run); |
| RunTestSequenceInContext(e1.context(), |
| IfElement(e1.identifier(), condition.Get(), |
| Then(Do(step1.Get()), Do(step2.Get())))); |
| } |
| |
| TEST_F(InteractiveTestTest, IfFails) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<bool(void)>, condition); |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| EXPECT_CALL(condition, Run).WillOnce(testing::Return(true)); |
| EXPECT_CALL(aborted, Run); |
| RunTestSequenceInContext( |
| e1.context(), |
| If(condition.Get(), Then(Check(base::BindOnce([]() { return false; }))))); |
| } |
| |
| TEST_F(InteractiveTestTest, IfThenElse_OnlyRunsThen) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<bool(void)>, condition); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, a); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, b); |
| |
| EXPECT_CALL(condition, Run).WillOnce(testing::Return(true)); |
| EXPECT_CALL(a, Run); |
| RunTestSequenceInContext( |
| kTestContext1, If(condition.Get(), Then(Do(a.Get())), Else(Do(b.Get())))); |
| } |
| |
| TEST_F(InteractiveTestTest, IfThenElse_OnlyRunsElse) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<bool(void)>, condition); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, a); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, b); |
| |
| EXPECT_CALL(condition, Run).WillOnce(testing::Return(false)); |
| EXPECT_CALL(b, Run); |
| RunTestSequenceInContext( |
| kTestContext1, If(condition.Get(), Then(Do(a.Get())), Else(Do(b.Get())))); |
| } |
| |
| TEST_F(InteractiveTestTest, IfThenElse_ThenFails) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<bool(void)>, condition); |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| |
| EXPECT_CALL(condition, Run).WillOnce(testing::Return(true)); |
| EXPECT_CALL(aborted, Run); |
| RunTestSequenceInContext( |
| kTestContext1, |
| If(condition.Get(), Then(Check(base::BindOnce([]() { return false; }))), |
| Else(Do(base::BindOnce([]() {}))))); |
| } |
| |
| TEST_F(InteractiveTestTest, IfThenElse_ElseFails) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<bool(void)>, condition); |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| |
| EXPECT_CALL(condition, Run).WillOnce(testing::Return(false)); |
| EXPECT_CALL(aborted, Run); |
| RunTestSequenceInContext( |
| kTestContext1, If(condition.Get(), Then(Do(base::BindOnce([]() {}))), |
| Else(Check(base::BindOnce([]() { return false; }))))); |
| } |
| |
| TEST_F(InteractiveTestTest, InParallel) { |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, seq1); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, seq2); |
| |
| EXPECT_CALL(seq1, Run); |
| EXPECT_CALL(seq2, Run); |
| RunTestSequenceInContext(kTestContext1, |
| InParallel(RunSubsequence(Do(seq1.Get())), |
| RunSubsequence(Do(seq2.Get())))); |
| } |
| |
| TEST_F(InteractiveTestTest, InParallelMultiStep) { |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, seq11); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, seq12); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, seq21); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, seq22); |
| |
| EXPECT_CALL(seq11, Run); |
| EXPECT_CALL(seq12, Run); |
| EXPECT_CALL(seq21, Run); |
| EXPECT_CALL(seq22, Run); |
| RunTestSequenceInContext( |
| kTestContext1, |
| InParallel(RunSubsequence(Do(seq11.Get()), Do(seq12.Get())), |
| RunSubsequence(Do(seq21.Get()), Do(seq22.Get())))); |
| } |
| |
| TEST_F(InteractiveTestTest, InParallelAsync) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<void(ui::TrackedElement*)>, seq1); |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<void(ui::TrackedElement*)>, seq2); |
| |
| TestElement e1(kTestId1, kTestContext1); |
| TestElement e2(kTestId2, kTestContext1); |
| |
| QueueActions([&e1]() { e1.Show(); }, [&e2]() { e2.Show(); }); |
| EXPECT_CALL(seq1, Run(&e1)); |
| EXPECT_CALL(seq2, Run(&e2)); |
| RunTestSequenceInContext( |
| kTestContext1, |
| InParallel(RunSubsequence(AfterShow(e1.identifier(), seq1.Get())), |
| RunSubsequence(AfterShow(e2.identifier(), seq2.Get())))); |
| } |
| |
| // Parallel sequences where one sequence triggers a step in another. |
| TEST_F(InteractiveTestTest, InParallelDependent) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<void(ui::TrackedElement*)>, seq1); |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<void(ui::TrackedElement*)>, seq2); |
| |
| TestElement e1(kTestId1, kTestContext1); |
| TestElement e2(kTestId2, kTestContext1); |
| |
| QueueActions([&e1]() { e1.Show(); }); |
| EXPECT_CALL(seq1, Run(&e1)).WillOnce([&e2](TrackedElement*) { e2.Show(); }); |
| EXPECT_CALL(seq2, Run(&e2)); |
| RunTestSequenceInContext( |
| kTestContext1, |
| InParallel(RunSubsequence(AfterShow(e1.identifier(), seq1.Get())), |
| RunSubsequence(AfterShow(e2.identifier(), seq2.Get())))); |
| } |
| |
| // Parallel sequences where one sequence triggers a step in another, which then |
| // triggers the final step in the first subsequence. |
| TEST_F(InteractiveTestTest, InParallelPingPong) { |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<void(ui::TrackedElement*)>, seq1); |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<void(ui::TrackedElement*)>, seq2); |
| UNCALLED_MOCK_CALLBACK(base::OnceCallback<void(ui::TrackedElement*)>, seq3); |
| |
| TestElement e1(kTestId1, kTestContext1); |
| TestElement e2(kTestId2, kTestContext1); |
| |
| QueueActions([&e1]() { e1.Show(); }); |
| EXPECT_CALL(seq1, Run(&e1)).WillOnce([&e2](TrackedElement*) { e2.Show(); }); |
| EXPECT_CALL(seq2, Run(&e2)).WillOnce([&e1](TrackedElement*) { |
| e1.SendCustomEvent(kTestEvent1); |
| }); |
| EXPECT_CALL(seq3, Run(&e1)); |
| RunTestSequenceInContext( |
| kTestContext1, |
| InParallel( |
| RunSubsequence(AfterShow(e1.identifier(), seq1.Get()), |
| AfterEvent(e1.identifier(), kTestEvent1, seq3.Get())), |
| RunSubsequence(AfterShow(e2.identifier(), seq2.Get())))); |
| } |
| |
| TEST_F(InteractiveTestTest, InParallelFails) { |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| EXPECT_CALL(aborted, Run); |
| RunTestSequenceInContext( |
| e1.context(), |
| InParallel( |
| RunSubsequence(Do(base::DoNothing())), |
| RunSubsequence(Check(base::BindOnce([]() { return false; }))))); |
| } |
| |
| TEST_F(InteractiveTestTest, AnyOf) { |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, seq1); |
| |
| EXPECT_CALL(seq1, Run).Times(1); |
| RunTestSequenceInContext( |
| kTestContext1, |
| AnyOf(RunSubsequence(Do(seq1.Get())), RunSubsequence(Do(seq1.Get())))); |
| } |
| |
| TEST_F(InteractiveTestTest, AnyOfOneFailsOneSucceeds) { |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, seq1); |
| |
| EXPECT_CALL(seq1, Run).Times(1); |
| RunTestSequenceInContext( |
| kTestContext1, |
| AnyOf(RunSubsequence(Check(base::BindOnce([]() { return false; }))), |
| RunSubsequence(Do(seq1.Get())))); |
| } |
| |
| TEST_F(InteractiveTestTest, AnyOfAllFail) { |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| EXPECT_CALL(aborted, Run); |
| RunTestSequenceInContext( |
| e1.context(), |
| InParallel( |
| RunSubsequence(Check(base::BindOnce([]() { return false; }))), |
| RunSubsequence(Check(base::BindOnce([]() { return false; }))))); |
| } |
| |
| // This is a regression test for an issue where there is a UAF when tearing down |
| // an AnyOf() inside an If(). |
| TEST_F(InteractiveTestTest, AnyOfInsideIf) { |
| TestElement el(kTestId1, kTestContext1); |
| QueueActions([&el]() { el.Show(); }, |
| [&el]() { el.SendCustomEvent(kTestEvent1); }); |
| |
| RunTestSequenceInContext( |
| kTestContext1, |
| If([]() { return true; }, |
| Then(AnyOf( |
| RunSubsequence(std::move(WaitForEvent(kTestId1, kTestEvent1) |
| .SetMustBeVisibleAtStart(false))), |
| RunSubsequence(WaitForShow(kTestId1), |
| WaitForEvent(kTestId1, kTestEvent2)))))); |
| } |
| |
| // This test that various types of logging can compile with different types of |
| // parameters. The output of this test must be verified manually. |
| TEST_F(InteractiveTestTest, Log) { |
| TestElement e1(kTestId1, kTestContext1); |
| int x = 1; |
| int y = 0; |
| constexpr char kSomeString[] = "A string."; |
| std::u16string deferred_string1; |
| const char* deferred_string2; |
| struct { |
| bool b = false; |
| } unnamed_struct, *unnamed_struct_ptr = nullptr; |
| |
| RunTestSequenceInContext( |
| e1.context(), Do([&]() { |
| y = 2; |
| deferred_string1 = u"The quick brown fox"; |
| deferred_string2 = "Lorem ipsum"; |
| unnamed_struct_ptr = &unnamed_struct; |
| }), |
| Log( |
| "Log() output follows:\nliteral int: ", x, |
| "\ndeferred int: ", std::ref(y), "\nconstexpr string: ", kSomeString, |
| "\ndeferred string 1: ", std::ref(deferred_string1), |
| "\ndeferred string 2: ", std::ref(deferred_string2), |
| "\npointer to object: ", &unnamed_struct, |
| "\ndeferred pointer to object: ", std::ref(unnamed_struct_ptr), |
| "\nlambda - should be 7: ", [x, &y]() { return x + y + 4; }, |
| "\nOnceCallback - should be 3: ", |
| base::BindOnce([](int x, int* y) { return x + *y; }, x, |
| base::Unretained(&y)), |
| "\nRepeatingCallback - should be 4: ", |
| base::BindRepeating([](int x, int* y) { return x + *y + 1; }, x, |
| base::Unretained(&y)), |
| "\nfunction pointer - should be 5: ", &ValueGeneratingFunction)); |
| } |
| |
| // This test that the element tree can be dumped. |
| // The output of this test must be checked manually. |
| TEST_F(InteractiveTestTest, DumpElements) { |
| TestElement e1(kTestId1, kTestContext1); |
| TestElement e2(kTestId2, kTestContext1); |
| TestElement e3(kTestId1, kTestContext1); |
| TestElement e4(kTestId3, kTestContext1); |
| TestElement e5(kTestId1, kTestContext2); |
| TestElement e6(kTestId2, kTestContext2); |
| TestElement e7(kTestId3, kTestContext2); |
| e1.Show(); |
| e2.Show(); |
| e3.Show(); |
| e4.Show(); |
| e5.Show(); |
| // e6 not shown |
| e7.Show(); |
| |
| RunTestSequenceInContext(e1.context(), DumpElements()); |
| } |
| |
| // This test that the element tree can be dumped. |
| // The output of this test must be checked manually. |
| TEST_F(InteractiveTestTest, DumpElementsInContext) { |
| TestElement e1(kTestId1, kTestContext1); |
| TestElement e2(kTestId2, kTestContext1); |
| TestElement e3(kTestId1, kTestContext1); |
| TestElement e4(kTestId3, kTestContext1); |
| TestElement e5(kTestId1, kTestContext2); |
| TestElement e6(kTestId2, kTestContext2); |
| TestElement e7(kTestId3, kTestContext2); |
| e1.Show(); |
| e2.Show(); |
| e3.Show(); |
| e4.Show(); |
| e5.Show(); |
| // e6 not shown |
| e7.Show(); |
| |
| RunTestSequenceInContext(e1.context(), DumpElementsInContext(), |
| InContext(e5.context(), DumpElementsInContext())); |
| } |
| |
| // This test ensures that binding of various types of functions and function |
| // arguments works correctly with actions. If the template logic is not correct, |
| // this test will likely not compile. |
| TEST_F(InteractiveTestTest, ActionBindingMethods) { |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| CallableObject callable{2}; |
| MutableCallableObject mutable_callable{0}; |
| EmptyCallableObject empty_callable; |
| auto lambda = []() { LOG(INFO) << "Stored lambda."; }; |
| auto once_callback = base::BindOnce([]() { LOG(INFO) << "Once callback."; }); |
| auto repeating_callback = |
| base::BindRepeating([]() { LOG(INFO) << "Repeating callback."; }); |
| int x = 1; |
| int y = 2; |
| RunTestSequenceInContext( |
| e1.context(), |
| |
| // Check all of the various ways to bind methods with Do(). |
| Do(base::DoNothing()), Do(&DoFunction), Do(lambda), |
| Do(std::move(once_callback)), Do(repeating_callback), |
| Do([x, &y]() { LOG(INFO) << "Bound args " << x << ", " << y; }), |
| Do(empty_callable), |
| |
| // Check various ways to verify a return value. |
| Check(base::BindOnce([]() { return true; })), |
| Check([]() { return true; }), CheckResult([x, &y]() { return x + y; }, 3), |
| Check(callable), CheckResult(std::move(mutable_callable), false), |
| CheckElement( |
| e1.identifier(), [](TrackedElement* el) { return el; }, &e1), |
| |
| // Verify that argument list reduction works with bare callbacks. |
| AfterShow(e1.identifier(), |
| [&e1](TrackedElement* el) { EXPECT_EQ(el, &e1); }), |
| WithElement(e1.identifier(), []() {}), |
| WithElement(e1.identifier(), |
| [](InteractionSequence*, TrackedElement*) {})); |
| } |
| |
| // This test ensures that binding of various types of functions and function |
| // arguments works correctly with conditionals. If the template logic is not |
| // correct, this test will likely not compile. |
| TEST_F(InteractiveTestTest, ConditionalBindingMethods) { |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, correct); |
| UNCALLED_MOCK_CALLBACK(base::OnceClosure, incorrect); |
| TestElement e1(kTestId1, kTestContext1); |
| e1.Show(); |
| |
| int x = 1; |
| int y = 2; |
| |
| EXPECT_CALL(correct, Run).Times(4); |
| RunTestSequenceInContext( |
| e1.context(), |
| If([]() { return true; }, Then(Do(correct.Get())), |
| Else(Do(incorrect.Get()))), |
| IfMatches([x, &y]() { return x + y; }, 2, Then(Do(incorrect.Get())), |
| Else(Do(correct.Get()))), |
| IfElement( |
| e1.identifier(), |
| [&e1](const TrackedElement* el) { return el == &e1; }, |
| Then(Do(correct.Get())), Else(Do(incorrect.Get()))), |
| IfElementMatches(kTestId2, &CheckElementFunction, testing::Ne(nullptr), |
| Then(Do(incorrect.Get())), Else(Do(correct.Get())))); |
| } |
| |
| namespace { |
| |
| template <typename T> |
| class TestObservable; |
| |
| template <typename T> |
| class TestObserver : public base::CheckedObserver { |
| public: |
| TestObserver() = default; |
| ~TestObserver() override = default; |
| |
| virtual void OnObservableValueChanged(TestObservable<T>* observable, |
| T value) {} |
| virtual void OnObservableDestroying(TestObservable<T>* observable) {} |
| }; |
| |
| template <typename T> |
| class TestObservable { |
| public: |
| explicit TestObservable(T value) : value_(value) {} |
| ~TestObservable() { |
| observers_.Notify(&TestObserver<T>::OnObservableDestroying, this); |
| } |
| |
| T value() const { return value_; } |
| |
| void SetValue(T value) { |
| value_ = value; |
| observers_.Notify(&TestObserver<T>::OnObservableValueChanged, this, value); |
| } |
| |
| void AddObserver(TestObserver<T>* observer) { |
| observers_.AddObserver(observer); |
| } |
| void RemoveObserver(TestObserver<T>* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| private: |
| T value_ = 0; |
| base::ObserverList<TestObserver<T>> observers_; |
| }; |
| |
| template <typename T> |
| class TestStateObserver |
| : public ObservationStateObserver<T, TestObservable<T>, TestObserver<T>> { |
| public: |
| using Base = ObservationStateObserver<T, TestObservable<T>, TestObserver<T>>; |
| |
| explicit TestStateObserver(TestObservable<T>* observable) |
| : Base(observable) {} |
| ~TestStateObserver() override = default; |
| |
| // ObservationStateObserver: |
| T GetStateObserverInitialState() const override { |
| return Base::source()->value(); |
| } |
| |
| // TestObserver: |
| void OnObservableValueChanged(TestObservable<T>* observable, |
| T value) override { |
| Base::OnStateObserverStateChanged(value); |
| } |
| void OnObservableDestroying(TestObservable<T>* observable) override { |
| Base::OnObservationStateObserverSourceDestroyed(); |
| } |
| }; |
| |
| struct MyStruct { |
| MyStruct() : my_int(0), my_bool(false) {} |
| MyStruct(int a, bool b) : my_int(a), my_bool(b) {} |
| MyStruct(const MyStruct&) = default; |
| bool operator==(const MyStruct& b) const = default; |
| MyStruct& operator=(const MyStruct& b) = default; |
| int my_int; |
| bool my_bool; |
| }; |
| |
| DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(TestStateObserver<int>, kIntTestState); |
| DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(TestStateObserver<std::string>, |
| kStringTestState); |
| DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(TestStateObserver<std::u16string>, |
| kWStringTestState); |
| DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(TestStateObserver<MyStruct>, |
| kStructTestState); |
| |
| } // namespace |
| |
| TEST_F(InteractiveTestTest, ObserveStateFromUniquePtr) { |
| TestObservable<int> observable(2); |
| QueueActions([&observable]() { observable.SetValue(0); }, |
| [&observable]() { observable.SetValue(3); }, |
| [&observable]() { observable.SetValue(1); }); |
| |
| RunTestSequenceInContext( |
| kTestContext1, |
| ObserveState(kIntTestState, |
| std::make_unique<TestStateObserver<int>>(&observable)), |
| WaitForState(kIntTestState, 3), |
| CheckResult([&observable]() { return observable.value(); }, 3), |
| WaitForState(kIntTestState, testing::Ne(3)), |
| CheckResult([&observable]() { return observable.value(); }, 1)); |
| } |
| |
| TEST_F(InteractiveTestTest, ObserveExistingStateProceedsImmediately) { |
| TestObservable<int> observable(2); |
| RunTestSequenceInContext(kTestContext1, |
| ObserveState(kIntTestState, &observable), |
| WaitForState(kIntTestState, 2)); |
| } |
| |
| TEST_F(InteractiveTestTest, ObserveStateFromArgsWithReferences) { |
| TestObservable<int> observable(2); |
| int target = 0; |
| TestObservable<int>* obs_ptr = nullptr; |
| QueueActions([&observable]() { observable.SetValue(0); }, |
| [&observable]() { observable.SetValue(3); }); |
| RunTestSequenceInContext( |
| kTestContext1, Do([&]() { |
| target = 3; |
| obs_ptr = &observable; |
| }), |
| ObserveState(kIntTestState, std::ref(obs_ptr)), |
| WaitForState(kIntTestState, std::ref(target)), |
| CheckResult([&observable]() { return observable.value(); }, 3)); |
| } |
| |
| TEST_F(InteractiveTestTest, ObserveStateFromArgsWithFunctions) { |
| TestObservable<int> observable(2); |
| QueueActions([&observable]() { observable.SetValue(0); }, |
| [&observable]() { observable.SetValue(3); }); |
| RunTestSequenceInContext( |
| kTestContext1, ObserveState(kIntTestState, [&]() { return &observable; }), |
| WaitForState(kIntTestState, base::BindRepeating([]() { return 3; })), |
| CheckResult([&observable]() { return observable.value(); }, 3)); |
| } |
| |
| TEST_F(InteractiveTestTest, ObserveStateResetsOnDestruction) { |
| auto observable = std::make_unique<TestObservable<int>>(2); |
| QueueActions([&observable]() { observable->SetValue(0); }, |
| [&observable]() { observable->SetValue(3); }, |
| [&observable]() { observable.reset(); }); |
| RunTestSequenceInContext(kTestContext1, |
| ObserveState(kIntTestState, observable.get()), |
| WaitForState(kIntTestState, 3), |
| WaitForState(kIntTestState, testing::Ne(3))); |
| } |
| |
| TEST_F(InteractiveTestTest, ObserveStateWithString) { |
| TestObservable<std::string> observable("foo"); |
| static const char* const kBar = "bar"; |
| constexpr char kBaz[] = "baz"; |
| QueueActions([&]() { observable.SetValue(kBar); }, |
| [&]() { observable.SetValue(kBaz); }); |
| RunTestSequenceInContext(kTestContext1, |
| ObserveState(kStringTestState, &observable), |
| WaitForState(kStringTestState, kBar), |
| WaitForState(kStringTestState, testing::Eq(kBaz))); |
| } |
| |
| TEST_F(InteractiveTestTest, ObserveStateWithWideString) { |
| TestObservable<std::u16string> observable(u"foo"); |
| static const char16_t* const kBar = u"bar"; |
| constexpr char16_t kBaz[] = u"baz"; |
| QueueActions([&]() { observable.SetValue(kBar); }, |
| [&]() { observable.SetValue(kBaz); }); |
| RunTestSequenceInContext(kTestContext1, |
| ObserveState(kWStringTestState, &observable), |
| WaitForState(kWStringTestState, kBar), |
| WaitForState(kWStringTestState, testing::Eq(kBaz))); |
| } |
| |
| TEST_F(InteractiveTestTest, ObserveStateWithStruct) { |
| TestObservable<MyStruct> observable(MyStruct(0, false)); |
| QueueActions([&]() { observable.SetValue(MyStruct(123, false)); }); |
| RunTestSequenceInContext( |
| kTestContext1, ObserveState(kStructTestState, &observable), |
| WaitForState(kStructTestState, testing::Field(&MyStruct::my_int, 123))); |
| } |
| |
| TEST_F(InteractiveTestTest, StopObservingState) { |
| TestObservable<int> observable(1); |
| RunTestSequenceInContext( |
| kTestContext1, |
| ObserveState(kIntTestState, |
| std::make_unique<TestStateObserver<int>>(&observable)), |
| WaitForState(kIntTestState, 1), StopObservingState(kIntTestState), |
| EnsureNotPresent(kIntTestState.identifier()), |
| Check([this]() { return state_observers().empty(); })); |
| } |
| |
| TEST_F(InteractiveTestTest, CheckStateSucceeds) { |
| TestObservable<std::string> observable("foo"); |
| static const char* const kBar = "bar"; |
| QueueActions([&]() { observable.SetValue(kBar); }); |
| RunTestSequenceInContext( |
| kTestContext1, ObserveState(kStringTestState, &observable), |
| WaitForState(kStringTestState, kBar), CheckState(kStringTestState, kBar), |
| CheckState(kStringTestState, testing::Ne("foo"))); |
| } |
| |
| TEST_F(InteractiveTestTest, CheckStateFails) { |
| UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); |
| private_test_impl().set_aborted_callback_for_testing(aborted.Get()); |
| |
| TestObservable<std::string> observable("foo"); |
| static const char* const kBar = "bar"; |
| |
| EXPECT_CALL_IN_SCOPE( |
| aborted, Run, |
| RunTestSequenceInContext(kTestContext1, |
| ObserveState(kStringTestState, &observable), |
| CheckState(kStringTestState, kBar))); |
| } |
| |
| DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(PollingStateObserver<int>, |
| kPollingTestState); |
| DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(PollingStateObserver<int>, |
| kPollingTestState2); |
| |
| TEST_F(InteractiveTestTest, PollingStateObserver) { |
| UNCALLED_MOCK_CALLBACK(PollingStateObserver<int>::PollCallback, poll_cb); |
| EXPECT_CALL(poll_cb, Run) |
| .WillOnce(testing::Return(0)) |
| .WillOnce(testing::Return(1)); |
| RunTestSequenceInContext( |
| kTestContext1, |
| PollState(kPollingTestState, poll_cb.Get(), base::Milliseconds(50)), |
| WaitForState(kPollingTestState, 1)); |
| } |
| |
| // Tests that the callback is issued exactly once to determine its initial |
| // state, and not triggered unnecessarily once the desired condition is met. |
| TEST_F(InteractiveTestTest, PollingStateCalledOnce) { |
| UNCALLED_MOCK_CALLBACK(PollingStateObserver<int>::PollCallback, callback); |
| EXPECT_CALL(callback, Run).Times(1).WillOnce(testing::Return(1)); |
| RunTestSequenceInContext(kTestContext1, |
| PollState(kPollingTestState, callback.Get()), |
| WaitForState(kPollingTestState, 1)); |
| EXPECT_CALL(callback, Run).Times(0); |
| } |
| |
| DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(PollingElementStateObserver<std::string>, |
| kPollingElementTestState); |
| |
| TEST_F(InteractiveTestTest, PollingElementStateObserver) { |
| UNCALLED_MOCK_CALLBACK( |
| PollingElementStateObserver<std::string>::PollElementCallback, poll_cb); |
| TestElement el(kTestId1, kTestContext1); |
| EXPECT_CALL(poll_cb, Run(&el)) |
| .WillOnce(testing::Return(std::string("foo"))) |
| .WillOnce(testing::Return(std::string("bar"))) |
| .WillOnce(testing::Return(std::string("baz"))); |
| |
| // Start with the element not visible, then show it. |
| QueueActions([&el] { el.Show(); }); |
| |
| RunTestSequenceInContext( |
| kTestContext1, |
| PollElement(kPollingElementTestState, el.identifier(), poll_cb.Get(), |
| base::Milliseconds(50)), |
| WaitForState(kPollingElementTestState, "baz")); |
| } |
| |
| TEST_F(InteractiveTestTest, PollStateUntil) { |
| UNCALLED_MOCK_CALLBACK(PollingStateObserver<int>::PollCallback, poll_cb); |
| EXPECT_CALL(poll_cb, Run) |
| .WillOnce(testing::Return(0)) |
| .WillOnce(testing::Return(1)); |
| RunTestSequenceInContext( |
| kTestContext1, PollStateUntil(kPollingTestState, poll_cb.Get(), |
| testing::Gt(0), base::Milliseconds(50))); |
| } |
| |
| TEST_F(InteractiveTestTest, PollStateUntilRepeatedly) { |
| UNCALLED_MOCK_CALLBACK(PollingStateObserver<int>::PollCallback, poll_cb); |
| EXPECT_CALL(poll_cb, Run) |
| .WillOnce(testing::Return(0)) |
| .WillOnce(testing::Return(1)) |
| .WillOnce(testing::Return(0)) |
| .WillOnce(testing::Return(-1)); |
| RunTestSequenceInContext( |
| kTestContext1, |
| PollStateUntil(kPollingTestState, poll_cb.Get(), testing::Gt(0), |
| base::Milliseconds(50)), |
| PollStateUntil(kPollingTestState, poll_cb.Get(), testing::Lt(0), |
| base::Milliseconds(50))); |
| } |
| |
| TEST_F(InteractiveTestTest, PollStateUntilInParallel) { |
| const auto kPollTime = base::Milliseconds(50); |
| UNCALLED_MOCK_CALLBACK(PollingStateObserver<int>::PollCallback, poll_cb); |
| UNCALLED_MOCK_CALLBACK(PollingStateObserver<int>::PollCallback, poll_cb2); |
| EXPECT_CALL(poll_cb, Run) |
| .WillOnce(testing::Return(0)) |
| .WillOnce(testing::Return(1)); |
| EXPECT_CALL(poll_cb2, Run) |
| .WillOnce(testing::Return(0)) |
| .WillOnce(testing::Return(-1)); |
| RunTestSequenceInContext( |
| kTestContext1, |
| InParallel( |
| RunSubsequence(PollStateUntil(kPollingTestState, poll_cb.Get(), |
| testing::Gt(0), kPollTime)), |
| RunSubsequence(PollStateUntil(kPollingTestState2, poll_cb2.Get(), |
| testing::Lt(0), kPollTime)))); |
| } |
| |
| TEST_F(InteractiveTestTest, PollUntil) { |
| int count = 0; |
| RunTestSequenceInContext( |
| kTestContext1, |
| PollUntil([&count]() { return count++ > 1; }, "Polling integer")); |
| } |
| |
| TEST_F(InteractiveTestTest, PollUntilRepeatedly) { |
| int count1 = 0; |
| int count2 = 0; |
| RunTestSequenceInContext( |
| kTestContext1, PollUntil([&count1]() { return count1++ > 2; }, "count1"), |
| PollUntil([&count2]() { return count2++ > 1; }, "count2")); |
| } |
| |
| TEST_F(InteractiveTestTest, SubsequenceHidesElement) { |
| TestElement el1(kTestId1, kTestContext1); |
| TestElement el2(kTestId2, kTestContext1); |
| |
| QueueActions([&]() { el1.Show(); }, [&]() { el2.Show(); }); |
| |
| RunTestSequenceInContext( |
| kTestContext1, WaitForShow(el1.identifier()), |
| InParallel(RunSubsequence(Do([&el1]() { el1.Hide(); })), |
| RunSubsequence(WaitForShow(el2.identifier())))); |
| } |
| |
| namespace { |
| static constexpr char kAdditionalContext1[] = "context1"; |
| static constexpr char kAdditionalContext2[] = "context2"; |
| } // namespace |
| |
| TEST_F(InteractiveTestTest, SetAndClearAdditionalContext) { |
| AdditionalContext context = private_test_impl().CreateAdditionalContext(); |
| RunTestSequenceInContext( |
| kTestContext1, |
| |
| // Verify the context is empty. |
| Check([this]() { |
| return private_test_impl().GetAdditionalContext().empty(); |
| }), |
| CheckResult([context]() { return context.Get(); }, ""), |
| |
| // Set context and verify value across steps. |
| CheckResult( |
| [this, context]() mutable { |
| context.Set(kAdditionalContext1); |
| return private_test_impl().GetAdditionalContext(); |
| }, |
| testing::Contains(kAdditionalContext1)), |
| CheckResult([context]() { return context.Get(); }, kAdditionalContext1), |
| CheckResult( |
| [this]() { return private_test_impl().GetAdditionalContext(); }, |
| testing::Contains(kAdditionalContext1)), |
| |
| // Clear context and verify value across steps. |
| Check([this, context]() mutable { |
| context.Clear(); |
| return private_test_impl().GetAdditionalContext().empty(); |
| }), |
| Check([this]() { |
| return private_test_impl().GetAdditionalContext().empty(); |
| }), |
| CheckResult([context]() { return context.Get(); }, ""), |
| |
| // Set value again and verify value across steps. |
| CheckResult( |
| [this, context]() mutable { |
| context.Set(kAdditionalContext1); |
| return private_test_impl().GetAdditionalContext(); |
| }, |
| testing::Contains(kAdditionalContext1)), |
| CheckResult( |
| [this]() { return private_test_impl().GetAdditionalContext(); }, |
| testing::Contains(kAdditionalContext1)), |
| CheckResult([context]() { return context.Get(); }, kAdditionalContext1)); |
| } |
| |
| TEST_F(InteractiveTestTest, AdditionalContextNotCleared) { |
| AdditionalContext context = private_test_impl().CreateAdditionalContext(); |
| RunTestSequenceInContext( |
| kTestContext1, |
| |
| // Set context. |
| Do([context]() mutable { context.Set(kAdditionalContext1); })); |
| |
| EXPECT_EQ(kAdditionalContext1, context.Get()); |
| EXPECT_THAT(private_test_impl().GetAdditionalContext(), |
| testing::ElementsAre(kAdditionalContext1)); |
| } |
| |
| TEST_F(InteractiveTestTest, DestructAdditionalContext) { |
| // Create a custom verb that has a local context. |
| auto custom_verb = [this]() { |
| AdditionalContext context = private_test_impl().CreateAdditionalContext(); |
| return Steps( |
| CheckResult( |
| [this, context]() mutable { |
| context.Set(kAdditionalContext1); |
| return private_test_impl().GetAdditionalContext(); |
| }, |
| testing::Contains(kAdditionalContext1)), |
| CheckResult( |
| [this, context]() { |
| return private_test_impl().GetAdditionalContext(); |
| }, |
| testing::Contains(kAdditionalContext1)), |
| CheckResult([context]() { return context.Get(); }, kAdditionalContext1), |
| Do([context]() mutable { context.Clear(); })); |
| }; |
| |
| RunTestSequenceInContext( |
| kTestContext1, |
| |
| // Run the custom verb. |
| custom_verb(), |
| |
| // After the verb has completed, there are no more references to the |
| // context. |
| Check([this]() { |
| return private_test_impl().GetAdditionalContext().empty(); |
| })); |
| } |
| |
| TEST_F(InteractiveTestTest, TwoAdditionalContexts) { |
| // Create a custom verb that has a local context. This will be called from |
| // inside `custom_verb()` below. |
| auto custom_verb2 = [this]() { |
| AdditionalContext context = private_test_impl().CreateAdditionalContext(); |
| |
| // The context for both this and the outer verb will be active. |
| auto expected = |
| testing::ElementsAre(kAdditionalContext1, kAdditionalContext2); |
| return Steps(CheckResult( |
| [this, context]() mutable { |
| context.Set(kAdditionalContext2); |
| return private_test_impl().GetAdditionalContext(); |
| }, |
| expected), |
| CheckResult( |
| [this, context]() { |
| return private_test_impl().GetAdditionalContext(); |
| }, |
| expected), |
| Do([context]() mutable { context.Clear(); })); |
| }; |
| |
| // Create a custom verb that has a local context and calls another verb with a |
| // local context. |
| auto custom_verb = [this, &custom_verb2]() { |
| AdditionalContext context = private_test_impl().CreateAdditionalContext(); |
| |
| return Steps(Do([context]() mutable { context.Set(kAdditionalContext1); }), |
| |
| custom_verb2(), |
| |
| // Outside of custom_verb(), only our context exists. |
| CheckResult( |
| [this, context]() { |
| return private_test_impl().GetAdditionalContext(); |
| }, |
| testing::Contains(kAdditionalContext1)), |
| Do([context]() mutable { context.Clear(); })); |
| }; |
| |
| RunTestSequenceInContext( |
| kTestContext1, |
| |
| // Run the custom verb. |
| custom_verb(), |
| |
| // After the verb has completed, there are no more references to the |
| // context. |
| Check([this]() { |
| return private_test_impl().GetAdditionalContext().empty(); |
| })); |
| } |
| |
| } // namespace ui::test |