use super::*;
#[test]
fn conversation_commands_parse_expected_actions() {
assert_eq!(
parse_conversation_command("/conversation list").unwrap(),
ConversationCommand::List
);
assert_eq!(
parse_conversation_command("/conversation show abc").unwrap(),
ConversationCommand::Show("abc".into())
);
assert_eq!(
parse_conversation_command("/conversation restore abc").unwrap(),
ConversationCommand::Restore("abc".into())
);
assert_eq!(
parse_conversation_command("/conversation rename abc A better title").unwrap(),
ConversationCommand::Rename {
id: "abc".into(),
title: "A better title".into()
}
);
assert_eq!(
parse_conversation_command("/conversation delete abc").unwrap(),
ConversationCommand::Delete("abc".into())
);
assert_eq!(
parse_conversation_command("/conversation rm abc").unwrap(),
ConversationCommand::Delete("abc".into())
);
}
#[test]
fn the_roadmap_read_subcommands_are_the_ones_a_retired_door_may_serve() {
use super::roadmap_subcommand_reads as reads;
assert!(reads(""), "bare /roadmap renders");
assert!(reads("show"));
assert!(reads("tree"), "the word /tree retired into");
assert!(reads("show 3"), "an argument does not make it a write");
assert!(reads("list"));
for sub in [
"new x",
"use 3",
"add task x",
"next",
"bind",
"done",
"drive",
"import p",
] {
assert!(!reads(sub), "`{sub}` mutates");
}
assert!(!reads("export json"));
}
#[test]
fn roadmap_tree_parses_as_the_view_it_names() {
let show = super::parse_roadmap_command("/roadmap show").expect("show parses");
let tree = super::parse_roadmap_command("/roadmap tree").expect("tree parses");
assert_eq!(show, tree, "tree IS show — one view, two names");
}
#[test]
fn the_retired_conversation_verb_reads_but_does_not_mutate() {
use super::{conversation_op_plan, ConversationOpPlan};
assert_eq!(
conversation_op_plan("/conversation list").unwrap(),
ConversationOpPlan::Run
);
assert_eq!(
conversation_op_plan("/conversation show abc").unwrap(),
ConversationOpPlan::Run
);
for (line, expect) in [
("/conversation restore abc", "/resume restore abc"),
(
"/conversation rename abc New Title",
"/resume rename abc New Title",
),
("/conversation delete abc", "/resume delete abc"),
] {
match conversation_op_plan(line).unwrap() {
ConversationOpPlan::Redirect(msg) => {
assert!(msg.contains(expect), "{line}: {msg}");
assert!(msg.contains("nothing changed"), "{line}: {msg}");
}
other => panic!("`{line}` must not mutate through the retired verb: {other:?}"),
}
}
}
#[test]
fn deleting_a_conversation_asks_first_and_names_what_is_lost() {
use super::{conversation_op_plan, ConversationOpPlan};
match conversation_op_plan("/resume delete abc").unwrap() {
ConversationOpPlan::Confirm { prompt, id } => {
assert_eq!(id, "abc");
assert!(prompt.contains("abc"), "{prompt}");
assert!(
prompt.contains("every turn"),
"names what is lost: {prompt}"
);
assert!(prompt.contains("cannot be undone"), "{prompt}");
}
other => panic!("a delete must ask: {other:?}"),
}
assert!(matches!(
conversation_op_plan("/resume rm abc").unwrap(),
ConversationOpPlan::Confirm { .. }
));
assert_eq!(
conversation_op_plan("/resume rename abc Title").unwrap(),
ConversationOpPlan::Run
);
}
#[test]
fn a_declined_or_unaskable_delete_removes_nothing() {
use newt_core::interaction_surface::SurfaceInteraction;
use newt_core::HumanQuestionOutcome;
let answering =
|a: &'static str| move |_: &SurfaceInteraction| HumanQuestionOutcome::Answer(a.to_string());
assert!(super::confirm_conversation_delete(&answering("y"), "q"));
assert!(super::confirm_conversation_delete(&answering("yes"), "q"));
assert!(!super::confirm_conversation_delete(&answering("n"), "q"));
assert!(!super::confirm_conversation_delete(&answering(""), "q"));
assert!(
!super::confirm_conversation_delete(&answering("delete it please"), "q"),
"an unresolvable answer is a no, not a yes"
);
let cancelled = |_: &SurfaceInteraction| HumanQuestionOutcome::Cancelled;
assert!(!super::confirm_conversation_delete(&cancelled, "q"));
}
#[test]
fn resume_routes_only_its_subcommands_to_the_conversation_ops() {
use super::resume_conversation_subcommand as sub;
for word in ["list", "show", "restore", "rename", "delete", "rm"] {
assert!(sub(&format!("resume {word} x")), "{word}");
}
assert!(sub("resume list"));
assert!(!sub("resume restored"), "restored is a search term");
assert!(!sub("resume listing"), "listing is a search term");
assert!(!sub("resume"), "bare /resume is browse");
assert!(!sub("resume tokio panic"));
assert!(!sub("resumex list"));
}
#[test]
fn recall_commands_parse_as_the_find_they_retired_into() {
assert_eq!(
parse_resume_command("/recall"),
ResumeCommand::Find(String::new())
);
assert_eq!(
parse_resume_command("/recall "),
ResumeCommand::Find(String::new())
);
assert_eq!(
parse_resume_command("/recall tokio panic"),
ResumeCommand::Find("tokio panic".into())
);
assert_ne!(
parse_resume_command("/recallx"),
ResumeCommand::Find("x".into())
);
assert_ne!(
parse_resume_command("/conversation list"),
ResumeCommand::Find("list".into())
);
}
#[test]
fn resume_find_searches_without_reopening() {
assert_eq!(
parse_resume_command("/resume find"),
ResumeCommand::Find(String::new())
);
assert_eq!(
parse_resume_command("/resume find tokio panic"),
ResumeCommand::Find("tokio panic".into())
);
assert_eq!(
parse_resume_command("/resume find 175200000000"),
ResumeCommand::Find("175200000000".into())
);
assert_eq!(
parse_resume_command("/resume 175200000000"),
ResumeCommand::Query("175200000000".into())
);
assert_eq!(
parse_resume_command("/resume findings"),
ResumeCommand::Query("findings".into())
);
}
#[test]
fn resume_commands_parse_expected_actions() {
assert_eq!(parse_resume_command("/resume"), ResumeCommand::Browse);
assert_eq!(parse_resume_command("/resume "), ResumeCommand::Browse);
assert_eq!(parse_resume_command("/resume 3"), ResumeCommand::Select(3));
assert_eq!(
parse_resume_command("/resume tokio panic"),
ResumeCommand::Query("tokio panic".into())
);
assert_eq!(
parse_resume_command("/resume 175200000000"),
ResumeCommand::Query("175200000000".into())
);
}
#[test]
fn resume_browse_numbers_rows_and_marks_liveness() {
let (_state, _ws, mut store) = recall_test_store();
store.set_owner_for_test("host", "boot", 1);
let a = store.create("Alpha", None).unwrap();
let b = store.create("Bravo", None).unwrap();
store.set_liveness_for_test(|_, _| true);
store.claim(&b).unwrap();
let (msg, ids) = resume_browse_message(&store, &a).unwrap();
assert_eq!(ids, vec![b.clone(), a.clone()]);
assert!(msg.contains("1. ●"), "held conversation marked live: {msg}");
assert!(msg.contains("2. ▶"), "the active id marked current: {msg}");
assert!(msg.contains("Alpha") && msg.contains("Bravo"));
}
#[test]
fn resume_search_lists_one_row_per_conversation() {
let (_state, _ws, store) = recall_test_store();
let id = store.create("Parser work", None).unwrap();
store
.append_turn(&id, "fix the parser tokens", "done")
.unwrap();
store.append_turn(&id, "more parser tokens", "ok").unwrap();
let (msg, ids) = resume_search_message(&store, "parser", "other-active").unwrap();
assert_eq!(ids, vec![id]);
assert!(msg.contains("1. "), "numbered: {msg}");
assert!(msg.contains("Parser work"));
}