// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include <QtTest/QTest>
#include <QtTest/QSignalSpy>
#include <QtQuick/qquickview.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlcomponent.h>
#include <QtQml/qqmlcontext.h>
#include <QtQml/qqmlexpression.h>
#include <QtQml/qqmlincubator.h>
#include <QtQuickShapes/private/qquickshape_p.h>
#include <QStandardPaths>

#include <QtQuickTestUtils/private/qmlutils_p.h>
#include <QtQuickTestUtils/private/viewtestutils_p.h>
#include <QtQuickTestUtils/private/visualtestutils_p.h>

using namespace QQuickViewTestUtils;
using namespace QQuickVisualTestUtils;

class PolygonProvider : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QList<QPolygonF> paths READ paths WRITE setPaths NOTIFY pathsChanged)

public:
    QList<QPolygonF> paths() const { return m_paths; }
    void setPaths(QList<QPolygonF> paths)
    {
        if (m_paths == paths)
            return;
        m_paths = paths;
        emit pathsChanged();
    }

signals:
    void pathsChanged();

private:
    QList<QPolygonF> m_paths;
};

class tst_QQuickShape : public QQmlDataTest
{
    Q_OBJECT
public:
    tst_QQuickShape();

private slots:
    void initValues();
    void vpInitValues();
    void basicShape();
    void changeSignals();
    void render();
    void renderWithMultipleSp();
    void radialGrad();
    void conicalGrad();
    void renderPolyline();
    void renderMultiline();
    void polylineDataTypes_data();
    void polylineDataTypes();
    void multilineDataTypes_data();
    void multilineDataTypes();
    void multilineStronglyTyped();
    void fillTransform();
    void changeElementsImperatively();

private:
    QList<QPolygonF> m_lowPolyLogo;
};

tst_QQuickShape::tst_QQuickShape()
    : QQmlDataTest(QT_QMLTEST_DATADIR)
{
    // Force the software backend to get reliable rendering results regardless of the hw and drivers.
    QQuickWindow::setGraphicsApi(QSGRendererInterface::Software);

    const char *uri = "tst_qquickpathitem";
    qmlRegisterType<QQuickShape>(uri, 1, 0, "Shape");
    qmlRegisterType<QQuickShapePath, 14>(uri, 1, 0, "ShapePath");
    qmlRegisterUncreatableType<QQuickShapeGradient>(uri, 1, 0, "ShapeGradient", QQuickShapeGradient::tr("ShapeGradient is an abstract base class"));
    qmlRegisterType<QQuickShapeLinearGradient>(uri, 1, 0, "LinearGradient");
    qmlRegisterType<QQuickShapeRadialGradient>(uri, 1, 0, "RadialGradient");
    qmlRegisterType<QQuickShapeConicalGradient>(uri, 1, 0, "ConicalGradient");
    qmlRegisterType<QQuickPathPolyline>(uri, 1, 0, "PathPolyline");
    qmlRegisterType<PolygonProvider>("Qt.test", 1, 0, "PolygonProvider");

    m_lowPolyLogo << (QPolygonF() <<
                      QPointF(20, 0) <<
                      QPointF(140, 0) <<
                      QPointF(140, 80) <<
                      QPointF(120, 100) <<
                      QPointF(0, 100) <<
                      QPointF(0, 20) <<
                      QPointF(20, 0) )
                  << (QPolygonF() << QPointF(20, 80) <<
                      QPointF(60, 80) <<
                      QPointF(80, 60) <<
                      QPointF(80, 20) <<
                      QPointF(40, 20) <<
                      QPointF(20, 40) <<
                      QPointF(20, 80) )
                  << (QPolygonF() << QPointF(80, 80) <<
                      QPointF(70, 70) )
                  << (QPolygonF() << QPointF(120, 80) <<
                      QPointF(100, 60) <<
                      QPointF(100, 20) )
                  << (QPolygonF() << QPointF(100, 40) <<
                      QPointF(120, 40) );
}

