use rumdl_lib::lint_context::LintContext;
use rumdl_lib::rule::Rule;
use rumdl_lib::rules::MD040FencedCodeLanguage;
use rumdl_lib::rules::md040_fenced_code_language::md040_config::{LanguageStyle, MD040Config};
use std::collections::HashMap;
fn assert_roundtrip(content: &str, config: MD040Config) {
let rule = MD040FencedCodeLanguage::with_config(config.clone());
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 rule2 = MD040FencedCodeLanguage::with_config(config);
let warnings = rule2.check(&ctx2).unwrap();
assert!(
warnings.is_empty(),
"Roundtrip failed: fix then re-check produced {} warnings on:\n{fixed}",
warnings.len()
);
}
#[test]
fn test_roundtrip_missing_language() {
assert_roundtrip("```\nsome code\n```\n", MD040Config::default());
}
#[test]
fn test_roundtrip_multiple_missing_languages() {
assert_roundtrip(
"```\nfirst\n```\n\n```python\nsecond\n```\n\n```\nthird\n```\n",
MD040Config::default(),
);
}
#[test]
fn test_roundtrip_consistent_mode() {
assert_roundtrip(
"```bash\necho hi\n```\n\n```sh\necho there\n```\n\n```bash\necho again\n```\n",
MD040Config {
style: LanguageStyle::Consistent,
..Default::default()
},
);
}
#[test]
fn test_roundtrip_consistent_mode_preferred_alias() {
let mut preferred = HashMap::new();
preferred.insert("Shell".to_string(), "sh".to_string());
assert_roundtrip(
"```bash\necho hi\n```\n\n```sh\necho there\n```\n",
MD040Config {
style: LanguageStyle::Consistent,
preferred_aliases: preferred,
..Default::default()
},
);
}
#[test]
fn test_roundtrip_indented_missing_language() {
assert_roundtrip("- item\n ```\n code\n ```\n", MD040Config::default());
}
#[test]
fn test_roundtrip_tilde_fences() {
assert_roundtrip("~~~\ncode\n~~~\n", MD040Config::default());
}
#[test]
fn test_roundtrip_tilde_consistent() {
assert_roundtrip(
"~~~bash\necho hi\n~~~\n\n~~~sh\necho there\n~~~\n",
MD040Config {
style: LanguageStyle::Consistent,
..Default::default()
},
);
}
#[test]
fn test_valid_code_blocks() {
let rule = MD040FencedCodeLanguage::default();
let content = "```rust\nfn main() {}\n```\n```python\nprint('hello')\n```";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_missing_language() {
let rule = MD040FencedCodeLanguage::default();
let content = "```\nsome code\n```";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "```text\nsome code\n```");
}
#[test]
fn test_multiple_code_blocks() {
let rule = MD040FencedCodeLanguage::default();
let content = "```rust\nfn main() {}\n```\n```\nsome code\n```\n```python\nprint('hello')\n```";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(
fixed,
"```rust\nfn main() {}\n```\n```text\nsome code\n```\n```python\nprint('hello')\n```"
);
}
#[test]
fn test_empty_code_block() {
let rule = MD040FencedCodeLanguage::default();
let content = "```\n```";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "```text\n```");
}
#[test]
fn test_indented_code_block() {
let rule = MD040FencedCodeLanguage::default();
let content = " ```\n some code\n ```";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, " ```text\n some code\n ```");
}
#[test]
fn test_mixed_code_blocks() {
let rule = MD040FencedCodeLanguage::default();
let content = "```rust\nfn main() {}\n```\nSome text\n```\nmore code\n```\n```js\nconsole.log('hi');\n```";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(
fixed,
"```rust\nfn main() {}\n```\nSome text\n```text\nmore code\n```\n```js\nconsole.log('hi');\n```"
);
}
#[test]
fn test_preserve_whitespace() {
let rule = MD040FencedCodeLanguage::default();
let content = "``` \nsome code\n```";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert_eq!(result.len(), 1);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "```text\nsome code\n```");
}
#[test]
fn test_nested_code_blocks_no_false_positives() {
let rule = MD040FencedCodeLanguage::default();
let content = "```markdown\n1. First item\n\n ```python\n code_in_list()\n ```\n\n2. Second item\n```";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(
result.is_empty(),
"Nested code blocks should not generate false positives"
);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, content, "Fix should not modify correctly nested code blocks");
}
#[test]
fn test_indented_closing_fence_not_flagged() {
let rule = MD040FencedCodeLanguage::default();
let content = "```markdown\nSome content\n ```python\n code()\n ```\nMore content\n```";
let ctx = LintContext::new(content, rumdl_lib::config::MarkdownFlavor::Standard, None);
let result = rule.check(&ctx).unwrap();
assert!(
result.is_empty(),
"4-space indented fences should be content, not separate blocks"
);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(
fixed, content,
"Fix should not modify indented content inside code blocks"
);
assert!(
!fixed.contains("```text"),
"Fix should not add 'text' to content inside code blocks"
);
}