Skip to content

rustdoc: add header map to the table of contents #120736

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Sep 5, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/doc/not_found.md
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@

<!-- Completely hide the TOC and the section numbers -->
<style type="text/css">
#TOC { display: none; }
#rustdoc-toc { display: none; }
.header-section-number { display: none; }
li {list-style-type: none; }
#search-input {
6 changes: 0 additions & 6 deletions src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
@@ -506,9 +506,6 @@ impl Item {
pub(crate) fn is_mod(&self) -> bool {
self.type_() == ItemType::Module
}
pub(crate) fn is_trait(&self) -> bool {
self.type_() == ItemType::Trait
}
pub(crate) fn is_struct(&self) -> bool {
self.type_() == ItemType::Struct
}
@@ -536,9 +533,6 @@ impl Item {
pub(crate) fn is_ty_method(&self) -> bool {
self.type_() == ItemType::TyMethod
}
pub(crate) fn is_type_alias(&self) -> bool {
self.type_() == ItemType::TypeAlias
}
pub(crate) fn is_primitive(&self) -> bool {
self.type_() == ItemType::Primitive
}
73 changes: 62 additions & 11 deletions src/librustdoc/html/markdown.rs
Original file line number Diff line number Diff line change
@@ -50,12 +50,12 @@ use rustc_span::{Span, Symbol};
use crate::clean::RenderedLink;
use crate::doctest;
use crate::doctest::GlobalTestOptions;
use crate::html::escape::Escape;
use crate::html::escape::{Escape, EscapeBodyText};
use crate::html::format::Buffer;
use crate::html::highlight;
use crate::html::length_limit::HtmlWithLimit;
use crate::html::render::small_url_encode;
use crate::html::toc::TocBuilder;
use crate::html::toc::{Toc, TocBuilder};

#[cfg(test)]
mod tests;
@@ -101,6 +101,7 @@ pub struct Markdown<'a> {
/// A struct like `Markdown` that renders the markdown with a table of contents.
pub(crate) struct MarkdownWithToc<'a> {
pub(crate) content: &'a str,
pub(crate) links: &'a [RenderedLink],
pub(crate) ids: &'a mut IdMap,
pub(crate) error_codes: ErrorCodes,
pub(crate) edition: Edition,
@@ -532,9 +533,11 @@ impl<'a, 'b, 'ids, I: Iterator<Item = SpannedEvent<'a>>> Iterator
let id = self.id_map.derive(id);

if let Some(ref mut builder) = self.toc {
let mut text_header = String::new();
plain_text_from_events(self.buf.iter().map(|(ev, _)| ev.clone()), &mut text_header);
let mut html_header = String::new();
html::push_html(&mut html_header, self.buf.iter().map(|(ev, _)| ev.clone()));
let sec = builder.push(level as u32, html_header, id.clone());
html_text_from_events(self.buf.iter().map(|(ev, _)| ev.clone()), &mut html_header);
let sec = builder.push(level as u32, text_header, html_header, id.clone());
self.buf.push_front((Event::Html(format!("{sec} ").into()), 0..0));
}

@@ -1415,10 +1418,23 @@ impl Markdown<'_> {
}

impl MarkdownWithToc<'_> {
pub(crate) fn into_string(self) -> String {
let MarkdownWithToc { content: md, ids, error_codes: codes, edition, playground } = self;
pub(crate) fn into_parts(self) -> (Toc, String) {
let MarkdownWithToc { content: md, links, ids, error_codes: codes, edition, playground } =
self;

let p = Parser::new_ext(md, main_body_opts()).into_offset_iter();
// This is actually common enough to special-case
if md.is_empty() {
return (Toc { entries: Vec::new() }, String::new());
}
let mut replacer = |broken_link: BrokenLink<'_>| {
links
.iter()
.find(|link| &*link.original_text == &*broken_link.reference)
.map(|link| (link.href.as_str().into(), link.tooltip.as_str().into()))
};

let p = Parser::new_with_broken_link_callback(md, main_body_opts(), Some(&mut replacer));
let p = p.into_offset_iter();

let mut s = String::with_capacity(md.len() * 3 / 2);

@@ -1432,7 +1448,11 @@ impl MarkdownWithToc<'_> {
html::push_html(&mut s, p);
}

format!("<nav id=\"TOC\">{toc}</nav>{s}", toc = toc.into_toc().print())
(toc.into_toc(), s)
}
pub(crate) fn into_string(self) -> String {
let (toc, s) = self.into_parts();
format!("<nav id=\"rustdoc\">{toc}</nav>{s}", toc = toc.print())
}
}

