about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--src/librustdoc/Cargo.toml1
-rw-r--r--src/librustdoc/html/escape.rs55
-rw-r--r--src/librustdoc/html/escape/tests.rs68
-rw-r--r--src/librustdoc/html/format.rs3
-rw-r--r--src/librustdoc/html/layout.rs2
-rw-r--r--src/librustdoc/html/render/mod.rs2
-rw-r--r--src/librustdoc/html/render/print_item.rs8
-rw-r--r--src/librustdoc/html/render/sidebar.rs16
-rw-r--r--src/librustdoc/html/static/css/rustdoc.css5
-rw-r--r--src/librustdoc/html/templates/page.html2
-rw-r--r--src/librustdoc/html/templates/sidebar.html8
-rw-r--r--src/tools/compiletest/src/runtest.rs1
-rw-r--r--tests/rustdoc-gui/duplicate-macro-reexport.goml4
-rw-r--r--tests/rustdoc-gui/font-weight.goml4
-rw-r--r--tests/rustdoc-gui/item-info.goml4
-rw-r--r--tests/rustdoc-gui/label-next-to-symbol.goml21
-rw-r--r--tests/rustdoc-gui/notable-trait.goml8
-rw-r--r--tests/rustdoc-gui/search-result-color.goml12
-rw-r--r--tests/rustdoc-gui/sidebar-macro-reexport.goml2
-rw-r--r--tests/rustdoc-gui/sidebar-mobile.goml7
-rw-r--r--tests/rustdoc-gui/sidebar-source-code.goml8
-rw-r--r--tests/rustdoc-gui/source-anchor-scroll.goml2
-rw-r--r--tests/rustdoc/extremely_long_typename.extremely_long_typename.html1
-rw-r--r--tests/rustdoc/extremely_long_typename.rs7
-rw-r--r--tests/rustdoc/item-desc-list-at-start.item-table.html2
26 files changed, 208 insertions, 46 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 281599a21fc..1a7d7e3f5d7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4826,6 +4826,7 @@ dependencies = [
  "tracing",
  "tracing-subscriber",
  "tracing-tree",
+ "unicode-segmentation",
 ]
 
 [[package]]
diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml
index fe531f0ff59..dfd7414652f 100644
--- a/src/librustdoc/Cargo.toml
+++ b/src/librustdoc/Cargo.toml
@@ -23,6 +23,7 @@ tempfile = "3"
 tracing = "0.1"
 tracing-tree = "0.3.0"
 threadpool = "1.8.1"
+unicode-segmentation = "1.9"
 
 [dependencies.tracing-subscriber]
 version = "0.3.3"
diff --git a/src/librustdoc/html/escape.rs b/src/librustdoc/html/escape.rs
index ea4b573aeb9..691f86847b5 100644
--- a/src/librustdoc/html/escape.rs
+++ b/src/librustdoc/html/escape.rs
@@ -5,6 +5,8 @@
 
 use std::fmt;
 
+use unicode_segmentation::UnicodeSegmentation;
+
 /// Wrapper struct which will emit the HTML-escaped version of the contained
 /// string when passed to a format string.
 pub(crate) struct Escape<'a>(pub &'a str);
@@ -74,3 +76,56 @@ impl<'a> fmt::Display for EscapeBodyText<'a> {
         Ok(())
     }
 }
