mod reading;
use super::Follower;
use crate::app::tests::{Harness, Rig};
use crate::git_tree::{Delta, Stream};
use crate::watch::Repaint;
use std::io::Write;
use std::path::Path;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
pub(super) const AGENT: &str = "c-1";
pub(super) fn text_delta(fragment: &str) -> String {
format!(
"{{\"type\":\"content_delta\",\"index\":0,\"delta\":{{\"text_delta\":\"{fragment}\"}}}}\n"
)
}
pub(super) fn thinking_delta(fragment: &str) -> String {
format!(
"{{\"type\":\"content_delta\",\"index\":0,\"delta\":{{\"thinking_delta\":\"{fragment}\"}}}}\n"
)
}
pub(super) fn response(h: &Harness, agent: &str, seq: u32) -> std::path::PathBuf {
let step =
h.fx.path
.join("steps")
.join(agent)
.join(format!("{seq:03}"));
std::fs::create_dir_all(&step).expect("step dir");
step.join("response.json")
}
pub(super) fn append(path: &Path, bytes: &str) {
let mut file = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(path)
.expect("open response");
file.write_all(bytes.as_bytes()).expect("append");
}
pub(super) fn painted(rig: &Rig, ws: &Path, agent: &str) -> Stream {
rig.tree(ws)
.and_then(|t| t.agents.iter().find(|a| a.agent_id == agent).cloned())
.map(|a| a.stream)
.unwrap_or_default()
}
fn derived(rig: &Rig, ws: &Path, agent: &str) -> Stream {
rig.model
.derivation()
.trees
.get(ws)
.and_then(|t| t.agents.iter().find(|a| a.agent_id == agent).cloned())
.map(|a| a.stream)
.unwrap_or_default()
}
pub(super) fn rigged(h: &Harness) -> (Rig, Follower) {
let (_clock, mut rig) = h.model();
let follower = rig.follower();
rig.focus_agent(&h.ws, AGENT);
rig.refresh();
(rig, follower)
}
pub(super) struct CountingRepaint(pub(super) Arc<AtomicUsize>);
impl Repaint for CountingRepaint {
fn request(&self) {
self.0.fetch_add(1, Ordering::Relaxed);
}
}
#[test]
fn bytes_appended_to_the_open_step_file_are_on_the_next_frame_with_no_derivation() {
let h = Harness::new();
let (mut rig, mut follower) = rigged(&h);
let file = response(&h, AGENT, 1);
append(&file, &text_delta("the first "));
assert!(follower.pass(), "the first bytes are news");
rig.refresh();
assert_eq!(
painted(&rig, &h.ws, AGENT).text.as_deref(),
Some("the first "),
"the follower's fold is what the frame paints"
);
let pinned = Arc::clone(rig.model.derivation());
append(&file, &text_delta("half."));
assert!(follower.pass(), "the append is seen");
rig.refresh();
assert_eq!(
painted(&rig, &h.ws, AGENT).text.as_deref(),
Some("the first half."),
"every character that landed is on the frame"
);
assert!(
Arc::ptr_eq(&pinned, rig.model.derivation()),
"and no derivation ran to put it there — this is the whole claim"
);
}
#[test]
fn the_derivation_the_gestures_read_is_untouched_by_the_tail() {
let h = Harness::new();
let (mut rig, mut follower) = rigged(&h);
append(&response(&h, AGENT, 1), &text_delta("only in RAM"));
follower.pass();
rig.refresh();
assert_eq!(
derived(&rig, &h.ws, AGENT),
Stream::default(),
"the derivation has not read the file, and the tail did not tell it"
);
assert_eq!(
painted(&rig, &h.ws, AGENT).text.as_deref(),
Some("only in RAM"),
"while the painted snapshot carries it"
);
}
#[test]
fn reasoning_streams_too_and_carries_the_doing_split_with_it() {
let h = Harness::new();
let (mut rig, mut follower) = rigged(&h);
let file = response(&h, AGENT, 1);
append(&file, &thinking_delta("first I "));
follower.pass();
rig.refresh();
let stream = painted(&rig, &h.ws, AGENT);
assert_eq!(stream.thinking.as_deref(), Some("first I "));
assert_eq!(stream.text, None, "no answer yet, and none invented");
assert_eq!(stream.last_delta, Some(Delta::Thinking));
append(&file, &thinking_delta("check the refs"));
assert!(follower.pass());
rig.refresh();
assert_eq!(
painted(&rig, &h.ws, AGENT).thinking.as_deref(),
Some("first I check the refs")
);
append(&file, &text_delta("here goes"));
assert!(follower.pass());
rig.refresh();
let stream = painted(&rig, &h.ws, AGENT);
assert_eq!(stream.text.as_deref(), Some("here goes"));
assert_eq!(stream.last_delta, Some(Delta::Text));
}