webfont-generator 0.11.0

Generate webfonts (SVG, TTF, EOT, WOFF, WOFF2) from SVG icons
Documentation
use super::*;

fn generate_writing(paths: Vec<String>, dest: &Path) -> GenerateWebfontsResult {
    let mut resolved = resolve_generate_webfonts_options(GenerateWebfontsOptions {
        css: Some(true),
        dest: dest.to_string_lossy().into_owned(),
        files: paths,
        html: Some(false),
        font_name: Some("rc".to_owned()),
        format_options: Some(stable_format_options(false)),
        ligature: Some(false),
        incremental: Some(true),
        write_files: Some(true),
        types: Some(vec![FontType::Woff2]),
        ..Default::default()
    })
    .unwrap();
    let source_files = load(&resolved.files);
    finalize_generate_webfonts_options(&mut resolved, &source_files).unwrap();
    generate_webfonts_sync(resolved, source_files).unwrap()
}

#[test]
fn regeneration_writes_edited_templates_instead_of_carried_companions() {
    let dir = temp_dir();
    let dest = dir.join("out");
    let source = write_icon(&dir, "a", D1);
    let css_template = dir.join("style.hbs");
    let html_template = dir.join("page.hbs");
    std::fs::write(&css_template, "first css").unwrap();
    std::fs::write(&html_template, "old {{{styles}}}").unwrap();
    let mut result = crate::generate_sync(
        GenerateWebfontsOptions {
            files: vec![source.clone()],
            dest: dest.to_string_lossy().into_owned(),
            font_name: Some("rc".into()),
            types: Some(vec![FontType::Svg]),
            css: Some(true),
            html: Some(true),
            css_template: Some(css_template.to_string_lossy().into_owned()),
            html_template: Some(html_template.to_string_lossy().into_owned()),
            incremental: Some(true),
            write_files: Some(true),
            ..Default::default()
        },
        None,
    )
    .unwrap();
    let css = dest.join("rc.css");
    let html = dest.join("rc.html");
    assert_eq!(std::fs::read_to_string(&css).unwrap(), "first css");
    assert_eq!(std::fs::read_to_string(&html).unwrap(), "old first css");

    for (template, text, geometry, expected_css, expected_html) in [
        (
            &html_template,
            "new {{{styles}}}",
            D_CHANGED,
            "first css",
            "new first css",
        ),
        (
            &css_template,
            "second css",
            D1,
            "second css",
            "new second css",
        ),
    ] {
        std::fs::write(template, text).unwrap();
        // A real source edit triggers regeneration; template-only watching is not supported.
        write_icon(&dir, "a", geometry);
        result
            .regenerate(
                &crate::RegenerationFiles::Single(vec![source.clone()]),
                &[(source.clone(), GlyphChange::Changed { name: None })],
            )
            .unwrap();
        assert_eq!(std::fs::read_to_string(&css).unwrap(), expected_css);
        assert_eq!(std::fs::read_to_string(&html).unwrap(), expected_html);
        assert_eq!(result.generate_css_pure(None).unwrap(), expected_css);
        assert_eq!(result.generate_html_pure(None).unwrap(), expected_html);
    }
    std::fs::remove_dir_all(dir).unwrap();
}

#[test]
fn regenerate_writes_changed_outputs_and_skips_unchanged() {
    let dir = temp_dir();
    let dest = dir.join("out");
    let a = write_icon(&dir, "a", D1);
    let b = write_icon(&dir, "b", D2);
    let files = vec![a.clone(), b.clone()];

    let mut result = generate_writing(files.clone(), &dest);
    let woff2_path = dest.join("rc.woff2");
    let css_path = dest.join("rc.css");

    // This helper builds in memory only, so this first regenerate performs the initial write.
    write_icon(&dir, "b", D_CHANGED);
    result
        .regenerate(
            &crate::RegenerationFiles::Single(files.clone()),
            &[(b.clone(), GlyphChange::Changed { name: None })],
        )
        .unwrap();

    let on_disk = std::fs::read(&woff2_path).unwrap();
    assert_eq!(on_disk.as_slice(), result.woff2_bytes().unwrap());
    let fresh = generate_with_css(files.clone(), false);
    assert_eq!(on_disk.as_slice(), fresh.woff2_bytes().unwrap());
    assert!(css_path.exists(), "CSS is written to disk too");

    // Re-running with no real change reproduces identical bytes, so the write is skipped: a
    // deleted output is NOT recreated.
    std::fs::remove_file(&woff2_path).unwrap();
    result
        .regenerate(
            &crate::RegenerationFiles::Single(files.clone()),
            &[(b.clone(), GlyphChange::Changed { name: None })],
        )
        .unwrap();
    assert!(
        !woff2_path.exists(),
        "an unchanged output must not be rewritten"
    );

    std::fs::remove_dir_all(&dir).ok();
}

#[test]
fn regenerate_write_failure_restores_published_output() {
    let dir = temp_dir();
    let dest = dir.join("blocked-output");
    let a = write_icon(&dir, "a", D1);
    let b = write_icon(&dir, "b", D2);
    let files = vec![a.clone(), b.clone()];
    let mut result = generate_writing(files.clone(), &dest);
    let before = result.woff2_bytes().unwrap().to_vec();

    std::fs::write(&dest, "not a directory").unwrap();
    write_icon(&dir, "b", D_CHANGED);
    result
        .regenerate(
            &crate::RegenerationFiles::Single(files.clone()),
            &[(b.clone(), GlyphChange::Changed { name: None })],
        )
        .expect_err("writing below a file must fail");
    assert_eq!(result.woff2_bytes().unwrap(), before);

    std::fs::remove_file(&dest).unwrap();
    result
        .regenerate(
            &crate::RegenerationFiles::Single(files.clone()),
            &[(b, GlyphChange::Changed { name: None })],
        )
        .unwrap();
    assert_same(&result, &generate_with_css(files, false));
    std::fs::remove_dir_all(&dir).ok();
}

#[test]
fn initial_write_seeds_skip_map_for_first_regenerate() {
    let dir = temp_dir();
    let dest = dir.join("out");
    let a = write_icon(&dir, "a", D1);
    let b = write_icon(&dir, "b", D2);
    let files = vec![a.clone(), b.clone()];

    let mut result = crate::generate_sync(
        GenerateWebfontsOptions {
            css: Some(false),
            dest: dest.to_string_lossy().into_owned(),
            files: files.clone(),
            html: Some(false),
            font_name: Some("rc".to_owned()),
            format_options: Some(stable_format_options(false)),
            ligature: Some(false),
            incremental: Some(true),
            write_files: Some(true),
            types: Some(vec![FontType::Woff2]),
            ..Default::default()
        },
        None,
    )
    .unwrap();

    let woff2_path = dest.join("rc.woff2");
    assert!(woff2_path.exists(), "the initial build wrote the font");

    // No real change -> identical output -> skipped because the initial write seeded the hash.
    std::fs::remove_file(&woff2_path).unwrap();
    result
        .regenerate(
            &crate::RegenerationFiles::Single(files.clone()),
            &[(b.clone(), GlyphChange::Changed { name: None })],
        )
        .unwrap();
    assert!(
        !woff2_path.exists(),
        "first regenerate must skip an output unchanged since the seeded initial write"
    );

    std::fs::remove_dir_all(&dir).ok();
}