Revision control

Copy as Markdown

// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtTest/QTest>
#include <QtTest/QSignalSpy>
#include <QtCore/qfile.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qdir.h>
#include <QtQml/qqmlcontext.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlcomponent.h>
#include <QtQuick/qquickitem.h>
#include <QtQuickShapes/private/qquickshape_p.h>
#include <QtQuickVectorImage/private/qquickvectorimage_p.h>
#include <QtQuickVectorImage/private/qquickvectorimage_p_p.h>
#include <QtQuickTestUtils/private/qmlutils_p.h>
#include <QtQuickTestUtils/private/viewtestutils_p.h>
#include <QtQuickTestUtils/private/visualtestutils_p.h>
class tst_QQuickVectorImage : public QQmlDataTest
{
Q_OBJECT
public:
tst_QQuickVectorImage();
private slots:
void parseFiles_data();
void parseFiles();
void parseBrokenFile();
void asyncShapes_data();
void asyncShapes();
void clearSource();
void renderFiles_data();
void renderFiles();
void renderAnimatedFrames();
void sourceData();
};
tst_QQuickVectorImage::tst_QQuickVectorImage()
: QQmlDataTest(QT_QMLTEST_DATADIR)
{
}
void tst_QQuickVectorImage::parseFiles_data()
{
QTest::addColumn<QString>("fileName");
QDir dir(QStringLiteral(":/svgs"));
const QFileInfoList infos = dir.entryInfoList({ QStringLiteral("*.svg") });
QVERIFY(!infos.isEmpty());
for (const QFileInfo &info : infos) {
QString fileName = info.fileName();
QByteArray rowName = fileName.toUtf8();
QTest::newRow(rowName) << fileName;
}
}
void tst_QQuickVectorImage::parseFiles()
{
QFETCH(QString, fileName);
QQmlEngine engine;
engine.rootContext()->setContextProperty(QStringLiteral("fileName"), QStringLiteral("qrc:/svgs/%1").arg(fileName));
QQmlComponent c(&engine, testFileUrl("vectorimage.qml"));
QQuickVectorImage *item = qobject_cast<QQuickVectorImage *>(c.create());
auto cleanup = qScopeGuard([&item] {
delete item;
item = nullptr;
});
QVERIFY(item != nullptr);
QVERIFY(!item->childItems().isEmpty());
QVERIFY(!item->childItems().first()->size().isNull());
QCOMPARE(item->status(), QQuickVectorImage::Status::Ready);
}
void tst_QQuickVectorImage::parseBrokenFile()
{
QQmlEngine engine;
engine.rootContext()->setContextProperty(QStringLiteral("fileName"), testFileUrl("svg/broken.svg"));
QQmlComponent c(&engine, testFileUrl("vectorimage.qml"));
QQuickVectorImage *item = qobject_cast<QQuickVectorImage *>(c.create());
auto cleanup = qScopeGuard([&item] {
delete item;
item = nullptr;
});
QVERIFY(item != nullptr);
QVERIFY(item->childItems().isEmpty());
QCOMPARE(item->status(), QQuickVectorImage::Status::Error);
}
void tst_QQuickVectorImage::asyncShapes_data()
{
QTest::addColumn<QString>("testFile");
QTest::addColumn<bool>("isAsync");
QTest::newRow("sync") << "vectorimage.qml" << false;
QTest::newRow("async") << "vectorimage-async.qml" << true;
}
void tst_QQuickVectorImage::asyncShapes()
{
QFETCH(QString, testFile);
QFETCH(bool, isAsync);
QQmlEngine engine;
engine.rootContext()->setContextProperty(QStringLiteral("fileName"), QStringLiteral("qrc:/svgs/gradientxform.svg"));
QQmlComponent c(&engine, testFileUrl(testFile));
QQuickItem *item = qobject_cast<QQuickItem *>(c.create());
auto cleanup = qScopeGuard([&item] {
delete item;
item = nullptr;
});
QVERIFY(item != nullptr);
QVERIFY(!item->childItems().isEmpty());
QVERIFY(!item->childItems().first()->size().isNull());
const QList<const QQuickShape *> shapes = item->findChildren<const QQuickShape *>();
QVERIFY(!shapes.isEmpty());
for (const QQuickShape *shape : shapes)
QCOMPARE(shape->asynchronous(), isAsync);
}
void tst_QQuickVectorImage::clearSource()
{
const QUrl fileName = QUrl("qrc:/svgs/gradientxform.svg");
QQmlEngine engine;
engine.rootContext()->setContextProperty(QStringLiteral("fileName"), fileName);
QQmlComponent c(&engine, testFileUrl("vectorimage.qml"));
QQuickVectorImage *item = qobject_cast<QQuickVectorImage *>(c.create());
auto cleanup = qScopeGuard([&item] {
delete item;
item = nullptr;
});
QVERIFY(item != nullptr);
QVERIFY(item->generatedItem() != nullptr);
QCOMPARE(item->status(), QQuickVectorImage::Status::Ready);
{
QSignalSpy statusChanged(item, &QQuickVectorImage::statusChanged);
item->setSource(QUrl{});
QCOMPARE(item->generatedItem(), nullptr);
QCOMPARE(statusChanged.size(), 1);
}
item->setAsynchronous(true);
item->setRetainWhileLoading(true);
item->setSource(fileName);
QTRY_COMPARE(item->status(), QQuickVectorImage::Status::Ready);
{
QSignalSpy statusChanged(item, &QQuickVectorImage::statusChanged);
item->setSource(QUrl{});
QCOMPARE(item->generatedItem(), nullptr);
QCOMPARE(statusChanged.size(), 1);
}
}
void tst_QQuickVectorImage::renderFiles_data()
{
QTest::addColumn<QUrl>("fileName");
QTest::newRow("intoverflow.svg") << testFileUrl("svg/intoverflow.svg");
}
void tst_QQuickVectorImage::renderFiles()
{
QFETCH(QUrl, fileName);
QQmlEngine engine;
engine.rootContext()->setContextProperty(QStringLiteral("fileName"), fileName);
QQuickWindow window;
window.resize(512, 512);
window.create();
QQmlComponent c(&engine, testFileUrl("vectorimage.qml"));
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
std::unique_ptr<QObject> object(c.create());
QQuickVectorImage *image = qobject_cast<QQuickVectorImage *>(object.get());
QVERIFY(image != nullptr);
image->setParentItem(window.contentItem());
window.grabWindow();
}
void tst_QQuickVectorImage::renderAnimatedFrames()
{
QQmlEngine engine;
engine.rootContext()->setContextProperty(QStringLiteral("fileName"),
testFileUrl("svg/indexbufferoverflow.svg"));
QQuickWindow window;
window.resize(512, 512);
window.create();
QQmlComponent c(&engine, testFileUrl("vectorimage.qml"));
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
std::unique_ptr<QObject> object(c.create());
QQuickVectorImage *image = qobject_cast<QQuickVectorImage *>(object.get());
QVERIFY(image != nullptr);
image->setParentItem(window.contentItem());
for (int i = 0; i < 150; ++i) {
window.grabWindow();
QTest::qWait(16);
}
}
void tst_QQuickVectorImage::sourceData()
{
QFile file(QStringLiteral(":/svgs/gradientxform.svg"));
QVERIFY(file.open(QIODevice::ReadOnly));
const QByteArray svgData = file.readAll();
QQmlEngine engine;
engine.rootContext()->setContextProperty(QStringLiteral("fileName"), testFileUrl("svg/broken.svg"));
QQmlComponent c(&engine, testFileUrl("vectorimage.qml"));
QQuickVectorImage *item = qobject_cast<QQuickVectorImage *>(c.create());
auto cleanup = qScopeGuard([&item] {
delete item;
item = nullptr;
});
QVERIFY(item != nullptr);
QCOMPARE(item->status(), QQuickVectorImage::Status::Error);
QQuickVectorImagePrivate *d_ptr = QQuickVectorImagePrivate::get(item);
QVERIFY(d_ptr != nullptr);
// A non-empty sourceData takes precedence over the broken source file
d_ptr->setSourceData(svgData);
QVERIFY(!item->childItems().isEmpty());
QVERIFY(!item->childItems().first()->size().isNull());
QCOMPARE(item->status(), QQuickVectorImage::Status::Ready);
// Clearing sourceData falls back to loading from source again
d_ptr->setSourceData(QByteArray());
QCOMPARE(item->status(), QQuickVectorImage::Status::Error);
}
QTEST_MAIN(tst_QQuickVectorImage)
#include "tst_qquickvectorimage.moc"