use serde::{Deserialize, Serialize};
use crate::plan::PhaseId;
mod parse;
pub use crate::plan::{snapshot, verify_unchanged, Snapshot, SnapshotError};
pub use parse::{parse, serialize, DeferredParseError};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeferredItem {
pub text: String,
pub done: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeferredPhase {
pub source_phase: PhaseId,
pub title: String,
pub body: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeferredDoc {
pub items: Vec<DeferredItem>,
pub phases: Vec<DeferredPhase>,
}
impl DeferredDoc {
pub fn empty() -> Self {
Self::default()
}
pub fn sweep(&mut self) {
self.items.retain(|item| !item.done);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pid(s: &str) -> PhaseId {
PhaseId::parse(s).unwrap()
}
#[test]
fn empty_is_default() {
assert_eq!(DeferredDoc::empty(), DeferredDoc::default());
let doc = DeferredDoc::empty();
assert!(doc.items.is_empty());
assert!(doc.phases.is_empty());
}
#[test]
fn sweep_removes_done_items_only() {
let mut doc = DeferredDoc {
items: vec![
DeferredItem {
text: "keep".into(),
done: false,
},
DeferredItem {
text: "drop".into(),
done: true,
},
DeferredItem {
text: "also keep".into(),
done: false,
},
],
phases: vec![DeferredPhase {
source_phase: pid("07"),
title: "untouched".into(),
body: String::new(),
}],
};
doc.sweep();
assert_eq!(
doc.items,
vec![
DeferredItem {
text: "keep".into(),
done: false,
},
DeferredItem {
text: "also keep".into(),
done: false,
},
]
);
assert_eq!(doc.phases.len(), 1);
}
#[test]
fn snapshot_re_exports_detect_drift_on_deferred_md() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("deferred.md");
std::fs::write(&path, "## Deferred items\n\n- [ ] x\n").unwrap();
let snap = snapshot(&path).unwrap();
verify_unchanged(&path, &snap).unwrap();
std::fs::write(&path, "## Deferred items\n\n- [x] x\n").unwrap();
let err = verify_unchanged(&path, &snap).unwrap_err();
assert!(matches!(err, SnapshotError::Mismatch { .. }));
}
#[test]
fn serde_round_trip() {
let doc = DeferredDoc {
items: vec![
DeferredItem {
text: "polish error message".into(),
done: false,
},
DeferredItem {
text: "remove unused stub".into(),
done: true,
},
],
phases: vec![DeferredPhase {
source_phase: pid("07"),
title: "rework agent trait".into(),
body: "Some body text\n- bullet\n".into(),
}],
};
let json = serde_json::to_string(&doc).unwrap();
let back: DeferredDoc = serde_json::from_str(&json).unwrap();
assert_eq!(doc, back);
}
}