void tst_QQuickShape::initValues()
{
    QQmlEngine engine;
    QQmlComponent c(&engine, testFileUrl("pathitem1.qml"));
    QQuickShape *obj = qobject_cast<QQuickShape *>(c.create());

    QVERIFY(obj != nullptr);
    QVERIFY(obj->rendererType() == QQuickShape::UnknownRenderer);
    QVERIFY(!obj->asynchronous());
    QVERIFY(!obj->vendorExtensionsEnabled());
    QVERIFY(obj->status() == QQuickShape::Null);
    auto vps = obj->data();
    QVERIFY(vps.count(&vps) == 0);

    delete obj;
}

void tst_QQuickShape::vpInitValues()
{
    QQmlEngine engine;
    QQmlComponent c(&engine, testFileUrl("pathitem2.qml"));
    QQuickShape *obj = qobject_cast<QQuickShape *>(c.create());

    QVERIFY(obj != nullptr);
    QVERIFY(obj->rendererType() == QQuickShape::UnknownRenderer);
    QVERIFY(!obj->asynchronous());
    QVERIFY(!obj->vendorExtensionsEnabled());
    QVERIFY(obj->status() == QQuickShape::Null);
    auto vps = obj->data();
    QVERIFY(vps.count(&vps) == 2);

    QQuickShapePath *vp = qobject_cast<QQuickShapePath *>(vps.at(&vps, 0));
    QVERIFY(vp != nullptr);
    QQmlListReference pathList(vp, "pathElements");
    QCOMPARE(pathList.count(), 0);
    QCOMPARE(vp->strokeColor(), QColor(Qt::white));
    QCOMPARE(vp->strokeWidth(), 1.0f);
    QCOMPARE(vp->cosmeticStroke(), false);
    QCOMPARE(vp->fillColor(), QColor(Qt::white));
    QCOMPARE(vp->fillRule(), QQuickShapePath::OddEvenFill);
    QCOMPARE(vp->joinStyle(), QQuickShapePath::BevelJoin);
    QCOMPARE(vp->miterLimit(), 2);
    QCOMPARE(vp->capStyle(), QQuickShapePath::SquareCap);
    QCOMPARE(vp->strokeStyle(), QQuickShapePath::SolidLine);
    QCOMPARE(vp->dashOffset(), 0.0f);
    QCOMPARE(vp->dashPattern(), QList<qreal>() << 4 << 2);
    QVERIFY(!vp->fillGradient());

    delete obj;
}

void tst_QQuickShape::basicShape()
{
    QScopedPointer<QQuickView> window(createView());

    window->setSource(testFileUrl("pathitem3.qml"));
    qApp->processEvents();

    QQuickShape *obj = findItem<QQuickShape>(window->rootObject(), "pathItem");
    QVERIFY(obj != nullptr);
    QQmlListReference list(obj, "data");
    QCOMPARE(list.count(), 1);
    QQuickShapePath *vp = qobject_cast<QQuickShapePath *>(list.at(0));
    QVERIFY(vp != nullptr);
    QCOMPARE(vp->strokeWidth(), 4.0f);
    QCOMPARE(vp->cosmeticStroke(), false);
    QVERIFY(vp->fillGradient() != nullptr);
    QCOMPARE(vp->strokeStyle(), QQuickShapePath::DashLine);

    vp->setStrokeWidth(5.0f);
    QCOMPARE(vp->strokeWidth(), 5.0f);
    vp->setStrokeWidth(-5.0f);
    QCOMPARE(vp->strokeWidth(), -5.0f); // but it won't render
    QCOMPARE(vp->cosmeticStroke(), false);
    vp->setCosmeticStroke(true);
    QCOMPARE(vp->cosmeticStroke(), true);
    QCOMPARE(vp->strokeWidth(), -5.0f);
    vp->setStrokeWidth(5.0f);
    QCOMPARE(vp->strokeWidth(), 5.0f);
    QCOMPARE(vp->strokeWidth(), 5.0f);
    QCOMPARE(vp->cosmeticStroke(), true);

    QQuickShapeLinearGradient *lgrad = qobject_cast<QQuickShapeLinearGradient *>(vp->fillGradient());
    QVERIFY(lgrad != nullptr);
    QCOMPARE(lgrad->spread(), QQuickShapeGradient::PadSpread);
    QCOMPARE(lgrad->x1(), 20.0f);
    QQmlListReference stopList(lgrad, "stops");
    QCOMPARE(stopList.count(), 5);
    QVERIFY(stopList.at(2) != nullptr);

    QQuickPath *path = vp;
    QCOMPARE(path->startX(), 20.0f);
    QQmlListReference pathList(path, "pathElements");
    QCOMPARE(pathList.count(), 3);
}

