chromium/ash/picker/views/picker_widget_unittest.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/picker/views/picker_widget.h"

#include <memory>
#include <string_view>

#include "ash/picker/metrics/picker_session_metrics.h"
#include "ash/picker/model/picker_action_type.h"
#include "ash/picker/model/picker_caps_lock_position.h"
#include "ash/picker/views/picker_preview_bubble.h"
#include "ash/picker/views/picker_view.h"
#include "ash/picker/views/picker_view_delegate.h"
#include "ash/test/ash_test_base.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/window.h"
#include "ui/display/screen.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/widget_utils.h"

namespace ash {
namespace {

using ::testing::ElementsAre;
using ::testing::Truly;

constexpr gfx::Rect kDefaultAnchorBounds(200, 100, 0, 10);

class FakePickerViewDelegate : public PickerViewDelegate {
 public:
  // PickerViewDelegate:
  std::vector<PickerCategory> GetAvailableCategories() override { return {}; }
  void GetZeroStateSuggestedResults(
      SuggestedResultsCallback callback) override {}
  void GetResultsForCategory(PickerCategory category,
                             SearchResultsCallback callback) override {}
  void StartSearch(std::u16string_view query,
                   std::optional<PickerCategory> category,
                   SearchResultsCallback callback) override {}
  void StopSearch() override {}
  void StartEmojiSearch(std::u16string_view query,
                        EmojiSearchResultsCallback callback) override {}
  void CloseWidgetThenInsertResultOnNextFocus(
      const PickerSearchResult& result) override {}
  void OpenResult(const PickerSearchResult& result) override {}
  void ShowEmojiPicker(ui::EmojiPickerCategory category,
                       std::u16string_view query) override {}
  void ShowEditor(std::optional<std::string> preset_query_id,
                  std::optional<std::string> freeform_text) override {}
  PickerAssetFetcher* GetAssetFetcher() override { return nullptr; }
  PickerSessionMetrics& GetSessionMetrics() override {
    return session_metrics_;
  }
  PickerActionType GetActionForResult(
      const PickerSearchResult& result) override {
    return PickerActionType::kInsert;
  }
  std::vector<PickerEmojiResult> GetSuggestedEmoji() override { return {}; }
  bool IsGifsEnabled() override { return true; }
  PickerModeType GetMode() override { return PickerModeType::kNoSelection; }
  PickerCapsLockPosition GetCapsLockPosition() override {
    return PickerCapsLockPosition::kTop;
  }

 private:
  PickerSessionMetrics session_metrics_;
};

using PickerWidgetTest = AshTestBase;

TEST_F(PickerWidgetTest, CreateWidgetHasCorrectHierarchy) {
  FakePickerViewDelegate delegate;
  auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);

  // Widget should contain a NonClientView, which has a NonClientFrameView for
  // borders and shadows, and a ClientView with a sole child of the PickerView.
  ASSERT_TRUE(widget);
  ASSERT_TRUE(widget->non_client_view());
  ASSERT_TRUE(widget->non_client_view()->frame_view());
  ASSERT_TRUE(widget->non_client_view()->client_view());
  EXPECT_THAT(widget->non_client_view()->client_view()->children(),
              ElementsAre(Truly(views::IsViewClass<PickerView>)));
}

TEST_F(PickerWidgetTest, CreateWidgetHasCorrectBorder) {
  FakePickerViewDelegate delegate;
  auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);

  EXPECT_TRUE(widget->non_client_view()->frame_view()->GetBorder());
}

TEST_F(PickerWidgetTest, ClickingOutsideClosesPickerWidget) {
  FakePickerViewDelegate delegate;
  auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
  widget->Show();

  gfx::Point point_outside_widget = widget->GetWindowBoundsInScreen().origin();
  point_outside_widget.Offset(-10, -10);
  GetEventGenerator()->MoveMouseTo(point_outside_widget);
  GetEventGenerator()->ClickLeftButton();

  EXPECT_TRUE(widget->IsClosed());
}

TEST_F(PickerWidgetTest, LosingFocusClosesPickerWidget) {
  // Create something other than the picker to focus.
  auto window = CreateTestWindow();
  window->Show();

  // Create the fake picker and make sure it has focus.
  FakePickerViewDelegate delegate;
  auto picker_widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
  picker_widget->Show();
  EXPECT_THAT(picker_widget->GetFocusManager()->GetFocusedView(),
              testing::NotNull());

  // Focus the other Widget and expect the picker to have closed.
  window->Focus();
  EXPECT_TRUE(window->HasFocus());

  EXPECT_TRUE(picker_widget->IsClosed());
}

TEST_F(PickerWidgetTest, PreviewBubbleDoesNotStealFocusPickerWidget) {
  std::unique_ptr<views::Widget> anchor_widget = CreateFramelessTestWidget();
  anchor_widget->SetContentsView(std::make_unique<views::View>());

  // Create the PickerWidget and make sure it has focus.
  FakePickerViewDelegate delegate;
  auto picker_widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
  picker_widget->Show();

  // Show bubble widget and expect the PickerWidget to not close.
  views::View* bubble_view =
      new PickerPreviewBubbleView(anchor_widget->GetContentsView());
  bubble_view->GetWidget()->Show();

  EXPECT_FALSE(picker_widget->IsClosed());

  bubble_view->GetWidget()->CloseNow();
}

TEST_F(PickerWidgetTest, CreatesCenteredWidget) {
  FakePickerViewDelegate delegate;
  auto widget =
      PickerWidget::CreateCentered(&delegate, gfx::Rect(10, 10, 10, 10));
  widget->Show();

  EXPECT_EQ(widget->GetWindowBoundsInScreen().CenterPoint(),
            display::Screen::GetScreen()
                ->GetPrimaryDisplay()
                .work_area()
                .CenterPoint());
}

}  // namespace
}  // namespace ash