use std::cell::RefCell;
use std::rc::Rc;
use abstracttui::app::{App, Driver, RunConfig};
use abstracttui::base::{Point, Size};
use abstracttui::testing::CaptureTerm;
use abstracttui::theme::TokenSet;
use abstracttui::ui::StyledCanvas;
use abstracttui::widgets::{FenceBlock, MarkdownView, Scroll};
use abstracttui::prelude::*;
const VIEWPORT: Size = Size::new(100, 30);
#[derive(Default)]
struct CountingFence {
widths: RefCell<Vec<i32>>,
}
impl FenceBlock for CountingFence {
fn measure(&self, lang: &str, _source: &str, width: i32) -> Option<i32> {
if lang != "counted" {
return None; }
self.widths.borrow_mut().push(width);
Some(3)
}
fn draw_row(
&self,
_lang: &str,
_source: &str,
_row: i32,
_at: Point,
_width: i32,
_tokens: &TokenSet,
_canvas: &mut dyn StyledCanvas,
) {
}
}
fn config() -> RunConfig {
RunConfig {
enter: None,
probe: false,
..RunConfig::default()
}
}
fn body() -> String {
let mut s = String::from("```counted\nblock\n```\n\n");
for i in 0..40 {
s.push_str(&format!(
"Paragraph {i} of a document with enough words on the line to wrap at a \
typical terminal width and land on more than one row.\n\n"
));
}
s
}
fn flex_beside_fixed(inner: abstracttui::ui::View) -> abstracttui::ui::View {
Element::new()
.style(LayoutStyle::row().width(Dimension::Percent(1.0)))
.child(
Element::new()
.style(
LayoutStyle::column()
.width(Dimension::Auto)
.height(Dimension::Auto),
)
.child(inner)
.build(),
)
.child(
Element::new()
.style(LayoutStyle::default().width(Dimension::Cells(20)))
.build(),
)
.build()
}
fn widths_asked(split: bool) -> Vec<i32> {
let fence: Rc<CountingFence> = Rc::new(CountingFence::default());
let mut term = CaptureTerm::new(VIEWPORT);
let mut app = App::new(VIEWPORT);
let f = fence.clone();
app.mount(move |cx| {
let view = MarkdownView::new(body()).fence_block(f.clone()).view(cx);
let inner = if split { flex_beside_fixed(view) } else { view };
Scroll::new(inner).view(cx)
})
.expect("mount");
let mut driver = Driver::new(&mut app, &mut term, config()).expect("driver");
let mut turns = 0;
while turns < 256 {
turns += 1;
if driver.turn(&mut app, &mut term).expect("turn").idle {
break;
}
}
assert!(turns < 256, "shape split={split} never reached idle");
let out = fence.widths.borrow().clone();
assert!(
!out.is_empty(),
"the fence was never asked to measure in shape split={split} — the observer is not \
wired, so every count below would read zero for the wrong reason"
);
out
}
#[test]
fn the_two_slot_typeset_cache_serves_both_widths_a_split_solve_asks_at() {
let control = widths_asked(false);
let split = widths_asked(true);
let distinct = |v: &[i32]| {
let mut d: Vec<i32> = v.to_vec();
d.sort_unstable();
d.dedup();
d
};
let (c_widths, s_widths) = (distinct(&control), distinct(&split));
eprintln!(
" control (widths agree) {} typesets, distinct {c_widths:?}, in order {control:?}",
control.len()
);
eprintln!(
" flex-beside-fixed {} typesets, distinct {s_widths:?}, in order {split:?}",
split.len()
);
assert_eq!(
c_widths.len(),
1,
"the width-agreeing control was itself queried at {c_widths:?}. It is supposed to be \
the arm where the solver asks once, so nothing below distinguishes the shapes and \
this file is measuring something other than the width split"
);
assert!(
s_widths.len() > 1,
"the flex shape was queried at only {s_widths:?}. The two-width split is what this \
file is built on and `commons#532` measured it at 99 and 82 — if the solver now \
asks once, the cache below is being tested against a workload that no longer \
exercises it, and this arm should be rewritten around the new behaviour"
);
let repeats = split.len() - s_widths.len();
assert_eq!(
repeats,
0,
"the flex shape typeset {} times across only {} distinct widths ({split:?}) — some \
width was laid out twice, so the cache is being evicted between the queries again. \
Recorded as [99, 82, 99] before the fix and [99, 82] after. Read \
`markdown.rs::WidthCache` — most likely it has gone back to one slot, or its miss \
path is evicting the most-recently-used entry instead of the older one.",
split.len(),
s_widths.len()
);
assert!(
split.len() > control.len(),
"the flex shape typeset {} times against the control's {} — parity. The two-width \
solve is what makes this shape cost more at all, so parity means the SOLVER \
changed, not the cache. Verify against `commons#532` before quoting this file.",
split.len(),
control.len()
);
assert_eq!(
control.len() - c_widths.len(),
0,
"the width-AGREEING control also re-computed a width it had already done \
({control:?}). Then the repeat asserted above is not attributable to the width \
split, and the two-entry conclusion drawn from it does not follow"
);
}