// 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 "ui/base/interaction/element_tracker_mac.h"

#import <Cocoa/Cocoa.h>

#include <initializer_list>
#include <map>
#include <set>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/test/mock_callback.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
#include "ui/base/interaction/element_tracker.h"

namespace ui {

namespace {

NSMenu* const kFakeMenu1 = [[NSMenu alloc] initWithTitle:@"1"];
NSMenu* const kFakeMenu2 = [[NSMenu alloc] initWithTitle:@"2"];
NSMenu* const kFakeMenu3 = [[NSMenu alloc] initWithTitle:@"3"];

constexpr gfx::Rect kScreenBounds1(30, 50, 100, 200);
constexpr gfx::Rect kScreenBounds2(60, 70, 120, 220);
constexpr gfx::Rect kScreenBounds3(90, 100, 110, 210);

DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kElementIdentifier1);
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kElementIdentifier2);
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kElementIdentifier3);

const ElementContext kElementContext1(1);
const ElementContext kElementContext2(2);

class ExpectedCall {
 public:
  enum Type { kShown, kActivated, kHidden };

  ExpectedCall(ElementIdentifier identifier,
               ElementContext context,
               Type type,
               int expected_count)
      : identifier_(identifier),
        context_(context),
        expected_count_(expected_count) {
    ElementTracker::Callback callback =
        base::BindRepeating(&ExpectedCall::OnCalled, base::Unretained(this));
    switch (type) {
      case kShown:
        subscription_ =
            ElementTracker::GetElementTracker()->AddElementShownCallback(
                identifier, context, callback);
        break;
      case kActivated:
        subscription_ =
            ElementTracker::GetElementTracker()->AddElementActivatedCallback(
                identifier, context, callback);
        break;
      case kHidden:
        subscription_ =
            ElementTracker::GetElementTracker()->AddElementHiddenCallback(
                identifier, context, callback);
        break;
    }
  }

  ExpectedCall(const ExpectedCall& other) = delete;
  void operator=(const ExpectedCall& other) = delete;

  ~ExpectedCall() { EXPECT_EQ(expected_count_, count_); }

  void set_expected_bounds(const gfx::Rect& expected_bounds) {
    expected_bounds_ = expected_bounds;
  }

  int count() const { return count_; }

 private:
  void OnCalled(TrackedElement* element) {
    ASSERT_TRUE(element->IsA<TrackedElementMac>());
    if (expected_bounds_.has_value()) {
      EXPECT_EQ(expected_bounds_.value(),
                element->AsA<TrackedElementMac>()->GetScreenBounds());
      expected_bounds_.reset();
    }
    EXPECT_EQ(identifier_, element->identifier());
    EXPECT_EQ(context_, element->context());
    ++count_;
    EXPECT_LE(count_, expected_count_);
  }

  const ElementIdentifier identifier_;
  const ElementContext context_;
  const int expected_count_;
  int count_ = 0;
  absl::optional<gfx::Rect> expected_bounds_;
  ElementTracker::Subscription subscription_;
};

class TestElementTrackerMac : public ElementTrackerMac {
 public:
  TestElementTrackerMac() = default;

  ~TestElementTrackerMac() override { EXPECT_FALSE(is_tracking_any_menus()); }

  void SetParent(NSMenu* menu, NSMenu* parent) {
    parenting_.emplace(menu, parent);
  }

 protected:
  // ElementTrackerMac:
  NSMenu* GetRootMenu(NSMenu* menu) const override {
    while (true) {
      const auto it = parenting_.find(menu);
      if (it == parenting_.end())
        return menu;
      menu = it->second;
    }
  }

 private:
  std::map<NSMenu*, NSMenu*> parenting_;
};

}  // namespace

class ElementTrackerMacTest : public PlatformTest {
 public:
  void SetUp() override {
    element_tracker_ = std::make_unique<TestElementTrackerMac>();
  }

  void TearDown() override { element_tracker_.reset(); }

 protected:
  std::unique_ptr<TestElementTrackerMac> element_tracker_;
};

TEST_F(ElementTrackerMacTest, OpenCloseNoEvents) {
  element_tracker_->NotifyMenuWillShow(kFakeMenu1, kElementContext1);
  element_tracker_->NotifyMenuDoneShowing(kFakeMenu1);
}

