webfont-generator 0.11.0

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

#[test]
fn regenerate_after_content_change_matches_fresh() {
    let dir = temp_dir();
    let a = write_icon(&dir, "a", D1);
    let b = write_icon(&dir, "b", D2);
    let c = write_icon(&dir, "c", D3);

    let mut result = generate(vec![a.clone(), b.clone(), c.clone()], true);
    write_icon(&dir, "b", D_CHANGED);
    result
        .regenerate(
            &crate::RegenerationFiles::Single(vec![a.clone(), b.clone(), c.clone()]),
            &[(b.clone(), GlyphChange::Changed { name: None })],
        )
        .unwrap();

    assert_same(&result, &generate(vec![a, b, c], false));
    std::fs::remove_dir_all(&dir).ok();
}

#[tokio::test]
async fn regenerate_async_matches_fresh_and_recovers_after_failure() {
    let dir = temp_dir();
    let a = write_icon(&dir, "a", D1);
    let b = write_icon(&dir, "b", D2);
    let files = vec![a.clone(), b.clone()];
    let result = generate(files.clone(), true);

    std::fs::remove_file(&b).unwrap();
    let error = match result
        .regenerate_async(
            crate::RegenerationFiles::Single(files.clone()),
            vec![(b.clone(), GlyphChange::Changed { name: None })],
        )
        .await
    {
        Err(error) => error,
        Ok(_) => panic!("missing changed file should fail"),
    };
    let result = error
        .into_result()
        .expect("ordinary failures are recoverable");

    write_icon(&dir, "b", D_CHANGED);
    let result = result
        .regenerate_async(
            crate::RegenerationFiles::Single(files.clone()),
            vec![(b, GlyphChange::Changed { name: None })],
        )
        .await
        .unwrap();
    assert_same(&result, &generate(files, false));
    std::fs::remove_dir_all(&dir).ok();
}

#[tokio::test]
async fn regenerate_all_async_matches_fresh() {
    let dir = temp_dir();
    let a = write_icon(&dir, "a", D1);
    let b = write_icon(&dir, "b", D2);
    let files = vec![a.clone(), b.clone()];
    let result = generate(files.clone(), true);

    write_icon(&dir, "b", D_CHANGED);
    let result = result
        .regenerate_all_async(crate::RegenerationFiles::Single(files.clone()))
        .await
        .unwrap();

    assert_same(&result, &generate(files, false));
    std::fs::remove_dir_all(&dir).ok();
}

#[test]
fn regenerate_after_add_matches_fresh() {
    let dir = temp_dir();
    let a = write_icon(&dir, "a", D1);
    let b = write_icon(&dir, "b", D2);

    let mut result = generate(vec![a.clone(), b.clone()], true);
    let c = write_icon(&dir, "c", D3);
    result
        .regenerate(
            &crate::RegenerationFiles::Single(vec![a.clone(), b.clone(), c.clone()]),
            &[(
                c.clone(),
                GlyphChange::Added {
                    name: Some("c".to_owned()),
                },
            )],
        )
        .unwrap();

    assert_same(&result, &generate(vec![a, b, c], false));
    std::fs::remove_dir_all(&dir).ok();
}

#[test]
fn regenerate_after_mid_order_add_matches_fresh() {
    let dir = temp_dir();
    let b = write_icon(&dir, "b", D2);
    let c = write_icon(&dir, "c", D3);

    let mut result = generate(vec![b.clone(), c.clone()], true);
    let a = write_icon(&dir, "a", D1);
    result
        .regenerate(
            &crate::RegenerationFiles::Single(vec![a.clone(), b.clone(), c.clone()]),
            &[(a.clone(), GlyphChange::Added { name: None })],
        )
        .unwrap();

    assert_same(&result, &generate(vec![a, b, c], false));
    std::fs::remove_dir_all(&dir).ok();
}

#[test]
fn regenerate_after_remove_matches_fresh() {
    let dir = temp_dir();
    let a = write_icon(&dir, "a", D1);
    let b = write_icon(&dir, "b", D2);
    let c = write_icon(&dir, "c", D3);

    let mut result = generate(vec![a.clone(), b.clone(), c.clone()], true);
    result
        .regenerate(
            &crate::RegenerationFiles::Single(vec![a.clone(), c.clone()]),
            &[(b, GlyphChange::Removed)],
        )
        .unwrap();

    assert_same(&result, &generate(vec![a, c], false));
    std::fs::remove_dir_all(&dir).ok();
}

#[test]
fn regenerate_all_after_content_change_matches_fresh() {
    let dir = temp_dir();
    let a = write_icon(&dir, "a", D1);
    let b = write_icon(&dir, "b", D2);
    let c = write_icon(&dir, "c", D3);

    let mut result = generate(vec![a.clone(), b.clone(), c.clone()], true);
    write_icon(&dir, "b", D_CHANGED);
    result
        .regenerate_all(&crate::RegenerationFiles::Single(vec![
            a.clone(),
            b.clone(),
            c.clone(),
        ]))
        .unwrap();

    assert_same(&result, &generate(vec![a, b, c], false));
    std::fs::remove_dir_all(&dir).ok();
}

