use super::{Caret, Recall, Step, prompts};
use crate::inboxview::{Deposit, Epitaph, InboxEntry};
use crate::transcript::{Entry, EntryKind, Transcript};
fn delivered(sender: &str, body: &str) -> Entry {
Entry {
name: format!("001-{sender}.md"),
raw: body.as_bytes().to_vec(),
kind: EntryKind::Delivered {
sender: sender.into(),
epitaph: None,
body: body.into(),
},
}
}
fn ended(sender: &str, body: &str) -> Entry {
Entry {
name: format!("002-{sender}.md"),
raw: body.as_bytes().to_vec(),
kind: EntryKind::Delivered {
sender: sender.into(),
epitaph: Some(Epitaph::FinalResponse),
body: body.into(),
},
}
}
fn model(text: &str) -> Entry {
Entry {
name: "003-claude.json".into(),
raw: Vec::new(),
kind: EntryKind::Model {
model_id: "claude".into(),
blocks: vec![crate::transcript::Block::Text(text.into())],
usage: crate::transcript::Usage::default(),
},
}
}
fn deposit(sender: Option<&str>, body: &str) -> InboxEntry {
InboxEntry {
name: format!("{}-001.md", sender.unwrap_or("anon")),
raw: body.as_bytes().to_vec(),
deposit: Deposit {
sender: sender.map(Into::into),
body: body.into(),
..Deposit::default()
},
}
}
#[test]
fn prompts_are_the_operators_own_messages_newest_first() {
let tx = Transcript {
entries: vec![
delivered("user", "first"),
model("some answer"),
delivered("user", "second"),
],
};
let pending = vec![deposit(Some("user"), "third")];
assert_eq!(prompts(&pending, &tx), ["third", "second", "first"]);
}
#[test]
fn nothing_said_to_you_is_offered_back() {
let tx = Transcript {
entries: vec![
delivered("peer-1", "a peer's mail"),
ended("user", "a result deposit"),
model("the agent"),
Entry {
name: "004-tool.json".into(),
raw: Vec::new(),
kind: EntryKind::ToolResult {
tool_use_id: "t1".into(),
content: "out".into(),
is_error: false,
},
},
],
};
let pending = vec![deposit(Some("peer-2"), "more mail"), deposit(None, "anon")];
assert!(prompts(&pending, &tx).is_empty());
}
#[test]
fn an_empty_conversation_offers_nothing() {
assert!(prompts(&[], &Transcript::default()).is_empty());
}
#[test]
fn the_caret_gate_opens_only_at_the_matching_edge() {
let empty = Caret { row: 0, rows: 0 };
assert!(empty.open(Step::Back) && empty.open(Step::Forward));
let top = Caret { row: 0, rows: 3 };
assert!(top.open(Step::Back) && !top.open(Step::Forward));
let middle = Caret { row: 1, rows: 3 };
assert!(!middle.open(Step::Back) && !middle.open(Step::Forward));
let bottom = Caret { row: 2, rows: 3 };
assert!(!bottom.open(Step::Back) && bottom.open(Step::Forward));
}
#[test]
fn back_walks_the_prompts_and_forward_restores_the_draft() {
let history = ["newest".to_string(), "older".to_string()];
let flat = Caret::default();
let mut recall = Recall::default();
assert_eq!(
recall
.step(Step::Back, flat, "half-typed", &history)
.unwrap(),
"newest"
);
assert_eq!(
recall.step(Step::Back, flat, "newest", &history).unwrap(),
"older"
);
assert_eq!(
recall.step(Step::Back, flat, "older", &history),
None,
"the oldest prompt is the end of the walk"
);
assert_eq!(
recall.step(Step::Forward, flat, "older", &history).unwrap(),
"newest"
);
assert_eq!(
recall
.step(Step::Forward, flat, "newest", &history)
.unwrap(),
"half-typed",
"forward past the newest hands the draft back"
);
assert_eq!(
recall.step(Step::Forward, flat, "half-typed", &history),
None,
"and there is nothing past the draft"
);
}
#[test]
fn a_caret_off_the_edge_keeps_its_key() {
let history = ["newest".to_string()];
let mut recall = Recall::default();
let middle = Caret { row: 1, rows: 3 };
assert_eq!(recall.step(Step::Back, middle, "a\nb\nc", &history), None);
assert_eq!(
recall.step(Step::Forward, middle, "a\nb\nc", &history),
None
);
}
#[test]
fn editing_a_recalled_prompt_leaves_the_recall_and_forgets_the_stash() {
let history = ["newest".to_string()];
let flat = Caret::default();
let mut recall = Recall::default();
recall.step(Step::Back, flat, "half-typed", &history);
recall.settle("newest, amended", &history);
assert_eq!(
recall.step(Step::Forward, flat, "newest, amended", &history),
None,
"depth is back to the draft, so forward has nowhere to go"
);
assert_eq!(
recall
.step(Step::Back, flat, "newest, amended", &history)
.unwrap(),
"newest",
"and back starts the walk over from the edited draft"
);
}
#[test]
fn a_send_settles_the_recall_out() {
let mut recall = Recall::default();
recall.step(Step::Back, Caret::default(), "", &["newest".to_string()]);
recall.settle("", &["sent".to_string(), "newest".to_string()]);
assert_eq!(
recall.step(Step::Forward, Caret::default(), "", &[]),
None,
"nothing is held open across the send"
);
}
#[test]
fn a_live_draft_is_never_settled_away() {
let mut recall = Recall::default();
recall.settle("half-typed", &[]);
assert_eq!(
recall
.step(Step::Back, Caret::default(), "half-typed", &["a".into()])
.unwrap(),
"a"
);
}