use rumdl_lib::lint_context::LintContext;
use rumdl_lib::rule::Rule;
use rumdl_lib::rules::{MD044ProperNames, MD045NoAltText, MD052ReferenceLinkImages};
#[test]
fn test_md044_unicode_proper_names() {
let rule = MD044ProperNames::new(
vec![
"JavaScript".to_string(),
"中文名称".to_string(),
"العربية".to_string(),
"Café".to_string(),
"naïve".to_string(),
"Zürich".to_string(),
"Москва".to_string(),
"🚀Rocket".to_string(),
],
true,
);
let content = "\
I love javascript and javascript is great.
The 中文名称 and 中文名稱 should be detected.
Visit москва for the conference.
Try the cafe in zurich.
The implementation is naive.
🚀rocket is launching soon.";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(
result.len(),
7,
"Should detect ALL improper capitalizations: javascript(x2), 中文名称, москва, cafe, zurich, naive, 🚀rocket"
);
let fixed = rule.fix(&ctx).unwrap();
assert!(fixed.contains("JavaScript"));
assert!(fixed.contains("Москва"));
assert!(fixed.contains("Café"), "Must fix cafe -> Café");
assert!(fixed.contains("Zürich"), "Must fix zurich -> Zürich");
assert!(fixed.contains("naïve"), "Must fix naive -> naïve");
assert!(fixed.contains("🚀Rocket"));
}
#[test]
fn test_md044_special_characters_names() {
let rule = MD044ProperNames::new(
vec![
"Node.js".to_string(),
"ASP.NET".to_string(),
"C++".to_string(),
"C#".to_string(),
"F#".to_string(),
".NET".to_string(),
"@angular/core".to_string(),
"package.json".to_string(),
"Wi-Fi".to_string(),
"e-mail".to_string(),
],
true,
);
let content = "\
I use node.js and nodejs for development.
Working with asp.net and ASP.net frameworks.
Programming in c++ and c# languages.
The .net framework and f# language.
Import from @angular/Core module.
Edit the Package.json file.
Connect to wifi or wi-fi network.
Send an Email or e-Mail message.";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.len() >= 8, "Should detect special character names");
let fixed = rule.fix(&ctx).unwrap();
assert!(fixed.contains("Node.js"));
assert!(fixed.contains("ASP.NET"));
assert!(fixed.contains("C++"));
assert!(fixed.contains("C#"));
assert!(fixed.contains("F#"));
assert!(fixed.contains("@angular/core"));
assert!(fixed.contains("package.json"));
}
#[test]
fn test_md044_word_boundaries() {
let rule = MD044ProperNames::new(
vec!["Go".to_string(), "IT".to_string(), "I".to_string(), "A".to_string()],
true,
);
let content = "\
Let's go with Go programming.
The word 'going' should not match go.
it department handles IT issues.
i think I should use a framework.
This is a test of A versus a.
Don't match 'ago' or 'bit' or 'ai'.";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(
result
.iter()
.any(|r| r.message.contains("go") && !r.message.contains("going"))
);
assert!(
result
.iter()
.any(|r| r.message.contains("it") && !r.message.contains("bit"))
);
}
#[test]
fn test_md044_code_exclusion() {
let rule = MD044ProperNames::new(
vec!["JavaScript".to_string(), "Python".to_string()],
false, );
let content = "\
Use javascript in production.
```javascript
// This javascript and python should be ignored
const javascript = 'python';
```
The `javascript` and `python` in backticks should be ignored.
```
plain javascript and python in code block
```
More javascript and python outside code.";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(
result.len(),
3,
"Should detect only javascript and python outside ALL code contexts"
);
}
#[test]
fn test_md044_html_comment_handling() {
let rule = MD044ProperNames::new(vec!["JavaScript".to_string()], true);
let content = "\
Use javascript here.
<!-- This javascript should be ignored -->
<!--
Multi-line comment with javascript
should also be ignored
-->
More javascript usage.
<!-- javascript --> between <!-- javascript --> comments";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(
result.len(),
6,
"Should detect all 6 javascript occurrences including in HTML comments"
);
}
#[test]
fn test_md044_complex_patterns() {
let rule = MD044ProperNames::new(
vec![
"GitHub".to_string(),
"GitLab".to_string(),
"LaTeX".to_string(),
"macOS".to_string(),
"iOS".to_string(),
"iPadOS".to_string(),
"TypeScript".to_string(),
"JavaScript".to_string(),
],
true,
);
let content = "\
Upload to github, GITHUB, or Github.
Compare gitlab with GITLAB and GitLAB.
Write in latex or LATEX format.
Develop for macos, MacOS, and ios.
Use typescript with javascript.
Support for ipados and IpadOS.";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let _result = rule.check(&ctx).unwrap();
let fixed = rule.fix(&ctx).unwrap();
assert!(!fixed.contains("github"));
assert!(!fixed.contains("GITHUB"));
assert!(!fixed.contains("Github"));
assert!(fixed.contains("GitHub"));
assert!(fixed.contains("macOS"));
assert!(fixed.contains("iOS"));
}
#[test]
fn test_md045_unicode_alt_text() {
let rule = MD045NoAltText::new();
let content = "\







";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 8, "Should detect all images with missing/empty alt text");
}
#[test]
fn test_md045_reference_style_images() {
let rule = MD045NoAltText::new();
let content = "\
![][ref1]
![ ][ref2]
![Valid alt text][ref3]
![][ref-with-unicode-图片]
[ref1]: image1.png
[ref2]: image2.png
[ref3]: image3.png
[ref-with-unicode-图片]: unicode.png
Shortcut reference: ![shortcut]
[shortcut]: shortcut.png";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 3, "Should detect reference images without alt text");
}
#[test]
fn test_md045_nested_constructs() {
let rule = MD045NoAltText::new();
let content = "\
- List item with 
- Nested 
> Blockquote with 
> > Nested quote 
| Table | Header |
|-------|--------|
| Cell |  |
[Link with  inside](url)
*Emphasis with  inside*
**Strong with  inside**";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 8, "Should detect images in all contexts");
}
#[test]
fn test_md045_code_exclusion() {
let rule = MD045NoAltText::new();
let content = "\
Regular image: 
`Inline code with  image`
```
Code block with 
```
```markdown
Even in markdown code blocks 
```
Four spaces code 
More regular: ";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 2, "Should only detect images outside code");
}
#[test]
fn test_md045_edge_patterns() {
let rule = MD045NoAltText::new();
let content = "\
![]()




(extra-parens)
\\
";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.len() >= 3, "Should detect valid images without alt text");
}
#[test]
fn test_md045_html_images() {
let rule = MD045NoAltText::new();
let content = "\

<img src=\"html.png\">
<img src=\"html-with-alt.png\" alt=\"Has alt text\">
<img src=\"html-empty-alt.png\" alt=\"\">
Mixed:  and <img src=\"html2.png\">";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 2, "Should only check Markdown images");
}
#[test]
fn test_md052_unicode_references() {
let rule = MD052ReferenceLinkImages::new();
let content = "\
Check [this link][中文引用]
See [another][עברית]
Image: ![alt][图片引用]
Unicode emoji ref: [click][🔗link]
Missing: [undefined][参照なし]
[中文引用]: https://example.com/chinese
[עברית]: https://example.com/hebrew
[图片引用]: image.png
Note: 🔗link is not defined";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 2, "Should detect missing Unicode references");
assert!(result.iter().any(|r| r.message.contains("参照なし")));
assert!(result.iter().any(|r| r.message.contains("🔗link")));
}
#[test]
fn test_md052_case_sensitivity() {
let rule = MD052ReferenceLinkImages::new();
let content = "\
Links: [text][REF], [text][ref], [text][Ref]
Images: ![alt][IMG], ![alt][img], ![alt][Img]
Missing: [text][MISSING], [text][missing]
[ref]: https://example.com
[IMG]: image.png";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(
result.len(),
1,
"Should detect exactly 1 unique missing reference (case-insensitive)"
);
}
#[test]
fn test_md052_shortcut_references() {
let rule = MD052ReferenceLinkImages::new();
let content = "\
Shortcut link: [shortcut]
Another: [defined]
Image shortcut: ![image-ref]
Undefined: [no-definition]
[defined]: https://example.com
[image-ref]: image.png
Mixed with [normal][ref] syntax
[ref]: https://ref.com";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 0, "Shortcut references are not checked by default");
}
#[test]
fn test_md052_code_exclusion() {
let rule = MD052ReferenceLinkImages::new();
let content = "\
Real reference: [link][ref1]
`Code with [link][ref2] inside`
```
Code block [link][ref3]
More [refs][ref4]
```
Indented code [link][ref5]
List context might affect this:
- Item with [link][ref6]
- Nested [link][ref7]
[ref1]: url1
[ref6]: url6
[ref7]: url7";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(!result.iter().any(|r| r.message.contains("ref2")));
assert!(!result.iter().any(|r| r.message.contains("ref3")));
assert!(!result.iter().any(|r| r.message.contains("ref4")));
}
#[test]
fn test_md052_complex_references() {
let rule = MD052ReferenceLinkImages::new();
let content = "\
Multiple on line: [a][ref1] and [b][ref2] and [c][ref3]
Nested: [outer [inner][ref4] text][ref5]
Adjacent: [first][ref6][second][ref7]
Empty ref: [text][]
Space in ref: [text][ref with spaces]
Special chars: [text][ref-with-dash_and_underscore]
[ref1]: url1
[ref3]: url3
[ref5]: url5
[ref6]: url6
[ref-with-dash_and_underscore]: special-url
Missing: ref2, ref4, ref7, ref with spaces";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.len() >= 4, "Should detect various missing references");
}
#[test]
fn test_md052_reference_definitions() {
let rule = MD052ReferenceLinkImages::new();
let content = "\
Use [link1][ref1] and [link2][ref2]
Also ![image1][img1] and ![image2][img2]
[ref1]: https://example.com \"Title\"
[ref2]: <https://example.com> 'Title'
[img1]: path/to/image.png (Title)
[img2]: ../relative/path.jpg
\"Multi-line title\"
Undefined: [missing][undefined]
Empty definition should work: [empty][empty-ref]
[empty-ref]:
Duplicate definitions:
[dup]: first.com
[dup]: second.com
[dup]: third.com
Using [dup][dup] should work";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1, "Should detect exactly 1 undefined reference");
assert!(result[0].message.contains("undefined"));
}
#[test]
fn test_inline_rules_interaction() {
let md044 = MD044ProperNames::new(vec!["JavaScript".to_string(), "GitHub".to_string()], true);
let md045 = MD045NoAltText::new();
let md052 = MD052ReferenceLinkImages::new();
let content = "\
Use javascript to upload images to github.
Here's an image without alt text: 
Check the [javascript guide][js-guide] on [github][gh].
Another image reference: ![github logo][gh-logo]
[gh]: https://github.com
[gh-logo]: github-logo.png
Note: js-guide is not defined";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result044 = md044.check(&ctx).unwrap();
let result045 = md045.check(&ctx).unwrap();
let result052 = md052.check(&ctx).unwrap();
assert_eq!(
result044.len(),
5,
"MD044: Must detect 5 improper names (2 standalone + 2 in link text + 1 in image alt)"
);
assert_eq!(result045.len(), 1, "MD045: Must detect 1 image without alt text");
assert_eq!(
result052.len(),
1,
"MD052: Must detect 1 undefined reference [js-guide]"
);
}
#[test]
fn test_md044_performance_edge_cases() {
let rule = MD044ProperNames::new(
vec!["Test".to_string(); 100], true,
);
let content = "test ".repeat(1000);
let ctx = LintContext::new(&content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1000, "Should handle many occurrences efficiently");
}
#[test]
fn test_md045_image_title_attribute() {
let rule = MD045NoAltText::new();
let content = "\



![][ref]
[ref]: image.png \"Reference title\"";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 3, "Title attribute doesn't replace alt text requirement");
}
#[test]
fn test_md052_nested_brackets() {
let rule = MD052ReferenceLinkImages::new();
let content = "\
Link with [brackets [inside]][ref1]
Image with ![brackets [in] alt][ref2]
Escaped \\[not a link\\][ref3]
Actually escaped: \\[link\\]\\[ref4\\]
But this is real: [link][ref5]
[ref1]: url1
[ref2]: url2
[ref5]: url5";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 0, "Should NOT detect escaped references as undefined");
}
#[test]
fn test_inline_content_front_matter() {
let md044 = MD044ProperNames::new(vec!["JavaScript".to_string()], true);
let content = "\
---
title: Using javascript
tags: [javascript, programming]
---
# Learning javascript
The javascript ecosystem is vast.";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = md044.check(&ctx).unwrap();
assert!(result.len() >= 2, "Should check front matter content");
}
#[test]
fn test_inline_content_html_mixed() {
let md045 = MD045NoAltText::new();
let md052 = MD052ReferenceLinkImages::new();
let content = "\
<div>

<img src=\"html-image.png\">
</div>
Regular  image.
<p>Link to [reference][ref] in HTML</p>
<!-- Comment with  -->
[ref]: defined.com";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result045 = md045.check(&ctx).unwrap();
let result052 = md052.check(&ctx).unwrap();
assert_eq!(
result045.len(),
1,
"Should only detect Markdown images outside HTML blocks (CommonMark compliance)"
);
assert_eq!(result052.len(), 0, "All references should be defined");
}
#[test]
fn test_md044_overlapping_names() {
let rule = MD044ProperNames::new(
vec![
"JavaScript".to_string(),
"Java".to_string(),
"Script".to_string(),
"TypeScript".to_string(),
],
true,
);
let content = "\
I love javascript and java programming.
The script uses typescript features.
Don't match 'manuscript' or 'subscription'.
But do match java and script separately.";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let _result = rule.check(&ctx).unwrap();
let fixed = rule.fix(&ctx).unwrap();
assert!(fixed.contains("JavaScript"));
assert!(fixed.contains("TypeScript"));
assert!(!fixed.contains("manuScript"));
}
#[test]
fn test_md045_multiline_images() {
let rule = MD045NoAltText::new();
let content = "\



";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(
result.len(),
2,
"Should handle multiline image syntax per CommonMark spec"
);
}
#[test]
fn test_md052_example_sections() {
let rule = MD052ReferenceLinkImages::new();
let content = "\
Regular reference: [link][ref1]
Example:
```
[example][ref2]
```
Examples:
- [another][ref3]
[ref1]: defined.com
Note: ref2 and ref3 in example sections might be excluded";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.len() <= 2, "May exclude example sections");
}