+
+/// Wrapper struct which will emit the HTML-escaped version of the contained
+/// string when passed to a format string. This function also word-breaks
+/// CamelCase and snake_case word names.
+///
+/// This is only safe to use for text nodes. If you need your output to be
+/// safely contained in an attribute, use [`Escape`]. If you don't know the
+/// difference, use [`Escape`].
+pub(crate) struct EscapeBodyTextWithWbr<'a>(pub &'a str);
+
+impl<'a> fmt::Display for EscapeBodyTextWithWbr<'a> {
+    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let EscapeBodyTextWithWbr(text) = *self;
+        if text.len() < 8 {
+            return EscapeBodyText(text).fmt(fmt);
+        }
+        let mut last = 0;
+        let mut it = text.grapheme_indices(true).peekable();
+        let _ = it.next(); // don't insert wbr before first char
+        while let Some((i, s)) = it.next() {
+            let pk = it.peek();
+            if s.chars().all(|c| c.is_whitespace()) {
+                // don't need "First <wbr>Second"; the space is enough
+                EscapeBodyText(&text[last..i]).fmt(fmt)?;
+                last = i;
+                continue;
+            }
+            let is_uppercase = || s.chars().any(|c| c.is_uppercase());
+            let next_is_uppercase =
+                || pk.map_or(true, |(_, t)| t.chars().any(|c| c.is_uppercase()));
+            let next_is_underscore = || pk.map_or(true, |(_, t)| t.contains('_'));
+            let next_is_colon = || pk.map_or(true, |(_, t)| t.contains(':'));
+            if i - last > 3 && is_uppercase() && !next_is_uppercase() {
+                EscapeBodyText(&text[last..i]).fmt(fmt)?;
+                fmt.write_str("<wbr>")?;
+                last = i;
+            } else if (s.contains(':') && !next_is_colon())
+                || (s.contains('_') && !next_is_underscore())
+            {
+                EscapeBodyText(&text[last..i + 1]).fmt(fmt)?;
+                fmt.write_str("<wbr>")?;
+                last = i + 1;
+            }
+        }
+        if last < text.len() {
+            EscapeBodyText(&text[last..]).fmt(fmt)?;
+        }
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests;
diff --git a/src/librustdoc/html/escape/tests.rs b/src/librustdoc/html/escape/tests.rs
new file mode 100644
index 00000000000..a09649e9e18
--- /dev/null
+++ b/src/librustdoc/html/escape/tests.rs
@@ -0,0 +1,68 @@
+// basic examples
+#[test]
+fn escape_body_text_with_wbr() {
+    use super::EscapeBodyTextWithWbr as E;
+    // extreme corner cases
+    assert_eq!(&E("").to_string(), "");
+    assert_eq!(&E("a").to_string(), "a");
+    assert_eq!(&E("A").to_string(), "A");
+    assert_eq!(&E("_").to_string(), "_");
+    assert_eq!(&E(":").to_string(), ":");
+    assert_eq!(&E(" ").to_string(), " ");
+    assert_eq!(&E("___________").to_string(), "___________");
+    assert_eq!(&E(":::::::::::").to_string(), ":::::::::::");
+    assert_eq!(&E("           ").to_string(), "           ");
+    // real(istic) examples
+    assert_eq!(&E("FirstSecond").to_string(), "First<wbr>Second");
+    assert_eq!(&E("First_Second").to_string(), "First_<wbr>Second");
+    assert_eq!(&E("First Second").to_string(), "First Second");
+    assert_eq!(&E("First HSecond").to_string(), "First HSecond");
+    assert_eq!(&E("First HTTPSecond").to_string(), "First HTTP<wbr>Second");
+    assert_eq!(&E("First SecondThird").to_string(), "First Second<wbr>Third");
+    assert_eq!(&E("First<T>_Second").to_string(), "First&lt;<wbr>T&gt;_<wbr>Second");
+    assert_eq!(&E("first_second").to_string(), "first_<wbr>second");
+    assert_eq!(&E("first:second").to_string(), "first:<wbr>second");
+    assert_eq!(&E("first::second").to_string(), "first::<wbr>second");
+    assert_eq!(&E("MY_CONSTANT").to_string(), "MY_<wbr>CONSTANT");
+    // a string won't get wrapped if it's less than 8 bytes
+    assert_eq!(&E("HashSet").to_string(), "HashSet");
+    // an individual word won't get wrapped if it's less than 4 bytes
+    assert_eq!(&E("VecDequeue").to_string(), "VecDequeue");
+    assert_eq!(&E("VecDequeueSet").to_string(), "VecDequeue<wbr>Set");
+    // how to handle acronyms
+    assert_eq!(&E("BTreeMap").to_string(), "BTree<wbr>Map");
+    assert_eq!(&E("HTTPSProxy").to_string(), "HTTPS<wbr>Proxy");
+    // more corners
+    assert_eq!(&E("ṼẽçÑñéå").to_string(), "Ṽẽç<wbr>Ññéå");
+    assert_eq!(&E("V\u{0300}e\u{0300}c\u{0300}D\u{0300}e\u{0300}q\u{0300}u\u{0300}e\u{0300}u\u{0300}e\u{0300}").to_string(), "V\u{0300}e\u{0300}c\u{0300}<wbr>D\u{0300}e\u{0300}q\u{0300}u\u{0300}e\u{0300}u\u{0300}e\u{0300}");
+    assert_eq!(&E("LPFNACCESSIBLEOBJECTFROMWINDOW").to_string(), "LPFNACCESSIBLEOBJECTFROMWINDOW");
+}
+// property test
+#[test]
+fn escape_body_text_with_wbr_makes_sense() {
+    use itertools::Itertools as _;
+
+    use super::EscapeBodyTextWithWbr as E;
+    const C: [u8; 3] = [b'a', b'A', b'_'];
+    for chars in [
+        C.into_iter(),
+        C.into_iter(),
+        C.into_iter(),
+        C.into_iter(),
+        C.into_iter(),
+        C.into_iter(),
+        C.into_iter(),
+        C.into_iter(),
+    ]
+    .into_iter()
+    .multi_cartesian_product()
+    {
+        let s = String::from_utf8(chars).unwrap();
+        assert_eq!(s.len(), 8);
+        let esc = E(&s).to_string();
+        assert!(!esc.contains("<wbr><wbr>"));
+        assert!(!esc.ends_with("<wbr>"));
+        assert!(!esc.starts_with("<wbr>"));
+        assert_eq!(&esc.replace("<wbr>", ""), &s);
+    }
+}
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index d6aed75103d..bb5ac303ffd 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -32,7 +32,7 @@ use crate::clean::utils::find_nearest_parent_module;
 use crate::clean::{self, ExternalCrate, PrimitiveType};
 use crate::formats::cache::Cache;
 use crate::formats::item_type::ItemType;
-use crate::html::escape::Escape;
+use crate::html::escape::{Escape, EscapeBodyText};
 use crate::html::render::Context;
 use crate::passes::collect_intra_doc_links::UrlFragment;
 
@@ -988,6 +988,7 @@ pub(crate) fn anchor<'a, 'cx: 'a>(
                 f,
                 r#"<a class="{short_ty}" href="{url}" title="{short_ty} {path}">{text}</a>"#,
                 path = join_with_double_colon(&fqp),
+                text = EscapeBodyText(text.as_str()),
             )
         } else {
             f.write_str(text.as_str())
diff --git a/src/librustdoc/html/layout.rs b/src/librustdoc/html/layout.rs
index 7dfcc88398f..780cda9b1cd 100644
--- a/src/librustdoc/html/layout.rs
+++ b/src/librustdoc/html/layout.rs
@@ -69,6 +69,8 @@ struct PageLayout<'a> {
     display_krate_version_extra: &'a str,
 }
 
