use rumdl_lib::lint_context::LintContext;
use rumdl_lib::rule::Rule;
use rumdl_lib::rules::MD054LinkImageStyle;
#[test]
fn test_name() {
let rule = MD054LinkImageStyle::default();
assert_eq!(rule.name(), "MD054");
}
#[test]
fn test_consistent_link_styles() {
let rule = MD054LinkImageStyle::default();
let content = r#"
This is a document with [inline links](https://example.com).
Here's another [link](https://example2.com).
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 0);
let content = r#"
This is a document with [inline links](https://example.com).
Here's an <https://example.com> autolink.
Here's a [collapsed][] link.
[collapsed]: https://example.com
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 0);
}
#[test]
fn test_inconsistent_link_styles() {
let rule = MD054LinkImageStyle::new(false, true, true, true, true, true);
let content = r#"
This is a document with [inline links](https://example.com).
Here's an <https://example.com> autolink.
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].line, 3);
assert_eq!(result[0].message, "Link/image style 'autolink' is not allowed");
}
#[test]
fn test_code_blocks_ignored() {
let rule = MD054LinkImageStyle::new(false, true, true, true, true, true);
let content = r#"
This is a document with [inline links](https://example.com).
```markdown
Here's an <https://example.com> autolink in a code block.
```
This is an inline code with a link: `<https://example.com>`
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 0);
}
#[test]
fn test_fix_unsupported() {
let rule = MD054LinkImageStyle::default();
let content = r#"
This has [inline](https://example.com) and <https://example.org> links.
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.fix(&ctx);
assert!(result.is_err());
}
#[test]
fn test_url_inline_style() {
let rule = MD054LinkImageStyle::new(true, true, true, true, true, false);
let content = r#"
This is a [https://example.com](https://example.com) URL-inline link.
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].line, 2);
assert_eq!(result[0].message, "Link/image style 'url-inline' is not allowed");
}
#[test]
fn test_full_and_shortcut_references() {
let rule = MD054LinkImageStyle::new(true, true, false, true, false, true);
let content = r#"
This is an [inline link](https://example.com).
This is a [full reference][ref] link.
This is a [shortcut] reference.
[ref]: https://example.com
[shortcut]: https://shortcut.com
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 2);
assert!(result.iter().any(|w| w.line == 3 && w.message.contains("full")));
assert!(result.iter().any(|w| w.line == 4 && w.message.contains("shortcut")));
}
#[test]
fn test_all_link_types() {
let rule = MD054LinkImageStyle::default();
let content = r#"
[Inline link](https://example.com)
<https://example.com>
[Collapsed][]
[Full reference][full]
[Shortcut]
[https://example.com](https://example.com)
[Collapsed]: https://example.com
[full]: https://example.com/full
[Shortcut]: https://example.com/shortcut
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 0);
}
#[test]
fn test_unicode_support() {
let rule = MD054LinkImageStyle::default();
let content = r#"
[Unicode café link](https://example.com/café)
<https://example.com/café>
[Unicode emoji 🔗][emoji-ref]
[Unicode 汉字 characters][han]
[🔗 emoji shortcut]
[café][]
[emoji-ref]: https://example.com/emoji/🔗
[han]: https://example.com/汉字
[🔗 emoji shortcut]: https://emoji.example.com
[café]: https://example.com/café
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 0);
let rule_restricted = MD054LinkImageStyle::new(false, true, true, true, true, true);
let content_mixed = r#"
[Unicode link](https://example.com/café)
<https://example.com/unicode/汉字>
"#;
let ctx_mixed = LintContext::new(content_mixed, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule_restricted.check(&ctx_mixed).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].line, 3);
assert_eq!(result[0].message, "Link/image style 'autolink' is not allowed");
let content_long = r#"
This is a very long line with some [Unicode content including many characters like café, 汉字, ñáéíóú, こんにちは, привет, שלום, مرحبا, and many more symbols like ⚡🔥🌟✨🌈⭐💫🌠 in a very long text](https://example.com/unicode).
"#;
let ctx_long = LintContext::new(content_long, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx_long).unwrap();
assert_eq!(result.len(), 0);
let content_reversed = r#"
This is a reversed link with Unicode: (Unicode café)[https://example.com/café]
"#;
let ctx_reversed = LintContext::new(content_reversed, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx_reversed).unwrap();
assert_eq!(result.len(), 0);
}
#[test]
fn test_image_styles() {
let rule = MD054LinkImageStyle::default();
let content = r#"
An .
An ![collapsed image][].
A ![full image][ref].
A ![shortcut image].
[collapsed image]: img.png
[ref]: img.png
[shortcut image]: img.png
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty(), "All image styles should be valid by default");
let rule_no_collapse = MD054LinkImageStyle::new(true, false, true, true, true, true);
let content_mix = r#"
An .
An ![collapsed image][].
[collapsed image]: img.png
"#;
let ctx_mix = LintContext::new(content_mix, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule_no_collapse.check(&ctx_mix).unwrap();
assert_eq!(result.len(), 1, "Should flag disallowed collapsed image style");
assert_eq!(result[0].line, 3);
assert!(result[0].message.contains("collapsed"));
let content_code = r#"
This has an `` in inline code.
And `![collapsed][]`
And `![full][ref]`
And `![shortcut]`
[collapsed]: img.png
[ref]: img.png
[shortcut]: img.png
"#;
let ctx_code = LintContext::new(content_code, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx_code).unwrap();
assert!(result.is_empty(), "Image styles in code spans should be ignored");
}
#[test]
fn test_shortcut_edge_cases() {
let rule = MD054LinkImageStyle::default();
let content = r#"
Link [shortcut] followed by [another].
Link [collapsed][] followed by text.
Link [full][ref] followed by text.
[shortcut]: /shortcut
[another]: /another
[collapsed]: /collapsed
[ref]: /full
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(
result.is_empty(),
"Shortcut detection should not interfere with other types"
);
let rule_no_shortcut = MD054LinkImageStyle::new(true, true, true, true, false, true);
let content_flag_shortcut = r#"
[Okay collapsed][]
[Okay full][ref]
[Not okay shortcut]
[Okay collapsed]: /
[ref]: /
[Not okay shortcut]: /
"#;
let ctx_flag_shortcut = LintContext::new(content_flag_shortcut, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule_no_shortcut.check(&ctx_flag_shortcut).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].line, 4);
assert!(result[0].message.contains("shortcut"));
}
#[test]
fn test_html_comments_are_ignored() {
let rule = MD054LinkImageStyle::new(false, false, false, false, false, false); let content = r#"
<!-- This is a comment with an autolink: <https://example.com> -->
<!-- Unicode autolink: <https://example.com/汉字> -->
<!-- [inline link](https://example.com) -->
<!-- [Unicode café link](https://example.com/café) -->
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 0, "Links in HTML comments should not be flagged");
}
#[test]
fn test_autolink_unicode_in_and_outside_comments() {
let rule = MD054LinkImageStyle::new(false, true, true, true, true, true); let content = r#"
This is an autolink: <https://example.com/汉字>
<!-- This is a comment with an autolink: <https://example.com/汉字> -->
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1, "Only autolink outside comment should be flagged");
assert_eq!(result[0].line, 2);
assert_eq!(result[0].message, "Link/image style 'autolink' is not allowed");
}
#[test]
fn test_mixed_styles_in_and_outside_comments() {
let rule = MD054LinkImageStyle::new(false, false, true, true, false, false); let content = r#"
[inline link](https://example.com)
[full ref][ref]
[shortcut]
<https://example.com>
<!-- [shortcut] and <https://example.com> in comment should not be flagged -->
<!-- [shortcut] <https://example.com> -->
[ref]: https://example.com
[shortcut]: https://example.com
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(
result.len(),
2,
"Only shortcut and autolink outside comments should be flagged"
);
assert!(result.iter().any(|w| w.message.contains("shortcut")));
assert!(result.iter().any(|w| w.message.contains("autolink")));
}
#[test]
fn test_task_list_checkboxes_not_flagged_as_shortcut() {
let rule = MD054LinkImageStyle::new(true, true, true, true, false, true);
let content = r#"
# Todos
- [ ] Task 1
- [x] Task 2
- [X] Task 3
* [ ] Task 4
+ [x] Task 5
[actual shortcut]: https://example.com
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(
result.len(),
0,
"Task list checkboxes should not be flagged as shortcut links"
);
}
#[test]
fn test_task_list_vs_shortcut_distinction() {
let rule = MD054LinkImageStyle::new(true, true, true, true, false, true);
let content = r#"
- [ ] This is a task list item
- [actual link] This is a shortcut link that should be flagged
[actual link]: https://example.com
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1, "Should flag shortcut link but not task list checkbox");
assert!(result[0].message.contains("shortcut"));
assert_eq!(result[0].line, 3, "Should flag the line with the shortcut link");
}
#[test]
fn test_html_tags_not_flagged_as_autolink() {
let rule = MD054LinkImageStyle::new(false, true, true, true, true, true);
let content = r#"
# Example
One line.<br>
Second line.
<https://example.com> This should be flagged
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1, "Should flag autolink but not HTML tag");
assert!(result[0].message.contains("autolink"));
assert_eq!(result[0].line, 7, "Should flag the line with the autolink");
}
#[test]
fn test_various_html_tags_not_flagged() {
let rule = MD054LinkImageStyle::new(false, true, true, true, true, true);
let content = r#"
<div>Content</div>
<span>Text</span>
<br>
<hr>
<img src="image.png">
<p>Paragraph</p>
<strong>Bold</strong>
<em>Italic</em>
<https://example.com> This should be flagged
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1, "Should flag autolink but not HTML tags");
assert!(result[0].message.contains("autolink"));
}
#[test]
fn test_autolink_urls_still_detected() {
let rule = MD054LinkImageStyle::new(false, true, true, true, true, true);
let content = r#"
<br> HTML tag should not be flagged
<https://example.com> Autolink should be flagged
<http://example.com> HTTP autolink should be flagged
<mailto:test@example.com> Mailto should be flagged
<ftp://example.com> FTP should be flagged
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 4, "Should flag all autolinks but not HTML tag");
assert!(result.iter().all(|w| w.message.contains("autolink")));
}