mirror of
https://github.com/rust-lang/rust.git
synced 2025-02-05 11:33:04 +00:00
Rollup merge of #117662 - GuillaumeGomez:links-in-headings, r=notriddle
[rustdoc] Allows links in headings Reopening of https://github.com/rust-lang/rust/pull/94360. # Explanations Rustdoc currently doesn't follow the markdown spec on headings: we don't allow links in them. So instead of having headings linking to themselves, this PR generates an anchor on the left side like this: ![image](https://github.com/rust-lang/rust/assets/3050060/a118a7e9-5ef8-4d07-914f-46defc3245c3) <details> <summary>previous version</summary> ![image](https://github.com/rust-lang/rust/assets/3050060/c34fa844-9cd4-47dc-bb51-b37f5f66afee) </details> Having the anchor always displayed allows for mobile devices users to be able to have a link to the anchor. The different color used for the anchor itself is the same as links so people notice when looking at it that they can click on it. You can test it [here](https://rustdoc.crud.net/imperio/links-in-headings/std/index.html). cc `@camelid` r? `@notriddle`
This commit is contained in:
commit
cad609d9e3
@ -530,7 +530,6 @@ impl<'a, 'b, 'ids, I: Iterator<Item = SpannedEvent<'a>>> Iterator
|
||||
for event in &mut self.inner {
|
||||
match &event.0 {
|
||||
Event::End(Tag::Heading(..)) => break,
|
||||
Event::Start(Tag::Link(_, _, _)) | Event::End(Tag::Link(..)) => {}
|
||||
Event::Text(text) | Event::Code(text) => {
|
||||
id.extend(text.chars().filter_map(slugify));
|
||||
self.buf.push_back(event);
|
||||
@ -549,12 +548,10 @@ impl<'a, 'b, 'ids, I: Iterator<Item = SpannedEvent<'a>>> Iterator
|
||||
|
||||
let level =
|
||||
std::cmp::min(level as u32 + (self.heading_offset as u32), MAX_HEADER_LEVEL);
|
||||
self.buf.push_back((Event::Html(format!("</a></h{level}>").into()), 0..0));
|
||||
self.buf.push_back((Event::Html(format!("</h{level}>").into()), 0..0));
|
||||
|
||||
let start_tags = format!(
|
||||
"<h{level} id=\"{id}\">\
|
||||
<a href=\"#{id}\">",
|
||||
);
|
||||
let start_tags =
|
||||
format!("<h{level} id=\"{id}\"><a class=\"doc-anchor\" href=\"#{id}\">§</a>");
|
||||
return Some((Event::Html(start_tags.into()), 0..0));
|
||||
}
|
||||
event
|
||||
|
@ -311,26 +311,38 @@ fn test_header() {
|
||||
assert_eq!(output, expect, "original: {}", input);
|
||||
}
|
||||
|
||||
t("# Foo bar", "<h2 id=\"foo-bar\"><a href=\"#foo-bar\">Foo bar</a></h2>");
|
||||
t(
|
||||
"# Foo bar",
|
||||
"<h2 id=\"foo-bar\"><a class=\"doc-anchor\" href=\"#foo-bar\">§</a>Foo bar</h2>",
|
||||
);
|
||||
t(
|
||||
"## Foo-bar_baz qux",
|
||||
"<h3 id=\"foo-bar_baz-qux\">\
|
||||
<a href=\"#foo-bar_baz-qux\">Foo-bar_baz qux</a></h3>",
|
||||
<a class=\"doc-anchor\" href=\"#foo-bar_baz-qux\">§</a>\
|
||||
Foo-bar_baz qux\
|
||||
</h3>",
|
||||
);
|
||||
t(
|
||||
"### **Foo** *bar* baz!?!& -_qux_-%",
|
||||
"<h4 id=\"foo-bar-baz--qux-\">\
|
||||
<a href=\"#foo-bar-baz--qux-\"><strong>Foo</strong> \
|
||||
<em>bar</em> baz!?!& -<em>qux</em>-%</a>\
|
||||
<a class=\"doc-anchor\" href=\"#foo-bar-baz--qux-\">§</a>\
|
||||
<strong>Foo</strong> <em>bar</em> baz!?!& -<em>qux</em>-%\
|
||||
</h4>",
|
||||
);
|
||||
t(
|
||||
"#### **Foo?** & \\*bar?!* _`baz`_ ❤ #qux",
|
||||
"<h5 id=\"foo--bar--baz--qux\">\
|
||||
<a href=\"#foo--bar--baz--qux\"><strong>Foo?</strong> & *bar?!* \
|
||||
<em><code>baz</code></em> ❤ #qux</a>\
|
||||
<a class=\"doc-anchor\" href=\"#foo--bar--baz--qux\">§</a>\
|
||||
<strong>Foo?</strong> & *bar?!* <em><code>baz</code></em> ❤ #qux\
|
||||
</h5>",
|
||||
);
|
||||
t(
|
||||
"# Foo [bar](https://hello.yo)",
|
||||
"<h2 id=\"foo-bar\">\
|
||||
<a class=\"doc-anchor\" href=\"#foo-bar\">§</a>\
|
||||
Foo <a href=\"https://hello.yo\">bar</a>\
|
||||
</h2>",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -351,12 +363,36 @@ fn test_header_ids_multiple_blocks() {
|
||||
assert_eq!(output, expect, "original: {}", input);
|
||||
}
|
||||
|
||||
t(&mut map, "# Example", "<h2 id=\"example\"><a href=\"#example\">Example</a></h2>");
|
||||
t(&mut map, "# Panics", "<h2 id=\"panics\"><a href=\"#panics\">Panics</a></h2>");
|
||||
t(&mut map, "# Example", "<h2 id=\"example-1\"><a href=\"#example-1\">Example</a></h2>");
|
||||
t(&mut map, "# Search", "<h2 id=\"search-1\"><a href=\"#search-1\">Search</a></h2>");
|
||||
t(&mut map, "# Example", "<h2 id=\"example-2\"><a href=\"#example-2\">Example</a></h2>");
|
||||
t(&mut map, "# Panics", "<h2 id=\"panics-1\"><a href=\"#panics-1\">Panics</a></h2>");
|
||||
t(
|
||||
&mut map,
|
||||
"# Example",
|
||||
"<h2 id=\"example\"><a class=\"doc-anchor\" href=\"#example\">§</a>Example</h2>",
|
||||
);
|
||||
t(
|
||||
&mut map,
|
||||
"# Panics",
|
||||
"<h2 id=\"panics\"><a class=\"doc-anchor\" href=\"#panics\">§</a>Panics</h2>",
|
||||
);
|
||||
t(
|
||||
&mut map,
|
||||
"# Example",
|
||||
"<h2 id=\"example-1\"><a class=\"doc-anchor\" href=\"#example-1\">§</a>Example</h2>",
|
||||
);
|
||||
t(
|
||||
&mut map,
|
||||
"# Search",
|
||||
"<h2 id=\"search-1\"><a class=\"doc-anchor\" href=\"#search-1\">§</a>Search</h2>",
|
||||
);
|
||||
t(
|
||||
&mut map,
|
||||
"# Example",
|
||||
"<h2 id=\"example-2\"><a class=\"doc-anchor\" href=\"#example-2\">§</a>Example</h2>",
|
||||
);
|
||||
t(
|
||||
&mut map,
|
||||
"# Panics",
|
||||
"<h2 id=\"panics-1\"><a class=\"doc-anchor\" href=\"#panics-1\">§</a>Panics</h2>",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1207,17 +1207,31 @@ impl<'a> AssocItemLink<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn write_impl_section_heading(mut w: impl fmt::Write, title: &str, id: &str) {
|
||||
pub fn write_section_heading(
|
||||
w: &mut impl fmt::Write,
|
||||
title: &str,
|
||||
id: &str,
|
||||
extra_class: Option<&str>,
|
||||
extra: impl fmt::Display,
|
||||
) {
|
||||
let (extra_class, whitespace) = match extra_class {
|
||||
Some(extra) => (extra, " "),
|
||||
None => ("", ""),
|
||||
};
|
||||
write!(
|
||||
w,
|
||||
"<h2 id=\"{id}\" class=\"section-header\">\
|
||||
"<h2 id=\"{id}\" class=\"{extra_class}{whitespace}section-header\">\
|
||||
{title}\
|
||||
<a href=\"#{id}\" class=\"anchor\">§</a>\
|
||||
</h2>"
|
||||
</h2>{extra}",
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn write_impl_section_heading(w: &mut impl fmt::Write, title: &str, id: &str) {
|
||||
write_section_heading(w, title, id, None, "")
|
||||
}
|
||||
|
||||
pub(crate) fn render_all_impls(
|
||||
mut w: impl Write,
|
||||
cx: &mut Context<'_>,
|
||||
|
@ -19,8 +19,8 @@ use super::{
|
||||
item_ty_to_section, notable_traits_button, notable_traits_json, render_all_impls,
|
||||
render_assoc_item, render_assoc_items, render_attributes_in_code, render_attributes_in_pre,
|
||||
render_impl, render_rightside, render_stability_since_raw,
|
||||
render_stability_since_raw_with_extra, AssocItemLink, AssocItemRender, Context,
|
||||
ImplRenderingParameters, RenderMode,
|
||||
render_stability_since_raw_with_extra, write_section_heading, AssocItemLink, AssocItemRender,
|
||||
Context, ImplRenderingParameters, RenderMode,
|
||||
};
|
||||
use crate::clean;
|
||||
use crate::config::ModuleSorting;
|
||||
@ -425,13 +425,12 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
|
||||
w.write_str(ITEM_TABLE_CLOSE);
|
||||
}
|
||||
last_section = Some(my_section);
|
||||
write!(
|
||||
write_section_heading(
|
||||
w,
|
||||
"<h2 id=\"{id}\" class=\"section-header\">\
|
||||
<a href=\"#{id}\">{name}</a>\
|
||||
</h2>{ITEM_TABLE_OPEN}",
|
||||
id = cx.derive_id(my_section.id()),
|
||||
name = my_section.name(),
|
||||
my_section.name(),
|
||||
&cx.derive_id(my_section.id()),
|
||||
None,
|
||||
ITEM_TABLE_OPEN,
|
||||
);
|
||||
}
|
||||
|
||||
@ -814,16 +813,6 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
|
||||
// Trait documentation
|
||||
write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
|
||||
|
||||
fn write_small_section_header(w: &mut Buffer, id: &str, title: &str, extra_content: &str) {
|
||||
write!(
|
||||
w,
|
||||
"<h2 id=\"{0}\" class=\"section-header\">\
|
||||
{1}<a href=\"#{0}\" class=\"anchor\">§</a>\
|
||||
</h2>{2}",
|
||||
id, title, extra_content
|
||||
)
|
||||
}
|
||||
|
||||
fn trait_item(w: &mut Buffer, cx: &mut Context<'_>, m: &clean::Item, t: &clean::Item) {
|
||||
let name = m.name.unwrap();
|
||||
info!("Documenting {name} on {ty_name:?}", ty_name = t.name);
|
||||
@ -857,10 +846,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
|
||||
}
|
||||
|
||||
if !required_types.is_empty() {
|
||||
write_small_section_header(
|
||||
write_section_heading(
|
||||
w,
|
||||
"required-associated-types",
|
||||
"Required Associated Types",
|
||||
"required-associated-types",
|
||||
None,
|
||||
"<div class=\"methods\">",
|
||||
);
|
||||
for t in required_types {
|
||||
@ -869,10 +859,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
|
||||
w.write_str("</div>");
|
||||
}
|
||||
if !provided_types.is_empty() {
|
||||
write_small_section_header(
|
||||
write_section_heading(
|
||||
w,
|
||||
"provided-associated-types",
|
||||
"Provided Associated Types",
|
||||
"provided-associated-types",
|
||||
None,
|
||||
"<div class=\"methods\">",
|
||||
);
|
||||
for t in provided_types {
|
||||
@ -882,10 +873,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
|
||||
}
|
||||
|
||||
if !required_consts.is_empty() {
|
||||
write_small_section_header(
|
||||
write_section_heading(
|
||||
w,
|
||||
"required-associated-consts",
|
||||
"Required Associated Constants",
|
||||
"required-associated-consts",
|
||||
None,
|
||||
"<div class=\"methods\">",
|
||||
);
|
||||
for t in required_consts {
|
||||
@ -894,10 +886,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
|
||||
w.write_str("</div>");
|
||||
}
|
||||
if !provided_consts.is_empty() {
|
||||
write_small_section_header(
|
||||
write_section_heading(
|
||||
w,
|
||||
"provided-associated-consts",
|
||||
"Provided Associated Constants",
|
||||
"provided-associated-consts",
|
||||
None,
|
||||
"<div class=\"methods\">",
|
||||
);
|
||||
for t in provided_consts {
|
||||
@ -908,10 +901,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
|
||||
|
||||
// Output the documentation for each function individually
|
||||
if !required_methods.is_empty() || must_implement_one_of_functions.is_some() {
|
||||
write_small_section_header(
|
||||
write_section_heading(
|
||||
w,
|
||||
"required-methods",
|
||||
"Required Methods",
|
||||
"required-methods",
|
||||
None,
|
||||
"<div class=\"methods\">",
|
||||
);
|
||||
|
||||
@ -929,10 +923,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
|
||||
w.write_str("</div>");
|
||||
}
|
||||
if !provided_methods.is_empty() {
|
||||
write_small_section_header(
|
||||
write_section_heading(
|
||||
w,
|
||||
"provided-methods",
|
||||
"Provided Methods",
|
||||
"provided-methods",
|
||||
None,
|
||||
"<div class=\"methods\">",
|
||||
);
|
||||
for m in provided_methods {
|
||||
@ -949,10 +944,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
|
||||
let mut extern_crates = FxHashSet::default();
|
||||
|
||||
if !t.is_object_safe(cx.tcx()) {
|
||||
write_small_section_header(
|
||||
write_section_heading(
|
||||
w,
|
||||
"object-safety",
|
||||
"Object Safety",
|
||||
"object-safety",
|
||||
None,
|
||||
&format!(
|
||||
"<div class=\"object-safety-info\">This trait is <b>not</b> \
|
||||
<a href=\"{base}/reference/items/traits.html#object-safety\">\
|
||||
@ -996,7 +992,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
|
||||
foreign.sort_by_cached_key(|i| ImplString::new(i, cx));
|
||||
|
||||
if !foreign.is_empty() {
|
||||
write_small_section_header(w, "foreign-impls", "Implementations on Foreign Types", "");
|
||||
write_section_heading(w, "Implementations on Foreign Types", "foreign-impls", None, "");
|
||||
|
||||
for implementor in foreign {
|
||||
let provided_methods = implementor.inner_impl().provided_trait_methods(tcx);
|
||||
@ -1021,10 +1017,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
|
||||
}
|
||||
}
|
||||
|
||||
write_small_section_header(
|
||||
write_section_heading(
|
||||
w,
|
||||
"implementors",
|
||||
"Implementors",
|
||||
"implementors",
|
||||
None,
|
||||
"<div id=\"implementors-list\">",
|
||||
);
|
||||
for implementor in concrete {
|
||||
@ -1033,10 +1030,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
|
||||
w.write_str("</div>");
|
||||
|
||||
if t.is_auto(tcx) {
|
||||
write_small_section_header(
|
||||
write_section_heading(
|
||||
w,
|
||||
"synthetic-implementors",
|
||||
"Auto implementors",
|
||||
"synthetic-implementors",
|
||||
None,
|
||||
"<div id=\"synthetic-implementors-list\">",
|
||||
);
|
||||
for implementor in synthetic {
|
||||
@ -1054,18 +1052,20 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
|
||||
} else {
|
||||
// even without any implementations to write in, we still want the heading and list, so the
|
||||
// implementors javascript file pulled in below has somewhere to write the impls into
|
||||
write_small_section_header(
|
||||
write_section_heading(
|
||||
w,
|
||||
"implementors",
|
||||
"Implementors",
|
||||
"implementors",
|
||||
None,
|
||||
"<div id=\"implementors-list\"></div>",
|
||||
);
|
||||
|
||||
if t.is_auto(tcx) {
|
||||
write_small_section_header(
|
||||
write_section_heading(
|
||||
w,
|
||||
"synthetic-implementors",
|
||||
"Auto implementors",
|
||||
"synthetic-implementors",
|
||||
None,
|
||||
"<div id=\"synthetic-implementors-list\"></div>",
|
||||
);
|
||||
}
|
||||
@ -1248,11 +1248,7 @@ fn item_type_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &c
|
||||
write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
|
||||
|
||||
if let Some(inner_type) = &t.inner_type {
|
||||
write!(
|
||||
w,
|
||||
"<h2 id=\"aliased-type\" class=\"section-header\">\
|
||||
Aliased Type<a href=\"#aliased-type\" class=\"anchor\">§</a></h2>"
|
||||
);
|
||||
write_section_heading(w, "Aliased Type", "aliased-type", None, "");
|
||||
|
||||
match inner_type {
|
||||
clean::TypeAliasInnerType::Enum { variants, is_non_exhaustive } => {
|
||||
@ -1673,16 +1669,14 @@ fn item_variants(
|
||||
enum_def_id: DefId,
|
||||
) {
|
||||
let tcx = cx.tcx();
|
||||
write!(
|
||||
write_section_heading(
|
||||
w,
|
||||
"<h2 id=\"variants\" class=\"variants section-header\">\
|
||||
Variants{}<a href=\"#variants\" class=\"anchor\">§</a>\
|
||||
</h2>\
|
||||
{}\
|
||||
<div class=\"variants\">",
|
||||
document_non_exhaustive_header(it),
|
||||
document_non_exhaustive(it)
|
||||
&format!("Variants{}", document_non_exhaustive_header(it)),
|
||||
"variants",
|
||||
Some("variants"),
|
||||
format!("{}<div class=\"variants\">", document_non_exhaustive(it)),
|
||||
);
|
||||
|
||||
let should_show_enum_discriminant = should_show_enum_discriminant(cx, enum_def_id, variants);
|
||||
for (index, variant) in variants.iter_enumerated() {
|
||||
if variant.is_stripped() {
|
||||
@ -1930,16 +1924,12 @@ fn item_fields(
|
||||
.peekable();
|
||||
if let None | Some(CtorKind::Fn) = ctor_kind {
|
||||
if fields.peek().is_some() {
|
||||
write!(
|
||||
w,
|
||||
"<h2 id=\"fields\" class=\"fields section-header\">\
|
||||
{}{}<a href=\"#fields\" class=\"anchor\">§</a>\
|
||||
</h2>\
|
||||
{}",
|
||||
let title = format!(
|
||||
"{}{}",
|
||||
if ctor_kind.is_none() { "Fields" } else { "Tuple Fields" },
|
||||
document_non_exhaustive_header(it),
|
||||
document_non_exhaustive(it)
|
||||
);
|
||||
write_section_heading(w, &title, "fields", Some("fields"), document_non_exhaustive(it));
|
||||
for (index, (field, ty)) in fields.enumerate() {
|
||||
let field_name =
|
||||
field.name.map_or_else(|| index.to_string(), |sym| sym.as_str().to_string());
|
||||
|
@ -849,11 +849,30 @@ nav.sub {
|
||||
h2.section-header > .anchor {
|
||||
padding-right: 6px;
|
||||
}
|
||||
a.doc-anchor {
|
||||
color: var(--main-color);
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: -17px;
|
||||
/* We add this padding so that when the cursor moves from the heading's text to the anchor,
|
||||
the anchor doesn't disappear. */
|
||||
padding-right: 5px;
|
||||
/* And this padding is used to make the anchor larger and easier to click on. */
|
||||
padding-left: 3px;
|
||||
}
|
||||
*:hover > .doc-anchor {
|
||||
display: block;
|
||||
}
|
||||
/* If the first element of the top doc block is a heading, we don't want to ever display its anchor
|
||||
because of the `[-]` element which would overlap with it. */
|
||||
.top-doc > .docblock > *:first-child > .doc-anchor {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.main-heading a:hover,
|
||||
.example-wrap .rust a:hover,
|
||||
.all-items a:hover,
|
||||
.docblock a:not(.test-arrow):not(.scrape-help):not(.tooltip):hover,
|
||||
.docblock a:not(.test-arrow):not(.scrape-help):not(.tooltip):hover:not(.doc-anchor),
|
||||
.docblock-short a:not(.test-arrow):not(.scrape-help):not(.tooltip):hover,
|
||||
.item-info a {
|
||||
text-decoration: underline;
|
||||
|
@ -6,7 +6,7 @@ reload:
|
||||
|
||||
// We first check that the headers in the `.top-doc` doc block still have their
|
||||
// bottom border.
|
||||
assert-text: (".top-doc .docblock > h3", "Hello")
|
||||
assert-text: (".top-doc .docblock > h3", "§Hello")
|
||||
assert-css: (
|
||||
".top-doc .docblock > h3",
|
||||
{"border-bottom": "1px solid #d2d2d2"},
|
||||
|
@ -1,4 +1,4 @@
|
||||
// This test check for headers text and background colors for the different themes.
|
||||
// This test check for headings text and background colors for the different themes.
|
||||
|
||||
define-function: (
|
||||
"check-colors",
|
||||
@ -45,7 +45,7 @@ call-function: (
|
||||
"color": "#c5c5c5",
|
||||
"code_header_color": "#e6e1cf",
|
||||
"focus_background_color": "rgba(255, 236, 164, 0.06)",
|
||||
"headings_color": "#39afd7",
|
||||
"headings_color": "#c5c5c5",
|
||||
},
|
||||
)
|
||||
call-function: (
|
||||
@ -55,7 +55,7 @@ call-function: (
|
||||
"color": "#ddd",
|
||||
"code_header_color": "#ddd",
|
||||
"focus_background_color": "#494a3d",
|
||||
"headings_color": "#d2991d",
|
||||
"headings_color": "#ddd",
|
||||
},
|
||||
)
|
||||
call-function: (
|
||||
@ -65,6 +65,6 @@ call-function: (
|
||||
"color": "black",
|
||||
"code_header_color": "black",
|
||||
"focus_background_color": "#fdffd3",
|
||||
"headings_color": "#3873ad",
|
||||
"headings_color": "black",
|
||||
},
|
||||
)
|
||||
|
32
tests/rustdoc-gui/headings-anchor.goml
Normal file
32
tests/rustdoc-gui/headings-anchor.goml
Normal file
@ -0,0 +1,32 @@
|
||||
// Test to ensure that the headings anchor behave as expected.
|
||||
go-to: "file://" + |DOC_PATH| + "/test_docs/struct.HeavilyDocumentedStruct.html"
|
||||
show-text: true
|
||||
|
||||
define-function: (
|
||||
"check-heading-anchor",
|
||||
(heading_id),
|
||||
block {
|
||||
// The anchor should not be displayed by default.
|
||||
assert-css: ("#" + |heading_id| + " .doc-anchor", { "display": "none" })
|
||||
// We ensure that hovering the heading makes the anchor visible.
|
||||
move-cursor-to: "#" + |heading_id|
|
||||
assert-css: ("#" + |heading_id| + ":hover .doc-anchor", { "display": "block" })
|
||||
// We then ensure that moving from the heading to the anchor doesn't make the anchor
|
||||
// disappear.
|
||||
move-cursor-to: "#" + |heading_id| + " .doc-anchor"
|
||||
assert-css: ("#" + |heading_id| + " .doc-anchor:hover", {
|
||||
"display": "block",
|
||||
// We also ensure that there is no underline decoration.
|
||||
"text-decoration-line": "none",
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
move-cursor-to: "#top-doc-prose-title"
|
||||
// If the top documentation block first element is a heading, we should never display its anchor
|
||||
// to prevent it from overlapping with the `[-]` element.
|
||||
assert-css: ("#top-doc-prose-title:hover .doc-anchor", { "display": "none" })
|
||||
|
||||
call-function: ("check-heading-anchor", ("top-doc-prose-sub-heading"))
|
||||
call-function: ("check-heading-anchor", ("top-doc-prose-sub-sub-heading"))
|
||||
call-function: ("check-heading-anchor", ("you-know-the-drill"))
|
@ -5,18 +5,23 @@
|
||||
pub struct Foo;
|
||||
|
||||
impl Foo {
|
||||
// @has - '//*[@id="examples"]//a' 'Examples'
|
||||
// @has - '//*[@id="panics"]//a' 'Panics'
|
||||
// @has - '//*[@id="examples"]' 'Examples'
|
||||
// @has - '//*[@id="examples"]/a[@href="#examples"]' '§'
|
||||
// @has - '//*[@id="panics"]' 'Panics'
|
||||
// @has - '//*[@id="panics"]/a[@href="#panics"]' '§'
|
||||
/// # Examples
|
||||
/// # Panics
|
||||
pub fn bar() {}
|
||||
|
||||
// @has - '//*[@id="examples-1"]//a' 'Examples'
|
||||
// @has - '//*[@id="examples-1"]' 'Examples'
|
||||
// @has - '//*[@id="examples-1"]/a[@href="#examples-1"]' '§'
|
||||
/// # Examples
|
||||
pub fn bar_1() {}
|
||||
|
||||
// @has - '//*[@id="examples-2"]//a' 'Examples'
|
||||
// @has - '//*[@id="panics-1"]//a' 'Panics'
|
||||
// @has - '//*[@id="examples-2"]' 'Examples'
|
||||
// @has - '//*[@id="examples-2"]/a[@href="#examples-2"]' '§'
|
||||
// @has - '//*[@id="panics-1"]' 'Panics'
|
||||
// @has - '//*[@id="panics-1"]/a[@href="#panics-1"]' '§'
|
||||
/// # Examples
|
||||
/// # Panics
|
||||
pub fn bar_2() {}
|
||||
|
14
tests/rustdoc/links-in-headings.rs
Normal file
14
tests/rustdoc/links-in-headings.rs
Normal file
@ -0,0 +1,14 @@
|
||||
#![crate_name = "foo"]
|
||||
|
||||
//! # Heading with [a link](https://a.com) inside
|
||||
//!
|
||||
//! And even with
|
||||
//!
|
||||
//! ## [multiple](https://b.com) [links](https://c.com)
|
||||
//!
|
||||
//! !
|
||||
|
||||
// @has 'foo/index.html'
|
||||
// @has - '//h2/a[@href="https://a.com"]' 'a link'
|
||||
// @has - '//h3/a[@href="https://b.com"]' 'multiple'
|
||||
// @has - '//h3/a[@href="https://c.com"]' 'links'
|
@ -1,9 +1,12 @@
|
||||
// It actually checks that the link is kept in the headings as expected now.
|
||||
|
||||
#![crate_name = "foo"]
|
||||
|
||||
// @has foo/fn.foo.html
|
||||
// @!has - '//a[@href="http://a.a"]' ''
|
||||
// @has - '//a[@href="#implementing-stuff-somewhere"]' 'Implementing stuff somewhere'
|
||||
// @has - '//a[@href="#another-one-urg"]' 'Another one urg'
|
||||
// @has - '//a[@href="http://a.a"]' 'stuff'
|
||||
// @has - '//*[@id="implementing-stuff-somewhere"]' 'Implementing stuff somewhere'
|
||||
// @has - '//a[@href="http://b.b"]' 'one'
|
||||
// @has - '//*[@id="another-one-urg"]' 'Another one urg'
|
||||
|
||||
/// fooo
|
||||
///
|
||||
@ -13,5 +16,5 @@
|
||||
///
|
||||
/// # Another [one][two] urg
|
||||
///
|
||||
/// [two]: http://a.a
|
||||
/// [two]: http://b.b
|
||||
pub fn foo() {}
|
||||
|
@ -2,8 +2,9 @@
|
||||
|
||||
// @has foo/index.html '//*[@class="desc docblock-short"]' 'fooo'
|
||||
// @!has foo/index.html '//*[@class="desc docblock-short"]/h1' 'fooo'
|
||||
// @has foo/fn.foo.html '//h2[@id="fooo"]/a[@href="#fooo"]' 'fooo'
|
||||
|
||||
// @has foo/fn.foo.html '//h2[@id="fooo"]' 'fooo'
|
||||
// @has foo/fn.foo.html '//h2[@id="fooo"]/a[@href="#fooo"]' '§'
|
||||
/// # fooo
|
||||
///
|
||||
/// foo
|
||||
@ -11,8 +12,9 @@ pub fn foo() {}
|
||||
|
||||
// @has foo/index.html '//*[@class="desc docblock-short"]' 'mooood'
|
||||
// @!has foo/index.html '//*[@class="desc docblock-short"]/h2' 'mooood'
|
||||
// @has foo/foo/index.html '//h3[@id="mooood"]/a[@href="#mooood"]' 'mooood'
|
||||
|
||||
// @has foo/foo/index.html '//h3[@id="mooood"]' 'mooood'
|
||||
// @has foo/foo/index.html '//h3[@id="mooood"]/a[@href="#mooood"]' '§'
|
||||
/// ## mooood
|
||||
///
|
||||
/// foo mod
|
||||
|
Loading…
Reference in New Issue
Block a user