katana-document-viewer 0.1.4

KatanA document viewer artifact, render evaluation, and export foundation.
Documentation
use super::super::{SurfaceBlock, SurfaceBlockFactory};
use crate::export_surface::page_plan::SurfacePagePlan;
use crate::forge::BuildGraph;
use crate::{
    BuildProfile, BuildRequest, DocumentSnapshotFactory, DocumentSource, KdvThemeSnapshot,
    RenderedDiagram, SourceKind, SourceRevision, SourceUri,
};
use katana_markdown_model::{
    CodeBlockRole, KatanaMarkdownModel, KmmNode, KmmNodeKind, MarkdownInput,
};

pub(crate) struct SurfaceTestSupport;

impl SurfaceTestSupport {
    pub(crate) fn graph_with_rendered_diagram(
        markdown: String,
    ) -> Result<BuildGraph, Box<dyn std::error::Error>> {
        Self::graph_with_rendered_diagram_svg(
            markdown,
            "<svg><text>Rendered diagram</text></svg>".to_string(),
        )
    }

    pub(crate) fn graph_with_rendered_diagram_svg(
        markdown: String,
        svg: String,
    ) -> Result<BuildGraph, Box<dyn std::error::Error>> {
        let graph = Self::graph_from_markdown("surface.md", markdown)?;
        let node_id = Self::diagram_node_id(&graph.snapshot.document.nodes)?;
        Ok(graph.with_rendered_diagrams(vec![RenderedDiagram {
            node_id,
            kind: "mermaid".to_string(),
            svg,
        }]))
    }

    pub(crate) fn graph_from_markdown(
        path: &str,
        markdown: String,
    ) -> Result<BuildGraph, Box<dyn std::error::Error>> {
        let markdown = markdown.replace("\r\n", "\n").replace('\r', "\n");
        let source = DocumentSource {
            uri: SourceUri(format!("file://{path}")),
            kind: SourceKind::Markdown,
            revision: SourceRevision("rev-1".to_string()),
            content: markdown,
        };
        let document =
            KatanaMarkdownModel::parse(MarkdownInput::from_content(path, source.content.clone()))?;
        let snapshot = DocumentSnapshotFactory::from_kmm(source, document);
        Ok(BuildGraph::from_request(&BuildRequest {
            snapshot,
            profile: BuildProfile::markdown_export(),
            theme: KdvThemeSnapshot::katana_light(),
        }))
    }

    pub(crate) fn surface_text(graph: &BuildGraph) -> String {
        SurfaceBlockFactory::create(graph, &graph.theme)
            .iter()
            .map(SurfaceBlock::text_for_tests)
            .collect::<Vec<_>>()
            .join("\n")
    }

    pub(crate) fn surface_debug(graph: &BuildGraph) -> String {
        SurfaceBlockFactory::create(graph, &graph.theme)
            .iter()
            .map(SurfaceBlock::debug_for_tests)
            .collect::<Vec<_>>()
            .join("\n")
    }

    pub(crate) fn surface_page_texts(graph: &BuildGraph) -> Vec<String> {
        let blocks = SurfaceBlockFactory::create(graph, &graph.theme);
        SurfacePagePlan::from_blocks(&blocks)
            .pages
            .iter()
            .map(|page| {
                page.iter()
                    .map(|index| blocks[*index].text_for_tests())
                    .collect::<Vec<_>>()
                    .join("\n")
            })
            .collect()
    }

    pub(crate) fn assert_contains_all(text: &str, needles: &[&str]) {
        for needle in needles {
            assert!(text.contains(needle), "missing {needle:?} in {text}");
        }
    }

    pub(crate) fn assert_not_contains_any(text: &str, needles: &[&str]) {
        for needle in needles {
            assert!(!text.contains(needle), "unexpected {needle:?} in {text}");
        }
    }

    fn diagram_node_id(nodes: &[KmmNode]) -> Result<String, Box<dyn std::error::Error>> {
        for node in nodes {
            if matches!(
                node.kind,
                KmmNodeKind::CodeBlock(CodeBlockRole::Diagram { .. })
            ) {
                return Ok(node.id.0.clone());
            }
        }
        Err("diagram node is required".into())
    }
}

#[cfg(test)]
mod tests {
    use super::SurfaceTestSupport;

    #[test]
    fn diagram_node_id_reports_missing_diagram_node() {
        assert!(SurfaceTestSupport::diagram_node_id(&[]).is_err());
    }
}