prosesmasher-app-core 0.1.7

Internal core checks crate for the prosesmasher workspace. Published to support the workspace dependency graph.
Documentation
use crate::check::Check;
use low_expectations::ExpectationSuite;
use prosesmasher_domain_types::{
    Block, CheckConfig, Document, DocumentMetadata, DocumentPolicyConfig, Heading, Locale,
    Paragraph, Section, Sentence,
};

type HeadingSpec<'a> = (u8, &'a str);

fn make_headed_doc(headings: &[HeadingSpec<'_>]) -> Document {
    let sections = headings
        .iter()
        .map(|(level, text)| Section {
            heading: Some(Heading {
                level: *level,
                text: (*text).to_owned(),
            }),
            blocks: vec![Block::Paragraph(Paragraph {
                sentences: vec![Sentence {
                    text: "Some content.".to_owned(),
                    words: vec![],
                }],
                has_bold: false,
                has_italic: false,
                links: vec![],
            })],
        })
        .collect();

    Document {
        locale: Locale::En,
        sections,
        metadata: DocumentMetadata::default(),
    }
}

fn config_enforcing_heading_hierarchy() -> CheckConfig {
    CheckConfig {
        document_policy: DocumentPolicyConfig {
            heading_hierarchy: true,
            ..DocumentPolicyConfig::default()
        },
        ..CheckConfig::default()
    }
}

#[test]
fn h1_present_fails() {
    let doc = make_headed_doc(&[(1, "My Title")]);
    let config = config_enforcing_heading_hierarchy();
    let mut suite = ExpectationSuite::new("test");
    super::HeadingHierarchyCheck.run(&doc, &config, &mut suite);
    let result = suite.into_suite_result();
    assert_eq!(
        result.statistics.unsuccessful_expectations, 1,
        "H1 in body should fail"
    );
    let vr = result.results.values().next();
    assert!(vr.is_some(), "heading hierarchy result should exist");
    if let Some(vr) = vr {
        let evidence = vr.result.partial_unexpected_list.as_ref();
        assert!(evidence.is_some(), "evidence should be present");
        assert_eq!(evidence.and_then(|e| e.first())
            .and_then(|item| item.get("heading_text"))
            .and_then(serde_json::Value::as_str), Some("My Title"), "heading text evidence");
    }
}

#[test]
fn h2_to_h4_skip_fails() {
    let doc = make_headed_doc(&[(2, "Section A"), (4, "Deep section")]);
    let config = config_enforcing_heading_hierarchy();
    let mut suite = ExpectationSuite::new("test");
    super::HeadingHierarchyCheck.run(&doc, &config, &mut suite);
    let result = suite.into_suite_result();
    // H4 triggers both "H4+ not allowed" AND "level skip H2→H4" = 2 failures
    assert_eq!(
        result.statistics.unsuccessful_expectations, 2,
        "H2→H4 skip should produce 2 failures (H4+ and skip)"
    );
}

#[test]
fn h2_h3_h2_passes() {
    let doc = make_headed_doc(&[(2, "First"), (3, "Sub"), (2, "Second")]);
    let config = config_enforcing_heading_hierarchy();
    let mut suite = ExpectationSuite::new("test");
    super::HeadingHierarchyCheck.run(&doc, &config, &mut suite);
    let result = suite.into_suite_result();
    assert_eq!(
        result.statistics.unsuccessful_expectations, 0,
        "H2→H3→H2 should pass"
    );
}

#[test]
fn h4_plus_fails() {
    let doc = make_headed_doc(&[(2, "Section"), (3, "Sub"), (5, "Too deep")]);
    let config = config_enforcing_heading_hierarchy();
    let mut suite = ExpectationSuite::new("test");
    super::HeadingHierarchyCheck.run(&doc, &config, &mut suite);
    let result = suite.into_suite_result();
    assert!(
        result.statistics.unsuccessful_expectations >= 1,
        "H5 should be flagged"
    );
}

#[test]
fn no_headings_passes() {
    let doc = Document {
        locale: Locale::En,
        sections: vec![Section {
            heading: None,
            blocks: vec![],
        }],
        metadata: DocumentMetadata::default(),
    };
    let config = config_enforcing_heading_hierarchy();
    let mut suite = ExpectationSuite::new("test");
    super::HeadingHierarchyCheck.run(&doc, &config, &mut suite);
    let result = suite.into_suite_result();
    assert_eq!(
        result.statistics.unsuccessful_expectations, 0,
        "no headings should pass"
    );
}

#[test]
fn check_id_and_label() {
    let check = super::HeadingHierarchyCheck;
    assert_eq!(check.id(), "heading-hierarchy", "id");
    assert_eq!(check.label(), "Heading Hierarchy", "label");
    assert!(check.supported_locales().is_none(), "supports all locales");
}