Files
2026-10-16 18:53:02 +02:00

144 lines
5.0 KiB
Rust

use super::*;
fn at(text: &str, needle: &str) -> Result<LinkTarget, String> {
let byte = text.find(needle).unwrap();
let prefix = &text[..byte];
let row = prefix.bytes().filter(|byte| *byte == b'\n').count();
let column = prefix.rsplit('\n').next().unwrap().graphemes(true).count();
link_at(text, TextPosition { row, column })
}
fn target(path: Option<&str>, fragment: Option<&str>) -> LinkTarget {
LinkTarget {
path: path.map(PathBuf::from),
fragment: fragment.map(str::to_owned),
}
}
#[test]
fn follows_inline_labels_destinations_and_grapheme_columns() {
let text = "👩‍💻 [go **there**](<../My%20Notes/%E6%97%A5%E6%9C%AC\\(1\\).md#caf%C3%A9>)";
let expected = target(Some("../My Notes/日本(1).md"), Some("café"));
assert_eq!(at(text, "there").unwrap(), expected);
assert_eq!(at(text, "My%20").unwrap(), expected);
assert_eq!(
at("[absolute](/Tmp/Note.md)", "absolute").unwrap(),
target(Some("/Tmp/Note.md"), None)
);
assert_eq!(
at("[here](#title)", "here").unwrap(),
target(None, Some("title"))
);
assert_eq!(at("[top]()", "top").unwrap(), target(None, None));
assert_eq!(
at("[here](#title)", "[here").unwrap(),
target(None, Some("title"))
);
assert_eq!(
at("[file](literal%23name.md)", "file").unwrap(),
target(Some("literal#name.md"), None)
);
assert_eq!(
at("[file](My\\[Notes\\].md)", "file").unwrap(),
target(Some("My[Notes].md"), None)
);
}
#[test]
fn resolves_all_reference_forms_and_multiline_block_definitions() {
let text = "[full][ A B ] [a b][] [A B]\n\n> [a b]:\n> <My%20Notes.md#title>\n> \"A title\"\n\n[a b]: ignored.md\n";
let expected = target(Some("My Notes.md"), Some("title"));
for needle in ["full", " A B ", "a b][]", "A B]"] {
assert_eq!(at(text, needle).unwrap(), expected);
}
assert_eq!(
at("[Straße]\n\n[STRASSE]: file.md", "Straße").unwrap(),
target(Some("file.md"), None)
);
let text = "> [multi\n> line][target]\n\n[target]: other.md\n";
assert_eq!(at(text, "line]").unwrap(), target(Some("other.md"), None));
}
#[test]
fn rejects_images_code_plaintext_and_external_urls() {
for (text, needle) in [
("![image](file.md)", "image"),
("`[code](file.md)`", "code"),
("```md\n[code](file.md)\n```", "file"),
(" [code](file.md)", "code"),
("plain file.md text", "file"),
("[web](https://example.com)", "web"),
("[web](//example.com/a)", "web"),
("[web](mailto:a@example.com)", "web"),
("<https://example.com>", "example"),
] {
assert!(at(text, needle).is_err(), "{text}");
}
assert!(
at("[missing]", "missing")
.unwrap_err()
.contains("not defined")
);
assert!(
at("[bad](file%ZZ.md)", "bad")
.unwrap_err()
.contains("percent")
);
assert!(
at("[bad](file%FF.md)", "bad")
.unwrap_err()
.contains("UTF-8")
);
assert!(at("[bad](#)", "bad").unwrap_err().contains("empty"));
}
#[test]
fn locates_visible_unicode_headings_setext_and_unique_slugs() {
let text = "# **Café** [日本](destination.md) `code` ###\n\n> ## Café 日本 code\n\nCafé 日本 code\n====\n\n# Café 日本 code-1\n\n# Cafe\u{301} _name_ ![alt](hidden.md)\n\n```md\n# Hidden\n```\n";
assert_eq!(heading_row(text, "café-日本-code").unwrap(), 0);
assert_eq!(heading_row(text, "café-日本-code-1").unwrap(), 2);
assert_eq!(heading_row(text, "café-日本-code-2").unwrap(), 4);
assert_eq!(heading_row(text, "café-日本-code-1-1").unwrap(), 7);
assert_eq!(heading_row(text, "cafe\u{301}-name-alt").unwrap(), 9);
assert!(heading_row(text, "hidden").is_err());
assert!(heading_row(text, "destinationmd").is_err());
assert!(heading_row(text, "").is_err());
assert_eq!(
heading_row("# [Heading][ref]\n\n[ref]: ignored.md", "heading").unwrap(),
0
);
assert_eq!(
heading_row("# ![Alt][ref]\n\n[ref]: ignored.md", "alt").unwrap(),
0
);
assert_eq!(heading_row("# ![Alt][missing]", "altmissing").unwrap(), 0);
}
#[test]
fn renders_entities_or_reports_the_explicit_entity_ceiling() {
assert_eq!(
at("[file](a&amp;b&#x20;c.md)", "file").unwrap(),
target(Some("a&b c.md"), None)
);
assert_eq!(
at("[file](&lt;name&gt;)", "file").unwrap(),
target(Some("<name>"), None)
);
assert!(
at("[file](a&copy;b.md)", "file")
.unwrap_err()
.contains("Unsupported Markdown HTML entity")
);
assert_eq!(heading_row("# A &amp; B &#xE9;\n", "a--b-é").unwrap(), 0);
assert!(
heading_row("# &copy;\n", "copy")
.unwrap_err()
.contains("Unsupported Markdown HTML entity")
);
assert_eq!(heading_row("# &copy;\n# Valid\n", "valid").unwrap(), 1);
assert_eq!(
heading_row("> First\n> Second\n> ------\n", "first-second").unwrap(),
0
);
}