mdka 2.1.8

A HTML to Markdown converter that balances conversion quality with runtime efficiency
Documentation
//! RFC 005 Slice A — structural-field characterisation.
//!
//! `drop_interactive_shell` and `unwrap_unknown_wrappers` are the two
//! `ConversionOptions` fields already known to be live (RFC 004's harvested
//! inventory, RFC 003's review). This file re-confirms both against the
//! public API on purpose-built fixtures, and records one thing the initial
//! discovery pass got wrong before correcting it: a naive wrapper fixture
//! (`<div><span>inner</span></div>` alone, or surrounded by `<p>` siblings)
//! shows *no* difference when `unwrap_unknown_wrappers` is toggled, because
//! neighbouring block elements' own blank-line spacing already dominates the
//! output either way. The fixture below uses bare sibling text nodes, which
//! do not contribute their own block spacing, so the wrapper's own
//! begin/end-block calls are the only source of separation - only then does
//! toggling the flag actually change anything observable.

mod common;
use common::conv_with;
use mdka::options::{ConversionMode, ConversionOptions};

const MODES: [ConversionMode; 5] = [
    ConversionMode::Balanced,
    ConversionMode::Strict,
    ConversionMode::Minimal,
    ConversionMode::Semantic,
    ConversionMode::Preserve,
];

const SHELL_HTML: &str = r#"<nav><a href="/">Home</a></nav><main><p>Content</p></main>"#;

/// No surrounding <p>/<div> to contribute independent block spacing - see
/// the module doc comment for why this shape is required to discriminate
/// unwrap_unknown_wrappers at all.
const WRAPPER_HTML: &str = r#"Before<div class="wrap"><span>inner</span></div>After"#;

#[test]
fn drop_interactive_shell_toggle_changes_output_in_every_mode() {
    let expected_baseline = [
        "[Home](/)\n\nContent\n", // balanced (default false)
        "[Home](/)\n\nContent\n", // strict (default false)
        "Content\n",              // minimal (default true)
        "[Home](/)\n\nContent\n", // semantic (default false)
        "[Home](/)\n\nContent\n", // preserve (default false)
    ];
    let expected_flipped = [
        "Content\n",              // balanced, flipped to true
        "Content\n",              // strict, flipped to true
        "[Home](/)\n\nContent\n", // minimal, flipped to false
        "Content\n",              // semantic, flipped to true
        "Content\n",              // preserve, flipped to true
    ];

    for ((mode, exp_base), exp_flip) in MODES
        .iter()
        .zip(expected_baseline.iter())
        .zip(expected_flipped.iter())
    {
        let mut opts = ConversionOptions::for_mode(*mode);
        let base = conv_with(SHELL_HTML, &opts);
        assert_eq!(&base, exp_base, "{mode} baseline mismatch");

        let default_value = opts.drop_interactive_shell;
        opts.drop_interactive_shell = !default_value;
        let flipped = conv_with(SHELL_HTML, &opts);
        assert_eq!(&flipped, exp_flip, "{mode} flipped mismatch");
        assert_ne!(
            base, flipped,
            "{mode}: drop_interactive_shell toggle unexpectedly changed nothing"
        );
    }
}

#[test]
fn unwrap_unknown_wrappers_toggle_changes_output_in_every_mode() {
    let expected_baseline = [
        "Before\n\ninner\n\nAfter\n", // balanced (default false: div kept, own block spacing)
        "Before\n\ninner\n\nAfter\n", // strict (default false)
        "BeforeinnerAfter\n",         // minimal (default true: div unwrapped, no spacing at all)
        "BeforeinnerAfter\n",         // semantic (default true)
        "Before\n\ninner\n\nAfter\n", // preserve (default false)
    ];
    let expected_flipped = [
        "BeforeinnerAfter\n",         // balanced, flipped to true
        "BeforeinnerAfter\n",         // strict, flipped to true
        "Before\n\ninner\n\nAfter\n", // minimal, flipped to false
        "Before\n\ninner\n\nAfter\n", // semantic, flipped to false
        "BeforeinnerAfter\n",         // preserve, flipped to true
    ];

    for ((mode, exp_base), exp_flip) in MODES
        .iter()
        .zip(expected_baseline.iter())
        .zip(expected_flipped.iter())
    {
        let mut opts = ConversionOptions::for_mode(*mode);
        let base = conv_with(WRAPPER_HTML, &opts);
        assert_eq!(&base, exp_base, "{mode} baseline mismatch");

        let default_value = opts.unwrap_unknown_wrappers;
        opts.unwrap_unknown_wrappers = !default_value;
        let flipped = conv_with(WRAPPER_HTML, &opts);
        assert_eq!(&flipped, exp_flip, "{mode} flipped mismatch");
        assert_ne!(
            base, flipped,
            "{mode}: unwrap_unknown_wrappers toggle unexpectedly changed nothing"
        );
    }
}

#[test]
fn unwrap_unknown_wrappers_naive_fixture_shows_no_difference() {
    // Recorded deliberately: this is the fixture the discovery pass tried
    // first, and it demonstrates why WRAPPER_HTML above had to be different.
    // Surrounding <p> tags each enforce their own blank-line separation, so
    // the wrapper's own begin/end-block calls are redundant either way and
    // toggling the flag is invisible. This is not a bug; it is a property of
    // how neighbouring block elements interact, worth recording so nobody
    // "fixes" WRAPPER_HTML back to this simpler shape later.
    let html = r#"<p>Before</p><div class="wrap"><span>inner</span></div><p>After</p>"#;
    for mode in MODES {
        let mut opts = ConversionOptions::for_mode(mode);
        let base = conv_with(html, &opts);
        let default_value = opts.unwrap_unknown_wrappers;
        opts.unwrap_unknown_wrappers = !default_value;
        let flipped = conv_with(html, &opts);
        assert_eq!(
            base, flipped,
            "{mode}: expected the naive fixture to show no difference"
        );
        assert_eq!(base, "Before\n\ninner\n\nAfter\n", "{mode} baseline value");
    }
}