chromium/chrome/browser/ui/ash/picker/picker_lacros_omnibox_search_provider_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 "chrome/browser/ui/ash/picker/picker_lacros_omnibox_search_provider.h"

#include <memory>
#include <string>
#include <utility>

#include "base/auto_reset.h"
#include "base/check.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "chrome/browser/ash/crosapi/search_controller_ash.h"
#include "chrome/browser/ash/crosapi/search_controller_factory_ash.h"
#include "chromeos/crosapi/mojom/launcher_search.mojom-forward.h"
#include "chromeos/crosapi/mojom/launcher_search.mojom.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

// TODO: b/326147929 - Share this code with `crosapi::SearchControllerAsh` and
// `crosapi::SearchControllerFactoryAsh` unit tests.
class TestMojomSearchController : public crosapi::mojom::SearchController {
 public:
  explicit TestMojomSearchController(bool bookmarks,
                                     bool history,
                                     bool open_tabs)
      : bookmarks_(bookmarks), history_(history), open_tabs_(open_tabs) {}
  ~TestMojomSearchController() override = default;

  void Bind(mojo::PendingReceiver<crosapi::mojom::SearchController> receiver) {
    receiver_.Bind(std::move(receiver));
  }

  void RunUntilSearch() {
    base::RunLoop loop;
    base::AutoReset<base::RepeatingClosure> quit_loop(&search_callback_,
                                                      loop.QuitClosure());
    loop.Run();
  }

  const std::u16string& last_query() { return last_query_; }
  bool bookmarks() const { return bookmarks_; }
  bool history() const { return history_; }
  bool open_tabs() const { return open_tabs_; }

 private:
  void Search(const std::u16string& query, SearchCallback callback) override {
    last_query_ = query;
    // We are not interested in the search callback in the class under test, but
    // we still need to run the callback with a valid receiver.
    std::move(callback).Run(
        mojo::AssociatedRemote<crosapi::mojom::SearchResultsPublisher>()
            .BindNewEndpointAndPassReceiver());

    search_callback_.Run();
  }

  base::RepeatingClosure search_callback_ = base::DoNothing();

  mojo::Receiver<crosapi::mojom::SearchController> receiver_{this};
  bool bookmarks_;
  bool history_;
  bool open_tabs_;
  std::u16string last_query_;
};

class TestMojomSearchControllerFactory
    : public crosapi::mojom::SearchControllerFactory {
 public:
  TestMojomSearchControllerFactory() = default;
  ~TestMojomSearchControllerFactory() override = default;

  mojo::PendingRemote<crosapi::mojom::SearchControllerFactory> BindToRemote() {
    return receiver_.BindNewPipeAndPassRemote();
  }

  void RunUntilCreateSearchController() {
    base::RunLoop loop;
    base::AutoReset<base::RepeatingClosure> quit_loop(
        &create_search_controller_callback_, loop.QuitClosure());
    loop.Run();
  }

  std::unique_ptr<TestMojomSearchController> TakeLastTestController() {
    return std::move(last_test_controller_);
  }

  // cam::SearchControllerFactory overrides:
  void CreateSearchControllerPicker(
      mojo::PendingReceiver<crosapi::mojom::SearchController> controller,
      bool bookmark,
      bool history,
      bool open_tab) override {
    CHECK(!last_test_controller_);
    last_test_controller_ = std::make_unique<TestMojomSearchController>(
        bookmark, history, open_tab);
    last_test_controller_->Bind(std::move(controller));

    create_search_controller_callback_.Run();
  }

 private:
  base::RepeatingClosure create_search_controller_callback_ = base::DoNothing();

  std::unique_ptr<TestMojomSearchController> last_test_controller_;
  mojo::Receiver<crosapi::mojom::SearchControllerFactory> receiver_{this};
};

using PickerLacrosOmniboxSearchProviderTest = ::testing::Test;

TEST_F(PickerLacrosOmniboxSearchProviderTest,
       ControllerIsNullptrWhenNotBoundOnConstruction) {
  base::test::SingleThreadTaskEnvironment environment;
  crosapi::SearchControllerFactoryAsh factory;
  ASSERT_FALSE(factory.IsBound());

  PickerLacrosOmniboxSearchProvider provider(&factory, false, false, false);
  crosapi::SearchControllerAsh* controller = provider.GetController();

  EXPECT_FALSE(controller);
}

TEST_F(PickerLacrosOmniboxSearchProviderTest,
       ControllerIsNonNullWhenBoundOnConstruction) {
  base::test::SingleThreadTaskEnvironment environment;
  crosapi::SearchControllerFactoryAsh factory;
  TestMojomSearchControllerFactory mojom_factory;
  factory.BindRemote(mojom_factory.BindToRemote());
  ASSERT_TRUE(factory.IsBound());

  PickerLacrosOmniboxSearchProvider provider(&factory, false, false, false);
  crosapi::SearchControllerAsh* controller = provider.GetController();

  EXPECT_TRUE(controller);
}

TEST_F(PickerLacrosOmniboxSearchProviderTest,
       ControllerIsNonNullWhenBoundAfterConstruction) {
  base::test::SingleThreadTaskEnvironment environment;
  crosapi::SearchControllerFactoryAsh factory;
  ASSERT_FALSE(factory.IsBound());

  PickerLacrosOmniboxSearchProvider provider(&factory, false, false, false);
  TestMojomSearchControllerFactory mojom_factory;
  factory.BindRemote(mojom_factory.BindToRemote());
  ASSERT_TRUE(factory.IsBound());
  crosapi::SearchControllerAsh* controller = provider.GetController();

  EXPECT_TRUE(controller);
}

