use std::{fs, path::Path};
fn read(relative: &str) -> String {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(relative);
fs::read_to_string(&path)
.unwrap_or_else(|error| panic!("cannot read {}: {error}", path.display()))
}
fn body_of<'a>(source: &'a str, header: &str) -> &'a str {
let start = source
.find(header)
.unwrap_or_else(|| panic!("no `{header}` in the source"));
let open = start
+ source[start..]
.find('{')
.unwrap_or_else(|| panic!("`{header}` has no body"));
let mut depth = 0_usize;
for (offset, ch) in source[open..].char_indices() {
match ch {
'{' => depth += 1,
'}' => {
depth -= 1;
if depth == 0 {
return &source[open + 1..open + offset];
}
}
_ => {}
}
}
panic!("`{header}` is never closed");
}
#[test]
fn every_way_into_a_step_fires_step_entered() {
let guide = read("ui/models/guide.slint");
for header in ["begin => {", "next => {", "back => {"] {
assert!(
body_of(&guide, header).contains("root.step_entered();"),
"`{header}` does not call root.step_entered(): the step it lands on would not \
clear its events, hold the original of a Build lesson or re-check its goal"
);
}
}
#[test]
fn back_marks_the_step_as_revisited_and_going_forward_clears_it() {
let guide = read("ui/models/guide.slint");
assert!(body_of(&guide, "back => {").contains("root.revisiting = true;"));
for header in ["begin => {", "next => {", "close => {"] {
assert!(
body_of(&guide, header).contains("root.revisiting = false;"),
"`{header}` leaves the guide marked as revisiting"
);
}
}
#[test]
fn a_revisited_step_does_not_advance_by_itself_and_offers_next() {
let panel = read("ui/components/guide_panel.slint");
assert!(
panel.contains("running: GuideModel.step_done && !GuideModel.revisiting;"),
"the advance timer must stop while the learner re-reads a step"
);
assert!(
panel.contains("enabled: (!GuideModel.step_done || GuideModel.revisiting)"),
"the Next button must stay usable on a revisited step that is done"
);
assert!(panel.contains("text: GuideModel.offers_next ?"));
}
#[test]
fn next_on_an_unfinished_step_performs_it_and_waits_for_the_result() {
let guide = read("ui/models/guide.slint");
let press = body_of(&guide, "go_on => {");
assert!(press.contains("root.perform(root.step_index);"));
assert!(press.contains("root.perform_busy = true;"));
assert!(
press.contains("root.next();"),
"a step without a recipe still skips"
);
for header in ["begin => {", "next => {", "back => {", "close => {"] {
assert!(
body_of(&guide, header).contains("root.reset_perform();"),
"`{header}` leaves a recipe's progress behind for the next step"
);
}
let panel = read("ui/components/guide_panel.slint");
assert!(panel.contains("clicked => { GuideModel.go_on(); }"));
assert!(panel.contains("&& !GuideModel.perform_busy;"));
assert!(
panel.contains("running: GuideModel.perform_busy;"),
"the overlay layer ticks while a recipe waits for its goal"
);
}
#[test]
fn the_body_finder_reads_nested_braces() {
let source =
"callback a();\n a => {\n if x { y(); }\n z();\n }\n b => { w(); }";
assert_eq!(
body_of(source, "a => {").trim(),
"if x { y(); }\n z();"
);
assert_eq!(body_of(source, "b => {").trim(), "w();");
}