use rumdl_lib::lint_context::LintContext;
use rumdl_lib::rule::Rule;
use rumdl_lib::rules::{MD004UnorderedListStyle, md004_unordered_list_style::UnorderedListStyle};
#[test]
fn test_check_consistent_valid() {
let content = "* Item 1\n* Item 2\n * Nested item";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let warnings = rule.check(&ctx).unwrap();
assert!(warnings.is_empty());
}
#[test]
fn test_check_consistent_invalid() {
let content = "* Item 1\n- Item 2\n + Nested item";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 2);
}
#[test]
fn test_check_specific_style_valid() {
let content = "- Item 1\n- Item 2";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Dash);
let warnings = rule.check(&ctx).unwrap();
assert!(warnings.is_empty());
}
#[test]
fn test_check_specific_style_invalid() {
let content = "* Item 1\n- Item 2";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Dash);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 1);
}
#[test]
fn test_fix_consistent_tie_prefers_dash() {
let content = "* Item 1\n- Item 2\n+ Item 3";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "- Item 1\n- Item 2\n- Item 3");
}
#[test]
fn test_fix_specific_style() {
let content = "* Item 1\n- Item 2\n+ Item 3";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "* Item 1\n* Item 2\n* Item 3");
}
#[test]
fn test_fix_with_indentation() {
let content = " * Item 1\n - Item 2";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, " * Item 1\n * Item 2");
}
#[test]
fn test_check_skip_code_blocks() {
let content = "```\n* Item 1\n- Item 2\n```\n* Item 3";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let warnings = rule.check(&ctx).unwrap();
assert!(warnings.is_empty());
}
#[test]
fn test_check_skip_front_matter() {
let content = "---\ntitle: Test\n---\n* Item 1\n- Item 2";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 1);
}
#[test]
fn test_fix_skip_code_blocks() {
let content = "```\n* Item 1\n- Item 2\n```\n* Item 3\n- Item 4";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "```\n* Item 1\n- Item 2\n```\n* Item 3\n* Item 4");
}
#[test]
fn test_fix_skip_front_matter() {
let content = "---\ntitle: Test\n---\n* Item 1\n- Item 2";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "---\ntitle: Test\n---\n* Item 1\n* Item 2");
}
#[test]
fn test_check_mixed_indentation() {
let content = "* Item 1\n - Sub Item 1\n* Item 2";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 1);
}
#[test]
fn test_check_consistent_tie_all_markers() {
let content = "+ Item 1\n* Item 2\n- Item 3";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 2); }
#[test]
fn test_check_consistent_dash_most_prevalent() {
let content = "- Item 1\n* Item 2\n- Item 3\n+ Item 4";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 2); }
#[test]
fn test_fix_consistent_tie_all_markers() {
let content = "+ Item 1\n* Item 2\n- Item 3";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "- Item 1\n- Item 2\n- Item 3");
}
#[test]
fn test_fix_consistent_dash_most_prevalent() {
let content = "- Item 1\n* Item 2\n- Item 3\n+ Item 4";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "- Item 1\n- Item 2\n- Item 3\n- Item 4");
}
#[test]
fn test_empty_content() {
let content = "";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let warnings = rule.check(&ctx).unwrap();
assert!(warnings.is_empty());
}
#[test]
fn test_no_list_items() {
let content = "# Heading\nSome text";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let warnings = rule.check(&ctx).unwrap();
assert!(warnings.is_empty());
}
#[test]
fn test_md004_asterisk_style() {
let ctx = LintContext::new(
"- Item 1\n+ Item 2\n - Nested 1\n + Nested 2\n* Item 3",
rumdl_lib::config::MarkdownFlavor::Standard,
None,
);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 4); let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "* Item 1\n* Item 2\n * Nested 1\n * Nested 2\n* Item 3");
}
#[test]
fn test_md004_plus_style() {
let ctx = LintContext::new(
"- Item 1\n* Item 2\n - Nested 1\n * Nested 2\n+ Item 3",
rumdl_lib::config::MarkdownFlavor::Standard,
None,
);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Plus);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 4); let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "+ Item 1\n+ Item 2\n + Nested 1\n + Nested 2\n+ Item 3");
}
#[test]
fn test_md004_dash_style() {
let ctx = LintContext::new(
"* Item 1\n+ Item 2\n * Nested 1\n + Nested 2\n- Item 3",
rumdl_lib::config::MarkdownFlavor::Standard,
None,
);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Dash);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 4); let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "- Item 1\n- Item 2\n - Nested 1\n - Nested 2\n- Item 3");
}
#[test]
fn test_md004_deeply_nested() {
let ctx = LintContext::new(
"* Level 1\n + Level 2\n - Level 3\n + Level 4\n * Back to 2\n* Level 1\n",
rumdl_lib::config::MarkdownFlavor::Standard,
None,
);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 3); let fixed = rule.fix(&ctx).unwrap();
assert_eq!(
fixed,
"* Level 1\n * Level 2\n * Level 3\n * Level 4\n * Back to 2\n* Level 1\n"
);
}
#[test]
fn test_md004_mixed_content() {
let ctx = LintContext::new(
"# Heading\n\n* Item 1\n Some text\n + Nested with text\n More text\n* Item 2\n",
rumdl_lib::config::MarkdownFlavor::Standard,
None,
);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(
fixed,
"# Heading\n\n* Item 1\n Some text\n * Nested with text\n More text\n* Item 2\n"
);
}
#[test]
fn test_md004_empty_content() {
let ctx = LintContext::new("", rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "");
}
#[test]
fn test_md004_no_lists() {
let ctx = LintContext::new(
"# Heading\n\nSome text\nMore text",
rumdl_lib::config::MarkdownFlavor::Standard,
None,
);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "# Heading\n\nSome text\nMore text");
}
#[test]
fn test_md004_code_blocks() {
let ctx = LintContext::new(
"* Item 1\n```\n* Not a list\n+ Also not a list\n```\n* Item 2\n",
rumdl_lib::config::MarkdownFlavor::Standard,
None,
);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "* Item 1\n```\n* Not a list\n+ Also not a list\n```\n* Item 2\n");
}
#[test]
fn test_md004_blockquotes() {
let ctx = LintContext::new(
"* Item 1\n> * Quoted item\n> + Another quoted item\n* Item 2\n",
rumdl_lib::config::MarkdownFlavor::Standard,
None,
);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1); let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "* Item 1\n> * Quoted item\n> * Another quoted item\n* Item 2\n");
}
#[test]
fn test_md004_list_continuations() {
let ctx = LintContext::new(
"* Item 1\n Continuation 1\n + Nested item\n Continuation 2\n* Item 2\n",
rumdl_lib::config::MarkdownFlavor::Standard,
None,
);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(
fixed,
"* Item 1\n Continuation 1\n * Nested item\n Continuation 2\n* Item 2\n"
);
}
#[test]
fn test_md004_mixed_ordered_unordered() {
let ctx = LintContext::new(
"1. Ordered item\n * Unordered sub-item\n + Another sub-item\n2. Ordered item\n",
rumdl_lib::config::MarkdownFlavor::Standard,
None,
);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(
fixed,
"1. Ordered item\n * Unordered sub-item\n * Another sub-item\n2. Ordered item\n"
);
}
#[test]
fn test_complex_list_patterns() {
let content = "* Level 1 item 1\n * Level 2 item 1\n * Level 3 item 1\n * Level 2 item 2\n* Level 1 item 2";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(
fixed,
"* Level 1 item 1\n * Level 2 item 1\n * Level 3 item 1\n * Level 2 item 2\n* Level 1 item 2"
);
}
#[test]
fn test_lists_in_code_blocks() {
let content =
"* Valid list item\n\n```\n* This is in a code block\n- Also in code block\n```\n\n* Another valid item";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 0);
let content = "* Valid list item\n\n```markdown\n* This is in a code block\n- Also in code block\n```\n\n* Another valid item";
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
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_nested_list_complexity() {
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let content = "* Item 1\n - Item 2\n + Item 3\n - Item 5\n* Item 6\n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 3); let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "- Item 1\n - Item 2\n - Item 3\n - Item 5\n- Item 6\n");
}
#[test]
fn test_indentation_handling() {
let content = "* Level 1\n * Indented with 4 spaces\n * Indented with 2 spaces";
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 0);
let content = "* Actual list item\nText with * asterisk that's not a list\n * Indented list item";
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
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_list_markers() {
let content = "* First item\n* Second item\n* Third item";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "* First item\n* Second item\n* Third item");
}
#[test]
fn test_performance_md004() {
let mut content = String::with_capacity(20_000);
for i in 0..50 {
let marker = match i % 3 {
0 => "*",
1 => "-",
_ => "+",
};
content.push_str(&format!("{marker} Top level item {i}\n"));
for j in 0..3 {
let marker = match (i + j) % 3 {
0 => "*",
1 => "-",
_ => "+",
};
content.push_str(&format!(" {marker} Second level item {i}.{j}\n"));
for k in 0..2 {
let marker = match (i + j + k) % 3 {
0 => "*",
1 => "-",
_ => "+",
};
content.push_str(&format!(" {marker} Third level item {i}.{j}.{k}\n"));
}
}
content.push('\n'); }
let start = std::time::Instant::now();
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let ctx = LintContext::new(&content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let _result = rule.check(&ctx).unwrap();
let _check_duration = start.elapsed();
let start = std::time::Instant::now();
let fixed_ctx = LintContext::new(&content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let _ = rule.fix(&fixed_ctx).unwrap();
let _fix_duration = start.elapsed();
}
#[test]
fn test_configuration_asterisk_style() {
let content = "- Item 1\n+ Item 2\n* Item 3";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 2); assert_eq!(warnings[0].message, "List marker '-' does not match expected style '*'");
assert_eq!(warnings[1].message, "List marker '+' does not match expected style '*'");
}
#[test]
fn test_configuration_dash_style() {
let content = "* Item 1\n+ Item 2\n- Item 3";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Dash);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 2); assert_eq!(warnings[0].message, "List marker '*' does not match expected style '-'");
assert_eq!(warnings[1].message, "List marker '+' does not match expected style '-'");
}
#[test]
fn test_configuration_plus_style() {
let content = "* Item 1\n- Item 2\n+ Item 3";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Plus);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 2); assert_eq!(warnings[0].message, "List marker '*' does not match expected style '+'");
assert_eq!(warnings[1].message, "List marker '-' does not match expected style '+'");
}
#[test]
fn test_configuration_consistent_style() {
let content = "* Item 1\n- Item 2\n* Item 3";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 1); assert_eq!(warnings[0].message, "List marker '-' does not match expected style '*'");
}
#[test]
fn test_sublist_style_matching() {
let content = "* Parent 1\n - Child 1\n + Child 2\n* Parent 2\n * Child 3";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 2);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "* Parent 1\n * Child 1\n * Child 2\n* Parent 2\n * Child 3");
}
#[test]
fn test_deeply_nested_sublist_style_matching() {
let content = "* Level 1\n * Level 2\n - Level 3\n + Level 4\n * Level 5";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 2);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(
fixed,
"* Level 1\n * Level 2\n * Level 3\n * Level 4\n * Level 5"
);
}
#[test]
fn test_lists_after_paragraphs() {
let content = "This is a paragraph.\n\n* Item 1\n- Item 2\n\nAnother paragraph.\n\n+ Item 3\n* Item 4";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 2); }
#[test]
fn test_lists_after_headings() {
let content = "# Heading 1\n\n- Item 1\n- Item 2\n\n## Heading 2\n\n* Item 3\n+ Item 4";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 2); }
#[test]
fn test_fix_preserves_list_content() {
let content = "* Item with **bold** text\n- Item with `code` text\n+ Item with [link](url)";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Dash);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(
fixed,
"- Item with **bold** text\n- Item with `code` text\n- Item with [link](url)"
);
}
#[test]
fn test_multiple_lists_in_blockquotes() {
let content = "> * Quoted item 1\n> - Quoted item 2\n>\n> + New list item 1\n> * New list item 2";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 2);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(
fixed,
"> * Quoted item 1\n> * Quoted item 2\n>\n> * New list item 1\n> * New list item 2"
);
}
#[test]
fn test_nested_blockquotes_with_lists() {
let content = "> * Level 1 quote\n> > - Level 2 quote\n> > + Another level 2";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 2, "Expected 2 warnings, got: {warnings:?}");
assert!(
warnings[0].message.contains("'-'"),
"Expected dash marker in message, got: {}",
warnings[0].message
);
assert!(
warnings[1].message.contains("'+'"),
"Expected plus marker in message, got: {}",
warnings[1].message
);
}
#[test]
fn test_fix_method_comprehensive() {
let content = "# Header\n\n* Item 1\n - Subitem 1.1\n + Subitem 1.2\n\n> - Quoted item\n> * Another quoted\n\n```\n* Code block item (should not change)\n- Another code item\n```\n\n* Item 2\n * Subitem 2.1";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let fixed = rule.fix(&ctx).unwrap();
let expected = "# Header\n\n* Item 1\n * Subitem 1.1\n * Subitem 1.2\n\n> * Quoted item\n> * Another quoted\n\n```\n* Code block item (should not change)\n- Another code item\n```\n\n* Item 2\n * Subitem 2.1";
assert_eq!(fixed, expected);
}
#[test]
fn test_check_method_comprehensive() {
let content = "* Valid item\n- Invalid item\n + Invalid nested\n * Valid nested\n\n> - Invalid quoted\n\n```\n- Ignored in code\n```";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 3);
assert_eq!(warnings[0].line, 2);
assert_eq!(warnings[0].message, "List marker '-' does not match expected style '*'");
assert_eq!(warnings[1].line, 3);
assert_eq!(warnings[1].message, "List marker '+' does not match expected style '*'");
assert_eq!(warnings[2].line, 6);
assert_eq!(warnings[2].message, "List marker '-' does not match expected style '*'");
}
mod roundtrip_safety {
use super::*;
fn assert_roundtrip(rule: &MD004UnorderedListStyle, content: &str) {
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let fixed = rule.fix(&ctx).unwrap();
let ctx2 = LintContext::new(&fixed, rumdl_lib::config::MarkdownFlavor::Standard, None);
let warnings = rule.check(&ctx2).unwrap();
assert!(
warnings.is_empty(),
"Roundtrip failed: after fix, re-check found {} warnings on content:\n---\n{}\n---\nwarnings: {:?}",
warnings.len(),
fixed,
warnings,
);
}
#[test]
fn roundtrip_consistent_mixed() {
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
assert_roundtrip(&rule, "* Item 1\n- Item 2\n+ Item 3");
}
#[test]
fn roundtrip_consistent_nested() {
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
assert_roundtrip(
&rule,
"* Item 1\n - Nested 1\n + Double nested\n - Nested 2\n* Item 2",
);
}
#[test]
fn roundtrip_asterisk() {
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
assert_roundtrip(&rule, "- Item 1\n+ Item 2\n* Item 3");
}
#[test]
fn roundtrip_dash() {
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Dash);
assert_roundtrip(&rule, "* Item 1\n+ Item 2\n- Item 3");
}
#[test]
fn roundtrip_plus() {
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Plus);
assert_roundtrip(&rule, "* Item 1\n- Item 2\n+ Item 3");
}
#[test]
fn roundtrip_sublist() {
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Sublist);
assert_roundtrip(&rule, "- Item 1\n - Item 2\n - Item 3\n - Item 4");
}
#[test]
fn roundtrip_with_trailing_newline() {
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
assert_roundtrip(&rule, "- Item 1\n+ Item 2\n* Item 3\n");
}
#[test]
fn roundtrip_code_blocks() {
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
assert_roundtrip(&rule, "- Item 1\n\n```\n* code\n- code\n```\n\n+ Item 2\n");
}
#[test]
fn roundtrip_blockquotes() {
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
assert_roundtrip(&rule, "- Item 1\n> + Quoted\n> - Another\n* Item 2\n");
}
#[test]
fn roundtrip_front_matter() {
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Dash);
assert_roundtrip(&rule, "---\ntitle: Test\n---\n* Item 1\n+ Item 2\n");
}
#[test]
fn roundtrip_deeply_nested() {
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Consistent);
assert_roundtrip(
&rule,
"* Level 1\n + Level 2\n - Level 3\n + Level 4\n * Back to 2\n* Level 1\n",
);
}
#[test]
fn roundtrip_mixed_ordered_unordered() {
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
assert_roundtrip(&rule, "1. Ordered\n - Unordered\n + Another\n2. Ordered\n");
}
}
mod parity_with_markdownlint {
use super::*;
#[test]
fn parity_mixed_markers_no_trailing_newline() {
let input = "* Item 1\n- Item 2\n+ Item 3";
let expected = "* Item 1\n* Item 2\n* Item 3";
let ctx = LintContext::new(input, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, expected);
}
#[test]
fn parity_mixed_markers_with_trailing_newline() {
let input = "* Item 1\n- Item 2\n+ Item 3\n";
let expected = "* Item 1\n* Item 2\n* Item 3\n";
let ctx = LintContext::new(input, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, expected);
}
#[test]
fn parity_nested_lists() {
let input = "* Level 1\n - Level 2\n + Level 3\n - Level 2b\n* Level 1b";
let expected = "* Level 1\n * Level 2\n * Level 3\n * Level 2b\n* Level 1b";
let ctx = LintContext::new(input, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, expected);
}
#[test]
fn parity_code_blocks_and_front_matter() {
let input = "---\ntitle: Test\n---\n* Item 1\n- Item 2\n```\n* Not a list\n- Not a list\n```\n* Item 3\n";
let expected = "---\ntitle: Test\n---\n* Item 1\n* Item 2\n```\n* Not a list\n- Not a list\n```\n* Item 3\n";
let ctx = LintContext::new(input, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, expected);
}
#[test]
fn parity_empty_input() {
let input = "";
let expected = "";
let ctx = LintContext::new(input, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, expected);
}
#[test]
fn parity_no_lists() {
let input = "# Heading\nSome text";
let expected = "# Heading\nSome text";
let ctx = LintContext::new(input, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, expected);
}
#[test]
fn parity_single_style_list() {
let input = "- Item 1\n- Item 2\n- Item 3";
let expected = "- Item 1\n- Item 2\n- Item 3";
let ctx = LintContext::new(input, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Dash);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, expected);
}
#[test]
fn parity_list_with_blank_lines() {
let input = "* Item 1\n\n- Item 2\n\n+ Item 3";
let expected = "* Item 1\n\n* Item 2\n\n* Item 3";
let ctx = LintContext::new(input, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, expected);
}
#[test]
fn parity_blockquote_with_lists() {
let input = "* Item 1\n> * Quoted item\n> + Another quoted item\n* Item 2";
let expected = "* Item 1\n> * Quoted item\n> * Another quoted item\n* Item 2";
let ctx = LintContext::new(input, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, expected);
}
#[test]
fn parity_single_item_list() {
let input = "* Only item";
let expected = "* Only item";
let ctx = LintContext::new(input, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, expected);
}
#[test]
fn parity_list_with_tabs_for_indentation() {
let input = "* Item 1\n\t- Nested with tab\n\t\t+ Double tab nested";
let expected = "* Item 1\n\t* Nested with tab\n\t\t* Double tab nested";
let ctx = LintContext::new(input, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, expected);
}
#[test]
fn parity_list_with_extra_whitespace_after_marker() {
let input = "* Item 1\n- Item 2\n+ Item 3";
let expected = "* Item 1\n* Item 2\n* Item 3";
let ctx = LintContext::new(input, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, expected);
}
}
#[test]
fn test_math_block_minus_not_flagged() {
let content = "# Example math\n\n$$\n- \\operatorname{Re} \\frac{L'(s, \\chi)}{L(s, \\chi)}\n$$\n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Dash);
let warnings = rule.check(&ctx).unwrap();
assert!(
warnings.is_empty(),
"MD004 should not flag lines starting with - inside math blocks: {warnings:?}"
);
}
#[test]
fn test_math_block_plus_not_flagged() {
let content = "# Example math\n\n$$\n+ \\frac{1}{2} \\log\\frac{q}{\\pi}\n$$\n";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Dash);
let warnings = rule.check(&ctx).unwrap();
assert!(
warnings.is_empty(),
"MD004 should not flag lines starting with + inside math blocks: {warnings:?}"
);
}
#[test]
fn test_math_block_multiline_not_flagged() {
let content = "\
# Example math
$$
- \\operatorname{Re} \\frac{L'(s, \\chi)}{L(s, \\chi)}
+ \\frac{1}{2} \\log\\frac{q}{\\pi}
$$
";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Dash);
let warnings = rule.check(&ctx).unwrap();
assert!(
warnings.is_empty(),
"MD004 should not flag multi-line math expressions: {warnings:?}"
);
}
#[test]
fn test_real_list_adjacent_to_math_block_still_checked() {
let content = "\
* Item 1
* Item 2
$$
- \\operatorname{Re}
$$
- Item 3
";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Dash);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(
warnings.len(),
2,
"MD004 should still flag real list items outside math blocks: {warnings:?}"
);
}
#[test]
fn test_math_block_inside_blockquote_not_flagged() {
let rule = MD004UnorderedListStyle::new(UnorderedListStyle::Asterisk);
let content = "\
> $$
> - \\operatorname{Re} \\frac{L'(s, \\chi)}{L(s, \\chi)}
> + \\frac{1}{2}
> $$
";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let warnings = rule.check(&ctx).unwrap();
assert!(
warnings.is_empty(),
"MD004 should not flag math operators inside blockquote math blocks: {warnings:?}"
);
}