use std::collections::BTreeSet;
use std::path::Path;
use crate::analysis::findings::Severity;
use crate::docs::tests::{run, wide};
use crate::docs::{Check, analyze};
fn kitchen_sink() -> String {
let mut doc = String::new();
doc.push_str("##\n"); doc.push_str("#Heading\n"); doc.push_str("trailing \n"); doc.push_str("has\ttab\n"); doc.push_str(&format!("{}\n", wide(130))); doc.push_str("see https://example.com/x\n"); doc.push_str("[broken](\n"); doc.push_str("\n\n\n"); doc.push_str("```rust\n"); doc.push_str("fn main() {}\n");
doc.push('\n'); doc
}
#[test]
fn every_check_can_fire() {
let fired: BTreeSet<String> = run(&kitchen_sink()).into_iter().map(|f| f.kind).collect();
let expected: BTreeSet<String> = Check::ALL.iter().map(|c| c.as_str().to_owned()).collect();
assert_eq!(fired, expected);
}
#[test]
fn findings_are_sorted_by_position() {
let findings = run(&kitchen_sink());
let positions: Vec<(u32, Option<u32>)> = findings.iter().map(|f| (f.line, f.column)).collect();
let mut sorted = positions.clone();
sorted.sort_unstable();
assert_eq!(positions, sorted, "output must read top to bottom");
}
#[test]
fn two_runs_over_the_same_content_are_identical() {
assert_eq!(run(&kitchen_sink()), run(&kitchen_sink()));
}
#[test]
fn a_clean_document_produces_nothing() {
let content = "# Title\n\nSome prose with a [link](https://example.com).\n\n\
```rust\nfn main() {}\n```\n\nMore prose.\n";
assert_eq!(run(content), Vec::new());
}
#[test]
fn an_empty_document_produces_nothing() {
assert_eq!(run(""), Vec::new());
assert_eq!(run("\n"), run("\n")); }
#[test]
fn every_finding_carries_the_path_it_was_given() {
let findings = analyze(Path::new("docs/guide.md"), &kitchen_sink());
assert!(!findings.is_empty());
for finding in findings {
assert_eq!(finding.file_path, "docs/guide.md");
}
}
#[test]
fn every_finding_carries_a_suggestion_and_a_severity_from_its_check() {
for finding in run(&kitchen_sink()) {
let check = Check::ALL
.into_iter()
.find(|c| c.as_str() == finding.kind)
.expect("kind must be a declared check");
assert_eq!(finding.severity, check.severity(), "{}", finding.kind);
assert_eq!(
finding.suggestion.as_deref(),
Some(check.suggestion()),
"{}",
finding.kind
);
}
}
#[test]
fn a_crlf_document_does_not_report_the_carriage_return_as_whitespace() {
let findings = run("# Title\r\n\r\nprose\r\n");
assert_eq!(findings, Vec::new(), "{findings:?}");
}
#[test]
fn a_line_number_is_never_zero() {
for finding in run(&kitchen_sink()) {
assert!(finding.line >= 1, "{}", finding.kind);
assert!(finding.column.is_some_and(|c| c >= 1), "{}", finding.kind);
}
}
#[test]
fn only_the_unclosed_fence_reaches_error() {
let findings = run(&kitchen_sink());
let errors: BTreeSet<String> = findings
.into_iter()
.filter(|f| f.severity == Severity::Error)
.map(|f| f.kind)
.collect();
assert_eq!(
errors,
BTreeSet::from(["unclosed_code_fence".to_owned()]),
"a document of ordinary hygiene problems must not gate at error"
);
}