#include "nocal/domain/date.hpp" #include "nocal/tui/Screen.hpp" #include "nocal/tui/TuiApp.hpp" #include #include #include #include #include #include #include namespace { int failures = 0; void check(bool condition, const char* message) { if (!condition) { ++failures; std::cerr << "FAIL: " << message << '\n'; } } void test_full_month_render() { using namespace std::chrono; const nocal::tui::ScreenState state{ .visible_month = year{2026} / July, .selected_day = year{2026} / July / day{17}, .today = year{2026} / July / day{17}, .width = 100, .height = 30, .show_help = false, .ansi = false, .focused_event_id = std::nullopt, .show_event_details = false, .confirm_delete = false, .notification = {}, .notification_is_error = false, }; const std::vector items{ {.id = "1", .title = "Design review", .day = year{2026} / July / day{17}, .time_of_day = hours{9} + minutes{30}, .all_day = false, .end_time_of_day = std::nullopt, .start_day = {}, .end_day = {}, .location = {}, .description = {}, .detail_title = {}, .detail_all_day = false, .detail_start_time_of_day = std::nullopt, .segment = nocal::tui::ItemSegment::single, .recurring = false, .recurrence_summary = {}, .source_time_zone = {}}, {.id = "2", .title = "Release day", .day = year{2026} / July / day{17}, .time_of_day = std::nullopt, .all_day = true, .end_time_of_day = std::nullopt, .start_day = {}, .end_day = {}, .location = {}, .description = {}, .detail_title = {}, .detail_all_day = true, .detail_start_time_of_day = std::nullopt, .segment = nocal::tui::ItemSegment::single, .recurring = false, .recurrence_summary = {}, .source_time_zone = {}}, }; const auto frame = nocal::tui::render_month(items, state); check(frame.find("Design") != std::string::npos, "appointment appears in its day cell"); check(frame.find("Release") != std::string::npos, "all-day appointment appears in its day cell"); check(frame.find("July 2026") != std::string::npos, "month title is rendered"); check(std::count(frame.begin(), frame.end(), '\n') == 29, "renderer fills the requested height deterministically"); check(frame.find("\x1b[") == std::string::npos, "ANSI can be disabled"); } void test_compact_and_input() { using namespace std::chrono; const nocal::tui::ScreenState state{ .visible_month = year{2026} / July, .selected_day = year{2026} / July / day{17}, .today = year{2026} / July / day{17}, .width = 30, .height = 12, .show_help = false, .ansi = false, .focused_event_id = std::nullopt, .show_event_details = false, .confirm_delete = false, .notification = {}, .notification_is_error = false, }; const auto frame = nocal::tui::render_month(std::span{}, state); check(frame.find("July 2026") != std::string::npos, "compact mode keeps month context"); check(nocal::tui::decode_key("\x1b[6~") == nocal::tui::Action::next_month, "PageDown is decoded"); check(nocal::tui::decode_key("k") == nocal::tui::Action::up, "Vim movement is decoded"); } void test_navigation() { using namespace std::chrono; std::vector events; nocal::tui::TuiApp app{events, -1, -1}; const auto initial = app.state().selected_day; app.dispatch(nocal::tui::Action::right); check(sys_days{app.state().selected_day} == sys_days{initial} + days{1}, "right moves by one civil day"); app.dispatch(nocal::tui::Action::down); check(sys_days{app.state().selected_day} == sys_days{initial} + days{8}, "down moves by one week"); } } // namespace int main() { test_full_month_render(); test_compact_and_input(); test_navigation(); if (failures != 0) { std::cerr << failures << " TUI test(s) failed\n"; return EXIT_FAILURE; } std::cout << "TUI tests passed\n"; return EXIT_SUCCESS; }