use rumdl_lib::lint_context::LintContext;
use rumdl_lib::rule::Rule;
use rumdl_lib::rules::MD037NoSpaceInEmphasis;
#[test]
fn test_valid_emphasis() {
let rule = MD037NoSpaceInEmphasis;
let content = "*text* and **text** and _text_ and __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_spaces_inside_asterisk_emphasis() {
let rule = MD037NoSpaceInEmphasis;
let content = "Text with * bad * here";
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_spaces_inside_double_asterisk() {
let rule = MD037NoSpaceInEmphasis;
let content = "** text ** and **text ** and ** text**";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 3); }
#[test]
fn test_spaces_inside_underscore_emphasis() {
let rule = MD037NoSpaceInEmphasis;
let content = "_ text _ and _text _ and _ text_";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 3);
}
#[test]
fn test_spaces_inside_double_underscore() {
let rule = MD037NoSpaceInEmphasis;
let content = "__ text __ and __text __ and __ text__";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 3); }
#[test]
fn test_emphasis_in_code_block() {
let rule = MD037NoSpaceInEmphasis;
let content = "```\n* text *\n```\nText with * text * here";
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_multiple_emphasis_on_line() {
let rule = MD037NoSpaceInEmphasis;
let content = "Here is * text * and _ text _ in one line";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 2); }
#[test]
fn test_mixed_emphasis() {
let rule = MD037NoSpaceInEmphasis;
let content = "Here is * text * and ** text ** mixed";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 2); }
#[test]
fn test_emphasis_with_punctuation() {
let rule = MD037NoSpaceInEmphasis;
let content = "Here is * text! * and * text? * end";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 2); }
#[test]
fn test_code_span_handling() {
let rule = MD037NoSpaceInEmphasis;
let content = "Use `*text*` as emphasis and `**text**` as strong emphasis";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
let content = "This is ``code with ` inside`` and `code with *asterisks*`";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
let content = "`*text*` at start and at end `*more text*`";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
let content = "Code `let x = 1;` and *emphasis* and more code `let y = 2;`";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_emphasis_edge_cases() {
let rule = MD037NoSpaceInEmphasis;
let content = "*text*.and **text**!";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
let content = "*text*\n*text*";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
let content = "*emphasis* with `code` and *more emphasis*";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
let content = "**strong _with emph_** and `code *with* asterisks`";
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_preserves_structure_emphasis() {
let rule = MD037NoSpaceInEmphasis;
let content = "* bad emphasis * and ```\n* text *\n```\n* more bad *";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let fixed = rule.fix(&ctx).unwrap();
let fixed_ctx = LintContext::new(&fixed, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&fixed_ctx).unwrap();
assert!(result.is_empty());
let content = "`code` with * bad * and **bad ** emphasis";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let fixed = rule.fix(&ctx).unwrap();
let fixed_ctx = LintContext::new(&fixed, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&fixed_ctx).unwrap();
assert!(result.is_empty());
let content = "* test * and ** strong ** emphasis";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let fixed = rule.fix(&ctx).unwrap();
let fixed_ctx = LintContext::new(&fixed, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&fixed_ctx).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_nested_emphasis() {
let rule = MD037NoSpaceInEmphasis;
let content = "**This is *nested* emphasis**";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
println!("Nested emphasis test - expected 1 issue, found {} issues", result.len());
for warning in &result {
println!(
" Warning at line {}:{} - {}",
warning.line, warning.column, warning.message
);
}
}
#[test]
fn test_emphasis_in_lists() {
let rule = MD037NoSpaceInEmphasis;
let content = "- Item with *emphasis*\n- Item with **strong**";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
println!("\nValid list items - expected 0 issues, found {} issues", result.len());
for warning in &result {
println!(
" Warning at line {}:{} - {}",
warning.line, warning.column, warning.message
);
}
let content = "- Item with * emphasis *\n- Item with ** strong **";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
println!("\nInvalid list items - expected 1 issue, found {} issues", result.len());
for warning in &result {
println!(
" Warning at line {}:{} - {}",
warning.line, warning.column, warning.message
);
}
}
#[test]
fn test_emphasis_with_special_characters() {
let rule = MD037NoSpaceInEmphasis;
let content = "*Special: !@#$%^&*()* and **More: []{}<>\"'**";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
let content = "Here is * Special: !@#$%^&() * and ** More: []{}<>\"' **";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 2); }
#[test]
fn test_emphasis_near_html() {
let rule = MD037NoSpaceInEmphasis;
let content = "<div>*Emphasis*</div> and **Strong** <span>text</span>";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
let content = "<div>* Emphasis *</div> and ** Strong ** <span>text</span>";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(
result.is_empty(),
"Emphasis inside HTML blocks should not be flagged. Got: {result:?}"
);
let content = "Here is * Emphasis * and ** Strong ** text";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(
result.len(),
2,
"Both emphasis spans in regular markdown should be flagged"
);
}
#[test]
fn test_emphasis_with_multiple_spaces() {
let rule = MD037NoSpaceInEmphasis;
let content = "Here is * multiple spaces * and ** more spaces **";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 2); }
#[test]
fn test_non_emphasis_asterisks() {
let rule = MD037NoSpaceInEmphasis;
let content = "* Not emphasis\n* Also not emphasis\n2 * 3 = 6";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(
result.len(),
0,
"List markers and math operations should not be flagged as emphasis issues"
);
let content = "* List item with *emphasis*\n* List item with *incorrect * emphasis";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(
result.len(),
1,
"Should only find the incorrectly formatted emphasis, not list markers"
);
}
#[test]
fn test_emphasis_at_boundaries() {
let rule = MD037NoSpaceInEmphasis;
let content = "Text * emphasis * more text";
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_emphasis_in_blockquotes() {
let rule = MD037NoSpaceInEmphasis;
let content = "> This is a *emphasized* text in a blockquote\n> And **strong** text too";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
let content = "> This is a * emphasized * text in a blockquote\n> And ** strong ** text too";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 2); }
#[test]
fn test_md037_in_text_code_block() {
let rule = MD037NoSpaceInEmphasis;
let content = r#"
```text
README.md:24:5: [MD037] Spaces inside emphasis markers: "* incorrect *" [*]
```
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(
result.is_empty(),
"MD037 should not trigger inside a code block, but got warnings: {result:?}"
);
}
#[test]
fn test_false_positive_punctuation_after_emphasis() {
let rule = MD037NoSpaceInEmphasis;
let test_cases = vec![
"This is *important*! And this is *also important*.",
"What about *this*? Or *that*?",
"Check this *out*, it's great.",
"The *result*; it was amazing.",
"Use *caution*: this is dangerous.",
];
for content in test_cases {
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
println!("Testing: {content}");
println!("Found {} warnings:", result.len());
for warning in &result {
println!(" Line {}:{} - {}", warning.line, warning.column, warning.message);
}
assert_eq!(
result.len(),
0,
"False positive detected in: '{}'. Found {} warnings when expecting 0",
content,
result.len()
);
}
}
#[test]
fn test_false_positive_nested_emphasis() {
let rule = MD037NoSpaceInEmphasis;
let test_cases = vec![
"This is **bold with *italic* inside**.",
"This is *italic with **bold** inside*.",
"Use ***triple emphasis*** for maximum impact.",
"Mix of **bold** and *italic* text.",
];
for content in test_cases {
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
println!("Testing nested emphasis: {content}");
println!("Found {} warnings:", result.len());
for warning in &result {
println!(" Line {}:{} - {}", warning.line, warning.column, warning.message);
}
assert_eq!(
result.len(),
0,
"False positive detected in nested emphasis: '{}'. Found {} warnings when expecting 0",
content,
result.len()
);
}
}
#[test]
fn test_false_positive_multiple_emphasis_same_line() {
let rule = MD037NoSpaceInEmphasis;
let test_cases = vec![
"This has *one* emphasis and *another* emphasis.",
"Mix of *italic* and **bold** and ***both***.",
"First *word* then *second* and *third* emphasis.",
"Use *this* or *that* approach.",
];
for content in test_cases {
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
println!("Testing multiple emphasis: {content}");
println!("Found {} warnings:", result.len());
for warning in &result {
println!(" Line {}:{} - {}", warning.line, warning.column, warning.message);
}
assert_eq!(
result.len(),
0,
"False positive detected in multiple emphasis: '{}'. Found {} warnings when expecting 0",
content,
result.len()
);
}
}
#[test]
fn test_true_positive_spaces_in_emphasis() {
let rule = MD037NoSpaceInEmphasis;
let test_cases = vec![
("This has * text with spaces * in it.", 1),
("This has ** bold with spaces ** text.", 1),
("This has _ underscore with spaces _ text.", 1),
("This has __ double underscore with spaces __ text.", 1),
("This has * start space* and *end space * issues.", 2),
];
for (content, expected_warnings) in test_cases {
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
println!("Testing true positive: {content}");
println!("Found {} warnings (expected {}):", result.len(), expected_warnings);
for warning in &result {
println!(" Line {}:{} - {}", warning.line, warning.column, warning.message);
}
assert_eq!(
result.len(),
expected_warnings,
"Expected {} warnings for: '{}', but found {}",
expected_warnings,
content,
result.len()
);
}
}
#[test]
fn test_emphasis_boundary_detection() {
let rule = MD037NoSpaceInEmphasis;
let test_cases = vec![
("*word*", 0),
("*word*.", 0),
("*word*!", 0),
("*word*?", 0),
("*word*,", 0),
("*word*;", 0),
("*word*:", 0),
("(*word*)", 0),
("[*word*]", 0),
("\"*word*\"", 0),
("Here is * word * there", 1),
("Here is *word * there", 1),
("Here is * word* there", 1),
];
for (content, expected_warnings) in test_cases {
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
println!(
"Testing boundary case: '{}' - expected {}, got {}",
content,
expected_warnings,
result.len()
);
for warning in &result {
println!(" Warning: {}", warning.message);
}
assert_eq!(
result.len(),
expected_warnings,
"Boundary test failed for: '{}'. Expected {} warnings, got {}",
content,
expected_warnings,
result.len()
);
}
}
#[test]
fn test_math_expressions_not_flagged() {
let rule = MD037NoSpaceInEmphasis;
let test_cases = vec![
"The expression a*b + c*d should not be flagged.",
"Calculate x*y where x > 0 and y < 10.",
"Formula: a*b*c = result",
"Multiply by *2 for the answer.",
"The value is 3*4*5 = 60.",
];
for content in test_cases {
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
println!("Testing math expression: {content}");
println!("Found {} warnings:", result.len());
for warning in &result {
println!(" Line {}:{} - {}", warning.line, warning.column, warning.message);
}
assert_eq!(
result.len(),
0,
"Math expression incorrectly flagged: '{}'. Found {} warnings when expecting 0",
content,
result.len()
);
}
}
#[test]
fn test_issue_186_list_item_with_asterisk_in_text() {
let rule = MD037NoSpaceInEmphasis;
let content = "* List item with asterisk * inside";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(
result.is_empty(),
"Issue #186: List item with asterisk in text incorrectly flagged as emphasis. Got: {result:?}"
);
let content = "- List item with asterisk * inside text";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty(), "Dash list with asterisk in text incorrectly flagged");
let content = "+ List item with asterisk * inside text";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty(), "Plus list with asterisk in text incorrectly flagged");
let content = "* List item with * bad emphasis * inside";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(
result.len(),
1,
"Should flag actual emphasis spacing issue in list item content"
);
}
#[test]
fn test_md037_empty_math_span_no_false_positives() {
let rule = MD037NoSpaceInEmphasis;
let content = "Empty $$ here and * bad emphasis *.";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1, "Should flag '* bad emphasis *' after empty $$");
assert!(
result[0].message.contains("bad emphasis"),
"Should flag the correct span, got: {}",
result[0].message
);
}
#[test]
fn test_md037_empty_math_cross_line_bug() {
let rule = MD037NoSpaceInEmphasis;
let content = "Empty $$ here and * bad emphasis *.\n\nMid-line $p * q$ and then * bad emphasis *.";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(
result.len(),
2,
"Should flag both '* bad emphasis *' spans, not cross-line artifacts"
);
for warning in &result {
assert!(
warning.message.contains("bad emphasis"),
"Should only flag bad emphasis, got: {}",
warning.message
);
}
}
#[test]
fn test_md037_minimal_math_content() {
let rule = MD037NoSpaceInEmphasis;
let content = "Math $a$ and $b$ should protect asterisks like $a * b$.\nBut * spaces * should flag.";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(
result.len(),
1,
"Only '* spaces *' should flag, not content in math spans"
);
assert_eq!(result[0].line, 2);
}
#[test]
fn test_md037_multiple_math_same_line_with_empty() {
let rule = MD037NoSpaceInEmphasis;
let content = "Math $a * b$ and $c * d$ and $$ empty and * bad *.";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(
result.len(),
1,
"Only real emphasis should flag, math protects asterisks"
);
assert!(result[0].message.contains("bad"));
}
#[test]
fn test_md037_display_math_protection() {
let rule = MD037NoSpaceInEmphasis;
let content = r#"Inline $x * y$ math.
Display $$a * b$$ math.
And * bad emphasis *."#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(
result.len(),
1,
"Only bad emphasis should flag, both inline and display math protect content"
);
assert_eq!(result[0].line, 3);
}
#[test]
fn test_md037_whitespace_only_math() {
let rule = MD037NoSpaceInEmphasis;
let content = "Math $ $ and $ $ should not cause issues with * bad *.";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1, "Only real emphasis should flag");
}
#[test]
fn test_md037_math_at_line_boundaries() {
let rule = MD037NoSpaceInEmphasis;
let content = r#"$start of line * with math$ and text.
Text and $end of line$
$entire line is math$
Regular * bad emphasis * text."#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1, "Only line 4 bad emphasis should flag");
assert_eq!(result[0].line, 4);
}
#[test]
fn test_md037_currency_not_math() {
let rule = MD037NoSpaceInEmphasis;
let content = "The cost is $5 * 10 = $50 total, and * this should flag *.";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(
result.iter().any(|w| w.message.contains("should flag")),
"Should flag real emphasis even with currency notation"
);
}
#[test]
fn test_md037_unclosed_math_graceful() {
let rule = MD037NoSpaceInEmphasis;
let content = "This has $unclosed math and * bad emphasis * here.";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(
result.len(),
1,
"Unclosed math should not prevent flagging real emphasis"
);
}