TEST_F(ElementTrackerMacTest, OpenShowOneIdentifier) {
  ExpectedCall shown(kElementIdentifier1, kElementContext1,
                     ExpectedCall::kShown, 1);
  ExpectedCall hidden(kElementIdentifier1, kElementContext1,
                      ExpectedCall::kHidden, 1);
  ExpectedCall activated(kElementIdentifier1, kElementContext1,
                         ExpectedCall::kActivated, 0);
  element_tracker_->NotifyMenuWillShow(kFakeMenu1, kElementContext1);
  element_tracker_->NotifyMenuItemShown(kFakeMenu1, kElementIdentifier1,
                                        kScreenBounds1);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu1, kElementIdentifier1);
  element_tracker_->NotifyMenuDoneShowing(kFakeMenu1);
}

TEST_F(ElementTrackerMacTest, OpenShowMultipleIdentifiers) {
  ExpectedCall shown(kElementIdentifier1, kElementContext1,
                     ExpectedCall::kShown, 1);
  ExpectedCall hidden(kElementIdentifier1, kElementContext1,
                      ExpectedCall::kHidden, 1);
  ExpectedCall activated(kElementIdentifier1, kElementContext1,
                         ExpectedCall::kActivated, 0);
  ExpectedCall shown2(kElementIdentifier2, kElementContext1,
                      ExpectedCall::kShown, 1);
  ExpectedCall hidden2(kElementIdentifier2, kElementContext1,
                       ExpectedCall::kHidden, 1);
  ExpectedCall activated2(kElementIdentifier2, kElementContext1,
                          ExpectedCall::kActivated, 0);
  element_tracker_->NotifyMenuWillShow(kFakeMenu1, kElementContext1);
  element_tracker_->NotifyMenuItemShown(kFakeMenu1, kElementIdentifier1,
                                        kScreenBounds1);
  element_tracker_->NotifyMenuItemShown(kFakeMenu1, kElementIdentifier2,
                                        kScreenBounds2);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu1, kElementIdentifier1);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu1, kElementIdentifier2);
  element_tracker_->NotifyMenuDoneShowing(kFakeMenu1);
}

TEST_F(ElementTrackerMacTest, OpenShowMultipleIdentifiersDifferentMenus) {
  ExpectedCall shown(kElementIdentifier1, kElementContext1,
                     ExpectedCall::kShown, 1);
  ExpectedCall hidden(kElementIdentifier1, kElementContext1,
                      ExpectedCall::kHidden, 1);
  ExpectedCall activated(kElementIdentifier1, kElementContext1,
                         ExpectedCall::kActivated, 0);
  ExpectedCall shown2(kElementIdentifier1, kElementContext2,
                      ExpectedCall::kShown, 1);
  ExpectedCall hidden2(kElementIdentifier1, kElementContext2,
                       ExpectedCall::kHidden, 1);
  ExpectedCall activated2(kElementIdentifier1, kElementContext2,
                          ExpectedCall::kActivated, 0);
  element_tracker_->NotifyMenuWillShow(kFakeMenu1, kElementContext1);
  element_tracker_->NotifyMenuItemShown(kFakeMenu1, kElementIdentifier1,
                                        kScreenBounds1);
  element_tracker_->NotifyMenuWillShow(kFakeMenu2, kElementContext2);
  element_tracker_->NotifyMenuItemShown(kFakeMenu2, kElementIdentifier1,
                                        kScreenBounds2);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu1, kElementIdentifier1);
  element_tracker_->NotifyMenuDoneShowing(kFakeMenu1);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu2, kElementIdentifier1);
  element_tracker_->NotifyMenuDoneShowing(kFakeMenu2);
}

TEST_F(ElementTrackerMacTest, OpenShowMultipleIdentifiersActivate) {
  ExpectedCall shown(kElementIdentifier1, kElementContext1,
                     ExpectedCall::kShown, 1);
  ExpectedCall hidden(kElementIdentifier1, kElementContext1,
                      ExpectedCall::kHidden, 1);
  ExpectedCall activated(kElementIdentifier1, kElementContext1,
                         ExpectedCall::kActivated, 1);
  ExpectedCall shown2(kElementIdentifier2, kElementContext1,
                      ExpectedCall::kShown, 1);
  ExpectedCall hidden2(kElementIdentifier2, kElementContext1,
                       ExpectedCall::kHidden, 1);
  ExpectedCall activated2(kElementIdentifier2, kElementContext1,
                          ExpectedCall::kActivated, 0);
  element_tracker_->NotifyMenuWillShow(kFakeMenu1, kElementContext1);
  element_tracker_->NotifyMenuItemShown(kFakeMenu1, kElementIdentifier1,
                                        kScreenBounds1);
  element_tracker_->NotifyMenuItemShown(kFakeMenu1, kElementIdentifier2,
                                        kScreenBounds2);
  element_tracker_->NotifyMenuItemActivated(kFakeMenu1, kElementIdentifier1);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu1, kElementIdentifier1);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu1, kElementIdentifier2);
  element_tracker_->NotifyMenuDoneShowing(kFakeMenu1);
}