void tst_QQuickShape::changeSignals()
{
    QScopedPointer<QQuickView> window(createView());

    window->setSource(testFileUrl("pathitem3.qml"));
    qApp->processEvents();

    QQuickShape *obj = findItem<QQuickShape>(window->rootObject(), "pathItem");
    QVERIFY(obj != nullptr);

    QSignalSpy asyncPropSpy(obj, SIGNAL(asynchronousChanged()));
    obj->setAsynchronous(true);
    obj->setAsynchronous(false);
    QCOMPARE(asyncPropSpy.size(), 2);

    QQmlListReference list(obj, "data");
    QQuickShapePath *vp = qobject_cast<QQuickShapePath *>(list.at(0));
    QVERIFY(vp != nullptr);

    // Verify that VisualPath property changes emit shapePathChanged().
    QSignalSpy vpChangeSpy(vp, SIGNAL(shapePathChanged()));
    QSignalSpy strokeColorPropSpy(vp, SIGNAL(strokeColorChanged()));
    vp->setStrokeColor(Qt::blue);
    vp->setStrokeWidth(1.0f);
    QQuickShapeGradient *g = vp->fillGradient();
    vp->setFillGradient(nullptr);
    vp->setFillColor(Qt::yellow);
    vp->setFillRule(QQuickShapePath::WindingFill);
    vp->setJoinStyle(QQuickShapePath::MiterJoin);
    vp->setMiterLimit(5);
    vp->setCapStyle(QQuickShapePath::RoundCap);
    vp->setDashOffset(10);
    vp->setDashPattern(QList<qreal>() << 1 << 2 << 3 << 4);
    QCOMPARE(strokeColorPropSpy.size(), 1);
    QCOMPARE(vpChangeSpy.size(), 10);

    // Verify that property changes from Path and its elements bubble up and result in shapePathChanged().
    QQuickPath *path = vp;
    path->setStartX(30);
    QCOMPARE(vpChangeSpy.size(), 11);
    QQmlListReference pathList(path, "pathElements");
    qobject_cast<QQuickPathLine *>(pathList.at(1))->setY(200);
    QCOMPARE(vpChangeSpy.size(), 12);

    // Verify that property changes from the gradient bubble up and result in shapePathChanged().
    vp->setFillGradient(g);
    QCOMPARE(vpChangeSpy.size(), 13);
    QQuickShapeLinearGradient *lgrad = qobject_cast<QQuickShapeLinearGradient *>(g);
    lgrad->setX2(200);
    QCOMPARE(vpChangeSpy.size(), 14);
    QQmlListReference stopList(lgrad, "stops");
    QCOMPARE(stopList.count(), 5);
    qobject_cast<QQuickGradientStop *>(stopList.at(1))->setPosition(0.3);
    QCOMPARE(vpChangeSpy.size(), 15);
    qobject_cast<QQuickGradientStop *>(stopList.at(1))->setColor(Qt::black);
    QCOMPARE(vpChangeSpy.size(), 16);
    vp->setFillTransform(QMatrix4x4(QTransform::fromScale(3, 0.14)));
    QCOMPARE(vpChangeSpy.size(), 17);
}

void tst_QQuickShape::render()
{
    SKIP_IF_NO_WINDOW_GRAB;

    QScopedPointer<QQuickView> window(createView());

    window->setSource(testFileUrl("pathitem3.qml"));
    window->show();
    QVERIFY(QTest::qWaitForWindowExposed(window.data()));

    QImage img = window->grabWindow();
    QVERIFY(!img.isNull());

    QImage refImg(testFile("pathitem3.png"));
    QVERIFY(!refImg.isNull());

    QString errorMessage;
    const QImage actualImg = img.convertToFormat(refImg.format());
    QVERIFY2(QQuickVisualTestUtils::compareImages(actualImg, refImg, &errorMessage),
             qPrintable(errorMessage));
}

