2017-11-16 15:51:12 +08:00
#[ macro_use ]
extern crate pretty_assertions ;
mod dummy_book ;
2019-05-26 02:50:41 +08:00
use crate ::dummy_book ::{ assert_contains_strings , assert_doesnt_contain_strings , DummyBook } ;
2017-11-16 15:51:12 +08:00
2020-05-21 05:32:00 +08:00
use anyhow ::Context ;
2018-07-24 01:45:01 +08:00
use mdbook ::config ::Config ;
use mdbook ::errors ::* ;
2019-06-20 10:49:18 +08:00
use mdbook ::utils ::fs ::write_file ;
2018-07-24 01:45:01 +08:00
use mdbook ::MDBook ;
use select ::document ::Document ;
use select ::predicate ::{ Class , Name , Predicate } ;
2020-05-27 02:35:15 +08:00
use std ::collections ::HashMap ;
2018-07-24 01:45:01 +08:00
use std ::ffi ::OsStr ;
2017-12-10 20:13:46 +08:00
use std ::fs ;
2022-06-23 04:55:52 +08:00
use std ::io ::{ Read , Write } ;
2020-05-30 04:15:24 +08:00
use std ::path ::{ Component , Path , PathBuf } ;
2022-03-26 14:34:07 +08:00
use std ::str ::FromStr ;
2018-03-27 07:47:37 +08:00
use tempfile ::Builder as TempFileBuilder ;
2018-07-24 01:45:01 +08:00
use walkdir ::{ DirEntry , WalkDir } ;
2017-07-10 18:17:19 +08:00
2019-05-07 02:20:58 +08:00
const BOOK_ROOT : & str = concat! ( env! ( " CARGO_MANIFEST_DIR " ) , " /tests/dummy_book " ) ;
const TOC_TOP_LEVEL : & [ & str ] = & [
2017-11-18 21:22:30 +08:00
" 1. First Chapter " ,
" 2. Second Chapter " ,
" Conclusion " ,
2018-07-26 01:45:20 +08:00
" Dummy Book " ,
2017-11-18 21:22:30 +08:00
" Introduction " ,
] ;
2019-05-07 02:20:58 +08:00
const TOC_SECOND_LEVEL : & [ & str ] = & [
2019-05-05 22:57:43 +08:00
" 1.1. Nested Chapter " ,
" 1.2. Includes " ,
" 1.3. Recursive " ,
2019-06-12 23:02:03 +08:00
" 1.4. Markdown " ,
2019-07-13 00:53:11 +08:00
" 1.5. Unicode " ,
2021-09-01 03:41:49 +08:00
" 1.6. No Headers " ,
2022-02-18 23:27:24 +08:00
" 1.7. Duplicate Headers " ,
2019-06-12 23:02:03 +08:00
" 2.1. Nested Chapter " ,
2019-05-05 22:57:43 +08:00
] ;
2017-11-16 15:51:12 +08:00
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 ( ) ;
2018-01-07 22:10:48 +08:00
let 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 ( ) ;
2018-01-07 22:10:48 +08:00
let 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 ( ) ) ;
2018-01-22 06:44:28 +08:00
let index_file = md . build_dir_for ( " html " ) . join ( " index.html " ) ;
assert! ( index_file . exists ( ) ) ;
2017-07-10 18:17:19 +08:00
}
#[ 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 ( ) ;
2018-01-07 22:10:48 +08:00
let 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 ( ) ;
2018-01-07 22:10:48 +08:00
let 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! [
2018-07-11 21:33:44 +08:00
r # "href="../intro.html""# ,
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 " ) ,
2021-02-13 08:37:07 +08:00
& [ r ## "<h2 id="some-section"><a class="header" href="#some-section">"## ] ,
2017-11-18 21:22:30 +08:00
) ;
assert_contains_strings (
first . join ( " nested.html " ) ,
2021-02-13 08:37:07 +08:00
& [ r ## "<h2 id="some-section"><a class="header" href="#some-section">"## ] ,
2017-11-18 21:22:30 +08:00
) ;
2017-07-10 18:17:19 +08:00
}
2019-01-15 04:49:23 +08:00
#[ test ]
fn check_correct_relative_links_in_print_page ( ) {
let temp = DummyBook ::new ( ) . build ( ) . unwrap ( ) ;
let md = MDBook ::load ( temp . path ( ) ) . unwrap ( ) ;
md . build ( ) . unwrap ( ) ;
let first = temp . path ( ) . join ( " book " ) ;
assert_contains_strings (
first . join ( " print.html " ) ,
2019-05-09 05:50:59 +08:00
& [
r ## "<a href="second/../first/nested.html">the first section</a>,"## ,
r ## "<a href="second/../../std/foo/bar.html">outside</a>"## ,
r ## "<img src="second/../images/picture.png" alt="Some image" />"## ,
2019-07-01 23:52:25 +08:00
r ## "<a href="second/nested.html#some-section">fragment link</a>"## ,
r ## "<a href="second/../first/markdown.html">HTML Link</a>"## ,
r ## "<img src="second/../images/picture.png" alt="raw html">"## ,
2019-05-09 05:50:59 +08:00
] ,
2019-01-15 04:49:23 +08:00
) ;
}
2017-07-10 18:17:19 +08:00
#[ test ]
2020-06-22 22:34:25 +08:00
fn rendered_code_has_playground_stuff ( ) {
2017-11-16 15:51:12 +08:00
let temp = DummyBook ::new ( ) . build ( ) . unwrap ( ) ;
2018-01-07 22:10:48 +08:00
let 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 " ) ;
2020-06-22 22:34:25 +08:00
let playground_class = vec! [ r # "class="playground""# ] ;
2017-07-10 18:17:19 +08:00
2020-06-22 22:34:25 +08:00
assert_contains_strings ( nested , & playground_class ) ;
2017-07-10 18:17:19 +08:00
let book_js = temp . path ( ) . join ( " book/book.js " ) ;
2020-06-22 22:34:25 +08:00
assert_contains_strings ( book_js , & [ " .playground " ] ) ;
2017-07-10 18:17:19 +08:00
}
2022-03-26 14:34:07 +08:00
#[ test ]
fn rendered_code_does_not_have_playground_stuff_in_html_when_disabled_in_config ( ) {
let temp = DummyBook ::new ( ) . build ( ) . unwrap ( ) ;
let config = Config ::from_str (
"
[ output . html . playground ]
runnable = false
" ,
)
. unwrap ( ) ;
let md = MDBook ::load_with_config ( temp . path ( ) , config ) . unwrap ( ) ;
md . build ( ) . unwrap ( ) ;
let nested = temp . path ( ) . join ( " book/first/nested.html " ) ;
let playground_class = vec! [ r # "class="playground""# ] ;
assert_doesnt_contain_strings ( nested , & playground_class ) ;
}
2019-10-06 06:27:03 +08:00
#[ test ]
fn anchors_include_text_between_but_not_anchor_comments ( ) {
let temp = DummyBook ::new ( ) . build ( ) . unwrap ( ) ;
let md = MDBook ::load ( temp . path ( ) ) . unwrap ( ) ;
md . build ( ) . unwrap ( ) ;
let nested = temp . path ( ) . join ( " book/first/nested.html " ) ;
let text_between_anchors = vec! [ " unique-string-for-anchor-test " ] ;
let anchor_text = vec! [ " ANCHOR " ] ;
assert_contains_strings ( nested . clone ( ) , & text_between_anchors ) ;
assert_doesnt_contain_strings ( nested , & anchor_text ) ;
}
#[ test ]
fn rustdoc_include_hides_the_unspecified_part_of_the_file ( ) {
let temp = DummyBook ::new ( ) . build ( ) . unwrap ( ) ;
let md = MDBook ::load ( temp . path ( ) ) . unwrap ( ) ;
md . build ( ) . unwrap ( ) ;
let nested = temp . path ( ) . join ( " book/first/nested.html " ) ;
2019-10-16 17:27:14 +08:00
let text = vec! [
" <span class= \" boring \" >fn some_function() { " ,
" <span class= \" boring \" >fn some_other_function() { " ,
] ;
2019-10-06 06:27:03 +08:00
assert_contains_strings ( nested , & text ) ;
}
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! [
2018-07-26 01:45:20 +08:00
( " index.html " , " This file is just here to cause the " ) ,
( " intro.html " , " Here's some interesting text " ) ,
2017-11-18 21:22:30 +08:00
( " 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 ( ) ;
2018-01-07 22:10:48 +08:00
let 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 " ) )
2019-05-07 02:20:58 +08:00
. filter_map ( std ::result ::Result ::ok )
2017-11-18 21:22:30 +08:00
. 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 {
2018-08-03 09:22:49 +08:00
let rendered_location = temp
. path ( )
2017-11-18 21:22:30 +08:00
. 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 ( )
2020-05-21 05:32:00 +08:00
. with_context ( | | " Couldn't create the dummy book " ) ? ;
2017-11-18 21:22:30 +08:00
MDBook ::load ( temp . path ( ) ) ?
. build ( )
2020-05-21 05:32:00 +08:00
. with_context ( | | " Book building failed " ) ? ;
2017-11-16 15:51:12 +08:00
let index_page = temp . path ( ) . join ( " book " ) . join ( " index.html " ) ;
2020-05-21 05:32:00 +08:00
let html = fs ::read_to_string ( & index_page ) . with_context ( | | " Unable to read index.html " ) ? ;
2017-11-16 15:51:12 +08:00
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 ) ;
2021-08-24 15:45:06 +08:00
should_be . sort_unstable ( ) ;
2017-11-16 15:51:12 +08:00
2019-10-19 15:56:08 +08:00
let pred = descendants! (
Class ( " chapter " ) ,
Name ( " li " ) ,
Name ( " li " ) ,
Name ( " a " ) . and ( Class ( " toggle " ) . not ( ) )
) ;
2017-11-16 15:51:12 +08:00
2018-08-03 09:22:49 +08:00
let mut children_of_children : Vec < _ > = doc
. find ( pred )
2017-11-18 21:22:30 +08:00
. 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 ) ;
2021-08-24 15:45:06 +08:00
should_be . sort_unstable ( ) ;
2017-11-16 15:51:12 +08:00
2019-10-19 15:56:08 +08:00
let pred = descendants! (
Class ( " chapter " ) ,
Name ( " li " ) ,
Name ( " a " ) . and ( Class ( " toggle " ) . not ( ) )
) ;
2017-11-16 15:51:12 +08:00
2018-08-03 09:22:49 +08:00
let mut children : Vec < _ > = doc
. find ( pred )
2017-11-18 21:22:30 +08:00
. 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 ( ) ;
2019-10-25 23:33:21 +08:00
let should_be = 2 ;
2017-11-16 15:51:12 +08:00
2018-08-03 09:22:49 +08:00
let num_spacers = doc
. find ( Class ( " chapter " ) . descendant ( Name ( " li " ) . and ( Class ( " spacer " ) ) ) )
2017-11-18 21:22:30 +08:00
. 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 ( ) {
2017-12-11 07:32:35 +08:00
let temp = DummyBook ::new ( ) . build ( ) . unwrap ( ) ;
fs ::remove_file ( temp . path ( ) . join ( " src " ) . join ( " intro.md " ) ) . unwrap ( ) ;
let mut cfg = Config ::default ( ) ;
cfg . build . create_missing = false ;
2017-12-03 14:53:05 +08:00
2017-12-11 07:32:35 +08:00
let got = MDBook ::load_with_config ( temp . path ( ) , cfg ) ;
assert! ( got . is_err ( ) ) ;
2017-12-03 14:53:05 +08:00
}
/// Ensure a missing file is created if `create-missing` is true.
#[ test ]
fn create_missing_file_with_config ( ) {
2017-11-18 23:21:59 +08:00
let temp = DummyBook ::new ( ) . build ( ) . unwrap ( ) ;
2017-12-10 20:13:46 +08:00
fs ::remove_file ( temp . path ( ) . join ( " src " ) . join ( " intro.md " ) ) . unwrap ( ) ;
2017-11-18 23:21:59 +08:00
2017-12-11 07:32:35 +08:00
let mut cfg = Config ::default ( ) ;
cfg . build . create_missing = true ;
assert! ( ! temp . path ( ) . join ( " src " ) . join ( " intro.md " ) . exists ( ) ) ;
let _md = MDBook ::load_with_config ( temp . path ( ) , cfg ) . unwrap ( ) ;
assert! ( temp . path ( ) . join ( " src " ) . join ( " intro.md " ) . exists ( ) ) ;
2017-12-03 14:53:05 +08:00
}
2020-06-22 22:34:25 +08:00
/// This makes sure you can include a Rust file with `{{#playground example.rs}}`.
2020-09-23 09:16:09 +08:00
/// Specification is in `guide/src/format/rust.md`
2017-11-18 23:21:59 +08:00
#[ test ]
2020-06-22 22:34:25 +08:00
fn able_to_include_playground_files_in_chapters ( ) {
2017-12-03 14:53:05 +08:00
let temp = DummyBook ::new ( ) . build ( ) . unwrap ( ) ;
2018-01-07 22:10:48 +08:00
let md = MDBook ::load ( temp . path ( ) ) . unwrap ( ) ;
2017-11-18 23:21:59 +08:00
md . build ( ) . unwrap ( ) ;
2017-12-03 14:53:05 +08:00
2017-11-18 23:21:59 +08:00
let second = temp . path ( ) . join ( " book/second.html " ) ;
2017-12-03 14:53:05 +08:00
2020-06-22 22:34:25 +08:00
let playground_strings = & [
r # "class="playground""# ,
2017-11-18 23:21:59 +08:00
r # "println!("Hello World!");"# ,
] ;
2018-01-22 06:44:28 +08:00
2020-06-22 22:34:25 +08:00
assert_contains_strings ( & second , playground_strings ) ;
assert_doesnt_contain_strings ( & second , & [ " {{#playground example.rs}} " ] ) ;
2018-01-22 06:44:28 +08:00
}
/// This makes sure you can include a Rust file with `{{#include ../SUMMARY.md}}`.
#[ test ]
fn able_to_include_files_in_chapters ( ) {
let temp = DummyBook ::new ( ) . build ( ) . unwrap ( ) ;
let md = MDBook ::load ( temp . path ( ) ) . unwrap ( ) ;
md . build ( ) . unwrap ( ) ;
let includes = temp . path ( ) . join ( " book/first/includes.html " ) ;
2019-06-03 20:31:15 +08:00
let summary_strings = & [
2021-02-13 08:37:07 +08:00
r ## "<h1 id="summary"><a class="header" href="#summary">Summary</a></h1>"## ,
2019-06-03 20:31:15 +08:00
" >First Chapter</a> " ,
] ;
2018-01-22 06:44:28 +08:00
assert_contains_strings ( & includes , summary_strings ) ;
assert_doesnt_contain_strings ( & includes , & [ " {{#include ../SUMMARY.md::}} " ] ) ;
2017-12-03 14:53:05 +08:00
}
2017-12-04 15:38:57 +08:00
2018-05-20 18:36:19 +08:00
/// Ensure cyclic includes are capped so that no exceptions occur
#[ test ]
fn recursive_includes_are_capped ( ) {
let temp = DummyBook ::new ( ) . build ( ) . unwrap ( ) ;
let md = MDBook ::load ( temp . path ( ) ) . unwrap ( ) ;
md . build ( ) . unwrap ( ) ;
let recursive = temp . path ( ) . join ( " book/first/recursive.html " ) ;
let content = & [ " Around the world, around the world
Around the world , around the world
Around the world , around the world " ];
assert_contains_strings ( & recursive , content ) ;
}
2017-12-04 15:38:57 +08:00
#[ test ]
fn example_book_can_build ( ) {
2017-12-10 20:13:46 +08:00
let example_book_dir = dummy_book ::new_copy_of_example_book ( ) . unwrap ( ) ;
2017-12-04 15:38:57 +08:00
2018-01-07 22:10:48 +08:00
let md = MDBook ::load ( example_book_dir . path ( ) ) . unwrap ( ) ;
2017-12-04 15:38:57 +08:00
2018-01-07 22:10:48 +08:00
md . build ( ) . unwrap ( ) ;
2017-12-10 20:13:46 +08:00
}
2018-01-07 00:02:23 +08:00
#[ test ]
fn book_with_a_reserved_filename_does_not_build ( ) {
2018-03-27 07:47:37 +08:00
let tmp_dir = TempFileBuilder ::new ( ) . prefix ( " mdBook " ) . tempdir ( ) . unwrap ( ) ;
2018-01-07 00:02:23 +08:00
let src_path = tmp_dir . path ( ) . join ( " src " ) ;
fs ::create_dir ( & src_path ) . unwrap ( ) ;
let summary_path = src_path . join ( " SUMMARY.md " ) ;
let print_path = src_path . join ( " print.md " ) ;
fs ::File ::create ( print_path ) . unwrap ( ) ;
let mut summary_file = fs ::File ::create ( summary_path ) . unwrap ( ) ;
writeln! ( summary_file , " [print](print.md) " ) . unwrap ( ) ;
2018-01-08 00:25:45 +08:00
let md = MDBook ::load ( tmp_dir . path ( ) ) . unwrap ( ) ;
2018-01-07 00:02:23 +08:00
let got = md . build ( ) ;
assert! ( got . is_err ( ) ) ;
}
2018-03-07 21:02:06 +08:00
2018-05-04 19:41:28 +08:00
#[ test ]
fn by_default_mdbook_use_index_preprocessor_to_convert_readme_to_index ( ) {
let temp = DummyBook ::new ( ) . build ( ) . unwrap ( ) ;
let mut cfg = Config ::default ( ) ;
2018-07-24 01:45:01 +08:00
cfg . set ( " book.src " , " src2 " )
. expect ( " Couldn't set config.book.src to \" src2 \" . " ) ;
2018-05-04 19:41:28 +08:00
let md = MDBook ::load_with_config ( temp . path ( ) , cfg ) . unwrap ( ) ;
md . build ( ) . unwrap ( ) ;
2018-07-24 01:45:01 +08:00
let first_index = temp . path ( ) . join ( " book " ) . join ( " first " ) . join ( " index.html " ) ;
2018-05-04 19:41:28 +08:00
let expected_strings = vec! [
2018-07-11 21:33:44 +08:00
r # "href="../first/index.html""# ,
r # "href="../second/index.html""# ,
2018-05-04 19:41:28 +08:00
" First README " ,
] ;
assert_contains_strings ( & first_index , & expected_strings ) ;
2019-05-07 02:20:58 +08:00
assert_doesnt_contain_strings ( & first_index , & [ " README.html " ] ) ;
2018-05-04 19:41:28 +08:00
2018-07-24 01:45:01 +08:00
let second_index = temp . path ( ) . join ( " book " ) . join ( " second " ) . join ( " index.html " ) ;
let unexpected_strings = vec! [ " Second README " ] ;
2018-05-04 19:41:28 +08:00
assert_doesnt_contain_strings ( & second_index , & unexpected_strings ) ;
}
2022-06-23 04:55:52 +08:00
#[ test ]
fn first_chapter_is_copied_as_index_even_if_not_first_elem ( ) {
let temp = DummyBook ::new ( ) . build ( ) . unwrap ( ) ;
let mut cfg = Config ::default ( ) ;
cfg . set ( " book.src " , " index_html_test " )
. expect ( " Couldn't set config.book.src to \" index_html_test \" " ) ;
let md = MDBook ::load_with_config ( temp . path ( ) , cfg ) . unwrap ( ) ;
md . build ( ) . unwrap ( ) ;
// In theory, just reading the entire files into memory and comparing is sufficient for *testing*,
// but since the files are temporary and get deleted when the test completes, we'll want to print
// the differences on failure.
// We could invoke `diff` on the files on failure, but that may not be portable (hi, Windows...)
// so we'll do the job ourselves—potentially a bit sloppily, but that can always be piped into
// `diff` manually afterwards.
let book_path = temp . path ( ) . join ( " book " ) ;
let read_file = | path : & str | {
let mut buf = String ::new ( ) ;
fs ::File ::open ( book_path . join ( path ) )
. with_context ( | | format! ( " Failed to read {} " , path ) )
. unwrap ( )
. read_to_string ( & mut buf )
. unwrap ( ) ;
buf
} ;
pretty_assertions ::assert_eq! ( read_file ( " chapter_1.html " ) , read_file ( " index.html " ) ) ;
}
2018-05-15 03:52:29 +08:00
#[ test ]
fn theme_dir_overrides_work_correctly ( ) {
let book_dir = dummy_book ::new_copy_of_example_book ( ) . unwrap ( ) ;
let book_dir = book_dir . path ( ) ;
let theme_dir = book_dir . join ( " theme " ) ;
2019-05-31 00:12:33 +08:00
let mut index = mdbook ::theme ::INDEX . to_vec ( ) ;
2018-05-15 03:52:29 +08:00
index . extend_from_slice ( b " \n <!-- This is a modified index.hbs! --> " ) ;
write_file ( & theme_dir , " index.hbs " , & index ) . unwrap ( ) ;
let md = MDBook ::load ( book_dir ) . unwrap ( ) ;
md . build ( ) . unwrap ( ) ;
let built_index = book_dir . join ( " book " ) . join ( " index.html " ) ;
dummy_book ::assert_contains_strings ( built_index , & [ " This is a modified index.hbs! " ] ) ;
}
2019-05-08 06:32:43 +08:00
#[ test ]
fn no_index_for_print_html ( ) {
let temp = DummyBook ::new ( ) . build ( ) . unwrap ( ) ;
let md = MDBook ::load ( temp . path ( ) ) . unwrap ( ) ;
md . build ( ) . unwrap ( ) ;
let print_html = temp . path ( ) . join ( " book/print.html " ) ;
assert_contains_strings ( print_html , & [ r ## "noindex"## ] ) ;
let index_html = temp . path ( ) . join ( " book/index.html " ) ;
assert_doesnt_contain_strings ( index_html , & [ r ## "noindex"## ] ) ;
}
2019-06-12 23:02:03 +08:00
#[ test ]
fn markdown_options ( ) {
let temp = DummyBook ::new ( ) . build ( ) . unwrap ( ) ;
let md = MDBook ::load ( temp . path ( ) ) . unwrap ( ) ;
md . build ( ) . unwrap ( ) ;
let path = temp . path ( ) . join ( " book/first/markdown.html " ) ;
assert_contains_strings (
& path ,
& [
" <th>foo</th> " ,
" <th>bar</th> " ,
" <td>baz</td> " ,
" <td>bim</td> " ,
] ,
) ;
2020-01-01 08:23:25 +08:00
assert_contains_strings (
& path ,
& [
r ## "<sup class="footnote-reference"><a href="#1">1</a></sup>"## ,
r ## "<sup class="footnote-reference"><a href="#word">2</a></sup>"## ,
r ## "<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>"## ,
r ## "<div class="footnote-definition" id="word"><sup class="footnote-definition-label">2</sup>"## ,
] ,
) ;
2019-06-12 23:02:03 +08:00
assert_contains_strings ( & path , & [ " <del>strikethrough example</del> " ] ) ;
assert_contains_strings (
& path ,
& [
" <li><input disabled= \" \" type= \" checkbox \" checked= \" \" /> \n Apples " ,
" <li><input disabled= \" \" type= \" checkbox \" checked= \" \" /> \n Broccoli " ,
" <li><input disabled= \" \" type= \" checkbox \" /> \n Carrots " ,
] ,
) ;
}
2020-05-27 02:35:15 +08:00
#[ test ]
fn redirects_are_emitted_correctly ( ) {
let temp = DummyBook ::new ( ) . build ( ) . unwrap ( ) ;
let mut md = MDBook ::load ( temp . path ( ) ) . unwrap ( ) ;
// override the "outputs.html.redirect" table
let redirects : HashMap < PathBuf , String > = vec! [
2020-05-30 04:11:11 +08:00
( PathBuf ::from ( " /overview.html " ) , String ::from ( " index.html " ) ) ,
2020-05-27 02:35:15 +08:00
(
2020-05-30 04:11:11 +08:00
PathBuf ::from ( " /nexted/page.md " ) ,
2020-05-27 02:35:15 +08:00
String ::from ( " https://rust-lang.org/ " ) ,
) ,
]
. into_iter ( )
. collect ( ) ;
md . config . set ( " output.html.redirect " , & redirects ) . unwrap ( ) ;
md . build ( ) . unwrap ( ) ;
for ( original , redirect ) in & redirects {
2020-05-30 04:15:24 +08:00
let mut redirect_file = md . build_dir_for ( " html " ) ;
// append everything except the bits that make it absolute
// (e.g. "/" or "C:\")
2021-08-24 15:45:06 +08:00
redirect_file . extend ( remove_absolute_components ( original ) ) ;
2020-05-27 02:35:15 +08:00
let contents = fs ::read_to_string ( & redirect_file ) . unwrap ( ) ;
assert! ( contents . contains ( redirect ) ) ;
}
}
2021-05-28 12:07:35 +08:00
#[ test ]
fn edit_url_has_default_src_dir_edit_url ( ) {
let temp = DummyBook ::new ( ) . build ( ) . unwrap ( ) ;
let book_toml = r #"
[ book ]
title = " implicit "
[ output . html ]
edit - url - template = " https://github.com/rust-lang/mdBook/edit/master/guide/{path} "
" #;
2021-08-24 15:45:06 +08:00
write_file ( temp . path ( ) , " book.toml " , book_toml . as_bytes ( ) ) . unwrap ( ) ;
2021-05-28 12:07:35 +08:00
let md = MDBook ::load ( temp . path ( ) ) . unwrap ( ) ;
md . build ( ) . unwrap ( ) ;
let index_html = temp . path ( ) . join ( " book " ) . join ( " index.html " ) ;
assert_contains_strings (
index_html ,
2021-08-24 15:48:24 +08:00
& [
2021-05-28 12:07:35 +08:00
r # "href="https://github.com/rust-lang/mdBook/edit/master/guide/src/README.md" title="Suggest an edit""# ,
] ,
) ;
}
#[ test ]
fn edit_url_has_configured_src_dir_edit_url ( ) {
let temp = DummyBook ::new ( ) . build ( ) . unwrap ( ) ;
let book_toml = r #"
[ book ]
title = " implicit "
src = " src2 "
[ output . html ]
edit - url - template = " https://github.com/rust-lang/mdBook/edit/master/guide/{path} "
" #;
2021-08-24 15:45:06 +08:00
write_file ( temp . path ( ) , " book.toml " , book_toml . as_bytes ( ) ) . unwrap ( ) ;
2021-05-28 12:07:35 +08:00
let md = MDBook ::load ( temp . path ( ) ) . unwrap ( ) ;
md . build ( ) . unwrap ( ) ;
let index_html = temp . path ( ) . join ( " book " ) . join ( " index.html " ) ;
assert_contains_strings (
index_html ,
2021-08-24 15:48:24 +08:00
& [
2021-05-28 12:07:35 +08:00
r # "href="https://github.com/rust-lang/mdBook/edit/master/guide/src2/README.md" title="Suggest an edit""# ,
] ,
) ;
}
2020-05-30 04:15:24 +08:00
fn remove_absolute_components ( path : & Path ) -> impl Iterator < Item = Component > + '_ {
path . components ( ) . skip_while ( | c | match c {
Component ::Prefix ( _ ) | Component ::RootDir = > true ,
_ = > false ,
} )
}
2022-04-15 11:35:39 +08:00
/// 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/formatted-summary.html " ) ;
assert_contains_strings (
rendered_path ,
& [
r # "<a href="formatted-summary.html" class="active"><strong aria-hidden="true">1.</strong> Italic code *escape* `escape2`</a>"# ,
r # "<a href="soft.html"><strong aria-hidden="true">2.</strong> Soft line break</a>"# ,
r # "<a href="escaped-tag.html"><strong aria-hidden="true">3.</strong> <escaped tag></a>"# ,
] ,
) ;
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 "
) ;
}
2022-04-28 13:13:58 +08:00
/// Ensure building fails if `[output.html].theme` points to a non-existent directory
#[ test ]
fn failure_on_missing_theme_directory ( ) {
// 1. Using default theme should work
let temp = DummyBook ::new ( ) . build ( ) . unwrap ( ) ;
let book_toml = r #"
[ book ]
title = " implicit "
src = " src "
" #;
write_file ( temp . path ( ) , " book.toml " , book_toml . as_bytes ( ) ) . unwrap ( ) ;
let md = MDBook ::load ( temp . path ( ) ) . unwrap ( ) ;
let got = md . build ( ) ;
assert! ( got . is_ok ( ) ) ;
// 2. Pointing to a normal directory should work
let temp = DummyBook ::new ( ) . build ( ) . unwrap ( ) ;
let created = fs ::create_dir ( temp . path ( ) . join ( " theme-directory " ) ) ;
assert! ( created . is_ok ( ) ) ;
let book_toml = r #"
[ book ]
title = " implicit "
src = " src "
[ output . html ]
theme = " ./theme-directory "
" #;
write_file ( temp . path ( ) , " book.toml " , book_toml . as_bytes ( ) ) . unwrap ( ) ;
let md = MDBook ::load ( temp . path ( ) ) . unwrap ( ) ;
let got = md . build ( ) ;
assert! ( got . is_ok ( ) ) ;
// 3. Pointing to a non-existent directory should fail
let temp = DummyBook ::new ( ) . build ( ) . unwrap ( ) ;
let book_toml = r #"
[ book ]
title = " implicit "
src = " src "
[ output . html ]
theme = " ./non-existent-directory "
" #;
write_file ( temp . path ( ) , " book.toml " , book_toml . as_bytes ( ) ) . unwrap ( ) ;
let md = MDBook ::load ( temp . path ( ) ) . unwrap ( ) ;
let got = md . build ( ) ;
assert! ( got . is_err ( ) ) ;
}
2018-03-07 21:02:06 +08:00
#[ cfg(feature = " search " ) ]
mod search {
2019-05-26 02:50:41 +08:00
use crate ::dummy_book ::DummyBook ;
2018-03-07 21:02:06 +08:00
use mdbook ::MDBook ;
2019-06-20 10:49:18 +08:00
use std ::fs ::{ self , File } ;
2018-07-24 01:45:01 +08:00
use std ::path ::Path ;
2018-03-07 21:02:06 +08:00
fn read_book_index ( root : & Path ) -> serde_json ::Value {
let index = root . join ( " book/searchindex.js " ) ;
2019-06-20 10:49:18 +08:00
let index = fs ::read_to_string ( index ) . unwrap ( ) ;
2019-05-08 06:29:46 +08:00
let index = index . trim_start_matches ( " Object.assign(window.search, " ) ;
let index = index . trim_end_matches ( " ); " ) ;
2021-08-24 15:45:06 +08:00
serde_json ::from_str ( index ) . unwrap ( )
2018-03-07 21:02:06 +08:00
}
#[ test ]
2019-05-07 02:20:58 +08:00
#[ allow(clippy::float_cmp) ]
2018-03-07 21:02:06 +08:00
fn book_creates_reasonable_search_index ( ) {
let temp = DummyBook ::new ( ) . build ( ) . unwrap ( ) ;
let md = MDBook ::load ( temp . path ( ) ) . unwrap ( ) ;
md . build ( ) . unwrap ( ) ;
let index = read_book_index ( temp . path ( ) ) ;
2018-06-14 04:15:58 +08:00
let doc_urls = index [ " doc_urls " ] . as_array ( ) . unwrap ( ) ;
2018-07-24 01:45:01 +08:00
let get_doc_ref =
| url : & str | -> String { doc_urls . iter ( ) . position ( | s | s = = url ) . unwrap ( ) . to_string ( ) } ;
2018-06-14 04:15:58 +08:00
let first_chapter = get_doc_ref ( " first/index.html#first-chapter " ) ;
let introduction = get_doc_ref ( " intro.html#introduction " ) ;
let some_section = get_doc_ref ( " first/index.html#some-section " ) ;
let summary = get_doc_ref ( " first/includes.html#summary " ) ;
2021-09-01 03:41:49 +08:00
let no_headers = get_doc_ref ( " first/no-headers.html " ) ;
2022-02-18 23:27:24 +08:00
let duplicate_headers_1 = get_doc_ref ( " first/duplicate-headers.html#header-text-1 " ) ;
2018-06-14 04:15:58 +08:00
let conclusion = get_doc_ref ( " conclusion.html#conclusion " ) ;
2018-03-07 21:02:06 +08:00
let bodyidx = & index [ " index " ] [ " index " ] [ " body " ] [ " root " ] ;
let textidx = & bodyidx [ " t " ] [ " e " ] [ " x " ] [ " t " ] ;
2022-02-18 23:27:24 +08:00
assert_eq! ( textidx [ " df " ] , 5 ) ;
2018-06-14 04:15:58 +08:00
assert_eq! ( textidx [ " docs " ] [ & first_chapter ] [ " tf " ] , 1.0 ) ;
assert_eq! ( textidx [ " docs " ] [ & introduction ] [ " tf " ] , 1.0 ) ;
2018-03-07 21:02:06 +08:00
let docs = & index [ " index " ] [ " documentStore " ] [ " docs " ] ;
2018-06-14 04:15:58 +08:00
assert_eq! ( docs [ & first_chapter ] [ " body " ] , " more text. " ) ;
assert_eq! ( docs [ & some_section ] [ " body " ] , " " ) ;
2018-03-07 21:02:06 +08:00
assert_eq! (
2018-06-14 04:15:58 +08:00
docs [ & summary ] [ " body " ] ,
2022-02-18 23:27:24 +08:00
" Dummy Book Introduction First Chapter Nested Chapter Includes Recursive Markdown Unicode No Headers Duplicate Headers Second Chapter Nested Chapter Conclusion "
2018-03-07 21:02:06 +08:00
) ;
2020-11-26 05:57:43 +08:00
assert_eq! (
docs [ & summary ] [ " breadcrumbs " ] ,
" First Chapter » Includes » Summary "
) ;
2018-07-24 01:45:01 +08:00
assert_eq! ( docs [ & conclusion ] [ " body " ] , " I put <HTML> in here! " ) ;
2021-09-01 03:41:49 +08:00
assert_eq! (
docs [ & no_headers ] [ " breadcrumbs " ] ,
" First Chapter » No Headers "
) ;
2022-02-18 23:27:24 +08:00
assert_eq! (
docs [ & duplicate_headers_1 ] [ " breadcrumbs " ] ,
" First Chapter » Duplicate Headers » Header Text "
) ;
2021-09-01 03:41:49 +08:00
assert_eq! (
docs [ & no_headers ] [ " body " ] ,
2022-05-22 20:57:09 +08:00
" Capybara capybara capybara. Capybara capybara capybara. ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex. "
2021-09-01 03:41:49 +08:00
) ;
2018-03-07 21:02:06 +08:00
}
// Setting this to `true` may cause issues with `cargo watch`,
// since it may not finish writing the fixture before the tests
// are run again.
2018-06-14 04:15:58 +08:00
const GENERATE_FIXTURE : bool = false ;
2018-03-07 21:02:06 +08:00
fn get_fixture ( ) -> serde_json ::Value {
if GENERATE_FIXTURE {
let temp = DummyBook ::new ( ) . build ( ) . unwrap ( ) ;
let md = MDBook ::load ( temp . path ( ) ) . unwrap ( ) ;
md . build ( ) . unwrap ( ) ;
let src = read_book_index ( temp . path ( ) ) ;
let dest = Path ::new ( env! ( " CARGO_MANIFEST_DIR " ) ) . join ( " tests/searchindex_fixture.json " ) ;
let dest = File ::create ( & dest ) . unwrap ( ) ;
serde_json ::to_writer_pretty ( dest , & src ) . unwrap ( ) ;
src
} else {
let json = include_str! ( " searchindex_fixture.json " ) ;
serde_json ::from_str ( json ) . expect ( " Unable to deserialize the fixture " )
}
}
// So you've broken the test. If you changed dummy_book, it's probably
// safe to regenerate the fixture. If you haven't then make sure that the
// search index still works. Run `cargo run -- serve tests/dummy_book`
// and try some searches. Are you getting results? Do the teasers look OK?
// Are there new errors in the JS console?
//
// If you're pretty sure you haven't broken anything, change `GENERATE_FIXTURE`
// above to `true`, and run `cargo test` to generate a new fixture. Then
2018-06-14 04:15:58 +08:00
// **change it back to `false`**. Include the changed `searchindex_fixture.json` in your commit.
2018-03-07 21:02:06 +08:00
#[ test ]
fn search_index_hasnt_changed_accidentally ( ) {
let temp = DummyBook ::new ( ) . build ( ) . unwrap ( ) ;
let md = MDBook ::load ( temp . path ( ) ) . unwrap ( ) ;
md . build ( ) . unwrap ( ) ;
let book_index = read_book_index ( temp . path ( ) ) ;
let fixture_index = get_fixture ( ) ;
// Uncomment this if you're okay with pretty-printing 32KB of JSON
//assert_eq!(fixture_index, book_index);
if book_index ! = fixture_index {
panic! ( " The search index has changed from the fixture " ) ;
}
}
}