TEST_F(ElementTrackerMacTest,
       OpenShowMultipleIdentifiersDifferentMenusActivate) {
  ExpectedCall shown(kElementIdentifier1, kElementContext1,
                     ExpectedCall::kShown, 1);
  ExpectedCall hidden(kElementIdentifier1, kElementContext1,
                      ExpectedCall::kHidden, 1);
  ExpectedCall activated(kElementIdentifier1, kElementContext1,
                         ExpectedCall::kActivated, 1);
  ExpectedCall shown2(kElementIdentifier1, kElementContext2,
                      ExpectedCall::kShown, 1);
  ExpectedCall hidden2(kElementIdentifier1, kElementContext2,
                       ExpectedCall::kHidden, 1);
  ExpectedCall activated2(kElementIdentifier1, kElementContext2,
                          ExpectedCall::kActivated, 1);
  element_tracker_->NotifyMenuWillShow(kFakeMenu1, kElementContext1);
  element_tracker_->NotifyMenuItemShown(kFakeMenu1, kElementIdentifier1,
                                        kScreenBounds1);
  element_tracker_->NotifyMenuWillShow(kFakeMenu2, kElementContext2);
  element_tracker_->NotifyMenuItemShown(kFakeMenu2, kElementIdentifier1,
                                        kScreenBounds2);
  element_tracker_->NotifyMenuItemActivated(kFakeMenu1, kElementIdentifier1);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu1, kElementIdentifier1);
  element_tracker_->NotifyMenuDoneShowing(kFakeMenu1);
  element_tracker_->NotifyMenuItemActivated(kFakeMenu2, kElementIdentifier1);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu2, kElementIdentifier1);
  element_tracker_->NotifyMenuDoneShowing(kFakeMenu2);
}

TEST_F(ElementTrackerMacTest, ShowHideMultipleTimes) {
  ExpectedCall shown(kElementIdentifier1, kElementContext1,
                     ExpectedCall::kShown, 2);
  ExpectedCall hidden(kElementIdentifier1, kElementContext1,
                      ExpectedCall::kHidden, 2);
  ExpectedCall activated(kElementIdentifier1, kElementContext1,
                         ExpectedCall::kActivated, 0);
  ExpectedCall shown2(kElementIdentifier2, kElementContext1,
                      ExpectedCall::kShown, 1);
  ExpectedCall hidden2(kElementIdentifier2, kElementContext1,
                       ExpectedCall::kHidden, 1);
  ExpectedCall activated2(kElementIdentifier2, kElementContext1,
                          ExpectedCall::kActivated, 0);
  element_tracker_->NotifyMenuWillShow(kFakeMenu1, kElementContext1);
  element_tracker_->NotifyMenuItemShown(kFakeMenu1, kElementIdentifier1,
                                        kScreenBounds1);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu1, kElementIdentifier1);
  element_tracker_->NotifyMenuItemShown(kFakeMenu1, kElementIdentifier1,
                                        kScreenBounds2);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu1, kElementIdentifier1);
  element_tracker_->NotifyMenuItemShown(kFakeMenu1, kElementIdentifier2,
                                        kScreenBounds3);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu1, kElementIdentifier2);
  element_tracker_->NotifyMenuDoneShowing(kFakeMenu1);
}

