//! `notescraft-cli export pdf` (SPEC 10.1, SPEC 16): the PDF export //! subcommand, its options and its honest `--print` report. mod common; use common::{empty_vault, note, run}; use serde_json::Value; /// A vault with one note ready to export. fn fixture(name: &str) -> std::path::PathBuf { let root = empty_vault(name); note(&root, "Report.md", "# Report\n\nBody with **bold** and a [link](https://example.com).\n\n- [x] done\n"); root } #[test] fn export_pdf_writes_the_file_and_reports_it() { let vault = fixture("export-text"); let run = run(&vault, &["export", "pdf", "Report.md"]); assert_eq!(run.code, 0, "{}", run.stderr); assert!(run.stdout.contains("wrote Report.pdf"), "{}", run.stdout); assert!(run.stdout.contains("pages: 1"), "{}", run.stdout); let bytes = std::fs::read(vault.join("Report.pdf")).expect("the PDF was written"); assert!(bytes.starts_with(b"%PDF-1.7\n"), "a real PDF landed in the vault"); assert!(bytes.ends_with(b"%%EOF\n")); } #[test] fn export_pdf_reports_its_result_as_json() { let vault = fixture("export-json"); let run = run(&vault, &["--json", "export", "pdf", "Report.md"]); assert_eq!(run.code, 0, "{}", run.stderr); let result: Value = run.result(); assert_eq!(result.get("path").and_then(Value::as_str), Some("Report.pdf")); assert_eq!(result.get("pages").and_then(Value::as_u64), Some(1)); assert!(result.get("bytes").and_then(Value::as_u64).unwrap_or_default() > 1_000, "{result}"); } #[test] fn out_chooses_the_destination_and_may_leave_the_vault() { let vault = fixture("export-out"); let inside = run(&vault, &["--json", "export", "pdf", "Report.md", "--out", "reports/report.pdf"]); assert_eq!(inside.code, 0, "{}", inside.stderr); assert!(vault.join("reports/report.pdf").exists(), "a relative --out stays in the vault"); let outside = vault.join("outside.pdf"); let absolute = run(&vault, &["--json", "export", "pdf", "Report.md", "--out", outside.display().to_string().as_str()]); assert_eq!(absolute.code, 0, "{}", absolute.stderr); assert!(outside.exists(), "an absolute --out is the one way out of the vault"); } #[test] fn page_and_margin_options_reach_the_engine() { let vault = fixture("export-options"); let letter = run(&vault, &["--json", "export", "pdf", "Report.md", "--page", "letter", "--margin-mm", "15"]); assert_eq!(letter.code, 0, "{}", letter.stderr); let bad_page = run(&vault, &["export", "pdf", "Report.md", "--page", "a3"]); assert_eq!(bad_page.code, 1, "an unknown page size is a command error"); let bad_margin = run(&vault, &["export", "pdf", "Report.md", "--margin-mm", "0"]); assert_eq!(bad_margin.code, 2, "a non-positive margin is a usage error"); let bad_number = run(&vault, &["export", "pdf", "Report.md", "--margin-mm", "wide"]); assert_eq!(bad_number.code, 2); } #[test] fn print_is_accepted_and_reports_print_unavailable() { let vault = fixture("export-print"); let json = run(&vault, &["--json", "export", "pdf", "Report.md", "--print"]); assert_eq!(json.code, 0, "{}", json.stderr); assert_eq!(json.result().get("print").and_then(Value::as_str), Some("print_unavailable")); let text = run(&vault, &["export", "pdf", "Report.md", "--print"]); assert!(text.stdout.contains("print: print_unavailable"), "{}", text.stdout); } #[test] fn a_bad_export_line_is_a_usage_error() { let vault = fixture("export-usage"); assert_eq!(run(&vault, &["export", "docx", "Report.md"]).code, 2, "only pdf exists"); assert_eq!(run(&vault, &["export", "pdf"]).code, 2, "the path is required"); assert_eq!(run(&vault, &["export", "pdf", "Report.md", "--nope"]).code, 2); assert_eq!(run(&vault, &["export", "pdf", "Missing.md"]).code, 1, "a missing note is a command error"); assert!(run(&vault, &["help"]).stdout.contains("export pdf "), "the usage text lists the subcommand"); } /// An absolute whiteboard `--out` is written by the CLI itself and never overwrites an existing file. #[test] fn whiteboard_svg_to_a_host_path_refuses_to_overwrite() { let vault = empty_vault("export-wb-host"); note(&vault, "Board.draw", r#"{"type":"excalidraw","version":2,"elements":[],"appState":{},"files":{}}"#); let target = vault.join("host-out.svg"); let out = target.display().to_string(); let first = run(&vault, &["--json", "export", "whiteboard", "Board.draw", "--out", out.as_str()]); assert_eq!(first.code, 0, "{}", first.stderr); assert!(target.exists(), "an absolute --out is written as asked"); let second = run(&vault, &["--json", "export", "whiteboard", "Board.draw", "--out", out.as_str()]); assert_ne!(second.code, 0, "the second export must not overwrite"); }