use std::time::Duration;
use super::{still_on, tick};
use crate::state::Link;
use crate::test_support::Scratch;
use crate::test_support::wire::{flat, wired};
use crate::ui::{Aim, Channel, Chunk, Model};
use serde_json::json;
fn own() -> Channel {
Channel {
name: crate::seat::OWN.to_owned(),
named_there: None,
dials: None,
}
}
fn watching() -> Model {
Model {
roster: vec![Chunk::of(own())],
aim: Some(Aim {
channel: own().name,
address: "home".to_owned(),
}),
conversation: Some("20260830T051200Z-a1b2".to_owned()),
..Model::default()
}
}
fn linked(model: &Model) -> Link {
let link = Link::new(Duration::from_millis(1));
let mut model = model.clone();
link.settle(&mut model);
link
}
#[test]
fn every_frame_of_the_held_read_is_absorbed_onto_an_empty_fold() {
let scratch = Scratch::new();
let engine = wired(
&scratch,
&flat(),
vec![vec![
json!({"ok": true, "kind": "follow", "tools": [],
"stream": {"text": "half a ", "delta": "text"}}),
json!({"ok": true, "kind": "follow", "tools": [],
"stream": {"thinking": "hm. ", "delta": "thinking"}}),
json!({"ok": true, "kind": "follow", "tools": [],
"stream": {"text": "sentence", "delta": "text"}}),
]],
);
let mut model = watching();
let link = linked(&model);
tick(&link, scratch.path());
link.settle(&mut model);
let live = model.live.expect("the accumulated tail");
assert_eq!(live.text.as_deref(), Some("half a sentence"));
assert_eq!(
live.thinking.as_deref(),
Some("hm. "),
"the two kinds accrete apart, and a frame carrying only one leaves the \
other standing"
);
assert_eq!(
live.last_delta,
Some(crate::reply::stream::Delta::Text),
"the newer delta kind wins"
);
assert!(
engine
.heard()
.contains(&json!({"op": "follow", "workspace": "home",
"agent": "20260830T051200Z-a1b2"})),
"{:?}",
engine.heard()
);
}
#[test]
fn a_second_read_replaces_rather_than_appending() {
let scratch = Scratch::new();
let _engine = wired(
&scratch,
&flat(),
vec![
vec![json!({"ok": true, "kind": "follow", "tools": [],
"stream": {"text": "the whole answer", "delta": "text"}})],
vec![json!({"ok": true, "kind": "follow", "tools": [],
"stream": {"text": "the whole answer, and more", "delta": "text"}})],
],
);
let mut model = watching();
let link = linked(&model);
tick(&link, scratch.path());
link.settle(&mut model);
tick(&link, scratch.path());
link.settle(&mut model);
assert_eq!(
model.live.and_then(|s| s.text).as_deref(),
Some("the whole answer, and more"),
"the second read's first frame is the whole tail, absorbed onto nothing"
);
}
#[test]
fn the_read_is_about_a_wall_and_a_conversation_together() {
let model = watching();
let link = linked(&model);
let aim = model.aim.clone().expect("an aim");
let conversation = model.conversation.clone().expect("a conversation");
assert!(still_on(&link, &aim, &conversation));
assert!(!still_on(&link, &aim, "somebody-else"));
let elsewhere = Aim {
channel: own().name,
address: "other".to_owned(),
};
assert!(!still_on(&link, &elsewhere, &conversation));
let idle = linked(&Model::default());
assert!(!still_on(&idle, &aim, &conversation));
}
#[test]
fn a_tail_for_a_conversation_the_operator_left_is_dropped() {
let link = Link::new(Duration::from_millis(1));
link.live(
&own(),
"the-one-that-was-left",
crate::reply::read(
&json!({"ok": true, "kind": "follow", "tools": [], "stream": {"text": "stale"}}),
),
);
link.live(
&own(),
"20260830T051200Z-a1b2",
crate::reply::read(
&json!({"ok": true, "kind": "follow", "tools": [], "stream": {"text": "wanted"}}),
),
);
let mut model = watching();
link.settle(&mut model);
assert_eq!(
model.live.and_then(|s| s.text),
Some("wanted".to_owned()),
"only the tail about what is selected is filed"
);
}
#[test]
fn with_no_conversation_there_is_no_held_read() {
let scratch = Scratch::new();
let mut watching = watching();
watching.conversation = None;
for model in [Model::default(), watching] {
let link = linked(&model);
tick(&link, scratch.path());
let mut settled = model.clone();
link.settle(&mut settled);
assert_eq!(settled.notice, None);
assert_eq!(settled.live, None);
}
}
#[test]
fn a_lane_that_cannot_be_opened_says_so_as_unreachable() {
let scratch = Scratch::new();
let model = watching();
let link = linked(&model);
tick(&link, scratch.path());
let mut settled = model.clone();
link.settle(&mut settled);
let crate::ui::Held::Unheld(why) = &settled.roster[0].held else {
panic!("the lane said nothing: {:?}", settled.roster[0].held);
};
assert!(why.contains("no wire provisioned"), "{why}");
assert_eq!(settled.notice, None, "not the shell-wide bar (bl-e620)");
}
#[test]
fn a_held_read_that_answers_something_other_than_a_tail_crosses_untouched() {
let scratch = Scratch::new();
let _engine = wired(
&scratch,
&flat(),
vec![vec![
json!({"ok": true, "kind": "follow", "tools": [], "stream": {"text": "said. "}}),
json!({"ok": false, "error": "the driver is gone"}),
]],
);
let mut model = watching();
let link = linked(&model);
tick(&link, scratch.path());
link.settle(&mut model);
assert_eq!(
model.notice,
Some(crate::ui::Notice::Refused("the driver is gone".to_owned())),
"the engine's own words, not a fold"
);
assert_eq!(
model.live.and_then(|s| s.text).as_deref(),
Some("said. "),
"and what the tail had already accumulated is still standing"
);
}