A script using `duplicate` failed with "unsupported command 'duplicate'"
because the clone logic was still inline in the tui command. Extract it to
core/app/edit.hpp::duplicate_module(System*, src, dst) -> {ok, error, parts,
signals}: a deep clone of a module (parts, pins with spec + nc_origin, signals
with type overrides, pin→signal wiring; no connections), refusing on an unknown
source or an already-taken destination name.
- the tui `duplicate` command renders the result (output unchanged);
- the script engine dispatches `duplicate` to it — the failing script now runs;
- the wx GUI gains Edit ▸ Duplicate module… (PickModule + a name prompt).
tests/test_edit.cpp: deep clone wires to the clone's own signal (not the
source's) and preserves the type; unknown source / existing destination
refused. 412 core assertions green; tui + wx build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
114 lines
3.8 KiB
C++
114 lines
3.8 KiB
C++
#include <doctest/doctest.h>
|
|
|
|
#include "core/app/edit.hpp"
|
|
#include "core/domain/modules.hpp"
|
|
#include "core/domain/parts.hpp"
|
|
#include "core/domain/pins.hpp"
|
|
#include "core/domain/signal_type.hpp"
|
|
#include "core/domain/signals.hpp"
|
|
#include "core/domain/system.hpp"
|
|
|
|
// app::set_connector_type is pure core: validate the kind, tag the part and
|
|
// apply the connector model. No Print/dialog/FTXUI.
|
|
|
|
TEST_CASE("set_connector_type tags a part with a free-form kind") {
|
|
Part p("J1");
|
|
p.add(new Pin("1"));
|
|
p.add(new Pin("2"));
|
|
|
|
app::SetConnectorTypeResult r = app::set_connector_type(&p, "myconn");
|
|
|
|
CHECK(r.ok);
|
|
CHECK(r.error.empty());
|
|
CHECK(p.connector_type == "myconn");
|
|
}
|
|
|
|
TEST_CASE("set_connector_type refuses a kind the part doesn't fit — no mutation") {
|
|
Part p("J1");
|
|
p.add(new Pin("1"));
|
|
p.add(new Pin("2"));
|
|
p.add(new Pin("3")); // numeric pins don't fit the VPX single-letter columns
|
|
|
|
app::SetConnectorTypeResult r = app::set_connector_type(&p, "vpx-3u-bkp-p0");
|
|
|
|
CHECK_FALSE(r.ok);
|
|
CHECK_FALSE(r.error.empty());
|
|
CHECK(p.connector_type.empty()); // refused before any change
|
|
}
|
|
|
|
TEST_CASE("set_connector_type on a null part fails cleanly") {
|
|
app::SetConnectorTypeResult r = app::set_connector_type(nullptr, "x");
|
|
CHECK_FALSE(r.ok);
|
|
CHECK_FALSE(r.error.empty());
|
|
}
|
|
|
|
TEST_CASE("attach_bsdl reports a parse failure without mutating the part") {
|
|
Part p("J1");
|
|
p.add(new Pin("1"));
|
|
|
|
app::AttachBsdlResult r = app::attach_bsdl(&p, "/nonexistent-xyz/none.bsd");
|
|
|
|
CHECK_FALSE(r.ok);
|
|
CHECK(r.error.find("cannot parse") != std::string::npos);
|
|
CHECK(p.bsdl_path.empty()); // failure leaves the part untouched
|
|
}
|
|
|
|
TEST_CASE("attach_bsdl on a null part fails cleanly") {
|
|
app::AttachBsdlResult r = app::attach_bsdl(nullptr, "x.bsd");
|
|
CHECK_FALSE(r.ok);
|
|
CHECK_FALSE(r.error.empty());
|
|
}
|
|
|
|
TEST_CASE("set_signal_type parses the name and sets the type") {
|
|
Signal s("NET");
|
|
app::SetSignalTypeResult r = app::set_signal_type(&s, "power");
|
|
CHECK(r.ok);
|
|
CHECK(r.type == SignalType::Power);
|
|
CHECK(s.type == SignalType::Power);
|
|
}
|
|
|
|
TEST_CASE("set_signal_type rejects an unknown name without mutating") {
|
|
Signal s("NET");
|
|
s.type = SignalType::Other;
|
|
app::SetSignalTypeResult r = app::set_signal_type(&s, "bogus");
|
|
CHECK_FALSE(r.ok);
|
|
CHECK(r.error.find("power, gnd, other") != std::string::npos);
|
|
CHECK(s.type == SignalType::Other); // unchanged
|
|
}
|
|
|
|
TEST_CASE("duplicate_module deep-clones parts, pins and signals") {
|
|
System sys;
|
|
Module *a = sys.modules()->merge("A");
|
|
Part *p = new Part("J1"); a->add(p);
|
|
Pin *pin = new Pin("1"); p->add(pin);
|
|
Signal *s = a->signals->merge("NET");
|
|
s->type = SignalType::Power;
|
|
s->add(pin); pin->connect(s);
|
|
|
|
app::DuplicateResult r = app::duplicate_module(&sys, "A", "B");
|
|
|
|
CHECK(r.ok);
|
|
CHECK(r.parts == 1);
|
|
CHECK(r.signals == 1);
|
|
REQUIRE(sys.modules()->exists("B"));
|
|
Module *b = sys.modules()->get("B");
|
|
CHECK(b->signals->get("NET")->type == SignalType::Power); // type preserved
|
|
Pin *bpin = b->get("J1")->get("1");
|
|
REQUIRE(bpin->signal() != nullptr);
|
|
CHECK(bpin->signal() == b->signals->get("NET")); // wired to the clone's own signal
|
|
CHECK(bpin->signal() != s); // not aliasing the source
|
|
}
|
|
|
|
TEST_CASE("duplicate_module refuses an unknown source or an existing destination") {
|
|
System sys;
|
|
sys.modules()->merge("A");
|
|
|
|
app::DuplicateResult dst = app::duplicate_module(&sys, "A", "A");
|
|
CHECK_FALSE(dst.ok);
|
|
CHECK(dst.error.find("already exists") != std::string::npos);
|
|
|
|
app::DuplicateResult unk = app::duplicate_module(&sys, "NOPE", "X");
|
|
CHECK_FALSE(unk.ok);
|
|
CHECK(unk.error.find("unknown module") != std::string::npos);
|
|
}
|