void tst_QQuickShape::renderWithMultipleSp()
{
    SKIP_IF_NO_WINDOW_GRAB;

    QScopedPointer<QQuickView> window(createView());

    window->setSource(testFileUrl("pathitem4.qml"));
    window->show();
    QVERIFY(QTest::qWaitForWindowExposed(window.data()));

    QImage img = window->grabWindow();
    QVERIFY(!img.isNull());

    QImage refImg(testFile("pathitem4.png"));
    QVERIFY(!refImg.isNull());

    QString errorMessage;
    const QImage actualImg = img.convertToFormat(refImg.format());
    QVERIFY2(QQuickVisualTestUtils::compareImages(actualImg, refImg, &errorMessage),
             qPrintable(errorMessage));
}

void tst_QQuickShape::radialGrad()
{
    SKIP_IF_NO_WINDOW_GRAB;

    QScopedPointer<QQuickView> window(createView());

    window->setSource(testFileUrl("pathitem5.qml"));
    window->show();
    QVERIFY(QTest::qWaitForWindowExposed(window.data()));

    QImage img = window->grabWindow();
    QVERIFY(!img.isNull());

    QImage refImg(testFile("pathitem5.png"));
    QVERIFY(!refImg.isNull());

    QString errorMessage;
    const QImage actualImg = img.convertToFormat(refImg.format());
    QVERIFY2(QQuickVisualTestUtils::compareImages(actualImg, refImg, &errorMessage),
             qPrintable(errorMessage));
}

void tst_QQuickShape::conicalGrad()
{
    SKIP_IF_NO_WINDOW_GRAB;

    QScopedPointer<QQuickView> window(createView());

    window->setSource(testFileUrl("pathitem6.qml"));
    window->show();
    QVERIFY(QTest::qWaitForWindowExposed(window.data()));

    QImage img = window->grabWindow();
    QVERIFY(!img.isNull());

    QImage refImg(testFile("pathitem6.png"));
    QVERIFY(!refImg.isNull());

    QString errorMessage;
    const QImage actualImg = img.convertToFormat(refImg.format());
    QVERIFY2(QQuickVisualTestUtils::compareImages(actualImg, refImg, &errorMessage),
             qPrintable(errorMessage));
}

void tst_QQuickShape::renderPolyline()
{
    SKIP_IF_NO_WINDOW_GRAB;

    QScopedPointer<QQuickView> window(createView());

    window->setSource(testFileUrl("pathitem7.qml"));
    window->show();
    QVERIFY(QTest::qWaitForWindowExposed(window.data()));

    QImage img = window->grabWindow();
    QVERIFY(!img.isNull());

    QImage refImg(testFile("pathitem3.png")); // It's a recreation of pathitem3 using PathPolyline
    QVERIFY(!refImg.isNull());

    QString errorMessage;
    const QImage actualImg = img.convertToFormat(refImg.format());
    const bool res = QQuickVisualTestUtils::compareImages(actualImg, refImg, &errorMessage);
    if (!res) { // For visual inspection purposes.
        QTest::qWait(5000);
        const QString &tempLocation = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
        actualImg.save(tempLocation + QLatin1String("/pathitem7.png"));
    }
    QVERIFY2(res, qPrintable(errorMessage));
}

void tst_QQuickShape::renderMultiline()
{
    SKIP_IF_NO_WINDOW_GRAB;

    QScopedPointer<QQuickView> window(createView());

    window->setSource(testFileUrl("pathitem8.qml"));
    window->show();
    QVERIFY(QTest::qWaitForWindowExposed(window.data()));

    QImage img = window->grabWindow();
    QVERIFY(!img.isNull());

    QImage refImg(testFile("pathitem8.png"));
    QVERIFY(!refImg.isNull());

    QString errorMessage;
    const QImage actualImg = img.convertToFormat(refImg.format());
    const bool res = QQuickVisualTestUtils::compareImages(actualImg, refImg, &errorMessage);
    if (!res) { // For visual inspection purposes.
        QTest::qWait(5000);
        const QString &tempLocation = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
        actualImg.save(tempLocation + QLatin1String("/pathitem8.png"));
    }
    QVERIFY2(res, qPrintable(errorMessage));
}

