extern crate mdbook; #[macro_use] extern crate pretty_assertions; extern crate select; extern crate tempdir; extern crate walkdir; mod dummy_book; use dummy_book::{assert_contains_strings, DummyBook}; use std::fs::{remove_file, File}; use std::io::Write; use std::path::Path; use std::ffi::OsStr; use walkdir::{DirEntry, WalkDir, WalkDirIterator}; use select::document::Document; use select::predicate::{Class, Name, Predicate}; use tempdir::TempDir; use mdbook::errors::*; use mdbook::utils::fs::file_to_string; use mdbook::MDBook; const BOOK_ROOT: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/dummy_book"); const TOC_TOP_LEVEL: &[&'static str] = &[ "1. First Chapter", "2. Second Chapter", "Conclusion", "Introduction", ]; const TOC_SECOND_LEVEL: &[&'static str] = &["1.1. Nested Chapter"]; /// Make sure you can load the dummy book and build it without panicking. #[test] fn build_the_dummy_book() { let temp = DummyBook::new().build().unwrap(); let mut md = MDBook::load(temp.path()).unwrap(); md.build().unwrap(); } #[test] fn by_default_mdbook_generates_rendered_content_in_the_book_directory() { let temp = DummyBook::new().build().unwrap(); let mut md = MDBook::load(temp.path()).unwrap(); 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() { let temp = DummyBook::new().build().unwrap(); let mut md = MDBook::load(temp.path()).unwrap(); md.build().unwrap(); let dest = temp.path().join("book"); let links = vec![ r#"href="intro.html""#, r#"href="first/index.html""#, r#"href="first/nested.html""#, r#"href="second.html""#, r#"href="conclusion.html""#, ]; let files_in_bottom_dir = vec!["index.html", "intro.html", "second.html", "conclusion.html"]; for filename in files_in_bottom_dir { assert_contains_strings(dest.join(filename), &links); } } #[test] fn check_correct_cross_links_in_nested_dir() { let temp = DummyBook::new().build().unwrap(); let mut md = MDBook::load(temp.path()).unwrap(); md.build().unwrap(); let first = temp.path().join("book").join("first"); let links = vec![ r#""#, r#"href="intro.html""#, r#"href="first/index.html""#, r#"href="first/nested.html""#, r#"href="second.html""#, r#"href="conclusion.html""#, ]; let files_in_nested_dir = vec!["index.html", "nested.html"]; for filename in files_in_nested_dir { assert_contains_strings(first.join(filename), &links); } assert_contains_strings( first.join("index.html"), &[ r##"href="first/index.html#some-section" id="some-section""##, ], ); assert_contains_strings( first.join("nested.html"), &[ r##"href="first/nested.html#some-section" id="some-section""##, ], ); } #[test] fn rendered_code_has_playpen_stuff() { let temp = DummyBook::new().build().unwrap(); let mut md = MDBook::load(temp.path()).unwrap(); md.build().unwrap(); let nested = temp.path().join("book/first/nested.html"); let playpen_class = vec![r#"class="playpen""#]; assert_contains_strings(nested, &playpen_class); let book_js = temp.path().join("book/book.js"); assert_contains_strings(book_js, &[".playpen"]); } #[test] fn chapter_content_appears_in_rendered_document() { 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"), ]; let temp = DummyBook::new().build().unwrap(); let mut md = MDBook::load(temp.path()).unwrap(); md.build().unwrap(); let destination = temp.path().join("book"); for (filename, text) in content { let path = destination.join(filename); assert_contains_strings(path, &[text]); } } /// 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"); 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")); for chapter in chapter_files { let rendered_location = temp.path() .join(chapter.strip_prefix(&src).unwrap()) .with_extension("html"); assert!( rendered_location.exists(), "{} doesn't exits", rendered_location.display() ); } } 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 { let temp = DummyBook::new() .build() .chain_err(|| "Couldn't create the dummy book")?; MDBook::load(temp.path())? .build() .chain_err(|| "Book building failed")?; 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")); let mut children_of_children: Vec<_> = doc.find(pred) .map(|elem| elem.text().trim().to_string()) .collect(); 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")); let mut children: Vec<_> = doc.find(pred) .map(|elem| elem.text().trim().to_string()) .collect(); children.sort(); assert_eq!(children, should_be); } #[test] fn check_spacers() { let doc = root_index_html().unwrap(); let should_be = 1; let num_spacers = doc.find(Class("chapter").descendant(Name("li").and(Class("spacer")))) .count(); assert_eq!(num_spacers, should_be); } /// 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) -> (MDBook, TempDir) { let temp = DummyBook::new().build().unwrap(); let md = MDBook::load(temp.path()).unwrap(); 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(), Some(false) => file.write_all(b"[build]\ncreate-missing = false\n") .unwrap(), None => (), } file.flush().unwrap(); remove_file(temp.path().join("src").join("intro.md")).unwrap(); (md, temp) }