@@ -1611,7 +1631,16 @@ pub(crate) fn plain_text_summary(md: &str, link_names: &[RenderedLink]) -> Strin

let p = Parser::new_with_broken_link_callback(md, summary_opts(), Some(&mut replacer));

for event in p {
plain_text_from_events(p, &mut s);

s
}

pub(crate) fn plain_text_from_events<'a>(
events: impl Iterator<Item = pulldown_cmark::Event<'a>>,
s: &mut String,
) {
for event in events {
match &event {
Event::Text(text) => s.push_str(text),
Event::Code(code) => {
@@ -1626,8 +1655,29 @@ pub(crate) fn plain_text_summary(md: &str, link_names: &[RenderedLink]) -> Strin
_ => (),
}
}
}

s
pub(crate) fn html_text_from_events<'a>(
events: impl Iterator<Item = pulldown_cmark::Event<'a>>,
s: &mut String,
) {
for event in events {
match &event {
Event::Text(text) => {
write!(s, "{}", EscapeBodyText(text)).expect("string alloc infallible")
}
Event::Code(code) => {
s.push_str("<code>");
write!(s, "{}", EscapeBodyText(code)).expect("string alloc infallible");
s.push_str("</code>");
}
Event::HardBreak | Event::SoftBreak => s.push(' '),
Event::Start(Tag::CodeBlock(..)) => break,
Event::End(TagEnd::Paragraph) => break,
Event::End(TagEnd::Heading(..)) => break,
_ => (),
}
}
}

#[derive(Debug)]
@@ -1978,7 +2028,8 @@ fn init_id_map() -> FxHashMap<Cow<'static, str>, usize> {
map.insert("default-settings".into(), 1);
map.insert("sidebar-vars".into(), 1);
map.insert("copy-path".into(), 1);
map.insert("TOC".into(), 1);
map.insert("rustdoc-toc".into(), 1);
map.insert("rustdoc-modnav".into(), 1);
// This is the list of IDs used by rustdoc sections (but still generated by
// rustdoc).
map.insert("fields".into(), 1);
6 changes: 4 additions & 2 deletions src/librustdoc/html/render/context.rs
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ use rustc_span::edition::Edition;
use rustc_span::{sym, FileName, Symbol};

use super::print_item::{full_path, item_path, print_item};
use super::sidebar::{print_sidebar, sidebar_module_like, Sidebar};
use super::sidebar::{print_sidebar, sidebar_module_like, ModuleLike, Sidebar};
use super::write_shared::write_shared;
use super::{collect_spans_and_sources, scrape_examples_help, AllTypes, LinkFromSrc, StylePath};
use crate::clean::types::ExternalLocation;
@@ -616,12 +616,14 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
let all = shared.all.replace(AllTypes::new());
let mut sidebar = Buffer::html();

let blocks = sidebar_module_like(all.item_sections());
// all.html is not customizable, so a blank id map is fine
let blocks = sidebar_module_like(all.item_sections(), &mut IdMap::new(), ModuleLike::Crate);
let bar = Sidebar {
title_prefix: "",
title: "",
is_crate: false,
is_mod: false,
parent_is_crate: false,
blocks: vec![blocks],
path: String::new(),
};
282 changes: 184 additions & 98 deletions src/librustdoc/html/render/sidebar.rs

Large diffs are not rendered by default.

14 changes: 13 additions & 1 deletion src/librustdoc/html/static/css/rustdoc.css
Original file line number Diff line number Diff line change
@@ -568,12 +568,16 @@ img {
width: 48px;
}

ul.block, .block li {
ul.block, .block li, .block ul {
padding: 0;
margin: 0;
list-style: none;
}

.block ul a {
padding-left: 1rem;
}

.sidebar-elems a,
.sidebar > h2 a {
display: block;
@@ -585,6 +589,14 @@ ul.block, .block li {
background-clip: border-box;
}

.hide-toc #rustdoc-toc, .hide-toc .in-crate {
display: none;
}

.hide-modnav #rustdoc-modnav {
display: none;
}

