ferromark 0.7.0

Ultra-high-performance Markdown to HTML compiler
Documentation
use ferromark::{BlockEvent, BlockParser, Options, to_html_with_options};

fn options() -> Options {
    Options {
        definition_lists: true,
        ..Options::default()
    }
}

fn render(input: &str) -> String {
    to_html_with_options(input, &options())
}

#[test]
fn definition_lists_are_disabled_by_default() {
    assert_eq!(
        to_html_with_options("Term\n: Definition", &Options::default()),
        "<p>Term\n: Definition</p>\n"
    );
}

#[test]
fn basic_definition_list_renders_semantic_html() {
    assert_eq!(
        render("Term\n: Definition"),
        "<dl>\n<dt>Term</dt>\n<dd>Definition</dd>\n</dl>\n"
    );
}

#[test]
fn terms_and_descriptions_support_inline_markdown() {
    assert_eq!(
        render("**Term**\n: A *definition*"),
        "<dl>\n<dt><strong>Term</strong></dt>\n<dd>A <em>definition</em></dd>\n</dl>\n"
    );
}

#[test]
fn multiple_terms_and_definitions_share_one_list() {
    let input = "\
Term one
Term *two*
: First definition
: Second definition

Term three
: Third definition";
    let html = render(input);

    assert_eq!(html.matches("<dl>").count(), 1);
    assert_eq!(html.matches("<dt>").count(), 3);
    assert_eq!(html.matches("<dd>").count(), 3);
    assert!(html.contains("<dt>Term <em>two</em></dt>"));
    assert!(html.contains("<dd>Second definition</dd>"));
}

#[test]
fn blank_before_definition_makes_its_paragraph_loose() {
    assert_eq!(
        render("Term\n\n: Definition"),
        "<dl>\n<dt>Term</dt>\n<dd>\n<p>Definition</p>\n</dd>\n</dl>\n"
    );
}

#[test]
fn descriptions_support_lazy_continuation() {
    assert_eq!(
        render("Term\n: First line\nlazy continuation"),
        "<dl>\n<dt>Term</dt>\n<dd>First line\nlazy continuation</dd>\n</dl>\n"
    );
}

#[test]
fn descriptions_support_multiple_blocks_and_nested_lists() {
    let input = "\
Term
: First paragraph.

    Second paragraph.

    - nested item";
    let html = render(input);

    assert_eq!(
        html,
        "<dl>\n<dt>Term</dt>\n<dd>First paragraph.<p>Second paragraph.</p>\n\n<ul>\n<li>nested item</li>\n</ul>\n</dd>\n</dl>\n"
    );
}

#[test]
fn ordinary_paragraphs_after_a_definition_list_stay_outside_the_dl() {
    assert_eq!(
        render("Term\n: Definition\n\nOrdinary paragraph."),
        "<dl>\n<dt>Term</dt>\n<dd>Definition</dd>\n</dl>\n<p>Ordinary paragraph.</p>\n"
    );
    assert_eq!(
        render("Term\n: Definition\n\nOrdinary paragraph.\n\n# Heading"),
        "<dl>\n<dt>Term</dt>\n<dd>Definition</dd>\n</dl>\n<p>Ordinary paragraph.</p>\n<h1 id=\"heading\">Heading</h1>\n"
    );
}

#[test]
fn definition_lists_work_inside_blockquotes_and_list_items() {
    assert_eq!(
        render("> Term\n> : Definition"),
        "<blockquote>\n<dl>\n<dt>Term</dt>\n<dd>Definition</dd>\n</dl>\n</blockquote>\n"
    );
    assert_eq!(
        render("- Term\n  : Definition"),
        "<ul>\n<li>\n<dl>\n<dt>Term</dt>\n<dd>Definition</dd>\n</dl>\n</li>\n</ul>\n"
    );
}

#[test]
fn definition_lists_consume_tight_list_item_block_spacing() {
    assert_eq!(
        render("- Term\n  : # Heading"),
        "<ul>\n<li>\n<dl>\n<dt>Term</dt>\n<dd><h1 id=\"heading\">Heading</h1>\n</dd>\n</dl>\n</li>\n</ul>\n"
    );
}

#[test]
fn definition_lists_start_on_a_new_line_inside_loose_list_items() {
    assert_eq!(
        render("- Term\n  : Definition\n\n- Other"),
        "<ul>\n<li>\n<dl>\n<dt>Term</dt>\n<dd>Definition</dd>\n</dl>\n</li>\n<li>\n<p>Other</p>\n</li>\n</ul>\n"
    );
}

#[test]
fn descriptions_can_contain_nested_definition_lists() {
    let html = render("Outer\n: Outer definition\n\n    Inner\n    : Inner definition");

    assert_eq!(html.matches("<dl>").count(), 2);
    assert!(
        html.contains(
            "<dd>Outer definition\n<dl>\n<dt>Inner</dt>\n<dd>Inner definition</dd>\n</dl>\n</dd>"
        ),
        "{html}"
    );
}

#[test]
fn colon_prefixed_prose_and_indented_markers_stay_commonmark() {
    assert!(!render(": ordinary prose").contains("<dl>"));
    assert!(render("Term\n   : definition").contains("<dl>"));
    assert!(!render("Term\n    : indented continuation").contains("<dl>"));
    assert!(!render("Term\n\\: escaped marker").contains("<dl>"));
}

#[test]
fn parser_emits_definition_structure_as_block_events() {
    let input = b"Term\n: Definition";
    let mut parser = BlockParser::new_with_options(input, options());
    let mut events = Vec::new();
    parser.parse(&mut events);

    assert!(matches!(events[0], BlockEvent::DefinitionListStart));
    assert!(matches!(events[1], BlockEvent::DefinitionTermStart));
    assert!(matches!(events[3], BlockEvent::DefinitionTermEnd));
    assert!(matches!(
        events[4],
        BlockEvent::DefinitionDescriptionStart { tight: true }
    ));
    assert!(matches!(
        events[events.len() - 2],
        BlockEvent::DefinitionDescriptionEnd
    ));
    assert!(matches!(
        events[events.len() - 1],
        BlockEvent::DefinitionListEnd
    ));
}