use std::fs;
use std::path::Path;
fn optimize_file(path: &Path) -> (String, usize, usize) {
let input = fs::read_to_string(path).unwrap();
let input_size = input.len();
let result = svgm_core::optimize(&input).unwrap();
let output_size = result.data.len();
(result.data, input_size, output_size)
}
fn assert_valid_svg(output: &str, source: &str) {
assert!(
output.trim_start().starts_with("<svg"),
"{source}: output does not start with <svg"
);
assert!(
output.trim_end().ends_with("</svg>"),
"{source}: output does not end with </svg>"
);
svgm_core::parser::parse(output)
.unwrap_or_else(|e| panic!("{source}: output is not valid XML: {e}"));
}
#[test]
fn synthetic_comments_and_metadata() {
let (output, input_size, output_size) = optimize_file(Path::new(
"tests/fixtures/synthetic/comments_and_metadata.svg",
));
assert_valid_svg(&output, "comments_and_metadata");
assert!(!output.contains("<!--"), "comments should be removed");
assert!(!output.contains("<metadata"), "metadata should be removed");
assert!(
!output.contains("inkscape"),
"inkscape data should be removed"
);
assert!(
!output.contains("sodipodi"),
"sodipodi data should be removed"
);
assert!(!output.contains("><g></g>"), "empty <g> should be removed");
assert!(output.contains("fill=\"red\"") || output.contains("fill=\"#f00\""));
assert!(
!output.contains("50.000"),
"trailing zeros should be removed"
);
assert!(
!output.contains("class=\"\""),
"empty class should be removed"
);
assert!(
output_size < input_size * 80 / 100,
"should be at least 20% smaller: {input_size} -> {output_size}"
);
}
#[test]
fn synthetic_nested_empty_groups() {
let (output, _, _) = optimize_file(Path::new(
"tests/fixtures/synthetic/nested_empty_groups.svg",
));
assert_valid_svg(&output, "nested_empty_groups");
assert!(
output.contains("<rect") || output.contains("<path"),
"rect (or its path equivalent) should be preserved"
);
let g_count = output.matches("<g>").count() + output.matches("<g ").count();
assert_eq!(
g_count, 0,
"all groups should be collapsed, found {g_count}"
);
}
#[test]
fn synthetic_colors_and_numbers() {
let (output, _, _) =
optimize_file(Path::new("tests/fixtures/synthetic/colors_and_numbers.svg"));
assert_valid_svg(&output, "colors_and_numbers");
assert!(!output.contains("500.000px"), "should strip px units");
assert!(!output.contains("500.000"), "should strip trailing zeros");
assert!(!output.contains("rgb("), "rgb() should be converted");
assert!(output.contains("#fff") || output.contains("white"));
assert!(output.contains("#000") || output.contains("black"));
}
#[test]
fn synthetic_empty_text_elements() {
let (output, _, _) = optimize_file(Path::new(
"tests/fixtures/synthetic/empty_text_elements.svg",
));
assert_valid_svg(&output, "empty_text_elements");
assert!(output.contains("Hello world"));
}
#[test]
fn synthetic_preserves_animation() {
let (output, _, _) = optimize_file(Path::new(
"tests/fixtures/synthetic/preserves_animation.svg",
));
assert_valid_svg(&output, "preserves_animation");
assert!(output.contains("<animate"), "animate should be preserved");
assert!(
output.contains("<animateTransform"),
"animateTransform should be preserved"
);
}
#[test]
fn regression_symbol_use_ref() {
let (output, _, _) = optimize_file(Path::new("tests/fixtures/regression/symbol_use_ref.svg"));
assert_valid_svg(&output, "symbol_use_ref");
assert!(output.contains("<symbol"), "symbol should be preserved");
assert!(output.contains("<use"), "use should be preserved");
assert!(
output.contains("href=\"#"),
"use href reference should be preserved"
);
}
#[test]
fn regression_foreign_object() {
let (output, _, _) = optimize_file(Path::new("tests/fixtures/regression/foreign_object.svg"));
assert_valid_svg(&output, "foreign_object");
assert!(
output.contains("<foreignObject"),
"foreignObject should be preserved"
);
assert!(output.contains("Hello world"));
}
#[test]
fn path_torture_fixtures() {
let fixture_dir = Path::new("tests/fixtures/regression/path_torture");
assert!(
fixture_dir.exists(),
"path_torture fixture directory missing"
);
let mut count = 0;
for entry in fs::read_dir(fixture_dir).unwrap() {
let path = entry.unwrap().path();
if path.extension().map_or(false, |e| e == "svg") {
let name = path.file_name().unwrap().to_str().unwrap().to_string();
let (output, _input_size, _output_size) = optimize_file(&path);
assert_valid_svg(&output, &name);
let result2 = svgm_core::optimize(&output).unwrap();
assert_eq!(
output, result2.data,
"{name}: not converged after second optimization"
);
svgm_core::parser::parse(&output)
.unwrap_or_else(|e| panic!("{name}: optimized output not parseable: {e}"));
count += 1;
}
}
assert!(
count >= 15,
"expected at least 15 path torture fixtures, found {count}"
);
}
#[test]
fn path_torture_structural_equivalence() {
let fixture_dir = Path::new("tests/fixtures/regression/path_torture");
assert!(
fixture_dir.exists(),
"path_torture fixture directory missing"
);
for entry in fs::read_dir(fixture_dir).unwrap() {
let path = entry.unwrap().path();
if path.extension().map_or(false, |e| e == "svg") {
let name = path.file_name().unwrap().to_str().unwrap().to_string();
let input = fs::read_to_string(&path).unwrap();
let output = svgm_core::optimize(&input).unwrap().data;
assert!(!output.is_empty(), "{name}: output should not be empty");
}
}
}
#[test]
fn real_svgs_parse_and_optimize() {
let fixture_dir = Path::new("tests/fixtures/real");
if !fixture_dir.exists() {
return;
}
for entry in fs::read_dir(fixture_dir).unwrap() {
let path = entry.unwrap().path();
if path.extension().map_or(false, |e| e == "svg") {
let (output, input_size, output_size) = optimize_file(&path);
let name = path.file_name().unwrap().to_str().unwrap();
assert_valid_svg(&output, name);
assert!(
output_size <= input_size,
"{name}: output ({output_size}) larger than input ({input_size})"
);
let result2 = svgm_core::optimize(&output).unwrap();
assert_eq!(
output, result2.data,
"{name}: second optimization produced different output (not converged)"
);
}
}
}