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

#include "pdf/pdf_caret.h"

#include <stdint.h>

#include "base/compiler_specific.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "pdf/accessibility_structs.h"
#include "pdf/page_character_index.h"
#include "pdf/page_orientation.h"
#include "pdf/pdf_caret_client.h"
#include "pdf/region_data.h"
#include "pdf/test/mock_pdf_caret_client.h"
#include "pdf/test/test_helpers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/input/web_keyboard_event.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkImageInfo.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/geometry/rect.h"

namespace chrome_pdf {

namespace {

using ::testing::_;
using ::testing::AnyNumber;
using ::testing::InSequence;
using ::testing::Mock;
using ::testing::Return;
using ::testing::StrictMock;

constexpr char kCaretFirstVisibleHistogram[] = "PDF.Caret.FirstVisible";

constexpr base::TimeDelta kOneMs = base::Milliseconds(1);

constexpr gfx::Size kCanvasSize(100, 100);
constexpr SkColor kDefaultColor = SK_ColorGREEN;

constexpr PageCharacterIndex kTestChar0{0, 0};
constexpr PageCharacterIndex kTestChar1{0, 1};
constexpr PageCharacterIndex kTestChar2{0, 2};
constexpr PageCharacterIndex kTestChar3{0, 3};
constexpr PageCharacterIndex kTestChar4{0, 4};
constexpr PageCharacterIndex kTestChar5{0, 5};
constexpr PageCharacterIndex kTestPage1Char0{1, 0};
constexpr PageCharacterIndex kTestPage1Char1{1, 1};
constexpr PageCharacterIndex kTestPage2Char0{2, 0};
constexpr PageCharacterIndex kTestPage3Char0{3, 0};

constexpr gfx::Rect kDefaultScreenRect{10, 10, 12, 12};
constexpr gfx::Rect kDefaultCaret{10, 10, 1, 12};
constexpr gfx::Rect kTestChar0ScreenRect{10, 10, 12, 14};
constexpr gfx::Rect kTestChar1ScreenRect{22, 10, 12, 14};
constexpr gfx::Rect kTestChar3ScreenRect{46, 10, 12, 14};
constexpr gfx::Rect kTestChar4ScreenRect{58, 10, 12, 14};
constexpr gfx::Rect kTestChar0Caret{10, 10, 1, 14};
constexpr gfx::Rect kTestChar0EndCaret{22, 10, 1, 14};
constexpr gfx::Rect kTestChar1Caret = kTestChar0EndCaret;
constexpr gfx::Rect kTestChar1EndCaret{34, 10, 1, 14};
constexpr gfx::Rect kTestChar2Caret = kTestChar1EndCaret;
constexpr gfx::Rect kTestChar0ZoomedCaret{20, 20, 1, 28};
constexpr gfx::Rect kTestChar3Caret{46, 10, 1, 14};

constexpr gfx::Rect kTestChar0TopCaret{10, 10, 12, 1};
constexpr gfx::Rect kTestChar0BottomCaret{10, 24, 12, 1};

constexpr gfx::Rect kTestMultiPage1Char0ScreenRect{15, 15, 8, 4};
constexpr gfx::Rect kTestMultiPage1Char1ScreenRect{23, 15, 8, 4};
constexpr gfx::Rect kTestMultiPage2Char0ScreenRect{40, 40, 15, 20};
constexpr gfx::Rect kTestMultiPage2NonTextScreenRect{40, 40, 1, 12};
constexpr gfx::Rect kTestMultiPage3Char0ScreenRect{50, 50, 16, 20};
constexpr gfx::Rect kTestMultiPage1Char0Caret{15, 15, 1, 4};
constexpr gfx::Rect kTestMultiPage1Char0EndCaret{23, 15, 1, 4};
constexpr gfx::Rect kTestMultiPage1Char1Caret = kTestMultiPage1Char0EndCaret;
constexpr gfx::Rect kTestMultiPage1Char1EndCaret{31, 15, 1, 4};
constexpr gfx::Rect kTestMultiPage2Char0Caret{40, 40, 1, 20};
constexpr gfx::Rect kTestMultiPage3Char0Caret{50, 50, 1, 20};
constexpr gfx::Rect kTestMultiPage3Char0EndCaret{66, 50, 1, 20};

AccessibilityTextRunInfo GenerateTestTextRunInfo(
    AccessibilityTextDirection direction) {
  // `PdfCaret` only uses the direction of the text run.
  AccessibilityTextRunInfo text_run;
  text_run.direction = direction;
  return text_run;
}

class PdfCaretTest : public testing::Test {
 public:
  PdfCaretTest() = default;
  PdfCaretTest(const PdfCaretTest&) = delete;
  PdfCaretTest& operator=(const PdfCaretTest&) = delete;
  ~PdfCaretTest() override = default;

  MockPdfCaretClient& client() { return client_; }

  PdfCaret& caret() { return *caret_; }

  void SetUp() override {
    ResetBitmap();
    EXPECT_CALL(client(), GetCurrentOrientation())
        .WillRepeatedly(Return(PageOrientation::kOriginal));
    EXPECT_CALL(client(), GetTextRunInfoAt(_))
        .WillRepeatedly(Return(
            GenerateTestTextRunInfo(AccessibilityTextDirection::kLeftToRight)));
    EXPECT_CALL(client(), IsSelecting()).WillRepeatedly(Return(false));
    EXPECT_CALL(client(), ScrollToChar(_)).Times(AnyNumber());
  }

  void InitializeCaretAtChar(const PageCharacterIndex& index) {
    caret_ = std::make_unique<PdfCaret>(&client_);
    caret_->SetChar(index);
  }

  void InitializeVisibleCaretAtChar(const PageCharacterIndex& index) {
    InitializeCaretAtChar(index);
    caret_->SetEnabled(true);
    caret_->SetVisible(true);
  }

  RegionData GetRegionData(const gfx::Point& location) {
    uint8_t* buffer = static_cast<uint8_t*>(bitmap_.getPixels());
    CHECK(buffer);

    size_t stride = bitmap_.rowBytes();
    size_t offset = location.y() * stride + location.x() * 4;
    // SAFETY: Skia guarantees bitmap_.height() * bitmap_.rowBytes() is the
    // exact size of the allocated pixel buffer, including row padding. However,
    // Skia does not have a span-based API for this.
    // TODO(crbug.com/357905831): Switch to SkSpan when possible.
    UNSAFE_BUFFERS(
        base::span<uint8_t> buffer_span(buffer, bitmap_.height() * stride));
    return RegionData(buffer_span.subspan(offset), stride);
  }

  void TestDrawCaret(const gfx::Rect& expected_caret) {
    EXPECT_EQ(expected_caret, client().invalidated_rect());
    EXPECT_TRUE(caret().MaybeDrawCaret(GetRegionData(expected_caret.origin()),
                                       expected_caret));
    EXPECT_TRUE(VerifyCaretRendering(expected_caret));

    // Reset for future calls.
    ResetBitmap();
  }

  void TestDrawCaretFails(const gfx::Rect& expected_caret) {
    EXPECT_FALSE(caret().MaybeDrawCaret(GetRegionData(expected_caret.origin()),
                                        expected_caret));
    EXPECT_TRUE(VerifyBlankRendering());

    // Reset for future calls.
    ResetBitmap();
  }

  bool VerifyCaretRendering(const gfx::Rect& expected_caret) {
    int width = bitmap_.width();
    int height = bitmap_.height();

    for (int y = 0; y < height; ++y) {
      for (int x = 0; x < width; ++x) {
        if (expected_caret.Contains(x, y) ==
            (bitmap_.getColor(x, y) == kDefaultColor)) {
          return false;
        }
      }
    }

    return true;
  }

  bool VerifyBlankRendering() {
    int width = bitmap_.width();
    int height = bitmap_.height();

    for (int y = 0; y < height; ++y) {
      for (int x = 0; x < width; ++x) {
        if (bitmap_.getColor(x, y) != kDefaultColor) {
          return false;
        }
      }
    }

    return true;
  }

  void ResetBitmap() {
    bitmap_.reset();

    sk_sp<SkSurface> surface =
        CreateSkiaSurfaceForTesting(kCanvasSize, kDefaultColor);
    SkImageInfo image_info =
        SkImageInfo::MakeN32Premul(kCanvasSize.width(), kCanvasSize.height());
    CHECK(bitmap_.tryAllocPixels(image_info));
    sk_sp<SkImage> image = surface->makeImageSnapshot();
    CHECK(image);
    CHECK(image->readPixels(bitmap_.info(), bitmap_.getPixels(),
                            bitmap_.rowBytes(), 0, 0));
  }

  void SetUpChar(const PageCharacterIndex& index,
                 std::vector<gfx::Rect> rects) {
    EXPECT_CALL(client_, GetScreenRectsForCaret(index))
        .WillRepeatedly(Return(std::move(rects)));
  }

  void SetUpPagesWithCharCounts(const std::vector<uint32_t>& char_counts) {
    EXPECT_CALL(client(), PageIndexInBounds(_)).WillRepeatedly(Return(false));
    for (size_t i = 0; i < char_counts.size(); ++i) {
      EXPECT_CALL(client(), PageIndexInBounds(i)).WillRepeatedly(Return(true));
      EXPECT_CALL(client(), GetCharCount(i))
          .WillRepeatedly(Return(char_counts[i]));
    }
  }

  void SetUpSingleCharLineTest() {
    SetUpPagesWithCharCounts({1});
    SetUpChar(kTestChar0, {kTestChar0ScreenRect});
  }

  void SetUpTwoCharLineTest() {
    SetUpPagesWithCharCounts({2});
    SetUpChar(kTestChar0, {kTestChar0ScreenRect});
    SetUpChar(kTestChar1, {kTestChar1ScreenRect});
  }

  void SetUpNoTextPageTest() {
    SetUpPagesWithCharCounts({0});
    SetUpChar(kTestChar0, {kDefaultScreenRect});
  }