void tst_QQuickShape::polylineDataTypes_data()
{
    QTest::addColumn<QVariant>("path");

    QTest::newRow("polygon") << QVariant::fromValue(m_lowPolyLogo.first());
    {
        QList<QPointF> points;
        points << m_lowPolyLogo.first();
        QTest::newRow("vector of points") << QVariant::fromValue(points);
    }
    {
        QList<QPointF> points;
        for (const auto &point : m_lowPolyLogo.first())
            points << point;
        QTest::newRow("list of points") << QVariant::fromValue(points);
    }
    {
        QVariantList points;
        for (const auto &point : m_lowPolyLogo.first())
            points << point;
        QTest::newRow("QVariantList of points") << QVariant::fromValue(points);
    }
    {
        QList<QPoint> points;
        for (const auto &point : m_lowPolyLogo.first())
            points << point.toPoint();
        QTest::newRow("vector of QPoint (integer points)") << QVariant::fromValue(points);
    }
    // Oddly, QPolygon is not supported, even though it's really QList<QPoint>.
    // We don't want to have a special case for it in QQuickPathPolyline::setPath(),
    // but it could potentially be supported by fixing one of the QVariant conversions.
}

void tst_QQuickShape::polylineDataTypes()
{
    SKIP_IF_NO_WINDOW_GRAB;

    QFETCH(QVariant, path);

    QScopedPointer<QQuickView> window(createView());
    window->setSource(testFileUrl("polyline.qml"));
    QQuickShape *shape = qobject_cast<QQuickShape *>(window->rootObject());
    QVERIFY(shape);
    shape->setProperty("path", path);
    window->show();
    QVERIFY(QTest::qWaitForWindowExposed(window.data()));

    QImage img = window->grabWindow();
    QVERIFY(!img.isNull());

    QImage refImg(testFile("polyline.png"));
    QVERIFY(!refImg.isNull());

    QString errorMessage;
    const QImage actualImg = img.convertToFormat(refImg.format());
    const bool res = QQuickVisualTestUtils::compareImages(actualImg, refImg, &errorMessage);
    if (!res) { // For visual inspection purposes.
        QTest::qWait(5000);
        const QString &tempLocation = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
        actualImg.save(tempLocation + QLatin1String("/polyline.png"));
    }
    QVERIFY2(res, qPrintable(errorMessage));

    QCOMPARE(shape->property("path").value<QList<QPointF>>(), m_lowPolyLogo.first());
    // Verify that QML sees it as an array of points
    int i = 0;
    for (QPointF p : m_lowPolyLogo.first()) {
        QMetaObject::invokeMethod(shape, "checkVertexAt", Q_ARG(QVariant, QVariant::fromValue<int>(i++)));
        QCOMPARE(shape->property("vertexBeingChecked").toPointF(), p);
    }
}

