#include "UnitTest++/UnitTest++.h" #include "TextEditor.hpp" #include #include #include 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(); }