use mdlint::config::Config;
use mdlint::fix::Fixer;
use mdlint::formatter;
use mdlint::lint::LintEngine;
use std::fs;
fn fixture(path: &str) -> String {
let full = format!("tests/fixtures/{path}");
fs::read_to_string(&full).unwrap_or_else(|e| panic!("cannot read fixture {full}: {e}"))
}
fn all_rules_engine() -> LintEngine {
LintEngine::new(Config {
default_enabled: true,
..Config::default()
})
}
#[test]
fn format_produces_expected_output() {
let input = fixture("format/input.md");
let expected = fixture("format/expected.md");
let got = formatter::format(&input);
assert_eq!(got, expected, "formatter output did not match golden file");
}
#[test]
fn format_is_idempotent_on_expected() {
let expected = fixture("format/expected.md");
let twice = formatter::format(&expected);
assert_eq!(
expected, twice,
"formatting the already-formatted file produced different output"
);
}
#[test]
fn format_empty_string_is_empty() {
assert_eq!(formatter::format(""), "");
}
#[test]
fn format_whitespace_only_is_empty() {
assert_eq!(formatter::format(" \n\n \t\n"), "");
}
#[test]
fn check_detects_heading_level_skip() {
let content = fixture("check/violations.md");
let engine = all_rules_engine();
let violations = engine.lint_content(&content).unwrap();
assert!(
violations.iter().any(|v| v.rule == "MD001"),
"MD001 (heading level skip) should fire; got: {violations:?}"
);
}
#[test]
fn check_detects_trailing_spaces() {
let content = "# Heading\n\nSome text \nMore text\n";
let engine = all_rules_engine();
let violations = engine.lint_content(content).unwrap();
assert!(
violations.iter().any(|v| v.rule == "MD009"),
"MD009 (trailing spaces) should fire; got: {violations:?}"
);
}
#[test]
fn check_fix_removes_trailing_spaces() {
let content = "# Heading\n\nSome text \nMore text\n";
let engine = all_rules_engine();
let violations = engine.lint_content(content).unwrap();
let fixes: Vec<_> = violations.iter().filter_map(|v| v.fix.clone()).collect();
assert!(!fixes.is_empty(), "MD009 should produce an inline fix");
let fixed = Fixer::new()
.apply_fixes_to_content(content, &fixes)
.unwrap();
assert_eq!(fixed, "# Heading\n\nSome text\nMore text\n");
}
#[test]
fn check_detects_hard_tabs() {
let content = fixture("check/violations.md");
let engine = all_rules_engine();
let violations = engine.lint_content(&content).unwrap();
assert!(
violations.iter().any(|v| v.rule == "MD010"),
"MD010 (hard tabs) should fire; got: {violations:?}"
);
}
#[test]
fn check_fix_replaces_hard_tabs() {
let content = "# Heading\n\n\tTabbed line\n";
let engine = all_rules_engine();
let violations = engine.lint_content(content).unwrap();
let fixes: Vec<_> = violations.iter().filter_map(|v| v.fix.clone()).collect();
assert!(!fixes.is_empty(), "MD010 should produce an inline fix");
let fixed = Fixer::new()
.apply_fixes_to_content(content, &fixes)
.unwrap();
assert!(!fixed.contains('\t'), "tabs should be replaced after fix");
}
#[test]
fn check_clean_file_has_no_violations() {
let content = fixture("format/expected.md");
let engine = LintEngine::new(Config {
default_enabled: true,
rules: {
std::collections::HashMap::new()
},
..Config::default()
});
let violations = engine.lint_content(&content).unwrap();
let non_trivial: Vec<_> = violations
.iter()
.filter(|v| !matches!(v.rule.as_str(), "MD013" | "MD043"))
.collect();
assert!(
non_trivial.is_empty(),
"formatted file should have no violations (except MD013/MD043): {non_trivial:?}"
);
}