feat: initial nuwiki Rust workspace (core, lsp, ls)
This commit is contained in:
@@ -0,0 +1,278 @@
|
||||
//! Cluster 4 — diary frequency / period primitives.
|
||||
//! Tests the date math for ISO weeks, monthly, yearly periods.
|
||||
|
||||
use nuwiki_core::date::{
|
||||
DiaryCalendar, DiaryDate, DiaryFrequency, DiaryPeriod, WeekStart, WeeklyStyle,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn frequency_parses_canonical_strings() {
|
||||
assert_eq!(DiaryFrequency::parse("daily"), DiaryFrequency::Daily);
|
||||
assert_eq!(DiaryFrequency::parse("weekly"), DiaryFrequency::Weekly);
|
||||
assert_eq!(DiaryFrequency::parse("monthly"), DiaryFrequency::Monthly);
|
||||
assert_eq!(DiaryFrequency::parse("yearly"), DiaryFrequency::Yearly);
|
||||
assert_eq!(DiaryFrequency::parse("WeEkLy"), DiaryFrequency::Weekly);
|
||||
// Unknown falls back to Daily (mirrors vimwiki's permissive behaviour).
|
||||
assert_eq!(DiaryFrequency::parse("hourly"), DiaryFrequency::Daily);
|
||||
assert_eq!(DiaryFrequency::parse(""), DiaryFrequency::Daily);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn period_format_round_trips() {
|
||||
let cases = [
|
||||
(
|
||||
"2026-05-12",
|
||||
DiaryPeriod::Day(DiaryDate::from_ymd(2026, 5, 12).unwrap()),
|
||||
),
|
||||
(
|
||||
"2026-W19",
|
||||
DiaryPeriod::Week {
|
||||
iso_year: 2026,
|
||||
iso_week: 19,
|
||||
},
|
||||
),
|
||||
(
|
||||
"2026-05",
|
||||
DiaryPeriod::Month {
|
||||
year: 2026,
|
||||
month: 5,
|
||||
},
|
||||
),
|
||||
("2026", DiaryPeriod::Year { year: 2026 }),
|
||||
];
|
||||
for (s, want) in cases {
|
||||
let parsed = DiaryPeriod::parse(s).unwrap_or_else(|| panic!("parse {s}"));
|
||||
assert_eq!(parsed, want, "parse({s})");
|
||||
assert_eq!(parsed.format(), s, "format({s})");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn period_parse_rejects_garbage() {
|
||||
assert!(DiaryPeriod::parse("garbage").is_none());
|
||||
assert!(DiaryPeriod::parse("2026-W00").is_none());
|
||||
assert!(DiaryPeriod::parse("2026-13").is_none()); // month 13
|
||||
assert!(DiaryPeriod::parse("2026-").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iso_week_jan_1_can_belong_to_prior_year() {
|
||||
// 2021-01-01 was a Friday → ISO week 53 of 2020.
|
||||
let d = DiaryDate::from_ymd(2021, 1, 1).unwrap();
|
||||
let p = DiaryPeriod::week_containing(d);
|
||||
assert_eq!(
|
||||
p,
|
||||
DiaryPeriod::Week {
|
||||
iso_year: 2020,
|
||||
iso_week: 53
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iso_week_dec_31_can_belong_to_next_year() {
|
||||
// 2024-12-30 (Monday) is in ISO week 1 of 2025.
|
||||
let d = DiaryDate::from_ymd(2024, 12, 30).unwrap();
|
||||
let p = DiaryPeriod::week_containing(d);
|
||||
assert_eq!(
|
||||
p,
|
||||
DiaryPeriod::Week {
|
||||
iso_year: 2025,
|
||||
iso_week: 1
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iso_week_mid_year_is_consistent() {
|
||||
// 2026-05-12 is a Tuesday in ISO week 20.
|
||||
let d = DiaryDate::from_ymd(2026, 5, 12).unwrap();
|
||||
let p = DiaryPeriod::week_containing(d);
|
||||
assert_eq!(
|
||||
p,
|
||||
DiaryPeriod::Week {
|
||||
iso_year: 2026,
|
||||
iso_week: 20
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn week_next_and_prev_step_by_seven_days() {
|
||||
let p = DiaryPeriod::Week {
|
||||
iso_year: 2026,
|
||||
iso_week: 20,
|
||||
};
|
||||
assert_eq!(
|
||||
p.next(),
|
||||
DiaryPeriod::Week {
|
||||
iso_year: 2026,
|
||||
iso_week: 21
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
p.prev(),
|
||||
DiaryPeriod::Week {
|
||||
iso_year: 2026,
|
||||
iso_week: 19
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn week_next_crosses_year_boundary() {
|
||||
// Week 53 of 2020 → week 53 ends; next should be week 1 of 2021.
|
||||
let p = DiaryPeriod::Week {
|
||||
iso_year: 2020,
|
||||
iso_week: 53,
|
||||
};
|
||||
assert_eq!(
|
||||
p.next(),
|
||||
DiaryPeriod::Week {
|
||||
iso_year: 2021,
|
||||
iso_week: 1
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn month_next_wraps_to_january() {
|
||||
let dec = DiaryPeriod::Month {
|
||||
year: 2026,
|
||||
month: 12,
|
||||
};
|
||||
assert_eq!(
|
||||
dec.next(),
|
||||
DiaryPeriod::Month {
|
||||
year: 2027,
|
||||
month: 1
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn month_prev_wraps_to_december() {
|
||||
let jan = DiaryPeriod::Month {
|
||||
year: 2026,
|
||||
month: 1,
|
||||
};
|
||||
assert_eq!(
|
||||
jan.prev(),
|
||||
DiaryPeriod::Month {
|
||||
year: 2025,
|
||||
month: 12
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn year_next_and_prev_increment_by_one() {
|
||||
let y = DiaryPeriod::Year { year: 2026 };
|
||||
assert_eq!(y.next(), DiaryPeriod::Year { year: 2027 });
|
||||
assert_eq!(y.prev(), DiaryPeriod::Year { year: 2025 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn day_next_and_prev_still_work() {
|
||||
let d = DiaryPeriod::Day(DiaryDate::from_ymd(2026, 5, 12).unwrap());
|
||||
assert_eq!(
|
||||
d.next(),
|
||||
DiaryPeriod::Day(DiaryDate::from_ymd(2026, 5, 13).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
d.prev(),
|
||||
DiaryPeriod::Day(DiaryDate::from_ymd(2026, 5, 11).unwrap())
|
||||
);
|
||||
}
|
||||
|
||||
// ===== DiaryCalendar — weekly naming styles + week-start =====
|
||||
// 2026-05-25 is a Monday (so 2026-05-27 is a Wednesday, 2026-05-24 a Sunday).
|
||||
|
||||
fn day(y: i32, m: u8, d: u8) -> DiaryPeriod {
|
||||
DiaryPeriod::Day(DiaryDate::from_ymd(y, m, d).unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn weekly_style_and_week_start_parse() {
|
||||
assert_eq!(WeeklyStyle::parse("date"), WeeklyStyle::Date);
|
||||
assert_eq!(WeeklyStyle::parse("vimwiki"), WeeklyStyle::Date);
|
||||
assert_eq!(WeeklyStyle::parse("iso"), WeeklyStyle::Iso);
|
||||
assert_eq!(WeeklyStyle::parse(""), WeeklyStyle::Iso); // default
|
||||
assert_eq!(WeeklyStyle::parse("WEEK"), WeeklyStyle::Iso);
|
||||
// WeekStart::parse is exercised via the calendar tests below; unknown
|
||||
// names fall back to Monday.
|
||||
assert_eq!(WeekStart::parse("sunday"), WeekStart::parse("SUNDAY"));
|
||||
assert_eq!(WeekStart::parse("nonsense"), WeekStart::monday());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn date_mode_weekly_today_is_a_day() {
|
||||
let cal = DiaryCalendar::new(
|
||||
DiaryFrequency::Weekly,
|
||||
WeeklyStyle::Date,
|
||||
WeekStart::monday(),
|
||||
);
|
||||
assert!(matches!(cal.today(), DiaryPeriod::Day(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iso_mode_weekly_today_is_a_week() {
|
||||
let cal = DiaryCalendar::new(
|
||||
DiaryFrequency::Weekly,
|
||||
WeeklyStyle::Iso,
|
||||
WeekStart::monday(),
|
||||
);
|
||||
assert!(matches!(cal.today(), DiaryPeriod::Week { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn date_mode_weekly_steps_seven_days_from_the_week_start_monday() {
|
||||
let cal = DiaryCalendar::new(
|
||||
DiaryFrequency::Weekly,
|
||||
WeeklyStyle::Date,
|
||||
WeekStart::monday(),
|
||||
);
|
||||
// From a Wednesday: snap back to Mon 05-25, then ±7.
|
||||
assert_eq!(cal.next(day(2026, 5, 27)), day(2026, 6, 1));
|
||||
assert_eq!(cal.prev(day(2026, 5, 27)), day(2026, 5, 18));
|
||||
// From the Monday itself: no snap, just ±7.
|
||||
assert_eq!(cal.next(day(2026, 5, 25)), day(2026, 6, 1));
|
||||
assert_eq!(cal.prev(day(2026, 5, 25)), day(2026, 5, 18));
|
||||
// Stem is the plain date, like upstream.
|
||||
assert_eq!(cal.next(day(2026, 5, 27)).format(), "2026-06-01");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn date_mode_weekly_honours_a_sunday_week_start() {
|
||||
let cal = DiaryCalendar::new(
|
||||
DiaryFrequency::Weekly,
|
||||
WeeklyStyle::Date,
|
||||
WeekStart::parse("sunday"),
|
||||
);
|
||||
// Wed 05-27 snaps back to Sun 05-24, +7 = Sun 05-31.
|
||||
assert_eq!(cal.next(day(2026, 5, 27)), day(2026, 5, 31));
|
||||
assert_eq!(cal.prev(day(2026, 5, 27)), day(2026, 5, 17));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iso_mode_weekly_delegates_to_iso_week_stepping() {
|
||||
let cal = DiaryCalendar::new(
|
||||
DiaryFrequency::Weekly,
|
||||
WeeklyStyle::Iso,
|
||||
WeekStart::monday(),
|
||||
);
|
||||
let wk = DiaryPeriod::week_containing(DiaryDate::from_ymd(2026, 5, 27).unwrap());
|
||||
assert_eq!(cal.next(wk), wk.next());
|
||||
assert_eq!(cal.prev(wk), wk.prev());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn daily_calendar_steps_one_day_regardless_of_weekly_fields() {
|
||||
let cal = DiaryCalendar::new(
|
||||
DiaryFrequency::Daily,
|
||||
WeeklyStyle::Date,
|
||||
WeekStart::parse("sunday"),
|
||||
);
|
||||
assert_eq!(cal.next(day(2026, 5, 27)), day(2026, 5, 28));
|
||||
assert_eq!(cal.prev(day(2026, 5, 27)), day(2026, 5, 26));
|
||||
}
|
||||
Reference in New Issue
Block a user