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, Locale, Paragraph, Section, Sentence,
    Word,
};

fn make_sentence(text: &str) -> Sentence {
    let words: Vec<Word> = text
        .split_whitespace()
        .map(|w| Word {
            text: w.to_owned(),
            syllable_count: 1,
        })
        .collect();
    Sentence {
        text: text.to_owned(),
        words,
    }
}

fn doc_with_paragraph_sentences(sentence_count: usize) -> Document {
    let sentences: Vec<Sentence> = (0..sentence_count)
        .map(|i| make_sentence(&format!("This is sentence {i}.")))
        .collect();

    Document {
        locale: Locale::En,
        sections: vec![Section {
            heading: None,
            blocks: vec![Block::Paragraph(Paragraph {
                sentences,
                has_bold: false,
                has_italic: false,
                links: vec![],
            })],
        }],
        metadata: DocumentMetadata::default(),
    }
}

#[test]
fn paragraph_exceeds_max_fails() {
    let doc = doc_with_paragraph_sentences(6);
    let config = config_with_max_sentences(4);
    let mut suite = ExpectationSuite::new("test");
    super::ParagraphLengthCheck.run(&doc, &config, &mut suite);
    let result = suite.into_suite_result();
    assert_eq!(
        result.statistics.unsuccessful_expectations, 1,
        "6 sentences with max=4 should fail"
    );
    let vr = result.results.get("paragraph-length");
    assert!(vr.is_some(), "paragraph-length 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("paragraph_text"))
            .and_then(serde_json::Value::as_str), Some("This is sentence 0. This is sentence 1. This is sentence 2. This is sentence 3. This is sentence 4. This is sentence 5."), "paragraph text");
        assert_eq!(evidence.and_then(|e| e.first())
            .and_then(|item| item.get("sentence_count"))
            .and_then(serde_json::Value::as_i64), Some(6), "sentence count");
    }
}

#[test]
fn paragraph_within_max_passes() {
    let doc = doc_with_paragraph_sentences(3);
    let config = config_with_max_sentences(4);
    let mut suite = ExpectationSuite::new("test");
    super::ParagraphLengthCheck.run(&doc, &config, &mut suite);
    let result = suite.into_suite_result();
    assert_eq!(
        result.statistics.successful_expectations, 1,
        "3 sentences with max=4 should pass"
    );
}

#[test]
fn paragraph_in_blockquote_checked() {
    let sentences: Vec<Sentence> = (0..6)
        .map(|i| make_sentence(&format!("Sentence {i}.")))
        .collect();

    let doc = Document {
        locale: Locale::En,
        sections: vec![Section {
            heading: None,
            blocks: vec![Block::BlockQuote(vec![Block::Paragraph(Paragraph {
                sentences,
                has_bold: false,
                has_italic: false,
                links: vec![],
            })])],
        }],
        metadata: DocumentMetadata::default(),
    };

    let config = config_with_max_sentences(4);
    let mut suite = ExpectationSuite::new("test");
    super::ParagraphLengthCheck.run(&doc, &config, &mut suite);
    let result = suite.into_suite_result();
    assert_eq!(
        result.statistics.unsuccessful_expectations, 1,
        "blockquote paragraph with 6 sentences should fail"
    );
}

#[test]
fn no_threshold_skips() {
    let doc = doc_with_paragraph_sentences(3);
    let config = CheckConfig::default();
    let mut suite = ExpectationSuite::new("test");
    super::ParagraphLengthCheck.run(&doc, &config, &mut suite);
    let result = suite.into_suite_result();
    assert_eq!(
        result.statistics.successful_expectations, 1,
        "default quality config should run paragraph-length with built-in max"
    );
}

#[test]
fn check_id_and_label() {
    let check = super::ParagraphLengthCheck;
    assert_eq!(check.id(), "paragraph-length", "id");
    assert_eq!(check.label(), "Paragraph Length", "label");
    assert!(check.supported_locales().is_none(), "supports all locales");
}

fn config_with_max_sentences(max_sentences: usize) -> CheckConfig {
    let mut config = CheckConfig {
        locale: Locale::En,
        ..CheckConfig::default()
    };
    config.quality.flow.paragraph_length.max_sentences = max_sentences;
    config
}