diff --git a/.gitignore b/.gitignore index 34e1d9b10f..1865ff9727 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ guide/book .vscode tests/dummy_book/book/ test_book/book/ +tests/testsuite/*/*/book/ # Ignore Jetbrains specific files. .idea/ diff --git a/Cargo.lock b/Cargo.lock index 082543f476..ef4c50eff1 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" @@ -110,22 +132,6 @@ version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" -[[package]] -name = "assert_cmd" -version = "2.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" -dependencies = [ - "anstyle", - "bstr", - "doc-comment", - "libc", - "predicates", - "predicates-core", - "predicates-tree", - "wait-timeout", -] - [[package]] name = "autocfg" version = "1.4.0" @@ -288,6 +294,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" @@ -427,12 +442,6 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" -[[package]] -name = "difflib" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" - [[package]] name = "digest" version = "0.10.7" @@ -455,10 +464,10 @@ dependencies = [ ] [[package]] -name = "doc-comment" -version = "0.3.3" +name = "dunce" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "elasticlunr-rs" @@ -529,15 +538,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "float-cmp" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" -dependencies = [ - "num-traits", -] - [[package]] name = "fnv" version = "1.0.7" @@ -737,6 +737,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" @@ -1209,7 +1218,6 @@ version = "0.4.48" dependencies = [ "ammonia", "anyhow", - "assert_cmd", "chrono", "clap", "clap_complete", @@ -1226,7 +1234,6 @@ dependencies = [ "once_cell", "opener", "pathdiff", - "predicates", "pretty_assertions", "pulldown-cmark 0.10.3", "regex", @@ -1236,6 +1243,7 @@ dependencies = [ "serde_json", "sha2", "shlex", + "snapbox", "tempfile", "tokio", "toml", @@ -1615,36 +1623,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" -[[package]] -name = "predicates" -version = "3.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" -dependencies = [ - "anstyle", - "difflib", - "float-cmp", - "normalize-line-endings", - "predicates-core", - "regex", -] - -[[package]] -name = "predicates-core" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" - -[[package]] -name = "predicates-tree" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" -dependencies = [ - "predicates-core", - "termtree", -] - [[package]] name = "pretty_assertions" version = "1.4.1" @@ -1920,6 +1898,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 +1931,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" @@ -2063,12 +2078,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "termtree" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" - [[package]] name = "thiserror" version = "1.0.69" @@ -2261,6 +2270,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 +2299,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" @@ -2302,15 +2323,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "wait-timeout" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" -dependencies = [ - "libc", -] - [[package]] name = "walkdir" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index 4c43d0ebf9..e6d881bc82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,10 +64,9 @@ elasticlunr-rs = { version = "3.0.2", optional = true } ammonia = { version = "4.0.0", optional = true } [dev-dependencies] -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/examples/nop-preprocessor.rs b/examples/nop-preprocessor.rs index bbb82a46bb..f194991a8e 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); } } diff --git a/tests/alternative_backends.rs b/tests/alternative_backends.rs deleted file mode 100644 index ecd2503130..0000000000 --- a/tests/alternative_backends.rs +++ /dev/null @@ -1,163 +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, 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); - - md.build().unwrap_err(); -} - -#[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); - assert!(md.build().is_ok()); -} - -#[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; - 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()); -} - -#[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 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 fail_cmd() -> &'static str { - if cfg!(windows) { - r#"cmd.exe /c "exit 1""# - } else { - "false" - } -} - -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(); - let status = std::process::Command::new("rustc") - .arg(rs) - .current_dir(temp) - .status() - .expect("rustc should run"); - assert!(status.success()); -} diff --git a/tests/build_process.rs b/tests/build_process.rs deleted file mode 100644 index 10d0b4a9a8..0000000000 --- a/tests/build_process.rs +++ /dev/null @@ -1,78 +0,0 @@ -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}; - -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 { - fn name(&self) -> &str { - "dummy" - } - - fn render(&self, _ctx: &RenderContext) -> Result<()> { - let mut inner = self.0.lock().unwrap(); - inner.run_count += 1; - Ok(()) - } -} - -#[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(); - - let temp = DummyBook::new().build().unwrap(); - let cfg = Config::default(); - - let mut book = MDBook::load_with_config(temp.path(), cfg).unwrap(); - book.with_renderer(Spy(Arc::clone(&spy))); - book.build().unwrap(); - - let inner = spy.lock().unwrap(); - assert_eq!(inner.run_count, 1); -} diff --git a/tests/cli/build.rs b/tests/cli/build.rs deleted file mode 100644 index 0daba9d727..0000000000 --- a/tests/cli/build.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::cli::cmd::mdbook_cmd; -use crate::dummy_book::DummyBook; - -#[test] -fn mdbook_cli_dummy_book_generates_index_html() { - let temp = DummyBook::new().build().unwrap(); - - // doesn't exist before - assert!(!temp.path().join("book").exists()); - - let mut cmd = mdbook_cmd(); - cmd.arg("build").current_dir(temp.path()); - cmd.assert() - .success() - .stderr( - predicates::str::is_match(r##"Stack depth exceeded in first[\\/]recursive.md."##) - .unwrap(), - ) - .stderr(predicates::str::contains( - r##"[INFO] (mdbook::book): Running the html backend"##, - )); - - // exists afterward - assert!(temp.path().join("book").exists()); - - let index_file = temp.path().join("book/index.html"); - assert!(index_file.exists()); -} diff --git a/tests/cli/cmd.rs b/tests/cli/cmd.rs deleted file mode 100644 index a612f3ad23..0000000000 --- a/tests/cli/cmd.rs +++ /dev/null @@ -1,7 +0,0 @@ -use assert_cmd::Command; - -pub(crate) fn mdbook_cmd() -> Command { - let mut cmd = Command::cargo_bin("mdbook").unwrap(); - cmd.env_remove("RUST_LOG"); - cmd -} diff --git a/tests/cli/init.rs b/tests/cli/init.rs deleted file mode 100644 index 51c2bfa389..0000000000 --- a/tests/cli/init.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::cli::cmd::mdbook_cmd; -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 -#[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 deleted file mode 100644 index 152b4ee91b..0000000000 --- a/tests/cli/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod build; -mod cmd; -mod init; -mod test; diff --git a/tests/cli/test.rs b/tests/cli/test.rs deleted file mode 100644 index 56e0f15955..0000000000 --- a/tests/cli/test.rs +++ /dev/null @@ -1,46 +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()); -} - -#[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/cli_tests.rs b/tests/cli_tests.rs deleted file mode 100644 index 0b005d0197..0000000000 --- a/tests/cli_tests.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod cli; -mod dummy_book; diff --git a/tests/custom_preprocessors.rs b/tests/custom_preprocessors.rs deleted file mode 100644 index c32e94d35d..0000000000 --- a/tests/custom_preprocessors.rs +++ /dev/null @@ -1,68 +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); -} - -#[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"# - ) - ); -} - -#[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/dummy_book/mod.rs b/tests/dummy_book/mod.rs deleted file mode 100644 index f91ed9f075..0000000000 --- a/tests/dummy_book/mod.rs +++ /dev/null @@ -1,145 +0,0 @@ -//! This will create an entire book in a temporary directory using some -//! dummy contents from the `tests/dummy-book/` directory. - -// Not all features are used in all test crates, so... -#![allow(dead_code, unused_variables, unused_imports, unused_extern_crates)] - -use anyhow::Context; -use mdbook::errors::*; -use mdbook::MDBook; -use std::fs::{self, File}; -use std::io::{Read, Write}; -use std::path::Path; -use tempfile::{Builder as TempFileBuilder, TempDir}; -use walkdir::WalkDir; - -/// Create a dummy book in a temporary directory, using the contents of -/// `SUMMARY_MD` as a guide. -/// -/// The "Nested Chapter" file contains a code block with a single -/// `assert!($TEST_STATUS)`. If you want to check MDBook's testing -/// functionality, `$TEST_STATUS` can be substitute for either `true` or -/// `false`. This is done using the `passing_test` parameter. -#[derive(Clone, Debug, PartialEq)] -pub struct DummyBook { - passing_test: bool, -} - -impl DummyBook { - /// Create a new `DummyBook` with all the defaults. - pub fn new() -> DummyBook { - DummyBook { passing_test: true } - } - - /// Whether the doc-test included in the "Nested Chapter" should pass or - /// fail (it passes by default). - pub fn with_passing_test(&mut self, test_passes: bool) -> &mut DummyBook { - self.passing_test = test_passes; - self - } - - /// Write a book to a temporary directory using the provided settings. - pub fn build(&self) -> Result { - let temp = TempFileBuilder::new() - .prefix("dummy_book-") - .tempdir() - .with_context(|| "Unable to create temp directory")?; - - let dummy_book_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/dummy_book"); - recursive_copy(&dummy_book_root, temp.path()).with_context(|| { - "Couldn't copy files into a \ - temporary directory" - })?; - - let sub_pattern = if self.passing_test { "true" } else { "false" }; - let files_containing_tests = [ - "src/first/nested.md", - "src/first/nested-test.rs", - "src/first/nested-test-with-anchors.rs", - "src/first/partially-included-test.rs", - "src/first/partially-included-test-with-anchors.rs", - ]; - for file in &files_containing_tests { - let path_containing_tests = temp.path().join(file); - replace_pattern_in_file(&path_containing_tests, "$TEST_STATUS", sub_pattern)?; - } - - Ok(temp) - } -} - -fn replace_pattern_in_file(filename: &Path, from: &str, to: &str) -> Result<()> { - let contents = fs::read_to_string(filename)?; - File::create(filename)?.write_all(contents.replace(from, to).as_bytes())?; - - Ok(()) -} - -/// Read the contents of the provided file into memory and then iterate through -/// the list of strings asserting that the file contains all of them. -pub fn assert_contains_strings>(filename: P, strings: &[&str]) { - let filename = filename.as_ref(); - let content = fs::read_to_string(filename).expect("Couldn't read the file's contents"); - - for s in strings { - assert!( - content.contains(s), - "Searching for {:?} in {}\n\n{}", - s, - filename.display(), - content - ); - } -} - -pub fn assert_doesnt_contain_strings>(filename: P, strings: &[&str]) { - let filename = filename.as_ref(); - let content = fs::read_to_string(filename).expect("Couldn't read the file's contents"); - - for s in strings { - assert!( - !content.contains(s), - "Found {:?} in {}\n\n{}", - s, - filename.display(), - content - ); - } -} - -/// Recursively copy an entire directory tree to somewhere else (a la `cp -r`). -fn recursive_copy, B: AsRef>(from: A, to: B) -> Result<()> { - let from = from.as_ref(); - let to = to.as_ref(); - - for entry in WalkDir::new(from) { - let entry = entry.with_context(|| "Unable to inspect directory entry")?; - - let original_location = entry.path(); - let relative = original_location - .strip_prefix(from) - .expect("`original_location` is inside the `from` directory"); - let new_location = to.join(relative); - - if original_location.is_file() { - if let Some(parent) = new_location.parent() { - fs::create_dir_all(parent).with_context(|| "Couldn't create directory")?; - } - - fs::copy(original_location, &new_location) - .with_context(|| "Unable to copy file contents")?; - } - } - - Ok(()) -} - -pub fn new_copy_of_example_book() -> Result { - let temp = TempFileBuilder::new().prefix("guide").tempdir()?; - - let guide = Path::new(env!("CARGO_MANIFEST_DIR")).join("guide"); - - recursive_copy(guide, temp.path())?; - - Ok(temp) -} diff --git a/tests/dummy_book/src/README.md b/tests/dummy_book/src/README.md deleted file mode 100644 index 7d946e35d1..0000000000 --- a/tests/dummy_book/src/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Dummy Book - -This file is just here to cause the index preprocessor to run. - -Does a pretty good job, too. \ No newline at end of file diff --git a/tests/dummy_book/src/first/includes.md b/tests/dummy_book/src/first/includes.md deleted file mode 100644 index a5a2fef1f3..0000000000 --- a/tests/dummy_book/src/first/includes.md +++ /dev/null @@ -1,3 +0,0 @@ -# Includes - -{{#include ../SUMMARY.md::}} \ No newline at end of file diff --git a/tests/dummy_book/src/first/nested-test-with-anchors.rs b/tests/dummy_book/src/first/nested-test-with-anchors.rs deleted file mode 100644 index 783ab14de3..0000000000 --- a/tests/dummy_book/src/first/nested-test-with-anchors.rs +++ /dev/null @@ -1,11 +0,0 @@ -// The next line will cause a `testing` test to fail if the anchor feature is broken in such a way -// that the whole file gets mistakenly included. -assert!(!$TEST_STATUS); - -// ANCHOR: myanchor -// ANCHOR: unendinganchor -// The next line will cause a `rendered_output` test to fail if the anchor feature is broken in -// such a way that the content between anchors isn't included. -// unique-string-for-anchor-test -assert!($TEST_STATUS); -// ANCHOR_END: myanchor diff --git a/tests/dummy_book/src/first/nested-test.rs b/tests/dummy_book/src/first/nested-test.rs deleted file mode 100644 index 2bc46e01a9..0000000000 --- a/tests/dummy_book/src/first/nested-test.rs +++ /dev/null @@ -1 +0,0 @@ -assert!($TEST_STATUS); diff --git a/tests/dummy_book/src/first/nested.md b/tests/dummy_book/src/first/nested.md deleted file mode 100644 index ae90763a06..0000000000 --- a/tests/dummy_book/src/first/nested.md +++ /dev/null @@ -1,31 +0,0 @@ -# Nested Chapter - -This file has some testable code. - -```rust -assert!($TEST_STATUS); -``` - -## Some Section - -```rust -{{#include nested-test.rs}} -``` - -## Anchors include the part of a file between special comments - -```rust -{{#include nested-test-with-anchors.rs:myanchor}} -``` - -## 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/dummy_book/src/intro.md b/tests/dummy_book/src/intro.md deleted file mode 100644 index 1990ef58c7..0000000000 --- a/tests/dummy_book/src/intro.md +++ /dev/null @@ -1,3 +0,0 @@ -# Introduction - -Here's some interesting text... \ No newline at end of file diff --git a/tests/dummy_book/src/second.md b/tests/dummy_book/src/second.md deleted file mode 100644 index adf4fca62e..0000000000 --- a/tests/dummy_book/src/second.md +++ /dev/null @@ -1,5 +0,0 @@ -# Second Chapter - -This makes sure you can insert runnable Rust files. - -{{#playground example.rs}} diff --git a/tests/dummy_book/src2/README.md b/tests/dummy_book/src2/README.md deleted file mode 100644 index ba23d94ef1..0000000000 --- a/tests/dummy_book/src2/README.md +++ /dev/null @@ -1 +0,0 @@ -# Root README diff --git a/tests/dummy_book/src2/SUMMARY.md b/tests/dummy_book/src2/SUMMARY.md deleted file mode 100644 index 6b279fc480..0000000000 --- a/tests/dummy_book/src2/SUMMARY.md +++ /dev/null @@ -1,7 +0,0 @@ -# This dummy book is for testing the conversion of README.md to index.html by IndexPreprocessor - -[Root README](README.md) - -- [1st README](first/README.md) -- [2nd README](second/README.md) - - [2nd index](second/index.md) diff --git a/tests/dummy_book/src2/first/README.md b/tests/dummy_book/src2/first/README.md deleted file mode 100644 index d062d2ccd3..0000000000 --- a/tests/dummy_book/src2/first/README.md +++ /dev/null @@ -1 +0,0 @@ -# First README diff --git a/tests/dummy_book/src2/second/README.md b/tests/dummy_book/src2/second/README.md deleted file mode 100644 index be81f856fd..0000000000 --- a/tests/dummy_book/src2/second/README.md +++ /dev/null @@ -1 +0,0 @@ -# Second README diff --git a/tests/dummy_book/src2/second/index.md b/tests/dummy_book/src2/second/index.md deleted file mode 100644 index f23274504d..0000000000 --- a/tests/dummy_book/src2/second/index.md +++ /dev/null @@ -1 +0,0 @@ -# Second index diff --git a/tests/init.rs b/tests/init.rs deleted file mode 100644 index ae064b31a2..0000000000 --- a/tests/init.rs +++ /dev/null @@ -1,157 +0,0 @@ -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; - -/// 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] -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] -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 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(); - 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/parse_existing_summary_files.rs b/tests/parse_existing_summary_files.rs deleted file mode 100644 index 418ec31fee..0000000000 --- a/tests/parse_existing_summary_files.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! Some integration tests to make sure the `SUMMARY.md` parser can deal with -//! some real-life examples. - -use mdbook::book; -use std::fs::File; -use std::io::Read; -use std::path::Path; - -macro_rules! summary_md_test { - ($name:ident, $filename:expr) => { - #[test] - fn $name() { - env_logger::try_init().ok(); - - let filename = Path::new(env!("CARGO_MANIFEST_DIR")) - .join("tests") - .join("summary_md_files") - .join($filename); - - if !filename.exists() { - panic!("{} Doesn't exist", filename.display()); - } - - let mut content = String::new(); - File::open(&filename) - .unwrap() - .read_to_string(&mut content) - .unwrap(); - - if let Err(e) = book::parse_summary(&content) { - eprintln!("Error parsing {}", filename.display()); - eprintln!(); - eprintln!("{:?}", e); - panic!(); - } - } - }; -} - -summary_md_test!(rust_by_example, "rust_by_example.md"); -summary_md_test!(rust_ffi_guide, "rust_ffi_guide.md"); -summary_md_test!(example_book, "example_book.md"); -summary_md_test!(the_book_2nd_edition, "the_book-2nd_edition.md"); diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs deleted file mode 100644 index df1750ee8b..0000000000 --- a/tests/rendered_output.rs +++ /dev/null @@ -1,1080 +0,0 @@ -mod dummy_book; - -use crate::dummy_book::{assert_contains_strings, assert_doesnt_contain_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 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; -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", -]; - -/// 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(); - let md = MDBook::load(temp.path()).unwrap(); - - assert!(!temp.path().join("book").exists()); - md.build().unwrap(); - - assert!(temp.path().join("book").exists()); - let index_file = md.build_dir_for("html").join("index.html"); - assert!(index_file.exists()); -} - -#[test] -fn check_correct_cross_links_in_nested_dir() { - let temp = DummyBook::new().build().unwrap(); - let md = MDBook::load(temp.path()).unwrap(); - md.build().unwrap(); - - let first = temp.path().join("book").join("first"); - - assert_contains_strings( - first.join("index.html"), - &[r##"

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

