sofie-engine 0.4.0

Screens: the template parser and renderer, and the component catalog.
Documentation
#![cfg(feature = "islands")]

mod common;

use serde_json::json;
use sofie_engine::islands::{IslandBundle, IslandLinks};
use sofie_engine::{Page, Template};

const SCREEN: &str = r#"<section><h2>Orders</h2><Island id="chart" name="order-chart" props={data.summary} /></section>"#;

#[test]
fn an_island_mounts_with_the_bound_value_as_its_props() {
    let template = common::loaded("order/member/orders", SCREEN);
    let data = json!({"summary": {"count": 2, "note": "</script>"}});

    let html = template.render(&data).unwrap();

    assert_eq!(
        html,
        "<section><h2>Orders</h2><order-chart id=\"chart\"><script type=\"application/json\">\
         {\"count\":2,\"note\":\"\\u003c/script\\u003e\"}</script></order-chart></section>"
    );
    let fragment = template.render_fragment(&data, "chart").unwrap();
    assert!(
        fragment.starts_with("<order-chart id=\"chart\">"),
        "{fragment}"
    );
}

#[test]
fn props_the_data_lacks_are_an_empty_object() {
    let template = common::loaded("t", r#"<Island name="order-chart" props={data.none} />"#);

    assert_eq!(
        template.render(&json!({})).unwrap(),
        "<order-chart><script type=\"application/json\">{}</script></order-chart>"
    );
}

#[test]
fn an_island_needs_a_custom_element_name_and_its_props() {
    for (source, expected) in [
        (
            r#"<Island name="chart" props={data} />"#,
            "`chart` is no island name",
        ),
        (
            r#"<Island name="{data.tag}" props={data} />"#,
            "needs `name` as a quoted literal",
        ),
        (r#"<Island name="order-chart" />"#, "needs the `props` prop"),
    ] {
        let error = Template::parse("t", source).unwrap_err().to_string();
        assert!(error.contains(expected), "{source}: {error}");
    }
}

#[test]
fn a_page_with_islands_loads_its_areas_bundle() {
    let bundle = IslandBundle::new("member", b"export {};", b"");
    let links = IslandLinks::of(&bundle);
    let page = Page {
        title: "Orders",
        lang: "en",
        body: "<p>x</p>",
        notice: &[],
        nonce: "n0nce",
        islands: Some(&links),
        script_notices: sofie_engine::ScriptNotices::plain(),
    };

    let html = page.render();

    let script = format!(
        "<script type=\"module\" src=\"{}\" nonce=\"n0nce\" data-sofie-islands></script></head>",
        links.script
    );
    assert!(html.contains(&script), "{html}");
    assert!(html.contains(&format!(
        "<link rel=\"stylesheet\" href=\"{}\" data-sofie-islands>",
        links.stylesheet
    )));
    let plain = Page {
        islands: None,
        ..page
    };
    assert!(!plain.render().contains("data-sofie-islands"));
}

#[cfg(feature = "parse")]
#[test]
fn an_island_screen_is_the_island_parsed() {
    let data = json!({"count": 1});
    let built = sofie_engine::islands::island_screen("t", "order-chart").unwrap();
    let parsed = Template::parse("t", r#"<Island name="order-chart" props={data} />"#).unwrap();

    assert_eq!(built.render(&data).unwrap(), parsed.render(&data).unwrap());
    assert!(sofie_engine::islands::island_screen("t", "Chart").is_err());
}