void tst_QQuickShape::multilineDataTypes_data()
{
    QTest::addColumn<QVariant>("paths");

    QTest::newRow("vector of polygons") << QVariant::fromValue(m_lowPolyLogo);
    {
        QList<QList<QPointF>> paths;
        for (const auto &poly : m_lowPolyLogo) {
            QList<QPointF> points;
            points << poly;
            paths << points;
        }
        QTest::newRow("vector of point vectors") << QVariant::fromValue(paths);
    }
    {
        QList<QList<QPointF>> paths;
        for (const auto &poly : m_lowPolyLogo) {
            QList<QPointF> points;
            points << poly;
            paths << points;
        }
        QTest::newRow("list of point vectors") << QVariant::fromValue(paths);
    }
    {
        QList<QList<QPointF>> paths;
        for (const auto &poly : m_lowPolyLogo) {
            QList<QPointF> points;
            for (const auto &point : poly)
                points << point;
            paths << points;
        }
        QTest::newRow("list of point lists") << QVariant::fromValue(paths);
    }
    {
        QVariantList paths;
        for (const auto &poly : m_lowPolyLogo) {
            QList<QPointF> points;
            points << poly;
            paths << QVariant::fromValue(points);
        }
        QTest::newRow("QVariantList of point vectors") << QVariant::fromValue(paths);
    }
    {
        QVariantList paths;
        for (const auto &poly : m_lowPolyLogo) {
            QList<QPointF> points;
            for (const auto &point : poly)
                points << point;
            paths << QVariant::fromValue(points);
        }
        QTest::newRow("QVariantList of point lists") << QVariant::fromValue(paths);
    }
    {
        QVariantList paths;
        for (const auto &poly : m_lowPolyLogo) {
            QVariantList points;
            for (const auto &point : poly)
                points << point;
            paths << QVariant::fromValue(points);
        }
        QTest::newRow("QVariantList of QVariantLists") << QVariant::fromValue(paths);
    }
    /* These could be supported if QVariant knew how to convert lists and vectors of QPolygon to QPolygonF.
       But they are omitted for now because we don't want to have special cases for them
       in QQuickPathMultiline::setPaths().  Floating point is preferred for geometry in Qt Quick.
    {
        QList<QPolygon> paths;
        for (const auto &poly : m_lowPolyLogo)
            paths << poly.toPolygon();
        QTest::newRow("list of QPolygon (integer points)") << QVariant::fromValue(paths);
    }
    {
        QList<QPolygon> paths;
        for (const auto &poly : m_lowPolyLogo)
            paths << poly.toPolygon();
        QTest::newRow("vector of QPolygon (integer points)") << QVariant::fromValue(paths);
    }
    */
    {
        QList<QList<QPoint>> paths;
        for (const auto &poly : m_lowPolyLogo) {
            QList<QPoint> points;
            for (const auto &point : poly)
                points << point.toPoint();
            paths << points;
        }
        QTest::newRow("list of integer point lists") << QVariant::fromValue(paths);
    }
    {
        QList<QList<QPoint>> paths;
        for (const auto &poly : m_lowPolyLogo) {
            QList<QPoint> points;
            for (const auto &point : poly)
                points << point.toPoint();
            paths << points;
        }
        QTest::newRow("vector of integer point lists") << QVariant::fromValue(paths);
    }
    {
        QList<QList<QPoint>> paths;
        for (const auto &poly : m_lowPolyLogo) {
            QList<QPoint> points;
            for (const auto &point : poly)
                points << point.toPoint();
            paths << points;
        }
        QTest::newRow("list of integer point vectors") << QVariant::fromValue(paths);
    }
}

void tst_QQuickShape::multilineDataTypes()
{
    SKIP_IF_NO_WINDOW_GRAB;

    QFETCH(QVariant, paths);

    QScopedPointer<QQuickView> window(createView());
    window->setSource(testFileUrl("multiline.qml"));
    QQuickShape *shape = qobject_cast<QQuickShape *>(window->rootObject());
    QVERIFY(shape);
    shape->setProperty("paths", paths);
    window->show();
    QVERIFY(QTest::qWaitForWindowExposed(window.data()));

    QImage img = window->grabWindow();
    QVERIFY(!img.isNull());

    QImage refImg(testFile("multiline.png"));
    QVERIFY(!refImg.isNull());

    QString errorMessage;
    const QImage actualImg = img.convertToFormat(refImg.format());
    const bool res = QQuickVisualTestUtils::compareImages(actualImg, refImg, &errorMessage);
    if (!res) { // For visual inspection purposes.
        QTest::qWait(5000);
        const QString &tempLocation = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
        actualImg.save(tempLocation + QLatin1String("/multiline.png"));
    }
    QVERIFY2(res, qPrintable(errorMessage));

    QList<QList<QPointF>> pointVectors;
    for (auto v : m_lowPolyLogo)
        pointVectors << v;
    QCOMPARE(shape->property("paths").value<QList<QList<QPointF>>>(), pointVectors);
    // Verify that QML sees it as an array of arrays of points
    int i = 0;
    for (auto pv : m_lowPolyLogo) {
        int j = 0;
        for (QPointF p : pv) {
            QMetaObject::invokeMethod(shape, "checkVertexAt", Q_ARG(QVariant, QVariant::fromValue<int>(i)), Q_ARG(QVariant, QVariant::fromValue<int>(j++)));
            QCOMPARE(shape->property("vertexBeingChecked").toPointF(), p);
        }
        ++i;
    }
}

