#include #include #include #include #include "text_pager.h" using namespace roro::files; void setUp() {} void tearDown() {} static int reads = 0; static size_t biggestRead = 0; static TextPager pagerFor(const std::string& text, int cols, int rows) { return TextPager( [&text](uint32_t offset, uint8_t* into, size_t len) { reads++; biggestRead = std::max(biggestRead, len); if (offset >= text.size()) return static_cast(0); size_t n = std::min(len, text.size() - offset); std::memcpy(into, text.data() + offset, n); return n; }, static_cast(text.size()), cols, rows); } void test_lines_and_wrapping() { std::string text = "one\r\ntwo words that are too long\n\nlast"; TextPager p = pagerFor(text, 10, 8); auto lines = p.lines(); TEST_ASSERT_EQUAL_size_t(6, lines.size()); TEST_ASSERT_EQUAL_STRING("one", lines[0].c_str()); // the \r is dropped TEST_ASSERT_EQUAL_STRING("two words", lines[1].c_str()); TEST_ASSERT_EQUAL_STRING("that are", lines[2].c_str()); TEST_ASSERT_EQUAL_STRING("too long", lines[3].c_str()); TEST_ASSERT_EQUAL_STRING("", lines[4].c_str()); TEST_ASSERT_EQUAL_STRING("last", lines[5].c_str()); TEST_ASSERT_TRUE(p.atEnd()); } void test_a_word_too_long_is_cut_and_a_space_at_the_edge_is_eaten() { std::string text = "abcdefghijklmno\nabcde fghij klm"; TextPager p = pagerFor(text, 5, 8); auto lines = p.lines(); TEST_ASSERT_EQUAL_STRING("abcde", lines[0].c_str()); TEST_ASSERT_EQUAL_STRING("fghij", lines[1].c_str()); TEST_ASSERT_EQUAL_STRING("klmno", lines[2].c_str()); TEST_ASSERT_EQUAL_STRING("abcde", lines[3].c_str()); TEST_ASSERT_EQUAL_STRING("fghij", lines[4].c_str()); TEST_ASSERT_EQUAL_STRING("klm", lines[5].c_str()); } void test_accents_count_as_one_column_and_tabs_show_as_spaces() { std::string text = "\xC3\xA9t\xC3\xA9 ici\n\ta\x01"; TextPager p = pagerFor(text, 3, 4); auto lines = p.lines(); TEST_ASSERT_EQUAL_STRING("\xC3\xA9t\xC3\xA9", lines[0].c_str()); TEST_ASSERT_EQUAL_STRING("ici", lines[1].c_str()); TEST_ASSERT_EQUAL_STRING(" a.", lines[2].c_str()); } // Scrolling down to the end and back up passes through the same line starts. void test_up_undoes_down() { std::string text; for (int i = 0; i < 300; i++) { text += "line " + std::to_string(i) + " with some words to wrap around the edge of the screen"; text += i % 7 == 0 ? "\n\n" : "\n"; } text += "a paragraph without any newline for a long while "; for (int i = 0; i < 200; i++) text += "word" + std::to_string(i) + " "; text += "\nthe end"; TextPager p = pagerFor(text, 38, 9); std::vector tops; for (int guard = 0; guard < 5000 && !p.atEnd(); guard++) { tops.push_back(p.top()); p.down(); } TEST_ASSERT_TRUE(p.atEnd()); TEST_ASSERT_TRUE(tops.size() > 500); TEST_ASSERT_EQUAL_STRING("the end", p.lines().back().c_str()); // Inside the long paragraph (further than the look-back from its start) lines may wrap from // another place on the way up. Before it, every line start met on the way down is met again. size_t paragraph = text.find("a paragraph without"); size_t expected = 0, met = 0; for (uint32_t t : tops) expected += t <= paragraph; for (int guard = 0; guard < 5000 && p.top() > 0; guard++) { p.up(); if (p.top() > paragraph) continue; TEST_ASSERT_TRUE(std::binary_search(tops.begin(), tops.end(), p.top())); met++; } TEST_ASSERT_EQUAL_size_t(expected, met); TEST_ASSERT_EQUAL_UINT32(0, p.top()); p.up(); TEST_ASSERT_EQUAL_UINT32(0, p.top()); } void test_to_end_shows_the_last_lines() { std::string text; for (int i = 0; i < 100; i++) text += "20:0" + std::to_string(i % 10) + " message " + std::to_string(i) + "\n"; TextPager p = pagerFor(text, 38, 9); p.toEnd(); auto lines = p.lines(); TEST_ASSERT_EQUAL_size_t(9, lines.size()); TEST_ASSERT_EQUAL_STRING("20:09 message 99", lines[8].c_str()); TEST_ASSERT_EQUAL_STRING("20:01 message 91", lines[0].c_str()); TEST_ASSERT_TRUE(p.atEnd()); p.down(); // nowhere to go TEST_ASSERT_EQUAL_STRING("20:01 message 91", p.lines()[0].c_str()); TEST_ASSERT_TRUE(p.percent() > 80); } void test_a_megabyte_is_read_a_kilobyte_at_a_time() { std::string text; while (text.size() < 1024 * 1024) text += "2026-10-05 20:00:00 says something here\n"; reads = 0; biggestRead = 0; TextPager p = pagerFor(text, 38, 9); p.toEnd(); TEST_ASSERT_EQUAL_size_t(9, p.lines().size()); p.up(20); p.down(5); TEST_ASSERT_TRUE(biggestRead <= 1024); TEST_ASSERT_TRUE(reads < 20); p.toStart(); TEST_ASSERT_EQUAL_STRING("2026-10-05 20:00:00 says", p.lines()[0].c_str()); } void test_empty_and_short_files() { std::string empty; TextPager p = pagerFor(empty, 38, 9); TEST_ASSERT_EQUAL_size_t(0, p.lines().size()); p.down(); p.up(); p.toEnd(); TEST_ASSERT_TRUE(p.atEnd()); std::string one = "no newline"; TextPager q = pagerFor(one, 38, 9); q.toEnd(); TEST_ASSERT_EQUAL_STRING("no newline", q.lines()[0].c_str()); } int main() { UNITY_BEGIN(); RUN_TEST(test_lines_and_wrapping); RUN_TEST(test_a_word_too_long_is_cut_and_a_space_at_the_edge_is_eaten); RUN_TEST(test_accents_count_as_one_column_and_tabs_show_as_spaces); RUN_TEST(test_up_undoes_down); RUN_TEST(test_to_end_shows_the_last_lines); RUN_TEST(test_a_megabyte_is_read_a_kilobyte_at_a_time); RUN_TEST(test_empty_and_short_files); return UNITY_END(); }