use rumdl_lib::lint_context::LintContext;
use rumdl_lib::rule::Rule;
use rumdl_lib::rules::MD033NoInlineHtml;
#[test]
fn test_no_html() {
let rule = MD033NoInlineHtml::default();
let content = "Just regular markdown\n\n# Heading\n\n* List item";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_simple_html_tag() {
let rule = MD033NoInlineHtml::default();
let content = "Some <b>bold</b> text";
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, 1);
assert_eq!(result[0].column, 6); }
#[test]
fn test_self_closing_tag() {
let rule = MD033NoInlineHtml::default();
let content = "An image: <img src=\"test.png\" />";
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, 1);
assert_eq!(result[0].column, 11); }
#[test]
fn test_allowed_elements() {
let rule = MD033NoInlineHtml::with_allowed(vec!["b".to_string(), "i".to_string()]);
let content = "Some <b>bold</b> and <i>italic</i> but not <u>underlined</u>";
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_html_in_code_block() {
let rule = MD033NoInlineHtml::default();
let content = "Normal text\n```\n<div>This is in a code block</div>\n```\nMore text";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_fix_html_tags() {
let rule = MD033NoInlineHtml::default();
let content = "Some <b>bold</b> text";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, content);
}
#[test]
fn test_fix_self_closing_tags() {
let rule = MD033NoInlineHtml::default();
let content = "Line break<br/>here";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, content);
}
#[test]
fn test_multiple_tags() {
let rule = MD033NoInlineHtml::default();
let content = "<div><p>Nested</p></div>";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 2); assert_eq!(result[0].column, 1); assert_eq!(result[1].column, 6); }
#[test]
fn test_attributes() {
let rule = MD033NoInlineHtml::default();
let content = "<div class=\"test\" id=\"main\">Content</div>";
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].column, 1); }
#[test]
fn test_mixed_content() {
let rule = MD033NoInlineHtml::default();
let content = "# Heading\n\n<div>HTML content</div>\n\n* List item\n\n<span>More HTML</span>";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 2); assert_eq!(result[0].line, 3); assert_eq!(result[1].line, 7); let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, content);
}
#[test]
fn test_preserve_content() {
let rule = MD033NoInlineHtml::default();
let content = "Text with <strong>important</strong> content";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, content);
}
#[test]
fn test_multiline_html() {
let rule = MD033NoInlineHtml::default();
let content = "<div>\nMultiline\ncontent\n</div>";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1);
}
#[test]
fn test_ignore_code_spans() {
let rule = MD033NoInlineHtml::default();
let content = "Use `<div>` for a block element";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_complex_code_block_patterns() {
let rule = MD033NoInlineHtml::default();
let content = "Text\n```\n<div>Code block 1</div>\n```\nMore text\n~~~\n<span>Code block 2</span>\n~~~\nEnd text";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 0);
let content = "```\n<div>Starts with code</div>\n```\nText with <b>bold</b>";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1);
let content = "Text with <i>italic</i>\n```\n<div>Ends with code</div>\n```";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1);
let content = "```\n<div>Block 1</div>\n```\n```\n<span>Block 2</span>\n```";
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_code_span_binary_search() {
let rule = MD033NoInlineHtml::default();
let content = "<span>`code`</span>";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1);
let content = "`code`<div>text</div>";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1);
let content = "Text `<div>` more text";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 0);
let content = "`1` `2` `3` `4` `5` `6` `7` `8` `9` `10` `11` `12` <span>text</span>";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1); }
#[test]
fn test_fix_preserves_structure_html() {
let rule = MD033NoInlineHtml::default();
let content = "Normal <b>bold</b>\n```\n<div>Code block</div>\n```\nMore <i>italic</i>";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, content);
let content = "Text with `<span>` and <div>block</div>";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, content);
let content = "<div><p>Nested content</p></div>";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, content);
}
#[test]
fn test_markdown_comments() {
let rule = MD033NoInlineHtml::default();
let content =
"Some content\n<!-- markdownlint-disable -->\nIgnored content\n<!-- markdownlint-enable -->\nMore content";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty(), "Markdown comments should not be flagged as HTML");
let content = "Some content\n<!-- This is a comment -->\nMore content";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty(), "HTML comments should not be flagged as HTML tags");
}
#[test]
fn test_urls_in_angle_brackets() {
let rule = MD033NoInlineHtml::default();
let content = "Visit <https://example.com> or <http://test.org>\n\
Download from <ftp://files.example.com/file.zip>\n\
Secure transfer: <ftps://secure.example.com/data>\n\
Contact us: <mailto:user@example.com>\n\
Complex URL: <https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers>";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(
result.is_empty(),
"URLs in angle brackets should not be flagged as HTML tags"
);
}
#[test]
fn test_mixed_urls_and_html() {
let rule = MD033NoInlineHtml::default();
let content = "Visit <https://example.com> for more info.\n\
This has <strong>real HTML</strong> tags.\n\
Email us at <mailto:test@example.com> or use <em>emphasis</em>.";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 2);
let flagged_content: Vec<String> = result
.iter()
.map(|w| {
let line_content = content.lines().nth(w.line - 1).unwrap();
let start = w.column - 1;
let tag_end = line_content[start..].find('>').unwrap() + start + 1;
line_content[start..tag_end].to_string()
})
.collect();
assert!(flagged_content.contains(&"<strong>".to_string()));
assert!(flagged_content.contains(&"<em>".to_string()));
assert!(!flagged_content.iter().any(|tag| tag.contains("https://")));
assert!(!flagged_content.iter().any(|tag| tag.contains("mailto:")));
}
#[test]
fn test_edge_case_urls() {
let rule = MD033NoInlineHtml::default();
let content = "Not a URL: <notaurl>\n\
Real URL: <https://example.com>\n\
Fake tag: <https>\n\
Real tag: <div>";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1);
let flagged_positions: Vec<(usize, usize)> = result.iter().map(|w| (w.line, w.column)).collect();
assert!(flagged_positions.contains(&(4, 11)));
}
#[test]
fn test_md033_commonmark_indented_html_ignored() {
let content = r#"# Test Document
Regular HTML: <div>should be flagged</div>
Indented HTML (CommonMark code block):
<div>should NOT be flagged</div>
<p>also should NOT be flagged</p>
More regular HTML: <span>should be flagged</span>"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD033NoInlineHtml::default();
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 2);
let flagged_lines: Vec<usize> = warnings.iter().map(|w| w.line).collect();
assert!(flagged_lines.contains(&3)); assert!(flagged_lines.contains(&10));
assert!(!flagged_lines.contains(&7)); assert!(!flagged_lines.contains(&8)); }
#[test]
fn test_md033_exactly_four_spaces_is_code_block() {
let content = r#"# Test Document
Three spaces (not code): <div>flagged</div>
Exactly four spaces (code block):
<div>not flagged</div>
Five spaces (also code block):
<div>not flagged</div>
Tab indented (code block):
<div>not flagged</div>
Regular HTML: <p>flagged</p>"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD033NoInlineHtml::default();
let warnings = rule.check(&ctx).unwrap();
let flagged_lines: Vec<usize> = warnings.iter().map(|w| w.line).collect();
assert!(flagged_lines.contains(&3)); assert!(flagged_lines.contains(&17));
assert!(!flagged_lines.contains(&7)); assert!(!flagged_lines.contains(&11)); assert!(!flagged_lines.contains(&15)); }
#[test]
fn test_md033_mixed_indented_content_in_code_blocks() {
let content = r#"# Test Document
Regular HTML: <div>flagged</div>
Mixed indented content (all in code block):
<div>HTML in code</div>
Regular text in code
More content in code
<p>More HTML in code</p>
Back to regular: <span>flagged</span>"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD033NoInlineHtml::default();
let warnings = rule.check(&ctx).unwrap();
let flagged_lines: Vec<usize> = warnings.iter().map(|w| w.line).collect();
assert!(flagged_lines.contains(&3)); assert!(flagged_lines.contains(&12));
assert!(!flagged_lines.contains(&7)); assert!(!flagged_lines.contains(&10)); }
#[test]
fn test_md033_indented_code_with_blank_lines() {
let content = r#"# Test Document
Regular: <div>flagged</div>
Indented code block with blank lines:
<div>first block</div>
<p>second block after blank line</p>
<span>third block</span>
Regular again: <em>flagged</em>"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD033NoInlineHtml::default();
let warnings = rule.check(&ctx).unwrap();
let flagged_lines: Vec<usize> = warnings.iter().map(|w| w.line).collect();
assert!(flagged_lines.contains(&3)); assert!(flagged_lines.contains(&13));
assert!(!flagged_lines.contains(&7)); assert!(!flagged_lines.contains(&9)); assert!(!flagged_lines.contains(&11)); }
#[test]
fn test_md033_fenced_vs_indented_code_blocks() {
let content = r#"# Test Document
Regular: <div>flagged</div>
Fenced code block:
```html
<div>not flagged in fenced</div>
<p>also not flagged</p>
```
Indented code block:
<div>not flagged in indented</div>
<p>also not flagged</p>
Regular: <span>flagged</span>"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD033NoInlineHtml::default();
let warnings = rule.check(&ctx).unwrap();
let flagged_lines: Vec<usize> = warnings.iter().map(|w| w.line).collect();
assert!(flagged_lines.contains(&3)); assert!(flagged_lines.contains(&16));
assert!(!flagged_lines.contains(&7)); assert!(!flagged_lines.contains(&8)); assert!(!flagged_lines.contains(&13)); assert!(!flagged_lines.contains(&14)); }
#[test]
fn test_md033_complex_html_in_indented_blocks() {
let content = r#"# Test Document
Regular: <div class="test">flagged</div>
Complex indented HTML:
<div class="container">
<p id="test">Nested HTML</p>
<span data-value="123">More content</span>
<!-- Comment in code -->
<img src="test.jpg" alt="image" />
</div>
Self-closing in regular: <br />
More indented:
<table>
<tr>
<td>Cell content</td>
</tr>
</table>
Regular: <em>flagged</em>"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD033NoInlineHtml::default();
let warnings = rule.check(&ctx).unwrap();
let flagged_lines: Vec<usize> = warnings.iter().map(|w| w.line).collect();
assert_eq!(warnings.len(), 3); assert!(flagged_lines.contains(&3)); assert!(flagged_lines.contains(&14)); assert!(flagged_lines.contains(&24)); }
#[test]
fn test_md033_edge_cases_indentation() {
let content = r#"# Test Document
Regular: <div>flagged</div>
Mixed indentation levels:
<div>4 spaces - code block</div>
<p>8 spaces - still code block</p>
<span>3 spaces - NOT code block</span>
<em>4 spaces - but code block was broken by line 9</em>
Regular: <strong>flagged</strong>"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD033NoInlineHtml::default();
let warnings = rule.check(&ctx).unwrap();
let flagged_lines: Vec<usize> = warnings.iter().map(|w| w.line).collect();
assert!(flagged_lines.contains(&3)); assert!(flagged_lines.contains(&9)); assert!(flagged_lines.contains(&10)); assert!(flagged_lines.contains(&12));
assert!(!flagged_lines.contains(&7)); assert!(!flagged_lines.contains(&8)); }
#[test]
fn test_md033_indented_html_in_lists() {
let content = r#"# Test Document
Regular: <div>flagged</div>
1. List item with indented code:
<div>should NOT be flagged</div>
<p>also should NOT be flagged</p>
2. Another item: <span>should be flagged</span>
But this indented HTML:
<em>should NOT be flagged</em>
Regular: <strong>flagged</strong>"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD033NoInlineHtml::default();
let warnings = rule.check(&ctx).unwrap();
let flagged_lines: Vec<usize> = warnings.iter().map(|w| w.line).collect();
assert!(flagged_lines.contains(&3)); assert!(flagged_lines.contains(&10)); assert!(flagged_lines.contains(&16));
assert!(!flagged_lines.contains(&7)); assert!(!flagged_lines.contains(&8)); assert!(!flagged_lines.contains(&14)); }
#[test]
fn test_md033_regression_original_behavior_preserved() {
let content = r#"# Test Document
Regular HTML tags should be flagged:
<div>flagged</div>
<p>flagged</p>
<span>flagged</span>
But indented code blocks should be ignored:
<div>not flagged</div>
<p>not flagged</p>
<span>not flagged</span>
And fenced code blocks should be ignored:
```html
<div>not flagged</div>
<p>not flagged</p>
```
Back to regular: <em>flagged</em>"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD033NoInlineHtml::default();
let warnings = rule.check(&ctx).unwrap();
let flagged_lines: Vec<usize> = warnings.iter().map(|w| w.line).collect();
assert!(flagged_lines.contains(&4)); assert!(flagged_lines.contains(&5)); assert!(flagged_lines.contains(&6)); assert!(flagged_lines.contains(&20));
assert!(!flagged_lines.contains(&10)); assert!(!flagged_lines.contains(&11)); assert!(!flagged_lines.contains(&12)); assert!(!flagged_lines.contains(&16)); assert!(!flagged_lines.contains(&17));
assert_eq!(warnings.len(), 4); }
#[test]
fn test_html_inside_html_comments_should_not_be_flagged() {
let rule = MD033NoInlineHtml::default();
let content = r#"# Backers
<!--
<table>
<tr>
<td align="center">
<a href="[PROFILE_URL]">
<img src="[PROFILE_IMG_SRC]" width="50" />
</a>
</td>
</tr>
</table>
-->
This should be flagged: <div>real HTML</div>
<!-- Another comment with <span>HTML</span> inside -->
More real HTML: <p>flagged</p>"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 2);
let flagged_lines: Vec<usize> = result.iter().map(|w| w.line).collect();
assert!(flagged_lines.contains(&15)); assert!(flagged_lines.contains(&19));
assert!(!flagged_lines.contains(&4)); assert!(!flagged_lines.contains(&5)); assert!(!flagged_lines.contains(&6)); assert!(!flagged_lines.contains(&7)); assert!(!flagged_lines.contains(&8)); assert!(!flagged_lines.contains(&17)); }