xtask/tests/docs_assemble.rs
//! `cargo xtask docs`: the books are assembled from their `.nornir/` chapters, each
//! chapter's headings demoted under its own title, and a chapter that is missing or
//! blank stops the book (`xtask/src/docsbook.rs`).
use std::path::PathBuf;
use xtask::docsbook::{assemble, run, Book, Chapter, BOOKS};
static CHAPTERS: &[Chapter] = &[
Chapter {
source: "one.md",
title: "The first",
},
Chapter {
source: "sub/two.md",
title: "The second",
},
];
static BOOK: Book = Book {
name: "test",
out: "docs/test.pdf",
title: "A test book",
subtitle: "Two chapters, assembled.",
chapters: CHAPTERS,
};
struct Scratch(PathBuf);
impl Drop for Scratch {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
fn scratch(name: &str, one: Option<&str>, two: Option<&str>) -> Scratch {
let root = std::env::temp_dir().join(format!("gunnar-docs-{name}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(root.join(".nornir/sub")).unwrap();
if let Some(t) = one {
std::fs::write(root.join(".nornir/one.md"), t).unwrap();
}
if let Some(t) = two {
std::fs::write(root.join(".nornir/sub/two.md"), t).unwrap();
}
Scratch(root)
}
#[test]
fn a_book_is_its_chapters_in_order_under_their_own_titles() {
let s = scratch(
"ok",
Some("# One\n\nprose\n\n```sh\n# a comment, not a heading\n```\n"),
Some("## Two\n"),
);
let md = assemble(&s.0, &BOOK).unwrap();
assert!(
md.starts_with(
"<!-- nornir:class: readme -->\n\n# A test book\n\nTwo chapters, assembled.\n"
),
"{md}"
);
assert!(md.contains("Sources: `.nornir/one.md`, `.nornir/sub/two.md`. Regenerate with `cargo xtask docs test`."), "{md}");
let first = md.find("# The first\n").expect("the first title");
let second = md.find("# The second\n").expect("the second title");
assert!(first < second);
assert!(
md.contains("\n## One\n") && md.contains("\n### Two\n"),
"the chapters' headings are demoted: {md}"
);
assert!(
md.contains("\n# a comment, not a heading\n"),
"a fenced comment is not a heading: {md}"
);
}
#[test]
fn a_missing_or_blank_chapter_stops_the_book() {
let s = scratch("missing", Some("# One\n"), None);
let e = assemble(&s.0, &BOOK).unwrap_err();
assert!(e.starts_with("read ") && e.contains("two.md"), "{e}");
let s = scratch("blank", Some("# One\n"), Some(" \n\n"));
let e = assemble(&s.0, &BOOK).unwrap_err();
assert!(e.contains("refusing to ship a blank chapter"), "{e}");
}
/// Every declared book assembles from this tree, with every chapter title in it.
#[test]
fn every_declared_book_assembles_from_this_tree() {
let root = xtask::docsbook::workspace_root();
assert!(!BOOKS.is_empty());
for book in BOOKS {
let md = assemble(&root, book).unwrap_or_else(|e| panic!("{}: {e}", book.name));
for c in book.chapters {
assert!(
md.contains(&format!("\n# {}\n", c.title)),
"{}: no chapter {}",
book.name,
c.title
);
}
}
}
#[test]
fn an_unknown_book_is_refused_with_the_known_ones() {
let Err(e) = run(&["no-such-book".to_string()]) else {
panic!("an unknown book was built");
};
assert!(e.starts_with("no book named `no-such-book`; have: "), "{e}");
for b in BOOKS {
assert!(e.contains(b.name), "{e}");
}
}