fusible 0.14.3

Electronic linter: a fuse that blows to block firmware code generation
use super::kit_data::build_kit;
use super::{BodyError, CLEARANCE, EDGE_TOLERANCE, NOTCH_MARGIN};

const BOARD: &str = r#"[
  {"type":"pcb_board","width":40,"height":30,"center":{"x":0,"y":0},"thickness":1.4},
  {"type":"pcb_hole","x":-15,"y":10,"hole_diameter":3.2},
  {"type":"pcb_plated_hole","x":5,"y":5,"shape":"circle","hole_diameter":1.0,"outer_diameter":1.6},
  {"type":"source_component","source_component_id":"sc1","name":"J1"},
  {"type":"source_component","source_component_id":"sc2","name":"R1"},
  {"type":"pcb_component","pcb_component_id":"pc1","source_component_id":"sc1",
   "center":{"x":18,"y":0},"width":6,"height":8,"layer":"top"},
  {"type":"pcb_component","pcb_component_id":"pc2","source_component_id":"sc2",
   "center":{"x":0,"y":0},"width":2,"height":1,"layer":"bottom"}
]"#;

#[test]
fn kit_lists_outline_holes_components_and_edge_openings() {
    let kit = build_kit(BOARD, Some("main".into())).unwrap();
    assert_eq!(kit.board.as_deref(), Some("main"));
    assert_eq!(kit.thickness, 1.4);
    assert_eq!(kit.outline.len(), 4);
    let xs: Vec<f64> = kit.outline.iter().map(|p| p[0]).collect();
    assert!(xs.iter().all(|x| x.abs() == 20.0), "{xs:?}");

    let holes: Vec<_> = kit
        .holes
        .iter()
        .map(|h| (h.x, h.y, h.diameter, h.plated))
        .collect();
    assert_eq!(holes, [(-15.0, 10.0, 3.2, false), (5.0, 5.0, 1.0, true)]);

    let names: Vec<_> = kit
        .components
        .iter()
        .map(|c| (c.name.as_str(), c.touches_edge))
        .collect();
    assert_eq!(names, [("J1", true), ("R1", false)]);
    assert_eq!(kit.components[1].layer, "bottom");

    assert_eq!(kit.openings.len(), 1);
    let opening = &kit.openings[0];
    assert_eq!((opening.component.as_str(), opening.side), ("J1", "+x"));
    assert!((opening.normal[0] - 1.0).abs() < 1e-9 && opening.normal[1].abs() < 1e-9);
    assert_eq!(opening.from[0], 20.0);
    assert_eq!(opening.to[0], 20.0);
    let mut ys = [opening.from[1], opening.to[1]];
    ys.sort_by(f64::total_cmp);
    assert_eq!(ys, [-4.5, 4.5]);
}

#[test]
fn kit_exports_the_notch_constants_the_generator_uses() {
    let kit = build_kit(BOARD, None).unwrap();
    assert_eq!(kit.notch.margin, NOTCH_MARGIN);
    assert_eq!(kit.notch.edge_tolerance, EDGE_TOLERANCE);
    assert_eq!(kit.notch.clearance, CLEARANCE);
    let value = serde_json::to_value(&kit).unwrap();
    assert_eq!(value["notch"]["margin"], 0.5);
    assert_eq!(value["warnings"], serde_json::json!([]));
}

#[test]
fn holes_without_a_diameter_are_left_out_with_a_warning() {
    let board = r#"[
      {"type":"pcb_board","width":40,"height":30,"center":{"x":0,"y":0}},
      {"type":"pcb_hole","x":1,"y":1,"hole_diameter":3.0},
      {"type":"pcb_hole","x":2,"y":2}
    ]"#;
    let kit = build_kit(board, None).unwrap();
    assert_eq!(kit.holes.len(), 1);
    assert_eq!(kit.holes[0].diameter, 3.0);
    assert_eq!(kit.warnings.len(), 1);
    assert!(kit.warnings[0].contains("1 hole(s)"), "{:?}", kit.warnings);
}

#[test]
fn invalid_circuit_json_is_an_error_not_an_empty_kit() {
    assert!(matches!(
        build_kit("not json", None),
        Err(BodyError::InvalidCircuit(_))
    ));
}

#[test]
fn kit_serializes_the_documented_keys() {
    let kit = build_kit(BOARD, None).unwrap();
    let value = serde_json::to_value(&kit).unwrap();
    for key in [
        "board",
        "units",
        "frame",
        "outline",
        "thickness",
        "holes",
        "components",
        "openings",
        "notch",
        "warnings",
    ] {
        assert!(value.get(key).is_some(), "missing `{key}`");
    }
    assert_eq!(value["units"], "mm");
    assert_eq!(value["openings"][0]["side"], "+x");
}

#[test]
fn kit_without_a_board_outline_is_an_error() {
    assert!(matches!(build_kit("[]", None), Err(BodyError::NoBoard)));
    let two_points = r#"[{"type":"pcb_board","outline":[{"x":0,"y":0},{"x":1,"y":1}]}]"#;
    assert!(matches!(
        build_kit(two_points, None),
        Err(BodyError::NoBoard)
    ));
}