blob: 41981e1ed6c7ed7075e988548574c80e13f98e23 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/views/controls/textfield/textfield_model.h"
#include <stddef.h>
#include <memory>
#include <string>
#include <vector>
#include "base/auto_reset.h"
#include "base/stl_util.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/gfx/range/range.h"
#include "ui/gfx/render_text.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/test/test_views_delegate.h"
#include "ui/views/test/views_test_base.h"
#define EXPECT_STR_EQ(ascii, utf16) EXPECT_EQ(base::ASCIIToUTF16(ascii), utf16)
namespace {
struct WordAndCursor {
WordAndCursor(const wchar_t* w, size_t c) : word(w), cursor(c) {}
const wchar_t* word;
size_t cursor;
};
} // namespace
namespace views {
class TextfieldModelTest : public ViewsTestBase,
public TextfieldModel::Delegate {
public:
TextfieldModelTest() = default;
// ::testing::Test:
void TearDown() override {
// Clear kill buffer used for "Yank" text editing command so that no state
// persists between tests.
TextfieldModel::ClearKillBuffer();
ViewsTestBase::TearDown();
}
void OnCompositionTextConfirmedOrCleared() override {
composition_text_confirmed_or_cleared_ = true;
}
protected:
void ResetModel(TextfieldModel* model) const {
model->SetText(base::string16(), 0);
model->ClearEditHistory();
}
const std::vector<base::string16> GetAllSelectionTexts(
TextfieldModel* model) const {
std::vector<base::string16> selected_texts;
for (auto range : model->render_text()->GetAllSelections())
selected_texts.push_back(model->GetTextFromRange(range));
return selected_texts;
}
void VerifyAllSelectionTexts(
TextfieldModel* model,
std::vector<std::string> expected_selected_texts) const {
std::vector<base::string16> selected_texts = GetAllSelectionTexts(model);
EXPECT_EQ(expected_selected_texts.size(), selected_texts.size());
for (size_t i = 0; i < selected_texts.size(); ++i)
EXPECT_STR_EQ(expected_selected_texts[i], selected_texts[i]);
}
bool composition_text_confirmed_or_cleared_ = false;
private:
DISALLOW_COPY_AND_ASSIGN(TextfieldModelTest);
};
TEST_F(TextfieldModelTest, EditString) {
TextfieldModel model(nullptr);
// Append two strings.
model.Append(base::ASCIIToUTF16("HILL"));
EXPECT_STR_EQ("HILL", model.text());
model.Append(base::ASCIIToUTF16("WORLD"));
EXPECT_STR_EQ("HILLWORLD", model.text());
// Insert "E" and replace "I" with "L" to make "HELLO".
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_NONE);
model.InsertChar('E');
EXPECT_STR_EQ("HEILLWORLD", model.text());
model.ReplaceChar('L');
EXPECT_STR_EQ("HELLLWORLD", model.text());
model.ReplaceChar('L');
model.ReplaceChar('O');
EXPECT_STR_EQ("HELLOWORLD", model.text());
// Delete 6th char "W", then delete 5th char "O".
EXPECT_EQ(5U, model.GetCursorPosition());
EXPECT_TRUE(model.Delete());
EXPECT_STR_EQ("HELLOORLD", model.text());
EXPECT_TRUE(model.Backspace());
EXPECT_EQ(4U, model.GetCursorPosition());
EXPECT_STR_EQ("HELLORLD", model.text());
// Move the cursor to start; backspace should fail.
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_NONE);
EXPECT_FALSE(model.Backspace());
EXPECT_STR_EQ("HELLORLD", model.text());
// Move the cursor to the end; delete should fail, but backspace should work.
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
EXPECT_FALSE(model.Delete());
EXPECT_STR_EQ("HELLORLD", model.text());
EXPECT_TRUE(model.Backspace());
EXPECT_STR_EQ("HELLORL", model.text());
model.MoveCursorTo(5);
model.ReplaceText(base::ASCIIToUTF16(" WOR"));
EXPECT_STR_EQ("HELLO WORL", model.text());
}
TEST_F(TextfieldModelTest, EditString_SimpleRTL) {
TextfieldModel model(nullptr);
// Append two strings.
model.Append(base::WideToUTF16(L"\x05d0\x05d1\x05d2"));
EXPECT_EQ(base::WideToUTF16(L"\x05d0\x05d1\x05d2"), model.text());
model.Append(base::WideToUTF16(L"\x05e0\x05e1\x05e2"));
EXPECT_EQ(base::WideToUTF16(L"\x05d0\x05d1\x05d2\x05e0\x05e1\x05e2"),
model.text());
// Insert "\x05f0".
model.MoveCursorTo(1);
model.InsertChar(0x05f0);
EXPECT_EQ(base::WideToUTF16(L"\x05d0\x05f0\x05d1\x05d2\x05e0\x05e1\x05e2"),
model.text());
// Replace "\x05d1" with "\x05f1".
model.ReplaceChar(0x05f1);
EXPECT_EQ(base::WideToUTF16(L"\x05d0\x05f0\x5f1\x05d2\x05e0\x05e1\x05e2"),
model.text());
// Test Delete and backspace.
EXPECT_EQ(3U, model.GetCursorPosition());
EXPECT_TRUE(model.Delete());
EXPECT_EQ(base::WideToUTF16(L"\x05d0\x05f0\x5f1\x05e0\x05e1\x05e2"),
model.text());
EXPECT_TRUE(model.Backspace());
EXPECT_EQ(2U, model.GetCursorPosition());
EXPECT_EQ(base::WideToUTF16(L"\x05d0\x05f0\x05e0\x05e1\x05e2"), model.text());
}
TEST_F(TextfieldModelTest, EditString_ComplexScript) {
TextfieldModel model(nullptr);
// Append two Hindi strings.
model.Append(base::WideToUTF16(L"\x0915\x093f\x0915\x094d\x0915"));
EXPECT_EQ(base::WideToUTF16(L"\x0915\x093f\x0915\x094d\x0915"), model.text());
model.Append(base::WideToUTF16(L"\x0915\x094d\x092e\x094d"));
EXPECT_EQ(base::WideToUTF16(
L"\x0915\x093f\x0915\x094d\x0915\x0915\x094d\x092e\x094d"),
model.text());
// Ensure the cursor cannot be placed in the middle of a grapheme.
model.MoveCursorTo(1);
EXPECT_EQ(0U, model.GetCursorPosition());
model.MoveCursorTo(2);
EXPECT_EQ(2U, model.GetCursorPosition());
model.InsertChar('a');
EXPECT_EQ(
base::WideToUTF16(
L"\x0915\x093f\x0061\x0915\x094d\x0915\x0915\x094d\x092e\x094d"),
model.text());
// ReplaceChar will replace the whole grapheme.
model.ReplaceChar('b');
// TODO(xji): temporarily disable in platform Win since the complex script
// characters turned into empty square due to font regression. So, not able
// to test 2 characters belong to the same grapheme.
#if defined(OS_LINUX)
EXPECT_EQ(
base::WideToUTF16(L"\x0915\x093f\x0061\x0062\x0915\x094d\x092e\x094d"),
model.text());
#endif
EXPECT_EQ(4U, model.GetCursorPosition());
// Delete should delete the whole grapheme.
model.MoveCursorTo(0);
// TODO(xji): temporarily disable in platform Win since the complex script
// characters turned into empty square due to font regression. So, not able
// to test 2 characters belong to the same grapheme.
#if defined(OS_LINUX)
EXPECT_TRUE(model.Delete());
EXPECT_EQ(base::WideToUTF16(L"\x0061\x0062\x0915\x094d\x092e\x094d"),
model.text());
model.MoveCursorTo(model.text().length());
EXPECT_EQ(model.text().length(), model.GetCursorPosition());
EXPECT_TRUE(model.Backspace());
EXPECT_EQ(base::WideToUTF16(L"\x0061\x0062\x0915\x094d\x092e"), model.text());
#endif
// Test cursor position and deletion for Hindi Virama.
model.SetText(base::WideToUTF16(L"\x0D38\x0D4D\x0D15\x0D16\x0D2E"), 0);
model.MoveCursorTo(0);
EXPECT_EQ(0U, model.GetCursorPosition());
model.MoveCursorTo(1);
EXPECT_EQ(0U, model.GetCursorPosition());
model.MoveCursorTo(3);
EXPECT_EQ(3U, model.GetCursorPosition());
// TODO(asvitkine): Temporarily disable the following check on Windows. It
// seems Windows treats "\x0D38\x0D4D\x0D15" as a single grapheme.
#if !defined(OS_WIN)
model.MoveCursorTo(2);
EXPECT_EQ(3U, model.GetCursorPosition());
EXPECT_TRUE(model.Backspace());
EXPECT_EQ(base::WideToUTF16(L"\x0D38\x0D4D\x0D16\x0D2E"), model.text());
#endif
model.SetText(
base::WideToUTF16(L"\x05d5\x05b7\x05D9\x05B0\x05D4\x05B4\x05D9"), 0);
model.MoveCursorTo(0);
EXPECT_TRUE(model.Delete());
EXPECT_TRUE(model.Delete());
EXPECT_TRUE(model.Delete());
EXPECT_TRUE(model.Delete());
EXPECT_EQ(base::WideToUTF16(L""), model.text());
// The first 2 characters are not strong directionality characters.
model.SetText(
base::WideToUTF16(L"\x002C\x0020\x05D1\x05BC\x05B7\x05E9\x05BC"), 0);
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_NONE);
EXPECT_TRUE(model.Backspace());
EXPECT_EQ(base::WideToUTF16(L"\x002C\x0020\x05D1\x05BC\x05B7\x05E9"),
model.text());
// Halfwidth katakana ダ:
// "HALFWIDTH KATAKANA LETTER TA" + "HALFWIDTH KATAKANA VOICED SOUND MARK"
// ("ABC" prefix as sanity check that the entire string isn't deleted).
model.SetText(base::WideToUTF16(L"ABC\xFF80\xFF9E"), 0);
model.MoveCursorTo(model.text().length());
model.Backspace();
#if defined(OS_MACOSX)
// On Mac, the entire cluster should be deleted to match
// NSTextField behavior.
EXPECT_EQ(base::WideToUTF16(L"ABC"), model.text());
EXPECT_EQ(3U, model.GetCursorPosition());
#else
EXPECT_EQ(base::WideToUTF16(L"ABC\xFF80"), model.text());
EXPECT_EQ(4U, model.GetCursorPosition());
#endif
// Emoji with Fitzpatrick modifier:
// 'BOY' + 'EMOJI MODIFIER FITZPATRICK TYPE-5'
model.SetText(base::WideToUTF16(L"\U0001F466\U0001F3FE"), 0);
model.MoveCursorTo(model.text().length());
model.Backspace();
#if defined(OS_MACOSX)
// On Mac, the entire emoji should be deleted to match NSTextField
// behavior.
EXPECT_EQ(base::WideToUTF16(L""), model.text());
EXPECT_EQ(0U, model.GetCursorPosition());
#else
// https://crbug.com/829040
EXPECT_EQ(base::WideToUTF16(L"\U0001F466"), model.text());
EXPECT_EQ(2U, model.GetCursorPosition());
#endif
}
TEST_F(TextfieldModelTest, EmptyString) {
TextfieldModel model(nullptr);
EXPECT_EQ(base::string16(), model.text());
EXPECT_EQ(base::string16(), model.GetSelectedText());
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT,
gfx::SELECTION_RETAIN);
EXPECT_EQ(0U, model.GetCursorPosition());
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_RETAIN);
EXPECT_EQ(0U, model.GetCursorPosition());
EXPECT_EQ(base::string16(), model.GetSelectedText());
EXPECT_FALSE(model.Delete());
EXPECT_FALSE(model.Backspace());
}
TEST_F(TextfieldModelTest, Selection) {
TextfieldModel model(nullptr);
model.Append(base::ASCIIToUTF16("HELLO"));
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_NONE);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_RETAIN);
EXPECT_STR_EQ("E", model.GetSelectedText());
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_RETAIN);
EXPECT_STR_EQ("EL", model.GetSelectedText());
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_RETAIN);
EXPECT_STR_EQ("H", model.GetSelectedText());
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_RETAIN);
EXPECT_STR_EQ("ELLO", model.GetSelectedText());
model.ClearSelection();
EXPECT_EQ(base::string16(), model.GetSelectedText());
// SelectAll(false) selects towards the end.
model.SelectAll(false);
EXPECT_STR_EQ("HELLO", model.GetSelectedText());
EXPECT_EQ(gfx::Range(0, 5), model.render_text()->selection());
// SelectAll(true) selects towards the beginning.
model.SelectAll(true);
EXPECT_STR_EQ("HELLO", model.GetSelectedText());
EXPECT_EQ(gfx::Range(5, 0), model.render_text()->selection());
// Select and move cursor.
model.SelectRange(gfx::Range(1U, 3U));
EXPECT_STR_EQ("EL", model.GetSelectedText());
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_NONE);
EXPECT_EQ(1U, model.GetCursorPosition());
model.SelectRange(gfx::Range(1U, 3U));
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_NONE);
EXPECT_EQ(3U, model.GetCursorPosition());
// Select multiple ranges and move cursor.
model.SelectRange(gfx::Range(1U, 3U));
model.SelectRange(gfx::Range(5U, 4U), false);
EXPECT_STR_EQ("EL", model.GetSelectedText());
EXPECT_EQ(3U, model.GetCursorPosition());
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_NONE);
EXPECT_TRUE(model.GetSelectedText().empty());
EXPECT_EQ(1U, model.GetCursorPosition());
EXPECT_TRUE(model.render_text()->secondary_selections().empty());
model.SelectRange(gfx::Range(1U, 3U));
model.SelectRange(gfx::Range(4U, 5U), false);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_NONE);
EXPECT_TRUE(model.GetSelectedText().empty());
EXPECT_EQ(3U, model.GetCursorPosition());
EXPECT_TRUE(model.render_text()->secondary_selections().empty());
// Select all and move cursor.
model.SelectAll(false);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_NONE);
EXPECT_EQ(0U, model.GetCursorPosition());
model.SelectAll(false);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_NONE);
EXPECT_EQ(5U, model.GetCursorPosition());
}
TEST_F(TextfieldModelTest, Selection_BidiWithNonSpacingMarks) {
// Selection is a logical operation. And it should work with the arrow
// keys doing visual movements, while the selection is logical between
// the (logical) start and end points. Selection is simply defined as
// the portion of text between the logical positions of the start and end
// caret positions.
TextfieldModel model(nullptr);
// TODO(xji): temporarily disable in platform Win since the complex script
// characters turned into empty square due to font regression. So, not able
// to test 2 characters belong to the same grapheme.
#if defined(OS_LINUX)
model.Append(
base::WideToUTF16(L"abc\x05E9\x05BC\x05C1\x05B8\x05E0\x05B8"
L"def"));
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_NONE);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_NONE);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_RETAIN);
EXPECT_EQ(gfx::Range(2, 3), model.render_text()->selection());
EXPECT_EQ(base::WideToUTF16(L"c"), model.GetSelectedText());
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_RETAIN);
EXPECT_EQ(gfx::Range(2, 7), model.render_text()->selection());
EXPECT_EQ(base::WideToUTF16(L"c\x05E9\x05BC\x05C1\x05B8"),
model.GetSelectedText());
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_RETAIN);
EXPECT_EQ(gfx::Range(2, 3), model.render_text()->selection());
EXPECT_EQ(base::WideToUTF16(L"c"), model.GetSelectedText());
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_RETAIN);
EXPECT_EQ(gfx::Range(2, 10), model.render_text()->selection());
EXPECT_EQ(base::WideToUTF16(L"c\x05E9\x05BC\x05C1\x05B8\x05E0\x05B8"
L"d"),
model.GetSelectedText());
model.ClearSelection();
EXPECT_EQ(base::string16(), model.GetSelectedText());
model.SelectAll(false);
EXPECT_EQ(base::WideToUTF16(L"abc\x05E9\x05BC\x05C1\x05B8\x05E0\x05B8"
L"def"),
model.GetSelectedText());
#endif
// In case of "aBc", this test shows how to select "aB" or "Bc", assume 'B' is
// an RTL character.
model.SetText(base::WideToUTF16(L"a\x05E9"
L"b"),
0);
model.MoveCursorTo(0);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_RETAIN);
EXPECT_EQ(base::WideToUTF16(L"a"), model.GetSelectedText());
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_RETAIN);
EXPECT_EQ(base::WideToUTF16(L"a"), model.GetSelectedText());
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_RETAIN);
EXPECT_EQ(base::WideToUTF16(L"a\x05E9"
L"b"),
model.GetSelectedText());
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_NONE);
EXPECT_EQ(3U, model.GetCursorPosition());
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT,
gfx::SELECTION_RETAIN);
EXPECT_EQ(base::WideToUTF16(L"b"), model.GetSelectedText());
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT,
gfx::SELECTION_RETAIN);
EXPECT_EQ(base::WideToUTF16(L"b"), model.GetSelectedText());
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT,
gfx::SELECTION_RETAIN);
EXPECT_EQ(base::WideToUTF16(L"a\x05E9"
L"b"),
model.GetSelectedText());
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_NONE);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_RETAIN);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_RETAIN);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT,
gfx::SELECTION_RETAIN);
EXPECT_EQ(base::WideToUTF16(L"a\x05E9"), model.GetSelectedText());
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT,
gfx::SELECTION_RETAIN);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT,
gfx::SELECTION_RETAIN);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_RETAIN);
EXPECT_EQ(base::WideToUTF16(L"\x05E9"
L"b"),
model.GetSelectedText());
model.ClearSelection();
EXPECT_EQ(base::string16(), model.GetSelectedText());
model.SelectAll(false);
EXPECT_EQ(base::WideToUTF16(L"a\x05E9"
L"b"),
model.GetSelectedText());
}
TEST_F(TextfieldModelTest, SelectionAndEdit) {
TextfieldModel model(nullptr);
model.Append(base::ASCIIToUTF16("HELLO"));
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_NONE);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_RETAIN);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_RETAIN); // "EL"
EXPECT_TRUE(model.Backspace());
EXPECT_STR_EQ("HLO", model.text());
model.Append(base::ASCIIToUTF16("ILL"));
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_RETAIN);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_RETAIN); // "LO"
EXPECT_TRUE(model.Delete());
EXPECT_STR_EQ("HILL", model.text());
EXPECT_EQ(1U, model.GetCursorPosition());
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_RETAIN); // "I"
model.InsertChar('E');
EXPECT_STR_EQ("HELL", model.text());
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_NONE);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_RETAIN); // "H"
model.ReplaceChar('B');
EXPECT_STR_EQ("BELL", model.text());
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT,
gfx::SELECTION_RETAIN);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT,
gfx::SELECTION_RETAIN); // "ELL"
model.ReplaceChar('E');
EXPECT_STR_EQ("BEE", model.text());
}
TEST_F(TextfieldModelTest, SelectionAndEdit_WithSecondarySelection) {
// Backspace
TextfieldModel model(nullptr);
model.Append(base::ASCIIToUTF16("asynchronous promises make the moon spin?"));
model.SelectRange(gfx::Range(0U, 4U));
model.SelectRange(gfx::Range(17U, 19U), false);
model.SelectRange(gfx::Range(15U, 7U), false);
model.SelectRange(gfx::Range(41U, 20U), false);
EXPECT_TRUE(model.Backspace());
EXPECT_STR_EQ("chrome", model.text());
EXPECT_TRUE(model.GetSelectedText().empty());
EXPECT_EQ(0U, model.GetCursorPosition());
EXPECT_TRUE(model.render_text()->secondary_selections().empty());
// Delete with an empty primary selection
model.Append(base::ASCIIToUTF16(" is constructor overloading bad?"));
model.SelectRange(gfx::Range(1U, 1U));
model.SelectRange(gfx::Range(22U, 12U), false);
model.SelectRange(gfx::Range(26U, 23U), false);
model.SelectRange(gfx::Range(27U, 38U), false);
EXPECT_TRUE(model.Delete());
EXPECT_STR_EQ("chrome is cool", model.text());
EXPECT_TRUE(model.GetSelectedText().empty());
EXPECT_EQ(1U, model.GetCursorPosition());
EXPECT_TRUE(model.render_text()->secondary_selections().empty());
// Insert
model.Append(base::ASCIIToUTF16(" are inherited classes heavy?"));
model.SelectRange(gfx::Range(27U, 16U));
model.SelectRange(gfx::Range(41U, 34U), false);
model.SelectRange(gfx::Range(42U, 43U), false);
model.InsertChar('n');
EXPECT_STR_EQ("chrome is cool and classy", model.text());
EXPECT_TRUE(model.GetSelectedText().empty());
EXPECT_EQ(17U, model.GetCursorPosition());
EXPECT_TRUE(model.render_text()->secondary_selections().empty());
// Replace
model.Append(
base::ASCIIToUTF16("help! why can't i instantiate an abstract sun!?"));
model.SelectRange(gfx::Range(71U, 72U));
model.SelectRange(gfx::Range(30U, 70U), false);
model.SelectRange(gfx::Range(29U, 25U), false);
model.ReplaceChar('!');
EXPECT_STR_EQ("chrome is cool and classy!!!", model.text());
EXPECT_TRUE(model.GetSelectedText().empty());
EXPECT_EQ(28U, model.GetCursorPosition());
EXPECT_TRUE(model.render_text()->secondary_selections().empty());
}
TEST_F(TextfieldModelTest, Word) {
TextfieldModel model(nullptr);
model.Append(
base::ASCIIToUTF16("The answer to Life, the Universe, and Everything"));
#if defined(OS_WIN) // Move right by word includes space/punctuation.
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
EXPECT_EQ(4U, model.GetCursorPosition());
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
EXPECT_EQ(11U, model.GetCursorPosition());
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
// Should pass the non word chars ', ' and be at the start of "the".
EXPECT_EQ(20U, model.GetCursorPosition());
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_RETAIN);
EXPECT_EQ(24U, model.GetCursorPosition());
EXPECT_STR_EQ("the ", model.GetSelectedText());
// Move to the end.
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_RETAIN);
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_RETAIN);
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_RETAIN);
EXPECT_STR_EQ("the Universe, and Everything", model.GetSelectedText());
// Should be safe to go next word at the end.
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_RETAIN);
EXPECT_STR_EQ("the Universe, and Everything", model.GetSelectedText());
model.InsertChar('2');
EXPECT_EQ(21U, model.GetCursorPosition());
// Now backwards.
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT,
gfx::SELECTION_NONE); // leave 2.
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_RETAIN);
EXPECT_EQ(14U, model.GetCursorPosition());
EXPECT_STR_EQ("Life, ", model.GetSelectedText());
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_RETAIN);
EXPECT_STR_EQ("to Life, ", model.GetSelectedText());
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_RETAIN);
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_RETAIN);
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT,
gfx::SELECTION_RETAIN); // Now at start.
EXPECT_STR_EQ("The answer to Life, ", model.GetSelectedText());
// Should be safe to go to the previous word at the beginning.
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_RETAIN);
EXPECT_STR_EQ("The answer to Life, ", model.GetSelectedText());
model.ReplaceChar('4');
EXPECT_EQ(base::string16(), model.GetSelectedText());
EXPECT_STR_EQ("42", model.text());
#else // Non-Windows: move right by word does NOT include space/punctuation.
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
EXPECT_EQ(3U, model.GetCursorPosition());
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
EXPECT_EQ(10U, model.GetCursorPosition());
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
EXPECT_EQ(18U, model.GetCursorPosition());
// Should passes the non word char ','
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_RETAIN);
EXPECT_EQ(23U, model.GetCursorPosition());
EXPECT_STR_EQ(", the", model.GetSelectedText());
// Move to the end.
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_RETAIN);
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_RETAIN);
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_RETAIN);
EXPECT_STR_EQ(", the Universe, and Everything", model.GetSelectedText());
// Should be safe to go next word at the end.
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_RETAIN);
EXPECT_STR_EQ(", the Universe, and Everything", model.GetSelectedText());
model.InsertChar('2');
EXPECT_EQ(19U, model.GetCursorPosition());
// Now backwards.
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT,
gfx::SELECTION_NONE); // leave 2.
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_RETAIN);
EXPECT_EQ(14U, model.GetCursorPosition());
EXPECT_STR_EQ("Life", model.GetSelectedText());
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_RETAIN);
EXPECT_STR_EQ("to Life", model.GetSelectedText());
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_RETAIN);
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_RETAIN);
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT,
gfx::SELECTION_RETAIN); // Now at start.
EXPECT_STR_EQ("The answer to Life", model.GetSelectedText());
// Should be safe to go to the previous word at the beginning.
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_RETAIN);
EXPECT_STR_EQ("The answer to Life", model.GetSelectedText());
model.ReplaceChar('4');
EXPECT_EQ(base::string16(), model.GetSelectedText());
EXPECT_STR_EQ("42", model.text());
#endif
}
TEST_F(TextfieldModelTest, SetText) {
TextfieldModel model(nullptr);
model.Append(base::ASCIIToUTF16("HELLO"));
// SetText moves cursor to the indicated position.
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
model.SetText(base::ASCIIToUTF16("GOODBYE"), 6);
EXPECT_STR_EQ("GOODBYE", model.text());
EXPECT_EQ(6U, model.GetCursorPosition());
model.SetText(base::ASCIIToUTF16("SUNSET"), 6);
EXPECT_STR_EQ("SUNSET", model.text());
EXPECT_EQ(6U, model.GetCursorPosition());
model.SelectAll(false);
EXPECT_STR_EQ("SUNSET", model.GetSelectedText());
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
EXPECT_EQ(6U, model.GetCursorPosition());
// Setting text to the current text should not modify the cursor position.
model.SetText(base::ASCIIToUTF16("SUNSET"), 3);
EXPECT_STR_EQ("SUNSET", model.text());
EXPECT_EQ(6U, model.GetCursorPosition());
// Setting text that's shorter than the indicated cursor moves the cursor to
// the text end.
model.SetText(base::ASCIIToUTF16("BYE"), 5);
EXPECT_EQ(3U, model.GetCursorPosition());
EXPECT_EQ(base::string16(), model.GetSelectedText());
// SetText with empty string.
model.SetText(base::string16(), 0);
EXPECT_EQ(0U, model.GetCursorPosition());
}
TEST_F(TextfieldModelTest, Clipboard) {
ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
const base::string16 initial_clipboard_text =
base::ASCIIToUTF16("initial text");
ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)
.WriteText(initial_clipboard_text);
base::string16 clipboard_text;
TextfieldModel model(nullptr);
model.Append(base::ASCIIToUTF16("HELLO WORLD"));
// Cut with an empty selection should do nothing.
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
EXPECT_FALSE(model.Cut());
clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
&clipboard_text);
EXPECT_EQ(initial_clipboard_text, clipboard_text);
EXPECT_STR_EQ("HELLO WORLD", model.text());
EXPECT_EQ(11U, model.GetCursorPosition());
// Copy with an empty selection should do nothing.
EXPECT_FALSE(model.Copy());
clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
&clipboard_text);
EXPECT_EQ(initial_clipboard_text, clipboard_text);
EXPECT_STR_EQ("HELLO WORLD", model.text());
EXPECT_EQ(11U, model.GetCursorPosition());
// Cut on obscured (password) text should do nothing.
model.render_text()->SetObscured(true);
model.SelectAll(false);
EXPECT_FALSE(model.Cut());
clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
&clipboard_text);
EXPECT_EQ(initial_clipboard_text, clipboard_text);
EXPECT_STR_EQ("HELLO WORLD", model.text());
EXPECT_STR_EQ("HELLO WORLD", model.GetSelectedText());
// Copy on obscured (password) text should do nothing.
model.SelectAll(false);
EXPECT_FALSE(model.Copy());
clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
&clipboard_text);
EXPECT_EQ(initial_clipboard_text, clipboard_text);
EXPECT_STR_EQ("HELLO WORLD", model.text());
EXPECT_STR_EQ("HELLO WORLD", model.GetSelectedText());
// Cut with non-empty selection.
model.render_text()->SetObscured(false);
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_RETAIN);
EXPECT_TRUE(model.Cut());
clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
&clipboard_text);
EXPECT_STR_EQ("WORLD", clipboard_text);
EXPECT_STR_EQ("HELLO ", model.text());
EXPECT_EQ(6U, model.GetCursorPosition());
// Copy with non-empty selection.
model.SelectAll(false);
EXPECT_TRUE(model.Copy());
clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
&clipboard_text);
EXPECT_STR_EQ("HELLO ", clipboard_text);
EXPECT_STR_EQ("HELLO ", model.text());
EXPECT_EQ(6U, model.GetCursorPosition());
// Test that paste works regardless of the obscured bit. Please note that
// trailing spaces and tabs in clipboard strings will be stripped.
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
EXPECT_TRUE(model.Paste());
EXPECT_STR_EQ("HELLO HELLO", model.text());
EXPECT_EQ(11U, model.GetCursorPosition());
model.render_text()->SetObscured(true);
EXPECT_TRUE(model.Paste());
EXPECT_STR_EQ("HELLO HELLOHELLO", model.text());
EXPECT_EQ(16U, model.GetCursorPosition());
// Paste should replace the selection.
model.render_text()->SetObscured(false);
model.SetText(base::ASCIIToUTF16("It's time to say goodbye."), 0);
model.SelectRange({17, 24});
EXPECT_TRUE(model.Paste());
clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
&clipboard_text);
EXPECT_STR_EQ("HELLO ", clipboard_text);
EXPECT_STR_EQ("It's time to say HELLO.", model.text());
EXPECT_EQ(22U, model.GetCursorPosition());
EXPECT_FALSE(model.HasSelection());
EXPECT_TRUE(model.render_text()->secondary_selections().empty());
// Paste with an empty clipboard should not replace the selection.
ui::Clipboard::GetForCurrentThread()->Clear(ui::ClipboardBuffer::kCopyPaste);
model.SelectRange({5, 8});
EXPECT_FALSE(model.Paste());
clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
&clipboard_text);
EXPECT_TRUE(clipboard_text.empty());
EXPECT_STR_EQ("It's time to say HELLO.", model.text());
EXPECT_EQ(8U, model.GetCursorPosition());
EXPECT_STR_EQ("tim", model.GetSelectedText());
}
TEST_F(TextfieldModelTest, Clipboard_WithSecondarySelections) {
ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
const base::string16 initial_clipboard_text =
base::ASCIIToUTF16("initial text");
ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)
.WriteText(initial_clipboard_text);
base::string16 clipboard_text;
TextfieldModel model(nullptr);
model.Append(base::ASCIIToUTF16("It's time to say HELLO."));
// Cut with multiple selections should copy only the primary selection but
// delete all selections.
model.SelectRange({0, 5});
model.SelectRange({13, 17}, false);
EXPECT_TRUE(model.Cut());
clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
&clipboard_text);
EXPECT_STR_EQ("It's ", clipboard_text);
EXPECT_STR_EQ("time to HELLO.", model.text());
EXPECT_EQ(0U, model.GetCursorPosition());
EXPECT_FALSE(model.HasSelection());
EXPECT_TRUE(model.render_text()->secondary_selections().empty());
// Copy with multiple selections should copy only the primary selection and
// retain multiple selections.
model.SelectRange({13, 8});
model.SelectRange({0, 4}, false);
EXPECT_TRUE(model.Copy());
clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
&clipboard_text);
EXPECT_STR_EQ("HELLO", clipboard_text);
EXPECT_STR_EQ("time to HELLO.", model.text());
EXPECT_EQ(8U, model.GetCursorPosition());
EXPECT_TRUE(model.HasSelection());
VerifyAllSelectionTexts(&model, {"HELLO", "time"});
// Paste with multiple selections should paste at the primary selection and
// delete all selections.
model.SelectRange({0, 1});
model.SelectRange({5, 8}, false);
model.SelectRange({14, 14}, false);
EXPECT_TRUE(model.Paste());
clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
&clipboard_text);
EXPECT_STR_EQ("HELLO", clipboard_text);
EXPECT_STR_EQ("HELLOime HELLO.", model.text());
EXPECT_EQ(5U, model.GetCursorPosition());
EXPECT_FALSE(model.HasSelection());
EXPECT_TRUE(model.render_text()->secondary_selections().empty());
// Paste with multiple selections and an empty clipboard should not change the
// text or selections.
ui::Clipboard::GetForCurrentThread()->Clear(ui::ClipboardBuffer::kCopyPaste);
model.SelectRange({1, 2});
model.SelectRange({4, 5}, false);
EXPECT_FALSE(model.Paste());
clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
&clipboard_text);
EXPECT_TRUE(clipboard_text.empty());
EXPECT_STR_EQ("HELLOime HELLO.", model.text());
EXPECT_EQ(2U, model.GetCursorPosition());
VerifyAllSelectionTexts(&model, {"E", "O"});
// Cut with an empty primary selection and nonempty secondary selections
// should neither delete the secondary selection nor replace the clipboard.
ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)
.WriteText(initial_clipboard_text);
model.SelectRange({2, 2});
model.SelectRange({4, 5}, false);
EXPECT_FALSE(model.Cut());
clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
&clipboard_text);
EXPECT_STR_EQ("initial text", clipboard_text);
EXPECT_STR_EQ("HELLOime HELLO.", model.text());
EXPECT_EQ(2U, model.GetCursorPosition());
VerifyAllSelectionTexts(&model, {"", "O"});
// Copy with an empty primary selection and nonempty secondary selections
// should not replace the clipboard.
EXPECT_FALSE(model.Copy());
clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
&clipboard_text);
EXPECT_STR_EQ("initial text", clipboard_text);
EXPECT_STR_EQ("HELLOime HELLO.", model.text());
EXPECT_EQ(2U, model.GetCursorPosition());
VerifyAllSelectionTexts(&model, {"", "O"});
// Paste with an empty primary selection, nonempty secondary selection, and
// empty clipboard should change neither the text nor the selections.
ui::Clipboard::GetForCurrentThread()->Clear(ui::ClipboardBuffer::kCopyPaste);
EXPECT_FALSE(model.Paste());
clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
&clipboard_text);
EXPECT_TRUE(clipboard_text.empty());
EXPECT_STR_EQ("HELLOime HELLO.", model.text());
EXPECT_EQ(2U, model.GetCursorPosition());
VerifyAllSelectionTexts(&model, {"", "O"});
// Paste with an empty primary selection and nonempty secondary selections
// should paste at the primary selection and delete the secondary selections.
ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)
.WriteText(initial_clipboard_text);
EXPECT_TRUE(model.Paste());
clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
&clipboard_text);
EXPECT_STR_EQ("initial text", clipboard_text);
EXPECT_STR_EQ("HEinitial textLLime HELLO.", model.text());
EXPECT_EQ(14U, model.GetCursorPosition());
EXPECT_FALSE(model.HasSelection());
}
static void SelectWordTestVerifier(
const TextfieldModel& model,
const base::string16& expected_selected_string,
size_t expected_cursor_pos) {
EXPECT_EQ(expected_selected_string, model.GetSelectedText());
EXPECT_EQ(expected_cursor_pos, model.GetCursorPosition());
}
TEST_F(TextfieldModelTest, SelectWordTest) {
TextfieldModel model(nullptr);
model.Append(base::ASCIIToUTF16(" HELLO !! WO RLD "));
// Test when cursor is at the beginning.
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_NONE);
model.SelectWord();
SelectWordTestVerifier(model, base::ASCIIToUTF16(" "), 2U);
// Test when cursor is at the beginning of a word.
model.MoveCursorTo(2);
model.SelectWord();
SelectWordTestVerifier(model, base::ASCIIToUTF16("HELLO"), 7U);
// Test when cursor is at the end of a word.
model.MoveCursorTo(15);
model.SelectWord();
SelectWordTestVerifier(model, base::ASCIIToUTF16(" "), 20U);
// Test when cursor is somewhere in a non-alpha-numeric fragment.
for (size_t cursor_pos = 8; cursor_pos < 13U; cursor_pos++) {
model.MoveCursorTo(cursor_pos);
model.SelectWord();
SelectWordTestVerifier(model, base::ASCIIToUTF16(" !! "), 13U);
}
// Test when cursor is somewhere in a whitespace fragment.
model.MoveCursorTo(17);
model.SelectWord();
SelectWordTestVerifier(model, base::ASCIIToUTF16(" "), 20U);
// Test when cursor is at the end.
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
model.SelectWord();
SelectWordTestVerifier(model, base::ASCIIToUTF16(" "), 24U);
}
// TODO(xji): temporarily disable in platform Win since the complex script
// characters and Chinese characters are turned into empty square due to font
// regression.
#if defined(OS_LINUX)
TEST_F(TextfieldModelTest, SelectWordTest_MixScripts) {
TextfieldModel model(nullptr);
std::vector<WordAndCursor> word_and_cursor;
word_and_cursor.emplace_back(L"a\x05d0", 2);
word_and_cursor.emplace_back(L"a\x05d0", 2);
word_and_cursor.emplace_back(L"\x05d1\x05d2", 5);
word_and_cursor.emplace_back(L"\x05d1\x05d2", 5);
word_and_cursor.emplace_back(L" ", 3);
word_and_cursor.emplace_back(L"a\x05d0", 2);
word_and_cursor.emplace_back(L"\x0915\x094d\x0915", 9);
word_and_cursor.emplace_back(L" ", 10);
word_and_cursor.emplace_back(L"\x4E2D\x56FD", 12);
word_and_cursor.emplace_back(L"\x4E2D\x56FD", 12);
word_and_cursor.emplace_back(L"\x82B1", 13);
word_and_cursor.emplace_back(L"\x5929", 14);
// The text consists of Ascii, Hebrew, Hindi with Virama sign, and Chinese.
model.SetText(base::WideToUTF16(L"a\x05d0 \x05d1\x05d2 \x0915\x094d\x0915 "
L"\x4E2D\x56FD\x82B1\x5929"),
0);
for (size_t i = 0; i < word_and_cursor.size(); ++i) {
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_NONE);
for (size_t j = 0; j < i; ++j)
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_NONE);
model.SelectWord();
SelectWordTestVerifier(model, base::WideToUTF16(word_and_cursor[i].word),
word_and_cursor[i].cursor);
}
}
#endif
TEST_F(TextfieldModelTest, RangeTest) {
TextfieldModel model(nullptr);
model.Append(base::ASCIIToUTF16("HELLO WORLD"));
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_NONE);
gfx::Range range = model.render_text()->selection();
EXPECT_TRUE(range.is_empty());
EXPECT_EQ(0U, range.start());
EXPECT_EQ(0U, range.end());
#if defined(OS_WIN) // Move/select right by word includes space/punctuation.
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_RETAIN);
range = model.render_text()->selection();
EXPECT_FALSE(range.is_empty());
EXPECT_FALSE(range.is_reversed());
EXPECT_EQ(0U, range.start());
EXPECT_EQ(6U, range.end());
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT,
gfx::SELECTION_RETAIN);
range = model.render_text()->selection();
EXPECT_FALSE(range.is_empty());
EXPECT_EQ(0U, range.start());
EXPECT_EQ(5U, range.end());
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_RETAIN);
range = model.render_text()->selection();
EXPECT_TRUE(range.is_empty());
EXPECT_EQ(0U, range.start());
EXPECT_EQ(0U, range.end());
// now from the end.
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
range = model.render_text()->selection();
EXPECT_TRUE(range.is_empty());
EXPECT_EQ(11U, range.start());
EXPECT_EQ(11U, range.end());
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_RETAIN);
range = model.render_text()->selection();
EXPECT_FALSE(range.is_empty());
EXPECT_TRUE(range.is_reversed());
EXPECT_EQ(11U, range.start());
EXPECT_EQ(6U, range.end());
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_RETAIN);
range = model.render_text()->selection();
EXPECT_FALSE(range.is_empty());
EXPECT_TRUE(range.is_reversed());
EXPECT_EQ(11U, range.start());
EXPECT_EQ(7U, range.end());
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_RETAIN);
range = model.render_text()->selection();
EXPECT_TRUE(range.is_empty());
EXPECT_EQ(11U, range.start());
EXPECT_EQ(11U, range.end());
#else
// Non-Windows: move/select right by word does NOT include space/punctuation.
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_RETAIN);
range = model.render_text()->selection();
EXPECT_FALSE(range.is_empty());
EXPECT_FALSE(range.is_reversed());
EXPECT_EQ(0U, range.start());
EXPECT_EQ(5U, range.end());
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT,
gfx::SELECTION_RETAIN);
range = model.render_text()->selection();
EXPECT_FALSE(range.is_empty());
EXPECT_EQ(0U, range.start());
EXPECT_EQ(4U, range.end());
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_RETAIN);
range = model.render_text()->selection();
EXPECT_TRUE(range.is_empty());
EXPECT_EQ(0U, range.start());
EXPECT_EQ(0U, range.end());
// now from the end.
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
range = model.render_text()->selection();
EXPECT_TRUE(range.is_empty());
EXPECT_EQ(11U, range.start());
EXPECT_EQ(11U, range.end());
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_RETAIN);
range = model.render_text()->selection();
EXPECT_FALSE(range.is_empty());
EXPECT_TRUE(range.is_reversed());
EXPECT_EQ(11U, range.start());
EXPECT_EQ(6U, range.end());
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_RETAIN);
range = model.render_text()->selection();
EXPECT_FALSE(range.is_empty());
EXPECT_TRUE(range.is_reversed());
EXPECT_EQ(11U, range.start());
EXPECT_EQ(7U, range.end());
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_RETAIN);
range = model.render_text()->selection();
EXPECT_TRUE(range.is_empty());
EXPECT_EQ(11U, range.start());
EXPECT_EQ(11U, range.end());
#endif
// Select All
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_RETAIN);
range = model.render_text()->selection();
EXPECT_FALSE(range.is_empty());
EXPECT_TRUE(range.is_reversed());
EXPECT_EQ(11U, range.start());
EXPECT_EQ(0U, range.end());
}
TEST_F(TextfieldModelTest, SelectRangeTest) {
TextfieldModel model(nullptr);
model.Append(base::ASCIIToUTF16("HELLO WORLD"));
gfx::Range range(0, 6);
EXPECT_FALSE(range.is_reversed());
model.SelectRange(range);
EXPECT_TRUE(model.HasSelection());
EXPECT_STR_EQ("HELLO ", model.GetSelectedText());
range = gfx::Range(6, 1);
EXPECT_TRUE(range.is_reversed());
model.SelectRange(range);
EXPECT_TRUE(model.HasSelection());
EXPECT_STR_EQ("ELLO ", model.GetSelectedText());
range = gfx::Range(2, 1000);
EXPECT_FALSE(range.is_reversed());
model.SelectRange(range);
EXPECT_TRUE(model.HasSelection());
EXPECT_STR_EQ("LLO WORLD", model.GetSelectedText());
range = gfx::Range(1000, 3);
EXPECT_TRUE(range.is_reversed());
model.SelectRange(range);
EXPECT_TRUE(model.HasSelection());
EXPECT_STR_EQ("LO WORLD", model.GetSelectedText());
range = gfx::Range(0, 0);
EXPECT_TRUE(range.is_empty());
model.SelectRange(range);
EXPECT_FALSE(model.HasSelection());
EXPECT_TRUE(model.GetSelectedText().empty());
range = gfx::Range(3, 3);
EXPECT_TRUE(range.is_empty());
model.SelectRange(range);
EXPECT_FALSE(model.HasSelection());
EXPECT_TRUE(model.GetSelectedText().empty());
range = gfx::Range(1000, 100);
EXPECT_FALSE(range.is_empty());
model.SelectRange(range);
EXPECT_FALSE(model.HasSelection());
EXPECT_TRUE(model.GetSelectedText().empty());
range = gfx::Range(1000, 1000);
EXPECT_TRUE(range.is_empty());
model.SelectRange(range);
EXPECT_FALSE(model.HasSelection());
EXPECT_TRUE(model.GetSelectedText().empty());
EXPECT_TRUE(range.is_empty());
model.SelectRange({1, 5});
model.SelectRange({100, 7}, false);
EXPECT_TRUE(model.HasSelection());
EXPECT_STR_EQ("ELLO", model.GetSelectedText());
VerifyAllSelectionTexts(&model, {"ELLO", "ORLD"});
}
TEST_F(TextfieldModelTest, SelectionTest) {
TextfieldModel model(nullptr);
model.Append(base::ASCIIToUTF16("HELLO WORLD"));
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_NONE);
gfx::Range selection = model.render_text()->selection();
EXPECT_EQ(gfx::Range(0), selection);
#if defined(OS_WIN) // Select word right includes trailing space/punctuation.
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_RETAIN);
selection = model.render_text()->selection();
EXPECT_EQ(gfx::Range(0, 6), selection);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT,
gfx::SELECTION_RETAIN);
selection = model.render_text()->selection();
EXPECT_EQ(gfx::Range(0, 5), selection);
#else // Non-Windows: select word right does NOT include space/punctuation.
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_RETAIN);
selection = model.render_text()->selection();
EXPECT_EQ(gfx::Range(0, 5), selection);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT,
gfx::SELECTION_RETAIN);
selection = model.render_text()->selection();
EXPECT_EQ(gfx::Range(0, 4), selection);
#endif
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_RETAIN);
selection = model.render_text()->selection();
EXPECT_EQ(gfx::Range(0), selection);
// now from the end.
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
selection = model.render_text()->selection();
EXPECT_EQ(gfx::Range(11), selection);
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_RETAIN);
selection = model.render_text()->selection();
EXPECT_EQ(gfx::Range(11, 6), selection);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_RETAIN);
selection = model.render_text()->selection();
EXPECT_EQ(gfx::Range(11, 7), selection);
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_RETAIN);
selection = model.render_text()->selection();
EXPECT_EQ(gfx::Range(11), selection);
// Select All
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_RETAIN);
selection = model.render_text()->selection();
EXPECT_EQ(gfx::Range(11, 0), selection);
}
TEST_F(TextfieldModelTest, SelectSelectionModelTest) {
TextfieldModel model(nullptr);
model.Append(base::ASCIIToUTF16("HELLO WORLD"));
model.SelectSelectionModel(
gfx::SelectionModel(gfx::Range(0, 6), gfx::CURSOR_BACKWARD));
EXPECT_STR_EQ("HELLO ", model.GetSelectedText());
model.SelectSelectionModel(
gfx::SelectionModel(gfx::Range(6, 1), gfx::CURSOR_FORWARD));
EXPECT_STR_EQ("ELLO ", model.GetSelectedText());
model.SelectSelectionModel(
gfx::SelectionModel(gfx::Range(2, 1000), gfx::CURSOR_BACKWARD));
EXPECT_STR_EQ("LLO WORLD", model.GetSelectedText());
model.SelectSelectionModel(
gfx::SelectionModel(gfx::Range(1000, 3), gfx::CURSOR_FORWARD));
EXPECT_STR_EQ("LO WORLD", model.GetSelectedText());
model.SelectSelectionModel(gfx::SelectionModel(0, gfx::CURSOR_FORWARD));
EXPECT_TRUE(model.GetSelectedText().empty());
model.SelectSelectionModel(gfx::SelectionModel(3, gfx::CURSOR_FORWARD));
EXPECT_TRUE(model.GetSelectedText().empty());
model.SelectSelectionModel(
gfx::SelectionModel(gfx::Range(1000, 100), gfx::CURSOR_FORWARD));
EXPECT_TRUE(model.GetSelectedText().empty());
model.SelectSelectionModel(gfx::SelectionModel(1000, gfx::CURSOR_BACKWARD));
EXPECT_TRUE(model.GetSelectedText().empty());
gfx::SelectionModel mutliselection_selection_model{{2, 3},
gfx::CURSOR_BACKWARD};
mutliselection_selection_model.AddSecondarySelection({5, 4});
mutliselection_selection_model.AddSecondarySelection({1, 0});
mutliselection_selection_model.AddSecondarySelection({20, 9});
mutliselection_selection_model.AddSecondarySelection({6, 6});
model.SelectSelectionModel(mutliselection_selection_model);
EXPECT_STR_EQ("L", model.GetSelectedText());
VerifyAllSelectionTexts(&model, {"L", "O", "H", "LD", ""});
}
TEST_F(TextfieldModelTest, CompositionTextTest) {
TextfieldModel model(this);
model.Append(base::ASCIIToUTF16("1234590"));
model.SelectRange(gfx::Range(5, 5));
EXPECT_FALSE(model.HasSelection());
EXPECT_EQ(5U, model.GetCursorPosition());
gfx::Range range;
model.GetTextRange(&range);
EXPECT_EQ(gfx::Range(0, 7), range);
ui::CompositionText composition;
composition.text = base::ASCIIToUTF16("678");
composition.ime_text_spans.push_back(
ui::ImeTextSpan(ui::ImeTextSpan::Type::kComposition, 0, 3,
ui::ImeTextSpan::Thickness::kThin));
// Cursor should be at the end of composition when characters are just typed.
composition.selection = gfx::Range(3, 3);
model.SetCompositionText(composition);
EXPECT_TRUE(model.HasCompositionText());
EXPECT_FALSE(model.HasSelection());
// Cancel the composition.
model.CancelCompositionText();
composition_text_confirmed_or_cleared_ = false;
// Restart composition with targeting "67" in "678".
composition.selection = gfx::Range(1, 3);
composition.ime_text_spans.clear();
composition.ime_text_spans.push_back(
ui::ImeTextSpan(ui::ImeTextSpan::Type::kComposition, 0, 2,
ui::ImeTextSpan::Thickness::kThick));
composition.ime_text_spans.push_back(
ui::ImeTextSpan(ui::ImeTextSpan::Type::kComposition, 2, 3,
ui::ImeTextSpan::Thickness::kThin));
model.SetCompositionText(composition);
EXPECT_TRUE(model.HasCompositionText());
EXPECT_TRUE(model.HasSelection());
#if !defined(OS_CHROMEOS)
// |composition.selection| is ignored because SetCompositionText checks
// if a thick underline exists first.
EXPECT_EQ(gfx::Range(5, 7), model.render_text()->selection());
EXPECT_EQ(7U, model.render_text()->cursor_position());
#else
// See SelectRangeInCompositionText().
EXPECT_EQ(gfx::Range(7, 5), model.render_text()->selection());
EXPECT_EQ(5U, model.render_text()->cursor_position());
#endif
model.GetTextRange(&range);
EXPECT_EQ(10U, range.end());
EXPECT_STR_EQ("1234567890", model.text());
model.GetCompositionTextRange(&range);
EXPECT_EQ(gfx::Range(5, 8), range);
// Check the composition text.
EXPECT_STR_EQ("456", model.GetTextFromRange(gfx::Range(3, 6)));
EXPECT_FALSE(composition_text_confirmed_or_cleared_);
model.CancelCompositionText();
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
EXPECT_FALSE(model.HasCompositionText());
EXPECT_FALSE(model.HasSelection());
EXPECT_EQ(5U, model.GetCursorPosition());
model.SetCompositionText(composition);
EXPECT_STR_EQ("1234567890", model.text());
EXPECT_TRUE(model.SetText(base::ASCIIToUTF16("1234567890"), 0));
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
// Also test the case where a selection exists but a thick underline doesn't.
composition.selection = gfx::Range(0, 1);
composition.ime_text_spans.clear();
model.SetCompositionText(composition);
EXPECT_STR_EQ("1234567890678", model.text());
EXPECT_TRUE(model.HasSelection());
#if !defined(OS_CHROMEOS)
EXPECT_EQ(gfx::Range(10, 11), model.render_text()->selection());
EXPECT_EQ(11U, model.render_text()->cursor_position());
#else
// See SelectRangeInCompositionText().
EXPECT_EQ(gfx::Range(11, 10), model.render_text()->selection());
EXPECT_EQ(10U, model.render_text()->cursor_position());
#endif
model.InsertText(base::UTF8ToUTF16("-"));
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
EXPECT_STR_EQ("1234567890-", model.text());
EXPECT_FALSE(model.HasCompositionText());
EXPECT_FALSE(model.HasSelection());
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT,
gfx::SELECTION_RETAIN);
EXPECT_STR_EQ("-", model.GetSelectedText());
model.SetCompositionText(composition);
EXPECT_STR_EQ("1234567890678", model.text());
model.ReplaceText(base::UTF8ToUTF16("-"));
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
EXPECT_STR_EQ("1234567890-", model.text());
EXPECT_FALSE(model.HasCompositionText());
EXPECT_FALSE(model.HasSelection());
model.SetCompositionText(composition);
model.Append(base::UTF8ToUTF16("-"));
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
EXPECT_STR_EQ("1234567890-678-", model.text());
model.SetCompositionText(composition);
model.Delete();
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
EXPECT_STR_EQ("1234567890-678-", model.text());
model.SetCompositionText(composition);
model.Backspace();
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
EXPECT_STR_EQ("1234567890-678-", model.text());
model.SetText(base::string16(), 0);
model.SetCompositionText(composition);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_NONE);
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
EXPECT_STR_EQ("678", model.text());
EXPECT_EQ(2U, model.GetCursorPosition());
model.SetCompositionText(composition);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_NONE);
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
EXPECT_STR_EQ("676788", model.text());
EXPECT_EQ(6U, model.GetCursorPosition());
model.SetCompositionText(composition);
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_NONE);
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
EXPECT_STR_EQ("676788678", model.text());
model.SetText(base::string16(), 0);
model.SetCompositionText(composition);
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
model.SetCompositionText(composition);
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_RETAIN);
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
EXPECT_STR_EQ("678678", model.text());
model.SetCompositionText(composition);
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
EXPECT_STR_EQ("678", model.text());
model.SetCompositionText(composition);
gfx::SelectionModel sel(
gfx::Range(model.render_text()->selection().start(), 0),
gfx::CURSOR_FORWARD);
model.MoveCursorTo(sel);
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
EXPECT_STR_EQ("678678", model.text());
model.SetCompositionText(composition);
model.SelectRange(gfx::Range(0, 3));
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
EXPECT_STR_EQ("678", model.text());
model.SetCompositionText(composition);
model.SelectAll(false);
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
EXPECT_STR_EQ("678", model.text());
model.SetCompositionText(composition);
model.SelectWord();
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
EXPECT_STR_EQ("678", model.text());
model.SetCompositionText(composition);
model.ClearSelection();
EXPECT_TRUE(composition_text_confirmed_or_cleared_);
composition_text_confirmed_or_cleared_ = false;
model.SetCompositionText(composition);
EXPECT_FALSE(model.Cut());
EXPECT_FALSE(composition_text_confirmed_or_cleared_);
}
TEST_F(TextfieldModelTest, UndoRedo_BasicTest) {
TextfieldModel model(nullptr);
model.InsertChar('a');
EXPECT_FALSE(model.Redo()); // There is nothing to redo.
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("", model.text());
EXPECT_TRUE(model.Redo());
EXPECT_STR_EQ("a", model.text());
// Continuous inserts are treated as one edit.
model.InsertChar('b');
model.InsertChar('c');
EXPECT_STR_EQ("abc", model.text());
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("a", model.text());
EXPECT_EQ(1U, model.GetCursorPosition());
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("", model.text());
EXPECT_EQ(0U, model.GetCursorPosition());
// Undoing further shouldn't change the text.
EXPECT_FALSE(model.Undo());
EXPECT_STR_EQ("", model.text());
EXPECT_FALSE(model.Undo());
EXPECT_STR_EQ("", model.text());
EXPECT_EQ(0U, model.GetCursorPosition());
// Redoing to the latest text.
EXPECT_TRUE(model.Redo());
EXPECT_STR_EQ("a", model.text());
EXPECT_EQ(1U, model.GetCursorPosition());
EXPECT_TRUE(model.Redo());
EXPECT_STR_EQ("abc", model.text());
EXPECT_EQ(3U, model.GetCursorPosition());
// Backspace ===============================
EXPECT_TRUE(model.Backspace());
EXPECT_STR_EQ("ab", model.text());
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("abc", model.text());
EXPECT_EQ(3U, model.GetCursorPosition());
EXPECT_TRUE(model.Redo());
EXPECT_STR_EQ("ab", model.text());
EXPECT_EQ(2U, model.GetCursorPosition());
// Continous backspaces are treated as one edit.
EXPECT_TRUE(model.Backspace());
EXPECT_TRUE(model.Backspace());
EXPECT_STR_EQ("", model.text());
// Extra backspace shouldn't affect the history.
EXPECT_FALSE(model.Backspace());
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("ab", model.text());
EXPECT_EQ(2U, model.GetCursorPosition());
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("abc", model.text());
EXPECT_EQ(3U, model.GetCursorPosition());
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("a", model.text());
EXPECT_EQ(1U, model.GetCursorPosition());
// Clear history
model.ClearEditHistory();
EXPECT_FALSE(model.Undo());
EXPECT_FALSE(model.Redo());
EXPECT_STR_EQ("a", model.text());
EXPECT_EQ(1U, model.GetCursorPosition());
// Delete ===============================
model.SetText(base::ASCIIToUTF16("ABCDE"), 0);
model.ClearEditHistory();
model.MoveCursorTo(2);
EXPECT_TRUE(model.Delete());
EXPECT_STR_EQ("ABDE", model.text());
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_NONE);
EXPECT_TRUE(model.Delete());
EXPECT_STR_EQ("BDE", model.text());
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("ABDE", model.text());
EXPECT_EQ(0U, model.GetCursorPosition());
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("ABCDE", model.text());
EXPECT_EQ(2U, model.GetCursorPosition());
EXPECT_TRUE(model.Redo());
EXPECT_STR_EQ("ABDE", model.text());
EXPECT_EQ(2U, model.GetCursorPosition());
// Continous deletes are treated as one edit.
EXPECT_TRUE(model.Delete());
EXPECT_TRUE(model.Delete());
EXPECT_STR_EQ("AB", model.text());
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("ABDE", model.text());
EXPECT_EQ(2U, model.GetCursorPosition());
EXPECT_TRUE(model.Redo());
EXPECT_STR_EQ("AB", model.text());
EXPECT_EQ(2U, model.GetCursorPosition());
}
TEST_F(TextfieldModelTest, UndoRedo_SetText) {
// This is to test the undo/redo behavior of omnibox.
TextfieldModel model(nullptr);
// Simulate typing www.y while www.google.com and www.youtube.com are
// autocompleted.
model.InsertChar('w'); // w|
EXPECT_STR_EQ("w", model.text());
EXPECT_EQ(1U, model.GetCursorPosition());
model.SetText(base::ASCIIToUTF16("www.google.com"), 1); // w|ww.google.com
model.SelectRange(gfx::Range(14, 1)); // w[ww.google.com]
EXPECT_EQ(1U, model.GetCursorPosition());
EXPECT_STR_EQ("www.google.com", model.text());
model.InsertChar('w'); // ww|
EXPECT_STR_EQ("ww", model.text());
model.SetText(base::ASCIIToUTF16("www.google.com"), 2); // ww|w.google.com
model.SelectRange(gfx::Range(14, 2)); // ww[w.google.com]
model.InsertChar('w'); // www|
EXPECT_STR_EQ("www", model.text());
model.SetText(base::ASCIIToUTF16("www.google.com"), 3); // www|.google.com
model.SelectRange(gfx::Range(14, 3)); // www[.google.com]
model.InsertChar('.'); // www.|
EXPECT_STR_EQ("www.", model.text());
model.SetText(base::ASCIIToUTF16("www.google.com"), 4); // www.|google.com
model.SelectRange(gfx::Range(14, 4)); // www.[google.com]
model.InsertChar('y'); // www.y|
EXPECT_STR_EQ("www.y", model.text());
model.SetText(base::ASCIIToUTF16("www.youtube.com"), 5); // www.y|outube.com
EXPECT_STR_EQ("www.youtube.com", model.text());
EXPECT_EQ(5U, model.GetCursorPosition());
// Undo until the initial edit.
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("www.google.com", model.text());
EXPECT_EQ(4U, model.GetCursorPosition());
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("www.google.com", model.text());
EXPECT_EQ(3U, model.GetCursorPosition());
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("www.google.com", model.text());
EXPECT_EQ(2U, model.GetCursorPosition());
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("www.google.com", model.text());
EXPECT_EQ(1U, model.GetCursorPosition());
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("", model.text());
EXPECT_EQ(0U, model.GetCursorPosition());
EXPECT_FALSE(model.Undo());
// Redo until the last edit.
EXPECT_TRUE(model.Redo());
EXPECT_STR_EQ("www.google.com", model.text());
EXPECT_EQ(1U, model.GetCursorPosition());
EXPECT_TRUE(model.Redo());
EXPECT_STR_EQ("www.google.com", model.text());
EXPECT_EQ(2U, model.GetCursorPosition());
EXPECT_TRUE(model.Redo());
EXPECT_STR_EQ("www.google.com", model.text());
EXPECT_EQ(3U, model.GetCursorPosition());
EXPECT_TRUE(model.Redo());
EXPECT_STR_EQ("www.google.com", model.text());
EXPECT_EQ(4U, model.GetCursorPosition());
EXPECT_TRUE(model.Redo());
EXPECT_STR_EQ("www.youtube.com", model.text());
EXPECT_EQ(5U, model.GetCursorPosition());
EXPECT_FALSE(model.Redo());
}
TEST_F(TextfieldModelTest, UndoRedo_BackspaceThenSetText) {
// This is to test the undo/redo behavior of omnibox.
TextfieldModel model(nullptr);
model.InsertChar('w');
EXPECT_STR_EQ("w", model.text());
EXPECT_EQ(1U, model.GetCursorPosition());
model.SetText(base::ASCIIToUTF16("www.google.com"), 1);
EXPECT_STR_EQ("www.google.com", model.text());
EXPECT_EQ(1U, model.GetCursorPosition());
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
EXPECT_EQ(14U, model.GetCursorPosition());
EXPECT_TRUE(model.Backspace());
EXPECT_TRUE(model.Backspace());
EXPECT_STR_EQ("www.google.c", model.text());
// Autocomplete sets the text.
model.SetText(base::ASCIIToUTF16("www.google.com/search=www.google.c"), 12);
EXPECT_STR_EQ("www.google.com/search=www.google.c", model.text());
EXPECT_EQ(12U, model.GetCursorPosition());
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("www.google.c", model.text());
EXPECT_EQ(12U, model.GetCursorPosition());
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("www.google.com", model.text());
EXPECT_EQ(14U, model.GetCursorPosition());
}
TEST_F(TextfieldModelTest, UndoRedo_CutCopyPasteTest) {
TextfieldModel model(nullptr);
model.SetText(base::ASCIIToUTF16("ABCDE"), 5);
EXPECT_FALSE(model.Redo()); // There is nothing to redo.
// Test Cut.
model.SelectRange(gfx::Range(1, 3)); // A[BC]DE
EXPECT_EQ(3U, model.GetCursorPosition());
model.Cut(); // A|DE
EXPECT_STR_EQ("ADE", model.text());
EXPECT_EQ(1U, model.GetCursorPosition());
EXPECT_TRUE(model.Undo()); // A[BC]DE
EXPECT_STR_EQ("ABCDE", model.text());
EXPECT_EQ(3U, model.GetCursorPosition());
EXPECT_TRUE(model.render_text()->selection().EqualsIgnoringDirection(
gfx::Range(1, 3)));
EXPECT_TRUE(model.Undo()); // |
EXPECT_STR_EQ("", model.text());
EXPECT_EQ(0U, model.GetCursorPosition());
EXPECT_FALSE(model.Undo()); // There is no more to undo. |
EXPECT_STR_EQ("", model.text());
EXPECT_TRUE(model.Redo()); // ABCDE|
EXPECT_STR_EQ("ABCDE", model.text());
EXPECT_EQ(5U, model.GetCursorPosition());
EXPECT_TRUE(model.Redo()); // A|DE
EXPECT_STR_EQ("ADE", model.text());
EXPECT_EQ(1U, model.GetCursorPosition());
EXPECT_FALSE(model.Redo()); // There is no more to redo. A|DE
EXPECT_STR_EQ("ADE", model.text());
model.Paste(); // ABC|DE
model.Paste(); // ABCBC|DE
model.Paste(); // ABCBCBC|DE
EXPECT_STR_EQ("ABCBCBCDE", model.text());
EXPECT_EQ(7U, model.GetCursorPosition());
EXPECT_TRUE(model.Undo()); // ABCBC|DE
EXPECT_STR_EQ("ABCBCDE", model.text());
EXPECT_EQ(5U, model.GetCursorPosition());
EXPECT_TRUE(model.Undo()); // ABC|DE
EXPECT_STR_EQ("ABCDE", model.text());
EXPECT_EQ(3U, model.GetCursorPosition());
EXPECT_TRUE(model.Undo()); // A|DE
EXPECT_STR_EQ("ADE", model.text());
EXPECT_EQ(1U, model.GetCursorPosition());
EXPECT_TRUE(model.Undo()); // A[BC]DE
EXPECT_STR_EQ("ABCDE", model.text());
EXPECT_EQ(3U, model.GetCursorPosition());
EXPECT_TRUE(model.render_text()->selection().EqualsIgnoringDirection(
gfx::Range(1, 3)));
EXPECT_TRUE(model.Undo()); // |
EXPECT_STR_EQ("", model.text());
EXPECT_EQ(0U, model.GetCursorPosition());
EXPECT_FALSE(model.Undo()); // |
EXPECT_STR_EQ("", model.text());
EXPECT_TRUE(model.Redo());
EXPECT_STR_EQ("ABCDE", model.text()); // ABCDE|
EXPECT_EQ(5U, model.GetCursorPosition());
// Test Redo.
EXPECT_TRUE(model.Redo()); // A|DE
EXPECT_STR_EQ("ADE", model.text());
EXPECT_EQ(1U, model.GetCursorPosition());
EXPECT_TRUE(model.Redo()); // ABC|DE
EXPECT_STR_EQ("ABCDE", model.text());
EXPECT_EQ(3U, model.GetCursorPosition());
EXPECT_TRUE(model.Redo()); // ABCBC|DE
EXPECT_STR_EQ("ABCBCDE", model.text());
EXPECT_EQ(5U, model.GetCursorPosition());
EXPECT_TRUE(model.Redo()); // ABCBCBC|DE
EXPECT_STR_EQ("ABCBCBCDE", model.text());
EXPECT_EQ(7U, model.GetCursorPosition());
EXPECT_FALSE(model.Redo()); // ABCBCBC|DE
// Test using SelectRange.
model.SelectRange(gfx::Range(1, 3)); // A[BC]BCBCDE
EXPECT_TRUE(model.Cut()); // A|BCBCDE
EXPECT_STR_EQ("ABCBCDE", model.text());
EXPECT_EQ(1U, model.GetCursorPosition());
model.SelectRange(gfx::Range(1, 1)); // A|BCBCDE
EXPECT_FALSE(model.Cut()); // A|BCBCDE
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
// ABCBCDE|
EXPECT_TRUE(model.Paste()); // ABCBCDEBC|
EXPECT_STR_EQ("ABCBCDEBC", model.text());
EXPECT_EQ(9U, model.GetCursorPosition());
EXPECT_TRUE(model.Undo()); // ABCBCDE|
EXPECT_STR_EQ("ABCBCDE", model.text());
EXPECT_EQ(7U, model.GetCursorPosition());
// An empty cut shouldn't create an edit.
EXPECT_TRUE(model.Undo()); // ABC|BCBCDE
EXPECT_STR_EQ("ABCBCBCDE", model.text());
EXPECT_EQ(3U, model.GetCursorPosition());
EXPECT_TRUE(model.render_text()->selection().EqualsIgnoringDirection(
gfx::Range(1, 3)));
// Test Copy.
ResetModel(&model);
model.SetText(base::ASCIIToUTF16("12345"), 5); // 12345|
EXPECT_STR_EQ("12345", model.text());
EXPECT_EQ(5U, model.GetCursorPosition());
model.SelectRange(gfx::Range(1, 3)); // 1[23]45
model.Copy(); // Copy "23". // 1[23]45
EXPECT_STR_EQ("12345", model.text());
EXPECT_EQ(3U, model.GetCursorPosition());
model.Paste(); // Paste "23" into "23". // 123|45
EXPECT_STR_EQ("12345", model.text());
EXPECT_EQ(3U, model.GetCursorPosition());
model.Paste(); // 12323|45
EXPECT_STR_EQ("1232345", model.text());
EXPECT_EQ(5U, model.GetCursorPosition());
EXPECT_TRUE(model.Undo()); // 123|45
EXPECT_STR_EQ("12345", model.text());
EXPECT_EQ(3U, model.GetCursorPosition());
// TODO(oshima): Change the return type from bool to enum.
EXPECT_FALSE(model.Undo()); // No text change. 1[23]45
EXPECT_STR_EQ("12345", model.text());
EXPECT_EQ(3U, model.GetCursorPosition());
EXPECT_TRUE(model.render_text()->selection().EqualsIgnoringDirection(
gfx::Range(1, 3)));
EXPECT_TRUE(model.Undo()); // |
EXPECT_STR_EQ("", model.text());
EXPECT_FALSE(model.Undo()); // |
// Test Redo.
EXPECT_TRUE(model.Redo()); // 12345|
EXPECT_STR_EQ("12345", model.text());
EXPECT_EQ(5U, model.GetCursorPosition());
EXPECT_TRUE(model.Redo()); // 12|345
EXPECT_STR_EQ("12345", model.text()); // For 1st paste
EXPECT_EQ(3U, model.GetCursorPosition());
EXPECT_TRUE(model.Redo()); // 12323|45
EXPECT_STR_EQ("1232345", model.text());
EXPECT_EQ(5U, model.GetCursorPosition());
EXPECT_FALSE(model.Redo()); // 12323|45
EXPECT_STR_EQ("1232345", model.text());
// Test using SelectRange.
model.SelectRange(gfx::Range(1, 3)); // 1[23]2345
model.Copy(); // 1[23]2345
EXPECT_STR_EQ("1232345", model.text());
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
// 1232345|
EXPECT_TRUE(model.Paste()); // 123234523|
EXPECT_STR_EQ("123234523", model.text());
EXPECT_EQ(9U, model.GetCursorPosition());
EXPECT_TRUE(model.Undo()); // 1232345|
EXPECT_STR_EQ("1232345", model.text());
EXPECT_EQ(7U, model.GetCursorPosition());
}
TEST_F(TextfieldModelTest, UndoRedo_CursorTest) {
TextfieldModel model(nullptr);
model.InsertChar('a');
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_NONE);
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT,
gfx::SELECTION_NONE);
model.InsertChar('b');
// Moving the cursor shouldn't create a new edit.
EXPECT_STR_EQ("ab", model.text());
EXPECT_FALSE(model.Redo());
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("", model.text());
EXPECT_FALSE(model.Undo());
EXPECT_STR_EQ("", model.text());
EXPECT_TRUE(model.Redo());
EXPECT_STR_EQ("ab", model.text());
EXPECT_EQ(2U, model.GetCursorPosition());
EXPECT_FALSE(model.Redo());
}
TEST_F(TextfieldModelTest, Undo_SelectionTest) {
gfx::Range range = gfx::Range(2, 4);
TextfieldModel model(nullptr);
model.SetText(base::ASCIIToUTF16("abcdef"), 0);
model.SelectRange(range);
EXPECT_EQ(model.render_text()->selection(), range);
// Deleting the selected text should change the text and the range.
EXPECT_TRUE(model.Backspace());
EXPECT_STR_EQ("abef", model.text());
EXPECT_EQ(model.render_text()->selection(), gfx::Range(2, 2));
// Undoing the deletion should restore the former range.
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("abcdef", model.text());
EXPECT_EQ(model.render_text()->selection(), range);
// When range.start = range.end, nothing is selected and
// range.start = range.end = cursor position
model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_NONE);
EXPECT_EQ(model.render_text()->selection(), gfx::Range(2, 2));
// Deleting a single character should change the text and cursor location.
EXPECT_TRUE(model.Backspace());
EXPECT_STR_EQ("acdef", model.text());
EXPECT_EQ(model.render_text()->selection(), gfx::Range(1, 1));
// Undoing the deletion should restore the former range.
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("abcdef", model.text());
EXPECT_EQ(model.render_text()->selection(), gfx::Range(2, 2));
model.MoveCursorTo(model.text().length());
EXPECT_TRUE(model.Backspace());
model.SelectRange(gfx::Range(1, 3));
model.SetText(base::ASCIIToUTF16("[set]"), 0);
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("abcde", model.text());
EXPECT_EQ(model.render_text()->selection(), gfx::Range(1, 3));
}
void RunInsertReplaceTest(TextfieldModel* model) {
const bool reverse = model->render_text()->selection().is_reversed();
model->InsertChar('1');
model->InsertChar('2');
model->InsertChar('3');
EXPECT_STR_EQ("a123d", model->text());
EXPECT_EQ(4U, model->GetCursorPosition());
EXPECT_TRUE(model->Undo());
EXPECT_STR_EQ("abcd", model->text());
EXPECT_EQ(reverse ? 1U : 3U, model->GetCursorPosition());
EXPECT_TRUE(model->Undo());
EXPECT_STR_EQ("", model->text());
EXPECT_EQ(0U, model->GetCursorPosition());
EXPECT_FALSE(model->Undo());
EXPECT_TRUE(model->Redo());
EXPECT_STR_EQ("abcd", model->text());
EXPECT_EQ(4U, model->GetCursorPosition());
EXPECT_TRUE(model->Redo());
EXPECT_STR_EQ("a123d", model->text());
EXPECT_EQ(4U, model->GetCursorPosition());
EXPECT_FALSE(model->Redo());
}
void RunOverwriteReplaceTest(TextfieldModel* model) {
const bool reverse = model->render_text()->selection().is_reversed();
model->ReplaceChar('1');
model->ReplaceChar('2');
model->ReplaceChar('3');
model->ReplaceChar('4');
EXPECT_STR_EQ("a1234", model->text());
EXPECT_EQ(5U, model->GetCursorPosition());
EXPECT_TRUE(model->Undo());
EXPECT_STR_EQ("abcd", model->text());
EXPECT_EQ(reverse ? 1U : 3U, model->GetCursorPosition());
EXPECT_TRUE(model->Undo());
EXPECT_STR_EQ("", model->text());
EXPECT_EQ(0U, model->GetCursorPosition());
EXPECT_FALSE(model->Undo());
EXPECT_TRUE(model->Redo());
EXPECT_STR_EQ("abcd", model->text());
EXPECT_EQ(4U, model->GetCursorPosition());
EXPECT_TRUE(model->Redo());
EXPECT_STR_EQ("a1234", model->text());
EXPECT_EQ(5U, model->GetCursorPosition());
EXPECT_FALSE(model->Redo());
}
TEST_F(TextfieldModelTest, UndoRedo_ReplaceTest) {
{
SCOPED_TRACE("Select forwards and insert.");
TextfieldModel model(nullptr);
model.SetText(base::ASCIIToUTF16("abcd"), 4);
model.SelectRange(gfx::Range(1, 3));
RunInsertReplaceTest(&model);
}
{
SCOPED_TRACE("Select reversed and insert.");
TextfieldModel model(nullptr);
model.SetText(base::ASCIIToUTF16("abcd"), 4);
model.SelectRange(gfx::Range(3, 1));
RunInsertReplaceTest(&model);
}
{
SCOPED_TRACE("Select forwards and overwrite.");
TextfieldModel model(nullptr);
model.SetText(base::ASCIIToUTF16("abcd"), 4);
model.SelectRange(gfx::Range(1, 3));
RunOverwriteReplaceTest(&model);
}
{
SCOPED_TRACE("Select reversed and overwrite.");
TextfieldModel model(nullptr);
model.SetText(base::ASCIIToUTF16("abcd"), 4);
model.SelectRange(gfx::Range(3, 1));
RunOverwriteReplaceTest(&model);
}
}
TEST_F(TextfieldModelTest, UndoRedo_CompositionText) {
TextfieldModel model(nullptr);
ui::CompositionText composition;
composition.text = base::ASCIIToUTF16("abc");
composition.ime_text_spans.push_back(
ui::ImeTextSpan(ui::ImeTextSpan::Type::kComposition, 0, 3,
ui::ImeTextSpan::Thickness::kThin));
composition.selection = gfx::Range(2, 3);
model.SetText(base::ASCIIToUTF16("ABCDE"), 0);
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
model.InsertChar('x');
EXPECT_STR_EQ("ABCDEx", model.text());
EXPECT_TRUE(model.Undo()); // set composition should forget undone edit.
model.SetCompositionText(composition);
EXPECT_TRUE(model.HasCompositionText());
EXPECT_TRUE(model.HasSelection());
EXPECT_STR_EQ("ABCDEabc", model.text());
// Confirm the composition.
model.ConfirmCompositionText();
EXPECT_STR_EQ("ABCDEabc", model.text());
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("ABCDE", model.text());
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("", model.text());
EXPECT_TRUE(model.Redo());
EXPECT_STR_EQ("ABCDE", model.text());
EXPECT_TRUE(model.Redo());
EXPECT_STR_EQ("ABCDEabc", model.text());
EXPECT_FALSE(model.Redo());
// Cancel the composition.
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_NONE);
model.SetCompositionText(composition);
EXPECT_STR_EQ("abcABCDEabc", model.text());
model.CancelCompositionText();
EXPECT_STR_EQ("ABCDEabc", model.text());
EXPECT_FALSE(model.Redo());
EXPECT_STR_EQ("ABCDEabc", model.text());
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("ABCDE", model.text());
EXPECT_TRUE(model.Redo());
EXPECT_STR_EQ("ABCDEabc", model.text());
EXPECT_FALSE(model.Redo());
// Call SetText with the same text as the result.
ResetModel(&model);
model.SetText(base::ASCIIToUTF16("ABCDE"), 0);
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
model.SetCompositionText(composition);
EXPECT_STR_EQ("ABCDEabc", model.text());
model.SetText(base::ASCIIToUTF16("ABCDEabc"), 0);
EXPECT_STR_EQ("ABCDEabc", model.text());
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("ABCDE", model.text());
EXPECT_TRUE(model.Redo());
EXPECT_STR_EQ("ABCDEabc", model.text());
EXPECT_FALSE(model.Redo());
// Call SetText with a different result; the composition should be forgotten.
ResetModel(&model);
model.SetText(base::ASCIIToUTF16("ABCDE"), 0);
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
model.SetCompositionText(composition);
EXPECT_STR_EQ("ABCDEabc", model.text());
model.SetText(base::ASCIIToUTF16("1234"), 0);
EXPECT_STR_EQ("1234", model.text());
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("ABCDE", model.text());
EXPECT_TRUE(model.Redo());
EXPECT_STR_EQ("1234", model.text());
EXPECT_FALSE(model.Redo());
// TODO(oshima): Test the behavior with an IME.
}
TEST_F(TextfieldModelTest, UndoRedo_TypingWithSecondarySelections) {
TextfieldModel model(nullptr);
// Type 'ab cd' as 'prefix ab xy suffix' and 'prefix ab cd suffix' are
// autocompleted.
// Type 'a', autocomplete [prefix ]a[b xy suffix]
model.InsertChar('a');
model.SetText(base::ASCIIToUTF16("prefix ab xy suffix"), 8);
model.SelectRange({19, 8});
model.SelectRange({0, 7}, false);
// Type 'ab', autocomplete [prefix ]ab[ xy suffix]
model.InsertChar('b');
model.SetText(base::ASCIIToUTF16("prefix ab xy suffix"), 9);
model.SelectRange({19, 9});
model.SelectRange({0, 7}, false);
// Type 'ab ', autocomplete [prefix ]ab [xy suffix]
model.InsertChar(' ');
model.SetText(base::ASCIIToUTF16("prefix ab xy suffix"), 10);
model.SelectRange({19, 10});
model.SelectRange({0, 7}, false);
// Type 'ab c', autocomplete changed to [prefix ]ab c[d suffix]
model.InsertChar('c');
model.SetText(base::ASCIIToUTF16("prefix ab cd suffix"), 11);
model.SelectRange({19, 11});
model.SelectRange({0, 7}, false);
// Type 'ab cd', autocomplete [prefix ]ab cd[ suffix]
model.InsertChar('d');
model.SetText(base::ASCIIToUTF16("prefix ab cd suffix"), 12);
model.SelectRange({19, 12});
model.SelectRange({0, 7}, false);
// Undo 3 times
EXPECT_TRUE(model.Undo()); // [prefix ]ab c[d suffix]
EXPECT_STR_EQ("prefix ab cd suffix", model.text());
EXPECT_EQ(11U, model.GetCursorPosition());
VerifyAllSelectionTexts(&model, {"d suffix", "prefix "});
EXPECT_TRUE(model.Undo()); // [prefix ]ab [xy suffix]
EXPECT_STR_EQ("prefix ab xy suffix", model.text());
EXPECT_EQ(10U, model.GetCursorPosition());
VerifyAllSelectionTexts(&model, {"xy suffix", "prefix "});
EXPECT_TRUE(model.Undo()); // [prefix ]ab[ xy suffix]
EXPECT_STR_EQ("prefix ab xy suffix", model.text());
EXPECT_EQ(9U, model.GetCursorPosition());
VerifyAllSelectionTexts(&model, {" xy suffix", "prefix "});
// Redo 3 times
EXPECT_TRUE(model.Redo()); // [prefix ]ab [xy suffix]
EXPECT_STR_EQ("prefix ab xy suffix", model.text());
EXPECT_EQ(10U, model.GetCursorPosition());
EXPECT_FALSE(model.HasSelection());
EXPECT_TRUE(model.Redo()); // [prefix ]ab c[d suffix]
EXPECT_STR_EQ("prefix ab cd suffix", model.text());
EXPECT_EQ(11U, model.GetCursorPosition());
EXPECT_FALSE(model.HasSelection());
EXPECT_TRUE(model.Redo()); // [prefix ]ab cd[ suffix]
EXPECT_STR_EQ("prefix ab cd suffix", model.text());
EXPECT_EQ(12U, model.GetCursorPosition());
EXPECT_FALSE(model.HasSelection());
}
TEST_F(TextfieldModelTest, UndoRedo_MergingEditsWithSecondarySelections) {
TextfieldModel model(nullptr);
// Test all possible merge combinations involving secondary selections.
// I.e. an initial [replace or delete] edit with secondary selections,
// followed by a second and third [insert, replace, or delete] edits, which
// are [continuous and discontinuous] respectively. Some cases of the third,
// discontinuous edit have been omitted when the the second edit would not
// been merged anyways.
// Note, the cursor and selections depend on whether we're traversing forward
// or backwards through edit history. I.e., `undo(); redo();` can result in a
// different outcome than `redo(); undo();`. In general, if our edit history
// consists of 3 edits: A->B, C->D, & E->F, then undo will traverse
// F->E->C->A, while redo will traverse A->B->D->F. Though, B & C and D & E
// will have the same text, they can have different cursors and selections.
// Replacement with secondary selections followed by insertions
model.SetText(base::ASCIIToUTF16("prefix infix suffix"), 13);
model.SelectRange({18, 13});
model.SelectRange({1, 6}, false); // p[refix] infix [suffi]x
// Replace
model.InsertChar('1'); // p infix 1|x
// Continuous insert (should merge)
model.InsertChar('3'); // p infix 13|x
// Discontinuous insert (should not merge)
model.SelectRange({9, 9}); // p infix 1|3x
model.InsertChar('2'); // p infix 12|3x
EXPECT_STR_EQ("p infix 123x", model.text());
EXPECT_FALSE(model.HasSelection());
// Edit history should be
// p[refix] infix [suffi]x -> p infix 13|x
// p infix 1|3x -> p infix 12|3x
// Undo 2 times
EXPECT_TRUE(model.Undo()); // p infix 1|3x
EXPECT_STR_EQ("p infix 13x", model.text());
EXPECT_EQ(9U, model.GetCursorPosition());
EXPECT_FALSE(model.HasSelection());
EXPECT_TRUE(model.Undo()); // p[refix] infix [suffi]x
EXPECT_STR_EQ("prefix infix suffix", model.text());
EXPECT_EQ(13U, model.GetCursorPosition());
VerifyAllSelectionTexts(&model, {"suffi", "refix"});
// Redo 2 times
EXPECT_TRUE(model.Redo()); // p infix 13|x
EXPECT_STR_EQ("p infix 13x", model.text());
EXPECT_EQ(10U, model.GetCursorPosition());
EXPECT_FALSE(model.HasSelection());
EXPECT_TRUE(model.Redo()); // p infix 12|3x
EXPECT_STR_EQ("p infix 123x", model.text());
EXPECT_EQ(10U, model.GetCursorPosition());
EXPECT_FALSE(model.HasSelection());
EXPECT_FALSE(model.Redo());
// Replacement with secondary selections followed by replacements
model.SetText(base::ASCIIToUTF16("prefix infix suffix"), 13);
model.SelectRange({15, 13});
model.SelectRange({1, 6}, false); // p[refix] infix [su]ffix
// Replace
model.InsertChar('1'); // p infix 1|ffix
// Continuous multiple characters, and backwards replace (should merge)
model.SelectRange({11, 9}); // p infix 1[ff]ix
model.InsertChar('3'); // p infix 13|ix
// Discontinuous but adjacent replace (should not merge)
model.SelectRange({10, 9}); // p infix 1[3]ix
model.InsertChar('2'); // p infix 12|ix
EXPECT_STR_EQ("p infix 12ix", model.text());
EXPECT_FALSE(model.HasSelection());
// Edit history should be
// p[refix] infix [su]ffix -> p infix 13|ix
// p infix 1[3]ix -> p infix 12|ix
// Undo 2 times
EXPECT_TRUE(model.Undo()); // p infix 1[3]ix
EXPECT_STR_EQ("p infix 13ix", model.text());
EXPECT_EQ(9U, model.GetCursorPosition());
VerifyAllSelectionTexts(&model, {"3"});
EXPECT_TRUE(model.Undo()); // p[refix] infix [su]ffix
EXPECT_STR_EQ("prefix infix suffix", model.text());
EXPECT_EQ(13U, model.GetCursorPosition());
VerifyAllSelectionTexts(&model, {"su", "refix"});
// Redo 2 times
EXPECT_TRUE(model.Redo()); // p infix 13|ix
EXPECT_STR_EQ("p infix 13ix", model.text());
EXPECT_EQ(10U, model.GetCursorPosition());
EXPECT_FALSE(model.HasSelection());
EXPECT_TRUE(model.Redo()); // p infix 12|ix
EXPECT_STR_EQ("p infix 12ix", model.text());
EXPECT_EQ(10U, model.GetCursorPosition());
EXPECT_FALSE(model.HasSelection());
EXPECT_FALSE(model.Redo());
// Replacement with secondary selections followed by deletion
model.SetText(base::ASCIIToUTF16("prefix infix suffix"), 13);
model.SelectRange({15, 13});
model.SelectRange({1, 6}, false); // p[refix] infix [su]ffix
// Replace
model.InsertChar('1'); // p infix 1|ffix
// Continuous delete (should not merge)
model.Delete(false); // p infix 1|fix
EXPECT_STR_EQ("p infix 1fix", model.text());
EXPECT_FALSE(model.HasSelection());
// Edit history should be
// p[refix] infix [su]ffix -> p infix 1|ffix
// p infix 1|ffix -> p infix 1|fix
// Undo 2 times
EXPECT_TRUE(model.Undo()); // p infix 1|ffix
EXPECT_STR_EQ("p infix 1ffix", model.text());
EXPECT_EQ(9U, model.GetCursorPosition());
EXPECT_FALSE(model.HasSelection());
EXPECT_TRUE(model.Undo()); // p[refix] infix [su]ffix
EXPECT_STR_EQ("prefix infix suffix", model.text());
EXPECT_EQ(13U, model.GetCursorPosition());
VerifyAllSelectionTexts(&model, {"su", "refix"});
// Redo 2 times
EXPECT_TRUE(model.Redo()); // p infix 1|ffix
EXPECT_STR_EQ("p infix 1ffix", model.text());
EXPECT_EQ(9U, model.GetCursorPosition());
EXPECT_FALSE(model.HasSelection());
EXPECT_TRUE(model.Redo()); // p infix 1|fix
EXPECT_STR_EQ("p infix 1fix", model.text());
EXPECT_EQ(9U, model.GetCursorPosition());
EXPECT_FALSE(model.HasSelection());
EXPECT_FALSE(model.Redo());
// Deletion with secondary selections followed by insertion
model.SetText(base::ASCIIToUTF16("prefix infix suffix"), 13);
model.SelectRange({15, 13});
model.SelectRange({1, 6}, false); // p[refix] infix [su]ffix
// Delete
model.Delete(false); // p infix |ffix
// Continuous insert (should not merge)
model.InsertChar('1'); // p infix 1|ffix
EXPECT_STR_EQ("p infix 1ffix", model.text());
EXPECT_FALSE(model.HasSelection());
// Edit history should be
// p[refix] infix [su]ffix -> p infix |ffix
// p infix |ffix -> p infix 1|ffix
// Undo 2 times
EXPECT_TRUE(model.Undo()); // p infix |ffix
EXPECT_STR_EQ("p infix ffix", model.text());
EXPECT_EQ(8U, model.GetCursorPosition());
EXPECT_FALSE(model.HasSelection());
EXPECT_TRUE(model.Undo()); // p[refix] infix [su]ffix
EXPECT_STR_EQ("prefix infix suffix", model.text());
EXPECT_EQ(13U, model.GetCursorPosition());
VerifyAllSelectionTexts(&model, {"su", "refix"});
// Redo 2 times
EXPECT_TRUE(model.Redo()); // p infix |ffix
EXPECT_STR_EQ("p infix ffix", model.text());
EXPECT_EQ(8U, model.GetCursorPosition());
EXPECT_FALSE(model.HasSelection());
EXPECT_TRUE(model.Redo()); // p infix 1|ffix
EXPECT_STR_EQ("p infix 1ffix", model.text());
EXPECT_EQ(9U, model.GetCursorPosition());
EXPECT_FALSE(model.HasSelection());
EXPECT_FALSE(model.Redo());
// Deletion with secondary selections followed by replacement
model.SetText(base::ASCIIToUTF16("prefix infix suffix"), 13);
model.SelectRange({15, 13});
model.SelectRange({1, 6}, false); // p[refix] infix [su]ffix
// Delete
model.Delete(false); // p infix |ffix
// Continuous replacement (should not merge)
model.SelectRange({8, 9}); // p infix [f]fix
model.InsertChar('1'); // p infix 1|fix
EXPECT_STR_EQ("p infix 1fix", model.text());
EXPECT_FALSE(model.HasSelection());
// Edit history should be
// p[refix] infix [su]ffix -> p infix |ffix
// p infix [f]fix -> p infix 1|fix
// Undo 2 times
EXPECT_TRUE(model.Undo()); // p infix [f]fix
EXPECT_STR_EQ("p infix ffix", model.text());
EXPECT_EQ(9U, model.GetCursorPosition());
VerifyAllSelectionTexts(&model, {"f"});
EXPECT_TRUE(model.Undo()); // p[refix] infix [su]ffix
EXPECT_STR_EQ("prefix infix suffix", model.text());
EXPECT_EQ(13U, model.GetCursorPosition());
VerifyAllSelectionTexts(&model, {"su", "refix"});
// Redo 2 times
EXPECT_TRUE(model.Redo()); // p infix |ffix
EXPECT_STR_EQ("p infix ffix", model.text());
EXPECT_EQ(8U, model.GetCursorPosition());
EXPECT_FALSE(model.HasSelection());
EXPECT_TRUE(model.Redo()); // p infix 1|fix
EXPECT_STR_EQ("p infix 1fix", model.text());
EXPECT_EQ(9U, model.GetCursorPosition());
EXPECT_FALSE(model.HasSelection());
EXPECT_FALSE(model.Redo());
// Deletion with secondary selections followed by deletion
model.SetText(base::ASCIIToUTF16("prefix infix suffix"), 13);
model.SelectRange({15, 13});
model.SelectRange({1, 6}, false); // p[refix] infix [su]ffix
// Delete
model.Delete(false); // p infix |ffix
// Continuous delete (should not merge)
model.Delete(false); // p infix |fix
EXPECT_STR_EQ("p infix fix", model.text());
EXPECT_FALSE(model.HasSelection());
// Edit history should be
// p[refix] infix [su]ffix -> p infix |ffix
// p infix |ffix -> p infix |fix
// Undo 2 times
EXPECT_TRUE(model.Undo()); // p infix |ffix
EXPECT_STR_EQ("p infix ffix", model.text());
EXPECT_EQ(8U, model.GetCursorPosition());
EXPECT_FALSE(model.HasSelection());
EXPECT_TRUE(model.Undo()); // p[refix] infix [su]ffix
EXPECT_STR_EQ("prefix infix suffix", model.text());
EXPECT_EQ(13U, model.GetCursorPosition());
VerifyAllSelectionTexts(&model, {"su", "refix"});
// Redo 2 times
EXPECT_TRUE(model.Redo()); // p infix |ffix
EXPECT_STR_EQ("p infix ffix", model.text());
EXPECT_EQ(8U, model.GetCursorPosition());
EXPECT_FALSE(model.HasSelection());
EXPECT_TRUE(model.Redo()); // p infix |fix
EXPECT_STR_EQ("p infix fix", model.text());
EXPECT_EQ(8U, model.GetCursorPosition());
EXPECT_FALSE(model.HasSelection());
EXPECT_FALSE(model.Redo());
}
// Tests that clipboard text with leading, trailing and interspersed tabs
// spaces etc is pasted correctly. Leading and trailing tabs should be
// stripped. Text separated by multiple tabs/spaces should be left alone.
// Text with just tabs and spaces should be pasted as one space.
TEST_F(TextfieldModelTest, Clipboard_WhiteSpaceStringTest) {
// Test 1
// Clipboard text with a leading tab should be pasted with the tab stripped.
ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)
.WriteText(base::ASCIIToUTF16("\tB"));
TextfieldModel model(nullptr);
model.Append(base::ASCIIToUTF16("HELLO WORLD"));
EXPECT_STR_EQ("HELLO WORLD", model.text());
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
EXPECT_EQ(11U, model.GetCursorPosition());
EXPECT_TRUE(model.Paste());
EXPECT_STR_EQ("HELLO WORLDB", model.text());
model.SelectAll(false);
model.DeleteSelection();
EXPECT_STR_EQ("", model.text());
// Test 2
// Clipboard text with multiple leading tabs and spaces should be pasted with
// all tabs and spaces stripped.
ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)
.WriteText(base::ASCIIToUTF16("\t\t\t B"));
model.Append(base::ASCIIToUTF16("HELLO WORLD"));
EXPECT_STR_EQ("HELLO WORLD", model.text());
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
EXPECT_EQ(11U, model.GetCursorPosition());
EXPECT_TRUE(model.Paste());
EXPECT_STR_EQ("HELLO WORLDB", model.text());
model.SelectAll(false);
model.DeleteSelection();
EXPECT_STR_EQ("", model.text());
// Test 3
// Clipboard text with multiple tabs separating the words should be pasted
// as-is.
ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)
.WriteText(base::ASCIIToUTF16("FOO \t\t BAR"));
model.Append(base::ASCIIToUTF16("HELLO WORLD"));
EXPECT_STR_EQ("HELLO WORLD", model.text());
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
EXPECT_EQ(11U, model.GetCursorPosition());
EXPECT_TRUE(model.Paste());
EXPECT_STR_EQ("HELLO WORLDFOO \t\t BAR", model.text());
model.SelectAll(false);
model.DeleteSelection();
EXPECT_STR_EQ("", model.text());
// Test 4
// Clipboard text with multiple leading tabs and multiple tabs separating
// the words should be pasted with the leading tabs stripped.
ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)
.WriteText(base::ASCIIToUTF16("\t\tFOO \t\t BAR"));
EXPECT_TRUE(model.Paste());
EXPECT_STR_EQ("FOO \t\t BAR", model.text());
model.SelectAll(false);
model.DeleteSelection();
EXPECT_STR_EQ("", model.text());
// Test 5
// Clipboard text with multiple trailing tabs should be pasted with all
// trailing tabs stripped.
ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)
.WriteText(base::ASCIIToUTF16("FOO BAR\t\t\t"));
EXPECT_TRUE(model.Paste());
EXPECT_STR_EQ("FOO BAR", model.text());
model.SelectAll(false);
model.DeleteSelection();
EXPECT_STR_EQ("", model.text());
// Test 6
// Clipboard text with only spaces and tabs should be pasted as a single
// space.
ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)
.WriteText(base::ASCIIToUTF16(" \t\t"));
EXPECT_TRUE(model.Paste());
EXPECT_STR_EQ(" ", model.text());
model.SelectAll(false);
model.DeleteSelection();
EXPECT_STR_EQ("", model.text());
// Test 7
// Clipboard text with lots of spaces between words should be pasted as-is.
ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)
.WriteText(base::ASCIIToUTF16("FOO BAR"));
EXPECT_TRUE(model.Paste());
EXPECT_STR_EQ("FOO BAR", model.text());
}
TEST_F(TextfieldModelTest, Transpose) {
const base::string16 ltr = base::ASCIIToUTF16("12");
const base::string16 rtl = base::WideToUTF16(L"\x0634\x0632");
const base::string16 ltr_transposed = base::ASCIIToUTF16("21");
const base::string16 rtl_transposed = base::WideToUTF16(L"\x0632\x0634");
// This is a string with an 'a' between two emojis.
const base::string16 surrogate_pairs({0xD83D, 0xDE07, 'a', 0xD83D, 0xDE0E});
const base::string16 test_strings[] = {ltr, rtl, surrogate_pairs};
struct TestCase {
gfx::Range range;
base::string16 expected_text;
gfx::Range expected_selection;
};
std::vector<TestCase> ltr_tests = {
{gfx::Range(0), ltr, gfx::Range(0)},
{gfx::Range(1), ltr_transposed, gfx::Range(2)},
{gfx::Range(2), ltr_transposed, gfx::Range(2)},
{gfx::Range(0, 2), ltr, gfx::Range(0, 2)}};
std::vector<TestCase> rtl_tests = {
{gfx::Range(0), rtl, gfx::Range(0)},
{gfx::Range(1), rtl_transposed, gfx::Range(2)},
{gfx::Range(2), rtl_transposed, gfx::Range(2)},
{gfx::Range(0, 1), rtl, gfx::Range(0, 1)}};
// Only test at valid grapheme boundaries.
std::vector<TestCase> surrogate_pairs_test = {
{gfx::Range(0), surrogate_pairs, gfx::Range(0)},
{gfx::Range(2), base::string16({'a', 0xD83D, 0xDE07, 0xD83D, 0xDE0E}),
gfx::Range(3)},
{gfx::Range(3), base::string16({0xD83D, 0xDE07, 0xD83D, 0xDE0E, 'a'}),
gfx::Range(5)},
{gfx::Range(5), base::string16({0xD83D, 0xDE07, 0xD83D, 0xDE0E, 'a'}),
gfx::Range(5)},
{gfx::Range(3, 5), surrogate_pairs, gfx::Range(3, 5)}};
std::vector<std::vector<TestCase>> all_tests = {ltr_tests, rtl_tests,
surrogate_pairs_test};
TextfieldModel model(nullptr);
EXPECT_EQ(all_tests.size(), base::size(test_strings));
for (size_t i = 0; i < base::size(test_strings); i++) {
for (size_t j = 0; j < all_tests[i].size(); j++) {
SCOPED_TRACE(testing::Message() << "Testing case " << i << ", " << j
<< " with string " << test_strings[i]);
const TestCase& test_case = all_tests[i][j];
model.SetText(test_strings[i], 0);
model.SelectRange(test_case.range);
EXPECT_EQ(test_case.range, model.render_text()->selection());
model.Transpose();
EXPECT_EQ(test_case.expected_text, model.text());
EXPECT_EQ(test_case.expected_selection, model.render_text()->selection());
}
}
}
TEST_F(TextfieldModelTest, Yank) {
TextfieldModel model(nullptr);
model.SetText(base::ASCIIToUTF16("abcdefgh"), 0);
model.SelectRange(gfx::Range(1, 3));
// Delete selection but don't add to kill buffer.
model.Delete(false);
EXPECT_STR_EQ("adefgh", model.text());
// Since the kill buffer is empty, yank should cause no change.
EXPECT_FALSE(model.Yank());
EXPECT_STR_EQ("adefgh", model.text());
// With a nonempty selection and an empty kill buffer, yank should delete the
// selection.
model.SelectRange(gfx::Range(4, 5));
EXPECT_TRUE(model.Yank());
EXPECT_STR_EQ("adefh", model.text());
// With multiple selections and an empty kill buffer, yank should delete the
// selections.
model.SelectRange(gfx::Range(2, 3));
model.SelectRange(gfx::Range(4, 5), false);
EXPECT_TRUE(model.Yank());
EXPECT_STR_EQ("adf", model.text());
// The kill buffer should remain empty after yanking without a kill buffer.
EXPECT_FALSE(model.Yank());
EXPECT_STR_EQ("adf", model.text());
// Delete selection and add to kill buffer.
model.SelectRange(gfx::Range(0, 1));
model.Delete(true);
EXPECT_STR_EQ("df", model.text());
// Yank twice.
EXPECT_TRUE(model.Yank());
EXPECT_TRUE(model.Yank());
EXPECT_STR_EQ("aadf", model.text());
// Ensure an empty deletion does not modify the kill buffer.
model.SelectRange(gfx::Range(4));
model.Delete(true);
EXPECT_TRUE(model.Yank());
EXPECT_STR_EQ("aadfa", model.text());
// Backspace twice but don't add to kill buffer.
model.Backspace(false);
model.Backspace(false);
EXPECT_STR_EQ("aad", model.text());
// Ensure kill buffer is not modified.
EXPECT_TRUE(model.Yank());
EXPECT_STR_EQ("aada", model.text());
// Backspace twice, each time modifying the kill buffer.
model.Backspace(true);
model.Backspace(true);
EXPECT_STR_EQ("aa", model.text());
// Ensure yanking inserts the modified kill buffer text.
EXPECT_TRUE(model.Yank());
EXPECT_STR_EQ("aad", model.text());
}
TEST_F(TextfieldModelTest, SetCompositionFromExistingText) {
TextfieldModel model(nullptr);
model.SetText(base::ASCIIToUTF16("abcde"), 0);
model.SetCompositionFromExistingText(gfx::Range(0, 1));
EXPECT_TRUE(model.HasCompositionText());
model.SetCompositionFromExistingText(gfx::Range(1, 3));
EXPECT_TRUE(model.HasCompositionText());
ui::CompositionText composition;
composition.text = base::ASCIIToUTF16("123");
model.SetCompositionText(composition);
EXPECT_STR_EQ("a123de", model.text());
}
TEST_F(TextfieldModelTest, SetCompositionFromExistingText_Empty) {
TextfieldModel model(nullptr);
model.SetText(base::ASCIIToUTF16("abc"), 0);
model.SetCompositionFromExistingText(gfx::Range(0, 2));
EXPECT_TRUE(model.HasCompositionText());
model.SetCompositionFromExistingText(gfx::Range(1, 1));
EXPECT_FALSE(model.HasCompositionText());
EXPECT_STR_EQ("abc", model.text());
}
TEST_F(TextfieldModelTest, SetCompositionFromExistingText_OutOfBounds) {
TextfieldModel model(nullptr);
model.SetText(base::string16(), 0);
model.SetCompositionFromExistingText(gfx::Range(0, 2));
EXPECT_FALSE(model.HasCompositionText());
model.SetText(base::ASCIIToUTF16("abc"), 0);
model.SetCompositionFromExistingText(gfx::Range(1, 4));
EXPECT_FALSE(model.HasCompositionText());
}
} // namespace views