text: // Copyright 2020 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 "chrome/browser/ui/commander/commander_controller.h"
  
  #include "base/callback.h"
  #include "base/macros.h"
  #include "base/run_loop.h"
  #include "base/strings/string16.h"
  #include "chrome/browser/ui/commander/command_source.h"
  #include "chrome/browser/ui/commander/commander_view_model.h"
  #include "chrome/test/base/browser_with_test_window_test.h"
  
  namespace commander {
  
  namespace {
  
  class TestCommandSource : public CommandSource {
   public:
    using GetCommandsHandler =
        base::RepeatingCallback<CommandResults(const base::string16&,
                                               Browser* browser)>;
    explicit TestCommandSource(GetCommandsHandler handler)
        : handler_(std::move(handler)) {}
    ~TestCommandSource() override = default;
  
    CommandResults GetCommands(const base::string16& input,
                               Browser* browser) const override {
      invocations_.push_back(input);
      return handler_.Run(input, browser);
    }
  
    const std::vector<base::string16>& invocations() const {
      return invocations_;
    }
  
   private:
    mutable std::vector<base::string16> invocations_;
    GetCommandsHandler handler_;
  };
  
  std::unique_ptr<TestCommandSource> CreateNoOpCommandSource() {
    return std::make_unique<TestCommandSource>(base::BindRepeating(
        [](const base::string16&,
           Browser* browser) -> CommandSource::CommandResults { return {}; }));
  }
  
  std::unique_ptr<CommandItem> CreateNoOpCommandItem(const base::string16& title,
                                                     double score) {
    auto item = std::make_unique<CommandItem>();
    item->title = title;
    item->score = score;
    item->matched_ranges.emplace_back(0, title.size());
    item->command = base::BindOnce([]() {});
    return item;
  }
  
  template <typename T>
  std::unique_ptr<CommandItem> CreateCompositeCommandItem(
      const base::string16& title,
      double scope) {}
  
  TestCommandSource* AddSource(
      std::vector<std::unique_ptr<CommandSource>>* sources,
      std::unique_ptr<TestCommandSource> source) {
    TestCommandSource* bare_ptr = source.get();
    sources->push_back(std::move(source));
    return bare_ptr;
  }
  
  }  // namespace
  
  class CommanderControllerTest : public BrowserWithTestWindowTest {
   public:
    class TestBackend : public CommanderBackend {
     public:
      explicit TestBackend(CommanderControllerTest* owner) {
        owner->SetTestBackend(this);
      }
      void OnTextChanged(const base::string16& text, Browser* browser) override {
        text_changed_invocations_.push_back(text);
      }
      void OnCommandSelected(size_t command_index, int result_set_id) override {
        command_selected_invocations_.push_back(command_index);
      }
      void SetUpdateCallback(ViewModelUpdateCallback callback) override {
        callback_ = std::move(callback);
      }
  
      void CallCallback() {
        CommanderViewModel vm;
        callback_.Run(vm);
      }
      const std::vector<base::string16> text_changed_invocations() {
        return text_changed_invocations_;
      }
      const std::vector<size_t> command_selected_invocations() {
        return command_selected_invocations_;
      }
  
     private:
      ViewModelUpdateCallback callback_;
      std::vector<base::string16> text_changed_invocations_;
      std::vector<size_t> command_selected_invocations_;
    };
  
    void SetUp() override {
      BrowserWithTestWindowTest::SetUp();
      expected_count_ = 0;
    }
  
    void ExpectViewModelCallbackCalls(int expected_count) {
      expected_count_ += expected_count;
    }
    void WaitForExpectedCallbacks() {
      if (expected_count_ <= 0)
        return;
      if (!run_loop_.get() || !run_loop_->running()) {
        run_loop_ = std::make_unique<base::RunLoop>();
        run_loop_->Run();
      }
    }
    void OnViewModelUpdated(CommanderViewModel view_model) {
      received_view_models_.push_back(view_model);
      if (expected_count_ > 0) {
        expected_count_--;
        if (run_loop_.get() && expected_count_ == 0)
          run_loop_->Quit();
      }
    }
  
    void SetTestBackend(TestBackend* test_backend) {
      test_backend_ = test_backend;
    }
  
   protected:
    std::unique_ptr<base::RunLoop> run_loop_;
    int expected_count_;
    std::vector<CommanderViewModel> received_view_models_;
    TestBackend* test_backend_;
  };
  
  class ViewModelCallbackWaiter {
   public:
    explicit ViewModelCallbackWaiter(CommanderControllerTest* test, int count = 1)
        : test_(test) {
      test_->ExpectViewModelCallbackCalls(count);
    }
  
    ~ViewModelCallbackWaiter() { test_->WaitForExpectedCallbacks(); }
  
   private:
    CommanderControllerTest* test_;
  };
  
  TEST_F(CommanderControllerTest, PassesInputToCommandSourcesOnTextChanged) {
    std::vector<std::unique_ptr<CommandSource>> sources;
    TestCommandSource* first = AddSource(&sources, CreateNoOpCommandSource());
    TestCommandSource* second = AddSource(&sources, CreateNoOpCommandSource());
  
    auto controller =
        CommanderController::CreateWithSourcesForTesting(std::move(sources));
    controller->SetUpdateCallback(base::BindRepeating(
        &CommanderControllerTest::OnViewModelUpdated, base::Unretained(this)));
  
    EXPECT_EQ(first->invocations().size(), 0u);
    EXPECT_EQ(second->invocations().size(), 0u);
  
    base::string16 input = base::ASCIIToUTF16("foobar");
    controller->OnTextChanged(input, browser());
  
    EXPECT_EQ(first->invocations().size(), 1u);
    EXPECT_EQ(second->invocations().size(), 1u);
  
    EXPECT_EQ(first->invocations().back(), input);
    EXPECT_EQ(second->invocations().back(), input);
  }
  
  TEST_F(CommanderControllerTest, ResultSetIdsDifferAcrossCalls) {
    std::vector<std::unique_ptr<CommandSource>> sources;
    ignore_result(AddSource(&sources, CreateNoOpCommandSource()));
    base::RunLoop run_loop;
    auto controller =
        CommanderController::CreateWithSourcesForTesting(std::move(sources));
    controller->SetUpdateCallback(base::BindRepeating(
        &CommanderControllerTest::OnViewModelUpdated, base::Unretained(this)));
    {
      ViewModelCallbackWaiter waiter(this);
      controller->OnTextChanged(base::ASCIIToUTF16("foobar"), browser());
    }
    // Assert since we're accessing an element.
    ASSERT_EQ(received_view_models_.size(), 1u);
    int first_id = received_view_models_.back().result_set_id;
  
    {
      ViewModelCallbackWaiter waiter(this);
      controller->OnTextChanged(base::ASCIIToUTF16("barfoo"), browser());
    }
    EXPECT_EQ(received_view_models_.size(), 2u);
    EXPECT_NE(received_view_models_.back().result_set_id, first_id);
  }
  
  TEST_F(CommanderControllerTest, ViewModelAggregatesResults) {
    std::vector<std::unique_ptr<CommandSource>> sources;
    auto first = std::make_unique<TestCommandSource>(
        base::BindRepeating([](const base::string16&, Browser* browser) {
          CommandSource::CommandResults result;
          result.push_back(
              CreateNoOpCommandItem(base::ASCIIToUTF16("first"), 100));
          return result;
        }));
    auto second = std::make_unique<TestCommandSource>(
        base::BindRepeating([](const base::string16&, Browser* browser) {
          CommandSource::CommandResults result;
          result.push_back(
              CreateNoOpCommandItem(base::ASCIIToUTF16("second"), 99));
          return result;
        }));
    sources.push_back(std::move(first));
    sources.push_back(std::move(second));
  
    auto controller =
        CommanderController::CreateWithSourcesForTesting(std::move(sources));
    controller->SetUpdateCallback(base::BindRepeating(
        &CommanderControllerTest::OnViewModelUpdated, base::Unretained(this)));
  
    {
      ViewModelCallbackWaiter waiter(this);
      controller->OnTextChanged(base::ASCIIToUTF16("foobar"), browser());
    }
    ASSERT_EQ(received_view_models_.size(), 1u);
    CommanderViewModel model = received_view_models_.back();
    ASSERT_EQ(model.items.size(), 2u);
    EXPECT_EQ(model.items[0].title, base::ASCIIToUTF16("first"));
    EXPECT_EQ(model.items[1].title, base::ASCIIToUTF16("second"));
  }
  
  // TODO(lgrey): This will need to change when scoring gets more sophisticated
  // than a simple sort.
  TEST_F(CommanderControllerTest, ViewModelSortsResults) {
    std::vector<std::unique_ptr<CommandSource>> sources;
    auto first = std::make_unique<TestCommandSource>(
        base::BindRepeating([](const base::string16&, Browser* browser) {
          CommandSource::CommandResults result;
          result.push_back(
              CreateNoOpCommandItem(base::ASCIIToUTF16("third"), 98));
          result.push_back(
              CreateNoOpCommandItem(base::ASCIIToUTF16("first"), 100));
          result.push_back(
              CreateNoOpCommandItem(base::ASCIIToUTF16("fourth"), 90));
  
          return result;
        }));
    auto second = std::make_unique<TestCommandSource>(
        base::BindRepeating([](const base::string16&, Browser* browser) {
          CommandSource::CommandResults result;
          result.push_back(
              CreateNoOpCommandItem(base::ASCIIToUTF16("second"), 99));
          result.push_back(CreateNoOpCommandItem(base::ASCIIToUTF16("fifth"), 1));
          return result;
        }));
    sources.push_back(std::move(first));
    sources.push_back(std::move(second));
  
    auto controller =
        CommanderController::CreateWithSourcesForTesting(std::move(sources));
    controller->SetUpdateCallback(base::BindRepeating(
        &CommanderControllerTest::OnViewModelUpdated, base::Unretained(this)));
  
    {
      ViewModelCallbackWaiter waiter(this);
      controller->OnTextChanged(base::ASCIIToUTF16("foobar"), browser());
    }
    ASSERT_EQ(received_view_models_.size(), 1u);
    CommanderViewModel model = received_view_models_.back();
    ASSERT_EQ(model.items.size(), 5u);
    EXPECT_EQ(model.items[0].title, base::ASCIIToUTF16("first"));
    EXPECT_EQ(model.items[1].title, base::ASCIIToUTF16("second"));
    EXPECT_EQ(model.items[2].title, base::ASCIIToUTF16("third"));
    EXPECT_EQ(model.items[3].title, base::ASCIIToUTF16("fourth"));
    EXPECT_EQ(model.items[4].title, base::ASCIIToUTF16("fifth"));
  }
  
  TEST_F(CommanderControllerTest, ViewModelRetainsBoldRanges) {
    std::vector<std::unique_ptr<CommandSource>> sources;
    auto source = std::make_unique<TestCommandSource>(
        base::BindRepeating([=](const base::string16&, Browser* browser) {
          auto first = CreateNoOpCommandItem(base::ASCIIToUTF16("first"), 100);
          auto second = CreateNoOpCommandItem(base::ASCIIToUTF16("second"), 99);
          first->matched_ranges.clear();
          first->matched_ranges.emplace_back(0, 2);
          first->matched_ranges.emplace_back(4, 1);
          second->matched_ranges.clear();
          second->matched_ranges.emplace_back(1, 4);
          CommandSource::CommandResults result;
          result.push_back(std::move(first));
          result.push_back(std::move(second));
          return result;
        }));
    sources.push_back(std::move(source));
    auto controller =
        CommanderController::CreateWithSourcesForTesting(std::move(sources));
    controller->SetUpdateCallback(base::BindRepeating(
        &CommanderControllerTest::OnViewModelUpdated, base::Unretained(this)));
  
    {
      ViewModelCallbackWaiter waiter(this);
      controller->OnTextChanged(base::ASCIIToUTF16("foobar"), browser());
    }
    ASSERT_EQ(received_view_models_.size(), 1u);
    CommanderViewModel model = received_view_models_.back();
    // Ensure |first| is at index 0;
    EXPECT_EQ(model.items[0].title, base::ASCIIToUTF16("first"));
    std::vector<gfx::Range> first_ranges = {gfx::Range(0, 2), gfx::Range(4, 1)};
    std::vector<gfx::Range> second_ranges = {gfx::Range(1, 4)};
    EXPECT_EQ(model.items[0].matched_ranges, first_ranges);
    EXPECT_EQ(model.items[1].matched_ranges, second_ranges);
  }
  
  TEST_F(CommanderControllerTest, OnCommandSelectedInvokesOneShotCommand) {
    std::vector<std::unique_ptr<CommandSource>> sources;
    bool first_called = false;
    bool second_called = false;
    auto source = std::make_unique<TestCommandSource>(base::BindRepeating(
        [](bool* first_called_ptr, bool* second_called_ptr, const base::string16&,
           Browser* browser) {
          auto first = CreateNoOpCommandItem(base::ASCIIToUTF16("first"), 100);
          auto second = CreateNoOpCommandItem(base::ASCIIToUTF16("second"), 99);
          first->command = base::BindOnce([](bool* called) { *called = true; },
                                          first_called_ptr);
          second->command = base::BindOnce([](bool* called) { *called = true; },
                                           second_called_ptr);
          CommandSource::CommandResults result;
          result.push_back(std::move(first));
          result.push_back(std::move(second));
          return result;
        },
        &first_called, &second_called));
    sources.push_back(std::move(source));
    auto controller =
        CommanderController::CreateWithSourcesForTesting(std::move(sources));
    controller->SetUpdateCallback(base::BindRepeating(
        &CommanderControllerTest::OnViewModelUpdated, base::Unretained(this)));
  
    {
      ViewModelCallbackWaiter waiter(this);
      controller->OnTextChanged(base::ASCIIToUTF16("foobar"), browser());
    }
    ASSERT_EQ(received_view_models_.size(), 1u);
    CommanderViewModel model = received_view_models_.back();
    // Ensure |first| is at index 0;
    EXPECT_EQ(model.items[0].title, base::ASCIIToUTF16("first"));
  
    {
      ViewModelCallbackWaiter waiter(this);
      controller->OnCommandSelected(0, model.result_set_id);
    }
    EXPECT_TRUE(first_called);
    EXPECT_FALSE(second_called);
    EXPECT_EQ(received_view_models_.size(), 2u);
    EXPECT_EQ(received_view_models_.back().action,
              CommanderViewModel::Action::kClose);
  }
  
  TEST_F(CommanderControllerTest, NoActionOnIncorrectResultId) {
    std::vector<std::unique_ptr<CommandSource>> sources;
    bool item_called = false;
    auto source = std::make_unique<TestCommandSource>(base::BindRepeating(
        [](bool* called_ptr, const base::string16&, Browser* browser) {
          auto item = CreateNoOpCommandItem(base::ASCIIToUTF16("first"), 100);
          item->command =
              base::BindOnce([](bool* called) { *called = true; }, called_ptr);
          CommandSource::CommandResults result;
          result.push_back(std::move(item));
          return result;
        },
        &item_called));
    sources.push_back(std::move(source));
    auto controller =
        CommanderController::CreateWithSourcesForTesting(std::move(sources));
    controller->SetUpdateCallback(base::BindRepeating(
        &CommanderControllerTest::OnViewModelUpdated, base::Unretained(this)));
  
    {
      ViewModelCallbackWaiter waiter(this);
      controller->OnTextChanged(base::ASCIIToUTF16("foobar"), browser());
    }
    ASSERT_EQ(received_view_models_.size(), 1u);
    CommanderViewModel model = received_view_models_.back();
  
    controller->OnCommandSelected(0, model.result_set_id - 1);
  
    EXPECT_FALSE(item_called);
  }
  
  TEST_F(CommanderControllerTest, NoActionOnOOBIndex) {
    std::vector<std::unique_ptr<CommandSource>> sources;
    bool item_called = false;
    auto source = std::make_unique<TestCommandSource>(base::BindRepeating(
        [](bool* called_ptr, const base::string16&, Browser* browser) {
          auto item = CreateNoOpCommandItem(base::ASCIIToUTF16("first"), 100);
          item->command =
              base::BindOnce([](bool* called) { *called = true; }, called_ptr);
          CommandSource::CommandResults result;
          result.push_back(std::move(item));
          return result;
        },
        &item_called));
    sources.push_back(std::move(source));
    auto controller =
        CommanderController::CreateWithSourcesForTesting(std::move(sources));
    controller->SetUpdateCallback(base::BindRepeating(
        &CommanderControllerTest::OnViewModelUpdated, base::Unretained(this)));
  
    {
      ViewModelCallbackWaiter waiter(this);
      controller->OnTextChanged(base::ASCIIToUTF16("foobar"), browser());
    }
    ASSERT_EQ(received_view_models_.size(), 1u);
    CommanderViewModel model = received_view_models_.back();
  
    controller->OnCommandSelected(1, model.result_set_id);
  
    EXPECT_FALSE(item_called);
  }
  
  TEST_F(CommanderControllerTest, InvokingCompositeCommandSendsPrompt) {
    auto source = std::make_unique<TestCommandSource>(base::BindRepeating(
        [](CommanderControllerTest* instance, const base::string16&,
           Browser* browser) -> CommandSource::CommandResults {
          auto item = std::make_unique<CommandItem>();
          item->title = base::ASCIIToUTF16("Do something...");
          item->score = 100;
          item->matched_ranges.emplace_back(0, item->title.size());
          item->delegate_factory = base::BindOnce(
              [](CommanderControllerTest* instance)
                  -> std::unique_ptr<CommanderBackend> {
                return std::make_unique<TestBackend>(instance);
              },
              instance);
          CommandSource::CommandResults results;
          results.push_back(std::move(item));
          return results;
        },
        this));
    std::vector<std::unique_ptr<CommandSource>> sources;
    sources.push_back(std::move(source));
    auto controller =
        CommanderController::CreateWithSourcesForTesting(std::move(sources));
    controller->SetUpdateCallback(base::BindRepeating(
        &CommanderControllerTest::OnViewModelUpdated, base::Unretained(this)));
  
    {
      ViewModelCallbackWaiter waiter(this);
      controller->OnTextChanged(base::ASCIIToUTF16("abracadabra"), browser());
    }
    ASSERT_EQ(received_view_models_.size(), 1u);
    {
      ViewModelCallbackWaiter waiter(this);
  
      controller->OnCommandSelected(0,
                                    received_view_models_.back().result_set_id);
    }
    EXPECT_EQ(received_view_models_.back().action,
              CommanderViewModel::Action::kPrompt);
  }
  
  TEST_F(CommanderControllerTest, OnTextChangedPassedToDelegate) {
    auto source = std::make_unique<TestCommandSource>(base::BindRepeating(
        [](CommanderControllerTest* instance, const base::string16&,
           Browser* browser) -> CommandSource::CommandResults {
          auto item = std::make_unique<CommandItem>();
          item->title = base::ASCIIToUTF16("Do something...");
          item->score = 100;
          item->matched_ranges.emplace_back(0, item->title.size());
          item->delegate_factory = base::BindOnce(
              [](CommanderControllerTest* instance)
                  -> std::unique_ptr<CommanderBackend> {
                return std::make_unique<TestBackend>(instance);
              },
              instance);
          CommandSource::CommandResults results;
          results.push_back(std::move(item));
          return results;
        },
        this));
    std::vector<std::unique_ptr<CommandSource>> sources;
    sources.push_back(std::move(source));
    auto controller =
        CommanderController::CreateWithSourcesForTesting(std::move(sources));
    controller->SetUpdateCallback(base::BindRepeating(
        &CommanderControllerTest::OnViewModelUpdated, base::Unretained(this)));
  
    {
      ViewModelCallbackWaiter waiter(this);
      controller->OnTextChanged(base::ASCIIToUTF16("abracadabra"), browser());
    }
    ASSERT_EQ(received_view_models_.size(), 1u);
    {
      ViewModelCallbackWaiter waiter(this);
  
      controller->OnCommandSelected(0,
                                    received_view_models_.back().result_set_id);
    }
    EXPECT_TRUE(test_backend_->text_changed_invocations().empty());
  
    controller->OnTextChanged(base::ASCIIToUTF16("hocus pocus"), browser());
    ASSERT_EQ(test_backend_->text_changed_invocations().size(), 1u);
    EXPECT_EQ(test_backend_->text_changed_invocations().back(),
              base::ASCIIToUTF16("hocus pocus"));
  }
  
  TEST_F(CommanderControllerTest, OnCommandSelectedPassedToDelegate) {
    auto source = std::make_unique<TestCommandSource>(base::BindRepeating(
        [](CommanderControllerTest* instance, const base::string16&,
           Browser* browser) -> CommandSource::CommandResults {
          auto item = std::make_unique<CommandItem>();
          item->title = base::ASCIIToUTF16("Do something...");
          item->score = 100;
          item->matched_ranges.emplace_back(0, item->title.size());
          item->delegate_factory = base::BindOnce(
              [](CommanderControllerTest* instance)
                  -> std::unique_ptr<CommanderBackend> {
                return std::make_unique<TestBackend>(instance);
              },
              instance);
          CommandSource::CommandResults results;
          results.push_back(std::move(item));
          return results;
        },
        this));
    std::vector<std::unique_ptr<CommandSource>> sources;
    sources.push_back(std::move(source));
    auto controller =
        CommanderController::CreateWithSourcesForTesting(std::move(sources));
    controller->SetUpdateCallback(base::BindRepeating(
        &CommanderControllerTest::OnViewModelUpdated, base::Unretained(this)));
    {
      ViewModelCallbackWaiter waiter(this);
      controller->OnTextChanged(base::ASCIIToUTF16("abracadabra"), browser());
    }
  
    ASSERT_EQ(received_view_models_.size(), 1u);
  
    {
      ViewModelCallbackWaiter waiter(this);
      controller->OnCommandSelected(0,
                                    received_view_models_.back().result_set_id);
    }
  
    EXPECT_TRUE(test_backend_->text_changed_invocations().empty());
  
    controller->OnCommandSelected(586,
                                  received_view_models_.back().result_set_id);
    ASSERT_EQ(test_backend_->command_selected_invocations().size(), 1u);
    EXPECT_EQ(test_backend_->command_selected_invocations().back(), 586u);
  }
  
  }  // namespace commander
  