blob: 9dfd221c9445bcaeab8033260977856318bcd88d [file] [log] [blame]
// Copyright 2017 PDFium 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 "xfa/fde/cfde_texteditengine.h"
#include "core/fxcrt/fx_extension.h"
#include "core/fxge/text_char_pos.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/xfa_test_environment.h"
#include "xfa/fgas/font/cfgas_gefont.h"
class CFDE_TextEditEngineTest : public testing::Test {
public:
class Delegate final : public CFDE_TextEditEngine::Delegate {
public:
void Reset() {
text_is_full = false;
fail_validation = false;
}
void NotifyTextFull() override { text_is_full = true; }
void OnCaretChanged() override {}
void OnTextWillChange(CFDE_TextEditEngine::TextChange* change) override {}
void OnTextChanged() override {}
void OnSelChanged() override {}
bool OnValidate(const WideString& wsText) override {
return !fail_validation;
}
void SetScrollOffset(float fScrollOffset) override {}
bool fail_validation = false;
bool text_is_full = false;
};
CFDE_TextEditEngineTest() = default;
~CFDE_TextEditEngineTest() override = default;
void SetUp() override {
font_ = CFGAS_GEFont::LoadFont(L"Arial Black", 0, 0);
ASSERT_TRUE(font_);
engine_ = std::make_unique<CFDE_TextEditEngine>();
engine_->SetFont(font_);
engine_->SetFontSize(12.0f);
}
void TearDown() override {
engine_.reset();
font_.Reset();
}
CFDE_TextEditEngine* engine() const { return engine_.get(); }
private:
RetainPtr<CFGAS_GEFont> font_;
std::unique_ptr<CFDE_TextEditEngine> engine_;
};
TEST_F(CFDE_TextEditEngineTest, Insert) {
EXPECT_STREQ(L"", engine()->GetText().c_str());
engine()->Insert(0, L"");
EXPECT_STREQ(L"", engine()->GetText().c_str());
EXPECT_EQ(0U, engine()->GetLength());
engine()->Insert(0, L"Hello");
EXPECT_STREQ(L"Hello", engine()->GetText().c_str());
EXPECT_EQ(5U, engine()->GetLength());
engine()->Insert(5, L" World");
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
EXPECT_EQ(11U, engine()->GetLength());
engine()->Insert(5, L" New");
EXPECT_STREQ(L"Hello New World", engine()->GetText().c_str());
engine()->Insert(100, L" Cat");
EXPECT_STREQ(L"Hello New World Cat", engine()->GetText().c_str());
engine()->Clear();
engine()->SetHasCharacterLimit(true);
engine()->SetCharacterLimit(5);
engine()->Insert(0, L"Hello");
// No delegate
engine()->Insert(5, L" World");
EXPECT_STREQ(L"Hello", engine()->GetText().c_str());
engine()->SetCharacterLimit(8);
engine()->Insert(5, L" World");
EXPECT_STREQ(L"Hello Wo", engine()->GetText().c_str());
engine()->Clear();
// With Delegate
auto delegate = std::make_unique<CFDE_TextEditEngineTest::Delegate>();
engine()->SetDelegate(delegate.get());
engine()->SetCharacterLimit(5);
engine()->Insert(0, L"Hello");
// Insert when full.
engine()->Insert(5, L" World");
EXPECT_TRUE(delegate->text_is_full);
EXPECT_STREQ(L"Hello", engine()->GetText().c_str());
delegate->Reset();
engine()->SetCharacterLimit(8);
engine()->Insert(5, L" World");
EXPECT_TRUE(delegate->text_is_full);
EXPECT_STREQ(L"Hello Wo", engine()->GetText().c_str());
delegate->Reset();
engine()->SetHasCharacterLimit(false);
engine()->Clear();
engine()->Insert(0, L"Hello");
// Insert Invalid text
delegate->fail_validation = true;
engine()->EnableValidation(true);
engine()->Insert(5, L" World");
EXPECT_STREQ(L"Hello", engine()->GetText().c_str());
delegate->fail_validation = false;
engine()->Insert(5, L" World");
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
engine()->EnableValidation(false);
engine()->Clear();
engine()->Insert(0, L"Hello\nWorld");
EXPECT_FALSE(delegate->text_is_full);
EXPECT_STREQ(L"Hello\nWorld", engine()->GetText().c_str());
delegate->Reset();
engine()->Clear();
// Insert with limited area and over-fill
engine()->LimitHorizontalScroll(true);
engine()->SetAvailableWidth(60.0f); // Fits 'Hello Wo'.
engine()->Insert(0, L"Hello");
EXPECT_FALSE(delegate->text_is_full);
engine()->Insert(5, L" World");
EXPECT_TRUE(delegate->text_is_full);
EXPECT_STREQ(L"Hello Wo", engine()->GetText().c_str());
engine()->LimitHorizontalScroll(false);
delegate->Reset();
engine()->Clear();
engine()->SetLineSpace(12.0f);
engine()->LimitVerticalScroll(true);
// Default is one line of text.
engine()->Insert(0, L"Hello");
EXPECT_FALSE(delegate->text_is_full);
engine()->Insert(5, L" Wo\nrld");
EXPECT_TRUE(delegate->text_is_full);
EXPECT_STREQ(L"Hello Wo\n", engine()->GetText().c_str());
engine()->LimitVerticalScroll(false);
engine()->SetDelegate(nullptr);
}
TEST_F(CFDE_TextEditEngineTest, InsertToggleLimit) {
engine()->SetHasCharacterLimit(true);
engine()->Insert(0, L"Hello World");
engine()->SetCharacterLimit(5);
engine()->Insert(0, L"Not Inserted before ");
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
engine()->SetHasCharacterLimit(false);
engine()->Insert(0, L"Inserted before ");
engine()->SetHasCharacterLimit(true);
engine()->Insert(0, L"Not Inserted before ");
EXPECT_STREQ(L"Inserted before Hello World", engine()->GetText().c_str());
}
TEST_F(CFDE_TextEditEngineTest, InsertSkipNotify) {
engine()->SetHasCharacterLimit(true);
engine()->SetCharacterLimit(8);
engine()->Insert(0, L"Hello");
engine()->Insert(5, L" World",
CFDE_TextEditEngine::RecordOperation::kSkipNotify);
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
engine()->Insert(0, L"Not inserted");
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
engine()->Delete(5, 1);
EXPECT_STREQ(L"HelloWorld", engine()->GetText().c_str());
engine()->Insert(0, L"****");
EXPECT_STREQ(L"*HelloWorld", engine()->GetText().c_str());
}
TEST_F(CFDE_TextEditEngineTest, InsertGrowGap) {
engine()->Insert(0, L"||");
for (size_t i = 1; i < 1023; ++i) {
engine()->Insert(i, L"a");
}
WideString result = engine()->GetText();
ASSERT_EQ(result.GetLength(), 1024u);
EXPECT_EQ(result[0], L'|');
EXPECT_EQ(result[1], L'a');
EXPECT_EQ(result[2], L'a');
// ...
EXPECT_EQ(result[1022], L'a');
EXPECT_EQ(result[1023], L'|');
}
TEST_F(CFDE_TextEditEngineTest, Delete) {
EXPECT_STREQ(L"", engine()->Delete(0, 50).c_str());
EXPECT_STREQ(L"", engine()->GetText().c_str());
engine()->Insert(0, L"Hello World");
EXPECT_STREQ(L" World", engine()->Delete(5, 6).c_str());
EXPECT_STREQ(L"Hello", engine()->GetText().c_str());
engine()->Clear();
engine()->Insert(0, L"Hello World");
EXPECT_STREQ(L" ", engine()->Delete(5, 1).c_str());
EXPECT_STREQ(L"HelloWorld", engine()->GetText().c_str());
EXPECT_STREQ(L"elloWorld", engine()->Delete(1, 50).c_str());
EXPECT_STREQ(L"H", engine()->GetText().c_str());
}
TEST_F(CFDE_TextEditEngineTest, Clear) {
EXPECT_STREQ(L"", engine()->GetText().c_str());
engine()->Clear();
EXPECT_STREQ(L"", engine()->GetText().c_str());
engine()->Insert(0, L"Hello World");
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
engine()->Clear();
EXPECT_STREQ(L"", engine()->GetText().c_str());
EXPECT_EQ(0U, engine()->GetLength());
}
TEST_F(CFDE_TextEditEngineTest, GetChar) {
// Out of bounds.
EXPECT_EQ(L'\0', engine()->GetChar(0));
engine()->Insert(0, L"Hello World");
EXPECT_EQ(L'H', engine()->GetChar(0));
EXPECT_EQ(L'd', engine()->GetChar(engine()->GetLength() - 1));
EXPECT_EQ(L' ', engine()->GetChar(5));
engine()->Insert(5, L" A");
EXPECT_STREQ(L"Hello A World", engine()->GetText().c_str());
EXPECT_EQ(L'W', engine()->GetChar(8));
engine()->EnablePasswordMode(true);
EXPECT_EQ(L'*', engine()->GetChar(8));
engine()->SetAliasChar(L'+');
EXPECT_EQ(L'+', engine()->GetChar(8));
}
TEST_F(CFDE_TextEditEngineTest, GetWidthOfChar) {
// Out of Bounds.
EXPECT_EQ(0U, engine()->GetWidthOfChar(0));
engine()->Insert(0, L"Hello World");
EXPECT_EQ(199920U, engine()->GetWidthOfChar(0));
EXPECT_EQ(159840U, engine()->GetWidthOfChar(1));
engine()->Insert(0, L"\t");
EXPECT_EQ(0U, engine()->GetWidthOfChar(0));
}
TEST_F(CFDE_TextEditEngineTest, GetDisplayPos) {
EXPECT_EQ(0U, engine()->GetDisplayPos(FDE_TEXTEDITPIECE()).size());
}
TEST_F(CFDE_TextEditEngineTest, Selection) {
EXPECT_FALSE(engine()->HasSelection());
engine()->SelectAll();
EXPECT_FALSE(engine()->HasSelection());
engine()->Insert(0, L"Hello World");
EXPECT_STREQ(L"", engine()->DeleteSelectedText().c_str());
EXPECT_FALSE(engine()->HasSelection());
engine()->SelectAll();
EXPECT_TRUE(engine()->HasSelection());
EXPECT_STREQ(L"Hello World", engine()->GetSelectedText().c_str());
engine()->ClearSelection();
EXPECT_FALSE(engine()->HasSelection());
EXPECT_STREQ(L"", engine()->GetSelectedText().c_str());
engine()->SelectAll();
size_t start_idx;
size_t count;
std::tie(start_idx, count) = engine()->GetSelection();
EXPECT_EQ(0U, start_idx);
EXPECT_EQ(11U, count);
// Selection before gap.
EXPECT_STREQ(L"Hello World", engine()->GetSelectedText().c_str());
EXPECT_TRUE(engine()->HasSelection());
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
engine()->Insert(5, L" A");
EXPECT_FALSE(engine()->HasSelection());
EXPECT_STREQ(L"", engine()->GetSelectedText().c_str());
// Selection over the gap.
engine()->SelectAll();
EXPECT_TRUE(engine()->HasSelection());
EXPECT_STREQ(L"Hello A World", engine()->GetSelectedText().c_str());
engine()->Clear();
engine()->Insert(0, L"Hello World");
engine()->SelectAll();
EXPECT_STREQ(L"Hello World", engine()->DeleteSelectedText().c_str());
EXPECT_FALSE(engine()->HasSelection());
EXPECT_STREQ(L"", engine()->GetText().c_str());
engine()->Insert(0, L"Hello World");
engine()->SetSelection(5, 5);
EXPECT_STREQ(L" Worl", engine()->DeleteSelectedText().c_str());
EXPECT_FALSE(engine()->HasSelection());
EXPECT_STREQ(L"Hellod", engine()->GetText().c_str());
engine()->Clear();
engine()->Insert(0, L"Hello World");
engine()->SelectAll();
engine()->ReplaceSelectedText(L"Goodbye Everybody");
EXPECT_FALSE(engine()->HasSelection());
EXPECT_STREQ(L"Goodbye Everybody", engine()->GetText().c_str());
engine()->Clear();
engine()->Insert(0, L"Hello World");
engine()->SetSelection(1, 4);
engine()->ReplaceSelectedText(L"i,");
EXPECT_FALSE(engine()->HasSelection());
EXPECT_STREQ(L"Hi, World", engine()->GetText().c_str());
// Selection fully after gap.
engine()->Clear();
engine()->Insert(0, L"Hello");
engine()->Insert(0, L"A ");
engine()->SetSelection(3, 6);
EXPECT_STREQ(L"ello", engine()->GetSelectedText().c_str());
engine()->Clear();
engine()->Insert(0, L"Hello World");
engine()->ClearSelection();
engine()->DeleteSelectedText();
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
}
TEST_F(CFDE_TextEditEngineTest, UndoRedo) {
EXPECT_FALSE(engine()->CanUndo());
EXPECT_FALSE(engine()->CanRedo());
EXPECT_FALSE(engine()->Undo());
EXPECT_FALSE(engine()->Redo());
engine()->Insert(0, L"Hello");
EXPECT_TRUE(engine()->CanUndo());
EXPECT_FALSE(engine()->CanRedo());
EXPECT_TRUE(engine()->Undo());
EXPECT_STREQ(L"", engine()->GetText().c_str());
EXPECT_FALSE(engine()->CanUndo());
EXPECT_TRUE(engine()->CanRedo());
EXPECT_TRUE(engine()->Redo());
EXPECT_STREQ(L"Hello", engine()->GetText().c_str());
EXPECT_TRUE(engine()->CanUndo());
EXPECT_FALSE(engine()->CanRedo());
engine()->Clear();
EXPECT_FALSE(engine()->CanUndo());
EXPECT_FALSE(engine()->CanRedo());
engine()->Insert(0, L"Hello World");
engine()->SelectAll();
engine()->DeleteSelectedText();
EXPECT_STREQ(L"", engine()->GetText().c_str());
EXPECT_TRUE(engine()->CanUndo());
EXPECT_TRUE(engine()->Undo());
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
EXPECT_TRUE(engine()->CanRedo());
EXPECT_TRUE(engine()->Redo());
EXPECT_STREQ(L"", engine()->GetText().c_str());
EXPECT_TRUE(engine()->CanUndo());
EXPECT_FALSE(engine()->CanRedo());
engine()->Insert(0, L"Hello World");
engine()->SelectAll();
engine()->ReplaceSelectedText(L"Goodbye Friend");
EXPECT_STREQ(L"Goodbye Friend", engine()->GetText().c_str());
EXPECT_TRUE(engine()->CanUndo());
EXPECT_TRUE(engine()->Undo());
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
EXPECT_TRUE(engine()->CanRedo());
EXPECT_TRUE(engine()->Redo());
EXPECT_STREQ(L"Goodbye Friend", engine()->GetText().c_str());
engine()->Clear();
engine()->SetMaxEditOperationsForTesting(3);
engine()->Insert(0, L"First ");
engine()->Insert(engine()->GetLength(), L"Second ");
engine()->Insert(engine()->GetLength(), L"Third");
EXPECT_TRUE(engine()->CanUndo());
EXPECT_TRUE(engine()->Undo());
EXPECT_STREQ(L"First Second ", engine()->GetText().c_str());
EXPECT_TRUE(engine()->CanUndo());
EXPECT_TRUE(engine()->Undo());
EXPECT_FALSE(
engine()->CanUndo()); // Can't undo First; undo buffer too small.
EXPECT_STREQ(L"First ", engine()->GetText().c_str());
EXPECT_TRUE(engine()->CanRedo());
EXPECT_TRUE(engine()->Redo());
EXPECT_TRUE(engine()->CanRedo());
EXPECT_TRUE(engine()->Redo());
EXPECT_FALSE(engine()->CanRedo());
EXPECT_STREQ(L"First Second Third", engine()->GetText().c_str());
engine()->Clear();
engine()->SetMaxEditOperationsForTesting(4);
// Go beyond the max operations limit.
engine()->Insert(0, L"H");
engine()->Insert(1, L"e");
engine()->Insert(2, L"l");
engine()->Insert(3, L"l");
engine()->Insert(4, L"o");
engine()->Insert(5, L" World");
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
// Do A, undo. Do B, undo. Redo should cause B.
engine()->Delete(4, 3);
EXPECT_STREQ(L"Hellorld", engine()->GetText().c_str());
EXPECT_TRUE(engine()->Undo());
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
engine()->Delete(5, 6);
EXPECT_STREQ(L"Hello", engine()->GetText().c_str());
EXPECT_TRUE(engine()->Undo());
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
EXPECT_TRUE(engine()->Redo());
EXPECT_STREQ(L"Hello", engine()->GetText().c_str());
// Undo down to the limit.
EXPECT_TRUE(engine()->Undo());
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
EXPECT_TRUE(engine()->Undo());
EXPECT_STREQ(L"Hello", engine()->GetText().c_str());
EXPECT_TRUE(engine()->Undo());
EXPECT_STREQ(L"Hell", engine()->GetText().c_str());
EXPECT_FALSE(engine()->Undo());
EXPECT_STREQ(L"Hell", engine()->GetText().c_str());
}
TEST_F(CFDE_TextEditEngineTest, GetIndexForPoint) {
engine()->SetFontSize(10.0f);
engine()->Insert(0, L"Hello World");
EXPECT_EQ(0U, engine()->GetIndexForPoint({0.0f, 0.0f}));
EXPECT_EQ(11U, engine()->GetIndexForPoint({999999.0f, 9999999.0f}));
EXPECT_EQ(11U, engine()->GetIndexForPoint({999999.0f, 0.0f}));
EXPECT_EQ(1U, engine()->GetIndexForPoint({5.0f, 5.0f}));
EXPECT_EQ(1U, engine()->GetIndexForPoint({10.0f, 5.0f}));
}
TEST_F(CFDE_TextEditEngineTest, GetIndexForPointLineWrap) {
engine()->SetFontSize(10.0f);
engine()->Insert(0,
L"A text long enough to span multiple lines and test "
L"getting indexes on multi-line edits.");
EXPECT_EQ(0U, engine()->GetIndexForPoint({0.0f, 0.0f}));
EXPECT_EQ(87U, engine()->GetIndexForPoint({999999.0f, 9999999.0f}));
EXPECT_EQ(11U, engine()->GetIndexForPoint({999999.0f, 0.0f}));
EXPECT_EQ(12U, engine()->GetIndexForPoint({1.0f, 10.0f}));
EXPECT_EQ(1U, engine()->GetIndexForPoint({5.0f, 5.0f}));
EXPECT_EQ(2U, engine()->GetIndexForPoint({10.0f, 5.0f}));
}
TEST_F(CFDE_TextEditEngineTest, GetIndexForPointSpaceAtEnd) {
engine()->SetFontSize(10.0f);
engine()->Insert(0, L"Hello World ");
EXPECT_EQ(0U, engine()->GetIndexForPoint({0.0f, 0.0f}));
EXPECT_EQ(12U, engine()->GetIndexForPoint({999999.0f, 9999999.0f}));
EXPECT_EQ(12U, engine()->GetIndexForPoint({999999.0f, 0.0f}));
}
TEST_F(CFDE_TextEditEngineTest, GetIndexForPointLineBreaks) {
engine()->SetFontSize(10.0f);
engine()->Insert(0, L"Hello\nWorld");
EXPECT_EQ(0U, engine()->GetIndexForPoint({0.0f, 0.0f}));
EXPECT_EQ(5U, engine()->GetIndexForPoint({999999.0f, 0.0f}));
EXPECT_EQ(6U, engine()->GetIndexForPoint({0.0f, 10.0f}));
EXPECT_EQ(11U, engine()->GetIndexForPoint({999999.0f, 9999999.0f}));
}
TEST_F(CFDE_TextEditEngineTest, CanGenerateCharacterInfo) {
RetainPtr<CFGAS_GEFont> font = engine()->GetFont();
ASSERT_TRUE(font);
// Has font but no text.
EXPECT_FALSE(engine()->CanGenerateCharacterInfo());
// Has font and text.
engine()->Insert(0, L"Hi!");
EXPECT_TRUE(engine()->CanGenerateCharacterInfo());
// Has text but no font.
engine()->SetFont(nullptr);
EXPECT_FALSE(engine()->CanGenerateCharacterInfo());
// Has no text and no font.
engine()->Clear();
EXPECT_FALSE(engine()->CanGenerateCharacterInfo());
}
TEST_F(CFDE_TextEditEngineTest, GetCharacterInfo) {
std::pair<int32_t, CFX_RectF> char_info;
engine()->Insert(0, L"Hi!");
ASSERT_EQ(3U, engine()->GetLength());
char_info = engine()->GetCharacterInfo(0);
EXPECT_EQ(0, char_info.first);
EXPECT_FLOAT_EQ(0.0f, char_info.second.Left());
EXPECT_FLOAT_EQ(0.0f, char_info.second.Top());
EXPECT_FLOAT_EQ(9.996f, char_info.second.Width());
EXPECT_FLOAT_EQ(12.0f, char_info.second.Height());
char_info = engine()->GetCharacterInfo(1);
EXPECT_EQ(0, char_info.first);
EXPECT_FLOAT_EQ(9.996f, char_info.second.Left());
EXPECT_FLOAT_EQ(0.0f, char_info.second.Top());
EXPECT_FLOAT_EQ(3.996f, char_info.second.Width());
EXPECT_FLOAT_EQ(12.0f, char_info.second.Height());
char_info = engine()->GetCharacterInfo(2);
EXPECT_EQ(0, char_info.first);
EXPECT_FLOAT_EQ(13.992f, char_info.second.Left());
EXPECT_FLOAT_EQ(0.0f, char_info.second.Top());
EXPECT_FLOAT_EQ(3.996f, char_info.second.Width());
EXPECT_FLOAT_EQ(12.0f, char_info.second.Height());
// Allow retrieving the character info for the end of the text, as that
// information can be used to determine where to draw a cursor positioned at
// the end.
char_info = engine()->GetCharacterInfo(3);
EXPECT_EQ(0, char_info.first);
EXPECT_FLOAT_EQ(17.988f, char_info.second.Left());
EXPECT_FLOAT_EQ(0.0f, char_info.second.Top());
EXPECT_FLOAT_EQ(0.0f, char_info.second.Width());
EXPECT_FLOAT_EQ(12.0f, char_info.second.Height());
}
TEST_F(CFDE_TextEditEngineTest, BoundsForWordAt) {
size_t start_idx;
size_t count;
std::tie(start_idx, count) = engine()->BoundsForWordAt(100);
EXPECT_EQ(0U, start_idx);
EXPECT_EQ(0U, count);
engine()->SetSelection(start_idx, count);
EXPECT_STREQ(L"", engine()->GetSelectedText().c_str());
engine()->Clear();
engine()->Insert(0, L"Hello");
std::tie(start_idx, count) = engine()->BoundsForWordAt(0);
EXPECT_EQ(0U, start_idx);
EXPECT_EQ(5U, count);
engine()->SetSelection(start_idx, count);
EXPECT_STREQ(L"Hello", engine()->GetSelectedText().c_str());
engine()->Clear();
engine()->Insert(0, L"Hello World");
std::tie(start_idx, count) = engine()->BoundsForWordAt(100);
EXPECT_EQ(0U, start_idx);
EXPECT_EQ(0U, count);
engine()->SetSelection(start_idx, count);
EXPECT_STREQ(L"", engine()->GetSelectedText().c_str());
std::tie(start_idx, count) = engine()->BoundsForWordAt(0);
EXPECT_EQ(0U, start_idx);
EXPECT_EQ(5U, count);
engine()->SetSelection(start_idx, count);
EXPECT_STREQ(L"Hello", engine()->GetSelectedText().c_str());
std::tie(start_idx, count) = engine()->BoundsForWordAt(1);
EXPECT_EQ(0U, start_idx);
EXPECT_EQ(5U, count);
engine()->SetSelection(start_idx, count);
EXPECT_STREQ(L"Hello", engine()->GetSelectedText().c_str());
std::tie(start_idx, count) = engine()->BoundsForWordAt(4);
EXPECT_EQ(0U, start_idx);
EXPECT_EQ(5U, count);
engine()->SetSelection(start_idx, count);
EXPECT_STREQ(L"Hello", engine()->GetSelectedText().c_str());
// Select the space
std::tie(start_idx, count) = engine()->BoundsForWordAt(5);
EXPECT_EQ(5U, start_idx);
EXPECT_EQ(1U, count);
engine()->SetSelection(start_idx, count);
EXPECT_STREQ(L" ", engine()->GetSelectedText().c_str());
std::tie(start_idx, count) = engine()->BoundsForWordAt(6);
EXPECT_EQ(6U, start_idx);
EXPECT_EQ(5U, count);
engine()->SetSelection(start_idx, count);
EXPECT_STREQ(L"World", engine()->GetSelectedText().c_str());
engine()->Clear();
engine()->Insert(0, L"123 456 789");
std::tie(start_idx, count) = engine()->BoundsForWordAt(5);
engine()->SetSelection(start_idx, count);
EXPECT_STREQ(L"456", engine()->GetSelectedText().c_str());
engine()->Clear();
engine()->Insert(0, L"123def789");
std::tie(start_idx, count) = engine()->BoundsForWordAt(5);
engine()->SetSelection(start_idx, count);
EXPECT_STREQ(L"123def789", engine()->GetSelectedText().c_str());
engine()->Clear();
engine()->Insert(0, L"abc456ghi");
std::tie(start_idx, count) = engine()->BoundsForWordAt(5);
engine()->SetSelection(start_idx, count);
EXPECT_STREQ(L"abc456ghi", engine()->GetSelectedText().c_str());
engine()->Clear();
engine()->Insert(0, L"hello, world");
std::tie(start_idx, count) = engine()->BoundsForWordAt(0);
engine()->SetSelection(start_idx, count);
EXPECT_STREQ(L"hello", engine()->GetSelectedText().c_str());
engine()->Clear();
engine()->Insert(0, L"hello, world");
std::tie(start_idx, count) = engine()->BoundsForWordAt(5);
engine()->SetSelection(start_idx, count);
EXPECT_STREQ(L",", engine()->GetSelectedText().c_str());
engine()->Clear();
engine()->Insert(0, L"np-complete");
std::tie(start_idx, count) = engine()->BoundsForWordAt(6);
engine()->SetSelection(start_idx, count);
EXPECT_STREQ(L"complete", engine()->GetSelectedText().c_str());
engine()->Clear();
engine()->Insert(0, L"(123) 456-7890");
std::tie(start_idx, count) = engine()->BoundsForWordAt(0);
engine()->SetSelection(start_idx, count);
EXPECT_STREQ(L"(", engine()->GetSelectedText().c_str());
std::tie(start_idx, count) = engine()->BoundsForWordAt(1);
engine()->SetSelection(start_idx, count);
EXPECT_STREQ(L"123", engine()->GetSelectedText().c_str());
std::tie(start_idx, count) = engine()->BoundsForWordAt(7);
engine()->SetSelection(start_idx, count);
EXPECT_STREQ(L"456", engine()->GetSelectedText().c_str());
std::tie(start_idx, count) = engine()->BoundsForWordAt(11);
engine()->SetSelection(start_idx, count);
EXPECT_STREQ(L"7890", engine()->GetSelectedText().c_str());
// Tests from:
// http://unicode.org/Public/UNIDATA/auxiliary/WordBreakTest.html#samples
struct bounds {
size_t start;
size_t end;
};
struct {
const wchar_t* str;
std::vector<const wchar_t*> results;
} tests[] = {
// {L"\r\na\n\u0308", {L"\r\n", L"a", L"\n", L"\u0308"}},
// {L"a\u0308", {L"a\u0308"}},
// {L" \u200d\u0646", {L" \u200d", L"\u0646"}},
// {L"\u0646\u200d ", {L"\u0646\u200d", L" "}},
{L"AAA", {L"AAA"}},
{L"A:A", {L"A:A"}},
{L"A::A", {L"A", L":", L":", L"A"}},
// {L"\u05d0'", {L"\u05d0'"}},
// {L"\u05d0\"\u05d0", {L"\u05d0\"\u05d0"}},
{L"A00A", {L"A00A"}},
{L"0,0", {L"0,0"}},
{L"0,,0", {L"0", L",", L",", L"0"}},
{L"\u3031\u3031", {L"\u3031\u3031"}},
{L"A_0_\u3031_", {L"A_0_\u3031_"}},
{L"A__A", {L"A__A"}},
// {L"\u200d\u2640", {L"\u200d\u2640"}},
// {L"a\u0308\u200b\u0308b", {L"a\u0308\u200b\u0308b"}},
};
for (auto t : tests) {
engine()->Clear();
engine()->Insert(0, t.str);
size_t idx = 0;
for (const auto* res : t.results) {
std::tie(start_idx, count) = engine()->BoundsForWordAt(idx);
engine()->SetSelection(start_idx, count);
EXPECT_STREQ(res, engine()->GetSelectedText().c_str())
<< "Input: '" << t.str << "'";
idx += count;
}
}
}
TEST_F(CFDE_TextEditEngineTest, CursorMovement) {
engine()->Clear();
engine()->Insert(0, L"Hello");
EXPECT_EQ(0U, engine()->GetIndexLeft(0));
EXPECT_EQ(5U, engine()->GetIndexRight(5));
EXPECT_EQ(2U, engine()->GetIndexUp(2));
EXPECT_EQ(2U, engine()->GetIndexDown(2));
EXPECT_EQ(1U, engine()->GetIndexLeft(2));
EXPECT_EQ(3U, engine()->GetIndexRight(2));
EXPECT_EQ(0U, engine()->GetIndexAtStartOfLine(2));
EXPECT_EQ(5U, engine()->GetIndexAtEndOfLine(2));
engine()->Clear();
engine()->Insert(0, L"The book is \"مدخل إلى C++\"");
EXPECT_FALSE(FX_IsOdd(engine()->GetCharacterInfo(3).first));
EXPECT_EQ(2U, engine()->GetIndexLeft(3));
EXPECT_EQ(4U, engine()->GetIndexRight(3));
EXPECT_TRUE(FX_IsOdd(engine()->GetCharacterInfo(15).first));
EXPECT_EQ(14U, engine()->GetIndexLeft(15));
EXPECT_EQ(16U, engine()->GetIndexRight(15));
EXPECT_FALSE(FX_IsOdd(engine()->GetCharacterInfo(23).first));
EXPECT_EQ(22U, engine()->GetIndexLeft(23));
EXPECT_EQ(24U, engine()->GetIndexRight(23));
engine()->Clear();
engine()->Insert(0, L"Hello\r\nWorld\r\nTest");
// Move to end of Hello from start of World.
engine()->SetSelection(engine()->GetIndexLeft(7U), 7);
EXPECT_STREQ(L"\r\nWorld", engine()->GetSelectedText().c_str());
// Second letter in Hello from second letter in World.
engine()->SetSelection(engine()->GetIndexUp(8U), 2);
EXPECT_STREQ(L"el", engine()->GetSelectedText().c_str());
// Second letter in World from second letter in Test.
engine()->SetSelection(engine()->GetIndexUp(15U), 2);
EXPECT_STREQ(L"or", engine()->GetSelectedText().c_str());
// Second letter in World from second letter in Hello.
engine()->SetSelection(engine()->GetIndexDown(1U), 2);
EXPECT_STREQ(L"or", engine()->GetSelectedText().c_str());
// Second letter in Test from second letter in World.
engine()->SetSelection(engine()->GetIndexDown(8U), 2);
EXPECT_STREQ(L"es", engine()->GetSelectedText().c_str());
size_t start_idx = engine()->GetIndexAtStartOfLine(8U);
size_t end_idx = engine()->GetIndexAtEndOfLine(8U);
engine()->SetSelection(start_idx, end_idx - start_idx);
EXPECT_STREQ(L"World", engine()->GetSelectedText().c_str());
// Move past \r\n to before W.
engine()->SetSelection(engine()->GetIndexRight(5U), 5);
EXPECT_STREQ(L"World", engine()->GetSelectedText().c_str());
engine()->Clear();
engine()->Insert(0, L"Short\nAnd a very long line");
engine()->SetSelection(engine()->GetIndexUp(14U), 11);
EXPECT_STREQ(L"\nAnd a very", engine()->GetSelectedText().c_str());
engine()->Clear();
engine()->Insert(0, L"A Very long line\nShort");
EXPECT_EQ(engine()->GetLength(), engine()->GetIndexDown(8U));
engine()->Clear();
engine()->Insert(0, L"Hello\rWorld\rTest");
// Move to end of Hello from start of World.
engine()->SetSelection(engine()->GetIndexLeft(6U), 6);
EXPECT_STREQ(L"\rWorld", engine()->GetSelectedText().c_str());
// Second letter in Hello from second letter in World.
engine()->SetSelection(engine()->GetIndexUp(7U), 2);
EXPECT_STREQ(L"el", engine()->GetSelectedText().c_str());
// Second letter in World from second letter in Test.
engine()->SetSelection(engine()->GetIndexUp(13U), 2);
EXPECT_STREQ(L"or", engine()->GetSelectedText().c_str());
// Second letter in World from second letter in Hello.
engine()->SetSelection(engine()->GetIndexDown(1U), 2);
EXPECT_STREQ(L"or", engine()->GetSelectedText().c_str());
// Second letter in Test from second letter in World.
engine()->SetSelection(engine()->GetIndexDown(7U), 2);
EXPECT_STREQ(L"es", engine()->GetSelectedText().c_str());
start_idx = engine()->GetIndexAtStartOfLine(7U);
end_idx = engine()->GetIndexAtEndOfLine(7U);
engine()->SetSelection(start_idx, end_idx - start_idx);
EXPECT_STREQ(L"World", engine()->GetSelectedText().c_str());
// Move past \r to before W.
engine()->SetSelection(engine()->GetIndexRight(5U), 5);
EXPECT_STREQ(L"World", engine()->GetSelectedText().c_str());
engine()->Clear();
engine()->Insert(0, L"Hello\nWorld\nTest");
// Move to end of Hello from start of World.
engine()->SetSelection(engine()->GetIndexLeft(6U), 6);
EXPECT_STREQ(L"\nWorld", engine()->GetSelectedText().c_str());
// Second letter in Hello from second letter in World.
engine()->SetSelection(engine()->GetIndexUp(7U), 2);
EXPECT_STREQ(L"el", engine()->GetSelectedText().c_str());
// Second letter in World from second letter in Test.
engine()->SetSelection(engine()->GetIndexUp(13U), 2);
EXPECT_STREQ(L"or", engine()->GetSelectedText().c_str());
// Second letter in World from second letter in Hello.
engine()->SetSelection(engine()->GetIndexDown(1U), 2);
EXPECT_STREQ(L"or", engine()->GetSelectedText().c_str());
// Second letter in Test from second letter in World.
engine()->SetSelection(engine()->GetIndexDown(7U), 2);
EXPECT_STREQ(L"es", engine()->GetSelectedText().c_str());
start_idx = engine()->GetIndexAtStartOfLine(7U);
end_idx = engine()->GetIndexAtEndOfLine(7U);
engine()->SetSelection(start_idx, end_idx - start_idx);
EXPECT_STREQ(L"World", engine()->GetSelectedText().c_str());
// Move past \r to before W.
engine()->SetSelection(engine()->GetIndexRight(5U), 5);
EXPECT_STREQ(L"World", engine()->GetSelectedText().c_str());
}