//! Cluster 4 — diary frequency wiring at the WikiConfig + index layer. //! //! These tests exercise the path/URI shape for each frequency and the //! index's ability to recognise non-daily diary entries. The //! integration through `executeCommand` is exercised in the LSP-level //! tests via the Neovim keymap harness; here we stay at the pure-Rust //! layer so failures land with a clean stack. use std::path::PathBuf; use nuwiki_core::date::{DiaryDate, DiaryFrequency, DiaryPeriod}; use nuwiki_core::syntax::vimwiki::VimwikiSyntax; use nuwiki_core::syntax::SyntaxPlugin; use nuwiki_lsp::config::WikiConfig; use nuwiki_lsp::diary; use nuwiki_lsp::index::{diary_period_for_uri, WorkspaceIndex}; use tower_lsp::lsp_types::Url; fn cfg(root: &str, frequency: &str) -> WikiConfig { let mut c = WikiConfig::from_root(PathBuf::from(root)); c.diary_rel_path = "diary".into(); c.diary_frequency = frequency.into(); c } #[test] fn frequency_helper_picks_up_config_value() { assert_eq!(cfg("/tmp", "daily").frequency(), DiaryFrequency::Daily); assert_eq!(cfg("/tmp", "weekly").frequency(), DiaryFrequency::Weekly); assert_eq!(cfg("/tmp", "monthly").frequency(), DiaryFrequency::Monthly); assert_eq!(cfg("/tmp", "yearly").frequency(), DiaryFrequency::Yearly); } #[test] fn diary_path_for_period_picks_right_stem_per_frequency() { let c = cfg("/wiki", "daily"); let day = DiaryPeriod::Day(DiaryDate::from_ymd(2026, 5, 12).unwrap()); let wk = DiaryPeriod::Week { iso_year: 2026, iso_week: 19, }; let mo = DiaryPeriod::Month { year: 2026, month: 5, }; let yr = DiaryPeriod::Year { year: 2026 }; assert_eq!( c.diary_path_for_period(&day), PathBuf::from("/wiki/diary/2026-05-12.wiki") ); assert_eq!( c.diary_path_for_period(&wk), PathBuf::from("/wiki/diary/2026-W19.wiki") ); assert_eq!( c.diary_path_for_period(&mo), PathBuf::from("/wiki/diary/2026-05.wiki") ); assert_eq!( c.diary_path_for_period(&yr), PathBuf::from("/wiki/diary/2026.wiki") ); } #[test] fn uri_for_period_produces_file_url_per_frequency() { let c = cfg("/wiki", "weekly"); let wk = DiaryPeriod::Week { iso_year: 2026, iso_week: 19, }; let u = diary::uri_for_period(&c, &wk).unwrap(); assert!( u.as_str().ends_with("/wiki/diary/2026-W19.wiki"), "got: {u}" ); } // ===== Index recognition ===== fn make_index_with(entries: &[&str]) -> WorkspaceIndex { let root = PathBuf::from("/wiki"); let mut idx = WorkspaceIndex::new(Some(root.clone())).with_diary_rel_path(Some("diary".into())); for stem in entries { let path = format!("/wiki/diary/{stem}.wiki"); let uri = Url::from_file_path(&path).unwrap(); let ast = VimwikiSyntax::new().parse("= entry =\n"); idx.upsert(uri, &ast); } idx } #[test] fn index_recognises_all_four_frequency_stems() { let idx = make_index_with(&["2026-05-12", "2026-W19", "2026-05", "2026"]); for (stem, expected) in [ ( "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 }), ] { let uri = Url::from_file_path(format!("/wiki/diary/{stem}.wiki")).unwrap(); let page = idx.page(&uri).expect(stem); assert_eq!( page.diary_period, Some(expected), "stem {stem} should index as {expected:?}" ); } } #[test] fn index_only_sets_diary_date_for_daily_entries() { let idx = make_index_with(&["2026-05-12", "2026-W19"]); let daily = Url::from_file_path("/wiki/diary/2026-05-12.wiki").unwrap(); let weekly = Url::from_file_path("/wiki/diary/2026-W19.wiki").unwrap(); assert!(idx.page(&daily).unwrap().diary_date.is_some()); assert!(idx.page(&weekly).unwrap().diary_date.is_none()); assert!(idx.page(&weekly).unwrap().diary_period.is_some()); } #[test] fn diary_period_for_uri_rejects_paths_outside_diary_dir() { // Non-diary path with a date-looking stem — should NOT count. let uri = Url::from_file_path("/wiki/notes/2026-05-12.wiki").unwrap(); let root = PathBuf::from("/wiki"); assert!(diary_period_for_uri(&uri, Some(&root), Some("diary")).is_none()); } #[test] fn next_prev_period_navigate_at_the_same_frequency() { // Mix entries at different frequencies. Navigation should stay // within the same flavour as the pivot. let idx = make_index_with(&[ "2026-W18", "2026-W19", "2026-W20", // weekly "2026-04", "2026-05", "2026-06", // monthly "2026-05-10", "2026-05-12", // daily ]); let pivot_w = DiaryPeriod::Week { iso_year: 2026, iso_week: 19, }; let next_w = diary::next_period(&idx, &pivot_w).unwrap(); assert_eq!( next_w.period, DiaryPeriod::Week { iso_year: 2026, iso_week: 20 } ); let prev_w = diary::prev_period(&idx, &pivot_w).unwrap(); assert_eq!( prev_w.period, DiaryPeriod::Week { iso_year: 2026, iso_week: 18 } ); let pivot_m = DiaryPeriod::Month { year: 2026, month: 5, }; let next_m = diary::next_period(&idx, &pivot_m).unwrap(); assert_eq!( next_m.period, DiaryPeriod::Month { year: 2026, month: 6 } ); let prev_m = diary::prev_period(&idx, &pivot_m).unwrap(); assert_eq!( prev_m.period, DiaryPeriod::Month { year: 2026, month: 4 } ); } #[test] fn next_prev_period_returns_none_past_the_edge() { let idx = make_index_with(&["2026-W19"]); let only = DiaryPeriod::Week { iso_year: 2026, iso_week: 19, }; assert!(diary::next_period(&idx, &only).is_none()); assert!(diary::prev_period(&idx, &only).is_none()); } #[test] fn diary_period_for_uri_handles_each_frequency_under_diary_dir() { let root = PathBuf::from("/wiki"); for (stem, expected) in [ ( "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 }), ] { let uri = Url::from_file_path(format!("/wiki/diary/{stem}.wiki")).unwrap(); let got = diary_period_for_uri(&uri, Some(&root), Some("diary")); assert_eq!(got, Some(expected), "stem {stem}"); } }