From 4d5419c5a5b9fd4b678828a089d14c00e5424390 Mon Sep 17 00:00:00 2001 From: Jheison Martinez Bolivar Date: Wed, 15 Jul 2026 11:56:49 -0500 Subject: [PATCH 1/4] fix: ask user before installing agent skill, run wizard interactively --- scripts/install.sh | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index dc63bea..9363340 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -131,12 +131,21 @@ SKILLS_REPO="https://github.com/UniverLab/skills" if [ -n "${SKIP_SKILL:-}" ]; then info "skill" "skipped (SKIP_SKILL set)" elif command -v npx >/dev/null 2>&1; then - info "skill" "adding '$SKILL' (npx skills add)" - if npx -y skills add "$SKILLS_REPO" --skill "$SKILL" Date: Wed, 15 Jul 2026 12:13:55 -0500 Subject: [PATCH 2/4] test: add 56 unit tests for color module and model deserialization --- src/color.rs | 224 +++++++++++++++++++++++++++++++ src/model.rs | 368 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 592 insertions(+) diff --git a/src/color.rs b/src/color.rs index 8d53aa5..b043feb 100644 --- a/src/color.rs +++ b/src/color.rs @@ -365,4 +365,228 @@ mod tests { assert_eq!(weight_to_dxf(0.50), 50); assert_eq!(weight_to_dxf(1.0), 100); } + + // ---- hex_to_aci edge cases ---- + + #[test] + fn hex_to_aci_all_zeros() { + assert_eq!(hex_to_aci("#000000"), 250); + } + + #[test] + fn hex_to_aci_all_ffs() { + assert_eq!(hex_to_aci("#FFFFFF"), 7); + } + + #[test] + fn hex_to_aci_without_hash_prefix() { + assert_eq!(hex_to_aci("FF0000"), 1); + } + + #[test] + fn hex_to_aci_empty_string() { + assert_eq!(hex_to_aci(""), 7); + } + + #[test] + fn hex_to_aci_too_short() { + assert_eq!(hex_to_aci("#FF"), 7); + assert_eq!(hex_to_aci("#FFF0"), 7); + } + + #[test] + fn hex_to_aci_too_long() { + assert_eq!(hex_to_aci("#FF0000FF"), 7); + } + + #[test] + fn hex_to_aci_non_hex_chars() { + assert_eq!(hex_to_aci("#ZZZZZZ"), 7); + assert_eq!(hex_to_aci("#GGGGGG"), 7); + } + + #[test] + fn hex_to_aci_each_primary_channel_boundary() { + // Pure red + assert_eq!(hex_to_aci("#FF0000"), 1); + // Pure green + assert_eq!(hex_to_aci("#00FF00"), 3); + // Pure blue + assert_eq!(hex_to_aci("#0000FF"), 5); + } + + #[test] + fn hex_to_aci_exact_palette_entry_roundtrips() { + // Pick a few exact palette entries and verify they roundtrip + for index in [1, 2, 3, 4, 5, 6, 7, 8, 9, 250] { + let hex = aci_to_hex(index); + assert_eq!(hex_to_aci(&hex), index, "failed for ACI {index}"); + } + } + + // ---- aci_to_hex edge cases ---- + + #[test] + fn aci_to_hex_out_of_range_returns_white() { + assert_eq!(aci_to_hex(0), "#FFFFFF"); // BYBLOCK + // 256 is out of u8 range, so test boundary at 255 and 0 + assert_eq!(aci_to_hex(0), "#FFFFFF"); // below valid range + assert_eq!(aci_to_hex(255), "#FFFFFF"); // 255 IS valid, and happens to be white + } + + #[test] + fn aci_to_hex_valid_range_never_white_except_known() { + // ACI 7 is white in AutoCAD; 0 and 255 are also white (out-of-range/BYBLOCK). + // All other entries 1-254 (except 7) should not be pure white. + for index in 1..=254u8 { + if index == 7 { + continue; // ACI 7 is legitimately white + } + assert_ne!( + aci_to_hex(index), + "#FFFFFF", + "ACI {index} unexpectedly returned white" + ); + } + } + + #[test] + fn aci_to_hex_black() { + assert_eq!(aci_to_hex(250), "#000000"); + } + + #[test] + fn aci_to_hex_always_has_hash_prefix() { + for index in [1, 50, 100, 150, 200, 255] { + assert!(aci_to_hex(index).starts_with('#'), "ACI {index} missing # prefix"); + } + } + + #[test] + fn aci_to_hex_always_7_chars() { + for index in 1..=255u8 { + assert_eq!(aci_to_hex(index).len(), 7, "ACI {index} wrong length"); + } + } + + // ---- hex_to_24bit edge cases ---- + + #[test] + fn hex_to_24bit_all_zeros() { + assert_eq!(hex_to_24bit("#000000"), 0x000000); + } + + #[test] + fn hex_to_24bit_all_ones() { + assert_eq!(hex_to_24bit("#FFFFFF"), 0xFFFFFF); + } + + #[test] + fn hex_to_24bit_without_hash() { + assert_eq!(hex_to_24bit("FF0000"), 0xFF0000); + } + + #[test] + fn hex_to_24bit_invalid_returns_default() { + assert_eq!(hex_to_24bit(""), 0x00FF_FFFF); + assert_eq!(hex_to_24bit("#ZZZZZZ"), 0x00FF_FFFF); + assert_eq!(hex_to_24bit("not_a_color"), 0x00FF_FFFF); + } + + #[test] + fn hex_to_24bit_short_hex() { + // from_str_radix accepts shorter strings (pads with leading zeros) + assert_eq!(hex_to_24bit("#FFF"), 0xFFF); + assert_eq!(hex_to_24bit("#0F0"), 0x0F0); + } + + #[test] + fn hex_to_24bit_single_byte() { + assert_eq!(hex_to_24bit("#0F"), 0x0F); + } + + // ---- weight_to_dxf edge cases ---- + + #[test] + fn weight_to_dxf_zero() { + assert_eq!(weight_to_dxf(0.0), 0); + } + + #[test] + fn weight_to_dxf_negative() { + assert_eq!(weight_to_dxf(-0.1), -10); + } + + #[test] + fn weight_to_dxf_very_small() { + // 0.01 mm = 1 hundredth + assert_eq!(weight_to_dxf(0.01), 1); + } + + #[test] + fn weight_to_dxf_fractional_rounding() { + // f64 * 100.0 then as i16 truncates toward zero + let result = weight_to_dxf(0.355); + assert_eq!(result, 35); // 0.355 * 100.0 = 35.5 → truncated to 35 + } + + #[test] + fn weight_to_dxf_large_value() { + // 100mm → 10000 hundredths + assert_eq!(weight_to_dxf(100.0), 10000); + } + + #[test] + fn weight_to_dxf_typical_lineweights() { + // Common DXF lineweights + assert_eq!(weight_to_dxf(0.13), 13); + assert_eq!(weight_to_dxf(0.18), 18); + assert_eq!(weight_to_dxf(0.25), 25); + assert_eq!(weight_to_dxf(0.30), 30); + assert_eq!(weight_to_dxf(0.35), 35); + assert_eq!(weight_to_dxf(0.50), 50); + assert_eq!(weight_to_dxf(0.70), 70); + assert_eq!(weight_to_dxf(1.00), 100); + assert_eq!(weight_to_dxf(1.40), 140); + assert_eq!(weight_to_dxf(2.00), 200); + } + + // ---- roundtrip conversions ---- + + #[test] + fn aci_hex_roundtrip_all_valid_indices() { + for index in 1..=255u8 { + let hex = aci_to_hex(index); + let back = hex_to_aci(&hex); + let hex2 = aci_to_hex(back); + assert_eq!( + hex, hex2, + "ACI {index}: {hex} → {back} → {hex2} — color drifted" + ); + } + } + + #[test] + fn hex_to_24bit_roundtrip_with_aci() { + // A true roundtrip: hex → ACI → hex → 24bit should be deterministic + let original = "#FF0000"; + let aci = hex_to_aci(original); + let hex_from_aci = aci_to_hex(aci); + let bits = hex_to_24bit(&hex_from_aci); + assert_eq!(bits, 0xFF0000); + } + + #[test] + fn hex_to_aci_consistency_across_prefixes() { + let with_hash = hex_to_aci("#FF0000"); + let without_hash = hex_to_aci("FF0000"); + assert_eq!(with_hash, without_hash); + } + + #[test] + fn hex_to_24bit_consistency_across_prefixes() { + let with_hash = hex_to_24bit("#AABBCC"); + let without_hash = hex_to_24bit("AABBCC"); + assert_eq!(with_hash, without_hash); + } } diff --git a/src/model.rs b/src/model.rs index f7e0fb6..b4baf6c 100644 --- a/src/model.rs +++ b/src/model.rs @@ -319,3 +319,371 @@ pub struct CfFile { #[serde(default, rename = "boolean")] pub booleans: Vec, } + +#[cfg(test)] +mod tests { + use super::*; + + // ── Default values ──────────────────────────────────────────────── + + #[test] + fn default_common_attrs() { + let c = CommonAttrs::default(); + assert!(c.id.is_none()); + assert!(c.color.is_none()); + assert!(c.weight.is_none()); + assert!(c.style.is_none()); + assert!(c.layer.is_none()); + assert!(c.belongs_to.is_none()); + assert!(!c.visible); // Default derive for bool = false; serde default_true applies at parse time + assert!(!c.locked); + assert!(c.extrude.is_none()); + assert!(c.elevation.is_none()); + } + + #[test] + fn default_cf_file() { + let f = CfFile::default(); + assert!(f.layer_meta.is_none()); + assert!(f.lines.is_empty()); + assert!(f.polylines.is_empty()); + assert!(f.rects.is_empty()); + assert!(f.circles.is_empty()); + assert!(f.arcs.is_empty()); + assert!(f.texts.is_empty()); + assert!(f.points.is_empty()); + assert!(f.dims.is_empty()); + assert!(f.hatches.is_empty()); + assert!(f.fills.is_empty()); + assert!(f.groups.is_empty()); + assert!(f.arrays.is_empty()); + assert!(f.mirrors.is_empty()); + assert!(f.solids.is_empty()); + assert!(f.booleans.is_empty()); + } + + #[test] + fn default_layer_meta() { + let l = LayerMeta::default(); + assert!(l.name.is_none()); + assert!(l.color.is_none()); + assert!(l.line_weight.is_none()); + assert!(!l.visible); // same as CommonAttrs + assert!(!l.locked); + } + + // ── Deserialization roundtrips (JSON → struct) ──────────────────── + + #[test] + fn deserialize_line() { + let json = r#"{"from":[0.0,0.0],"to":[10.0,5.0],"color":"red"}"#; + let line: CfLine = serde_json::from_str(json).unwrap(); + assert_eq!(line.from, [0.0, 0.0]); + assert_eq!(line.to, [10.0, 5.0]); + assert_eq!(line.common.color.as_deref(), Some("red")); + } + + #[test] + fn deserialize_polyline() { + let json = r#"{"points":[[0,0],[1,2],[3,4]],"closed":true,"layer":"walls"}"#; + let pl: CfPolyline = serde_json::from_str(json).unwrap(); + assert_eq!(pl.points.len(), 3); + assert!(pl.closed); + assert_eq!(pl.common.layer.as_deref(), Some("walls")); + } + + #[test] + fn deserialize_polyline_closed_default() { + let json = r#"{"points":[[0,0],[1,1]]}"#; + let pl: CfPolyline = serde_json::from_str(json).unwrap(); + assert!(!pl.closed); + } + + #[test] + fn deserialize_rect() { + let json = r#"{"origin":[1.0,2.0],"width":10.0,"height":5.0,"visible":false}"#; + let r: CfRect = serde_json::from_str(json).unwrap(); + assert_eq!(r.origin, [1.0, 2.0]); + assert_eq!(r.width, 10.0); + assert_eq!(r.height, 5.0); + assert!(!r.common.visible); + } + + #[test] + fn deserialize_circle() { + let json = r#"{"center":[5.0,5.0],"radius":3.0,"color":"blue"}"#; + let c: CfCircle = serde_json::from_str(json).unwrap(); + assert_eq!(c.center, [5.0, 5.0]); + assert_eq!(c.radius, 3.0); + } + + #[test] + fn deserialize_arc() { + let json = r#"{"center":[0,0],"radius":5.0,"from_angle":0.0,"to_angle":90.0}"#; + let a: CfArc = serde_json::from_str(json).unwrap(); + assert_eq!(a.radius, 5.0); + assert_eq!(a.from_angle, 0.0); + assert_eq!(a.to_angle, 90.0); + } + + #[test] + fn deserialize_text_defaults() { + let json = r#"{"position":[1.0,1.0],"content":"hello"}"#; + let t: CfText = serde_json::from_str(json).unwrap(); + assert_eq!(t.content, "hello"); + assert_eq!(t.size, 2.5); // default_text_size + assert!(t.align.is_none()); + assert!(t.font.is_none()); + assert!(t.rotation.is_none()); + assert!(t.bold.is_none()); + assert!(t.italic.is_none()); + } + + #[test] + fn deserialize_text_full() { + let json = r#"{"position":[0,0],"content":"x","size":12.0,"align":"center","font":"mono","rotation":45.0,"bold":true,"italic":true}"#; + let t: CfText = serde_json::from_str(json).unwrap(); + assert_eq!(t.size, 12.0); + assert!(matches!(t.align, Some(TextAlign::Center))); + assert_eq!(t.font.as_deref(), Some("mono")); + assert_eq!(t.rotation, Some(45.0)); + assert_eq!(t.bold, Some(true)); + assert_eq!(t.italic, Some(true)); + } + + #[test] + fn deserialize_point() { + let json = r#"{"position":[7.0,8.0],"id":"p1"}"#; + let p: CfPoint = serde_json::from_str(json).unwrap(); + assert_eq!(p.position, [7.0, 8.0]); + assert_eq!(p.common.id.as_deref(), Some("p1")); + } + + #[test] + fn deserialize_dim_defaults() { + let json = r#"{"from":[0,0],"to":[10,0]}"#; + let d: CfDim = serde_json::from_str(json).unwrap(); + assert_eq!(d.offset, 0.5); // default_offset + assert!(d.dim_type.is_none()); + assert!(d.text_size.is_none()); + assert!(d.precision.is_none()); + assert!(d.show_units.is_none()); + } + + #[test] + fn deserialize_dim_full() { + let json = r#"{"type":"linear","from":[0,0],"to":[10,0],"offset":1.0,"text_size":0.5,"precision":3,"show_units":false}"#; + let d: CfDim = serde_json::from_str(json).unwrap(); + assert!(matches!(d.dim_type, Some(DimType::Linear))); + assert_eq!(d.offset, 1.0); + assert_eq!(d.text_size, Some(0.5)); + assert_eq!(d.precision, Some(3)); + assert_eq!(d.show_units, Some(false)); + } + + #[test] + fn deserialize_hatch_defaults() { + let json = r#"{}"#; + let h: CfHatch = serde_json::from_str(json).unwrap(); + assert_eq!(h.pattern, "ansi31"); + assert_eq!(h.scale, 1.0); + assert_eq!(h.angle, 45.0); + assert!(h.boundary.is_none()); + assert!(h.points.is_none()); + } + + #[test] + fn deserialize_hatch_with_boundary_ref() { + let json = r#"{"boundary":"polyline1","pattern":"solid","scale":2.0,"angle":0.0}"#; + let h: CfHatch = serde_json::from_str(json).unwrap(); + assert_eq!(h.boundary.as_deref(), Some("polyline1")); + assert_eq!(h.pattern, "solid"); + assert_eq!(h.scale, 2.0); + assert_eq!(h.angle, 0.0); + } + + #[test] + fn deserialize_hatch_inline_points() { + let json = r#"{"points":[[0,0],[10,0],[10,10],[0,10]]}"#; + let h: CfHatch = serde_json::from_str(json).unwrap(); + assert!(h.points.is_some()); + assert_eq!(h.points.unwrap().len(), 4); + } + + #[test] + fn deserialize_group() { + let json = r#"{"members":["line1","rect1"],"id":"g1"}"#; + let g: CfGroup = serde_json::from_str(json).unwrap(); + assert_eq!(g.members, vec!["line1".to_string(), "rect1".to_string()]); + assert_eq!(g.common.id.as_deref(), Some("g1")); + } + + #[test] + fn deserialize_array_linear() { + let json = r#"{"target":"rect1","mode":"linear","count":4,"offset":[2.0,0.0],"rotate_items":false}"#; + let a: CfArray = serde_json::from_str(json).unwrap(); + assert_eq!(a.target.as_deref(), Some("rect1")); + assert_eq!(a.mode, ArrayMode::Linear); + assert_eq!(a.count, 4); + assert_eq!(a.offset, Some([2.0, 0.0])); + assert!(!a.rotate_items); + } + + #[test] + fn deserialize_array_polar() { + let json = r#"{"targets":["col1","col2"],"mode":"polar","count":8,"center":[0,0],"step_angle":45.0}"#; + let a: CfArray = serde_json::from_str(json).unwrap(); + assert_eq!(a.targets, Some(vec!["col1".to_string(), "col2".to_string()])); + assert_eq!(a.mode, ArrayMode::Polar); + assert_eq!(a.center, Some([0.0, 0.0])); + assert_eq!(a.step_angle, Some(45.0)); + assert!(a.rotate_items); // default_true + } + + #[test] + fn deserialize_mirror() { + let json = r#"{"target":"line1","axis":[[0,0],[0,1]]}"#; + let m: CfMirror = serde_json::from_str(json).unwrap(); + assert_eq!(m.target.as_deref(), Some("line1")); + assert_eq!(m.axis, [[0.0, 0.0], [0.0, 1.0]]); + } + + #[test] + fn deserialize_fill() { + let json = r#"{"boundary":"rect1"}"#; + let f: CfFill = serde_json::from_str(json).unwrap(); + assert_eq!(f.boundary.as_deref(), Some("rect1")); + assert!(f.points.is_none()); + } + + #[test] + fn deserialize_fill_inline() { + let json = r#"{"points":[[0,0],[5,0],[5,5],[0,5]]}"#; + let f: CfFill = serde_json::from_str(json).unwrap(); + assert!(f.boundary.is_none()); + assert_eq!(f.points.as_ref().unwrap().len(), 4); + } + + #[test] + fn deserialize_solid_box() { + let json = r#"{"id":"box1","shape":"box","at":[0,0,0],"size":[10,20,5],"color":"gray"}"#; + let s: CfSolid = serde_json::from_str(json).unwrap(); + assert_eq!(s.id, "box1"); + assert_eq!(s.shape, "box"); + assert_eq!(s.at, Some([0.0, 0.0, 0.0])); + assert_eq!(s.size, Some([10.0, 20.0, 5.0])); + assert_eq!(s.color.as_deref(), Some("gray")); + } + + #[test] + fn deserialize_solid_cylinder() { + let json = r#"{"id":"cyl1","shape":"cylinder","radius":3.0,"height":10.0,"segments":60}"#; + let s: CfSolid = serde_json::from_str(json).unwrap(); + assert_eq!(s.radius, Some(3.0)); + assert_eq!(s.height, Some(10.0)); + assert_eq!(s.segments, Some(60)); + } + + #[test] + fn deserialize_boolean() { + let json = r#"{"op":"difference","base":"box1","tools":["cyl1","cyl2"],"color":"red"}"#; + let b: CfBoolean = serde_json::from_str(json).unwrap(); + assert_eq!(b.op, "difference"); + assert_eq!(b.base, "box1"); + assert_eq!(b.tools, vec!["cyl1".to_string(), "cyl2".to_string()]); + assert_eq!(b.color.as_deref(), Some("red")); + } + + #[test] + fn deserialize_boolean_default_tools() { + let json = r#"{"op":"union","base":"a"}"#; + let b: CfBoolean = serde_json::from_str(json).unwrap(); + assert!(b.tools.is_empty()); + assert!(b.id.is_none()); + } + + // ── LineStyle / enum variants ───────────────────────────────────── + + #[test] + fn deserialize_line_style_variants() { + let variants = ["solid", "dashed", "dotted", "dashdot"]; + for v in variants { + let json = format!(r#"{{"style":"{}"}}"#, v); + let c: CommonAttrs = serde_json::from_str(&json).unwrap(); + assert!(c.style.is_some(), "failed for {}", v); + } + } + + #[test] + fn deserialize_dim_type_variants() { + let variants = ["linear", "angular", "radial"]; + for v in variants { + let json = format!(r#"{{"type":"{}","from":[0,0],"to":[1,1]}}"#, v); + let d: CfDim = serde_json::from_str(&json).unwrap(); + assert!(d.dim_type.is_some(), "failed for {}", v); + } + } + + #[test] + fn deserialize_text_align_variants() { + let variants = ["left", "center", "right"]; + for v in variants { + let json = format!( + r#"{{"position":[0,0],"content":"x","align":"{}"}}"#, + v + ); + let t: CfText = serde_json::from_str(&json).unwrap(); + assert!(t.align.is_some(), "failed for {}", v); + } + } + + // ── CfFile full deserialization ─────────────────────────────────── + + #[test] + fn deserialize_cf_file_empty() { + let json = "{}"; + let f: CfFile = serde_json::from_str(json).unwrap(); + assert!(f.lines.is_empty()); + assert!(f.layer_meta.is_none()); + } + + #[test] + fn deserialize_cf_file_mixed() { + let json = r#"{ + "layer": {"name": "main", "color": "ff0000"}, + "line": [{"from":[0,0],"to":[1,1]}], + "rect": [{"origin":[2,2],"width":5.0,"height":3.0}], + "circle": [{"center":[0,0],"radius":1.0}], + "text": [{"position":[0,0],"content":"label"}], + "solid": [{"id":"s1","shape":"box","size":[1,1,1]}] + }"#; + let f: CfFile = serde_json::from_str(json).unwrap(); + assert!(f.layer_meta.is_some()); + assert_eq!(f.lines.len(), 1); + assert_eq!(f.rects.len(), 1); + assert_eq!(f.circles.len(), 1); + assert_eq!(f.texts.len(), 1); + assert_eq!(f.solids.len(), 1); + assert_eq!(f.polylines.len(), 0); + assert_eq!(f.booleans.len(), 0); + } + + // ── Clone & Debug ───────────────────────────────────────────────── + + #[test] + fn clone_common_attrs() { + let c = CommonAttrs { + id: Some("x".into()), + ..Default::default() + }; + let c2 = c.clone(); + assert_eq!(c.id, c2.id); + } + + #[test] + fn debug_common_attrs() { + let c = CommonAttrs::default(); + let dbg = format!("{:?}", c); + assert!(dbg.contains("CommonAttrs")); + } +} From f508d3907ab3615ebe2ff6e024e434c550f2d46a Mon Sep 17 00:00:00 2001 From: Jheison Martinez Bolivar Date: Wed, 15 Jul 2026 12:45:10 -0500 Subject: [PATCH 3/4] chore: add *.mp4 to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 6c83c0f..d20a93e 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,6 @@ CLAUDE.md AGENTS.md .mimocode/ + +# Large video files +*.mp4 From f8a5e7b0258df73cd5072a528f1b81beb8ec21e2 Mon Sep 17 00:00:00 2001 From: Jheison Martinez Bolivar Date: Tue, 21 Jul 2026 11:50:14 -0500 Subject: [PATCH 4/4] style: apply rustfmt --- src/color.rs | 9 ++++++--- src/model.rs | 10 +++++----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/color.rs b/src/color.rs index b043feb..0151373 100644 --- a/src/color.rs +++ b/src/color.rs @@ -428,8 +428,8 @@ mod tests { #[test] fn aci_to_hex_out_of_range_returns_white() { - assert_eq!(aci_to_hex(0), "#FFFFFF"); // BYBLOCK - // 256 is out of u8 range, so test boundary at 255 and 0 + assert_eq!(aci_to_hex(0), "#FFFFFF"); // BYBLOCK + // 256 is out of u8 range, so test boundary at 255 and 0 assert_eq!(aci_to_hex(0), "#FFFFFF"); // below valid range assert_eq!(aci_to_hex(255), "#FFFFFF"); // 255 IS valid, and happens to be white } @@ -458,7 +458,10 @@ mod tests { #[test] fn aci_to_hex_always_has_hash_prefix() { for index in [1, 50, 100, 150, 200, 255] { - assert!(aci_to_hex(index).starts_with('#'), "ACI {index} missing # prefix"); + assert!( + aci_to_hex(index).starts_with('#'), + "ACI {index} missing # prefix" + ); } } diff --git a/src/model.rs b/src/model.rs index b4baf6c..73240d3 100644 --- a/src/model.rs +++ b/src/model.rs @@ -533,7 +533,10 @@ mod tests { fn deserialize_array_polar() { let json = r#"{"targets":["col1","col2"],"mode":"polar","count":8,"center":[0,0],"step_angle":45.0}"#; let a: CfArray = serde_json::from_str(json).unwrap(); - assert_eq!(a.targets, Some(vec!["col1".to_string(), "col2".to_string()])); + assert_eq!( + a.targets, + Some(vec!["col1".to_string(), "col2".to_string()]) + ); assert_eq!(a.mode, ArrayMode::Polar); assert_eq!(a.center, Some([0.0, 0.0])); assert_eq!(a.step_angle, Some(45.0)); @@ -628,10 +631,7 @@ mod tests { fn deserialize_text_align_variants() { let variants = ["left", "center", "right"]; for v in variants { - let json = format!( - r#"{{"position":[0,0],"content":"x","align":"{}"}}"#, - v - ); + let json = format!(r#"{{"position":[0,0],"content":"x","align":"{}"}}"#, v); let t: CfText = serde_json::from_str(&json).unwrap(); assert!(t.align.is_some(), "failed for {}", v); }