139
crates/domain/tests/cycle/cycle_calendar_test.rs
Normal file
139
crates/domain/tests/cycle/cycle_calendar_test.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
use domain::cycle::CycleCalendar;
|
||||
use domain::entry::Date;
|
||||
|
||||
fn on(day: &str) -> Date {
|
||||
Date::from_persistence(day.parse().unwrap())
|
||||
}
|
||||
|
||||
fn calendar(starts: &[&str]) -> CycleCalendar {
|
||||
CycleCalendar::new(starts.iter().map(|day| on(day)).collect())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_day_a_cycle_starts_is_its_first_day() {
|
||||
let position = calendar(&["2026-01-01"])
|
||||
.position_on(&on("2026-01-01"))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(position.day().value(), 1);
|
||||
assert!(position.progress().abs() < f64::EPSILON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_date_before_anything_was_recorded_has_no_place_in_a_cycle() {
|
||||
assert!(
|
||||
calendar(&["2026-02-01"])
|
||||
.position_on(&on("2026-01-15"))
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nothing_recorded_at_all_means_nothing_derived() {
|
||||
assert!(calendar(&[]).position_on(&on("2026-01-15")).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_closed_cycle_is_measured_by_its_own_length() {
|
||||
let starts = calendar(&["2026-01-01", "2026-01-25"]);
|
||||
|
||||
let halfway = starts.position_on(&on("2026-01-13")).unwrap();
|
||||
|
||||
assert_eq!(halfway.day().value(), 13);
|
||||
assert!(
|
||||
(halfway.progress() - 0.5).abs() < 1e-12,
|
||||
"twelve days into a twenty-four day cycle, got {}",
|
||||
halfway.progress()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_cycle_still_running_is_measured_against_the_usual_length() {
|
||||
let starts = calendar(&["2026-01-01", "2026-01-21", "2026-02-10", "2026-03-02"]);
|
||||
|
||||
let open = starts.position_on(&on("2026-03-12")).unwrap();
|
||||
|
||||
assert_eq!(open.day().value(), 11);
|
||||
assert!(
|
||||
(open.progress() - 0.5).abs() < 1e-12,
|
||||
"ten days into a twenty-day cycle, got {}",
|
||||
open.progress()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_nothing_to_go_on_the_usual_length_is_assumed() {
|
||||
let starts = calendar(&["2026-01-01"]);
|
||||
|
||||
let position = starts.position_on(&on("2026-01-15")).unwrap();
|
||||
|
||||
assert_eq!(position.day().value(), 15);
|
||||
assert!(
|
||||
(position.progress() - 0.5).abs() < 1e-12,
|
||||
"fourteen days into an assumed twenty-eight day cycle, got {}",
|
||||
position.progress()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correcting_the_start_moves_every_day_that_depended_on_it() {
|
||||
let before = calendar(&["2026-01-01"])
|
||||
.position_on(&on("2026-01-10"))
|
||||
.unwrap();
|
||||
let after = calendar(&["2026-01-03"])
|
||||
.position_on(&on("2026-01-10"))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(before.day().value(), 10);
|
||||
assert_eq!(after.day().value(), 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_gap_too_long_to_be_one_cycle_means_a_start_was_missed() {
|
||||
let position = calendar(&["2026-01-01"]).position_on(&on("2026-06-01"));
|
||||
|
||||
assert!(
|
||||
position.is_none(),
|
||||
"a hundred and fifty days is a missing record, not a long cycle"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_order_and_repetition_of_what_was_recorded_does_not_matter() {
|
||||
let tidy = calendar(&["2026-01-01", "2026-01-25"]);
|
||||
let messy = calendar(&["2026-01-25", "2026-01-01", "2026-01-25"]);
|
||||
|
||||
let from_tidy = tidy.position_on(&on("2026-01-13")).unwrap();
|
||||
let from_messy = messy.position_on(&on("2026-01-13")).unwrap();
|
||||
|
||||
assert_eq!(from_tidy.day().value(), from_messy.day().value());
|
||||
assert!((from_tidy.progress() - from_messy.progress()).abs() < f64::EPSILON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_last_day_of_a_cycle_has_run_its_whole_course() {
|
||||
let starts = calendar(&["2026-01-01", "2026-01-29"]);
|
||||
|
||||
let last = starts.position_on(&on("2026-01-28")).unwrap();
|
||||
|
||||
assert_eq!(last.day().value(), 28);
|
||||
assert!(
|
||||
(last.progress() - 27.0 / 28.0).abs() < 1e-12,
|
||||
"got {}",
|
||||
last.progress()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn progress_never_runs_past_the_end_of_a_cycle() {
|
||||
let starts = calendar(&["2026-01-01", "2026-01-15", "2026-01-29"]);
|
||||
|
||||
let overdue = starts.position_on(&on("2026-02-28")).unwrap();
|
||||
|
||||
assert_eq!(overdue.day().value(), 31);
|
||||
assert!(
|
||||
(overdue.progress() - 1.0).abs() < f64::EPSILON,
|
||||
"a late cycle sits at its end rather than beyond it, got {}",
|
||||
overdue.progress()
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user