  void SetUpMultiPageTest() {
    SetUpPagesWithCharCounts({1, 2, 0, 1});
    // Page 0: "ab".
    // Page 1: "c".
    // Page 2: No text.
    // Page 3: "d".
    SetUpChar(kTestChar0, {kTestChar0ScreenRect});
    SetUpChar(kTestPage1Char0, {kTestMultiPage1Char0ScreenRect});
    SetUpChar(kTestPage1Char1, {kTestMultiPage1Char1ScreenRect});
    SetUpChar(kTestPage2Char0, {kTestMultiPage2NonTextScreenRect});
    SetUpChar(kTestPage3Char0, {kTestMultiPage3Char0ScreenRect});
  }

  blink::WebKeyboardEvent GenerateKeyboardEvent(ui::KeyboardCode key) {
    blink::WebKeyboardEvent event(
        blink::WebInputEvent::Type::kRawKeyDown, 0,
        blink::WebInputEvent::GetStaticTimeStampForTests());
    event.windows_key_code = key;
    return event;
  }

 private:
  StrictMock<MockPdfCaretClient> client_;
  std::unique_ptr<PdfCaret> caret_;
  SkBitmap bitmap_;
};

TEST_F(PdfCaretTest, NoTextPage) {
  base::HistogramTester histograms;
  SetUpNoTextPageTest();
  InitializeVisibleCaretAtChar(kTestChar0);

  TestDrawCaret(kDefaultCaret);
  histograms.ExpectUniqueSample(kCaretFirstVisibleHistogram, true, 1);
}

TEST_F(PdfCaretTest, SetEnabled) {
  base::HistogramTester histograms;
  SetUpSingleCharLineTest();
  InitializeCaretAtChar(kTestChar0);

  caret().SetVisible(true);

  // Default disabled.
  TestDrawCaretFails(kTestChar0Caret);
  histograms.ExpectTotalCount(kCaretFirstVisibleHistogram, 0);

  caret().SetEnabled(true);

  TestDrawCaret(kTestChar0Caret);
  histograms.ExpectUniqueSample(kCaretFirstVisibleHistogram, true, 1);

  caret().SetEnabled(false);

  TestDrawCaretFails(kTestChar0Caret);
  histograms.ExpectUniqueSample(kCaretFirstVisibleHistogram, true, 1);

  GetPdfTestTaskEnvironment().FastForwardBy(PdfCaret::kDefaultBlinkInterval);

  TestDrawCaretFails(kTestChar0Caret);
  histograms.ExpectUniqueSample(kCaretFirstVisibleHistogram, true, 1);

  caret().SetEnabled(true);

  TestDrawCaret(kTestChar0Caret);
  histograms.ExpectUniqueSample(kCaretFirstVisibleHistogram, true, 1);
}

TEST_F(PdfCaretTest, SetVisible) {
  base::HistogramTester histograms;
  SetUpSingleCharLineTest();
  InitializeCaretAtChar(kTestChar0);

  caret().SetEnabled(true);

  // Default not visible.
  TestDrawCaretFails(kTestChar0Caret);
  histograms.ExpectTotalCount(kCaretFirstVisibleHistogram, 0);

  caret().SetVisible(true);

  TestDrawCaret(kTestChar0Caret);

  caret().SetVisible(false);

  TestDrawCaretFails(kTestChar0Caret);
  histograms.ExpectUniqueSample(kCaretFirstVisibleHistogram, true, 1);

  GetPdfTestTaskEnvironment().FastForwardBy(PdfCaret::kDefaultBlinkInterval);

  TestDrawCaretFails(kTestChar0Caret);
  histograms.ExpectUniqueSample(kCaretFirstVisibleHistogram, true, 1);

  caret().SetVisible(true);

  TestDrawCaret(kTestChar0Caret);
  histograms.ExpectUniqueSample(kCaretFirstVisibleHistogram, true, 1);
}

TEST_F(PdfCaretTest, SetBlinkIntervalWhileNotVisible) {
  SetUpSingleCharLineTest();
  InitializeCaretAtChar(kTestChar0);

  TestDrawCaretFails(kTestChar0Caret);

  // Blinks by default, but not visible.
  GetPdfTestTaskEnvironment().FastForwardBy(PdfCaret::kDefaultBlinkInterval);
  TestDrawCaretFails(kTestChar0Caret);

  // Turn off blinking. Still not visible.
  caret().SetBlinkInterval(base::TimeDelta());

  TestDrawCaretFails(kTestChar0Caret);

  GetPdfTestTaskEnvironment().FastForwardBy(PdfCaret::kDefaultBlinkInterval);
  TestDrawCaretFails(kTestChar0Caret);

  // Turn on blinking. Still not visible.
  constexpr base::TimeDelta kBlinkInterval = base::Milliseconds(200);
  caret().SetBlinkInterval(kBlinkInterval);

  TestDrawCaretFails(kTestChar0Caret);

  GetPdfTestTaskEnvironment().FastForwardBy(kBlinkInterval);
  TestDrawCaretFails(kTestChar0Caret);
}

TEST_F(PdfCaretTest, SetBlinkIntervalWhileVisible) {
  SetUpSingleCharLineTest();
  InitializeVisibleCaretAtChar(kTestChar0);

  TestDrawCaret(kTestChar0Caret);

  // Blinks by default.
  GetPdfTestTaskEnvironment().FastForwardBy(PdfCaret::kDefaultBlinkInterval);
  TestDrawCaretFails(kTestChar0Caret);

  // Turn off blinking. Caret should always be visible.
  caret().SetBlinkInterval(base::TimeDelta());

  TestDrawCaret(kTestChar0Caret);

  GetPdfTestTaskEnvironment().FastForwardBy(PdfCaret::kDefaultBlinkInterval);
  TestDrawCaret(kTestChar0Caret);

  // Turn on blinking.
  constexpr base::TimeDelta kBlinkInterval = base::Milliseconds(300);
  caret().SetBlinkInterval(kBlinkInterval);

  TestDrawCaret(kTestChar0Caret);

  GetPdfTestTaskEnvironment().FastForwardBy(kBlinkInterval);
  TestDrawCaretFails(kTestChar0Caret);

  GetPdfTestTaskEnvironment().FastForwardBy(kBlinkInterval - kOneMs);
  TestDrawCaretFails(kTestChar0Caret);

  // Set to the same blink interval. Should not reset the blink timer.
  caret().SetBlinkInterval(kBlinkInterval);

  TestDrawCaretFails(kTestChar0Caret);

  GetPdfTestTaskEnvironment().FastForwardBy(kOneMs);
  TestDrawCaret(kTestChar0Caret);
}

TEST_F(PdfCaretTest, SetBlinkIntervalNegative) {
  SetUpSingleCharLineTest();
  InitializeVisibleCaretAtChar(kTestChar0);

  // Setting blink interval to negative does nothing.
  caret().SetBlinkInterval(base::Milliseconds(-100));

  TestDrawCaret(kTestChar0Caret);

  GetPdfTestTaskEnvironment().FastForwardBy(PdfCaret::kDefaultBlinkInterval);
  TestDrawCaretFails(kTestChar0Caret);

  GetPdfTestTaskEnvironment().FastForwardBy(base::Milliseconds(100));
  TestDrawCaretFails(kTestChar0Caret);
}

TEST_F(PdfCaretTest, MaybeDrawCaret) {
  base::HistogramTester histograms;
  SetUpSingleCharLineTest();
  InitializeCaretAtChar(kTestChar0);

  // Not yet visible.
  EXPECT_FALSE(caret().MaybeDrawCaret(GetRegionData(kTestChar0Caret.origin()),
                                      kTestChar0Caret));
  histograms.ExpectTotalCount(kCaretFirstVisibleHistogram, 0);

  caret().SetEnabled(true);
  caret().SetVisible(true);

  // Not dirty in screen.
  EXPECT_FALSE(caret().MaybeDrawCaret(GetRegionData(gfx::Point(70, 70)),
                                      gfx::Rect(70, 70, 20, 30)));
  histograms.ExpectTotalCount(kCaretFirstVisibleHistogram, 0);

  // Partially dirty in screen. For testing purposes, origin is bottom left
  // instead of top right.
  EXPECT_TRUE(caret().MaybeDrawCaret(GetRegionData(gfx::Point(5, 5)),
                                     gfx::Rect(5, 5, 20, 30)));
  VerifyCaretRendering(gfx::Rect(5, 5, 1, 9));
  histograms.ExpectUniqueSample(kCaretFirstVisibleHistogram, true, 1);
  ResetBitmap();

  // Fully dirty in screen.
  EXPECT_TRUE(caret().MaybeDrawCaret(GetRegionData(kTestChar0Caret.origin()),
                                     kTestChar0Caret));
  VerifyCaretRendering(kTestChar0Caret);
  histograms.ExpectUniqueSample(kCaretFirstVisibleHistogram, true, 1);
}

TEST_F(PdfCaretTest, Blink) {
  SetUpTwoCharLineTest();
  InitializeVisibleCaretAtChar(kTestChar0);

  TestDrawCaret(kTestChar0Caret);

  GetPdfTestTaskEnvironment().FastForwardBy(PdfCaret::kDefaultBlinkInterval -
                                            kOneMs);
  TestDrawCaret(kTestChar0Caret);

  GetPdfTestTaskEnvironment().FastForwardBy(kOneMs);
  TestDrawCaretFails(kTestChar0Caret);

  GetPdfTestTaskEnvironment().FastForwardBy(PdfCaret::kDefaultBlinkInterval -
                                            kOneMs);
  TestDrawCaretFails(kTestChar0Caret);

  GetPdfTestTaskEnvironment().FastForwardBy(kOneMs);
  TestDrawCaret(kTestChar0Caret);

  // Moving to another char should reset the blink duration.
  GetPdfTestTaskEnvironment().FastForwardBy(kOneMs);

  caret().SetCharAndDraw(kTestChar1);
  TestDrawCaret(kTestChar1Caret);

  GetPdfTestTaskEnvironment().FastForwardBy(PdfCaret::kDefaultBlinkInterval -
                                            kOneMs);
  TestDrawCaret(kTestChar1Caret);

  GetPdfTestTaskEnvironment().FastForwardBy(kOneMs);
  TestDrawCaretFails(kTestChar1Caret);

  // Moving to another char should make the caret reappear immediately.
  caret().SetCharAndDraw(kTestChar0);
  TestDrawCaret(kTestChar0Caret);
}

TEST_F(PdfCaretTest, OnGeometryChanged) {
  SetUpSingleCharLineTest();
  InitializeVisibleCaretAtChar(kTestChar0);

  EXPECT_EQ(kTestChar0Caret, client().invalidated_rect());

  caret().OnGeometryChanged();

  EXPECT_EQ(kTestChar0Caret, client().invalidated_rect());

  // Simulate a 200% zoom geometry change.
  SetUpChar(kTestChar0, {kTestChar0ZoomedCaret});
  caret().OnGeometryChanged();

  EXPECT_EQ(kTestChar0ZoomedCaret, client().invalidated_rect());
  EXPECT_TRUE(caret().MaybeDrawCaret(GetRegionData(gfx::Point()),
                                     gfx::Rect(kCanvasSize)));
  EXPECT_TRUE(VerifyCaretRendering(kTestChar0ZoomedCaret));

  ResetBitmap();

  // Simulate a scroll geometry change.
  constexpr gfx::Rect kTestChar0ZoomedScrolledCaret{40, 60, 1, 28};
  SetUpChar(kTestChar0, {kTestChar0ZoomedScrolledCaret});
  caret().OnGeometryChanged();

  EXPECT_EQ(kTestChar0ZoomedScrolledCaret, client().invalidated_rect());
  EXPECT_TRUE(caret().MaybeDrawCaret(GetRegionData(gfx::Point()),
                                     gfx::Rect(kCanvasSize)));
  EXPECT_TRUE(VerifyCaretRendering(kTestChar0ZoomedScrolledCaret));

  ResetBitmap();

  // Simulate a scroll geometry change such that the caret is off-screen.
  constexpr gfx::Rect kOffScreenCaret{140, 160, 1, 28};
  SetUpChar(kTestChar0, {kOffScreenCaret});
  caret().OnGeometryChanged();

  EXPECT_EQ(kOffScreenCaret, client().invalidated_rect());
  EXPECT_FALSE(caret().MaybeDrawCaret(GetRegionData(gfx::Point()),
                                      gfx::Rect(kCanvasSize)));
  EXPECT_TRUE(VerifyBlankRendering());
}

TEST_F(PdfCaretTest, OnGeometryChangedToggleEnabled) {
  SetUpSingleCharLineTest();
  InitializeVisibleCaretAtChar(kTestChar0);

  EXPECT_EQ(kTestChar0Caret, client().invalidated_rect());

  caret().SetEnabled(false);

  // Call `OnGeometryChanged()` while the caret is disabled.
  SetUpChar(kTestChar0, {kTestChar0ZoomedCaret});
  caret().OnGeometryChanged();

  EXPECT_EQ(kTestChar0Caret, client().invalidated_rect());

  caret().SetEnabled(true);

  EXPECT_EQ(kTestChar0ZoomedCaret, client().invalidated_rect());
  EXPECT_TRUE(caret().MaybeDrawCaret(GetRegionData(gfx::Point()),
                                     gfx::Rect(kCanvasSize)));
  EXPECT_TRUE(VerifyCaretRendering(kTestChar0ZoomedCaret));
}

TEST_F(PdfCaretTest, OnGeometryChangedToggleVisible) {
  SetUpSingleCharLineTest();
  InitializeVisibleCaretAtChar(kTestChar0);

  EXPECT_EQ(kTestChar0Caret, client().invalidated_rect());

  caret().SetVisible(false);

  // Call `OnGeometryChanged()` while the caret is not visible.
  SetUpChar(kTestChar0, {kTestChar0ZoomedCaret});
  caret().OnGeometryChanged();

  EXPECT_EQ(kTestChar0Caret, client().invalidated_rect());

  caret().SetVisible(true);

  EXPECT_EQ(kTestChar0ZoomedCaret, client().invalidated_rect());
  EXPECT_TRUE(caret().MaybeDrawCaret(GetRegionData(gfx::Point()),
                                     gfx::Rect(kCanvasSize)));
  EXPECT_TRUE(VerifyCaretRendering(kTestChar0ZoomedCaret));
}

TEST_F(PdfCaretTest, SetChar) {
  SetUpSingleCharLineTest();
  InitializeVisibleCaretAtChar(kTestChar0);

  EXPECT_EQ(kTestChar0Caret, client().invalidated_rect());

  caret().SetChar(kTestChar1);

  // New caret position should not be invalidated.
  EXPECT_EQ(kTestChar0Caret, client().invalidated_rect());

  caret().SetChar(kTestChar0);

  // Old caret position should be invalidated.
  EXPECT_EQ(kTestChar0EndCaret, client().invalidated_rect());
}

TEST_F(PdfCaretTest, SetCharAndDraw) {
  SetUpPagesWithCharCounts({2});
  // "ab".
  SetUpChar(kTestChar0, {kTestChar0ScreenRect});
  // Set up second char two pixels to the right of the first char.
  SetUpChar(kTestChar1, {gfx::Rect(24, 10, 12, 14)});
  InitializeVisibleCaretAtChar(kTestChar0);

  caret().SetCharAndDraw(kTestChar0);
  TestDrawCaret(kTestChar0Caret);

  caret().SetCharAndDraw(kTestChar1);
  TestDrawCaret(gfx::Rect(24, 10, 1, 14));

  constexpr gfx::Rect kSecondCharEndCaret{36, 10, 1, 14};
  caret().SetCharAndDraw(kTestChar2);
  TestDrawCaret(kSecondCharEndCaret);

  // Setting the position should still work, even when not visible. The effects
  // will only appear when the caret is set to visible again.
  caret().SetEnabled(false);
  caret().SetCharAndDraw(kTestChar0);
  EXPECT_EQ(kSecondCharEndCaret, client().invalidated_rect());

  caret().SetEnabled(true);
  TestDrawCaret(kTestChar0Caret);
}

TEST_F(PdfCaretTest, SetCharAndDrawSpecialChars) {
  SetUpPagesWithCharCounts({4});
  // "a \n"
  // "b".
  SetUpChar(kTestChar0, {kTestChar0ScreenRect});
  SetUpChar(kTestChar1, {});
  SetUpChar(kTestChar2, {});
  SetUpChar(kTestChar3, {gfx::Rect(10, 26, 10, 8)});
  InitializeVisibleCaretAtChar(kTestChar0);

  caret().SetCharAndDraw(kTestChar0);
  TestDrawCaret(kTestChar0Caret);

  // Synthetic whitespaces and newlines added by PDFium do not have screen
  // rects. Caret should be directly to the right of the first char's rect.
  caret().SetCharAndDraw(kTestChar1);
  TestDrawCaret(kTestChar1Caret);

  // Consecutive chars with empty screen rects should still use the right of the
  // previous char's rect.
  caret().SetCharAndDraw(kTestChar2);
  TestDrawCaret(kTestChar1Caret);

  // Char with different width and height after newline.
  caret().SetCharAndDraw(kTestChar3);
  TestDrawCaret(gfx::Rect{10, 26, 1, 8});
}

TEST_F(PdfCaretTest, SetCharAndDrawMultiPage) {
  SetUpMultiPageTest();
  InitializeVisibleCaretAtChar(kTestChar0);

  caret().SetCharAndDraw(kTestChar0);
  TestDrawCaret(kTestChar0Caret);

  caret().SetCharAndDraw(kTestPage3Char0);
  TestDrawCaret(kTestMultiPage3Char0Caret);

  caret().SetCharAndDraw({3, 1});
  TestDrawCaret(kTestMultiPage3Char0EndCaret);

  caret().SetCharAndDraw(kTestPage1Char1);
  TestDrawCaret(kTestMultiPage1Char1Caret);

  caret().SetCharAndDraw(kTestPage1Char0);
  TestDrawCaret(kTestMultiPage1Char0Caret);
}

class PdfCaretTextDirectionTest : public PdfCaretTest {
 public:
  void SetUpTextDirectionTest(const PageCharacterIndex& start_index,
                              AccessibilityTextDirection direction) {
    SetUpPagesWithCharCounts({2});
    // "a\n".
    SetUpChar(kTestChar0, {kTestChar0ScreenRect});
    SetUpChar(kTestChar1, {});
    EXPECT_CALL(client(), GetTextRunInfoAt(_))
        .WillRepeatedly(Return(GenerateTestTextRunInfo(direction)));
    InitializeVisibleCaretAtChar(start_index);
  }

