use std::path::Path;
use super::diagnostic::byte_offset_at;
use super::*;
fn parse_ok(json: &str) -> Circuit {
parse(json, Path::new("circuit.json")).expect("fixture must parse")
}
#[test]
fn parses_a_heterogeneous_array() {
let json = r#"[
{"type": "source_component", "component_id": "U1", "kind": "chip"},
{"type": "source_net", "net_id": "n1", "name": "GND"},
{"type": "source_port", "port_id": "U1.1", "name": "VSS", "net_id": "n1"},
{"type": "source_pad", "pad_id": "p1", "net_id": "n1"},
{"type": "source_trace", "trace_id": "t1", "connected_source_port_ids": ["U1.1", "U1.2"]},
{"type": "silkscreen_text", "text": "not modeled in phase 1"}
]"#;
let circuit = parse_ok(json);
assert_eq!(circuit.elements.len(), 6);
assert_eq!(circuit.components().count(), 1);
assert_eq!(circuit.nets().count(), 1);
assert_eq!(circuit.ports().count(), 1);
assert_eq!(circuit.pads().count(), 1);
assert_eq!(circuit.traces().count(), 1);
assert!(matches!(circuit.elements[5], CircuitElement::Unknown));
}
#[test]
fn accepts_canonical_snake_case_and_defensive_camelcase_aliases() {
let json = r#"[{"type": "source_trace", "source_trace_id": "t1",
"connected_source_port_ids": ["U1.1"] }]"#;
let circuit = parse_ok(json);
let trace = circuit.traces().next().expect("a trace");
assert_eq!(trace.trace_id.as_deref(), Some("t1"));
assert_eq!(trace.connected_source_port_ids, vec!["U1.1"]);
let json = r#"[{"type": "source_component", "sourceComponentId": "R1", "ftype": "resistor"}]"#;
let circuit = parse_ok(json);
let component = circuit.components().next().expect("a component");
assert_eq!(component.component_id.as_deref(), Some("R1"));
assert_eq!(component.kind.as_deref(), Some("resistor"));
}
#[test]
fn recognized_elements_without_identifiers_parse_to_none() {
let json = r#"[
{"type": "source_net", "name": "GND"},
{"type": "source_port", "name": "SDA"}
]"#;
let circuit = parse_ok(json);
let net = circuit.nets().next().expect("a net");
let port = circuit.ports().next().expect("a port");
assert!(net.net_id.is_none());
assert!(port.port_id.is_none());
assert!(port.net_id.is_none());
}
#[test]
fn object_root_and_empty_input_are_rejected() {
let err = parse("{\"type\": \"source_net\"}", Path::new("circuit.json"))
.expect_err("an object root must not parse");
assert!(err.message.contains("sequence"), "got: {}", err.message);
let err = parse("", Path::new("circuit.json")).expect_err("empty input must not parse");
assert!(err.message.contains("EOF"), "got: {}", err.message);
}
#[test]
fn span_mapping_handles_multiline_crlf_and_multibyte() {
let lf = "[\n{\"a\": 1,\n{\"b\": }\n]";
let offset = byte_offset_at(lf, 3, 7);
assert_eq!(offset, 17);
assert_eq!(lf.as_bytes()[offset], b'}');
let crlf = "[\r\n{\"a\": 1,\r\n{\"b\": }\r\n]";
let offset = byte_offset_at(crlf, 3, 7);
assert_eq!(offset, 19);
assert_eq!(crlf.as_bytes()[offset], b'}');
let multi = "[\n{\"a\": \"éé\", \"b\": }\n]";
let offset = byte_offset_at(multi, 2, 20);
assert_eq!(offset, 21);
assert_eq!(multi.as_bytes()[offset], b'}');
let offset = byte_offset_at(multi, 2, 9);
assert!(
multi.is_char_boundary(offset),
"offset {offset} splits a char"
);
assert_eq!(byte_offset_at(lf, 99, 1), lf.len());
}