"##], - ); -} - -#[test] -fn 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##"Some image"##, - r##"fragment link"##, - r##"HTML Link"##, - r##"raw html"##, - ], - ); -} - -#[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(); - 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 anchors_include_text_between_but_not_anchor_comments() { - let temp = DummyBook::new().build().unwrap(); - let md = MDBook::load(temp.path()).unwrap(); - md.build().unwrap(); - - let nested = temp.path().join("book/first/nested.html"); - let text_between_anchors = vec!["unique-string-for-anchor-test"]; - let anchor_text = vec!["ANCHOR"]; - - assert_contains_strings(nested.clone(), &text_between_anchors); - assert_doesnt_contain_strings(nested, &anchor_text); -} - -#[test] -fn rustdoc_include_hides_the_unspecified_part_of_the_file() { - let temp = DummyBook::new().build().unwrap(); - let md = MDBook::load(temp.path()).unwrap(); - md.build().unwrap(); - - let nested = temp.path().join("book/first/nested.html"); - 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![ - ("index.html", "This file is just here to cause the"), - ("intro.html", "Here's some interesting text"), - ("second.html", "Second Chapter"), - ("first/nested.html", "testable code"), - ("first/index.html", "more text"), - ("conclusion.html", "Conclusion"), - ]; - - let temp = DummyBook::new().build().unwrap(); - let md = MDBook::load(temp.path()).unwrap(); - md.build().unwrap(); - - let destination = temp.path().join("book"); - - for (filename, text) in content { - let path = destination.join(filename); - assert_contains_strings(path, &[text]); - } -} - -/// Apply a series of predicates to some root predicate, where each -/// successive predicate is the descendant of the last one. Similar to how you -/// might do `ul.foo li a` in CSS to access all anchor tags in the `foo` list. -macro_rules! descendants { - ($root:expr, $($child:expr),*) => { - $root - $( - .descendant($child) - )* - }; -} - -/// Make sure that all `*.md` files (excluding `SUMMARY.md`) were rendered -/// and placed in the `book` directory with their extensions set to `*.html`. -#[test] -fn chapter_files_were_rendered_to_html() { - let temp = DummyBook::new().build().unwrap(); - let src = Path::new(BOOK_ROOT).join("src"); - - let chapter_files = WalkDir::new(&src) - .into_iter() - .filter_entry(|entry| entry_ends_with(entry, ".md")) - .filter_map(std::result::Result::ok) - .map(|entry| entry.path().to_path_buf()) - .filter(|path| path.file_name().and_then(OsStr::to_str) != Some("SUMMARY.md")); - - for chapter in chapter_files { - let rendered_location = temp - .path() - .join(chapter.strip_prefix(&src).unwrap()) - .with_extension("html"); - assert!( - rendered_location.exists(), - "{} doesn't exits", - rendered_location.display() - ); - } -} - -fn entry_ends_with(entry: &DirEntry, ending: &str) -> bool { - entry.file_name().to_string_lossy().ends_with(ending) -} - -/// Read the TOC (`book/toc.js`) nested HTML and expose it as a DOM which we -/// can search with the `select` crate -fn toc_js_html() -> Result { - let temp = DummyBook::new() - .build() - .with_context(|| "Couldn't create the dummy book")?; - MDBook::load(temp.path())? - .build() - .with_context(|| "Book building failed")?; - - let toc_path = temp.path().join("book").join("toc.js"); - let html = fs::read_to_string(toc_path).with_context(|| "Unable to read index.html")?; - for line in html.lines() { - if let Some(left) = line.strip_prefix(" this.innerHTML = '") { - if let Some(html) = left.strip_suffix("';") { - return Ok(Document::from(html)); - } - } - } - panic!("cannot find toc in file") -} - -/// Read the TOC fallback (`book/toc.html`) HTML and expose it as a DOM which we -/// can search with the `select` crate -fn toc_fallback_html() -> Result { - let temp = DummyBook::new() - .build() - .with_context(|| "Couldn't create the dummy book")?; - MDBook::load(temp.path())? - .build() - .with_context(|| "Book building failed")?; - - let toc_path = temp.path().join("book").join("toc.html"); - let html = fs::read_to_string(toc_path).with_context(|| "Unable to read index.html")?; - Ok(Document::from(html.as_str())) -} - -#[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(); - 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(); - 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() { - let doc = toc_js_html().unwrap(); - - let num_parent_links = doc - .find( - Class("chapter") - .descendant(Name("li")) - .descendant(Name("a").and(Attr("target", "_parent"))), - ) - .count(); - assert_eq!(num_parent_links, 0); -} - -// don't use target="_parent" in IFRAME -#[test] -fn check_link_target_fallback() { - let doc = toc_fallback_html().unwrap(); - - let num_parent_links = doc - .find( - Class("chapter") - .descendant(Name("li")) - .descendant(Name("a").and(Attr("target", "_parent"))), - ) - .count(); - assert_eq!( - num_parent_links, - TOC_TOP_LEVEL.len() + TOC_SECOND_LEVEL.len() - ); -} - -/// 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() { - 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] -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}}"]); -} - -/// 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##"

