max / ripgrow
1 file changed,
+59 insertions,
-11 deletions
| @@ -59,10 +59,13 @@ pub fn export_to_pdf( | |||
| 59 | 59 | fs::write(&data_path, render_json(session))?; | |
| 60 | 60 | fs::write(&template_path, TEMPLATE)?; | |
| 61 | 61 | ||
| 62 | + | // Typst confines file reads to the template's root and treats any path | |
| 63 | + | // passed via --input as relative to it, so hand the template a bare | |
| 64 | + | // filename; both live in the same scratch dir. | |
| 62 | 65 | let status = Command::new("typst") | |
| 63 | 66 | .arg("compile") | |
| 64 | 67 | .arg("--input") | |
| 65 | - | .arg(format!("data-path={}", data_path.display())) | |
| 68 | + | .arg("data-path=data.json") | |
| 66 | 69 | .arg(&template_path) | |
| 67 | 70 | .arg(&output_path) | |
| 68 | 71 | .output(); | |
| @@ -291,18 +294,63 @@ mod tests { | |||
| 291 | 294 | assert!(out.contains(r#"back\\slash"#)); | |
| 292 | 295 | } | |
| 293 | 296 | ||
| 297 | + | fn typst_available() -> bool { | |
| 298 | + | std::process::Command::new("typst") | |
| 299 | + | .arg("--version") | |
| 300 | + | .output() | |
| 301 | + | .map(|o| o.status.success()) | |
| 302 | + | .unwrap_or(false) | |
| 303 | + | } | |
| 304 | + | ||
| 305 | + | #[test] | |
| 306 | + | fn export_produces_pdf_end_to_end() { | |
| 307 | + | if !typst_available() { | |
| 308 | + | eprintln!("skipping: typst not on PATH"); | |
| 309 | + | return; | |
| 310 | + | } | |
| 311 | + | let s = SessionExport { | |
| 312 | + | profile: "self".into(), | |
| 313 | + | date: NaiveDate::from_ymd_opt(2026, 7, 18).unwrap(), | |
| 314 | + | warmup: vec!["chest".into(), "shoulders".into(), "core".into()], | |
| 315 | + | slots: vec![ | |
| 316 | + | SlotExport { | |
| 317 | + | name: "bench".into(), | |
| 318 | + | prescription: "3 x 5 @ 82.5 kg".into(), | |
| 319 | + | glyph: "up".into(), | |
| 320 | + | }, | |
| 321 | + | SlotExport { | |
| 322 | + | name: "pullup".into(), | |
| 323 | + | prescription: "3 x 8".into(), | |
| 324 | + | glyph: "flat".into(), | |
| 325 | + | }, | |
| 326 | + | SlotExport { | |
| 327 | + | name: "row \"paused\"".into(), | |
| 328 | + | prescription: "3 x 6 @ 60 kg".into(), | |
| 329 | + | glyph: "".into(), | |
| 330 | + | }, | |
| 331 | + | ], | |
| 332 | + | }; | |
| 333 | + | let dir = std::env::temp_dir().join(format!( | |
| 334 | + | "ripgrow-pdf-e2e-{}", | |
| 335 | + | std::time::SystemTime::now() | |
| 336 | + | .duration_since(std::time::UNIX_EPOCH) | |
| 337 | + | .unwrap() | |
| 338 | + | .as_nanos() | |
| 339 | + | )); | |
| 340 | + | std::fs::create_dir_all(&dir).unwrap(); | |
| 341 | + | let out = export_to_pdf(&s, &dir).unwrap(); | |
| 342 | + | assert!(out.exists(), "output PDF should exist at {}", out.display()); | |
| 343 | + | let bytes = std::fs::read(&out).unwrap(); | |
| 344 | + | assert!(bytes.starts_with(b"%PDF-"), "output should be a valid PDF"); | |
| 345 | + | let _ = std::fs::remove_dir_all(&dir); | |
| 346 | + | } | |
| 347 | + | ||
| 294 | 348 | #[test] | |
| 295 | 349 | fn export_returns_typst_missing_when_absent() { | |
| 296 | - | // typst is not installed in test env; ensure we surface a clear | |
| 297 | - | // error rather than a raw io::Error. If typst IS installed, this | |
| 298 | - | // test correctly fails — remove the guard or install per-CI norms. | |
| 299 | - | if std::process::Command::new("which") | |
| 300 | - | .arg("typst") | |
| 301 | - | .output() | |
| 302 | - | .ok() | |
| 303 | - | .and_then(|o| o.status.success().then_some(())) | |
| 304 | - | .is_some() | |
| 305 | - | { | |
| 350 | + | // Guards against a regression in the missing-binary error path. | |
| 351 | + | // Skips when typst is present; the end-to-end test covers the | |
| 352 | + | // happy path. | |
| 353 | + | if typst_available() { | |
| 306 | 354 | eprintln!("skipping: typst is on PATH in this environment"); | |
| 307 | 355 | return; | |
| 308 | 356 | } |