use rumdl_lib::lint_context::LintContext;
use rumdl_lib::rule::Rule;
use rumdl_lib::rules::MD023HeadingStartLeft;
#[test]
fn test_complex_mixed_headings() {
let rule = MD023HeadingStartLeft;
let content = r#"# Valid heading
## Indented ATX heading
### Valid heading
#### Another indented heading
Setext heading
-------------
Another setext heading
---------------------
# Indented closed ATX heading #
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx);
assert!(result.is_ok());
let warnings = result.unwrap();
assert_eq!(warnings.len(), 5);
assert_eq!(warnings[0].line, 3); assert_eq!(warnings[1].line, 7); assert_eq!(warnings[2].line, 12); assert_eq!(warnings[3].line, 13); assert_eq!(warnings[4].line, 15);
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let fixed = rule.fix(&ctx).unwrap();
assert!(!fixed.contains(" ## Indented"));
assert!(!fixed.contains(" ####"));
assert!(!fixed.contains(" Another setext"));
assert!(!fixed.contains(" # Indented closed"));
assert!(fixed.contains("# Valid heading"));
assert!(fixed.contains("### Valid heading"));
}
#[test]
fn test_front_matter_with_headings() {
let rule = MD023HeadingStartLeft;
let content = r#"---
title: Test Document
author: Test Author
---
# Valid heading
## Indented heading in content
Content after front matter
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx);
assert!(result.is_ok());
let warnings = result.unwrap();
assert_eq!(warnings.len(), 1);
assert_eq!(warnings[0].line, 8);
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let fixed = rule.fix(&ctx).unwrap();
assert!(fixed.contains("---\ntitle:"));
assert!(fixed.contains("---\n\n# Valid"));
assert!(fixed.contains("## Indented heading"));
assert!(!fixed.contains(" ## Indented"));
}
#[test]
fn test_code_blocks_with_headings() {
let rule = MD023HeadingStartLeft;
let content = r#"# Valid heading
```markdown
# This is a heading in a code block
## This is an indented heading in a code block
```
## This is an indented heading outside code block
```
# Another code block heading
```
### Another indented heading
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx);
assert!(result.is_ok());
let warnings = result.unwrap();
assert_eq!(warnings.len(), 2);
assert_eq!(warnings[0].line, 8); assert_eq!(warnings[1].line, 14);
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let fixed = rule.fix(&ctx).unwrap();
assert!(fixed.contains("```markdown\n# This is a heading"));
assert!(fixed.contains(" ## This is an indented heading in a code block"));
assert!(fixed.contains("```\n\n## This is an indented")); assert!(fixed.contains("```\n # Another code block heading\n```"));
assert!(fixed.contains("### Another indented heading")); }
#[test]
fn test_nested_headings_with_mixed_styles() {
let rule = MD023HeadingStartLeft;
let content = r#"# Main heading
## Subheading
### Indented ATX Subheading
Indented Setext SubSubheading
----------------------------
#### Regular SubSubSubheading
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx);
assert!(result.is_ok());
let warnings = result.unwrap();
assert_eq!(warnings.len(), 3);
assert_eq!(warnings[0].line, 5); assert_eq!(warnings[1].line, 7);
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let fixed = rule.fix(&ctx).unwrap();
assert!(fixed.contains("### Indented ATX Subheading")); assert!(fixed.contains("Indented Setext SubSubheading")); assert!(fixed.contains("----------------------------")); assert!(!fixed.contains(" ### Indented"));
assert!(!fixed.contains(" Indented Setext"));
}
#[test]
fn test_heading_with_special_characters() {
let rule = MD023HeadingStartLeft;
let content = r#"# Heading with *emphasis*
## Indented heading with **bold** and `code`
### Indented heading with [link](https://example.com)
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx);
assert!(result.is_ok());
let warnings = result.unwrap();
assert_eq!(warnings.len(), 2);
assert_eq!(warnings[0].line, 3); assert_eq!(warnings[1].line, 5);
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let fixed = rule.fix(&ctx).unwrap();
assert!(fixed.contains("## Indented heading with **bold** and `code`"));
assert!(fixed.contains("### Indented heading with [link](https://example.com)"));
assert!(!fixed.contains(" ## Indented"));
assert!(!fixed.contains(" ### Indented"));
}
#[test]
fn test_empty_indented_headings() {
let rule = MD023HeadingStartLeft;
let content = r#"# Valid heading
##
###
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx);
assert!(result.is_ok());
let warnings = result.unwrap();
assert_eq!(warnings.len(), 2);
assert_eq!(warnings[0].line, 3); assert_eq!(warnings[1].line, 5);
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let fixed = rule.fix(&ctx).unwrap();
assert!(fixed.contains("##"));
assert!(fixed.contains("###"));
assert!(!fixed.contains(" ##"));
assert!(!fixed.contains(" ###"));
}
#[test]
fn test_multiple_indentation_levels() {
let rule = MD023HeadingStartLeft;
let content = r#"# Valid heading
## Heading with 1 space
## Heading with 2 spaces
## Heading with 3 spaces
## Heading with 4 spaces
"#;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx);
assert!(result.is_ok());
let warnings = result.unwrap();
assert_eq!(warnings.len(), 3);
assert_eq!(warnings[0].line, 3); assert_eq!(warnings[1].line, 5); assert_eq!(warnings[2].line, 7);
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed.matches("## Heading with").count(), 4);
assert!(fixed.contains(" ## Heading with 4 spaces"));
assert!(!fixed.contains(" ## Heading with 1"));
assert!(!fixed.contains(" ## Heading with 2"));
assert!(!fixed.contains(" ## Heading with 3"));
}
#[test]
fn test_md023_heading_after_list() {
let content = r#"# Configuration Precedence
Settings are applied in the following order (later sources override earlier ones):
1. **Built-in defaults**
2. **Configuration file** (`.rumdl.toml` or `pyproject.toml`)
3. **Command-line arguments**
### Example: Precedence in Action
Given this configuration file:"#;
let rule = MD023HeadingStartLeft;
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let warnings = rule.check(&ctx).unwrap();
assert_eq!(warnings.len(), 1, "Should detect one indented heading");
assert_eq!(warnings[0].line, 9);
assert!(warnings[0].message.contains("should not be indented"));
let fixed = rule.fix(&ctx).unwrap();
assert_ne!(fixed, content, "MD023 should fix indented heading after list");
assert!(fixed.contains("### Example: Precedence in Action"));
assert!(!fixed.contains(" ### Example: Precedence in Action"));
}