mirror of
https://github.com/rust-lang/mdBook.git
synced 2025-12-27 09:05:40 -05:00
This fixes an issue when folding is enabled. The folding was not properly hiding the sub-chapters because it was assuming it could hide the next list element. However, the heading nav was the next list element, so the remaining chapters remained visible. The solution required some deeper changes to how the chapters were organized in the sidebar. Instead of nested chapters being a list element *sibling*, the nested chapter's `ol` is now a *child* of its parent chapter. This makes it much easier to just hide everything without regard of the exact sibling order. This required wrapping the chapter title and the toggle chevron inside a span so that the flex layout could be localized to just those elements, and allow the following `ol` elements to lay out regularly. Closes https://github.com/rust-lang/mdBook/issues/2880
192 lines
4.6 KiB
Rust
192 lines
4.6 KiB
Rust
//! Tests for table of contents (sidebar).
|
|
|
|
use crate::prelude::*;
|
|
use anyhow::Result;
|
|
use select::document::Document;
|
|
use select::predicate::{Attr, Class, Name, Predicate};
|
|
|
|
const TOC_TOP_LEVEL: &[&str] = &[
|
|
"1. With Readme",
|
|
"3. Deep Nest 1",
|
|
"Prefix 1",
|
|
"Prefix 2",
|
|
"Suffix 1",
|
|
"Suffix 2",
|
|
];
|
|
const TOC_SECOND_LEVEL: &[&str] = &[
|
|
"1.1. Nested Index",
|
|
"1.2. Nested two",
|
|
"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<Document> {
|
|
let mut test = BookTest::from_dir("toc/basic_toc");
|
|
test.build();
|
|
|
|
let toc_path = test.dir.join("book").join("toc.html");
|
|
let html = read_to_string(toc_path);
|
|
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#"
|
|
<ol class="chapter">
|
|
<li class="chapter-item expanded ">
|
|
<span class="chapter-link-wrapper">
|
|
<a href="formatted-summary.html">
|
|
<strong aria-hidden="true">1.</strong> Italic code *escape* `escape2`</a>
|
|
</span>
|
|
</li>
|
|
<li class="chapter-item expanded ">
|
|
<span class="chapter-link-wrapper">
|
|
<a href="soft.html">
|
|
<strong aria-hidden="true">2.</strong> Soft line break</a>
|
|
</span>
|
|
</li>
|
|
<li class="chapter-item expanded ">
|
|
<span class="chapter-link-wrapper">
|
|
<a href="escaped-tag.html">
|
|
<strong aria-hidden="true">3.</strong> <escaped tag></a>
|
|
</span>
|
|
</li>
|
|
</ol>
|
|
"#]])
|
|
.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>
|
|
|
|
"#]],
|
|
);
|
|
}
|