1 #[macro_use]
2 extern crate pretty_assertions;
3
4 mod dummy_book;
5
6 use crate::dummy_book::{assert_contains_strings, assert_doesnt_contain_strings, DummyBook};
7
8 use anyhow::Context;
9 use mdbook::config::Config;
10 use mdbook::errors::*;
11 use mdbook::utils::fs::write_file;
12 use mdbook::MDBook;
13 use select::document::Document;
14 use select::predicate::{Class, Name, Predicate};
15 use std::collections::HashMap;
16 use std::ffi::OsStr;
17 use std::fs;
18 use std::io::Write;
19 use std::path::{Component, Path, PathBuf};
20 use tempfile::Builder as TempFileBuilder;
21 use walkdir::{DirEntry, WalkDir};
22
23 const BOOK_ROOT: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/dummy_book");
24 const TOC_TOP_LEVEL: &[&str] = &[
25 "1. First Chapter",
26 "2. Second Chapter",
27 "Conclusion",
28 "Dummy Book",
29 "Introduction",
30 ];
31 const TOC_SECOND_LEVEL: &[&str] = &[
32 "1.1. Nested Chapter",
33 "1.2. Includes",
34 "1.3. Recursive",
35 "1.4. Markdown",
36 "1.5. Unicode",
37 "1.6. No Headers",
38 "2.1. Nested Chapter",
39 ];
40
41 /// Make sure you can load the dummy book and build it without panicking.
42 #[test]
build_the_dummy_book()43 fn build_the_dummy_book() {
44 let temp = DummyBook::new().build().unwrap();
45 let md = MDBook::load(temp.path()).unwrap();
46
47 md.build().unwrap();
48 }
49
50 #[test]
by_default_mdbook_generates_rendered_content_in_the_book_directory()51 fn by_default_mdbook_generates_rendered_content_in_the_book_directory() {
52 let temp = DummyBook::new().build().unwrap();
53 let md = MDBook::load(temp.path()).unwrap();
54
55 assert!(!temp.path().join("book").exists());
56 md.build().unwrap();
57
58 assert!(temp.path().join("book").exists());
59 let index_file = md.build_dir_for("html").join("index.html");
60 assert!(index_file.exists());
61 }
62
63 #[test]
make_sure_bottom_level_files_contain_links_to_chapters()64 fn make_sure_bottom_level_files_contain_links_to_chapters() {
65 let temp = DummyBook::new().build().unwrap();
66 let md = MDBook::load(temp.path()).unwrap();
67 md.build().unwrap();
68
69 let dest = temp.path().join("book");
70 let links = vec![
71 r#"href="intro.html""#,
72 r#"href="first/index.html""#,
73 r#"href="first/nested.html""#,
74 r#"href="second.html""#,
75 r#"href="conclusion.html""#,
76 ];
77
78 let files_in_bottom_dir = vec!["index.html", "intro.html", "second.html", "conclusion.html"];
79
80 for filename in files_in_bottom_dir {
81 assert_contains_strings(dest.join(filename), &links);
82 }
83 }
84
85 #[test]
check_correct_cross_links_in_nested_dir()86 fn check_correct_cross_links_in_nested_dir() {
87 let temp = DummyBook::new().build().unwrap();
88 let md = MDBook::load(temp.path()).unwrap();
89 md.build().unwrap();
90
91 let first = temp.path().join("book").join("first");
92 let links = vec![
93 r#"href="../intro.html""#,
94 r#"href="../first/index.html""#,
95 r#"href="../first/nested.html""#,
96 r#"href="../second.html""#,
97 r#"href="../conclusion.html""#,
98 ];
99
100 let files_in_nested_dir = vec!["index.html", "nested.html"];
101
102 for filename in files_in_nested_dir {
103 assert_contains_strings(first.join(filename), &links);
104 }
105
106 assert_contains_strings(
107 first.join("index.html"),
108 &[r##"<h2 id="some-section"><a class="header" href="#some-section">"##],
109 );
110
111 assert_contains_strings(
112 first.join("nested.html"),
113 &[r##"<h2 id="some-section"><a class="header" href="#some-section">"##],
114 );
115 }
116
117 #[test]
check_correct_relative_links_in_print_page()118 fn check_correct_relative_links_in_print_page() {
119 let temp = DummyBook::new().build().unwrap();
120 let md = MDBook::load(temp.path()).unwrap();
121 md.build().unwrap();
122
123 let first = temp.path().join("book");
124
125 assert_contains_strings(
126 first.join("print.html"),
127 &[
128 r##"<a href="second/../first/nested.html">the first section</a>,"##,
129 r##"<a href="second/../../std/foo/bar.html">outside</a>"##,
130 r##"<img src="second/../images/picture.png" alt="Some image" />"##,
131 r##"<a href="second/nested.html#some-section">fragment link</a>"##,
132 r##"<a href="second/../first/markdown.html">HTML Link</a>"##,
133 r##"<img src="second/../images/picture.png" alt="raw html">"##,
134 ],
135 );
136 }
137
138 #[test]
rendered_code_has_playground_stuff()139 fn rendered_code_has_playground_stuff() {
140 let temp = DummyBook::new().build().unwrap();
141 let md = MDBook::load(temp.path()).unwrap();
142 md.build().unwrap();
143
144 let nested = temp.path().join("book/first/nested.html");
145 let playground_class = vec![r#"class="playground""#];
146
147 assert_contains_strings(nested, &playground_class);
148
149 let book_js = temp.path().join("book/book.js");
150 assert_contains_strings(book_js, &[".playground"]);
151 }
152
153 #[test]
anchors_include_text_between_but_not_anchor_comments()154 fn anchors_include_text_between_but_not_anchor_comments() {
155 let temp = DummyBook::new().build().unwrap();
156 let md = MDBook::load(temp.path()).unwrap();
157 md.build().unwrap();
158
159 let nested = temp.path().join("book/first/nested.html");
160 let text_between_anchors = vec!["unique-string-for-anchor-test"];
161 let anchor_text = vec!["ANCHOR"];
162
163 assert_contains_strings(nested.clone(), &text_between_anchors);
164 assert_doesnt_contain_strings(nested, &anchor_text);
165 }
166
167 #[test]
rustdoc_include_hides_the_unspecified_part_of_the_file()168 fn rustdoc_include_hides_the_unspecified_part_of_the_file() {
169 let temp = DummyBook::new().build().unwrap();
170 let md = MDBook::load(temp.path()).unwrap();
171 md.build().unwrap();
172
173 let nested = temp.path().join("book/first/nested.html");
174 let text = vec![
175 "<span class=\"boring\">fn some_function() {",
176 "<span class=\"boring\">fn some_other_function() {",
177 ];
178
179 assert_contains_strings(nested, &text);
180 }
181
182 #[test]
chapter_content_appears_in_rendered_document()183 fn chapter_content_appears_in_rendered_document() {
184 let content = vec![
185 ("index.html", "This file is just here to cause the"),
186 ("intro.html", "Here's some interesting text"),
187 ("second.html", "Second Chapter"),
188 ("first/nested.html", "testable code"),
189 ("first/index.html", "more text"),
190 ("conclusion.html", "Conclusion"),
191 ];
192
193 let temp = DummyBook::new().build().unwrap();
194 let md = MDBook::load(temp.path()).unwrap();
195 md.build().unwrap();
196
197 let destination = temp.path().join("book");
198
199 for (filename, text) in content {
200 let path = destination.join(filename);
201 assert_contains_strings(path, &[text]);
202 }
203 }
204
205 /// Apply a series of predicates to some root predicate, where each
206 /// successive predicate is the descendant of the last one. Similar to how you
207 /// might do `ul.foo li a` in CSS to access all anchor tags in the `foo` list.
208 macro_rules! descendants {
209 ($root:expr, $($child:expr),*) => {
210 $root
211 $(
212 .descendant($child)
213 )*
214 };
215 }
216
217 /// Make sure that all `*.md` files (excluding `SUMMARY.md`) were rendered
218 /// and placed in the `book` directory with their extensions set to `*.html`.
219 #[test]
chapter_files_were_rendered_to_html()220 fn chapter_files_were_rendered_to_html() {
221 let temp = DummyBook::new().build().unwrap();
222 let src = Path::new(BOOK_ROOT).join("src");
223
224 let chapter_files = WalkDir::new(&src)
225 .into_iter()
226 .filter_entry(|entry| entry_ends_with(entry, ".md"))
227 .filter_map(std::result::Result::ok)
228 .map(|entry| entry.path().to_path_buf())
229 .filter(|path| path.file_name().and_then(OsStr::to_str) != Some("SUMMARY.md"));
230
231 for chapter in chapter_files {
232 let rendered_location = temp
233 .path()
234 .join(chapter.strip_prefix(&src).unwrap())
235 .with_extension("html");
236 assert!(
237 rendered_location.exists(),
238 "{} doesn't exits",
239 rendered_location.display()
240 );
241 }
242 }
243
entry_ends_with(entry: &DirEntry, ending: &str) -> bool244 fn entry_ends_with(entry: &DirEntry, ending: &str) -> bool {
245 entry.file_name().to_string_lossy().ends_with(ending)
246 }
247
248 /// Read the main page (`book/index.html`) and expose it as a DOM which we
249 /// can search with the `select` crate
root_index_html() -> Result<Document>250 fn root_index_html() -> Result<Document> {
251 let temp = DummyBook::new()
252 .build()
253 .with_context(|| "Couldn't create the dummy book")?;
254 MDBook::load(temp.path())?
255 .build()
256 .with_context(|| "Book building failed")?;
257
258 let index_page = temp.path().join("book").join("index.html");
259 let html = fs::read_to_string(&index_page).with_context(|| "Unable to read index.html")?;
260
261 Ok(Document::from(html.as_str()))
262 }
263
264 #[test]
check_second_toc_level()265 fn check_second_toc_level() {
266 let doc = root_index_html().unwrap();
267 let mut should_be = Vec::from(TOC_SECOND_LEVEL);
268 should_be.sort_unstable();
269
270 let pred = descendants!(
271 Class("chapter"),
272 Name("li"),
273 Name("li"),
274 Name("a").and(Class("toggle").not())
275 );
276
277 let mut children_of_children: Vec<_> = doc
278 .find(pred)
279 .map(|elem| elem.text().trim().to_string())
280 .collect();
281 children_of_children.sort();
282
283 assert_eq!(children_of_children, should_be);
284 }
285
286 #[test]
check_first_toc_level()287 fn check_first_toc_level() {
288 let doc = root_index_html().unwrap();
289 let mut should_be = Vec::from(TOC_TOP_LEVEL);
290
291 should_be.extend(TOC_SECOND_LEVEL);
292 should_be.sort_unstable();
293
294 let pred = descendants!(
295 Class("chapter"),
296 Name("li"),
297 Name("a").and(Class("toggle").not())
298 );
299
300 let mut children: Vec<_> = doc
301 .find(pred)
302 .map(|elem| elem.text().trim().to_string())
303 .collect();
304 children.sort();
305
306 assert_eq!(children, should_be);
307 }
308
309 #[test]
check_spacers()310 fn check_spacers() {
311 let doc = root_index_html().unwrap();
312 let should_be = 2;
313
314 let num_spacers = doc
315 .find(Class("chapter").descendant(Name("li").and(Class("spacer"))))
316 .count();
317 assert_eq!(num_spacers, should_be);
318 }
319
320 /// Ensure building fails if `create-missing` is false and one of the files does
321 /// not exist.
322 #[test]
failure_on_missing_file()323 fn failure_on_missing_file() {
324 let temp = DummyBook::new().build().unwrap();
325 fs::remove_file(temp.path().join("src").join("intro.md")).unwrap();
326
327 let mut cfg = Config::default();
328 cfg.build.create_missing = false;
329
330 let got = MDBook::load_with_config(temp.path(), cfg);
331 assert!(got.is_err());
332 }
333
334 /// Ensure a missing file is created if `create-missing` is true.
335 #[test]
create_missing_file_with_config()336 fn create_missing_file_with_config() {
337 let temp = DummyBook::new().build().unwrap();
338 fs::remove_file(temp.path().join("src").join("intro.md")).unwrap();
339
340 let mut cfg = Config::default();
341 cfg.build.create_missing = true;
342
343 assert!(!temp.path().join("src").join("intro.md").exists());
344 let _md = MDBook::load_with_config(temp.path(), cfg).unwrap();
345 assert!(temp.path().join("src").join("intro.md").exists());
346 }
347
348 /// This makes sure you can include a Rust file with `{{#playground example.rs}}`.
349 /// Specification is in `guide/src/format/rust.md`
350 #[test]
able_to_include_playground_files_in_chapters()351 fn able_to_include_playground_files_in_chapters() {
352 let temp = DummyBook::new().build().unwrap();
353 let md = MDBook::load(temp.path()).unwrap();
354 md.build().unwrap();
355
356 let second = temp.path().join("book/second.html");
357
358 let playground_strings = &[
359 r#"class="playground""#,
360 r#"println!("Hello World!");"#,
361 ];
362
363 assert_contains_strings(&second, playground_strings);
364 assert_doesnt_contain_strings(&second, &["{{#playground example.rs}}"]);
365 }
366
367 /// This makes sure you can include a Rust file with `{{#include ../SUMMARY.md}}`.
368 #[test]
able_to_include_files_in_chapters()369 fn able_to_include_files_in_chapters() {
370 let temp = DummyBook::new().build().unwrap();
371 let md = MDBook::load(temp.path()).unwrap();
372 md.build().unwrap();
373
374 let includes = temp.path().join("book/first/includes.html");
375
376 let summary_strings = &[
377 r##"<h1 id="summary"><a class="header" href="#summary">Summary</a></h1>"##,
378 ">First Chapter</a>",
379 ];
380 assert_contains_strings(&includes, summary_strings);
381
382 assert_doesnt_contain_strings(&includes, &["{{#include ../SUMMARY.md::}}"]);
383 }
384
385 /// Ensure cyclic includes are capped so that no exceptions occur
386 #[test]
recursive_includes_are_capped()387 fn recursive_includes_are_capped() {
388 let temp = DummyBook::new().build().unwrap();
389 let md = MDBook::load(temp.path()).unwrap();
390 md.build().unwrap();
391
392 let recursive = temp.path().join("book/first/recursive.html");
393 let content = &["Around the world, around the world
394 Around the world, around the world
395 Around the world, around the world"];
396 assert_contains_strings(&recursive, content);
397 }
398
399 #[test]
example_book_can_build()400 fn example_book_can_build() {
401 let example_book_dir = dummy_book::new_copy_of_example_book().unwrap();
402
403 let md = MDBook::load(example_book_dir.path()).unwrap();
404
405 md.build().unwrap();
406 }
407
408 #[test]
book_with_a_reserved_filename_does_not_build()409 fn book_with_a_reserved_filename_does_not_build() {
410 let tmp_dir = TempFileBuilder::new().prefix("mdBook").tempdir().unwrap();
411 let src_path = tmp_dir.path().join("src");
412 fs::create_dir(&src_path).unwrap();
413
414 let summary_path = src_path.join("SUMMARY.md");
415 let print_path = src_path.join("print.md");
416
417 fs::File::create(print_path).unwrap();
418 let mut summary_file = fs::File::create(summary_path).unwrap();
419 writeln!(summary_file, "[print](print.md)").unwrap();
420
421 let md = MDBook::load(tmp_dir.path()).unwrap();
422 let got = md.build();
423 assert!(got.is_err());
424 }
425
426 #[test]
by_default_mdbook_use_index_preprocessor_to_convert_readme_to_index()427 fn by_default_mdbook_use_index_preprocessor_to_convert_readme_to_index() {
428 let temp = DummyBook::new().build().unwrap();
429 let mut cfg = Config::default();
430 cfg.set("book.src", "src2")
431 .expect("Couldn't set config.book.src to \"src2\".");
432 let md = MDBook::load_with_config(temp.path(), cfg).unwrap();
433 md.build().unwrap();
434
435 let first_index = temp.path().join("book").join("first").join("index.html");
436 let expected_strings = vec![
437 r#"href="../first/index.html""#,
438 r#"href="../second/index.html""#,
439 "First README",
440 ];
441 assert_contains_strings(&first_index, &expected_strings);
442 assert_doesnt_contain_strings(&first_index, &["README.html"]);
443
444 let second_index = temp.path().join("book").join("second").join("index.html");
445 let unexpected_strings = vec!["Second README"];
446 assert_doesnt_contain_strings(&second_index, &unexpected_strings);
447 }
448
449 #[test]
theme_dir_overrides_work_correctly()450 fn theme_dir_overrides_work_correctly() {
451 let book_dir = dummy_book::new_copy_of_example_book().unwrap();
452 let book_dir = book_dir.path();
453 let theme_dir = book_dir.join("theme");
454
455 let mut index = mdbook::theme::INDEX.to_vec();
456 index.extend_from_slice(b"\n<!-- This is a modified index.hbs! -->");
457
458 write_file(&theme_dir, "index.hbs", &index).unwrap();
459
460 let md = MDBook::load(book_dir).unwrap();
461 md.build().unwrap();
462
463 let built_index = book_dir.join("book").join("index.html");
464 dummy_book::assert_contains_strings(built_index, &["This is a modified index.hbs!"]);
465 }
466
467 #[test]
no_index_for_print_html()468 fn no_index_for_print_html() {
469 let temp = DummyBook::new().build().unwrap();
470 let md = MDBook::load(temp.path()).unwrap();
471 md.build().unwrap();
472
473 let print_html = temp.path().join("book/print.html");
474 assert_contains_strings(print_html, &[r##"noindex"##]);
475
476 let index_html = temp.path().join("book/index.html");
477 assert_doesnt_contain_strings(index_html, &[r##"noindex"##]);
478 }
479
480 #[test]
markdown_options()481 fn markdown_options() {
482 let temp = DummyBook::new().build().unwrap();
483 let md = MDBook::load(temp.path()).unwrap();
484 md.build().unwrap();
485
486 let path = temp.path().join("book/first/markdown.html");
487 assert_contains_strings(
488 &path,
489 &[
490 "<th>foo</th>",
491 "<th>bar</th>",
492 "<td>baz</td>",
493 "<td>bim</td>",
494 ],
495 );
496 assert_contains_strings(
497 &path,
498 &[
499 r##"<sup class="footnote-reference"><a href="#1">1</a></sup>"##,
500 r##"<sup class="footnote-reference"><a href="#word">2</a></sup>"##,
501 r##"<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>"##,
502 r##"<div class="footnote-definition" id="word"><sup class="footnote-definition-label">2</sup>"##,
503 ],
504 );
505 assert_contains_strings(&path, &["<del>strikethrough example</del>"]);
506 assert_contains_strings(
507 &path,
508 &[
509 "<li><input disabled=\"\" type=\"checkbox\" checked=\"\"/>\nApples",
510 "<li><input disabled=\"\" type=\"checkbox\" checked=\"\"/>\nBroccoli",
511 "<li><input disabled=\"\" type=\"checkbox\"/>\nCarrots",
512 ],
513 );
514 }
515
516 #[test]
redirects_are_emitted_correctly()517 fn redirects_are_emitted_correctly() {
518 let temp = DummyBook::new().build().unwrap();
519 let mut md = MDBook::load(temp.path()).unwrap();
520
521 // override the "outputs.html.redirect" table
522 let redirects: HashMap<PathBuf, String> = vec![
523 (PathBuf::from("/overview.html"), String::from("index.html")),
524 (
525 PathBuf::from("/nexted/page.md"),
526 String::from("https://rust-lang.org/"),
527 ),
528 ]
529 .into_iter()
530 .collect();
531 md.config.set("output.html.redirect", &redirects).unwrap();
532
533 md.build().unwrap();
534
535 for (original, redirect) in &redirects {
536 let mut redirect_file = md.build_dir_for("html");
537 // append everything except the bits that make it absolute
538 // (e.g. "/" or "C:\")
539 redirect_file.extend(remove_absolute_components(original));
540 let contents = fs::read_to_string(&redirect_file).unwrap();
541 assert!(contents.contains(redirect));
542 }
543 }
544
545 #[test]
edit_url_has_default_src_dir_edit_url()546 fn edit_url_has_default_src_dir_edit_url() {
547 let temp = DummyBook::new().build().unwrap();
548 let book_toml = r#"
549 [book]
550 title = "implicit"
551
552 [output.html]
553 edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}"
554 "#;
555
556 write_file(temp.path(), "book.toml", book_toml.as_bytes()).unwrap();
557
558 let md = MDBook::load(temp.path()).unwrap();
559 md.build().unwrap();
560
561 let index_html = temp.path().join("book").join("index.html");
562 assert_contains_strings(
563 index_html,
564 &[
565 r#"href="https://github.com/rust-lang/mdBook/edit/master/guide/src/README.md" title="Suggest an edit""#,
566 ],
567 );
568 }
569
570 #[test]
edit_url_has_configured_src_dir_edit_url()571 fn edit_url_has_configured_src_dir_edit_url() {
572 let temp = DummyBook::new().build().unwrap();
573 let book_toml = r#"
574 [book]
575 title = "implicit"
576 src = "src2"
577
578 [output.html]
579 edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}"
580 "#;
581
582 write_file(temp.path(), "book.toml", book_toml.as_bytes()).unwrap();
583
584 let md = MDBook::load(temp.path()).unwrap();
585 md.build().unwrap();
586
587 let index_html = temp.path().join("book").join("index.html");
588 assert_contains_strings(
589 index_html,
590 &[
591 r#"href="https://github.com/rust-lang/mdBook/edit/master/guide/src2/README.md" title="Suggest an edit""#,
592 ],
593 );
594 }
595
remove_absolute_components(path: &Path) -> impl Iterator<Item = Component> + '_596 fn remove_absolute_components(path: &Path) -> impl Iterator<Item = Component> + '_ {
597 path.components().skip_while(|c| match c {
598 Component::Prefix(_) | Component::RootDir => true,
599 _ => false,
600 })
601 }
602
603 #[cfg(feature = "search")]
604 mod search {
605 use crate::dummy_book::DummyBook;
606 use mdbook::MDBook;
607 use std::fs::{self, File};
608 use std::path::Path;
609
read_book_index(root: &Path) -> serde_json::Value610 fn read_book_index(root: &Path) -> serde_json::Value {
611 let index = root.join("book/searchindex.js");
612 let index = fs::read_to_string(index).unwrap();
613 let index = index.trim_start_matches("Object.assign(window.search, ");
614 let index = index.trim_end_matches(");");
615 serde_json::from_str(index).unwrap()
616 }
617
618 #[test]
619 #[allow(clippy::float_cmp)]
book_creates_reasonable_search_index()620 fn book_creates_reasonable_search_index() {
621 let temp = DummyBook::new().build().unwrap();
622 let md = MDBook::load(temp.path()).unwrap();
623 md.build().unwrap();
624
625 let index = read_book_index(temp.path());
626
627 let doc_urls = index["doc_urls"].as_array().unwrap();
628 let get_doc_ref =
629 |url: &str| -> String { doc_urls.iter().position(|s| s == url).unwrap().to_string() };
630
631 let first_chapter = get_doc_ref("first/index.html#first-chapter");
632 let introduction = get_doc_ref("intro.html#introduction");
633 let some_section = get_doc_ref("first/index.html#some-section");
634 let summary = get_doc_ref("first/includes.html#summary");
635 let no_headers = get_doc_ref("first/no-headers.html");
636 let conclusion = get_doc_ref("conclusion.html#conclusion");
637
638 let bodyidx = &index["index"]["index"]["body"]["root"];
639 let textidx = &bodyidx["t"]["e"]["x"]["t"];
640 assert_eq!(textidx["df"], 2);
641 assert_eq!(textidx["docs"][&first_chapter]["tf"], 1.0);
642 assert_eq!(textidx["docs"][&introduction]["tf"], 1.0);
643
644 let docs = &index["index"]["documentStore"]["docs"];
645 assert_eq!(docs[&first_chapter]["body"], "more text.");
646 assert_eq!(docs[&some_section]["body"], "");
647 assert_eq!(
648 docs[&summary]["body"],
649 "Dummy Book Introduction First Chapter Nested Chapter Includes Recursive Markdown Unicode No Headers Second Chapter Nested Chapter Conclusion"
650 );
651 assert_eq!(
652 docs[&summary]["breadcrumbs"],
653 "First Chapter » Includes » Summary"
654 );
655 assert_eq!(docs[&conclusion]["body"], "I put <HTML> in here!");
656 assert_eq!(
657 docs[&no_headers]["breadcrumbs"],
658 "First Chapter » No Headers"
659 );
660 assert_eq!(
661 docs[&no_headers]["body"],
662 "Capybara capybara capybara. Capybara capybara capybara."
663 );
664 }
665
666 // Setting this to `true` may cause issues with `cargo watch`,
667 // since it may not finish writing the fixture before the tests
668 // are run again.
669 const GENERATE_FIXTURE: bool = false;
670
get_fixture() -> serde_json::Value671 fn get_fixture() -> serde_json::Value {
672 if GENERATE_FIXTURE {
673 let temp = DummyBook::new().build().unwrap();
674 let md = MDBook::load(temp.path()).unwrap();
675 md.build().unwrap();
676
677 let src = read_book_index(temp.path());
678
679 let dest = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/searchindex_fixture.json");
680 let dest = File::create(&dest).unwrap();
681 serde_json::to_writer_pretty(dest, &src).unwrap();
682
683 src
684 } else {
685 let json = include_str!("searchindex_fixture.json");
686 serde_json::from_str(json).expect("Unable to deserialize the fixture")
687 }
688 }
689
690 // So you've broken the test. If you changed dummy_book, it's probably
691 // safe to regenerate the fixture. If you haven't then make sure that the
692 // search index still works. Run `cargo run -- serve tests/dummy_book`
693 // and try some searches. Are you getting results? Do the teasers look OK?
694 // Are there new errors in the JS console?
695 //
696 // If you're pretty sure you haven't broken anything, change `GENERATE_FIXTURE`
697 // above to `true`, and run `cargo test` to generate a new fixture. Then
698 // **change it back to `false`**. Include the changed `searchindex_fixture.json` in your commit.
699 #[test]
search_index_hasnt_changed_accidentally()700 fn search_index_hasnt_changed_accidentally() {
701 let temp = DummyBook::new().build().unwrap();
702 let md = MDBook::load(temp.path()).unwrap();
703 md.build().unwrap();
704
705 let book_index = read_book_index(temp.path());
706
707 let fixture_index = get_fixture();
708
709 // Uncomment this if you're okay with pretty-printing 32KB of JSON
710 //assert_eq!(fixture_index, book_index);
711
712 if book_index != fixture_index {
713 panic!("The search index has changed from the fixture");
714 }
715 }
716 }
717