use abstracttui::prelude::*;
use abstracttui::app::{Driver, RunConfig}; use abstracttui::gfx::Bitmap; use abstracttui::term::Capabilities;
use abstracttui::testing::CaptureTerm; use abstracttui::testing::VtScreen;
use abstracttui::widgets::{Button, Checkbox, Image, List, TextInput};
struct Harness {
app: App,
term: CaptureTerm,
driver: Driver,
vt: VtScreen,
}
impl Harness {
fn boot(size: Size, component: impl FnOnce(Scope) -> View) -> Harness {
let mut term = CaptureTerm::new(size);
let mut app = App::new(size);
app.mount(component).expect("mount");
let cfg = RunConfig {
caps: Some(Capabilities::default()),
enter: None,
probe: false,
};
let driver = Driver::new(&mut app, &mut term, cfg).expect("driver enters");
let mut vt = VtScreen::new(size);
let mut h = Harness {
app,
term,
driver,
vt: VtScreen::new(size),
};
for _ in 0..8 {
let idle = h.driver.turn(&mut h.app, &mut h.term).expect("turn").idle;
vt.feed(&h.term.take_bytes());
if idle {
break;
}
}
h.vt = vt;
h
}
fn key(&mut self, bytes: &[u8]) {
self.term.push_input(bytes);
for _ in 0..6 {
let idle = self
.driver
.turn(&mut self.app, &mut self.term)
.expect("turn")
.idle;
self.vt.feed(&self.term.take_bytes());
if idle {
break;
}
}
}
fn text(&self) -> String {
self.vt.to_text()
}
}
#[test]
fn app_a_list_drives_detail_pane() {
let size = Size::new(60, 12);
let mut h = Harness::boot(size, |cx| {
let items = vec!["Alpha".to_string(), "Beta".to_string(), "Gamma".to_string()];
let selection = cx.signal(0usize);
let t = ¤t_theme().tokens;
let detail_items = items.clone();
let detail = dyn_view(LayoutStyle::line(1), move || {
let i = selection.get();
text(format!("PICKED:{}", detail_items[i]))
});
Element::new()
.style(LayoutStyle::column())
.child(
List::new(items)
.selection(selection)
.layout(LayoutStyle::line(4))
.element(cx, t)
.build(),
)
.child(detail)
.build()
});
let frame = h.text();
assert!(frame.contains("Alpha"), "list item not rendered:\n{frame}");
assert!(
frame.contains("PICKED:Alpha"),
"detail pane not driven by selection:\n{frame}"
);
h.key(b"\t");
h.key(b"\x1b[B"); let frame = h.text();
assert!(
frame.contains("PICKED:Beta") || frame.contains("PICKED:Gamma"),
"arrow-key selection did not drive the detail pane:\n{frame}"
);
}
#[test]
fn app_b_baseline_text_in_a_panel() {
let size = Size::new(40, 8);
let h = Harness::boot(size, |cx| {
let _ = cx;
let t = ¤t_theme().tokens;
Element::new()
.style(LayoutStyle::column())
.child(
Block::new()
.title("picture")
.layout(LayoutStyle::default().grow(1.0))
.child(text("inside the panel"))
.element(t)
.build(),
)
.build()
});
let frame = h.text();
assert!(
frame.contains("picture"),
"panel title missing (Block chrome broken):\n{frame}"
);
assert!(
frame.contains("inside the panel"),
"panel content missing:\n{frame}"
);
}
#[test]
fn app_b_image_in_a_panel() {
let size = Size::new(40, 16);
let h = Harness::boot(size, |cx| {
let _ = cx;
let t = ¤t_theme().tokens;
let bitmap = std::sync::Arc::new(Bitmap::new(8, 8, Rgba::rgb(120, 60, 200)));
Element::new()
.style(LayoutStyle::column())
.child(
Block::new()
.title("picture")
.layout(LayoutStyle::default().grow(1.0))
.child(
Image::from_bitmap(bitmap)
.layout(LayoutStyle::default().grow(1.0))
.element(t)
.build(),
)
.element(t)
.build(),
)
.build()
});
let frame = h.text();
assert!(
frame.contains("picture"),
"RT8-8: image overdrew the panel title:\n{frame}"
);
assert!(
frame.contains('┌') && frame.contains('┐'),
"RT8-8: image overdrew the top border:\n{frame}"
);
}
#[test]
fn app_c_themed_form_with_validation() {
let size = Size::new(50, 12);
let mut h = Harness::boot(size, |cx| {
let name = cx.signal(String::new());
let accept = cx.signal(false);
let error = cx.signal(String::new());
let t = ¤t_theme().tokens;
let submit = {
move || {
if name.get_untracked().is_empty() {
error.set("name is required".into());
} else if !accept.get_untracked() {
error.set("please accept the terms".into());
} else {
error.set("submitted!".into());
}
}
};
let err_view = dyn_view(LayoutStyle::line(1), move || text(error.get()));
Element::new()
.style(LayoutStyle::column())
.child(
TextInput::new()
.value(name)
.placeholder("name")
.element(cx, t)
.build(),
)
.child(
Checkbox::new("accept terms")
.checked(accept)
.element(cx, t)
.build(),
)
.child(
Button::new("submit")
.on_click(submit)
.element(cx, t)
.build(),
)
.child(err_view)
.build()
});
h.key(b"\t");
h.key(b"\t");
h.key(b"\t");
h.key(b"\r"); let frame = h.text();
assert!(
frame.contains("required") || frame.contains("accept") || frame.contains("submitted"),
"form validation message never rendered:\n{frame}"
);
}