+pub(crate) use crate::html::render::sidebar::filters;
+
 pub(crate) fn render<T: Print, S: Print>(
     layout: &Layout,
     page: &Page<'_>,
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index aaac8678264..b5cc495ce41 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -30,7 +30,7 @@ mod tests;
 
 mod context;
 mod print_item;
-mod sidebar;
+pub(crate) mod sidebar;
 mod span_map;
 mod type_layout;
 mod write_shared;
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index 24476e80778..eec6df9dd20 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -29,7 +29,7 @@ use crate::clean;
 use crate::config::ModuleSorting;
 use crate::formats::item_type::ItemType;
 use crate::formats::Impl;
-use crate::html::escape::Escape;
+use crate::html::escape::{Escape, EscapeBodyTextWithWbr};
 use crate::html::format::{
     display_fn, join_with_double_colon, print_abi_with_space, print_constness_with_space,
     print_where_clause, visibility_print_with_space, Buffer, Ending, PrintWithSpace,
@@ -423,7 +423,7 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
                         "<div class=\"item-name\"><code>{}extern crate {} as {};",
                         visibility_print_with_space(myitem, cx),
                         anchor(myitem.item_id.expect_def_id(), src, cx),
-                        myitem.name.unwrap(),
+                        EscapeBodyTextWithWbr(myitem.name.unwrap().as_str()),
                     ),
                     None => write!(
                         w,
@@ -520,7 +520,7 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
                         {stab_tags}\
                      </div>\
                      {docs_before}{docs}{docs_after}",
-                    name = myitem.name.unwrap(),
+                    name = EscapeBodyTextWithWbr(myitem.name.unwrap().as_str()),
                     visibility_and_hidden = visibility_and_hidden,
                     stab_tags = extra_info_tags(myitem, item, tcx),
                     class = myitem.type_(),
@@ -558,7 +558,7 @@ fn extra_info_tags<'a, 'tcx: 'a>(
             display_fn(move |f| {
                 write!(
                     f,
-                    r#"<span class="stab {class}" title="{title}">{contents}</span>"#,
+                    r#"<wbr><span class="stab {class}" title="{title}">{contents}</span>"#,
                     title = Escape(title),
                 )
             })
diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs
index 6e826446c0e..101cc839f09 100644
--- a/src/librustdoc/html/render/sidebar.rs
+++ b/src/librustdoc/html/render/sidebar.rs
@@ -77,6 +77,22 @@ impl<'a> Link<'a> {
     }
 }
 
+pub(crate) mod filters {
+    use std::fmt::Display;
+
+    use rinja::filters::Safe;
+
+    use crate::html::escape::EscapeBodyTextWithWbr;
+    use crate::html::render::display_fn;
+    pub(crate) fn wrapped<T>(v: T) -> rinja::Result<Safe<impl Display>>
+    where
+        T: Display,
+    {
+        let string = v.to_string();
+        Ok(Safe(display_fn(move |f| EscapeBodyTextWithWbr(&string).fmt(f))))
+    }
+}
+
 pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buffer) {
     let blocks: Vec<LinkBlock<'_>> = match *it.kind {
         clean::StructItem(ref s) => sidebar_struct(cx, it, s),
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index e936e1ca07e..cafe5fe4c87 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -586,12 +586,15 @@ ul.block, .block li {
 }
 
 .sidebar h2 {
+	text-wrap: balance;
 	overflow-wrap: anywhere;
 	padding: 0;
 	margin: 0.7rem 0;
 }
 
 .sidebar h3 {
+	text-wrap: balance;
+	overflow-wrap: anywhere;
 	font-size: 1.125rem; /* 18px */
 	padding: 0;
 	margin: 0;
@@ -2222,7 +2225,7 @@ in src-script.js and main.js
 		width: 33%;
 	}
 	.item-table > li > div {
-		word-break: break-all;
+		overflow-wrap: anywhere;
 	}
 }
 
