Files
qelectrotech-source-mirror/tests/qttest/tst_smart_device.cpp
T
2026-09-11 20:11:28 +03:00

326 lines
13 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/>.
*/
#include <QtTest>
#include <QTemporaryDir>
#include <QSettings>
#include <QSqlError>
#include <algorithm>
#include "ElementsCollection/xmlelementcollection.h"
#include "dataBase/projectdatabase.h"
#include "devicebomexport.h"
#include "diagram.h"
#include "qetgraphicsitem/element.h"
#include "qetinformation.h"
#include "qetproject.h"
#include "qetresult.h"
#include "ui/elementinfopartwidget.h"
#include "ui/elementinfowidget.h"
class SmartDeviceTest : public QObject
{
Q_OBJECT
QTemporaryDir m_settings;
Element *addElement(QETProject &project, Diagram *diagram, const QString &tag,
ElementData::Type type = ElementData::Simple)
{
QDomDocument doc;
doc.setContent(QStringLiteral(
"<definition version=\"0.100.0\" type=\"element\" link_type=\"simple\" "
"width=\"20\" height=\"20\" hotspot_x=\"0\" hotspot_y=\"0\">"
"<names><name lang=\"en\">Test device</name></names>"
"<description><rect x=\"0\" y=\"0\" width=\"20\" height=\"20\" "
"antialias=\"false\" style=\"line-style:normal;line-weight:normal;"
"filling:none;color:black\"/></description></definition>"));
const QString name = QUuid::createUuid().toString(QUuid::WithoutBraces) + ".elmt";
if (!project.embeddedElementCollection()->addElementDefinition("import", name, doc.documentElement()))
return nullptr;
int state = -1;
auto *element = new Element(ElementsLocation("embed://import/" + name, &project), nullptr, &state);
if (state != 0) {
delete element;
return nullptr;
}
diagram->addItem(element);
auto data = element->elementData();
data.m_type = type;
data.m_informations.addValue("label", tag);
element->setElementData(data);
return element;
}
DeviceInformation sample() const
{
DeviceInformation info;
info.m_device_tag = "-K1";
info.m_manufacturer = QString::fromUtf8("\xC3\x96rnek Elektrik");
info.m_part_number = "001234";
info.m_model = "RX-24";
info.m_description = QString::fromUtf8("G\xC3\xBC\xC3\xA7 kontakt\xC3\xB6r\xC3\xBC");
info.m_category = "Contactor";
info.m_quantity = "2.5";
info.m_voltage_rating = "24 V DC";
info.m_current_rating = "16 A";
info.m_notes = "Panel A, \"spare\"; check\nSecond line";
return info;
}
private slots:
void initTestCase()
{
QCoreApplication::setOrganizationName("QET-SmartDeviceTests");
QCoreApplication::setApplicationName("IsolatedTests");
QSettings::setDefaultFormat(QSettings::IniFormat);
QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, m_settings.path());
QSettings::setPath(QSettings::IniFormat, QSettings::SystemScope, m_settings.path());
QETProject::setBackupEnabled(false);
}
void emptyAndLegacyContext()
{
const auto empty = DeviceInformation::fromContext(DiagramContext());
QVERIFY(empty.toContext().keys().isEmpty());
QVERIFY(empty.m_quantity.isEmpty());
QDomDocument doc;
QVERIFY(doc.setContent(QStringLiteral(
"<elementInformations><elementInformation name=\"label\">K7</elementInformation>"
"<elementInformation name=\"manufacturer_reference\">0007</elementInformation>"
"</elementInformations>")));
DiagramContext context;
context.fromXml(doc.documentElement(), "elementInformation");
const auto legacy = DeviceInformation::fromContext(context);
QCOMPARE(legacy.m_device_tag, QString("K7"));
QCOMPARE(legacy.m_part_number, QString("0007"));
QVERIFY(legacy.m_model.isEmpty());
QVERIFY(legacy.m_voltage_rating.isEmpty());
QVERIFY(legacy.m_current_rating.isEmpty());
QVERIFY(legacy.m_notes.isEmpty());
}
void contextRoundTripAndMerge()
{
DiagramContext original;
original.addValue("custom_value", "Keep", false);
original.addValue("manufacturer", "Old", false);
original.addValue("exclude_from_bom", "true");
auto info = sample();
auto context = info.toContext(original);
QVERIFY(!context.keyMustShow("manufacturer"));
QVERIFY(!context.keyMustShow("custom_value"));
QCOMPARE(context.value("custom_value").toString(), QString("Keep"));
QDomDocument doc;
auto xml = doc.createElement("elementInformations");
doc.appendChild(xml);
context.toXml(xml, "elementInformation");
QDomDocument reparsed;
QVERIFY(reparsed.setContent(doc.toByteArray()));
DiagramContext restored;
restored.fromXml(reparsed.documentElement(), "elementInformation");
QVERIFY(restored == context);
QVERIFY(DeviceInformation::fromContext(restored).toContext() == info.toContext());
info.m_model.clear();
const auto cleared = info.toContext(restored);
QVERIFY(!cleared.contains("model"));
QCOMPARE(cleared.value("exclude_from_bom").toString(), QString("true"));
}
void projectSaveLoad()
{
QTemporaryDir temp;
QETProject project;
auto *diagram = project.addNewDiagram();
diagram->border_and_titleblock.setFolio("A-01");
auto *element = addElement(project, diagram, "-K1");
QVERIFY(element);
element->setElementInformations(sample().toContext());
auto *second_diagram = project.addNewDiagram();
second_diagram->border_and_titleblock.setFolio("A-02");
diagram->border_and_titleblock.setFolioData(1, 2);
second_diagram->border_and_titleblock.setFolioData(2, 2);
auto *legacy = addElement(project, second_diagram, "-K2");
QVERIFY(legacy);
const auto before = DeviceBomExport::toCsv(DeviceBomExport::collect(project));
project.setFilePath(temp.filePath("roundtrip.qet"));
const auto result = project.write();
QVERIFY2(result.isOk(), qPrintable(result.errorMessage()));
QETProject restored(project.filePath());
QCOMPARE(restored.state(), QETProject::Ok);
QCOMPARE(restored.diagrams().size(), 2);
const auto rows = DeviceBomExport::collect(restored);
QCOMPARE(rows.size(), 2);
QCOMPARE(rows.first().m_page, QString("A-01"));
QCOMPARE(rows.last().m_page, QString("A-02"));
QVERIFY(rows.first().m_device.toContext() == sample().toContext());
QVERIFY(rows.last().m_device.m_model.isEmpty());
QVERIFY(rows.last().m_device.m_quantity.isEmpty());
QCOMPARE(DeviceBomExport::toCsv(rows), before);
const QString artifacts = qEnvironmentVariable("QET_SMART_DEVICE_ARTIFACT_DIR");
if (!artifacts.isEmpty()) {
QDir().mkpath(artifacts);
restored.setFilePath(QDir(artifacts).filePath("sample.qet"));
QVERIFY(restored.write().isOk());
QVERIFY(DeviceBomExport::writeCsv(restored, QDir(artifacts).filePath("sample.csv")));
}
}
void collectDevices()
{
QETProject project;
auto *first = project.addNewDiagram();
auto *second = project.addNewDiagram();
first->border_and_titleblock.setFolio("DEV-01");
second->border_and_titleblock.setFolio("DEV-10");
first->border_and_titleblock.setFolioData(1, 2);
second->border_and_titleblock.setFolioData(2, 2);
for (auto type : {ElementData::Simple, ElementData::Master, ElementData::Terminal,
ElementData::Slave, ElementData::NextReport, ElementData::PreviousReport,
ElementData::Thumbnail, ElementData::ConductorDefinition}) {
auto *element = addElement(project, first, QString::number(type), type);
QVERIFY(element);
if (type == ElementData::Master) {
auto data = element->elementData();
data.m_master_type = ElementData::PLC;
data.m_plc_master_data.ios.resize(2);
element->setElementData(data);
}
}
auto *excluded = addElement(project, first, "Excluded");
QVERIFY(excluded);
auto context = excluded->elementInformations();
context.addValue("exclude_from_bom", "true");
excluded->setElementInformations(context);
for (const QString tag : {"Z2", "A2"}) {
auto *element = addElement(project, second, tag);
QVERIFY(element);
auto info = sample();
info.m_device_tag = tag;
element->setElementInformations(info.toContext());
}
const auto rows = DeviceBomExport::collect(project);
QCOMPARE(rows.size(), 4);
QCOMPARE(rows.at(0).m_page, QString("DEV-01"));
QCOMPARE(rows.at(0).m_device.m_device_tag, QString("1"));
QCOMPARE(rows.at(1).m_device.m_device_tag, QString("8"));
QCOMPARE(rows.at(2).m_page, QString("DEV-10"));
QCOMPARE(rows.at(2).m_device.m_device_tag, QString("A2"));
QCOMPARE(rows.at(3).m_device.m_device_tag, QString("Z2"));
QCOMPARE(rows.at(2).m_device.m_part_number, rows.at(3).m_device.m_part_number);
QCOMPARE(rows.at(2).m_device.m_quantity, QString("2.5"));
QCOMPARE(DeviceBomExport::toCsv(rows), DeviceBomExport::toCsv(DeviceBomExport::collect(project)));
}
void emptyFolioFallsBackToPagePosition()
{
QETProject project;
auto *diagram = project.addNewDiagram();
diagram->border_and_titleblock.setFolio(QString());
diagram->border_and_titleblock.setFolioData(1, 1);
QVERIFY(addElement(project, diagram, "K1"));
const auto rows = DeviceBomExport::collect(project);
QCOMPARE(rows.size(), 1);
QCOMPARE(rows.first().m_page, QString("1"));
}
void csvEscapingAndEncoding()
{
const auto csv = DeviceBomExport::toCsv({{sample(), "F-03"}});
const QByteArray header("\xEF\xBB\xBF\"No\",\"Device tag\",\"Manufacturer\",\"Part number\","
"\"Model\",\"Description\",\"Category\",\"Quantity\",\"Page\",\"Notes\"\r\n");
QVERIFY(csv.startsWith(header));
QVERIFY(csv.contains(sample().m_manufacturer.toUtf8()));
QVERIFY(csv.contains("\"001234\""));
QVERIFY(!csv.contains("24 V DC"));
QVERIFY(!csv.contains("16 A"));
QVERIFY(csv.endsWith("\"2.5\",\"F-03\",\"Panel A, \"\"spare\"\"; check\nSecond line\"\r\n"));
QCOMPARE(DeviceBomExport::toCsv({}), header);
}
void fileSuccessAndFailure()
{
QTemporaryDir temp;
QETProject project;
QString error = "old error";
QVERIFY(!DeviceBomExport::writeCsv(project, temp.filePath("absent/bom.csv"), &error));
QVERIFY(!error.isEmpty());
const auto path = temp.filePath("bom.csv");
QVERIFY(DeviceBomExport::writeCsv(project, path, &error));
QVERIFY(error.isEmpty());
QVERIFY(DeviceBomExport::writeCsv(project, path, &error));
QFile file(path);
QVERIFY(file.open(QIODevice::ReadOnly));
QCOMPARE(file.readAll(), DeviceBomExport::toCsv({}));
}
void propertyEditorUndoRedo()
{
QETProject project;
auto *diagram = project.addNewDiagram();
auto *element = addElement(project, diagram, "-K1");
QVERIFY(element);
ElementInfoWidget editor(element);
const auto context = sample().toContext();
const auto widgets = editor.findChildren<ElementInfoPartWidget *>();
for (const QString &key : context.keys()) {
auto it = std::find_if(widgets.begin(), widgets.end(), [&key](ElementInfoPartWidget *widget) {
return widget->key() == key;
});
QVERIFY2(it != widgets.end(), qPrintable(key));
(*it)->setText(context.value(key).toString().replace('\n', ' '));
}
const int undo_count = diagram->undoStack().count();
editor.apply();
QCOMPARE(element->elementInformations().value("model").toString(), QString("RX-24"));
QCOMPARE(diagram->undoStack().count(), undo_count + 1);
diagram->undoStack().undo();
QVERIFY(element->elementInformations().value("model").toString().isEmpty());
diagram->undoStack().redo();
QCOMPARE(element->elementInformations().value("voltage_rating").toString(), QString("24 V DC"));
QCOMPARE(DeviceBomExport::collect(project).first().m_device.m_model, QString("RX-24"));
editor.resize(600, 750);
editor.show();
QCoreApplication::processEvents();
const QString artifacts = qEnvironmentVariable("QET_SMART_DEVICE_ARTIFACT_DIR");
if (!artifacts.isEmpty()) {
QVERIFY(editor.grab().save(QDir(artifacts).filePath("properties.png")));
}
}
void existingDatabaseReports()
{
QETProject project;
auto *element = addElement(project, project.addNewDiagram(), "-K1");
QVERIFY(element);
element->setElementInformations(sample().toContext());
project.dataBase()->updateDB();
auto query = project.dataBase()->newQuery(QStringLiteral(
"SELECT model,category,voltage_rating,current_rating,notes FROM element_nomenclature_view"));
QVERIFY2(query.exec(), qPrintable(query.lastError().text()));
QVERIFY(query.next());
QCOMPARE(query.value(0).toString(), sample().m_model);
QCOMPARE(query.value(1).toString(), sample().m_category);
QCOMPARE(query.value(2).toString(), sample().m_voltage_rating);
QCOMPARE(query.value(3).toString(), sample().m_current_rating);
QCOMPARE(query.value(4).toString(), sample().m_notes);
}
};
QTEST_MAIN(SmartDeviceTest)
#include "tst_smart_device.moc"