  void TestLeftToRight() {
    InSequence sequence;
    TestOrientation(PageOrientation::kOriginal, kTestChar0Caret);
    TestOrientation(PageOrientation::kClockwise90, kTestChar0TopCaret);
    TestOrientation(PageOrientation::kClockwise180, kTestChar0EndCaret);
    TestOrientation(PageOrientation::kClockwise270, kTestChar0BottomCaret);
  }

  void TestRightToLeft() {
    InSequence sequence;
    TestOrientation(PageOrientation::kOriginal, kTestChar0EndCaret);
    TestOrientation(PageOrientation::kClockwise90, kTestChar0BottomCaret);
    TestOrientation(PageOrientation::kClockwise180, kTestChar0Caret);
    TestOrientation(PageOrientation::kClockwise270, kTestChar0TopCaret);
  }

  void TestTopToBottom() {
    InSequence sequence;
    TestOrientation(PageOrientation::kOriginal, kTestChar0TopCaret);
    TestOrientation(PageOrientation::kClockwise90, kTestChar0EndCaret);
    TestOrientation(PageOrientation::kClockwise180, kTestChar0BottomCaret);
    TestOrientation(PageOrientation::kClockwise270, kTestChar0Caret);
  }

  void TestBottomToTop() {
    InSequence sequence;
    TestOrientation(PageOrientation::kOriginal, kTestChar0BottomCaret);
    TestOrientation(PageOrientation::kClockwise90, kTestChar0Caret);
    TestOrientation(PageOrientation::kClockwise180, kTestChar0TopCaret);
    TestOrientation(PageOrientation::kClockwise270, kTestChar0EndCaret);
  }