diff --git a/src/librustdoc/html/templates/page.html b/src/librustdoc/html/templates/page.html
index cdf01fa7a97..65c4304e202 100644
--- a/src/librustdoc/html/templates/page.html
+++ b/src/librustdoc/html/templates/page.html
@@ -98,7 +98,7 @@
             </a> {# #}
             {% endif %}
             <h2> {# #}
-                <a href="{{page.root_path|safe}}{{display_krate_with_trailing_slash|safe}}index.html">{{display_krate}}</a> {# #}
+                <a href="{{page.root_path|safe}}{{display_krate_with_trailing_slash|safe}}index.html">{{display_krate|wrapped|safe}}</a> {# #}
                 {% if !display_krate_version_number.is_empty() %}
                     <span class="version">{{+ display_krate_version_number}}</span>
                 {% endif %}
diff --git a/src/librustdoc/html/templates/sidebar.html b/src/librustdoc/html/templates/sidebar.html
index 3251b4c14c9..0990c2716b8 100644
--- a/src/librustdoc/html/templates/sidebar.html
+++ b/src/librustdoc/html/templates/sidebar.html
@@ -1,6 +1,6 @@
 {% if !title.is_empty() %}
     <h2 class="location"> {# #}
-        <a href="#">{{title_prefix}}{{title}}</a> {# #}
+        <a href="#">{{title_prefix}}{{title|wrapped|safe}}</a> {# #}
     </h2>
 {% endif %}
 <div class="sidebar-elems">
@@ -15,7 +15,9 @@
             {% for block in blocks %}
                 {% if block.should_render() %}
                     {% if !block.heading.name.is_empty() %}
-                        <h3><a href="#{{block.heading.href|safe}}">{{block.heading.name}}</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 %}">
@@ -29,6 +31,6 @@
         </section>
     {% endif %}
     {% if !path.is_empty() %}
-        <h2><a href="{% if is_mod %}../{% endif %}index.html">In {{+ path}}</a></h2>
+        <h2><a href="{% if is_mod %}../{% endif %}index.html">In {{+ path|wrapped|safe}}</a></h2>
     {% endif %}
 </div>
diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs
index f6e8fdd6244..1f15605d8be 100644
--- a/src/tools/compiletest/src/runtest.rs
+++ b/src/tools/compiletest/src/runtest.rs
@@ -2731,6 +2731,7 @@ impl<'test> TestCx<'test> {
 
         #[rustfmt::skip]
         let tidy_args = [
+            "--new-blocklevel-tags", "rustdoc-search",
             "--indent", "yes",
             "--indent-spaces", "2",
             "--wrap", "0",
diff --git a/tests/rustdoc-gui/duplicate-macro-reexport.goml b/tests/rustdoc-gui/duplicate-macro-reexport.goml
index 7d01c88f31b..a838d99c4bf 100644
--- a/tests/rustdoc-gui/duplicate-macro-reexport.goml
+++ b/tests/rustdoc-gui/duplicate-macro-reexport.goml
@@ -4,11 +4,11 @@ go-to: "file://" + |DOC_PATH| + "/test_docs/macro.a.html"
 wait-for: ".sidebar-elems .macro"
 // Check there is only one macro named "a" listed in the sidebar.
 assert-count: (
-    "//*[@class='sidebar-elems']//*[@class='block macro']//li/a[text()='a']",
+    "//*[@class='sidebar-elems']//*[@class='block macro']//li/a[normalize-space()='a']",
     1,
 )
 // Check there is only one macro named "b" listed in the sidebar.
 assert-count: (
-    "//*[@class='sidebar-elems']//*[@class='block macro']//li/a[text()='b']",
+    "//*[@class='sidebar-elems']//*[@class='block macro']//li/a[normalize-space()='b']",
     1,
 )
diff --git a/tests/rustdoc-gui/font-weight.goml b/tests/rustdoc-gui/font-weight.goml
index 602b8d6f5b3..26e9bf515a3 100644
--- a/tests/rustdoc-gui/font-weight.goml
+++ b/tests/rustdoc-gui/font-weight.goml
@@ -1,8 +1,8 @@
 // This test checks that the font weight is correctly applied.
 go-to: "file://" + |DOC_PATH| + "/lib2/struct.Foo.html"
-assert-css: ("//*[@class='rust item-decl']//a[text()='Alias']", {"font-weight": "400"})
+assert-css: ("//*[@class='rust item-decl']//a[normalize-space()='Alias']", {"font-weight": "400"})
 assert-css: (
-    "//*[@class='structfield section-header']//a[text()='Alias']",
+    "//*[@class='structfield section-header']//a[normalize-space()='Alias']",
     {"font-weight": "400"},
 )
 assert-css: ("#method\.a_method > .code-header", {"font-weight": "600"})
diff --git a/tests/rustdoc-gui/item-info.goml b/tests/rustdoc-gui/item-info.goml
index 1eb46e832b7..7a0194c6cc1 100644
--- a/tests/rustdoc-gui/item-info.goml
+++ b/tests/rustdoc-gui/item-info.goml
@@ -12,11 +12,11 @@ assert-position: (".item-info .stab", {"x": 245})
 // test for <https://github.com/rust-lang/rust/issues/118615>.
 set-window-size: (850, 800)
 store-position: (
-    "//*[@class='stab portability']//code[text()='Win32_System']",
+    "//*[@class='stab portability']//code[normalize-space()='Win32_System']",
     {"x": first_line_x, "y": first_line_y},
 )
 store-position: (
-    "//*[@class='stab portability']//code[text()='Win32_System_Diagnostics']",
+    "//*[@class='stab portability']//code[normalize-space()='Win32_System_Diagnostics']",
     {"x": second_line_x, "y": second_line_y},
 )
 assert: |first_line_x| != |second_line_x| && |first_line_x| == 516 && |second_line_x| == 272
diff --git a/tests/rustdoc-gui/label-next-to-symbol.goml b/tests/rustdoc-gui/label-next-to-symbol.goml
index 0582bd2cad3..a8363f29dd5 100644
--- a/tests/rustdoc-gui/label-next-to-symbol.goml
+++ b/tests/rustdoc-gui/label-next-to-symbol.goml
@@ -23,11 +23,12 @@ assert-css: (
 // table like view
 assert-css: (".desc.docblock-short", { "padding-left": "0px" })
 compare-elements-position-near: (
-    "//*[@class='item-name']//a[text()='replaced_function']",
+    "//*[@class='item-name']//a[normalize-space()='replaced_function']",
     ".item-name .stab.deprecated",
     {"y": 2},
 )
-compare-elements-position: (
+// "Unix" part is on second line
+compare-elements-position-false: (
     ".item-name .stab.deprecated",
     ".item-name .stab.portability",
     ["y"],
@@ -35,8 +36,8 @@ compare-elements-position: (
 
 // Ensure no wrap
 compare-elements-position: (
-    "//*[@class='item-name']//a[text()='replaced_function']/..",
-    "//*[@class='desc docblock-short'][text()='a thing with a label']",
+    "//*[@class='item-name']//a[normalize-space()='replaced_function']/..",
+    "//*[@class='desc docblock-short'][normalize-space()='a thing with a label']",
     ["y"],
 )
 
@@ -45,7 +46,7 @@ set-window-size: (600, 600)
 // staggered layout with 2em spacing
 assert-css: (".desc.docblock-short", { "padding-left": "32px" })
 compare-elements-position-near: (
-    "//*[@class='item-name']//a[text()='replaced_function']",
+    "//*[@class='item-name']//a[normalize-space()='replaced_function']",
     ".item-name .stab.deprecated",
     {"y": 2},
 )
@@ -57,13 +58,13 @@ compare-elements-position: (
 
 // Ensure wrap
 compare-elements-position-false: (
-    "//*[@class='item-name']//a[text()='replaced_function']/..",
-    "//*[@class='desc docblock-short'][text()='a thing with a label']",
+    "//*[@class='item-name']//a[normalize-space()='replaced_function']/..",
+    "//*[@class='desc docblock-short'][normalize-space()='a thing with a label']",
     ["y"],
 )
 compare-elements-position-false: (
     ".item-name .stab.deprecated",
-    "//*[@class='desc docblock-short'][text()='a thing with a label']",
+    "//*[@class='desc docblock-short'][normalize-space()='a thing with a label']",
     ["y"],
 )
 
@@ -73,7 +74,7 @@ go-to: "file://" + |DOC_PATH| + "/test_docs/cfgs/index.html"
 // This part of the tags should not be on the same line as the beginning since the width
 // is too small for that.
 compare-elements-position-false: (
-    "//*[@class='stab portability']/code[text()='appservice-api-c']",
-    "//*[@class='stab portability']/code[text()='server']",
+    "//*[@class='stab portability']/code[normalize-space()='appservice-api-c']",
+    "//*[@class='stab portability']/code[normalize-space()='server']",
     ["y"],
 )
diff --git a/tests/rustdoc-gui/notable-trait.goml b/tests/rustdoc-gui/notable-trait.goml
index 6ee810c5768..e2a8a43007e 100644
--- a/tests/rustdoc-gui/notable-trait.goml
+++ b/tests/rustdoc-gui/notable-trait.goml
@@ -9,19 +9,19 @@ define-function: (
     block {
         // Checking they have the same y position.
         compare-elements-position: (
-            "//*[@id='method.create_an_iterator_from_read']//a[text()='NotableStructWithLongName']",
+            "//*[@id='method.create_an_iterator_from_read']//a[normalize-space()='NotableStructWithLongName']",
             "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']",
             ["y"],
         )
         // Checking they don't have the same x position.
         compare-elements-position-false: (
-            "//*[@id='method.create_an_iterator_from_read']//a[text()='NotableStructWithLongName']",
+            "//*[@id='method.create_an_iterator_from_read']//a[normalize-space()='NotableStructWithLongName']",
             "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']",
             ["x"],
         )
         // The `i` should be *after* the type.
         assert-position: (
-            "//*[@id='method.create_an_iterator_from_read']//a[text()='NotableStructWithLongName']",
+            "//*[@id='method.create_an_iterator_from_read']//a[normalize-space()='NotableStructWithLongName']",
             {"x": |x|},
         )
         assert-position: (
@@ -70,7 +70,7 @@ call-function: ("check-notable-tooltip-position-complete", {
 // Now only the `i` should be on the next line.
 set-window-size: (1055, 600)
 compare-elements-position-false: (
-    "//*[@id='method.create_an_iterator_from_read']//a[text()='NotableStructWithLongName']",
+    "//*[@id='method.create_an_iterator_from_read']//a[normalize-space()='NotableStructWithLongName']",
     "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']",
     ["y", "x"],
 )
diff --git a/tests/rustdoc-gui/search-result-color.goml b/tests/rustdoc-gui/search-result-color.goml
index 9825f92b453..e8da43eb896 100644
--- a/tests/rustdoc-gui/search-result-color.goml
+++ b/tests/rustdoc-gui/search-result-color.goml
@@ -20,11 +20,11 @@ define-function: (
             ALL,
         )
         assert-css: (
-            "//*[@class='desc'][text()='Just a normal struct.']",
+            "//*[@class='desc'][normalize-space()='Just a normal struct.']",
             {"color": |desc_color|},
         )
         assert-css: (
-            "//*[@class='result-name']//*[text()='test_docs::']",
+            "//*[@class='result-name']//*[normalize-space()='test_docs::']",
             {"color": |path_color|},
         )
 
@@ -85,19 +85,19 @@ define-function: (
         move-cursor-to: ".search-input"
         focus: ".search-input" // To ensure the `<a>` container isn't focused or hovered.
         assert-css: (
-            "//*[@class='result-name']//*[text()='test_docs::']/ancestor::a",
+            "//*[@class='result-name']//*[normalize-space()='test_docs::']/ancestor::a",
             {"color": |path_color|, "background-color": "transparent"},
             ALL,
         )
 
         // Checking color and background on hover.
-        move-cursor-to: "//*[@class='desc'][text()='Just a normal struct.']"
+        move-cursor-to: "//*[@class='desc'][normalize-space()='Just a normal struct.']"
         assert-css: (
-            "//*[@class='result-name']//*[text()='test_docs::']",
+            "//*[@class='result-name']//*[normalize-space()='test_docs::']",
             {"color": |hover_path_color|},
         )
         assert-css: (
-            "//*[@class='result-name']//*[text()='test_docs::']/ancestor::a",
+            "//*[@class='result-name']//*[normalize-space()='test_docs::']/ancestor::a",
             {"color": |hover_path_color|, "background-color": |hover_background|},
         )
     }
diff --git a/tests/rustdoc-gui/sidebar-macro-reexport.goml b/tests/rustdoc-gui/sidebar-macro-reexport.goml
index 0f7ef6c3558..cad25507fbb 100644
--- a/tests/rustdoc-gui/sidebar-macro-reexport.goml
+++ b/tests/rustdoc-gui/sidebar-macro-reexport.goml
@@ -2,4 +2,4 @@
 // displayed twice in the sidebar.
 go-to: "file://" + |DOC_PATH| + "/test_docs/macro.repro.html"
 wait-for: ".sidebar-elems .block.macro a"
-assert-count: ("//*[@class='sidebar-elems']//*[@class='block macro']//a[text()='repro']", 1)
+assert-count: ("//*[@class='sidebar-elems']//*[@class='block macro']//a[normalize-space()='repro']", 1)
diff --git a/tests/rustdoc-gui/sidebar-mobile.goml b/tests/rustdoc-gui/sidebar-mobile.goml
index b4ff483c180..4ada4837a57 100644
--- a/tests/rustdoc-gui/sidebar-mobile.goml
+++ b/tests/rustdoc-gui/sidebar-mobile.goml
@@ -25,9 +25,12 @@ click: ".sidebar-menu-toggle"
 assert-css: (".sidebar", {"left": "0px"})
 
 // Make sure the "struct Foo" header is hidden, since the mobile topbar already does it.
-assert-css: ("//nav[contains(@class, 'sidebar')]//h2/a[text()='Foo']/parent::h2", {"display": "none"})
+assert-css: ("//nav[contains(@class, 'sidebar')]//h2/a[normalize-space()='Foo']/parent::h2", {"display": "none"})
 // Make sure the global navigation is still here.
-assert-css: ("//nav[contains(@class, 'sidebar')]//h2/a[text()='In crate test_docs']/parent::h2", {"display": "block"})
+assert-css: (
+    "//nav[contains(@class, 'sidebar')]//h2/a[normalize-space()='In crate test_docs']/parent::h2",
+    {"display": "block"}
+)
 
 // Click elsewhere.
 click: "body"
diff --git a/tests/rustdoc-gui/sidebar-source-code.goml b/tests/rustdoc-gui/sidebar-source-code.goml
index ef0b5ab38b1..6afccf6a95f 100644
--- a/tests/rustdoc-gui/sidebar-source-code.goml
+++ b/tests/rustdoc-gui/sidebar-source-code.goml
@@ -66,12 +66,12 @@ click: "#sidebar-button"
 // We wait for the sidebar to be expanded.
 wait-for-css: (".src-sidebar-expanded nav.sidebar", {"width": "300px"})
 assert: "//*[@class='dir-entry' and @open]/*[text()='lib2']"
-assert: "//*[@class='dir-entry' and @open]/*[text()='another_folder']"
-assert: "//*[@class='dir-entry' and @open]/*[text()='sub_mod']"
+assert: "//*[@class='dir-entry' and @open]/*[normalize-space()='another_folder']"
+assert: "//*[@class='dir-entry' and @open]/*[normalize-space()='sub_mod']"
 // Only "another_folder" should be "open" in "lib2".
-assert: "//*[@class='dir-entry' and not(@open)]/*[text()='another_mod']"
+assert: "//*[@class='dir-entry' and not(@open)]/*[normalize-space()='another_mod']"
 // All other trees should be collapsed.
-assert-count: ("//*[@id='src-sidebar']/details[not(text()='lib2') and not(@open)]", 11)
+assert-count: ("//*[@id='src-sidebar']/details[not(normalize-space()='lib2') and not(@open)]", 11)
 
 // We now switch to mobile mode.
 set-window-size: (600, 600)
diff --git a/tests/rustdoc-gui/source-anchor-scroll.goml b/tests/rustdoc-gui/source-anchor-scroll.goml
index 940851ea146..3508b26a0bf 100644
--- a/tests/rustdoc-gui/source-anchor-scroll.goml
+++ b/tests/rustdoc-gui/source-anchor-scroll.goml
@@ -11,7 +11,7 @@ click: '//a[text() = "barbar" and @href="#5-7"]'
 assert-property: ("html", {"scrollTop": "123"})
 click: '//a[text() = "bar" and @href="#28-36"]'
 assert-property: ("html", {"scrollTop": "154"})
-click: '//a[text() = "sub_fn" and @href="#2-4"]'
+click: '//a[normalize-space() = "sub_fn" and @href="#2-4"]'
 assert-property: ("html", {"scrollTop": "51"})
 
 // We now check that clicking on lines doesn't change the scroll
diff --git a/tests/rustdoc/extremely_long_typename.extremely_long_typename.html b/tests/rustdoc/extremely_long_typename.extremely_long_typename.html
new file mode 100644
index 00000000000..b20e59866da
--- /dev/null
+++ b/tests/rustdoc/extremely_long_typename.extremely_long_typename.html
@@ -0,0 +1 @@
+<li><div class="item-name"><a class="struct" href="struct.CreateSubscriptionPaymentSettingsPaymentMethodOptionsCustomerBalanceBankTransferEuBankTransfer.html" title="struct extremely_long_typename::CreateSubscriptionPaymentSettingsPaymentMethodOptionsCustomerBalanceBankTransferEuBankTransfer">Create<wbr />Subscription<wbr />Payment<wbr />Settings<wbr />Payment<wbr />Method<wbr />Options<wbr />Customer<wbr />Balance<wbr />Bank<wbr />Transfer<wbr />EuBank<wbr />Transfer</a></div></li>
\ No newline at end of file
diff --git a/tests/rustdoc/extremely_long_typename.rs b/tests/rustdoc/extremely_long_typename.rs
new file mode 100644
index 00000000000..212afe2d110
--- /dev/null
+++ b/tests/rustdoc/extremely_long_typename.rs
@@ -0,0 +1,7 @@
+// ignore-tidy-linelength
+// Make sure that, if an extremely long type name is named,
+// the item table has it line wrapped.
+// There should be some reasonably-placed `<wbr>` tags in the snapshot file.
+
+// @snapshot extremely_long_typename "extremely_long_typename/index.html" '//ul[@class="item-table"]/li'
+pub struct CreateSubscriptionPaymentSettingsPaymentMethodOptionsCustomerBalanceBankTransferEuBankTransfer;
diff --git a/tests/rustdoc/item-desc-list-at-start.item-table.html b/tests/rustdoc/item-desc-list-at-start.item-table.html
index 72bde573cea..cff4f816529 100644
--- a/tests/rustdoc/item-desc-list-at-start.item-table.html
+++ b/tests/rustdoc/item-desc-list-at-start.item-table.html
@@ -1 +1 @@
-<ul class="item-table"><li><div class="item-name"><a class="constant" href="constant.MY_CONSTANT.html" title="constant item_desc_list_at_start::MY_CONSTANT">MY_CONSTANT</a></div><div class="desc docblock-short">Groups: <code>SamplePatternSGIS</code>, <code>SamplePatternEXT</code></div></li></ul>
\ No newline at end of file
+<ul class="item-table"><li><div class="item-name"><a class="constant" href="constant.MY_CONSTANT.html" title="constant item_desc_list_at_start::MY_CONSTANT">MY_<wbr />CONSTANT</a></div><div class="desc docblock-short">Groups: <code>SamplePatternSGIS</code>, <code>SamplePatternEXT</code></div></li></ul>
\ No newline at end of file