TEST_F(ElementTrackerMacTest, ScreenBounds) {
  ExpectedCall shown(kElementIdentifier1, kElementContext1,
                     ExpectedCall::kShown, 2);
  ExpectedCall hidden(kElementIdentifier1, kElementContext1,
                      ExpectedCall::kHidden, 2);
  ExpectedCall activated(kElementIdentifier1, kElementContext1,
                         ExpectedCall::kActivated, 0);
  ExpectedCall shown2(kElementIdentifier2, kElementContext1,
                      ExpectedCall::kShown, 1);
  ExpectedCall hidden2(kElementIdentifier2, kElementContext1,
                       ExpectedCall::kHidden, 1);
  ExpectedCall activated2(kElementIdentifier2, kElementContext1,
                          ExpectedCall::kActivated, 0);
  element_tracker_->NotifyMenuWillShow(kFakeMenu1, kElementContext1);
  shown.set_expected_bounds(kScreenBounds1);
  element_tracker_->NotifyMenuItemShown(kFakeMenu1, kElementIdentifier1,
                                        kScreenBounds1);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu1, kElementIdentifier1);
  shown.set_expected_bounds(kScreenBounds2);
  element_tracker_->NotifyMenuItemShown(kFakeMenu1, kElementIdentifier1,
                                        kScreenBounds2);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu1, kElementIdentifier1);
  shown2.set_expected_bounds(kScreenBounds3);
  element_tracker_->NotifyMenuItemShown(kFakeMenu1, kElementIdentifier2,
                                        kScreenBounds3);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu1, kElementIdentifier2);
  element_tracker_->NotifyMenuDoneShowing(kFakeMenu1);
}

// Note that while we don't yet officially support nested menus with
// identifiers, the infrastructure is set up to support them (assuming we can)
// get the callbacks to work properly.
//
// So we will still test nested menus.

TEST_F(ElementTrackerMacTest, NestedMenus) {
  ExpectedCall shown(kElementIdentifier1, kElementContext1,
                     ExpectedCall::kShown, 1);
  ExpectedCall hidden(kElementIdentifier1, kElementContext1,
                      ExpectedCall::kHidden, 1);
  ExpectedCall activated(kElementIdentifier1, kElementContext1,
                         ExpectedCall::kActivated, 0);
  ExpectedCall shown2(kElementIdentifier2, kElementContext1,
                      ExpectedCall::kShown, 1);
  ExpectedCall hidden2(kElementIdentifier2, kElementContext1,
                       ExpectedCall::kHidden, 1);
  ExpectedCall activated2(kElementIdentifier2, kElementContext1,
                          ExpectedCall::kActivated, 0);
  element_tracker_->SetParent(kFakeMenu2, kFakeMenu1);
  element_tracker_->NotifyMenuWillShow(kFakeMenu1, kElementContext1);
  element_tracker_->NotifyMenuItemShown(kFakeMenu1, kElementIdentifier1,
                                        kScreenBounds1);
  element_tracker_->NotifyMenuItemShown(kFakeMenu2, kElementIdentifier2,
                                        kScreenBounds2);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu2, kElementIdentifier2);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu1, kElementIdentifier1);
  element_tracker_->NotifyMenuDoneShowing(kFakeMenu1);
}

TEST_F(ElementTrackerMacTest, NestedMenusActivate) {
  ExpectedCall shown(kElementIdentifier1, kElementContext1,
                     ExpectedCall::kShown, 1);
  ExpectedCall hidden(kElementIdentifier1, kElementContext1,
                      ExpectedCall::kHidden, 1);
  ExpectedCall activated(kElementIdentifier1, kElementContext1,
                         ExpectedCall::kActivated, 0);
  ExpectedCall shown2(kElementIdentifier2, kElementContext1,
                      ExpectedCall::kShown, 1);
  ExpectedCall hidden2(kElementIdentifier2, kElementContext1,
                       ExpectedCall::kHidden, 1);
  ExpectedCall activated2(kElementIdentifier2, kElementContext1,
                          ExpectedCall::kActivated, 1);
  element_tracker_->SetParent(kFakeMenu2, kFakeMenu1);
  element_tracker_->NotifyMenuWillShow(kFakeMenu1, kElementContext1);
  element_tracker_->NotifyMenuItemShown(kFakeMenu1, kElementIdentifier1,
                                        kScreenBounds1);
  element_tracker_->NotifyMenuItemShown(kFakeMenu2, kElementIdentifier2,
                                        kScreenBounds2);
  element_tracker_->NotifyMenuItemActivated(kFakeMenu2, kElementIdentifier2);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu2, kElementIdentifier2);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu1, kElementIdentifier1);
  element_tracker_->NotifyMenuDoneShowing(kFakeMenu1);
}

