Some checks are pending
CI / Lint and Format (push) Waiting to run
CI / Test Python 3.11 on macos-latest (push) Waiting to run
CI / Test Python 3.12 on macos-latest (push) Waiting to run
CI / Test Python 3.13 on macos-latest (push) Waiting to run
CI / Test Python 3.10 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.11 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.12 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.13 on ubuntu-latest (push) Waiting to run
CI / Security Scan (push) Waiting to run
CI / Build Package (push) Blocked by required conditions
Wire collision detection: apply_batch now tracks placed wire segments and detects collinear stubs on the same axis with overlapping ranges belonging to different nets. Colliding wires shift perpendicular to their axis by 1.27mm, preventing KiCad from merging wire segments into mega-nets. Project-local library resolution: apply_batch now scans batch component lib_ids for unknown libraries and registers them with kicad-sch-api's SymbolLibraryCache via sym-lib-table parsing before component placement. Unblocks projects using Samacsys and other non-standard symbol libraries. Root ERC: run_schematic_erc accepts root=True to resolve to the project root schematic before running kicad-cli, enabling hierarchy-aware ERC that eliminates ~180 false-positive global_label_dangling warnings from sub-sheet isolation. 270/270 tests pass, ruff + mypy clean.
656 lines
21 KiB
Python
656 lines
21 KiB
Python
"""Tests for the batch operations tool."""
|
|
|
|
import json
|
|
import os
|
|
|
|
import pytest
|
|
|
|
from tests.conftest import requires_sch_api
|
|
|
|
|
|
class TestBatchValidation:
|
|
"""Tests for batch JSON validation (no kicad-sch-api needed for some)."""
|
|
|
|
def test_bad_json_file(self, tmp_output_dir):
|
|
from mckicad.tools.batch import apply_batch
|
|
|
|
# Create a schematic file (minimal)
|
|
sch_path = os.path.join(tmp_output_dir, "test.kicad_sch")
|
|
with open(sch_path, "w") as f:
|
|
f.write("(kicad_sch (version 20230121))")
|
|
|
|
# Create a bad JSON file
|
|
bad_json = os.path.join(tmp_output_dir, "bad.json")
|
|
with open(bad_json, "w") as f:
|
|
f.write("{invalid json}")
|
|
|
|
result = apply_batch(
|
|
schematic_path=sch_path,
|
|
batch_file=bad_json,
|
|
)
|
|
|
|
assert result["success"] is False
|
|
assert "json" in result["error"].lower() or "JSON" in result["error"]
|
|
|
|
def test_nonexistent_batch_file(self, tmp_output_dir):
|
|
from mckicad.tools.batch import apply_batch
|
|
|
|
sch_path = os.path.join(tmp_output_dir, "test.kicad_sch")
|
|
with open(sch_path, "w") as f:
|
|
f.write("(kicad_sch (version 20230121))")
|
|
|
|
result = apply_batch(
|
|
schematic_path=sch_path,
|
|
batch_file="/nonexistent/batch.json",
|
|
)
|
|
|
|
assert result["success"] is False
|
|
assert "not found" in result["error"].lower()
|
|
|
|
def test_bad_schematic_path(self, batch_json_file):
|
|
from mckicad.tools.batch import apply_batch
|
|
|
|
result = apply_batch(
|
|
schematic_path="/nonexistent/path.kicad_sch",
|
|
batch_file=batch_json_file,
|
|
)
|
|
|
|
assert result["success"] is False
|
|
|
|
|
|
@requires_sch_api
|
|
class TestBatchDryRun:
|
|
"""Tests for batch dry_run mode."""
|
|
|
|
def test_dry_run_returns_preview(self, populated_schematic_with_ic, batch_json_file):
|
|
from mckicad.tools.batch import apply_batch
|
|
|
|
result = apply_batch(
|
|
schematic_path=populated_schematic_with_ic,
|
|
batch_file=batch_json_file,
|
|
dry_run=True,
|
|
)
|
|
|
|
assert result["success"] is True
|
|
assert result["dry_run"] is True
|
|
assert "preview" in result
|
|
assert result["preview"]["components"] == 2
|
|
assert result["preview"]["wires"] == 1
|
|
assert result["preview"]["labels"] == 1
|
|
assert result["preview"]["no_connects"] == 1
|
|
assert result["validation"] == "passed"
|
|
|
|
def test_dry_run_catches_missing_fields(self, populated_schematic_with_ic, tmp_output_dir):
|
|
from mckicad.tools.batch import apply_batch
|
|
|
|
bad_data = {
|
|
"components": [{"lib_id": "Device:R"}], # missing x, y
|
|
}
|
|
batch_path = os.path.join(tmp_output_dir, "bad_batch.json")
|
|
with open(batch_path, "w") as f:
|
|
json.dump(bad_data, f)
|
|
|
|
result = apply_batch(
|
|
schematic_path=populated_schematic_with_ic,
|
|
batch_file=batch_path,
|
|
dry_run=True,
|
|
)
|
|
|
|
assert result["success"] is False
|
|
assert "validation_errors" in result
|
|
|
|
def test_empty_batch_rejected(self, populated_schematic_with_ic, tmp_output_dir):
|
|
from mckicad.tools.batch import apply_batch
|
|
|
|
batch_path = os.path.join(tmp_output_dir, "empty_batch.json")
|
|
with open(batch_path, "w") as f:
|
|
json.dump({}, f)
|
|
|
|
result = apply_batch(
|
|
schematic_path=populated_schematic_with_ic,
|
|
batch_file=batch_path,
|
|
)
|
|
|
|
assert result["success"] is False
|
|
|
|
|
|
@requires_sch_api
|
|
class TestBatchApply:
|
|
"""Integration tests for applying batch operations."""
|
|
|
|
def test_apply_components_and_wires(self, populated_schematic_with_ic, batch_json_file):
|
|
from kicad_sch_api import load_schematic
|
|
|
|
from mckicad.tools.batch import apply_batch
|
|
|
|
result = apply_batch(
|
|
schematic_path=populated_schematic_with_ic,
|
|
batch_file=batch_json_file,
|
|
)
|
|
|
|
assert result["success"] is True
|
|
assert result["components_placed"] == 2
|
|
assert result["wires_placed"] == 1
|
|
assert result["labels_placed"] == 1
|
|
assert result["no_connects_placed"] == 1
|
|
|
|
# Verify components exist in saved schematic
|
|
sch = load_schematic(populated_schematic_with_ic)
|
|
r10 = sch.components.get("R10")
|
|
assert r10 is not None
|
|
assert r10.value == "1k"
|
|
|
|
def test_apply_with_power_symbols(self, populated_schematic_with_ic, tmp_output_dir):
|
|
from mckicad.tools.batch import apply_batch
|
|
|
|
data = {
|
|
"components": [
|
|
{"lib_id": "Device:C", "reference": "C20", "value": "100nF", "x": 300, "y": 100},
|
|
],
|
|
"power_symbols": [
|
|
{"net": "GND", "pin_ref": "C20", "pin_number": "2"},
|
|
],
|
|
}
|
|
batch_path = os.path.join(tmp_output_dir, "pwr_batch.json")
|
|
with open(batch_path, "w") as f:
|
|
json.dump(data, f)
|
|
|
|
result = apply_batch(
|
|
schematic_path=populated_schematic_with_ic,
|
|
batch_file=batch_path,
|
|
)
|
|
|
|
assert result["success"] is True
|
|
assert result["components_placed"] == 1
|
|
assert result["power_symbols_placed"] == 1
|
|
|
|
def test_mckicad_sidecar_lookup(self, populated_schematic_with_ic, tmp_output_dir):
|
|
"""Test that relative paths resolve to .mckicad/ directory."""
|
|
from mckicad.tools.batch import apply_batch
|
|
|
|
# Create .mckicad/ sidecar next to schematic
|
|
sch_dir = os.path.dirname(populated_schematic_with_ic)
|
|
mckicad_dir = os.path.join(sch_dir, ".mckicad")
|
|
os.makedirs(mckicad_dir, exist_ok=True)
|
|
|
|
data = {
|
|
"labels": [{"text": "SIDECAR_TEST", "x": 100, "y": 100}],
|
|
}
|
|
sidecar_path = os.path.join(mckicad_dir, "sidecar_batch.json")
|
|
with open(sidecar_path, "w") as f:
|
|
json.dump(data, f)
|
|
|
|
# Use relative path — should find it in .mckicad/
|
|
result = apply_batch(
|
|
schematic_path=populated_schematic_with_ic,
|
|
batch_file="sidecar_batch.json",
|
|
)
|
|
|
|
assert result["success"] is True
|
|
assert result["labels_placed"] == 1
|
|
|
|
def test_batch_labels_persist_in_file(self, populated_schematic_with_ic, tmp_output_dir):
|
|
"""Batch labels (local and global) must appear in the saved file."""
|
|
from mckicad.tools.batch import apply_batch
|
|
|
|
data = {
|
|
"labels": [
|
|
{"text": "BATCH_LOCAL", "x": 150, "y": 100},
|
|
{"text": "BATCH_GLOBAL", "x": 160, "y": 110, "global": True},
|
|
],
|
|
}
|
|
batch_path = os.path.join(tmp_output_dir, "label_persist_batch.json")
|
|
with open(batch_path, "w") as f:
|
|
json.dump(data, f)
|
|
|
|
result = apply_batch(
|
|
schematic_path=populated_schematic_with_ic,
|
|
batch_file=batch_path,
|
|
)
|
|
|
|
assert result["success"] is True
|
|
assert result["labels_placed"] == 2
|
|
|
|
with open(populated_schematic_with_ic) as f:
|
|
content = f.read()
|
|
|
|
assert '(label "BATCH_LOCAL"' in content
|
|
assert '(global_label "BATCH_GLOBAL"' in content
|
|
|
|
|
|
@requires_sch_api
|
|
class TestBatchPinRefLabels:
|
|
"""Tests for pin-referenced label placement in batch operations."""
|
|
|
|
def test_pin_ref_label_validation_accepts_valid(
|
|
self, populated_schematic_with_ic, tmp_output_dir,
|
|
):
|
|
"""Pin-ref labels with valid references pass validation."""
|
|
from mckicad.tools.batch import apply_batch
|
|
|
|
data = {
|
|
"labels": [
|
|
{"text": "PIN_REF_NET", "pin_ref": "R1", "pin_number": "1", "global": True},
|
|
],
|
|
}
|
|
batch_path = os.path.join(tmp_output_dir, "pinref_valid.json")
|
|
with open(batch_path, "w") as f:
|
|
json.dump(data, f)
|
|
|
|
result = apply_batch(
|
|
schematic_path=populated_schematic_with_ic,
|
|
batch_file=batch_path,
|
|
dry_run=True,
|
|
)
|
|
|
|
assert result["success"] is True
|
|
|
|
def test_pin_ref_label_validation_rejects_missing_ref(
|
|
self, populated_schematic_with_ic, tmp_output_dir,
|
|
):
|
|
"""Pin-ref labels with unknown references fail validation."""
|
|
from mckicad.tools.batch import apply_batch
|
|
|
|
data = {
|
|
"labels": [
|
|
{"text": "BAD", "pin_ref": "U99", "pin_number": "1"},
|
|
],
|
|
}
|
|
batch_path = os.path.join(tmp_output_dir, "pinref_bad.json")
|
|
with open(batch_path, "w") as f:
|
|
json.dump(data, f)
|
|
|
|
result = apply_batch(
|
|
schematic_path=populated_schematic_with_ic,
|
|
batch_file=batch_path,
|
|
dry_run=True,
|
|
)
|
|
|
|
assert result["success"] is False
|
|
assert any("U99" in e for e in result["validation_errors"])
|
|
|
|
def test_label_requires_coords_or_pin_ref(
|
|
self, populated_schematic_with_ic, tmp_output_dir,
|
|
):
|
|
"""Labels without coords or pin_ref fail validation."""
|
|
from mckicad.tools.batch import apply_batch
|
|
|
|
data = {
|
|
"labels": [
|
|
{"text": "ORPHAN"},
|
|
],
|
|
}
|
|
batch_path = os.path.join(tmp_output_dir, "pinref_orphan.json")
|
|
with open(batch_path, "w") as f:
|
|
json.dump(data, f)
|
|
|
|
result = apply_batch(
|
|
schematic_path=populated_schematic_with_ic,
|
|
batch_file=batch_path,
|
|
dry_run=True,
|
|
)
|
|
|
|
assert result["success"] is False
|
|
assert any("pin-reference" in e or "coordinate" in e for e in result["validation_errors"])
|
|
|
|
def test_pin_ref_label_creates_label_and_wire(
|
|
self, populated_schematic_with_ic, tmp_output_dir,
|
|
):
|
|
"""Pin-referenced label creates both a label and a wire stub in the file."""
|
|
from mckicad.tools.batch import apply_batch
|
|
|
|
data = {
|
|
"labels": [
|
|
{"text": "GPIO_TEST", "pin_ref": "R1", "pin_number": "1", "global": True},
|
|
],
|
|
}
|
|
batch_path = os.path.join(tmp_output_dir, "pinref_apply.json")
|
|
with open(batch_path, "w") as f:
|
|
json.dump(data, f)
|
|
|
|
result = apply_batch(
|
|
schematic_path=populated_schematic_with_ic,
|
|
batch_file=batch_path,
|
|
)
|
|
|
|
assert result["success"] is True
|
|
assert result["labels_placed"] == 1
|
|
|
|
with open(populated_schematic_with_ic) as f:
|
|
content = f.read()
|
|
|
|
assert '(global_label "GPIO_TEST"' in content
|
|
assert "(wire\n" in content
|
|
|
|
|
|
@requires_sch_api
|
|
class TestBatchLabelConnections:
|
|
"""Tests for label_connections batch operations."""
|
|
|
|
def test_label_connections_validation_accepts_valid(
|
|
self, populated_schematic_with_ic, tmp_output_dir,
|
|
):
|
|
"""label_connections with valid refs pass validation."""
|
|
from mckicad.tools.batch import apply_batch
|
|
|
|
data = {
|
|
"label_connections": [
|
|
{
|
|
"net": "SHARED_NET",
|
|
"global": True,
|
|
"connections": [
|
|
{"ref": "R1", "pin": "1"},
|
|
{"ref": "C1", "pin": "1"},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
batch_path = os.path.join(tmp_output_dir, "lc_valid.json")
|
|
with open(batch_path, "w") as f:
|
|
json.dump(data, f)
|
|
|
|
result = apply_batch(
|
|
schematic_path=populated_schematic_with_ic,
|
|
batch_file=batch_path,
|
|
dry_run=True,
|
|
)
|
|
|
|
assert result["success"] is True
|
|
assert result["preview"]["label_connections"] == 2
|
|
|
|
def test_label_connections_validation_rejects_bad_ref(
|
|
self, populated_schematic_with_ic, tmp_output_dir,
|
|
):
|
|
"""label_connections with unknown refs fail validation."""
|
|
from mckicad.tools.batch import apply_batch
|
|
|
|
data = {
|
|
"label_connections": [
|
|
{
|
|
"net": "BAD_NET",
|
|
"connections": [
|
|
{"ref": "MISSING_REF", "pin": "1"},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
batch_path = os.path.join(tmp_output_dir, "lc_bad.json")
|
|
with open(batch_path, "w") as f:
|
|
json.dump(data, f)
|
|
|
|
result = apply_batch(
|
|
schematic_path=populated_schematic_with_ic,
|
|
batch_file=batch_path,
|
|
dry_run=True,
|
|
)
|
|
|
|
assert result["success"] is False
|
|
assert any("MISSING_REF" in e for e in result["validation_errors"])
|
|
|
|
def test_label_connections_creates_labels_at_different_positions(
|
|
self, populated_schematic_with_ic, tmp_output_dir,
|
|
):
|
|
"""label_connections places same-named labels at unique pin positions."""
|
|
from mckicad.tools.batch import apply_batch
|
|
|
|
data = {
|
|
"label_connections": [
|
|
{
|
|
"net": "MULTI_PIN_NET",
|
|
"global": True,
|
|
"connections": [
|
|
{"ref": "R1", "pin": "1"},
|
|
{"ref": "C1", "pin": "1"},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
batch_path = os.path.join(tmp_output_dir, "lc_multi.json")
|
|
with open(batch_path, "w") as f:
|
|
json.dump(data, f)
|
|
|
|
result = apply_batch(
|
|
schematic_path=populated_schematic_with_ic,
|
|
batch_file=batch_path,
|
|
)
|
|
|
|
assert result["success"] is True
|
|
assert result["labels_placed"] >= 2
|
|
|
|
with open(populated_schematic_with_ic) as f:
|
|
content = f.read()
|
|
|
|
# Two labels with same text
|
|
import re
|
|
|
|
matches = re.findall(r'\(global_label "MULTI_PIN_NET"', content)
|
|
assert len(matches) == 2
|
|
|
|
# Wire stubs present
|
|
wire_matches = re.findall(r"\(wire\n", content)
|
|
assert len(wire_matches) >= 2
|
|
|
|
|
|
@requires_sch_api
|
|
class TestBatchPinRefNoConnects:
|
|
"""Tests for pin-referenced no_connect placement in batch operations."""
|
|
|
|
def test_pin_ref_no_connect_placed(
|
|
self, populated_schematic_with_ic, tmp_output_dir,
|
|
):
|
|
"""Pin-referenced no_connect resolves pin position and places marker."""
|
|
from mckicad.tools.batch import apply_batch
|
|
|
|
data = {
|
|
"no_connects": [
|
|
{"pin_ref": "R1", "pin_number": "1"},
|
|
],
|
|
}
|
|
batch_path = os.path.join(tmp_output_dir, "nc_pinref.json")
|
|
with open(batch_path, "w") as f:
|
|
json.dump(data, f)
|
|
|
|
result = apply_batch(
|
|
schematic_path=populated_schematic_with_ic,
|
|
batch_file=batch_path,
|
|
)
|
|
|
|
assert result["success"] is True
|
|
assert result["no_connects_placed"] == 1
|
|
|
|
def test_pin_ref_no_connect_validation_rejects_bad_ref(
|
|
self, populated_schematic_with_ic, tmp_output_dir,
|
|
):
|
|
"""Pin-referenced no_connect with unknown ref fails validation."""
|
|
from mckicad.tools.batch import apply_batch
|
|
|
|
data = {
|
|
"no_connects": [
|
|
{"pin_ref": "MISSING99", "pin_number": "1"},
|
|
],
|
|
}
|
|
batch_path = os.path.join(tmp_output_dir, "nc_bad_ref.json")
|
|
with open(batch_path, "w") as f:
|
|
json.dump(data, f)
|
|
|
|
result = apply_batch(
|
|
schematic_path=populated_schematic_with_ic,
|
|
batch_file=batch_path,
|
|
dry_run=True,
|
|
)
|
|
|
|
assert result["success"] is False
|
|
assert any("MISSING99" in e for e in result["validation_errors"])
|
|
|
|
def test_no_connect_requires_coords_or_pin_ref(
|
|
self, populated_schematic_with_ic, tmp_output_dir,
|
|
):
|
|
"""No-connect without coords or pin_ref fails validation."""
|
|
from mckicad.tools.batch import apply_batch
|
|
|
|
data = {
|
|
"no_connects": [
|
|
{},
|
|
],
|
|
}
|
|
batch_path = os.path.join(tmp_output_dir, "nc_orphan.json")
|
|
with open(batch_path, "w") as f:
|
|
json.dump(data, f)
|
|
|
|
result = apply_batch(
|
|
schematic_path=populated_schematic_with_ic,
|
|
batch_file=batch_path,
|
|
dry_run=True,
|
|
)
|
|
|
|
assert result["success"] is False
|
|
assert any("pin-reference" in e or "coordinate" in e for e in result["validation_errors"])
|
|
|
|
|
|
@requires_sch_api
|
|
class TestBatchHierarchyContext:
|
|
"""Tests for hierarchy context in batch operations."""
|
|
|
|
def test_hierarchy_context_sets_instance_path(
|
|
self, populated_schematic_with_ic, tmp_output_dir,
|
|
):
|
|
"""Passing parent_uuid and sheet_uuid sets hierarchy context on the schematic."""
|
|
from unittest.mock import patch
|
|
|
|
from mckicad.tools.batch import apply_batch
|
|
|
|
data = {
|
|
"labels": [
|
|
{"text": "HIERARCHY_TEST", "x": 100, "y": 100},
|
|
],
|
|
}
|
|
batch_path = os.path.join(tmp_output_dir, "hier_batch.json")
|
|
with open(batch_path, "w") as f:
|
|
json.dump(data, f)
|
|
|
|
with patch("mckicad.tools.batch._ksa_load") as mock_load:
|
|
# Let the real load happen but spy on set_hierarchy_context
|
|
from kicad_sch_api import load_schematic
|
|
|
|
real_sch = load_schematic(populated_schematic_with_ic)
|
|
mock_load.return_value = real_sch
|
|
|
|
result = apply_batch(
|
|
schematic_path=populated_schematic_with_ic,
|
|
batch_file=batch_path,
|
|
parent_uuid="aaaa-bbbb-cccc",
|
|
sheet_uuid="dddd-eeee-ffff",
|
|
)
|
|
|
|
assert result["success"] is True
|
|
# Verify the hierarchy path was set
|
|
assert real_sch._hierarchy_path == "/aaaa-bbbb-cccc/dddd-eeee-ffff"
|
|
|
|
def test_no_hierarchy_context_without_params(
|
|
self, populated_schematic_with_ic, tmp_output_dir,
|
|
):
|
|
"""Without parent_uuid/sheet_uuid, no hierarchy context is set."""
|
|
from mckicad.tools.batch import apply_batch
|
|
|
|
data = {
|
|
"labels": [
|
|
{"text": "NO_HIER_TEST", "x": 100, "y": 100},
|
|
],
|
|
}
|
|
batch_path = os.path.join(tmp_output_dir, "no_hier_batch.json")
|
|
with open(batch_path, "w") as f:
|
|
json.dump(data, f)
|
|
|
|
result = apply_batch(
|
|
schematic_path=populated_schematic_with_ic,
|
|
batch_file=batch_path,
|
|
)
|
|
|
|
assert result["success"] is True
|
|
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestRegisterProjectLibraries:
|
|
"""Tests for project-local library registration in apply_batch."""
|
|
|
|
def test_registers_unknown_library_from_sym_lib_table(self, tmp_path):
|
|
"""Libraries listed in sym-lib-table are registered with the cache."""
|
|
from mckicad.tools.batch import _register_project_libraries
|
|
|
|
# Create a minimal .kicad_sym file
|
|
lib_dir = tmp_path / "libs"
|
|
lib_dir.mkdir()
|
|
kicad_sym = lib_dir / "CustomLib.kicad_sym"
|
|
kicad_sym.write_text(
|
|
'(kicad_symbol_lib (version 20211014) (generator test)\n'
|
|
' (symbol "MyPart" (in_bom yes) (on_board yes)\n'
|
|
' (property "Reference" "U" (at 0 0 0) (effects (font (size 1.27 1.27))))\n'
|
|
' (property "Value" "MyPart" (at 0 0 0) (effects (font (size 1.27 1.27))))\n'
|
|
' )\n'
|
|
')\n'
|
|
)
|
|
|
|
# Create a sym-lib-table that references it
|
|
sym_lib_table = tmp_path / "sym-lib-table"
|
|
sym_lib_table.write_text(
|
|
'(sym_lib_table\n'
|
|
' (version 7)\n'
|
|
' (lib (name "CustomLib")(type "KiCad")(uri "${KIPRJMOD}/libs/CustomLib.kicad_sym")(options "")(descr "Test lib"))\n'
|
|
')\n'
|
|
)
|
|
|
|
# Create a minimal .kicad_pro so _find_project_root works
|
|
kicad_pro = tmp_path / "test.kicad_pro"
|
|
kicad_pro.write_text("{}")
|
|
|
|
sch_path = str(tmp_path / "test.kicad_sch")
|
|
|
|
data = {
|
|
"components": [
|
|
{"lib_id": "CustomLib:MyPart", "reference": "U1", "value": "MyPart", "x": 100, "y": 100},
|
|
],
|
|
}
|
|
|
|
registered = _register_project_libraries(data, sch_path)
|
|
assert "CustomLib" in registered
|
|
|
|
def test_skips_already_known_library(self, tmp_path):
|
|
"""Standard libraries (Device, etc.) are not re-registered."""
|
|
from mckicad.tools.batch import _register_project_libraries
|
|
|
|
sch_path = str(tmp_path / "test.kicad_sch")
|
|
data = {
|
|
"components": [
|
|
{"lib_id": "Device:R", "reference": "R1", "value": "10k", "x": 100, "y": 100},
|
|
],
|
|
}
|
|
|
|
# Device is already in the cache from /usr/share/kicad/symbols/
|
|
registered = _register_project_libraries(data, sch_path)
|
|
assert "Device" not in registered
|
|
|
|
def test_no_components_returns_empty(self, tmp_path):
|
|
"""Batch with no components produces no registrations."""
|
|
from mckicad.tools.batch import _register_project_libraries
|
|
|
|
sch_path = str(tmp_path / "test.kicad_sch")
|
|
data = {"labels": [{"text": "NET1", "x": 0, "y": 0}]}
|
|
|
|
registered = _register_project_libraries(data, sch_path)
|
|
assert registered == []
|
|
|
|
def test_missing_library_file_not_registered(self, tmp_path):
|
|
"""Non-existent library file returns empty (no crash)."""
|
|
from mckicad.tools.batch import _register_project_libraries
|
|
|
|
sch_path = str(tmp_path / "test.kicad_sch")
|
|
data = {
|
|
"components": [
|
|
{"lib_id": "NonExistentLib:FakePart", "reference": "U1", "value": "X", "x": 0, "y": 0},
|
|
],
|
|
}
|
|
|
|
registered = _register_project_libraries(data, sch_path)
|
|
assert registered == []
|