.sidebar h2 {
text-wrap: balance;
overflow-wrap: anywhere;
4 changes: 2 additions & 2 deletions src/librustdoc/html/static/js/main.js
Original file line number Diff line number Diff line change
@@ -499,7 +499,7 @@ function preLoadCss(cssUrl) {
if (!window.SIDEBAR_ITEMS) {
return;
}
const sidebar = document.getElementsByClassName("sidebar-elems")[0];
const sidebar = document.getElementById("rustdoc-modnav");

/**
* Append to the sidebar a "block" of links - a heading along with a list (`<ul>`) of items.
@@ -885,7 +885,7 @@ function preLoadCss(cssUrl) {
if (!window.ALL_CRATES) {
return;
}
const sidebarElems = document.getElementsByClassName("sidebar-elems")[0];
const sidebarElems = document.getElementById("rustdoc-modnav");
if (!sidebarElems) {
return;
}
29 changes: 29 additions & 0 deletions src/librustdoc/html/static/js/settings.js
Original file line number Diff line number Diff line change
@@ -36,6 +36,20 @@
removeClass(document.documentElement, "hide-sidebar");
}
break;
case "hide-toc":
if (value === true) {
addClass(document.documentElement, "hide-toc");
} else {
removeClass(document.documentElement, "hide-toc");
}
break;
case "hide-modnav":
if (value === true) {
addClass(document.documentElement, "hide-modnav");
} else {
removeClass(document.documentElement, "hide-modnav");
}
break;
}
}

@@ -102,6 +116,11 @@
let output = "";

for (const setting of settings) {
if (setting === "hr") {
output += "<hr>";
continue;
}

const js_data_name = setting["js_name"];
const setting_name = setting["name"];

@@ -198,6 +217,16 @@
"js_name": "hide-sidebar",
"default": false,
},
{
"name": "Hide table of contents",
"js_name": "hide-toc",
"default": false,
},
{
"name": "Hide module navigation",
"js_name": "hide-modnav",
"default": false,
},
{
"name": "Disable keyboard shortcuts",
"js_name": "disable-shortcuts",
13 changes: 9 additions & 4 deletions src/librustdoc/html/static/js/storage.js
Original file line number Diff line number Diff line change
@@ -196,16 +196,21 @@ updateTheme();
// This needs to be done here because this JS is render-blocking,
// so that the sidebar doesn't "jump" after appearing on screen.
// The user interaction to change this is set up in main.js.
//
// At this point in page load, `document.body` is not available yet.
// Set a class on the `<html>` element instead.
if (getSettingValue("source-sidebar-show") === "true") {
// At this point in page load, `document.body` is not available yet.
// Set a class on the `<html>` element instead.
addClass(document.documentElement, "src-sidebar-expanded");
}
if (getSettingValue("hide-sidebar") === "true") {
// At this point in page load, `document.body` is not available yet.
// Set a class on the `<html>` element instead.
addClass(document.documentElement, "hide-sidebar");
}
if (getSettingValue("hide-toc") === "true") {
addClass(document.documentElement, "hide-toc");
}
if (getSettingValue("hide-modnav") === "true") {
addClass(document.documentElement, "hide-modnav");
}
function updateSidebarWidth() {
const desktopSidebarWidth = getSettingValue("desktop-sidebar-width");
if (desktopSidebarWidth && desktopSidebarWidth !== "null") {
49 changes: 38 additions & 11 deletions src/librustdoc/html/templates/sidebar.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
{% if !title.is_empty() %}
<h2 class="location"> {# #}
<a href="#">{{title_prefix}}{{title|wrapped|safe}}</a> {# #}
</h2>
{% endif %}
<div class="sidebar-elems">
{% if is_crate %}
<ul class="block"> {# #}
@@ -11,26 +6,58 @@ <h2 class="location"> {# #}
{% endif %}

{% if self.should_render_blocks() %}
<section>
<section id="rustdoc-toc">
{% if !title.is_empty() %}
<h2 class="location"> {# #}
<a href="#">{{title_prefix}}{{title|wrapped|safe}}</a> {# #}
</h2>
{% endif %}
{% for block in blocks %}
{% if block.should_render() %}
{% if !block.heading.name.is_empty() %}
<h3><a href="#{{block.heading.href|safe}}"> {# #}
{{block.heading.name|wrapped|safe}} {# #}
</a></h3> {# #}
<h3> {# #}
<a href="#{{block.heading.href|safe}}">{{block.heading.name|wrapped|safe}}</a> {# #}
</h3>
{% endif %}
{% if !block.links.is_empty() %}
<ul class="block{% if !block.class.is_empty() +%} {{+block.class}}{% endif %}">
{% for link in block.links %}
<li><a href="#{{link.href|safe}}">{{link.name}}</a></li>
<li> {# #}
<a href="#{{link.href|safe}}" title="{{link.name}}">
{% match link.name_html %}
{% when Some with (html) %}
{{html|safe}}
{% else %}
{{link.name}}
{% endmatch %}
</a> {# #}
{% if !link.children.is_empty() %}
<ul>
{% for child in link.children %}
<li><a href="#{{child.href|safe}}" title="{{child.name}}">
{% match child.name_html %}
{% when Some with (html) %}
{{html|safe}}
{% else %}
{{child.name}}
{% endmatch %}
</a></li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
{% endif %}
{% endfor %}
</section>
{% endif %}
<div id="rustdoc-modnav">
{% if !path.is_empty() %}
<h2><a href="{% if is_mod %}../{% endif %}index.html">In {{+ path|wrapped|safe}}</a></h2>
<h2{% if parent_is_crate +%} class="in-crate"{% endif %}> {# #}
<a href="{% if is_mod %}../{% endif %}index.html">In {{+ path|wrapped|safe}}</a> {# #}
</h2>
{% endif %}
</div> {# #}
</div>
26 changes: 17 additions & 9 deletions src/librustdoc/html/toc.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! Table-of-contents creation.
use crate::html::escape::Escape;

/// A (recursive) table of contents
#[derive(Debug, PartialEq)]
@@ -16,7 +17,7 @@ pub(crate) struct Toc {
/// ### A
/// ## B
/// ```
entries: Vec<TocEntry>,
pub(crate) entries: Vec<TocEntry>,
}

impl Toc {
@@ -27,11 +28,16 @@ impl Toc {

#[derive(Debug, PartialEq)]
pub(crate) struct TocEntry {
level: u32,
sec_number: String,
name: String,
id: String,
children: Toc,
pub(crate) level: u32,
pub(crate) sec_number: String,
// name is a plain text header that works in a `title` tag
// html includes `<code>` tags
// the tooltip is used so that, when a toc is truncated,
// you can mouse over it to see the whole thing
pub(crate) name: String,
pub(crate) html: String,
pub(crate) id: String,
pub(crate) children: Toc,
}

/// Progressive construction of a table of contents.
@@ -115,7 +121,7 @@ impl TocBuilder {
/// Push a level `level` heading into the appropriate place in the
/// hierarchy, returning a string containing the section number in
/// `<num>.<num>.<num>` format.
pub(crate) fn push(&mut self, level: u32, name: String, id: String) -> &str {
pub(crate) fn push(&mut self, level: u32, name: String, html: String, id: String) -> &str {
assert!(level >= 1);

// collapse all previous sections into their parents until we
@@ -149,6 +155,7 @@ impl TocBuilder {
self.chain.push(TocEntry {
level,
name,
html,
sec_number,
id,
children: Toc { entries: Vec::new() },
@@ -170,10 +177,11 @@ impl Toc {
// recursively format this table of contents
let _ = write!(
v,
"\n<li><a href=\"#{id}\">{num} {name}</a>",
"\n<li><a href=\"#{id}\" title=\"{name}\">{num} {html}</a>",
id = entry.id,
num = entry.sec_number,
name = entry.name
name = Escape(&entry.name),
html = &entry.html,
);
entry.children.print_inner(&mut *v);
v.push_str("</li>");
6 changes: 5 additions & 1 deletion src/librustdoc/html/toc/tests.rs
Original file line number Diff line number Diff line change
@@ -9,7 +9,10 @@ fn builder_smoke() {
// there's been no macro mistake.
macro_rules! push {
($level: expr, $name: expr) => {
assert_eq!(builder.push($level, $name.to_string(), "".to_string()), $name);
assert_eq!(
builder.push($level, $name.to_string(), $name.to_string(), "".to_string()),
$name
);
};
}
push!(2, "0.1");
@@ -48,6 +51,7 @@ fn builder_smoke() {
TocEntry {
level: $level,
name: $name.to_string(),
html: $name.to_string(),
sec_number: $name.to_string(),
id: "".to_string(),
children: toc!($($sub),*)
1 change: 1 addition & 0 deletions src/librustdoc/markdown.rs
Original file line number Diff line number Diff line change
@@ -72,6 +72,7 @@ pub(crate) fn render<P: AsRef<Path>>(
let text = if !options.markdown_no_toc {
MarkdownWithToc {
content: text,
links: &[],
ids: &mut ids,
error_codes,
edition,
44 changes: 44 additions & 0 deletions tests/rustdoc-gui/sidebar-modnav-position.goml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Verifies that, when TOC is hidden, modnav is always in exactly the same spot
// This is driven by a reasonably common use case:
//
// - There are three or more items that might meet my needs.
// - I open the first one, decide it's not what I want, switch to the second one using the sidebar.
// - The second one also doesn't meet my needs, so I switch to the third.
// - The third also doesn't meet my needs, so...
//
// because the sibling module nav is in exactly the same place every time,
// it's very easy to find and switch between pages that way.

go-to: "file://" + |DOC_PATH| + "/test_docs/enum.WhoLetTheDogOut.html"
show-text: true
set-local-storage: {"rustdoc-hide-toc": "true"}

define-function: (
"check-positions",
[url],
block {
go-to: "file://" + |DOC_PATH| + |url|
// Checking results colors.
assert-position: ("#rustdoc-modnav > h2", {"x": |h2_x|, "y": |h2_y|})
assert-position: (
"#rustdoc-modnav > ul:first-of-type > li:first-of-type",
{"x": |x|, "y": |y|}
)
},
)

// First, at test_docs root
go-to: "file://" + |DOC_PATH| + "/test_docs/enum.WhoLetTheDogOut.html"
store-position: ("#rustdoc-modnav > h2", {"x": h2_x, "y": h2_y})
store-position: ("#rustdoc-modnav > ul:first-of-type > li:first-of-type", {"x": x, "y": y})
call-function: ("check-positions", {"url": "/test_docs/enum.WhoLetTheDogOut.html"})
call-function: ("check-positions", {"url": "/test_docs/struct.StructWithPublicUndocumentedFields.html"})
call-function: ("check-positions", {"url": "/test_docs/codeblock_sub/index.html"})

// Now in a submodule
go-to: "file://" + |DOC_PATH| + "/test_docs/fields/struct.Struct.html"
store-position: ("#rustdoc-modnav > h2", {"x": h2_x, "y": h2_y})
store-position: ("#rustdoc-modnav > ul:first-of-type > li:first-of-type", {"x": x, "y": y})
call-function: ("check-positions", {"url": "/test_docs/fields/struct.Struct.html"})
call-function: ("check-positions", {"url": "/test_docs/fields/union.Union.html"})
call-function: ("check-positions", {"url": "/test_docs/fields/enum.Enum.html"})
45 changes: 39 additions & 6 deletions tests/rustdoc-gui/sidebar.goml
Original file line number Diff line number Diff line change
@@ -118,16 +118,16 @@ assert-false: ".sidebar-elems > .crate"
go-to: "./module/index.html"
assert-property: (".sidebar", {"clientWidth": "200"})
assert-text: (".sidebar > .sidebar-crate > h2 > a", "lib2")
assert-text: (".sidebar > .location", "Module module")
assert-text: (".sidebar .location", "Module module")
assert-count: (".sidebar .location", 1)
assert-text: (".sidebar-elems ul.block > li.current > a", "module")
// Module page requires three headings:
// - Presistent crate branding (name and version)
// - Module name, followed by TOC for module headings
// - "In crate [name]" parent pointer, followed by sibling navigation
assert-count: (".sidebar h2", 3)
assert-text: (".sidebar > .sidebar-elems > h2", "In crate lib2")
assert-property: (".sidebar > .sidebar-elems > h2 > a", {
assert-text: (".sidebar > .sidebar-elems > #rustdoc-modnav > h2", "In crate lib2")
assert-property: (".sidebar > .sidebar-elems > #rustdoc-modnav > h2 > a", {
"href": "/lib2/index.html",
}, ENDS_WITH)
// We check that we don't have the crate list.
@@ -136,9 +136,9 @@ assert-false: ".sidebar-elems > .crate"
go-to: "./sub_module/sub_sub_module/index.html"
assert-property: (".sidebar", {"clientWidth": "200"})
assert-text: (".sidebar > .sidebar-crate > h2 > a", "lib2")
assert-text: (".sidebar > .location", "Module sub_sub_module")
assert-text: (".sidebar > .sidebar-elems > h2", "In lib2::module::sub_module")
assert-property: (".sidebar > .sidebar-elems > h2 > a", {
assert-text: (".sidebar .location", "Module sub_sub_module")
assert-text: (".sidebar > .sidebar-elems > #rustdoc-modnav > h2", "In lib2::module::sub_module")
assert-property: (".sidebar > .sidebar-elems > #rustdoc-modnav > h2 > a", {
"href": "/module/sub_module/index.html",
}, ENDS_WITH)
assert-text: (".sidebar-elems ul.block > li.current > a", "sub_sub_module")
@@ -198,3 +198,36 @@ assert-position-false: (".sidebar-crate > h2 > a", {"x": -3})
// when line-wrapped, see that it becomes flush-left again
drag-and-drop: ((205, 100), (108, 100))
assert-position: (".sidebar-crate > h2 > a", {"x": -3})

// Configuration option to show TOC in sidebar.
set-local-storage: {"rustdoc-hide-toc": "true"}
go-to: "file://" + |DOC_PATH| + "/test_docs/enum.WhoLetTheDogOut.html"
assert-css: ("#rustdoc-toc", {"display": "none"})
assert-css: (".sidebar .in-crate", {"display": "none"})
set-local-storage: {"rustdoc-hide-toc": "false"}
go-to: "file://" + |DOC_PATH| + "/test_docs/enum.WhoLetTheDogOut.html"
assert-css: ("#rustdoc-toc", {"display": "block"})
assert-css: (".sidebar .in-crate", {"display": "block"})

set-local-storage: {"rustdoc-hide-modnav": "true"}
go-to: "file://" + |DOC_PATH| + "/test_docs/enum.WhoLetTheDogOut.html"
assert-css: ("#rustdoc-modnav", {"display": "none"})
set-local-storage: {"rustdoc-hide-modnav": "false"}
go-to: "file://" + |DOC_PATH| + "/test_docs/enum.WhoLetTheDogOut.html"
assert-css: ("#rustdoc-modnav", {"display": "block"})

set-local-storage: {"rustdoc-hide-toc": "true"}
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
assert-css: ("#rustdoc-toc", {"display": "none"})
assert-false: ".sidebar .in-crate"
set-local-storage: {"rustdoc-hide-toc": "false"}
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
assert-css: ("#rustdoc-toc", {"display": "block"})
assert-false: ".sidebar .in-crate"

set-local-storage: {"rustdoc-hide-modnav": "true"}
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
assert-css: ("#rustdoc-modnav", {"display": "none"})
set-local-storage: {"rustdoc-hide-modnav": "false"}
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
assert-css: ("#rustdoc-modnav", {"display": "block"})
16 changes: 16 additions & 0 deletions tests/rustdoc/sidebar/module.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#![crate_name = "foo"]

//@ has 'foo/index.html'
//@ has - '//section[@id="rustdoc-toc"]/h3' 'Crate Items'

//@ has 'foo/bar/index.html'
//@ has - '//section[@id="rustdoc-toc"]/h3' 'Module Items'
pub mod bar {
//@ has 'foo/bar/struct.Baz.html'
//@ !has - '//section[@id="rustdoc-toc"]/h3' 'Module Items'
pub struct Baz;
}

//@ has 'foo/baz/index.html'
//@ !has - '//section[@id="rustdoc-toc"]/h3' 'Module Items'
pub mod baz {}
File renamed without changes.
File renamed without changes.
File renamed without changes.
23 changes: 23 additions & 0 deletions tests/rustdoc/sidebar/top-toc-html.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// ignore-tidy-linelength

#![crate_name = "foo"]
#![feature(lazy_type_alias)]
#![allow(incomplete_features)]

//! # Basic [link](https://example.com), *emphasis*, **_very emphasis_** and `code`
//!
//! This test case covers TOC entries with rich text inside.
//! Rustdoc normally supports headers with links, but for the
//! TOC, that would break the layout.
//!
//! For consistency, emphasis is also filtered out.
//@ has foo/index.html
// User header
//@ has - '//section[@id="rustdoc-toc"]/h3' 'Sections'
//@ has - '//section[@id="rustdoc-toc"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-emphasis-very-emphasis-and-code"]/@title' 'Basic link, emphasis, very emphasis and `code`'
//@ has - '//section[@id="rustdoc-toc"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-emphasis-very-emphasis-and-code"]' 'Basic link, emphasis, very emphasis and code'
//@ count - '//section[@id="rustdoc-toc"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-emphasis-very-emphasis-and-code"]/em' 0
//@ count - '//section[@id="rustdoc-toc"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-emphasis-very-emphasis-and-code"]/a' 0
//@ count - '//section[@id="rustdoc-toc"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-emphasis-very-emphasis-and-code"]/code' 1
//@ has - '//section[@id="rustdoc-toc"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-emphasis-very-emphasis-and-code"]/code' 'code'
44 changes: 44 additions & 0 deletions tests/rustdoc/sidebar/top-toc-idmap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#![crate_name = "foo"]
#![feature(lazy_type_alias)]
#![allow(incomplete_features)]

//! # Structs
//!
//! This header has the same name as a built-in header,
//! and we need to make sure they're disambiguated with
//! suffixes.
//!
//! Module-like headers get derived from the internal ID map,
//! so the *internal* one gets a suffix here. To make sure it
//! works right, the one in the `top-toc` needs to match the one
//! in the `top-doc`, and the one that's not in the `top-doc`
//! needs to match the one that isn't in the `top-toc`.
//@ has foo/index.html
// User header
//@ has - '//section[@id="rustdoc-toc"]/ul[@class="block top-toc"]/li/a[@href="#structs"]' 'Structs'
//@ has - '//details[@class="toggle top-doc"]/div[@class="docblock"]/h2[@id="structs"]' 'Structs'
// Built-in header
//@ has - '//section[@id="rustdoc-toc"]/ul[@class="block"]/li/a[@href="#structs-1"]' 'Structs'
//@ has - '//section[@id="main-content"]/h2[@id="structs-1"]' 'Structs'

/// # Fields
/// ## Fields
/// ### Fields
///
/// The difference between struct-like headers and module-like headers
/// is strange, but not actually a problem as long as we're consistent.
//@ has foo/struct.MyStruct.html
// User header
//@ has - '//section[@id="rustdoc-toc"]/ul[@class="block top-toc"]/li/a[@href="#fields-1"]' 'Fields'
//@ has - '//details[@class="toggle top-doc"]/div[@class="docblock"]/h2[@id="fields-1"]' 'Fields'
// Only one level of nesting
//@ count - '//section[@id="rustdoc-toc"]/ul[@class="block top-toc"]//a' 2
// Built-in header
//@ has - '//section[@id="rustdoc-toc"]/h3/a[@href="#fields"]' 'Fields'
//@ has - '//section[@id="main-content"]/h2[@id="fields"]' 'Fields'

pub struct MyStruct {
pub fields: i32,
}
7 changes: 7 additions & 0 deletions tests/rustdoc/sidebar/top-toc-nil.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![crate_name = "foo"]

//! This test case covers missing top TOC entries.
//@ has foo/index.html
// User header
//@ !has - '//section[@id="rustdoc-toc"]/ul[@class="block top-toc"]' 'Basic link and emphasis'
2 changes: 1 addition & 1 deletion tests/rustdoc/strip-enum-variant.no-not-shown.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<ul class="block variant"><li><a href="#variant.Shown">Shown</a></li></ul>
<ul class="block variant"><li><a href="#variant.Shown" title="Shown">Shown</a></li></ul>