use rumdl_lib::config::MarkdownFlavor;
use rumdl_lib::lint_context::LintContext;
use rumdl_lib::rule::Rule;
use rumdl_lib::rules::MD031BlanksAroundFences;
use rumdl_lib::rules::{CodeBlockStyle, MD046CodeBlockStyle};
#[test]
fn test_md031_fix_inserts_blank_before_colon_fence() {
let content = "Some text\n:::python\ncode\n:::\n";
let ctx = LintContext::new(content, MarkdownFlavor::AzureDevOps, None);
let rule = MD031BlanksAroundFences::new(true);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "Some text\n\n:::python\ncode\n:::\n");
}
#[test]
fn test_md031_fix_inserts_blank_after_colon_fence() {
let content = ":::python\ncode\n:::\nSome text\n";
let ctx = LintContext::new(content, MarkdownFlavor::AzureDevOps, None);
let rule = MD031BlanksAroundFences::new(true);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, ":::python\ncode\n:::\n\nSome text\n");
}
#[test]
fn test_md031_fix_inserts_blanks_both_sides_of_colon_fence() {
let content = "Some text\n:::python\ncode\n:::\nMore text\n";
let ctx = LintContext::new(content, MarkdownFlavor::AzureDevOps, None);
let rule = MD031BlanksAroundFences::new(true);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, "Some text\n\n:::python\ncode\n:::\n\nMore text\n");
}
#[test]
fn test_md031_fix_leaves_conforming_colon_fence_unchanged() {
let content = "Some text\n\n:::python\ncode\n:::\n\nMore text\n";
let ctx = LintContext::new(content, MarkdownFlavor::AzureDevOps, None);
let rule = MD031BlanksAroundFences::new(true);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(fixed, content, "fix() must not alter a conforming document");
}
#[test]
fn test_md031_fix_standard_flavor_ignores_colon_fences() {
let content = "Some text\n:::python\ncode\n:::\nMore text\n";
let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
let rule = MD031BlanksAroundFences::new(true);
let fixed = rule.fix(&ctx).unwrap();
assert_eq!(
fixed, content,
"Standard flavor: fix() must not alter content around colon fences"
);
}
fn azure_ctx(content: &str) -> LintContext<'_> {
LintContext::new(content, MarkdownFlavor::AzureDevOps, None)
}
#[test]
fn test_md031_colon_fence_missing_blank_before() {
let content = "Some text\n::: mermaid\ndiagram\n:::\n";
let ctx = azure_ctx(content);
let rule = MD031BlanksAroundFences::new(true);
let warnings = rule.check(&ctx).unwrap();
assert!(!warnings.is_empty(), "should warn: no blank line before colon fence");
assert!(
warnings[0].message.contains("before"),
"message: {:?}",
warnings[0].message
);
}
#[test]
fn test_md031_colon_fence_missing_blank_after() {
let content = "::: mermaid\ndiagram\n:::\nSome text\n";
let ctx = azure_ctx(content);
let rule = MD031BlanksAroundFences::new(true);
let warnings = rule.check(&ctx).unwrap();
assert!(!warnings.is_empty(), "should warn: no blank line after colon fence");
assert!(
warnings[0].message.contains("after"),
"message: {:?}",
warnings[0].message
);
}
#[test]
fn test_md031_colon_fence_with_blank_lines_no_warning() {
let content = "Some text\n\n::: mermaid\ndiagram\n:::\n\nSome text\n";
let ctx = azure_ctx(content);
let rule = MD031BlanksAroundFences::new(true);
let warnings = rule.check(&ctx).unwrap();
assert!(
warnings.is_empty(),
"should not warn when blank lines present: {warnings:?}"
);
}
#[test]
fn test_md031_standard_flavor_colon_not_enforced() {
let content = "Some text\n::: mermaid\ndiagram\n:::\nSome text\n";
let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
let rule = MD031BlanksAroundFences::new(true);
let warnings = rule.check(&ctx).unwrap();
for w in &warnings {
assert!(!w.message.contains("colon"), "unexpected warning: {w:?}");
}
}
#[test]
fn test_md046_colon_fence_with_inner_backtick_not_counted() {
let content = "::: mermaid\n```\nsome content\n```\n:::\n\n```rust\nfn main() {}\n```\n";
let ctx = azure_ctx(content);
let rule = MD046CodeBlockStyle::new(CodeBlockStyle::Consistent);
let warnings = rule.check(&ctx).unwrap();
assert!(
warnings.is_empty(),
"MD046 should not flag colon fence content: {warnings:?}"
);
}
#[test]
fn test_md048_colon_fence_with_inner_backtick_not_counted() {
use rumdl_lib::rules::CodeFenceStyle;
use rumdl_lib::rules::MD048CodeFenceStyle;
let content = "::: mermaid\n```\ncontent\n```\n:::\n\n~~~rust\nfn main() {}\n~~~\n";
let ctx = azure_ctx(content);
let rule = MD048CodeFenceStyle::new(CodeFenceStyle::Consistent);
let warnings = rule.check(&ctx).unwrap();
assert!(
warnings.is_empty(),
"MD048 should not flag colon fence content: {warnings:?}"
);
}
#[test]
fn test_colon_fence_only_opaque_in_azure_flavor() {
let content = "::: mermaid\ngraph TD\n:::\n";
let azure = azure_ctx(content);
assert!(
azure.lines[0].in_code_block,
"azure: fence opener must be in_code_block"
);
assert!(
azure.lines[1].in_code_block,
"azure: fence content must be in_code_block"
);
let standard = LintContext::new(content, MarkdownFlavor::Standard, None);
assert!(
!standard.lines[0].in_code_block,
"standard: ::: line must not be in_code_block"
);
assert!(
!standard.lines[1].in_code_block,
"standard: content must not be in_code_block"
);
}
#[test]
fn test_link_parser_does_not_flag_content_inside_colon_fence() {
use rumdl_lib::rules::MD034NoBareUrls;
let content = "::: mermaid\nA --> https://example.com/very/long/path\n:::\n";
let ctx = azure_ctx(content);
let rule = MD034NoBareUrls;
let warnings = rule.check(&ctx).unwrap();
assert!(
warnings.is_empty(),
"MD034 must not flag URLs inside colon fence: {warnings:?}"
);
}
#[test]
fn test_multiple_colon_fences_in_document() {
use rumdl_lib::rules::MD013LineLength;
let long = "A".repeat(150);
let content = format!("# Heading\n\n::: mermaid\n{long}\n:::\n\nNormal paragraph.\n\n::: mermaid\n{long}\n:::\n");
let ctx = azure_ctx(&content);
let rule = MD013LineLength::default();
let warnings = rule.check(&ctx).unwrap();
assert!(
warnings.is_empty(),
"MD013 must not fire in any colon fence: {warnings:?}"
);
}
#[test]
fn test_pandoc_flavor_not_affected() {
use rumdl_lib::rules::MD013LineLength;
let long_line = "word ".repeat(30); let content = format!("::: note\n{long_line}\n:::\n");
let ctx = LintContext::new(&content, MarkdownFlavor::Pandoc, None);
let rule = MD013LineLength::default();
let warnings = rule.check(&ctx).unwrap();
assert!(
!warnings.is_empty(),
"Pandoc flavor: MD013 must fire inside fenced div — content is not suppressed"
);
}
#[test]
fn test_unclosed_colon_fence_does_not_panic() {
let content = "::: mermaid\ndiagram content\n";
let ctx = azure_ctx(content);
assert!(ctx.lines[0].in_code_block);
assert!(ctx.lines[1].in_code_block);
}
#[test]
fn test_autodoc_marker_treated_as_code_fence_in_azure_flavor() {
use rumdl_lib::rules::MD013LineLength;
let long = "A".repeat(150);
let content = format!("::: module.Class\n{long}\n:::\n");
let ctx = azure_ctx(&content);
let rule = MD013LineLength::default();
let warnings = rule.check(&ctx).unwrap();
assert!(
warnings.is_empty(),
"azure_devops: ::: module.Class should be opaque code fence"
);
}
#[test]
fn test_tab_indented_opener_is_not_a_colon_fence() {
let content = "\t::: mermaid\ncontent\n:::\n";
let ctx = azure_ctx(content);
assert!(
ctx.colon_fence_ranges().is_empty(),
"tab-indented ::: must not create a colon fence range"
);
assert!(
!ctx.lines[1].in_code_block,
"content after tab-indented ::: must not be in_code_block"
);
}
#[test]
fn test_bare_closer_without_opener_is_not_a_block() {
let content = "Some text\n:::\nMore text\n";
let ctx = azure_ctx(content);
assert!(
!ctx.lines[0].in_code_block,
"prose before bare ::: must not be in_code_block"
);
assert!(
!ctx.lines[1].in_code_block,
"bare ::: without opener must not be in_code_block"
);
assert!(
!ctx.lines[2].in_code_block,
"prose after bare ::: must not be in_code_block"
);
}
#[test]
fn test_leading_spaces_1_2_3_are_valid_openers() {
for spaces in 1..=3usize {
let indent = " ".repeat(spaces);
let content = format!("{indent}::: mermaid\ncontent\n{indent}:::\n");
let ctx = azure_ctx(&content);
assert!(
ctx.lines[0].in_code_block,
"{spaces}-space indent: opener must be in_code_block"
);
assert!(
ctx.lines[1].in_code_block,
"{spaces}-space indent: content must be in_code_block"
);
assert!(
ctx.lines[2].in_code_block,
"{spaces}-space indent: closer must be in_code_block"
);
}
}
#[test]
fn test_four_leading_spaces_is_not_a_colon_fence() {
let content = " ::: mermaid\ncontent\n:::\n";
let ctx = azure_ctx(content);
assert!(
ctx.colon_fence_ranges().is_empty(),
"4-space indent: must not create a colon fence range"
);
assert!(
!ctx.lines[1].in_code_block,
"4-space indent: following content must not be in_code_block"
);
}