Skip to content

Commit 95609cf

Browse files
Created regression tests for the table of contents
1 parent 79dd03e commit 95609cf

File tree

3 files changed

+231
-7
lines changed

3 files changed

+231
-7
lines changed

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ ws = { version = "0.7", optional = true}
4343
[build-dependencies]
4444
error-chain = "0.11"
4545

46+
[dev-dependencies]
47+
select = "0.4"
48+
pretty_assertions = "*"
49+
walkdir = "1.0"
50+
4651
[features]
4752
default = ["output", "watch", "serve"]
4853
debug = []

tests/dummy/mod.rs

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,21 @@
22
//! dummy contents from the `tests/dummy-book/` directory.
33
44
// Not all features are used in all test crates, so...
5-
#![allow(dead_code, unused_extern_crates)]
6-
5+
#![allow(dead_code, unused_variables, unused_imports, unused_extern_crates)]
6+
extern crate mdbook;
77
extern crate tempdir;
8+
extern crate walkdir;
89

9-
use std::fs::{create_dir_all, File};
10-
use std::io::Write;
10+
use std::path::Path;
11+
use std::fs::{self, File};
12+
use std::io::{Read, Write};
13+
use std::error::Error;
1114

12-
use tempdir::TempDir;
15+
// The funny `self::` here is because we've got an `extern crate ...` and are
16+
// in a submodule
17+
use self::tempdir::TempDir;
18+
use self::walkdir::WalkDir;
19+
use self::mdbook::MDBook;
1320

1421

1522
const SUMMARY_MD: &'static str = include_str!("book/SUMMARY.md");
@@ -55,10 +62,10 @@ impl DummyBook {
5562
let temp = TempDir::new("dummy_book").unwrap();
5663

5764
let src = temp.path().join("src");
58-
create_dir_all(&src).unwrap();
65+
fs::create_dir_all(&src).unwrap();
5966

6067
let first = src.join("first");
61-
create_dir_all(&first).unwrap();
68+
fs::create_dir_all(&first).unwrap();
6269

6370
let to_substitute = if self.passing_test { "true" } else { "false" };
6471
let nested_text = NESTED.replace("$TEST_STATUS", to_substitute);
@@ -85,3 +92,64 @@ impl Default for DummyBook {
8592
DummyBook { passing_test: true }
8693
}
8794
}
95+
96+
97+
/// Read the contents of the provided file into memory and then iterate through
98+
/// the list of strings asserting that the file contains all of them.
99+
pub fn assert_contains_strings<P: AsRef<Path>>(filename: P, strings: &[&str]) {
100+
let filename = filename.as_ref();
101+
102+
let mut content = String::new();
103+
File::open(&filename)
104+
.expect("Couldn't open the provided file")
105+
.read_to_string(&mut content)
106+
.expect("Couldn't read the file's contents");
107+
108+
for s in strings {
109+
assert!(content.contains(s), "Searching for {:?} in {}\n\n{}", s, filename.display(), content);
110+
}
111+
}
112+
113+
114+
/// Copy the example book to a temporary directory and build it.
115+
pub fn build_example_book() -> TempDir {
116+
let temp = TempDir::new("mdbook").expect("Couldn't create a temporary directory");
117+
let book_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("book-example");
118+
119+
recursive_copy(&book_root, temp.path()).expect("Couldn't copy the book example to a temporary directory");
120+
121+
let book = MDBook::new(temp.path()).build().expect("Building failed");
122+
temp
123+
}
124+
125+
126+
/// Recursively copy an entire directory tree to somewhere else (a la `cp -r`).
127+
fn recursive_copy<A: AsRef<Path>, B: AsRef<Path>>(from: A, to: B) -> Result<(), Box<Error>> {
128+
let from = from.as_ref();
129+
let to = to.as_ref();
130+
131+
for entry in WalkDir::new(&from) {
132+
let entry = entry?;
133+
134+
let original_location = entry.path();
135+
let relative = original_location.strip_prefix(&from)?;
136+
let new_location = to.join(relative);
137+
138+
if original_location.is_file() {
139+
if let Some(parent) = new_location.parent() {
140+
fs::create_dir_all(parent)?;
141+
}
142+
143+
fs::copy(&original_location, &new_location)?;
144+
}
145+
}
146+
147+
Ok(())
148+
}
149+
150+
pub fn read_file<P: AsRef<Path>>(filename: P) -> Result<String, Box<Error>> {
151+
let mut contents = String::new();
152+
File::open(filename)?.read_to_string(&mut contents)?;
153+
154+
Ok(contents)
155+
}