  void TestOrientation(PageOrientation orientation, gfx::Rect expected_caret) {
    EXPECT_CALL(client(), GetCurrentOrientation())
        .WillOnce(Return(orientation));
    caret().OnGeometryChanged();
    TestDrawCaret(expected_caret);
  }
};

TEST_F(PdfCaretTextDirectionTest, NoTextPage) {
  SetUpNoTextPageTest();
  EXPECT_CALL(client(), GetTextRunInfoAt(_))
      .WillRepeatedly(Return(std::nullopt));
  InitializeVisibleCaretAtChar(kTestChar0);

  InSequence sequence;
  TestOrientation(PageOrientation::kOriginal, kDefaultCaret);
  TestOrientation(PageOrientation::kClockwise90, kTestChar0TopCaret);
  TestOrientation(PageOrientation::kClockwise180, gfx::Rect(22, 10, 1, 12));
  TestOrientation(PageOrientation::kClockwise270, gfx::Rect(10, 22, 12, 1));
}

TEST_F(PdfCaretTextDirectionTest, LeftToRight) {
  SetUpTextDirectionTest(kTestChar0, AccessibilityTextDirection::kLeftToRight);
  TestLeftToRight();
}

TEST_F(PdfCaretTextDirectionTest, RightToLeft) {
  SetUpTextDirectionTest(kTestChar0, AccessibilityTextDirection::kRightToLeft);
  TestRightToLeft();
}

TEST_F(PdfCaretTextDirectionTest, TopToBottom) {
  SetUpTextDirectionTest(kTestChar0, AccessibilityTextDirection::kTopToBottom);
  TestTopToBottom();
}

TEST_F(PdfCaretTextDirectionTest, BottomToTop) {
  SetUpTextDirectionTest(kTestChar0, AccessibilityTextDirection::kBottomToTop);
  TestBottomToTop();
}

TEST_F(PdfCaretTextDirectionTest, LeftToRightEmptyScreenRect) {
  SetUpTextDirectionTest(kTestChar1, AccessibilityTextDirection::kLeftToRight);
  // When on a character with an empty screen rect, the previous character's
  // screen rect is used, but flipped.
  TestRightToLeft();
}

TEST_F(PdfCaretTextDirectionTest, RightToLeftEmptyScreenRect) {
  SetUpTextDirectionTest(kTestChar1, AccessibilityTextDirection::kRightToLeft);
  // When on a character with an empty screen rect, the previous character's
  // screen rect is used, but flipped.
  TestLeftToRight();
}

TEST_F(PdfCaretTextDirectionTest, TopToBottomEmptyScreenRect) {
  SetUpTextDirectionTest(kTestChar1, AccessibilityTextDirection::kTopToBottom);
  // When on a character with an empty screen rect, the previous character's
  // screen rect is used, but flipped.
  TestBottomToTop();
}

TEST_F(PdfCaretTextDirectionTest, BottomToTopEmptyScreenRect) {
  SetUpTextDirectionTest(kTestChar1, AccessibilityTextDirection::kBottomToTop);
  // When on a character with an empty screen rect, the previous character's
  // screen rect is used, but flipped.
  TestTopToBottom();
}

class PdfCaretMoveTest : public PdfCaretTest {
 public:
  void SetUp() override {
    PdfCaretTest::SetUp();
    EXPECT_CALL(client(), IsSynthesizedNewline(_))
        .WillRepeatedly(Return(false));
    EXPECT_CALL(client(), ClearTextSelection()).Times(AnyNumber());
  }

  void SetUpPagesWithSynthesizedChars(
      const std::vector<std::vector<uint32_t>>& synthesized_chars) {
    for (size_t page_index = 0; page_index < synthesized_chars.size();
         ++page_index) {
      for (uint32_t synthesized_char : synthesized_chars[page_index]) {
        PageCharacterIndex index{static_cast<uint32_t>(page_index),
                                 synthesized_char};
        EXPECT_CALL(client(), IsSynthesizedNewline(index))
            .WillRepeatedly(Return(true));
      }
    }
  }

  void SetUpMultiLineTest() {
    SetUpPagesWithCharCounts({10});
    SetUpPagesWithSynthesizedChars({{2, 3, 6, 7}});
    // "ab\r\n"
    // "cd\r\n"
    // "ef".
    SetUpChar(kTestChar0, {kTestChar0ScreenRect});
    SetUpChar(kTestChar1, {kTestChar1ScreenRect});
    SetUpChar(kTestChar2, {});
    SetUpChar(kTestChar3, {});
    SetUpChar(kTestChar4, {gfx::Rect(11, 26, 10, 12)});
    SetUpChar(kTestChar5, {gfx::Rect(21, 26, 10, 12)});
    SetUpChar({0, 6}, {});
    SetUpChar({0, 7}, {});
    SetUpChar({0, 8}, {gfx::Rect(10, 50, 14, 16)});
    SetUpChar({0, 9}, {gfx::Rect(24, 50, 14, 16)});
  }

  void SetUpTwoWordWithUnicodeTest() {
    SetUpPagesWithCharCounts({5});
    SetUpCharWithUnicode(kTestChar0, 'a', {kTestChar0ScreenRect});
    SetUpCharWithUnicode(kTestChar1, 'b', {kTestChar1ScreenRect});
    SetUpCharWithUnicode(kTestChar2, ' ', {});
    SetUpCharWithUnicode(kTestChar3, 'c', {kTestChar3ScreenRect});
    SetUpCharWithUnicode(kTestChar4, 'd', {kTestChar4ScreenRect});
  }

  void SetUpCharWithUnicode(const PageCharacterIndex& index,
                            uint32_t unicode_char,
                            std::vector<gfx::Rect> rects) {
    SetUpChar(index, rects);
    EXPECT_CALL(client(), GetCharUnicode(index))
        .WillRepeatedly(Return(unicode_char));
  }

  blink::WebKeyboardEvent GenerateControlKeyboardEvent(ui::KeyboardCode key) {
    blink::WebKeyboardEvent event = GenerateKeyboardEvent(key);
#if BUILDFLAG(IS_MAC)
    event.SetModifiers(blink::WebInputEvent::Modifiers::kAltKey);
#else
    event.SetModifiers(blink::WebInputEvent::Modifiers::kControlKey);
#endif  // BUILDFLAG(IS_MAC)
    return event;
  }

  bool MoveLeftByWordWithKeyboard() {
    return caret().OnKeyDown(
        GenerateControlKeyboardEvent(ui::KeyboardCode::VKEY_LEFT));
  }

  bool MoveRightByWordWithKeyboard() {
    return caret().OnKeyDown(
        GenerateControlKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT));
  }
};

TEST_F(PdfCaretMoveTest, OnKeyDownNotEnabledNotVisible) {
  SetUpSingleCharLineTest();
  InitializeCaretAtChar(kTestChar0);

  EXPECT_FALSE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_0)));
  EXPECT_FALSE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_LEFT)));
  EXPECT_FALSE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));
  EXPECT_FALSE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_UP)));
  EXPECT_FALSE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));
}

TEST_F(PdfCaretMoveTest, OnKeyDownNotEnabledVisible) {
  SetUpSingleCharLineTest();
  InitializeCaretAtChar(kTestChar0);

  caret().SetVisible(true);

  EXPECT_FALSE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_0)));
  EXPECT_FALSE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_LEFT)));
  EXPECT_FALSE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));
  EXPECT_FALSE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_UP)));
  EXPECT_FALSE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));
}

TEST_F(PdfCaretMoveTest, OnKeyDownEnabledNotVisible) {
  SetUpSingleCharLineTest();
  InitializeCaretAtChar(kTestChar0);

  caret().SetEnabled(true);

  EXPECT_FALSE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_0)));
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_LEFT)));
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_UP)));
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));
}

TEST_F(PdfCaretMoveTest, OnKeyDownEnabledVisible) {
  SetUpSingleCharLineTest();
  InitializeVisibleCaretAtChar(kTestChar0);

  EXPECT_FALSE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_0)));
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_LEFT)));
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_UP)));
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));
}

TEST_F(PdfCaretMoveTest, MoveCharLeftRight) {
  SetUpTwoCharLineTest();

  // Start at left of char 0.
  InitializeVisibleCaretAtChar(kTestChar0);

  // Left of char 1.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));
  TestDrawCaret(kTestChar1Caret);

  // Right of char 1.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));
  TestDrawCaret(kTestChar1EndCaret);

  // Right of char 1.
  EXPECT_CALL(client(), IsSynthesizedNewline(kTestChar2)).Times(0);
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));
  TestDrawCaret(kTestChar1EndCaret);

  // Left of char 1.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_LEFT)));
  TestDrawCaret(kTestChar1Caret);

  // Left of char 0.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_LEFT)));
  TestDrawCaret(kTestChar0Caret);

  // Left of char 0.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_LEFT)));
  TestDrawCaret(kTestChar0Caret);
}

TEST_F(PdfCaretMoveTest, MoveCharLeftRightMultiPage) {
  SetUpMultiPageTest();

  // Start at left of page 1, char 0.
  InitializeVisibleCaretAtChar(kTestPage1Char0);

  // Right of page 0, char 0.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_LEFT)));
  TestDrawCaret(kTestChar0EndCaret);

  // Left of page 1, char 0.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));
  TestDrawCaret(kTestMultiPage1Char0Caret);

  // Left of page 1, char 1.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));
  TestDrawCaret(kTestMultiPage1Char1Caret);

  // Right of page 1, char 1.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));
  TestDrawCaret(kTestMultiPage1Char1EndCaret);

  // Top-left of page 2. Page 2 does not have any chars.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));
  TestDrawCaret(kTestMultiPage2NonTextScreenRect);

  // Left of page 3, char 0.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));
  TestDrawCaret(kTestMultiPage3Char0Caret);

  // Top-left of page 2.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_LEFT)));
  TestDrawCaret(kTestMultiPage2NonTextScreenRect);

  // Right of page 1, char 1.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_LEFT)));
  TestDrawCaret(kTestMultiPage1Char1EndCaret);
}