void tst_QQuickShape::multilineStronglyTyped()
{
    SKIP_IF_NO_WINDOW_GRAB;

    QScopedPointer<QQuickView> window(createView());
    window->setSource(testFileUrl("multilineStronglyTyped.qml"));
    QQuickShape *shape = qobject_cast<QQuickShape *>(window->rootObject());
    QVERIFY(shape);
    PolygonProvider *provider = shape->findChild<PolygonProvider*>("provider");
    QVERIFY(provider);
    window->show();
    QVERIFY(QTest::qWaitForWindowExposed(window.data()));
    provider->setPaths(m_lowPolyLogo);

    QImage img = window->grabWindow();
    QVERIFY(!img.isNull());

    QImage refImg(testFile("multiline.png"));
    QVERIFY(!refImg.isNull());

    QString errorMessage;
    const QImage actualImg = img.convertToFormat(refImg.format());
    const bool res = QQuickVisualTestUtils::compareImages(actualImg, refImg, &errorMessage);
    if (!res) { // For visual inspection purposes.
        QTest::qWait(5000);
        const QString &tempLocation = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
        actualImg.save(tempLocation + QLatin1String("/multilineStronglyTyped.png"));
    }
    QVERIFY2(res, qPrintable(errorMessage));

    QList<QList<QPointF>> pointVectors;
    for (auto v : m_lowPolyLogo)
        pointVectors << v;
    QCOMPARE(shape->property("paths").value<QList<QList<QPointF>>>(), pointVectors);
    // Verify that QML sees it as an array of arrays of points
    int i = 0;
    for (auto pv : m_lowPolyLogo) {
        int j = 0;
        for (QPointF p : pv) {
            QMetaObject::invokeMethod(shape, "checkVertexAt", Q_ARG(QVariant, QVariant::fromValue<int>(i)), Q_ARG(QVariant, QVariant::fromValue<int>(j++)));
            QCOMPARE(shape->property("vertexBeingChecked").toPointF(), p);
        }
        ++i;
    }
}

void tst_QQuickShape::fillTransform()
{
    QScopedPointer<QQuickView> window(createView());

    window->setSource(testFileUrl("filltransform.qml"));
    qApp->processEvents();

    QQuickShape *obj = findItem<QQuickShape>(window->rootObject(), "shape1");
    QVERIFY(obj != nullptr);
    QQmlListReference list(obj, "data");
    QCOMPARE(list.count(), 2);

    QQuickShapePath *p1 = qobject_cast<QQuickShapePath *>(list.at(0));
    QVERIFY(p1 != nullptr);
    QVERIFY(p1->objectName() == "path1");
    QVERIFY(p1->fillTransform() == QMatrix4x4(2,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1));

    QQuickShapePath *p2 = qobject_cast<QQuickShapePath *>(list.at(1));
    QVERIFY(p2 != nullptr);
    QVERIFY(p2->objectName() == "path2");
    QVERIFY(p2->fillTransform().isIdentity());

    QMatrix4x4 xf(QTransform::fromTranslate(-36, 0).shear(0.35, 0));
    p1->setFillTransform(xf);
    QVERIFY(p1->fillTransform() == xf);

    QVERIFY(p2->fillTransform().isIdentity());
    p2->setFillTransform(xf);
    QVERIFY(p2->fillTransform() == xf);

    p1->setFillTransform(QMatrix4x4{});
    QVERIFY(p1->fillTransform().isIdentity());
}

void tst_QQuickShape::changeElementsImperatively()
{
    QQuickView window;
    // Shouldn't crash.
    QVERIFY(QQuickTest::showView(window, testFileUrl("changeElementsImperatively.qml")));
}

QTEST_MAIN(tst_QQuickShape)

#include "tst_qquickshape.moc"
