| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/base/cocoa/bubble_closer.h" |
| |
| #include "base/bind.h" |
| #import "ui/base/test/cocoa_helper.h" |
| #import "ui/base/test/menu_test_observer.h" |
| #import "ui/events/test/cocoa_test_event_utils.h" |
| |
| namespace ui { |
| |
| class BubbleCloserTest : public CocoaTest { |
| public: |
| enum Button { LEFT, RIGHT }; |
| enum InOrOut { INSIDE, OUTSIDE }; |
| |
| BubbleCloserTest() = default; |
| |
| void SetUp() override { |
| CocoaTest::SetUp(); |
| bubble_window_.reset([[NSWindow alloc] |
| initWithContentRect:NSMakeRect(100, 100, 320, 200) |
| styleMask:NSBorderlessWindowMask |
| backing:NSBackingStoreBuffered |
| defer:NO]); |
| [bubble_window_ setReleasedWhenClosed:NO]; // Owned by scoped_nsobject. |
| [bubble_window_ makeKeyAndOrderFront:nil]; |
| bubble_closer_ = std::make_unique<BubbleCloser>( |
| bubble_window_, |
| base::BindRepeating([](int* i) { *i += 1; }, &click_outside_count_)); |
| } |
| |
| void TearDown() override { |
| [bubble_window_ close]; |
| bubble_closer_ = nullptr; |
| bubble_window_.reset(); |
| CocoaTest::TearDown(); |
| } |
| |
| void SendClick(Button left_or_right, InOrOut in_or_out) { |
| NSWindow* window = in_or_out == INSIDE ? bubble_window_ : test_window(); |
| NSEvent* event = |
| left_or_right == LEFT |
| ? cocoa_test_event_utils::LeftMouseDownAtPointInWindow( |
| NSMakePoint(10, 10), window) |
| : cocoa_test_event_utils::RightMouseDownAtPointInWindow( |
| NSMakePoint(10, 10), window); |
| [NSApp sendEvent:event]; |
| } |
| |
| void ResetCloser() { bubble_closer_ = nullptr; } |
| |
| protected: |
| int click_outside_count() const { return click_outside_count_; } |
| NSWindow* bubble_window() { return bubble_window_; } |
| bool IsCloserReset() const { return !bubble_closer_; } |
| |
| private: |
| base::scoped_nsobject<NSWindow> bubble_window_; |
| std::unique_ptr<BubbleCloser> bubble_closer_; |
| int click_outside_count_ = 0; |
| |
| DISALLOW_COPY_AND_ASSIGN(BubbleCloserTest); |
| }; |
| |
| // Test for lifetime issues around NSEvent monitors. |
| TEST_F(BubbleCloserTest, SecondBubbleCloser) { |
| auto resetter = [](BubbleCloserTest* me) { me->ResetCloser(); }; |
| auto deleter = std::make_unique<BubbleCloser>( |
| bubble_window(), base::BindRepeating(resetter, this)); |
| SendClick(LEFT, OUTSIDE); |
| |
| // The order is non-deterministic, so click_outside_count() may not change. |
| // But either way, the closer should have been reset. |
| EXPECT_TRUE(IsCloserReset()); |
| } |
| |
| // Test that clicking outside the window fires the callback and clicking inside |
| // does not. |
| TEST_F(BubbleCloserTest, ClickInsideAndOut) { |
| EXPECT_EQ(0, click_outside_count()); |
| SendClick(LEFT, OUTSIDE); |
| EXPECT_EQ(1, click_outside_count()); |
| SendClick(RIGHT, OUTSIDE); |
| EXPECT_EQ(2, click_outside_count()); |
| SendClick(LEFT, INSIDE); |
| EXPECT_EQ(2, click_outside_count()); |
| SendClick(RIGHT, INSIDE); |
| EXPECT_EQ(2, click_outside_count()); |
| } |
| |
| // Test that right-clicking the window to display a context menu works. |
| TEST_F(BubbleCloserTest, RightClickOutsideClosesWithContextMenu) { |
| base::scoped_nsobject<NSMenu> context_menu( |
| [[NSMenu alloc] initWithTitle:@""]); |
| [context_menu addItemWithTitle:@"ContextMenuTest" |
| action:nil |
| keyEquivalent:@""]; |
| |
| // Set the menu as the contextual menu of contentView of test_window(). |
| [[test_window() contentView] setMenu:context_menu]; |
| |
| base::scoped_nsobject<MenuTestObserver> menu_observer( |
| [[MenuTestObserver alloc] initWithMenu:context_menu]); |
| [menu_observer setCloseAfterOpening:YES]; |
| [menu_observer setOpenCallback:^(MenuTestObserver* observer) { |
| // Verify click is seen when contextual menu is open. |
| EXPECT_TRUE([observer isOpen]); |
| EXPECT_EQ(1, click_outside_count()); |
| }]; |
| |
| EXPECT_FALSE([menu_observer isOpen]); |
| EXPECT_FALSE([menu_observer didOpen]); |
| |
| EXPECT_EQ(0, click_outside_count()); |
| |
| // RightMouseDown in test_window() would close the bubble window and then |
| // display the contextual menu. |
| SendClick(RIGHT, OUTSIDE); |
| |
| // When we got here, menu has already run its RunLoop. |
| EXPECT_EQ(1, click_outside_count()); |
| |
| EXPECT_FALSE([menu_observer isOpen]); |
| EXPECT_TRUE([menu_observer didOpen]); |
| } |
| |
| } // namespace ui |