Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/gunnar

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}");
    }
}