//! Offline native integration: actual browser pixels and declared dependency failures. use serde_json::{Value, json}; use std::{ fs, path::PathBuf, process::{Command, Output}, sync::atomic::{AtomicUsize, Ordering}, }; static NEXT: AtomicUsize = AtomicUsize::new(0); struct Fixture { root: PathBuf, project: PathBuf, store: PathBuf, } impl Fixture { fn new() -> Self { let root = std::env::temp_dir().join(format!( "component-native-{}-{}", std::process::id(), NEXT.fetch_add(1, Ordering::Relaxed) )); let project = root.join("project"); let store = root.join("store"); fs::create_dir_all(&project).unwrap(); let png = impeccable_comp::png_io::encode_png( &impeccable_comp::raster::create_image(100, 100, [25, 90, 65, 180]), &[], ) .unwrap(); fs::write(project.join("art.png"), &png).unwrap(); fs::write(project.join("comp.png"), &png).unwrap(); fs::write(project.join("component.html"),"").unwrap(); fs::write( project.join("component.css"), "body{margin:0;background:#faf5ec}button{color:green}", ) .unwrap(); Self { root, project, store, } } fn manifest(&self) -> Value { json!({"schemaVersion":1,"id":"components","title":"Capture fixture","comp":{"path":"comp.png","width":100,"height":100},"components":[{"id":"art","name":"Cutout","medium":"Raster","note":"PNG","box":{"x":0,"y":0,"w":1,"h":1},"preview":{"kind":"image","path":"art.png"},"dependencies":[]},{"id":"code","name":"Code","medium":"HTML/CSS/SVG","note":"Actual code","box":{"x":0,"y":0,"w":1,"h":1},"preview":{"kind":"page","path":"component.html"},"dependencies":["component.css","art.png"]}]}) } fn capture(&self, input: &Value) -> Output { fs::write( self.project.join("review.json"), serde_json::to_vec(input).unwrap(), ) .unwrap(); Command::new(env!("CARGO_BIN_EXE_impeccable")) .current_dir(&self.project) .args([ "component-review", "capture", "--manifest", "review.json", "--store", ]) .arg(&self.store) .output() .unwrap() } fn state(&self, output: &Output) -> Value { assert!( output.status.success(), "{}", String::from_utf8_lossy(&output.stderr) ); let reply: Value = serde_json::from_slice(&output.stdout).unwrap(); serde_json::from_slice( &fs::read( self.store .join(reply["session"].as_str().unwrap()) .join("current.json"), ) .unwrap(), ) .unwrap() } } impl Drop for Fixture { fn drop(&mut self) { let _ = fs::remove_dir_all(&self.root); } } #[test] fn captures_code_and_png_with_real_pixels_and_rejects_missing_inputs_without_replacing_review() { let f = Fixture::new(); let input = f.manifest(); let state = f.state(&f.capture(&input)); assert_eq!(state["capture"]["schema"], "native-component-previews-v1"); let proof = &state["capture"]["components"][1]["views"]["preview"]; assert_eq!(proof["semanticControls"], 1); assert_eq!(proof["svgElements"], 1); assert_eq!(proof["rasterElements"], 1); assert!(proof["observedDependencies"]["component.css"].is_string()); assert_eq!(state["packet"]["components"][1]["preview"]["kind"], "image"); assert_eq!( state["packet"]["components"][1]["preview"]["sourceKind"], "page" ); let view = state["packet"]["components"][1]["preview"]["url"] .as_str() .unwrap(); let path = view.splitn(4, '/').nth(3).unwrap(); assert!(path.starts_with("_review_captures/")); assert!(state["sources"].get(path).is_none()); let repeated = f.state(&f.capture(&input)); assert_eq!(state["packet"]["revision"], repeated["packet"]["revision"]); let mut missing = input.clone(); missing["components"][1]["dependencies"] = json!(["art.png"]); let output = f.capture(&missing); assert!(!output.status.success()); assert!( String::from_utf8_lossy(&output.stderr).contains("component.css"), "{}", String::from_utf8_lossy(&output.stderr) ); let id = fs::read_dir(&f.store) .unwrap() .next() .unwrap() .unwrap() .path(); let after: Value = serde_json::from_slice(&fs::read(id.join("current.json")).unwrap()).unwrap(); assert_eq!(state, after); fs::write(f.project.join("component.html"),"").unwrap(); let script = f.capture(&input); assert!(!script.status.success()); assert!(String::from_utf8_lossy(&script.stderr).contains("static HTML/CSS/SVG")); }