5.1 KiB · text History d76cd01
//! Smoke test: every page template loads from the embedded environment and
//! renders with a plausible context. Templates are data, not code, so a typo
//! in one is invisible to `cargo check` — this is what catches it.
use bismuth::build_environment;
use bismuth::handlers::cards::options;
use bismuth::render::{palette, render_svg, CardSpec, Output};
use minijinja::context;

fn user() -> minijinja::Value {
    context! {
        username => "alice",
        email => "alice@example.com",
        is_active => true,
        created_at => "2026-08-03T00:00:00Z",
        last_login_at => "2026-08-03T12:00:00Z",
    }
}

fn snippet() -> minijinja::Value {
    context! {
        slug => "abcde23456",
        title => "main.rs — Konsole",
        language => "Rust",
        chrome => "keramik",
        palette => "auto",
    }
}

fn svg() -> String {
    render_svg(&CardSpec::default(), Output::Inline)
}

/// Every full page renders and carries the app name from `APP_NAME`.
#[test]
fn renders_every_page() {
    let env = build_environment();
    let spec = CardSpec::default();
    let editor = |user: Option<minijinja::Value>, slug: Option<&str>| {
        context! {
            title => "New card", user => user, spec => &spec, slug => slug,
            options => options(), max_padding => 160, card_svg => svg(),
        }
    };
    let show = |is_owner: bool| {
        context! {
            title => "main.rs", user => user(), snippet => snippet(), is_owner => is_owner,
            base => "https://bismuth.example", palette => "keramik", card_svg => svg(),
        }
    };
    let cases: Vec<(&str, minijinja::Value)> = vec![
        ("editor.html", editor(None, None)),
        ("editor.html", editor(Some(user()), None)),
        ("editor.html", editor(Some(user()), Some("abcde23456"))),
        ("snippets/show.html", show(true)),
        ("snippets/show.html", show(false)),
        ("snippets/list.html", context! { title => "Snippets", user => user(), snippets => vec![snippet()] }),
        ("auth/login.html", context! { title => "Login" }),
        (
            "auth/register.html",
            context! { title => "Register", invite_token => "tok-123", registration_open => false },
        ),
        ("auth/profile.html", context! { title => "Profile", user => user() }),
        (
            "auth/invites.html",
            context! {
                title => "Invites",
                user => user(),
                now => "2026-08-03T00:00:00Z",
                invites => vec![context! {
                    token => "tok-123",
                    email => "friend@example.com",
                    expires_at => "2026-09-03T00:00:00Z",
                    is_used => false,
                }],
            },
        ),
    ];

    for (name, ctx) in cases {
        let tmpl = env.get_template(name).unwrap_or_else(|e| panic!("load {name}: {e}"));
        let html = tmpl.render(ctx).unwrap_or_else(|e| panic!("render {name}: {e}"));
        assert!(html.contains(bismuth::APP_NAME), "{name} did not render the app name");
    }
}

/// The empty-collection branches of the list pages.
#[test]
fn renders_empty_lists() {
    let env = build_environment();
    let pages = [("auth/invites.html", "invites"), ("snippets/list.html", "snippets")];
    for (name, key) in pages {
        let ctx = context! { user => user(), now => "2026-08-03T00:00:00Z", ..
            minijinja::Value::from_iter([(key, Vec::<minijinja::Value>::new())]) };
        let tmpl = env.get_template(name).unwrap();
        tmpl.render(ctx).unwrap_or_else(|e| panic!("render empty {name}: {e}"));
    }
}

/// The editor's selects carry every decoration, palette and highlight theme,
/// and mark the spec's own choice selected.
#[test]
fn editor_lists_every_option() {
    let env = build_environment();
    let spec = CardSpec { palette: "blueprint".into(), ..CardSpec::default() };
    let html = env
        .get_template("editor.html")
        .unwrap()
        .render(context! {
            spec => &spec, slug => None::<&str>, options => options(),
            max_padding => 160, card_svg => svg(),
        })
        .unwrap();
    for p in palette::PALETTES.iter() {
        assert!(html.contains(&format!(r#"value="{}""#, p.id)), "palette {} missing", p.id);
    }
    assert!(html.contains(r#"value="blueprint" selected"#));
    assert!(html.contains(r#"value="solarized" selected"#), "solarized should be the default");
}

/// The embed speaks the theme handshake and lists the palettes it accepts,
/// including every scheme name presentato can send.
#[test]
fn embed_speaks_the_handshake() {
    let env = build_environment();
    let html = env
        .get_template("embed.html")
        .unwrap()
        .render(context! {
            snippet => snippet(), palette => "keramik",
            themes => palette::ids().join(" "), src => "/s/abcde23456/card.svg", card_svg => svg(),
        })
        .unwrap();
    assert!(html.contains("bismuth:set-theme"));
    assert!(html.contains("bismuth:ready"));
    for presentato in ["blueprint", "her", "afterdark", "forest", "sky", "clays", "stones", "light", "dark"] {
        assert!(palette::get(presentato).is_some(), "presentato scheme {presentato} has no palette");
    }
}