use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
const EXE: &str = env!("CARGO_BIN_EXE_uncad");
const CORPUS_DWG: &str = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../lib/libredwg/test/test-data/2000/circle.dwg"
);
const CORPUS_DXF: &str = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../lib/libredwg/test/test-data/2000/entities-2d.dxf"
);
struct TempFile(PathBuf);
impl Drop for TempFile {
fn drop(&mut self) {
let _ = fs::remove_file(&self.0);
}
}
impl TempFile {
fn new(name: &str) -> Self {
TempFile(std::env::temp_dir().join(format!("uncad-cli-{}-{}", std::process::id(), name)))
}
fn path(&self) -> &Path {
&self.0
}
fn arg(&self) -> &str {
self.0.to_str().expect("temp paths are UTF-8 here")
}
fn bytes(&self) -> Vec<u8> {
fs::read(&self.0).expect("the command reported success, so the file should exist")
}
}
fn run(args: &[&str]) -> Output {
Command::new(EXE)
.args(args)
.output()
.expect("the test binary should be runnable")
}
fn png_size(bytes: &[u8]) -> (u32, u32) {
assert!(
bytes.starts_with(b"\x89PNG\r\n\x1a\n"),
"not a PNG: {:?}",
&bytes[..bytes.len().min(8)]
);
let read = |at: usize| u32::from_be_bytes(bytes[at..at + 4].try_into().unwrap());
(read(16), read(20))
}
#[test]
fn prints_a_summary_when_no_output_is_requested() {
let out = run(&[CORPUS_DXF]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("LINE"),
"summary should list entity types: {stdout}"
);
}
#[test]
fn writes_an_svg() {
let svg = TempFile::new("out.svg");
let out = run(&[CORPUS_DXF, "-o", svg.arg()]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let written = String::from_utf8(svg.bytes()).expect("SVG is text");
assert!(written.contains("<svg"), "not an SVG document: {written}");
}
#[test]
fn writes_a_png() {
let png = TempFile::new("out.png");
let out = run(&[CORPUS_DXF, "-o", png.arg()]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let (w, h) = png_size(&png.bytes());
assert!(w > 0 && h > 0, "degenerate PNG: {w}x{h}");
}
#[test]
fn scale_actually_scales_the_raster() {
let one = TempFile::new("scale1.png");
let two = TempFile::new("scale2.png");
assert!(run(&[CORPUS_DXF, "-o", one.arg()]).status.success());
assert!(run(&[CORPUS_DXF, "-o", two.arg(), "--scale", "2"])
.status
.success());
let (w1, h1) = png_size(&one.bytes());
let (w2, h2) = png_size(&two.bytes());
assert!(
w2 > w1 && h2 > h1,
"--scale 2 should enlarge the raster: {w1}x{h1} -> {w2}x{h2}"
);
}
#[test]
fn writes_json() {
let json = TempFile::new("out.json");
let out = run(&[CORPUS_DXF, "-o", json.arg()]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let value: serde_json::Value =
serde_json::from_slice(&json.bytes()).expect("the output should be valid JSON");
let entities = value["entities"]
.as_array()
.expect("`entities` should be an array");
assert!(
!entities.is_empty(),
"a drawing with contents should export at least one entity"
);
assert!(value["tables"].is_object(), "`tables` should be an object");
assert!(
entities.iter().all(|e| e["type"].is_string()),
"every entity should carry a string `type`: {value}"
);
assert!(
entities.iter().any(|e| e["type"] == "LINE"),
"entities-2d.dxf contains LINE entities: {value}"
);
}
#[test]
fn writes_json_from_a_dwg_too() {
let json = TempFile::new("from-dwg.json");
let out = run(&[CORPUS_DWG, "-o", json.arg()]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let value: serde_json::Value =
serde_json::from_slice(&json.bytes()).expect("the output should be valid JSON");
assert!(
value["entities"]
.as_array()
.is_some_and(|entities| !entities.is_empty()),
"circle.dwg should export at least one entity: {value}"
);
let block_records = value["tables"]["block_records"]
.as_object()
.expect("`tables.block_records` should be an object");
let model_space = block_records
.iter()
.find(|(name, _)| name.eq_ignore_ascii_case("*MODEL_SPACE"))
.map(|(_, record)| record)
.expect("a model-space block record should be exported");
assert!(
model_space["entities"]
.as_array()
.is_some_and(|entities| !entities.is_empty()),
"model space should carry its entities: {model_space}"
);
}
#[test]
fn pretty_changes_the_layout_but_not_the_content() {
let compact = TempFile::new("compact.json");
let pretty = TempFile::new("pretty.json");
assert!(run(&[CORPUS_DXF, "-o", compact.arg()]).status.success());
assert!(run(&[CORPUS_DXF, "-o", pretty.arg(), "--pretty"])
.status
.success());
let compact_bytes = compact.bytes();
let pretty_bytes = pretty.bytes();
assert!(
!compact_bytes.contains(&b'\n'),
"the default should be a single line"
);
assert!(
pretty_bytes.contains(&b'\n'),
"--pretty should indent across lines"
);
let a: serde_json::Value = serde_json::from_slice(&compact_bytes).expect("valid JSON");
let b: serde_json::Value = serde_json::from_slice(&pretty_bytes).expect("valid JSON");
assert_eq!(a, b, "--pretty should change only whitespace");
}
#[test]
fn every_documented_space_is_accepted() {
for space in ["model", "paper", "all"] {
let svg = TempFile::new(&format!("space-{space}.svg"));
let out = run(&[CORPUS_DXF, "-o", svg.arg(), "--space", space]);
assert!(
out.status.success(),
"--space {space} failed: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(String::from_utf8_lossy(&svg.bytes()).contains("<svg"));
}
}
#[test]
fn rejects_a_space_that_is_not_documented() {
let svg = TempFile::new("bad-space.svg");
let out = run(&[CORPUS_DXF, "-o", svg.arg(), "--space", "sideways"]);
assert!(!out.status.success(), "an unknown --space should fail");
assert!(!svg.path().exists(), "a rejected run should write nothing");
}
#[test]
fn rejects_a_scale_that_is_not_a_positive_number() {
for bad in ["0", "-1", "abc"] {
let png = TempFile::new(&format!("bad-scale-{bad}.png"));
let out = run(&[CORPUS_DXF, "-o", png.arg(), "--scale", bad]);
assert!(!out.status.success(), "--scale {bad} should be rejected");
assert!(!png.path().exists(), "a rejected run should write nothing");
}
}
#[test]
fn rejects_an_output_extension_it_cannot_write() {
for name in ["out.pdf", "out.dxf", "out.dwg"] {
let other = TempFile::new(name);
let out = run(&[CORPUS_DXF, "-o", other.arg()]);
assert!(!out.status.success(), "-o {name} should be refused");
assert!(
!other.path().exists(),
"a refused run should write nothing for {name}"
);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains(".json, .svg, .png"),
"the refusal should list what is supported: {stderr}"
);
}
}
#[test]
fn reports_a_missing_input_without_a_raw_library_code() {
let missing = std::env::temp_dir().join("uncad-cli-definitely-not-here.dwg");
let out = run(&[missing.to_str().unwrap()]);
assert!(!out.status.success());
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
!stderr.contains("4096"),
"a missing file should not report a raw library code: {stderr}"
);
}
#[test]
fn fit_sets_the_longer_side() {
let png = TempFile::new("fit.png");
let out = run(&[CORPUS_DXF, "-o", png.arg(), "--fit", "300"]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let (w, h) = png_size(&png.bytes());
assert_eq!(w.max(h), 300, "{w}x{h}");
}
#[test]
fn an_image_past_the_limit_is_refused_with_the_options_that_fix_it() {
let png = TempFile::new("limit.png");
let out = run(&[
CORPUS_DXF,
"-o",
png.arg(),
"--fit",
"300",
"--max-edge",
"100",
]);
assert!(!out.status.success());
let stderr = String::from_utf8_lossy(&out.stderr);
for flag in ["--fit", "--scale", "--max-edge"] {
assert!(stderr.contains(flag), "should name {flag}: {stderr}");
}
assert!(!png.path().exists(), "nothing is written");
let out = run(&[
CORPUS_DXF,
"-o",
png.arg(),
"--fit",
"300",
"--max-edge",
"300",
]);
assert!(out.status.success());
}
#[test]
fn background_and_stroke_change_the_raster() {
let plain = TempFile::new("plain.png");
let clear = TempFile::new("clear.png");
let thick = TempFile::new("thick.png");
assert!(run(&[CORPUS_DXF, "-o", plain.arg()]).status.success());
assert!(
run(&[CORPUS_DXF, "-o", clear.arg(), "--background", "transparent"])
.status
.success()
);
assert!(run(&[CORPUS_DXF, "-o", thick.arg(), "--stroke", "5"])
.status
.success());
assert_ne!(plain.bytes(), clear.bytes(), "--background had no effect");
assert_ne!(plain.bytes(), thick.bytes(), "--stroke had no effect");
}
#[test]
fn padding_widens_the_picture() {
let plain = TempFile::new("plain.svg");
let padded = TempFile::new("padded.svg");
assert!(run(&[CORPUS_DXF, "-o", plain.arg()]).status.success());
assert!(run(&[CORPUS_DXF, "-o", padded.arg(), "--padding", "50"])
.status
.success());
assert_ne!(plain.bytes(), padded.bytes(), "--padding had no effect");
}
#[test]
fn an_svg_stroke_is_pixels_on_the_screen() {
let plain = TempFile::new("stroke-plain.svg");
let screen = TempFile::new("stroke-screen.svg");
assert!(run(&[CORPUS_DXF, "-o", plain.arg()]).status.success());
let out = run(&[CORPUS_DXF, "-o", screen.arg(), "--stroke", "1.5"]);
assert!(
out.status.success(),
"{}",
String::from_utf8_lossy(&out.stderr)
);
let screen = String::from_utf8(screen.bytes()).unwrap();
assert!(
screen.contains("<style>*{vector-effect:non-scaling-stroke;stroke-width:1.5px}</style>"),
"{}",
&screen[..screen.len().min(400)]
);
assert!(!String::from_utf8(plain.bytes())
.unwrap()
.contains("<style>"));
}
#[test]
fn an_option_the_output_has_no_use_for_is_refused_by_name() {
for (output, args, named) in [
("x.svg", &["--scale", "2"][..], "--scale"),
(
"x.svg",
&["--fit", "300", "--background", "white"],
"--fit, --background",
),
("x.json", &["--space", "paper"], "--space"),
("x.png", &["--pretty"], "--pretty"),
] {
let file = TempFile::new(output);
let mut all = vec![CORPUS_DXF, "-o", file.arg()];
all.extend_from_slice(args);
let out = run(&all);
assert!(!out.status.success(), "{output} {args:?} should be refused");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(stderr.contains(named), "{output} {args:?}: {stderr}");
assert!(
!std::path::Path::new(file.arg()).exists(),
"{output} written"
);
}
let summary = run(&[CORPUS_DXF, "--stroke", "2"]);
assert!(!summary.status.success());
assert!(String::from_utf8_lossy(&summary.stderr).contains("the summary"));
let png = TempFile::new("ok.png");
assert!(run(&[
CORPUS_DXF,
"-o",
png.arg(),
"--stroke",
"2",
"--paper",
"dark"
])
.status
.success());
}
#[test]
fn rejects_scale_and_fit_together_and_bad_values() {
for args in [
&["--scale", "2", "--fit", "300"][..],
&["--fit", "0"],
&["--max-edge", "-1"],
&["--stroke", "0"],
&["--background", "black"],
&["--padding", "-1"],
] {
let png = TempFile::new("bad.png");
let mut all = vec![CORPUS_DXF, "-o", png.arg()];
all.extend_from_slice(args);
let out = run(&all);
assert!(!out.status.success(), "{args:?} should be refused");
}
}
#[test]
fn help_exits_successfully_and_lists_the_options() {
let out = run(&["--help"]);
assert!(out.status.success(), "--help should exit 0");
assert!(
out.stderr.is_empty(),
"{}",
String::from_utf8_lossy(&out.stderr)
);
let bare = run(&[]);
assert!(!bare.status.success());
assert!(bare.stdout.is_empty());
assert!(String::from_utf8_lossy(&bare.stderr).contains("Usage:"));
for verb in [&["summarize", "--help"][..], &["export", "--help"][..]] {
let out = run(verb);
assert!(out.status.success(), "{verb:?}");
assert!(!out.stdout.is_empty() && out.stderr.is_empty(), "{verb:?}");
}
let text = String::from_utf8_lossy(&out.stdout);
for flag in [
"--space",
"--scale",
"--no-trim",
"--pretty",
"--padding",
"--fit",
"--max-edge",
"--stroke",
"--background",
"--window",
"--paper",
] {
assert!(text.contains(flag), "usage should document {flag}: {text}");
}
}
#[test]
fn no_arguments_is_a_usage_error() {
let out = run(&[]);
assert!(
!out.status.success(),
"invoking with no input should fail, not exit 0"
);
}
#[test]
fn space_paper_renders_something_different_from_space_model() {
let model = TempFile::new("differential-model.svg");
let paper = TempFile::new("differential-paper.svg");
assert!(run(&[CORPUS_DXF, "-o", model.arg(), "--space", "model"])
.status
.success());
assert!(run(&[CORPUS_DXF, "-o", paper.arg(), "--space", "paper"])
.status
.success());
let model_svg = String::from_utf8(model.bytes()).expect("SVG is text");
let paper_svg = String::from_utf8(paper.bytes()).expect("SVG is text");
assert!(
model_svg.contains("<line") || model_svg.contains("<path"),
"model space of entities-2d.dxf should carry geometry: {model_svg}"
);
assert_ne!(
model_svg, paper_svg,
"--space paper should not render the same document as --space model"
);
}
fn dxf_with_an_outlier() -> String {
fn line(x1: f64, y1: f64, x2: f64, y2: f64) -> String {
format!(
" 0\nLINE\n 8\n0\n 10\n{x1}\n 20\n{y1}\n 30\n0.0\n 11\n{x2}\n 21\n{y2}\n 31\n0.0\n"
)
}
let mut dxf = String::from(" 0\nSECTION\n 2\nENTITIES\n");
dxf.push_str(&line(0.0, 0.0, 10.0, 0.0));
dxf.push_str(&line(10.0, 0.0, 10.0, 10.0));
dxf.push_str(&line(10.0, 10.0, 0.0, 10.0));
dxf.push_str(&line(0.0, 10.0, 0.0, 0.0));
dxf.push_str(&line(1_000_000.0, 1_000_000.0, 1_000_001.0, 1_000_001.0));
dxf.push_str(" 0\nENDSEC\n 0\nEOF\n");
dxf
}
fn view_box(svg: &[u8]) -> String {
let text = String::from_utf8_lossy(svg);
let start = text
.find("viewBox=\"")
.expect("the SVG root should carry a viewBox")
+ "viewBox=\"".len();
let end = text[start..]
.find('"')
.expect("the viewBox attribute should be closed")
+ start;
text[start..end].to_string()
}
#[test]
fn no_trim_keeps_the_outlier_inside_the_viewbox() {
let input = TempFile::new("outlier.dxf");
fs::write(input.path(), dxf_with_an_outlier()).expect("temp dir should be writable");
let trimmed = TempFile::new("trimmed.svg");
let untrimmed = TempFile::new("untrimmed.svg");
let out = run(&[input.arg(), "-o", trimmed.arg()]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let out = run(&[input.arg(), "-o", untrimmed.arg(), "--no-trim"]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let width = |vb: &str| -> f64 {
vb.split_whitespace()
.nth(2)
.and_then(|w| w.parse().ok())
.expect("viewBox has four numbers")
};
let vb_trimmed = view_box(&trimmed.bytes());
let vb_untrimmed = view_box(&untrimmed.bytes());
assert!(
width(&vb_trimmed) < 1_000.0,
"the default should trim the outlier: viewBox {vb_trimmed}"
);
assert!(
width(&vb_untrimmed) > 1_000_000.0,
"--no-trim should keep the outlier inside the viewBox: {vb_untrimmed}"
);
}
const CORPUS_WITH_A_GIANT: &str = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../lib/libredwg/test/test-data/example_2000.dwg"
);
#[test]
fn the_default_frame_sets_a_giant_aside_and_names_it() {
let framed = TempFile::new("giant-default.svg");
let untrimmed = TempFile::new("giant-no-trim.svg");
let out = run(&[CORPUS_WITH_A_GIANT, "-o", framed.arg()]);
assert!(out.status.success());
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("farther than the rest of the drawing") && stderr.contains("INSERT "),
"the default should name what it set aside: {stderr}"
);
let out = run(&[CORPUS_WITH_A_GIANT, "-o", untrimmed.arg(), "--no-trim"]);
assert!(out.status.success());
assert!(
!String::from_utf8_lossy(&out.stderr).contains("farther than the rest of the drawing"),
"--no-trim sets nothing aside"
);
let width = |vb: &str| -> f64 {
vb.split_whitespace()
.nth(2)
.and_then(|w| w.parse().ok())
.expect("viewBox has four numbers")
};
let framed = width(&view_box(&framed.bytes()));
let untrimmed = width(&view_box(&untrimmed.bytes()));
assert!(framed < 100_000.0, "default viewBox width {framed}");
assert!(
untrimmed > 1_000_000.0,
"--no-trim viewBox width {untrimmed}"
);
}
#[test]
fn version_and_the_parsers_refusals() {
let run = |args: &[&str]| {
std::process::Command::new(EXE)
.args(args)
.output()
.expect("the binary runs")
};
let v = run(&["--version"]);
assert!(v.status.success());
assert_eq!(
String::from_utf8_lossy(&v.stdout).trim(),
format!("uncad {}", env!("CARGO_PKG_VERSION"))
);
for (args, needle) in [
(&["x.dwg", "--bogus"][..], "unknown option '--bogus'"),
(&["--bogus", "x.dwg"][..], "unknown option '--bogus'"),
(&["x.dwg", "y.dwg"][..], "unexpected argument 'y.dwg'"),
(&["x.dwg", "-o"][..], "-o needs a value"),
] {
let out = run(args);
assert!(!out.status.success(), "{args:?} should fail");
let err = String::from_utf8_lossy(&out.stderr);
assert!(err.contains(needle), "{args:?}: {err}");
}
}
#[test]
fn include_hidden_draws_more() {
let input = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../uncad/tests/fixtures/hidden_layers_r2000.dxf"
);
let dir = std::env::temp_dir().join(format!("uncad-cli-hidden-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let render = |name: &str, extra: &[&str]| {
let path = dir.join(name);
let out = std::process::Command::new(EXE)
.arg(input)
.arg("-o")
.arg(&path)
.args(extra)
.output()
.expect("the binary runs");
assert!(
out.status.success(),
"{}",
String::from_utf8_lossy(&out.stderr)
);
std::fs::read_to_string(&path).unwrap()
};
let plain = render("plain.svg", &[]);
let all = render("all.svg", &["--include-hidden"]);
assert!(all.len() > plain.len(), "{} vs {}", all.len(), plain.len());
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn export_writes_a_package_and_refuses_unknown_options() {
let input = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../lib/libredwg/test/test-data/example_2000.dwg"
);
let dir = std::env::temp_dir().join(format!("uncad-cli-export-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
let out = std::process::Command::new(EXE)
.args(["export", input, "-o"])
.arg(&dir)
.args(["--max-levels", "0"])
.output()
.expect("the binary runs");
assert!(
out.status.success(),
"{}",
String::from_utf8_lossy(&out.stderr)
);
assert!(dir.join("manifest.json").is_file());
assert!(dir.join("overview.png").is_file());
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("non-fatal problems (UNHANDLEDCLASS"),
"{stderr}"
);
let _ = std::fs::remove_dir_all(&dir);
let bad = std::process::Command::new(EXE)
.args(["export", input, "--bogus"])
.output()
.expect("the binary runs");
assert!(!bad.status.success());
assert!(String::from_utf8_lossy(&bad.stderr).contains("unknown option '--bogus'"));
let run = |args: &[&str]| {
let out = std::process::Command::new(EXE)
.args(args)
.output()
.expect("the binary runs");
assert!(!out.status.success(), "{args:?} should fail");
String::from_utf8_lossy(&out.stderr).into_owned()
};
let missing = run(&["export", "no-such-drawing.dwg", "-o", "unused"]);
assert!(
missing.contains("cannot open input file 'no-such-drawing.dwg'"),
"{missing}"
);
let not_a_drawing = run(&[
"export",
concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml"),
"-o",
"unused",
]);
assert!(
not_a_drawing.contains("is it a valid DWG/DXF file?"),
"{not_a_drawing}"
);
let profile = run(&["export", input, "--profile", "gemini", "-o", "unused"]);
assert!(
profile.contains("unknown profile 'gemini' (one of: claude, claude-hires, openai-patch)"),
"{profile}"
);
let version = std::process::Command::new(EXE)
.args(["export", "--version"])
.output()
.expect("the binary runs");
assert!(version.status.success());
assert_eq!(
String::from_utf8_lossy(&version.stdout).trim(),
format!("uncad {}", env!("CARGO_PKG_VERSION"))
);
}
const G1: &str = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../uncad/tests/golden/g1.expected.json"
);
fn view_box_numbers(svg: &[u8]) -> Vec<f64> {
view_box(svg)
.split(' ')
.map(|n| n.parse().expect("a viewBox number"))
.collect()
}
#[test]
fn window_frames_exactly_the_rectangle_asked_for_with_the_padding() {
let svg = TempFile::new("window.svg");
let out = run(&[
G1,
"-o",
svg.arg(),
"--window",
"10,15,60,40",
"--padding",
"2",
]);
assert!(
out.status.success(),
"{}",
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(
view_box_numbers(&svg.bytes()),
[10.0 - 2.0, -(40.0 + 2.0), 50.0 + 4.0, 25.0 + 4.0]
);
}
#[test]
fn rejects_a_window_that_is_not_a_rectangle_and_a_paper_it_does_not_know() {
for args in [
&["--window", "1,2,3"][..],
&["--window", "1,2,3,4,5"],
&["--window", "0,0,10,nan"],
&["--window", "0,0,inf,10"],
&["--window", "10,0,5,10"],
&["--window", "0,0,0,10"],
&["--window", "0,10,10,10"],
&["--window", "0,0,10,10", "--no-trim"],
&["--paper", "grey"],
] {
let svg = TempFile::new("bad.svg");
let mut all = vec![G1, "-o", svg.arg()];
all.extend_from_slice(args);
let out = run(&all);
assert!(!out.status.success(), "{args:?} should be refused");
assert!(!svg.path().exists(), "{args:?} wrote a file");
}
}
#[test]
fn a_dark_paper_starts_the_document_with_a_black_page() {
let light = TempFile::new("light.svg");
let dark = TempFile::new("dark.svg");
assert!(run(&[G1, "-o", light.arg()]).status.success());
assert!(run(&[G1, "-o", dark.arg(), "--paper", "dark"])
.status
.success());
let text = String::from_utf8(dark.bytes()).unwrap();
let first = text.lines().nth(1).unwrap().trim();
let expected: Vec<String> = view_box_numbers(&dark.bytes())
.iter()
.map(|n| n.to_string())
.collect();
assert_eq!(
first,
format!(
"<rect x=\"{}\" y=\"{}\" width=\"{}\" height=\"{}\" fill=\"#000000\" stroke=\"none\"/>",
expected[0], expected[1], expected[2], expected[3]
)
);
assert!(!String::from_utf8(light.bytes()).unwrap().contains("<rect"));
let light_png = TempFile::new("light.png");
let dark_png = TempFile::new("dark.png");
assert!(run(&[G1, "-o", light_png.arg()]).status.success());
assert!(run(&[G1, "-o", dark_png.arg(), "--paper", "dark"])
.status
.success());
assert_ne!(light_png.bytes(), dark_png.bytes(), "--paper had no effect");
}