TEST_F(ElementTrackerMacTest, NestMenusSendHideEvents) {
  ExpectedCall shown(kElementIdentifier1, kElementContext1,
                     ExpectedCall::kShown, 1);
  ExpectedCall hidden(kElementIdentifier1, kElementContext1,
                      ExpectedCall::kHidden, 1);
  ExpectedCall activated(kElementIdentifier1, kElementContext1,
                         ExpectedCall::kActivated, 0);
  ExpectedCall shown2(kElementIdentifier2, kElementContext1,
                      ExpectedCall::kShown, 2);
  ExpectedCall hidden2(kElementIdentifier2, kElementContext1,
                       ExpectedCall::kHidden, 2);
  ExpectedCall activated2(kElementIdentifier2, kElementContext1,
                          ExpectedCall::kActivated, 1);
  element_tracker_->SetParent(kFakeMenu2, kFakeMenu1);
  element_tracker_->NotifyMenuWillShow(kFakeMenu1, kElementContext1);
  element_tracker_->NotifyMenuItemShown(kFakeMenu1, kElementIdentifier1,
                                        kScreenBounds1);
  element_tracker_->NotifyMenuItemShown(kFakeMenu2, kElementIdentifier2,
                                        kScreenBounds2);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu2, kElementIdentifier2);
  // Note that the "hide" event isn't sent until the menu is destroyed or
  // another submenu is shown.
  element_tracker_->NotifyMenuItemShown(kFakeMenu2, kElementIdentifier2,
                                        kScreenBounds3);
  EXPECT_EQ(1, hidden2.count());
  element_tracker_->NotifyMenuItemActivated(kFakeMenu2, kElementIdentifier2);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu2, kElementIdentifier2);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu1, kElementIdentifier1);
  element_tracker_->NotifyMenuDoneShowing(kFakeMenu1);
}

TEST_F(ElementTrackerMacTest, NestMenusSendHideEvents_SecondSubmenuOpened) {
  ExpectedCall shown(kElementIdentifier1, kElementContext1,
                     ExpectedCall::kShown, 1);
  ExpectedCall hidden(kElementIdentifier1, kElementContext1,
                      ExpectedCall::kHidden, 1);
  ExpectedCall activated(kElementIdentifier1, kElementContext1,
                         ExpectedCall::kActivated, 0);
  ExpectedCall shown2(kElementIdentifier2, kElementContext1,
                      ExpectedCall::kShown, 1);
  ExpectedCall hidden2(kElementIdentifier2, kElementContext1,
                       ExpectedCall::kHidden, 1);
  ExpectedCall activated2(kElementIdentifier2, kElementContext1,
                          ExpectedCall::kActivated, 0);
  ExpectedCall shown3(kElementIdentifier3, kElementContext1,
                      ExpectedCall::kShown, 1);
  ExpectedCall hidden3(kElementIdentifier3, kElementContext1,
                       ExpectedCall::kHidden, 1);
  ExpectedCall activated3(kElementIdentifier3, kElementContext1,
                          ExpectedCall::kActivated, 1);
  element_tracker_->SetParent(kFakeMenu2, kFakeMenu1);
  element_tracker_->SetParent(kFakeMenu3, kFakeMenu1);
  element_tracker_->NotifyMenuWillShow(kFakeMenu1, kElementContext1);
  element_tracker_->NotifyMenuItemShown(kFakeMenu1, kElementIdentifier1,
                                        kScreenBounds1);
  element_tracker_->NotifyMenuItemShown(kFakeMenu2, kElementIdentifier2,
                                        kScreenBounds2);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu2, kElementIdentifier2);
  // Note that the "hide" event isn't sent until the menu is destroyed or
  // another submenu is shown.
  element_tracker_->NotifyMenuItemShown(kFakeMenu3, kElementIdentifier3,
                                        kScreenBounds3);
  EXPECT_EQ(1, hidden2.count());
  element_tracker_->NotifyMenuItemActivated(kFakeMenu3, kElementIdentifier3);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu3, kElementIdentifier3);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu1, kElementIdentifier1);
  element_tracker_->NotifyMenuDoneShowing(kFakeMenu1);
}

