Files
nuwiki/crates/nuwiki-core/tests/diary_period.rs
T
gffranco d8fa59a63a parity(4): diary weekly / monthly / yearly frequency support
vimwiki's `diary_frequency` config has been honoured by the existing
`nuwiki.diary.openToday` / `openYesterday` / `openTomorrow` commands
end-to-end. Setting `diary_frequency = 'weekly'` (or `monthly` /
`yearly`) now creates and addresses entries at that cadence — file
stems follow the canonical formats:

  daily    YYYY-MM-DD     2026-05-12.wiki
  weekly   YYYY-Www       2026-W19.wiki      (ISO 8601 week)
  monthly  YYYY-MM        2026-05.wiki
  yearly   YYYY           2026.wiki

Core additions (nuwiki_core::date):
  - DiaryFrequency enum + permissive `parse` (unknown → Daily)
  - DiaryPeriod enum unifying Day / Week / Month / Year
  - format / parse / next / prev / today_utc + first_day, with
    proper ISO-week math (Thursday rule, year-boundary handling)

LSP wiring:
  - WikiConfig::frequency() and diary_path_for_period()
  - crate::diary::uri_for_period
  - Index now records `diary_period` for any of the four flavours;
    `diary_date` is preserved (filtered to Day-only) for back-compat
    with existing daily-only callers
  - `nuwiki.diary.next` / `nuwiki.diary.prev` now navigate at the
    *same flavour* as the current entry (or at the wiki's configured
    frequency when off a diary page), via new
    `crate::diary::next_period` / `prev_period` helpers
  - `nuwiki.diary.openToday` returns the period stem + frequency in
    its response payload alongside the URI

Tests:
  - 12 in crates/nuwiki-core/tests/diary_period.rs covering the date
    math (ISO week boundaries, format round-trips, next/prev across
    year edges, etc.)
  - 9 in crates/nuwiki-lsp/tests/diary_frequency.rs covering
    WikiConfig path computation, index recognition for all four
    stems, period navigation, and diary-dir filtering

Gates: 450 Rust / 39 Neovim / 12 Vim.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 22:09:14 +00:00

185 lines
4.6 KiB
Rust

//! Cluster 4 — diary frequency / period primitives.
//! Tests the date math for ISO weeks, monthly, yearly periods.
use nuwiki_core::date::{DiaryDate, DiaryFrequency, DiaryPeriod};
#[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())
);
}