TEST_F(PdfCaretMoveTest, MoveCharLeftRightSkipNewlines) {
  SetUpPagesWithCharCounts({4});
  SetUpPagesWithSynthesizedChars({{1, 2}});
  // "a\r\n"
  // "b".
  SetUpChar(kTestChar0, {kTestChar0ScreenRect});
  SetUpChar(kTestChar1, {});
  SetUpChar(kTestChar2, {});
  SetUpChar(kTestChar3, {gfx::Rect(10, 26, 12, 14)});

  // Start at left of page 0, char 0.
  InitializeVisibleCaretAtChar(kTestChar0);

  // Right of page 0, char 0.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));
  TestDrawCaret(kTestChar0EndCaret);

  // Left of page 0, char 3 'b', skipping one newline.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));
  TestDrawCaret(gfx::Rect(10, 26, 1, 14));

  // Right of page 0, char 0, skipping one newline.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_LEFT)));
  TestDrawCaret(kTestChar0EndCaret);
}

TEST_F(PdfCaretMoveTest, MoveCharLeftRightStartEndNewlines) {
  SetUpPagesWithCharCounts({2});
  // "\n"
  // "\n".
  SetUpChar(kTestChar0, {kTestChar0ScreenRect});
  SetUpChar(kTestChar1, {kTestChar1ScreenRect});

  // Start at left of page 0, char 0.
  InitializeVisibleCaretAtChar(kTestChar0);

  // Left of page 0, char 1.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));
  TestDrawCaret(kTestChar1Caret);

  // Right of page 0, char 1.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));
  TestDrawCaret(kTestChar1EndCaret);

  // Left of page 0, char 1.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_LEFT)));
  TestDrawCaret(kTestChar1Caret);

  // Left of page 0, char 0.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_LEFT)));
  TestDrawCaret(kTestChar0Caret);
}

TEST_F(PdfCaretMoveTest, MoveCharLeftRightConsecutiveNewlines) {
  SetUpPagesWithCharCounts({5});
  SetUpPagesWithSynthesizedChars({{1, 2}});
  // "a\r\n"
  // "\n"
  // "b".
  SetUpChar(kTestChar0, {kTestChar0ScreenRect});
  SetUpChar(kTestChar1, {});
  SetUpChar(kTestChar2, {});
  SetUpChar(kTestChar3, {gfx::Rect(10, 26, 12, 14)});
  SetUpChar(kTestChar4, {gfx::Rect(22, 26, 12, 14)});

  // Start at left of page 0, char 1.
  InitializeVisibleCaretAtChar(kTestChar1);

  // Left of page 0, char 3 '\n', skipping one newline.
  constexpr gfx::Rect kTestChar3NewlineCaret{10, 26, 1, 14};
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));
  TestDrawCaret(kTestChar3NewlineCaret);

  // Left of page 0, char 4 'b'.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));
  TestDrawCaret(gfx::Rect(22, 26, 1, 14));

  // Left of page 0, char 3.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_LEFT)));
  TestDrawCaret(kTestChar3NewlineCaret);

  // Right of page 0, char 0, skipping one newline.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_LEFT)));
  TestDrawCaret(kTestChar0EndCaret);
}

TEST_F(PdfCaretMoveTest, MoveCharLeftRightSingleSyntheticNewline) {
  SetUpPagesWithCharCounts({3});
  SetUpPagesWithSynthesizedChars({{1}});
  // "a\n"
  // "b".
  SetUpChar(kTestChar0, {kTestChar0ScreenRect});
  SetUpChar(kTestChar1, {});
  SetUpChar(kTestChar2, {gfx::Rect(10, 26, 12, 14)});

  // Start at left of page 0, char 0.
  InitializeVisibleCaretAtChar(kTestChar0);

  // Right of page 0, char 1.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));
  TestDrawCaret(kTestChar0EndCaret);

  // Left of page 0, char 2 'b'.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));
  TestDrawCaret(gfx::Rect(10, 26, 1, 14));

  // Right of page 0, char 1.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_LEFT)));
  TestDrawCaret(kTestChar0EndCaret);
}

TEST_F(PdfCaretMoveTest, MoveCharUpDown) {
  SetUpPagesWithCharCounts({4});
  SetUpPagesWithSynthesizedChars({{1, 2}});
  // "a\r\n"
  // "b".
  SetUpChar(kTestChar0, {kTestChar0ScreenRect});
  SetUpChar(kTestChar1, {});
  SetUpChar(kTestChar2, {});
  SetUpChar(kTestChar3, {gfx::Rect(10, 24, 12, 14)});

  // Start at left of char 0.
  InitializeVisibleCaretAtChar(kTestChar0);

  // Left of char 3 'b'.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));
  TestDrawCaret(gfx::Rect(10, 24, 1, 14));

  // Left of char 0.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_UP)));
  TestDrawCaret(kTestChar0Caret);
}

TEST_F(PdfCaretMoveTest, MoveCharUpDownNonTextPage) {
  SetUpNoTextPageTest();

  InitializeVisibleCaretAtChar(kTestChar0);

  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));
  TestDrawCaret(kDefaultCaret);

  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_UP)));
  TestDrawCaret(kDefaultCaret);
}

TEST_F(PdfCaretMoveTest, MoveCharUpDownSingleLine) {
  SetUpTwoCharLineTest();

  // Start at right of char 0.
  InitializeVisibleCaretAtChar(kTestChar1);

  // Left of char 0.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_UP)));
  TestDrawCaret(kTestChar0Caret);

  // No change.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_UP)));
  TestDrawCaret(kTestChar0Caret);

  // Right of char 1 'b'.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));
  TestDrawCaret(kTestChar1EndCaret);

  // No change.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));
  TestDrawCaret(kTestChar1EndCaret);
}

TEST_F(PdfCaretMoveTest, MoveCharUpDownMultiLine) {
  SetUpMultiLineTest();

  // Start at left of char 1 'b'.
  InitializeVisibleCaretAtChar(kTestChar1);

  // Left of char 5 'd'.
  constexpr gfx::Rect kTestChar5Caret{21, 26, 1, 12};
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));
  TestDrawCaret(kTestChar5Caret);

  // Left of char 9 'f'.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));
  TestDrawCaret(gfx::Rect(24, 50, 1, 16));

  // Left of char 5.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_UP)));
  TestDrawCaret(kTestChar5Caret);

  // Left of char 1.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_UP)));
  TestDrawCaret(kTestChar1Caret);
}

TEST_F(PdfCaretMoveTest, MoveCharUpDownStartOnNewline) {
  SetUpPagesWithCharCounts({6});
  SetUpPagesWithSynthesizedChars({{2, 3}});
  // "ab\r\n"
  // "cd".
  SetUpChar(kTestChar0, {kTestChar0ScreenRect});
  SetUpChar(kTestChar1, {kTestChar1ScreenRect});
  SetUpChar(kTestChar2, {});
  SetUpChar(kTestChar3, {});
  SetUpChar(kTestChar4, {gfx::Rect(10, 22, 12, 14)});
  SetUpChar(kTestChar5, {gfx::Rect(22, 22, 12, 14)});

  // Start at right of char 1 '\r'.
  InitializeVisibleCaretAtChar(kTestChar2);

  TestDrawCaret(kTestChar1EndCaret);

  // Right of char 5 'd'.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));
  TestDrawCaret(gfx::Rect(34, 22, 1, 14));

  // Reset back to right of char 1.
  caret().SetCharAndDraw(kTestChar2);
  TestDrawCaret(kTestChar1EndCaret);

  // Left of char 0 'a'.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_UP)));
  TestDrawCaret(kTestChar0Caret);
}

TEST_F(PdfCaretMoveTest, MoveCharUpDownMultiPage) {
  SetUpMultiPageTest();

  // Start at right of page 0, char 0 'a'.
  InitializeVisibleCaretAtChar(kTestChar1);

  // Left of page 1, char 1 'c', which is closer than the right.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));
  TestDrawCaret(kTestMultiPage1Char1Caret);

  // Top-left of page 2. Page 2 does not have any chars.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));
  TestDrawCaret(kTestMultiPage2NonTextScreenRect);

  // Left of page 3, char 0 'd', which is closer than the right.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));
  TestDrawCaret(kTestMultiPage3Char0Caret);

  // Top-left of page 2.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_UP)));
  TestDrawCaret(kTestMultiPage2NonTextScreenRect);

  // Right of page 1, char 1, which is closer than the left.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_UP)));
  TestDrawCaret(kTestMultiPage1Char1EndCaret);

  // Right of page 0, char 0.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_UP)));
  TestDrawCaret(kTestChar0EndCaret);
}

TEST_F(PdfCaretMoveTest, MoveCharUpDownLongerFirstLine) {
  SetUpPagesWithCharCounts({6});
  SetUpPagesWithSynthesizedChars({{3, 4}});
  // "abc\r\n"
  // "d".
  SetUpChar(kTestChar0, {kTestChar0ScreenRect});
  SetUpChar(kTestChar1, {kTestChar1ScreenRect});
  SetUpChar(kTestChar2, {gfx::Rect(34, 10, 12, 14)});
  SetUpChar(kTestChar3, {});
  SetUpChar(kTestChar4, {});
  SetUpChar(kTestChar5, {gfx::Rect(10, 22, 12, 14)});

  // Start at left of char 2 'c'.
  InitializeVisibleCaretAtChar(kTestChar2);

  TestDrawCaret(kTestChar2Caret);

  // Move down to char with closest screen rect. Right of char 5 'd'.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));
  TestDrawCaret(gfx::Rect(22, 22, 1, 14));

  // Move up to char with closest screen rect. Left of char 1 'b'.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_UP)));
  TestDrawCaret(kTestChar1Caret);
}