#[test]
fn regenerate_all_after_add_and_remove_matches_fresh() {
    let dir = temp_dir();
    let a = write_icon(&dir, "a", D1);
    let b = write_icon(&dir, "b", D2);
    let c = write_icon(&dir, "c", D3);

    let mut result = generate(vec![a.clone(), b], true);
    result
        .regenerate_all(&crate::RegenerationFiles::Single(vec![
            a.clone(),
            c.clone(),
        ]))
        .unwrap();

    assert_same(&result, &generate(vec![a, c], false));
    std::fs::remove_dir_all(&dir).ok();
}

#[test]
fn regenerate_all_without_incremental_errors() {
    let dir = temp_dir();
    let a = write_icon(&dir, "a", D1);
    let mut result = generate(vec![a.clone()], false);
    let error = result
        .regenerate_all(&crate::RegenerationFiles::Single(vec![a]))
        .unwrap_err();

    assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);
    std::fs::remove_dir_all(&dir).ok();
}

#[test]
fn regenerate_without_incremental_errors() {
    let dir = temp_dir();
    let a = write_icon(&dir, "a", D1);
    let mut result = generate(vec![a.clone()], false);
    let changes = [(a.clone(), GlyphChange::Changed { name: None })];
    let error = result
        .regenerate(&crate::RegenerationFiles::Single(vec![a.clone()]), &changes)
        .unwrap_err();
    assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);
    std::fs::remove_dir_all(&dir).ok();
}

#[test]
fn regenerate_with_context_callback_state_errors() {
    let dir = temp_dir();
    let a = write_icon(&dir, "a", D1);
    let mut result = generate(vec![a.clone()], true);
    result.css_context = Some(Default::default());

    let error = result
        .regenerate(&crate::RegenerationFiles::Single(vec![a]), &[])
        .expect_err("regenerate must reject pre-mutated callback contexts");

    assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);
    assert!(error.to_string().contains("cssContext/htmlContext"));
    std::fs::remove_dir_all(&dir).ok();
}

#[test]
fn regenerate_failure_preserves_incremental_state_for_retry() {
    let dir = temp_dir();
    let a = write_icon(&dir, "a", D1);
    let b = write_icon(&dir, "b", D2);
    let c = dir.join("c.svg").to_string_lossy().into_owned();
    let mut result = generate(vec![a.clone(), b.clone()], true);
    let before = result.svg_string().unwrap().to_owned();
    let parse_count = with_regeneration_state(&result, |state| state.glyph_cache.parse_count);

    let error = result
        .regenerate(
            &crate::RegenerationFiles::Single(vec![a.clone(), b.clone(), c.clone()]),
            &[(
                c.clone(),
                GlyphChange::Added {
                    name: Some("c".to_owned()),
                },
            )],
        )
        .expect_err("missing added file should fail");
    assert_eq!(error.kind(), std::io::ErrorKind::NotFound);
    assert!(
        result.regeneration_state.lock().unwrap().is_some(),
        "failed regenerate must leave the incremental cache available"
    );
    assert_eq!(result.svg_string().unwrap(), before);
    assert_eq!(
        with_regeneration_state(&result, |state| state.glyph_cache.parse_count),
        parse_count,
        "failure before cache mutation should preserve the warm cache"
    );

    write_icon(&dir, "c", D3);
    result
        .regenerate(
            &crate::RegenerationFiles::Single(vec![a.clone(), b.clone(), c.clone()]),
            &[(
                c.clone(),
                GlyphChange::Added {
                    name: Some("c".to_owned()),
                },
            )],
        )
        .unwrap();

    assert_same(&result, &generate(vec![a, b, c], false));
    std::fs::remove_dir_all(&dir).ok();
}

#[test]
fn regenerate_rejects_duplicate_glyph_names() {
    let dir = temp_dir();
    let a = write_icon(&dir, "a", D1);
    let b = write_icon(&dir, "b", D2);
    let mut result = generate(vec![a.clone(), b.clone()], true);
    let before = result.svg_string().unwrap().to_owned();

    let error = result
        .regenerate(
            &crate::RegenerationFiles::Single(vec![a.clone(), b.clone()]),
            &[(
                b.clone(),
                GlyphChange::Changed {
                    name: Some("a".to_owned()),
                },
            )],
        )
        .expect_err("duplicate names should match fresh generate validation");

    assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);
    assert!(error.to_string().contains("must be unique"));
    assert!(result.regeneration_state.lock().unwrap().is_some());
    assert_eq!(result.svg_string().unwrap(), before);
    std::fs::remove_dir_all(&dir).ok();
}