microsoft-webui-press 0.0.27

High-performance Static Site Generator powered by WebUI Framework
Documentation
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

use super::*;
use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};

static NEXT_DIR: AtomicUsize = AtomicUsize::new(0);
type TestResult = std::result::Result<(), Box<dyn std::error::Error>>;

fn region(html: &str) -> RegionConfig {
    RegionConfig {
        html: Some(html.to_string()),
        html_file: None,
        state: None,
        state_file: None,
        script_file: None,
    }
}

fn temp_dir() -> Result<PathBuf> {
    let id = NEXT_DIR.fetch_add(1, Ordering::Relaxed);
    let path = std::env::temp_dir().join(format!("webui-press-region-{}-{id}", std::process::id()));
    fs::remove_dir_all(&path).ok();
    fs::create_dir_all(&path).map_err(|error| Error::Io(error.to_string()))?;
    Ok(path)
}

#[test]
fn renders_layout_scoped_and_global_regions() -> TestResult {
    let template = concat!(
        "<main>",
        "<webui-press-region name = \"home.panel\" layout = \"home\"><p>Default</p></webui-press-region>",
        "<webui-press-region name=\"home.default\" layout=\"home\"><h1>After Hero</h1></webui-press-region>",
        "<webui-press-region name=\"shared.footer\" />",
        "<webui-press-region name=\"optional\" />",
        "</main>"
    );
    let configs = BTreeMap::from([
        ("home.panel".to_string(), region("<home-card></home-card>")),
        (
            "shared.footer".to_string(),
            region("<site-note></site-note>"),
        ),
    ]);
    let regions = RegionSet::load(&configs, Path::new("."), template.to_string())?;

    assert_eq!(
        regions.render("home"),
        "<main><home-card></home-card><h1>After Hero</h1><site-note></site-note></main>"
    );
    assert_eq!(
        regions.render("doc"),
        "<main><site-note></site-note></main>"
    );
    assert_eq!(regions.template_shell(), "<main></main>");
    Ok(())
}

#[test]
fn bundled_template_exposes_stable_regions() -> TestResult {
    let template = include_str!("../../template/index.html");
    let regions = RegionSet::load(&BTreeMap::new(), Path::new("."), template.to_string())?;
    let names: Vec<&str> = regions
        .regions
        .iter()
        .map(|region| region.name.as_str())
        .collect();

    assert_eq!(
        names,
        [
            "site.navigation",
            "site.announcement",
            "home.hero",
            "home.afterHero",
            "home.features",
            "home.footer",
            "doc.sidebar",
            "doc.context",
            "doc.beforeContent",
            "page.beforeContent",
            "full.beforeContent",
            "doc.afterContent",
            "page.afterContent",
            "full.afterContent",
            "doc.pageNavigation",
            "doc.footer",
            "page.footer",
        ]
    );
    assert!(regions
        .regions
        .iter()
        .find(|region| region.name == "home.hero")
        .and_then(|region| region.html.as_deref())
        .is_some_and(|html| html.contains("home-hero")));
    Ok(())
}

