mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-14 09:14:14 +02:00
Address smart device BOM review feedback
This commit is contained in:
+109
-299
@@ -15,311 +15,121 @@
|
||||
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 "bomexport.h"
|
||||
|
||||
#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"
|
||||
#include <QFile>
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlError>
|
||||
#include <QSqlQuery>
|
||||
#include <QTemporaryDir>
|
||||
#include <QtTest>
|
||||
|
||||
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);
|
||||
}
|
||||
private slots:
|
||||
void metadataColumns();
|
||||
void defaultQueryAndCsv();
|
||||
void atomicWrite();
|
||||
};
|
||||
|
||||
QTEST_MAIN(SmartDeviceTest)
|
||||
void SmartDeviceTest::metadataColumns()
|
||||
{
|
||||
const auto columns = BomExport::defaultColumns();
|
||||
for (const QString &column : {
|
||||
QStringLiteral("label"), QStringLiteral("manufacturer"),
|
||||
QStringLiteral("manufacturer_reference"), QStringLiteral("model"),
|
||||
QStringLiteral("description"), QStringLiteral("category"),
|
||||
QStringLiteral("quantity"), QStringLiteral("voltage_rating"),
|
||||
QStringLiteral("current_rating"), QStringLiteral("folio"),
|
||||
QStringLiteral("notes")})
|
||||
{
|
||||
QVERIFY2(columns.contains(column), qPrintable(column));
|
||||
}
|
||||
}
|
||||
|
||||
void SmartDeviceTest::defaultQueryAndCsv()
|
||||
{
|
||||
const QString connection = QStringLiteral("smart-device-%1")
|
||||
.arg(QUuid::createUuid().toString());
|
||||
{
|
||||
auto db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), connection);
|
||||
db.setDatabaseName(QStringLiteral(":memory:"));
|
||||
QVERIFY2(db.open(), qPrintable(db.lastError().text()));
|
||||
QSqlQuery setup(db);
|
||||
QVERIFY(setup.exec(QStringLiteral(
|
||||
"CREATE TABLE element_nomenclature_view ("
|
||||
"label TEXT, designation TEXT, manufacturer TEXT, "
|
||||
"manufacturer_reference TEXT, model TEXT, description TEXT, "
|
||||
"category TEXT, quantity TEXT, voltage_rating TEXT, "
|
||||
"current_rating TEXT, folio TEXT, notes TEXT, "
|
||||
"element_type TEXT, diagram_position INTEGER, position INTEGER)")));
|
||||
|
||||
QSqlQuery insert(db);
|
||||
insert.prepare(QStringLiteral(
|
||||
"INSERT INTO element_nomenclature_view VALUES "
|
||||
"(?, '', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
|
||||
auto add = [&insert](const QString &tag, const QString &type, int page,
|
||||
int position, const QString &manufacturer) {
|
||||
insert.bindValue(0, tag);
|
||||
insert.bindValue(1, manufacturer);
|
||||
insert.bindValue(2, QStringLiteral("0012"));
|
||||
insert.bindValue(3, QStringLiteral("M-1"));
|
||||
insert.bindValue(4, QStringLiteral("Power contactor"));
|
||||
insert.bindValue(5, QStringLiteral("Control"));
|
||||
insert.bindValue(6, QStringLiteral("1"));
|
||||
insert.bindValue(7, QStringLiteral("24 V DC"));
|
||||
insert.bindValue(8, QStringLiteral("16 A"));
|
||||
insert.bindValue(9, QString::number(page));
|
||||
insert.bindValue(10, QStringLiteral("Quoted \"note\"\nnext line"));
|
||||
insert.bindValue(11, type);
|
||||
insert.bindValue(12, page);
|
||||
insert.bindValue(13, position);
|
||||
return insert.exec();
|
||||
};
|
||||
QVERIFY(add(QStringLiteral("-K2"), QStringLiteral("simple"), 2, 1,
|
||||
QStringLiteral("日本電機")));
|
||||
QVERIFY(add(QStringLiteral("-K1"), QStringLiteral("master"), 1, 2,
|
||||
QStringLiteral("Müller")));
|
||||
QVERIFY(add(QStringLiteral("-K1.1"), QStringLiteral("slave"), 1, 3,
|
||||
QStringLiteral("Must not be exported")));
|
||||
QVERIFY(add(QStringLiteral("X1"), QStringLiteral("terminal"), 1, 4,
|
||||
QStringLiteral("Must not be exported")));
|
||||
|
||||
QSqlQuery query(db);
|
||||
QVERIFY2(query.exec(BomExport::defaultQuery()),
|
||||
qPrintable(query.lastError().text()));
|
||||
int rows = 0;
|
||||
const auto csv = BomExport::toCsv(
|
||||
query, BomExport::defaultColumns(), true, &rows);
|
||||
QCOMPARE(rows, 2);
|
||||
QVERIFY(csv.startsWith("\xEF\xBB\xBF\"label\";\"designation\";"));
|
||||
QVERIFY(csv.contains(QStringLiteral("Müller").toUtf8()));
|
||||
QVERIFY(csv.contains(QStringLiteral("日本電機").toUtf8()));
|
||||
QVERIFY(csv.contains("\"Quoted \"\"note\"\"\nnext line\""));
|
||||
QVERIFY(!csv.contains("Must not be exported"));
|
||||
QVERIFY(csv.indexOf("-K1") < csv.indexOf("-K2"));
|
||||
}
|
||||
QSqlDatabase::removeDatabase(connection);
|
||||
}
|
||||
|
||||
void SmartDeviceTest::atomicWrite()
|
||||
{
|
||||
QTemporaryDir temporary;
|
||||
QVERIFY(temporary.isValid());
|
||||
const auto path = temporary.filePath(QStringLiteral("bom.csv"));
|
||||
const QByteArray csv("\xEF\xBB\xBF\"label\"\n");
|
||||
QString error = QStringLiteral("stale");
|
||||
QVERIFY(BomExport::writeCsv(path, csv, &error));
|
||||
QVERIFY(error.isEmpty());
|
||||
QFile file(path);
|
||||
QVERIFY(file.open(QIODevice::ReadOnly));
|
||||
QCOMPARE(file.readAll(), csv);
|
||||
QVERIFY(!BomExport::writeCsv(
|
||||
temporary.filePath(QStringLiteral("missing/bom.csv")), csv, &error));
|
||||
QVERIFY(!error.isEmpty());
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(SmartDeviceTest)
|
||||
#include "tst_smart_device.moc"
|
||||
|
||||
Reference in New Issue
Block a user