TEST_F(PdfCaretMoveTest, MoveCharUpDownLongerSecondLine) {
  SetUpPagesWithCharCounts({6});
  SetUpPagesWithSynthesizedChars({{1, 2}});
  // "a\r\n"
  // "bcd".
  SetUpChar(kTestChar0, {kTestChar0ScreenRect});
  SetUpChar(kTestChar1, {});
  SetUpChar(kTestChar2, {});
  SetUpChar(kTestChar3, {gfx::Rect(10, 22, 12, 14)});
  SetUpChar(kTestChar4, {gfx::Rect(22, 22, 12, 14)});
  SetUpChar(kTestChar5, {gfx::Rect(34, 22, 12, 14)});

  // Start at right of char 5 'd'.
  InitializeVisibleCaretAtChar({0, 6});

  TestDrawCaret(gfx::Rect(46, 22, 1, 14));

  // Right of char 0 'a'.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_UP)));
  TestDrawCaret(kTestChar0EndCaret);

  // Left of char 4 'c'.
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));
  TestDrawCaret(gfx::Rect(22, 22, 1, 14));
}

TEST_F(PdfCaretMoveTest, MoveCharScroll) {
  SetUpMultiPageTest();
  InitializeVisibleCaretAtChar(kTestPage1Char1);

  InSequence sequence;

  EXPECT_CALL(client(), ScrollToChar(kTestPage1Char0));
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_LEFT)));

  EXPECT_CALL(client(), ScrollToChar(kTestPage1Char1));
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));

  // No-text page.
  EXPECT_CALL(client(), ScrollToChar(kTestPage2Char0));
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));

  EXPECT_CALL(client(), ScrollToChar(kTestPage1Char1));
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_UP)));

  caret().SetCharAndDraw(kTestPage3Char0);

  // Page 3 char 1 does not have a screen rect, so scroll to the previous char
  // with one.
  EXPECT_CALL(client(), ScrollToChar(kTestPage3Char0));
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));
}

TEST_F(PdfCaretMoveTest, MoveWordLeftRightNoTextPage) {
  SetUpNoTextPageTest();

  InitializeVisibleCaretAtChar(kTestChar0);

  EXPECT_TRUE(MoveLeftByWordWithKeyboard());
  TestDrawCaret(kDefaultCaret);

  EXPECT_TRUE(MoveRightByWordWithKeyboard());
  TestDrawCaret(kDefaultCaret);
}

TEST_F(PdfCaretMoveTest, MoveWordLeft) {
  SetUpTwoWordWithUnicodeTest();

  // Start at the right of a word.
  InitializeVisibleCaretAtChar(kTestChar5);

  EXPECT_TRUE(MoveLeftByWordWithKeyboard());
  TestDrawCaret(kTestChar3Caret);

  // Start at the middle of a word.
  caret().SetCharAndDraw(kTestChar4);

  EXPECT_TRUE(MoveLeftByWordWithKeyboard());
  TestDrawCaret(kTestChar3Caret);

  // Start at the left of a word.
  caret().SetCharAndDraw(kTestChar3);

  EXPECT_TRUE(MoveLeftByWordWithKeyboard());
  TestDrawCaret(kTestChar0Caret);

  // Start at the start of the PDF.
  caret().SetCharAndDraw(kTestChar0);

  EXPECT_TRUE(MoveLeftByWordWithKeyboard());
  TestDrawCaret(kTestChar0Caret);
}

TEST_F(PdfCaretMoveTest, MoveWordLeftWordBoundaries) {
  SetUpPagesWithCharCounts({5});
  SetUpCharWithUnicode(kTestChar0, 'a', {kTestChar0ScreenRect});
  SetUpCharWithUnicode(kTestChar1, ',', {kTestChar1ScreenRect});
  SetUpCharWithUnicode(kTestChar2, ' ', {});
  SetUpCharWithUnicode(kTestChar3, 'b', {kTestChar3ScreenRect});
  SetUpCharWithUnicode(kTestChar4, 'c', {kTestChar4ScreenRect});

  // Start at two to the right of a word boundary.
  InitializeVisibleCaretAtChar(kTestChar4);

  EXPECT_TRUE(MoveLeftByWordWithKeyboard());
  TestDrawCaret(kTestChar3Caret);

  // Start at the right of a word boundary.
  caret().SetCharAndDraw(kTestChar3);

  EXPECT_TRUE(MoveLeftByWordWithKeyboard());
  // Blink's behavior would be to draw the caret at `kTestChar1Caret`.
  // `PdfCaret`'s behavior is a simpler approximation of Blink's.
  TestDrawCaret(kTestChar0Caret);

  // Start at the second word boundary.
  caret().SetCharAndDraw(kTestChar2);

  EXPECT_TRUE(MoveLeftByWordWithKeyboard());
  // Blink's behavior would be to draw the caret at `kTestChar1Caret`.
  // `PdfCaret`'s behavior is a simpler approximation of Blink's.
  TestDrawCaret(kTestChar0Caret);

  // Start at the first word boundary.
  caret().SetCharAndDraw(kTestChar1);

  EXPECT_TRUE(MoveLeftByWordWithKeyboard());
  TestDrawCaret(kTestChar0Caret);
}

TEST_F(PdfCaretMoveTest, MoveWordLeftNewlines) {
  SetUpPagesWithCharCounts({6});
  SetUpPagesWithSynthesizedChars({{2, 3}});
  SetUpCharWithUnicode(kTestChar0, 'a', {kTestChar0ScreenRect});
  SetUpCharWithUnicode(kTestChar1, ',', {kTestChar1ScreenRect});
  SetUpCharWithUnicode(kTestChar2, '\r', {});
  SetUpCharWithUnicode(kTestChar3, '\n', {});
  SetUpCharWithUnicode(kTestChar4, 'b', {gfx::Rect(10, 24, 12, 14)});
  SetUpCharWithUnicode(kTestChar5, 'c', {gfx::Rect(22, 24, 12, 14)});

  // Start at two to the right of a newline.
  InitializeVisibleCaretAtChar(kTestChar5);

  EXPECT_TRUE(MoveLeftByWordWithKeyboard());
  TestDrawCaret(gfx::Rect(10, 24, 1, 14));

  // Start at the right of a newline.
  caret().SetCharAndDraw(kTestChar4);

  EXPECT_TRUE(MoveLeftByWordWithKeyboard());
  TestDrawCaret(kTestChar2Caret);

  // Start at the second newline.
  caret().SetCharAndDraw(kTestChar3);

  EXPECT_TRUE(MoveLeftByWordWithKeyboard());
  TestDrawCaret(kTestChar2Caret);

  // Start at the first newline to the right of a word boundary.
  caret().SetCharAndDraw(kTestChar2);

  EXPECT_TRUE(MoveLeftByWordWithKeyboard());
  // Blink's behavior would be to draw the caret at `kTestChar1Caret`.
  // `PdfCaret`'s behavior is a simpler approximation of Blink's.
  TestDrawCaret(kTestChar0Caret);
}

TEST_F(PdfCaretMoveTest, MoveWordLeftMultiPage) {
  SetUpPagesWithCharCounts({2, 1, 1});
  SetUpCharWithUnicode(kTestChar0, 'a', {kTestChar0ScreenRect});
  SetUpCharWithUnicode(kTestChar1, ',', {kTestChar1ScreenRect});
  SetUpCharWithUnicode(kTestPage1Char0, 'b', {kTestMultiPage1Char0ScreenRect});
  SetUpCharWithUnicode(kTestPage2Char0, 'c', {kTestMultiPage2Char0ScreenRect});

  // Start at the end of page 2.
  InitializeVisibleCaretAtChar({2, 1});

  EXPECT_TRUE(MoveLeftByWordWithKeyboard());
  TestDrawCaret(kTestMultiPage2Char0Caret);

  // Start at the start of page 2.
  caret().SetCharAndDraw(kTestPage2Char0);

  EXPECT_TRUE(MoveLeftByWordWithKeyboard());
  TestDrawCaret(kTestMultiPage1Char0EndCaret);

  // Start at the start of page 1.
  caret().SetCharAndDraw(kTestPage1Char0);

  EXPECT_TRUE(MoveLeftByWordWithKeyboard());
  TestDrawCaret(kTestChar1EndCaret);
}

TEST_F(PdfCaretMoveTest, MoveWordRight) {
  SetUpTwoWordWithUnicodeTest();

  // Start at the left of a word.
  InitializeVisibleCaretAtChar(kTestChar0);

  EXPECT_TRUE(MoveRightByWordWithKeyboard());
  TestDrawCaret(kTestChar2Caret);

  // Start at the middle of a word.
  caret().SetCharAndDraw(kTestChar1);

  EXPECT_TRUE(MoveRightByWordWithKeyboard());
  TestDrawCaret(kTestChar2Caret);

  // Start at the right of a word.
  caret().SetCharAndDraw(kTestChar2);

  constexpr gfx::Rect kTestChar4EndCaret{70, 10, 1, 14};
  EXPECT_TRUE(MoveRightByWordWithKeyboard());
  TestDrawCaret(kTestChar4EndCaret);

  // Start at the end of the PDF.
  caret().SetCharAndDraw(kTestChar5);

  EXPECT_TRUE(MoveRightByWordWithKeyboard());
  TestDrawCaret(kTestChar4EndCaret);
}

TEST_F(PdfCaretMoveTest, MoveWordRightWordBoundaries) {
  SetUpPagesWithCharCounts({4});
  SetUpCharWithUnicode(kTestChar0, 'a', {kTestChar0ScreenRect});
  SetUpCharWithUnicode(kTestChar1, ' ', {});
  SetUpCharWithUnicode(kTestChar2, '-', {gfx::Rect(34, 10, 12, 14)});
  SetUpCharWithUnicode(kTestChar3, 'c', {kTestChar3ScreenRect});

  // Start at one to the left of a word boundary.
  InitializeVisibleCaretAtChar(kTestChar0);

  EXPECT_TRUE(MoveRightByWordWithKeyboard());
  TestDrawCaret(kTestChar1Caret);

  // Start at the first word boundary.
  caret().SetCharAndDraw(kTestChar1);

  EXPECT_TRUE(MoveRightByWordWithKeyboard());
  // Blink's behavior would be to draw the caret at `kTestChar3Caret`.
  // `PdfCaret`'s behavior is a simpler approximation of Blink's.
  TestDrawCaret(kTestChar2Caret);

  // Start at the second word boundary.
  caret().SetCharAndDraw(kTestChar2);

  constexpr gfx::Rect kTestChar3EndCaret{58, 10, 1, 14};
  EXPECT_TRUE(MoveRightByWordWithKeyboard());
  // Blink's behavior would be to draw the caret at `kTestChar3Caret`.
  // `PdfCaret`'s behavior is a simpler approximation of Blink's.
  TestDrawCaret(kTestChar3EndCaret);

  // Start at the right of a word boundary.
  caret().SetCharAndDraw(kTestChar3);

  EXPECT_TRUE(MoveRightByWordWithKeyboard());
  TestDrawCaret(kTestChar3EndCaret);
}

