2017-07-10 18:17:19 +08:00
|
|
|
extern crate mdbook;
|
2017-11-16 15:51:12 +08:00
|
|
|
#[macro_use]
|
|
|
|
extern crate pretty_assertions;
|
|
|
|
extern crate select;
|
2017-12-03 14:53:05 +08:00
|
|
|
extern crate tempdir;
|
2017-11-16 15:51:12 +08:00
|
|
|
extern crate walkdir;
|
|
|
|
|
|
|
|
mod dummy_book;
|
|
|
|
|
|
|
|
use dummy_book::{assert_contains_strings, DummyBook};
|
|
|
|
|
2017-11-18 21:22:30 +08:00
|
|
|
use std::fs::{remove_file, File};
|
2017-12-03 14:53:05 +08:00
|
|
|
use std::io::Write;
|
2017-11-16 15:51:12 +08:00
|
|
|
use std::path::Path;
|
|
|
|
use std::ffi::OsStr;
|
|
|
|
use walkdir::{DirEntry, WalkDir, WalkDirIterator};
|
|
|
|
use select::document::Document;
|
|
|
|
use select::predicate::{Class, Name, Predicate};
|
2017-12-03 14:53:05 +08:00
|
|
|
use tempdir::TempDir;
|
2017-11-16 15:51:12 +08:00
|
|
|
use mdbook::errors::*;
|
|
|
|
use mdbook::utils::fs::file_to_string;
|
2017-07-10 18:17:19 +08:00
|
|
|
use mdbook::MDBook;
|
|
|
|
|
|
|
|
|
2017-11-16 15:51:12 +08:00
|
|
|
const BOOK_ROOT: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/dummy_book");
|
2017-11-18 21:22:30 +08:00
|
|
|
const TOC_TOP_LEVEL: &[&'static str] = &[
|
|
|
|
"1. First Chapter",
|
|
|
|
"2. Second Chapter",
|
|
|
|
"Conclusion",
|
|
|
|
"Introduction",
|
|
|
|
];
|
2017-11-16 15:51:12 +08:00
|
|
|
const TOC_SECOND_LEVEL: &[&'static str] = &["1.1. Nested Chapter"];
|
|
|
|
|
2017-08-02 22:29:28 +08:00
|
|
|
/// Make sure you can load the dummy book and build it without panicking.
|
2017-07-10 18:17:19 +08:00
|
|
|
#[test]
|
|
|
|
fn build_the_dummy_book() {
|
2017-11-16 15:51:12 +08:00
|
|
|
let temp = DummyBook::new().build().unwrap();
|
2017-11-18 20:41:04 +08:00
|
|
|
let mut md = MDBook::load(temp.path()).unwrap();
|
2017-07-10 18:17:19 +08:00
|
|
|
|
|
|
|
md.build().unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn by_default_mdbook_generates_rendered_content_in_the_book_directory() {
|
2017-11-16 15:51:12 +08:00
|
|
|
let temp = DummyBook::new().build().unwrap();
|
2017-11-18 20:41:04 +08:00
|
|
|
let mut md = MDBook::load(temp.path()).unwrap();
|
2017-07-10 18:17:19 +08:00
|
|
|
|
|
|
|
assert!(!temp.path().join("book").exists());
|
|
|
|
md.build().unwrap();
|
|
|
|
|
|
|
|
assert!(temp.path().join("book").exists());
|
|
|
|
assert!(temp.path().join("book").join("index.html").exists());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn make_sure_bottom_level_files_contain_links_to_chapters() {
|
2017-11-16 15:51:12 +08:00
|
|
|
let temp = DummyBook::new().build().unwrap();
|
2017-11-18 20:41:04 +08:00
|
|
|
let mut md = MDBook::load(temp.path()).unwrap();
|
2017-07-10 18:17:19 +08:00
|
|
|
md.build().unwrap();
|
|
|
|
|
|
|
|
let dest = temp.path().join("book");
|
2017-11-18 21:22:30 +08:00
|
|
|
let links = vec![
|
|
|
|
r#"href="intro.html""#,
|
2017-11-18 22:16:35 +08:00
|
|
|
r#"href="first/index.html""#,
|
|
|
|
r#"href="first/nested.html""#,
|
|
|
|
r#"href="second.html""#,
|
|
|
|
r#"href="conclusion.html""#,
|
2017-11-18 21:22:30 +08:00
|
|
|
];
|
2017-07-10 18:17:19 +08:00
|
|
|
|
|
|
|
let files_in_bottom_dir = vec!["index.html", "intro.html", "second.html", "conclusion.html"];
|
|
|
|
|
|
|
|
for filename in files_in_bottom_dir {
|
2017-09-02 07:40:39 +08:00
|
|
|
assert_contains_strings(dest.join(filename), &links);
|
2017-07-10 18:17:19 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn check_correct_cross_links_in_nested_dir() {
|
2017-11-16 15:51:12 +08:00
|
|
|
let temp = DummyBook::new().build().unwrap();
|
2017-11-18 20:41:04 +08:00
|
|
|
let mut md = MDBook::load(temp.path()).unwrap();
|
2017-07-10 18:17:19 +08:00
|
|
|
md.build().unwrap();
|
|
|
|
|
|
|
|
let first = temp.path().join("book").join("first");
|
2017-11-18 21:22:30 +08:00
|
|
|
let links = vec![
|
|
|
|
r#"<base href="../">"#,
|
|
|
|
r#"href="intro.html""#,
|
2017-11-18 22:16:35 +08:00
|
|
|
r#"href="first/index.html""#,
|
|
|
|
r#"href="first/nested.html""#,
|
|
|
|
r#"href="second.html""#,
|
|
|
|
r#"href="conclusion.html""#,
|
2017-11-18 21:22:30 +08:00
|
|
|
];
|
2017-07-10 18:17:19 +08:00
|
|
|
|
|
|
|
let files_in_nested_dir = vec!["index.html", "nested.html"];
|
|
|
|
|
|
|
|
for filename in files_in_nested_dir {
|
2017-09-02 07:40:39 +08:00
|
|
|
assert_contains_strings(first.join(filename), &links);
|
2017-07-10 18:17:19 +08:00
|
|
|
}
|
2017-09-02 07:54:57 +08:00
|
|
|
|
2017-11-18 21:22:30 +08:00
|
|
|
assert_contains_strings(
|
|
|
|
first.join("index.html"),
|
|
|
|
&[
|
2017-11-18 22:16:35 +08:00
|
|
|
r##"href="first/index.html#some-section" id="some-section""##,
|
2017-11-18 21:22:30 +08:00
|
|
|
],
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_contains_strings(
|
|
|
|
first.join("nested.html"),
|
|
|
|
&[
|
2017-11-18 22:16:35 +08:00
|
|
|
r##"href="first/nested.html#some-section" id="some-section""##,
|
2017-11-18 21:22:30 +08:00
|
|
|
],
|
|
|
|
);
|
2017-07-10 18:17:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn rendered_code_has_playpen_stuff() {
|
2017-11-16 15:51:12 +08:00
|
|
|
let temp = DummyBook::new().build().unwrap();
|
2017-11-18 20:41:04 +08:00
|
|
|
let mut md = MDBook::load(temp.path()).unwrap();
|
2017-07-10 18:17:19 +08:00
|
|
|
md.build().unwrap();
|
|
|
|
|
|
|
|
let nested = temp.path().join("book/first/nested.html");
|
|
|
|
let playpen_class = vec![r#"class="playpen""#];
|
|
|
|
|
2017-09-02 07:40:39 +08:00
|
|
|
assert_contains_strings(nested, &playpen_class);
|
2017-07-10 18:17:19 +08:00
|
|
|
|
|
|
|
let book_js = temp.path().join("book/book.js");
|
2017-09-02 07:40:39 +08:00
|
|
|
assert_contains_strings(book_js, &[".playpen"]);
|
2017-07-10 18:17:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn chapter_content_appears_in_rendered_document() {
|
2017-11-18 21:22:30 +08:00
|
|
|
let content = vec![
|
|
|
|
("index.html", "Here's some interesting text"),
|
|
|
|
("second.html", "Second Chapter"),
|
|
|
|
("first/nested.html", "testable code"),
|
|
|
|
("first/index.html", "more text"),
|
|
|
|
("conclusion.html", "Conclusion"),
|
|
|
|
];
|
2017-07-10 18:17:19 +08:00
|
|
|
|
2017-11-16 15:51:12 +08:00
|
|
|
let temp = DummyBook::new().build().unwrap();
|
2017-11-18 20:41:04 +08:00
|
|
|
let mut md = MDBook::load(temp.path()).unwrap();
|
2017-07-10 18:17:19 +08:00
|
|
|
md.build().unwrap();
|
|
|
|
|
|
|
|
let destination = temp.path().join("book");
|
|
|
|
|
|
|
|
for (filename, text) in content {
|
|
|
|
let path = destination.join(filename);
|
2017-09-02 07:40:39 +08:00
|
|
|
assert_contains_strings(path, &[text]);
|
2017-07-10 18:17:19 +08:00
|
|
|
}
|
2017-09-02 07:40:39 +08:00
|
|
|
}
|
2017-11-16 15:51:12 +08:00
|
|
|
|
|
|
|
|
|
|
|
/// Apply a series of predicates to some root predicate, where each
|
|
|
|
/// successive predicate is the descendant of the last one. Similar to how you
|
|
|
|
/// might do `ul.foo li a` in CSS to access all anchor tags in the `foo` list.
|
|
|
|
macro_rules! descendants {
|
|
|
|
($root:expr, $($child:expr),*) => {
|
|
|
|
$root
|
|
|
|
$(
|
|
|
|
.descendant($child)
|
|
|
|
)*
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Make sure that all `*.md` files (excluding `SUMMARY.md`) were rendered
|
|
|
|
/// and placed in the `book` directory with their extensions set to `*.html`.
|
|
|
|
#[test]
|
|
|
|
fn chapter_files_were_rendered_to_html() {
|
|
|
|
let temp = DummyBook::new().build().unwrap();
|
|
|
|
let src = Path::new(BOOK_ROOT).join("src");
|
|
|
|
|
2017-11-18 21:22:30 +08:00
|
|
|
let chapter_files = WalkDir::new(&src)
|
|
|
|
.into_iter()
|
|
|
|
.filter_entry(|entry| entry_ends_with(entry, ".md"))
|
|
|
|
.filter_map(|entry| entry.ok())
|
|
|
|
.map(|entry| entry.path().to_path_buf())
|
|
|
|
.filter(|path| path.file_name().and_then(OsStr::to_str) != Some("SUMMARY.md"));
|
2017-11-16 15:51:12 +08:00
|
|
|
|
|
|
|
for chapter in chapter_files {
|
2017-11-18 21:22:30 +08:00
|
|
|
let rendered_location = temp.path()
|
|
|
|
.join(chapter.strip_prefix(&src).unwrap())
|
|
|
|
.with_extension("html");
|
|
|
|
assert!(
|
|
|
|
rendered_location.exists(),
|
|
|
|
"{} doesn't exits",
|
|
|
|
rendered_location.display()
|
|
|
|
);
|
2017-11-16 15:51:12 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn entry_ends_with(entry: &DirEntry, ending: &str) -> bool {
|
|
|
|
entry.file_name().to_string_lossy().ends_with(ending)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Read the main page (`book/index.html`) and expose it as a DOM which we
|
|
|
|
/// can search with the `select` crate
|
|
|
|
fn root_index_html() -> Result<Document> {
|
2017-11-18 21:22:30 +08:00
|
|
|
let temp = DummyBook::new()
|
|
|
|
.build()
|
|
|
|
.chain_err(|| "Couldn't create the dummy book")?;
|
|
|
|
MDBook::load(temp.path())?
|
|
|
|
.build()
|
|
|
|
.chain_err(|| "Book building failed")?;
|
2017-11-16 15:51:12 +08:00
|
|
|
|
|
|
|
let index_page = temp.path().join("book").join("index.html");
|
|
|
|
let html = file_to_string(&index_page).chain_err(|| "Unable to read index.html")?;
|
|
|
|
|
|
|
|
Ok(Document::from(html.as_str()))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn check_second_toc_level() {
|
|
|
|
let doc = root_index_html().unwrap();
|
|
|
|
let mut should_be = Vec::from(TOC_SECOND_LEVEL);
|
|
|
|
should_be.sort();
|
|
|
|
|
|
|
|
let pred = descendants!(Class("chapter"), Name("li"), Name("li"), Name("a"));
|
|
|
|
|
2017-11-18 21:22:30 +08:00
|
|
|
let mut children_of_children: Vec<_> = doc.find(pred)
|
|
|
|
.map(|elem| elem.text().trim().to_string())
|
|
|
|
.collect();
|
2017-11-16 15:51:12 +08:00
|
|
|
children_of_children.sort();
|
|
|
|
|
|
|
|
assert_eq!(children_of_children, should_be);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn check_first_toc_level() {
|
|
|
|
let doc = root_index_html().unwrap();
|
|
|
|
let mut should_be = Vec::from(TOC_TOP_LEVEL);
|
|
|
|
|
|
|
|
should_be.extend(TOC_SECOND_LEVEL);
|
|
|
|
should_be.sort();
|
|
|
|
|
|
|
|
let pred = descendants!(Class("chapter"), Name("li"), Name("a"));
|
|
|
|
|
2017-11-18 21:22:30 +08:00
|
|
|
let mut children: Vec<_> = doc.find(pred)
|
|
|
|
.map(|elem| elem.text().trim().to_string())
|
|
|
|
.collect();
|
2017-11-16 15:51:12 +08:00
|
|
|
children.sort();
|
|
|
|
|
|
|
|
assert_eq!(children, should_be);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn check_spacers() {
|
|
|
|
let doc = root_index_html().unwrap();
|
|
|
|
let should_be = 1;
|
|
|
|
|
2017-11-18 21:22:30 +08:00
|
|
|
let num_spacers = doc.find(Class("chapter").descendant(Name("li").and(Class("spacer"))))
|
|
|
|
.count();
|
2017-11-16 15:51:12 +08:00
|
|
|
assert_eq!(num_spacers, should_be);
|
|
|
|
}
|
2017-12-03 14:53:05 +08:00
|
|
|
|
|
|
|
/// Ensure building fails if `create-missing` is false and one of the files does
|
|
|
|
/// not exist.
|
|
|
|
#[test]
|
|
|
|
fn failure_on_missing_file() {
|
|
|
|
let (md, _temp) = create_missing_setup(Some(false));
|
|
|
|
|
|
|
|
// On failure, `build()` does not return a specific error, so assume
|
|
|
|
// any error is a failure due to a missing file.
|
|
|
|
assert!(md.read_config().unwrap().build().is_err());
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Ensure a missing file is created if `create-missing` is true.
|
|
|
|
#[test]
|
|
|
|
fn create_missing_file_with_config() {
|
|
|
|
let (md, temp) = create_missing_setup(Some(true));
|
|
|
|
|
|
|
|
md.read_config().unwrap().build().unwrap();
|
|
|
|
assert!(temp.path().join("src").join("intro.md").exists());
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Ensure a missing file is created if `create-missing` is not set (the default
|
|
|
|
/// is true).
|
|
|
|
#[test]
|
|
|
|
fn create_missing_file_without_config() {
|
|
|
|
let (md, temp) = create_missing_setup(None);
|
|
|
|
|
|
|
|
md.read_config().unwrap().build().unwrap();
|
|
|
|
assert!(temp.path().join("src").join("intro.md").exists());
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_missing_setup(create_missing: Option<bool>) -> (MDBook, TempDir) {
|
|
|
|
let temp = DummyBook::new().build().unwrap();
|
2017-11-18 20:41:04 +08:00
|
|
|
let md = MDBook::load(temp.path()).unwrap();
|
2017-12-03 14:53:05 +08:00
|
|
|
|
|
|
|
let mut file = File::create(temp.path().join("book.toml")).unwrap();
|
|
|
|
match create_missing {
|
|
|
|
Some(true) => file.write_all(b"[build]\ncreate-missing = true\n").unwrap(),
|
2017-11-18 21:22:30 +08:00
|
|
|
Some(false) => file.write_all(b"[build]\ncreate-missing = false\n")
|
|
|
|
.unwrap(),
|
2017-12-03 14:53:05 +08:00
|
|
|
None => (),
|
|
|
|
}
|
|
|
|
file.flush().unwrap();
|
|
|
|
|
|
|
|
remove_file(temp.path().join("src").join("intro.md")).unwrap();
|
|
|
|
|
|
|
|
(md, temp)
|
|
|
|
}
|