From f10d23e8933be3a6607a13b13162842e059b9d1b Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 18:22:08 -0700
Subject: [PATCH 01/69] Introduce the new BookTest-based testsuite
This is a new testsuite intended to replace the other tests, which
provides an easy facility to update tests, validate output, and more.
---
Cargo.lock | 96 ++++++++
Cargo.toml | 1 +
tests/testsuite/README.md | 43 ++++
tests/testsuite/book_test.rs | 442 +++++++++++++++++++++++++++++++++++
tests/testsuite/main.rs | 10 +
5 files changed, 592 insertions(+)
create mode 100644 tests/testsuite/README.md
create mode 100644 tests/testsuite/book_test.rs
create mode 100644 tests/testsuite/main.rs
diff --git a/Cargo.lock b/Cargo.lock
index 082543f4..ae1b6f75 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -75,6 +75,15 @@ version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
+[[package]]
+name = "anstyle-lossy"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "934ff8719effd2023a48cf63e69536c1c3ced9d3895068f6f5cc9a4ff845e59b"
+dependencies = [
+ "anstyle",
+]
+
[[package]]
name = "anstyle-parse"
version = "0.2.6"
@@ -93,6 +102,19 @@ dependencies = [
"windows-sys 0.59.0",
]
+[[package]]
+name = "anstyle-svg"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3607949e9f6de49ea4bafe12f5e4fd73613ebf24795e48587302a8cc0e4bb35"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "anstyle-lossy",
+ "html-escape",
+ "unicode-width",
+]
+
[[package]]
name = "anstyle-wincon"
version = "3.0.7"
@@ -288,6 +310,15 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
+[[package]]
+name = "content_inspector"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
@@ -460,6 +491,12 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
+[[package]]
+name = "dunce"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
+
[[package]]
name = "elasticlunr-rs"
version = "3.0.2"
@@ -737,6 +774,15 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+[[package]]
+name = "html-escape"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476"
+dependencies = [
+ "utf8-width",
+]
+
[[package]]
name = "html5ever"
version = "0.26.0"
@@ -1236,6 +1282,7 @@ dependencies = [
"serde_json",
"sha2",
"shlex",
+ "snapbox",
"tempfile",
"tokio",
"toml",
@@ -1920,6 +1967,12 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+[[package]]
+name = "similar"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
+
[[package]]
name = "siphasher"
version = "0.3.11"
@@ -1947,6 +2000,37 @@ version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+[[package]]
+name = "snapbox"
+version = "0.6.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96dcfc4581e3355d70ac2ee14cfdf81dce3d85c85f1ed9e2c1d3013f53b3436b"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "anstyle-svg",
+ "content_inspector",
+ "dunce",
+ "filetime",
+ "normalize-line-endings",
+ "regex",
+ "serde",
+ "serde_json",
+ "similar",
+ "snapbox-macros",
+ "tempfile",
+ "walkdir",
+]
+
+[[package]]
+name = "snapbox-macros"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16569f53ca23a41bb6f62e0a5084aa1661f4814a67fa33696a79073e03a664af"
+dependencies = [
+ "anstream",
+]
+
[[package]]
name = "socket2"
version = "0.5.8"
@@ -2261,6 +2345,12 @@ version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
+[[package]]
+name = "unicode-width"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
+
[[package]]
name = "url"
version = "2.5.4"
@@ -2284,6 +2374,12 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
+[[package]]
+name = "utf8-width"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3"
+
[[package]]
name = "utf8_iter"
version = "1.0.4"
diff --git a/Cargo.toml b/Cargo.toml
index 4c43d0eb..00afec38 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -68,6 +68,7 @@ assert_cmd = "2.0.11"
predicates = "3.0.3"
select = "0.6.0"
semver = "1.0.17"
+snapbox = { version = "0.6.21", features = ["diff", "dir", "term-svg", "regex", "json"] }
pretty_assertions = "1.3.0"
walkdir = "2.3.3"
diff --git a/tests/testsuite/README.md b/tests/testsuite/README.md
new file mode 100644
index 00000000..73c594e3
--- /dev/null
+++ b/tests/testsuite/README.md
@@ -0,0 +1,43 @@
+# Testsuite
+
+## Introduction
+
+This is the main testsuite for exercising all functionality of mdBook.
+
+Tests should be organized into modules based around major features. Tests should use `BookTest` to drive the test. `BookTest` will set up a temp directory, and provides a variety of methods to help create a build books.
+
+## Basic structure of a test
+
+Using `BookTest`, you typically use it to copy a directory into a temp directory, and then run mdbook commands in that temp directory. You can run the `mdbook` executable, or use the mdbook API to perform whatever tasks you need. Running the executable has the benefit of being able to validate the console output.
+
+See `build::basic_build` for a simple test example. I recommend reviewing the methods on `BookTest` to learn more, and reviewing some of the existing tests to get a feel for how they are structured.
+
+For example, let's say you are creating a new theme test. In the `testsuite/theme` directory, create a new directory with the book source that you want to exercise. At a minimum, this needs a `src/SUMMARY.md`, but often you'll also want `book.toml`. Then, in `testsuite/theme.rs`, add a test with `BookTest::from_dir("theme/mytest")`, and then use the methods to perform whatever actions you want.
+
+`BookTest` is designed to be able to chain a series of actions. For example, you can do something like:
+
+```rust
+BookTest::from_dir("theme/mytest")
+ .build()
+ .check_main_file("book/index.html", str![["file contents"]])
+ .change_file("src/index.md", "new contents")
+ .build()
+ .check_main_file("book/index.html", str![["new contents"]]);
+```
+
+## Snapbox
+
+The testsuite uses [`snapbox`] to drive most of the tests. This library provides the ability to compare strings using a variety of methods. These strings are written in the source code using either the [`str!`] or [`file!`] macros.
+
+The magic is that you can set the `SNAPSHOTS=overwrite` environment variable, and snapbox will automatically update the strings contents of `str!`, or the file contents of `file!`. This makes it easier to update tests. Snapbox provides nice diffing output, and quite a few other features.
+
+Expected contents can have wildcards like `...` (matches any lines) or `[..]` (matches any characters on a line). See [snapbox filters] for more info and other filters.
+
+Typically when writing a test, I'll just start with an empty `str!` or `file!`, and let snapbox fill it in. Then I review the contents to make sure they are what I expect.
+
+Note that there is some normalization applied to the strings. See `book_test::assert` for how some of these normalizations happen.
+
+[`snapbox`]: https://docs.rs/snapbox/latest/snapbox/
+[`str!`]: https://docs.rs/snapbox/latest/snapbox/macro.str.html
+[`file!`]: https://docs.rs/snapbox/latest/snapbox/macro.file.html
+[snapbox filters]: https://docs.rs/snapbox/latest/snapbox/assert/struct.Assert.html#method.eq
diff --git a/tests/testsuite/book_test.rs b/tests/testsuite/book_test.rs
new file mode 100644
index 00000000..427c38d1
--- /dev/null
+++ b/tests/testsuite/book_test.rs
@@ -0,0 +1,442 @@
+//! Utility for building and running tests against mdbook.
+
+use mdbook::book::BookBuilder;
+use mdbook::MDBook;
+use snapbox::IntoData;
+use std::collections::BTreeMap;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+use std::sync::atomic::{AtomicU32, Ordering};
+
+/// Test number used for generating unique temp directory names.
+static NEXT_TEST_ID: AtomicU32 = AtomicU32::new(0);
+
+#[derive(Clone, Copy, Eq, PartialEq)]
+enum StatusCode {
+ Success,
+ Failure,
+ Code(i32),
+}
+
+/// Main helper for driving mdbook tests.
+pub struct BookTest {
+ /// The temp directory where the test should perform its work.
+ pub dir: PathBuf,
+ assert: snapbox::Assert,
+ /// This indicates whether or not the book has been built.
+ built: bool,
+}
+
+impl BookTest {
+ /// Creates a new test, copying the contents from the given directory into
+ /// a temp directory.
+ pub fn from_dir(dir: &str) -> BookTest {
+ // Copy this test book to a temp directory.
+ let dir = Path::new("tests/testsuite").join(dir);
+ assert!(dir.exists(), "{dir:?} should exist");
+ let tmp = Self::new_tmp();
+ mdbook::utils::fs::copy_files_except_ext(
+ &dir,
+ &tmp,
+ true,
+ Some(&PathBuf::from("book")),
+ &[],
+ )
+ .unwrap_or_else(|e| panic!("failed to copy test book {dir:?} to {tmp:?}: {e:?}"));
+ Self::new(tmp)
+ }
+
+ /// Creates a new test with an empty temp directory.
+ pub fn empty() -> BookTest {
+ Self::new(Self::new_tmp())
+ }
+
+ /// Creates a new test with the given function to initialize a new book.
+ ///
+ /// The book itself is not built.
+ pub fn init(f: impl Fn(&mut BookBuilder)) -> BookTest {
+ let tmp = Self::new_tmp();
+ let mut bb = MDBook::init(&tmp);
+ f(&mut bb);
+ bb.build()
+ .unwrap_or_else(|e| panic!("failed to initialize book at {tmp:?}: {e:?}"));
+ Self::new(tmp)
+ }
+
+ fn new_tmp() -> PathBuf {
+ let id = NEXT_TEST_ID.fetch_add(1, Ordering::SeqCst);
+ let tmp = Path::new(env!("CARGO_TARGET_TMPDIR"))
+ .join("ts")
+ .join(format!("t{id}"));
+ if tmp.exists() {
+ std::fs::remove_dir_all(&tmp)
+ .unwrap_or_else(|e| panic!("failed to remove {tmp:?}: {e:?}"));
+ }
+ std::fs::create_dir_all(&tmp).unwrap_or_else(|e| panic!("failed to create {tmp:?}: {e:?}"));
+ tmp
+ }
+
+ fn new(dir: PathBuf) -> BookTest {
+ let assert = assert(&dir);
+ BookTest {
+ dir,
+ assert,
+ built: false,
+ }
+ }
+
+ /// Checks the contents of an HTML file that it has the given contents
+ /// between the `` tag.
+ ///
+ /// Normally the contents outside of the `` tag aren't interesting,
+ /// and they add a significant amount of noise.
+ pub fn check_main_file(&mut self, path: &str, expected: impl IntoData) -> &mut Self {
+ if !self.built {
+ self.build();
+ }
+ let full_path = self.dir.join(path);
+ let actual = read_to_string(&full_path);
+ let start = actual
+ .find("")
+ .unwrap_or_else(|| panic!("didn't find in:\n{actual}"));
+ let end = actual.find(" ").unwrap();
+ let contents = actual[start + 6..end - 7].trim();
+ self.assert.eq(contents, expected);
+ self
+ }
+
+ /// Checks the summary contents of `toc.js` against the expected value.
+ pub fn check_toc_js(&mut self, expected: impl IntoData) -> &mut Self {
+ if !self.built {
+ self.build();
+ }
+ let inner = self.toc_js_html();
+ // Would be nice if this were prettified, but a primitive wrapping will do for now.
+ let inner = inner.replace("><", ">\n<");
+ self.assert.eq(inner, expected);
+ self
+ }
+
+ /// Returns the summary contents from `toc.js`.
+ pub fn toc_js_html(&self) -> String {
+ let full_path = self.dir.join("book/toc.js");
+ let actual = read_to_string(&full_path);
+ let inner = actual
+ .lines()
+ .filter_map(|line| {
+ let line = line.trim().strip_prefix("this.innerHTML = '")?;
+ let line = line.strip_suffix("';")?;
+ Some(line)
+ })
+ .next()
+ .expect("should have innerHTML");
+ inner.to_string()
+ }
+
+ /// Checks that the contents of the given file matches the expected value.
+ pub fn check_file(&mut self, path: &str, expected: impl IntoData) -> &mut Self {
+ if !self.built {
+ self.build();
+ }
+ let path = self.dir.join(path);
+ let actual = read_to_string(&path);
+ self.assert.eq(actual, expected);
+ self
+ }
+
+ /// Checks that the given file contains the given string somewhere.
+ pub fn check_file_contains(&mut self, path: &str, expected: &str) -> &mut Self {
+ if !self.built {
+ self.build();
+ }
+ let path = self.dir.join(path);
+ let actual = read_to_string(&path);
+ assert!(
+ actual.contains(expected),
+ "Did not find {expected:?} in {path:?}\n\n{actual}",
+ );
+ self
+ }
+
+ /// Checks that the given file does not contain the given string anywhere.
+ ///
+ /// Beware that using this is fragile, as it may be unable to catch
+ /// regressions (it can't tell the difference between success, or the
+ /// string being looked for changed).
+ pub fn check_file_doesnt_contain(&mut self, path: &str, string: &str) -> &mut Self {
+ if !self.built {
+ self.build();
+ }
+ let path = self.dir.join(path);
+ let actual = read_to_string(&path);
+ assert!(
+ !actual.contains(string),
+ "Unexpectedly found {string:?} in {path:?}\n\n{actual}",
+ );
+ self
+ }
+
+ /// Checks that the list of files at the given path matches the given value.
+ pub fn check_file_list(&mut self, path: &str, expected: impl IntoData) -> &mut Self {
+ let mut all_paths: Vec<_> = walkdir::WalkDir::new(&self.dir.join(path))
+ .into_iter()
+ // Skip the outer directory.
+ .skip(1)
+ .map(|e| {
+ e.unwrap()
+ .into_path()
+ .strip_prefix(&self.dir)
+ .unwrap()
+ .to_str()
+ .unwrap()
+ .replace('\\', "/")
+ })
+ .collect();
+ all_paths.sort();
+ let actual = all_paths.join("\n");
+ self.assert.eq(actual, expected);
+ self
+ }
+
+ /// Loads an [`MDBook`] from the temp directory.
+ pub fn load_book(&self) -> MDBook {
+ MDBook::load(&self.dir).unwrap_or_else(|e| panic!("book failed to load: {e:?}"))
+ }
+
+ /// Builds the book in the temp directory.
+ pub fn build(&mut self) -> &mut Self {
+ let book = self.load_book();
+ book.build()
+ .unwrap_or_else(|e| panic!("book failed to build: {e:?}"));
+ self.built = true;
+ self
+ }
+
+ /// Runs the `mdbook` binary in the temp directory.
+ ///
+ /// This runs `mdbook` with the given args. The args are split on spaces
+ /// (if you need args with spaces, use the `args` method). The given
+ /// callback receives a [`BookCommand`] for you to customize how the
+ /// executable is run.
+ pub fn run(&mut self, args: &str, f: impl Fn(&mut BookCommand)) -> &mut Self {
+ let mut cmd = BookCommand {
+ assert: self.assert.clone(),
+ dir: self.dir.clone(),
+ args: split_args(args),
+ env: BTreeMap::new(),
+ expect_status: StatusCode::Success,
+ expect_stderr_data: None,
+ expect_stdout_data: None,
+ };
+ f(&mut cmd);
+ cmd.run();
+ self
+ }
+
+ /// Change a file's contents in the given path.
+ pub fn change_file(&mut self, path: impl AsRef, body: &str) -> &mut Self {
+ let path = self.dir.join(path);
+ std::fs::write(&path, body).unwrap_or_else(|e| panic!("failed to write {path:?}: {e:?}"));
+ self
+ }
+
+ /// Builds a Rust program with the given src.
+ ///
+ /// The given path should be the path where to output the executable in
+ /// the temp directory.
+ pub fn rust_program(&mut self, path: &str, src: &str) -> &mut Self {
+ let rs = self.dir.join(path).with_extension("rs");
+ let parent = rs.parent().unwrap();
+ if !parent.exists() {
+ std::fs::create_dir_all(&parent).unwrap();
+ }
+ std::fs::write(&rs, src).unwrap_or_else(|e| panic!("failed to write {rs:?}: {e:?}"));
+ let status = std::process::Command::new("rustc")
+ .arg(&rs)
+ .current_dir(&parent)
+ .status()
+ .expect("rustc should run");
+ assert!(status.success());
+ self
+ }
+}
+
+/// A builder for preparing to run the `mdbook` executable.
+///
+/// By default, it expects the process to succeed.
+pub struct BookCommand {
+ pub dir: PathBuf,
+ assert: snapbox::Assert,
+ args: Vec,
+ env: BTreeMap>,
+ expect_status: StatusCode,
+ expect_stderr_data: Option,
+ expect_stdout_data: Option,
+}
+
+impl BookCommand {
+ /// Indicates that the process should fail.
+ pub fn expect_failure(&mut self) -> &mut Self {
+ self.expect_status = StatusCode::Failure;
+ self
+ }
+
+ /// Indicates the process should fail with the given exit code.
+ pub fn expect_code(&mut self, code: i32) -> &mut Self {
+ self.expect_status = StatusCode::Code(code);
+ self
+ }
+
+ /// Verifies that stderr matches the given value.
+ pub fn expect_stderr(&mut self, expected: impl snapbox::IntoData) -> &mut Self {
+ self.expect_stderr_data = Some(expected.into_data());
+ self
+ }
+
+ /// Verifies that stdout matches the given value.
+ pub fn expect_stdout(&mut self, expected: impl snapbox::IntoData) -> &mut Self {
+ self.expect_stdout_data = Some(expected.into_data());
+ self
+ }
+
+ /// Adds arguments to the command to run.
+ pub fn args(&mut self, args: &[&str]) -> &mut Self {
+ self.args.extend(args.into_iter().map(|t| t.to_string()));
+ self
+ }
+
+ /// Specifies an environment variable to set on the executable.
+ pub fn env>(&mut self, key: &str, value: T) -> &mut Self {
+ self.env.insert(key.to_string(), Some(value.into()));
+ self
+ }
+
+ /// Runs the command, and verifies the output.
+ fn run(&mut self) {
+ let mut cmd = Command::new(env!("CARGO_BIN_EXE_mdbook"));
+ cmd.current_dir(&self.dir)
+ .args(&self.args)
+ .env_remove("RUST_LOG")
+ // Don't read the system git config which is out of our control.
+ .env("GIT_CONFIG_NOSYSTEM", "1")
+ .env("GIT_CONFIG_GLOBAL", &self.dir)
+ .env("GIT_CONFIG_SYSTEM", &self.dir)
+ .env_remove("GIT_AUTHOR_EMAIL")
+ .env_remove("GIT_AUTHOR_NAME")
+ .env_remove("GIT_COMMITTER_EMAIL")
+ .env_remove("GIT_COMMITTER_NAME");
+
+ for (k, v) in &self.env {
+ match v {
+ Some(v) => cmd.env(k, v),
+ None => cmd.env_remove(k),
+ };
+ }
+
+ let output = cmd.output().expect("mdbook should be runnable");
+ let stdout = std::str::from_utf8(&output.stdout).expect("stdout is not utf8");
+ let stderr = std::str::from_utf8(&output.stderr).expect("stderr is not utf8");
+ let render_output = || format!("\n--- stdout\n{stdout}\n--- stderr\n{stderr}");
+ match (self.expect_status, output.status.success()) {
+ (StatusCode::Success, false) => {
+ panic!("mdbook failed, but expected success{}", render_output())
+ }
+ (StatusCode::Failure, true) => {
+ panic!("mdbook succeeded, but expected failure{}", render_output())
+ }
+ (StatusCode::Code(expected), _) => match output.status.code() {
+ Some(actual) => assert_eq!(
+ actual, expected,
+ "process exit code did not match as expected"
+ ),
+ None => panic!("process exited via signal {:?}", output.status),
+ },
+ _ => {}
+ }
+ self.expect_status = StatusCode::Success; // Reset to default.
+ if let Some(expect_stderr_data) = &self.expect_stderr_data {
+ if let Err(e) = self.assert.try_eq(
+ Some(&"stderr"),
+ stderr.into_data(),
+ expect_stderr_data.clone(),
+ ) {
+ panic!("{e}");
+ }
+ }
+ if let Some(expect_stdout_data) = &self.expect_stdout_data {
+ if let Err(e) = self.assert.try_eq(
+ Some(&"stdout"),
+ stdout.into_data(),
+ expect_stdout_data.clone(),
+ ) {
+ panic!("{e}");
+ }
+ }
+ }
+}
+
+fn split_args(s: &str) -> Vec {
+ s.split_whitespace()
+ .map(|arg| {
+ if arg.contains(&['"', '\''][..]) {
+ panic!("shell-style argument parsing is not supported");
+ }
+ String::from(arg)
+ })
+ .collect()
+}
+
+static LITERAL_REDACTIONS: &[(&str, &str)] = &[
+ // Unix message for an entity was not found
+ ("[NOT_FOUND]", "No such file or directory (os error 2)"),
+ // Windows message for an entity was not found
+ (
+ "[NOT_FOUND]",
+ "The system cannot find the file specified. (os error 2)",
+ ),
+ (
+ "[NOT_FOUND]",
+ "The system cannot find the path specified. (os error 3)",
+ ),
+ ("[NOT_FOUND]", "program not found"),
+ // Unix message for exit status
+ ("[EXIT_STATUS]", "exit status"),
+ // Windows message for exit status
+ ("[EXIT_STATUS]", "exit code"),
+ ("[TAB]", "\t"),
+ ("[EXE]", std::env::consts::EXE_SUFFIX),
+];
+
+/// This makes it easier to write regex replacements that are guaranteed to only
+/// get compiled once
+macro_rules! regex {
+ ($re:literal $(,)?) => {{
+ static RE: std::sync::OnceLock = std::sync::OnceLock::new();
+ RE.get_or_init(|| regex::Regex::new($re).unwrap())
+ }};
+}
+
+fn assert(root: &Path) -> snapbox::Assert {
+ let mut subs = snapbox::Redactions::new();
+ subs.insert("[ROOT]", root.to_path_buf()).unwrap();
+ subs.insert(
+ "[TIMESTAMP]",
+ regex!(r"(?m)(?20\d\d-\d{2}-\d{2} \d{2}:\d{2}:\d{2})"),
+ )
+ .unwrap();
+ subs.insert("[VERSION]", mdbook::MDBOOK_VERSION).unwrap();
+
+ subs.extend(LITERAL_REDACTIONS.into_iter().cloned())
+ .unwrap();
+
+ snapbox::Assert::new()
+ .action_env(snapbox::assert::DEFAULT_ACTION_ENV)
+ .redact_with(subs)
+}
+
+/// Helper to read a string from the filesystem.
+#[track_caller]
+pub fn read_to_string>(path: P) -> String {
+ let path = path.as_ref();
+ std::fs::read_to_string(path).unwrap_or_else(|e| panic!("could not read file {path:?}: {e:?}"))
+}
diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs
new file mode 100644
index 00000000..8ed64dd3
--- /dev/null
+++ b/tests/testsuite/main.rs
@@ -0,0 +1,10 @@
+//! Main testsuite for exercising all functionality of mdBook.
+//!
+//! See README.md for documentation.
+
+mod book_test;
+
+mod prelude {
+ pub use crate::book_test::BookTest;
+ pub use snapbox::str;
+}
From b9e433710d8e9ff10f3584a40ba80634f50b297e Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 18:25:43 -0700
Subject: [PATCH 02/69] Migrate build_the_dummy_book to BookTest
(build::basic_build)
This doesn't exercise *everything* that the old test did, but other
tests will take care of those gaps. This is intended as just a smoke
test.
---
tests/rendered_output.rs | 9 ---------
tests/testsuite/build.rs | 18 ++++++++++++++++++
tests/testsuite/build/basic_build/book.toml | 2 ++
.../testsuite/build/basic_build/src/SUMMARY.md | 3 +++
.../build/basic_build/src/chapter_1.md | 1 +
tests/testsuite/main.rs | 1 +
6 files changed, 25 insertions(+), 9 deletions(-)
create mode 100644 tests/testsuite/build.rs
create mode 100644 tests/testsuite/build/basic_build/book.toml
create mode 100644 tests/testsuite/build/basic_build/src/SUMMARY.md
create mode 100644 tests/testsuite/build/basic_build/src/chapter_1.md
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index df1750ee..04c789d5 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -40,15 +40,6 @@ const TOC_SECOND_LEVEL: &[&str] = &[
"2.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 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();
diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs
new file mode 100644
index 00000000..1f75199c
--- /dev/null
+++ b/tests/testsuite/build.rs
@@ -0,0 +1,18 @@
+//! General build tests.
+//!
+//! More specific tests should usually go into a module based on the feature.
+//! This module should just have general build tests, or misc small things.
+
+use crate::prelude::*;
+
+// Simple smoke test that building works.
+#[test]
+fn basic_build() {
+ BookTest::from_dir("build/basic_build").run("build", |cmd| {
+ cmd.expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book): Book building has started
+[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
+
+"#]]);
+ });
+}
diff --git a/tests/testsuite/build/basic_build/book.toml b/tests/testsuite/build/basic_build/book.toml
new file mode 100644
index 00000000..58d56cb6
--- /dev/null
+++ b/tests/testsuite/build/basic_build/book.toml
@@ -0,0 +1,2 @@
+[book]
+title = "basic_build"
diff --git a/tests/testsuite/build/basic_build/src/SUMMARY.md b/tests/testsuite/build/basic_build/src/SUMMARY.md
new file mode 100644
index 00000000..7390c828
--- /dev/null
+++ b/tests/testsuite/build/basic_build/src/SUMMARY.md
@@ -0,0 +1,3 @@
+# Summary
+
+- [Chapter 1](./chapter_1.md)
diff --git a/tests/testsuite/build/basic_build/src/chapter_1.md b/tests/testsuite/build/basic_build/src/chapter_1.md
new file mode 100644
index 00000000..b743fda3
--- /dev/null
+++ b/tests/testsuite/build/basic_build/src/chapter_1.md
@@ -0,0 +1 @@
+# Chapter 1
diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs
index 8ed64dd3..11cd9a81 100644
--- a/tests/testsuite/main.rs
+++ b/tests/testsuite/main.rs
@@ -3,6 +3,7 @@
//! See README.md for documentation.
mod book_test;
+mod build;
mod prelude {
pub use crate::book_test::BookTest;
From ba8107120c01fd5f91977e5cc7c9ba9c8aacaa8b Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 18:32:40 -0700
Subject: [PATCH 03/69] Migrate failure_on_missing_file to BookTest
---
tests/rendered_output.rs | 14 --------------
tests/testsuite/build.rs | 13 +++++++++++++
tests/testsuite/build/missing_file/book.toml | 5 +++++
tests/testsuite/build/missing_file/src/SUMMARY.md | 3 +++
4 files changed, 21 insertions(+), 14 deletions(-)
create mode 100644 tests/testsuite/build/missing_file/book.toml
create mode 100644 tests/testsuite/build/missing_file/src/SUMMARY.md
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index 04c789d5..ec5b1ecf 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -348,20 +348,6 @@ fn check_link_target_fallback() {
);
}
-/// Ensure building fails if `create-missing` is false and one of the files does
-/// not exist.
-#[test]
-fn failure_on_missing_file() {
- 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;
-
- let got = MDBook::load_with_config(temp.path(), cfg);
- assert!(got.is_err());
-}
-
/// Ensure a missing file is created if `create-missing` is true.
#[test]
fn create_missing_file_with_config() {
diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs
index 1f75199c..9c5d9d65 100644
--- a/tests/testsuite/build.rs
+++ b/tests/testsuite/build.rs
@@ -16,3 +16,16 @@ fn basic_build() {
"#]]);
});
}
+
+// Ensure building fails if `create-missing` is false and one of the files does
+// not exist.
+#[test]
+fn failure_on_missing_file() {
+ BookTest::from_dir("build/missing_file").run("build", |cmd| {
+ cmd.expect_failure().expect_stderr(str![[r#"
+[TIMESTAMP] [ERROR] (mdbook::utils): Error: Chapter file not found, ./chapter_1.md
+[TIMESTAMP] [ERROR] (mdbook::utils): [TAB]Caused By: [NOT_FOUND]
+
+"#]]);
+ });
+}
diff --git a/tests/testsuite/build/missing_file/book.toml b/tests/testsuite/build/missing_file/book.toml
new file mode 100644
index 00000000..67e5e5f3
--- /dev/null
+++ b/tests/testsuite/build/missing_file/book.toml
@@ -0,0 +1,5 @@
+[book]
+title = "missing_file"
+
+[build]
+create-missing = false
diff --git a/tests/testsuite/build/missing_file/src/SUMMARY.md b/tests/testsuite/build/missing_file/src/SUMMARY.md
new file mode 100644
index 00000000..7390c828
--- /dev/null
+++ b/tests/testsuite/build/missing_file/src/SUMMARY.md
@@ -0,0 +1,3 @@
+# Summary
+
+- [Chapter 1](./chapter_1.md)
From adcea9b3b9a1acc78e8ebab3c891047edb7d314d Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 18:33:35 -0700
Subject: [PATCH 04/69] Migrate create_missing_file_with_config to BookTest
---
tests/rendered_output.rs | 14 --------------
tests/testsuite/build.rs | 10 ++++++++++
tests/testsuite/build/create_missing/book.toml | 2 ++
.../testsuite/build/create_missing/src/SUMMARY.md | 3 +++
4 files changed, 15 insertions(+), 14 deletions(-)
create mode 100644 tests/testsuite/build/create_missing/book.toml
create mode 100644 tests/testsuite/build/create_missing/src/SUMMARY.md
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index ec5b1ecf..49f2c6f4 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -348,20 +348,6 @@ fn check_link_target_fallback() {
);
}
-/// Ensure a missing file is created if `create-missing` is true.
-#[test]
-fn create_missing_file_with_config() {
- 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 = 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());
-}
-
/// This makes sure you can include a Rust file with `{{#playground example.rs}}`.
/// Specification is in `guide/src/format/rust.md`
#[test]
diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs
index 9c5d9d65..34532d62 100644
--- a/tests/testsuite/build.rs
+++ b/tests/testsuite/build.rs
@@ -29,3 +29,13 @@ fn failure_on_missing_file() {
"#]]);
});
}
+
+// Ensure a missing file is created if `create-missing` is true.
+#[test]
+fn create_missing() {
+ let test = BookTest::from_dir("build/create_missing");
+ assert!(test.dir.join("src/SUMMARY.md").exists());
+ assert!(!test.dir.join("src/chapter_1.md").exists());
+ test.load_book();
+ assert!(test.dir.join("src/chapter_1.md").exists());
+}
diff --git a/tests/testsuite/build/create_missing/book.toml b/tests/testsuite/build/create_missing/book.toml
new file mode 100644
index 00000000..d347afe8
--- /dev/null
+++ b/tests/testsuite/build/create_missing/book.toml
@@ -0,0 +1,2 @@
+[book]
+title = "create_missing"
diff --git a/tests/testsuite/build/create_missing/src/SUMMARY.md b/tests/testsuite/build/create_missing/src/SUMMARY.md
new file mode 100644
index 00000000..7390c828
--- /dev/null
+++ b/tests/testsuite/build/create_missing/src/SUMMARY.md
@@ -0,0 +1,3 @@
+# Summary
+
+- [Chapter 1](./chapter_1.md)
From 3706ddc5cc2195917faf2ee16a3c3643737a3203 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 18:34:40 -0700
Subject: [PATCH 05/69] Migrate book_with_a_reserved_filename_does_not_build to
BookTest
---
tests/rendered_output.rs | 18 ------------------
tests/testsuite/build.rs | 14 ++++++++++++++
.../build/no_reserved_filename/book.toml | 2 ++
.../build/no_reserved_filename/src/SUMMARY.md | 3 +++
.../build/no_reserved_filename/src/print.md | 1 +
5 files changed, 20 insertions(+), 18 deletions(-)
create mode 100644 tests/testsuite/build/no_reserved_filename/book.toml
create mode 100644 tests/testsuite/build/no_reserved_filename/src/SUMMARY.md
create mode 100644 tests/testsuite/build/no_reserved_filename/src/print.md
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index 49f2c6f4..f648b72d 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -405,24 +405,6 @@ fn example_book_can_build() {
md.build().unwrap();
}
-#[test]
-fn book_with_a_reserved_filename_does_not_build() {
- let tmp_dir = TempFileBuilder::new().prefix("mdBook").tempdir().unwrap();
- 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();
-
- let md = MDBook::load(tmp_dir.path()).unwrap();
- let got = md.build();
- assert!(got.is_err());
-}
-
#[test]
fn by_default_mdbook_use_index_preprocessor_to_convert_readme_to_index() {
let temp = DummyBook::new().build().unwrap();
diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs
index 34532d62..f276ab96 100644
--- a/tests/testsuite/build.rs
+++ b/tests/testsuite/build.rs
@@ -39,3 +39,17 @@ fn create_missing() {
test.load_book();
assert!(test.dir.join("src/chapter_1.md").exists());
}
+
+// Checks that it fails if the summary has a reserved filename.
+#[test]
+fn no_reserved_filename() {
+ BookTest::from_dir("build/no_reserved_filename").run("build", |cmd| {
+ cmd.expect_failure().expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book): Book building has started
+[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
+[TIMESTAMP] [ERROR] (mdbook::utils): Error: Rendering failed
+[TIMESTAMP] [ERROR] (mdbook::utils): [TAB]Caused By: print.md is reserved for internal use
+
+"#]]);
+ });
+}
diff --git a/tests/testsuite/build/no_reserved_filename/book.toml b/tests/testsuite/build/no_reserved_filename/book.toml
new file mode 100644
index 00000000..58c1144e
--- /dev/null
+++ b/tests/testsuite/build/no_reserved_filename/book.toml
@@ -0,0 +1,2 @@
+[book]
+title = "no_reserved_filename"
diff --git a/tests/testsuite/build/no_reserved_filename/src/SUMMARY.md b/tests/testsuite/build/no_reserved_filename/src/SUMMARY.md
new file mode 100644
index 00000000..2c8b949e
--- /dev/null
+++ b/tests/testsuite/build/no_reserved_filename/src/SUMMARY.md
@@ -0,0 +1,3 @@
+# Summary
+
+- [Print](print.md)
diff --git a/tests/testsuite/build/no_reserved_filename/src/print.md b/tests/testsuite/build/no_reserved_filename/src/print.md
new file mode 100644
index 00000000..849c2d3e
--- /dev/null
+++ b/tests/testsuite/build/no_reserved_filename/src/print.md
@@ -0,0 +1 @@
+# Print
From ac3e4b6c1e1d41213567f95f5e62074d5cf8cb52 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 18:36:47 -0700
Subject: [PATCH 06/69] Migrate book_toml_isnt_required to BookTest
---
tests/init.rs | 10 ----------
tests/testsuite/build.rs | 12 ++++++++++++
2 files changed, 12 insertions(+), 10 deletions(-)
diff --git a/tests/init.rs b/tests/init.rs
index ae064b31..9b2d8ea5 100644
--- a/tests/init.rs
+++ b/tests/init.rs
@@ -98,16 +98,6 @@ fn run_mdbook_init_with_custom_book_and_src_locations() {
);
}
-#[test]
-fn book_toml_isnt_required() {
- let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
- let md = MDBook::init(temp.path()).build().unwrap();
-
- let _ = fs::remove_file(temp.path().join("book.toml"));
-
- md.build().unwrap();
-}
-
#[test]
fn copy_theme() {
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs
index f276ab96..f197e974 100644
--- a/tests/testsuite/build.rs
+++ b/tests/testsuite/build.rs
@@ -53,3 +53,15 @@ fn no_reserved_filename() {
"#]]);
});
}
+
+// Build without book.toml should be OK.
+#[test]
+fn book_toml_isnt_required() {
+ let mut test = BookTest::init(|_| {});
+ std::fs::remove_file(test.dir.join("book.toml")).unwrap();
+ test.build();
+ test.check_main_file(
+ "book/chapter_1.html",
+ str![[r##" "##]],
+ );
+}
From dd778d50f94a1bb7a0ea20b496faab2ac98b7bca Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 18:39:25 -0700
Subject: [PATCH 07/69] Add some basic help tests
---
tests/cli/test.rs | 12 ------
tests/testsuite/cli.rs | 36 ++++++++++++++++
tests/testsuite/cli/help.term.svg | 63 ++++++++++++++++++++++++++++
tests/testsuite/cli/no_args.term.svg | 63 ++++++++++++++++++++++++++++
tests/testsuite/main.rs | 1 +
5 files changed, 163 insertions(+), 12 deletions(-)
create mode 100644 tests/testsuite/cli.rs
create mode 100644 tests/testsuite/cli/help.term.svg
create mode 100644 tests/testsuite/cli/no_args.term.svg
diff --git a/tests/cli/test.rs b/tests/cli/test.rs
index 56e0f159..63114d3a 100644
--- a/tests/cli/test.rs
+++ b/tests/cli/test.rs
@@ -32,15 +32,3 @@ fn mdbook_cli_detects_book_with_failing_tests() {
.stderr(predicates::str::is_match(r##"returned an error:\n\n"##).unwrap())
.stderr(predicates::str::is_match(r##"Nested_Chapter::Rustdoc_include_works_with_anchors_too \(line \d+\) ... FAILED"##).unwrap());
}
-
-#[test]
-fn empty_cli() {
- let mut cmd = mdbook_cmd();
- cmd.assert()
- .failure()
- .code(2)
- .stdout(predicates::str::is_empty())
- .stderr(predicates::str::contains(
- "Creates a book from markdown files",
- ));
-}
diff --git a/tests/testsuite/cli.rs b/tests/testsuite/cli.rs
new file mode 100644
index 00000000..caa59752
--- /dev/null
+++ b/tests/testsuite/cli.rs
@@ -0,0 +1,36 @@
+//! Basic tests for mdbook's CLI.
+
+use crate::prelude::*;
+use snapbox::file;
+
+// Test with no args.
+#[test]
+#[cfg_attr(
+ not(all(feature = "watch", feature = "serve")),
+ ignore = "needs all features"
+)]
+fn no_args() {
+ BookTest::empty().run("", |cmd| {
+ cmd.expect_code(2)
+ .expect_stdout(str![[""]])
+ .expect_stderr(file!["cli/no_args.term.svg"]);
+ });
+}
+
+// Help command.
+#[test]
+#[cfg_attr(
+ not(all(feature = "watch", feature = "serve")),
+ ignore = "needs all features"
+)]
+fn help() {
+ BookTest::empty()
+ .run("help", |cmd| {
+ cmd.expect_stdout(file!["cli/help.term.svg"])
+ .expect_stderr(str![[""]]);
+ })
+ .run("--help", |cmd| {
+ cmd.expect_stdout(file!["cli/help.term.svg"])
+ .expect_stderr(str![[""]]);
+ });
+}
diff --git a/tests/testsuite/cli/help.term.svg b/tests/testsuite/cli/help.term.svg
new file mode 100644
index 00000000..e27fa313
--- /dev/null
+++ b/tests/testsuite/cli/help.term.svg
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+ Creates a book from markdown files
+
+
+
+ Usage: mdbook[EXE] [COMMAND]
+
+
+
+ Commands:
+
+ init Creates the boilerplate structure and files for a new book
+
+ build Builds a book from its markdown files
+
+ test Tests that a book's Rust code samples compile
+
+ clean Deletes a built book
+
+ completions Generate shell completions for your shell to stdout
+
+ watch Watches a book's files and rebuilds it on changes
+
+ serve Serves a book at http://localhost:3000, and rebuilds it on changes
+
+ help Print this message or the help of the given subcommand(s)
+
+
+
+ Options:
+
+ -h, --help Print help
+
+ -V, --version Print version
+
+
+
+ For more information about a specific command, try `mdbook <command> --help`
+
+ The source code for mdBook is available at: https://github.com/rust-lang/mdBook
+
+
+
+
+
+
diff --git a/tests/testsuite/cli/no_args.term.svg b/tests/testsuite/cli/no_args.term.svg
new file mode 100644
index 00000000..e27fa313
--- /dev/null
+++ b/tests/testsuite/cli/no_args.term.svg
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+ Creates a book from markdown files
+
+
+
+ Usage: mdbook[EXE] [COMMAND]
+
+
+
+ Commands:
+
+ init Creates the boilerplate structure and files for a new book
+
+ build Builds a book from its markdown files
+
+ test Tests that a book's Rust code samples compile
+
+ clean Deletes a built book
+
+ completions Generate shell completions for your shell to stdout
+
+ watch Watches a book's files and rebuilds it on changes
+
+ serve Serves a book at http://localhost:3000, and rebuilds it on changes
+
+ help Print this message or the help of the given subcommand(s)
+
+
+
+ Options:
+
+ -h, --help Print help
+
+ -V, --version Print version
+
+
+
+ For more information about a specific command, try `mdbook <command> --help`
+
+ The source code for mdBook is available at: https://github.com/rust-lang/mdBook
+
+
+
+
+
+
diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs
index 11cd9a81..c8630267 100644
--- a/tests/testsuite/main.rs
+++ b/tests/testsuite/main.rs
@@ -4,6 +4,7 @@
mod book_test;
mod build;
+mod cli;
mod prelude {
pub use crate::book_test::BookTest;
From 03470a7531a73f152f569c4ebadc5eccb209b600 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 18:42:20 -0700
Subject: [PATCH 08/69] Migrate able_to_include_files_in_chapters to BookTest
---
tests/rendered_output.rs | 18 -------------
tests/testsuite/includes.rs | 25 +++++++++++++++++++
.../testsuite/includes/all_includes/book.toml | 6 +++++
.../includes/all_includes/src/SUMMARY.md | 8 ++++++
.../includes/all_includes/src/anchors.md | 5 ++++
.../includes/all_includes/src/example.rs | 6 +++++
.../includes/all_includes/src/includes.md | 4 +++
.../src/nested-test-with-anchors.rs | 6 +++++
.../partially-included-test-with-anchors.rs | 11 ++++++++
.../src/partially-included-test.rs | 7 ++++++
.../includes/all_includes/src/playground.md | 3 +++
.../includes/all_includes/src/recursive.md | 2 ++
.../all_includes/src/relative/includes.md | 3 +++
.../includes/all_includes/src/rustdoc.md | 13 ++++++++++
.../includes/all_includes/src/sample.md | 3 +++
tests/testsuite/main.rs | 1 +
16 files changed, 103 insertions(+), 18 deletions(-)
create mode 100644 tests/testsuite/includes.rs
create mode 100644 tests/testsuite/includes/all_includes/book.toml
create mode 100644 tests/testsuite/includes/all_includes/src/SUMMARY.md
create mode 100644 tests/testsuite/includes/all_includes/src/anchors.md
create mode 100644 tests/testsuite/includes/all_includes/src/example.rs
create mode 100644 tests/testsuite/includes/all_includes/src/includes.md
create mode 100644 tests/testsuite/includes/all_includes/src/nested-test-with-anchors.rs
create mode 100644 tests/testsuite/includes/all_includes/src/partially-included-test-with-anchors.rs
create mode 100644 tests/testsuite/includes/all_includes/src/partially-included-test.rs
create mode 100644 tests/testsuite/includes/all_includes/src/playground.md
create mode 100644 tests/testsuite/includes/all_includes/src/recursive.md
create mode 100644 tests/testsuite/includes/all_includes/src/relative/includes.md
create mode 100644 tests/testsuite/includes/all_includes/src/rustdoc.md
create mode 100644 tests/testsuite/includes/all_includes/src/sample.md
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index f648b72d..0a151a54 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -364,24 +364,6 @@ fn able_to_include_playground_files_in_chapters() {
assert_doesnt_contain_strings(&second, &["{{#playground example.rs}}"]);
}
-/// 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");
-
- let summary_strings = &[
- r##" "##,
- ">First Chapter",
- ];
- assert_contains_strings(&includes, summary_strings);
-
- assert_doesnt_contain_strings(&includes, &["{{#include ../SUMMARY.md::}}"]);
-}
-
/// Ensure cyclic includes are capped so that no exceptions occur
#[test]
fn recursive_includes_are_capped() {
diff --git a/tests/testsuite/includes.rs b/tests/testsuite/includes.rs
new file mode 100644
index 00000000..1e4a7c53
--- /dev/null
+++ b/tests/testsuite/includes.rs
@@ -0,0 +1,25 @@
+//! Tests for include preprocessor.
+
+use crate::prelude::*;
+
+// Basic test for #include.
+#[test]
+fn include() {
+ BookTest::from_dir("includes/all_includes")
+ .check_main_file(
+ "book/includes.html",
+ str![[r##"
+
+
+This is a sample include.
+"##]],
+ )
+ .check_main_file(
+ "book/relative/includes.html",
+ str![[r##"
+
+
+This is a sample include.
+"##]],
+ );
+}
diff --git a/tests/testsuite/includes/all_includes/book.toml b/tests/testsuite/includes/all_includes/book.toml
new file mode 100644
index 00000000..622694be
--- /dev/null
+++ b/tests/testsuite/includes/all_includes/book.toml
@@ -0,0 +1,6 @@
+[book]
+authors = ["Eric Huss"]
+language = "en"
+multilingual = false
+src = "src"
+title = "all_includes"
diff --git a/tests/testsuite/includes/all_includes/src/SUMMARY.md b/tests/testsuite/includes/all_includes/src/SUMMARY.md
new file mode 100644
index 00000000..25a9a15f
--- /dev/null
+++ b/tests/testsuite/includes/all_includes/src/SUMMARY.md
@@ -0,0 +1,8 @@
+# Summary
+
+- [Basic Includes](./includes.md)
+ - [Relative Includes](./relative/includes.md)
+- [Recursive Includes](./recursive.md)
+- [Include Anchors](./anchors.md)
+- [Rustdoc Includes](./rustdoc.md)
+- [Playground Includes](./playground.md)
diff --git a/tests/testsuite/includes/all_includes/src/anchors.md b/tests/testsuite/includes/all_includes/src/anchors.md
new file mode 100644
index 00000000..0ffbc788
--- /dev/null
+++ b/tests/testsuite/includes/all_includes/src/anchors.md
@@ -0,0 +1,5 @@
+# Include Anchors
+
+```rust
+{{#include nested-test-with-anchors.rs:myanchor}}
+```
diff --git a/tests/testsuite/includes/all_includes/src/example.rs b/tests/testsuite/includes/all_includes/src/example.rs
new file mode 100644
index 00000000..6b49705c
--- /dev/null
+++ b/tests/testsuite/includes/all_includes/src/example.rs
@@ -0,0 +1,6 @@
+fn main() {
+ println!("Hello World!");
+#
+# // You can even hide lines! :D
+# println!("I am hidden! Expand the code snippet to see me");
+}
diff --git a/tests/testsuite/includes/all_includes/src/includes.md b/tests/testsuite/includes/all_includes/src/includes.md
new file mode 100644
index 00000000..539aa939
--- /dev/null
+++ b/tests/testsuite/includes/all_includes/src/includes.md
@@ -0,0 +1,4 @@
+# Basic Includes
+
+{{#include sample.md}}
+
diff --git a/tests/testsuite/includes/all_includes/src/nested-test-with-anchors.rs b/tests/testsuite/includes/all_includes/src/nested-test-with-anchors.rs
new file mode 100644
index 00000000..93c61dd4
--- /dev/null
+++ b/tests/testsuite/includes/all_includes/src/nested-test-with-anchors.rs
@@ -0,0 +1,6 @@
+// This is a test of includes with anchors.
+
+// ANCHOR: myanchor
+// ANCHOR: unendinganchor
+let x = 1;
+// ANCHOR_END: myanchor
diff --git a/tests/testsuite/includes/all_includes/src/partially-included-test-with-anchors.rs b/tests/testsuite/includes/all_includes/src/partially-included-test-with-anchors.rs
new file mode 100644
index 00000000..8e732d0b
--- /dev/null
+++ b/tests/testsuite/includes/all_includes/src/partially-included-test-with-anchors.rs
@@ -0,0 +1,11 @@
+fn some_other_function() {
+ // ANCHOR: unused-anchor-that-should-be-stripped
+ println!("unused anchor");
+ // ANCHOR_END: unused-anchor-that-should-be-stripped
+}
+
+// ANCHOR: rustdoc-include-anchor
+fn main() {
+ some_other_function();
+}
+// ANCHOR_END: rustdoc-include-anchor
diff --git a/tests/testsuite/includes/all_includes/src/partially-included-test.rs b/tests/testsuite/includes/all_includes/src/partially-included-test.rs
new file mode 100644
index 00000000..915651ea
--- /dev/null
+++ b/tests/testsuite/includes/all_includes/src/partially-included-test.rs
@@ -0,0 +1,7 @@
+fn some_function() {
+ println!("some function");
+}
+
+fn main() {
+ some_function();
+}
diff --git a/tests/testsuite/includes/all_includes/src/playground.md b/tests/testsuite/includes/all_includes/src/playground.md
new file mode 100644
index 00000000..9c20530c
--- /dev/null
+++ b/tests/testsuite/includes/all_includes/src/playground.md
@@ -0,0 +1,3 @@
+# Playground Includes
+
+{{#playground example.rs}}
diff --git a/tests/testsuite/includes/all_includes/src/recursive.md b/tests/testsuite/includes/all_includes/src/recursive.md
new file mode 100644
index 00000000..cb82a52f
--- /dev/null
+++ b/tests/testsuite/includes/all_includes/src/recursive.md
@@ -0,0 +1,2 @@
+Around the world, around the world
+{{#include recursive.md}}
diff --git a/tests/testsuite/includes/all_includes/src/relative/includes.md b/tests/testsuite/includes/all_includes/src/relative/includes.md
new file mode 100644
index 00000000..f0a6fce6
--- /dev/null
+++ b/tests/testsuite/includes/all_includes/src/relative/includes.md
@@ -0,0 +1,3 @@
+# Relative Includes
+
+{{#include ../sample.md}}
diff --git a/tests/testsuite/includes/all_includes/src/rustdoc.md b/tests/testsuite/includes/all_includes/src/rustdoc.md
new file mode 100644
index 00000000..8a342d90
--- /dev/null
+++ b/tests/testsuite/includes/all_includes/src/rustdoc.md
@@ -0,0 +1,13 @@
+# Rustdoc Includes
+
+## Rustdoc include adds the rest of the file as hidden
+
+```rust
+{{#rustdoc_include partially-included-test.rs:5:7}}
+```
+
+## Rustdoc include works with anchors too
+
+```rust
+{{#rustdoc_include partially-included-test-with-anchors.rs:rustdoc-include-anchor}}
+```
diff --git a/tests/testsuite/includes/all_includes/src/sample.md b/tests/testsuite/includes/all_includes/src/sample.md
new file mode 100644
index 00000000..91071ef8
--- /dev/null
+++ b/tests/testsuite/includes/all_includes/src/sample.md
@@ -0,0 +1,3 @@
+## Sample
+
+This is a sample include.
diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs
index c8630267..d273b992 100644
--- a/tests/testsuite/main.rs
+++ b/tests/testsuite/main.rs
@@ -5,6 +5,7 @@
mod book_test;
mod build;
mod cli;
+mod includes;
mod prelude {
pub use crate::book_test::BookTest;
From 7add0dbf1062643e6581cf59739a23c458d1a467 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 18:44:53 -0700
Subject: [PATCH 09/69] Migrate
anchors_include_text_between_but_not_anchor_comments to BookTest
---
tests/rendered_output.rs | 14 --------------
tests/testsuite/includes.rs | 15 +++++++++++++++
2 files changed, 15 insertions(+), 14 deletions(-)
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index 0a151a54..46e770f9 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -127,20 +127,6 @@ fn rendered_code_does_not_have_playground_stuff_in_html_when_disabled_in_config(
assert_doesnt_contain_strings(nested, &playground_class);
}
-#[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();
diff --git a/tests/testsuite/includes.rs b/tests/testsuite/includes.rs
index 1e4a7c53..f1aca1f0 100644
--- a/tests/testsuite/includes.rs
+++ b/tests/testsuite/includes.rs
@@ -23,3 +23,18 @@ fn include() {
"##]],
);
}
+
+// Checks for anchored includes.
+#[test]
+fn anchored_include() {
+ BookTest::from_dir("includes/all_includes").check_main_file(
+ "book/anchors.html",
+ str![[r##"
+
+#![allow(unused)]
+ fn main() {
+ let x = 1;
+}
+"##]],
+ );
+}
From 9952ac15a52bbebeefacd5a490529fcdb3227741 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 18:45:41 -0700
Subject: [PATCH 10/69] Migrate recursive_includes_are_capped to BookTest
---
tests/rendered_output.rs | 14 --------------
tests/testsuite/includes.rs | 30 ++++++++++++++++++++++++++++++
2 files changed, 30 insertions(+), 14 deletions(-)
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index 46e770f9..a67f01f3 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -350,20 +350,6 @@ fn able_to_include_playground_files_in_chapters() {
assert_doesnt_contain_strings(&second, &["{{#playground example.rs}}"]);
}
-/// 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);
-}
-
#[test]
fn example_book_can_build() {
let example_book_dir = dummy_book::new_copy_of_example_book().unwrap();
diff --git a/tests/testsuite/includes.rs b/tests/testsuite/includes.rs
index f1aca1f0..d63be13c 100644
--- a/tests/testsuite/includes.rs
+++ b/tests/testsuite/includes.rs
@@ -38,3 +38,33 @@ fn anchored_include() {
"##]],
);
}
+
+// Checks behavior of recursive include.
+#[test]
+fn recursive_include() {
+ BookTest::from_dir("includes/all_includes")
+ .run("build", |cmd| {
+ cmd.expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book): Book building has started
+[TIMESTAMP] [ERROR] (mdbook::preprocess::links): Stack depth exceeded in recursive.md. Check for cyclic includes
+[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
+
+"#]]);
+ })
+ .check_main_file(
+ "book/recursive.html",
+ str![[r#"
+Around the world, around the world
+Around the world, around the world
+Around the world, around the world
+Around the world, around the world
+Around the world, around the world
+Around the world, around the world
+Around the world, around the world
+Around the world, around the world
+Around the world, around the world
+Around the world, around the world
+Around the world, around the world
+"#]],
+ );
+}
From 342b6ee7b54d0632028c325552b29fa27717d283 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 18:46:46 -0700
Subject: [PATCH 11/69] Migrate able_to_include_playground_files_in_chapters to
BookTest
---
tests/rendered_output.rs | 16 ----------------
tests/testsuite/includes.rs | 16 ++++++++++++++++
2 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index a67f01f3..5cf91f66 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -334,22 +334,6 @@ fn check_link_target_fallback() {
);
}
-/// This makes sure you can include a Rust file with `{{#playground example.rs}}`.
-/// Specification is in `guide/src/format/rust.md`
-#[test]
-fn able_to_include_playground_files_in_chapters() {
- let temp = DummyBook::new().build().unwrap();
- let md = MDBook::load(temp.path()).unwrap();
- md.build().unwrap();
-
- let second = temp.path().join("book/second.html");
-
- let playground_strings = &[r#"class="playground""#, r#"println!("Hello World!");"#];
-
- assert_contains_strings(&second, playground_strings);
- assert_doesnt_contain_strings(&second, &["{{#playground example.rs}}"]);
-}
-
#[test]
fn example_book_can_build() {
let example_book_dir = dummy_book::new_copy_of_example_book().unwrap();
diff --git a/tests/testsuite/includes.rs b/tests/testsuite/includes.rs
index d63be13c..c7c28a1a 100644
--- a/tests/testsuite/includes.rs
+++ b/tests/testsuite/includes.rs
@@ -68,3 +68,19 @@ Around the world, around the world
"#]],
);
}
+
+// Checks the behavior of `{{#playground}}` include.
+#[test]
+fn playground_include() {
+ BookTest::from_dir("includes/all_includes")
+ .check_main_file("book/playground.html",
+ str![[r##"
+
+fn main() {
+ println!("Hello World!");
+
+ // You can even hide lines! :D
+ println!("I am hidden! Expand the code snippet to see me");
+ }
+"##]]);
+}
From 0f397ebdb5480ad8cf225aa4a6bb37f7539f62d0 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 18:51:59 -0700
Subject: [PATCH 12/69] Migrate
rustdoc_include_hides_the_unspecified_part_of_the_file to BookTest
---
tests/rendered_output.rs | 15 ---------------
tests/testsuite/includes.rs | 26 ++++++++++++++++++++++++++
2 files changed, 26 insertions(+), 15 deletions(-)
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index 5cf91f66..591634db 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -127,21 +127,6 @@ fn rendered_code_does_not_have_playground_stuff_in_html_when_disabled_in_config(
assert_doesnt_contain_strings(nested, &playground_class);
}
-#[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");
- let text = vec![
- "fn some_function() {",
- "fn some_other_function() {",
- ];
-
- assert_contains_strings(nested, &text);
-}
-
#[test]
fn chapter_content_appears_in_rendered_document() {
let content = vec![
diff --git a/tests/testsuite/includes.rs b/tests/testsuite/includes.rs
index c7c28a1a..312caede 100644
--- a/tests/testsuite/includes.rs
+++ b/tests/testsuite/includes.rs
@@ -84,3 +84,29 @@ fn playground_include() {
}
"##]]);
}
+
+// Checks the behavior of `{{#rustdoc_include}}`.
+#[test]
+fn rustdoc_include() {
+ BookTest::from_dir("includes/all_includes")
+ .check_main_file("book/rustdoc.html",
+ str![[r##"
+
+
+fn some_function() {
+ println!("some function");
+ }
+
+ fn main() {
+ some_function();
+}
+
+fn some_other_function() {
+ println!("unused anchor");
+ }
+
+ fn main() {
+ some_other_function();
+}
+"##]]);
+}
From c6d9f15cba3450ae225b3238ad7959a50f621bd0 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 18:53:36 -0700
Subject: [PATCH 13/69] Migrate
by_default_mdbook_use_index_preprocessor_to_convert_readme_to_index to
BookTest
---
tests/rendered_output.rs | 20 ----------
tests/testsuite/index.rs | 38 +++++++++++++++++++
tests/testsuite/index/basic_readme/book.toml | 2 +
.../index/basic_readme/src/README.md | 1 +
.../index/basic_readme/src/SUMMARY.md | 6 +++
.../index/basic_readme/src/first/README | 1 +
.../index/basic_readme/src/second/Readme.md | 1 +
tests/testsuite/main.rs | 1 +
8 files changed, 50 insertions(+), 20 deletions(-)
create mode 100644 tests/testsuite/index.rs
create mode 100644 tests/testsuite/index/basic_readme/book.toml
create mode 100644 tests/testsuite/index/basic_readme/src/README.md
create mode 100644 tests/testsuite/index/basic_readme/src/SUMMARY.md
create mode 100644 tests/testsuite/index/basic_readme/src/first/README
create mode 100644 tests/testsuite/index/basic_readme/src/second/Readme.md
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index 591634db..7a726f87 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -328,26 +328,6 @@ fn example_book_can_build() {
md.build().unwrap();
}
-#[test]
-fn by_default_mdbook_use_index_preprocessor_to_convert_readme_to_index() {
- let temp = DummyBook::new().build().unwrap();
- let mut cfg = Config::default();
- cfg.set("book.src", "src2")
- .expect("Couldn't set config.book.src to \"src2\".");
- let md = MDBook::load_with_config(temp.path(), cfg).unwrap();
- md.build().unwrap();
-
- let first_index = temp.path().join("book").join("toc.js");
- let expected_strings = vec![
- r#"href="first/index.html""#,
- r#"href="second/index.html""#,
- "1st README",
- "2nd README",
- ];
- assert_contains_strings(&first_index, &expected_strings);
- assert_doesnt_contain_strings(&first_index, &["README.html", "Second README"]);
-}
-
#[test]
fn first_chapter_is_copied_as_index_even_if_not_first_elem() {
let temp = DummyBook::new().build().unwrap();
diff --git a/tests/testsuite/index.rs b/tests/testsuite/index.rs
new file mode 100644
index 00000000..0555cc63
--- /dev/null
+++ b/tests/testsuite/index.rs
@@ -0,0 +1,38 @@
+//! Tests for the index preprocessor.
+
+use crate::prelude::*;
+
+// Checks basic README to index.html conversion.
+#[test]
+fn readme_to_index() {
+ let mut test = BookTest::from_dir("index/basic_readme");
+ test.check_main_file(
+ "book/index.html",
+ str![[r##" "##]],
+ )
+ .check_main_file(
+ "book/first/index.html",
+ str![[r##" "##]],
+ )
+ .check_main_file(
+ "book/second/index.html",
+ str![[r##" "##]],
+ )
+ .check_toc_js(str![[r#"
+
+
+Intro
+
+
+
+1. First
+
+
+
+2. Second
+
+
+"#]]);
+ assert!(test.dir.join("book/index.html").exists());
+ assert!(!test.dir.join("book/README.html").exists());
+}
diff --git a/tests/testsuite/index/basic_readme/book.toml b/tests/testsuite/index/basic_readme/book.toml
new file mode 100644
index 00000000..271699ca
--- /dev/null
+++ b/tests/testsuite/index/basic_readme/book.toml
@@ -0,0 +1,2 @@
+[book]
+title = "basic_readme"
diff --git a/tests/testsuite/index/basic_readme/src/README.md b/tests/testsuite/index/basic_readme/src/README.md
new file mode 100644
index 00000000..1e0981f1
--- /dev/null
+++ b/tests/testsuite/index/basic_readme/src/README.md
@@ -0,0 +1 @@
+# Intro
diff --git a/tests/testsuite/index/basic_readme/src/SUMMARY.md b/tests/testsuite/index/basic_readme/src/SUMMARY.md
new file mode 100644
index 00000000..fd4cbac2
--- /dev/null
+++ b/tests/testsuite/index/basic_readme/src/SUMMARY.md
@@ -0,0 +1,6 @@
+# Summary
+
+[Intro](./README.md)
+
+- [First](first/README)
+- [Second](second/Readme.md)
diff --git a/tests/testsuite/index/basic_readme/src/first/README b/tests/testsuite/index/basic_readme/src/first/README
new file mode 100644
index 00000000..f6758542
--- /dev/null
+++ b/tests/testsuite/index/basic_readme/src/first/README
@@ -0,0 +1 @@
+# First
diff --git a/tests/testsuite/index/basic_readme/src/second/Readme.md b/tests/testsuite/index/basic_readme/src/second/Readme.md
new file mode 100644
index 00000000..64e2582b
--- /dev/null
+++ b/tests/testsuite/index/basic_readme/src/second/Readme.md
@@ -0,0 +1 @@
+# Second
diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs
index d273b992..6141c4f7 100644
--- a/tests/testsuite/main.rs
+++ b/tests/testsuite/main.rs
@@ -6,6 +6,7 @@ mod book_test;
mod build;
mod cli;
mod includes;
+mod index;
mod prelude {
pub use crate::book_test::BookTest;
From 6fdd7b4a174498d977c43194e3821dd4b72542c5 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 18:56:10 -0700
Subject: [PATCH 14/69] Migrate base_mdbook_init_should_create_default_content
to BookTest
---
tests/init.rs | 26 ---------------
tests/testsuite/init.rs | 73 +++++++++++++++++++++++++++++++++++++++++
tests/testsuite/main.rs | 1 +
3 files changed, 74 insertions(+), 26 deletions(-)
create mode 100644 tests/testsuite/init.rs
diff --git a/tests/init.rs b/tests/init.rs
index 9b2d8ea5..4b6ee5fa 100644
--- a/tests/init.rs
+++ b/tests/init.rs
@@ -7,32 +7,6 @@ use std::io::prelude::*;
use std::path::PathBuf;
use tempfile::Builder as TempFileBuilder;
-/// Run `mdbook init` in an empty directory and make sure the default files
-/// are created.
-#[test]
-fn base_mdbook_init_should_create_default_content() {
- let created_files = vec!["book", "src", "src/SUMMARY.md", "src/chapter_1.md"];
-
- let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
- for file in &created_files {
- assert!(!temp.path().join(file).exists());
- }
-
- MDBook::init(temp.path()).build().unwrap();
-
- for file in &created_files {
- let target = temp.path().join(file);
- println!("{}", target.display());
- assert!(target.exists(), "{file} doesn't exist");
- }
-
- let contents = fs::read_to_string(temp.path().join("book.toml")).unwrap();
- assert_eq!(
- contents,
- "[book]\nauthors = []\nlanguage = \"en\"\nsrc = \"src\"\n"
- );
-}
-
/// Run `mdbook init` in a directory containing a SUMMARY.md should create the
/// files listed in the summary.
#[test]
diff --git a/tests/testsuite/init.rs b/tests/testsuite/init.rs
new file mode 100644
index 00000000..9e185184
--- /dev/null
+++ b/tests/testsuite/init.rs
@@ -0,0 +1,73 @@
+//! Tests for `mdbook init`.
+
+use crate::prelude::*;
+use mdbook::MDBook;
+
+// Tests "init" with no args.
+#[test]
+fn basic_init() {
+ let mut test = BookTest::empty();
+ test.run("init", |cmd| {
+ cmd.expect_stdout(str![[r#"
+
+Do you want a .gitignore to be created? (y/n)
+What title would you like to give the book?
+
+All done, no errors...
+
+"#]])
+ .expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book::init): Creating a new book with stub content
+
+"#]]);
+ })
+ .check_file(
+ "book.toml",
+ str![[r#"
+[book]
+authors = []
+language = "en"
+src = "src"
+
+"#]],
+ )
+ .check_file(
+ "src/SUMMARY.md",
+ str
+
+"#]],
+ )
+ .check_file(
+ "src/chapter_1.md",
+ str![[r#"
+# Chapter 1
+
+"#]],
+ )
+ .check_main_file(
+ "book/chapter_1.html",
+ str![[r##" "##]],
+ );
+ assert!(!test.dir.join(".gitignore").exists());
+ assert!(test.dir.join("book").exists());
+}
+
+// Test init via API. This does a little less than the CLI does.
+#[test]
+fn init_api() {
+ let mut test = BookTest::empty();
+ MDBook::init(&test.dir).build().unwrap();
+ test.check_file_list(
+ ".",
+ str![[r#"
+book
+book.toml
+src
+src/SUMMARY.md
+src/chapter_1.md
+"#]],
+ );
+}
diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs
index 6141c4f7..59d9e28a 100644
--- a/tests/testsuite/main.rs
+++ b/tests/testsuite/main.rs
@@ -7,6 +7,7 @@ mod build;
mod cli;
mod includes;
mod index;
+mod init;
mod prelude {
pub use crate::book_test::BookTest;
From 41bfbc69e6b36604e57347ec74da257d204c8e54 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 18:57:26 -0700
Subject: [PATCH 15/69] Migrate base_mdbook_init_can_skip_confirmation_prompts
to BookTest
---
tests/cli/init.rs | 20 --------------------
tests/testsuite/init.rs | 28 ++++++++++++++++++++++++++++
2 files changed, 28 insertions(+), 20 deletions(-)
diff --git a/tests/cli/init.rs b/tests/cli/init.rs
index 51c2bfa3..2bdc6943 100644
--- a/tests/cli/init.rs
+++ b/tests/cli/init.rs
@@ -3,26 +3,6 @@ use crate::dummy_book::DummyBook;
use mdbook::config::Config;
-/// Run `mdbook init` with `--force` to skip the confirmation prompts
-#[test]
-fn base_mdbook_init_can_skip_confirmation_prompts() {
- let temp = DummyBook::new().build().unwrap();
-
- // doesn't exist before
- assert!(!temp.path().join("book").exists());
-
- let mut cmd = mdbook_cmd();
- cmd.args(["init", "--force"]).current_dir(temp.path());
- cmd.assert()
- .success()
- .stdout(predicates::str::contains("\nAll done, no errors...\n"));
-
- let config = Config::from_disk(temp.path().join("book.toml")).unwrap();
- assert_eq!(config.book.title, None);
-
- assert!(!temp.path().join(".gitignore").exists());
-}
-
/// Run `mdbook init` with `--title` without git config.
///
/// Regression test for https://github.com/rust-lang/mdBook/issues/2485
diff --git a/tests/testsuite/init.rs b/tests/testsuite/init.rs
index 9e185184..120a3037 100644
--- a/tests/testsuite/init.rs
+++ b/tests/testsuite/init.rs
@@ -71,3 +71,31 @@ src/chapter_1.md
"#]],
);
}
+
+// Run `mdbook init` with `--force` to skip the confirmation prompts
+#[test]
+fn init_force() {
+ let mut test = BookTest::empty();
+ test.run("init --force", |cmd| {
+ cmd.expect_stdout(str![[r#"
+
+All done, no errors...
+
+"#]])
+ .expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book::init): Creating a new book with stub content
+
+"#]]);
+ })
+ .check_file(
+ "book.toml",
+ str![[r#"
+[book]
+authors = []
+language = "en"
+src = "src"
+
+"#]],
+ );
+ assert!(!test.dir.join(".gitignore").exists());
+}
From 3e1d750efa47e6743ec477fd4c6a765660ab1947 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 18:59:25 -0700
Subject: [PATCH 16/69] Migrate no_git_config_with_title to BookTest
---
tests/cli/init.rs | 27 ---------------------------
tests/cli/mod.rs | 1 -
tests/testsuite/init.rs | 34 ++++++++++++++++++++++++++++++++++
3 files changed, 34 insertions(+), 28 deletions(-)
delete mode 100644 tests/cli/init.rs
diff --git a/tests/cli/init.rs b/tests/cli/init.rs
deleted file mode 100644
index 2bdc6943..00000000
--- a/tests/cli/init.rs
+++ /dev/null
@@ -1,27 +0,0 @@
-use crate::cli::cmd::mdbook_cmd;
-use crate::dummy_book::DummyBook;
-
-use mdbook::config::Config;
-
-/// Run `mdbook init` with `--title` without git config.
-///
-/// Regression test for https://github.com/rust-lang/mdBook/issues/2485
-#[test]
-fn no_git_config_with_title() {
- let temp = DummyBook::new().build().unwrap();
-
- // doesn't exist before
- assert!(!temp.path().join("book").exists());
-
- let mut cmd = mdbook_cmd();
- cmd.args(["init", "--title", "Example title"])
- .current_dir(temp.path())
- .env("GIT_CONFIG_GLOBAL", "")
- .env("GIT_CONFIG_NOSYSTEM", "1");
- cmd.assert()
- .success()
- .stdout(predicates::str::contains("\nAll done, no errors...\n"));
-
- let config = Config::from_disk(temp.path().join("book.toml")).unwrap();
- assert_eq!(config.book.title.as_deref(), Some("Example title"));
-}
diff --git a/tests/cli/mod.rs b/tests/cli/mod.rs
index 152b4ee9..989f443f 100644
--- a/tests/cli/mod.rs
+++ b/tests/cli/mod.rs
@@ -1,4 +1,3 @@
mod build;
mod cmd;
-mod init;
mod test;
diff --git a/tests/testsuite/init.rs b/tests/testsuite/init.rs
index 120a3037..b3a79022 100644
--- a/tests/testsuite/init.rs
+++ b/tests/testsuite/init.rs
@@ -99,3 +99,37 @@ src = "src"
);
assert!(!test.dir.join(".gitignore").exists());
}
+
+// Run `mdbook init` with `--title` without git config.
+//
+// Regression test for https://github.com/rust-lang/mdBook/issues/2485
+#[test]
+fn no_git_config_with_title() {
+ let mut test = BookTest::empty();
+ test.run("init", |cmd| {
+ cmd.expect_stdout(str![[r#"
+
+Do you want a .gitignore to be created? (y/n)
+
+All done, no errors...
+
+"#]])
+ .expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book::init): Creating a new book with stub content
+
+"#]])
+ .args(&["--title", "Example title"]);
+ })
+ .check_file(
+ "book.toml",
+ str![[r#"
+[book]
+authors = []
+language = "en"
+src = "src"
+title = "Example title"
+
+"#]],
+ );
+ assert!(!test.dir.join(".gitignore").exists());
+}
From 4019060ef4c0df395ada1dcb960599670fe48c04 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 19:00:22 -0700
Subject: [PATCH 17/69] Migrate
run_mdbook_init_should_create_content_from_summary to BookTest
---
tests/init.rs | 30 -------------------
tests/testsuite/init.rs | 29 ++++++++++++++++++
.../init/init_from_summary/src/SUMMARY.md | 8 +++++
3 files changed, 37 insertions(+), 30 deletions(-)
create mode 100644 tests/testsuite/init/init_from_summary/src/SUMMARY.md
diff --git a/tests/init.rs b/tests/init.rs
index 4b6ee5fa..7cc93211 100644
--- a/tests/init.rs
+++ b/tests/init.rs
@@ -7,36 +7,6 @@ use std::io::prelude::*;
use std::path::PathBuf;
use tempfile::Builder as TempFileBuilder;
-/// Run `mdbook init` in a directory containing a SUMMARY.md should create the
-/// files listed in the summary.
-#[test]
-fn run_mdbook_init_should_create_content_from_summary() {
- let created_files = vec!["intro.md", "first.md", "outro.md"];
-
- let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
- let src_dir = temp.path().join("src");
- fs::create_dir_all(src_dir.clone()).unwrap();
- static SUMMARY: &str = r#"# Summary
-
-[intro](intro.md)
-
-- [First chapter](first.md)
-
-[outro](outro.md)
-
-"#;
-
- let mut summary = File::create(src_dir.join("SUMMARY.md")).unwrap();
- summary.write_all(SUMMARY.as_bytes()).unwrap();
- MDBook::init(temp.path()).build().unwrap();
-
- for file in &created_files {
- let target = src_dir.join(file);
- println!("{}", target.display());
- assert!(target.exists(), "{file} doesn't exist");
- }
-}
-
/// Set some custom arguments for where to place the source and destination
/// files, then call `mdbook init`.
#[test]
diff --git a/tests/testsuite/init.rs b/tests/testsuite/init.rs
index b3a79022..9f66719b 100644
--- a/tests/testsuite/init.rs
+++ b/tests/testsuite/init.rs
@@ -133,3 +133,32 @@ title = "Example title"
);
assert!(!test.dir.join(".gitignore").exists());
}
+
+// Run `mdbook init` in a directory containing a SUMMARY.md should create the
+// files listed in the summary.
+#[test]
+fn init_from_summary() {
+ BookTest::from_dir("init/init_from_summary")
+ .run("init", |_| {})
+ .check_file(
+ "src/intro.md",
+ str![[r#"
+# intro
+
+"#]],
+ )
+ .check_file(
+ "src/first.md",
+ str![[r#"
+# First chapter
+
+"#]],
+ )
+ .check_file(
+ "src/outro.md",
+ str![[r#"
+# outro
+
+"#]],
+ );
+}
diff --git a/tests/testsuite/init/init_from_summary/src/SUMMARY.md b/tests/testsuite/init/init_from_summary/src/SUMMARY.md
new file mode 100644
index 00000000..93d7b15b
--- /dev/null
+++ b/tests/testsuite/init/init_from_summary/src/SUMMARY.md
@@ -0,0 +1,8 @@
+# Summary
+
+[intro](intro.md)
+
+- [First chapter](first.md)
+
+[outro](outro.md)
+
From 29338b5ade04e706777de4fab28bf8c8d97e44cf Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 19:02:06 -0700
Subject: [PATCH 18/69] Migrate
run_mdbook_init_with_custom_book_and_src_locations to BookTest
---
tests/init.rs | 40 -----------------------------------
tests/testsuite/init.rs | 47 ++++++++++++++++++++++++++++++++++++++++-
2 files changed, 46 insertions(+), 41 deletions(-)
diff --git a/tests/init.rs b/tests/init.rs
index 7cc93211..dc43bd2f 100644
--- a/tests/init.rs
+++ b/tests/init.rs
@@ -1,47 +1,7 @@
-use mdbook::config::Config;
use mdbook::MDBook;
use pretty_assertions::assert_eq;
-use std::fs;
-use std::fs::File;
-use std::io::prelude::*;
-use std::path::PathBuf;
use tempfile::Builder as TempFileBuilder;
-/// Set some custom arguments for where to place the source and destination
-/// files, then call `mdbook init`.
-#[test]
-fn run_mdbook_init_with_custom_book_and_src_locations() {
- let created_files = vec!["out", "in", "in/SUMMARY.md", "in/chapter_1.md"];
-
- let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
- for file in &created_files {
- assert!(
- !temp.path().join(file).exists(),
- "{file} shouldn't exist yet!"
- );
- }
-
- let mut cfg = Config::default();
- cfg.book.src = PathBuf::from("in");
- cfg.build.build_dir = PathBuf::from("out");
-
- MDBook::init(temp.path()).with_config(cfg).build().unwrap();
-
- for file in &created_files {
- let target = temp.path().join(file);
- assert!(
- target.exists(),
- "{file} should have been created by `mdbook init`"
- );
- }
-
- let contents = fs::read_to_string(temp.path().join("book.toml")).unwrap();
- assert_eq!(
- contents,
- "[book]\nauthors = []\nlanguage = \"en\"\nsrc = \"in\"\n\n[build]\nbuild-dir = \"out\"\ncreate-missing = true\nextra-watch-dirs = []\nuse-default-preprocessors = true\n"
- );
-}
-
#[test]
fn copy_theme() {
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
diff --git a/tests/testsuite/init.rs b/tests/testsuite/init.rs
index 9f66719b..b4524a58 100644
--- a/tests/testsuite/init.rs
+++ b/tests/testsuite/init.rs
@@ -1,7 +1,8 @@
//! Tests for `mdbook init`.
use crate::prelude::*;
-use mdbook::MDBook;
+use mdbook::{Config, MDBook};
+use std::path::PathBuf;
// Tests "init" with no args.
#[test]
@@ -162,3 +163,47 @@ fn init_from_summary() {
"#]],
);
}
+
+// Set some custom arguments for where to place the source and destination
+// files, then call `mdbook init`.
+#[test]
+fn init_with_custom_book_and_src_locations() {
+ let mut test = BookTest::empty();
+ let mut cfg = Config::default();
+ cfg.book.src = PathBuf::from("in");
+ cfg.build.build_dir = PathBuf::from("out");
+ MDBook::init(&test.dir).with_config(cfg).build().unwrap();
+ test.check_file(
+ "book.toml",
+ str![[r#"
+[book]
+authors = []
+language = "en"
+src = "in"
+
+[build]
+build-dir = "out"
+create-missing = true
+extra-watch-dirs = []
+use-default-preprocessors = true
+
+"#]],
+ )
+ .check_file(
+ "in/SUMMARY.md",
+ str
+
+"#]],
+ )
+ .check_file(
+ "in/chapter_1.md",
+ str![[r#"
+# Chapter 1
+
+"#]],
+ );
+ assert!(test.dir.join("out").exists());
+}
From 0732cb47b9a4dea90aba21331fba6688140a736b Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 19:03:33 -0700
Subject: [PATCH 19/69] Migrate copy_theme to BookTest
---
tests/init.rs | 51 -----------------------------------------
tests/testsuite/init.rs | 44 +++++++++++++++++++++++++++++++++++
2 files changed, 44 insertions(+), 51 deletions(-)
delete mode 100644 tests/init.rs
diff --git a/tests/init.rs b/tests/init.rs
deleted file mode 100644
index dc43bd2f..00000000
--- a/tests/init.rs
+++ /dev/null
@@ -1,51 +0,0 @@
-use mdbook::MDBook;
-use pretty_assertions::assert_eq;
-use tempfile::Builder as TempFileBuilder;
-
-#[test]
-fn copy_theme() {
- let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
- MDBook::init(temp.path()).copy_theme(true).build().unwrap();
- let expected = vec![
- "book.js",
- "css/chrome.css",
- "css/general.css",
- "css/print.css",
- "css/variables.css",
- "favicon.png",
- "favicon.svg",
- "fonts/OPEN-SANS-LICENSE.txt",
- "fonts/SOURCE-CODE-PRO-LICENSE.txt",
- "fonts/fonts.css",
- "fonts/open-sans-v17-all-charsets-300.woff2",
- "fonts/open-sans-v17-all-charsets-300italic.woff2",
- "fonts/open-sans-v17-all-charsets-600.woff2",
- "fonts/open-sans-v17-all-charsets-600italic.woff2",
- "fonts/open-sans-v17-all-charsets-700.woff2",
- "fonts/open-sans-v17-all-charsets-700italic.woff2",
- "fonts/open-sans-v17-all-charsets-800.woff2",
- "fonts/open-sans-v17-all-charsets-800italic.woff2",
- "fonts/open-sans-v17-all-charsets-italic.woff2",
- "fonts/open-sans-v17-all-charsets-regular.woff2",
- "fonts/source-code-pro-v11-all-charsets-500.woff2",
- "highlight.css",
- "highlight.js",
- "index.hbs",
- ];
- let theme_dir = temp.path().join("theme");
- let mut actual: Vec<_> = walkdir::WalkDir::new(&theme_dir)
- .into_iter()
- .filter_map(|e| e.ok())
- .filter(|e| !e.file_type().is_dir())
- .map(|e| {
- e.path()
- .strip_prefix(&theme_dir)
- .unwrap()
- .to_str()
- .unwrap()
- .replace('\\', "/")
- })
- .collect();
- actual.sort();
- assert_eq!(actual, expected);
-}
diff --git a/tests/testsuite/init.rs b/tests/testsuite/init.rs
index b4524a58..e5989eb6 100644
--- a/tests/testsuite/init.rs
+++ b/tests/testsuite/init.rs
@@ -207,3 +207,47 @@ use-default-preprocessors = true
);
assert!(test.dir.join("out").exists());
}
+
+// Copies the theme into the initialized directory.
+#[test]
+fn copy_theme() {
+ BookTest::empty()
+ .run("init --theme", |_| {})
+ .check_file_list(
+ ".",
+ str![[r#"
+book
+book.toml
+src
+src/SUMMARY.md
+src/chapter_1.md
+theme
+theme/book.js
+theme/css
+theme/css/chrome.css
+theme/css/general.css
+theme/css/print.css
+theme/css/variables.css
+theme/favicon.png
+theme/favicon.svg
+theme/fonts
+theme/fonts/OPEN-SANS-LICENSE.txt
+theme/fonts/SOURCE-CODE-PRO-LICENSE.txt
+theme/fonts/fonts.css
+theme/fonts/open-sans-v17-all-charsets-300.woff2
+theme/fonts/open-sans-v17-all-charsets-300italic.woff2
+theme/fonts/open-sans-v17-all-charsets-600.woff2
+theme/fonts/open-sans-v17-all-charsets-600italic.woff2
+theme/fonts/open-sans-v17-all-charsets-700.woff2
+theme/fonts/open-sans-v17-all-charsets-700italic.woff2
+theme/fonts/open-sans-v17-all-charsets-800.woff2
+theme/fonts/open-sans-v17-all-charsets-800italic.woff2
+theme/fonts/open-sans-v17-all-charsets-italic.woff2
+theme/fonts/open-sans-v17-all-charsets-regular.woff2
+theme/fonts/source-code-pro-v11-all-charsets-500.woff2
+theme/highlight.css
+theme/highlight.js
+theme/index.hbs
+"#]],
+ );
+}
From 74e01ea6e3da420052b87118eee94c528bcb8cf0 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 19:08:27 -0700
Subject: [PATCH 20/69] Migrate markdown_options to BookTest
---
tests/rendered_output.rs | 64 ------------
tests/testsuite/main.rs | 1 +
tests/testsuite/markdown.rs | 99 +++++++++++++++++++
.../markdown/footnotes/src/SUMMARY.md | 1 +
.../markdown/footnotes/src/footnotes.md | 37 +++++++
.../markdown/strikethrough/src/SUMMARY.md | 1 +
.../strikethrough/src/strikethrough.md | 4 +
.../testsuite/markdown/tables/src/SUMMARY.md | 1 +
tests/testsuite/markdown/tables/src/tables.md | 9 ++
.../markdown/tasklists/src/SUMMARY.md | 1 +
.../markdown/tasklists/src/tasklists.md | 5 +
11 files changed, 159 insertions(+), 64 deletions(-)
create mode 100644 tests/testsuite/markdown.rs
create mode 100644 tests/testsuite/markdown/footnotes/src/SUMMARY.md
create mode 100644 tests/testsuite/markdown/footnotes/src/footnotes.md
create mode 100644 tests/testsuite/markdown/strikethrough/src/SUMMARY.md
create mode 100644 tests/testsuite/markdown/strikethrough/src/strikethrough.md
create mode 100644 tests/testsuite/markdown/tables/src/SUMMARY.md
create mode 100644 tests/testsuite/markdown/tables/src/tables.md
create mode 100644 tests/testsuite/markdown/tasklists/src/SUMMARY.md
create mode 100644 tests/testsuite/markdown/tasklists/src/tasklists.md
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index 7a726f87..5da167c0 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -374,70 +374,6 @@ fn no_index_for_print_html() {
assert_doesnt_contain_strings(index_html, &[r##"noindex"##]);
}
-#[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,
- &[
- "foo ",
- "bar ",
- "baz ",
- "bim ",
- ],
- );
- assert_contains_strings(
- &path,
- &[
- r##""##,
- r##""##,
- r##""##,
- r##"
-
-"##,
- ],
- );
- assert_contains_strings(&path, &["strikethrough example"]);
- assert_contains_strings(
- &path,
- &[
- " \nApples",
- " \nBroccoli",
- " \nCarrots",
- ],
- );
-}
-
#[test]
fn redirects_are_emitted_correctly() {
let temp = DummyBook::new().build().unwrap();
diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs
index 59d9e28a..e2a3ebd9 100644
--- a/tests/testsuite/main.rs
+++ b/tests/testsuite/main.rs
@@ -8,6 +8,7 @@ mod cli;
mod includes;
mod index;
mod init;
+mod markdown;
mod prelude {
pub use crate::book_test::BookTest;
diff --git a/tests/testsuite/markdown.rs b/tests/testsuite/markdown.rs
new file mode 100644
index 00000000..18c9daf7
--- /dev/null
+++ b/tests/testsuite/markdown.rs
@@ -0,0 +1,99 @@
+//! Tests for special markdown rendering.
+
+use crate::prelude::*;
+
+// Test for a variety of footnote renderings.
+#[test]
+fn footnotes() {
+ BookTest::from_dir("markdown/footnotes")
+ .check_main_file("book/footnotes.html", str![[r##"
+
+Footnote example, or with a word.
+There are multiple references to word.
+Footnote without a paragraph
+Footnote with multiple paragraphs
+Footnote name with wacky characters
+Testing when referring to something earlier.
+
+
+"##]]);
+}
+
+// Basic table test.
+#[test]
+fn tables() {
+ BookTest::from_dir("markdown/tables").check_main_file(
+ "book/tables.html",
+ str![[r##"
+
+foo bar
+baz bim
+Backslash in code /
+Double back in code //
+Pipe in code |
+Pipe in code2 test | inside
+
+
+"##]],
+ );
+}
+
+// Strikethrough test.
+#[test]
+fn strikethrough() {
+ BookTest::from_dir("markdown/strikethrough").check_main_file(
+ "book/strikethrough.html",
+ str![[r##"
+
+strikethrough example
+"##]],
+ );
+}
+
+// Tasklist test.
+#[test]
+fn tasklists() {
+ BookTest::from_dir("markdown/tasklists").check_main_file(
+ "book/tasklists.html",
+ str![[r##"
+
+
+"##]],
+ );
+}
diff --git a/tests/testsuite/markdown/footnotes/src/SUMMARY.md b/tests/testsuite/markdown/footnotes/src/SUMMARY.md
new file mode 100644
index 00000000..a36ad791
--- /dev/null
+++ b/tests/testsuite/markdown/footnotes/src/SUMMARY.md
@@ -0,0 +1 @@
+- [Footnotes](footnotes.md)
diff --git a/tests/testsuite/markdown/footnotes/src/footnotes.md b/tests/testsuite/markdown/footnotes/src/footnotes.md
new file mode 100644
index 00000000..f1fbacca
--- /dev/null
+++ b/tests/testsuite/markdown/footnotes/src/footnotes.md
@@ -0,0 +1,37 @@
+# Footnote tests
+
+Footnote example[^1], or with a word[^word].
+
+[^1]: This is a footnote.
+
+[^word]: A longer footnote.
+ With multiple lines. [Link to other](other.md).
+ With a reference inside.[^1]
+
+There are multiple references to word[^word].
+
+Footnote without a paragraph[^para]
+
+[^para]:
+ 1. Item one
+ 1. Sub-item
+ 2. Item two
+
+Footnote with multiple paragraphs[^multiple]
+
+[^define-before-use]: This is defined before it is referred to.
+
+[^multiple]:
+ One
+
+ Two
+
+ Three
+
+[^unused]: This footnote is defined by not used.
+
+Footnote name with wacky characters[^"wacky"]
+
+[^"wacky"]: Testing footnote id with special characters.
+
+Testing when referring to something earlier.[^define-before-use]
diff --git a/tests/testsuite/markdown/strikethrough/src/SUMMARY.md b/tests/testsuite/markdown/strikethrough/src/SUMMARY.md
new file mode 100644
index 00000000..c9a3a836
--- /dev/null
+++ b/tests/testsuite/markdown/strikethrough/src/SUMMARY.md
@@ -0,0 +1 @@
+- [Strikethrough](strikethrough.md)
diff --git a/tests/testsuite/markdown/strikethrough/src/strikethrough.md b/tests/testsuite/markdown/strikethrough/src/strikethrough.md
new file mode 100644
index 00000000..4cb45fef
--- /dev/null
+++ b/tests/testsuite/markdown/strikethrough/src/strikethrough.md
@@ -0,0 +1,4 @@
+# Strikethrough
+
+~~strikethrough example~~
+
diff --git a/tests/testsuite/markdown/tables/src/SUMMARY.md b/tests/testsuite/markdown/tables/src/SUMMARY.md
new file mode 100644
index 00000000..3c2061aa
--- /dev/null
+++ b/tests/testsuite/markdown/tables/src/SUMMARY.md
@@ -0,0 +1 @@
+- [Tables](tables.md)
diff --git a/tests/testsuite/markdown/tables/src/tables.md b/tests/testsuite/markdown/tables/src/tables.md
new file mode 100644
index 00000000..d3721ef3
--- /dev/null
+++ b/tests/testsuite/markdown/tables/src/tables.md
@@ -0,0 +1,9 @@
+# Tables
+
+| foo | bar |
+| --- | --- |
+| baz | bim |
+| Backslash in code | `\` |
+| Double back in code | `\\` |
+| Pipe in code | `\|` |
+| Pipe in code2 | `test \| inside` |
diff --git a/tests/testsuite/markdown/tasklists/src/SUMMARY.md b/tests/testsuite/markdown/tasklists/src/SUMMARY.md
new file mode 100644
index 00000000..a1fc91c5
--- /dev/null
+++ b/tests/testsuite/markdown/tasklists/src/SUMMARY.md
@@ -0,0 +1 @@
+- [Tasklists](tasklists.md)
diff --git a/tests/testsuite/markdown/tasklists/src/tasklists.md b/tests/testsuite/markdown/tasklists/src/tasklists.md
new file mode 100644
index 00000000..2a90ff56
--- /dev/null
+++ b/tests/testsuite/markdown/tasklists/src/tasklists.md
@@ -0,0 +1,5 @@
+## Tasklisks
+
+- [X] Apples
+- [X] Broccoli
+- [ ] Carrots
From 6904653a824e296ae1a239a41f7ab38966cec505 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 11:30:11 -0700
Subject: [PATCH 21/69] Migrate custom_header_attributes to BookTest
---
tests/rendered_output.rs | 16 ----------------
tests/testsuite/markdown.rs | 11 +++++++++++
.../markdown/custom_header_attributes/book.toml | 2 ++
.../custom_header_attributes/src/SUMMARY.md | 3 +++
.../src/custom_header_attributes.md | 5 +++++
5 files changed, 21 insertions(+), 16 deletions(-)
create mode 100644 tests/testsuite/markdown/custom_header_attributes/book.toml
create mode 100644 tests/testsuite/markdown/custom_header_attributes/src/SUMMARY.md
create mode 100644 tests/testsuite/markdown/custom_header_attributes/src/custom_header_attributes.md
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index 5da167c0..a231bf16 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -829,22 +829,6 @@ fn custom_fonts() {
);
}
-#[test]
-fn custom_header_attributes() {
- let temp = DummyBook::new().build().unwrap();
- let md = MDBook::load(temp.path()).unwrap();
- md.build().unwrap();
-
- let contents = temp.path().join("book/first/heading-attributes.html");
-
- let summary_strings = &[
- r##" "##,
- r##" "##,
- r##" "##,
- ];
- assert_contains_strings(&contents, summary_strings);
-}
-
#[test]
fn with_no_source_path() {
// Test for a regression where search would fail if source_path is None.
diff --git a/tests/testsuite/markdown.rs b/tests/testsuite/markdown.rs
index 18c9daf7..61317e6b 100644
--- a/tests/testsuite/markdown.rs
+++ b/tests/testsuite/markdown.rs
@@ -2,6 +2,17 @@
use crate::prelude::*;
+// Checks custom header id and classes.
+#[test]
+fn custom_header_attributes() {
+ BookTest::from_dir("markdown/custom_header_attributes")
+ .check_main_file("book/custom_header_attributes.html", str![[r##"
+
+
+
+"##]]);
+}
+
// Test for a variety of footnote renderings.
#[test]
fn footnotes() {
diff --git a/tests/testsuite/markdown/custom_header_attributes/book.toml b/tests/testsuite/markdown/custom_header_attributes/book.toml
new file mode 100644
index 00000000..3c4b914b
--- /dev/null
+++ b/tests/testsuite/markdown/custom_header_attributes/book.toml
@@ -0,0 +1,2 @@
+[book]
+title = "custom_header_attributes"
diff --git a/tests/testsuite/markdown/custom_header_attributes/src/SUMMARY.md b/tests/testsuite/markdown/custom_header_attributes/src/SUMMARY.md
new file mode 100644
index 00000000..56dd8b10
--- /dev/null
+++ b/tests/testsuite/markdown/custom_header_attributes/src/SUMMARY.md
@@ -0,0 +1,3 @@
+# Summary
+
+- [Heading Attributes](./custom_header_attributes.md)
diff --git a/tests/testsuite/markdown/custom_header_attributes/src/custom_header_attributes.md b/tests/testsuite/markdown/custom_header_attributes/src/custom_header_attributes.md
new file mode 100644
index 00000000..a09a22b6
--- /dev/null
+++ b/tests/testsuite/markdown/custom_header_attributes/src/custom_header_attributes.md
@@ -0,0 +1,5 @@
+# Heading Attributes {#attrs}
+
+## Heading with classes {.class1 .class2}
+
+## Heading with id and classes {#both .class1 .class2}
From 8c8f0a4dbfcbde9a6155af0b956c1767a176f07c Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 19:09:26 -0700
Subject: [PATCH 22/69] Add test for smart punctuation
---
tests/testsuite/markdown.rs | 36 +++++++++++++++++++
.../markdown/smart_punctuation/src/SUMMARY.md | 1 +
.../src/smart_punctuation.md | 7 ++++
3 files changed, 44 insertions(+)
create mode 100644 tests/testsuite/markdown/smart_punctuation/src/SUMMARY.md
create mode 100644 tests/testsuite/markdown/smart_punctuation/src/smart_punctuation.md
diff --git a/tests/testsuite/markdown.rs b/tests/testsuite/markdown.rs
index 61317e6b..50471826 100644
--- a/tests/testsuite/markdown.rs
+++ b/tests/testsuite/markdown.rs
@@ -108,3 +108,39 @@ Carrots
"##]],
);
}
+
+// Smart punctuation test.
+#[test]
+fn smart_punctuation() {
+ BookTest::from_dir("markdown/smart_punctuation")
+ // Default is off.
+ .check_main_file(
+ "book/smart_punctuation.html",
+ str![[r##"
+
+
+En dash: --
+Em dash: ---
+Ellipsis: ...
+Double quote: "quote"
+Single quote: 'quote'
+
+"##]],
+ )
+ .run("build", |cmd| {
+ cmd.env("MDBOOK_OUTPUT__HTML__SMART_PUNCTUATION", "true");
+ })
+ .check_main_file(
+ "book/smart_punctuation.html",
+ str![[r##"
+
+
+En dash: –
+Em dash: —
+Ellipsis: …
+Double quote: “quote”
+Single quote: ‘quote’
+
+"##]],
+ );
+}
diff --git a/tests/testsuite/markdown/smart_punctuation/src/SUMMARY.md b/tests/testsuite/markdown/smart_punctuation/src/SUMMARY.md
new file mode 100644
index 00000000..7ec259e3
--- /dev/null
+++ b/tests/testsuite/markdown/smart_punctuation/src/SUMMARY.md
@@ -0,0 +1 @@
+- [Smart Punctuation](smart_punctuation.md)
diff --git a/tests/testsuite/markdown/smart_punctuation/src/smart_punctuation.md b/tests/testsuite/markdown/smart_punctuation/src/smart_punctuation.md
new file mode 100644
index 00000000..a27f3c32
--- /dev/null
+++ b/tests/testsuite/markdown/smart_punctuation/src/smart_punctuation.md
@@ -0,0 +1,7 @@
+# Smart Punctuation
+
+- En dash: --
+- Em dash: ---
+- Ellipsis: ...
+- Double quote: "quote"
+- Single quote: 'quote'
From 20d42a53d3fabeff1c3ca687bb1d41cfa0850d37 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 19:11:07 -0700
Subject: [PATCH 23/69] Migrate rendered_code_has_playground_stuff to BookTest
---
tests/rendered_output.rs | 15 ---------------
tests/testsuite/main.rs | 1 +
tests/testsuite/playground.rs | 18 ++++++++++++++++++
.../playground_on_rust_code/book.toml | 2 ++
.../playground_on_rust_code/src/SUMMARY.md | 3 +++
.../playground_on_rust_code/src/index.md | 5 +++++
6 files changed, 29 insertions(+), 15 deletions(-)
create mode 100644 tests/testsuite/playground.rs
create mode 100644 tests/testsuite/playground/playground_on_rust_code/book.toml
create mode 100644 tests/testsuite/playground/playground_on_rust_code/src/SUMMARY.md
create mode 100644 tests/testsuite/playground/playground_on_rust_code/src/index.md
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index a231bf16..8552d6f4 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -93,21 +93,6 @@ fn check_correct_relative_links_in_print_page() {
);
}
-#[test]
-fn rendered_code_has_playground_stuff() {
- 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 playground_class = vec![r#"class="playground""#];
-
- assert_contains_strings(nested, &playground_class);
-
- let book_js = temp.path().join("book/book.js");
- assert_contains_strings(book_js, &[".playground"]);
-}
-
#[test]
fn rendered_code_does_not_have_playground_stuff_in_html_when_disabled_in_config() {
let temp = DummyBook::new().build().unwrap();
diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs
index e2a3ebd9..5b2d7db1 100644
--- a/tests/testsuite/main.rs
+++ b/tests/testsuite/main.rs
@@ -9,6 +9,7 @@ mod includes;
mod index;
mod init;
mod markdown;
+mod playground;
mod prelude {
pub use crate::book_test::BookTest;
diff --git a/tests/testsuite/playground.rs b/tests/testsuite/playground.rs
new file mode 100644
index 00000000..5cbd7ab1
--- /dev/null
+++ b/tests/testsuite/playground.rs
@@ -0,0 +1,18 @@
+//! Tests for Rust playground support.
+
+use crate::prelude::*;
+
+// Verifies that a rust codeblock gets the playground class.
+#[test]
+fn playground_on_rust_code() {
+ BookTest::from_dir("playground/playground_on_rust_code").check_main_file(
+ "book/index.html",
+ str![[r##"
+
+#![allow(unused)]
+ fn main() {
+ let x = 1;
+}
+"##]],
+ );
+}
diff --git a/tests/testsuite/playground/playground_on_rust_code/book.toml b/tests/testsuite/playground/playground_on_rust_code/book.toml
new file mode 100644
index 00000000..7bf7c972
--- /dev/null
+++ b/tests/testsuite/playground/playground_on_rust_code/book.toml
@@ -0,0 +1,2 @@
+[book]
+title = "playground_on_rust_code"
diff --git a/tests/testsuite/playground/playground_on_rust_code/src/SUMMARY.md b/tests/testsuite/playground/playground_on_rust_code/src/SUMMARY.md
new file mode 100644
index 00000000..6f47d01a
--- /dev/null
+++ b/tests/testsuite/playground/playground_on_rust_code/src/SUMMARY.md
@@ -0,0 +1,3 @@
+# Summary
+
+- [Rust Playground](./index.md)
diff --git a/tests/testsuite/playground/playground_on_rust_code/src/index.md b/tests/testsuite/playground/playground_on_rust_code/src/index.md
new file mode 100644
index 00000000..93234d98
--- /dev/null
+++ b/tests/testsuite/playground/playground_on_rust_code/src/index.md
@@ -0,0 +1,5 @@
+# Rust Sample
+
+```rust
+let x = 1;
+```
From aa29ef04a2a79d278c6f3c21e8f53ee438e8d014 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 19:11:53 -0700
Subject: [PATCH 24/69] Migrate
rendered_code_does_not_have_playground_stuff_in_html_when_disabled_in_config
to BookTest
---
tests/rendered_output.rs | 19 -------------------
tests/testsuite/playground.rs | 12 ++++++++++++
.../playground/disabled_playground/book.toml | 5 +++++
.../disabled_playground/src/SUMMARY.md | 3 +++
.../disabled_playground/src/index.md | 5 +++++
5 files changed, 25 insertions(+), 19 deletions(-)
create mode 100644 tests/testsuite/playground/disabled_playground/book.toml
create mode 100644 tests/testsuite/playground/disabled_playground/src/SUMMARY.md
create mode 100644 tests/testsuite/playground/disabled_playground/src/index.md
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index 8552d6f4..88d44fef 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -93,25 +93,6 @@ fn check_correct_relative_links_in_print_page() {
);
}
-#[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);
-}
-
#[test]
fn chapter_content_appears_in_rendered_document() {
let content = vec![
diff --git a/tests/testsuite/playground.rs b/tests/testsuite/playground.rs
index 5cbd7ab1..1983757d 100644
--- a/tests/testsuite/playground.rs
+++ b/tests/testsuite/playground.rs
@@ -16,3 +16,15 @@ fn playground_on_rust_code() {
"##]],
);
}
+
+// When the playground is disabled, there should be no playground class.
+#[test]
+fn disabled_playground() {
+ BookTest::from_dir("playground/disabled_playground").check_main_file(
+ "book/index.html",
+ str![[r##"
+
+let x = 1;
+"##]],
+ );
+}
diff --git a/tests/testsuite/playground/disabled_playground/book.toml b/tests/testsuite/playground/disabled_playground/book.toml
new file mode 100644
index 00000000..3a9b2297
--- /dev/null
+++ b/tests/testsuite/playground/disabled_playground/book.toml
@@ -0,0 +1,5 @@
+[book]
+title = "playground_on_rust_code"
+
+[output.html.playground]
+runnable = false
diff --git a/tests/testsuite/playground/disabled_playground/src/SUMMARY.md b/tests/testsuite/playground/disabled_playground/src/SUMMARY.md
new file mode 100644
index 00000000..6f47d01a
--- /dev/null
+++ b/tests/testsuite/playground/disabled_playground/src/SUMMARY.md
@@ -0,0 +1,3 @@
+# Summary
+
+- [Rust Playground](./index.md)
diff --git a/tests/testsuite/playground/disabled_playground/src/index.md b/tests/testsuite/playground/disabled_playground/src/index.md
new file mode 100644
index 00000000..93234d98
--- /dev/null
+++ b/tests/testsuite/playground/disabled_playground/src/index.md
@@ -0,0 +1,5 @@
+# Rust Sample
+
+```rust
+let x = 1;
+```
From ba448a9dd5839162f7012ed875047c718e4e09de Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 19:16:29 -0700
Subject: [PATCH 25/69] Migrate mdbook_runs_preprocessors to BookTest
---
tests/build_process.rs | 36 ----------------------------
tests/testsuite/main.rs | 1 +
tests/testsuite/preprocessor.rs | 42 +++++++++++++++++++++++++++++++++
3 files changed, 43 insertions(+), 36 deletions(-)
create mode 100644 tests/testsuite/preprocessor.rs
diff --git a/tests/build_process.rs b/tests/build_process.rs
index 10d0b4a9..c59db328 100644
--- a/tests/build_process.rs
+++ b/tests/build_process.rs
@@ -1,10 +1,8 @@
mod dummy_book;
use crate::dummy_book::DummyBook;
-use mdbook::book::Book;
use mdbook::config::Config;
use mdbook::errors::*;
-use mdbook::preprocess::{Preprocessor, PreprocessorContext};
use mdbook::renderer::{RenderContext, Renderer};
use mdbook::MDBook;
use std::sync::{Arc, Mutex};
@@ -14,20 +12,6 @@ struct Spy(Arc>);
#[derive(Debug, Default)]
struct Inner {
run_count: usize,
- rendered_with: Vec,
-}
-
-impl Preprocessor for Spy {
- fn name(&self) -> &str {
- "dummy"
- }
-
- fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result {
- let mut inner = self.0.lock().unwrap();
- inner.run_count += 1;
- inner.rendered_with.push(ctx.renderer.clone());
- Ok(book)
- }
}
impl Renderer for Spy {
@@ -42,26 +26,6 @@ impl Renderer for Spy {
}
}
-#[test]
-fn mdbook_runs_preprocessors() {
- let spy: Arc> = Default::default();
-
- let temp = DummyBook::new().build().unwrap();
- let cfg = Config::default();
-
- let mut book = MDBook::load_with_config(temp.path(), cfg).unwrap();
- book.with_preprocessor(Spy(Arc::clone(&spy)));
- book.build().unwrap();
-
- let inner = spy.lock().unwrap();
- assert_eq!(inner.run_count, 1);
- assert_eq!(inner.rendered_with.len(), 1);
- assert_eq!(
- "html", inner.rendered_with[0],
- "We should have been run with the default HTML renderer"
- );
-}
-
#[test]
fn mdbook_runs_renderers() {
let spy: Arc> = Default::default();
diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs
index 5b2d7db1..daec2814 100644
--- a/tests/testsuite/main.rs
+++ b/tests/testsuite/main.rs
@@ -10,6 +10,7 @@ mod index;
mod init;
mod markdown;
mod playground;
+mod preprocessor;
mod prelude {
pub use crate::book_test::BookTest;
diff --git a/tests/testsuite/preprocessor.rs b/tests/testsuite/preprocessor.rs
new file mode 100644
index 00000000..8c8793a6
--- /dev/null
+++ b/tests/testsuite/preprocessor.rs
@@ -0,0 +1,42 @@
+//! Tests for custom preprocessors.
+
+use crate::prelude::*;
+use mdbook::book::Book;
+use mdbook::errors::Result;
+use mdbook::preprocess::{Preprocessor, PreprocessorContext};
+use std::sync::{Arc, Mutex};
+
+struct Spy(Arc>);
+
+#[derive(Debug, Default)]
+struct Inner {
+ run_count: usize,
+ rendered_with: Vec,
+}
+
+impl Preprocessor for Spy {
+ fn name(&self) -> &str {
+ "dummy"
+ }
+
+ fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result {
+ let mut inner = self.0.lock().unwrap();
+ inner.run_count += 1;
+ inner.rendered_with.push(ctx.renderer.clone());
+ Ok(book)
+ }
+}
+
+// Test that preprocessor gets run.
+#[test]
+fn runs_preprocessors() {
+ let test = BookTest::init(|_| {});
+ let spy: Arc> = Default::default();
+ let mut book = test.load_book();
+ book.with_preprocessor(Spy(Arc::clone(&spy)));
+ book.build().unwrap();
+
+ let inner = spy.lock().unwrap();
+ assert_eq!(inner.run_count, 1);
+ assert_eq!(inner.rendered_with, ["html"]);
+}
From b4221680e4ba00246f738d4079361cc803509125 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 19:18:16 -0700
Subject: [PATCH 26/69] Print more context for debugging nop-preprocessor
---
examples/nop-preprocessor.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/examples/nop-preprocessor.rs b/examples/nop-preprocessor.rs
index bbb82a46..f194991a 100644
--- a/examples/nop-preprocessor.rs
+++ b/examples/nop-preprocessor.rs
@@ -26,7 +26,7 @@ fn main() {
if let Some(sub_args) = matches.subcommand_matches("supports") {
handle_supports(&preprocessor, sub_args);
} else if let Err(e) = handle_preprocessing(&preprocessor) {
- eprintln!("{e}");
+ eprintln!("{e:?}");
process::exit(1);
}
}
From fca149a52cd4860136a0724514146fce46966e0b Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 19:19:30 -0700
Subject: [PATCH 27/69] Migrate process_the_dummy_book to BookTest
---
tests/custom_preprocessors.rs | 10 ----------
tests/testsuite/preprocessor.rs | 12 ++++++++++++
.../preprocessor/nop_preprocessor/book.toml | 2 ++
.../preprocessor/nop_preprocessor/src/SUMMARY.md | 0
4 files changed, 14 insertions(+), 10 deletions(-)
create mode 100644 tests/testsuite/preprocessor/nop_preprocessor/book.toml
create mode 100644 tests/testsuite/preprocessor/nop_preprocessor/src/SUMMARY.md
diff --git a/tests/custom_preprocessors.rs b/tests/custom_preprocessors.rs
index c32e94d3..49e934f1 100644
--- a/tests/custom_preprocessors.rs
+++ b/tests/custom_preprocessors.rs
@@ -56,13 +56,3 @@ fn ask_the_preprocessor_to_blow_up() {
)
);
}
-
-#[test]
-fn process_the_dummy_book() {
- let dummy_book = DummyBook::new();
- let temp = dummy_book.build().unwrap();
- let mut md = MDBook::load(temp.path()).unwrap();
- md.with_preprocessor(example());
-
- md.build().unwrap();
-}
diff --git a/tests/testsuite/preprocessor.rs b/tests/testsuite/preprocessor.rs
index 8c8793a6..8b87926b 100644
--- a/tests/testsuite/preprocessor.rs
+++ b/tests/testsuite/preprocessor.rs
@@ -40,3 +40,15 @@ fn runs_preprocessors() {
assert_eq!(inner.run_count, 1);
assert_eq!(inner.rendered_with, ["html"]);
}
+
+// No-op preprocessor works.
+#[test]
+fn nop_preprocessor() {
+ BookTest::from_dir("preprocessor/nop_preprocessor").run("build", |cmd| {
+ cmd.expect_stdout(str![[""]]).expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book): Book building has started
+[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
+
+"#]]);
+ });
+}
diff --git a/tests/testsuite/preprocessor/nop_preprocessor/book.toml b/tests/testsuite/preprocessor/nop_preprocessor/book.toml
new file mode 100644
index 00000000..5532d943
--- /dev/null
+++ b/tests/testsuite/preprocessor/nop_preprocessor/book.toml
@@ -0,0 +1,2 @@
+[preprocessor.nop-preprocessor]
+command = "cargo run --quiet --example nop-preprocessor --"
diff --git a/tests/testsuite/preprocessor/nop_preprocessor/src/SUMMARY.md b/tests/testsuite/preprocessor/nop_preprocessor/src/SUMMARY.md
new file mode 100644
index 00000000..e69de29b
From d815b0cc52107ed9bc08399e3a8bf5ceb7e3b13c Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 19:20:51 -0700
Subject: [PATCH 28/69] Migrate ask_the_preprocessor_to_blow_up to BookTest
---
tests/custom_preprocessors.rs | 28 -------------------
tests/testsuite/preprocessor.rs | 16 +++++++++++
.../failing_preprocessor/book.toml | 4 +++
.../failing_preprocessor/src/SUMMARY.md | 0
4 files changed, 20 insertions(+), 28 deletions(-)
create mode 100644 tests/testsuite/preprocessor/failing_preprocessor/book.toml
create mode 100644 tests/testsuite/preprocessor/failing_preprocessor/src/SUMMARY.md
diff --git a/tests/custom_preprocessors.rs b/tests/custom_preprocessors.rs
index 49e934f1..6baa5db9 100644
--- a/tests/custom_preprocessors.rs
+++ b/tests/custom_preprocessors.rs
@@ -28,31 +28,3 @@ fn example_doesnt_support_not_supported() {
assert_eq!(got, false);
}
-
-#[test]
-fn ask_the_preprocessor_to_blow_up() {
- let dummy_book = DummyBook::new();
- let temp = dummy_book.build().unwrap();
- let mut md = MDBook::load(temp.path()).unwrap();
- md.with_preprocessor(example());
-
- md.config
- .set("preprocessor.nop-preprocessor.blow-up", true)
- .unwrap();
-
- let got = md.build();
-
- assert!(got.is_err());
- let error_message = got.err().unwrap().to_string();
- let status = if cfg!(windows) {
- "exit code: 1"
- } else {
- "exit status: 1"
- };
- assert_eq!(
- error_message,
- format!(
- r#"The "nop-preprocessor" preprocessor exited unsuccessfully with {status} status"#
- )
- );
-}
diff --git a/tests/testsuite/preprocessor.rs b/tests/testsuite/preprocessor.rs
index 8b87926b..4c620c3a 100644
--- a/tests/testsuite/preprocessor.rs
+++ b/tests/testsuite/preprocessor.rs
@@ -52,3 +52,19 @@ fn nop_preprocessor() {
"#]]);
});
}
+
+// Failing preprocessor generates an error.
+#[test]
+fn failing_preprocessor() {
+ BookTest::from_dir("preprocessor/failing_preprocessor")
+ .run("build", |cmd| {
+ cmd.expect_failure()
+ .expect_stdout(str![[""]])
+ .expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book): Book building has started
+Boom!!1!
+[TIMESTAMP] [ERROR] (mdbook::utils): Error: The "nop-preprocessor" preprocessor exited unsuccessfully with [EXIT_STATUS]: 1 status
+
+"#]]);
+ });
+}
diff --git a/tests/testsuite/preprocessor/failing_preprocessor/book.toml b/tests/testsuite/preprocessor/failing_preprocessor/book.toml
new file mode 100644
index 00000000..f281ae0d
--- /dev/null
+++ b/tests/testsuite/preprocessor/failing_preprocessor/book.toml
@@ -0,0 +1,4 @@
+[preprocessor.nop-preprocessor]
+command = "cargo run --quiet --example nop-preprocessor --"
+blow-up = true
+
diff --git a/tests/testsuite/preprocessor/failing_preprocessor/src/SUMMARY.md b/tests/testsuite/preprocessor/failing_preprocessor/src/SUMMARY.md
new file mode 100644
index 00000000..e69de29b
From 5034707a734d3e5f50f3c84eba03358d373004f8 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 19:22:10 -0700
Subject: [PATCH 29/69] Migrate CmdPreprocessor tests to testsuite
---
tests/custom_preprocessors.rs | 30 ------------------------------
tests/testsuite/preprocessor.rs | 27 ++++++++++++++++++++++++++-
2 files changed, 26 insertions(+), 31 deletions(-)
delete mode 100644 tests/custom_preprocessors.rs
diff --git a/tests/custom_preprocessors.rs b/tests/custom_preprocessors.rs
deleted file mode 100644
index 6baa5db9..00000000
--- a/tests/custom_preprocessors.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-mod dummy_book;
-
-use crate::dummy_book::DummyBook;
-use mdbook::preprocess::{CmdPreprocessor, Preprocessor};
-use mdbook::MDBook;
-
-fn example() -> CmdPreprocessor {
- CmdPreprocessor::new(
- "nop-preprocessor".to_string(),
- "cargo run --example nop-preprocessor --".to_string(),
- )
-}
-
-#[test]
-fn example_supports_whatever() {
- let cmd = example();
-
- let got = cmd.supports_renderer("whatever");
-
- assert_eq!(got, true);
-}
-
-#[test]
-fn example_doesnt_support_not_supported() {
- let cmd = example();
-
- let got = cmd.supports_renderer("not-supported");
-
- assert_eq!(got, false);
-}
diff --git a/tests/testsuite/preprocessor.rs b/tests/testsuite/preprocessor.rs
index 4c620c3a..d2c1608b 100644
--- a/tests/testsuite/preprocessor.rs
+++ b/tests/testsuite/preprocessor.rs
@@ -3,7 +3,7 @@
use crate::prelude::*;
use mdbook::book::Book;
use mdbook::errors::Result;
-use mdbook::preprocess::{Preprocessor, PreprocessorContext};
+use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext};
use std::sync::{Arc, Mutex};
struct Spy(Arc>);
@@ -68,3 +68,28 @@ Boom!!1!
"#]]);
});
}
+
+fn example() -> CmdPreprocessor {
+ CmdPreprocessor::new(
+ "nop-preprocessor".to_string(),
+ "cargo run --quiet --example nop-preprocessor --".to_string(),
+ )
+}
+
+#[test]
+fn example_supports_whatever() {
+ let cmd = example();
+
+ let got = cmd.supports_renderer("whatever");
+
+ assert_eq!(got, true);
+}
+
+#[test]
+fn example_doesnt_support_not_supported() {
+ let cmd = example();
+
+ let got = cmd.supports_renderer("not-supported");
+
+ assert_eq!(got, false);
+}
From 3e22a5cdadb84fa789d20afe984eaf6025d30e8f Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 19:23:24 -0700
Subject: [PATCH 30/69] Migrate check_correct_relative_links_in_print_page to
BookTest
---
tests/rendered_output.rs | 21 -----------------
tests/testsuite/main.rs | 1 +
tests/testsuite/print.rs | 23 +++++++++++++++++++
.../testsuite/print/relative_links/book.toml | 2 ++
.../print/relative_links/src/SUMMARY.md | 5 ++++
.../print/relative_links/src/first/index.md | 1 +
.../print/relative_links/src/first/nested.md | 1 +
.../print/relative_links/src/second/nested.md | 16 +++++++++++++
8 files changed, 49 insertions(+), 21 deletions(-)
create mode 100644 tests/testsuite/print.rs
create mode 100644 tests/testsuite/print/relative_links/book.toml
create mode 100644 tests/testsuite/print/relative_links/src/SUMMARY.md
create mode 100644 tests/testsuite/print/relative_links/src/first/index.md
create mode 100644 tests/testsuite/print/relative_links/src/first/nested.md
create mode 100644 tests/testsuite/print/relative_links/src/second/nested.md
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index 88d44fef..47b5fc9f 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -72,27 +72,6 @@ fn check_correct_cross_links_in_nested_dir() {
);
}
-#[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"),
- &[
- r##"the first section ,"##,
- r##"outside "##,
- r##" "##,
- r##"fragment link "##,
- r##"HTML Link "##,
- r##" "##,
- ],
- );
-}
-
#[test]
fn chapter_content_appears_in_rendered_document() {
let content = vec![
diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs
index daec2814..298cf12d 100644
--- a/tests/testsuite/main.rs
+++ b/tests/testsuite/main.rs
@@ -11,6 +11,7 @@ mod init;
mod markdown;
mod playground;
mod preprocessor;
+mod print;
mod prelude {
pub use crate::book_test::BookTest;
diff --git a/tests/testsuite/print.rs b/tests/testsuite/print.rs
new file mode 100644
index 00000000..bac27a06
--- /dev/null
+++ b/tests/testsuite/print.rs
@@ -0,0 +1,23 @@
+//! Tests for print page.
+
+use crate::prelude::*;
+
+// Tests relative links from the print page.
+#[test]
+fn relative_links() {
+ BookTest::from_dir("print/relative_links")
+ .check_main_file("book/print.html",
+ str![[r##"
+
+
+
+When we link to the first section , it should work on
+both the print page and the non-print page.
+A fragment link should work.
+Link outside .
+
+HTML Link
+
+
+"##]]);
+}
diff --git a/tests/testsuite/print/relative_links/book.toml b/tests/testsuite/print/relative_links/book.toml
new file mode 100644
index 00000000..80822350
--- /dev/null
+++ b/tests/testsuite/print/relative_links/book.toml
@@ -0,0 +1,2 @@
+[book]
+title = "relative_links"
diff --git a/tests/testsuite/print/relative_links/src/SUMMARY.md b/tests/testsuite/print/relative_links/src/SUMMARY.md
new file mode 100644
index 00000000..229e7aac
--- /dev/null
+++ b/tests/testsuite/print/relative_links/src/SUMMARY.md
@@ -0,0 +1,5 @@
+# Summary
+
+- [First Chapter](first/index.md)
+ - [First Nested](first/nested.md)
+- [Second Chapter](second/nested.md)
diff --git a/tests/testsuite/print/relative_links/src/first/index.md b/tests/testsuite/print/relative_links/src/first/index.md
new file mode 100644
index 00000000..4f8bda17
--- /dev/null
+++ b/tests/testsuite/print/relative_links/src/first/index.md
@@ -0,0 +1 @@
+# First Chapter
diff --git a/tests/testsuite/print/relative_links/src/first/nested.md b/tests/testsuite/print/relative_links/src/first/nested.md
new file mode 100644
index 00000000..e207dd37
--- /dev/null
+++ b/tests/testsuite/print/relative_links/src/first/nested.md
@@ -0,0 +1 @@
+# First Nested
diff --git a/tests/testsuite/print/relative_links/src/second/nested.md b/tests/testsuite/print/relative_links/src/second/nested.md
new file mode 100644
index 00000000..faf1187f
--- /dev/null
+++ b/tests/testsuite/print/relative_links/src/second/nested.md
@@ -0,0 +1,16 @@
+# Testing relative links for the print page
+
+When we link to [the first section](../first/nested.md), it should work on
+both the print page and the non-print page.
+
+A [fragment link](#some-section) should work.
+
+Link [outside](../../std/foo/bar.html).
+
+
+
+HTML Link
+
+
+
+## Some section
From 50dfa365c7d95aea4e91cbe5396a5e82a52bbb9d Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 19:24:13 -0700
Subject: [PATCH 31/69] Migrate no_index_for_print_html to BookTest
---
tests/rendered_output.rs | 13 -------------
tests/testsuite/print.rs | 9 +++++++++
tests/testsuite/print/noindex/src/SUMMARY.md | 1 +
3 files changed, 10 insertions(+), 13 deletions(-)
create mode 100644 tests/testsuite/print/noindex/src/SUMMARY.md
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index 47b5fc9f..e66791f6 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -306,19 +306,6 @@ fn theme_dir_overrides_work_correctly() {
dummy_book::assert_contains_strings(built_index, &["This is a modified index.hbs!"]);
}
-#[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"##]);
-}
-
#[test]
fn redirects_are_emitted_correctly() {
let temp = DummyBook::new().build().unwrap();
diff --git a/tests/testsuite/print.rs b/tests/testsuite/print.rs
index bac27a06..0e0cdfe0 100644
--- a/tests/testsuite/print.rs
+++ b/tests/testsuite/print.rs
@@ -21,3 +21,12 @@ both the print page and the non-print page.
"##]]);
}
+
+// Checks that print.html is noindex.
+#[test]
+fn noindex() {
+ let robots = r#" "#;
+ BookTest::from_dir("print/noindex")
+ .check_file_contains("book/print.html", robots)
+ .check_file_doesnt_contain("book/index.html", robots);
+}
diff --git a/tests/testsuite/print/noindex/src/SUMMARY.md b/tests/testsuite/print/noindex/src/SUMMARY.md
new file mode 100644
index 00000000..655a0ded
--- /dev/null
+++ b/tests/testsuite/print/noindex/src/SUMMARY.md
@@ -0,0 +1 @@
+- [Intro](index.md)
From cb2a63ea0ae575c666174cb4fa2754f80c552d68 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 19:25:44 -0700
Subject: [PATCH 32/69] Migrate redirects_are_emitted_correctly to BookTest
---
tests/rendered_output.rs | 38 +------------------
tests/testsuite/main.rs | 1 +
tests/testsuite/redirects.rs | 18 +++++++++
.../redirects_are_emitted_correctly/book.toml | 6 +++
.../expected/nested/page.html | 12 ++++++
.../expected/overview.html | 12 ++++++
.../src/SUMMARY.md | 3 ++
.../src/chapter_1.md | 1 +
8 files changed, 54 insertions(+), 37 deletions(-)
create mode 100644 tests/testsuite/redirects.rs
create mode 100644 tests/testsuite/redirects/redirects_are_emitted_correctly/book.toml
create mode 100644 tests/testsuite/redirects/redirects_are_emitted_correctly/expected/nested/page.html
create mode 100644 tests/testsuite/redirects/redirects_are_emitted_correctly/expected/overview.html
create mode 100644 tests/testsuite/redirects/redirects_are_emitted_correctly/src/SUMMARY.md
create mode 100644 tests/testsuite/redirects/redirects_are_emitted_correctly/src/chapter_1.md
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index e66791f6..c75464b9 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -1,6 +1,6 @@
mod dummy_book;
-use crate::dummy_book::{assert_contains_strings, assert_doesnt_contain_strings, DummyBook};
+use crate::dummy_book::{assert_contains_strings, DummyBook};
use anyhow::Context;
use mdbook::book::Chapter;
@@ -11,10 +11,8 @@ use mdbook::{BookItem, MDBook};
use pretty_assertions::assert_eq;
use select::document::Document;
use select::predicate::{Attr, Class, Name, Predicate};
-use std::collections::HashMap;
use std::ffi::OsStr;
use std::fs;
-use std::io::Write;
use std::path::{Component, Path, PathBuf};
use std::str::FromStr;
use tempfile::Builder as TempFileBuilder;
@@ -306,35 +304,6 @@ fn theme_dir_overrides_work_correctly() {
dummy_book::assert_contains_strings(built_index, &["This is a modified index.hbs!"]);
}
-#[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 = vec![
- (PathBuf::from("/overview.html"), String::from("index.html")),
- (
- PathBuf::from("/nexted/page.md"),
- 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 {
- let mut redirect_file = md.build_dir_for("html");
- // append everything except the bits that make it absolute
- // (e.g. "/" or "C:\")
- redirect_file.extend(remove_absolute_components(original));
- let contents = fs::read_to_string(&redirect_file).unwrap();
- assert!(contents.contains(redirect));
- }
-}
-
#[test]
fn edit_url_has_default_src_dir_edit_url() {
let temp = DummyBook::new().build().unwrap();
@@ -386,11 +355,6 @@ fn edit_url_has_configured_src_dir_edit_url() {
);
}
-fn remove_absolute_components(path: &Path) -> impl Iterator- + '_ {
- path.components()
- .skip_while(|c| matches!(c, Component::Prefix(_) | Component::RootDir))
-}
-
/// Checks formatting of summary names with inline elements.
#[test]
fn summary_with_markdown_formatting() {
diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs
index 298cf12d..79502355 100644
--- a/tests/testsuite/main.rs
+++ b/tests/testsuite/main.rs
@@ -12,6 +12,7 @@ mod markdown;
mod playground;
mod preprocessor;
mod print;
+mod redirects;
mod prelude {
pub use crate::book_test::BookTest;
diff --git a/tests/testsuite/redirects.rs b/tests/testsuite/redirects.rs
new file mode 100644
index 00000000..3501c223
--- /dev/null
+++ b/tests/testsuite/redirects.rs
@@ -0,0 +1,18 @@
+//! Tests for the HTML redirect feature.
+
+use crate::prelude::*;
+use snapbox::file;
+
+// Basic check of redirects.
+#[test]
+fn redirects_are_emitted_correctly() {
+ BookTest::from_dir("redirects/redirects_are_emitted_correctly")
+ .check_file(
+ "book/overview.html",
+ file!["redirects/redirects_are_emitted_correctly/expected/overview.html"],
+ )
+ .check_file(
+ "book/nested/page.html",
+ file!["redirects/redirects_are_emitted_correctly/expected/nested/page.html"],
+ );
+}
diff --git a/tests/testsuite/redirects/redirects_are_emitted_correctly/book.toml b/tests/testsuite/redirects/redirects_are_emitted_correctly/book.toml
new file mode 100644
index 00000000..46e4b865
--- /dev/null
+++ b/tests/testsuite/redirects/redirects_are_emitted_correctly/book.toml
@@ -0,0 +1,6 @@
+[book]
+title = "redirects_are_emitted_correctly"
+
+[output.html.redirect]
+"/overview.html" = "index.html"
+"/nested/page.html" = "https://rust-lang.org/"
diff --git a/tests/testsuite/redirects/redirects_are_emitted_correctly/expected/nested/page.html b/tests/testsuite/redirects/redirects_are_emitted_correctly/expected/nested/page.html
new file mode 100644
index 00000000..08d6e929
--- /dev/null
+++ b/tests/testsuite/redirects/redirects_are_emitted_correctly/expected/nested/page.html
@@ -0,0 +1,12 @@
+
+
+
+
+ Redirecting...
+
+
+
+
+ Redirecting to... https://rust-lang.org/ .
+
+
diff --git a/tests/testsuite/redirects/redirects_are_emitted_correctly/expected/overview.html b/tests/testsuite/redirects/redirects_are_emitted_correctly/expected/overview.html
new file mode 100644
index 00000000..80323084
--- /dev/null
+++ b/tests/testsuite/redirects/redirects_are_emitted_correctly/expected/overview.html
@@ -0,0 +1,12 @@
+
+
+
+
+ Redirecting...
+
+
+
+
+ Redirecting to... index.html .
+
+
diff --git a/tests/testsuite/redirects/redirects_are_emitted_correctly/src/SUMMARY.md b/tests/testsuite/redirects/redirects_are_emitted_correctly/src/SUMMARY.md
new file mode 100644
index 00000000..4f683946
--- /dev/null
+++ b/tests/testsuite/redirects/redirects_are_emitted_correctly/src/SUMMARY.md
@@ -0,0 +1,3 @@
+# Redirects
+
+- [Chapter 1](chapter_1.md)
diff --git a/tests/testsuite/redirects/redirects_are_emitted_correctly/src/chapter_1.md b/tests/testsuite/redirects/redirects_are_emitted_correctly/src/chapter_1.md
new file mode 100644
index 00000000..b743fda3
--- /dev/null
+++ b/tests/testsuite/redirects/redirects_are_emitted_correctly/src/chapter_1.md
@@ -0,0 +1 @@
+# Chapter 1
From 15c6f3f318afa5325a75f55f3868cf7302b34d6e Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 19:28:17 -0700
Subject: [PATCH 33/69] Migrate mdbook_runs_renderers to BookTest
---
tests/testsuite/main.rs | 1 +
.../renderer.rs} | 18 +++++++-----------
2 files changed, 8 insertions(+), 11 deletions(-)
rename tests/{build_process.rs => testsuite/renderer.rs} (67%)
diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs
index 79502355..5c030213 100644
--- a/tests/testsuite/main.rs
+++ b/tests/testsuite/main.rs
@@ -13,6 +13,7 @@ mod playground;
mod preprocessor;
mod print;
mod redirects;
+mod renderer;
mod prelude {
pub use crate::book_test::BookTest;
diff --git a/tests/build_process.rs b/tests/testsuite/renderer.rs
similarity index 67%
rename from tests/build_process.rs
rename to tests/testsuite/renderer.rs
index c59db328..f4e5cabc 100644
--- a/tests/build_process.rs
+++ b/tests/testsuite/renderer.rs
@@ -1,10 +1,8 @@
-mod dummy_book;
+//! Tests for custom renderers.
-use crate::dummy_book::DummyBook;
-use mdbook::config::Config;
-use mdbook::errors::*;
+use crate::prelude::*;
+use mdbook::errors::Result;
use mdbook::renderer::{RenderContext, Renderer};
-use mdbook::MDBook;
use std::sync::{Arc, Mutex};
struct Spy(Arc>);
@@ -26,14 +24,12 @@ impl Renderer for Spy {
}
}
+// Test that renderer gets run.
#[test]
-fn mdbook_runs_renderers() {
+fn runs_renderers() {
+ let test = BookTest::init(|_| {});
let spy: Arc> = Default::default();
-
- let temp = DummyBook::new().build().unwrap();
- let cfg = Config::default();
-
- let mut book = MDBook::load_with_config(temp.path(), cfg).unwrap();
+ let mut book = test.load_book();
book.with_renderer(Spy(Arc::clone(&spy)));
book.build().unwrap();
From 5bc25e32eb70c44ea93b3d43f96ebf28fb7ae885 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 19:30:22 -0700
Subject: [PATCH 34/69] Remove passing_alternate_backend
After some testing I notice that this test is failing randomly because
the `true` program is exiting before mdbook is able to transmit the
JSON, and it fails with a broken pipe.
This will be replaced with backends_receive_render_context_via_stdin,
which does essentially the same thing, but does suffer from the same
problem.
---
tests/alternative_backends.rs | 15 ---------------
1 file changed, 15 deletions(-)
diff --git a/tests/alternative_backends.rs b/tests/alternative_backends.rs
index ecd25031..a1b21ac4 100644
--- a/tests/alternative_backends.rs
+++ b/tests/alternative_backends.rs
@@ -6,13 +6,6 @@ use std::fs;
use std::path::Path;
use tempfile::{Builder as TempFileBuilder, TempDir};
-#[test]
-fn passing_alternate_backend() {
- let (md, _temp) = dummy_book_with_backend("passing", success_cmd(), false);
-
- md.build().unwrap();
-}
-
#[test]
fn failing_alternate_backend() {
let (md, _temp) = dummy_book_with_backend("failing", fail_cmd(), false);
@@ -143,14 +136,6 @@ fn fail_cmd() -> &'static str {
}
}
-fn success_cmd() -> &'static str {
- if cfg!(windows) {
- r#"cmd.exe /c "exit 0""#
- } else {
- "true"
- }
-}
-
fn rust_exe(temp: &Path, name: &str, src: &str) {
let rs = temp.join(name).with_extension("rs");
fs::write(&rs, src).unwrap();
From a2cf838bafecc62718f7641de0d01a8110a66161 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 19:31:25 -0700
Subject: [PATCH 35/69] Migrate failing_alternate_backend to BookTest
---
tests/alternative_backends.rs | 15 ---------------
tests/testsuite/renderer.rs | 36 +++++++++++++++++++++++++++++++++++
2 files changed, 36 insertions(+), 15 deletions(-)
diff --git a/tests/alternative_backends.rs b/tests/alternative_backends.rs
index a1b21ac4..f787c862 100644
--- a/tests/alternative_backends.rs
+++ b/tests/alternative_backends.rs
@@ -6,13 +6,6 @@ use std::fs;
use std::path::Path;
use tempfile::{Builder as TempFileBuilder, TempDir};
-#[test]
-fn failing_alternate_backend() {
- let (md, _temp) = dummy_book_with_backend("failing", fail_cmd(), false);
-
- md.build().unwrap_err();
-}
-
#[test]
fn missing_backends_are_fatal() {
let (md, _temp) = dummy_book_with_backend("missing", "trduyvbhijnorgevfuhn", false);
@@ -128,14 +121,6 @@ fn dummy_book_with_backend(
(md, temp)
}
-fn fail_cmd() -> &'static str {
- if cfg!(windows) {
- r#"cmd.exe /c "exit 1""#
- } else {
- "false"
- }
-}
-
fn rust_exe(temp: &Path, name: &str, src: &str) {
let rs = temp.join(name).with_extension("rs");
fs::write(&rs, src).unwrap();
diff --git a/tests/testsuite/renderer.rs b/tests/testsuite/renderer.rs
index f4e5cabc..98be19c9 100644
--- a/tests/testsuite/renderer.rs
+++ b/tests/testsuite/renderer.rs
@@ -36,3 +36,39 @@ fn runs_renderers() {
let inner = spy.lock().unwrap();
assert_eq!(inner.run_count, 1);
}
+
+// Test renderer with a failing command fails.
+#[test]
+fn failing_command() {
+ BookTest::init(|_| {})
+ .rust_program(
+ "failing",
+ r#"
+ fn main() {
+ // Read from stdin to avoid random pipe failures on Linux.
+ use std::io::Read;
+ let mut s = String::new();
+ std::io::stdin().read_to_string(&mut s).unwrap();
+ std::process::exit(1);
+ }
+ "#,
+ )
+ .change_file(
+ "book.toml",
+ "[output.failing]\n\
+ command = './failing'\n",
+ )
+ .run("build", |cmd| {
+ cmd.expect_failure()
+ .expect_stdout(str![[""]])
+ .expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book): Book building has started
+[TIMESTAMP] [INFO] (mdbook::book): Running the failing backend
+[TIMESTAMP] [INFO] (mdbook::renderer): Invoking the "failing" renderer
+[TIMESTAMP] [ERROR] (mdbook::renderer): Renderer exited with non-zero return code.
+[TIMESTAMP] [ERROR] (mdbook::utils): Error: Rendering failed
+[TIMESTAMP] [ERROR] (mdbook::utils): [TAB]Caused By: The "failing" renderer failed
+
+"#]]);
+ });
+}
From 86638abea907c053225a2e1b92892c96389a477f Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 19:33:43 -0700
Subject: [PATCH 36/69] Migrate missing_backends_are_fatal to BookTest
---
tests/alternative_backends.rs | 9 ---------
tests/testsuite/renderer.rs | 19 +++++++++++++++++++
.../renderer/missing_renderer/book.toml | 3 +++
.../renderer/missing_renderer/src/SUMMARY.md | 0
4 files changed, 22 insertions(+), 9 deletions(-)
create mode 100644 tests/testsuite/renderer/missing_renderer/book.toml
create mode 100644 tests/testsuite/renderer/missing_renderer/src/SUMMARY.md
diff --git a/tests/alternative_backends.rs b/tests/alternative_backends.rs
index f787c862..6410f9da 100644
--- a/tests/alternative_backends.rs
+++ b/tests/alternative_backends.rs
@@ -6,15 +6,6 @@ use std::fs;
use std::path::Path;
use tempfile::{Builder as TempFileBuilder, TempDir};
-#[test]
-fn missing_backends_are_fatal() {
- let (md, _temp) = dummy_book_with_backend("missing", "trduyvbhijnorgevfuhn", false);
- let got = md.build();
- assert!(got.is_err());
- let error_message = got.err().unwrap().to_string();
- assert_eq!(error_message, "Rendering failed");
-}
-
#[test]
fn missing_optional_backends_are_not_fatal() {
let (md, _temp) = dummy_book_with_backend("missing", "trduyvbhijnorgevfuhn", true);
diff --git a/tests/testsuite/renderer.rs b/tests/testsuite/renderer.rs
index 98be19c9..8732a6c6 100644
--- a/tests/testsuite/renderer.rs
+++ b/tests/testsuite/renderer.rs
@@ -72,3 +72,22 @@ fn failing_command() {
"#]]);
});
}
+
+// Renderer command is missing.
+#[test]
+fn missing_renderer() {
+ BookTest::from_dir("renderer/missing_renderer").run("build", |cmd| {
+ cmd.expect_failure()
+ .expect_stdout(str![[""]])
+ .expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book): Book building has started
+[TIMESTAMP] [INFO] (mdbook::book): Running the missing backend
+[TIMESTAMP] [INFO] (mdbook::renderer): Invoking the "missing" renderer
+[TIMESTAMP] [ERROR] (mdbook::renderer): The command `trduyvbhijnorgevfuhn` wasn't found, is the "missing" backend installed? If you want to ignore this error when the "missing" backend is not installed, set `optional = true` in the `[output.missing]` section of the book.toml configuration file.
+[TIMESTAMP] [ERROR] (mdbook::utils): Error: Rendering failed
+[TIMESTAMP] [ERROR] (mdbook::utils): [TAB]Caused By: Unable to start the backend
+[TIMESTAMP] [ERROR] (mdbook::utils): [TAB]Caused By: [NOT_FOUND]
+
+"#]]);
+ });
+}
diff --git a/tests/testsuite/renderer/missing_renderer/book.toml b/tests/testsuite/renderer/missing_renderer/book.toml
new file mode 100644
index 00000000..ddeac5d7
--- /dev/null
+++ b/tests/testsuite/renderer/missing_renderer/book.toml
@@ -0,0 +1,3 @@
+[output.missing]
+command = "trduyvbhijnorgevfuhn"
+
diff --git a/tests/testsuite/renderer/missing_renderer/src/SUMMARY.md b/tests/testsuite/renderer/missing_renderer/src/SUMMARY.md
new file mode 100644
index 00000000..e69de29b
From f482aeaca3f2791dd47b0978a6bb92579190fd6f Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 19:35:35 -0700
Subject: [PATCH 37/69] Migrate missing_optional_backends_are_not_fatal to
BookTest
---
tests/alternative_backends.rs | 6 ------
tests/testsuite/renderer.rs | 14 ++++++++++++++
.../renderer/missing_optional_not_fatal/book.toml | 4 ++++
.../missing_optional_not_fatal/src/SUMMARY.md | 0
4 files changed, 18 insertions(+), 6 deletions(-)
create mode 100644 tests/testsuite/renderer/missing_optional_not_fatal/book.toml
create mode 100644 tests/testsuite/renderer/missing_optional_not_fatal/src/SUMMARY.md
diff --git a/tests/alternative_backends.rs b/tests/alternative_backends.rs
index 6410f9da..85f0360d 100644
--- a/tests/alternative_backends.rs
+++ b/tests/alternative_backends.rs
@@ -6,12 +6,6 @@ use std::fs;
use std::path::Path;
use tempfile::{Builder as TempFileBuilder, TempDir};
-#[test]
-fn missing_optional_backends_are_not_fatal() {
- let (md, _temp) = dummy_book_with_backend("missing", "trduyvbhijnorgevfuhn", true);
- assert!(md.build().is_ok());
-}
-
#[test]
fn alternate_backend_with_arguments() {
let (md, _temp) = dummy_book_with_backend("arguments", "echo Hello World!", false);
diff --git a/tests/testsuite/renderer.rs b/tests/testsuite/renderer.rs
index 8732a6c6..e53a920f 100644
--- a/tests/testsuite/renderer.rs
+++ b/tests/testsuite/renderer.rs
@@ -91,3 +91,17 @@ fn missing_renderer() {
"#]]);
});
}
+
+// Optional missing is not an error.
+#[test]
+fn missing_optional_not_fatal() {
+ BookTest::from_dir("renderer/missing_optional_not_fatal").run("build", |cmd| {
+ cmd.expect_stdout(str![[""]]).expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book): Book building has started
+[TIMESTAMP] [INFO] (mdbook::book): Running the missing backend
+[TIMESTAMP] [INFO] (mdbook::renderer): Invoking the "missing" renderer
+[TIMESTAMP] [WARN] (mdbook::renderer): The command `trduyvbhijnorgevfuhn` for backend `missing` was not found, but was marked as optional.
+
+"#]]);
+ });
+}
diff --git a/tests/testsuite/renderer/missing_optional_not_fatal/book.toml b/tests/testsuite/renderer/missing_optional_not_fatal/book.toml
new file mode 100644
index 00000000..79a7ceba
--- /dev/null
+++ b/tests/testsuite/renderer/missing_optional_not_fatal/book.toml
@@ -0,0 +1,4 @@
+[output.missing]
+command = "trduyvbhijnorgevfuhn"
+optional = true
+
diff --git a/tests/testsuite/renderer/missing_optional_not_fatal/src/SUMMARY.md b/tests/testsuite/renderer/missing_optional_not_fatal/src/SUMMARY.md
new file mode 100644
index 00000000..e69de29b
From 82000d917f11d3e8424c7da6f4fabef419c4e5ba Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Mon, 21 Apr 2025 19:44:23 -0700
Subject: [PATCH 38/69] Migrate alternate_backend_with_arguments to BookTest
---
tests/alternative_backends.rs | 7 -----
tests/testsuite/renderer.rs | 31 +++++++++++++++++++
.../renderer_with_arguments/book.toml | 3 ++
.../renderer_with_arguments/src/SUMMARY.md | 0
4 files changed, 34 insertions(+), 7 deletions(-)
create mode 100644 tests/testsuite/renderer/renderer_with_arguments/book.toml
create mode 100644 tests/testsuite/renderer/renderer_with_arguments/src/SUMMARY.md
diff --git a/tests/alternative_backends.rs b/tests/alternative_backends.rs
index 85f0360d..f9080285 100644
--- a/tests/alternative_backends.rs
+++ b/tests/alternative_backends.rs
@@ -6,13 +6,6 @@ use std::fs;
use std::path::Path;
use tempfile::{Builder as TempFileBuilder, TempDir};
-#[test]
-fn alternate_backend_with_arguments() {
- let (md, _temp) = dummy_book_with_backend("arguments", "echo Hello World!", false);
-
- md.build().unwrap();
-}
-
#[test]
fn backends_receive_render_context_via_stdin() {
use mdbook::renderer::RenderContext;
diff --git a/tests/testsuite/renderer.rs b/tests/testsuite/renderer.rs
index e53a920f..3d0dfe95 100644
--- a/tests/testsuite/renderer.rs
+++ b/tests/testsuite/renderer.rs
@@ -105,3 +105,34 @@ fn missing_optional_not_fatal() {
"#]]);
});
}
+
+// Command can include arguments.
+#[test]
+fn renderer_with_arguments() {
+ BookTest::from_dir("renderer/renderer_with_arguments")
+ .rust_program(
+ "arguments",
+ r#"
+ fn main() {
+ let args: Vec<_> = std::env::args().skip(1).collect();
+ assert_eq!(args, &["arg1", "arg2"]);
+ println!("Hello World!");
+ use std::io::Read;
+ let mut s = String::new();
+ std::io::stdin().read_to_string(&mut s).unwrap();
+ }
+ "#,
+ )
+ .run("build", |cmd| {
+ cmd.expect_stdout(str![[r#"
+Hello World!
+
+"#]])
+ .expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book): Book building has started
+[TIMESTAMP] [INFO] (mdbook::book): Running the arguments backend
+[TIMESTAMP] [INFO] (mdbook::renderer): Invoking the "arguments" renderer
+
+"#]]);
+ });
+}
diff --git a/tests/testsuite/renderer/renderer_with_arguments/book.toml b/tests/testsuite/renderer/renderer_with_arguments/book.toml
new file mode 100644
index 00000000..7722c529
--- /dev/null
+++ b/tests/testsuite/renderer/renderer_with_arguments/book.toml
@@ -0,0 +1,3 @@
+[output.arguments]
+command = "./arguments arg1 arg2"
+
diff --git a/tests/testsuite/renderer/renderer_with_arguments/src/SUMMARY.md b/tests/testsuite/renderer/renderer_with_arguments/src/SUMMARY.md
new file mode 100644
index 00000000..e69de29b
From a38a30da1e7ef400e26244aef3bd815f34a0077c Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 08:50:10 -0700
Subject: [PATCH 39/69] Migrate backends_receive_render_context_via_stdin to
BookTest
---
tests/alternative_backends.rs | 56 +--------------
tests/testsuite/renderer.rs | 72 +++++++++++++++++++
.../book.toml | 3 +
.../src/SUMMARY.md | 1 +
.../src/chapter_1.md | 1 +
5 files changed, 78 insertions(+), 55 deletions(-)
create mode 100644 tests/testsuite/renderer/backends_receive_render_context_via_stdin/book.toml
create mode 100644 tests/testsuite/renderer/backends_receive_render_context_via_stdin/src/SUMMARY.md
create mode 100644 tests/testsuite/renderer/backends_receive_render_context_via_stdin/src/chapter_1.md
diff --git a/tests/alternative_backends.rs b/tests/alternative_backends.rs
index f9080285..057453b1 100644
--- a/tests/alternative_backends.rs
+++ b/tests/alternative_backends.rs
@@ -4,37 +4,7 @@ use mdbook::config::Config;
use mdbook::MDBook;
use std::fs;
use std::path::Path;
-use tempfile::{Builder as TempFileBuilder, TempDir};
-
-#[test]
-fn backends_receive_render_context_via_stdin() {
- use mdbook::renderer::RenderContext;
- use std::fs::File;
-
- let (md, temp) = dummy_book_with_backend("cat-to-file", "renderers/myrenderer", false);
-
- let renderers = temp.path().join("renderers");
- fs::create_dir(&renderers).unwrap();
- rust_exe(
- &renderers,
- "myrenderer",
- r#"fn main() {
- use std::io::Read;
- let mut s = String::new();
- std::io::stdin().read_to_string(&mut s).unwrap();
- std::fs::write("out.txt", s).unwrap();
- }"#,
- );
-
- let out_file = temp.path().join("book/out.txt");
-
- assert!(!out_file.exists());
- md.build().unwrap();
- assert!(out_file.exists());
-
- let got = RenderContext::from_json(File::open(&out_file).unwrap());
- assert!(got.is_ok());
-}
+use tempfile::Builder as TempFileBuilder;
#[test]
fn relative_command_path() {
@@ -75,30 +45,6 @@ fn relative_command_path() {
do_test("renderers/myrenderer");
}
-fn dummy_book_with_backend(
- name: &str,
- command: &str,
- backend_is_optional: bool,
-) -> (MDBook, TempDir) {
- let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
-
- let mut config = Config::default();
- config
- .set(format!("output.{name}.command"), command)
- .unwrap();
-
- if backend_is_optional {
- config.set(format!("output.{name}.optional"), true).unwrap();
- }
-
- let md = MDBook::init(temp.path())
- .with_config(config)
- .build()
- .unwrap();
-
- (md, temp)
-}
-
fn rust_exe(temp: &Path, name: &str, src: &str) {
let rs = temp.join(name).with_extension("rs");
fs::write(&rs, src).unwrap();
diff --git a/tests/testsuite/renderer.rs b/tests/testsuite/renderer.rs
index 3d0dfe95..45f06319 100644
--- a/tests/testsuite/renderer.rs
+++ b/tests/testsuite/renderer.rs
@@ -3,6 +3,8 @@
use crate::prelude::*;
use mdbook::errors::Result;
use mdbook::renderer::{RenderContext, Renderer};
+use snapbox::IntoData;
+use std::fs::File;
use std::sync::{Arc, Mutex};
struct Spy(Arc>);
@@ -136,3 +138,73 @@ Hello World!
"#]]);
});
}
+
+// Checks the render context received by the renderer.
+#[test]
+fn backends_receive_render_context_via_stdin() {
+ let mut test = BookTest::from_dir("renderer/backends_receive_render_context_via_stdin");
+ test.rust_program(
+ "cat-to-file",
+ r#"
+ fn main() {
+ use std::io::Read;
+ let mut s = String::new();
+ std::io::stdin().read_to_string(&mut s).unwrap();
+ std::fs::write("out.txt", s).unwrap();
+ }
+ "#,
+ )
+ .run("build", |cmd| {
+ cmd.expect_stdout(str![[""]]).expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book): Book building has started
+[TIMESTAMP] [INFO] (mdbook::book): Running the cat-to-file backend
+[TIMESTAMP] [INFO] (mdbook::renderer): Invoking the "cat-to-file" renderer
+
+"#]]);
+ })
+ .check_file(
+ "book/out.txt",
+ str![[r##"
+{
+ "book": {
+ "__non_exhaustive": null,
+ "sections": [
+ {
+ "Chapter": {
+ "content": "# Chapter 1\n",
+ "name": "Chapter 1",
+ "number": [
+ 1
+ ],
+ "parent_names": [],
+ "path": "chapter_1.md",
+ "source_path": "chapter_1.md",
+ "sub_items": []
+ }
+ }
+ ]
+ },
+ "config": {
+ "book": {
+ "authors": [],
+ "language": "en",
+ "src": "src"
+ },
+ "output": {
+ "cat-to-file": {
+ "command": "./cat-to-file"
+ }
+ }
+ },
+ "destination": "[ROOT]/book",
+ "root": "[ROOT]",
+ "version": "[VERSION]"
+}
+"##]]
+ .is_json(),
+ );
+
+ // Can round-trip.
+ let f = File::open(test.dir.join("book/out.txt")).unwrap();
+ RenderContext::from_json(f).unwrap();
+}
diff --git a/tests/testsuite/renderer/backends_receive_render_context_via_stdin/book.toml b/tests/testsuite/renderer/backends_receive_render_context_via_stdin/book.toml
new file mode 100644
index 00000000..db63e652
--- /dev/null
+++ b/tests/testsuite/renderer/backends_receive_render_context_via_stdin/book.toml
@@ -0,0 +1,3 @@
+[output.cat-to-file]
+command = "./cat-to-file"
+
diff --git a/tests/testsuite/renderer/backends_receive_render_context_via_stdin/src/SUMMARY.md b/tests/testsuite/renderer/backends_receive_render_context_via_stdin/src/SUMMARY.md
new file mode 100644
index 00000000..742767cc
--- /dev/null
+++ b/tests/testsuite/renderer/backends_receive_render_context_via_stdin/src/SUMMARY.md
@@ -0,0 +1 @@
+- [Chapter 1](./chapter_1.md)
diff --git a/tests/testsuite/renderer/backends_receive_render_context_via_stdin/src/chapter_1.md b/tests/testsuite/renderer/backends_receive_render_context_via_stdin/src/chapter_1.md
new file mode 100644
index 00000000..b743fda3
--- /dev/null
+++ b/tests/testsuite/renderer/backends_receive_render_context_via_stdin/src/chapter_1.md
@@ -0,0 +1 @@
+# Chapter 1
From 2f10831a80be2f137baf9a255496316e9563d600 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 08:52:34 -0700
Subject: [PATCH 40/69] Migrate relative_command_path to BookTest
---
tests/alternative_backends.rs | 57 -----------------------------------
tests/testsuite/renderer.rs | 54 +++++++++++++++++++++++++++++++++
2 files changed, 54 insertions(+), 57 deletions(-)
delete mode 100644 tests/alternative_backends.rs
diff --git a/tests/alternative_backends.rs b/tests/alternative_backends.rs
deleted file mode 100644
index 057453b1..00000000
--- a/tests/alternative_backends.rs
+++ /dev/null
@@ -1,57 +0,0 @@
-//! Integration tests to make sure alternative backends work.
-
-use mdbook::config::Config;
-use mdbook::MDBook;
-use std::fs;
-use std::path::Path;
-use tempfile::Builder as TempFileBuilder;
-
-#[test]
-fn relative_command_path() {
- // Checks behavior of relative paths for the `command` setting.
- let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
- let renderers = temp.path().join("renderers");
- fs::create_dir(&renderers).unwrap();
- rust_exe(
- &renderers,
- "myrenderer",
- r#"fn main() {
- std::fs::write("output", "test").unwrap();
- }"#,
- );
- let do_test = |cmd_path| {
- let mut config = Config::default();
- config
- .set("output.html", toml::value::Table::new())
- .unwrap();
- config.set("output.myrenderer.command", cmd_path).unwrap();
- let md = MDBook::init(temp.path())
- .with_config(config)
- .build()
- .unwrap();
- let output = temp.path().join("book/myrenderer/output");
- assert!(!output.exists());
- md.build().unwrap();
- assert!(output.exists());
- fs::remove_file(output).unwrap();
- };
- // Legacy paths work, relative to the output directory.
- if cfg!(windows) {
- do_test("../../renderers/myrenderer.exe");
- } else {
- do_test("../../renderers/myrenderer");
- }
- // Modern path, relative to the book directory.
- do_test("renderers/myrenderer");
-}
-
-fn rust_exe(temp: &Path, name: &str, src: &str) {
- let rs = temp.join(name).with_extension("rs");
- fs::write(&rs, src).unwrap();
- let status = std::process::Command::new("rustc")
- .arg(rs)
- .current_dir(temp)
- .status()
- .expect("rustc should run");
- assert!(status.success());
-}
diff --git a/tests/testsuite/renderer.rs b/tests/testsuite/renderer.rs
index 45f06319..1e162447 100644
--- a/tests/testsuite/renderer.rs
+++ b/tests/testsuite/renderer.rs
@@ -208,3 +208,57 @@ fn backends_receive_render_context_via_stdin() {
let f = File::open(test.dir.join("book/out.txt")).unwrap();
RenderContext::from_json(f).unwrap();
}
+
+// Legacy relative renderer paths.
+//
+// https://github.com/rust-lang/mdBook/pull/1418
+#[test]
+fn legacy_relative_command_path() {
+ let mut test = BookTest::init(|_| {});
+ test.rust_program(
+ "renderers/myrenderer",
+ r#"
+ fn main() {
+ use std::io::Read;
+ let mut s = String::new();
+ std::io::stdin().read_to_string(&mut s).unwrap();
+ std::fs::write("output", "test").unwrap();
+ }
+ "#,
+ )
+ // Test with a modern path, relative to the book directory.
+ .change_file(
+ "book.toml",
+ "[output.myrenderer]\n\
+ command = 'renderers/myrenderer'\n",
+ )
+ .run("build", |cmd| {
+ cmd.expect_stdout(str![[""]]).expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book): Book building has started
+[TIMESTAMP] [INFO] (mdbook::book): Running the myrenderer backend
+[TIMESTAMP] [INFO] (mdbook::renderer): Invoking the "myrenderer" renderer
+
+"#]]);
+ })
+ .check_file("book/output", "test");
+ std::fs::remove_file(test.dir.join("book/output")).unwrap();
+ // Test with legacy path, relative to the output directory.
+ test.change_file(
+ "book.toml",
+ &format!(
+ "[output.myrenderer]\n\
+ command = '../renderers/myrenderer{exe}'\n",
+ exe = std::env::consts::EXE_SUFFIX
+ ),
+ )
+ .run("build", |cmd| {
+ cmd.expect_stdout(str![[""]]).expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book): Book building has started
+[TIMESTAMP] [INFO] (mdbook::book): Running the myrenderer backend
+[TIMESTAMP] [INFO] (mdbook::renderer): Invoking the "myrenderer" renderer
+[TIMESTAMP] [WARN] (mdbook::renderer): Renderer command `../renderers/myrenderer[EXE]` uses a path relative to the renderer output directory `[ROOT]/book`. This was previously accepted, but has been deprecated. Relative executable paths should be relative to the book root.
+
+"#]]);
+ })
+ .check_file("book/output", "test");
+}
From d23bdaa527d3e68636ee8dc1e0da76a61278bef6 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 08:54:31 -0700
Subject: [PATCH 41/69] Migrate edit-url-template tests to BookTest
---
tests/rendered_output.rs | 53 +------------------
tests/testsuite/main.rs | 1 +
tests/testsuite/rendering.rs | 23 ++++++++
.../rendering/edit_url_template/book.toml | 5 ++
.../edit_url_template/src/SUMMARY.md | 1 +
.../edit_url_template_explicit_src/book.toml | 6 +++
.../src2/SUMMARY.md | 1 +
7 files changed, 38 insertions(+), 52 deletions(-)
create mode 100644 tests/testsuite/rendering.rs
create mode 100644 tests/testsuite/rendering/edit_url_template/book.toml
create mode 100644 tests/testsuite/rendering/edit_url_template/src/SUMMARY.md
create mode 100644 tests/testsuite/rendering/edit_url_template_explicit_src/book.toml
create mode 100644 tests/testsuite/rendering/edit_url_template_explicit_src/src2/SUMMARY.md
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index c75464b9..c935fae0 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -13,7 +13,7 @@ use select::document::Document;
use select::predicate::{Attr, Class, Name, Predicate};
use std::ffi::OsStr;
use std::fs;
-use std::path::{Component, Path, PathBuf};
+use std::path::{Path, PathBuf};
use std::str::FromStr;
use tempfile::Builder as TempFileBuilder;
use walkdir::{DirEntry, WalkDir};
@@ -304,57 +304,6 @@ fn theme_dir_overrides_work_correctly() {
dummy_book::assert_contains_strings(built_index, &["This is a modified index.hbs!"]);
}
-#[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}"
- "#;
-
- write_file(temp.path(), "book.toml", book_toml.as_bytes()).unwrap();
-
- 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,
- &[
- 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}"
- "#;
-
- write_file(temp.path(), "book.toml", book_toml.as_bytes()).unwrap();
-
- 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,
- &[
- r#"href="https://github.com/rust-lang/mdBook/edit/master/guide/src2/README.md" title="Suggest an edit""#,
- ],
- );
-}
-
/// Checks formatting of summary names with inline elements.
#[test]
fn summary_with_markdown_formatting() {
diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs
index 5c030213..38c60999 100644
--- a/tests/testsuite/main.rs
+++ b/tests/testsuite/main.rs
@@ -14,6 +14,7 @@ mod preprocessor;
mod print;
mod redirects;
mod renderer;
+mod rendering;
mod prelude {
pub use crate::book_test::BookTest;
diff --git a/tests/testsuite/rendering.rs b/tests/testsuite/rendering.rs
new file mode 100644
index 00000000..750e27dd
--- /dev/null
+++ b/tests/testsuite/rendering.rs
@@ -0,0 +1,23 @@
+//! Tests for HTML rendering.
+
+use crate::prelude::*;
+
+// Checks that edit-url-template works.
+#[test]
+fn edit_url_template() {
+ BookTest::from_dir("rendering/edit_url_template").check_file_contains(
+ "book/index.html",
+ "",
+ );
+}
+
+// Checks that an alternate `src` setting works with the edit url template.
+#[test]
+fn edit_url_template_explicit_src() {
+ BookTest::from_dir("rendering/edit_url_template_explicit_src").check_file_contains(
+ "book/index.html",
+ " ",
+ );
+}
diff --git a/tests/testsuite/rendering/edit_url_template/book.toml b/tests/testsuite/rendering/edit_url_template/book.toml
new file mode 100644
index 00000000..fb942d4a
--- /dev/null
+++ b/tests/testsuite/rendering/edit_url_template/book.toml
@@ -0,0 +1,5 @@
+[book]
+title = "edit_url_template"
+
+[output.html]
+edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}"
diff --git a/tests/testsuite/rendering/edit_url_template/src/SUMMARY.md b/tests/testsuite/rendering/edit_url_template/src/SUMMARY.md
new file mode 100644
index 00000000..3edeb8d3
--- /dev/null
+++ b/tests/testsuite/rendering/edit_url_template/src/SUMMARY.md
@@ -0,0 +1 @@
+- [Intro](README.md)
diff --git a/tests/testsuite/rendering/edit_url_template_explicit_src/book.toml b/tests/testsuite/rendering/edit_url_template_explicit_src/book.toml
new file mode 100644
index 00000000..4573d8a9
--- /dev/null
+++ b/tests/testsuite/rendering/edit_url_template_explicit_src/book.toml
@@ -0,0 +1,6 @@
+[book]
+title = "edit_url_template"
+src = "src2"
+
+[output.html]
+edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}"
diff --git a/tests/testsuite/rendering/edit_url_template_explicit_src/src2/SUMMARY.md b/tests/testsuite/rendering/edit_url_template_explicit_src/src2/SUMMARY.md
new file mode 100644
index 00000000..3edeb8d3
--- /dev/null
+++ b/tests/testsuite/rendering/edit_url_template_explicit_src/src2/SUMMARY.md
@@ -0,0 +1 @@
+- [Intro](README.md)
From 3fce1151dd9a4448a3f9822f0ba43da7f428c302 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 08:55:24 -0700
Subject: [PATCH 42/69] Migrate
first_chapter_is_copied_as_index_even_if_not_first_elem to BookTest
---
tests/rendered_output.rs | 15 --------------
tests/testsuite/rendering.rs | 20 +++++++++++++++++++
.../src/SUMMARY.md | 11 ++++++++++
3 files changed, 31 insertions(+), 15 deletions(-)
create mode 100644 tests/testsuite/rendering/first_chapter_is_copied_as_index_even_if_not_first_elem/src/SUMMARY.md
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index c935fae0..d16c7a3f 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -271,21 +271,6 @@ fn example_book_can_build() {
md.build().unwrap();
}
-#[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();
-
- let root = temp.path().join("book");
- let chapter = fs::read_to_string(root.join("chapter_1.html")).expect("read chapter 1");
- let index = fs::read_to_string(root.join("index.html")).expect("read index");
- pretty_assertions::assert_eq!(chapter, index);
-}
-
#[test]
fn theme_dir_overrides_work_correctly() {
let book_dir = dummy_book::new_copy_of_example_book().unwrap();
diff --git a/tests/testsuite/rendering.rs b/tests/testsuite/rendering.rs
index 750e27dd..1d6cf476 100644
--- a/tests/testsuite/rendering.rs
+++ b/tests/testsuite/rendering.rs
@@ -21,3 +21,23 @@ fn edit_url_template_explicit_src() {
title=\"Suggest an edit\" aria-label=\"Suggest an edit\">",
);
}
+
+// Checks that index.html is generated correctly, even when the first few
+// chapters are drafts.
+#[test]
+fn first_chapter_is_copied_as_index_even_if_not_first_elem() {
+ BookTest::from_dir("rendering/first_chapter_is_copied_as_index_even_if_not_first_elem")
+ // These two files should be equal.
+ .check_main_file(
+ "book/chapter_1.html",
+ str![[
+ r##" "##
+ ]],
+ )
+ .check_main_file(
+ "book/index.html",
+ str![[
+ r##" "##
+ ]],
+ );
+}
diff --git a/tests/testsuite/rendering/first_chapter_is_copied_as_index_even_if_not_first_elem/src/SUMMARY.md b/tests/testsuite/rendering/first_chapter_is_copied_as_index_even_if_not_first_elem/src/SUMMARY.md
new file mode 100644
index 00000000..37bf68cd
--- /dev/null
+++ b/tests/testsuite/rendering/first_chapter_is_copied_as_index_even_if_not_first_elem/src/SUMMARY.md
@@ -0,0 +1,11 @@
+# Summary
+
+---
+
+- [None of these should be treated as the "index chapter"]()
+
+# Part 1
+
+- [Not this either]()
+- [Chapter 1](./chapter_1.md)
+- [And not this]()
From cad8988f8d95f9edff3046575c6fd932249276a7 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 09:00:47 -0700
Subject: [PATCH 43/69] Migrate book_creates_reasonable_search_index to
BookTest
---
tests/rendered_output.rs | 61 ---------------
tests/testsuite/main.rs | 2 +
tests/testsuite/search.rs | 78 +++++++++++++++++++
.../reasonable_search_index/expected_index.js | 1 +
.../reasonable_search_index/src/SUMMARY.md | 10 +++
.../src/first/duplicate-headers.md | 9 +++
.../src/first/heading-attributes.md | 5 ++
.../src/first/includes.md | 3 +
.../src/first/index.md | 5 ++
.../src/first/no-headers.md | 5 ++
.../src/first/unicode.md | 21 +++++
.../reasonable_search_index/src/intro.md | 28 +++++++
12 files changed, 167 insertions(+), 61 deletions(-)
create mode 100644 tests/testsuite/search.rs
create mode 100644 tests/testsuite/search/reasonable_search_index/expected_index.js
create mode 100644 tests/testsuite/search/reasonable_search_index/src/SUMMARY.md
create mode 100644 tests/testsuite/search/reasonable_search_index/src/first/duplicate-headers.md
create mode 100644 tests/testsuite/search/reasonable_search_index/src/first/heading-attributes.md
create mode 100644 tests/testsuite/search/reasonable_search_index/src/first/includes.md
create mode 100644 tests/testsuite/search/reasonable_search_index/src/first/index.md
create mode 100644 tests/testsuite/search/reasonable_search_index/src/first/no-headers.md
create mode 100644 tests/testsuite/search/reasonable_search_index/src/first/unicode.md
create mode 100644 tests/testsuite/search/reasonable_search_index/src/intro.md
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index d16c7a3f..1418c6bc 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -393,67 +393,6 @@ mod search {
serde_json::from_str(&index.replace("\\'", "'").replace("\\\\", "\\")).unwrap()
}
- #[test]
- 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());
-
- let doc_urls = index["doc_urls"].as_array().unwrap();
- eprintln!("doc_urls={doc_urls:#?}",);
- let get_doc_ref =
- |url: &str| -> String { doc_urls.iter().position(|s| s == url).unwrap().to_string() };
-
- 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");
- let no_headers = get_doc_ref("first/no-headers.html");
- let duplicate_headers_1 = get_doc_ref("first/duplicate-headers.html#header-text-1");
- let conclusion = get_doc_ref("conclusion.html#conclusion");
- let heading_attrs = get_doc_ref("first/heading-attributes.html#both");
-
- let bodyidx = &index["index"]["index"]["body"]["root"];
- let textidx = &bodyidx["t"]["e"]["x"]["t"];
- assert_eq!(textidx["df"], 5);
- assert_eq!(textidx["docs"][&first_chapter]["tf"], 1.0);
- assert_eq!(textidx["docs"][&introduction]["tf"], 1.0);
-
- let docs = &index["index"]["documentStore"]["docs"];
- assert_eq!(docs[&first_chapter]["body"], "more text.");
- assert_eq!(docs[&some_section]["body"], "");
- assert_eq!(
- docs[&summary]["body"],
- "Dummy Book Introduction First Chapter Nested Chapter Includes Recursive Markdown Unicode No Headers Duplicate Headers Heading Attributes Second Chapter Nested Chapter Conclusion"
- );
- assert_eq!(
- docs[&summary]["breadcrumbs"],
- "First Chapter » Includes » Summary"
- );
- // See note about InlineHtml in search.rs. Ideally the `alert()` part
- // should not be in the index, but we don't have a way to scrub inline
- // html.
- assert_eq!(docs[&conclusion]["body"], "I put <HTML> in here! Sneaky inline event alert(\"inline\");. But regular inline is indexed.");
- assert_eq!(
- docs[&no_headers]["breadcrumbs"],
- "First Chapter » No Headers"
- );
- assert_eq!(
- docs[&duplicate_headers_1]["breadcrumbs"],
- "First Chapter » Duplicate Headers » Header Text"
- );
- assert_eq!(
- docs[&no_headers]["body"],
- "Capybara capybara capybara. Capybara capybara capybara. ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex."
- );
- assert_eq!(
- docs[&heading_attrs]["breadcrumbs"],
- "First Chapter » Heading Attributes » Heading with id and classes"
- );
- }
-
#[test]
fn can_disable_individual_chapters() {
let temp = DummyBook::new().build().unwrap();
diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs
index 38c60999..95756d51 100644
--- a/tests/testsuite/main.rs
+++ b/tests/testsuite/main.rs
@@ -15,6 +15,8 @@ mod print;
mod redirects;
mod renderer;
mod rendering;
+#[cfg(feature = "search")]
+mod search;
mod prelude {
pub use crate::book_test::BookTest;
diff --git a/tests/testsuite/search.rs b/tests/testsuite/search.rs
new file mode 100644
index 00000000..2220adc8
--- /dev/null
+++ b/tests/testsuite/search.rs
@@ -0,0 +1,78 @@
+//! Tests for search support.
+
+use crate::prelude::*;
+use std::path::Path;
+
+fn read_book_index(root: &Path) -> serde_json::Value {
+ let index = root.join("book/searchindex.js");
+ let index = std::fs::read_to_string(index).unwrap();
+ let index = index.trim_start_matches("window.search = JSON.parse('");
+ let index = index.trim_end_matches("');");
+ // We need unescape the string as it's supposed to be an escaped JS string.
+ serde_json::from_str(&index.replace("\\'", "'").replace("\\\\", "\\")).unwrap()
+}
+
+// Some spot checks for the generation of the search index.
+#[test]
+fn reasonable_search_index() {
+ let mut test = BookTest::from_dir("search/reasonable_search_index");
+ test.build();
+ let index = read_book_index(&test.dir);
+
+ let doc_urls = index["doc_urls"].as_array().unwrap();
+ eprintln!("doc_urls={doc_urls:#?}",);
+ let get_doc_ref = |url: &str| -> String {
+ doc_urls
+ .iter()
+ .position(|s| s == url)
+ .unwrap_or_else(|| panic!("failed to find {url}"))
+ .to_string()
+ };
+
+ 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");
+ let no_headers = get_doc_ref("first/no-headers.html");
+ let duplicate_headers_1 = get_doc_ref("first/duplicate-headers.html#header-text-1");
+ let heading_attrs = get_doc_ref("first/heading-attributes.html#both");
+ let sneaky = get_doc_ref("intro.html#sneaky");
+
+ let bodyidx = &index["index"]["index"]["body"]["root"];
+ let textidx = &bodyidx["t"]["e"]["x"]["t"];
+ assert_eq!(textidx["df"], 5);
+ assert_eq!(textidx["docs"][&first_chapter]["tf"], 1.0);
+ assert_eq!(textidx["docs"][&introduction]["tf"], 1.0);
+
+ let docs = &index["index"]["documentStore"]["docs"];
+ assert_eq!(docs[&first_chapter]["body"], "more text.");
+ assert_eq!(docs[&some_section]["body"], "");
+ assert_eq!(
+ docs[&summary]["body"],
+ "Introduction First Chapter Includes Unicode No Headers Duplicate Headers Heading Attributes"
+ );
+ assert_eq!(
+ docs[&summary]["breadcrumbs"],
+ "First Chapter » Includes » Summary"
+ );
+ // See note about InlineHtml in search.rs. Ideally the `alert()` part
+ // should not be in the index, but we don't have a way to scrub inline
+ // html.
+ assert_eq!(docs[&sneaky]["body"], "I put <HTML> in here! Sneaky inline event alert(\"inline\");. But regular inline is indexed.");
+ assert_eq!(
+ docs[&no_headers]["breadcrumbs"],
+ "First Chapter » No Headers"
+ );
+ assert_eq!(
+ docs[&duplicate_headers_1]["breadcrumbs"],
+ "First Chapter » Duplicate Headers » Header Text"
+ );
+ assert_eq!(
+ docs[&no_headers]["body"],
+ "Capybara capybara capybara. Capybara capybara capybara. ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex."
+ );
+ assert_eq!(
+ docs[&heading_attrs]["breadcrumbs"],
+ "First Chapter » Heading Attributes » Heading with id and classes"
+ );
+}
diff --git a/tests/testsuite/search/reasonable_search_index/expected_index.js b/tests/testsuite/search/reasonable_search_index/expected_index.js
new file mode 100644
index 00000000..776cb46d
--- /dev/null
+++ b/tests/testsuite/search/reasonable_search_index/expected_index.js
@@ -0,0 +1 @@
+Object.assign(window.search, {"doc_urls":["intro.html#introduction","intro.html#sneaky","first/index.html#first-chapter","first/index.html#some-section","first/includes.html#includes","first/includes.html#summary","first/unicode.html#unicode-stress-tests","first/no-headers.html","first/duplicate-headers.html#duplicate-headers","first/duplicate-headers.html#header-text","first/duplicate-headers.html#header-text-1","first/duplicate-headers.html#header-text-2","first/heading-attributes.html#attrs","first/heading-attributes.html#heading-with-classes","first/heading-attributes.html#both"],"index":{"documentStore":{"docInfo":{"0":{"body":3,"breadcrumbs":2,"title":1},"1":{"body":10,"breadcrumbs":2,"title":1},"10":{"body":0,"breadcrumbs":6,"title":2},"11":{"body":0,"breadcrumbs":6,"title":2},"12":{"body":0,"breadcrumbs":6,"title":2},"13":{"body":0,"breadcrumbs":6,"title":2},"14":{"body":0,"breadcrumbs":7,"title":3},"2":{"body":2,"breadcrumbs":4,"title":2},"3":{"body":0,"breadcrumbs":3,"title":1},"4":{"body":0,"breadcrumbs":4,"title":1},"5":{"body":10,"breadcrumbs":4,"title":1},"6":{"body":29,"breadcrumbs":6,"title":3},"7":{"body":6,"breadcrumbs":3,"title":2},"8":{"body":5,"breadcrumbs":6,"title":2},"9":{"body":0,"breadcrumbs":6,"title":2}},"docs":{"0":{"body":"Here's some interesting text...","breadcrumbs":"Introduction » Introduction","id":"0","title":"Introduction"},"1":{"body":"I put <HTML> in here! Sneaky inline event alert(/"inline/");. But regular inline is indexed.","breadcrumbs":"Introduction » Sneaky","id":"1","title":"Sneaky"},"10":{"body":"","breadcrumbs":"First Chapter » Duplicate Headers » Header Text","id":"10","title":"Header Text"},"11":{"body":"","breadcrumbs":"First Chapter » Duplicate Headers » header-text","id":"11","title":"header-text"},"12":{"body":"","breadcrumbs":"First Chapter » Heading Attributes » Heading Attributes","id":"12","title":"Heading Attributes"},"13":{"body":"","breadcrumbs":"First Chapter » Heading Attributes » Heading with classes","id":"13","title":"Heading with classes"},"14":{"body":"","breadcrumbs":"First Chapter » Heading Attributes » Heading with id and classes","id":"14","title":"Heading with id and classes"},"2":{"body":"more text.","breadcrumbs":"First Chapter » First Chapter","id":"2","title":"First Chapter"},"3":{"body":"","breadcrumbs":"First Chapter » Some Section","id":"3","title":"Some Section"},"4":{"body":"","breadcrumbs":"First Chapter » Includes » Includes","id":"4","title":"Includes"},"5":{"body":"Introduction First Chapter Includes Unicode No Headers Duplicate Headers Heading Attributes","breadcrumbs":"First Chapter » Includes » Summary","id":"5","title":"Summary"},"6":{"body":"Please be careful editing, this contains carefully crafted characters. Two byte character: spatiëring Combining character: spatiëring Three byte character: 书こんにちは Four byte character: 𐌀𐌁𐌂𐌃𐌄𐌅𐌆𐌇𐌈 Right-to-left: مرحبا Emoticons: 🔊 😍 💜 1️⃣ right-to-left mark: hello באמת! Zalgo: ǫ̛̖̱̗̝͈̋͒͋̏ͥͫ̒̆ͩ̏͌̾͊͐ͪ̾̚","breadcrumbs":"First Chapter » Unicode » Unicode stress tests","id":"6","title":"Unicode stress tests"},"7":{"body":"Capybara capybara capybara. Capybara capybara capybara. ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex.","breadcrumbs":"First Chapter » No Headers","id":"7","title":"First Chapter"},"8":{"body":"This page validates behaviour of duplicate headers.","breadcrumbs":"First Chapter » Duplicate Headers » Duplicate headers","id":"8","title":"Duplicate headers"},"9":{"body":"","breadcrumbs":"First Chapter » Duplicate Headers » Header Text","id":"9","title":"Header Text"}},"length":15,"save":true},"fields":["title","body","breadcrumbs"],"index":{"body":{"root":{"1":{"df":1,"docs":{"6":{"tf":1.0}}},"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"(":{"/"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"12":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"6":{"tf":1.7320508075688772}}}}}},"c":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"b":{"a":{"df":0,"docs":{},"r":{"a":{"df":1,"docs":{"7":{"tf":2.449489742783178}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"6":{"tf":1.0}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"6":{"tf":1.0}}}}}}}}}},"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"2":{"tf":1.0},"5":{"tf":1.0},"7":{"tf":1.0}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":2.23606797749979}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"13":{"tf":1.0},"14":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"5":{"tf":1.0},"8":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"2":{"tf":1.0},"5":{"tf":1.0},"7":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":4,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.0},"5":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"5":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":1,"docs":{"6":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"0":{"tf":1.0}}},"df":1,"docs":{"1":{"tf":1.0}}}}}},"i":{"d":{"df":1,"docs":{"14":{"tf":1.0}}},"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":2,"docs":{"4":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"0":{"tf":1.0},"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}}},"t":{";":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"&":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"2":{"tf":1.0}}}}}},"p":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":1,"docs":{"8":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.0}}}}}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"̈":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}},"ë":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"6":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}},"x":{"df":0,"docs":{},"t":{"df":5,"docs":{"0":{"tf":1.0},"10":{"tf":1.0},"11":{"tf":1.0},"2":{"tf":1.0},"9":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"w":{"df":0,"docs":{},"o":{"df":1,"docs":{"6":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"d":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"z":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"breadcrumbs":{"root":{"1":{"df":1,"docs":{"6":{"tf":1.0}}},"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"(":{"/"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":4,"docs":{"12":{"tf":1.7320508075688772},"13":{"tf":1.0},"14":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"6":{"tf":1.7320508075688772}}}}}},"c":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"b":{"a":{"df":0,"docs":{},"r":{"a":{"df":1,"docs":{"7":{"tf":2.449489742783178}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"6":{"tf":1.0}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"6":{"tf":1.0}}}}}}}}}},"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":13,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.0},"2":{"tf":1.7320508075688772},"3":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.4142135623730951},"6":{"tf":1.0},"7":{"tf":1.4142135623730951},"8":{"tf":1.0},"9":{"tf":1.0}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":2.23606797749979}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"14":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":5,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"5":{"tf":1.0},"8":{"tf":2.0},"9":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":13,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.0},"2":{"tf":1.7320508075688772},"3":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.4142135623730951},"6":{"tf":1.0},"7":{"tf":1.4142135623730951},"8":{"tf":1.0},"9":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":4,"docs":{"12":{"tf":1.7320508075688772},"13":{"tf":1.7320508075688772},"14":{"tf":1.7320508075688772},"5":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":6,"docs":{"10":{"tf":1.7320508075688772},"11":{"tf":1.7320508075688772},"5":{"tf":1.4142135623730951},"7":{"tf":1.0},"8":{"tf":2.0},"9":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":1,"docs":{"6":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"0":{"tf":1.0}}},"df":1,"docs":{"1":{"tf":1.0}}}}}},"i":{"d":{"df":1,"docs":{"14":{"tf":1.4142135623730951}}},"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":2,"docs":{"4":{"tf":1.7320508075688772},"5":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"0":{"tf":1.7320508075688772},"1":{"tf":1.0},"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}}},"t":{";":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"&":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"2":{"tf":1.0}}}}}},"p":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":1,"docs":{"8":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"̈":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}},"ë":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}}}}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}},"x":{"df":0,"docs":{},"t":{"df":5,"docs":{"0":{"tf":1.0},"10":{"tf":1.4142135623730951},"11":{"tf":1.4142135623730951},"2":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"w":{"df":0,"docs":{},"o":{"df":1,"docs":{"6":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"d":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"z":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"title":{"root":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"12":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"2":{"tf":1.0},"7":{"tf":1.0}}}}}}},"df":0,"docs":{}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"13":{"tf":1.0},"14":{"tf":1.0}}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"2":{"tf":1.0},"7":{"tf":1.0}}}}}}},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"i":{"d":{"df":1,"docs":{"14":{"tf":1.0}}},"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":1,"docs":{"4":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.0}}}}}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"6":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}},"x":{"df":0,"docs":{},"t":{"df":3,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"9":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"lang":"English","pipeline":["trimmer","stopWordFilter","stemmer"],"ref":"id","version":"0.9.5"},"results_options":{"limit_results":30,"teaser_word_count":30},"search_options":{"bool":"OR","expand":true,"fields":{"body":{"boost":1},"breadcrumbs":{"boost":1},"title":{"boost":2}}}});
\ No newline at end of file
diff --git a/tests/testsuite/search/reasonable_search_index/src/SUMMARY.md b/tests/testsuite/search/reasonable_search_index/src/SUMMARY.md
new file mode 100644
index 00000000..c4fe0838
--- /dev/null
+++ b/tests/testsuite/search/reasonable_search_index/src/SUMMARY.md
@@ -0,0 +1,10 @@
+# Summary
+
+[Introduction](intro.md)
+
+- [First Chapter](first/index.md)
+ - [Includes](first/includes.md)
+ - [Unicode](first/unicode.md)
+ - [No Headers](first/no-headers.md)
+ - [Duplicate Headers](first/duplicate-headers.md)
+ - [Heading Attributes](first/heading-attributes.md)
diff --git a/tests/testsuite/search/reasonable_search_index/src/first/duplicate-headers.md b/tests/testsuite/search/reasonable_search_index/src/first/duplicate-headers.md
new file mode 100644
index 00000000..83522b44
--- /dev/null
+++ b/tests/testsuite/search/reasonable_search_index/src/first/duplicate-headers.md
@@ -0,0 +1,9 @@
+# Duplicate headers
+
+This page validates behaviour of duplicate headers.
+
+# Header Text
+
+# Header Text
+
+# header-text
diff --git a/tests/testsuite/search/reasonable_search_index/src/first/heading-attributes.md b/tests/testsuite/search/reasonable_search_index/src/first/heading-attributes.md
new file mode 100644
index 00000000..a09a22b6
--- /dev/null
+++ b/tests/testsuite/search/reasonable_search_index/src/first/heading-attributes.md
@@ -0,0 +1,5 @@
+# Heading Attributes {#attrs}
+
+## Heading with classes {.class1 .class2}
+
+## Heading with id and classes {#both .class1 .class2}
diff --git a/tests/testsuite/search/reasonable_search_index/src/first/includes.md b/tests/testsuite/search/reasonable_search_index/src/first/includes.md
new file mode 100644
index 00000000..93ce0721
--- /dev/null
+++ b/tests/testsuite/search/reasonable_search_index/src/first/includes.md
@@ -0,0 +1,3 @@
+# Includes
+
+{{#include ../SUMMARY.md::}}
diff --git a/tests/testsuite/search/reasonable_search_index/src/first/index.md b/tests/testsuite/search/reasonable_search_index/src/first/index.md
new file mode 100644
index 00000000..200672b9
--- /dev/null
+++ b/tests/testsuite/search/reasonable_search_index/src/first/index.md
@@ -0,0 +1,5 @@
+# First Chapter
+
+more text.
+
+## Some Section
diff --git a/tests/testsuite/search/reasonable_search_index/src/first/no-headers.md b/tests/testsuite/search/reasonable_search_index/src/first/no-headers.md
new file mode 100644
index 00000000..5d799aa6
--- /dev/null
+++ b/tests/testsuite/search/reasonable_search_index/src/first/no-headers.md
@@ -0,0 +1,5 @@
+Capybara capybara capybara.
+
+Capybara capybara capybara.
+
+ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex.
diff --git a/tests/testsuite/search/reasonable_search_index/src/first/unicode.md b/tests/testsuite/search/reasonable_search_index/src/first/unicode.md
new file mode 100644
index 00000000..160cc367
--- /dev/null
+++ b/tests/testsuite/search/reasonable_search_index/src/first/unicode.md
@@ -0,0 +1,21 @@
+# Unicode stress tests
+
+Please be careful editing, this contains carefully crafted characters.
+
+Two byte character: spatiëring
+
+Combining character: spatiëring
+
+Three byte character: 书こんにちは
+
+Four byte character: 𐌀𐌁𐌂𐌃𐌄𐌅𐌆𐌇𐌈
+
+Right-to-left: مرحبا
+
+Emoticons: 🔊 😍 💜 1️⃣
+
+right-to-left mark: hello באמת!
+
+
+Zalgo: ǫ̛̖̱̗̝͈̋͒͋̏ͥͫ̒̆ͩ̏͌̾͊͐ͪ̾̚
+
diff --git a/tests/testsuite/search/reasonable_search_index/src/intro.md b/tests/testsuite/search/reasonable_search_index/src/intro.md
new file mode 100644
index 00000000..93dbcb82
--- /dev/null
+++ b/tests/testsuite/search/reasonable_search_index/src/intro.md
@@ -0,0 +1,28 @@
+# Introduction
+
+Here's some interesting text...
+
+## Sneaky
+
+
+
+I put <HTML> in here!
+
+
+
+
+Sneaky inline event .
+
+But regular inline is indexed.
From a8660048ca31887379532c17a909df0ecb4b49ff Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 09:03:25 -0700
Subject: [PATCH 44/69] Migrate search_index_hasnt_changed_accidentally to
BookTest
---
tests/rendered_output.rs | 53 +-
tests/searchindex_fixture.json | 8130 -----------------
tests/testsuite/search.rs | 10 +
.../reasonable_search_index/expected_index.js | 2 +-
4 files changed, 12 insertions(+), 8183 deletions(-)
delete mode 100644 tests/searchindex_fixture.json
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index 1418c6bc..b19a43a0 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -381,7 +381,7 @@ mod search {
use crate::dummy_book::DummyBook;
use mdbook::utils::fs::write_file;
use mdbook::MDBook;
- use std::fs::{self, File};
+ use std::fs;
use std::path::Path;
fn read_book_index(root: &Path) -> serde_json::Value {
@@ -437,57 +437,6 @@ mod search {
"[output.html.search.chapter] key `does-not-exist` does not match any chapter paths"
));
}
-
- // Setting this to `true` may cause issues with `cargo watch`,
- // since it may not finish writing the fixture before the tests
- // are run again.
- const GENERATE_FIXTURE: bool = false;
-
- 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
- // **change it back to `false`**. Include the changed `searchindex_fixture.json` in your commit.
- #[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");
- }
- }
}
#[test]
diff --git a/tests/searchindex_fixture.json b/tests/searchindex_fixture.json
deleted file mode 100644
index 3a6dd657..00000000
--- a/tests/searchindex_fixture.json
+++ /dev/null
@@ -1,8130 +0,0 @@
-{
- "doc_urls": [
- "index.html#dummy-book",
- "intro.html#introduction",
- "first/index.html#first-chapter",
- "first/index.html#some-section",
- "first/nested.html#nested-chapter",
- "first/nested.html#some-section",
- "first/nested.html#anchors-include-the-part-of-a-file-between-special-comments",
- "first/nested.html#rustdoc-include-adds-the-rest-of-the-file-as-hidden",
- "first/nested.html#rustdoc-include-works-with-anchors-too",
- "first/includes.html#includes",
- "first/includes.html#summary",
- "first/recursive.html",
- "first/markdown.html#markdown-tests",
- "first/markdown.html#tables",
- "first/markdown.html#footnotes",
- "first/markdown.html#strikethrough",
- "first/markdown.html#tasklisks",
- "first/unicode.html#unicode-stress-tests",
- "first/no-headers.html",
- "first/duplicate-headers.html#duplicate-headers",
- "first/duplicate-headers.html#header-text",
- "first/duplicate-headers.html#header-text-1",
- "first/duplicate-headers.html#header-text-2",
- "first/heading-attributes.html#attrs",
- "first/heading-attributes.html#heading-with-classes",
- "first/heading-attributes.html#both",
- "second.html#second-chapter",
- "second/nested.html#testing-relative-links-for-the-print-page",
- "second/nested.html#some-section",
- "conclusion.html#conclusion"
- ],
- "index": {
- "documentStore": {
- "docInfo": {
- "0": {
- "body": 9,
- "breadcrumbs": 4,
- "title": 2
- },
- "1": {
- "body": 3,
- "breadcrumbs": 2,
- "title": 1
- },
- "10": {
- "body": 21,
- "breadcrumbs": 4,
- "title": 1
- },
- "11": {
- "body": 44,
- "breadcrumbs": 3,
- "title": 2
- },
- "12": {
- "body": 3,
- "breadcrumbs": 5,
- "title": 2
- },
- "13": {
- "body": 4,
- "breadcrumbs": 4,
- "title": 1
- },
- "14": {
- "body": 55,
- "breadcrumbs": 4,
- "title": 1
- },
- "15": {
- "body": 2,
- "breadcrumbs": 4,
- "title": 1
- },
- "16": {
- "body": 3,
- "breadcrumbs": 4,
- "title": 1
- },
- "17": {
- "body": 29,
- "breadcrumbs": 6,
- "title": 3
- },
- "18": {
- "body": 6,
- "breadcrumbs": 3,
- "title": 2
- },
- "19": {
- "body": 5,
- "breadcrumbs": 6,
- "title": 2
- },
- "2": {
- "body": 2,
- "breadcrumbs": 4,
- "title": 2
- },
- "20": {
- "body": 0,
- "breadcrumbs": 6,
- "title": 2
- },
- "21": {
- "body": 0,
- "breadcrumbs": 6,
- "title": 2
- },
- "22": {
- "body": 0,
- "breadcrumbs": 6,
- "title": 2
- },
- "23": {
- "body": 0,
- "breadcrumbs": 6,
- "title": 2
- },
- "24": {
- "body": 0,
- "breadcrumbs": 6,
- "title": 2
- },
- "25": {
- "body": 0,
- "breadcrumbs": 7,
- "title": 3
- },
- "26": {
- "body": 20,
- "breadcrumbs": 4,
- "title": 2
- },
- "27": {
- "body": 18,
- "breadcrumbs": 9,
- "title": 5
- },
- "28": {
- "body": 0,
- "breadcrumbs": 5,
- "title": 1
- },
- "29": {
- "body": 10,
- "breadcrumbs": 2,
- "title": 1
- },
- "3": {
- "body": 0,
- "breadcrumbs": 3,
- "title": 1
- },
- "4": {
- "body": 4,
- "breadcrumbs": 6,
- "title": 2
- },
- "5": {
- "body": 1,
- "breadcrumbs": 5,
- "title": 1
- },
- "6": {
- "body": 21,
- "breadcrumbs": 11,
- "title": 7
- },
- "7": {
- "body": 6,
- "breadcrumbs": 10,
- "title": 6
- },
- "8": {
- "body": 6,
- "breadcrumbs": 8,
- "title": 4
- },
- "9": {
- "body": 0,
- "breadcrumbs": 4,
- "title": 1
- }
- },
- "docs": {
- "0": {
- "body": "This file is just here to cause the index preprocessor to run. Does a pretty good job, too.",
- "breadcrumbs": "Dummy Book » Dummy Book",
- "id": "0",
- "title": "Dummy Book"
- },
- "1": {
- "body": "Here's some interesting text...",
- "breadcrumbs": "Introduction » Introduction",
- "id": "1",
- "title": "Introduction"
- },
- "10": {
- "body": "Dummy Book Introduction First Chapter Nested Chapter Includes Recursive Markdown Unicode No Headers Duplicate Headers Heading Attributes Second Chapter Nested Chapter Conclusion",
- "breadcrumbs": "First Chapter » Includes » Summary",
- "id": "10",
- "title": "Summary"
- },
- "11": {
- "body": "Around the world, around the world Around the world, around the world Around the world, around the world Around the world, around the world Around the world, around the world Around the world, around the world Around the world, around the world Around the world, around the world Around the world, around the world Around the world, around the world Around the world, around the world",
- "breadcrumbs": "First Chapter » Recursive",
- "id": "11",
- "title": "First Chapter"
- },
- "12": {
- "body": "Tests for some markdown output.",
- "breadcrumbs": "First Chapter » Markdown » Markdown tests",
- "id": "12",
- "title": "Markdown tests"
- },
- "13": {
- "body": "foo bar baz bim",
- "breadcrumbs": "First Chapter » Markdown » Tables",
- "id": "13",
- "title": "Tables"
- },
- "14": {
- "body": "Footnote example [1] , or with a word [2] . This is a footnote. A longer footnote. With multiple lines. Link to unicode . With a reference inside. [1] There are multiple references to word [2] . Footnote without a paragraph [3] Item one Sub-item Item two Footnote with multiple paragraphs [4] This is defined before it is referred to. OneTwoThree This footnote is defined by not used. Footnote name with wacky characters [7] Testing footnote id with special characters. Testing when referring to something earlier. [5]",
- "breadcrumbs": "First Chapter » Markdown » Footnotes",
- "id": "14",
- "title": "Footnotes"
- },
- "15": {
- "body": "strikethrough example",
- "breadcrumbs": "First Chapter » Markdown » Strikethrough",
- "id": "15",
- "title": "Strikethrough"
- },
- "16": {
- "body": "Apples Broccoli Carrots",
- "breadcrumbs": "First Chapter » Markdown » Tasklisks",
- "id": "16",
- "title": "Tasklisks"
- },
- "17": {
- "body": "Please be careful editing, this contains carefully crafted characters. Two byte character: spatiëring Combining character: spatiëring Three byte character: 书こんにちは Four byte character: 𐌀𐌁𐌂𐌃𐌄𐌅𐌆𐌇𐌈 Right-to-left: مرحبا Emoticons: 🔊 😍 💜 1️⃣ right-to-left mark: hello באמת! Zalgo: ǫ̛̖̱̗̝͈̋͒͋̏ͥͫ̒̆ͩ̏͌̾͊͐ͪ̾̚",
- "breadcrumbs": "First Chapter » Unicode » Unicode stress tests",
- "id": "17",
- "title": "Unicode stress tests"
- },
- "18": {
- "body": "Capybara capybara capybara. Capybara capybara capybara. ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex.",
- "breadcrumbs": "First Chapter » No Headers",
- "id": "18",
- "title": "First Chapter"
- },
- "19": {
- "body": "This page validates behaviour of duplicate headers.",
- "breadcrumbs": "First Chapter » Duplicate Headers » Duplicate headers",
- "id": "19",
- "title": "Duplicate headers"
- },
- "2": {
- "body": "more text.",
- "breadcrumbs": "First Chapter » First Chapter",
- "id": "2",
- "title": "First Chapter"
- },
- "20": {
- "body": "",
- "breadcrumbs": "First Chapter » Duplicate Headers » Header Text",
- "id": "20",
- "title": "Header Text"
- },
- "21": {
- "body": "",
- "breadcrumbs": "First Chapter » Duplicate Headers » Header Text",
- "id": "21",
- "title": "Header Text"
- },
- "22": {
- "body": "",
- "breadcrumbs": "First Chapter » Duplicate Headers » header-text",
- "id": "22",
- "title": "header-text"
- },
- "23": {
- "body": "",
- "breadcrumbs": "First Chapter » Heading Attributes » Heading Attributes",
- "id": "23",
- "title": "Heading Attributes"
- },
- "24": {
- "body": "",
- "breadcrumbs": "First Chapter » Heading Attributes » Heading with classes",
- "id": "24",
- "title": "Heading with classes"
- },
- "25": {
- "body": "",
- "breadcrumbs": "First Chapter » Heading Attributes » Heading with id and classes",
- "id": "25",
- "title": "Heading with id and classes"
- },
- "26": {
- "body": "This makes sure you can insert runnable Rust files. fn main() { println!(\"Hello World!\");\n#\n# // You can even hide lines! :D\n# println!(\"I am hidden! Expand the code snippet to see me\");\n}",
- "breadcrumbs": "Second Chapter » Second Chapter",
- "id": "26",
- "title": "Second Chapter"
- },
- "27": {
- "body": "When we link to the first section , it should work on both the print page and the non-print page. A fragment link should work. Link outside . Some image HTML Link",
- "breadcrumbs": "Second Chapter » Nested Chapter » Testing relative links for the print page",
- "id": "27",
- "title": "Testing relative links for the print page"
- },
- "28": {
- "body": "",
- "breadcrumbs": "Second Chapter » Nested Chapter » Some section",
- "id": "28",
- "title": "Some section"
- },
- "29": {
- "body": "I put <HTML> in here! Sneaky inline event alert(\"inline\");. But regular inline is indexed.",
- "breadcrumbs": "Conclusion » Conclusion",
- "id": "29",
- "title": "Conclusion"
- },
- "3": {
- "body": "",
- "breadcrumbs": "First Chapter » Some Section",
- "id": "3",
- "title": "Some Section"
- },
- "4": {
- "body": "This file has some testable code. assert!(true);",
- "breadcrumbs": "First Chapter » Nested Chapter » Nested Chapter",
- "id": "4",
- "title": "Nested Chapter"
- },
- "5": {
- "body": "assert!(true);",
- "breadcrumbs": "First Chapter » Nested Chapter » Some Section",
- "id": "5",
- "title": "Some Section"
- },
- "6": {
- "body": "// The next line will cause a `rendered_output` test to fail if the anchor feature is broken in\n// such a way that the content between anchors isn't included.\n// unique-string-for-anchor-test\nassert!(true);",
- "breadcrumbs": "First Chapter » Nested Chapter » Anchors include the part of a file between special comments",
- "id": "6",
- "title": "Anchors include the part of a file between special comments"
- },
- "7": {
- "body": "# fn some_function() {\n# assert!(true);\n# }\n# fn main() { some_function();\n}",
- "breadcrumbs": "First Chapter » Nested Chapter » Rustdoc include adds the rest of the file as hidden",
- "id": "7",
- "title": "Rustdoc include adds the rest of the file as hidden"
- },
- "8": {
- "body": "# fn some_other_function() {\n# assert!(true);\n# }\n# fn main() { some_other_function();\n}",
- "breadcrumbs": "First Chapter » Nested Chapter » Rustdoc include works with anchors too",
- "id": "8",
- "title": "Rustdoc include works with anchors too"
- },
- "9": {
- "body": "",
- "breadcrumbs": "First Chapter » Includes » Includes",
- "id": "9",
- "title": "Includes"
- }
- },
- "length": 30,
- "save": true
- },
- "fields": [
- "title",
- "body",
- "breadcrumbs"
- ],
- "index": {
- "body": {
- "root": {
- "1": {
- "df": 2,
- "docs": {
- "14": {
- "tf": 1.4142135623730951
- },
- "17": {
- "tf": 1.0
- }
- }
- },
- "2": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.4142135623730951
- }
- }
- },
- "3": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- },
- "4": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- },
- "5": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- },
- "7": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- },
- "a": {
- "d": {
- "d": {
- "df": 1,
- "docs": {
- "7": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "t": {
- "(": {
- "\"": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "29": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- },
- "n": {
- "c": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 2,
- "docs": {
- "6": {
- "tf": 2.0
- },
- "8": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "p": {
- "df": 0,
- "docs": {},
- "p": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 1,
- "docs": {
- "16": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "n": {
- "d": {
- "df": 1,
- "docs": {
- "11": {
- "tf": 4.69041575982343
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- },
- "s": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "t": {
- "!": {
- "(": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 5,
- "docs": {
- "4": {
- "tf": 1.0
- },
- "5": {
- "tf": 1.0
- },
- "6": {
- "tf": 1.0
- },
- "7": {
- "tf": 1.0
- },
- "8": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- }
- },
- "t": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "i": {
- "b": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 2,
- "docs": {
- "10": {
- "tf": 1.0
- },
- "23": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- }
- },
- "b": {
- "a": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 1,
- "docs": {
- "13": {
- "tf": 1.0
- }
- }
- },
- "z": {
- "df": 1,
- "docs": {
- "13": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "f": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "h": {
- "a": {
- "df": 0,
- "docs": {},
- "v": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 1,
- "docs": {
- "19": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "t": {
- "df": 0,
- "docs": {},
- "w": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.4142135623730951
- }
- }
- }
- }
- }
- }
- }
- },
- "i": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 1,
- "docs": {
- "13": {
- "tf": 1.0
- }
- }
- }
- },
- "o": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "k": {
- "df": 2,
- "docs": {
- "0": {
- "tf": 1.0
- },
- "10": {
- "tf": 1.0
- }
- }
- }
- },
- "t": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 1,
- "docs": {
- "27": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "o": {
- "c": {
- "c": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 1,
- "docs": {
- "16": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {},
- "k": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- },
- "y": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.7320508075688772
- }
- }
- }
- }
- }
- },
- "c": {
- "a": {
- "df": 0,
- "docs": {},
- "p": {
- "df": 0,
- "docs": {},
- "y": {
- "b": {
- "a": {
- "df": 0,
- "docs": {},
- "r": {
- "a": {
- "df": 1,
- "docs": {
- "18": {
- "tf": 2.449489742783178
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {}
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- },
- "f": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "16": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "u": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 2,
- "docs": {
- "0": {
- "tf": 1.0
- },
- "6": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "h": {
- "a": {
- "df": 0,
- "docs": {},
- "p": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 6,
- "docs": {
- "10": {
- "tf": 2.0
- },
- "11": {
- "tf": 1.0
- },
- "18": {
- "tf": 1.0
- },
- "2": {
- "tf": 1.0
- },
- "26": {
- "tf": 1.0
- },
- "4": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "r": {
- "a": {
- "c": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 2,
- "docs": {
- "14": {
- "tf": 1.4142135623730951
- },
- "17": {
- "tf": 2.23606797749979
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {}
- }
- },
- "df": 0,
- "docs": {}
- },
- "l": {
- "a": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 2,
- "docs": {
- "24": {
- "tf": 1.0
- },
- "25": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "o": {
- "d": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 2,
- "docs": {
- "26": {
- "tf": 1.0
- },
- "4": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "m": {
- "b": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- },
- "n": {
- "c": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 2,
- "docs": {
- "10": {
- "tf": 1.0
- },
- "29": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "t": {
- "a": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- },
- "r": {
- "a": {
- "df": 0,
- "docs": {},
- "f": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "d": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- },
- "e": {
- "df": 0,
- "docs": {},
- "f": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.4142135623730951
- }
- }
- }
- }
- }
- },
- "u": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 2,
- "docs": {
- "0": {
- "tf": 1.0
- },
- "10": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "p": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "i": {
- "c": {
- "df": 2,
- "docs": {
- "10": {
- "tf": 1.0
- },
- "19": {
- "tf": 1.4142135623730951
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "e": {
- "a": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- },
- "d": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "i": {
- "c": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- },
- "v": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- },
- "t": {
- "df": 1,
- "docs": {
- "29": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "x": {
- "a": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "p": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 2,
- "docs": {
- "14": {
- "tf": 1.0
- },
- "15": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "p": {
- "a": {
- "df": 0,
- "docs": {},
- "n": {
- "d": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- },
- "f": {
- "a": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "e": {
- "a": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "i": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 5,
- "docs": {
- "0": {
- "tf": 1.0
- },
- "26": {
- "tf": 1.0
- },
- "4": {
- "tf": 1.0
- },
- "6": {
- "tf": 1.0
- },
- "7": {
- "tf": 1.0
- }
- }
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 5,
- "docs": {
- "10": {
- "tf": 1.0
- },
- "11": {
- "tf": 1.0
- },
- "18": {
- "tf": 1.0
- },
- "2": {
- "tf": 1.0
- },
- "27": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "n": {
- "df": 3,
- "docs": {
- "26": {
- "tf": 1.0
- },
- "7": {
- "tf": 1.4142135623730951
- },
- "8": {
- "tf": 1.4142135623730951
- }
- }
- },
- "o": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 1,
- "docs": {
- "13": {
- "tf": 1.0
- }
- },
- "t": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 3.0
- }
- }
- }
- }
- }
- }
- },
- "u": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "r": {
- "a": {
- "df": 0,
- "docs": {},
- "g": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "27": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "g": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "o": {
- "d": {
- "df": 1,
- "docs": {
- "0": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- },
- "h": {
- "df": 0,
- "docs": {},
- "e": {
- "a": {
- "d": {
- "df": 4,
- "docs": {
- "10": {
- "tf": 1.0
- },
- "23": {
- "tf": 1.0
- },
- "24": {
- "tf": 1.0
- },
- "25": {
- "tf": 1.0
- }
- },
- "e": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 5,
- "docs": {
- "10": {
- "tf": 1.4142135623730951
- },
- "19": {
- "tf": 1.4142135623730951
- },
- "20": {
- "tf": 1.0
- },
- "21": {
- "tf": 1.0
- },
- "22": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "e": {
- "'": {
- "df": 1,
- "docs": {
- "1": {
- "tf": 1.0
- }
- }
- },
- "df": 2,
- "docs": {
- "0": {
- "tf": 1.0
- },
- "29": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "i": {
- "d": {
- "d": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 2,
- "docs": {
- "26": {
- "tf": 1.0
- },
- "7": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "e": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "t": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 1,
- "docs": {
- "27": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "i": {
- "d": {
- "df": 2,
- "docs": {
- "14": {
- "tf": 1.0
- },
- "25": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {},
- "m": {
- "a": {
- "df": 0,
- "docs": {},
- "g": {
- "df": 1,
- "docs": {
- "27": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "n": {
- "c": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "u": {
- "d": {
- "df": 5,
- "docs": {
- "10": {
- "tf": 1.0
- },
- "6": {
- "tf": 1.4142135623730951
- },
- "7": {
- "tf": 1.0
- },
- "8": {
- "tf": 1.0
- },
- "9": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- },
- "d": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "x": {
- "df": 2,
- "docs": {
- "0": {
- "tf": 1.0
- },
- "29": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "29": {
- "tf": 1.4142135623730951
- }
- }
- }
- }
- },
- "s": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "i": {
- "d": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "t": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "1": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "o": {
- "d": {
- "df": 0,
- "docs": {},
- "u": {
- "c": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 2,
- "docs": {
- "1": {
- "tf": 1.0
- },
- "10": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- },
- "s": {
- "df": 0,
- "docs": {},
- "n": {
- "'": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "t": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.7320508075688772
- }
- }
- }
- }
- }
- },
- "j": {
- "df": 0,
- "docs": {},
- "o": {
- "b": {
- "df": 1,
- "docs": {
- "0": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "l": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "f": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.4142135623730951
- }
- }
- }
- }
- },
- "i": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 3,
- "docs": {
- "14": {
- "tf": 1.0
- },
- "26": {
- "tf": 1.0
- },
- "6": {
- "tf": 1.0
- }
- }
- },
- "k": {
- "df": 2,
- "docs": {
- "14": {
- "tf": 1.0
- },
- "27": {
- "tf": 2.23606797749979
- }
- }
- }
- }
- },
- "o": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 0,
- "docs": {},
- "g": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- },
- "t": {
- ";": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "l": {
- "&": {
- "df": 0,
- "docs": {},
- "g": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "29": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "m": {
- "a": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 3,
- "docs": {
- "26": {
- "tf": 1.0
- },
- "7": {
- "tf": 1.0
- },
- "8": {
- "tf": 1.0
- }
- }
- }
- },
- "k": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- }
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "k": {
- "d": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "w": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 2,
- "docs": {
- "10": {
- "tf": 1.0
- },
- "12": {
- "tf": 1.4142135623730951
- }
- }
- }
- }
- }
- },
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 1,
- "docs": {
- "2": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "u": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "p": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.7320508075688772
- }
- }
- }
- }
- }
- }
- }
- }
- },
- "n": {
- "a": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 2,
- "docs": {
- "10": {
- "tf": 1.4142135623730951
- },
- "4": {
- "tf": 1.0
- }
- }
- }
- },
- "x": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "o": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "27": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "o": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- },
- "e": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "w": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- },
- "u": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "p": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "12": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "s": {
- "df": 0,
- "docs": {},
- "i": {
- "d": {
- "df": 1,
- "docs": {
- "27": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- }
- },
- "p": {
- "a": {
- "df": 0,
- "docs": {},
- "g": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 2,
- "docs": {
- "19": {
- "tf": 1.0
- },
- "27": {
- "tf": 1.7320508075688772
- }
- }
- }
- },
- "r": {
- "a": {
- "df": 0,
- "docs": {},
- "g": {
- "df": 0,
- "docs": {},
- "r": {
- "a": {
- "df": 0,
- "docs": {},
- "p": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.4142135623730951
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- },
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "e": {
- "a": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "p": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "o": {
- "c": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 1,
- "docs": {
- "0": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- },
- "t": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 1,
- "docs": {
- "0": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "i": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "27": {
- "tf": 1.7320508075688772
- }
- },
- "l": {
- "df": 0,
- "docs": {},
- "n": {
- "!": {
- "(": {
- "\"": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- },
- "i": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- }
- }
- },
- "u": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "29": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "e": {
- "c": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 1,
- "docs": {
- "10": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "f": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 2.0
- }
- }
- }
- }
- },
- "g": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "l": {
- "a": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 1,
- "docs": {
- "29": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- },
- "l": {
- "df": 1,
- "docs": {
- "27": {
- "tf": 1.0
- }
- }
- },
- "n": {
- "d": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "e": {
- "d": {
- "_": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "p": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "s": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "7": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "i": {
- "df": 0,
- "docs": {},
- "g": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.4142135623730951
- }
- }
- }
- }
- }
- },
- "u": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "0": {
- "tf": 1.0
- }
- },
- "n": {
- "a": {
- "b": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {}
- }
- },
- "s": {
- "df": 0,
- "docs": {},
- "t": {
- "d": {
- "df": 0,
- "docs": {},
- "o": {
- "c": {
- "df": 2,
- "docs": {
- "7": {
- "tf": 1.0
- },
- "8": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "s": {
- "df": 0,
- "docs": {},
- "e": {
- "c": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "n": {
- "d": {
- "df": 2,
- "docs": {
- "10": {
- "tf": 1.0
- },
- "26": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "t": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 4,
- "docs": {
- "27": {
- "tf": 1.0
- },
- "28": {
- "tf": 1.0
- },
- "3": {
- "tf": 1.0
- },
- "5": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "e": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- }
- }
- },
- "n": {
- "df": 0,
- "docs": {},
- "e": {
- "a": {
- "df": 0,
- "docs": {},
- "k": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 1,
- "docs": {
- "29": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "i": {
- "df": 0,
- "docs": {},
- "p": {
- "df": 0,
- "docs": {},
- "p": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- },
- "o": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "e": {
- "_": {
- "df": 0,
- "docs": {},
- "f": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "n": {
- "c": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "7": {
- "tf": 1.4142135623730951
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- },
- "o": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "r": {
- "_": {
- "df": 0,
- "docs": {},
- "f": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "n": {
- "c": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "8": {
- "tf": 1.4142135623730951
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- },
- "p": {
- "a": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "̈": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "ë": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "e": {
- "c": {
- "df": 0,
- "docs": {},
- "i": {
- "a": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 2,
- "docs": {
- "14": {
- "tf": 1.0
- },
- "6": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "t": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "i": {
- "df": 0,
- "docs": {},
- "k": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "g": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 1,
- "docs": {
- "15": {
- "tf": 1.4142135623730951
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- },
- "n": {
- "df": 0,
- "docs": {},
- "g": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- },
- "u": {
- "b": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- },
- "c": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "m": {
- "a": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 1,
- "docs": {
- "10": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "t": {
- "a": {
- "b": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 1,
- "docs": {
- "13": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "k": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "k": {
- "df": 1,
- "docs": {
- "16": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "t": {
- "a": {
- "b": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 1,
- "docs": {
- "4": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 5,
- "docs": {
- "12": {
- "tf": 1.4142135623730951
- },
- "14": {
- "tf": 1.4142135623730951
- },
- "17": {
- "tf": 1.0
- },
- "27": {
- "tf": 1.0
- },
- "6": {
- "tf": 1.4142135623730951
- }
- }
- }
- },
- "x": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 5,
- "docs": {
- "1": {
- "tf": 1.0
- },
- "2": {
- "tf": 1.0
- },
- "20": {
- "tf": 1.0
- },
- "21": {
- "tf": 1.0
- },
- "22": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "h": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "w": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 2,
- "docs": {
- "14": {
- "tf": 1.0
- },
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "u": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 0,
- "docs": {},
- "i": {
- "c": {
- "df": 0,
- "docs": {},
- "o": {
- "d": {
- "df": 3,
- "docs": {
- "10": {
- "tf": 1.0
- },
- "14": {
- "tf": 1.0
- },
- "17": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "df": 0,
- "docs": {},
- "q": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "s": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- }
- },
- "v": {
- "a": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "i": {
- "d": {
- "df": 1,
- "docs": {
- "19": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "w": {
- "a": {
- "c": {
- "df": 0,
- "docs": {},
- "k": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "y": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- },
- "o": {
- "df": 0,
- "docs": {},
- "r": {
- "d": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.4142135623730951
- }
- }
- },
- "df": 0,
- "docs": {},
- "k": {
- "df": 2,
- "docs": {
- "27": {
- "tf": 1.4142135623730951
- },
- "8": {
- "tf": 1.0
- }
- }
- },
- "l": {
- "d": {
- "df": 2,
- "docs": {
- "11": {
- "tf": 4.69041575982343
- },
- "26": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- },
- "z": {
- "a": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "g": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- },
- "breadcrumbs": {
- "root": {
- "1": {
- "df": 2,
- "docs": {
- "14": {
- "tf": 1.4142135623730951
- },
- "17": {
- "tf": 1.0
- }
- }
- },
- "2": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.4142135623730951
- }
- }
- },
- "3": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- },
- "4": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- },
- "5": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- },
- "7": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- },
- "a": {
- "d": {
- "d": {
- "df": 1,
- "docs": {
- "7": {
- "tf": 1.4142135623730951
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "t": {
- "(": {
- "\"": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "29": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- },
- "n": {
- "c": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 2,
- "docs": {
- "6": {
- "tf": 2.23606797749979
- },
- "8": {
- "tf": 1.4142135623730951
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "p": {
- "df": 0,
- "docs": {},
- "p": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 1,
- "docs": {
- "16": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "n": {
- "d": {
- "df": 1,
- "docs": {
- "11": {
- "tf": 4.69041575982343
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- },
- "s": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "t": {
- "!": {
- "(": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 5,
- "docs": {
- "4": {
- "tf": 1.0
- },
- "5": {
- "tf": 1.0
- },
- "6": {
- "tf": 1.0
- },
- "7": {
- "tf": 1.0
- },
- "8": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- }
- },
- "t": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "i": {
- "b": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 4,
- "docs": {
- "10": {
- "tf": 1.0
- },
- "23": {
- "tf": 1.7320508075688772
- },
- "24": {
- "tf": 1.0
- },
- "25": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- }
- },
- "b": {
- "a": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 1,
- "docs": {
- "13": {
- "tf": 1.0
- }
- }
- },
- "z": {
- "df": 1,
- "docs": {
- "13": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "f": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "h": {
- "a": {
- "df": 0,
- "docs": {},
- "v": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 1,
- "docs": {
- "19": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "t": {
- "df": 0,
- "docs": {},
- "w": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.7320508075688772
- }
- }
- }
- }
- }
- }
- }
- },
- "i": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 1,
- "docs": {
- "13": {
- "tf": 1.0
- }
- }
- }
- },
- "o": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "k": {
- "df": 2,
- "docs": {
- "0": {
- "tf": 1.7320508075688772
- },
- "10": {
- "tf": 1.0
- }
- }
- }
- },
- "t": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 1,
- "docs": {
- "27": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "o": {
- "c": {
- "c": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 1,
- "docs": {
- "16": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {},
- "k": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- },
- "y": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.7320508075688772
- }
- }
- }
- }
- }
- },
- "c": {
- "a": {
- "df": 0,
- "docs": {},
- "p": {
- "df": 0,
- "docs": {},
- "y": {
- "b": {
- "a": {
- "df": 0,
- "docs": {},
- "r": {
- "a": {
- "df": 1,
- "docs": {
- "18": {
- "tf": 2.449489742783178
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {}
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- },
- "f": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "16": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "u": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 2,
- "docs": {
- "0": {
- "tf": 1.0
- },
- "6": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "h": {
- "a": {
- "df": 0,
- "docs": {},
- "p": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 27,
- "docs": {
- "10": {
- "tf": 2.23606797749979
- },
- "11": {
- "tf": 1.4142135623730951
- },
- "12": {
- "tf": 1.0
- },
- "13": {
- "tf": 1.0
- },
- "14": {
- "tf": 1.0
- },
- "15": {
- "tf": 1.0
- },
- "16": {
- "tf": 1.0
- },
- "17": {
- "tf": 1.0
- },
- "18": {
- "tf": 1.4142135623730951
- },
- "19": {
- "tf": 1.0
- },
- "2": {
- "tf": 1.7320508075688772
- },
- "20": {
- "tf": 1.0
- },
- "21": {
- "tf": 1.0
- },
- "22": {
- "tf": 1.0
- },
- "23": {
- "tf": 1.0
- },
- "24": {
- "tf": 1.0
- },
- "25": {
- "tf": 1.0
- },
- "26": {
- "tf": 1.7320508075688772
- },
- "27": {
- "tf": 1.4142135623730951
- },
- "28": {
- "tf": 1.4142135623730951
- },
- "3": {
- "tf": 1.0
- },
- "4": {
- "tf": 2.0
- },
- "5": {
- "tf": 1.4142135623730951
- },
- "6": {
- "tf": 1.4142135623730951
- },
- "7": {
- "tf": 1.4142135623730951
- },
- "8": {
- "tf": 1.4142135623730951
- },
- "9": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "r": {
- "a": {
- "c": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 2,
- "docs": {
- "14": {
- "tf": 1.4142135623730951
- },
- "17": {
- "tf": 2.23606797749979
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {}
- }
- },
- "df": 0,
- "docs": {}
- },
- "l": {
- "a": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 2,
- "docs": {
- "24": {
- "tf": 1.4142135623730951
- },
- "25": {
- "tf": 1.4142135623730951
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "o": {
- "d": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 2,
- "docs": {
- "26": {
- "tf": 1.0
- },
- "4": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "m": {
- "b": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.4142135623730951
- }
- }
- }
- }
- }
- }
- },
- "n": {
- "c": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 2,
- "docs": {
- "10": {
- "tf": 1.0
- },
- "29": {
- "tf": 1.7320508075688772
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "t": {
- "a": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- },
- "r": {
- "a": {
- "df": 0,
- "docs": {},
- "f": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "d": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- },
- "e": {
- "df": 0,
- "docs": {},
- "f": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.4142135623730951
- }
- }
- }
- }
- }
- },
- "u": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 2,
- "docs": {
- "0": {
- "tf": 1.7320508075688772
- },
- "10": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "p": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "i": {
- "c": {
- "df": 5,
- "docs": {
- "10": {
- "tf": 1.0
- },
- "19": {
- "tf": 2.0
- },
- "20": {
- "tf": 1.0
- },
- "21": {
- "tf": 1.0
- },
- "22": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "e": {
- "a": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- },
- "d": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "i": {
- "c": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- },
- "v": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- },
- "t": {
- "df": 1,
- "docs": {
- "29": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "x": {
- "a": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "p": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 2,
- "docs": {
- "14": {
- "tf": 1.0
- },
- "15": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "p": {
- "a": {
- "df": 0,
- "docs": {},
- "n": {
- "d": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- },
- "f": {
- "a": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "e": {
- "a": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "i": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 5,
- "docs": {
- "0": {
- "tf": 1.0
- },
- "26": {
- "tf": 1.0
- },
- "4": {
- "tf": 1.0
- },
- "6": {
- "tf": 1.4142135623730951
- },
- "7": {
- "tf": 1.4142135623730951
- }
- }
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 25,
- "docs": {
- "10": {
- "tf": 1.4142135623730951
- },
- "11": {
- "tf": 1.4142135623730951
- },
- "12": {
- "tf": 1.0
- },
- "13": {
- "tf": 1.0
- },
- "14": {
- "tf": 1.0
- },
- "15": {
- "tf": 1.0
- },
- "16": {
- "tf": 1.0
- },
- "17": {
- "tf": 1.0
- },
- "18": {
- "tf": 1.4142135623730951
- },
- "19": {
- "tf": 1.0
- },
- "2": {
- "tf": 1.7320508075688772
- },
- "20": {
- "tf": 1.0
- },
- "21": {
- "tf": 1.0
- },
- "22": {
- "tf": 1.0
- },
- "23": {
- "tf": 1.0
- },
- "24": {
- "tf": 1.0
- },
- "25": {
- "tf": 1.0
- },
- "27": {
- "tf": 1.0
- },
- "3": {
- "tf": 1.0
- },
- "4": {
- "tf": 1.0
- },
- "5": {
- "tf": 1.0
- },
- "6": {
- "tf": 1.0
- },
- "7": {
- "tf": 1.0
- },
- "8": {
- "tf": 1.0
- },
- "9": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "n": {
- "df": 3,
- "docs": {
- "26": {
- "tf": 1.0
- },
- "7": {
- "tf": 1.4142135623730951
- },
- "8": {
- "tf": 1.4142135623730951
- }
- }
- },
- "o": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 1,
- "docs": {
- "13": {
- "tf": 1.0
- }
- },
- "t": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 3.1622776601683795
- }
- }
- }
- }
- }
- }
- },
- "u": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "r": {
- "a": {
- "df": 0,
- "docs": {},
- "g": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "27": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "g": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "o": {
- "d": {
- "df": 1,
- "docs": {
- "0": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- },
- "h": {
- "df": 0,
- "docs": {},
- "e": {
- "a": {
- "d": {
- "df": 4,
- "docs": {
- "10": {
- "tf": 1.0
- },
- "23": {
- "tf": 1.7320508075688772
- },
- "24": {
- "tf": 1.7320508075688772
- },
- "25": {
- "tf": 1.7320508075688772
- }
- },
- "e": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 6,
- "docs": {
- "10": {
- "tf": 1.4142135623730951
- },
- "18": {
- "tf": 1.0
- },
- "19": {
- "tf": 2.0
- },
- "20": {
- "tf": 1.7320508075688772
- },
- "21": {
- "tf": 1.7320508075688772
- },
- "22": {
- "tf": 1.7320508075688772
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "e": {
- "'": {
- "df": 1,
- "docs": {
- "1": {
- "tf": 1.0
- }
- }
- },
- "df": 2,
- "docs": {
- "0": {
- "tf": 1.0
- },
- "29": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "i": {
- "d": {
- "d": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 2,
- "docs": {
- "26": {
- "tf": 1.0
- },
- "7": {
- "tf": 1.4142135623730951
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "e": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "t": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 1,
- "docs": {
- "27": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "i": {
- "d": {
- "df": 2,
- "docs": {
- "14": {
- "tf": 1.0
- },
- "25": {
- "tf": 1.4142135623730951
- }
- }
- },
- "df": 0,
- "docs": {},
- "m": {
- "a": {
- "df": 0,
- "docs": {},
- "g": {
- "df": 1,
- "docs": {
- "27": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "n": {
- "c": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "u": {
- "d": {
- "df": 5,
- "docs": {
- "10": {
- "tf": 1.4142135623730951
- },
- "6": {
- "tf": 1.7320508075688772
- },
- "7": {
- "tf": 1.4142135623730951
- },
- "8": {
- "tf": 1.4142135623730951
- },
- "9": {
- "tf": 1.7320508075688772
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- },
- "d": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "x": {
- "df": 2,
- "docs": {
- "0": {
- "tf": 1.0
- },
- "29": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "29": {
- "tf": 1.4142135623730951
- }
- }
- }
- }
- },
- "s": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "i": {
- "d": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "t": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "1": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "o": {
- "d": {
- "df": 0,
- "docs": {},
- "u": {
- "c": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 2,
- "docs": {
- "1": {
- "tf": 1.7320508075688772
- },
- "10": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- },
- "s": {
- "df": 0,
- "docs": {},
- "n": {
- "'": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "t": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.7320508075688772
- }
- }
- }
- }
- }
- },
- "j": {
- "df": 0,
- "docs": {},
- "o": {
- "b": {
- "df": 1,
- "docs": {
- "0": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "l": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "f": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.4142135623730951
- }
- }
- }
- }
- },
- "i": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 3,
- "docs": {
- "14": {
- "tf": 1.0
- },
- "26": {
- "tf": 1.0
- },
- "6": {
- "tf": 1.0
- }
- }
- },
- "k": {
- "df": 2,
- "docs": {
- "14": {
- "tf": 1.0
- },
- "27": {
- "tf": 2.449489742783178
- }
- }
- }
- }
- },
- "o": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 0,
- "docs": {},
- "g": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- },
- "t": {
- ";": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "l": {
- "&": {
- "df": 0,
- "docs": {},
- "g": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "29": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "m": {
- "a": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 3,
- "docs": {
- "26": {
- "tf": 1.0
- },
- "7": {
- "tf": 1.0
- },
- "8": {
- "tf": 1.0
- }
- }
- }
- },
- "k": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- }
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "k": {
- "d": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "w": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 6,
- "docs": {
- "10": {
- "tf": 1.0
- },
- "12": {
- "tf": 2.0
- },
- "13": {
- "tf": 1.0
- },
- "14": {
- "tf": 1.0
- },
- "15": {
- "tf": 1.0
- },
- "16": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 1,
- "docs": {
- "2": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "u": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "p": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.7320508075688772
- }
- }
- }
- }
- }
- }
- }
- }
- },
- "n": {
- "a": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 8,
- "docs": {
- "10": {
- "tf": 1.4142135623730951
- },
- "27": {
- "tf": 1.0
- },
- "28": {
- "tf": 1.0
- },
- "4": {
- "tf": 1.7320508075688772
- },
- "5": {
- "tf": 1.0
- },
- "6": {
- "tf": 1.0
- },
- "7": {
- "tf": 1.0
- },
- "8": {
- "tf": 1.0
- }
- }
- }
- },
- "x": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "o": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "27": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "o": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- },
- "e": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "w": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- },
- "u": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "p": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "12": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "s": {
- "df": 0,
- "docs": {},
- "i": {
- "d": {
- "df": 1,
- "docs": {
- "27": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- }
- },
- "p": {
- "a": {
- "df": 0,
- "docs": {},
- "g": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 2,
- "docs": {
- "19": {
- "tf": 1.0
- },
- "27": {
- "tf": 2.0
- }
- }
- }
- },
- "r": {
- "a": {
- "df": 0,
- "docs": {},
- "g": {
- "df": 0,
- "docs": {},
- "r": {
- "a": {
- "df": 0,
- "docs": {},
- "p": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.4142135623730951
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- },
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.4142135623730951
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "e": {
- "a": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "p": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "o": {
- "c": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 1,
- "docs": {
- "0": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- },
- "t": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 1,
- "docs": {
- "0": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "i": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "27": {
- "tf": 2.0
- }
- },
- "l": {
- "df": 0,
- "docs": {},
- "n": {
- "!": {
- "(": {
- "\"": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- },
- "i": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- }
- }
- },
- "u": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "29": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "e": {
- "c": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 2,
- "docs": {
- "10": {
- "tf": 1.0
- },
- "11": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "f": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 2.0
- }
- }
- }
- }
- },
- "g": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "l": {
- "a": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 1,
- "docs": {
- "29": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- },
- "l": {
- "df": 1,
- "docs": {
- "27": {
- "tf": 1.4142135623730951
- }
- }
- },
- "n": {
- "d": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "e": {
- "d": {
- "_": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "p": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "s": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "7": {
- "tf": 1.4142135623730951
- }
- }
- }
- }
- },
- "i": {
- "df": 0,
- "docs": {},
- "g": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.4142135623730951
- }
- }
- }
- }
- }
- },
- "u": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "0": {
- "tf": 1.0
- }
- },
- "n": {
- "a": {
- "b": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {}
- }
- },
- "s": {
- "df": 0,
- "docs": {},
- "t": {
- "d": {
- "df": 0,
- "docs": {},
- "o": {
- "c": {
- "df": 2,
- "docs": {
- "7": {
- "tf": 1.4142135623730951
- },
- "8": {
- "tf": 1.4142135623730951
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "s": {
- "df": 0,
- "docs": {},
- "e": {
- "c": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "n": {
- "d": {
- "df": 4,
- "docs": {
- "10": {
- "tf": 1.0
- },
- "26": {
- "tf": 1.7320508075688772
- },
- "27": {
- "tf": 1.0
- },
- "28": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "t": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 4,
- "docs": {
- "27": {
- "tf": 1.0
- },
- "28": {
- "tf": 1.4142135623730951
- },
- "3": {
- "tf": 1.4142135623730951
- },
- "5": {
- "tf": 1.4142135623730951
- }
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "e": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- }
- }
- },
- "n": {
- "df": 0,
- "docs": {},
- "e": {
- "a": {
- "df": 0,
- "docs": {},
- "k": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 1,
- "docs": {
- "29": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "i": {
- "df": 0,
- "docs": {},
- "p": {
- "df": 0,
- "docs": {},
- "p": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- },
- "o": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "e": {
- "_": {
- "df": 0,
- "docs": {},
- "f": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "n": {
- "c": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "7": {
- "tf": 1.4142135623730951
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- },
- "o": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "r": {
- "_": {
- "df": 0,
- "docs": {},
- "f": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "n": {
- "c": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "8": {
- "tf": 1.4142135623730951
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- },
- "p": {
- "a": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "̈": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "ë": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "e": {
- "c": {
- "df": 0,
- "docs": {},
- "i": {
- "a": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 2,
- "docs": {
- "14": {
- "tf": 1.0
- },
- "6": {
- "tf": 1.4142135623730951
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "t": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.4142135623730951
- }
- }
- }
- }
- },
- "i": {
- "df": 0,
- "docs": {},
- "k": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "g": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 1,
- "docs": {
- "15": {
- "tf": 1.7320508075688772
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- },
- "n": {
- "df": 0,
- "docs": {},
- "g": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- },
- "u": {
- "b": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- },
- "c": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "m": {
- "a": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 1,
- "docs": {
- "10": {
- "tf": 1.4142135623730951
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "t": {
- "a": {
- "b": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 1,
- "docs": {
- "13": {
- "tf": 1.4142135623730951
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "k": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "k": {
- "df": 1,
- "docs": {
- "16": {
- "tf": 1.4142135623730951
- }
- }
- }
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "t": {
- "a": {
- "b": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 1,
- "docs": {
- "4": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 5,
- "docs": {
- "12": {
- "tf": 1.7320508075688772
- },
- "14": {
- "tf": 1.4142135623730951
- },
- "17": {
- "tf": 1.4142135623730951
- },
- "27": {
- "tf": 1.4142135623730951
- },
- "6": {
- "tf": 1.4142135623730951
- }
- }
- }
- },
- "x": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 5,
- "docs": {
- "1": {
- "tf": 1.0
- },
- "2": {
- "tf": 1.0
- },
- "20": {
- "tf": 1.4142135623730951
- },
- "21": {
- "tf": 1.4142135623730951
- },
- "22": {
- "tf": 1.4142135623730951
- }
- }
- }
- }
- },
- "h": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "w": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 2,
- "docs": {
- "14": {
- "tf": 1.0
- },
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "u": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 0,
- "docs": {},
- "i": {
- "c": {
- "df": 0,
- "docs": {},
- "o": {
- "d": {
- "df": 3,
- "docs": {
- "10": {
- "tf": 1.0
- },
- "14": {
- "tf": 1.0
- },
- "17": {
- "tf": 1.7320508075688772
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "df": 0,
- "docs": {},
- "q": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "s": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- }
- },
- "v": {
- "a": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "i": {
- "d": {
- "df": 1,
- "docs": {
- "19": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "w": {
- "a": {
- "c": {
- "df": 0,
- "docs": {},
- "k": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "y": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- },
- "o": {
- "df": 0,
- "docs": {},
- "r": {
- "d": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.4142135623730951
- }
- }
- },
- "df": 0,
- "docs": {},
- "k": {
- "df": 2,
- "docs": {
- "27": {
- "tf": 1.4142135623730951
- },
- "8": {
- "tf": 1.4142135623730951
- }
- }
- },
- "l": {
- "d": {
- "df": 2,
- "docs": {
- "11": {
- "tf": 4.69041575982343
- },
- "26": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- },
- "z": {
- "a": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "g": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- },
- "title": {
- "root": {
- "a": {
- "d": {
- "d": {
- "df": 1,
- "docs": {
- "7": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {},
- "n": {
- "c": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 2,
- "docs": {
- "6": {
- "tf": 1.0
- },
- "8": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "t": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "i": {
- "b": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "23": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- }
- },
- "b": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "w": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- },
- "o": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "k": {
- "df": 1,
- "docs": {
- "0": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "c": {
- "df": 0,
- "docs": {},
- "h": {
- "a": {
- "df": 0,
- "docs": {},
- "p": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 5,
- "docs": {
- "11": {
- "tf": 1.0
- },
- "18": {
- "tf": 1.0
- },
- "2": {
- "tf": 1.0
- },
- "26": {
- "tf": 1.0
- },
- "4": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "l": {
- "a": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 2,
- "docs": {
- "24": {
- "tf": 1.0
- },
- "25": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "o": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- },
- "n": {
- "c": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 1,
- "docs": {
- "29": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- },
- "d": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 1,
- "docs": {
- "0": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "p": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "i": {
- "c": {
- "df": 1,
- "docs": {
- "19": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "f": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 2,
- "docs": {
- "6": {
- "tf": 1.0
- },
- "7": {
- "tf": 1.0
- }
- }
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 3,
- "docs": {
- "11": {
- "tf": 1.0
- },
- "18": {
- "tf": 1.0
- },
- "2": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "o": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "14": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- }
- },
- "h": {
- "df": 0,
- "docs": {},
- "e": {
- "a": {
- "d": {
- "df": 3,
- "docs": {
- "23": {
- "tf": 1.0
- },
- "24": {
- "tf": 1.0
- },
- "25": {
- "tf": 1.0
- }
- },
- "e": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 4,
- "docs": {
- "19": {
- "tf": 1.0
- },
- "20": {
- "tf": 1.0
- },
- "21": {
- "tf": 1.0
- },
- "22": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {}
- },
- "i": {
- "d": {
- "d": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "7": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "df": 0,
- "docs": {}
- }
- },
- "i": {
- "d": {
- "df": 1,
- "docs": {
- "25": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {},
- "n": {
- "c": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "u": {
- "d": {
- "df": 4,
- "docs": {
- "6": {
- "tf": 1.0
- },
- "7": {
- "tf": 1.0
- },
- "8": {
- "tf": 1.0
- },
- "9": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- },
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "o": {
- "d": {
- "df": 0,
- "docs": {},
- "u": {
- "c": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "1": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- }
- },
- "l": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 0,
- "docs": {},
- "k": {
- "df": 1,
- "docs": {
- "27": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "m": {
- "a": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "k": {
- "d": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "w": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 1,
- "docs": {
- "12": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "n": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "4": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "p": {
- "a": {
- "df": 0,
- "docs": {},
- "g": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 1,
- "docs": {
- "27": {
- "tf": 1.0
- }
- }
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "27": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- },
- "r": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 1,
- "docs": {
- "27": {
- "tf": 1.0
- }
- }
- },
- "s": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 1,
- "docs": {
- "7": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "u": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "t": {
- "d": {
- "df": 0,
- "docs": {},
- "o": {
- "c": {
- "df": 2,
- "docs": {
- "7": {
- "tf": 1.0
- },
- "8": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- },
- "s": {
- "df": 0,
- "docs": {},
- "e": {
- "c": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "n": {
- "d": {
- "df": 1,
- "docs": {
- "26": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "t": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 3,
- "docs": {
- "28": {
- "tf": 1.0
- },
- "3": {
- "tf": 1.0
- },
- "5": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- },
- "p": {
- "df": 0,
- "docs": {},
- "e": {
- "c": {
- "df": 0,
- "docs": {},
- "i": {
- "a": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 1,
- "docs": {
- "6": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "t": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "i": {
- "df": 0,
- "docs": {},
- "k": {
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "u": {
- "df": 0,
- "docs": {},
- "g": {
- "df": 0,
- "docs": {},
- "h": {
- "df": 1,
- "docs": {
- "15": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- },
- "u": {
- "df": 0,
- "docs": {},
- "m": {
- "df": 0,
- "docs": {},
- "m": {
- "a": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 1,
- "docs": {
- "10": {
- "tf": 1.0
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- }
- },
- "t": {
- "a": {
- "b": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 1,
- "docs": {
- "13": {
- "tf": 1.0
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "k": {
- "df": 0,
- "docs": {},
- "l": {
- "df": 0,
- "docs": {},
- "i": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "k": {
- "df": 1,
- "docs": {
- "16": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- }
- },
- "df": 0,
- "docs": {},
- "e": {
- "df": 0,
- "docs": {},
- "s": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 3,
- "docs": {
- "12": {
- "tf": 1.0
- },
- "17": {
- "tf": 1.0
- },
- "27": {
- "tf": 1.0
- }
- }
- }
- },
- "x": {
- "df": 0,
- "docs": {},
- "t": {
- "df": 3,
- "docs": {
- "20": {
- "tf": 1.0
- },
- "21": {
- "tf": 1.0
- },
- "22": {
- "tf": 1.0
- }
- }
- }
- }
- }
- },
- "u": {
- "df": 0,
- "docs": {},
- "n": {
- "df": 0,
- "docs": {},
- "i": {
- "c": {
- "df": 0,
- "docs": {},
- "o": {
- "d": {
- "df": 1,
- "docs": {
- "17": {
- "tf": 1.0
- }
- }
- },
- "df": 0,
- "docs": {}
- }
- },
- "df": 0,
- "docs": {}
- }
- }
- },
- "w": {
- "df": 0,
- "docs": {},
- "o": {
- "df": 0,
- "docs": {},
- "r": {
- "df": 0,
- "docs": {},
- "k": {
- "df": 1,
- "docs": {
- "8": {
- "tf": 1.0
- }
- }
- }
- }
- }
- }
- }
- }
- },
- "lang": "English",
- "pipeline": [
- "trimmer",
- "stopWordFilter",
- "stemmer"
- ],
- "ref": "id",
- "version": "0.9.5"
- },
- "results_options": {
- "limit_results": 30,
- "teaser_word_count": 30
- },
- "search_options": {
- "bool": "OR",
- "expand": true,
- "fields": {
- "body": {
- "boost": 1
- },
- "breadcrumbs": {
- "boost": 1
- },
- "title": {
- "boost": 2
- }
- }
- }
-}
\ No newline at end of file
diff --git a/tests/testsuite/search.rs b/tests/testsuite/search.rs
index 2220adc8..244532e1 100644
--- a/tests/testsuite/search.rs
+++ b/tests/testsuite/search.rs
@@ -1,6 +1,7 @@
//! Tests for search support.
use crate::prelude::*;
+use snapbox::file;
use std::path::Path;
fn read_book_index(root: &Path) -> serde_json::Value {
@@ -76,3 +77,12 @@ fn reasonable_search_index() {
"First Chapter » Heading Attributes » Heading with id and classes"
);
}
+
+// This test is here to catch any unexpected changes to the search index.
+#[test]
+fn search_index_hasnt_changed_accidentally() {
+ BookTest::from_dir("search/reasonable_search_index").check_file(
+ "book/searchindex.js",
+ file!["search/reasonable_search_index/expected_index.js"],
+ );
+}
diff --git a/tests/testsuite/search/reasonable_search_index/expected_index.js b/tests/testsuite/search/reasonable_search_index/expected_index.js
index 776cb46d..a700ee24 100644
--- a/tests/testsuite/search/reasonable_search_index/expected_index.js
+++ b/tests/testsuite/search/reasonable_search_index/expected_index.js
@@ -1 +1 @@
-Object.assign(window.search, {"doc_urls":["intro.html#introduction","intro.html#sneaky","first/index.html#first-chapter","first/index.html#some-section","first/includes.html#includes","first/includes.html#summary","first/unicode.html#unicode-stress-tests","first/no-headers.html","first/duplicate-headers.html#duplicate-headers","first/duplicate-headers.html#header-text","first/duplicate-headers.html#header-text-1","first/duplicate-headers.html#header-text-2","first/heading-attributes.html#attrs","first/heading-attributes.html#heading-with-classes","first/heading-attributes.html#both"],"index":{"documentStore":{"docInfo":{"0":{"body":3,"breadcrumbs":2,"title":1},"1":{"body":10,"breadcrumbs":2,"title":1},"10":{"body":0,"breadcrumbs":6,"title":2},"11":{"body":0,"breadcrumbs":6,"title":2},"12":{"body":0,"breadcrumbs":6,"title":2},"13":{"body":0,"breadcrumbs":6,"title":2},"14":{"body":0,"breadcrumbs":7,"title":3},"2":{"body":2,"breadcrumbs":4,"title":2},"3":{"body":0,"breadcrumbs":3,"title":1},"4":{"body":0,"breadcrumbs":4,"title":1},"5":{"body":10,"breadcrumbs":4,"title":1},"6":{"body":29,"breadcrumbs":6,"title":3},"7":{"body":6,"breadcrumbs":3,"title":2},"8":{"body":5,"breadcrumbs":6,"title":2},"9":{"body":0,"breadcrumbs":6,"title":2}},"docs":{"0":{"body":"Here's some interesting text...","breadcrumbs":"Introduction » Introduction","id":"0","title":"Introduction"},"1":{"body":"I put <HTML> in here! Sneaky inline event alert(/"inline/");. But regular inline is indexed.","breadcrumbs":"Introduction » Sneaky","id":"1","title":"Sneaky"},"10":{"body":"","breadcrumbs":"First Chapter » Duplicate Headers » Header Text","id":"10","title":"Header Text"},"11":{"body":"","breadcrumbs":"First Chapter » Duplicate Headers » header-text","id":"11","title":"header-text"},"12":{"body":"","breadcrumbs":"First Chapter » Heading Attributes » Heading Attributes","id":"12","title":"Heading Attributes"},"13":{"body":"","breadcrumbs":"First Chapter » Heading Attributes » Heading with classes","id":"13","title":"Heading with classes"},"14":{"body":"","breadcrumbs":"First Chapter » Heading Attributes » Heading with id and classes","id":"14","title":"Heading with id and classes"},"2":{"body":"more text.","breadcrumbs":"First Chapter » First Chapter","id":"2","title":"First Chapter"},"3":{"body":"","breadcrumbs":"First Chapter » Some Section","id":"3","title":"Some Section"},"4":{"body":"","breadcrumbs":"First Chapter » Includes » Includes","id":"4","title":"Includes"},"5":{"body":"Introduction First Chapter Includes Unicode No Headers Duplicate Headers Heading Attributes","breadcrumbs":"First Chapter » Includes » Summary","id":"5","title":"Summary"},"6":{"body":"Please be careful editing, this contains carefully crafted characters. Two byte character: spatiëring Combining character: spatiëring Three byte character: 书こんにちは Four byte character: 𐌀𐌁𐌂𐌃𐌄𐌅𐌆𐌇𐌈 Right-to-left: مرحبا Emoticons: 🔊 😍 💜 1️⃣ right-to-left mark: hello באמת! Zalgo: ǫ̛̖̱̗̝͈̋͒͋̏ͥͫ̒̆ͩ̏͌̾͊͐ͪ̾̚","breadcrumbs":"First Chapter » Unicode » Unicode stress tests","id":"6","title":"Unicode stress tests"},"7":{"body":"Capybara capybara capybara. Capybara capybara capybara. ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex.","breadcrumbs":"First Chapter » No Headers","id":"7","title":"First Chapter"},"8":{"body":"This page validates behaviour of duplicate headers.","breadcrumbs":"First Chapter » Duplicate Headers » Duplicate headers","id":"8","title":"Duplicate headers"},"9":{"body":"","breadcrumbs":"First Chapter » Duplicate Headers » Header Text","id":"9","title":"Header Text"}},"length":15,"save":true},"fields":["title","body","breadcrumbs"],"index":{"body":{"root":{"1":{"df":1,"docs":{"6":{"tf":1.0}}},"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"(":{"/"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"12":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"6":{"tf":1.7320508075688772}}}}}},"c":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"b":{"a":{"df":0,"docs":{},"r":{"a":{"df":1,"docs":{"7":{"tf":2.449489742783178}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"6":{"tf":1.0}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"6":{"tf":1.0}}}}}}}}}},"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"2":{"tf":1.0},"5":{"tf":1.0},"7":{"tf":1.0}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":2.23606797749979}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"13":{"tf":1.0},"14":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"5":{"tf":1.0},"8":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"2":{"tf":1.0},"5":{"tf":1.0},"7":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":4,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.0},"5":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"5":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":1,"docs":{"6":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"0":{"tf":1.0}}},"df":1,"docs":{"1":{"tf":1.0}}}}}},"i":{"d":{"df":1,"docs":{"14":{"tf":1.0}}},"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":2,"docs":{"4":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"0":{"tf":1.0},"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}}},"t":{";":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"&":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"2":{"tf":1.0}}}}}},"p":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":1,"docs":{"8":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.0}}}}}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"̈":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}},"ë":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"6":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}},"x":{"df":0,"docs":{},"t":{"df":5,"docs":{"0":{"tf":1.0},"10":{"tf":1.0},"11":{"tf":1.0},"2":{"tf":1.0},"9":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"w":{"df":0,"docs":{},"o":{"df":1,"docs":{"6":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"d":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"z":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"breadcrumbs":{"root":{"1":{"df":1,"docs":{"6":{"tf":1.0}}},"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"(":{"/"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":4,"docs":{"12":{"tf":1.7320508075688772},"13":{"tf":1.0},"14":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"6":{"tf":1.7320508075688772}}}}}},"c":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"b":{"a":{"df":0,"docs":{},"r":{"a":{"df":1,"docs":{"7":{"tf":2.449489742783178}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"6":{"tf":1.0}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"6":{"tf":1.0}}}}}}}}}},"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":13,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.0},"2":{"tf":1.7320508075688772},"3":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.4142135623730951},"6":{"tf":1.0},"7":{"tf":1.4142135623730951},"8":{"tf":1.0},"9":{"tf":1.0}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":2.23606797749979}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"14":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":5,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"5":{"tf":1.0},"8":{"tf":2.0},"9":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":13,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.0},"2":{"tf":1.7320508075688772},"3":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.4142135623730951},"6":{"tf":1.0},"7":{"tf":1.4142135623730951},"8":{"tf":1.0},"9":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":4,"docs":{"12":{"tf":1.7320508075688772},"13":{"tf":1.7320508075688772},"14":{"tf":1.7320508075688772},"5":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":6,"docs":{"10":{"tf":1.7320508075688772},"11":{"tf":1.7320508075688772},"5":{"tf":1.4142135623730951},"7":{"tf":1.0},"8":{"tf":2.0},"9":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":1,"docs":{"6":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"0":{"tf":1.0}}},"df":1,"docs":{"1":{"tf":1.0}}}}}},"i":{"d":{"df":1,"docs":{"14":{"tf":1.4142135623730951}}},"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":2,"docs":{"4":{"tf":1.7320508075688772},"5":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"0":{"tf":1.7320508075688772},"1":{"tf":1.0},"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}}},"t":{";":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"&":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"2":{"tf":1.0}}}}}},"p":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":1,"docs":{"8":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"̈":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}},"ë":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}}}}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}},"x":{"df":0,"docs":{},"t":{"df":5,"docs":{"0":{"tf":1.0},"10":{"tf":1.4142135623730951},"11":{"tf":1.4142135623730951},"2":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"w":{"df":0,"docs":{},"o":{"df":1,"docs":{"6":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"d":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"z":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"title":{"root":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"12":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"2":{"tf":1.0},"7":{"tf":1.0}}}}}}},"df":0,"docs":{}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"13":{"tf":1.0},"14":{"tf":1.0}}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"2":{"tf":1.0},"7":{"tf":1.0}}}}}}},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"i":{"d":{"df":1,"docs":{"14":{"tf":1.0}}},"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":1,"docs":{"4":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.0}}}}}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"6":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}},"x":{"df":0,"docs":{},"t":{"df":3,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"9":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"lang":"English","pipeline":["trimmer","stopWordFilter","stemmer"],"ref":"id","version":"0.9.5"},"results_options":{"limit_results":30,"teaser_word_count":30},"search_options":{"bool":"OR","expand":true,"fields":{"body":{"boost":1},"breadcrumbs":{"boost":1},"title":{"boost":2}}}});
\ No newline at end of file
+window.search = JSON.parse('{"doc_urls":["intro.html#introduction","intro.html#sneaky","first/index.html#first-chapter","first/index.html#some-section","first/includes.html#includes","first/includes.html#summary","first/unicode.html#unicode-stress-tests","first/no-headers.html","first/duplicate-headers.html#duplicate-headers","first/duplicate-headers.html#header-text","first/duplicate-headers.html#header-text-1","first/duplicate-headers.html#header-text-2","first/heading-attributes.html#attrs","first/heading-attributes.html#heading-with-classes","first/heading-attributes.html#both"],"index":{"documentStore":{"docInfo":{"0":{"body":3,"breadcrumbs":2,"title":1},"1":{"body":10,"breadcrumbs":2,"title":1},"10":{"body":0,"breadcrumbs":6,"title":2},"11":{"body":0,"breadcrumbs":6,"title":2},"12":{"body":0,"breadcrumbs":6,"title":2},"13":{"body":0,"breadcrumbs":6,"title":2},"14":{"body":0,"breadcrumbs":7,"title":3},"2":{"body":2,"breadcrumbs":4,"title":2},"3":{"body":0,"breadcrumbs":3,"title":1},"4":{"body":0,"breadcrumbs":4,"title":1},"5":{"body":10,"breadcrumbs":4,"title":1},"6":{"body":29,"breadcrumbs":6,"title":3},"7":{"body":6,"breadcrumbs":3,"title":2},"8":{"body":5,"breadcrumbs":6,"title":2},"9":{"body":0,"breadcrumbs":6,"title":2}},"docs":{"0":{"body":"Here/'s some interesting text...","breadcrumbs":"Introduction » Introduction","id":"0","title":"Introduction"},"1":{"body":"I put <HTML> in here! Sneaky inline event alert(//"inline//");. But regular inline is indexed.","breadcrumbs":"Introduction » Sneaky","id":"1","title":"Sneaky"},"10":{"body":"","breadcrumbs":"First Chapter » Duplicate Headers » Header Text","id":"10","title":"Header Text"},"11":{"body":"","breadcrumbs":"First Chapter » Duplicate Headers » header-text","id":"11","title":"header-text"},"12":{"body":"","breadcrumbs":"First Chapter » Heading Attributes » Heading Attributes","id":"12","title":"Heading Attributes"},"13":{"body":"","breadcrumbs":"First Chapter » Heading Attributes » Heading with classes","id":"13","title":"Heading with classes"},"14":{"body":"","breadcrumbs":"First Chapter » Heading Attributes » Heading with id and classes","id":"14","title":"Heading with id and classes"},"2":{"body":"more text.","breadcrumbs":"First Chapter » First Chapter","id":"2","title":"First Chapter"},"3":{"body":"","breadcrumbs":"First Chapter » Some Section","id":"3","title":"Some Section"},"4":{"body":"","breadcrumbs":"First Chapter » Includes » Includes","id":"4","title":"Includes"},"5":{"body":"Introduction First Chapter Includes Unicode No Headers Duplicate Headers Heading Attributes","breadcrumbs":"First Chapter » Includes » Summary","id":"5","title":"Summary"},"6":{"body":"Please be careful editing, this contains carefully crafted characters. Two byte character: spatiëring Combining character: spatiëring Three byte character: 书こんにちは Four byte character: 𐌀𐌁𐌂𐌃𐌄𐌅𐌆𐌇𐌈 Right-to-left: مرحبا Emoticons: 🔊 😍 💜 1️⃣ right-to-left mark: hello באמת! Zalgo: ǫ̛̖̱̗̝͈̋͒͋̏ͥͫ̒̆ͩ̏͌̾͊͐ͪ̾̚","breadcrumbs":"First Chapter » Unicode » Unicode stress tests","id":"6","title":"Unicode stress tests"},"7":{"body":"Capybara capybara capybara. Capybara capybara capybara. ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex.","breadcrumbs":"First Chapter » No Headers","id":"7","title":"First Chapter"},"8":{"body":"This page validates behaviour of duplicate headers.","breadcrumbs":"First Chapter » Duplicate Headers » Duplicate headers","id":"8","title":"Duplicate headers"},"9":{"body":"","breadcrumbs":"First Chapter » Duplicate Headers » Header Text","id":"9","title":"Header Text"}},"length":15,"save":true},"fields":["title","body","breadcrumbs"],"index":{"body":{"root":{"1":{"df":1,"docs":{"6":{"tf":1.0}}},"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"(":{"//"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"12":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"6":{"tf":1.7320508075688772}}}}}},"c":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"b":{"a":{"df":0,"docs":{},"r":{"a":{"df":1,"docs":{"7":{"tf":2.449489742783178}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"6":{"tf":1.0}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"6":{"tf":1.0}}}}}}}}}},"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"2":{"tf":1.0},"5":{"tf":1.0},"7":{"tf":1.0}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":2.23606797749979}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"13":{"tf":1.0},"14":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"5":{"tf":1.0},"8":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"2":{"tf":1.0},"5":{"tf":1.0},"7":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":4,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.0},"5":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"5":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951},"9":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":1,"docs":{"6":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"/'":{"df":1,"docs":{"0":{"tf":1.0}}},"df":1,"docs":{"1":{"tf":1.0}}}}}},"i":{"d":{"df":1,"docs":{"14":{"tf":1.0}}},"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":2,"docs":{"4":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"0":{"tf":1.0},"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}}},"t":{";":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"&":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"2":{"tf":1.0}}}}}},"p":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":1,"docs":{"8":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.0}}}}}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"̈":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}},"ë":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"6":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}},"x":{"df":0,"docs":{},"t":{"df":5,"docs":{"0":{"tf":1.0},"10":{"tf":1.0},"11":{"tf":1.0},"2":{"tf":1.0},"9":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"w":{"df":0,"docs":{},"o":{"df":1,"docs":{"6":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"d":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"z":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"breadcrumbs":{"root":{"1":{"df":1,"docs":{"6":{"tf":1.0}}},"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"(":{"//"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":4,"docs":{"12":{"tf":1.7320508075688772},"13":{"tf":1.0},"14":{"tf":1.0},"5":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"8":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"6":{"tf":1.7320508075688772}}}}}},"c":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"b":{"a":{"df":0,"docs":{},"r":{"a":{"df":1,"docs":{"7":{"tf":2.449489742783178}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"6":{"tf":1.0}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"6":{"tf":1.0}}}}}}}}}},"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":13,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.0},"2":{"tf":1.7320508075688772},"3":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.4142135623730951},"6":{"tf":1.0},"7":{"tf":1.4142135623730951},"8":{"tf":1.0},"9":{"tf":1.0}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":2.23606797749979}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"14":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":5,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"5":{"tf":1.0},"8":{"tf":2.0},"9":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":13,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.0},"2":{"tf":1.7320508075688772},"3":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.4142135623730951},"6":{"tf":1.0},"7":{"tf":1.4142135623730951},"8":{"tf":1.0},"9":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":4,"docs":{"12":{"tf":1.7320508075688772},"13":{"tf":1.7320508075688772},"14":{"tf":1.7320508075688772},"5":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":6,"docs":{"10":{"tf":1.7320508075688772},"11":{"tf":1.7320508075688772},"5":{"tf":1.4142135623730951},"7":{"tf":1.0},"8":{"tf":2.0},"9":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":1,"docs":{"6":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"/'":{"df":1,"docs":{"0":{"tf":1.0}}},"df":1,"docs":{"1":{"tf":1.0}}}}}},"i":{"d":{"df":1,"docs":{"14":{"tf":1.4142135623730951}}},"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":2,"docs":{"4":{"tf":1.7320508075688772},"5":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"0":{"tf":1.7320508075688772},"1":{"tf":1.0},"5":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}}},"t":{";":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"&":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"2":{"tf":1.0}}}}}},"p":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":1,"docs":{"8":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"̈":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}},"ë":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}}}}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}},"x":{"df":0,"docs":{},"t":{"df":5,"docs":{"0":{"tf":1.0},"10":{"tf":1.4142135623730951},"11":{"tf":1.4142135623730951},"2":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"w":{"df":0,"docs":{},"o":{"df":1,"docs":{"6":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"d":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"z":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"title":{"root":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"12":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"2":{"tf":1.0},"7":{"tf":1.0}}}}}}},"df":0,"docs":{}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"13":{"tf":1.0},"14":{"tf":1.0}}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"2":{"tf":1.0},"7":{"tf":1.0}}}}}}},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"i":{"d":{"df":1,"docs":{"14":{"tf":1.0}}},"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":1,"docs":{"4":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.0}}}}}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"i":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"6":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}},"x":{"df":0,"docs":{},"t":{"df":3,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"9":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"lang":"English","pipeline":["trimmer","stopWordFilter","stemmer"],"ref":"id","version":"0.9.5"},"results_options":{"limit_results":30,"teaser_word_count":30},"search_options":{"bool":"OR","expand":true,"fields":{"body":{"boost":1},"breadcrumbs":{"boost":1},"title":{"boost":2}}}}');
\ No newline at end of file
From 8bfa6462f8a2095c2f86d754af83265084261caf Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 09:04:55 -0700
Subject: [PATCH 45/69] Migrate can_disable_individual_chapters to BookTest
---
tests/rendered_output.rs | 38 -------------------
tests/testsuite/search.rs | 18 +++++++++
.../search/disable_search_chapter/book.toml | 6 +++
.../disable_search_chapter/src/SUMMARY.md | 6 +++
.../src/first/disable_me.md | 1 +
.../src/first/keep_me.md | 1 +
.../disable_search_chapter/src/second.md | 1 +
.../src/second/nested.md | 1 +
8 files changed, 34 insertions(+), 38 deletions(-)
create mode 100644 tests/testsuite/search/disable_search_chapter/book.toml
create mode 100644 tests/testsuite/search/disable_search_chapter/src/SUMMARY.md
create mode 100644 tests/testsuite/search/disable_search_chapter/src/first/disable_me.md
create mode 100644 tests/testsuite/search/disable_search_chapter/src/first/keep_me.md
create mode 100644 tests/testsuite/search/disable_search_chapter/src/second.md
create mode 100644 tests/testsuite/search/disable_search_chapter/src/second/nested.md
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index b19a43a0..467a4d4a 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -381,44 +381,6 @@ mod search {
use crate::dummy_book::DummyBook;
use mdbook::utils::fs::write_file;
use mdbook::MDBook;
- use std::fs;
- use std::path::Path;
-
- fn read_book_index(root: &Path) -> serde_json::Value {
- let index = root.join("book/searchindex.js");
- let index = fs::read_to_string(index).unwrap();
- let index = index.trim_start_matches("window.search = JSON.parse('");
- let index = index.trim_end_matches("');");
- // We need unescape the string as it's supposed to be an escaped JS string.
- serde_json::from_str(&index.replace("\\'", "'").replace("\\\\", "\\")).unwrap()
- }
-
- #[test]
- fn can_disable_individual_chapters() {
- let temp = DummyBook::new().build().unwrap();
- let book_toml = r#"
- [book]
- title = "Search Test"
-
- [output.html.search.chapter]
- "second" = { enable = false }
- "first/unicode.md" = { enable = false }
- "#;
- write_file(temp.path(), "book.toml", book_toml.as_bytes()).unwrap();
- let md = MDBook::load(temp.path()).unwrap();
- md.build().unwrap();
- let index = read_book_index(temp.path());
- let doc_urls = index["doc_urls"].as_array().unwrap();
- let contains = |path| {
- doc_urls
- .iter()
- .any(|p| p.as_str().unwrap().starts_with(path))
- };
- assert!(contains("second.html"));
- assert!(!contains("second/"));
- assert!(!contains("first/unicode.html"));
- assert!(contains("first/markdown.html"));
- }
#[test]
fn chapter_settings_validation_error() {
diff --git a/tests/testsuite/search.rs b/tests/testsuite/search.rs
index 244532e1..4e19f134 100644
--- a/tests/testsuite/search.rs
+++ b/tests/testsuite/search.rs
@@ -86,3 +86,21 @@ fn search_index_hasnt_changed_accidentally() {
file!["search/reasonable_search_index/expected_index.js"],
);
}
+
+// Ability to disable search chapters.
+#[test]
+fn can_disable_individual_chapters() {
+ let mut test = BookTest::from_dir("search/disable_search_chapter");
+ test.build();
+ let index = read_book_index(&test.dir);
+ let doc_urls = index["doc_urls"].as_array().unwrap();
+ let contains = |path| {
+ doc_urls
+ .iter()
+ .any(|p| p.as_str().unwrap().starts_with(path))
+ };
+ assert!(contains("second.html"));
+ assert!(!contains("second/"));
+ assert!(!contains("first/disable_me.html"));
+ assert!(contains("first/keep_me.html"));
+}
diff --git a/tests/testsuite/search/disable_search_chapter/book.toml b/tests/testsuite/search/disable_search_chapter/book.toml
new file mode 100644
index 00000000..318a39a5
--- /dev/null
+++ b/tests/testsuite/search/disable_search_chapter/book.toml
@@ -0,0 +1,6 @@
+[book]
+title = "disable_search_chapter"
+
+[output.html.search.chapter]
+"second" = { enable = false }
+"first/disable_me.md" = { enable = false }
diff --git a/tests/testsuite/search/disable_search_chapter/src/SUMMARY.md b/tests/testsuite/search/disable_search_chapter/src/SUMMARY.md
new file mode 100644
index 00000000..8f7d7e80
--- /dev/null
+++ b/tests/testsuite/search/disable_search_chapter/src/SUMMARY.md
@@ -0,0 +1,6 @@
+# Summary
+
+- [Keep Me](first/keep_me.md)
+- [Disable Me](first/disable_me.md)
+- [Second](second.md)
+ - [Second Nested](second/nested.md)
diff --git a/tests/testsuite/search/disable_search_chapter/src/first/disable_me.md b/tests/testsuite/search/disable_search_chapter/src/first/disable_me.md
new file mode 100644
index 00000000..a3e32719
--- /dev/null
+++ b/tests/testsuite/search/disable_search_chapter/src/first/disable_me.md
@@ -0,0 +1 @@
+# Disable Me
diff --git a/tests/testsuite/search/disable_search_chapter/src/first/keep_me.md b/tests/testsuite/search/disable_search_chapter/src/first/keep_me.md
new file mode 100644
index 00000000..b9b18ac1
--- /dev/null
+++ b/tests/testsuite/search/disable_search_chapter/src/first/keep_me.md
@@ -0,0 +1 @@
+# Keep Me
diff --git a/tests/testsuite/search/disable_search_chapter/src/second.md b/tests/testsuite/search/disable_search_chapter/src/second.md
new file mode 100644
index 00000000..64e2582b
--- /dev/null
+++ b/tests/testsuite/search/disable_search_chapter/src/second.md
@@ -0,0 +1 @@
+# Second
diff --git a/tests/testsuite/search/disable_search_chapter/src/second/nested.md b/tests/testsuite/search/disable_search_chapter/src/second/nested.md
new file mode 100644
index 00000000..b06e47b4
--- /dev/null
+++ b/tests/testsuite/search/disable_search_chapter/src/second/nested.md
@@ -0,0 +1 @@
+# Second Nested
From 2056c87e286fec1dbd35a586a41035971a16e16c Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 09:06:06 -0700
Subject: [PATCH 46/69] Migrate with_no_source_path to BookTest
---
tests/rendered_output.rs | 23 ++---------------------
tests/testsuite/search.rs | 23 ++++++++++++++++++++++-
2 files changed, 24 insertions(+), 22 deletions(-)
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index 467a4d4a..4f4ccd9a 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -3,17 +3,16 @@ mod dummy_book;
use crate::dummy_book::{assert_contains_strings, DummyBook};
use anyhow::Context;
-use mdbook::book::Chapter;
use mdbook::config::Config;
use mdbook::errors::*;
use mdbook::utils::fs::write_file;
-use mdbook::{BookItem, MDBook};
+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, PathBuf};
+use std::path::Path;
use std::str::FromStr;
use tempfile::Builder as TempFileBuilder;
use walkdir::{DirEntry, WalkDir};
@@ -508,21 +507,3 @@ fn custom_fonts() {
&["fonts.css", "myfont.woff"]
);
}
-
-#[test]
-fn with_no_source_path() {
- // Test for a regression where search would fail if source_path is None.
- let temp = DummyBook::new().build().unwrap();
- let mut md = MDBook::load(temp.path()).unwrap();
- let chapter = Chapter {
- name: "Sample chapter".to_string(),
- content: "".to_string(),
- number: None,
- sub_items: Vec::new(),
- path: Some(PathBuf::from("sample.html")),
- source_path: None,
- parent_names: Vec::new(),
- };
- md.book.sections.push(BookItem::Chapter(chapter));
- md.build().unwrap();
-}
diff --git a/tests/testsuite/search.rs b/tests/testsuite/search.rs
index 4e19f134..b7e31212 100644
--- a/tests/testsuite/search.rs
+++ b/tests/testsuite/search.rs
@@ -1,8 +1,10 @@
//! Tests for search support.
use crate::prelude::*;
+use mdbook::book::Chapter;
+use mdbook::BookItem;
use snapbox::file;
-use std::path::Path;
+use std::path::{Path, PathBuf};
fn read_book_index(root: &Path) -> serde_json::Value {
let index = root.join("book/searchindex.js");
@@ -104,3 +106,22 @@ fn can_disable_individual_chapters() {
assert!(!contains("first/disable_me.html"));
assert!(contains("first/keep_me.html"));
}
+
+// Test for a regression where search would fail if source_path is None.
+// https://github.com/rust-lang/mdBook/pull/2550
+#[test]
+fn with_no_source_path() {
+ let test = BookTest::from_dir("search/reasonable_search_index");
+ let mut book = test.load_book();
+ let chapter = Chapter {
+ name: "Sample chapter".to_string(),
+ content: "".to_string(),
+ number: None,
+ sub_items: Vec::new(),
+ path: Some(PathBuf::from("sample.html")),
+ source_path: None,
+ parent_names: Vec::new(),
+ };
+ book.book.sections.push(BookItem::Chapter(chapter));
+ book.build().unwrap();
+}
From 0b577ebd7621372ea605956f852517bb2599b119 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 09:06:45 -0700
Subject: [PATCH 47/69] Migrate chapter_settings_validation_error to BookTest
---
tests/rendered_output.rs | 25 -------------------
tests/testsuite/search.rs | 14 +++++++++++
.../book.toml | 5 ++++
.../src/SUMMARY.md | 0
4 files changed, 19 insertions(+), 25 deletions(-)
create mode 100644 tests/testsuite/search/chapter_settings_validation_error/book.toml
create mode 100644 tests/testsuite/search/chapter_settings_validation_error/src/SUMMARY.md
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index 4f4ccd9a..cef61ac9 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -375,31 +375,6 @@ fn failure_on_missing_theme_directory() {
assert!(got.is_err());
}
-#[cfg(feature = "search")]
-mod search {
- use crate::dummy_book::DummyBook;
- use mdbook::utils::fs::write_file;
- use mdbook::MDBook;
-
- #[test]
- fn chapter_settings_validation_error() {
- let temp = DummyBook::new().build().unwrap();
- let book_toml = r#"
- [book]
- title = "Search Test"
-
- [output.html.search.chapter]
- "does-not-exist" = { enable = false }
- "#;
- write_file(temp.path(), "book.toml", book_toml.as_bytes()).unwrap();
- let md = MDBook::load(temp.path()).unwrap();
- let err = md.build().unwrap_err();
- assert!(format!("{err:?}").contains(
- "[output.html.search.chapter] key `does-not-exist` does not match any chapter paths"
- ));
- }
-}
-
#[test]
fn custom_fonts() {
// Tests to ensure custom fonts are copied as expected.
diff --git a/tests/testsuite/search.rs b/tests/testsuite/search.rs
index b7e31212..d7e142a9 100644
--- a/tests/testsuite/search.rs
+++ b/tests/testsuite/search.rs
@@ -125,3 +125,17 @@ fn with_no_source_path() {
book.book.sections.push(BookItem::Chapter(chapter));
book.build().unwrap();
}
+
+// Checks that invalid settings in search chapter is rejected.
+#[test]
+fn chapter_settings_validation_error() {
+ BookTest::from_dir("search/chapter_settings_validation_error").run("build", |cmd| {
+ cmd.expect_failure().expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book): Book building has started
+[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
+[TIMESTAMP] [ERROR] (mdbook::utils): Error: Rendering failed
+[TIMESTAMP] [ERROR] (mdbook::utils): [TAB]Caused By: [output.html.search.chapter] key `does-not-exist` does not match any chapter paths
+
+"#]]);
+ });
+}
diff --git a/tests/testsuite/search/chapter_settings_validation_error/book.toml b/tests/testsuite/search/chapter_settings_validation_error/book.toml
new file mode 100644
index 00000000..e1a18d65
--- /dev/null
+++ b/tests/testsuite/search/chapter_settings_validation_error/book.toml
@@ -0,0 +1,5 @@
+[book]
+title = "Search Test"
+
+[output.html.search.chapter]
+"does-not-exist" = { enable = false }
diff --git a/tests/testsuite/search/chapter_settings_validation_error/src/SUMMARY.md b/tests/testsuite/search/chapter_settings_validation_error/src/SUMMARY.md
new file mode 100644
index 00000000..e69de29b
From 5a84d641cd72adf202c29b9ce998d27bbd8ca8ac Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 10:56:05 -0700
Subject: [PATCH 48/69] Migrate pass/fail `mdbook test` to BookTest
---
tests/cli/mod.rs | 1 -
tests/cli/test.rs | 34 ------------
tests/testing.rs | 21 -------
tests/testsuite/main.rs | 1 +
tests/testsuite/test.rs | 55 +++++++++++++++++++
tests/testsuite/test/failing_tests/book.toml | 2 +
.../test/failing_tests/src/SUMMARY.md | 4 ++
.../test/failing_tests/src/failing.md | 5 ++
.../test/failing_tests/src/failing_include.md | 5 ++
.../testsuite/test/failing_tests/src/test1.rs | 11 ++++
tests/testsuite/test/passing_tests/book.toml | 2 +
.../test/passing_tests/src/SUMMARY.md | 6 ++
.../test/passing_tests/src/passing1.md | 29 ++++++++++
.../test/passing_tests/src/passing2.md | 5 ++
.../testsuite/test/passing_tests/src/test1.rs | 1 +
.../testsuite/test/passing_tests/src/test2.rs | 11 ++++
.../testsuite/test/passing_tests/src/test3.rs | 1 +
17 files changed, 138 insertions(+), 56 deletions(-)
delete mode 100644 tests/cli/test.rs
create mode 100644 tests/testsuite/test.rs
create mode 100644 tests/testsuite/test/failing_tests/book.toml
create mode 100644 tests/testsuite/test/failing_tests/src/SUMMARY.md
create mode 100644 tests/testsuite/test/failing_tests/src/failing.md
create mode 100644 tests/testsuite/test/failing_tests/src/failing_include.md
create mode 100644 tests/testsuite/test/failing_tests/src/test1.rs
create mode 100644 tests/testsuite/test/passing_tests/book.toml
create mode 100644 tests/testsuite/test/passing_tests/src/SUMMARY.md
create mode 100644 tests/testsuite/test/passing_tests/src/passing1.md
create mode 100644 tests/testsuite/test/passing_tests/src/passing2.md
create mode 100644 tests/testsuite/test/passing_tests/src/test1.rs
create mode 100644 tests/testsuite/test/passing_tests/src/test2.rs
create mode 100644 tests/testsuite/test/passing_tests/src/test3.rs
diff --git a/tests/cli/mod.rs b/tests/cli/mod.rs
index 989f443f..4c2d1892 100644
--- a/tests/cli/mod.rs
+++ b/tests/cli/mod.rs
@@ -1,3 +1,2 @@
mod build;
mod cmd;
-mod test;
diff --git a/tests/cli/test.rs b/tests/cli/test.rs
deleted file mode 100644
index 63114d3a..00000000
--- a/tests/cli/test.rs
+++ /dev/null
@@ -1,34 +0,0 @@
-use crate::cli::cmd::mdbook_cmd;
-use crate::dummy_book::DummyBook;
-
-use predicates::boolean::PredicateBooleanExt;
-
-#[test]
-fn mdbook_cli_can_correctly_test_a_passing_book() {
- let temp = DummyBook::new().with_passing_test(true).build().unwrap();
-
- let mut cmd = mdbook_cmd();
- cmd.arg("test").current_dir(temp.path());
- cmd.assert().success()
- .stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "README.md""##).unwrap())
- .stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "intro.md""##).unwrap())
- .stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "first[\\/]index.md""##).unwrap())
- .stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "first[\\/]nested.md""##).unwrap())
- .stderr(predicates::str::is_match(r##"returned an error:\n\n"##).unwrap().not())
- .stderr(predicates::str::is_match(r##"Nested_Chapter::Rustdoc_include_works_with_anchors_too \(line \d+\) ... FAILED"##).unwrap().not());
-}
-
-#[test]
-fn mdbook_cli_detects_book_with_failing_tests() {
- let temp = DummyBook::new().with_passing_test(false).build().unwrap();
-
- let mut cmd = mdbook_cmd();
- cmd.arg("test").current_dir(temp.path());
- cmd.assert().failure()
- .stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "README.md""##).unwrap())
- .stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "intro.md""##).unwrap())
- .stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "first[\\/]index.md""##).unwrap())
- .stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "first[\\/]nested.md""##).unwrap())
- .stderr(predicates::str::is_match(r##"returned an error:\n\n"##).unwrap())
- .stderr(predicates::str::is_match(r##"Nested_Chapter::Rustdoc_include_works_with_anchors_too \(line \d+\) ... FAILED"##).unwrap());
-}
diff --git a/tests/testing.rs b/tests/testing.rs
index 3030c5cb..6f5a299a 100644
--- a/tests/testing.rs
+++ b/tests/testing.rs
@@ -4,27 +4,6 @@ use crate::dummy_book::DummyBook;
use mdbook::MDBook;
-#[test]
-fn mdbook_can_correctly_test_a_passing_book() {
- let temp = DummyBook::new().with_passing_test(true).build().unwrap();
- let mut md = MDBook::load(temp.path()).unwrap();
-
- let result = md.test(vec![]);
- assert!(
- result.is_ok(),
- "Tests failed with {}",
- result.err().unwrap()
- );
-}
-
-#[test]
-fn mdbook_detects_book_with_failing_tests() {
- let temp = DummyBook::new().with_passing_test(false).build().unwrap();
- let mut md = MDBook::load(temp.path()).unwrap();
-
- assert!(md.test(vec![]).is_err());
-}
-
#[test]
fn mdbook_test_chapter() {
let temp = DummyBook::new().with_passing_test(true).build().unwrap();
diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs
index 95756d51..e622fe0f 100644
--- a/tests/testsuite/main.rs
+++ b/tests/testsuite/main.rs
@@ -17,6 +17,7 @@ mod renderer;
mod rendering;
#[cfg(feature = "search")]
mod search;
+mod test;
mod prelude {
pub use crate::book_test::BookTest;
diff --git a/tests/testsuite/test.rs b/tests/testsuite/test.rs
new file mode 100644
index 00000000..b39b183c
--- /dev/null
+++ b/tests/testsuite/test.rs
@@ -0,0 +1,55 @@
+//! Tests for the `mdbook test` command.
+
+use crate::prelude::*;
+
+// Simple test for passing tests.
+#[test]
+fn passing_tests() {
+ BookTest::from_dir("test/passing_tests").run("test", |cmd| {
+ cmd.expect_stdout(str![[""]]).expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book): Testing chapter 'Intro': "intro.md"
+[TIMESTAMP] [INFO] (mdbook::book): Testing chapter 'Passing 1': "passing1.md"
+[TIMESTAMP] [INFO] (mdbook::book): Testing chapter 'Passing 2': "passing2.md"
+
+"#]]);
+ });
+}
+
+// Test for a test failure
+#[test]
+fn failing_tests() {
+ BookTest::from_dir("test/failing_tests").run("test", |cmd| {
+ cmd.expect_code(101)
+ .expect_stdout(str![[""]])
+ // This redacts a large number of lines that come from rustdoc and
+ // libtest. If the output from those ever changes, then it would not
+ // make it possible to test against different versions of Rust. This
+ // still includes a little bit of output, so if that is a problem,
+ // add more redactions.
+ .expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book): Testing chapter 'Failing Tests': "failing.md"
+[TIMESTAMP] [ERROR] (mdbook::book): rustdoc returned an error:
+
+--- stdout
+
+...
+test failing.md - Failing_Tests (line 3) ... FAILED
+...
+thread 'main' panicked at failing.md:3:1:
+fail
+...
+[TIMESTAMP] [INFO] (mdbook::book): Testing chapter 'Failing Include': "failing_include.md"
+[TIMESTAMP] [ERROR] (mdbook::book): rustdoc returned an error:
+
+--- stdout
+...
+test failing_include.md - Failing_Include (line 3) ... FAILED
+...
+thread 'main' panicked at failing_include.md:3:1:
+failing!
+...
+[TIMESTAMP] [ERROR] (mdbook::utils): Error: One or more tests failed
+
+"#]]);
+ });
+}
diff --git a/tests/testsuite/test/failing_tests/book.toml b/tests/testsuite/test/failing_tests/book.toml
new file mode 100644
index 00000000..37c9bcf4
--- /dev/null
+++ b/tests/testsuite/test/failing_tests/book.toml
@@ -0,0 +1,2 @@
+[book]
+title = "failing_tests"
diff --git a/tests/testsuite/test/failing_tests/src/SUMMARY.md b/tests/testsuite/test/failing_tests/src/SUMMARY.md
new file mode 100644
index 00000000..b141f28d
--- /dev/null
+++ b/tests/testsuite/test/failing_tests/src/SUMMARY.md
@@ -0,0 +1,4 @@
+# Summary
+
+- [Failing Tests](./failing.md)
+- [Failing Include](./failing_include.md)
diff --git a/tests/testsuite/test/failing_tests/src/failing.md b/tests/testsuite/test/failing_tests/src/failing.md
new file mode 100644
index 00000000..635f8b51
--- /dev/null
+++ b/tests/testsuite/test/failing_tests/src/failing.md
@@ -0,0 +1,5 @@
+# Failing Tests
+
+```rust
+panic!("fail");
+```
diff --git a/tests/testsuite/test/failing_tests/src/failing_include.md b/tests/testsuite/test/failing_tests/src/failing_include.md
new file mode 100644
index 00000000..7c369200
--- /dev/null
+++ b/tests/testsuite/test/failing_tests/src/failing_include.md
@@ -0,0 +1,5 @@
+# Failing Include
+
+```rust
+{{#include test1.rs:FAILING}}
+```
diff --git a/tests/testsuite/test/failing_tests/src/test1.rs b/tests/testsuite/test/failing_tests/src/test1.rs
new file mode 100644
index 00000000..b3f19502
--- /dev/null
+++ b/tests/testsuite/test/failing_tests/src/test1.rs
@@ -0,0 +1,11 @@
+fn test2() {
+ println!("test2");
+}
+
+// ANCHOR: PASSING
+println!("passing!");
+// ANCHOR_END: PASSING
+
+// ANCHOR: FAILING
+panic!("failing!");
+// ANCHOR_END: FAILING
diff --git a/tests/testsuite/test/passing_tests/book.toml b/tests/testsuite/test/passing_tests/book.toml
new file mode 100644
index 00000000..959cd17c
--- /dev/null
+++ b/tests/testsuite/test/passing_tests/book.toml
@@ -0,0 +1,2 @@
+[book]
+title = "passing_tests"
diff --git a/tests/testsuite/test/passing_tests/src/SUMMARY.md b/tests/testsuite/test/passing_tests/src/SUMMARY.md
new file mode 100644
index 00000000..fb2a2b53
--- /dev/null
+++ b/tests/testsuite/test/passing_tests/src/SUMMARY.md
@@ -0,0 +1,6 @@
+# Summary
+
+[Intro](./intro.md)
+
+- [Passing 1](./passing1.md)
+- [Passing 2](./passing2.md)
diff --git a/tests/testsuite/test/passing_tests/src/passing1.md b/tests/testsuite/test/passing_tests/src/passing1.md
new file mode 100644
index 00000000..9f1b91ba
--- /dev/null
+++ b/tests/testsuite/test/passing_tests/src/passing1.md
@@ -0,0 +1,29 @@
+# Passing Tests 1
+
+```rust
+assert!(true);
+```
+
+```rust
+println!("hello!");
+```
+
+## Also check includes
+
+```rust
+{{#include test1.rs}}
+```
+
+```rust
+{{#include test2.rs:2}}
+```
+
+```rust
+{{#include test2.rs:PASSING}}
+```
+
+```rust
+{{#rustdoc_include test3.rs:2}}
+```
+
+{{#playground test1.rs}}
diff --git a/tests/testsuite/test/passing_tests/src/passing2.md b/tests/testsuite/test/passing_tests/src/passing2.md
new file mode 100644
index 00000000..dc290970
--- /dev/null
+++ b/tests/testsuite/test/passing_tests/src/passing2.md
@@ -0,0 +1,5 @@
+# Passing Tests 2
+
+```rust
+println!("also passing");
+```
diff --git a/tests/testsuite/test/passing_tests/src/test1.rs b/tests/testsuite/test/passing_tests/src/test1.rs
new file mode 100644
index 00000000..226bd6af
--- /dev/null
+++ b/tests/testsuite/test/passing_tests/src/test1.rs
@@ -0,0 +1 @@
+println!("test1");
diff --git a/tests/testsuite/test/passing_tests/src/test2.rs b/tests/testsuite/test/passing_tests/src/test2.rs
new file mode 100644
index 00000000..b3f19502
--- /dev/null
+++ b/tests/testsuite/test/passing_tests/src/test2.rs
@@ -0,0 +1,11 @@
+fn test2() {
+ println!("test2");
+}
+
+// ANCHOR: PASSING
+println!("passing!");
+// ANCHOR_END: PASSING
+
+// ANCHOR: FAILING
+panic!("failing!");
+// ANCHOR_END: FAILING
diff --git a/tests/testsuite/test/passing_tests/src/test3.rs b/tests/testsuite/test/passing_tests/src/test3.rs
new file mode 100644
index 00000000..1832ea31
--- /dev/null
+++ b/tests/testsuite/test/passing_tests/src/test3.rs
@@ -0,0 +1 @@
+println!("test3");
From f324aebdec53558dc9e2f93b71ab2daf487c79f0 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 10:58:27 -0700
Subject: [PATCH 49/69] Migrate mdbook_test_chapter to BookTest
---
tests/testing.rs | 13 -------------
tests/testsuite/test.rs | 21 +++++++++++++++++++++
2 files changed, 21 insertions(+), 13 deletions(-)
diff --git a/tests/testing.rs b/tests/testing.rs
index 6f5a299a..8ab16c3d 100644
--- a/tests/testing.rs
+++ b/tests/testing.rs
@@ -4,19 +4,6 @@ use crate::dummy_book::DummyBook;
use mdbook::MDBook;
-#[test]
-fn mdbook_test_chapter() {
- let temp = DummyBook::new().with_passing_test(true).build().unwrap();
- let mut md = MDBook::load(temp.path()).unwrap();
-
- let result = md.test_chapter(vec![], Some("Introduction"));
- assert!(
- result.is_ok(),
- "test_chapter failed with {}",
- result.err().unwrap()
- );
-}
-
#[test]
fn mdbook_test_chapter_not_found() {
let temp = DummyBook::new().with_passing_test(true).build().unwrap();
diff --git a/tests/testsuite/test.rs b/tests/testsuite/test.rs
index b39b183c..c5f4530a 100644
--- a/tests/testsuite/test.rs
+++ b/tests/testsuite/test.rs
@@ -53,3 +53,24 @@ failing!
"#]]);
});
}
+
+// Test with a specific chapter.
+#[test]
+fn test_individual_chapter() {
+ let mut test = BookTest::from_dir("test/passing_tests");
+ test.run("test -c", |cmd| {
+ cmd.args(&["Passing 1"])
+ .expect_stdout(str![[""]])
+ .expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book): Testing chapter 'Passing 1': "passing1.md"
+
+"#]]);
+ })
+ // Can also be a source path.
+ .run("test -c passing2.md", |cmd| {
+ cmd.expect_stdout(str![[""]]).expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book): Testing chapter 'Passing 2': "passing2.md"
+
+"#]]);
+ });
+}
From 909bd1c54e5f4a1c05191bbe4731699a20acc426 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 10:58:55 -0700
Subject: [PATCH 50/69] Migrate mdbook_test_chapter_not_found to BookTest
---
tests/testing.rs | 13 -------------
tests/testsuite/test.rs | 13 +++++++++++++
2 files changed, 13 insertions(+), 13 deletions(-)
delete mode 100644 tests/testing.rs
diff --git a/tests/testing.rs b/tests/testing.rs
deleted file mode 100644
index 8ab16c3d..00000000
--- a/tests/testing.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-mod dummy_book;
-
-use crate::dummy_book::DummyBook;
-
-use mdbook::MDBook;
-
-#[test]
-fn mdbook_test_chapter_not_found() {
- let temp = DummyBook::new().with_passing_test(true).build().unwrap();
- let mut md = MDBook::load(temp.path()).unwrap();
-
- assert!(md.test_chapter(vec![], Some("Bogus Chapter Name")).is_err());
-}
diff --git a/tests/testsuite/test.rs b/tests/testsuite/test.rs
index c5f4530a..54be8695 100644
--- a/tests/testsuite/test.rs
+++ b/tests/testsuite/test.rs
@@ -74,3 +74,16 @@ fn test_individual_chapter() {
"#]]);
});
}
+
+// Unknown chapter name.
+#[test]
+fn chapter_not_found() {
+ BookTest::from_dir("test/passing_tests").run("test -c bogus", |cmd| {
+ cmd.expect_failure()
+ .expect_stdout(str![[""]])
+ .expect_stderr(str![[r#"
+[TIMESTAMP] [ERROR] (mdbook::utils): Error: Chapter not found: bogus
+
+"#]]);
+ });
+}
From 10fae8596cf4e5630ce5f91bce89909d9f87c7c8 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 11:05:48 -0700
Subject: [PATCH 51/69] Migrate missing theme to BookTest
---
tests/rendered_output.rs | 16 ----------------
tests/testsuite/main.rs | 1 +
tests/testsuite/theme.rs | 19 +++++++++++++++++++
tests/testsuite/theme/missing_theme/book.toml | 5 +++++
.../theme/missing_theme/src/SUMMARY.md | 3 +++
.../theme/missing_theme/src/chapter_1.md | 1 +
6 files changed, 29 insertions(+), 16 deletions(-)
create mode 100644 tests/testsuite/theme.rs
create mode 100644 tests/testsuite/theme/missing_theme/book.toml
create mode 100644 tests/testsuite/theme/missing_theme/src/SUMMARY.md
create mode 100644 tests/testsuite/theme/missing_theme/src/chapter_1.md
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index cef61ac9..9f026839 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -357,22 +357,6 @@ fn failure_on_missing_theme_directory() {
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());
}
#[test]
diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs
index e622fe0f..162f850a 100644
--- a/tests/testsuite/main.rs
+++ b/tests/testsuite/main.rs
@@ -18,6 +18,7 @@ mod rendering;
#[cfg(feature = "search")]
mod search;
mod test;
+mod theme;
mod prelude {
pub use crate::book_test::BookTest;
diff --git a/tests/testsuite/theme.rs b/tests/testsuite/theme.rs
new file mode 100644
index 00000000..e403b6c4
--- /dev/null
+++ b/tests/testsuite/theme.rs
@@ -0,0 +1,19 @@
+//! Tests for theme handling.
+
+use crate::prelude::*;
+
+// Checks what happens if the theme directory is missing.
+#[test]
+fn missing_theme() {
+ BookTest::from_dir("theme/missing_theme")
+ .run("build", |cmd| {
+cmd.expect_failure()
+ .expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book): Book building has started
+[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
+[TIMESTAMP] [ERROR] (mdbook::utils): Error: Rendering failed
+[TIMESTAMP] [ERROR] (mdbook::utils): [TAB]Caused By: theme dir [ROOT]/./non-existent-directory does not exist
+
+"#]]);
+ });
+}
diff --git a/tests/testsuite/theme/missing_theme/book.toml b/tests/testsuite/theme/missing_theme/book.toml
new file mode 100644
index 00000000..367db850
--- /dev/null
+++ b/tests/testsuite/theme/missing_theme/book.toml
@@ -0,0 +1,5 @@
+[book]
+title = "missing_theme"
+
+[output.html]
+theme = "./non-existent-directory"
diff --git a/tests/testsuite/theme/missing_theme/src/SUMMARY.md b/tests/testsuite/theme/missing_theme/src/SUMMARY.md
new file mode 100644
index 00000000..7390c828
--- /dev/null
+++ b/tests/testsuite/theme/missing_theme/src/SUMMARY.md
@@ -0,0 +1,3 @@
+# Summary
+
+- [Chapter 1](./chapter_1.md)
diff --git a/tests/testsuite/theme/missing_theme/src/chapter_1.md b/tests/testsuite/theme/missing_theme/src/chapter_1.md
new file mode 100644
index 00000000..b743fda3
--- /dev/null
+++ b/tests/testsuite/theme/missing_theme/src/chapter_1.md
@@ -0,0 +1 @@
+# Chapter 1
From 25b9acc32195126a5192c35afac7d4d6e0771150 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 11:06:48 -0700
Subject: [PATCH 52/69] Migrate empty theme to BookTest
---
tests/rendered_output.rs | 35 -------------------
tests/testsuite/theme.rs | 13 +++++++
tests/testsuite/theme/empty_theme/book.toml | 5 +++
.../theme/empty_theme/src/SUMMARY.md | 3 ++
.../theme/empty_theme/src/chapter_1.md | 1 +
5 files changed, 22 insertions(+), 35 deletions(-)
create mode 100644 tests/testsuite/theme/empty_theme/book.toml
create mode 100644 tests/testsuite/theme/empty_theme/src/SUMMARY.md
create mode 100644 tests/testsuite/theme/empty_theme/src/chapter_1.md
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index 9f026839..bd52b551 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -324,41 +324,6 @@ fn summary_with_markdown_formatting() {
);
}
-/// 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());
-}
-
#[test]
fn custom_fonts() {
// Tests to ensure custom fonts are copied as expected.
diff --git a/tests/testsuite/theme.rs b/tests/testsuite/theme.rs
index e403b6c4..c1c39227 100644
--- a/tests/testsuite/theme.rs
+++ b/tests/testsuite/theme.rs
@@ -17,3 +17,16 @@ cmd.expect_failure()
"#]]);
});
}
+
+// Checks what happens if the theme directory is empty.
+#[test]
+fn empty_theme() {
+ BookTest::from_dir("theme/empty_theme").run("build", |cmd| {
+ std::fs::create_dir(cmd.dir.join("theme")).unwrap();
+ cmd.expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book): Book building has started
+[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
+
+"#]]);
+ });
+}
diff --git a/tests/testsuite/theme/empty_theme/book.toml b/tests/testsuite/theme/empty_theme/book.toml
new file mode 100644
index 00000000..1c3ab31e
--- /dev/null
+++ b/tests/testsuite/theme/empty_theme/book.toml
@@ -0,0 +1,5 @@
+[book]
+title = "empty_theme"
+
+[output.html]
+theme = "./theme"
diff --git a/tests/testsuite/theme/empty_theme/src/SUMMARY.md b/tests/testsuite/theme/empty_theme/src/SUMMARY.md
new file mode 100644
index 00000000..7390c828
--- /dev/null
+++ b/tests/testsuite/theme/empty_theme/src/SUMMARY.md
@@ -0,0 +1,3 @@
+# Summary
+
+- [Chapter 1](./chapter_1.md)
diff --git a/tests/testsuite/theme/empty_theme/src/chapter_1.md b/tests/testsuite/theme/empty_theme/src/chapter_1.md
new file mode 100644
index 00000000..b743fda3
--- /dev/null
+++ b/tests/testsuite/theme/empty_theme/src/chapter_1.md
@@ -0,0 +1 @@
+# Chapter 1
From dd27c4f8ba0e510602d51a7436e6d59f1752e2a4 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 11:08:23 -0700
Subject: [PATCH 53/69] Migrate theme_dir_overrides_work_correctly to BookTest
---
tests/rendered_output.rs | 18 ------------------
tests/testsuite/theme.rs | 12 ++++++++++++
.../theme/override_index/src/SUMMARY.md | 1 +
.../theme/override_index/theme/index.hbs | 1 +
4 files changed, 14 insertions(+), 18 deletions(-)
create mode 100644 tests/testsuite/theme/override_index/src/SUMMARY.md
create mode 100644 tests/testsuite/theme/override_index/theme/index.hbs
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index bd52b551..b9e14ac4 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -270,24 +270,6 @@ fn example_book_can_build() {
md.build().unwrap();
}
-#[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");
-
- let mut index = mdbook::theme::INDEX.to_vec();
- index.extend_from_slice(b"\n");
-
- 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!"]);
-}
-
/// Checks formatting of summary names with inline elements.
#[test]
fn summary_with_markdown_formatting() {
diff --git a/tests/testsuite/theme.rs b/tests/testsuite/theme.rs
index c1c39227..dd91e705 100644
--- a/tests/testsuite/theme.rs
+++ b/tests/testsuite/theme.rs
@@ -30,3 +30,15 @@ fn empty_theme() {
"#]]);
});
}
+
+// Checks overriding index.hbs.
+#[test]
+fn override_index() {
+ BookTest::from_dir("theme/override_index").check_file(
+ "book/index.html",
+ str![[r#"
+This is a modified index.hbs!
+
+"#]],
+ );
+}
diff --git a/tests/testsuite/theme/override_index/src/SUMMARY.md b/tests/testsuite/theme/override_index/src/SUMMARY.md
new file mode 100644
index 00000000..655a0ded
--- /dev/null
+++ b/tests/testsuite/theme/override_index/src/SUMMARY.md
@@ -0,0 +1 @@
+- [Intro](index.md)
diff --git a/tests/testsuite/theme/override_index/theme/index.hbs b/tests/testsuite/theme/override_index/theme/index.hbs
new file mode 100644
index 00000000..2db8dcce
--- /dev/null
+++ b/tests/testsuite/theme/override_index/theme/index.hbs
@@ -0,0 +1 @@
+This is a modified index.hbs!
From 0274ad6e8718590b74d90a19537641babe1aecf3 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 11:09:20 -0700
Subject: [PATCH 54/69] Migrate (no theme) default fonts to BookTest
---
tests/rendered_output.rs | 8 --------
tests/testsuite/theme.rs | 26 ++++++++++++++++++++++++++
2 files changed, 26 insertions(+), 8 deletions(-)
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index b9e14ac4..ff83051e 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -339,14 +339,6 @@ fn custom_fonts() {
contents.contains("fonts/fonts.css")
};
- // No theme:
- let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
- let p = temp.path();
- MDBook::init(p).build().unwrap();
- MDBook::load(p).unwrap().build().unwrap();
- assert_eq!(actual_files(&p.join("book/fonts")), &builtin_fonts);
- assert!(has_fonts_css(p));
-
// Full theme.
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
let p = temp.path();
diff --git a/tests/testsuite/theme.rs b/tests/testsuite/theme.rs
index dd91e705..593fe073 100644
--- a/tests/testsuite/theme.rs
+++ b/tests/testsuite/theme.rs
@@ -42,3 +42,29 @@ This is a modified index.hbs!
"#]],
);
}
+
+// After building, what are the default set of fonts?
+#[test]
+fn default_fonts() {
+ BookTest::init(|_| {})
+ .check_file_contains("book/index.html", "fonts/fonts.css")
+ .check_file_list(
+ "book/fonts",
+ str![[r#"
+book/fonts/OPEN-SANS-LICENSE.txt
+book/fonts/SOURCE-CODE-PRO-LICENSE.txt
+book/fonts/fonts.css
+book/fonts/open-sans-v17-all-charsets-300.woff2
+book/fonts/open-sans-v17-all-charsets-300italic.woff2
+book/fonts/open-sans-v17-all-charsets-600.woff2
+book/fonts/open-sans-v17-all-charsets-600italic.woff2
+book/fonts/open-sans-v17-all-charsets-700.woff2
+book/fonts/open-sans-v17-all-charsets-700italic.woff2
+book/fonts/open-sans-v17-all-charsets-800.woff2
+book/fonts/open-sans-v17-all-charsets-800italic.woff2
+book/fonts/open-sans-v17-all-charsets-italic.woff2
+book/fonts/open-sans-v17-all-charsets-regular.woff2
+book/fonts/source-code-pro-v11-all-charsets-500.woff2
+"#]],
+ );
+}
From 5f227613aab6b782ddb7a531622e621a001ba052 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 11:10:20 -0700
Subject: [PATCH 55/69] Migrate copy theme default fonts to BookTest
---
tests/rendered_output.rs | 25 ---------------------
tests/testsuite/theme.rs | 47 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 47 insertions(+), 25 deletions(-)
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index ff83051e..f9b4c48e 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -309,22 +309,6 @@ fn summary_with_markdown_formatting() {
#[test]
fn custom_fonts() {
// Tests to ensure custom fonts are copied as expected.
- let builtin_fonts = [
- "OPEN-SANS-LICENSE.txt",
- "SOURCE-CODE-PRO-LICENSE.txt",
- "fonts.css",
- "open-sans-v17-all-charsets-300.woff2",
- "open-sans-v17-all-charsets-300italic.woff2",
- "open-sans-v17-all-charsets-600.woff2",
- "open-sans-v17-all-charsets-600italic.woff2",
- "open-sans-v17-all-charsets-700.woff2",
- "open-sans-v17-all-charsets-700italic.woff2",
- "open-sans-v17-all-charsets-800.woff2",
- "open-sans-v17-all-charsets-800italic.woff2",
- "open-sans-v17-all-charsets-italic.woff2",
- "open-sans-v17-all-charsets-regular.woff2",
- "source-code-pro-v11-all-charsets-500.woff2",
- ];
let actual_files = |path: &Path| -> Vec {
let mut actual: Vec<_> = path
.read_dir()
@@ -339,15 +323,6 @@ fn custom_fonts() {
contents.contains("fonts/fonts.css")
};
- // Full theme.
- let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
- let p = temp.path();
- MDBook::init(p).copy_theme(true).build().unwrap();
- assert_eq!(actual_files(&p.join("theme/fonts")), &builtin_fonts);
- MDBook::load(p).unwrap().build().unwrap();
- assert_eq!(actual_files(&p.join("book/fonts")), &builtin_fonts);
- assert!(has_fonts_css(p));
-
// Mixed with copy-fonts=true
// Should ignore the copy-fonts setting since the user has provided their own fonts.css.
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
diff --git a/tests/testsuite/theme.rs b/tests/testsuite/theme.rs
index 593fe073..c552d452 100644
--- a/tests/testsuite/theme.rs
+++ b/tests/testsuite/theme.rs
@@ -68,3 +68,50 @@ book/fonts/source-code-pro-v11-all-charsets-500.woff2
"#]],
);
}
+
+// When the theme is initialized, what does the fonts list look like?
+#[test]
+fn theme_fonts_copied() {
+ BookTest::init(|bb| {
+ bb.copy_theme(true);
+ })
+ .check_file_contains("book/index.html", "fonts/fonts.css")
+ .check_file_list(
+ "theme/fonts",
+ str![[r#"
+theme/fonts/OPEN-SANS-LICENSE.txt
+theme/fonts/SOURCE-CODE-PRO-LICENSE.txt
+theme/fonts/fonts.css
+theme/fonts/open-sans-v17-all-charsets-300.woff2
+theme/fonts/open-sans-v17-all-charsets-300italic.woff2
+theme/fonts/open-sans-v17-all-charsets-600.woff2
+theme/fonts/open-sans-v17-all-charsets-600italic.woff2
+theme/fonts/open-sans-v17-all-charsets-700.woff2
+theme/fonts/open-sans-v17-all-charsets-700italic.woff2
+theme/fonts/open-sans-v17-all-charsets-800.woff2
+theme/fonts/open-sans-v17-all-charsets-800italic.woff2
+theme/fonts/open-sans-v17-all-charsets-italic.woff2
+theme/fonts/open-sans-v17-all-charsets-regular.woff2
+theme/fonts/source-code-pro-v11-all-charsets-500.woff2
+"#]],
+ )
+ .check_file_list(
+ "book/fonts",
+ str![[r#"
+book/fonts/OPEN-SANS-LICENSE.txt
+book/fonts/SOURCE-CODE-PRO-LICENSE.txt
+book/fonts/fonts.css
+book/fonts/open-sans-v17-all-charsets-300.woff2
+book/fonts/open-sans-v17-all-charsets-300italic.woff2
+book/fonts/open-sans-v17-all-charsets-600.woff2
+book/fonts/open-sans-v17-all-charsets-600italic.woff2
+book/fonts/open-sans-v17-all-charsets-700.woff2
+book/fonts/open-sans-v17-all-charsets-700italic.woff2
+book/fonts/open-sans-v17-all-charsets-800.woff2
+book/fonts/open-sans-v17-all-charsets-800italic.woff2
+book/fonts/open-sans-v17-all-charsets-italic.woff2
+book/fonts/open-sans-v17-all-charsets-regular.woff2
+book/fonts/source-code-pro-v11-all-charsets-500.woff2
+"#]],
+ );
+}
From c2c37705e7af909797ebd42327bda379802724c1 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 11:12:01 -0700
Subject: [PATCH 56/69] Migrate custom fonts.css to BookTest
---
tests/rendered_output.rs | 14 ------------
tests/testsuite/theme.rs | 22 +++++++++++++++++++
.../testsuite/theme/fonts_css/src/SUMMARY.md | 1 +
.../theme/fonts_css/theme/fonts/fonts.css | 1 +
.../theme/fonts_css/theme/fonts/myfont.woff | 0
5 files changed, 24 insertions(+), 14 deletions(-)
create mode 100644 tests/testsuite/theme/fonts_css/src/SUMMARY.md
create mode 100644 tests/testsuite/theme/fonts_css/theme/fonts/fonts.css
create mode 100644 tests/testsuite/theme/fonts_css/theme/fonts/myfont.woff
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index f9b4c48e..20a91825 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -323,20 +323,6 @@ fn custom_fonts() {
contents.contains("fonts/fonts.css")
};
- // Mixed with copy-fonts=true
- // Should ignore the copy-fonts setting since the user has provided their own fonts.css.
- let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
- let p = temp.path();
- MDBook::init(p).build().unwrap();
- write_file(&p.join("theme/fonts"), "fonts.css", b"/*custom*/").unwrap();
- write_file(&p.join("theme/fonts"), "myfont.woff", b"").unwrap();
- MDBook::load(p).unwrap().build().unwrap();
- assert!(has_fonts_css(p));
- assert_eq!(
- actual_files(&p.join("book/fonts")),
- ["fonts.css", "myfont.woff"]
- );
-
// copy-fonts=false, no theme
// This should generate a deprecation warning.
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
diff --git a/tests/testsuite/theme.rs b/tests/testsuite/theme.rs
index c552d452..9cab6e57 100644
--- a/tests/testsuite/theme.rs
+++ b/tests/testsuite/theme.rs
@@ -115,3 +115,25 @@ book/fonts/source-code-pro-v11-all-charsets-500.woff2
"#]],
);
}
+
+// Custom fonts.css.
+#[test]
+fn fonts_css() {
+ BookTest::from_dir("theme/fonts_css")
+ .check_file_contains("book/index.html", "fonts/fonts.css")
+ .check_file(
+ "book/fonts/fonts.css",
+ str![[r#"
+/*custom*/
+
+"#]],
+ )
+ .check_file("book/fonts/myfont.woff", str![[""]])
+ .check_file_list(
+ "book/fonts",
+ str![[r#"
+book/fonts/fonts.css
+book/fonts/myfont.woff
+"#]],
+ );
+}
diff --git a/tests/testsuite/theme/fonts_css/src/SUMMARY.md b/tests/testsuite/theme/fonts_css/src/SUMMARY.md
new file mode 100644
index 00000000..510ba886
--- /dev/null
+++ b/tests/testsuite/theme/fonts_css/src/SUMMARY.md
@@ -0,0 +1 @@
+- [With Fonts](index.md)
diff --git a/tests/testsuite/theme/fonts_css/theme/fonts/fonts.css b/tests/testsuite/theme/fonts_css/theme/fonts/fonts.css
new file mode 100644
index 00000000..b94b7f50
--- /dev/null
+++ b/tests/testsuite/theme/fonts_css/theme/fonts/fonts.css
@@ -0,0 +1 @@
+/*custom*/
diff --git a/tests/testsuite/theme/fonts_css/theme/fonts/myfont.woff b/tests/testsuite/theme/fonts_css/theme/fonts/myfont.woff
new file mode 100644
index 00000000..e69de29b
From 9a1f983e65e990cbe3586d961b90dac20d7b7102 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 11:12:30 -0700
Subject: [PATCH 57/69] Copy copy-fonts=false no theme to BookTest
---
tests/rendered_output.rs | 13 -------------
tests/testsuite/theme.rs | 18 ++++++++++++++++++
.../theme/copy_fonts_false_no_theme/book.toml | 2 ++
.../copy_fonts_false_no_theme/src/SUMMARY.md | 1 +
4 files changed, 21 insertions(+), 13 deletions(-)
create mode 100644 tests/testsuite/theme/copy_fonts_false_no_theme/book.toml
create mode 100644 tests/testsuite/theme/copy_fonts_false_no_theme/src/SUMMARY.md
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index 20a91825..12b07ad7 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -323,19 +323,6 @@ fn custom_fonts() {
contents.contains("fonts/fonts.css")
};
- // copy-fonts=false, no theme
- // This should generate a deprecation warning.
- let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
- let p = temp.path();
- MDBook::init(p).build().unwrap();
- let config = Config::from_str("output.html.copy-fonts = false").unwrap();
- MDBook::load_with_config(p, config)
- .unwrap()
- .build()
- .unwrap();
- assert!(!has_fonts_css(p));
- assert!(!p.join("book/fonts").exists());
-
// copy-fonts=false with empty fonts.css
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
let p = temp.path();
diff --git a/tests/testsuite/theme.rs b/tests/testsuite/theme.rs
index 9cab6e57..9916c4d2 100644
--- a/tests/testsuite/theme.rs
+++ b/tests/testsuite/theme.rs
@@ -137,3 +137,21 @@ book/fonts/myfont.woff
"#]],
);
}
+
+// copy-fonts=false, no theme, deprecated
+#[test]
+fn copy_fonts_false_no_theme() {
+ BookTest::from_dir("theme/copy_fonts_false_no_theme")
+ .run("build", |cmd| {
+ cmd.expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book): Book building has started
+[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
+[TIMESTAMP] [WARN] (mdbook::renderer::html_handlebars::static_files): output.html.copy-fonts is deprecated.
+This book appears to have copy-fonts=false in book.toml without a fonts.css file.
+Add an empty `theme/fonts/fonts.css` file to squelch this warning.
+
+"#]]);
+ })
+ .check_file_doesnt_contain("book/index.html", "fonts.css")
+ .check_file_list("book/fonts", str![[""]]);
+}
diff --git a/tests/testsuite/theme/copy_fonts_false_no_theme/book.toml b/tests/testsuite/theme/copy_fonts_false_no_theme/book.toml
new file mode 100644
index 00000000..e4929c83
--- /dev/null
+++ b/tests/testsuite/theme/copy_fonts_false_no_theme/book.toml
@@ -0,0 +1,2 @@
+[output.html]
+copy-fonts = false
diff --git a/tests/testsuite/theme/copy_fonts_false_no_theme/src/SUMMARY.md b/tests/testsuite/theme/copy_fonts_false_no_theme/src/SUMMARY.md
new file mode 100644
index 00000000..655a0ded
--- /dev/null
+++ b/tests/testsuite/theme/copy_fonts_false_no_theme/src/SUMMARY.md
@@ -0,0 +1 @@
+- [Intro](index.md)
From bdd16e25fa8660af945185363ee53ea4cd31f150 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 11:13:03 -0700
Subject: [PATCH 58/69] Migrate copy-fonts=false empty fonts.css to BookTest
---
tests/rendered_output.rs | 13 -------------
tests/testsuite/theme.rs | 15 +++++++++++++++
.../book.toml | 2 ++
.../src/SUMMARY.md | 1 +
.../theme/fonts/fonts.css | 0
5 files changed, 18 insertions(+), 13 deletions(-)
create mode 100644 tests/testsuite/theme/copy_fonts_false_with_empty_fonts_css/book.toml
create mode 100644 tests/testsuite/theme/copy_fonts_false_with_empty_fonts_css/src/SUMMARY.md
create mode 100644 tests/testsuite/theme/copy_fonts_false_with_empty_fonts_css/theme/fonts/fonts.css
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index 12b07ad7..cc22e650 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -323,19 +323,6 @@ fn custom_fonts() {
contents.contains("fonts/fonts.css")
};
- // copy-fonts=false with empty fonts.css
- let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
- let p = temp.path();
- MDBook::init(p).build().unwrap();
- write_file(&p.join("theme/fonts"), "fonts.css", b"").unwrap();
- let config = Config::from_str("output.html.copy-fonts = false").unwrap();
- MDBook::load_with_config(p, config)
- .unwrap()
- .build()
- .unwrap();
- assert!(!has_fonts_css(p));
- assert!(!p.join("book/fonts").exists());
-
// copy-fonts=false with fonts theme
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
let p = temp.path();
diff --git a/tests/testsuite/theme.rs b/tests/testsuite/theme.rs
index 9916c4d2..f4b0c7b7 100644
--- a/tests/testsuite/theme.rs
+++ b/tests/testsuite/theme.rs
@@ -155,3 +155,18 @@ Add an empty `theme/fonts/fonts.css` file to squelch this warning.
.check_file_doesnt_contain("book/index.html", "fonts.css")
.check_file_list("book/fonts", str![[""]]);
}
+
+// copy-fonts=false, empty fonts.css
+#[test]
+fn copy_fonts_false_with_empty_fonts_css() {
+ BookTest::from_dir("theme/copy_fonts_false_with_empty_fonts_css")
+ .run("build", |cmd| {
+ cmd.expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book): Book building has started
+[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
+
+"#]]);
+ })
+ .check_file_doesnt_contain("book/index.html", "fonts.css")
+ .check_file_list("book/fonts", str![[""]]);
+}
diff --git a/tests/testsuite/theme/copy_fonts_false_with_empty_fonts_css/book.toml b/tests/testsuite/theme/copy_fonts_false_with_empty_fonts_css/book.toml
new file mode 100644
index 00000000..e4929c83
--- /dev/null
+++ b/tests/testsuite/theme/copy_fonts_false_with_empty_fonts_css/book.toml
@@ -0,0 +1,2 @@
+[output.html]
+copy-fonts = false
diff --git a/tests/testsuite/theme/copy_fonts_false_with_empty_fonts_css/src/SUMMARY.md b/tests/testsuite/theme/copy_fonts_false_with_empty_fonts_css/src/SUMMARY.md
new file mode 100644
index 00000000..655a0ded
--- /dev/null
+++ b/tests/testsuite/theme/copy_fonts_false_with_empty_fonts_css/src/SUMMARY.md
@@ -0,0 +1 @@
+- [Intro](index.md)
diff --git a/tests/testsuite/theme/copy_fonts_false_with_empty_fonts_css/theme/fonts/fonts.css b/tests/testsuite/theme/copy_fonts_false_with_empty_fonts_css/theme/fonts/fonts.css
new file mode 100644
index 00000000..e69de29b
From 707319e004b96455da04f0099033ed9af88b605a Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 11:13:39 -0700
Subject: [PATCH 59/69] Migrate custom fonts with filled fonts.css to BookTest
---
tests/rendered_output.rs | 35 -------------------
tests/testsuite/theme.rs | 21 +++++++++++
.../copy_fonts_false_with_fonts_css/book.toml | 2 ++
.../src/SUMMARY.md | 1 +
.../theme/fonts/fonts.css | 1 +
.../theme/fonts/myfont.woff | 0
6 files changed, 25 insertions(+), 35 deletions(-)
create mode 100644 tests/testsuite/theme/copy_fonts_false_with_fonts_css/book.toml
create mode 100644 tests/testsuite/theme/copy_fonts_false_with_fonts_css/src/SUMMARY.md
create mode 100644 tests/testsuite/theme/copy_fonts_false_with_fonts_css/theme/fonts/fonts.css
create mode 100644 tests/testsuite/theme/copy_fonts_false_with_fonts_css/theme/fonts/myfont.woff
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index cc22e650..696de857 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -305,38 +305,3 @@ fn summary_with_markdown_formatting() {
"# <escaped tag>\n"
);
}
-
-#[test]
-fn custom_fonts() {
- // Tests to ensure custom fonts are copied as expected.
- let actual_files = |path: &Path| -> Vec {
- let mut actual: Vec<_> = path
- .read_dir()
- .unwrap()
- .map(|entry| entry.unwrap().file_name().into_string().unwrap())
- .collect();
- actual.sort();
- actual
- };
- let has_fonts_css = |path: &Path| -> bool {
- let contents = fs::read_to_string(path.join("book/index.html")).unwrap();
- contents.contains("fonts/fonts.css")
- };
-
- // copy-fonts=false with fonts theme
- let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
- let p = temp.path();
- MDBook::init(p).build().unwrap();
- write_file(&p.join("theme/fonts"), "fonts.css", b"/*custom*/").unwrap();
- write_file(&p.join("theme/fonts"), "myfont.woff", b"").unwrap();
- let config = Config::from_str("output.html.copy-fonts = false").unwrap();
- MDBook::load_with_config(p, config)
- .unwrap()
- .build()
- .unwrap();
- assert!(has_fonts_css(p));
- assert_eq!(
- actual_files(&p.join("book/fonts")),
- &["fonts.css", "myfont.woff"]
- );
-}
diff --git a/tests/testsuite/theme.rs b/tests/testsuite/theme.rs
index f4b0c7b7..245952f5 100644
--- a/tests/testsuite/theme.rs
+++ b/tests/testsuite/theme.rs
@@ -170,3 +170,24 @@ fn copy_fonts_false_with_empty_fonts_css() {
.check_file_doesnt_contain("book/index.html", "fonts.css")
.check_file_list("book/fonts", str![[""]]);
}
+
+// copy-fonts=false, fonts.css has contents
+#[test]
+fn copy_fonts_false_with_fonts_css() {
+ BookTest::from_dir("theme/copy_fonts_false_with_fonts_css")
+ .run("build", |cmd| {
+ cmd.expect_stderr(str![[r#"
+[TIMESTAMP] [INFO] (mdbook::book): Book building has started
+[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
+
+"#]]);
+ })
+ .check_file_contains("book/index.html", "fonts.css")
+ .check_file_list(
+ "book/fonts",
+ str![[r#"
+book/fonts/fonts.css
+book/fonts/myfont.woff
+"#]],
+ );
+}
diff --git a/tests/testsuite/theme/copy_fonts_false_with_fonts_css/book.toml b/tests/testsuite/theme/copy_fonts_false_with_fonts_css/book.toml
new file mode 100644
index 00000000..e4929c83
--- /dev/null
+++ b/tests/testsuite/theme/copy_fonts_false_with_fonts_css/book.toml
@@ -0,0 +1,2 @@
+[output.html]
+copy-fonts = false
diff --git a/tests/testsuite/theme/copy_fonts_false_with_fonts_css/src/SUMMARY.md b/tests/testsuite/theme/copy_fonts_false_with_fonts_css/src/SUMMARY.md
new file mode 100644
index 00000000..655a0ded
--- /dev/null
+++ b/tests/testsuite/theme/copy_fonts_false_with_fonts_css/src/SUMMARY.md
@@ -0,0 +1 @@
+- [Intro](index.md)
diff --git a/tests/testsuite/theme/copy_fonts_false_with_fonts_css/theme/fonts/fonts.css b/tests/testsuite/theme/copy_fonts_false_with_fonts_css/theme/fonts/fonts.css
new file mode 100644
index 00000000..b94b7f50
--- /dev/null
+++ b/tests/testsuite/theme/copy_fonts_false_with_fonts_css/theme/fonts/fonts.css
@@ -0,0 +1 @@
+/*custom*/
diff --git a/tests/testsuite/theme/copy_fonts_false_with_fonts_css/theme/fonts/myfont.woff b/tests/testsuite/theme/copy_fonts_false_with_fonts_css/theme/fonts/myfont.woff
new file mode 100644
index 00000000..e69de29b
From 14d412b27940ad9f93cf9eb6536080b4b1118303 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 11:17:40 -0700
Subject: [PATCH 60/69] Migrate check_second_toc_level to BookTest
---
tests/rendered_output.rs | 25 ---------
tests/testsuite/main.rs | 1 +
tests/testsuite/toc.rs | 55 +++++++++++++++++++
tests/testsuite/toc/basic_toc/book.toml | 2 +
tests/testsuite/toc/basic_toc/src/README.md | 1 +
tests/testsuite/toc/basic_toc/src/SUMMARY.md | 23 ++++++++
.../toc/basic_toc/src/deep/a/b/index.md | 1 +
.../toc/basic_toc/src/deep/a/index.md | 1 +
.../testsuite/toc/basic_toc/src/deep/index.md | 1 +
.../toc/basic_toc/src/nested/index.md | 1 +
.../testsuite/toc/basic_toc/src/nested/two.md | 1 +
tests/testsuite/toc/basic_toc/src/prefix1.md | 1 +
tests/testsuite/toc/basic_toc/src/prefix2.md | 1 +
tests/testsuite/toc/basic_toc/src/suffix1.md | 1 +
tests/testsuite/toc/basic_toc/src/suffix2.md | 1 +
15 files changed, 91 insertions(+), 25 deletions(-)
create mode 100644 tests/testsuite/toc.rs
create mode 100644 tests/testsuite/toc/basic_toc/book.toml
create mode 100644 tests/testsuite/toc/basic_toc/src/README.md
create mode 100644 tests/testsuite/toc/basic_toc/src/SUMMARY.md
create mode 100644 tests/testsuite/toc/basic_toc/src/deep/a/b/index.md
create mode 100644 tests/testsuite/toc/basic_toc/src/deep/a/index.md
create mode 100644 tests/testsuite/toc/basic_toc/src/deep/index.md
create mode 100644 tests/testsuite/toc/basic_toc/src/nested/index.md
create mode 100644 tests/testsuite/toc/basic_toc/src/nested/two.md
create mode 100644 tests/testsuite/toc/basic_toc/src/prefix1.md
create mode 100644 tests/testsuite/toc/basic_toc/src/prefix2.md
create mode 100644 tests/testsuite/toc/basic_toc/src/suffix1.md
create mode 100644 tests/testsuite/toc/basic_toc/src/suffix2.md
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index 696de857..c7618335 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -5,7 +5,6 @@ use crate::dummy_book::{assert_contains_strings, DummyBook};
use anyhow::Context;
use mdbook::config::Config;
use mdbook::errors::*;
-use mdbook::utils::fs::write_file;
use mdbook::MDBook;
use pretty_assertions::assert_eq;
use select::document::Document;
@@ -13,8 +12,6 @@ use select::predicate::{Attr, Class, Name, Predicate};
use std::ffi::OsStr;
use std::fs;
use std::path::Path;
-use std::str::FromStr;
-use tempfile::Builder as TempFileBuilder;
use walkdir::{DirEntry, WalkDir};
const BOOK_ROOT: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/dummy_book");
@@ -172,28 +169,6 @@ fn toc_fallback_html() -> Result {
Ok(Document::from(html.as_str()))
}
-#[test]
-fn check_second_toc_level() {
- let doc = toc_js_html().unwrap();
- let mut should_be = Vec::from(TOC_SECOND_LEVEL);
- should_be.sort_unstable();
-
- let pred = descendants!(
- Class("chapter"),
- Name("li"),
- Name("li"),
- Name("a").and(Class("toggle").not())
- );
-
- 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 = toc_js_html().unwrap();
diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs
index 162f850a..cc9bef92 100644
--- a/tests/testsuite/main.rs
+++ b/tests/testsuite/main.rs
@@ -19,6 +19,7 @@ mod rendering;
mod search;
mod test;
mod theme;
+mod toc;
mod prelude {
pub use crate::book_test::BookTest;
diff --git a/tests/testsuite/toc.rs b/tests/testsuite/toc.rs
new file mode 100644
index 00000000..2787b995
--- /dev/null
+++ b/tests/testsuite/toc.rs
@@ -0,0 +1,55 @@
+//! Tests for table of contents (sidebar).
+
+use crate::prelude::*;
+use select::document::Document;
+use select::predicate::{Class, Name, Predicate};
+
+const TOC_SECOND_LEVEL: &[&str] = &[
+ "1.1. Nested Index",
+ "1.2. Nested two",
+ "3.1. Deep Nest 2",
+ "3.1.1. Deep Nest 3",
+];
+
+/// 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)
+ )*
+ };
+}
+
+/// 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() -> Document {
+ let mut test = BookTest::from_dir("toc/basic_toc");
+ test.build();
+ let html = test.toc_js_html();
+ Document::from(html.as_str())
+}
+
+#[test]
+fn check_second_toc_level() {
+ let doc = toc_js_html();
+ let mut should_be = Vec::from(TOC_SECOND_LEVEL);
+ should_be.sort_unstable();
+
+ let pred = descendants!(
+ Class("chapter"),
+ Name("li"),
+ Name("li"),
+ Name("a").and(Class("toggle").not())
+ );
+
+ 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);
+}
diff --git a/tests/testsuite/toc/basic_toc/book.toml b/tests/testsuite/toc/basic_toc/book.toml
new file mode 100644
index 00000000..cebbd270
--- /dev/null
+++ b/tests/testsuite/toc/basic_toc/book.toml
@@ -0,0 +1,2 @@
+[book]
+title = "basic_toc"
diff --git a/tests/testsuite/toc/basic_toc/src/README.md b/tests/testsuite/toc/basic_toc/src/README.md
new file mode 100644
index 00000000..8ad6862f
--- /dev/null
+++ b/tests/testsuite/toc/basic_toc/src/README.md
@@ -0,0 +1 @@
+# With Readme
diff --git a/tests/testsuite/toc/basic_toc/src/SUMMARY.md b/tests/testsuite/toc/basic_toc/src/SUMMARY.md
new file mode 100644
index 00000000..9060ce68
--- /dev/null
+++ b/tests/testsuite/toc/basic_toc/src/SUMMARY.md
@@ -0,0 +1,23 @@
+# Summary
+
+[Prefix 1](prefix1.md)
+[Prefix 2](prefix2.md)
+
+- [With Readme](README.md)
+ - [Nested Index](nested/index.md)
+ - [Nested two](nested/two.md)
+- [Draft]()
+
+---
+
+# Deep Nest
+
+- [Deep Nest 1](deep/index.md)
+ - [Deep Nest 2](deep/a/index.md)
+ - [Deep Nest 3](deep/a/b/index.md)
+ [Deep Nest 4](deep/a/b/c/index.md)
+
+---
+
+[Suffix 1](suffix1.md)
+[Suffix 2](suffix2.md)
diff --git a/tests/testsuite/toc/basic_toc/src/deep/a/b/index.md b/tests/testsuite/toc/basic_toc/src/deep/a/b/index.md
new file mode 100644
index 00000000..2bd51169
--- /dev/null
+++ b/tests/testsuite/toc/basic_toc/src/deep/a/b/index.md
@@ -0,0 +1 @@
+# Deep Nest 3
diff --git a/tests/testsuite/toc/basic_toc/src/deep/a/index.md b/tests/testsuite/toc/basic_toc/src/deep/a/index.md
new file mode 100644
index 00000000..01fcd70c
--- /dev/null
+++ b/tests/testsuite/toc/basic_toc/src/deep/a/index.md
@@ -0,0 +1 @@
+# Deep Nest 2
diff --git a/tests/testsuite/toc/basic_toc/src/deep/index.md b/tests/testsuite/toc/basic_toc/src/deep/index.md
new file mode 100644
index 00000000..847f336d
--- /dev/null
+++ b/tests/testsuite/toc/basic_toc/src/deep/index.md
@@ -0,0 +1 @@
+# Deep Nest 1
diff --git a/tests/testsuite/toc/basic_toc/src/nested/index.md b/tests/testsuite/toc/basic_toc/src/nested/index.md
new file mode 100644
index 00000000..ecbbbc86
--- /dev/null
+++ b/tests/testsuite/toc/basic_toc/src/nested/index.md
@@ -0,0 +1 @@
+# Nested Index
diff --git a/tests/testsuite/toc/basic_toc/src/nested/two.md b/tests/testsuite/toc/basic_toc/src/nested/two.md
new file mode 100644
index 00000000..e600bff7
--- /dev/null
+++ b/tests/testsuite/toc/basic_toc/src/nested/two.md
@@ -0,0 +1 @@
+# Nested two
diff --git a/tests/testsuite/toc/basic_toc/src/prefix1.md b/tests/testsuite/toc/basic_toc/src/prefix1.md
new file mode 100644
index 00000000..4ddb4143
--- /dev/null
+++ b/tests/testsuite/toc/basic_toc/src/prefix1.md
@@ -0,0 +1 @@
+# Prefix 1
diff --git a/tests/testsuite/toc/basic_toc/src/prefix2.md b/tests/testsuite/toc/basic_toc/src/prefix2.md
new file mode 100644
index 00000000..11ff4958
--- /dev/null
+++ b/tests/testsuite/toc/basic_toc/src/prefix2.md
@@ -0,0 +1 @@
+# Prefix 2
diff --git a/tests/testsuite/toc/basic_toc/src/suffix1.md b/tests/testsuite/toc/basic_toc/src/suffix1.md
new file mode 100644
index 00000000..59a81900
--- /dev/null
+++ b/tests/testsuite/toc/basic_toc/src/suffix1.md
@@ -0,0 +1 @@
+# Suffix 1
diff --git a/tests/testsuite/toc/basic_toc/src/suffix2.md b/tests/testsuite/toc/basic_toc/src/suffix2.md
new file mode 100644
index 00000000..2bf99ebe
--- /dev/null
+++ b/tests/testsuite/toc/basic_toc/src/suffix2.md
@@ -0,0 +1 @@
+# Suffix 2
From efc5ee44491ced2ac77b07af80e9af75d8f84352 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 11:19:21 -0700
Subject: [PATCH 61/69] Migrate check_first_toc_level to BookTest
---
tests/rendered_output.rs | 23 -----------------------
tests/testsuite/toc.rs | 31 +++++++++++++++++++++++++++++++
2 files changed, 31 insertions(+), 23 deletions(-)
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index c7618335..a2a1ebfb 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -169,29 +169,6 @@ fn toc_fallback_html() -> Result {
Ok(Document::from(html.as_str()))
}
-#[test]
-fn check_first_toc_level() {
- let doc = toc_js_html().unwrap();
- let mut should_be = Vec::from(TOC_TOP_LEVEL);
-
- should_be.extend(TOC_SECOND_LEVEL);
- should_be.sort_unstable();
-
- let pred = descendants!(
- Class("chapter"),
- Name("li"),
- Name("a").and(Class("toggle").not())
- );
-
- 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 = toc_js_html().unwrap();
diff --git a/tests/testsuite/toc.rs b/tests/testsuite/toc.rs
index 2787b995..38d4c4f9 100644
--- a/tests/testsuite/toc.rs
+++ b/tests/testsuite/toc.rs
@@ -4,6 +4,14 @@ use crate::prelude::*;
use select::document::Document;
use select::predicate::{Class, Name, Predicate};
+const TOC_TOP_LEVEL: &[&str] = &[
+ "1. With Readme",
+ "3. Deep Nest 1",
+ "Prefix 1",
+ "Prefix 2",
+ "Suffix 1",
+ "Suffix 2",
+];
const TOC_SECOND_LEVEL: &[&str] = &[
"1.1. Nested Index",
"1.2. Nested two",
@@ -53,3 +61,26 @@ fn check_second_toc_level() {
assert_eq!(children_of_children, should_be);
}
+
+#[test]
+fn check_first_toc_level() {
+ let doc = toc_js_html();
+ let mut should_be = Vec::from(TOC_TOP_LEVEL);
+
+ should_be.extend(TOC_SECOND_LEVEL);
+ should_be.sort_unstable();
+
+ let pred = descendants!(
+ Class("chapter"),
+ Name("li"),
+ Name("a").and(Class("toggle").not())
+ );
+
+ let mut children: Vec<_> = doc
+ .find(pred)
+ .map(|elem| elem.text().trim().to_string())
+ .collect();
+ children.sort();
+
+ assert_eq!(children, should_be);
+}
From 20f71af4cba1cb557ffce91b4f6537b4344f0ee3 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 11:20:04 -0700
Subject: [PATCH 62/69] Migrate check_spacers to BookTest
---
tests/rendered_output.rs | 23 -----------------------
tests/testsuite/toc.rs | 11 +++++++++++
2 files changed, 11 insertions(+), 23 deletions(-)
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index a2a1ebfb..9b8d3579 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -89,18 +89,6 @@ fn chapter_content_appears_in_rendered_document() {
}
}
-/// 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]
@@ -169,17 +157,6 @@ fn toc_fallback_html() -> Result {
Ok(Document::from(html.as_str()))
}
-#[test]
-fn check_spacers() {
- let doc = toc_js_html().unwrap();
- let should_be = 2;
-
- let num_spacers = doc
- .find(Class("chapter").descendant(Name("li").and(Class("spacer"))))
- .count();
- assert_eq!(num_spacers, should_be);
-}
-
// don't use target="_parent" in JS
#[test]
fn check_link_target_js() {
diff --git a/tests/testsuite/toc.rs b/tests/testsuite/toc.rs
index 38d4c4f9..3cc32e77 100644
--- a/tests/testsuite/toc.rs
+++ b/tests/testsuite/toc.rs
@@ -84,3 +84,14 @@ fn check_first_toc_level() {
assert_eq!(children, should_be);
}
+
+#[test]
+fn check_spacers() {
+ let doc = toc_js_html();
+ let should_be = 2;
+
+ let num_spacers = doc
+ .find(Class("chapter").descendant(Name("li").and(Class("spacer"))))
+ .count();
+ assert_eq!(num_spacers, should_be);
+}
From 5f2453e4463f224a8418ec50d502867552bb4c0d Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 11:21:01 -0700
Subject: [PATCH 63/69] Migrate check_link_target_js to BookTest
---
tests/rendered_output.rs | 37 -------------------------------------
tests/testsuite/toc.rs | 17 ++++++++++++++++-
2 files changed, 16 insertions(+), 38 deletions(-)
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index 9b8d3579..f27c436e 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -120,28 +120,6 @@ 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 {
@@ -157,21 +135,6 @@ fn toc_fallback_html() -> Result {
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() {
diff --git a/tests/testsuite/toc.rs b/tests/testsuite/toc.rs
index 3cc32e77..4d4743a8 100644
--- a/tests/testsuite/toc.rs
+++ b/tests/testsuite/toc.rs
@@ -2,7 +2,7 @@
use crate::prelude::*;
use select::document::Document;
-use select::predicate::{Class, Name, Predicate};
+use select::predicate::{Attr, Class, Name, Predicate};
const TOC_TOP_LEVEL: &[&str] = &[
"1. With Readme",
@@ -95,3 +95,18 @@ fn check_spacers() {
.count();
assert_eq!(num_spacers, should_be);
}
+
+// don't use target="_parent" in JS
+#[test]
+fn check_link_target_js() {
+ let doc = toc_js_html();
+
+ 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);
+}
From 69972080f055de0b97be1318835041cf616ab035 Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 11:23:56 -0700
Subject: [PATCH 64/69] Migrate check_link_target_fallback to BookTest
---
tests/rendered_output.rs | 55 ----------------------------------------
tests/testsuite/toc.rs | 32 +++++++++++++++++++++++
2 files changed, 32 insertions(+), 55 deletions(-)
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index f27c436e..c2e7c600 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -2,37 +2,15 @@ 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() {
@@ -120,39 +98,6 @@ fn entry_ends_with(entry: &DirEntry, ending: &str) -> bool {
entry.file_name().to_string_lossy().ends_with(ending)
}
-/// 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 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();
diff --git a/tests/testsuite/toc.rs b/tests/testsuite/toc.rs
index 4d4743a8..77021f66 100644
--- a/tests/testsuite/toc.rs
+++ b/tests/testsuite/toc.rs
@@ -1,8 +1,11 @@
//! Tests for table of contents (sidebar).
use crate::prelude::*;
+use anyhow::Context;
+use mdbook::errors::*;
use select::document::Document;
use select::predicate::{Attr, Class, Name, Predicate};
+use std::fs;
const TOC_TOP_LEVEL: &[&str] = &[
"1. With Readme",
@@ -40,6 +43,17 @@ fn toc_js_html() -> Document {
Document::from(html.as_str())
}
+/// 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 mut test = BookTest::from_dir("toc/basic_toc");
+ test.build();
+
+ let toc_path = test.dir.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()))
+}
+
#[test]
fn check_second_toc_level() {
let doc = toc_js_html();
@@ -110,3 +124,21 @@ fn check_link_target_js() {
.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()
+ );
+}
From d65d2b2a8e7b713f44864e60efb2f4e3cb7455eb Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 11:25:13 -0700
Subject: [PATCH 65/69] Migrate summary_with_markdown_formatting to BookTest
---
tests/rendered_output.rs | 36 ----------------
tests/testsuite/toc.rs | 43 +++++++++++++++++++
.../src/SUMMARY.md | 6 +++
3 files changed, 49 insertions(+), 36 deletions(-)
create mode 100644 tests/testsuite/toc/summary_with_markdown_formatting/src/SUMMARY.md
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
index c2e7c600..7cb1888d 100644
--- a/tests/rendered_output.rs
+++ b/tests/rendered_output.rs
@@ -106,39 +106,3 @@ fn example_book_can_build() {
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#"1. Italic code *escape* `escape2` "#,
- r#"2. Soft line break "#,
- r#"3. <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"
- );
-}
diff --git a/tests/testsuite/toc.rs b/tests/testsuite/toc.rs
index 77021f66..eafbdc48 100644
--- a/tests/testsuite/toc.rs
+++ b/tests/testsuite/toc.rs
@@ -142,3 +142,46 @@ fn check_link_target_fallback() {
TOC_TOP_LEVEL.len() + TOC_SECOND_LEVEL.len()
);
}
+
+// Checks formatting of summary names with inline elements.
+#[test]
+fn summary_with_markdown_formatting() {
+ BookTest::from_dir("toc/summary_with_markdown_formatting")
+ .check_toc_js(str![[r#"
+
+
+
+1. Italic code *escape* `escape2`
+
+
+
+2. Soft line break
+
+
+
+3. <escaped tag>
+
+
+"#]])
+ .check_file(
+ "src/formatted-summary.md",
+ str![[r#"
+# Italic code *escape* `escape2`
+
+"#]],
+ )
+ .check_file(
+ "src/soft.md",
+ str![[r#"
+# Soft line break
+
+"#]],
+ )
+ .check_file(
+ "src/escaped-tag.md",
+ str![[r#"
+# <escaped tag>
+
+"#]],
+ );
+}
diff --git a/tests/testsuite/toc/summary_with_markdown_formatting/src/SUMMARY.md b/tests/testsuite/toc/summary_with_markdown_formatting/src/SUMMARY.md
new file mode 100644
index 00000000..336218d8
--- /dev/null
+++ b/tests/testsuite/toc/summary_with_markdown_formatting/src/SUMMARY.md
@@ -0,0 +1,6 @@
+# Summary formatting tests
+
+- [*Italic* `code` \*escape\* \`escape2\`](formatted-summary.md)
+- [Soft
+line break](soft.md)
+- [\](escaped-tag.md)
From ae2fc9a9d13cc9229425f7c57e77ce419091bf3e Mon Sep 17 00:00:00 2001
From: Eric Huss
Date: Tue, 22 Apr 2025 11:34:50 -0700
Subject: [PATCH 66/69] Remove remaining rendered tests
I don't think these exercise anything in particular that aren't
necessarily covered by other things. If this ends up exposing a lack
of coverage somewhere, I would prefer to add more focused test for
specific things.
---
tests/rendered_output.rs | 108 ---------------------------------------
1 file changed, 108 deletions(-)
delete mode 100644 tests/rendered_output.rs
diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs
deleted file mode 100644
index 7cb1888d..00000000
--- a/tests/rendered_output.rs
+++ /dev/null
@@ -1,108 +0,0 @@
-mod dummy_book;
-
-use crate::dummy_book::{assert_contains_strings, DummyBook};
-
-use mdbook::config::Config;
-use mdbook::MDBook;
-use pretty_assertions::assert_eq;
-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");
-
-#[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##"