use crate::transcript::{AutoExpand, Entry, EntryKind, Transcript};
use std::collections::HashSet;
const NARROW: (f32, f32) = (400.0, 120.0);
fn long() -> Transcript {
let entries = (0..40)
.map(|i| Entry {
name: format!("{i:03}-user.md"),
raw: Vec::new(),
kind: EntryKind::Delivered {
sender: "user".into(),
epitaph: None,
body: format!("line-{i:03}"),
},
})
.collect();
Transcript { entries }
}
fn settled(t: &Transcript) -> String {
let mut folds = HashSet::new();
crate::paint_probe::paint_settled(NARROW.0, NARROW.1, |ui| {
super::plain(ui, t, false, AutoExpand::default(), &mut folds);
})
}
fn settled_span(t: &Transcript) -> (f32, f32) {
let mut folds = HashSet::new();
let painted = crate::paint_probe::painted_settled(NARROW.0, NARROW.1, |ui| {
super::plain(ui, t, false, AutoExpand::default(), &mut folds);
});
crate::paint_probe::span(&painted)
}
fn underfull() -> Transcript {
Transcript {
entries: long().entries.into_iter().take(2).collect(),
}
}
fn event_frame(ctx: &egui::Context, t: &Transcript, events: Vec<egui::Event>) -> String {
let mut folds = HashSet::new();
let input = egui::RawInput {
events,
..crate::paint_probe::screen_sized(NARROW.0, NARROW.1)
};
let output = ctx.run(input, |ctx| {
egui::CentralPanel::default().show(ctx, |ui| {
super::plain(ui, t, false, AutoExpand::default(), &mut folds);
});
});
crate::paint_probe::text_of(&output)
}
fn wheel(dy: f32) -> Vec<egui::Event> {
vec![
egui::Event::PointerMoved(egui::pos2(20.0, NARROW.1 / 2.0)),
egui::Event::MouseWheel {
unit: egui::MouseWheelUnit::Point,
delta: egui::vec2(0.0, dy),
modifiers: egui::Modifiers::default(),
},
]
}
#[test]
fn scrolling_up_releases_the_tail_and_growth_does_not_recapture_it() {
let mut t = long();
let ctx = egui::Context::default();
let _ = event_frame(&ctx, &t, Vec::new());
let painted = event_frame(&ctx, &t, Vec::new());
assert!(painted.contains("line-039"), "settled on the tail");
let _ = event_frame(&ctx, &t, wheel(3000.0));
let mut painted = String::new();
for _ in 0..20 {
painted = event_frame(&ctx, &t, Vec::new());
}
assert!(
!painted.contains("line-039"),
"a scroll-up must leave the tail at once:\n{painted}"
);
t.entries.push(Entry {
name: "040-x.json".into(),
raw: Vec::new(),
kind: EntryKind::Streaming {
thinking: String::new(),
text: "fresh tail".into(),
},
});
painted = event_frame(&ctx, &t, Vec::new());
assert!(
!painted.contains("fresh tail"),
"growth must not recapture a released view:\n{painted}"
);
let _ = event_frame(&ctx, &t, wheel(-9000.0));
for _ in 0..20 {
painted = event_frame(&ctx, &t, Vec::new());
}
assert!(
painted.contains("fresh tail"),
"back on the tail:\n{painted}"
);
t.entries.push(Entry {
name: "041-x.json".into(),
raw: Vec::new(),
kind: EntryKind::Streaming {
thinking: String::new(),
text: "freshest tail".into(),
},
});
for _ in 0..8 {
painted = event_frame(&ctx, &t, Vec::new());
}
assert!(
painted.contains("freshest tail"),
"re-engaged: the view rides new rows again:\n{painted}"
);
}
#[test]
fn overflowing_transcript_shows_its_newest_rows_not_its_oldest() {
let painted = settled(&long());
assert!(
painted.contains("line-039"),
"newest row must be on screen:\n{painted}"
);
assert!(
!painted.contains("line-000"),
"oldest row must have scrolled off the top:\n{painted}"
);
}
#[test]
fn growing_transcript_keeps_the_new_tail_on_screen() {
let mut grown = long();
grown.entries.push(Entry {
name: "040-x.json".into(),
raw: Vec::new(),
kind: EntryKind::Streaming {
thinking: String::new(),
text: "partial output".into(),
},
});
let painted = settled(&grown);
assert!(painted.contains("partial output"), "got:\n{painted}");
assert!(!painted.contains("line-000"), "got:\n{painted}");
}
#[test]
fn a_transcript_that_fits_shows_everything_it_has() {
let painted = settled(&underfull());
assert!(painted.contains("line-000"), "got:\n{painted}");
assert!(painted.contains("line-001"), "got:\n{painted}");
}
#[test]
fn a_transcript_that_fits_still_sits_on_the_bottom_edge() {
let (_, overfull_bottom) = settled_span(&long());
let (_, bottom) = settled_span(&underfull());
assert!(
(bottom - overfull_bottom).abs() < 1.0,
"an underfull transcript must end on the same edge as a full one: \
{bottom} vs {overfull_bottom}"
);
}
#[test]
fn the_space_an_underfull_transcript_leaves_is_above_it() {
let (overfull_top, _) = settled_span(&long());
let (top, _) = settled_span(&underfull());
assert!(
top - overfull_top > 40.0,
"two rows must start well below where forty start: {top} vs {overfull_top}"
);
}