tests/regression.rs

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
//! High-level regression testing which will build the example book and *try*
2+
//! to make sure key elements don't get accidentally broken.
3+
//!
4+
//! # Warning
5+
//!
6+
//! These tests will need to be updated every time the example book changes.
7+
//! Hopefully Travis will let you know when that happens.
8+
9+
#![feature(conservative_impl_trait)]
10+
11+
extern crate mdbook;
12+
#[macro_use]
13+
extern crate pretty_assertions;
14+
extern crate select;
15+
extern crate tempdir;
16+
extern crate walkdir;
17+
18+
mod helpers;
19+
20+
use std::path::Path;
21+
use walkdir::{WalkDir, WalkDirIterator};
22+
use select::document::Document;
23+
use select::predicate::{Class, Descendant, Name, Predicate};
24+
25+
26+
const BOOK_ROOT: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/book-example");
27+
const TOC_TOP_LEVEL: &[&'static str] = &[
28+
"1. mdBook",
29+
"2. Command Line Tool",
30+
"3. Format",
31+
"4. Rust Library",
32+
"Contributors",
33+
];
34+
const TOC_SECOND_LEVEL: &[&'static str] = &[
35+
"2.1. init",
36+
"2.2. build",
37+
"2.3. watch",
38+
"2.4. serve",
39+
"2.5. test",
40+
"3.1. SUMMARY.md",
41+
"3.2. Configuration",
42+
"3.3. Theme",
43+
"3.4. MathJax Support",
44+
"3.5. Rust code specific features",
45+
];
46+
const TOC_THIRD_LEVEL: &[&'static str] = &["3.3.1. index.hbs", "3.3.2. Syntax highlighting"];
47+
48+
/// Apply a series of predicates to some root predicate, where each
49+
/// successive predicate is the descendant of the last one.
50+
macro_rules! descendants {
51+
($root:expr, $($child:expr),*) => {
52+
$root
53+
$(
54+
.descendant($child)
55+
)*
56+
};
57+
}
58+
59+
60+
/// Make sure that all `*.md` files (excluding `SUMMARY.md`) were rendered
61+
/// and placed in the `book` directory with their extensions set to `*.html`.
62+
#[test]
63+
fn chapter_files_were_rendered_to_html() {
64+
let temp = helpers::build_example_book();
65+
let src = Path::new(BOOK_ROOT).join("src");
66+
67+
let chapter_files = WalkDir::new(&src)
68+
.into_iter()
69+
.filter_entry(|entry| entry.file_name().to_string_lossy().ends_with(".md"))
70+
.filter_map(|entry| entry.ok())
71+
.map(|entry| entry.path().to_path_buf())
72+
.filter(|path| path.file_name().unwrap() != "SUMMARY");
73+
74+
for chapter in chapter_files {
75+
let rendered_location = temp.path()
76+
.join(chapter.strip_prefix(&src).unwrap())
77+
.with_extension("html");
78+
assert!(rendered_location.exists(), "{} doesn't exits", rendered_location.display());
79+
}
80+
}
81+
82+
fn root_index_html() -> Document {
83+
let temp = helpers::build_example_book();
84+
85+
let index_page = temp.path().join("book").join("index.html");
86+
let html = helpers::read_file(&index_page).unwrap();
87+
Document::from(html.as_str())
88+
}
89+
90+
#[test]
91+
fn check_third_toc_level() {
92+
let doc = root_index_html();
93+
let should_be = TOC_THIRD_LEVEL;
94+
95+
let children_of_children_of_children: Vec<String> = doc.find(
96+
Class("chapter")
97+
.descendant(Name("li"))
98+
.descendant(Name("li"))
99+
.descendant(Name("li"))
100+
.descendant(Name("a")),
101+
).map(|elem| elem.text().trim().to_string())
102+
.collect();
103+
assert_eq!(children_of_children_of_children, should_be);
104+
}
105+
106+
#[test]
107+
fn check_second_toc_level() {
108+
let doc = root_index_html();
109+
let mut should_be = Vec::from(TOC_SECOND_LEVEL);
110+
111+
should_be.extend(TOC_THIRD_LEVEL);
112+
should_be.sort();
113+
114+
let pred = descendants!(Class("chapter"), Name("li"), Name("li"), Name("a"));
115+
116+
let mut children_of_children: Vec<String> = doc.find(pred)
117+
.map(|elem| elem.text().trim().to_string())
118+
.collect();
119+
children_of_children.sort();
120+
121+
assert_eq!(children_of_children, should_be);
122+
}
123+
124+
#[test]
125+
fn check_first_toc_level() {
126+
let doc = root_index_html();
127+
let mut should_be = Vec::from(TOC_TOP_LEVEL);
128+
129+
should_be.extend(TOC_SECOND_LEVEL);
130+
should_be.extend(TOC_THIRD_LEVEL);
131+
should_be.sort();
132+
133+
let pred = descendants!(Class("chapter"), Name("li"), Name("a"));
134+
135+
let mut children: Vec<String> = doc.find(pred)
136+
.map(|elem| elem.text().trim().to_string())
137+
.collect();
138+
children.sort();
139+
140+
assert_eq!(children, should_be);
141+
}
142+
143+
#[test]
144+
fn check_spacers() {
145+
let doc = root_index_html();
146+
let should_be = 1;
147+
148+
let num_spacers = doc.find(Class("chapter").descendant(Name("li").and(Class("spacer"))))
149+
.count();
150+
assert_eq!(num_spacers, should_be);
151+
}

0 commit comments

Comments
 (0)