use crate::paint_probe::frame::Window;
use crate::paint_probe::{fills_of, seen_of};
use crate::reply::transcript::{Block, Entry, EntryKind, Transcript, Usage};
use crate::test_support::window::{numbered, said, seated};
use crate::ui::Model;
use crate::ui::theme;
fn entry(kind: EntryKind) -> Entry {
Entry {
name: "003-entry.json".to_owned(),
raw: "{}".to_owned(),
kind,
}
}
fn turn(model_id: &str, text: &str) -> Entry {
entry(EntryKind::Model {
model_id: model_id.to_owned(),
blocks: vec![Block::Text(text.to_owned())],
usage: Usage::new(),
})
}
fn tool(content: &str, is_error: bool) -> Entry {
entry(EntryKind::ToolResult {
tool_use_id: "tu-1".to_owned(),
content: content.to_owned(),
is_error,
})
}
fn glass(entries: Vec<Entry>) -> egui::FullOutput {
let mut model = Model {
transcript: Transcript { entries },
..seated()
};
let window = Window::sized(1400.0, 900.0);
window.frame(Vec::new(), |ctx| crate::ui::render(ctx, &mut model));
window.frame(Vec::new(), |ctx| crate::ui::render(ctx, &mut model))
}
fn run(output: &egui::FullOutput, header: &str) -> crate::paint_probe::Seen {
seen_of(output)
.into_iter()
.find(|seen| seen.text == header)
.unwrap_or_else(|| panic!("nothing on the glass reads {header:?}"))
}
fn beside(output: &egui::FullOutput, header: &str) -> Vec<egui::Color32> {
let head = run(output, header);
let (at, left) = (head.shown.center().y, head.laid.min.x);
fills_of(output)
.into_iter()
.filter(|(rect, _)| {
(rect.width() - theme::RULE).abs() < 0.5
&& rect.min.y <= at
&& at <= rect.max.y
&& rect.max.x <= left
&& left - rect.min.x < 40.0
})
.map(|(_, fill)| fill)
.collect()
}
#[test]
fn each_speaker_s_block_wears_its_own_weight() {
let ended = entry(EntryKind::Delivered {
sender: "child".to_owned(),
sender_name: None,
epitaph: Some("delivered".to_owned()),
body: "landed".to_owned(),
});
let output = glass(vec![
said("op", "port it"),
said("user", "and again"),
said("judge-one", "seconded"),
ended,
turn("model-a", "the seam is real"),
]);
for (header, weight) in [
("op", theme::speaker(theme::Speaker::Operator)),
("user", theme::speaker(theme::Speaker::Operator)),
("judge-one", theme::speaker(theme::Speaker::Peer)),
("child (delivered)", theme::speaker(theme::Speaker::Ended)),
("model-a", theme::speaker(theme::Speaker::Model)),
] {
assert!(
beside(&output, header).contains(&weight),
"{header:?} does not wear {weight:?}: {:?}",
beside(&output, header)
);
}
assert!(beside(&output, "op").contains(&theme::BRAND));
assert!(beside(&output, "model-a").contains(&theme::INK));
}
#[test]
fn a_failed_tool_result_wears_the_error_accent_and_a_returned_one_does_not() {
let red = theme::accent(theme::State::Error);
let output = glass(vec![tool("no such file", true)]);
assert_eq!(run(&output, "tu-1 failed").ink, red);
assert!(beside(&output, "tu-1 failed").contains(&red));
let returned = glass(vec![tool("done", false)]);
assert_ne!(run(&returned, "tu-1 returned").ink, red);
assert!(!beside(&returned, "tu-1 returned").contains(&red));
}
#[test]
fn a_folded_head_stands_in_weak_ink() {
let output = glass(vec![tool(&numbered(700), false)]);
let head = seen_of(&output)
.into_iter()
.find(|seen| seen.text.starts_with("line 001"))
.expect("the head of the answer is on the glass");
assert_eq!(head.ink, theme::INK_WEAK, "{:?}", head.text);
}
#[test]
fn entries_are_parted_by_air_and_never_by_a_line() {
let output = glass(vec![turn("model-a", "first"), turn("model-a", "second")]);
let rules: Vec<egui::Rect> = fills_of(&output)
.into_iter()
.filter(|(rect, fill)| {
(rect.width() - theme::RULE).abs() < 0.5
&& *fill == theme::speaker(theme::Speaker::Model)
})
.map(|(rect, _)| rect)
.collect();
assert_eq!(rules.len(), 2, "{rules:?}");
let gap = rules[1].min.y - rules[0].max.y;
assert!(gap >= theme::space::M - 0.5, "gap {gap}");
let first = run(&output, "first");
let dividers = fills_of(&output)
.into_iter()
.filter(|(rect, _)| {
rect.height() <= 1.5
&& rect.width() > 100.0
&& rect.min.y > first.laid.max.y
&& rect.max.y < rules[1].min.y
})
.count();
assert_eq!(dividers, 0, "no line between two entries");
assert!(
first.laid.height() >= theme::type_scale::LEADING - 0.5,
"{:?}",
first.laid
);
}