facett-docview/tests/robot.rs
//! **Robot manual-viewer scenario for `DocView`** (`egui_kittest`).
//!
//! Renders the REAL [`facett_docview::DocView`] body over a synthetic 3-page SVG
//! book (each page a real `<text>` word, so the pages carry a genuine searchable
//! text layer) and asserts — off the live AccessKit tree + the viewer's
//! `state_json` — that every premium-reader capability reaches the surface a robot
//! (or a screen reader) sees, matching the markdown Manual pane's capability set:
//!
//! 1. **Sidebar navigation** — the table-of-contents entries paint as clickable
//! labels, and clicking one sets the target page (the same `JumpTo` a TOC click
//! emits) — proven by an AccessKit click driving `current_page`.
//! 2. **Continuous scroll** — with the whole short book in a tall viewport, the
//! continuous mode reports ≥2 pages' regions rasterized in one scroll view.
//! 3. **Paged mode** — toggling to paged reports exactly the current page visible
//! (one page at a time), and the mode is reflected in `state_json`.
//! 4. **Search** — a query over the real text layer finds the known word on the
//! pages that carry it, and next-hit navigation lands on a hit page. A book
//! with no text layer would honestly report `search_available:false` (covered
//! by the crate's unit tests).
//! 5. No crash: the whole viewer body runs through the harness without panicking.
//!
//! RED-when-broken: neutralizing the sidebar, the continuous band, the paged
//! single-page path, or the search index turns an assertion red.
use std::sync::{Arc, Mutex};
use egui_kittest::kittest::Queryable;
use egui_kittest::Harness;
use facett_docview::{DocPage, DocView, Facet};
/// A 1-page SVG carrying a single real `<text>` word — a searchable text layer.
fn word_page(word: &str) -> String {
format!(
r#"<svg xmlns="http://www.w3.org/2000/svg" width="200" height="80" viewBox="0 0 200 80">
<rect x="0" y="0" width="200" height="80" fill="white"/>
<text x="10" y="55" font-size="32" fill="black">{word}</text>
</svg>"#
)
}
/// A 3-page book with real text (alpha / beta / gamma-beta) + a TOC per page.
fn book() -> DocView {
let pages = vec![
DocPage::from_svg_bytes(word_page("alpha").as_bytes()).unwrap(),
DocPage::from_svg_bytes(word_page("beta").as_bytes()).unwrap(),
DocPage::from_svg_bytes(word_page("gamma beta").as_bytes()).unwrap(),
];
let toc = vec![
("Alpha".to_string(), 0usize),
("Beta".to_string(), 1usize),
("Gamma".to_string(), 2usize),
];
DocView::new("robot manual", pages, toc)
}
#[test]
fn docview_renders_sidebar_continuous_paged_and_search() {
let view = Arc::new(Mutex::new(book()));
let mut harness = Harness::builder().with_size(egui::vec2(760.0, 900.0)).build_ui({
let view = Arc::clone(&view);
move |ui| {
Facet::ui(&mut *view.lock().unwrap(), ui);
}
});
// A frame — proves the whole viewer body paints without panicking (no crash).
harness.run();
let seen = |h: &Harness, label: &str| h.query_all_by_label(label).count() >= 1;
// (1) Sidebar navigation: every TOC entry paints as a label …
let toc_all_shown = seen(&harness, "Alpha") && seen(&harness, "Beta") && seen(&harness, "Gamma");
assert!(toc_all_shown, "the TOC sidebar lists every page's entry");
// (2) Continuous scroll: the short book fits a tall viewport → ≥2 pages'
// regions rasterize in one scroll view.
let visible_continuous = view.lock().unwrap().state_json()["visible_pages"]
.as_array()
.unwrap()
.len();
assert!(
visible_continuous >= 2,
"continuous mode rasterizes ≥2 pages in one view, got {visible_continuous}"
);
// (1b) A TOC CLICK sets the target page (the JumpTo a click emits).
harness.get_by_label("Gamma").click();
harness.run();
let after_click = view.lock().unwrap().state_json()["current_page"].as_u64().unwrap();
assert_eq!(after_click, 2, "clicking the 'Gamma' TOC entry jumped to page 3 (index 2)");
// (4) Search over the real text layer: "beta" is on pages 1 and 2 (0-based).
{
let mut g = view.lock().unwrap();
assert!(g.search_available(), "the book carries a searchable text layer");
g.set_query("beta");
}
harness.run();
let hits: Vec<u64> = view.lock().unwrap().state_json()["search_hits"]
.as_array()
.unwrap()
.iter()
.map(|v| v.as_u64().unwrap())
.collect();
assert_eq!(hits, vec![1, 2], "search found the 'beta' pages");
// Next-hit navigation targets the first hit page. Asserted on the model right
// after the message (continuous mode then RE-derives current_page as the
// centremost visible page on the next render — expected for a short book in a
// tall viewport — so we assert the navigation before that re-derivation).
{
let mut g = view.lock().unwrap();
g.next_hit();
assert_eq!(g.current_page(), 1, "next-hit navigated to the first 'beta' page");
assert_eq!(g.search_hits(), &[1, 2]);
}
// (3) Paged mode: toggle, then exactly the current page is visible.
view.lock().unwrap().clear_search();
view.lock().unwrap().toggle_mode();
harness.run();
let s = view.lock().unwrap().state_json();
assert_eq!(s["mode"], "paged", "mode toggled to paged");
let visible_paged = s["visible_pages"].as_array().unwrap();
assert_eq!(visible_paged.len(), 1, "paged mode shows exactly one page");
// testmatrix cell — facett_docview has SVG-book parity with the Markdown viewer
// (sidebar TOC, paged/continuous, text search); surfaces in the dwarves testmatrix.
facett_core::testmatrix::emit(
"facett-docview",
"docview_renders_sidebar_continuous_paged_and_search",
toc_all_shown && visible_continuous >= 2 && hits == vec![1, 2] && visible_paged.len() == 1,
&format!(
"SVG docview: TOC listed, {visible_continuous} pages in continuous, search 'beta' hit {hits:?}, paged shows 1"
),
);
}