TEST_F(ElementTrackerMacTest, NestMenusSendHideEvents_DoubleSubmenu) {
  ExpectedCall shown(kElementIdentifier1, kElementContext1,
                     ExpectedCall::kShown, 1);
  ExpectedCall hidden(kElementIdentifier1, kElementContext1,
                      ExpectedCall::kHidden, 1);
  ExpectedCall activated(kElementIdentifier1, kElementContext1,
                         ExpectedCall::kActivated, 0);
  ExpectedCall shown2(kElementIdentifier2, kElementContext1,
                      ExpectedCall::kShown, 1);
  ExpectedCall hidden2(kElementIdentifier2, kElementContext1,
                       ExpectedCall::kHidden, 1);
  ExpectedCall activated2(kElementIdentifier2, kElementContext1,
                          ExpectedCall::kActivated, 0);
  ExpectedCall shown3(kElementIdentifier3, kElementContext1,
                      ExpectedCall::kShown, 1);
  ExpectedCall hidden3(kElementIdentifier3, kElementContext1,
                       ExpectedCall::kHidden, 1);
  ExpectedCall activated3(kElementIdentifier3, kElementContext1,
                          ExpectedCall::kActivated, 1);
  element_tracker_->SetParent(kFakeMenu2, kFakeMenu1);
  element_tracker_->SetParent(kFakeMenu3, kFakeMenu2);
  element_tracker_->NotifyMenuWillShow(kFakeMenu1, kElementContext1);
  element_tracker_->NotifyMenuItemShown(kFakeMenu1, kElementIdentifier1,
                                        kScreenBounds1);
  element_tracker_->NotifyMenuItemShown(kFakeMenu2, kElementIdentifier2,
                                        kScreenBounds2);
  element_tracker_->NotifyMenuItemShown(kFakeMenu3, kElementIdentifier3,
                                        kScreenBounds3);
  element_tracker_->NotifyMenuItemActivated(kFakeMenu3, kElementIdentifier3);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu3, kElementIdentifier3);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu2, kElementIdentifier2);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu1, kElementIdentifier1);
  element_tracker_->NotifyMenuDoneShowing(kFakeMenu1);
}

// The following tests are regression tests for the likely cause of
// crbug.com/1432480.

TEST_F(ElementTrackerMacTest, TrackerIsRobustToOutOfOrderActivation) {
  ExpectedCall shown(kElementIdentifier1, kElementContext1,
                     ExpectedCall::kShown, 1);
  ExpectedCall hidden(kElementIdentifier1, kElementContext1,
                      ExpectedCall::kHidden, 1);
  ExpectedCall activated(kElementIdentifier1, kElementContext1,
                         ExpectedCall::kActivated, 1);

  element_tracker_->NotifyMenuWillShow(kFakeMenu1, kElementContext1);
  element_tracker_->NotifyMenuItemShown(kFakeMenu1, kElementIdentifier1,
                                        kScreenBounds1);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu1, kElementIdentifier1);
  element_tracker_->NotifyMenuItemActivated(kFakeMenu1, kElementIdentifier1);
  element_tracker_->NotifyMenuDoneShowing(kFakeMenu1);
}

TEST_F(ElementTrackerMacTest,
       TrackerIsRobustToOutOfOrderActivationInNestedMenu) {
  ExpectedCall shown(kElementIdentifier1, kElementContext1,
                     ExpectedCall::kShown, 1);
  ExpectedCall hidden(kElementIdentifier1, kElementContext1,
                      ExpectedCall::kHidden, 1);
  ExpectedCall activated(kElementIdentifier1, kElementContext1,
                         ExpectedCall::kActivated, 0);
  ExpectedCall shown2(kElementIdentifier2, kElementContext1,
                      ExpectedCall::kShown, 1);
  ExpectedCall hidden2(kElementIdentifier2, kElementContext1,
                       ExpectedCall::kHidden, 1);
  ExpectedCall activated2(kElementIdentifier2, kElementContext1,
                          ExpectedCall::kActivated, 1);
  element_tracker_->SetParent(kFakeMenu2, kFakeMenu1);
  element_tracker_->NotifyMenuWillShow(kFakeMenu1, kElementContext1);
  element_tracker_->NotifyMenuItemShown(kFakeMenu1, kElementIdentifier1,
                                        kScreenBounds1);
  element_tracker_->NotifyMenuItemShown(kFakeMenu2, kElementIdentifier2,
                                        kScreenBounds2);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu2, kElementIdentifier2);
  element_tracker_->NotifyMenuItemHidden(kFakeMenu1, kElementIdentifier1);
  element_tracker_->NotifyMenuItemActivated(kFakeMenu2, kElementIdentifier2);
  element_tracker_->NotifyMenuDoneShowing(kFakeMenu1);
}

}  // namespace ui
