use super::{Phase, Start};
use crate::reply::read;
use crate::test_support::window::{own, seated};
use crate::ui::{Aim, Model};
use serde_json::json;
fn staged(workspace: &str, goal: &str) -> crate::reply::Read {
read(&json!({"ok": true, "kind": "prepared",
"prepared": {"workspace": workspace, "goal": goal}}))
}
#[test]
fn a_start_is_two_acts_and_the_second_is_composed_from_the_first_s_answer() {
let mut model = Model {
conversation: None,
draft: "do the thing".to_owned(),
..seated()
};
model.stage("home");
assert_eq!(
model.outbox,
vec![crate::ui::Posted::act(crate::verbs::prepare(
"home".to_owned(),
None
))]
);
assert_eq!(model.draft, "", "what was staged is no longer a draft");
assert_eq!(
model.start.as_ref().map(|start| start.phase.clone()),
Some(Phase::Staging)
);
model.absorb(&own().channel, staged("home", ""));
assert_eq!(model.outbox.len(), 2, "the fire is composed, not posted");
assert_eq!(model.outbox[1].envelope["op"], json!("prompt"));
assert_eq!(model.outbox[1].envelope["goal"], json!("do the thing"));
model.absorb(
&own().channel,
read(&json!({"ok": true, "kind": "started", "conversation": "brisk-otter"})),
);
let start = model.start.as_ref().expect("the start");
assert_eq!(start.phase, Phase::Started("brisk-otter".to_owned()));
assert!(!start.outstanding());
assert_eq!(start.line(), "started «brisk-otter» in home");
}
#[test]
fn the_fire_goes_where_the_start_was_staged_even_if_the_aim_moved() {
let mut model = Model {
conversation: None,
draft: "do it".to_owned(),
..seated()
};
model.stage("home");
model.aim = Some(Aim {
channel: "elsewhere".to_owned(),
address: "other".to_owned(),
});
model.absorb(&own().channel, staged("home", ""));
assert_eq!(
model.outbox[1].envelope["prepared"]["workspace"],
json!("home")
);
}
#[test]
fn a_blank_goal_stages_nothing_and_keeps_what_was_typed() {
let mut model = Model {
draft: " ".to_owned(),
..Model::default()
};
model.stage("home");
assert!(model.outbox.is_empty());
assert_eq!(model.start, None);
assert_eq!(model.draft, " ", "the draft is still the operator's");
}
#[test]
fn a_body_nothing_staged_fires_on_its_own_terms_or_not_at_all() {
let mut model = Model::default();
model.absorb(&own().channel, staged("home", "the rung's own prefill"));
assert_eq!(model.outbox.len(), 1);
assert_eq!(
model.outbox[0].envelope["goal"],
json!("the rung's own prefill")
);
assert_eq!(
model.start.as_ref().map(|start| start.phase.clone()),
Some(Phase::Firing)
);
let mut bare = Model::default();
bare.absorb(&own().channel, staged("home", " "));
assert!(bare.outbox.is_empty(), "a blank goal never fires");
assert_eq!(bare.start, None);
}
#[test]
fn a_receipt_with_nothing_held_still_carries_the_minted_name() {
for (model, address) in [(Model::default(), ""), (seated(), "home")] {
let mut model = model;
model.absorb(
&own().channel,
read(&json!({"ok": true, "kind": "started", "conversation": "brisk-otter"})),
);
assert_eq!(
model.start,
Some(Start {
address: address.to_owned(),
goal: String::new(),
phase: Phase::Started("brisk-otter".to_owned()),
spread: None,
})
);
}
}
#[test]
fn a_start_in_flight_reads_the_same_whichever_act_is_out() {
let staging = Start {
address: "home".to_owned(),
goal: "do it".to_owned(),
phase: Phase::Staging,
spread: None,
};
let firing = Start {
phase: Phase::Firing,
..staging.clone()
};
assert_eq!(staging.line(), "starting in home: do it…");
assert_eq!(firing.line(), staging.line());
assert!(staging.outstanding() && firing.outstanding());
}
fn refused() -> crate::reply::Read {
read(&json!({"ok": false, "error": "sign in first: no provider holds a credential"}))
}
fn out(op: &str) -> Model {
let mut model = Model {
conversation: None,
draft: "do the thing".to_owned(),
..seated()
};
model.stage("home");
if op == crate::verbs::PROMPT {
model.absorb(&own().channel, staged("home", ""));
}
model
}
#[test]
fn a_refusal_of_either_act_retires_the_start_and_gives_the_goal_back() {
for op in [crate::verbs::PREPARE, crate::verbs::PROMPT] {
let mut model = out(op);
assert_eq!(model.draft, "", "{op}: the goal is held, not drafted");
model.receipt(&own().channel, op, refused());
let start = model.start.as_ref().expect("the start stands, refused");
assert_eq!(
start.phase,
Phase::Refused("sign in first: no provider holds a credential".to_owned()),
"{op}"
);
assert!(!start.outstanding(), "{op}: the box is back");
assert_eq!(
start.line(),
"not started in home: sign in first: no provider holds a credential"
);
assert_eq!(
model.draft, "do the thing",
"{op}: the goal is the operator's again"
);
assert_eq!(
model.notice, None,
"{op}: the sentence is the composer's, not the bar's"
);
}
}
#[test]
fn another_op_s_refusal_or_a_late_one_leaves_the_start_alone() {
let mut model = out(crate::verbs::PREPARE);
model.receipt(&own().channel, "nudge", refused());
assert_eq!(
model.start.as_ref().map(|start| start.phase.clone()),
Some(Phase::Staging)
);
assert_eq!(model.draft, "");
assert!(
model.notice.is_some(),
"somebody else's refusal is the bar's"
);
let mut landed = out(crate::verbs::PROMPT);
landed.absorb(
&own().channel,
read(&json!({"ok": true, "kind": "started", "conversation": "brisk-otter"})),
);
landed.receipt(&own().channel, crate::verbs::PROMPT, refused());
assert_eq!(
landed.start.as_ref().map(|start| start.phase.clone()),
Some(Phase::Started("brisk-otter".to_owned()))
);
assert!(landed.notice.is_some());
}
#[test]
fn an_unreadable_receipt_or_none_at_all_takes_the_start_back() {
let mut model = out(crate::verbs::PROMPT);
model.receipt(
&own().channel,
crate::verbs::PROMPT,
read(&json!({"ok": true, "kind": "a-kind-this-build-cannot-paint"})),
);
assert_eq!(model.start, None);
assert_eq!(model.draft, "do the thing");
assert!(matches!(
model.notice,
Some(crate::ui::Notice::Unreadable(_))
));
let mut model = out(crate::verbs::PREPARE);
model.acted(
crate::verbs::PREPARE,
&crate::channel::Reach::Unsent("no channel".to_owned()),
None,
);
assert_eq!(model.start, None);
assert_eq!(model.draft, "do the thing");
assert!(matches!(
model.notice,
Some(crate::ui::Notice::Unsent { .. })
));
let mut model = out(crate::verbs::PREPARE);
model.acted(
"nudge",
&crate::channel::Reach::Unsent("no channel".to_owned()),
None,
);
assert!(model.start.is_some());
assert_eq!(model.draft, "");
}
#[test]
fn the_refund_never_overwrites_a_draft_typed_elsewhere() {
let mut model = out(crate::verbs::PROMPT);
model.draft = "a deposit drafted meanwhile".to_owned();
model.receipt(&own().channel, crate::verbs::PROMPT, refused());
assert_eq!(model.draft, "a deposit drafted meanwhile");
assert!(matches!(
model.start.as_ref().map(|start| &start.phase),
Some(Phase::Refused(_))
));
}