TEST_F(PickerLacrosOmniboxSearchProviderTest,
       CallsFactoryOnConstructionIfBound) {
  base::test::SingleThreadTaskEnvironment environment;
  crosapi::SearchControllerFactoryAsh factory;
  TestMojomSearchControllerFactory mojom_factory;
  factory.BindRemote(mojom_factory.BindToRemote());
  ASSERT_TRUE(factory.IsBound());

  PickerLacrosOmniboxSearchProvider provider(&factory, false, false, false);
  mojom_factory.RunUntilCreateSearchController();

  EXPECT_TRUE(mojom_factory.TakeLastTestController());
}

TEST_F(PickerLacrosOmniboxSearchProviderTest,
       CallsFactoryAfterConstructionWhenBound) {
  base::test::SingleThreadTaskEnvironment environment;
  crosapi::SearchControllerFactoryAsh factory;
  ASSERT_FALSE(factory.IsBound());

  PickerLacrosOmniboxSearchProvider provider(&factory, false, false, false);
  TestMojomSearchControllerFactory mojom_factory;
  factory.BindRemote(mojom_factory.BindToRemote());
  ASSERT_TRUE(factory.IsBound());
  mojom_factory.RunUntilCreateSearchController();

  EXPECT_TRUE(mojom_factory.TakeLastTestController());
}

TEST_F(PickerLacrosOmniboxSearchProviderTest, CallsFactoryWhenRebound) {
  base::test::SingleThreadTaskEnvironment environment;
  crosapi::SearchControllerFactoryAsh factory;
  PickerLacrosOmniboxSearchProvider provider(&factory, false, false, false);
  {
    // Connect a remote factory...
    TestMojomSearchControllerFactory mojom_factory;
    factory.BindRemote(mojom_factory.BindToRemote());
    mojom_factory.RunUntilCreateSearchController();
    ASSERT_TRUE(mojom_factory.TakeLastTestController());
    // ...then disconnect it.
  }
  // Ensure that the factory receives the disconnection so it can be rebound.
  // TODO: b/326147929 - Use a `QuitClosure` for this.
  base::RunLoop().RunUntilIdle();

  // Rebind another remote factory.
  TestMojomSearchControllerFactory mojom_factory;
  factory.BindRemote(mojom_factory.BindToRemote());
  mojom_factory.RunUntilCreateSearchController();

  EXPECT_TRUE(mojom_factory.TakeLastTestController());
}

TEST_F(PickerLacrosOmniboxSearchProviderTest, DoesNotCallFactoryMultipleTimes) {
  base::test::SingleThreadTaskEnvironment environment;
  crosapi::SearchControllerFactoryAsh factory;
  TestMojomSearchControllerFactory mojom_factory;
  factory.BindRemote(mojom_factory.BindToRemote());

  PickerLacrosOmniboxSearchProvider provider(&factory, false, false, false);
  mojom_factory.RunUntilCreateSearchController();
  ASSERT_TRUE(mojom_factory.TakeLastTestController());
  (void)provider.GetController();
  (void)provider.GetController();
  // Ensure that the factory is NOT called.
  // There is no way to have a callback for "function is not called", so we need
  // to use `RunUntilIdle` here.
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(mojom_factory.TakeLastTestController());
}

TEST_F(PickerLacrosOmniboxSearchProviderTest,
       ControllerSendsToRemoteControllerFromFactory) {
  base::test::SingleThreadTaskEnvironment environment;
  crosapi::SearchControllerFactoryAsh factory;
  TestMojomSearchControllerFactory mojom_factory;
  factory.BindRemote(mojom_factory.BindToRemote());
  PickerLacrosOmniboxSearchProvider provider(&factory, false, false, false);
  crosapi::SearchControllerAsh* controller = provider.GetController();
  ASSERT_TRUE(controller);
  mojom_factory.RunUntilCreateSearchController();
  std::unique_ptr<TestMojomSearchController> mojom_controller =
      mojom_factory.TakeLastTestController();
  ASSERT_TRUE(mojom_controller);

  controller->Search(u"cat", base::DoNothing());
  mojom_controller->RunUntilSearch();

  EXPECT_EQ(mojom_controller->last_query(), u"cat");
}

TEST_F(PickerLacrosOmniboxSearchProviderTest,
       RemoteControllerHasCorrectProviderTypes) {
  base::test::SingleThreadTaskEnvironment environment;
  crosapi::SearchControllerFactoryAsh factory;
  TestMojomSearchControllerFactory mojom_factory;
  factory.BindRemote(mojom_factory.BindToRemote());

  PickerLacrosOmniboxSearchProvider provider(
      &factory, /*bookmarks=*/true, /*history=*/false, /*open_tabs=*/true);
  mojom_factory.RunUntilCreateSearchController();

  std::unique_ptr<TestMojomSearchController> mojom_controller =
      mojom_factory.TakeLastTestController();
  ASSERT_TRUE(mojom_controller);
  EXPECT_TRUE(mojom_controller->bookmarks());
  EXPECT_FALSE(mojom_controller->history());
  EXPECT_TRUE(mojom_controller->open_tabs());
}

}  // namespace