// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/blob/master/LICENSE.md #include #include #include TEST_CASE("Text BlockCursor Ctor/Dtor ") { using namespace gui; SECTION("default is npos") { auto cursor = BlockCursor(nullptr, 0, 0, nullptr); REQUIRE(cursor.atBegin() == false); REQUIRE(cursor.atEnd() == false); REQUIRE(cursor.getPosition() == text::npos); REQUIRE(cursor.getBlockNumber() == text::npos); REQUIRE(cursor.operator bool() == false); } SECTION("default with empty document - is npos") { auto [empty_doc, font] = mockup::buildEmptyTestDocument(); auto cursor = BlockCursor(&empty_doc, 0, 0, nullptr); REQUIRE(cursor.atBegin() == false); REQUIRE(cursor.atEnd() == false); REQUIRE(cursor.getPosition() == text::npos); REQUIRE(cursor.getBlockNumber() == text::npos); REQUIRE(cursor.operator bool() == false); } SECTION("default with non empty document, from 0,0") { auto [doc, font] = mockup::buildTestDocument(); auto cursor = BlockCursor(&doc, 0, 0, nullptr); REQUIRE(cursor.atBegin() == true); REQUIRE(cursor.atEnd() == false); REQUIRE(cursor.getPosition() != text::npos); REQUIRE(cursor.getBlockNumber() != text::npos); REQUIRE(cursor.operator bool() == true); } SECTION("default with non empty document, form end -1") { REQUIRE(text::npos == std::numeric_limits().max()); auto [doc, font] = mockup::buildTestDocument(); auto cursor = BlockCursor(&doc, text::npos - 1, text::npos - 1, nullptr); REQUIRE(cursor.atBegin() == false); REQUIRE(cursor.atEnd() == true); REQUIRE(cursor.getPosition() != text::npos); REQUIRE(cursor.getBlockNumber() != text::npos); REQUIRE(cursor.operator bool() == true); } SECTION("default with non empty document, in the middle") { auto [doc, font] = mockup::buildTestDocument(); auto block_nr = doc.getBlocks().size() / 2; REQUIRE(block_nr > 0); auto block_len = std::next(doc.getBlocks().begin(), block_nr)->length(); REQUIRE(block_len > 0); auto cursor = BlockCursor(&doc, block_len / 2, block_nr, nullptr); REQUIRE(cursor.atBegin() == false); REQUIRE(cursor.atEnd() == false); REQUIRE(cursor.getPosition() != text::npos); REQUIRE(cursor.getBlockNumber() != text::npos); REQUIRE(cursor.operator bool() == true); } SECTION("default with non empty document, in the end of doc") { auto [doc, font] = mockup::buildTestDocument(); auto block_nr = doc.getBlocks().size() - 1; REQUIRE(block_nr > 0); auto block_len = std::next(doc.getBlocks().begin(), block_nr)->length(); REQUIRE(block_len == 0); auto cursor = BlockCursor(&doc, block_len, block_nr, nullptr); REQUIRE(cursor.atBegin() == false); REQUIRE(cursor.atEnd() == true); REQUIRE(cursor.getPosition() != text::npos); REQUIRE(cursor.getBlockNumber() != text::npos); REQUIRE(cursor.operator bool() == true); } } TEST_CASE("TextDocument <-> BlockCursor fencing tests") { using namespace gui; auto [document, testfont] = mockup::buildTestDocument(); REQUIRE(testfont != nullptr); auto &blocks = document.getBlocks(); auto offset = blocks.front().length(); SECTION("begin - last char") { auto cursor = document.getBlockCursor(offset - 1); REQUIRE(cursor.getPosition() != text::npos); auto block_text = std::string(blocks.front().getText()); // cant do so on our utf.. auto test_text = std::string(block_text.begin() + offset - 1, block_text.end()); auto text = document.getText(cursor); REQUIRE(text == test_text); REQUIRE(cursor->getFormat()->getFont() == testfont); } SECTION("true fence hit - just next block") { auto cursor = document.getBlockCursor(offset); REQUIRE(cursor.getPosition() != text::npos); auto text = document.getText(cursor); REQUIRE(text == std::string(std::next(blocks.begin())->getText())); REQUIRE(cursor->getFormat()->getFont() == testfont); } SECTION("1st block + 1 -> 2nd block - 1st char") { auto cursor = document.getBlockCursor(offset + 1); REQUIRE(cursor.getPosition() != text::npos); auto block_text = std::string(std::next(blocks.begin())->getText()); auto test_text = std::string(block_text.begin() + 1, block_text.end()); auto text = document.getText(cursor); REQUIRE(text == test_text); REQUIRE(cursor->getFormat()->getFont() == testfont); } SECTION("fence fence hit - fence of textBlocks and block with newline") { auto cursor = document.getBlockCursor(document.getText().length()); REQUIRE(cursor.getPosition() == 0); auto text = document.getText(cursor); REQUIRE(text == ""); } SECTION("fence fence hit - fence of textBlocks and block without newline") { auto [document, testfont] = mockup::buildTestDocument(gui::TextBlock::End::None); auto cursor = document.getBlockCursor(document.getText().length()); REQUIRE(cursor.getPosition() == text::npos); auto text = document.getText(cursor); REQUIRE(text == ""); } } TEST_CASE("TextDocument <-> BlockCursor operators: +, ++, -, -- tests") { using namespace gui; /// tests below could probably be generated with some parametrized section testing suite option as all the tests are /// super similar SECTION("no document - (in/de)crementing npos does nothin") { auto cursor = BlockCursor(nullptr, 0, 0, nullptr); ++cursor; REQUIRE(cursor.atBegin() == false); REQUIRE(cursor.atEnd() == false); REQUIRE(cursor.getPosition() == text::npos); REQUIRE(cursor.getBlockNumber() == text::npos); REQUIRE(cursor.operator bool() == false); --cursor; REQUIRE(cursor.atBegin() == false); REQUIRE(cursor.atEnd() == false); REQUIRE(cursor.getPosition() == text::npos); REQUIRE(cursor.getBlockNumber() == text::npos); REQUIRE(cursor.operator bool() == false); } SECTION("empty document - (in/de)crementing npos does nothing") { auto [empty_doc, font] = mockup::buildEmptyTestDocument(); auto cursor = BlockCursor(&empty_doc, 0, 0, nullptr); ++cursor; REQUIRE(cursor.atBegin() == false); REQUIRE(cursor.atEnd() == false); REQUIRE(cursor.getPosition() == text::npos); REQUIRE(cursor.getBlockNumber() == text::npos); REQUIRE(cursor.operator bool() == false); --cursor; REQUIRE(cursor.atBegin() == false); REQUIRE(cursor.atEnd() == false); REQUIRE(cursor.getPosition() == text::npos); REQUIRE(cursor.getBlockNumber() == text::npos); REQUIRE(cursor.operator bool() == false); } SECTION("begin of block + && ++ till the end of block && -- till the begining") { auto super_large_offset = 1000; auto [doc, font] = mockup::buildTestDocument(); auto cursor = BlockCursor(&doc, 0, 0, nullptr); REQUIRE(super_large_offset > static_cast(doc.getText().length())); ++cursor; REQUIRE(cursor.atBegin() == false); REQUIRE(cursor.atEnd() == false); REQUIRE(cursor.getPosition() != text::npos); REQUIRE(cursor.getBlockNumber() != text::npos); REQUIRE(cursor.operator bool() == true); cursor += super_large_offset; REQUIRE(cursor.atBegin() == false); REQUIRE(cursor.atEnd() == true); REQUIRE(cursor.getPosition() != text::npos); REQUIRE(cursor.getBlockNumber() != text::npos); REQUIRE(cursor.operator bool() == true); cursor -= super_large_offset; REQUIRE(cursor.atBegin() == true); REQUIRE(cursor.atEnd() == false); REQUIRE(cursor.getPosition() != text::npos); REQUIRE(cursor.getBlockNumber() != text::npos); REQUIRE(cursor.operator bool() == true); } SECTION("begin of block - && --") { auto super_large_offset = 1000; auto [doc, font] = mockup::buildTestDocument(); auto cursor = BlockCursor(&doc, 0, 0, nullptr); REQUIRE(super_large_offset > static_cast(doc.getText().length())); REQUIRE(cursor.atBegin() == true); REQUIRE(cursor.atEnd() == false); REQUIRE(cursor.getPosition() != text::npos); REQUIRE(cursor.getBlockNumber() != text::npos); REQUIRE(cursor.operator bool() == true); --cursor; REQUIRE(cursor.atBegin() == true); REQUIRE(cursor.atEnd() == false); REQUIRE(cursor.getPosition() != text::npos); REQUIRE(cursor.getBlockNumber() != text::npos); REQUIRE(cursor.operator bool() == true); cursor -= super_large_offset; REQUIRE(cursor.atBegin() == true); REQUIRE(cursor.atEnd() == false); REQUIRE(cursor.getPosition() != text::npos); REQUIRE(cursor.getBlockNumber() != text::npos); REQUIRE(cursor.operator bool() == true); } SECTION("end document + && ++") { auto [doc, font] = mockup::buildTestDocument(); auto end_position = doc.getText().length(); auto end_block = doc.getBlocks().back().length() - 1; auto cursor = BlockCursor(&doc, end_position, end_block, nullptr); auto any_offset_really = 1000; REQUIRE(cursor.atBegin() == false); REQUIRE(cursor.atEnd() == true); REQUIRE(cursor.getPosition() != text::npos); REQUIRE(cursor.getBlockNumber() != text::npos); REQUIRE(cursor.operator bool() == true); ++cursor; REQUIRE(cursor.atBegin() == false); REQUIRE(cursor.atEnd() == true); REQUIRE(cursor.getPosition() != text::npos); REQUIRE(cursor.getBlockNumber() != text::npos); REQUIRE(cursor.operator bool() == true); cursor += any_offset_really; REQUIRE(cursor.atBegin() == false); REQUIRE(cursor.atEnd() == true); REQUIRE(cursor.getPosition() != text::npos); REQUIRE(cursor.getBlockNumber() != text::npos); REQUIRE(cursor.operator bool() == true); } SECTION("block (in/de)cremention") { auto [doc, font] = mockup::buildTestDocument(); auto cursor = BlockCursor(&doc, 0, 0, nullptr); unsigned int first_block_size_offset = doc.getBlocks().front().length() - 1; unsigned int check_offset = 2; unsigned int block_move_penalty = 1; // +1 because with first_block_size_offset we are still in 1st block REQUIRE(first_block_size_offset > check_offset); REQUIRE(first_block_size_offset < doc.getText().length()); REQUIRE(cursor.atBegin() == true); REQUIRE(cursor.atEnd() == false); REQUIRE(cursor.getPosition() != text::npos); REQUIRE(cursor.getBlockNumber() != text::npos); REQUIRE(cursor.getBlockNumber() == 0); REQUIRE(cursor.operator bool() == true); cursor += first_block_size_offset + block_move_penalty; REQUIRE(cursor.getBlockNumber() == 1); REQUIRE(cursor.getPosition() == 0); cursor -= check_offset; REQUIRE(cursor.getBlockNumber() == 0); REQUIRE(cursor.getPosition() == first_block_size_offset - check_offset + block_move_penalty); for (unsigned int i = 0; i < check_offset; ++i) { ++cursor; } REQUIRE(cursor.getBlockNumber() == 1); REQUIRE(cursor.getPosition() == 0); } } TEST_CASE("add Char to empty") { using namespace gui; { auto [doc, font] = mockup::buildEmptyTestDocument(); auto cursor = BlockCursor(&doc, 0, 0, nullptr); cursor.addChar('a'); REQUIRE(doc.getText() == "a"); REQUIRE(doc.getText().length() == 1); } } TEST_CASE("add Char") { using namespace gui; auto [doc, font] = mockup::buildOnelineTestDocument("one line"); auto cursor = BlockCursor(&doc, 0, 0, nullptr); SECTION(" to second block -in the middle") { auto blocks = doc.getBlocks(); auto test_text = blocks.front().getText(); test_text.insertCode('x', test_text.length() / 2); cursor += blocks.front().length() / 2; cursor.addChar('x'); REQUIRE(doc.getBlocks().front().getText() == test_text); } SECTION("at the end of block") { auto blocks = doc.getBlocks(); auto test_text = blocks.front().getText(); test_text.insertCode('x', test_text.length()); cursor += blocks.front().length(); cursor.addChar('x'); REQUIRE(doc.getBlocks().front().getText() == test_text); } } TEST_CASE("remove Char in empty") { using namespace gui; auto [doc, font] = mockup::buildEmptyTestDocument(); auto cursor = BlockCursor(&doc, 0, 0, nullptr); cursor.removeChar(); REQUIRE(doc.getText() == ""); REQUIRE(doc.getText().length() == 0); } TEST_CASE("remove Char") { using namespace gui; auto [doc, font] = mockup::buildTestDocument(); auto cursor = BlockCursor(&doc, 0, 0, nullptr); SECTION("text remove") { UTF8 text = "a"; text.removeChar(0); REQUIRE(text == ""); } SECTION("remove first char") { auto no_of_chars_prior_to_remove = doc.getBlocks().begin()->length(); ++cursor; // move cursor to point on first character REQUIRE(cursor.removeChar()); REQUIRE(doc.getBlocks().begin()->length() == no_of_chars_prior_to_remove - 1); } SECTION("remove last char") { auto no_of_chars_prior_to_remove = doc.getBlocks().begin()->length(); // move to end of first block cursor += no_of_chars_prior_to_remove - 1; REQUIRE(cursor.removeChar()); REQUIRE(doc.getBlocks().begin()->length() == no_of_chars_prior_to_remove - 1); } SECTION("empty whole first block & remove first block ( with newline)") { auto block_count_start = doc.getBlocks().size(); auto how_many = doc.getBlocks().begin()->length(); // move to end of first block cursor += how_many; for (unsigned int i = 0; i <= how_many + 1; ++i) { REQUIRE(cursor.removeChar()); --cursor; } REQUIRE(block_count_start > 0); REQUIRE(doc.getBlocks().size() < block_count_start); REQUIRE(cursor.getBlockNumber() == 0); } SECTION("empty whole second block & remove second block") { auto start_block_nr = cursor.getBlockNumber(); auto block_count_start = doc.getBlocks().size(); auto how_many = std::next(doc.getBlocks().begin())->length(); // move to end of second block +1 is for newline cursor += doc.getBlocks().front().length() + how_many - 1; for (unsigned int i = 0; i <= how_many + 1; ++i) { REQUIRE(cursor.removeChar()); --cursor; } REQUIRE(block_count_start > 0); REQUIRE(doc.getBlocks().size() < block_count_start); REQUIRE(start_block_nr == cursor.getBlockNumber()); } SECTION("block with just newline - remove newline block") { auto doc = mockup::buildJustNewlineTestDocument(); auto cur = BlockCursor(&doc, 0, 0, nullptr); REQUIRE(!doc.isEmpty()); cur.removeChar(); REQUIRE(doc.isEmpty()); } } TEST_CASE("add newline") { using namespace gui; std::string text = "some long text"; auto [doc, font] = mockup::buildOnelineTestDocument(text); auto cursor = BlockCursor(&doc, 0, 0, nullptr); auto no_of_blocks = doc.getBlocks().size(); cursor += text.find(" "); cursor.addChar('\n'); REQUIRE(no_of_blocks < doc.getBlocks().size()); } TEST_CASE("add newline at the end") { using namespace gui; std::string text = "some long text."; auto [doc, font] = mockup::buildOnelineTestDocument(text); auto cursor = BlockCursor(&doc, 0, 0, nullptr); cursor += text.size(); cursor.addChar('\n'); ++cursor; cursor.addChar('s'); REQUIRE(doc.getBlocks().size() == 2); REQUIRE(doc.getBlocks().front().getText() == "some long text.\n"); REQUIRE(doc.getBlocks().back().getText() == "s"); } TEST_CASE("atEOL()") { using namespace gui; std::string text = "some long text"; auto [doc, font] = mockup::buildOnelineTestDocument(text); auto cursor = BlockCursor(&doc, 0, 0, nullptr); REQUIRE(cursor.atEnd() == false); cursor += text.length(); REQUIRE(cursor.atEnd() == true); cursor += text.length(); REQUIRE(cursor.atEnd() == true); } TEST_CASE("operator-> for Text block with text") { using namespace gui; std::string text = "some long text"; auto [doc, font] = mockup::buildOnelineTestDocument(text); auto cursor = BlockCursor(&doc, 0, 0, nullptr); // one block text len will be same for len of whole doc REQUIRE(cursor->length() == doc.getText().length()); } TEST_CASE("operator* for TextBlock") { using namespace gui; std::string text = "some long text"; auto [doc, font] = mockup::buildOnelineTestDocument(text); auto cursor = BlockCursor(&doc, 0, 0, nullptr); REQUIRE((*cursor).length() == doc.getText().length()); } TEST_CASE("check sentence beginning") { using namespace gui; auto [doc, font] = mockup::buildTestDocument(); auto cursor = BlockCursor(&doc, 0, 0, nullptr); auto addStringToBlock = [](const std::string &input, gui::BlockCursor &cursor) { for (unsigned int i = 0; i < input.length(); ++i) { cursor.addChar(input[i]); ++cursor; } }; auto removeNSignsInBlock = [](unsigned int n, gui::BlockCursor &cursor) { for (unsigned int i = 0; i < n; ++i) { cursor.removeChar(); --cursor; } }; // Empty document sentence beginning. REQUIRE(cursor.checkSentenceBeginning()); // Add empty spaces at document beginning - still sentence beginning addStringToBlock(" ", cursor); REQUIRE(cursor.checkSentenceBeginning()); // Add one sign and check sentence beginning addStringToBlock("P", cursor); REQUIRE(!cursor.checkSentenceBeginning()); // Remove one sign to check sentence beginning removeNSignsInBlock(1, cursor); REQUIRE(cursor.checkSentenceBeginning()); // Add string ending with dot addStringToBlock("Test.", cursor); // No white space so not sentence beginning REQUIRE(!cursor.checkSentenceBeginning()); // Add empty space. New sentence should be detected addStringToBlock(" ", cursor); REQUIRE(cursor.checkSentenceBeginning()); // Remove space. removeNSignsInBlock(1, cursor); REQUIRE(!cursor.checkSentenceBeginning()); // Add string ending with exclamation mark and white space addStringToBlock("Test! ", cursor); REQUIRE(cursor.checkSentenceBeginning()); // Add extra sign addStringToBlock("P", cursor); REQUIRE(!cursor.checkSentenceBeginning()); // Add string ending with question mark and white space addStringToBlock("Test? ", cursor); REQUIRE(cursor.checkSentenceBeginning()); // Check number of blocks REQUIRE(cursor.getBlockNumber() == 0); // Add newline - new block and new sentence addStringToBlock("\n", cursor); // Check number of blocks REQUIRE(cursor.getBlockNumber() == 1); REQUIRE(cursor.checkSentenceBeginning()); // Add string ending with dot addStringToBlock("Test.", cursor); // No white space so not sentence beginning REQUIRE(!cursor.checkSentenceBeginning()); }