Summary

"##, - ">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() { - 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(); - - let md = MDBook::load(example_book_dir.path()).unwrap(); - - 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(); - 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(); - 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(); - 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!"]); -} - -#[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 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##"1"##, - r##"2"##, - r##"2"##, - r##"
-
  1. -

    This is a footnote. ↩2

    -
  2. -
  3. -

    A longer footnote. -With multiple lines. Link to unicode. -With a reference inside.1 ↩2

    -
  4. -
  5. -
      -
    1. Item one -
        -
      1. Sub-item
      2. -
      -
    2. -
    3. Item two
    4. -
    -
  6. -
  7. One

    Two

    Three

    -
  8. -
  9. -

    Testing footnote id with special characters.

    -
  10. -
  11. -

    This is defined before it is referred to.

    -
  12. -
-"##, - ], - ); - 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(); - 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(); - 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""#, - ], - ); -} - -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() { - let temp = DummyBook::new().build().unwrap(); - let mut cfg = Config::default(); - cfg.set("book.src", "summary-formatting").unwrap(); - let md = MDBook::load_with_config(temp.path(), cfg).unwrap(); - md.build().unwrap(); - - let rendered_path = temp.path().join("book/toc.js"); - assert_contains_strings( - rendered_path, - &[ - r#" Italic code *escape* `escape2`"#, - r#" Soft line break"#, - r#" <escaped tag>"#, - ], - ); - - let generated_md = temp.path().join("summary-formatting/formatted-summary.md"); - assert_eq!( - fs::read_to_string(generated_md).unwrap(), - "# Italic code *escape* `escape2`\n" - ); - let generated_md = temp.path().join("summary-formatting/soft.md"); - assert_eq!( - fs::read_to_string(generated_md).unwrap(), - "# Soft line break\n" - ); - let generated_md = temp.path().join("summary-formatting/escaped-tag.md"); - assert_eq!( - fs::read_to_string(generated_md).unwrap(), - "# <escaped tag>\n" - ); -} - -/// Ensure building fails if `[output.html].theme` points to a non-existent directory -#[test] -fn failure_on_missing_theme_directory() { - // 1. Using default theme should work - let temp = DummyBook::new().build().unwrap(); - let book_toml = r#" - [book] - title = "implicit" - src = "src" - "#; - - write_file(temp.path(), "book.toml", book_toml.as_bytes()).unwrap(); - let md = MDBook::load(temp.path()).unwrap(); - let got = md.build(); - assert!(got.is_ok()); - - // 2. Pointing to a normal directory should work - let temp = DummyBook::new().build().unwrap(); - let created = fs::create_dir(temp.path().join("theme-directory")); - assert!(created.is_ok()); - let book_toml = r#" - [book] - title = "implicit" - src = "src" - - [output.html] - theme = "./theme-directory" - "#; - - write_file(temp.path(), "book.toml", book_toml.as_bytes()).unwrap(); - let md = MDBook::load(temp.path()).unwrap(); - let got = md.build(); - assert!(got.is_ok()); - - // 3. Pointing to a non-existent directory should fail - let temp = DummyBook::new().build().unwrap(); - let book_toml = r#" - [book] - title = "implicit" - src = "src" - - [output.html] - theme = "./non-existent-directory" - "#; - - write_file(temp.path(), "book.toml", book_toml.as_bytes()).unwrap(); - let md = MDBook::load(temp.path()).unwrap(); - let got = md.build(); - assert!(got.is_err()); -} - -#[cfg(feature = "search")] -mod search { - use crate::dummy_book::DummyBook; - use mdbook::utils::fs::write_file; - use mdbook::MDBook; - use std::fs::{self, File}; - 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 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(); - 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() { - 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" - )); - } - - // 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] -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() - .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") - }; - - // 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(); - 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(); - 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(); - 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(); - 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(); - 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"] - ); -} - -#[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##"

    Heading Attributes

    "##, - r##"

    Heading with classes

    "##, - r##"

    Heading with id and classes

    "##, - ]; - 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. - 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/searchindex_fixture.json b/tests/searchindex_fixture.json deleted file mode 100644 index 3a6dd65727..0000000000 --- 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/summary_md_files/example_book.md b/tests/summary_md_files/example_book.md deleted file mode 100644 index ff3911c72c..0000000000 --- a/tests/summary_md_files/example_book.md +++ /dev/null @@ -1,20 +0,0 @@ -# Summary - -- [mdBook](README.md) -- [Command Line Tool](cli/cli-tool.md) - - [init](cli/init.md) - - [build](cli/build.md) - - [watch](cli/watch.md) - - [serve](cli/serve.md) - - [test](cli/test.md) -- [Format](format/format.md) - - [SUMMARY.md](format/summary.md) - - [Configuration](format/config.md) - - [Theme](format/theme/theme.md) - - [index.hbs](format/theme/index-hbs.md) - - [Syntax highlighting](format/theme/syntax-highlighting.md) - - [MathJax Support](format/mathjax.md) - - [Rust code specific features](format/rust.md) -- [Rust Library](lib/lib.md) ------------ -[Contributors](misc/contributors.md) diff --git a/tests/summary_md_files/rust_by_example.md b/tests/summary_md_files/rust_by_example.md deleted file mode 100644 index de7e6f6ee7..0000000000 --- a/tests/summary_md_files/rust_by_example.md +++ /dev/null @@ -1,191 +0,0 @@ -# Summary - -[Introduction](index.md) - -- [Hello World](hello.md) - - [Comments](hello/comment.md) - - [Formatted print](hello/print.md) - - [Debug](hello/print/print_debug.md) - - [Display](hello/print/print_display.md) - - [Testcase: List](hello/print/print_display/testcase_list.md) - - [Formatting](hello/print/fmt.md) - -- [Primitives](primitives.md) - - [Literals and operators](primitives/literals.md) - - [Tuples](primitives/tuples.md) - - [Arrays and Slices](primitives/array.md) - -- [Custom Types](custom_types.md) - - [Structures](custom_types/structs.md) - - [Enums](custom_types/enum.md) - - [use](custom_types/enum/enum_use.md) - - [C-like](custom_types/enum/c_like.md) - - [Testcase: linked-list](custom_types/enum/testcase_linked_list.md) - - [constants](custom_types/constants.md) - -- [Variable Bindings](variable_bindings.md) - - [Mutability](variable_bindings/mut.md) - - [Scope and Shadowing](variable_bindings/scope.md) - - [Declare first](variable_bindings/declare.md) - -- [Types](types.md) - - [Casting](types/cast.md) - - [Literals](types/literals.md) - - [Inference](types/inference.md) - - [Aliasing](types/alias.md) - -- [Conversion](conversion.md) - - [From and Into](conversion/from_into.md) - - [To and From String](conversion/string.md) - -- [Expressions](expression.md) - -- [Flow Control](flow_control.md) - - [if/else](flow_control/if_else.md) - - [loop](flow_control/loop.md) - - [Nesting and labels](flow_control/loop/nested.md) - - [Returning from loops](flow_control/loop/return.md) - - [while](flow_control/while.md) - - [for and range](flow_control/for.md) - - [match](flow_control/match.md) - - [Destructuring](flow_control/match/destructuring.md) - - [tuples](flow_control/match/destructuring/destructure_tuple.md) - - [enums](flow_control/match/destructuring/destructure_enum.md) - - [pointers/ref](flow_control/match/destructuring/destructure_pointers.md) - - [structs](flow_control/match/destructuring/destructure_structures.md) - - [Guards](flow_control/match/guard.md) - - [Binding](flow_control/match/binding.md) - - [if let](flow_control/if_let.md) - - [while let](flow_control/while_let.md) - -- [Functions](fn.md) - - [Methods](fn/methods.md) - - [Closures](fn/closures.md) - - [Capturing](fn/closures/capture.md) - - [As input parameters](fn/closures/input_parameters.md) - - [Type anonymity](fn/closures/anonymity.md) - - [Input functions](fn/closures/input_functions.md) - - [As output parameters](fn/closures/output_parameters.md) - - [Examples in `std`](fn/closures/closure_examples.md) - - [Iterator::any](fn/closures/closure_examples/iter_any.md) - - [Iterator::find](fn/closures/closure_examples/iter_find.md) - - [Higher Order Functions](fn/hof.md) - -- [Modules](mod.md) - - [Visibility](mod/visibility.md) - - [Struct visibility](mod/struct_visibility.md) - - [The `use` declaration](mod/use.md) - - [`super` and `self`](mod/super.md) - - [File hierarchy](mod/split.md) - -- [Crates](crates.md) - - [Library](crates/lib.md) - - [`extern crate`](crates/link.md) - -- [Attributes](attribute.md) - - [`dead_code`](attribute/unused.md) - - [Crates](attribute/crate.md) - - [`cfg`](attribute/cfg.md) - - [Custom](attribute/cfg/custom.md) - -- [Generics](generics.md) - - [Functions](generics/gen_fn.md) - - [Implementation](generics/impl.md) - - [Traits](generics/gen_trait.md) - - [Bounds](generics/bounds.md) - - [Testcase: empty bounds](generics/bounds/testcase_empty.md) - - [Multiple bounds](generics/multi_bounds.md) - - [Where clauses](generics/where.md) - - [New Type Idiom](generics/new_types.md) - - [Associated items](generics/assoc_items.md) - - [The Problem](generics/assoc_items/the_problem.md) - - [Associated types](generics/assoc_items/types.md) - - [Phantom type parameters](generics/phantom.md) - - [Testcase: unit clarification](generics/phantom/testcase_units.md) - -- [Scoping rules](scope.md) - - [RAII](scope/raii.md) - - [Ownership and moves](scope/move.md) - - [Mutability](scope/move/mut.md) - - [Borrowing](scope/borrow.md) - - [Mutability](scope/borrow/mut.md) - - [Freezing](scope/borrow/freeze.md) - - [Aliasing](scope/borrow/alias.md) - - [The ref pattern](scope/borrow/ref.md) - - [Lifetimes](scope/lifetime.md) - - [Explicit annotation](scope/lifetime/explicit.md) - - [Functions](scope/lifetime/fn.md) - - [Methods](scope/lifetime/methods.md) - - [Structs](scope/lifetime/struct.md) - - [Bounds](scope/lifetime/lifetime_bounds.md) - - [Coercion](scope/lifetime/lifetime_coercion.md) - - [static](scope/lifetime/static_lifetime.md) - - [elision](scope/lifetime/elision.md) - -- [Traits](trait.md) - - [Derive](trait/derive.md) - - [Operator Overloading](trait/ops.md) - - [Drop](trait/drop.md) - - [Iterators](trait/iter.md) - - [Clone](trait/clone.md) - -- [macro_rules!](macros.md) - - [Syntax](macro/syntax.md) - - [Designators](macros/designators.md) - - [Overload](macros/overload.md) - - [Repeat](macros/repeat.md) - - [DRY (Don't Repeat Yourself)](macros/dry.md) - - [DSL (Domain Specific Languages)](macros/dsl.md) - - [Variadics](macros/variadics.md) - -- [Error handling](error.md) - - [`panic`](error/panic.md) - - [`Option` & `unwrap`](error/option_unwrap.md) - - [Combinators: `map`](error/option_unwrap/map.md) - - [Combinators: `and_then`](error/option_unwrap/and_then.md) - - [`Result`](error/result.md) - - [`map` for `Result`](error/result/result_map.md) - - [aliases for `Result`](error/result/result_alias.md) - - [Early returns](error/result/early_returns.md) - - [Introducing `?`](error/result/enter_question_mark.md) - - [Multiple error types](error/multiple_error_types.md) - - [Pulling `Result`s out of `Option`s](error/multiple_error_types/option_result.md) - - [Defining an error type](error/multiple_error_types/define_error_type.md) - - [`Box`ing errors](error/multiple_error_types/boxing_errors.md) - - [Other uses of `?`](error/multiple_error_types/reenter_question_mark.md) - - [Wrapping errors](error/multiple_error_types/wrap_error.md) - - [Iterating over `Result`s](error/iter_result.md) - -- [Std library types](std.md) - - [Box, stack and heap](std/box.md) - - [Vectors](std/vec.md) - - [Strings](std/str.md) - - [`Option`](std/option.md) - - [`Result`](std/result.md) - - [`?`](std/result/question_mark.md) - - [`panic!`](std/panic.md) - - [HashMap](std/hash.md) - - [Alternate/custom key types](std/hash/alt_key_types.md) - - [HashSet](std/hash/hashset.md) - -- [Std misc](std_misc.md) - - [Threads](std_misc/threads.md) - - [Testcase: map-reduce](std_misc/threads/testcase_mapreduce.md) - - [Channels](std_misc/channels.md) - - [Path](std_misc/path.md) - - [File I/O](std_misc/file.md) - - [`open`](std_misc/file/open.md) - - [`create`](std_misc/file/create.md) - - [Child processes](std_misc/process.md) - - [Pipes](std_misc/process/pipe.md) - - [Wait](std_misc/process/wait.md) - - [Filesystem Operations](std_misc/fs.md) - - [Program arguments](std_misc/arg.md) - - [Argument parsing](std_misc/arg/matching.md) - - [Foreign Function Interface](std_misc/ffi.md) - -- [Meta](meta.md) - - [Documentation](meta/doc.md) - - [Testing](meta/test.md) - -- [Unsafe Operations](unsafe.md) diff --git a/tests/summary_md_files/rust_ffi_guide.md b/tests/summary_md_files/rust_ffi_guide.md deleted file mode 100644 index f64fcab1b6..0000000000 --- a/tests/summary_md_files/rust_ffi_guide.md +++ /dev/null @@ -1,19 +0,0 @@ -# Summary - -- [Overview](./overview.md) -- [Setting Up](./setting_up.md) -- [Core Client Library](./client.md) -- [Constructing a Basic Request](./basic_request.md) -- [Sending the Request](./send_basic.md) -- [Generating a Header File](./cbindgen.md) -- [Better Error Handling](./error_handling.md) -- [Asynchronous Operations](./async.md) -- [More Complex Requests](./complex_request.md) -- [Testing](./testing.md) -- [Dynamic Loading & Plugins](./dynamic_loading.md) - ---- - -- [Break All The Things!!1!](./fun/index.md) - - [Problems](./fun/problems.md) - - [Solutions](./fun/solutions.md) \ No newline at end of file diff --git a/tests/summary_md_files/the_book-2nd_edition.md b/tests/summary_md_files/the_book-2nd_edition.md deleted file mode 100644 index c7f8306748..0000000000 --- a/tests/summary_md_files/the_book-2nd_edition.md +++ /dev/null @@ -1,130 +0,0 @@ -# The Rust Programming Language - -## Getting started - -- [Introduction](ch01-00-introduction.md) - - [Installation](ch01-01-installation.md) - - [Hello, World!](ch01-02-hello-world.md) - -- [Guessing Game Tutorial](ch02-00-guessing-game-tutorial.md) - -- [Common Programming Concepts](ch03-00-common-programming-concepts.md) - - [Variables and Mutability](ch03-01-variables-and-mutability.md) - - [Data Types](ch03-02-data-types.md) - - [How Functions Work](ch03-03-how-functions-work.md) - - [Comments](ch03-04-comments.md) - - [Control Flow](ch03-05-control-flow.md) - -- [Understanding Ownership](ch04-00-understanding-ownership.md) - - [What is Ownership?](ch04-01-what-is-ownership.md) - - [References & Borrowing](ch04-02-references-and-borrowing.md) - - [Slices](ch04-03-slices.md) - -- [Using Structs to Structure Related Data](ch05-00-structs.md) - - [Defining and Instantiating Structs](ch05-01-defining-structs.md) - - [An Example Program Using Structs](ch05-02-example-structs.md) - - [Method Syntax](ch05-03-method-syntax.md) - -- [Enums and Pattern Matching](ch06-00-enums.md) - - [Defining an Enum](ch06-01-defining-an-enum.md) - - [The `match` Control Flow Operator](ch06-02-match.md) - - [Concise Control Flow with `if let`](ch06-03-if-let.md) - -## Basic Rust Literacy - -- [Modules](ch07-00-modules.md) - - [`mod` and the Filesystem](ch07-01-mod-and-the-filesystem.md) - - [Controlling Visibility with `pub`](ch07-02-controlling-visibility-with-pub.md) - - [Referring to Names in Different Modules](ch07-03-importing-names-with-use.md) - -- [Common Collections](ch08-00-common-collections.md) - - [Vectors](ch08-01-vectors.md) - - [Strings](ch08-02-strings.md) - - [Hash Maps](ch08-03-hash-maps.md) - -- [Error Handling](ch09-00-error-handling.md) - - [Unrecoverable Errors with `panic!`](ch09-01-unrecoverable-errors-with-panic.md) - - [Recoverable Errors with `Result`](ch09-02-recoverable-errors-with-result.md) - - [To `panic!` or Not To `panic!`](ch09-03-to-panic-or-not-to-panic.md) - -- [Generic Types, Traits, and Lifetimes](ch10-00-generics.md) - - [Generic Data Types](ch10-01-syntax.md) - - [Traits: Defining Shared Behavior](ch10-02-traits.md) - - [Validating References with Lifetimes](ch10-03-lifetime-syntax.md) - -- [Testing](ch11-00-testing.md) - - [Writing tests](ch11-01-writing-tests.md) - - [Running tests](ch11-02-running-tests.md) - - [Test Organization](ch11-03-test-organization.md) - -- [An I/O Project: Building a Command Line Program](ch12-00-an-io-project.md) - - [Accepting Command Line Arguments](ch12-01-accepting-command-line-arguments.md) - - [Reading a File](ch12-02-reading-a-file.md) - - [Refactoring to Improve Modularity and Error Handling](ch12-03-improving-error-handling-and-modularity.md) - - [Developing the Library’s Functionality with Test Driven Development](ch12-04-testing-the-librarys-functionality.md) - - [Working with Environment Variables](ch12-05-working-with-environment-variables.md) - - [Writing Error Messages to Standard Error Instead of Standard Output](ch12-06-writing-to-stderr-instead-of-stdout.md) - -## Thinking in Rust - -- [Functional Language Features: Iterators and Closures](ch13-00-functional-features.md) - - [Closures: Anonymous Functions that Can Capture Their Environment](ch13-01-closures.md) - - [Processing a Series of Items with Iterators](ch13-02-iterators.md) - - [Improving Our I/O Project](ch13-03-improving-our-io-project.md) - - [Comparing Performance: Loops vs. Iterators](ch13-04-performance.md) - -- [More about Cargo and Crates.io](ch14-00-more-about-cargo.md) - - [Customizing Builds with Release Profiles](ch14-01-release-profiles.md) - - [Publishing a Crate to Crates.io](ch14-02-publishing-to-crates-io.md) - - [Cargo Workspaces](ch14-03-cargo-workspaces.md) - - [Installing Binaries from Crates.io with `cargo install`](ch14-04-installing-binaries.md) - - [Extending Cargo with Custom Commands](ch14-05-extending-cargo.md) - -- [Smart Pointers](ch15-00-smart-pointers.md) - - [`Box` Points to Data on the Heap and Has a Known Size](ch15-01-box.md) - - [The `Deref` Trait Allows Access to the Data Through a Reference](ch15-02-deref.md) - - [The `Drop` Trait Runs Code on Cleanup](ch15-03-drop.md) - - [`Rc`, the Reference Counted Smart Pointer](ch15-04-rc.md) - - [`RefCell` and the Interior Mutability Pattern](ch15-05-interior-mutability.md) - - [Creating Reference Cycles and Leaking Memory is Safe](ch15-06-reference-cycles.md) - -- [Fearless Concurrency](ch16-00-concurrency.md) - - [Threads](ch16-01-threads.md) - - [Message Passing](ch16-02-message-passing.md) - - [Shared State](ch16-03-shared-state.md) - - [Extensible Concurrency: `Sync` and `Send`](ch16-04-extensible-concurrency-sync-and-send.md) - -- [Is Rust an Object-Oriented Programming Language?](ch17-00-oop.md) - - [What Does Object-Oriented Mean?](ch17-01-what-is-oo.md) - - [Trait Objects for Using Values of Different Types](ch17-02-trait-objects.md) - - [Object-Oriented Design Pattern Implementations](ch17-03-oo-design-patterns.md) - -## Advanced Topics - -- [Patterns Match the Structure of Values](ch18-00-patterns.md) - - [All the Places Patterns May be Used](ch18-01-all-the-places-for-patterns.md) - - [Refutability: Whether a Pattern Might Fail to Match](ch18-02-refutability.md) - - [All the Pattern Syntax](ch18-03-pattern-syntax.md) - -- [Advanced Features](ch19-00-advanced-features.md) - - [Unsafe Rust](ch19-01-unsafe-rust.md) - - [Advanced Lifetimes](ch19-02-advanced-lifetimes.md) - - [Advanced Traits](ch19-03-advanced-traits.md) - - [Advanced Types](ch19-04-advanced-types.md) - - [Advanced Functions & Closures](ch19-05-advanced-functions-and-closures.md) - -- [Final Project: Building a Multithreaded Web Server](ch20-00-final-project-a-web-server.md) - - [A Single Threaded Web Server](ch20-01-single-threaded.md) - - [How Slow Requests Affect Throughput](ch20-02-slow-requests.md) - - [Designing the Thread Pool Interface](ch20-03-designing-the-interface.md) - - [Creating the Thread Pool and Storing Threads](ch20-04-storing-threads.md) - - [Sending Requests to Threads Via Channels](ch20-05-sending-requests-via-channels.md) - - [Graceful Shutdown and Cleanup](ch20-06-graceful-shutdown-and-cleanup.md) - -- [Appendix](appendix-00.md) - - [A - Keywords](appendix-01-keywords.md) - - [B - Operators and Symbols](appendix-02-operators.md) - - [C - Derivable Traits](appendix-03-derivable-traits.md) - - [D - Macros](appendix-04-macros.md) - - [E - Translations](appendix-05-translation.md) - - [F - Newest Features](appendix-06-newest-features.md) diff --git a/tests/testing.rs b/tests/testing.rs deleted file mode 100644 index 3030c5cb66..0000000000 --- a/tests/testing.rs +++ /dev/null @@ -1,47 +0,0 @@ -mod dummy_book; - -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(); - 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(); - let mut md = MDBook::load(temp.path()).unwrap(); - - assert!(md.test_chapter(vec![], Some("Bogus Chapter Name")).is_err()); -} diff --git a/tests/testsuite/README.md b/tests/testsuite/README.md new file mode 100644 index 0000000000..73c594e3f3 --- /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 0000000000..427c38d18a --- /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/build.rs b/tests/testsuite/build.rs new file mode 100644 index 0000000000..f197e974bb --- /dev/null +++ b/tests/testsuite/build.rs @@ -0,0 +1,67 @@ +//! 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 + +"#]]); + }); +} + +// 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] + +"#]]); + }); +} + +// 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()); +} + +// 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 + +"#]]); + }); +} + +// 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##"

    Chapter 1

    "##]], + ); +} diff --git a/tests/testsuite/build/basic_build/book.toml b/tests/testsuite/build/basic_build/book.toml new file mode 100644 index 0000000000..58d56cb6cf --- /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 0000000000..7390c82896 --- /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/dummy_book/index_html_test/chapter_1.md b/tests/testsuite/build/basic_build/src/chapter_1.md similarity index 100% rename from tests/dummy_book/index_html_test/chapter_1.md rename to tests/testsuite/build/basic_build/src/chapter_1.md diff --git a/tests/testsuite/build/create_missing/book.toml b/tests/testsuite/build/create_missing/book.toml new file mode 100644 index 0000000000..d347afe8e5 --- /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 0000000000..7390c82896 --- /dev/null +++ b/tests/testsuite/build/create_missing/src/SUMMARY.md @@ -0,0 +1,3 @@ +# Summary + +- [Chapter 1](./chapter_1.md) diff --git a/tests/testsuite/build/missing_file/book.toml b/tests/testsuite/build/missing_file/book.toml new file mode 100644 index 0000000000..67e5e5f374 --- /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 0000000000..7390c82896 --- /dev/null +++ b/tests/testsuite/build/missing_file/src/SUMMARY.md @@ -0,0 +1,3 @@ +# Summary + +- [Chapter 1](./chapter_1.md) 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 0000000000..58c1144ec7 --- /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 0000000000..2c8b949e2f --- /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 0000000000..849c2d3e88 --- /dev/null +++ b/tests/testsuite/build/no_reserved_filename/src/print.md @@ -0,0 +1 @@ +# Print diff --git a/tests/testsuite/cli.rs b/tests/testsuite/cli.rs new file mode 100644 index 0000000000..caa5975273 --- /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 0000000000..e27fa313c2 --- /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 0000000000..e27fa313c2 --- /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/includes.rs b/tests/testsuite/includes.rs new file mode 100644 index 0000000000..312caede8a --- /dev/null +++ b/tests/testsuite/includes.rs @@ -0,0 +1,112 @@ +//! 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##" +

    Basic Includes

    +

    Sample

    +

    This is a sample include.

    +"##]], + ) + .check_main_file( + "book/relative/includes.html", + str![[r##" +

    Relative Includes

    +

    Sample

    +

    This is a sample include.

    +"##]], + ); +} + +// Checks for anchored includes. +#[test] +fn anchored_include() { + BookTest::from_dir("includes/all_includes").check_main_file( + "book/anchors.html", + str![[r##" +

    Include Anchors

    +
    #![allow(unused)]
    +fn main() {
    +let x = 1;
    +}
    +"##]], + ); +} + +// 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

    +"#]], + ); +} + +// Checks the behavior of `{{#playground}}` include. +#[test] +fn playground_include() { + BookTest::from_dir("includes/all_includes") + .check_main_file("book/playground.html", + str![[r##" +

    Playground Includes

    +
    fn main() {
    +    println!("Hello World!");
    +
    +   // You can even hide lines! :D
    +  println!("I am hidden! Expand the code snippet to see me");
    +}
    +"##]]); +} + +// Checks the behavior of `{{#rustdoc_include}}`. +#[test] +fn rustdoc_include() { + BookTest::from_dir("includes/all_includes") + .check_main_file("book/rustdoc.html", + str![[r##" +

    Rustdoc Includes

    +

    Rustdoc include adds the rest of the file as hidden

    +
    fn some_function() {
    +    println!("some function");
    +}
    +
    +fn main() {
    +    some_function();
    +}
    +

    Rustdoc include works with anchors too

    +
    fn some_other_function() {
    +    println!("unused anchor");
    +}
    +
    +fn main() {
    +    some_other_function();
    +}
    +"##]]); +} diff --git a/tests/testsuite/includes/all_includes/book.toml b/tests/testsuite/includes/all_includes/book.toml new file mode 100644 index 0000000000..622694bedf --- /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 0000000000..25a9a15f71 --- /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 0000000000..0ffbc78837 --- /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/dummy_book/src/example.rs b/tests/testsuite/includes/all_includes/src/example.rs similarity index 100% rename from tests/dummy_book/src/example.rs rename to tests/testsuite/includes/all_includes/src/example.rs 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 0000000000..539aa93907 --- /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 0000000000..93c61dd4e3 --- /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/dummy_book/src/first/partially-included-test-with-anchors.rs b/tests/testsuite/includes/all_includes/src/partially-included-test-with-anchors.rs similarity index 89% rename from tests/dummy_book/src/first/partially-included-test-with-anchors.rs rename to tests/testsuite/includes/all_includes/src/partially-included-test-with-anchors.rs index 17b6afeffd..8e732d0b40 100644 --- a/tests/dummy_book/src/first/partially-included-test-with-anchors.rs +++ b/tests/testsuite/includes/all_includes/src/partially-included-test-with-anchors.rs @@ -1,6 +1,6 @@ fn some_other_function() { // ANCHOR: unused-anchor-that-should-be-stripped - assert!($TEST_STATUS); + println!("unused anchor"); // ANCHOR_END: unused-anchor-that-should-be-stripped } diff --git a/tests/dummy_book/src/first/partially-included-test.rs b/tests/testsuite/includes/all_includes/src/partially-included-test.rs similarity index 65% rename from tests/dummy_book/src/first/partially-included-test.rs rename to tests/testsuite/includes/all_includes/src/partially-included-test.rs index 1f8421b542..915651ea5e 100644 --- a/tests/dummy_book/src/first/partially-included-test.rs +++ b/tests/testsuite/includes/all_includes/src/partially-included-test.rs @@ -1,5 +1,5 @@ fn some_function() { - assert!($TEST_STATUS); + println!("some function"); } fn main() { 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 0000000000..9c20530c4b --- /dev/null +++ b/tests/testsuite/includes/all_includes/src/playground.md @@ -0,0 +1,3 @@ +# Playground Includes + +{{#playground example.rs}} diff --git a/tests/dummy_book/src/first/recursive.md b/tests/testsuite/includes/all_includes/src/recursive.md similarity index 100% rename from tests/dummy_book/src/first/recursive.md rename to tests/testsuite/includes/all_includes/src/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 0000000000..f0a6fce63e --- /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 0000000000..8a342d90b3 --- /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 0000000000..91071ef8f7 --- /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/index.rs b/tests/testsuite/index.rs new file mode 100644 index 0000000000..0555cc6308 --- /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##"

    Intro

    "##]], + ) + .check_main_file( + "book/first/index.html", + str![[r##"

    First

    "##]], + ) + .check_main_file( + "book/second/index.html", + str![[r##"

    Second

    "##]], + ) + .check_toc_js(str![[r#" +
      +
    1. +Intro +
    2. +
    3. + + First +
    4. +
    5. + + Second +
    6. +
    +"#]]); + 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 0000000000..271699ca07 --- /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 0000000000..1e0981f10f --- /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 0000000000..fd4cbac2f6 --- /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 0000000000..f67585421f --- /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 0000000000..64e2582b49 --- /dev/null +++ b/tests/testsuite/index/basic_readme/src/second/Readme.md @@ -0,0 +1 @@ +# Second diff --git a/tests/testsuite/init.rs b/tests/testsuite/init.rs new file mode 100644 index 0000000000..e5989eb62c --- /dev/null +++ b/tests/testsuite/init.rs @@ -0,0 +1,253 @@ +//! Tests for `mdbook init`. + +use crate::prelude::*; +use mdbook::{Config, MDBook}; +use std::path::PathBuf; + +// 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![[r#" +# Summary + +- [Chapter 1](./chapter_1.md) + +"#]], + ) + .check_file( + "src/chapter_1.md", + str![[r#" +# Chapter 1 + +"#]], + ) + .check_main_file( + "book/chapter_1.html", + str![[r##"

    Chapter 1

    "##]], + ); + 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 +"#]], + ); +} + +// 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()); +} + +// 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()); +} + +// 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 + +"#]], + ); +} + +// 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![[r#" +# Summary + +- [Chapter 1](./chapter_1.md) + +"#]], + ) + .check_file( + "in/chapter_1.md", + str![[r#" +# Chapter 1 + +"#]], + ); + 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 +"#]], + ); +} 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 0000000000..93d7b15b0d --- /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) + diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs new file mode 100644 index 0000000000..cc9bef9239 --- /dev/null +++ b/tests/testsuite/main.rs @@ -0,0 +1,27 @@ +//! Main testsuite for exercising all functionality of mdBook. +//! +//! See README.md for documentation. + +mod book_test; +mod build; +mod cli; +mod includes; +mod index; +mod init; +mod markdown; +mod playground; +mod preprocessor; +mod print; +mod redirects; +mod renderer; +mod rendering; +#[cfg(feature = "search")] +mod search; +mod test; +mod theme; +mod toc; + +mod prelude { + pub use crate::book_test::BookTest; + pub use snapbox::str; +} diff --git a/tests/testsuite/markdown.rs b/tests/testsuite/markdown.rs new file mode 100644 index 0000000000..5047182614 --- /dev/null +++ b/tests/testsuite/markdown.rs @@ -0,0 +1,146 @@ +//! Tests for special markdown rendering. + +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##" +

    Heading Attributes

    +

    Heading with classes

    +

    Heading with id and classes

    +"##]]); +} + +// Test for a variety of footnote renderings. +#[test] +fn footnotes() { + BookTest::from_dir("markdown/footnotes") + .check_main_file("book/footnotes.html", str![[r##" +

    Footnote tests

    +

    Footnote example1, or with a word2.

    +

    There are multiple references to word2.

    +

    Footnote without a paragraph3

    +

    Footnote with multiple paragraphs4

    +

    Footnote name with wacky characters5

    +

    Testing when referring to something earlier.6

    +
    +
    1. +

      This is a footnote. ↩2

      +
    2. +
    3. +

      A longer footnote. +With multiple lines. Link to other. +With a reference inside.1 ↩2

      +
    4. +
    5. +
        +
      1. Item one +
          +
        1. Sub-item
        2. +
        +
      2. +
      3. Item two
      4. +
      +
    6. +
    7. +

      One

      +

      Two

      +

      Three

      +
    8. +
    9. +

      Testing footnote id with special characters.

      +
    10. +
    11. +

      This is defined before it is referred to.

      +
    12. +
    +"##]]); +} + +// Basic table test. +#[test] +fn tables() { + BookTest::from_dir("markdown/tables").check_main_file( + "book/tables.html", + str![[r##" +

    Tables

    +
    + + + + + +
    foobar
    bazbim
    Backslash in code/
    Double back in code//
    Pipe in code|
    Pipe in code2test | inside
    +
    +"##]], + ); +} + +// Strikethrough test. +#[test] +fn strikethrough() { + BookTest::from_dir("markdown/strikethrough").check_main_file( + "book/strikethrough.html", + str![[r##" +

    Strikethrough

    +

    strikethrough example

    +"##]], + ); +} + +// Tasklist test. +#[test] +fn tasklists() { + BookTest::from_dir("markdown/tasklists").check_main_file( + "book/tasklists.html", + str![[r##" +

    Tasklisks

    +
      +
    • +Apples
    • +
    • +Broccoli
    • +
    • +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##" +

    Smart Punctuation

    +
      +
    • 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##" +

    Smart Punctuation

    +
      +
    • En dash: –
    • +
    • Em dash: —
    • +
    • Ellipsis: …
    • +
    • Double quote: “quote”
    • +
    • Single quote: ‘quote’
    • +
    +"##]], + ); +} 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 0000000000..3c4b914b0d --- /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 0000000000..56dd8b100d --- /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/dummy_book/src/first/heading-attributes.md b/tests/testsuite/markdown/custom_header_attributes/src/custom_header_attributes.md similarity index 100% rename from tests/dummy_book/src/first/heading-attributes.md rename to tests/testsuite/markdown/custom_header_attributes/src/custom_header_attributes.md diff --git a/tests/testsuite/markdown/footnotes/src/SUMMARY.md b/tests/testsuite/markdown/footnotes/src/SUMMARY.md new file mode 100644 index 0000000000..a36ad79182 --- /dev/null +++ b/tests/testsuite/markdown/footnotes/src/SUMMARY.md @@ -0,0 +1 @@ +- [Footnotes](footnotes.md) diff --git a/tests/dummy_book/src/first/markdown.md b/tests/testsuite/markdown/footnotes/src/footnotes.md similarity index 56% rename from tests/dummy_book/src/first/markdown.md rename to tests/testsuite/markdown/footnotes/src/footnotes.md index ec2958e018..f1fbacca5d 100644 --- a/tests/dummy_book/src/first/markdown.md +++ b/tests/testsuite/markdown/footnotes/src/footnotes.md @@ -1,21 +1,11 @@ -# Markdown tests - -Tests for some markdown output. - -## Tables - -| foo | bar | -| --- | --- | -| baz | bim | - -## Footnotes +# Footnote tests Footnote example[^1], or with a word[^word]. [^1]: This is a footnote. [^word]: A longer footnote. - With multiple lines. [Link to unicode](unicode.md). + With multiple lines. [Link to other](other.md). With a reference inside.[^1] There are multiple references to word[^word]. @@ -31,10 +21,12 @@ Footnote with multiple paragraphs[^multiple] [^define-before-use]: This is defined before it is referred to. - -[^multiple]:

    One

    Two

    Three

    +[^multiple]: + One + + Two + + Three [^unused]: This footnote is defined by not used. @@ -43,13 +35,3 @@ Footnote name with wacky characters[^"wacky"] [^"wacky"]: Testing footnote id with special characters. Testing when referring to something earlier.[^define-before-use] - -## Strikethrough - -~~strikethrough example~~ - -## Tasklisks - -- [X] Apples -- [X] Broccoli -- [ ] Carrots 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 0000000000..7ec259e31e --- /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 0000000000..a27f3c3208 --- /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' diff --git a/tests/testsuite/markdown/strikethrough/src/SUMMARY.md b/tests/testsuite/markdown/strikethrough/src/SUMMARY.md new file mode 100644 index 0000000000..c9a3a8364a --- /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 0000000000..4cb45fef6f --- /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 0000000000..3c2061aa5d --- /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 0000000000..d3721ef3d5 --- /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 0000000000..a1fc91c5ad --- /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 0000000000..2a90ff56f5 --- /dev/null +++ b/tests/testsuite/markdown/tasklists/src/tasklists.md @@ -0,0 +1,5 @@ +## Tasklisks + +- [X] Apples +- [X] Broccoli +- [ ] Carrots diff --git a/tests/testsuite/playground.rs b/tests/testsuite/playground.rs new file mode 100644 index 0000000000..1983757dfe --- /dev/null +++ b/tests/testsuite/playground.rs @@ -0,0 +1,30 @@ +//! 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##" +

    Rust Sample

    +
    #![allow(unused)]
    +fn main() {
    +let x = 1;
    +}
    +"##]], + ); +} + +// 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##" +

    Rust Sample

    +
    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 0000000000..3a9b22975e --- /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 0000000000..6f47d01a9a --- /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 0000000000..93234d98cd --- /dev/null +++ b/tests/testsuite/playground/disabled_playground/src/index.md @@ -0,0 +1,5 @@ +# Rust Sample + +```rust +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 0000000000..7bf7c972c6 --- /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 0000000000..6f47d01a9a --- /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 0000000000..93234d98cd --- /dev/null +++ b/tests/testsuite/playground/playground_on_rust_code/src/index.md @@ -0,0 +1,5 @@ +# Rust Sample + +```rust +let x = 1; +``` diff --git a/tests/testsuite/preprocessor.rs b/tests/testsuite/preprocessor.rs new file mode 100644 index 0000000000..d2c1608bd7 --- /dev/null +++ b/tests/testsuite/preprocessor.rs @@ -0,0 +1,95 @@ +//! Tests for custom preprocessors. + +use crate::prelude::*; +use mdbook::book::Book; +use mdbook::errors::Result; +use mdbook::preprocess::{CmdPreprocessor, 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"]); +} + +// 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 + +"#]]); + }); +} + +// 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 + +"#]]); + }); +} + +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); +} diff --git a/tests/testsuite/preprocessor/failing_preprocessor/book.toml b/tests/testsuite/preprocessor/failing_preprocessor/book.toml new file mode 100644 index 0000000000..f281ae0d60 --- /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 0000000000..e69de29bb2 diff --git a/tests/testsuite/preprocessor/nop_preprocessor/book.toml b/tests/testsuite/preprocessor/nop_preprocessor/book.toml new file mode 100644 index 0000000000..5532d943bd --- /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 0000000000..e69de29bb2 diff --git a/tests/testsuite/print.rs b/tests/testsuite/print.rs new file mode 100644 index 0000000000..0e0cdfe05c --- /dev/null +++ b/tests/testsuite/print.rs @@ -0,0 +1,32 @@ +//! 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##" +

    First Chapter

    +

    First Nested

    +

    Testing relative links for the print page

    +

    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

    +raw html +

    Some section

    +"##]]); +} + +// 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 0000000000..655a0dedf8 --- /dev/null +++ b/tests/testsuite/print/noindex/src/SUMMARY.md @@ -0,0 +1 @@ +- [Intro](index.md) diff --git a/tests/testsuite/print/relative_links/book.toml b/tests/testsuite/print/relative_links/book.toml new file mode 100644 index 0000000000..80822350c5 --- /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 0000000000..229e7aacbc --- /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 0000000000..4f8bda1723 --- /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 0000000000..e207dd37ae --- /dev/null +++ b/tests/testsuite/print/relative_links/src/first/nested.md @@ -0,0 +1 @@ +# First Nested diff --git a/tests/dummy_book/src/second/nested.md b/tests/testsuite/print/relative_links/src/second/nested.md similarity index 100% rename from tests/dummy_book/src/second/nested.md rename to tests/testsuite/print/relative_links/src/second/nested.md diff --git a/tests/testsuite/redirects.rs b/tests/testsuite/redirects.rs new file mode 100644 index 0000000000..3501c2236a --- /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 0000000000..46e4b8652e --- /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 0000000000..08d6e92957 --- /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 0000000000..8032308497 --- /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 0000000000..4f68394610 --- /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 0000000000..b743fda354 --- /dev/null +++ b/tests/testsuite/redirects/redirects_are_emitted_correctly/src/chapter_1.md @@ -0,0 +1 @@ +# Chapter 1 diff --git a/tests/testsuite/renderer.rs b/tests/testsuite/renderer.rs new file mode 100644 index 0000000000..1e1624474f --- /dev/null +++ b/tests/testsuite/renderer.rs @@ -0,0 +1,264 @@ +//! Tests for custom renderers. + +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>); + +#[derive(Debug, Default)] +struct Inner { + run_count: usize, +} + +impl Renderer for Spy { + fn name(&self) -> &str { + "dummy" + } + + fn render(&self, _ctx: &RenderContext) -> Result<()> { + let mut inner = self.0.lock().unwrap(); + inner.run_count += 1; + Ok(()) + } +} + +// Test that renderer gets run. +#[test] +fn runs_renderers() { + let test = BookTest::init(|_| {}); + let spy: Arc> = Default::default(); + let mut book = test.load_book(); + book.with_renderer(Spy(Arc::clone(&spy))); + book.build().unwrap(); + + 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 + +"#]]); + }); +} + +// 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] + +"#]]); + }); +} + +// 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. + +"#]]); + }); +} + +// 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 + +"#]]); + }); +} + +// 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(); +} + +// 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"); +} 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 0000000000..db63e65268 --- /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 0000000000..742767cc6b --- /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 0000000000..b743fda354 --- /dev/null +++ b/tests/testsuite/renderer/backends_receive_render_context_via_stdin/src/chapter_1.md @@ -0,0 +1 @@ +# Chapter 1 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 0000000000..79a7ceba3f --- /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 0000000000..e69de29bb2 diff --git a/tests/testsuite/renderer/missing_renderer/book.toml b/tests/testsuite/renderer/missing_renderer/book.toml new file mode 100644 index 0000000000..ddeac5d742 --- /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 0000000000..e69de29bb2 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 0000000000..7722c52982 --- /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 0000000000..e69de29bb2 diff --git a/tests/testsuite/rendering.rs b/tests/testsuite/rendering.rs new file mode 100644 index 0000000000..1d6cf476e8 --- /dev/null +++ b/tests/testsuite/rendering.rs @@ -0,0 +1,43 @@ +//! 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", + "", + ); +} + +// 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##"

    Chapter 1

    "## + ]], + ) + .check_main_file( + "book/index.html", + str![[ + r##"

    Chapter 1

    "## + ]], + ); +} 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 0000000000..fb942d4a9b --- /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 0000000000..3edeb8d308 --- /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 0000000000..4573d8a913 --- /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 0000000000..3edeb8d308 --- /dev/null +++ b/tests/testsuite/rendering/edit_url_template_explicit_src/src2/SUMMARY.md @@ -0,0 +1 @@ +- [Intro](README.md) diff --git a/tests/dummy_book/index_html_test/SUMMARY.md b/tests/testsuite/rendering/first_chapter_is_copied_as_index_even_if_not_first_elem/src/SUMMARY.md similarity index 100% rename from tests/dummy_book/index_html_test/SUMMARY.md rename to tests/testsuite/rendering/first_chapter_is_copied_as_index_even_if_not_first_elem/src/SUMMARY.md diff --git a/tests/testsuite/search.rs b/tests/testsuite/search.rs new file mode 100644 index 0000000000..d7e142a984 --- /dev/null +++ b/tests/testsuite/search.rs @@ -0,0 +1,141 @@ +//! Tests for search support. + +use crate::prelude::*; +use mdbook::book::Chapter; +use mdbook::BookItem; +use snapbox::file; +use std::path::{Path, PathBuf}; + +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" + ); +} + +// 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"], + ); +} + +// 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")); +} + +// 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(); +} + +// 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 0000000000..e1a18d651c --- /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 0000000000..e69de29bb2 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 0000000000..318a39a5f6 --- /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 0000000000..8f7d7e80c7 --- /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 0000000000..a3e3271963 --- /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 0000000000..b9b18ac190 --- /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 0000000000..64e2582b49 --- /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 0000000000..b06e47b4cd --- /dev/null +++ b/tests/testsuite/search/disable_search_chapter/src/second/nested.md @@ -0,0 +1 @@ +# Second Nested 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 0000000000..a700ee242e --- /dev/null +++ b/tests/testsuite/search/reasonable_search_index/expected_index.js @@ -0,0 +1 @@ +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 diff --git a/tests/dummy_book/src/SUMMARY.md b/tests/testsuite/search/reasonable_search_index/src/SUMMARY.md similarity index 53% rename from tests/dummy_book/src/SUMMARY.md rename to tests/testsuite/search/reasonable_search_index/src/SUMMARY.md index f310508b24..c4fe08384f 100644 --- a/tests/dummy_book/src/SUMMARY.md +++ b/tests/testsuite/search/reasonable_search_index/src/SUMMARY.md @@ -1,23 +1,10 @@ # Summary -[Dummy Book](README.md) - ---- - [Introduction](intro.md) - [First Chapter](first/index.md) - - [Nested Chapter](first/nested.md) - [Includes](first/includes.md) - - [Recursive](first/recursive.md) - - [Markdown](first/markdown.md) - [Unicode](first/unicode.md) - [No Headers](first/no-headers.md) - [Duplicate Headers](first/duplicate-headers.md) - [Heading Attributes](first/heading-attributes.md) -- [Second Chapter](second.md) - - [Nested Chapter](second/nested.md) - ---- - -[Conclusion](conclusion.md) diff --git a/tests/dummy_book/src/first/duplicate-headers.md b/tests/testsuite/search/reasonable_search_index/src/first/duplicate-headers.md similarity index 100% rename from tests/dummy_book/src/first/duplicate-headers.md rename to tests/testsuite/search/reasonable_search_index/src/first/duplicate-headers.md 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 0000000000..a09a22b6b8 --- /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 0000000000..93ce0721cd --- /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/dummy_book/src/first/index.md b/tests/testsuite/search/reasonable_search_index/src/first/index.md similarity index 100% rename from tests/dummy_book/src/first/index.md rename to tests/testsuite/search/reasonable_search_index/src/first/index.md diff --git a/tests/dummy_book/src/first/no-headers.md b/tests/testsuite/search/reasonable_search_index/src/first/no-headers.md similarity index 100% rename from tests/dummy_book/src/first/no-headers.md rename to tests/testsuite/search/reasonable_search_index/src/first/no-headers.md diff --git a/tests/dummy_book/src/first/unicode.md b/tests/testsuite/search/reasonable_search_index/src/first/unicode.md similarity index 100% rename from tests/dummy_book/src/first/unicode.md rename to tests/testsuite/search/reasonable_search_index/src/first/unicode.md diff --git a/tests/dummy_book/src/conclusion.md b/tests/testsuite/search/reasonable_search_index/src/intro.md similarity index 85% rename from tests/dummy_book/src/conclusion.md rename to tests/testsuite/search/reasonable_search_index/src/intro.md index 5c29ec7d93..93dbcb8205 100644 --- a/tests/dummy_book/src/conclusion.md +++ b/tests/testsuite/search/reasonable_search_index/src/intro.md @@ -1,4 +1,8 @@ -# Conclusion +# Introduction + +Here's some interesting text... + +## Sneaky

    diff --git a/tests/testsuite/test.rs b/tests/testsuite/test.rs new file mode 100644 index 0000000000..54be8695fd --- /dev/null +++ b/tests/testsuite/test.rs @@ -0,0 +1,89 @@ +//! 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 + +"#]]); + }); +} + +// 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" + +"#]]); + }); +} + +// 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 + +"#]]); + }); +} diff --git a/tests/testsuite/test/failing_tests/book.toml b/tests/testsuite/test/failing_tests/book.toml new file mode 100644 index 0000000000..37c9bcf4d6 --- /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 0000000000..b141f28df3 --- /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 0000000000..635f8b51dd --- /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 0000000000..7c36920017 --- /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 0000000000..b3f1950299 --- /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 0000000000..959cd17cf8 --- /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 0000000000..fb2a2b5357 --- /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 0000000000..9f1b91ba14 --- /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 0000000000..dc290970ca --- /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 0000000000..226bd6afde --- /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 0000000000..b3f1950299 --- /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 0000000000..1832ea3151 --- /dev/null +++ b/tests/testsuite/test/passing_tests/src/test3.rs @@ -0,0 +1 @@ +println!("test3"); diff --git a/tests/testsuite/theme.rs b/tests/testsuite/theme.rs new file mode 100644 index 0000000000..245952f5cc --- /dev/null +++ b/tests/testsuite/theme.rs @@ -0,0 +1,193 @@ +//! 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 + +"#]]); + }); +} + +// 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 + +"#]]); + }); +} + +// 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! + +"#]], + ); +} + +// 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 +"#]], + ); +} + +// 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 +"#]], + ); +} + +// 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 +"#]], + ); +} + +// 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![[""]]); +} + +// 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![[""]]); +} + +// 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_no_theme/book.toml b/tests/testsuite/theme/copy_fonts_false_no_theme/book.toml new file mode 100644 index 0000000000..e4929c83e1 --- /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 0000000000..655a0dedf8 --- /dev/null +++ b/tests/testsuite/theme/copy_fonts_false_no_theme/src/SUMMARY.md @@ -0,0 +1 @@ +- [Intro](index.md) 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 0000000000..e4929c83e1 --- /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 0000000000..655a0dedf8 --- /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 0000000000..e69de29bb2 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 0000000000..e4929c83e1 --- /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 0000000000..655a0dedf8 --- /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 0000000000..b94b7f506b --- /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 0000000000..e69de29bb2 diff --git a/tests/testsuite/theme/empty_theme/book.toml b/tests/testsuite/theme/empty_theme/book.toml new file mode 100644 index 0000000000..1c3ab31e55 --- /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 0000000000..7390c82896 --- /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 0000000000..b743fda354 --- /dev/null +++ b/tests/testsuite/theme/empty_theme/src/chapter_1.md @@ -0,0 +1 @@ +# Chapter 1 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 0000000000..510ba886a0 --- /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 0000000000..b94b7f506b --- /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 0000000000..e69de29bb2 diff --git a/tests/testsuite/theme/missing_theme/book.toml b/tests/testsuite/theme/missing_theme/book.toml new file mode 100644 index 0000000000..367db8507c --- /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 0000000000..7390c82896 --- /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 0000000000..b743fda354 --- /dev/null +++ b/tests/testsuite/theme/missing_theme/src/chapter_1.md @@ -0,0 +1 @@ +# Chapter 1 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 0000000000..655a0dedf8 --- /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 0000000000..2db8dccea7 --- /dev/null +++ b/tests/testsuite/theme/override_index/theme/index.hbs @@ -0,0 +1 @@ +This is a modified index.hbs! diff --git a/tests/testsuite/toc.rs b/tests/testsuite/toc.rs new file mode 100644 index 0000000000..eafbdc4857 --- /dev/null +++ b/tests/testsuite/toc.rs @@ -0,0 +1,187 @@ +//! 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", + "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", + "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()) +} + +/// 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(); + 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(); + 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(); + 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() { + 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); +} + +// 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() + ); +} + +// 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. +
    3. + + Soft line break +
    4. +
    5. + + <escaped tag> +
    6. +
    +"#]]) + .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/basic_toc/book.toml b/tests/testsuite/toc/basic_toc/book.toml new file mode 100644 index 0000000000..cebbd270f1 --- /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 0000000000..8ad6862f5a --- /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 0000000000..9060ce68fb --- /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 0000000000..2bd5116962 --- /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 0000000000..01fcd70c54 --- /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 0000000000..847f336d56 --- /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 0000000000..ecbbbc8685 --- /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 0000000000..e600bff761 --- /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 0000000000..4ddb4143d8 --- /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 0000000000..11ff4958c7 --- /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 0000000000..59a81900ec --- /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 0000000000..2bf99ebebb --- /dev/null +++ b/tests/testsuite/toc/basic_toc/src/suffix2.md @@ -0,0 +1 @@ +# Suffix 2 diff --git a/tests/dummy_book/summary-formatting/SUMMARY.md b/tests/testsuite/toc/summary_with_markdown_formatting/src/SUMMARY.md similarity index 100% rename from tests/dummy_book/summary-formatting/SUMMARY.md rename to tests/testsuite/toc/summary_with_markdown_formatting/src/SUMMARY.md