#include "nocal/domain/domain.hpp" #include #include #include #include #include #include #include namespace { int failures = 0; void check(bool condition, std::string_view message) { if (!condition) { ++failures; std::cerr << "FAIL: " << message << '\n'; } } template void check_throws(Function&& function, std::string_view message) { try { function(); check(false, message); } catch (const std::exception&) { } } void test_dates() { using namespace std::chrono; const nocal::Date leap{2024y / February / 29d}; check(nocal::valid_date(leap), "leap day is valid"); check(nocal::format_date(leap) == "2024-02-29", "date formats as ISO 8601"); check(nocal::parse_date("2024-02-29") == leap, "date parses from ISO 8601"); check(nocal::from_sys_days(nocal::to_sys_days(leap) + days{1}) == nocal::Date{2024y / March / 1d}, "civil date arithmetic crosses a leap month"); check(nocal::monday_first_weekday(nocal::Date{2026y / July / 6d}) == 0, "Monday has index zero"); check(nocal::monday_first_weekday(nocal::Date{2026y / July / 12d}) == 6, "Sunday has index six"); check_throws([] { (void)nocal::parse_date("2023-02-29"); }, "invalid date is rejected"); } void test_month_layout() { using namespace std::chrono; const auto layout = nocal::make_month_layout(2026y / July); check(layout.cells.size() == 42, "month layout always has 42 cells"); check(layout.first_visible_date() == nocal::Date{2026y / June / 29d}, "July 2026 begins after Monday-leading spill cells"); check(layout.last_visible_date() == nocal::Date{2026y / August / 9d}, "six-week grid has the expected final date"); check(layout.at(0, 0).column == 0 && layout.at(5, 6).row == 5, "cell coordinates match their positions"); check(!layout.at(0, 0).in_month && layout.at(0, 2).in_month, "spill cells are distinguished from current-month cells"); const auto* july_17 = layout.find(nocal::Date{2026y / July / 17d}); check(july_17 != nullptr && july_17->row == 2 && july_17->column == 4, "date lookup finds the correct Friday cell"); check_throws([&] { (void)layout.at(6, 0); }, "out-of-grid access is rejected"); } nocal::Event event(std::string uid, nocal::Date start_date, unsigned start_hour, nocal::Date end_date, unsigned end_hour, bool all_day = false) { nocal::Event result; result.uid = std::move(uid); result.title = result.uid; result.start = nocal::make_local_time(start_date, start_hour); result.end = nocal::make_local_time(end_date, end_hour); result.all_day = all_day; return result; } void test_event_queries() { using namespace std::chrono; const nocal::Date previous{2026y / July / 16d}; const nocal::Date day{2026y / July / 17d}; const nocal::Date next{2026y / July / 18d}; std::vector events; events.push_back(event("late", day, 15, day, 16)); events.push_back(event("all-day", day, 0, next, 0, true)); events.push_back(event("early", day, 9, day, 10)); events.push_back(event("carry", previous, 23, day, 1)); events.push_back(event("ends-at-boundary", previous, 22, day, 0)); events.push_back(event("next-day", next, 0, next, 1)); const nocal::Date august_first{2026y / August / 1d}; events.push_back(event("next-month", august_first, 0, august_first, 1)); nocal::Event instant = event("instant", day, 12, day, 12); events.push_back(instant); const auto on_day = nocal::events_on_day(events, day); check(on_day.size() == 5, "day query uses overlap and half-open boundaries"); check(on_day[0].get().uid == "all-day", "all-day events sort before timed events"); check(on_day[1].get().uid == "carry", "overlapping prior-day event is included"); check(on_day[2].get().uid == "early" && on_day[3].get().uid == "instant" && on_day[4].get().uid == "late", "timed events sort by start time"); check(nocal::event_occurs_on(instant, day), "zero-duration event occurs on the day containing its instant"); const auto in_july = nocal::events_in_month(events, 2026y / July); check(in_july.size() == 7, "month query excludes an event beginning August 1"); nocal::Event invalid = event("invalid", day, 12, day, 11); check(!nocal::event_overlaps(invalid, nocal::local_day_start(day), nocal::local_day_end(day)), "invalid backwards event never overlaps"); } } // namespace int main() { test_dates(); test_month_layout(); test_event_queries(); if (failures != 0) { std::cerr << failures << " domain test(s) failed\n"; return EXIT_FAILURE; } std::cout << "domain tests passed\n"; return EXIT_SUCCESS; }