Restores the piece-table text editor from agent-work on the merged Containers AVL tree, with the test build no longer gated behind the undefined MODULES_BUILD_TESTS option. The module is registered in the root build.
261 lines
6.2 KiB
C++
261 lines
6.2 KiB
C++
#include "UnitTest++/UnitTest++.h"
|
|
|
|
#include "TextEditor.hpp"
|
|
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <string>
|
|
|
|
using namespace tp;
|
|
|
|
namespace {
|
|
Input inputOf(const char* text) { return { text, Index(std::strlen(text)) }; }
|
|
|
|
std::string contentsOf(const TextEditor& editor) {
|
|
std::string out;
|
|
for (auto iter = editor.begin(); iter != editor.end(); ++iter) {
|
|
out += iter.character();
|
|
}
|
|
return out;
|
|
}
|
|
|
|
Pointer pointerAt(const std::string& text, size_t offset) {
|
|
Pointer out;
|
|
for (size_t idx = 0; idx < offset; idx++) {
|
|
out += text[idx];
|
|
}
|
|
return out;
|
|
}
|
|
|
|
void placeCaret(TextEditor& editor, Pointer at) { editor.setCursorEnd(at); }
|
|
|
|
void select(TextEditor& editor, Pointer from, Pointer to) {
|
|
editor.setCursorEnd(to);
|
|
editor.setCursor(from);
|
|
}
|
|
}
|
|
|
|
SUITE(TextEditor) {
|
|
TEST(EmptyEditorHasNoText) {
|
|
TextEditor editor;
|
|
|
|
CHECK_EQUAL("", contentsOf(editor));
|
|
CHECK_EQUAL(0, editor.mNLines());
|
|
}
|
|
|
|
TEST(InsertIntoEmptyEditor) {
|
|
TextEditor editor;
|
|
|
|
editor.insert(inputOf("abc\nd"));
|
|
|
|
CHECK_EQUAL("abc\nd", contentsOf(editor));
|
|
CHECK(editor.getCursor() == Pointer(1, 1));
|
|
}
|
|
|
|
TEST(OriginalTextIsReadBack) {
|
|
TextEditor editor(inputOf("hello\nworld"));
|
|
|
|
CHECK_EQUAL("hello\nworld", contentsOf(editor));
|
|
CHECK_EQUAL(1, editor.mNLines());
|
|
}
|
|
|
|
TEST(InsertAtStart) {
|
|
TextEditor editor(inputOf("world"));
|
|
|
|
placeCaret(editor, { 0, 0 });
|
|
editor.insert(inputOf("hello "));
|
|
|
|
CHECK_EQUAL("hello world", contentsOf(editor));
|
|
}
|
|
|
|
TEST(InsertAtEnd) {
|
|
TextEditor editor(inputOf("hello"));
|
|
|
|
placeCaret(editor, { 0, 5 });
|
|
editor.insert(inputOf("\nworld"));
|
|
|
|
CHECK_EQUAL("hello\nworld", contentsOf(editor));
|
|
CHECK(editor.getCursor() == Pointer(1, 5));
|
|
}
|
|
|
|
TEST(InsertInTheMiddleSplitsPiece) {
|
|
TextEditor editor(inputOf("hello\nworld"));
|
|
|
|
placeCaret(editor, { 0, 2 });
|
|
editor.insert(inputOf("XY"));
|
|
|
|
CHECK_EQUAL("heXYllo\nworld", contentsOf(editor));
|
|
CHECK(editor.getCursor() == Pointer(0, 4));
|
|
}
|
|
|
|
TEST(InsertOnSecondLine) {
|
|
TextEditor editor(inputOf("hello\nworld"));
|
|
|
|
placeCaret(editor, { 1, 3 });
|
|
editor.insert(inputOf("-"));
|
|
|
|
CHECK_EQUAL("hello\nwor-ld", contentsOf(editor));
|
|
}
|
|
|
|
TEST(TypingContinuesTheSamePiece) {
|
|
TextEditor editor(inputOf("ab"));
|
|
|
|
placeCaret(editor, { 0, 1 });
|
|
editor.insert(inputOf("1"));
|
|
editor.insert(inputOf("2"));
|
|
editor.insert(inputOf("\n3"));
|
|
|
|
CHECK_EQUAL("a12\n3b", contentsOf(editor));
|
|
CHECK_EQUAL(1, editor.mNLines());
|
|
}
|
|
|
|
TEST(CaretIsClampedToLine) {
|
|
TextEditor editor(inputOf("abc\nde"));
|
|
|
|
placeCaret(editor, { 0, 100 });
|
|
CHECK(editor.getCursor() == Pointer(0, 3));
|
|
|
|
placeCaret(editor, { 100, 100 });
|
|
CHECK(editor.getCursor() == Pointer(1, 2));
|
|
}
|
|
|
|
TEST(BackspaceRemovesPreviousCharacter) {
|
|
TextEditor editor(inputOf("hello"));
|
|
|
|
placeCaret(editor, { 0, 3 });
|
|
editor.remove();
|
|
|
|
CHECK_EQUAL("helo", contentsOf(editor));
|
|
CHECK(editor.getCursor() == Pointer(0, 2));
|
|
}
|
|
|
|
TEST(BackspaceJoinsLines) {
|
|
TextEditor editor(inputOf("hello\nworld"));
|
|
|
|
placeCaret(editor, { 1, 0 });
|
|
editor.remove();
|
|
|
|
CHECK_EQUAL("helloworld", contentsOf(editor));
|
|
CHECK(editor.getCursor() == Pointer(0, 5));
|
|
}
|
|
|
|
TEST(BackspaceAtStartDoesNothing) {
|
|
TextEditor editor(inputOf("abc"));
|
|
|
|
placeCaret(editor, { 0, 0 });
|
|
editor.remove();
|
|
|
|
CHECK_EQUAL("abc", contentsOf(editor));
|
|
}
|
|
|
|
TEST(DeleteRemovesNextCharacter) {
|
|
TextEditor editor(inputOf("abc\ndef"));
|
|
|
|
placeCaret(editor, { 0, 0 });
|
|
editor.remove(false, TextEditor::RIGHT);
|
|
CHECK_EQUAL("bc\ndef", contentsOf(editor));
|
|
|
|
placeCaret(editor, { 0, 2 });
|
|
editor.remove(false, TextEditor::RIGHT);
|
|
CHECK_EQUAL("bcdef", contentsOf(editor));
|
|
}
|
|
|
|
TEST(DeleteAtEndDoesNothing) {
|
|
TextEditor editor(inputOf("abc"));
|
|
|
|
placeCaret(editor, { 0, 3 });
|
|
editor.remove(false, TextEditor::RIGHT);
|
|
|
|
CHECK_EQUAL("abc", contentsOf(editor));
|
|
}
|
|
|
|
TEST(SelectionRemovalAcrossPieces) {
|
|
TextEditor editor(inputOf("0123456789"));
|
|
|
|
placeCaret(editor, { 0, 5 });
|
|
editor.insert(inputOf("abc"));
|
|
CHECK_EQUAL("01234abc56789", contentsOf(editor));
|
|
|
|
select(editor, { 0, 3 }, { 0, 10 });
|
|
editor.remove(true);
|
|
|
|
CHECK_EQUAL("012789", contentsOf(editor));
|
|
CHECK(editor.getCursor() == Pointer(0, 3));
|
|
}
|
|
|
|
TEST(InsertReplacesSelection) {
|
|
TextEditor editor(inputOf("one two three"));
|
|
|
|
select(editor, { 0, 4 }, { 0, 7 });
|
|
editor.insert(inputOf("2"));
|
|
|
|
CHECK_EQUAL("one 2 three", contentsOf(editor));
|
|
}
|
|
|
|
TEST(RemoveEverything) {
|
|
TextEditor editor(inputOf("ab\ncd"));
|
|
|
|
select(editor, { 0, 0 }, { 1, 2 });
|
|
editor.remove(true);
|
|
|
|
CHECK_EQUAL("", contentsOf(editor));
|
|
|
|
editor.insert(inputOf("x"));
|
|
CHECK_EQUAL("x", contentsOf(editor));
|
|
}
|
|
|
|
TEST(ManyInsertionsKeepOrder) {
|
|
TextEditor editor(inputOf("."));
|
|
std::string expected = ".";
|
|
|
|
for (int idx = 0; idx < 300; idx++) {
|
|
const char letter[] = { char('a' + idx % 26), 0 };
|
|
const size_t offset = (idx * 7) % (expected.size() + 1);
|
|
|
|
placeCaret(editor, pointerAt(expected, offset));
|
|
editor.insert(inputOf(letter));
|
|
expected.insert(expected.begin() + offset, letter[0]);
|
|
}
|
|
|
|
CHECK_EQUAL(expected, contentsOf(editor));
|
|
}
|
|
|
|
TEST(RandomEditsMatchReferenceString) {
|
|
const char* original = "first line\nsecond line\nthird";
|
|
TextEditor editor(inputOf(original));
|
|
std::string expected = original;
|
|
const char* inserts[] = { "x", "yz", "\n", "a\nb", "long insertion" };
|
|
|
|
std::srand(12345);
|
|
|
|
for (int step = 0; step < 2000; step++) {
|
|
const size_t offset = std::rand() % (expected.size() + 1);
|
|
const int action = std::rand() % 3;
|
|
|
|
if (action == 0 || expected.empty()) {
|
|
const char* text = inserts[std::rand() % 5];
|
|
placeCaret(editor, pointerAt(expected, offset));
|
|
editor.insert(inputOf(text));
|
|
expected.insert(offset, text);
|
|
} else if (action == 1 && offset > 0) {
|
|
placeCaret(editor, pointerAt(expected, offset));
|
|
editor.remove();
|
|
expected.erase(offset - 1, 1);
|
|
} else {
|
|
const size_t end = offset + std::rand() % 5;
|
|
const size_t clampedEnd = end > expected.size() ? expected.size() : end;
|
|
select(editor, pointerAt(expected, offset), pointerAt(expected, clampedEnd));
|
|
editor.remove(true);
|
|
expected.erase(offset, clampedEnd - offset);
|
|
}
|
|
|
|
if (contentsOf(editor) != expected) {
|
|
CHECK_EQUAL(expected, contentsOf(editor));
|
|
CHECK_EQUAL(-1, step);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int main() { return UnitTest::RunAllTests(); }
|