use rumdl_lib::rule::{LintWarning, Rule};
use rumdl_lib::rules::MD043RequiredHeadings;
fn assert_warning_message(result: &[LintWarning], expected: &str) {
assert!(!result.is_empty(), "Expected at least one warning");
assert!(
result.iter().all(|warning| warning.message == expected),
"Expected all warnings to have message {expected:?}, got: {result:?}"
);
}
#[test]
fn test_matching_headings() {
let required = vec![
"# Introduction".to_string(),
"# Methods".to_string(),
"# Results".to_string(),
];
let rule = MD043RequiredHeadings::new(required);
let content = "# Introduction\n\n# Methods\n\n# Results";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_missing_heading() {
let required = vec![
"# Introduction".to_string(),
"# Methods".to_string(),
"# Results".to_string(),
];
let rule = MD043RequiredHeadings::new(required);
let content = "# Introduction\n\n# Results";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(!result.is_empty());
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, content);
}
#[test]
fn test_structure_mismatch_message_reports_first_different_heading() {
let required = vec![
"# Introduction".to_string(),
"# Methods".to_string(),
"# Results".to_string(),
];
let rule = MD043RequiredHeadings::new(required);
let content = "# Introduction\n\n# Results";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_warning_message(
&result,
"Heading structure does not match required structure. Expected heading '# Methods' at position 2, but found '# Results'",
);
}
#[test]
fn test_structure_mismatch_message_reports_missing_trailing_heading() {
let required = vec!["# Introduction".to_string(), "# Methods".to_string()];
let rule = MD043RequiredHeadings::new(required);
let content = "# Introduction";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_warning_message(
&result,
"Heading structure does not match required structure. Expected heading '# Methods' at position 2, but found no more headings",
);
}
#[test]
fn test_structure_mismatch_message_reports_extra_trailing_heading() {
let required = vec!["# Introduction".to_string()];
let rule = MD043RequiredHeadings::new(required);
let content = "# Introduction\n\n## Extra";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_warning_message(
&result,
"Heading structure does not match required structure. Expected no more headings after position 1, but found '## Extra' at position 2",
);
}
#[test]
fn test_structure_mismatch_message_reports_extra_trailing_heading_after_wildcard() {
let required = vec!["# Introduction".to_string(), "*".to_string(), "# Results".to_string()];
let rule = MD043RequiredHeadings::new(required);
let content = "# Introduction\n\n## Optional 1\n\n## Optional 2\n\n# Results\n\n## Extra";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_warning_message(
&result,
"Heading structure does not match required structure. Expected no more headings after position 4, but found '## Extra' at position 5",
);
}
#[test]
fn test_structure_mismatch_message_handles_plus_before_required_heading() {
let required = vec!["# Introduction".to_string(), "+".to_string(), "# Results".to_string()];
let rule = MD043RequiredHeadings::new(required);
let content = "# Introduction\n\n# Results";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_warning_message(
&result,
"Heading structure does not match required structure. Expected one or more headings before '# Results', but found '# Results' immediately",
);
}
#[test]
fn test_structure_mismatch_message_preserves_actual_case_after_wildcard() {
let required = vec!["+".to_string(), "# Results".to_string()];
let rule = MD043RequiredHeadings::new(required);
let content = "# RESULTS";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_warning_message(
&result,
"Heading structure does not match required structure. Expected one or more headings before '# Results', but found '# RESULTS' immediately",
);
}
#[test]
fn test_structure_mismatch_message_handles_question_before_required_heading() {
let required = vec!["?".to_string(), "## Description".to_string()];
let rule = MD043RequiredHeadings::new(required);
let content = "## Description";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_warning_message(
&result,
"Heading structure does not match required structure. Expected one heading before '## Description', but found '## Description' immediately",
);
}
#[test]
fn test_structure_mismatch_message_handles_asterisk_missing_required_heading() {
let required = vec!["# Introduction".to_string(), "*".to_string(), "# Results".to_string()];
let rule = MD043RequiredHeadings::new(required);
let content = "# Introduction\n\n## Optional 1\n\n## Optional 2";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_warning_message(
&result,
"Heading structure does not match required structure. Expected heading '# Results' at position 4, but found no more headings",
);
}
#[test]
fn test_extra_heading() {
let required = vec!["# Introduction".to_string(), "# Results".to_string()];
let rule = MD043RequiredHeadings::new(required);
let content = "# Introduction\n\n# Methods\n\n# Results";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(!result.is_empty());
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, content);
}
#[test]
fn test_wrong_order() {
let required = vec![
"# Introduction".to_string(),
"# Methods".to_string(),
"# Results".to_string(),
];
let rule = MD043RequiredHeadings::new(required);
let content = "# Introduction\n\n# Results\n\n# Methods";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(!result.is_empty());
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, content);
}
#[test]
fn test_empty_required_headings() {
let required = vec![];
let rule = MD043RequiredHeadings::new(required);
let content = "# Any heading\n\n# Another heading";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, content);
}
#[test]
fn test_case_sensitive() {
let required = vec!["# Introduction".to_string()];
let rule = MD043RequiredHeadings::new(required);
let content = "# INTRODUCTION";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty()); }
#[test]
fn test_mixed_heading_styles() {
let required = vec!["# Introduction".to_string(), "======= Methods".to_string()];
let rule = MD043RequiredHeadings::new(required);
let content = "# Introduction\nContent\nMethods\n=======";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_asterisk_wildcard_integration() {
let required = vec!["# README".to_string(), "*".to_string(), "## License".to_string()];
let rule = MD043RequiredHeadings::new(required);
let content = "# README\n\n## Installation\n\n## Usage\n\n### Examples\n\n## License";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
let content = "# README\n\n## License";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_plus_wildcard_integration() {
let required = vec![
"# Documentation".to_string(),
"+".to_string(),
"## Contributing".to_string(),
];
let rule = MD043RequiredHeadings::new(required);
let content = "# Documentation\n\n## API\n\n## Contributing";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
let content = "# Documentation\n\n## Contributing";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(!result.is_empty());
}
#[test]
fn test_question_wildcard_integration() {
let required = vec![
"?".to_string(),
"## Description".to_string(),
"## Installation".to_string(),
];
let rule = MD043RequiredHeadings::new(required);
let content = "# My Awesome Project\n\n## Description\n\n## Installation";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
let content = "# Another Project\n\n## Description\n\n## Installation";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
let content = "## Description\n\n## Installation";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(!result.is_empty());
}
#[test]
fn test_real_world_documentation_pattern() {
let required = vec![
"?".to_string(), "## Overview".to_string(), "*".to_string(), "## Installation".to_string(), "*".to_string(), "## License".to_string(), ];
let rule = MD043RequiredHeadings::new(required);
let content = "# MyLib\n\n## Overview\n\n## Installation\n\n## License";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
let content = "# MyLib\n\n## Overview\n\n## Features\n\n## Screenshots\n\n## Installation\n\n## Usage\n\n## API Reference\n\n## Examples\n\n## License";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
let content = "# MyLib\n\n## Overview\n\n## Features\n\n## License";
let ctx = rumdl_lib::lint_context::LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(!result.is_empty());
}