mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-05 18:54:14 +02:00
d76f3528ae
Schemas now get saved with a UUID and do not have to be looked up according to the form. They can be renamed and it gets reflected in existing elements. The GUI for editing them is now much easier, e.g. forms can be reshuffled. A formula can be applied. Many options now also for conductors and sheets.
977 lines
43 KiB
C++
977 lines
43 KiB
C++
/*
|
|
Copyright 2006-2026 The QElectroTech Team
|
|
This file is part of QElectroTech.
|
|
|
|
QElectroTech is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
QElectroTech is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
// Element numbering schemes have an id, and elements follow a scheme by it
|
|
// (elementInformation "formula_id"). Runs the real binary on
|
|
// examples/industrial.qet, written before the ids existed: its schemes
|
|
// "Equipment" and "XV" are followed by elements, and it also holds
|
|
// elements whose formula no scheme defines any more (%prefixV1:%sequ_1...,
|
|
// left behind by earlier edits of a scheme), which must stay unlinked.
|
|
|
|
#include <QtTest>
|
|
|
|
#include <QDomDocument>
|
|
#include <QFile>
|
|
#include <QJsonArray>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QProcess>
|
|
#include <QProcessEnvironment>
|
|
#include <QTemporaryDir>
|
|
#include <QUuid>
|
|
|
|
class tst_elementautonumids : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
QTemporaryDir m_dir;
|
|
int m_run = 0;
|
|
|
|
QString m_preferences; // contents of the settings file of the next run
|
|
|
|
QProcessEnvironment environment()
|
|
{
|
|
const QString home = m_dir.filePath(QStringLiteral("home%1").arg(m_run++));
|
|
QDir().mkpath(home);
|
|
if (!m_preferences.isEmpty()) {
|
|
QDir().mkpath(home + QStringLiteral("/config/QElectroTech"));
|
|
QFile settings(home + QStringLiteral("/config/QElectroTech/QElectroTech.conf"));
|
|
if (settings.open(QIODevice::WriteOnly)) {
|
|
settings.write(m_preferences.toUtf8());
|
|
}
|
|
}
|
|
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"));
|
|
env.insert(QStringLiteral("TMPDIR"), m_dir.path());
|
|
return env;
|
|
}
|
|
|
|
QString resave(const QString &in)
|
|
{
|
|
const QString out = m_dir.filePath(QStringLiteral("out%1.qet").arg(m_run));
|
|
QProcess proc;
|
|
proc.setProcessEnvironment(environment());
|
|
proc.start(QStringLiteral(QET_TEST_BINARY_PATH), {QStringLiteral("--resave"), in, out});
|
|
if (!proc.waitForFinished(180000) || proc.exitCode() != 0) return {};
|
|
return out;
|
|
}
|
|
|
|
QJsonObject run(const QString &script, const QString &project)
|
|
{
|
|
const QString path = m_dir.filePath(QStringLiteral("probe%1.js").arg(m_run));
|
|
QFile f(path);
|
|
if (!f.open(QIODevice::WriteOnly)) return {};
|
|
f.write(script.toUtf8());
|
|
f.close();
|
|
|
|
QProcess proc;
|
|
proc.setProcessEnvironment(environment());
|
|
proc.start(QStringLiteral(QET_TEST_BINARY_PATH), {QStringLiteral("--run"), path, project});
|
|
if (!proc.waitForFinished(180000)) 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();
|
|
}
|
|
qWarning().noquote() << out;
|
|
return {};
|
|
}
|
|
|
|
static QByteArray read(const QString &path)
|
|
{
|
|
QFile f(path);
|
|
return f.open(QIODevice::ReadOnly) ? f.readAll() : QByteArray();
|
|
}
|
|
|
|
struct Saved
|
|
{
|
|
QHash<QString, QUuid> scheme_id; // title -> id
|
|
QHash<QString, QString> scheme_formula; // title -> formula
|
|
QString current, current_id;
|
|
// per element: formula and formula_id
|
|
QList<QPair<QString, QString>> elements;
|
|
};
|
|
|
|
static Saved parse(const QByteArray &xml)
|
|
{
|
|
Saved s;
|
|
QDomDocument doc;
|
|
if (!doc.setContent(xml)) return s;
|
|
const QDomNodeList groups = doc.elementsByTagName(QStringLiteral("element_autonums"));
|
|
if (!groups.isEmpty()) {
|
|
const QDomElement g = groups.at(0).toElement();
|
|
s.current = g.attribute(QStringLiteral("current_autonum"));
|
|
s.current_id = g.attribute(QStringLiteral("current_autonum_id"));
|
|
const QDomNodeList schemes = g.elementsByTagName(QStringLiteral("element_autonum"));
|
|
for (int i = 0 ; i < schemes.count() ; ++i) {
|
|
const QDomElement e = schemes.at(i).toElement();
|
|
s.scheme_id.insert(e.attribute(QStringLiteral("title")),
|
|
QUuid(e.attribute(QStringLiteral("id"))));
|
|
s.scheme_formula.insert(e.attribute(QStringLiteral("title")),
|
|
e.attribute(QStringLiteral("formula")));
|
|
}
|
|
}
|
|
const QDomNodeList elements = doc.elementsByTagName(QStringLiteral("element"));
|
|
for (int i = 0 ; i < elements.count() ; ++i) {
|
|
const QDomElement infos = elements.at(i).toElement()
|
|
.firstChildElement(QStringLiteral("elementInformations"));
|
|
if (infos.isNull()) continue;
|
|
QString formula, id;
|
|
for (QDomElement info = infos.firstChildElement(QStringLiteral("elementInformation"));
|
|
!info.isNull();
|
|
info = info.nextSiblingElement(QStringLiteral("elementInformation"))) {
|
|
if (info.attribute(QStringLiteral("name")) == QLatin1String("formula"))
|
|
formula = info.text();
|
|
else if (info.attribute(QStringLiteral("name")) == QLatin1String("formula_id"))
|
|
id = info.text();
|
|
}
|
|
s.elements << qMakePair(formula, id);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
// industrial.qet with the element labelled @p label frozen
|
|
QString withFrozen(const QString &label)
|
|
{
|
|
QFile in(QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
|
|
if (!in.open(QIODevice::ReadOnly)) return {};
|
|
QDomDocument doc;
|
|
if (!doc.setContent(&in)) return {};
|
|
int frozen = 0;
|
|
const QDomNodeList elements = doc.elementsByTagName(QStringLiteral("element"));
|
|
for (int i = 0 ; i < elements.count() ; ++i) {
|
|
QDomElement e = elements.at(i).toElement();
|
|
const QDomElement infos = e.firstChildElement(QStringLiteral("elementInformations"));
|
|
for (QDomElement info = infos.firstChildElement(QStringLiteral("elementInformation"));
|
|
!info.isNull();
|
|
info = info.nextSiblingElement(QStringLiteral("elementInformation"))) {
|
|
if (info.attribute(QStringLiteral("name")) == QLatin1String("label")
|
|
&& info.text() == label) {
|
|
e.setAttribute(QStringLiteral("freezeLabel"), QStringLiteral("true"));
|
|
++frozen;
|
|
}
|
|
}
|
|
}
|
|
if (frozen != 1) return {};
|
|
const QString out = m_dir.filePath(QStringLiteral("frozen%1.qet").arg(m_run));
|
|
QFile f(out);
|
|
if (!f.open(QIODevice::WriteOnly)) return {};
|
|
f.write(doc.toByteArray());
|
|
return out;
|
|
}
|
|
|
|
static QStringList diagramAttributes(const QByteArray &xml, const QString &attribute)
|
|
{
|
|
QStringList values;
|
|
QDomDocument doc;
|
|
if (!doc.setContent(xml)) return values;
|
|
const QDomNodeList diagrams = doc.elementsByTagName(QStringLiteral("diagram"));
|
|
for (int i = 0 ; i < diagrams.count() ; ++i)
|
|
values << diagrams.at(i).toElement().attribute(attribute);
|
|
return values;
|
|
}
|
|
|
|
// qet.autoNums() lists the numberings as "name: formula='...'"
|
|
static QStringList titlesOf(const QJsonValue &list)
|
|
{
|
|
QStringList titles;
|
|
for (const QJsonValue &entry : list.toArray()) {
|
|
const QString text = entry.toString();
|
|
const int cut = text.indexOf(QLatin1String(": formula="));
|
|
titles << (cut >= 0 ? text.left(cut) : text);
|
|
}
|
|
titles.sort();
|
|
return titles;
|
|
}
|
|
|
|
static int linkedTo(const Saved &s, const QString &title)
|
|
{
|
|
int n = 0;
|
|
for (const auto &e : s.elements)
|
|
if (QUuid(e.second) == s.scheme_id.value(title)) ++n;
|
|
return n;
|
|
}
|
|
|
|
private slots:
|
|
void initTestCase()
|
|
{
|
|
QVERIFY(m_dir.isValid());
|
|
QVERIFY(QFile::exists(QStringLiteral(QET_TEST_BINARY_PATH)));
|
|
}
|
|
|
|
// A file without ids: every scheme gets one, the same on every load;
|
|
// an element is linked to the one scheme with its formula, and only then.
|
|
void legacyFileGetsIds()
|
|
{
|
|
const QString project = QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet");
|
|
const QString first = resave(project);
|
|
QVERIFY2(!first.isEmpty(), "--resave failed");
|
|
const QString again = resave(project);
|
|
QVERIFY2(!again.isEmpty(), "second --resave of the original failed");
|
|
//Two separate loads write attributes in their own order, so
|
|
//compare what they saved, not the bytes
|
|
const Saved s = parse(read(first));
|
|
const Saved s_again = parse(read(again));
|
|
QVERIFY2(s.scheme_id == s_again.scheme_id,
|
|
"the ids derived for a legacy file differ between loads");
|
|
QVERIFY2(s.elements == s_again.elements,
|
|
"the elements were linked differently on another load");
|
|
QVERIFY(!s.scheme_id.isEmpty());
|
|
QSet<QUuid> ids;
|
|
for (auto it = s.scheme_id.constBegin() ; it != s.scheme_id.constEnd() ; ++it) {
|
|
QVERIFY2(!it.value().isNull(), qPrintable(it.key() + QStringLiteral(" has no id")));
|
|
ids << it.value();
|
|
}
|
|
QCOMPARE(ids.size(), s.scheme_id.size());
|
|
|
|
QMultiHash<QString, QString> titles_by_formula;
|
|
for (auto it = s.scheme_formula.constBegin() ; it != s.scheme_formula.constEnd() ; ++it)
|
|
titles_by_formula.insert(it.value(), it.key());
|
|
|
|
for (const auto &e : s.elements) {
|
|
const QStringList titles = titles_by_formula.values(e.first);
|
|
if (!e.first.isEmpty() && titles.size() == 1) {
|
|
QCOMPARE(QUuid(e.second), s.scheme_id.value(titles.first()));
|
|
} else {
|
|
QVERIFY2(e.second.isEmpty(),
|
|
qPrintable(QStringLiteral("element with formula '%1' is linked")
|
|
.arg(e.first)));
|
|
}
|
|
}
|
|
QVERIFY(linkedTo(s, QStringLiteral("Equipment")) > 0);
|
|
QVERIFY(linkedTo(s, QStringLiteral("XV")) > 0);
|
|
|
|
const QString second = resave(first);
|
|
QVERIFY2(!second.isEmpty(), "--resave of the resaved file failed");
|
|
QVERIFY2(read(first) == read(second), "the second save changed the file");
|
|
}
|
|
|
|
// Rename, refused rename and removal, an edit that keeps the numbers,
|
|
// an edit that renumbers, and undo/redo: through the scripting API,
|
|
// which goes through the same ElementAutoNumSchemeCommand as the UI.
|
|
void renameEditRemoveUndo()
|
|
{
|
|
const QString saved = m_dir.filePath(QStringLiteral("scripted.qet"));
|
|
const QString script = QStringLiteral(R"JS(
|
|
function linked(formula) {
|
|
var r = [];
|
|
for (var f = 0; f < qet.folioCount(); ++f) {
|
|
var u = qet.elementUuids(f);
|
|
for (var i = 0; i < u.length; ++i)
|
|
if (qet.elementInfo(f, u[i], 'formula') === formula) r.push([f, u[i]]);
|
|
}
|
|
return r;
|
|
}
|
|
function label(e) { return qet.elementInfo(e[0], e[1], 'label'); }
|
|
function fid(e) { return qet.elementInfo(e[0], e[1], 'formula_id'); }
|
|
var xv = linked('%prefix2:%sequ_1');
|
|
var eq = linked('%id%prefix%sequ_1');
|
|
var r = {xv: xv.length, eq: eq.length};
|
|
var e0 = xv[0];
|
|
r.id0 = fid(e0);
|
|
r.label0 = label(e0);
|
|
r.renamed = qet.renameAutoNum('element', 'XV', 'Terminals XV');
|
|
r.idAfterRename = fid(e0);
|
|
r.clash = qet.renameAutoNum('element', 'Terminals XV', ' equipment ');
|
|
r.empty = qet.renameAutoNum('element', 'Terminals XV', ' ');
|
|
r.removeUsed = qet.removeAutoNum('element', 'Terminals XV');
|
|
r.removeUnused = qet.removeAutoNum('element', 'TB "XPE"');
|
|
r.edited = qet.addAutoNum('element', 'Terminals XV', ['elementprefix', 'string:X', 'unit:1']);
|
|
r.formulaAfterEdit = qet.elementInfo(e0[0], e0[1], 'formula');
|
|
r.labelAfterEdit = label(e0);
|
|
r.idAfterEdit = fid(e0);
|
|
qet.undo();
|
|
r.formulaAfterUndo = qet.elementInfo(e0[0], e0[1], 'formula');
|
|
r.labelAfterUndo = label(e0);
|
|
qet.redo();
|
|
r.labelAfterRedo = label(e0);
|
|
r.renumbered = qet.addAutoNum('element', 'Equipment', ['idfolio', 'elementprefix', 'string:-', 'ten:1']);
|
|
r.eqStillLinked = linked('%id%prefix-%seqt_1').length;
|
|
r.typed = qet.setElementInfo(eq[0][0], eq[0][1], 'formula', '%id%prefix%sequ_1');
|
|
r.typedId = fid(eq[0]);
|
|
r.internal = qet.setElementInfo(eq[1][0], eq[1][1], 'formula_id', '{00000000-0000-0000-0000-000000000001}');
|
|
r.names = qet.autoNums('element');
|
|
r.save = qet.save(%1);
|
|
qet.log('PROBE ' + JSON.stringify(r));
|
|
)JS").arg(QStringLiteral("'") + saved + QStringLiteral("'"));
|
|
|
|
const QJsonObject r = run(script, QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
|
|
QVERIFY2(!r.isEmpty(), "the script logged nothing");
|
|
QVERIFY(r.value(QStringLiteral("xv")).toInt() > 0);
|
|
QVERIFY(r.value(QStringLiteral("eq")).toInt() > 1);
|
|
|
|
const QString id0 = r.value(QStringLiteral("id0")).toString();
|
|
QVERIFY(!QUuid(id0).isNull());
|
|
|
|
// a rename touches no element
|
|
QVERIFY(r.value(QStringLiteral("renamed")).toBool());
|
|
QCOMPARE(r.value(QStringLiteral("idAfterRename")).toString(), id0);
|
|
// names are unique ignoring case and surrounding spaces, never empty
|
|
QVERIFY(!r.value(QStringLiteral("clash")).toBool());
|
|
QVERIFY(!r.value(QStringLiteral("empty")).toBool());
|
|
// a scheme elements follow cannot be removed, an unused one can
|
|
QVERIFY(!r.value(QStringLiteral("removeUsed")).toBool());
|
|
QVERIFY(r.value(QStringLiteral("removeUnused")).toBool());
|
|
|
|
// same sequential parts: new formula, same number, still linked
|
|
QVERIFY(r.value(QStringLiteral("edited")).toBool());
|
|
QCOMPARE(r.value(QStringLiteral("formulaAfterEdit")).toString(),
|
|
QStringLiteral("%prefixX%sequ_1"));
|
|
QCOMPARE(r.value(QStringLiteral("idAfterEdit")).toString(), id0);
|
|
QString expected = r.value(QStringLiteral("label0")).toString();
|
|
const int colon = expected.lastIndexOf(QStringLiteral("2:"));
|
|
QVERIFY(colon >= 0);
|
|
expected.replace(colon, 2, QStringLiteral("X"));
|
|
QCOMPARE(r.value(QStringLiteral("labelAfterEdit")).toString(), expected);
|
|
|
|
// one undo step, both ways
|
|
QCOMPARE(r.value(QStringLiteral("formulaAfterUndo")).toString(),
|
|
QStringLiteral("%prefix2:%sequ_1"));
|
|
QCOMPARE(r.value(QStringLiteral("labelAfterUndo")).toString(),
|
|
r.value(QStringLiteral("label0")).toString());
|
|
QCOMPARE(r.value(QStringLiteral("labelAfterRedo")).toString(), expected);
|
|
|
|
// other sequential parts: renumbered, every element still linked
|
|
QVERIFY(r.value(QStringLiteral("renumbered")).toBool());
|
|
QCOMPARE(r.value(QStringLiteral("eqStillLinked")).toInt(),
|
|
r.value(QStringLiteral("eq")).toInt());
|
|
|
|
// a formula written by hand follows no scheme, formula_id is internal
|
|
QVERIFY(r.value(QStringLiteral("typed")).toBool());
|
|
QVERIFY(r.value(QStringLiteral("typedId")).toString().isEmpty());
|
|
QVERIFY(!r.value(QStringLiteral("internal")).toBool());
|
|
|
|
QVERIFY(r.value(QStringLiteral("save")).toBool());
|
|
const Saved s = parse(read(saved));
|
|
QVERIFY(s.scheme_id.contains(QStringLiteral("Terminals XV")));
|
|
QVERIFY(!s.scheme_id.contains(QStringLiteral("XV")));
|
|
QVERIFY(!s.scheme_id.contains(QStringLiteral("TB \"XPE\"")));
|
|
QCOMPARE(s.scheme_id.value(QStringLiteral("Terminals XV")), QUuid(id0));
|
|
QCOMPARE(linkedTo(s, QStringLiteral("Terminals XV")), r.value(QStringLiteral("xv")).toInt());
|
|
QCOMPARE(linkedTo(s, QStringLiteral("Equipment")), r.value(QStringLiteral("eq")).toInt() - 1);
|
|
}
|
|
|
|
// Giving a numbering to elements that have none (what picking it in the
|
|
// element's information window does): formula, id and the next number,
|
|
// the counter moving on, refused where something would be lost, and one
|
|
// undo step that gives the number back.
|
|
void assignScheme()
|
|
{
|
|
const QString script = QStringLiteral(R"JS(
|
|
function find(pred, max) {
|
|
var r = [];
|
|
for (var f = 0; f < qet.folioCount() && r.length < max; ++f) {
|
|
var u = qet.elementUuids(f);
|
|
for (var i = 0; i < u.length && r.length < max; ++i)
|
|
if (pred(f, u[i])) r.push([f, u[i]]);
|
|
}
|
|
return r;
|
|
}
|
|
function info(e, k) { return qet.elementInfo(e[0], e[1], k); }
|
|
var r = {};
|
|
var candidates = find(function (f, u) { return qet.elementInfo(f, u, 'formula') === ''; }, 40);
|
|
var done = [];
|
|
for (var i = 0; i < candidates.length && done.length < 3; ++i)
|
|
if (qet.assignElementAutoNum('Equipment', candidates[i][0], candidates[i][1], false)) done.push(candidates[i]);
|
|
r.assigned = done.length;
|
|
var a = done[0], b = done[1], c = done[2];
|
|
r.formulaA = info(a, 'formula');
|
|
r.idA = info(a, 'formula_id');
|
|
r.labelA = info(a, 'label');
|
|
r.labelB = info(b, 'label');
|
|
r.labelC = info(c, 'label');
|
|
r.again = qet.assignElementAutoNum('Equipment', a[0], a[1], false);
|
|
r.other = qet.assignElementAutoNum('XV', a[0], a[1], false);
|
|
r.otherOverwrite = qet.assignElementAutoNum('XV', a[0], a[1], true);
|
|
r.formulaAfterOverwrite = info(a, 'formula');
|
|
r.idAfterOverwrite = info(a, 'formula_id');
|
|
r.unknown = qet.assignElementAutoNum('Nope', a[0], a[1], true);
|
|
qet.undo();
|
|
r.formulaAfterUndo = info(a, 'formula');
|
|
r.idAfterUndo = info(a, 'formula_id');
|
|
r.save = qet.save(%1);
|
|
qet.log('PROBE ' + JSON.stringify(r));
|
|
)JS").arg(QStringLiteral("'") + m_dir.filePath(QStringLiteral("assigned.qet")) + QStringLiteral("'"));
|
|
|
|
const QJsonObject r = run(script, QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
|
|
QVERIFY2(!r.isEmpty(), "the script logged nothing");
|
|
QCOMPARE(r.value(QStringLiteral("assigned")).toInt(), 3);
|
|
|
|
QCOMPARE(r.value(QStringLiteral("formulaA")).toString(), QStringLiteral("%id%prefix%sequ_1"));
|
|
QVERIFY(!QUuid(r.value(QStringLiteral("idA")).toString()).isNull());
|
|
// the saved counter of "Equipment" is 9: the next numbers, in order
|
|
// (the label is the folio id, the prefix, then the number)
|
|
QVERIFY2(r.value(QStringLiteral("labelA")).toString().endsWith(QLatin1Char('9')),
|
|
qPrintable(r.value(QStringLiteral("labelA")).toString()));
|
|
QVERIFY2(r.value(QStringLiteral("labelB")).toString().endsWith(QStringLiteral("10")),
|
|
qPrintable(r.value(QStringLiteral("labelB")).toString()));
|
|
QVERIFY2(r.value(QStringLiteral("labelC")).toString().endsWith(QStringLiteral("11")),
|
|
qPrintable(r.value(QStringLiteral("labelC")).toString()));
|
|
|
|
QVERIFY(!r.value(QStringLiteral("again")).toBool());
|
|
QVERIFY(!r.value(QStringLiteral("other")).toBool());
|
|
QVERIFY(r.value(QStringLiteral("otherOverwrite")).toBool());
|
|
QCOMPARE(r.value(QStringLiteral("formulaAfterOverwrite")).toString(),
|
|
QStringLiteral("%prefix2:%sequ_1"));
|
|
QVERIFY(r.value(QStringLiteral("idAfterOverwrite")).toString()
|
|
!= r.value(QStringLiteral("idA")).toString());
|
|
QVERIFY(!r.value(QStringLiteral("unknown")).toBool());
|
|
|
|
// one undo gives the element its previous numbering back
|
|
QCOMPARE(r.value(QStringLiteral("formulaAfterUndo")).toString(),
|
|
QStringLiteral("%id%prefix%sequ_1"));
|
|
QCOMPARE(r.value(QStringLiteral("idAfterUndo")).toString(),
|
|
r.value(QStringLiteral("idA")).toString());
|
|
|
|
QVERIFY(r.value(QStringLiteral("save")).toBool());
|
|
const Saved saved = parse(read(m_dir.filePath(QStringLiteral("assigned.qet"))));
|
|
// three elements gained the numbering, whose counter moved past them
|
|
QVERIFY(linkedTo(saved, QStringLiteral("Equipment")) >= 3);
|
|
}
|
|
|
|
// An element whose label is frozen is left as it is by a renumbering, and
|
|
// the numbering goes past its number instead of giving it to another
|
|
// element. On industrial.qet the element labelled 5F3 is frozen: without
|
|
// that, the numbering would give 5F3 to another element of folio 5.
|
|
void frozenElementsAreSkipped()
|
|
{
|
|
const QString body = QStringLiteral(R"JS(
|
|
function labels() {
|
|
var r = {};
|
|
for (var f = 0; f < qet.folioCount(); ++f) {
|
|
var u = qet.elementUuids(f);
|
|
for (var i = 0; i < u.length; ++i)
|
|
if (qet.elementInfo(f, u[i], 'formula') === '%id%prefix%sequ_1')
|
|
r[f + '/' + u[i]] = qet.elementInfo(f, u[i], 'label');
|
|
}
|
|
return r;
|
|
}
|
|
var before = labels();
|
|
var r = {before: before};
|
|
r.frozen = qet.renumberElementAutoNum('Equipment');
|
|
r.after = labels();
|
|
r.missing = qet.renumberElementAutoNum('Nope');
|
|
qet.undo();
|
|
r.undone = labels();
|
|
qet.log('PROBE ' + JSON.stringify(r));
|
|
)JS");
|
|
|
|
// control: nothing frozen, 5F3 goes to another element
|
|
const QJsonObject c = run(body, QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
|
|
QVERIFY2(!c.isEmpty(), "the control script logged nothing");
|
|
QCOMPARE(c.value(QStringLiteral("frozen")).toInt(), 0);
|
|
const QJsonObject cb = c.value(QStringLiteral("before")).toObject();
|
|
const QJsonObject ca = c.value(QStringLiteral("after")).toObject();
|
|
QString holder;
|
|
int given = 0;
|
|
for (auto it = cb.constBegin() ; it != cb.constEnd() ; ++it) {
|
|
if (it.value().toString() == QLatin1String("5F3")) holder = it.key();
|
|
}
|
|
QVERIFY2(!holder.isEmpty(), "no element is labelled 5F3 in industrial.qet");
|
|
for (auto it = ca.constBegin() ; it != ca.constEnd() ; ++it) {
|
|
if (it.value().toString() == QLatin1String("5F3") && it.key() != holder) ++given;
|
|
}
|
|
QVERIFY2(given == 1, "the control renumbering does not give 5F3 to another element: "
|
|
"this test would prove nothing");
|
|
|
|
// with the element frozen
|
|
const QString project = withFrozen(QStringLiteral("5F3"));
|
|
QVERIFY2(!project.isEmpty(), "cannot freeze the element");
|
|
const QJsonObject r = run(body, project);
|
|
QVERIFY2(!r.isEmpty(), "the script logged nothing");
|
|
QCOMPARE(r.value(QStringLiteral("frozen")).toInt(), 1);
|
|
QCOMPARE(r.value(QStringLiteral("missing")).toInt(), -1);
|
|
|
|
const QJsonObject before = r.value(QStringLiteral("before")).toObject();
|
|
const QJsonObject after = r.value(QStringLiteral("after")).toObject();
|
|
QCOMPARE(after.size(), before.size());
|
|
QCOMPARE(after.value(holder).toString(), QStringLiteral("5F3"));
|
|
|
|
QSet<QString> seen;
|
|
int changed = 0;
|
|
for (auto it = after.constBegin() ; it != after.constEnd() ; ++it) {
|
|
const QString label = it.value().toString();
|
|
QVERIFY2(!seen.contains(label), qPrintable(QStringLiteral("two elements are labelled %1").arg(label)));
|
|
seen << label;
|
|
if (before.value(it.key()).toString() != label) ++changed;
|
|
}
|
|
QVERIFY(changed > 0); // the others were numbered again
|
|
|
|
// one undo gives every label back
|
|
QCOMPARE(r.value(QStringLiteral("undone")).toObject(), before);
|
|
}
|
|
|
|
// A scheme whose formula would change cannot be edited while an element
|
|
// with a frozen label follows it. A rename, and a change of the counter
|
|
// (same formula), still can: they touch no label.
|
|
void frozenElementsBlockAnEdit()
|
|
{
|
|
const QString project = withFrozen(QStringLiteral("5F3"));
|
|
QVERIFY2(!project.isEmpty(), "cannot freeze the element");
|
|
const QString script = QStringLiteral(R"JS(
|
|
function find(label) {
|
|
for (var f = 0; f < qet.folioCount(); ++f) {
|
|
var u = qet.elementUuids(f);
|
|
for (var i = 0; i < u.length; ++i)
|
|
if (qet.elementInfo(f, u[i], 'label') === label) return [f, u[i]];
|
|
}
|
|
return null;
|
|
}
|
|
var frozen = find('5F3'), other = find('5F4');
|
|
var r = {oldFormula: qet.elementInfo(frozen[0], frozen[1], 'formula')};
|
|
r.refused = qet.addAutoNum('element', 'Equipment', ['idfolio', 'elementprefix', 'unit:1', 'string:x']);
|
|
r.frozenFormula = qet.elementInfo(frozen[0], frozen[1], 'formula');
|
|
r.frozenLabel = qet.elementInfo(frozen[0], frozen[1], 'label');
|
|
r.otherFormula = qet.elementInfo(other[0], other[1], 'formula');
|
|
r.otherLabel = qet.elementInfo(other[0], other[1], 'label');
|
|
r.counter = qet.addAutoNum('element', 'Equipment', ['idfolio', 'elementprefix', 'unit:40']);
|
|
r.otherAfterCounter = qet.elementInfo(other[0], other[1], 'label');
|
|
r.renamed = qet.renameAutoNum('element', 'Equipment', 'Equipment 2');
|
|
r.frozenId = qet.elementInfo(frozen[0], frozen[1], 'formula_id');
|
|
r.names = qet.autoNums('element');
|
|
qet.log('PROBE ' + JSON.stringify(r));
|
|
)JS");
|
|
const QJsonObject r = run(script, project);
|
|
QVERIFY2(!r.isEmpty(), "the script logged nothing");
|
|
QVERIFY(!r.value(QStringLiteral("refused")).toBool());
|
|
QCOMPARE(r.value(QStringLiteral("frozenFormula")).toString(),
|
|
r.value(QStringLiteral("oldFormula")).toString());
|
|
QCOMPARE(r.value(QStringLiteral("frozenLabel")).toString(), QStringLiteral("5F3"));
|
|
QCOMPARE(r.value(QStringLiteral("otherFormula")).toString(), r.value(QStringLiteral("oldFormula")).toString());
|
|
QCOMPARE(r.value(QStringLiteral("otherLabel")).toString(), QStringLiteral("5F4"));
|
|
|
|
// same formula: allowed, and no label moves
|
|
QVERIFY(r.value(QStringLiteral("counter")).toBool());
|
|
QCOMPARE(r.value(QStringLiteral("otherAfterCounter")).toString(), QStringLiteral("5F4"));
|
|
// a rename is allowed, the frozen element still follows the numbering
|
|
QVERIFY(r.value(QStringLiteral("renamed")).toBool());
|
|
QVERIFY(!QUuid(r.value(QStringLiteral("frozenId")).toString()).isNull());
|
|
QVERIFY(QJsonDocument(r.value(QStringLiteral("names")).toArray()).toJson().contains("Equipment 2"));
|
|
}
|
|
|
|
// Duplicating elements which follow a numbering gives the copies the next
|
|
// numbers of it instead of the labels of the originals, and the numbering
|
|
// moves on; one undo takes the copies away and gives the numbers back, one
|
|
// redo brings the same numbers again.
|
|
void duplicatesAreNumbered()
|
|
{
|
|
const QString script = QStringLiteral(R"JS(
|
|
function labelsOf(f) {
|
|
var r = [], u = qet.elementUuids(f);
|
|
for (var i = 0; i < u.length; ++i)
|
|
if (qet.elementInfo(f, u[i], 'formula') === '%id%prefix%sequ_1') r.push(qet.elementInfo(f, u[i], 'label'));
|
|
return r;
|
|
}
|
|
function find(label) {
|
|
for (var f = 0; f < qet.folioCount(); ++f) {
|
|
var u = qet.elementUuids(f);
|
|
for (var i = 0; i < u.length; ++i)
|
|
if (qet.elementInfo(f, u[i], 'label') === label) return [f, u[i]];
|
|
}
|
|
return null;
|
|
}
|
|
var a = find('8F1'), b = find('8F2');
|
|
var r = {before: labelsOf(a[0])};
|
|
var created = qet.duplicateElements(a[0], [a[1], b[1]], a[0], 0, 60);
|
|
r.count = created.length;
|
|
r.labels = created.map(function (u) { return qet.elementInfo(a[0], u, 'label'); });
|
|
r.formulas = created.map(function (u) { return qet.elementInfo(a[0], u, 'formula'); });
|
|
r.ids = created.map(function (u) { return qet.elementInfo(a[0], u, 'formula_id'); });
|
|
r.originalId = qet.elementInfo(a[0], a[1], 'formula_id');
|
|
r.afterAll = labelsOf(a[0]);
|
|
qet.undo();
|
|
r.afterUndo = labelsOf(a[0]);
|
|
qet.redo();
|
|
r.afterRedo = labelsOf(a[0]);
|
|
qet.undo();
|
|
// the counter went back: copying again gives the same numbers
|
|
var again = qet.duplicateElements(a[0], [a[1], b[1]], a[0], 0, 60);
|
|
r.again = again.map(function (u) { return qet.elementInfo(a[0], u, 'label'); });
|
|
qet.log('PROBE ' + JSON.stringify(r));
|
|
)JS");
|
|
m_preferences.clear();
|
|
const QJsonObject r = run(script, QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
|
|
QVERIFY2(!r.isEmpty(), "the script logged nothing");
|
|
QCOMPARE(r.value(QStringLiteral("count")).toInt(), 2);
|
|
|
|
const QJsonArray labels = r.value(QStringLiteral("labels")).toArray();
|
|
const QJsonArray before = r.value(QStringLiteral("before")).toArray();
|
|
QCOMPARE(labels.size(), 2);
|
|
for (const QJsonValue &label : labels) {
|
|
QVERIFY2(!label.toString().isEmpty(), "a copy has no label");
|
|
QVERIFY2(!before.contains(label), qPrintable(QStringLiteral("a copy has the label %1 of another element").arg(label.toString())));
|
|
}
|
|
QVERIFY(labels.at(0) != labels.at(1));
|
|
QVERIFY(labels.at(0).toString().startsWith(QLatin1String("8F")));
|
|
|
|
const QJsonArray formulas = r.value(QStringLiteral("formulas")).toArray();
|
|
const QJsonArray ids = r.value(QStringLiteral("ids")).toArray();
|
|
for (int i = 0 ; i < 2 ; ++i) {
|
|
QCOMPARE(formulas.at(i).toString(), QStringLiteral("%id%prefix%sequ_1"));
|
|
QCOMPARE(ids.at(i).toString(), r.value(QStringLiteral("originalId")).toString());
|
|
}
|
|
|
|
// the originals are untouched, and every label is unique
|
|
const QJsonArray all = r.value(QStringLiteral("afterAll")).toArray();
|
|
QCOMPARE(all.size(), before.size() + 2);
|
|
QSet<QString> unique;
|
|
for (const QJsonValue &label : all) unique << label.toString();
|
|
QCOMPARE(unique.size(), all.size());
|
|
for (const QJsonValue &label : before) QVERIFY(all.contains(label));
|
|
|
|
// undo, redo, undo and copy again: the same numbers
|
|
QCOMPARE(r.value(QStringLiteral("afterUndo")).toArray(), before);
|
|
QCOMPARE(r.value(QStringLiteral("afterRedo")).toArray().size(), all.size());
|
|
QCOMPARE(r.value(QStringLiteral("again")).toArray(), labels);
|
|
}
|
|
|
|
// The preference "number pasted elements" is off: a copy is what a copy
|
|
// was, whatever the label preference says. Erasing (the default) leaves
|
|
// it with no label and no numbering; keeping leaves it with the label of
|
|
// the original, still tied to the same numbering.
|
|
void duplicatesAreNotNumberedWhenSwitchedOff()
|
|
{
|
|
const QString script = QStringLiteral(R"JS(
|
|
function find(label) {
|
|
for (var f = 0; f < qet.folioCount(); ++f) {
|
|
var u = qet.elementUuids(f);
|
|
for (var i = 0; i < u.length; ++i)
|
|
if (qet.elementInfo(f, u[i], 'label') === label) return [f, u[i]];
|
|
}
|
|
return null;
|
|
}
|
|
var a = find('8F1');
|
|
var created = qet.duplicateElements(a[0], [a[1]], a[0], 0, 60);
|
|
var r = {label: qet.elementInfo(a[0], created[0], 'label'),
|
|
formula: qet.elementInfo(a[0], created[0], 'formula'),
|
|
id: qet.elementInfo(a[0], created[0], 'formula_id')};
|
|
qet.log('PROBE ' + JSON.stringify(r));
|
|
)JS");
|
|
m_preferences = QStringLiteral("[diagramcommands]\nautonumber-pasted-elements=false\n");
|
|
const QJsonObject erased = run(script, QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
|
|
QVERIFY2(!erased.isEmpty(), "the script logged nothing");
|
|
QVERIFY(erased.value(QStringLiteral("label")).toString().isEmpty());
|
|
QVERIFY(erased.value(QStringLiteral("formula")).toString().isEmpty());
|
|
QVERIFY(erased.value(QStringLiteral("id")).toString().isEmpty());
|
|
|
|
m_preferences = QStringLiteral("[diagramcommands]\nautonumber-pasted-elements=false\nerase-label-on-copy=false\n");
|
|
const QJsonObject kept = run(script, QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
|
|
QVERIFY2(!kept.isEmpty(), "the script logged nothing");
|
|
QCOMPARE(kept.value(QStringLiteral("label")).toString(), QStringLiteral("8F1"));
|
|
QCOMPARE(kept.value(QStringLiteral("formula")).toString(), QStringLiteral("%id%prefix%sequ_1"));
|
|
QVERIFY(!QUuid(kept.value(QStringLiteral("id")).toString()).isNull());
|
|
m_preferences.clear();
|
|
}
|
|
|
|
// Conductor numberings: names are unique and never empty, a rename moves
|
|
// the folios which read the numbering with it (26 folios of
|
|
// industrial.qet read "Wire"), a numbering which folios read cannot be
|
|
// removed, an unused one can, and every step is one undo.
|
|
void conductorNumberings()
|
|
{
|
|
const QString saved = m_dir.filePath(QStringLiteral("conductors.qet"));
|
|
const QString script = QStringLiteral(R"JS(
|
|
var r = {};
|
|
r.names = qet.autoNums('conductor');
|
|
r.clash = qet.addAutoNum('conductor', ' wire ', ['unit:1']);
|
|
r.empty = qet.addAutoNum('conductor', ' ', ['unit:1']);
|
|
r.added = qet.addAutoNum('conductor', 'Spare', ['string:S', 'unit:1']);
|
|
r.renameClash = qet.renameAutoNum('conductor', 'Spare', 'WIRE');
|
|
r.removeUsed = qet.removeAutoNum('conductor', 'Wire');
|
|
r.renamed = qet.renameAutoNum('conductor', 'Wire', 'Cable');
|
|
r.afterRename = qet.autoNums('conductor');
|
|
r.missing = qet.renameAutoNum('conductor', 'Nope', 'X');
|
|
r.removeSpare = qet.removeAutoNum('conductor', 'Spare');
|
|
r.afterRemove = qet.autoNums('conductor');
|
|
qet.undo(); // the removal
|
|
r.afterUndoRemove = qet.autoNums('conductor');
|
|
qet.undo(); qet.undo(); // the rename, then the creation
|
|
qet.redo(); qet.redo(); // the creation, then the rename
|
|
r.afterRedo = qet.autoNums('conductor');
|
|
r.save = qet.save(%1);
|
|
qet.log('PROBE ' + JSON.stringify(r));
|
|
)JS").arg(QStringLiteral("'") + saved + QStringLiteral("'"));
|
|
|
|
const QJsonObject r = run(script, QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
|
|
QVERIFY2(!r.isEmpty(), "the script logged nothing");
|
|
const auto names = [&](const char *key) {
|
|
return titlesOf(r.value(QLatin1String(key)));
|
|
};
|
|
QCOMPARE(names("names"), QStringList{QStringLiteral("Wire")});
|
|
QVERIFY(!r.value(QStringLiteral("clash")).toBool()); // " wire " is "Wire"
|
|
QVERIFY(!r.value(QStringLiteral("empty")).toBool());
|
|
QVERIFY(r.value(QStringLiteral("added")).toBool());
|
|
QVERIFY(!r.value(QStringLiteral("renameClash")).toBool()); // "WIRE" is "Wire"
|
|
QVERIFY(!r.value(QStringLiteral("removeUsed")).toBool()); // 26 folios read it
|
|
QVERIFY(r.value(QStringLiteral("renamed")).toBool());
|
|
QCOMPARE(names("afterRename"), (QStringList{QStringLiteral("Cable"), QStringLiteral("Spare")}));
|
|
QVERIFY(!r.value(QStringLiteral("missing")).toBool());
|
|
QVERIFY(r.value(QStringLiteral("removeSpare")).toBool()); // no folio reads it
|
|
QCOMPARE(names("afterRemove"), QStringList{QStringLiteral("Cable")});
|
|
QCOMPARE(names("afterUndoRemove"), (QStringList{QStringLiteral("Cable"), QStringLiteral("Spare")}));
|
|
QCOMPARE(names("afterRedo"), (QStringList{QStringLiteral("Cable"), QStringLiteral("Spare")}));
|
|
|
|
// the folios read the numbering under its new name
|
|
QVERIFY(r.value(QStringLiteral("save")).toBool());
|
|
const QByteArray xml = read(saved);
|
|
const QStringList followed = diagramAttributes(xml, QStringLiteral("conductorAutonum"));
|
|
QCOMPARE(followed.count(QStringLiteral("Cable")), 26);
|
|
QCOMPARE(followed.count(QStringLiteral("Wire")), 0);
|
|
}
|
|
|
|
// Folio numberings. A folio numbering is applied once: the number is
|
|
// written into the folio field and the title block keeps the name (49 of
|
|
// the 50 folios of industrial.qet name "Name of the new numbering", none
|
|
// has a folio field which still holds %autonum). A rename moves every name,
|
|
// removing a numbering which folios only name is allowed, and undo and
|
|
// redo give back the same names.
|
|
void folioNumberings()
|
|
{
|
|
const QString renamed_file = m_dir.filePath(QStringLiteral("folio-renamed.qet"));
|
|
const QString undone_file = m_dir.filePath(QStringLiteral("folio-undone.qet"));
|
|
const QString script = QStringLiteral(R"JS(
|
|
var r = {};
|
|
r.names = qet.autoNums('folio');
|
|
r.clash = qet.addAutoNum('folio', 'name of the new numbering', ['unit:1']);
|
|
r.empty = qet.addAutoNum('folio', ' ', ['unit:1']);
|
|
r.renamed = qet.renameAutoNum('folio', 'Name of the new numbering', 'Folios');
|
|
r.afterRename = qet.autoNums('folio');
|
|
r.saveRenamed = qet.save(%1);
|
|
r.removed = qet.removeAutoNum('folio', 'Folios');
|
|
r.afterRemove = qet.autoNums('folio');
|
|
qet.undo(); // the removal
|
|
r.afterUndoRemove = qet.autoNums('folio');
|
|
qet.undo(); // the rename
|
|
r.afterUndoRename = qet.autoNums('folio');
|
|
r.saveUndone = qet.save(%2);
|
|
qet.redo();
|
|
r.afterRedo = qet.autoNums('folio');
|
|
qet.log('PROBE ' + JSON.stringify(r));
|
|
)JS").arg(QStringLiteral("'") + renamed_file + QStringLiteral("'"),
|
|
QStringLiteral("'") + undone_file + QStringLiteral("'"));
|
|
|
|
const QJsonObject r = run(script, QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
|
|
QVERIFY2(!r.isEmpty(), "the script logged nothing");
|
|
const auto names = [&](const char *key) {
|
|
return titlesOf(r.value(QLatin1String(key)));
|
|
};
|
|
const QString old_name = QStringLiteral("Name of the new numbering");
|
|
QCOMPARE(names("names"), QStringList{old_name});
|
|
QVERIFY(!r.value(QStringLiteral("clash")).toBool());
|
|
QVERIFY(!r.value(QStringLiteral("empty")).toBool());
|
|
QVERIFY(r.value(QStringLiteral("renamed")).toBool());
|
|
QCOMPARE(names("afterRename"), QStringList{QStringLiteral("Folios")});
|
|
QVERIFY(r.value(QStringLiteral("removed")).toBool());
|
|
QCOMPARE(names("afterRemove"), QStringList());
|
|
QCOMPARE(names("afterUndoRemove"), QStringList{QStringLiteral("Folios")});
|
|
QCOMPARE(names("afterUndoRename"), QStringList{old_name});
|
|
QCOMPARE(names("afterRedo"), QStringList{QStringLiteral("Folios")});
|
|
|
|
QVERIFY(r.value(QStringLiteral("saveRenamed")).toBool());
|
|
QStringList named = diagramAttributes(read(renamed_file), QStringLiteral("auto_page_num"));
|
|
QCOMPARE(named.size(), 50);
|
|
QCOMPARE(named.count(QStringLiteral("Folios")), 49);
|
|
QCOMPARE(named.count(old_name), 0);
|
|
|
|
QVERIFY(r.value(QStringLiteral("saveUndone")).toBool());
|
|
named = diagramAttributes(read(undone_file), QStringLiteral("auto_page_num"));
|
|
QCOMPARE(named.count(old_name), 49);
|
|
QCOMPARE(named.count(QStringLiteral("Folios")), 0);
|
|
}
|
|
|
|
// Free numbers. An element which follows a numbering of one sequence may be
|
|
// given a number nobody has, and keeps following the numbering: the case of
|
|
// an element which, after a reshuffle, must have its real-life number back.
|
|
// Only free numbers are offered and accepted; one undo gives the number back;
|
|
// a number past the counter moves the counter, so that the next element is
|
|
// not given the same label.
|
|
void freeNumbers()
|
|
{
|
|
const QString script = QStringLiteral(R"JS(
|
|
function find(label) {
|
|
for (var f = 0; f < qet.folioCount(); ++f) {
|
|
var u = qet.elementUuids(f);
|
|
for (var i = 0; i < u.length; ++i)
|
|
if (qet.elementInfo(f, u[i], 'label') === label) return [f, u[i]];
|
|
}
|
|
return null;
|
|
}
|
|
function number(label) { var m = /(\d+)$/.exec(label); return m ? parseInt(m[1]) : -1; }
|
|
function followers() {
|
|
var r = [];
|
|
for (var f = 0; f < qet.folioCount(); ++f) {
|
|
var u = qet.elementUuids(f);
|
|
for (var i = 0; i < u.length; ++i)
|
|
if (qet.elementInfo(f, u[i], 'formula') === '%id%prefix%sequ_1') r.push(number(qet.elementInfo(f, u[i], 'label')));
|
|
}
|
|
return r.sort(function (a, b) { return a - b; });
|
|
}
|
|
var e = find('8F2'), other = find('8F1');
|
|
var r = {used: followers()};
|
|
r.before = qet.elementInfo(e[0], e[1], 'label');
|
|
r.own = number(r.before);
|
|
r.free = qet.freeElementNumbers(e[0], e[1]);
|
|
r.numberOfOther = number(qet.elementInfo(other[0], other[1], 'label'));
|
|
r.idBefore = qet.elementInfo(e[0], e[1], 'formula_id');
|
|
|
|
r.assignUsed = qet.assignElementNumber(e[0], e[1], r.numberOfOther); // taken: refused
|
|
r.assignZero = qet.assignElementNumber(e[0], e[1], 0);
|
|
r.assignFree = qet.assignElementNumber(e[0], e[1], r.free[0]);
|
|
r.after = qet.elementInfo(e[0], e[1], 'label');
|
|
r.formulaAfter = qet.elementInfo(e[0], e[1], 'formula');
|
|
r.idAfter = qet.elementInfo(e[0], e[1], 'formula_id');
|
|
r.freeAfter = qet.freeElementNumbers(e[0], e[1]); // its old number is free now
|
|
qet.undo();
|
|
r.undone = qet.elementInfo(e[0], e[1], 'label');
|
|
|
|
// a number past the counter: the counter moves past it
|
|
var high = r.free[r.free.length - 1];
|
|
r.high = high;
|
|
r.assignHigh = qet.assignElementNumber(e[0], e[1], high);
|
|
r.highLabel = qet.elementInfo(e[0], e[1], 'label');
|
|
var plain = null;
|
|
for (var f = 0; f < qet.folioCount() && !plain; ++f) {
|
|
var u = qet.elementUuids(f);
|
|
for (var i = 0; i < u.length; ++i)
|
|
if (qet.elementInfo(f, u[i], 'formula') === '') { plain = [f, u[i]]; break; }
|
|
}
|
|
// an element which follows nothing has no numbers to choose from, nor can it be given one
|
|
r.noFree = qet.freeElementNumbers(plain[0], plain[1]).length;
|
|
r.assignNothing = qet.assignElementNumber(plain[0], plain[1], 5);
|
|
// the next element of the numbering is past the number given by hand
|
|
r.assignNext = qet.assignElementAutoNum('Equipment', plain[0], plain[1], false);
|
|
r.nextNumber = number(qet.elementInfo(plain[0], plain[1], 'label'));
|
|
qet.log('PROBE ' + JSON.stringify(r));
|
|
)JS");
|
|
const QJsonObject r = run(script, QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
|
|
QVERIFY2(!r.isEmpty(), "the script logged nothing");
|
|
|
|
const QJsonArray used = r.value(QStringLiteral("used")).toArray();
|
|
const QJsonArray free = r.value(QStringLiteral("free")).toArray();
|
|
QVERIFY(!free.isEmpty());
|
|
const int own = r.value(QStringLiteral("own")).toInt();
|
|
for (const QJsonValue &n : free)
|
|
QVERIFY2(!used.contains(n) || n.toInt() == own, "a number in use is offered");
|
|
QVERIFY(!free.contains(r.value(QStringLiteral("numberOfOther"))));
|
|
|
|
QVERIFY(!r.value(QStringLiteral("assignUsed")).toBool());
|
|
QVERIFY(!r.value(QStringLiteral("assignZero")).toBool());
|
|
QVERIFY(r.value(QStringLiteral("assignFree")).toBool());
|
|
const QString after = r.value(QStringLiteral("after")).toString();
|
|
QVERIFY(after != r.value(QStringLiteral("before")).toString());
|
|
QVERIFY2(after.endsWith(QString::number(free.at(0).toInt())), qPrintable(after));
|
|
QVERIFY(after.startsWith(QLatin1String("8F")));
|
|
QCOMPARE(r.value(QStringLiteral("formulaAfter")).toString(), QStringLiteral("%id%prefix%sequ_1"));
|
|
QCOMPARE(r.value(QStringLiteral("idAfter")).toString(), r.value(QStringLiteral("idBefore")).toString());
|
|
// the number it had is free for another element now
|
|
QVERIFY(r.value(QStringLiteral("freeAfter")).toArray().size() >= free.size());
|
|
QCOMPARE(r.value(QStringLiteral("undone")).toString(), r.value(QStringLiteral("before")).toString());
|
|
|
|
// a number past the counter moves the counter on
|
|
QVERIFY(r.value(QStringLiteral("assignHigh")).toBool());
|
|
QVERIFY(r.value(QStringLiteral("assignNext")).toBool());
|
|
QVERIFY2(r.value(QStringLiteral("nextNumber")).toInt() > r.value(QStringLiteral("high")).toInt(),
|
|
"the next element was given a number which is not past the one given by hand");
|
|
QCOMPARE(r.value(QStringLiteral("noFree")).toInt(), 0);
|
|
QVERIFY(!r.value(QStringLiteral("assignNothing")).toBool());
|
|
}
|
|
|
|
// A numbering whose counter is set back over numbers in use does not hand
|
|
// them out again: placing an element takes the first number whose label
|
|
// nobody carries, and the counter goes on from there; one undo gives the
|
|
// counter back. A label held by an element which does not follow the
|
|
// numbering counts too.
|
|
void placementSkipsLabelsInUse()
|
|
{
|
|
const QString script = QStringLiteral(R"JS(
|
|
function labels() {
|
|
var r = [];
|
|
for (var f = 0; f < qet.folioCount(); ++f) {
|
|
var u = qet.elementUuids(f);
|
|
for (var i = 0; i < u.length; ++i) {
|
|
var l = qet.elementInfo(f, u[i], 'label');
|
|
if (l !== '') r.push(l);
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
// number the first element without a formula which accepts one (a slave or a report does not)
|
|
function numberOne(skip) {
|
|
for (var f = 0; f < qet.folioCount(); ++f) {
|
|
var u = qet.elementUuids(f);
|
|
for (var i = 0; i < u.length; ++i) {
|
|
if (qet.elementInfo(f, u[i], 'formula') !== '' || skip.indexOf(u[i]) >= 0) continue;
|
|
if (qet.numberElement(f, u[i])) return [f, u[i]];
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
var r = {};
|
|
// the counter of "Equipment" back at 1, over the 29 elements which have numbers: allowed
|
|
r.back = qet.addAutoNum('element', 'Equipment', ['idfolio', 'elementprefix', 'unit:1']);
|
|
r.use = qet.useElementAutoNum('Equipment');
|
|
var held = labels();
|
|
var a = numberOne([]);
|
|
r.placedA = a !== null;
|
|
r.labelA = qet.elementInfo(a[0], a[1], 'label');
|
|
r.heldA = held.indexOf(r.labelA) >= 0;
|
|
var b = numberOne([a[1]]);
|
|
r.placedB = b !== null;
|
|
r.labelB = qet.elementInfo(b[0], b[1], 'label');
|
|
r.heldB = held.indexOf(r.labelB) >= 0 || r.labelB === r.labelA;
|
|
r.formulaA = qet.elementInfo(a[0], a[1], 'formula');
|
|
// one undo takes B's number back, the next undo A's
|
|
qet.undo();
|
|
r.afterUndoB = qet.elementInfo(b[0], b[1], 'label');
|
|
qet.undo();
|
|
r.afterUndoA = qet.elementInfo(a[0], a[1], 'label');
|
|
r.again = qet.numberElement(a[0], a[1]);
|
|
r.labelAgain = qet.elementInfo(a[0], a[1], 'label');
|
|
qet.log('PROBE ' + JSON.stringify(r));
|
|
)JS");
|
|
const QJsonObject r = run(script, QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
|
|
QVERIFY2(!r.isEmpty(), "the script logged nothing");
|
|
QVERIFY(r.value(QStringLiteral("back")).toBool());
|
|
QVERIFY(r.value(QStringLiteral("use")).toBool());
|
|
QVERIFY(r.value(QStringLiteral("placedA")).toBool());
|
|
QVERIFY(r.value(QStringLiteral("placedB")).toBool());
|
|
const QString a = r.value(QStringLiteral("labelA")).toString();
|
|
const QString b = r.value(QStringLiteral("labelB")).toString();
|
|
QVERIFY2(!a.isEmpty() && !b.isEmpty(), "an element was not given a label");
|
|
QVERIFY2(!r.value(QStringLiteral("heldA")).toBool(), qPrintable(QStringLiteral("%1 is held by another element").arg(a)));
|
|
QVERIFY2(!r.value(QStringLiteral("heldB")).toBool(), qPrintable(QStringLiteral("%1 is held by another element").arg(b)));
|
|
QVERIFY(a != b);
|
|
QCOMPARE(r.value(QStringLiteral("formulaA")).toString(), QStringLiteral("%id%prefix%sequ_1"));
|
|
// undoing gives the numbers back, and numbering again gives the same label
|
|
QVERIFY(r.value(QStringLiteral("afterUndoB")).toString() != b);
|
|
QVERIFY(r.value(QStringLiteral("afterUndoA")).toString() != a);
|
|
QVERIFY(r.value(QStringLiteral("again")).toBool());
|
|
QCOMPARE(r.value(QStringLiteral("labelAgain")).toString(), a);
|
|
}
|
|
};
|
|
|
|
QTEST_APPLESS_MAIN(tst_elementautonumids)
|
|
|
|
#include "tst_elementautonumids.moc"
|