174 lines
6.4 KiB
C++
174 lines
6.4 KiB
C++
#include "nocal/domain/date.hpp"
|
|
#include "nocal/tui/Screen.hpp"
|
|
#include "nocal/tui/TuiApp.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cstdlib>
|
|
#include <iostream>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
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,
|
|
.search_prompt = false,
|
|
.show_search_results = false,
|
|
.search_query = {},
|
|
.search_results = {},
|
|
.search_result_index = 0,
|
|
.notification = {},
|
|
.notification_is_error = false,
|
|
};
|
|
const std::vector<nocal::tui::CalendarItem> 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,
|
|
.search_prompt = false,
|
|
.show_search_results = false,
|
|
.search_query = {},
|
|
.search_results = {},
|
|
.search_result_index = 0,
|
|
.notification = {},
|
|
.notification_is_error = false,
|
|
};
|
|
const auto frame = nocal::tui::render_month(std::span<const nocal::tui::CalendarItem>{}, 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<nocal::Event> 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");
|
|
}
|
|
|
|
void test_search_frames()
|
|
{
|
|
using namespace std::chrono;
|
|
auto state = nocal::tui::ScreenState{};
|
|
state.visible_month = year{2026} / July;
|
|
state.selected_day = year{2026} / July / day{17};
|
|
state.today = state.selected_day;
|
|
state.width = 72;
|
|
state.height = 12;
|
|
state.ansi = false;
|
|
state.search_prompt = true;
|
|
state.search_query = "team";
|
|
auto frame = nocal::tui::render_month(
|
|
std::span<const nocal::tui::CalendarItem>{}, state);
|
|
check(frame.find("SEARCH APPOINTMENTS") != std::string::npos &&
|
|
frame.find("/ team_") != std::string::npos,
|
|
"search prompt renders its query and context");
|
|
check(std::count(frame.begin(), frame.end(), '\n') == 11,
|
|
"search prompt fills the requested height");
|
|
|
|
state.search_prompt = false;
|
|
state.show_search_results = true;
|
|
state.search_results = {
|
|
{.focus_id = "one", .title = "Team planning",
|
|
.day = year{2026} / July / day{18}, .time_of_day = hours{9},
|
|
.all_day = false, .location = "Studio", .recurring = false},
|
|
{.focus_id = "two", .title = "Team holiday",
|
|
.day = year{2026} / July / day{19}, .time_of_day = std::nullopt,
|
|
.all_day = true, .location = {}, .recurring = true},
|
|
};
|
|
state.search_result_index = 1;
|
|
frame = nocal::tui::render_month(
|
|
std::span<const nocal::tui::CalendarItem>{}, state);
|
|
check(frame.find("2 matches") != std::string::npos &&
|
|
frame.find("2026-07-18 09:00") != std::string::npos &&
|
|
frame.find("Team holiday") != std::string::npos,
|
|
"search results render counts, dates, times, and titles");
|
|
check(frame.find("\x1b[") == std::string::npos,
|
|
"search remains readable with ANSI disabled");
|
|
check(nocal::tui::decode_key("/") == nocal::tui::Action::search,
|
|
"slash opens appointment search");
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main()
|
|
{
|
|
test_full_month_render();
|
|
test_compact_and_input();
|
|
test_navigation();
|
|
test_search_frames();
|
|
if (failures != 0) {
|
|
std::cerr << failures << " TUI test(s) failed\n";
|
|
return EXIT_FAILURE;
|
|
}
|
|
std::cout << "TUI tests passed\n";
|
|
return EXIT_SUCCESS;
|
|
}
|