" } else { "~~" }),
Style::Link { serial, .. } => {
self.last_link = self.last_link.max(serial);
+ self.last_link_content = self.last_link_content.max(serial);
if self.output.ends_with('!') {
self.output.insert(self.output.len() - 1, '\\');
}
diff --git a/moli-html2md/tests/regressions.rs b/moli-html2md/tests/regressions.rs
index f5e12486a8b..c6f537d6e2c 100644
--- a/moli-html2md/tests/regressions.rs
+++ b/moli-html2md/tests/regressions.rs
@@ -57,6 +57,40 @@ fn empty_links_keep_destinations_across_adjacent_and_nested_elements() {
}
}
+#[test]
+fn empty_links_use_their_own_accessible_label_without_replacing_visible_text() {
+ for (html, expected) in [
+ (
+ "",
+ "[Open report](/report)",
+ ),
+ (
+ "Visible report",
+ "[Visible report](/report)",
+ ),
+ (
+ " ",
+ "[Open report](/report)",
+ ),
+ ("", "[](/report)"),
+ (
+ "",
+ r"[\[Open\] \*report\*](/report)",
+ ),
+ (
+ "",
+ "[One](/one)[Two](/two)",
+ ),
+ (
+ "
",
+ "[](/report)",
+ ),
+ ("", ""),
+ ] {
+ assert_eq!(markdown(html, false), expected, "{html}");
+ }
+}
+
#[test]
fn href_without_a_destination_does_not_create_an_empty_markdown_link() {
for html in [
@@ -445,6 +479,91 @@ fn embedded_image_references_survive_regardless_of_pixel_content() {
}
}
+#[test]
+fn native_media_elements_keep_declared_resource_urls() {
+ let result = markdown(
+ "",
+ false,
+ );
+ for expected in [
+ "",
+ "[Video](/direct.mp4)",
+ "[Media source](/movie.webm)",
+ "[Media source](/movie.mp4)",
+ "[Media track](/captions.vtt)",
+ "[Audio](/episode.ogg)",
+ ] {
+ assert!(result.contains(expected), "missing {expected}: {result}");
+ }
+}
+
+#[test]
+fn media_elements_do_not_emit_active_script_urls() {
+ for unsafe_url in [
+ "javascript:alert(1)",
+ "java
script:alert(1)",
+ "vbscript:bad",
+ "data:text/html,bad",
+ "about:blank",
+ "#fragment",
+ ] {
+ let source = format!("");
+ let result = markdown(&source, false);
+ assert!(result.is_empty(), "{source}: {result}");
+ }
+}
+
+#[test]
+fn preformatted_directory_links_keep_destinations_outside_literal_text() {
+ let html = "file.mp4 checksum
";
+ for preformatted in [false, true] {
+ assert_eq!(
+ markdown(html, preformatted),
+ "```\nfile.mp4 checksum\n```\n\n[file.mp4](release/file.mp4)\n[checksum](release/checksum.txt)"
+ );
+ }
+}
+
+#[test]
+fn preformatted_code_cross_references_preserve_literal_source() {
+ let html = "println!("hello");\nlet values = Vec::new();
";
+ for preformatted in [false, true] {
+ assert_eq!(
+ markdown(html, preformatted),
+ "```rust\nprintln!(\"hello\");\nlet values = Vec::new();\n```\n\n[println!](/macros/println)\n[Vec](/types/Vec)"
+ );
+ }
+}
+
+#[test]
+fn preformatted_directory_links_survive_around_code_examples() {
+ let html = "before\nValue value;\nafter
";
+ for preformatted in [false, true] {
+ assert_eq!(
+ markdown(html, preformatted),
+ "```\nbefore\nValue value;\nafter\n```\n\n[before](before.txt)\n[Value](/types/Value)\n[after](after.txt)"
+ );
+ }
+}
+
+#[test]
+fn preformatted_signatures_and_language_blocks_keep_source_without_code_wrappers() {
+ for (html, expected) in [
+ (
+ "public String getText()
",
+ "```\npublic String getText()\n```\n\n[String](https://docs.example/String)",
+ ),
+ (
+ "let value: String = String::new();
",
+ "```rust\nlet value: String = String::new();\n```\n\n[String](/types/String)",
+ ),
+ ] {
+ for preformatted in [false, true] {
+ assert_eq!(markdown(html, preformatted), expected, "{html}");
+ }
+ }
+}
+
#[test]
fn list_paragraphs_and_code_blocks_require_blank_lines_between_items() {
for (html, expected) in [
@@ -488,3 +607,45 @@ fn fallback_block_elements_separate_neighboring_words() {
);
}
}
+
+#[test]
+fn accessible_link_fallback_never_duplicates_visible_inline_or_block_descendants() {
+ for content in [
+ "Visible",
+ "
",
+ "code",
+ "preformatted
",
+ "nested code
",
+ "Paragraph
",
+ "Heading
",
+ "",
+ "Quotation
",
+ "",
+ ] {
+ let plain = format!("{content}");
+ let labeled = format!("{content}");
+ assert_eq!(
+ markdown(&labeled, false),
+ markdown(&plain, false),
+ "{content}"
+ );
+ }
+}
+
+#[test]
+fn empty_link_fallback_belongs_only_to_that_anchor() {
+ assert_eq!(
+ markdown(
+ "",
+ false
+ ),
+ "[](/one)[Own](/two)"
+ );
+ assert_eq!(
+ markdown(
+ "",
+ false
+ ),
+ "[Own](/target)"
+ );
+}