TEST_F(PdfCaretMoveTest, MoveWordRightNewlines) {
  SetUpPagesWithCharCounts({5});
  SetUpPagesWithSynthesizedChars({{1, 2}});
  SetUpCharWithUnicode(kTestChar0, 'a', {kTestChar0ScreenRect});
  SetUpCharWithUnicode(kTestChar1, '\r', {});
  SetUpCharWithUnicode(kTestChar2, '\n', {});
  SetUpCharWithUnicode(kTestChar3, '-', {gfx::Rect(10, 24, 12, 14)});
  SetUpCharWithUnicode(kTestChar4, 'c', {gfx::Rect(22, 24, 12, 14)});

  // Start at one to the left of a newline.
  InitializeVisibleCaretAtChar(kTestChar0);

  EXPECT_TRUE(MoveRightByWordWithKeyboard());
  TestDrawCaret(kTestChar1Caret);

  // Start at the first newline.
  caret().SetCharAndDraw(kTestChar1);

  constexpr gfx::Rect kTestChar4Caret{22, 24, 1, 14};
  EXPECT_TRUE(MoveRightByWordWithKeyboard());
  TestDrawCaret(kTestChar4Caret);

  // Start at the second newline.
  caret().SetCharAndDraw(kTestChar2);

  EXPECT_TRUE(MoveRightByWordWithKeyboard());
  TestDrawCaret(kTestChar4Caret);

  // Start at the right of a newline to the left of a word boundary.
  caret().SetCharAndDraw(kTestChar3);

  EXPECT_TRUE(MoveRightByWordWithKeyboard());
  TestDrawCaret({34, 24, 1, 14});
}

TEST_F(PdfCaretMoveTest, MoveWordRightMultiPage) {
  SetUpPagesWithCharCounts({1, 2, 1});
  SetUpCharWithUnicode(kTestChar0, 'a', {kTestChar0ScreenRect});
  SetUpCharWithUnicode(kTestPage1Char0, '-', {kTestMultiPage1Char0ScreenRect});
  SetUpCharWithUnicode(kTestPage1Char1, 'b', {kTestMultiPage1Char1ScreenRect});
  SetUpCharWithUnicode(kTestPage2Char0, 'c', {kTestMultiPage2Char0ScreenRect});

  // Start at the start of page 0.
  InitializeVisibleCaretAtChar(kTestChar0);

  EXPECT_TRUE(MoveRightByWordWithKeyboard());
  TestDrawCaret(kTestChar0EndCaret);

  // Start at the end of page 0.
  caret().SetCharAndDraw(kTestChar1);

  EXPECT_TRUE(MoveRightByWordWithKeyboard());
  TestDrawCaret(kTestMultiPage1Char0Caret);

  // Start at the end of page 1.
  caret().SetCharAndDraw({1, 2});

  EXPECT_TRUE(MoveRightByWordWithKeyboard());
  TestDrawCaret(kTestMultiPage2Char0Caret);
}

class PdfCaretSelectionTest : public PdfCaretMoveTest {
 public:
  // Same as `SetUpTwoCharLineTest()` but with unicode chars.
  void SetUpTwoCharLineWithUnicodeTest() {
    SetUpPagesWithCharCounts({2});
    SetUpCharWithUnicode(kTestChar0, 'a', {kTestChar0ScreenRect});
    SetUpCharWithUnicode(kTestChar1, 'b', {kTestChar1ScreenRect});
  }

  blink::WebKeyboardEvent GenerateShiftKeyboardEvent(ui::KeyboardCode key) {
    blink::WebKeyboardEvent event = GenerateKeyboardEvent(key);
    event.SetModifiers(blink::WebInputEvent::Modifiers::kShiftKey);
    return event;
  }

  void ExpectExtendAndInvalidateSelectionByChar(
      const PageCharacterIndex& index) {
    EXPECT_CALL(client(), ExtendAndInvalidateSelectionByChar(index))
        .WillOnce([&]() {
          // When text selection is extended, the client will normally hide the
          // caret.
          caret().SetVisible(false);
        });
  }
};

TEST_F(PdfCaretSelectionTest, SelectRight) {
  SetUpTwoCharLineTest();

  // Start at left of char 0.
  InitializeVisibleCaretAtChar(kTestChar0);

  // Move right. Select char 1.
  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(false));
  EXPECT_CALL(client(), StartSelection(kTestChar0));
  ExpectExtendAndInvalidateSelectionByChar(kTestChar1);
  EXPECT_TRUE(caret().OnKeyDown(
      GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));

  TestDrawCaretFails(kTestChar1Caret);

  // Move right. Select char 2.
  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(true));
  ExpectExtendAndInvalidateSelectionByChar(kTestChar2);
  EXPECT_TRUE(caret().OnKeyDown(
      GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));

  TestDrawCaretFails(kTestChar1EndCaret);

  // Move right without shift.
  EXPECT_CALL(client(), ClearTextSelection());
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));
  TestDrawCaret(kTestChar1EndCaret);
}

TEST_F(PdfCaretSelectionTest, SelectLeft) {
  SetUpTwoCharLineTest();

  // Start at right of char 1.
  InitializeVisibleCaretAtChar(kTestChar2);

  // Move left. Select char 1.
  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(false));
  EXPECT_CALL(client(), StartSelection(kTestChar2));
  ExpectExtendAndInvalidateSelectionByChar(kTestChar1);
  EXPECT_TRUE(caret().OnKeyDown(
      GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_LEFT)));

  TestDrawCaretFails(kTestChar1Caret);

  // Move left. Select char 0.
  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(true));
  ExpectExtendAndInvalidateSelectionByChar(kTestChar0);
  EXPECT_TRUE(caret().OnKeyDown(
      GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_LEFT)));

  TestDrawCaretFails(kTestChar0Caret);

  // Move left without shift.
  EXPECT_CALL(client(), ClearTextSelection());
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_LEFT)));
  TestDrawCaret(kTestChar0Caret);
}

TEST_F(PdfCaretSelectionTest, SelectDown) {
  SetUpMultiLineTest();

  // Start at left of char 1 'b'.
  InitializeVisibleCaretAtChar(kTestChar1);

  // Move down. Select chars 1, 2, 3, 4.
  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(false));
  EXPECT_CALL(client(), StartSelection(kTestChar1));
  ExpectExtendAndInvalidateSelectionByChar(PageCharacterIndex(0, 5));
  EXPECT_TRUE(caret().OnKeyDown(
      GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));

  // Move down. Select chars 5, 6, 7, 8.
  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(true));
  ExpectExtendAndInvalidateSelectionByChar(PageCharacterIndex(0, 9));
  EXPECT_TRUE(caret().OnKeyDown(
      GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));

  // Move down. Select char 9 'f' (end of page).
  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(true));
  ExpectExtendAndInvalidateSelectionByChar(PageCharacterIndex(0, 10));
  EXPECT_TRUE(caret().OnKeyDown(
      GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));

  constexpr gfx::Rect kTestChar9EndCaret{38, 50, 1, 16};
  TestDrawCaretFails(kTestChar9EndCaret);

  // Move down without shift.
  EXPECT_CALL(client(), ClearTextSelection());
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));
  TestDrawCaret(kTestChar9EndCaret);
}

TEST_F(PdfCaretSelectionTest, SelectUp) {
  SetUpMultiLineTest();

  // Start at left of char 9 'f'.
  constexpr PageCharacterIndex kTestChar9{0, 9};
  InitializeVisibleCaretAtChar(kTestChar9);

  // Move up. Select chars 8, 7, 6, 5.
  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(false));
  EXPECT_CALL(client(), StartSelection(kTestChar9));
  ExpectExtendAndInvalidateSelectionByChar(PageCharacterIndex(0, 5));
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_UP)));

  // Move up. Select chars 4, 3, 2, 1.
  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(true));
  ExpectExtendAndInvalidateSelectionByChar(kTestChar1);
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_UP)));

  // Move up. Select char 0 'a' (start of page).
  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(true));
  ExpectExtendAndInvalidateSelectionByChar(kTestChar0);
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_UP)));

  TestDrawCaretFails(kTestChar0Caret);

  // Move up without shift.
  EXPECT_CALL(client(), ClearTextSelection());
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateKeyboardEvent(ui::KeyboardCode::VKEY_UP)));
  TestDrawCaret(kTestChar0Caret);
}

TEST_F(PdfCaretSelectionTest, SelectCtrlRight) {
  SetUpTwoCharLineWithUnicodeTest();

  // Start at left of char 0.
  InitializeVisibleCaretAtChar(kTestChar0);

  // Move right by word, selecting chars 0 and 1.
  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(false));
  EXPECT_CALL(client(), StartSelection(kTestChar0));
  ExpectExtendAndInvalidateSelectionByChar(kTestChar2);
  blink::WebKeyboardEvent event =
      GenerateControlKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT);
  event.SetModifiers(event.GetModifiers() |
                     blink::WebInputEvent::Modifiers::kShiftKey);
  EXPECT_TRUE(caret().OnKeyDown(event));

  TestDrawCaretFails(kTestChar1EndCaret);

  // Move right without shift.
  EXPECT_CALL(client(), ClearTextSelection());
  EXPECT_TRUE(MoveRightByWordWithKeyboard());
  TestDrawCaret(kTestChar1EndCaret);
}

