<reponame>mghgroup/Glide-Browser
// 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
