//! list / health / journal — the Organizational + working-set surface (§6, §7). use heph_core::{Attention, FixedClock, ListFilter, LocalStore, NewTask, Store, TaskState}; fn store() -> LocalStore { LocalStore::open_in_memory(Box::new(FixedClock(1_700_000_000_000))).unwrap() } fn task(s: &mut LocalStore, title: &str, attention: Attention) -> String { s.create_task(NewTask { title: title.into(), attention: Some(attention), ..Default::default() }) .unwrap() .node_id } #[test] fn list_enumerates_outstanding_including_blue_by_default() { let mut s = store(); task(&mut s, "white", Attention::White); task(&mut s, "blue", Attention::Blue); let done = task(&mut s, "done", Attention::Red); s.set_task_state(&done, TaskState::Done).unwrap(); // Default list: outstanding only, blue included; done excluded. let all = s.list(&ListFilter::default()).unwrap(); let titles: Vec<_> = all.iter().map(|t| t.attention.unwrap()).collect::>(); assert_eq!(all.len(), 2); assert!(titles.contains(&Attention::White)); assert!(titles.contains(&Attention::Blue)); } #[test] fn list_can_exclude_blue_and_filter_by_attention() { let mut s = store(); task(&mut s, "white", Attention::White); task(&mut s, "blue", Attention::Blue); task(&mut s, "orange1", Attention::Orange); task(&mut s, "orange2", Attention::Orange); let no_blue = ListFilter { attention_not: vec![Attention::Blue], ..Default::default() }; assert_eq!(s.list(&no_blue).unwrap().len(), 3); // blue excluded let only_orange = ListFilter { attention_in: vec![Attention::Orange], ..Default::default() }; assert_eq!(s.list(&only_orange).unwrap().len(), 2); } #[test] fn list_rows_carry_title_and_canonical_context() { let mut s = store(); let id = task(&mut s, "Buy milk", Attention::Orange); // The Organizational view needs titles + the one-keystroke context jump // without an N+1 node.get (tech-spec §6, §8). let rows = s.list(&ListFilter::default()).unwrap(); assert_eq!(rows.len(), 1); assert_eq!(rows[0].node_id, id); assert_eq!(rows[0].title, "Buy milk"); assert!(rows[0].canonical_context_id.is_some()); } #[test] fn list_scopes_to_a_project() { let mut s = store(); let project = s .create_node(heph_core::NewNode { kind: heph_core::NodeKind::Project, title: "Work".into(), body: None, }) .unwrap(); s.create_task(NewTask { title: "work task".into(), attention: Some(Attention::White), project_id: Some(project.id.clone()), ..Default::default() }) .unwrap(); task(&mut s, "life task", Attention::White); let scoped = ListFilter { scope: vec![project.id.clone()], ..Default::default() }; assert_eq!(s.list(&scoped).unwrap().len(), 1); } #[test] fn health_counts_the_working_set_honestly() { let mut s = store(); task(&mut s, "r", Attention::Red); task(&mut s, "o1", Attention::Orange); task(&mut s, "o2", Attention::Orange); task(&mut s, "w", Attention::White); task(&mut s, "b1", Attention::Blue); task(&mut s, "b2", Attention::Blue); let h = s.health().unwrap(); assert_eq!(h.orange_count, 2); assert_eq!(h.active_count, 4); // red + 2 orange + white assert_eq!(h.on_deck_count, 2); // 2 blue assert_eq!(h.conflict_count, 0); assert_eq!(h.sync_status, "local"); } #[test] fn journal_open_or_create_is_idempotent_with_deterministic_id() { let mut s = store(); let a = s.journal_open_or_create("2026-05-31").unwrap(); let b = s.journal_open_or_create("2026-05-31").unwrap(); assert_eq!(a.id, b.id); assert_eq!(a.kind, heph_core::NodeKind::Journal); assert_eq!(a.title, "2026-05-31"); // The id is deterministic in (owner, date). assert_eq!( a.id, heph_core::deterministic_id(s.owner_id(), heph_core::NodeKind::Journal, "2026-05-31") ); } #[test] fn journal_rejects_non_iso_dates() { let mut s = store(); assert!(s.journal_open_or_create("May 31").is_err()); assert!(s.journal_open_or_create("2026-5-1").is_err()); }