katana-document-viewer 0.1.4

KatanA document viewer artifact, render evaluation, and export foundation.
Documentation
use super::*;
use crate::theme::KdvThemeSnapshot;
use katana_markdown_model::{
    ByteRange, HeadingNode, KmmNode, KmmNodeId, KmmNodeKind, LineColumn, LineColumnRange,
    RawSnippet, SourceSpan, TextSpan,
};

#[test]
fn append_clamps_level_below_range() {
    let mut html = String::new();
    let theme = KdvThemeSnapshot::katana_light();
    let node = node(
        KmmNodeKind::Heading(HeadingNode {
            level: 0,
            text: "# heading".to_string(),
        }),
        "heading",
        Vec::new(),
    );
    HeadingHtmlWriter::append(&mut html, &node, 0, "fallback", &theme);
    assert!(html.starts_with("<h1>"));
}

#[test]
fn append_clamps_level_above_range() {
    let mut html = String::new();
    let theme = KdvThemeSnapshot::katana_light();
    let node = node(
        KmmNodeKind::Heading(HeadingNode {
            level: 9,
            text: "## heading".to_string(),
        }),
        "heading",
        Vec::new(),
    );
    HeadingHtmlWriter::append(&mut html, &node, 9, "fallback", &theme);
    assert!(html.starts_with("<h6>"));
}

#[test]
fn append_strips_first_child_marker() {
    let mut html = String::new();
    let theme = KdvThemeSnapshot::katana_light();
    let heading_node = node(
        KmmNodeKind::Heading(HeadingNode {
            level: 2,
            text: "# fallback".to_string(),
        }),
        "## heading",
        vec![node(
            KmmNodeKind::Text(TextSpan {
                text: "## heading".to_string(),
            }),
            "## heading",
            Vec::new(),
        )],
    );
    HeadingHtmlWriter::append(&mut html, &heading_node, 2, "fallback", &theme);
    assert_eq!(html, "<h2>heading</h2>\n");
}

#[test]
fn append_uses_raw_text_when_no_children() {
    let mut html = String::new();
    let theme = KdvThemeSnapshot::katana_light();
    let heading_node = node(
        KmmNodeKind::Heading(HeadingNode {
            level: 2,
            text: "raw".to_string(),
        }),
        "Raw",
        Vec::new(),
    );
    HeadingHtmlWriter::append(&mut html, &heading_node, 2, "Raw fallback", &theme);
    assert!(html.contains("Raw fallback"));
}

#[test]
fn append_children_without_marker_uses_child_when_not_marker() {
    let mut html = String::new();
    let theme = KdvThemeSnapshot::katana_light();
    let heading_node = node(
        KmmNodeKind::Heading(katana_markdown_model::HeadingNode {
            level: 2,
            text: "x".to_string(),
        }),
        "## heading",
        vec![node(KmmNodeKind::BlockQuote, "not marker", Vec::new())],
    );

    let wrote = HeadingHtmlWriter::append_children_without_marker(&mut html, &heading_node, &theme);

    assert!(wrote);
    assert!(html.contains("not marker"));
}

#[test]
fn strip_heading_marker_ignores_non_text_node() {
    let heading = KmmNode {
        id: KmmNodeId("heading".to_string()),
        kind: KmmNodeKind::Heading(katana_markdown_model::HeadingNode {
            level: 2,
            text: "heading".to_string(),
        }),
        source: source_span("heading"),
        children: Vec::new(),
    };
    assert!(HeadingHtmlWriter::try_strip_heading_marker(&heading).is_none());
}

#[test]
fn strip_heading_marker_returns_none_when_hash_count_is_invalid() {
    assert!(HeadingHtmlWriter::strip_heading_marker("######## title").is_none());
    assert!(HeadingHtmlWriter::strip_heading_marker("no marker").is_none());
}

fn node(kind: KmmNodeKind, raw: &str, children: Vec<KmmNode>) -> KmmNode {
    KmmNode {
        id: KmmNodeId("heading".to_string()),
        kind,
        source: source_span(raw),
        children,
    }
}

fn source_span(text: &str) -> SourceSpan {
    SourceSpan {
        byte_range: ByteRange {
            start: 0,
            end: text.len(),
        },
        line_column_range: LineColumnRange {
            start: LineColumn { line: 1, column: 1 },
            end: LineColumn {
                line: 1,
                column: text.len() + 1,
            },
        },
        raw: RawSnippet {
            text: text.to_string(),
        },
    }
}