TEST_F(PdfCaretSelectionTest, SelectCtrlLeft) {
  SetUpTwoCharLineWithUnicodeTest();

  // Start at right of char 1.
  InitializeVisibleCaretAtChar(kTestChar2);

  // Move left by word, selecting chars 0 and 1.
  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(false));
  EXPECT_CALL(client(), StartSelection(kTestChar2));
  ExpectExtendAndInvalidateSelectionByChar(kTestChar0);
  blink::WebKeyboardEvent event =
      GenerateControlKeyboardEvent(ui::KeyboardCode::VKEY_LEFT);
  event.SetModifiers(event.GetModifiers() |
                     blink::WebInputEvent::Modifiers::kShiftKey);
  EXPECT_TRUE(caret().OnKeyDown(event));

  TestDrawCaretFails(kTestChar0Caret);

  // Move left without shift.
  EXPECT_CALL(client(), ClearTextSelection());
  EXPECT_TRUE(MoveLeftByWordWithKeyboard());
  TestDrawCaret(kTestChar0Caret);
}

TEST_F(PdfCaretSelectionTest, SelectStartOnNonTextPageMoveToNonTextPage) {
  SetUpPagesWithCharCounts({0, 0});
  // Page 0: No text.
  // Page 1: No text.
  SetUpChar(kTestChar0, {kDefaultScreenRect});
  SetUpChar(kTestPage1Char0, {gfx::Rect(10, 50, 1, 12)});

  InitializeVisibleCaretAtChar(kTestChar0);

  // Moving from a no-text page to another no-text page should not start a
  // selection.
  EXPECT_CALL(client(), StartSelection(_)).Times(0);

  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(false));
  EXPECT_TRUE(caret().OnKeyDown(
      GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));

  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(false));
  EXPECT_TRUE(caret().OnKeyDown(
      GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_LEFT)));
}

TEST_F(PdfCaretSelectionTest, SelectStartOnTextPageMoveToNonTextPages) {
  SetUpPagesWithCharCounts({1, 0, 0});
  // Page 0: "\0".
  // Page 1: No text.
  // Page 2: No text.
  SetUpChar(kTestChar0, {kTestChar0ScreenRect});
  SetUpChar(kTestPage1Char0, {gfx::Rect(10, 50, 1, 12)});
  SetUpChar(kTestPage2Char0, {gfx::Rect(10, 100, 1, 12)});

  InitializeVisibleCaretAtChar(kTestChar0);

  // Select page 0, char 0.
  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(false));
  EXPECT_CALL(client(), StartSelection(kTestChar0));
  ExpectExtendAndInvalidateSelectionByChar(kTestChar1);
  EXPECT_TRUE(caret().OnKeyDown(
      GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));

  // Moving to multiple no-text pages should not extend selection.
  EXPECT_CALL(client(), ExtendAndInvalidateSelectionByChar(_)).Times(0);

  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(true));
  EXPECT_TRUE(caret().OnKeyDown(
      GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));

  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(true));
  EXPECT_TRUE(caret().OnKeyDown(
      GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));
}

TEST_F(PdfCaretSelectionTest, SelectNonTextPage) {
  SetUpNoTextPageTest();

  InitializeVisibleCaretAtChar(kTestChar0);

  EXPECT_CALL(client(), StartSelection(_)).Times(0);
  EXPECT_CALL(client(), ExtendAndInvalidateSelectionByChar(_)).Times(0);

  EXPECT_TRUE(caret().OnKeyDown(
      GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_LEFT)));
  EXPECT_TRUE(caret().OnKeyDown(
      GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_RIGHT)));
  EXPECT_TRUE(caret().OnKeyDown(
      GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_UP)));
}

TEST_F(PdfCaretSelectionTest, SelectStartingOnNonTextPage) {
  SetUpMultiPageTest();

  // Start on the no-text page.
  InitializeVisibleCaretAtChar(kTestPage2Char0);

  // `StartSelection()` should be called on the nearest caret position in the
  // direction of movement. In this case, it would be right of page 1, char 1.
  constexpr PageCharacterIndex kTestPage1Char1End{1, 2};
  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(false));
  EXPECT_CALL(client(), StartSelection(kTestPage1Char1End));
  ExpectExtendAndInvalidateSelectionByChar(kTestPage1Char1End);
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_UP)));
}

TEST_F(PdfCaretSelectionTest, MoveCaretWithShiftDownMultiPage) {
  SetUpMultiPageTest();

  // Start at right of page 0, char 0.
  InitializeVisibleCaretAtChar(kTestChar1);

  // Move down. Select page 1, char 0 'b'.
  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(false));
  EXPECT_CALL(client(), StartSelection(kTestChar1));
  ExpectExtendAndInvalidateSelectionByChar(kTestPage1Char1);
  EXPECT_TRUE(caret().OnKeyDown(
      GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));

  // Move down. Select page 1, char 1 'c'. Caret should be on the no-text page.
  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(true));
  ExpectExtendAndInvalidateSelectionByChar(PageCharacterIndex(1, 2));
  EXPECT_TRUE(caret().OnKeyDown(
      GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));

  // Move down. The selection should extend past the no-text page.
  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(true));
  ExpectExtendAndInvalidateSelectionByChar(PageCharacterIndex(3, 0));
  EXPECT_TRUE(caret().OnKeyDown(
      GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));

  // Move down. Select page 3, char 0 'd'.
  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(true));
  ExpectExtendAndInvalidateSelectionByChar(PageCharacterIndex(3, 1));
  EXPECT_TRUE(caret().OnKeyDown(
      GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_DOWN)));
}

TEST_F(PdfCaretSelectionTest, MoveCaretWithShiftUpMultiPage) {
  SetUpMultiPageTest();

  // Start at right of page 3, char 0 'd'.
  InitializeVisibleCaretAtChar({3, 1});

  // Move up. Select page 3, char 0. Caret should be on no-text page.
  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(false));
  EXPECT_CALL(client(), StartSelection(PageCharacterIndex(3, 1)));
  ExpectExtendAndInvalidateSelectionByChar(PageCharacterIndex(3, 0));
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_UP)));

  // Move up. The selection should extend past the no-text page.
  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(true));
  ExpectExtendAndInvalidateSelectionByChar(PageCharacterIndex(1, 2));
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_UP)));

  // Move up. Select page 1, char 1 'c' and page 1, char 0 'b'.
  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(true));
  ExpectExtendAndInvalidateSelectionByChar(kTestChar1);
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_UP)));

  // Move up. Select page 0, char 0 'a'.
  EXPECT_CALL(client(), IsSelecting()).WillOnce(Return(true));
  ExpectExtendAndInvalidateSelectionByChar(kTestChar0);
  EXPECT_TRUE(
      caret().OnKeyDown(GenerateShiftKeyboardEvent(ui::KeyboardCode::VKEY_UP)));
}

class PdfCaretMoveWithTextDirectionTest : public PdfCaretMoveTest {
 public:
  void SetUpTextDirectionTest(AccessibilityTextDirection direction) {
    // To simplify tests, just use a single character.
    SetUpSingleCharLineTest();
    EXPECT_CALL(client(), GetTextRunInfoAt(_))
        .WillRepeatedly(Return(GenerateTestTextRunInfo(direction)));
    InitializeVisibleCaretAtChar(kTestChar0);
  }

  void TestMove(ui::KeyboardCode key, const gfx::Rect& expected_caret) {
    EXPECT_TRUE(caret().OnKeyDown(GenerateKeyboardEvent(key)));
    TestDrawCaret(expected_caret);
  }
};

TEST_F(PdfCaretMoveWithTextDirectionTest, LeftToRight) {
  SetUpTextDirectionTest(AccessibilityTextDirection::kLeftToRight);
  TestDrawCaret(kTestChar0Caret);
  TestMove(ui::KeyboardCode::VKEY_RIGHT, kTestChar0EndCaret);
  TestMove(ui::KeyboardCode::VKEY_LEFT, kTestChar0Caret);
  TestMove(ui::KeyboardCode::VKEY_DOWN, kTestChar0EndCaret);
  TestMove(ui::KeyboardCode::VKEY_UP, kTestChar0Caret);
}

TEST_F(PdfCaretMoveWithTextDirectionTest, RightToLeft) {
  SetUpTextDirectionTest(AccessibilityTextDirection::kRightToLeft);
  TestDrawCaret(kTestChar0EndCaret);
  TestMove(ui::KeyboardCode::VKEY_LEFT, kTestChar0Caret);
  TestMove(ui::KeyboardCode::VKEY_RIGHT, kTestChar0EndCaret);
  TestMove(ui::KeyboardCode::VKEY_DOWN, kTestChar0Caret);
  TestMove(ui::KeyboardCode::VKEY_UP, kTestChar0EndCaret);
}

TEST_F(PdfCaretMoveWithTextDirectionTest, TopToBottom) {
  SetUpTextDirectionTest(AccessibilityTextDirection::kTopToBottom);
  TestDrawCaret(kTestChar0TopCaret);
  TestMove(ui::KeyboardCode::VKEY_DOWN, kTestChar0BottomCaret);
  TestMove(ui::KeyboardCode::VKEY_UP, kTestChar0TopCaret);
  TestMove(ui::KeyboardCode::VKEY_LEFT, kTestChar0BottomCaret);
  TestMove(ui::KeyboardCode::VKEY_RIGHT, kTestChar0TopCaret);
}

TEST_F(PdfCaretMoveWithTextDirectionTest, BottomToTop) {
  SetUpTextDirectionTest(AccessibilityTextDirection::kBottomToTop);
  TestDrawCaret(kTestChar0BottomCaret);
  TestMove(ui::KeyboardCode::VKEY_UP, kTestChar0TopCaret);
  TestMove(ui::KeyboardCode::VKEY_DOWN, kTestChar0BottomCaret);
  TestMove(ui::KeyboardCode::VKEY_LEFT, kTestChar0TopCaret);
  TestMove(ui::KeyboardCode::VKEY_RIGHT, kTestChar0BottomCaret);
}

}  // namespace

}  // namespace chrome_pdf
