Modules/TextEditor/tests/TextEditorTests.cpp
agent e045603df8
Some checks failed
Windows / build (pull_request) Has been cancelled
CMake on a single platform / build (pull_request) Has been cancelled
CMake / build (pull_request) Has been cancelled
TextEditor: piece-tree editor on the current AVL tree
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.
2026-10-29 13:06:36 +03:00

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(); }