2025-04-22 11:17:40 -07:00
|
|
|
//! Tests for table of contents (sidebar).
|
|
|
|
|
|
|
|
|
|
use crate::prelude::*;
|
2025-07-21 11:37:46 -07:00
|
|
|
use anyhow::Result;
|
2025-04-22 11:17:40 -07:00
|
|
|
use select::document::Document;
|
2025-04-22 11:21:01 -07:00
|
|
|
use select::predicate::{Attr, Class, Name, Predicate};
|
2025-04-22 11:17:40 -07:00
|
|
|
|
2025-04-22 11:19:21 -07:00
|
|
|
const TOC_TOP_LEVEL: &[&str] = &[
|
|
|
|
|
"1. With Readme",
|
|
|
|
|
"3. Deep Nest 1",
|
|
|
|
|
"Prefix 1",
|
|
|
|
|
"Prefix 2",
|
|
|
|
|
"Suffix 1",
|
|
|
|
|
"Suffix 2",
|
|
|
|
|
];
|
2025-04-22 11:17:40 -07:00
|
|
|
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())
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-22 11:23:56 -07:00
|
|
|
/// 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");
|
2025-08-11 19:26:05 -07:00
|
|
|
let html = read_to_string(toc_path);
|
2025-04-22 11:23:56 -07:00
|
|
|
Ok(Document::from(html.as_str()))
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-22 11:17:40 -07:00
|
|
|
#[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);
|
|
|
|
|
}
|
2025-04-22 11:19:21 -07:00
|
|
|
|
|
|
|
|
#[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);
|
|
|
|
|
}
|
2025-04-22 11:20:04 -07:00
|
|
|
|
|
|
|
|
#[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);
|
|
|
|
|
}
|
2025-04-22 11:21:01 -07:00
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
}
|
2025-04-22 11:23:56 -07:00
|
|
|
|
|
|
|
|
// 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()
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-04-22 11:25:13 -07:00
|
|
|
|
|
|
|
|
// 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 ">
|
2025-10-20 17:29:31 -07:00
|
|
|
<span class="chapter-link-wrapper">
|
2025-04-22 11:25:13 -07:00
|
|
|
<a href="formatted-summary.html">
|
|
|
|
|
<strong aria-hidden="true">1.</strong> Italic code *escape* `escape2`</a>
|
2025-10-20 17:29:31 -07:00
|
|
|
</span>
|
2025-04-22 11:25:13 -07:00
|
|
|
</li>
|
|
|
|
|
<li class="chapter-item expanded ">
|
2025-10-20 17:29:31 -07:00
|
|
|
<span class="chapter-link-wrapper">
|
2025-04-22 11:25:13 -07:00
|
|
|
<a href="soft.html">
|
|
|
|
|
<strong aria-hidden="true">2.</strong> Soft line break</a>
|
2025-10-20 17:29:31 -07:00
|
|
|
</span>
|
2025-04-22 11:25:13 -07:00
|
|
|
</li>
|
|
|
|
|
<li class="chapter-item expanded ">
|
2025-10-20 17:29:31 -07:00
|
|
|
<span class="chapter-link-wrapper">
|
2025-04-22 11:25:13 -07:00
|
|
|
<a href="escaped-tag.html">
|
|
|
|
|
<strong aria-hidden="true">3.</strong> <escaped tag></a>
|
2025-10-20 17:29:31 -07:00
|
|
|
</span>
|
2025-04-22 11:25:13 -07:00
|
|
|
</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>
|
|
|
|
|
|
|
|
|
|
"#]],
|
|
|
|
|
);
|
|
|
|
|
}
|