mod dummy_book; use crate::dummy_book::{assert_contains_strings, DummyBook}; use anyhow::Context; use mdbook::config::Config; use mdbook::errors::*; use mdbook::MDBook; use pretty_assertions::assert_eq; use select::document::Document; use select::predicate::{Attr, Class, Name, Predicate}; use std::ffi::OsStr; use std::fs; use std::path::Path; use walkdir::{DirEntry, WalkDir}; const BOOK_ROOT: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/dummy_book"); const TOC_TOP_LEVEL: &[&str] = &[ "1. First Chapter", "2. Second Chapter", "Conclusion", "Dummy Book", "Introduction", ]; const TOC_SECOND_LEVEL: &[&str] = &[ "1.1. Nested Chapter", "1.2. Includes", "1.3. Recursive", "1.4. Markdown", "1.5. Unicode", "1.6. No Headers", "1.7. Duplicate Headers", "1.8. Heading Attributes", "2.1. Nested Chapter", ]; #[test] fn by_default_mdbook_generates_rendered_content_in_the_book_directory() { let temp = DummyBook::new().build().unwrap(); let md = MDBook::load(temp.path()).unwrap(); assert!(!temp.path().join("book").exists()); md.build().unwrap(); assert!(temp.path().join("book").exists()); let index_file = md.build_dir_for("html").join("index.html"); assert!(index_file.exists()); } #[test] fn check_correct_cross_links_in_nested_dir() { let temp = DummyBook::new().build().unwrap(); let md = MDBook::load(temp.path()).unwrap(); md.build().unwrap(); let first = temp.path().join("book").join("first"); assert_contains_strings( first.join("index.html"), &[r##"

"##], ); assert_contains_strings( first.join("nested.html"), &[r##"

"##], ); } #[test] fn chapter_content_appears_in_rendered_document() { let content = vec![ ("index.html", "This file is just here to cause the"), ("intro.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 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]); } } /// 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(std::result::Result::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 TOC (`book/toc.js`) nested HTML and expose it as a DOM which we /// can search with the `select` crate fn toc_js_html() -> Result { let temp = DummyBook::new() .build() .with_context(|| "Couldn't create the dummy book")?; MDBook::load(temp.path())? .build() .with_context(|| "Book building failed")?; let toc_path = temp.path().join("book").join("toc.js"); let html = fs::read_to_string(toc_path).with_context(|| "Unable to read index.html")?; for line in html.lines() { if let Some(left) = line.strip_prefix(" this.innerHTML = '") { if let Some(html) = left.strip_suffix("';") { return Ok(Document::from(html)); } } } panic!("cannot find toc in file") } /// Read the TOC fallback (`book/toc.html`) HTML and expose it as a DOM which we /// can search with the `select` crate fn toc_fallback_html() -> Result { let temp = DummyBook::new() .build() .with_context(|| "Couldn't create the dummy book")?; MDBook::load(temp.path())? .build() .with_context(|| "Book building failed")?; let toc_path = temp.path().join("book").join("toc.html"); let html = fs::read_to_string(toc_path).with_context(|| "Unable to read index.html")?; Ok(Document::from(html.as_str())) } // don't use target="_parent" in JS #[test] fn check_link_target_js() { let doc = toc_js_html().unwrap(); let num_parent_links = doc .find( Class("chapter") .descendant(Name("li")) .descendant(Name("a").and(Attr("target", "_parent"))), ) .count(); assert_eq!(num_parent_links, 0); } // don't use target="_parent" in IFRAME #[test] fn check_link_target_fallback() { let doc = toc_fallback_html().unwrap(); let num_parent_links = doc .find( Class("chapter") .descendant(Name("li")) .descendant(Name("a").and(Attr("target", "_parent"))), ) .count(); assert_eq!( num_parent_links, TOC_TOP_LEVEL.len() + TOC_SECOND_LEVEL.len() ); } #[test] fn example_book_can_build() { let example_book_dir = dummy_book::new_copy_of_example_book().unwrap(); let md = MDBook::load(example_book_dir.path()).unwrap(); md.build().unwrap(); } /// Checks formatting of summary names with inline elements. #[test] fn summary_with_markdown_formatting() { let temp = DummyBook::new().build().unwrap(); let mut cfg = Config::default(); cfg.set("book.src", "summary-formatting").unwrap(); let md = MDBook::load_with_config(temp.path(), cfg).unwrap(); md.build().unwrap(); let rendered_path = temp.path().join("book/toc.js"); assert_contains_strings( rendered_path, &[ r#" Italic code *escape* `escape2`"#, r#" Soft line break"#, r#" <escaped tag>"#, ], ); let generated_md = temp.path().join("summary-formatting/formatted-summary.md"); assert_eq!( fs::read_to_string(generated_md).unwrap(), "# Italic code *escape* `escape2`\n" ); let generated_md = temp.path().join("summary-formatting/soft.md"); assert_eq!( fs::read_to_string(generated_md).unwrap(), "# Soft line break\n" ); let generated_md = temp.path().join("summary-formatting/escaped-tag.md"); assert_eq!( fs::read_to_string(generated_md).unwrap(), "# <escaped tag>\n" ); }