mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-28 21:34:12 +02:00
Merge remote-tracking branch 'upstream/master' into feat/lasting-wire-uuid
# Conflicts: # tests/qttest/CMakeLists.txt
This commit is contained in:
@@ -437,6 +437,24 @@ void Diagram::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
}
|
||||
|
||||
rememberSelection();
|
||||
//Clicking again on a member of a group that is selected whole picks
|
||||
//that member out, to edit it on its own (discussion #1070): noted
|
||||
//here, decided on release, since a drag must still move the group.
|
||||
//Ctrl keeps its usual meaning.
|
||||
m_member_to_pick.clear();
|
||||
if (event->button() == Qt::LeftButton
|
||||
&& !event->modifiers().testFlag(Qt::ControlModifier)) {
|
||||
QTransform view_transform;
|
||||
if (event->widget()) {
|
||||
if (auto view = qobject_cast<QGraphicsView *>(event->widget()->parentWidget())) {
|
||||
view_transform = view->transform();
|
||||
}
|
||||
}
|
||||
if (QGraphicsItem *member = ItemGroups::memberToPick(
|
||||
itemAt(event->scenePos(), view_transform))) {
|
||||
m_member_to_pick = member->toGraphicsObject();
|
||||
}
|
||||
}
|
||||
QGraphicsScene::mousePressEvent(event);
|
||||
completeGroupSelection();
|
||||
}
|
||||
@@ -477,6 +495,19 @@ void Diagram::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
||||
}
|
||||
|
||||
QGraphicsScene::mouseReleaseEvent(event);
|
||||
|
||||
//A click that did not drag, on a member of a group selected whole:
|
||||
//Qt has left only that member selected, and it stays so.
|
||||
QGraphicsObject *picked = m_member_to_pick.data();
|
||||
m_member_to_pick.clear();
|
||||
if (picked
|
||||
&& (event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength()
|
||||
< QApplication::startDragDistance()
|
||||
&& selectedItems() == QList<QGraphicsItem *>{picked}) {
|
||||
rememberSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
//A click on an already selected item changes the selection on
|
||||
//release, not on press (Ctrl toggles it, a plain click keeps only it).
|
||||
completeGroupSelection();
|
||||
|
||||
@@ -142,6 +142,9 @@ class Diagram : public QGraphicsScene
|
||||
//Selection before the current click, see completeGroupSelection()
|
||||
QList<QPointer<QGraphicsObject>> m_previous_selection;
|
||||
void rememberSelection();
|
||||
//Member of a wholly selected group under the current click, which
|
||||
//the click picks out on its own if it ends without a drag
|
||||
QPointer<QGraphicsObject> m_member_to_pick;
|
||||
|
||||
bool uuidUsedByOtherDiagram(const QUuid &uuid) const;
|
||||
QUuid derivedUuid(const QDomElement &root, const QString &reason) const;
|
||||
|
||||
+25
-11
@@ -144,6 +144,27 @@ bool DiagramContext::operator!=(const DiagramContext &dc) const
|
||||
return(!(*this == dc));
|
||||
}
|
||||
|
||||
namespace {
|
||||
/**
|
||||
The value as it is saved, and so as it is read back: stray leading and
|
||||
trailing whitespace around real content trimmed, but not a value that IS
|
||||
whitespace -- unconditionally trimming an all-whitespace string collapses
|
||||
it to "", which is silently indistinguishable from a value that was never
|
||||
set. A title-block custom variable set to a single space -- a workaround
|
||||
for #973, where an unset variable renders as its own literal placeholder
|
||||
-- would otherwise vanish on the very next save.
|
||||
Applied when reading as well as when writing, so that what is in memory
|
||||
after a load is what the next save writes: a label shown from an
|
||||
untrimmed value would otherwise keep its spaces on screen and in its
|
||||
displayed copy until the project was saved and opened again, and a
|
||||
just-saved project would change on its second save.
|
||||
*/
|
||||
QString storedValue(const QString &raw)
|
||||
{
|
||||
return raw.trimmed().isEmpty() ? raw : raw.trimmed();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
Export this context properties under the \a e XML element, using tags
|
||||
named \a tag_name (defaults to "property").
|
||||
@@ -161,15 +182,7 @@ void DiagramContext::toXml(QDomElement &e, const QString &tag_name) const
|
||||
property.removeAttribute("name");
|
||||
property.setAttribute("show", m_content_show[key]);
|
||||
property.setAttribute("name", key);
|
||||
// Trim stray leading/trailing whitespace around real content, but
|
||||
// not a value that IS whitespace: unconditionally trimming an
|
||||
// all-whitespace string collapses it to "", which is silently
|
||||
// indistinguishable from a value that was never set. A title-block
|
||||
// custom variable set to a single space -- a workaround for #973,
|
||||
// where an unset variable renders as its own literal placeholder --
|
||||
// would otherwise vanish on the very next save.
|
||||
const QString stored = raw.trimmed().isEmpty() ? raw : raw.trimmed();
|
||||
QDomText value = e.ownerDocument().createTextNode(stored);
|
||||
QDomText value = e.ownerDocument().createTextNode(storedValue(raw));
|
||||
property.appendChild(value);
|
||||
e.appendChild(property);
|
||||
}
|
||||
@@ -182,7 +195,7 @@ void DiagramContext::toXml(QDomElement &e, const QString &tag_name) const
|
||||
void DiagramContext::fromXml(const QDomElement &e, const QString &tag_name) {
|
||||
foreach (QDomElement property, QET::findInDomElement(e, tag_name)) {
|
||||
if (!property.hasAttribute("name")) continue;
|
||||
addValue(property.attribute("name"), QVariant(property.text()));
|
||||
addValue(property.attribute("name"), QVariant(storedValue(property.text())));
|
||||
m_content_show.insert(property.attribute("name"), property.attribute("show", "1").toInt());
|
||||
}
|
||||
}
|
||||
@@ -198,7 +211,8 @@ void DiagramContext::fromXml(const pugi::xml_node &dom_element, const QString &t
|
||||
{
|
||||
for(auto node = dom_element.child(tag_name.toStdString().c_str()) ; node ; node = node.next_sibling(tag_name.toStdString().c_str()))
|
||||
{
|
||||
addValue(node.attribute("name").as_string(), QVariant(node.text().as_string()));
|
||||
addValue(node.attribute("name").as_string(),
|
||||
QVariant(storedValue(QString::fromUtf8(node.text().as_string()))));
|
||||
m_content_show.insert(node.attribute("name").as_string(), node.attribute("show").empty()? 1 : node.attribute("show").as_int());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,47 @@ QUuid ItemGroups::read(const QDomElement &xml)
|
||||
return QUuid(xml.attribute(QString::fromLatin1(xml_attribute)));
|
||||
}
|
||||
|
||||
/**
|
||||
@return @a item, or its nearest ancestor, that belongs to a group -- a
|
||||
click on a symbol's own text hits the text, but the symbol is the member
|
||||
-- or nullptr if none does.
|
||||
*/
|
||||
QGraphicsItem *ItemGroups::groupedItem(QGraphicsItem *item)
|
||||
{
|
||||
for (; item; item = item->parentItem()) {
|
||||
if (!groupOf(item).isNull()) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
@return the member a click on @a hit may pick out on its own: the grouped
|
||||
item hit, when every member of its group is selected already. A first
|
||||
click selects the whole group; a second click, on a member of the group
|
||||
it selected, is how the user asks for that member alone (discussion
|
||||
#1070). nullptr when the click is not that.
|
||||
*/
|
||||
QGraphicsItem *ItemGroups::memberToPick(QGraphicsItem *hit)
|
||||
{
|
||||
QGraphicsItem *member = groupedItem(hit);
|
||||
if (!member || !member->isSelected() || !member->scene()) {
|
||||
return nullptr;
|
||||
}
|
||||
const QUuid group = groupOf(member);
|
||||
int members = 0;
|
||||
for (QGraphicsItem *item : member->scene()->items()) {
|
||||
if (groupOf(item) == group) {
|
||||
if (!item->isSelected()) {
|
||||
return nullptr;
|
||||
}
|
||||
++members;
|
||||
}
|
||||
}
|
||||
return members > 1 ? member : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
Make the selection of @a scene whole groups again after it changed.
|
||||
A group with a selected member is selected entirely, except when the
|
||||
@@ -122,3 +163,34 @@ bool ItemGroups::completeSelection(QGraphicsScene *scene,
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
/**
|
||||
@return the group @a selected is exactly, whole -- every item in it
|
||||
belongs to that group and every member of the group is in it -- or a null
|
||||
uuid. Rotating such a selection turns the group as one piece rather than
|
||||
each member in place (discussion #1070). A member picked out on its own
|
||||
is not a whole group, and turns in place.
|
||||
@param selected : the selected items that can be members (the caller
|
||||
leaves out wires, which follow their symbols)
|
||||
*/
|
||||
QUuid ItemGroups::soleWholeGroup(const QList<QGraphicsItem *> &selected)
|
||||
{
|
||||
if (selected.isEmpty() || !selected.first()->scene()) {
|
||||
return QUuid();
|
||||
}
|
||||
const QUuid group = groupOf(selected.first());
|
||||
if (group.isNull()) {
|
||||
return QUuid();
|
||||
}
|
||||
for (QGraphicsItem *item : selected) {
|
||||
if (groupOf(item) != group) {
|
||||
return QUuid();
|
||||
}
|
||||
}
|
||||
for (QGraphicsItem *item : selected.first()->scene()->items()) {
|
||||
if (groupOf(item) == group && !item->isSelected()) {
|
||||
return QUuid();
|
||||
}
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
@@ -52,9 +52,14 @@ namespace ItemGroups
|
||||
void write(QDomElement &xml, const QGraphicsItem *item);
|
||||
QUuid read(const QDomElement &xml);
|
||||
|
||||
QGraphicsItem *groupedItem(QGraphicsItem *item);
|
||||
QGraphicsItem *memberToPick(QGraphicsItem *hit);
|
||||
|
||||
bool completeSelection(QGraphicsScene *scene,
|
||||
const QList<QGraphicsItem *> &previous,
|
||||
bool toggling);
|
||||
|
||||
QUuid soleWholeGroup(const QList<QGraphicsItem *> &selected);
|
||||
}
|
||||
|
||||
#endif // ITEMGROUPS_H
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "ElementsCollection/elementpickerpopup.h"
|
||||
#include "shortcutbarsettings.h"
|
||||
#include "qetgraphicsitem/conductor.h"
|
||||
#include "itemgroups.h"
|
||||
#include "commandsearchpopup.h"
|
||||
#include "QWidgetAnimation/qwidgetanimation.h"
|
||||
#include "autoNum/ui/autonumberingdockwidget.h"
|
||||
@@ -2078,7 +2079,17 @@ void QETDiagramEditor::selectionGroupTriggered(QAction *action)
|
||||
}
|
||||
else if (value == "rotate_selection")
|
||||
{
|
||||
RotateSelectionCommand *c = new RotateSelectionCommand(diagram);
|
||||
//A selection that is exactly one whole group turns as one piece,
|
||||
//as "Pivoter le groupe" does, rather than each member in place
|
||||
//(discussion #1070). Wires follow their symbols either way.
|
||||
QList<QGraphicsItem *> members;
|
||||
for (QGraphicsItem *item : diagram->selectedItems()) {
|
||||
if (item->type() != Conductor::Type) {
|
||||
members << item;
|
||||
}
|
||||
}
|
||||
const bool whole_group = !ItemGroups::soleWholeGroup(members).isNull();
|
||||
RotateSelectionCommand *c = new RotateSelectionCommand(diagram, 90, nullptr, whole_group);
|
||||
if(c->isValid())
|
||||
diagram->undoStack().push(c);
|
||||
}
|
||||
|
||||
@@ -1033,7 +1033,11 @@ QDomElement Element::toXml(
|
||||
QDomElement infos =
|
||||
document.createElement(QStringLiteral("elementInformations"));
|
||||
m_data.m_informations.toXml(infos, QStringLiteral("elementInformation"));
|
||||
element.appendChild(infos);
|
||||
//toXml() skips empty values: an element whose information is
|
||||
//all empty would otherwise be written an empty block, which the
|
||||
//next load reads as no information and the next save drops.
|
||||
if (infos.hasChildNodes())
|
||||
element.appendChild(infos);
|
||||
}
|
||||
|
||||
//Save override properties (For now, only used when the element is a terminal)
|
||||
|
||||
@@ -354,3 +354,31 @@ target_link_libraries(tst_derivedwireuuid PRIVATE Qt::Test Qt::Xml)
|
||||
target_compile_definitions(tst_derivedwireuuid PRIVATE
|
||||
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\""
|
||||
"QET_EXAMPLES_DIR=\"${QET_DIR}/examples\"")
|
||||
|
||||
# Saving a project that was just saved changes nothing: runs the real
|
||||
# binary's --resave twice on every project in examples/, and on one whose
|
||||
# title block holds a single-space value (#973).
|
||||
|
||||
add_executable(
|
||||
tst_resaveunchanged
|
||||
tst_resaveunchanged.cpp)
|
||||
add_test(NAME tst_resaveunchanged COMMAND tst_resaveunchanged)
|
||||
add_dependencies(tst_resaveunchanged qelectrotech)
|
||||
target_link_libraries(tst_resaveunchanged PRIVATE Qt::Test)
|
||||
target_compile_definitions(tst_resaveunchanged PRIVATE
|
||||
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\""
|
||||
"QET_EXAMPLES_DIR=\"${QET_DIR}/examples\"")
|
||||
|
||||
# DiagramContext::fromXml() -- the two readers (QDom for projects, pugixml
|
||||
# for element definitions in the collection) give the same values: stray
|
||||
# spaces trimmed, accents kept.
|
||||
add_executable(
|
||||
tst_diagramcontext
|
||||
tst_diagramcontext.cpp
|
||||
${QET_DIR}/sources/diagramcontext.cpp
|
||||
${QET_DIR}/sources/qet.cpp
|
||||
${QET_DIR}/sources/qeticons.cpp
|
||||
${QET_DIR}/sources/shortcutmanager.cpp)
|
||||
add_test(NAME tst_diagramcontext COMMAND tst_diagramcontext)
|
||||
target_include_directories(tst_diagramcontext PRIVATE ${QET_DIR}/sources)
|
||||
target_link_libraries(tst_diagramcontext PRIVATE Qt::Test Qt::Widgets Qt::Xml pugixml::pugixml)
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include <QtTest>
|
||||
|
||||
#include "diagramcontext.h"
|
||||
#include "qetapp.h"
|
||||
|
||||
QString QETApp::m_interface_language;
|
||||
|
||||
/**
|
||||
DiagramContext::fromXml() has two readers: QDom, for projects, and
|
||||
pugixml, for element definitions in the collection. Both must give the
|
||||
values the next save writes -- stray spaces around real content trimmed,
|
||||
accented characters kept.
|
||||
*/
|
||||
class tst_diagramcontext : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
static QByteArray xml(const QString &value)
|
||||
{
|
||||
return QStringLiteral(
|
||||
"<elementInformations>"
|
||||
"<elementInformation name=\"v\" show=\"1\">%1</elementInformation>"
|
||||
"</elementInformations>")
|
||||
.arg(value)
|
||||
.toUtf8();
|
||||
}
|
||||
|
||||
static QString fromDom(const QByteArray &data)
|
||||
{
|
||||
QDomDocument doc;
|
||||
if (!doc.setContent(data)) return QStringLiteral("<parse error>");
|
||||
DiagramContext dc;
|
||||
dc.fromXml(doc.documentElement(), QStringLiteral("elementInformation"));
|
||||
return dc.value(QStringLiteral("v")).toString();
|
||||
}
|
||||
|
||||
static QString fromPugi(const QByteArray &data)
|
||||
{
|
||||
pugi::xml_document doc;
|
||||
if (!doc.load_buffer(data.constData(), size_t(data.size())))
|
||||
return QStringLiteral("<parse error>");
|
||||
DiagramContext dc;
|
||||
dc.fromXml(doc.document_element(), QStringLiteral("elementInformation"));
|
||||
return dc.value(QStringLiteral("v")).toString();
|
||||
}
|
||||
|
||||
private slots:
|
||||
void bothReadersAgree_data()
|
||||
{
|
||||
QTest::addColumn<QString>("value");
|
||||
QTest::addColumn<QString>("expected");
|
||||
|
||||
QTest::newRow("plain") << "PRISE" << "PRISE";
|
||||
QTest::newRow("stray spaces") << " PRISE " << "PRISE";
|
||||
QTest::newRow("accents") << "Armoire façade été" << "Armoire façade été";
|
||||
QTest::newRow("accents and stray spaces") << " Moteur à cage " << "Moteur à cage";
|
||||
QTest::newRow("non-Latin") << "Двигатель 電機" << "Двигатель 電機";
|
||||
}
|
||||
|
||||
void bothReadersAgree()
|
||||
{
|
||||
QFETCH(QString, value);
|
||||
QFETCH(QString, expected);
|
||||
QCOMPARE(fromDom(xml(value)), expected);
|
||||
QCOMPARE(fromPugi(xml(value)), expected);
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_APPLESS_MAIN(tst_diagramcontext)
|
||||
|
||||
#include "tst_diagramcontext.moc"
|
||||
@@ -121,6 +121,48 @@ private slots:
|
||||
b = nullptr;
|
||||
}
|
||||
|
||||
// A second click on a member of a group selected whole picks that member
|
||||
// out; a click on a member of a group not selected whole does not.
|
||||
void aMemberOfAWholeGroupCanBePicked()
|
||||
{
|
||||
select({a, b});
|
||||
QCOMPARE(ItemGroups::memberToPick(a), a);
|
||||
QCOMPARE(ItemGroups::memberToPick(b), b);
|
||||
}
|
||||
|
||||
void aMemberOfAPartlySelectedGroupIsNotPicked()
|
||||
{
|
||||
select({a}); // after one member was picked
|
||||
QCOMPARE(ItemGroups::memberToPick(a), nullptr);
|
||||
select({});
|
||||
QCOMPARE(ItemGroups::memberToPick(a), nullptr);
|
||||
}
|
||||
|
||||
void anUngroupedItemIsNotPicked()
|
||||
{
|
||||
select({c});
|
||||
QCOMPARE(ItemGroups::memberToPick(c), nullptr);
|
||||
QCOMPARE(ItemGroups::memberToPick(nullptr), nullptr);
|
||||
}
|
||||
|
||||
void aGroupOfOneIsNotPicked()
|
||||
{
|
||||
ItemGroups::setGroup(e, QUuid()); // g2 is now d alone
|
||||
select({d});
|
||||
QCOMPARE(ItemGroups::memberToPick(d), nullptr);
|
||||
}
|
||||
|
||||
// A click lands on a symbol's own text, not on the symbol: the member is
|
||||
// the nearest grouped ancestor.
|
||||
void aClickOnAMembersChildPicksTheMember()
|
||||
{
|
||||
auto child = new QGraphicsRectItem(0, 0, 2, 2, a);
|
||||
QCOMPARE(ItemGroups::groupedItem(child), a);
|
||||
select({a, b});
|
||||
QCOMPARE(ItemGroups::memberToPick(child), a);
|
||||
QCOMPARE(ItemGroups::groupedItem(c), nullptr);
|
||||
}
|
||||
|
||||
void xmlRoundTrip()
|
||||
{
|
||||
QDomDocument doc;
|
||||
@@ -136,6 +178,34 @@ private slots:
|
||||
ItemGroups::setGroup(a, QUuid());
|
||||
QVERIFY(ItemGroups::groupOf(a).isNull());
|
||||
}
|
||||
|
||||
// Rotate turns a selection that is exactly one whole group as one piece.
|
||||
void aWholeGroupAloneIsASoleWholeGroup()
|
||||
{
|
||||
select({a, b});
|
||||
QCOMPARE(ItemGroups::soleWholeGroup(selection()), g1);
|
||||
}
|
||||
|
||||
void aPickedMemberIsNotAWholeGroup()
|
||||
{
|
||||
select({a});
|
||||
QVERIFY(ItemGroups::soleWholeGroup(selection()).isNull());
|
||||
}
|
||||
|
||||
void aGroupWithOtherItemsIsNotASoleGroup()
|
||||
{
|
||||
select({a, b, c});
|
||||
QVERIFY(ItemGroups::soleWholeGroup(selection()).isNull());
|
||||
select({a, b, d, e});
|
||||
QVERIFY(ItemGroups::soleWholeGroup(selection()).isNull());
|
||||
}
|
||||
|
||||
void ungroupedItemsAreNotAGroup()
|
||||
{
|
||||
select({c});
|
||||
QVERIFY(ItemGroups::soleWholeGroup(selection()).isNull());
|
||||
QVERIFY(ItemGroups::soleWholeGroup({}).isNull());
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(tst_itemgroups)
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include <QtTest>
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QProcess>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QRegularExpression>
|
||||
#include <QTemporaryDir>
|
||||
|
||||
// Saving a project that was just saved must change nothing. Two things
|
||||
// made the second save differ from the first, both cleanup done on save
|
||||
// but not on load:
|
||||
// - symbol information whose values were all empty was written as an
|
||||
// empty <elementInformations/> block, which the next load read as no
|
||||
// information and the next save dropped (Projet_vierge.qet);
|
||||
// - information values were trimmed on save but not on load, so a label
|
||||
// with stray spaces kept them in its displayed copy until the project
|
||||
// was opened again (m_000.qet).
|
||||
// Runs the real binary's --resave twice on every example, and on a
|
||||
// project whose title block holds a value that is a single space (#973).
|
||||
class tst_resaveunchanged : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QTemporaryDir m_dir;
|
||||
int m_run = 0;
|
||||
|
||||
// --resave @p in to a new file, in a sandbox of its own (so a running
|
||||
// QElectroTech cannot answer instead); returns the new file's path.
|
||||
QString resave(const QString &in)
|
||||
{
|
||||
const QString out = m_dir.filePath(QStringLiteral("out%1.qet").arg(m_run));
|
||||
const QString home = m_dir.filePath(QStringLiteral("home%1").arg(m_run++));
|
||||
QDir().mkpath(home);
|
||||
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
||||
env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen"));
|
||||
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("--resave"), in, out});
|
||||
if (!proc.waitForFinished(120000) || proc.exitCode() != 0) return {};
|
||||
return out;
|
||||
}
|
||||
|
||||
static QByteArray read(const QString &path)
|
||||
{
|
||||
QFile f(path);
|
||||
return f.open(QIODevice::ReadOnly) ? f.readAll() : QByteArray();
|
||||
}
|
||||
|
||||
private slots:
|
||||
void initTestCase()
|
||||
{
|
||||
QVERIFY(m_dir.isValid());
|
||||
QVERIFY(QFile::exists(QStringLiteral(QET_TEST_BINARY_PATH)));
|
||||
}
|
||||
|
||||
// Projet_vierge.qet has empty information values, m_000.qet values
|
||||
// with stray spaces; every other example is here so a new cause shows.
|
||||
void secondSaveChangesNothing_data()
|
||||
{
|
||||
QTest::addColumn<QString>("project");
|
||||
const QDir examples(QStringLiteral(QET_EXAMPLES_DIR));
|
||||
const QStringList projects =
|
||||
examples.entryList({QStringLiteral("*.qet")}, QDir::Files, QDir::Name);
|
||||
QVERIFY(!projects.isEmpty());
|
||||
for (const QString &project : projects)
|
||||
QTest::newRow(project.toUtf8().constData()) << examples.filePath(project);
|
||||
}
|
||||
|
||||
void secondSaveChangesNothing()
|
||||
{
|
||||
QFETCH(QString, project);
|
||||
const QString first = resave(project);
|
||||
QVERIFY2(!first.isEmpty(), "first --resave failed");
|
||||
const QString second = resave(first);
|
||||
QVERIFY2(!second.isEmpty(), "second --resave failed");
|
||||
const QByteArray a = read(first), b = read(second);
|
||||
QVERIFY(!a.isEmpty());
|
||||
QVERIFY2(a == b, "the second save changed the file");
|
||||
}
|
||||
|
||||
// A title-block value that is a single space is kept through two saves
|
||||
// (#973), and a value with accents comes back as it went in.
|
||||
void singleSpaceValueKept()
|
||||
{
|
||||
QByteArray xml = read(QStringLiteral(QET_EXAMPLES_DIR "/Projet_vierge.qet"));
|
||||
QVERIFY(xml.contains("<properties>"));
|
||||
xml.replace("<properties>",
|
||||
"<properties>"
|
||||
"<property show=\"1\" name=\"space\"> </property>"
|
||||
"<property show=\"1\" name=\"accents\">Armoire façade été</property>");
|
||||
const QString in = m_dir.filePath(QStringLiteral("space.qet"));
|
||||
QFile f(in);
|
||||
QVERIFY(f.open(QIODevice::WriteOnly));
|
||||
f.write(xml);
|
||||
f.close();
|
||||
|
||||
const QString first = resave(in);
|
||||
QVERIFY2(!first.isEmpty(), "first --resave failed");
|
||||
const QString second = resave(first);
|
||||
QVERIFY2(!second.isEmpty(), "second --resave failed");
|
||||
const QByteArray a = read(first), b = read(second);
|
||||
QVERIFY2(a == b, "the second save changed the file");
|
||||
|
||||
const QString saved = QString::fromUtf8(b);
|
||||
QVERIFY2(saved.contains(QRegularExpression(
|
||||
QStringLiteral("<property [^>]*name=\"space\"[^>]*> </property>"))),
|
||||
"the single-space value was lost");
|
||||
QVERIFY2(saved.contains(QRegularExpression(
|
||||
QStringLiteral("<property [^>]*name=\"accents\"[^>]*>Armoire façade été</property>"))),
|
||||
"the accented value changed");
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_APPLESS_MAIN(tst_resaveunchanged)
|
||||
|
||||
#include "tst_resaveunchanged.moc"
|
||||
Reference in New Issue
Block a user