#[test]
fn loads_files_and_namespaces_state() -> TestResult {
    let dir = temp_dir()?;
    fs::write(dir.join("region.html"), "<summary-card></summary-card>")?;
    fs::write(dir.join("state.json"), r#"{"title":"Status"}"#)?;
    let configs = BTreeMap::from([(
        "home.panel".to_string(),
        RegionConfig {
            html: None,
            html_file: Some("region.html".to_string()),
            state: None,
            state_file: Some("state.json".to_string()),
            script_file: Some("region.ts".to_string()),
        },
    )]);
    let template = "<webui-press-region name=\"home.panel\" layout=\"home\"></webui-press-region>";
    let regions = RegionSet::load(&configs, &dir, template.to_string())?;
    let mut state = Value::Object(Map::new());
    regions.apply_state("home", &mut state)?;

    assert_eq!(regions.render("home"), "<summary-card></summary-card>");
    assert_eq!(state["regions"]["home"]["panel"]["title"], "Status");
    assert_eq!(
        regions.script_files("home").collect::<Vec<_>>(),
        ["region.ts"]
    );
    assert_eq!(regions.script_files("doc").count(), 0);

    fs::remove_dir_all(dir).ok();
    Ok(())
}

#[test]
fn state_only_config_keeps_default_html() -> TestResult {
    let template =
        "<webui-press-region name=\"summary\"><summary-card></summary-card></webui-press-region>";
    let mut config = region("");
    config.html = None;
    config.state = Some(Value::Object(Map::from_iter([(
        "title".to_string(),
        Value::String("Status".to_string()),
    )])));
    let regions = RegionSet::load(
        &BTreeMap::from([("summary".to_string(), config)]),
        Path::new("."),
        template.to_string(),
    )?;

    assert_eq!(regions.render("doc"), "<summary-card></summary-card>");
    Ok(())
}

#[test]
fn rejects_invalid_declarations() {
    let cases = [
        (
            concat!(
                "<webui-press-region name=\"same\" />",
                "<webui-press-region name=\"same\" />"
            ),
            "more than once",
        ),
        (
            "<webui-press-region name=\"x\" layout />",
            "requires a quoted value",
        ),
        (
            "<webui-press-region name=\"x\" layout=home />",
            "requires a quoted value",
        ),
        (
            "<webui-press-region name=\"x\" layout=\"\" />",
            "requires a non-empty value",
        ),
        (
            "<webui-press-region name=\"x\" layout=\"home\" layout=\"doc\" />",
            "declared more than once",
        ),
        (
            "<webui-press-region name=\"x\" unexpected=\"value\" />",
            "unsupported attribute",
        ),
        (
            concat!(
                "<webui-press-region name=\"outer\">",
                "<webui-press-region name=\"inner\"></webui-press-region>",
                "</webui-press-region>"
            ),
            "cannot be nested",
        ),
    ];
    for (template, expected) in cases {
        let result = RegionSet::load(&BTreeMap::new(), Path::new("."), template.to_string());
        assert!(
            matches!(result, Err(Error::Build(message)) if message.contains(expected)),
            "expected {expected:?} for {template:?}"
        );
    }
}

#[test]
fn ignores_commented_markers() -> TestResult {
    let template = concat!(
        "<!-- <webui-press-region name=\"ignored\" /> -->",
        "<webui-press-region name=\"active\">Active</webui-press-region>"
    );
    let regions = RegionSet::load(&BTreeMap::new(), Path::new("."), template.to_string())?;

    assert_eq!(
        regions.render("doc"),
        "<!-- <webui-press-region name=\"ignored\" /> -->Active"
    );
    Ok(())
}

#[test]
fn rejects_undeclared_config_and_invalid_names() {
    let configs = BTreeMap::from([("missing".to_string(), region("<p>x</p>"))]);
    assert!(matches!(
        RegionSet::load(&configs, Path::new("."), "<main></main>".to_string()),
        Err(Error::Build(message)) if message.contains("does not declare")
    ));

    for name in ["home..panel", "home."] {
        let template = format!("<webui-press-region name=\"{name}\" />");
        assert!(matches!(
            RegionSet::load(&BTreeMap::new(), Path::new("."), template),
            Err(Error::Build(message)) if message.contains("invalid characters")
        ));
    }
}

#[test]
fn rejects_state_prefix_collisions_but_allows_html_only_prefixes() {
    let template = concat!(
        "<webui-press-region name=\"summary\" />",
        "<webui-press-region name=\"summary.details\" />"
    )
    .to_string();
    let mut parent = region("<p>Summary</p>");
    parent.state = Some(Value::Object(Map::new()));
    let mut child = region("<p>Details</p>");
    child.state = Some(Value::Object(Map::new()));
    let stateful = BTreeMap::from([
        ("summary".to_string(), parent),
        ("summary.details".to_string(), child),
    ]);
    assert!(matches!(
        RegionSet::load(&stateful, Path::new("."), template.clone()),
        Err(Error::Build(message)) if message.contains("distinct path")
    ));

    let html_only = BTreeMap::from([
        ("summary".to_string(), region("<p>Summary</p>")),
        ("summary.details".to_string(), region("<p>Details</p>")),
    ]);
    assert!(RegionSet::load(&html_only, Path::new("."), template).is_ok());
}

#[test]
fn rejects_conflicting_sources_and_non_object_state() {
    let template = "<webui-press-region name=\"x\" />".to_string();
    let mut conflicting = region("<p>x</p>");
    conflicting.html_file = Some("x.html".to_string());
    assert!(matches!(
        RegionSet::load(
            &BTreeMap::from([("x".to_string(), conflicting)]),
            Path::new("."),
            template.clone()
        ),
        Err(Error::Build(message)) if message.contains("mutually exclusive")
    ));

    let mut invalid_state = region("<p>x</p>");
    invalid_state.state = Some(Value::Array(Vec::new()));
    assert!(matches!(
        RegionSet::load(
            &BTreeMap::from([("x".to_string(), invalid_state)]),
            Path::new("."),
            template
        ),
        Err(Error::Build(message)) if message.contains("must be a JSON object")
    ));
}