Files
qelectrotech-source-mirror/tests/qttest/tst_scriptconductoruuid.cpp
T
ispyisail 2e2801b617 Fix qet.deleteElement() leaving the element's wires on the folio
qet.deleteElement() built its DeleteQGraphicsItemCommand from the element
alone. The Delete key's selection also carries the wires on the element's
terminals (DiagramContent's conductors to update), and the command removes
those with it. From a script they stayed: still listed by
qet.conductorUuids(), still saved, attached to an element that was gone.

The wires on the element's terminals now go into the command, as for the
Delete key. tst_scriptconductoruuid checks that no wire is left with an
end on the deleted element (it failed before this change: 1 left).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
2026-10-03 22:26:51 +13:00

127 lines
5.0 KiB
C++

// SPDX-License-Identifier: GPL-2.0-or-later
#include <QtTest>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QProcess>
#include <QProcessEnvironment>
#include <QTemporaryDir>
#include <QUuid>
// qet.conductorUuids(folio) and qet.conductorEnds(folio, uuid): a script can
// list a folio's conductors by uuid and find where each one runs, in the
// "{element} terminal N" form conductors() prints and the conductor calls
// take. Runs a script through the real binary's --run.
class tst_scriptconductoruuid : public QObject
{
Q_OBJECT
QTemporaryDir m_dir;
// Run @p script on the fixture in a sandbox of its own and return the
// JSON object it logged.
QJsonObject run(const QString &script)
{
const QString path = m_dir.filePath(QStringLiteral("probe.js"));
const QString home = m_dir.filePath(QStringLiteral("home"));
QDir().mkpath(home);
QFile f(path);
if (!f.open(QIODevice::WriteOnly)) return {};
f.write(script.toUtf8());
f.close();
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen"));
env.insert(QStringLiteral("QET_ENABLE_SCRIPTING"), QStringLiteral("1"));
env.insert(QStringLiteral("HOME"), home);
env.insert(QStringLiteral("XDG_CONFIG_HOME"), home + QStringLiteral("/config"));
env.insert(QStringLiteral("XDG_DATA_HOME"), home + QStringLiteral("/data"));
QProcess proc;
proc.setProcessEnvironment(env);
proc.start(QStringLiteral(QET_TEST_BINARY_PATH),
{QStringLiteral("--run"), path,
QFINDTESTDATA("fixtures/qet_bug_repro_resaved.qet")});
if (!proc.waitForFinished(60000)) return {};
const QString out = QString::fromUtf8(proc.readAllStandardOutput()
+ proc.readAllStandardError());
const QString mark = QStringLiteral("PROBE ");
for (const QString &line : out.split(QLatin1Char('\n'))) {
const int i = line.indexOf(mark);
if (i >= 0)
return QJsonDocument::fromJson(line.mid(i + mark.size()).toUtf8()).object();
}
return {};
}
private slots:
void initTestCase()
{
QVERIFY(m_dir.isValid());
QVERIFY(QFile::exists(QStringLiteral(QET_TEST_BINARY_PATH)));
}
void uuidsAndEndsMatchConductors()
{
const QJsonObject r = run(QStringLiteral(
"var uuids = qet.conductorUuids(0);\n"
"var ends = uuids.map(function (u) { return qet.conductorEnds(0, u); });\n"
"qet.log('PROBE ' + JSON.stringify({lines: qet.conductors(0), uuids: uuids, ends: ends,\n"
" unknown: qet.conductorEnds(0, '{00000000-0000-0000-0000-000000000001}'),\n"
" junk: qet.conductorEnds(0, 'not a uuid'),\n"
" badFolio: qet.conductorUuids(99)}));\n"));
QVERIFY2(!r.isEmpty(), "the script logged nothing");
const QJsonArray lines = r.value(QStringLiteral("lines")).toArray();
const QJsonArray uuids = r.value(QStringLiteral("uuids")).toArray();
const QJsonArray ends = r.value(QStringLiteral("ends")).toArray();
QCOMPARE(lines.size(), 7); // the fixture's conductors
QCOMPARE(uuids.size(), lines.size());
QSet<QString> distinct;
for (int i = 0; i < uuids.size(); ++i) {
const QString u = uuids.at(i).toString();
QVERIFY2(!QUuid(u).isNull(), qPrintable(u));
distinct.insert(u);
// same order as conductors(), and the same two ends it prints
const QJsonArray e = ends.at(i).toArray();
QCOMPARE(e.size(), 2);
const QString expected = e.at(0).toString() + QStringLiteral(" -- ")
+ e.at(1).toString() + QStringLiteral(" : ");
QVERIFY2(lines.at(i).toString().startsWith(expected),
qPrintable(lines.at(i).toString() + QStringLiteral(" | ") + expected));
}
QCOMPARE(distinct.size(), uuids.size());
QVERIFY(r.value(QStringLiteral("unknown")).toArray().isEmpty());
QVERIFY(r.value(QStringLiteral("junk")).toArray().isEmpty());
QVERIFY(r.value(QStringLiteral("badFolio")).toArray().isEmpty());
}
// qet.deleteElement() takes the wires on the element's terminals with
// it, as the Delete key does; they used to stay on the folio, attached
// to an element that was gone.
void deleteElementTakesItsWires()
{
const QJsonObject r = run(QStringLiteral(
"var p = 'embed://import/probe/v2_fuse.elmt';\n"
"var a = qet.addElement(0, p, 400, 400);\n"
"var b = qet.addElement(0, p, 470, 490);\n"
"qet.addConductor(0, a, 0, b, 0);\n"
"var before = qet.conductorUuids(0).length;\n"
"var deleted = qet.deleteElement(0, a);\n"
"var dangling = qet.conductorUuids(0).filter(function (u) {\n"
" return qet.conductorEnds(0, u).some(function (e) { return e.indexOf(a) === 0; }); });\n"
"qet.log('PROBE ' + JSON.stringify({deleted: deleted, before: before,\n"
" after: qet.conductorUuids(0).length, dangling: dangling.length}));\n"));
QVERIFY2(!r.isEmpty(), "the script logged nothing");
QVERIFY(r.value(QStringLiteral("deleted")).toBool());
QCOMPARE(r.value(QStringLiteral("dangling")).toInt(), 0);
QCOMPARE(r.value(QStringLiteral("after")).toInt(), r.value(QStringLiteral("before")).toInt() - 1);
}
};
QTEST_APPLESS_MAIN(tst_scriptconductoruuid)
#include "tst_scriptconductoruuid.moc"