//! Tests for HTML rendering. //! //! Note that markdown-specific rendering tests are in the `markdown` module. 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

"## ]], ); } // Fontawesome `` tag support. #[test] fn fontawesome() { BookTest::from_dir("rendering/fontawesome") .run("build", |cmd| { cmd.expect_stderr(str![[r#" INFO Book building has started INFO Running the html backend WARN failed to find Font Awesome icon for icon `does-not-exist` with type `regular` in `fa.md`: Invalid Font Awesome icon name: visit https://fontawesome.com/icons?d=gallery&m=free to see valid names INFO HTML book written to `[ROOT]/book` "#]]); }) .check_all_main_files(); } // Verifies that an invalid `git-repository-icon` in book.toml produces a // helpful error message with the icon name, type, and a link to FontAwesome. #[test] fn fontawesome_error_message() { BookTest::from_dir("rendering/fontawesome_error") .run("build", |cmd| { cmd.expect_failure(); cmd.expect_stderr(str![[r#" INFO Book building has started INFO Running the html backend ERROR Rendering failed [TAB]Caused by: Error rendering "index" line [..], col [..]: Unknown Font Awesome icon `github` for type `regular`. Hint: check the icon name and prefix (fas (solid), fab (brands), or far (regular)) at https://fontawesome.com/v6/search?m=free [TAB]Caused by: Unknown Font Awesome icon `github` for type `regular`. Hint: check the icon name and prefix (fas (solid), fab (brands), or far (regular)) at https://fontawesome.com/v6/search?m=free "#]]); }); } // Tests the rendering when setting the default rust edition. #[test] fn default_rust_edition() { BookTest::from_dir("rendering/default_rust_edition").check_all_main_files(); } // Tests the rendering for editable code blocks. #[test] fn editable_rust_block() { BookTest::from_dir("rendering/editable_rust_block").check_all_main_files(); } // Tests for custom hide lines. #[test] fn hidelines() { BookTest::from_dir("rendering/hidelines").check_all_main_files(); } // Tests for code blocks of basic rust code. #[test] fn language_rust_playground() { fn expect(input: &str, info: &str, expected: impl snapbox::IntoData) { BookTest::init(|_| {}) .change_file("book.toml", "output.html.playground.editable = true") .change_file("src/chapter_1.md", &format!("```rust {info}\n{input}\n```")) .check_main_file("book/chapter_1.html", expected); } // No-main should be wrapped in `fn main` boring lines. expect( "x()", "", str![[r#"
#![allow(unused)]
fn main() {
x()
}
"#]], ); // `fn main` should not be wrapped, not boring. expect( "fn main() {}", "", str![[r#"
fn main() {}
"#]], ); // Lines starting with `#` are boring. expect( "let s = \"foo\n # bar\n\";", "editable", str![[r#"
let s = "foo
 bar
";
"#]], ); // `##` is not boring and is used as an escape. expect( "let s = \"foo\n ## bar\n\";", "editable", str![[r#"
let s = "foo
 # bar
";
"#]], ); // `#` on a line by itself is boring. expect( "let s = \"foo\n # bar\n#\n\";", "editable", str![[r#"
let s = "foo
 bar

";
"#]], ); // `#` must be followed by a space to be boring. expect( "#x;", "", str![[r#"
#![allow(unused)]
fn main() {
#x;
}
"#]], ); // Other classes like "ignore" should not change things, and the class is // included in the code tag. expect( "let s = \"foo\n # bar\n\";", "ignore", str![[r#"
let s = "foo
 bar
";
"#]], ); // Inner attributes and normal attributes are not boring. expect( "#![no_std]\nlet s = \"foo\";\n #[some_attr]", "editable", str![[r#"
#![no_std]
let s = "foo";
 #[some_attr]
"#]], ); } // Rust code block in a list. #[test] fn code_block_in_list() { BookTest::init(|_| {}) .change_file( "src/chapter_1.md", r#"- inside list ```rust fn foo() { let x = 1; } ``` "#, ) .check_main_file( "book/chapter_1.html", str![[r#" "#]], ); } // Checks the rendering of links added to headers. #[test] fn header_links() { BookTest::from_dir("rendering/header_links").check_all_main_files(); } // A corrupted HTML end tag. #[test] fn busted_end_tag() { BookTest::init(|_| {}) .change_file("src/chapter_1.md", "
xfooy
") .run("build", |cmd| { cmd.expect_stderr(str![[r#" INFO Book building has started INFO Running the html backend WARN html parse error in `chapter_1.md`: Self-closing end tag Html text was:
xfooy
INFO HTML book written to `[ROOT]/book` "#]]); }) .check_main_file("book/chapter_1.html", str!["
xfooy
"]); } // Various html blocks. #[test] fn html_blocks() { BookTest::from_dir("rendering/html_blocks").check_all_main_files(); } // Test for a fenced code block that is also indented. #[test] fn code_block_fenced_with_indent() { BookTest::from_dir("rendering/code_blocks_fenced_with_indent").check_all_main_files(); } // Unclosed HTML tags. // // Note that the HTML parsing algorithm is much more complicated than what // this is checking. #[test] fn unclosed_html_tags() { BookTest::init(|_| {}) .change_file("src/chapter_1.md", "
xfooxyz") .run("build", |cmd| { cmd.expect_stderr(str![[r#" INFO Book building has started INFO Running the html backend WARN unclosed HTML tag `` found in `chapter_1.md` WARN unclosed HTML tag `` found in `chapter_1.md` WARN unclosed HTML tag `
` found in `chapter_1.md` INFO HTML book written to `[ROOT]/book` "#]]); }) .check_main_file( "book/chapter_1.html", str!["
xfooxyz
"], ); } // Test for HTML tags out of sync. #[test] fn unbalanced_html_tags() { BookTest::init(|_| {}) .change_file("src/chapter_1.md", "
xfoo
") .run("build", |cmd| { cmd.expect_stderr(str![[r#" INFO Book building has started INFO Running the html backend WARN unexpected HTML end tag `
` found in `chapter_1.md` Check that the HTML tags are properly balanced. WARN unclosed HTML tag `
` found in `chapter_1.md` INFO HTML book written to `[ROOT]/book` "#]]); }) .check_main_file("book/chapter_1.html", str!["
xfoo
"]); } // Test for bug with unbalanced HTML handling in the heading. #[test] fn heading_with_unbalanced_html() { BookTest::init(|_| {}) .change_file("src/chapter_1.md", "### Option") .run("build", |cmd| { cmd.expect_stderr(str![[r#" INFO Book building has started INFO Running the html backend WARN unclosed HTML tag `` found in `chapter_1.md` while exiting Heading(H3) HTML tags must be closed before exiting a markdown element. INFO HTML book written to `[ROOT]/book` "#]]); }) .check_main_file( "book/chapter_1.html", str![[r##"

Option

"##]], ); } // The following tests cover the `output.html.site-url` feature, which makes // every generated link absolute (rooted at `site-url`) so a book served from a // subdirectory resolves cross-chapter, asset, and sidebar links regardless of // the page's own depth. See https://github.com/rust-lang/mdBook/pull/1802. // Root-relative `./` links written in chapter content are anchored to the site // URL, while links with a scheme (e.g. `https`) are left untouched. #[test] fn site_url_rewrites_content_links() { BookTest::from_dir("rendering/site_url") .check_file_contains( "book/nested/deep.html", "other chapter", ) .check_file_contains( "book/index.html", "deep chapter", ) .check_file_contains( "book/index.html", "external link", ); } // `path_to_root` (used by the page chrome, prev/next navigation, and the // JavaScript sidebar in `toc.js`) becomes the absolute site URL on every page, // independent of how deeply the page is nested. #[test] fn site_url_sets_absolute_path_to_root() { BookTest::from_dir("rendering/site_url").check_file_contains( "book/nested/deep.html", "const path_to_root = \"https://example.com/docs/\";", ); } // Static assets resolved through the `{{resource}}` helper are emitted with the // absolute site URL rather than a depth-relative `../` prefix. #[test] fn site_url_makes_assets_absolute() { BookTest::from_dir("rendering/site_url").check_file_contains( "book/nested/deep.html", "` of the site URL so its root-relative chapter links resolve // absolutely. #[test] fn site_url_sets_toc_html_base() { BookTest::from_dir("rendering/site_url") .check_file_contains("book/toc.html", ""); } // The `` from `toc.html` must not leak onto regular chapter pages, // which would break their page-relative content links. #[test] fn site_url_no_base_href_on_chapter_pages() { BookTest::from_dir("rendering/site_url") .check_file_doesnt_contain("book/nested/deep.html", "` // is emitted: the feature is strictly opt-in. #[test] fn site_url_absent_keeps_links_relative() { BookTest::init(|_| {}) .check_file_contains("book/index.html", "const path_to_root = \"\";") .check_file_doesnt_contain("book/index.html", "deep chapter") .check_file_contains("book/print.html", "other chapter") .check_file_contains( "book/print.html", "external link", ); }