diff options
Diffstat (limited to 'tests/auto/quick')
31 files changed, 1064 insertions, 86 deletions
diff --git a/tests/auto/quick/doc/how-tos/how-to-qml/tst_how-to-qml.cpp b/tests/auto/quick/doc/how-tos/how-to-qml/tst_how-to-qml.cpp index fdc8981932..4c6e331efa 100644 --- a/tests/auto/quick/doc/how-tos/how-to-qml/tst_how-to-qml.cpp +++ b/tests/auto/quick/doc/how-tos/how-to-qml/tst_how-to-qml.cpp @@ -60,6 +60,7 @@ void tst_HowToQml::activeFocusDebugging() auto *window = qobject_cast<QQuickWindow*>(engine.rootObjects().at(0)); window->show(); + window->requestActivate(); QTest::ignoreMessage(QtDebugMsg, QRegularExpression("activeFocusItem: .*\"ActiveFocusDebuggingMain\"")); QVERIFY(QTest::qWaitForWindowActive(window)); diff --git a/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp b/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp index 0a126a13e8..ce5473c8a5 100644 --- a/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp +++ b/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp @@ -101,6 +101,7 @@ private slots: void restartAnimationGroupWhenDirty(); void restartNestedAnimationGroupWhenDirty(); void targetsDeletedNotRemoved(); + void alwaysRunToEndSetFalseRestartBug(); }; #define QTIMED_COMPARE(lhs, rhs) do { \ @@ -2295,6 +2296,41 @@ void tst_qquickanimations::targetsDeletedNotRemoved() } } +//QTBUG-125224 +void tst_qquickanimations::alwaysRunToEndSetFalseRestartBug() +{ + QQuickRectangle rect; + QQuickSequentialAnimation sequential; + QQuickPropertyAnimation beginAnim; + QQuickPropertyAnimation endAnim; + + beginAnim.setTargetObject(&rect); + beginAnim.setProperty("x"); + beginAnim.setTo(200); + beginAnim.setDuration(1000); + + endAnim.setTargetObject(&rect); + endAnim.setProperty("x"); + endAnim.setFrom(200); + endAnim.setDuration(1000); + + beginAnim.setGroup(&sequential); + endAnim.setGroup(&sequential); + + sequential.setLoops(-1); + sequential.setAlwaysRunToEnd(true); + + QCOMPARE(sequential.loops(), -1); + QVERIFY(sequential.alwaysRunToEnd()); + sequential.start(); + sequential.stop(); + sequential.setAlwaysRunToEnd(false); + sequential.start(); + QCOMPARE(sequential.isRunning(), true); + sequential.stop(); + QCOMPARE(sequential.isRunning(), false); +} + QTEST_MAIN(tst_qquickanimations) #include "tst_qquickanimations.moc" diff --git a/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp b/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp index a440c0c2f8..b3633a9dcc 100644 --- a/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp +++ b/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp @@ -246,7 +246,7 @@ void tst_qquickapplication::styleHints() { // technically not in QQuickApplication, but testing anyway here QQmlComponent component(&engine); - component.setData("import QtQuick 2.0; Item { property variant styleHints: Qt.styleHints }", QUrl::fromLocalFile("")); + component.setData("import QtQuick 2.0; Item { property variant styleHints: Application.styleHints }", QUrl::fromLocalFile("")); QQuickItem *item = qobject_cast<QQuickItem *>(component.create()); QVERIFY(item); QQuickView view; diff --git a/tests/auto/quick/qquickdragattached/tst_qquickdragattached.cpp b/tests/auto/quick/qquickdragattached/tst_qquickdragattached.cpp index 5ce72a747f..d82ebe77c2 100644 --- a/tests/auto/quick/qquickdragattached/tst_qquickdragattached.cpp +++ b/tests/auto/quick/qquickdragattached/tst_qquickdragattached.cpp @@ -44,6 +44,9 @@ void tst_QQuickDragAttached::setMimeData_data() QTest::addRow("text/uri-list, string") << makeMap("text/uri-list", QString("https://qt-project.org")) << QStringList{"text/uri-list"}; + QTest::addRow("text/uri-list, RFC2483 string") + << makeMap("text/uri-list", QString("https://qt-project.org\r\nhttps://www.test.com")) + << QStringList{"text/uri-list"}; QTest::addRow("text/uri-list, strings") << makeMap("text/uri-list", QStringList{"file://foo", "https://www.test.com"}) << QStringList{"text/uri-list"}; diff --git a/tests/auto/quick/qquickimage/tst_qquickimage.cpp b/tests/auto/quick/qquickimage/tst_qquickimage.cpp index 343bde5f5f..ca2314c336 100644 --- a/tests/auto/quick/qquickimage/tst_qquickimage.cpp +++ b/tests/auto/quick/qquickimage/tst_qquickimage.cpp @@ -180,6 +180,11 @@ void tst_qquickimage::imageSource() QFETCH(bool, cache); QFETCH(QString, error); +#if !QT_CONFIG(qml_network) + if (remote) + QSKIP("Skipping due to lack of QML network feature"); +#endif + TestHTTPServer server; if (remote) { QVERIFY2(server.listen(), qPrintable(server.errorString())); @@ -550,6 +555,10 @@ void tst_qquickimage::tiling_QTBUG_6716_data() void tst_qquickimage::noLoading() { +#if !QT_CONFIG(qml_network) + QSKIP("Skipping due to lack of QML network feature"); +#endif + qRegisterMetaType<QQuickImageBase::Status>(); TestHTTPServer server; @@ -692,6 +701,10 @@ void tst_qquickimage::sourceSize_QTBUG_16389() // QTBUG-15690 void tst_qquickimage::nullPixmapPaint() { +#if !QT_CONFIG(qml_network) + QSKIP("Skipping due to lack of QML network feature"); +#endif + QScopedPointer<QQuickView> window(new QQuickView(nullptr)); window->setSource(testFileUrl("nullpixmap.qml")); window->show(); @@ -714,6 +727,10 @@ void tst_qquickimage::nullPixmapPaint() void tst_qquickimage::imageCrash_QTBUG_22125() { +#if !QT_CONFIG(qml_network) + QSKIP("Skipping due to lack of QML network feature"); +#endif + TestHTTPServer server; QVERIFY2(server.listen(), qPrintable(server.errorString())); server.serveDirectory(dataDirectory(), TestHTTPServer::Delay); @@ -826,6 +843,10 @@ void tst_qquickimage::sourceSizeChanges() QTRY_COMPARE(sourceSizeSpy.size(), 3); // Remote +#if !QT_CONFIG(qml_network) + QSKIP("Skipping due to lack of QML network feature"); +#endif + ctxt->setContextProperty("srcImage", server.url("/heart.png")); QTRY_COMPARE(img->status(), QQuickImage::Ready); QTRY_COMPARE(sourceSizeSpy.size(), 4); @@ -959,6 +980,10 @@ void tst_qquickimage::progressAndStatusChanges() QTRY_COMPARE(statusSpy.size(), 1); // Loading remote file +#if !QT_CONFIG(qml_network) + QSKIP("Skipping due to lack of QML network feature"); +#endif + ctxt->setContextProperty("srcImage", server.url("/heart.png")); QTRY_COMPARE(obj->status(), QQuickImage::Loading); QTRY_COMPARE(obj->progress(), 0.0); diff --git a/tests/auto/quick/qquickitem/tst_qquickitem.cpp b/tests/auto/quick/qquickitem/tst_qquickitem.cpp index 061dea3ad7..1db150e675 100644 --- a/tests/auto/quick/qquickitem/tst_qquickitem.cpp +++ b/tests/auto/quick/qquickitem/tst_qquickitem.cpp @@ -1527,7 +1527,7 @@ void tst_qquickitem::polishLoopDetection_data() QTest::newRow("test1.100") << PolishItemSpans({ {1, 100} }) << 0; QTest::newRow("test1.1002") << PolishItemSpans({ {1, 1002} }) << 3; - QTest::newRow("test1.2020") << PolishItemSpans({ {1, 2020} }) << 10; + QTest::newRow("test1.2020") << PolishItemSpans({ {1, 2020} }) << 5; QTest::newRow("test5.1") << PolishItemSpans({ {5, 1} }) << 0; QTest::newRow("test5.10") << PolishItemSpans({ {5, 10} }) << 0; diff --git a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp index ebd1749e68..f6de1b482c 100644 --- a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp +++ b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp @@ -2015,7 +2015,7 @@ void tst_QQuickItem::layoutMirroringIllegalParent() { QQmlComponent component(&engine); component.setData("import QtQuick 2.0; QtObject { LayoutMirroring.enabled: true; LayoutMirroring.childrenInherit: true }", QUrl::fromLocalFile("")); - QTest::ignoreMessage(QtWarningMsg, "<Unknown File>:1:21: QML QtObject: LayoutDirection attached property only works with Items and Windows"); + QTest::ignoreMessage(QtWarningMsg, "<Unknown File>:1:21: QML QtObject: LayoutMirroring attached property only works with Items and Windows"); QObject *object = component.create(); QVERIFY(object != nullptr); } diff --git a/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml b/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml index 8524366f14..5454cf672b 100644 --- a/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml +++ b/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml @@ -936,7 +936,19 @@ Item { }, layoutWidth: 0, expectedWidths: [0] - } + },{ + tag: "preferred_infinity", // Do not crash/assert when the preferred size is infinity + layout: { + type: "RowLayout", + items: [ + {minimumWidth: 10, preferredWidth: Number.POSITIVE_INFINITY, fillWidth: true}, + {minimumWidth: 20, preferredWidth: Number.POSITIVE_INFINITY, fillWidth: true}, + ] + }, + layoutWidth: 31, // Important that this is between minimum and preferred width of the layout. + expectedWidths: [10, 21] // The result here does not have to be exact. (This + // test is mostly concerned about not crashing). + } ]; } @@ -1178,6 +1190,78 @@ Item { } Component { + id: sizeHintBindingLoopComp + Item { + id: root + anchors.fill: parent + property var customWidth: 100 + RowLayout { + id: col + Item { + id: item + implicitHeight: 80 + implicitWidth: Math.max(col2.implicitWidth, root.customWidth + 20) + ColumnLayout { + id: col2 + width: parent.width + Item { + id: rect + implicitWidth: root.customWidth + implicitHeight: 80 + } + } + } + } + } + } + + function test_sizeHintBindingLoopIssue() { + var item = createTemporaryObject(sizeHintBindingLoopComp, container) + waitForRendering(item) + item.customWidth += 10 + waitForRendering(item) + verify(!LayoutSetup.bindingLoopDetected, "Detected binding loop") + LayoutSetup.resetBindingLoopDetectedFlag() + } + + Component { + id: polishLayoutItemComp + Item { + anchors.fill: parent + implicitHeight: contentLayout.implicitHeight + implicitWidth: contentLayout.implicitWidth + property alias textLayout: contentLayout + RowLayout { + width: parent.width + height: parent.height + ColumnLayout { + id: contentLayout + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + Layout.maximumWidth: 200 + Repeater { + model: 2 + Text { + Layout.fillWidth: true + text: "This is a long text causing line breaks to show the bug." + wrapMode: Text.Wrap + } + } + Item { + Layout.fillHeight: true + } + } + } + } + } + + function test_polishLayoutItemIssue() { + var rootItem = createTemporaryObject(polishLayoutItemComp, container) + waitForRendering(rootItem) + var textItem = rootItem.textLayout.children[1] + verify(textItem.y >= rootItem.textLayout.children[0].height) + } + + Component { id: rearrangeNestedLayouts_Component RowLayout { id: layout @@ -1520,8 +1604,8 @@ Item { compare(rootItem.maxWidth, 66) // Should not trigger a binding loop - verify(!BindingLoopDetector.bindingLoopDetected, "Detected binding loop") - BindingLoopDetector.reset() + verify(!LayoutSetup.bindingLoopDetected, "Detected binding loop") + LayoutSetup.resetBindingLoopDetectedFlag() } diff --git a/tests/auto/quick/qquicklayouts/tst_qquicklayouts.cpp b/tests/auto/quick/qquicklayouts/tst_qquicklayouts.cpp index 26b01b806d..1576a00c81 100644 --- a/tests/auto/quick/qquicklayouts/tst_qquicklayouts.cpp +++ b/tests/auto/quick/qquicklayouts/tst_qquicklayouts.cpp @@ -15,13 +15,12 @@ public: bool wasBindingLoopDetected() const { return mBindingLoopDetected; } public slots: - void reset() { mBindingLoopDetected = false; } + void resetBindingLoopDetectedFlag() { mBindingLoopDetected = false; } void qmlEngineAvailable(QQmlEngine *engine) { connect(engine, &QQmlEngine::warnings, this, &Setup::qmlWarnings); - - qmlRegisterSingletonInstance("org.qtproject.Test", 1, 0, "BindingLoopDetector", this); + qmlRegisterSingletonInstance("org.qtproject.Test", 1, 0, "LayoutSetup", this); } void qmlWarnings(const QList<QQmlError> &warnings) diff --git a/tests/auto/quick/qquicklistview/data/emptymodel.qml b/tests/auto/quick/qquicklistview/data/emptymodel.qml index 3feec691cf..c7f1df31d2 100644 --- a/tests/auto/quick/qquicklistview/data/emptymodel.qml +++ b/tests/auto/quick/qquicklistview/data/emptymodel.qml @@ -6,6 +6,8 @@ Rectangle { } ListView { id: list + width: 100 + height: 100 model: model delegate: Item { } diff --git a/tests/auto/quick/qquicklistview/data/snapToItemWithSectionAtStart.qml b/tests/auto/quick/qquicklistview/data/snapToItemWithSectionAtStart.qml new file mode 100644 index 0000000000..0d4c233345 --- /dev/null +++ b/tests/auto/quick/qquicklistview/data/snapToItemWithSectionAtStart.qml @@ -0,0 +1,52 @@ +import QtQuick + +ListView { + id: listView + width: 240 + height: 300 + + model: ListModel { + ListElement { section: "section 1" } + ListElement { section: "section 1" } + ListElement { section: "section 1" } + ListElement { section: "section 2" } + ListElement { section: "section 2" } + ListElement { section: "section 2" } + ListElement { section: "section 3" } + ListElement { section: "section 3" } + ListElement { section: "section 3" } + ListElement { section: "section 4" } + ListElement { section: "section 4" } + ListElement { section: "section 4" } + ListElement { section: "section 5" } + ListElement { section: "section 5" } + ListElement { section: "section 5" } + } + + section.property: "section" + section.labelPositioning: ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart + section.delegate: Rectangle { + width: listView.width + height: 30 + Text { + anchors.fill: parent + text: section + } + color: "lightblue" + } + + snapMode: ListView.SnapToItem + + delegate: Rectangle { + width: listView.width + height: 30 + Text { + anchors.fill: parent + text: index + } + border { + width: 1 + color: "black" + } + } +} diff --git a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp index 46e1254453..747478fc9a 100644 --- a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp +++ b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp @@ -171,6 +171,7 @@ private slots: void headerSnapToItem_data(); void headerSnapToItem(); void snapToItemWithSpacing_QTBUG_59852(); + void snapToItemWithSectionAtStart(); void snapOneItemResize_QTBUG_43555(); void snapOneItem_data(); void snapOneItem(); @@ -5395,6 +5396,27 @@ void tst_QQuickListView::snapToItemWithSpacing_QTBUG_59852() releaseView(window); } +void tst_QQuickListView::snapToItemWithSectionAtStart() // QTBUG-30768 +{ + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("snapToItemWithSectionAtStart.qml"))); + QQuickListView *listView = qobject_cast<QQuickListView *>(window.rootObject()); + QTRY_VERIFY(listView); + + // Both sections and elements are 30px high. The list height is 300px, so + // it fits exactly 10 elements. We can do some random flicks, but the + // content position always MUST be divisible by 30. + for (int i = 0; i < 10; ++i) { + const bool even = (i % 2 == 0); + const QPoint start = even ? QPoint(20, 100 + i * 5) : QPoint(20, 20 + i * 3); + const QPoint end = even ? start - QPoint(0, 50 + i * 10) : start + QPoint(0, 50 + i * 5); + + flick(&window, start, end, 180); + QTRY_COMPARE(listView->isMoving(), false); // wait until it stops + QCOMPARE(int(listView->contentY()) % 30, 0); + } +} + static void drag_helper(QWindow *window, QPoint *startPos, const QPoint &delta) { QPoint pos = *startPos; diff --git a/tests/auto/quick/qquicklistview2/data/nestedSnap.qml b/tests/auto/quick/qquicklistview2/data/nestedSnap.qml new file mode 100644 index 0000000000..d7f064da01 --- /dev/null +++ b/tests/auto/quick/qquicklistview2/data/nestedSnap.qml @@ -0,0 +1,63 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +pragma ComponentBehavior: Bound + +import QtQuick + +ListView { + id: row + + width: 300 + height: 300 + + orientation: Qt.Horizontal + snapMode: ListView.SnapOneItem + highlightRangeMode: ListView.StrictlyEnforceRange + + model: 3 + delegate: ListView { + id: column + objectName: "vertical column " + index + + required property int index + + width: 300 + height: 300 + + orientation: Qt.Vertical + snapMode: ListView.SnapOneItem + highlightRangeMode: ListView.StrictlyEnforceRange + + model: 3 + delegate: Rectangle { + id: cell + + required property int index + + width: 300 + height: 300 + color: "transparent" + border.color: "#000" + border.width: 5 + radius: 15 + + Text { + anchors.centerIn: parent + text: `Row: ${cell.index}` + } + } + + Text { + anchors.verticalCenterOffset: -height + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: `Column: ${column.index}\ncurrentIndex: ${column.currentIndex}` + } + } + + Text { + x: 10; y: 10 + text: `currentIndex: ${row.currentIndex}` + } +} diff --git a/tests/auto/quick/qquicklistview2/data/visibleBoundToCountGreaterThanZero.qml b/tests/auto/quick/qquicklistview2/data/visibleBoundToCountGreaterThanZero.qml new file mode 100644 index 0000000000..e75c779584 --- /dev/null +++ b/tests/auto/quick/qquicklistview2/data/visibleBoundToCountGreaterThanZero.qml @@ -0,0 +1,31 @@ +import QtQuick +import QtQuick.Layouts + +ColumnLayout { + property alias listView: listView + + ListView { + id: listView + + visible: count > 0 // actual defect. countChanged never fires so this never turns true + + Layout.fillWidth: true + Layout.preferredHeight: contentHeight // grow with content, initially 0 + + model: ListModel { + id: idModel + } + + delegate: Text { + required property string name + text: name + } + + Timer { + running: true + interval: 10 + repeat: true + onTriggered: idModel.append({name:"Hello"}) + } + } +} diff --git a/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp b/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp index c240f36a80..e114cc1591 100644 --- a/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp +++ b/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp @@ -18,6 +18,8 @@ Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests") using namespace QQuickViewTestUtils; using namespace QQuickVisualTestUtils; +static const int oneSecondInMs = 1000; + class tst_QQuickListView2 : public QQmlDataTest { Q_OBJECT @@ -35,6 +37,7 @@ private slots: void delegateModelRefresh(); void wheelSnap(); void wheelSnap_data(); + void nestedWheelSnap(); void sectionsNoOverlap(); void metaSequenceAsModel(); @@ -62,6 +65,7 @@ private slots: void changingOrientationResetsPreviousAxisValues_data(); void changingOrientationResetsPreviousAxisValues(); + void visibleBoundToCountGreaterThanZero(); private: void flickWithTouch(QQuickWindow *window, const QPoint &from, const QPoint &to); @@ -838,6 +842,67 @@ void tst_QQuickListView2::wheelSnap_data() << 210.0; } +void tst_QQuickListView2::nestedWheelSnap() +{ + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("nestedSnap.qml"))); + + quint64 timestamp = 10; + auto sendWheelEvent = [×tamp, &window](const QPoint &pixelDelta, Qt::ScrollPhase phase) { + const QPoint pos(100, 100); + QWheelEvent event(pos, window.mapToGlobal(pos), pixelDelta, pixelDelta, Qt::NoButton, + Qt::NoModifier, phase, false, Qt::MouseEventSynthesizedBySystem); + event.setAccepted(false); + event.setTimestamp(timestamp); + QGuiApplication::sendEvent(&window, &event); + timestamp += 50; + }; + + QQuickListView *outerListView = qobject_cast<QQuickListView *>(window.rootObject()); + QTRY_VERIFY(outerListView); + QSignalSpy outerCurrentIndexSpy(outerListView, &QQuickListView::currentIndexChanged); + int movingAtIndex = -1; + + // send horizontal pixel-delta wheel events with phases; confirm that ListView hits the next item boundary + sendWheelEvent({}, Qt::ScrollBegin); + for (int i = 1; i < 4; ++i) { + sendWheelEvent({-50, 0}, Qt::ScrollUpdate); + if (movingAtIndex < 0 && outerListView->isMoving()) + movingAtIndex = i; + } + QVERIFY(outerListView->isDragging()); + sendWheelEvent({}, Qt::ScrollEnd); + QCOMPARE(outerListView->isDragging(), false); + QTRY_COMPARE(outerListView->isMoving(), false); // wait until it stops + qCDebug(lcTests) << "outer got moving after" << movingAtIndex + << "horizontal events; stopped at" << outerListView->contentX() << outerListView->currentIndex(); + QCOMPARE_GT(movingAtIndex, 0); + QCOMPARE(outerListView->contentX(), 300); + QCOMPARE(outerCurrentIndexSpy.size(), 1); + + movingAtIndex = -1; + QQuickListView *innerListView = qobject_cast<QQuickListView *>(outerListView->currentItem()); + QTRY_VERIFY(innerListView); + QSignalSpy innerCurrentIndexSpy(innerListView, &QQuickListView::currentIndexChanged); + + // send vertical pixel-delta wheel events with phases; confirm that ListView hits the next item boundary + sendWheelEvent({}, Qt::ScrollBegin); + for (int i = 1; i < 4; ++i) { + sendWheelEvent({0, -50}, Qt::ScrollUpdate); + if (movingAtIndex < 0 && innerListView->isMoving()) + movingAtIndex = i; + } + QVERIFY(innerListView->isDragging()); + sendWheelEvent({}, Qt::ScrollEnd); + QCOMPARE(innerListView->isDragging(), false); + QTRY_COMPARE(innerListView->isMoving(), false); // wait until it stops + qCDebug(lcTests) << "inner got moving after" << movingAtIndex + << "vertical events; stopped at" << innerListView->contentY() << innerListView->currentIndex(); + QCOMPARE_GT(movingAtIndex, 0); + QCOMPARE(innerListView->contentY(), 300); + QCOMPARE(innerCurrentIndexSpy.size(), 1); +} + class FriendlyItemView : public QQuickItemView { friend class ItemViewAccessor; @@ -1153,6 +1218,23 @@ void tst_QQuickListView2::changingOrientationResetsPreviousAxisValues() // QTBUG QVERIFY(!listView->property("isYReset").toBool()); } +void tst_QQuickListView2::visibleBoundToCountGreaterThanZero() +{ + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("visibleBoundToCountGreaterThanZero.qml"))); + + auto *listView = window.rootObject()->property("listView").value<QQuickListView *>(); + QVERIFY(listView); + + QSignalSpy countChangedSpy(listView, SIGNAL(countChanged())); + QVERIFY(countChangedSpy.isValid()); + + QTRY_COMPARE_GT_WITH_TIMEOUT(listView->count(), 1, oneSecondInMs); + // Using the TRY variant here as well is necessary. + QTRY_COMPARE_GT_WITH_TIMEOUT(countChangedSpy.count(), 1, oneSecondInMs); + QVERIFY(listView->isVisible()); +} + QTEST_MAIN(tst_QQuickListView2) #include "tst_qquicklistview2.moc" diff --git a/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp b/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp index 6d2aa267dd..d5b3b75215 100644 --- a/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp +++ b/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp @@ -939,6 +939,9 @@ void tst_QQuickMouseArea::doubleClick() QCOMPARE(window.rootObject()->property("clicked").toInt(), 1); QCOMPARE(window.rootObject()->property("doubleClicked").toInt(), 1); QCOMPARE(window.rootObject()->property("released").toInt(), 2); + + // wait long enough to avoid affecting the next test function + QTest::qWait(QGuiApplication::styleHints()->mouseDoubleClickInterval()); } void tst_QQuickMouseArea::doubleTap() // QTBUG-112434 @@ -970,6 +973,9 @@ void tst_QQuickMouseArea::doubleTap() // QTBUG-112434 QCOMPARE(mouseArea->pressed(), false); QCOMPARE(window.rootObject()->property("clicked").toInt(), 1); + // avoid getting a double-click event next + QTest::qWait(QGuiApplication::styleHints()->mouseDoubleClickInterval()); + // now tap with two fingers simultaneously: only one of them generates synth-mouse QPoint p2 = p1 + QPoint(50, 5); QTest::touchEvent(&window, device).press(2, p1).press(3, p2); @@ -991,8 +997,8 @@ void tst_QQuickMouseArea::doubleTap() // QTBUG-112434 QTest::touchEvent(&window, device).release(4, p1).release(5, p2); QQuickTouchUtils::flush(&window); QCOMPARE(window.rootObject()->property("released").toInt(), 4); - QCOMPARE(window.rootObject()->property("clicked").toInt(), 2); - QCOMPARE(window.rootObject()->property("doubleClicked").toInt(), 2); + QCOMPARE(window.rootObject()->property("clicked").toInt(), 3); + QCOMPARE(window.rootObject()->property("doubleClicked").toInt(), 1); QCOMPARE(mouseArea->pressed(), false); // make sure it doesn't get stuck } diff --git a/tests/auto/quick/qquickpathview/data/qtbug46487.qml b/tests/auto/quick/qquickpathview/data/qtbug46487.qml new file mode 100644 index 0000000000..840d77ffe4 --- /dev/null +++ b/tests/auto/quick/qquickpathview/data/qtbug46487.qml @@ -0,0 +1,28 @@ +import QtQuick 2.0 + +PathView { + id: view + property int delegatesCreated: 0 + property int delegatesDestroyed: 0 + + width: 400 + height: 400 + preferredHighlightBegin: 0.5 + preferredHighlightEnd: 0.5 + pathItemCount: 5 + currentIndex: 1 + model: customModel + delegate: Text { + text: "item: " + index + " of: " + view.count + Component.onCompleted: view.delegatesCreated++; + Component.onDestruction: view.delegatesDestroyed++; + } + path: Path { + startX: 50 + startY: 0 + PathLine { + x: 50 + y: 400 + } + } +} diff --git a/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp b/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp index 90c3060235..7d41d907fb 100644 --- a/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp +++ b/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp @@ -133,6 +133,7 @@ private slots: void requiredPropertiesInDelegatePreventUnrelated(); void touchMove(); void mousePressAfterFlick(); + void qtbug46487(); private: QScopedPointer<QPointingDevice> touchDevice = QScopedPointer<QPointingDevice>(QTest::createTouchDevice()); @@ -2895,6 +2896,68 @@ void tst_QQuickPathView::mousePressAfterFlick() // QTBUG-115121 QCOMPARE(pressedSpy.size(), 0); } +class CustomModel : public QAbstractListModel +{ +public: + CustomModel(QObject *parent = 0) : QAbstractListModel(parent) { + m_values << 0 << 1 << 2 << 3 << 4; + } + + int rowCount(const QModelIndex &parent = QModelIndex()) const { + Q_UNUSED(parent); + return m_values.count(); + } + QVariant data(const QModelIndex &index, int role) const { + if (index.row() < 0 || m_values.count() <= index.row()) + return QVariant(); + + return m_values[index.row()]; + } + + Q_INVOKABLE void shrink() { + beginResetModel(); + m_values.takeLast(); + m_values.takeLast(); + endResetModel(); + } + +private: + QList<int> m_values; +}; + +void tst_QQuickPathView::qtbug46487() +{ + QScopedPointer<QQuickView> window(createView()); + + CustomModel* model = new CustomModel; + QQmlContext *ctxt = window->rootContext(); + ctxt->setContextProperty("customModel", model); + + window->setSource(testFileUrl("qtbug46487.qml")); + window->show(); + qApp->processEvents(); + + QQuickPathView *pathview = qobject_cast<QQuickPathView*>(window->rootObject()); + QVERIFY(pathview); + + QTest::qWait(500); + + // Should create just pathItemCount amount and not destroy any + QCOMPARE(pathview->count(), 5); + QCOMPARE(pathview->property("delegatesCreated").toInt(), 5); + QCOMPARE(pathview->property("delegatesDestroyed").toInt(), 0); + + // Resets the model and removes 2 items. + model->shrink(); + QTest::qWait(500); + + // Should destroy previous items (begin/endResetModel) and + // (re)create 3 new items. + QCOMPARE(pathview->count(), 3); + QCOMPARE(pathview->property("delegatesCreated").toInt(), 5 + 3); + QCOMPARE(pathview->property("delegatesDestroyed").toInt(), 5); +} + QTEST_MAIN(tst_QQuickPathView) #include "tst_qquickpathview.moc" diff --git a/tests/auto/quick/qquickpixmapcache/tst_qquickpixmapcache.cpp b/tests/auto/quick/qquickpixmapcache/tst_qquickpixmapcache.cpp index 590f022e4e..4e0534edf0 100644 --- a/tests/auto/quick/qquickpixmapcache/tst_qquickpixmapcache.cpp +++ b/tests/auto/quick/qquickpixmapcache/tst_qquickpixmapcache.cpp @@ -108,6 +108,11 @@ void tst_qquickpixmapcache::single() QFETCH(bool, exists); QFETCH(bool, neterror); +#if !QT_CONFIG(qml_network) + if (target.scheme() == "http") + QSKIP("Skipping due to lack of QML network feature"); +#endif + QString expectedError; if (neterror) { expectedError = "Error transferring " + target.toString() + " - server replied: Not found"; @@ -196,6 +201,11 @@ void tst_qquickpixmapcache::parallel() QFETCH(int, incache); QFETCH(int, cancel); +#if !QT_CONFIG(qml_network) + if (target1.scheme() == "http" || target2.scheme() == "http") + QSKIP("Skipping due to lack of QML network feature"); +#endif + QList<QUrl> targets; targets << target1 << target2; diff --git a/tests/auto/quick/qquickstates/data/anchorRewindBug3.qml b/tests/auto/quick/qquickstates/data/anchorRewindBug3.qml new file mode 100644 index 0000000000..6d829be363 --- /dev/null +++ b/tests/auto/quick/qquickstates/data/anchorRewindBug3.qml @@ -0,0 +1,38 @@ +import QtQuick 2.0 + +Rectangle { + id: root + width: 100; height: 100 + + Rectangle { + id: rectangle + objectName: "inner" + color: "green" + // Width and height end up to be 50 + // after root Component.onCompleted + width: 75 + height: 75 + anchors.top: root.top + anchors.left: root.left + } + + // Start with anchored state + state: "anchored" + states: [ + State { + name: "anchored" + AnchorChanges { + target: rectangle + anchors.top: undefined + anchors.left: undefined + anchors.right: root.right + anchors.bottom: root.bottom + } + } + ] + + Component.onCompleted: { + rectangle.width = 50 + rectangle.height = 50 + } +} diff --git a/tests/auto/quick/qquickstates/tst_qquickstates.cpp b/tests/auto/quick/qquickstates/tst_qquickstates.cpp index be1361e4ab..7332db93fd 100644 --- a/tests/auto/quick/qquickstates/tst_qquickstates.cpp +++ b/tests/auto/quick/qquickstates/tst_qquickstates.cpp @@ -154,6 +154,7 @@ private slots: void anchorChangesCrash(); void anchorRewindBug(); void anchorRewindBug2(); + void anchorRewind_keepsSize_whenStateResetsDefaultAnchors(); void script(); void restoreEntryValues(); void explicitChanges(); @@ -1087,6 +1088,30 @@ void tst_qquickstates::anchorRewindBug2() QCOMPARE(mover->width(), qreal(50.0)); } +// QTBUG-126057 +void tst_qquickstates::anchorRewind_keepsSize_whenStateResetsDefaultAnchors() +{ + // Arrange + QQmlEngine engine; + + // NOTE: Contains two nested rectangles, inner is by default anchored to the top left corner of + // its parent. A state is initially "anchored" which removes the default anchoring and anchors + // the inner rectangle to the bottom right corner of the parent. The size of the inner rectangle + // is assigned to 50x50 on Component.onCompleted of outer rectangle. + QQmlComponent rectComponent(&engine, testFileUrl("anchorRewindBug3.qml")); + QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(rectComponent.create())); + QVERIFY(rect != nullptr); + QQuickRectangle *mover = rect->findChild<QQuickRectangle*>("inner"); + QVERIFY(mover != nullptr); + + // Act + QQuickItemPrivate::get(rect.get())->setState(""); + + // Assert + QCOMPARE(mover->width(), qreal(50.0)); + QCOMPARE(mover->height(), qreal(50.0)); +} + void tst_qquickstates::script() { QQmlEngine engine; diff --git a/tests/auto/quick/qquicktableview/data/invalidateModelContextObject.qml b/tests/auto/quick/qquicktableview/data/invalidateModelContextObject.qml new file mode 100644 index 0000000000..ff552e856c --- /dev/null +++ b/tests/auto/quick/qquicktableview/data/invalidateModelContextObject.qml @@ -0,0 +1,38 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +ApplicationWindow { + width: 720 + height: 480 + visible: true + + property alias tableView: tableView + property int modelData: 15 + + Page { + anchors.fill: parent + header: Rectangle { + height: 40 + color: "red" + } + TableView { + id: tableView + anchors.fill: parent + model: modelData + contentY: Math.max(0, contentHeight - height) + contentHeight: 40 * rows + rowHeightProvider: () => 40 + columnWidthProvider: () => 200 + delegate : Rectangle { + width: 40; + height: 40; + color: "green" + Text { + anchors.fill: parent + text: index + } + } + } + } +} diff --git a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp index 96137877cb..7482367057 100644 --- a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp +++ b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp @@ -162,12 +162,16 @@ private slots: void checkSyncView_childViews_data(); void checkSyncView_childViews(); void checkSyncView_differentSizedModels(); - void checkSyncView_differentGeometry(); + void checkSyncView_differentGeometry_vertical(); + void checkSyncView_differentGeometry_horizontal(); + void checkSyncView_differentGeometry_both_directions(); void checkSyncView_connect_late_data(); void checkSyncView_connect_late(); void checkSyncView_pageFlicking(); void checkSyncView_emptyModel(); void checkSyncView_topLeftChanged(); + void checkSyncView_dontRelayoutWhileFlicking(); + void checkSyncView_detectTopLeftPositionChanged(); void delegateWithRequiredProperties(); void checkThatFetchMoreIsCalledWhenScrolledToTheEndOfTable(); void replaceModel(); @@ -278,6 +282,7 @@ private slots: void checkScroll_data(); void checkScroll(); void checkRebuildJsModel(); + void invalidateTableInstanceModelContextObject(); }; tst_QQuickTableView::tst_QQuickTableView() @@ -3070,7 +3075,7 @@ void tst_QQuickTableView::checkSyncView_differentSizedModels() QVERIFY(tableViewHVPrivate->loadedColumns.isEmpty()); } -void tst_QQuickTableView::checkSyncView_differentGeometry() +void tst_QQuickTableView::checkSyncView_differentGeometry_vertical() { // Check that you can have two tables in a syncView relation, where // the sync "child" is larger than the sync view. This means that the @@ -3083,46 +3088,106 @@ void tst_QQuickTableView::checkSyncView_differentGeometry() GET_QML_TABLEVIEW(tableViewV); GET_QML_TABLEVIEW(tableViewHV); - tableView->setWidth(40); - tableView->setHeight(40); + tableView->setHeight(90); + tableViewH->setSyncView(nullptr); + tableViewHV->setSyncView(nullptr); auto tableViewModel = TestModelAsVariant(100, 100); tableView->setModel(tableViewModel); - tableViewH->setModel(tableViewModel); tableViewV->setModel(tableViewModel); - tableViewHV->setModel(tableViewModel); WAIT_UNTIL_POLISHED; - // Check that the column widths are in sync - for (int column = tableViewH->leftColumn(); column < tableViewH->rightColumn(); ++column) { - QCOMPARE(tableViewH->columnWidth(column), tableView->columnWidth(column)); - QCOMPARE(tableViewHV->columnWidth(column), tableView->columnWidth(column)); - } + // Check that the row heights are in sync + for (int row = tableViewV->topRow(); row < tableViewV->bottomRow(); ++row) + QCOMPARE(tableViewV->rowHeight(row), tableView->rowHeight(row)); + + // Flick in a new row + tableView->setContentY(20); // Check that the row heights are in sync - for (int row = tableViewV->topRow(); row < tableViewV->bottomRow(); ++row) { + for (int row = tableViewV->topRow(); row <= tableViewV->bottomRow(); ++row) QCOMPARE(tableViewV->rowHeight(row), tableView->rowHeight(row)); - QCOMPARE(tableViewHV->rowHeight(row), tableView->rowHeight(row)); - } +} + +void tst_QQuickTableView::checkSyncView_differentGeometry_horizontal() +{ + // Check that you can have two tables in a syncView relation, where + // the sync "child" is larger than the sync view. This means that the + // child will display more rows and columns than the parent. + // In that case, the sync view will anyway need to load the same rows + // and columns as the child, otherwise the column and row sizes + // cannot be determined for the child. + LOAD_TABLEVIEW("syncviewsimple.qml"); + GET_QML_TABLEVIEW(tableViewH); + GET_QML_TABLEVIEW(tableViewV); + GET_QML_TABLEVIEW(tableViewHV); + + tableView->setWidth(90); + tableViewV->setSyncView(nullptr); + tableViewHV->setSyncView(nullptr); + + auto tableViewModel = TestModelAsVariant(100, 100); + + tableView->setModel(tableViewModel); + tableViewH->setModel(tableViewModel); - // Flick a bit, and do the same test again - tableView->setContentX(200); - tableView->setContentY(200); WAIT_UNTIL_POLISHED; // Check that the column widths are in sync - for (int column = tableViewH->leftColumn(); column < tableViewH->rightColumn(); ++column) { + for (int column = tableViewH->leftColumn(); column < tableViewH->rightColumn(); ++column) QCOMPARE(tableViewH->columnWidth(column), tableView->columnWidth(column)); - QCOMPARE(tableViewHV->columnWidth(column), tableView->columnWidth(column)); - } + + // Flick in a new column + tableView->setContentX(20); + + // Check that the column widths are in sync + for (int column = tableViewH->leftColumn(); column < tableViewH->rightColumn(); ++column) + QCOMPARE(tableViewH->columnWidth(column), tableView->columnWidth(column)); +} + +void tst_QQuickTableView::checkSyncView_differentGeometry_both_directions() { + // Check that you can have two tables in a syncView relation, where + // the sync "child" is larger than the sync view. This means that the + // child will display more rows and columns than the parent. + // In that case, the sync view will anyway need to load the same rows + // and columns as the child, otherwise the column and row sizes + // cannot be determined for the child. + LOAD_TABLEVIEW("syncviewsimple.qml"); + GET_QML_TABLEVIEW(tableViewH); + GET_QML_TABLEVIEW(tableViewV); + GET_QML_TABLEVIEW(tableViewHV); + + tableView->setWidth(90); + tableView->setHeight(90); + tableViewHV->setSyncView(nullptr); + + auto tableViewModel = TestModelAsVariant(100, 100); + + tableView->setModel(tableViewModel); + tableViewH->setModel(tableViewModel); + tableViewV->setModel(tableViewModel); + + WAIT_UNTIL_POLISHED; // Check that the row heights are in sync - for (int row = tableViewV->topRow(); row < tableViewV->bottomRow(); ++row) { + for (int row = tableViewV->topRow(); row < tableViewV->bottomRow(); ++row) QCOMPARE(tableViewV->rowHeight(row), tableView->rowHeight(row)); - QCOMPARE(tableViewHV->rowHeight(row), tableView->rowHeight(row)); - } + // Check that the column widths are in sync + for (int column = tableViewH->leftColumn(); column < tableViewH->rightColumn(); ++column) + QCOMPARE(tableViewH->columnWidth(column), tableView->columnWidth(column)); + + // Flick in a new row + tableView->setContentX(20); + tableView->setContentY(20); + + // Check that the row heights are in sync + for (int row = tableViewV->topRow(); row <= tableViewV->bottomRow(); ++row) + QCOMPARE(tableViewV->rowHeight(row), tableView->rowHeight(row)); + // Check that the column widths are in sync + for (int column = tableViewH->leftColumn(); column < tableViewH->rightColumn(); ++column) + QCOMPARE(tableViewH->columnWidth(column), tableView->columnWidth(column)); } void tst_QQuickTableView::checkSyncView_connect_late_data() @@ -3328,6 +3393,85 @@ void tst_QQuickTableView::checkSyncView_topLeftChanged() QCOMPARE(tableViewV->topRow(), tableView->topRow()); } +void tst_QQuickTableView::checkSyncView_dontRelayoutWhileFlicking() +{ + // Check that we don't do a full relayout in a sync child when + // a new row or column is flicked into the view. Normal load + // and unload of edges should suffice, equal to how the main + // TableView (syncView) does it. + LOAD_TABLEVIEW("syncviewsimple.qml"); + GET_QML_TABLEVIEW(tableViewHV); + + auto model = TestModelAsVariant(100, 100); + tableView->setModel(model); + tableViewHV->setModel(model); + + tableView->setColumnWidthProvider(QJSValue()); + tableView->setRowHeightProvider(QJSValue()); + view->rootObject()->setProperty("delegateWidth", 50); + view->rootObject()->setProperty("delegateHeight", 50); + + WAIT_UNTIL_POLISHED; + + // To check that we don't do a relayout when flicking horizontally, we use a "trick" + // where we check the rebuildOptions when we receive the rightColumnChanged + // signal. If this signal is emitted as a part of a relayout, rebuildOptions + // would still be different from RebuildOption::None at that point. + bool columnFlickedIn = false; + connect(tableViewHV, &QQuickTableView::rightColumnChanged, [&] { + columnFlickedIn = true; + QCOMPARE(tableViewHVPrivate->rebuildOptions, QQuickTableViewPrivate::RebuildOption::None); + }); + + // We do the same for vertical flicking + bool rowFlickedIn = false; + connect(tableViewHV, &QQuickTableView::bottomRowChanged, [&] { + rowFlickedIn = true; + QCOMPARE(tableViewHVPrivate->rebuildOptions, QQuickTableViewPrivate::RebuildOption::None); + }); + + // Move the main tableview so that a new column is flicked in + tableView->setContentX(60); + QTRY_VERIFY(columnFlickedIn); + + // Move the main tableview so that a new row is flicked in + tableView->setContentY(60); + QTRY_VERIFY(rowFlickedIn); +} + +void tst_QQuickTableView::checkSyncView_detectTopLeftPositionChanged() +{ + // It can happen that, during a resize of columns or rows from using a float-based + // slider, that the position of the top-left delegate item is shifted a bit left or + // right because of rounding issues. And this again can over time, as you flick, make + // the loadedTableOuterRect get slightly out of sync in the sync child compared to the + // sync view. TableView will detect if this happens (in syncSyncView), and correct for + // it. And this test will test that it works. + LOAD_TABLEVIEW("syncviewsimple.qml"); + GET_QML_TABLEVIEW(tableViewHV); + + auto model = TestModelAsVariant(100, 100); + tableView->setModel(model); + tableViewHV->setModel(model); + + WAIT_UNTIL_POLISHED; + + // Writing an auto test to trigger this rounding issue is very hard. So to keep it + // simple, we cheat by just moving the loadedTableOuterRect directly, and + // check that the syncView child detects it, and corrects it, upon doing a + // forceLayout() + tableViewPrivate->loadedTableOuterRect.moveLeft(20); + tableViewPrivate->loadedTableOuterRect.moveTop(30); + tableViewPrivate->relayoutTableItems(); + tableViewHV->forceLayout(); + + QCOMPARE(tableViewPrivate->loadedTableOuterRect.left(), 20); + QCOMPARE(tableViewHVPrivate->loadedTableOuterRect.left(), 20); + + QCOMPARE(tableViewPrivate->loadedTableOuterRect.top(), 30); + QCOMPARE(tableViewHVPrivate->loadedTableOuterRect.top(), 30); +} + void tst_QQuickTableView::checkThatFetchMoreIsCalledWhenScrolledToTheEndOfTable() { LOAD_TABLEVIEW("plaintableview.qml"); @@ -7523,6 +7667,29 @@ void tst_QQuickTableView::checkRebuildJsModel() QCOMPARE(tableView->property(modelUpdated).toInt(), 1); } +void tst_QQuickTableView::invalidateTableInstanceModelContextObject() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("invalidateModelContextObject.qml")); + + std::unique_ptr<QQuickWindow> window(qobject_cast<QQuickWindow*>(component.create())); + QVERIFY(window); + + auto tableView = window->property("tableView").value<QQuickTableView *>(); + QVERIFY(tableView); + + const int modelData = window->property("modelData").toInt(); + QTRY_COMPARE(tableView->rows(), modelData); + + bool tableViewDestroyed = false; + connect(tableView, &QObject::destroyed, [&] { + tableViewDestroyed = true; + }); + + window.reset(); + QTRY_COMPARE(tableViewDestroyed, true); +} + QTEST_MAIN(tst_QQuickTableView) #include "tst_qquicktableview.moc" diff --git a/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp b/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp index 63d4f24a54..23d2e006ba 100644 --- a/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp +++ b/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp @@ -3,14 +3,17 @@ #include <qtest.h> #include <QtTest/QtTest> +#include <QtQuick/QQuickView> #include <QtQuick/QQuickTextDocument> #include <QtQuick/QQuickItem> #include <QtQuick/private/qquicktextedit_p.h> #include <QtQuick/private/qquicktextdocument_p.h> #include <QtGui/QTextDocument> +#include <QtGui/QTextBlock> #include <QtGui/QTextDocumentWriter> #include <QtQml/QQmlEngine> #include <QtQml/QQmlComponent> +#include <QtQuickTest/QtQuickTest> #include <QtQuickTestUtils/private/qmlutils_p.h> class tst_qquicktextdocument : public QQmlDataTest @@ -22,6 +25,8 @@ public: private slots: void textDocumentWriter(); void textDocumentWithImage(); + void changeCharFormatInRange_data(); + void changeCharFormatInRange(); }; QString text = QStringLiteral("foo bar"); @@ -68,6 +73,53 @@ void tst_qquicktextdocument::textDocumentWithImage() QCOMPARE(image, document.resource(QTextDocument::ImageResource, name).value<QImage>()); } +void tst_qquicktextdocument::changeCharFormatInRange_data() +{ + QTest::addColumn<bool>("editBlock"); + + QTest::newRow("begin/end") << true; + QTest::newRow("no edit block") << false; // QTBUG-126886 : don't crash +} + +void tst_qquicktextdocument::changeCharFormatInRange() +{ + QFETCH(bool, editBlock); + QQuickView window(testFileUrl("text.qml")); + window.showNormal(); + QQuickTextEdit *textEdit = qobject_cast<QQuickTextEdit *>(window.rootObject()); + QVERIFY(textEdit); + QVERIFY(textEdit->textDocument()); + + auto *doc = textEdit->textDocument()->textDocument(); + QVERIFY(doc); + + QSignalSpy contentSpy(doc, &QTextDocument::contentsChanged); + const auto data = QStringLiteral("Format String"); + doc->setPlainText(data); + const auto block = doc->findBlockByNumber(0); + + auto formatText = [block, data] { + QTextLayout::FormatRange formatText; + formatText.start = 0; + formatText.length = data.size(); + formatText.format.setForeground(Qt::green); + block.layout()->setFormats({formatText}); + }; + + // change the char format of this block, and verify visual effect + if (editBlock) { + QTextCursor cursor(doc); + cursor.beginEditBlock(); + formatText(); + cursor.endEditBlock(); + } else { + formatText(); + } + + QVERIFY(QQuickTest::qWaitForPolish(textEdit)); + QCOMPARE(contentSpy.size(), editBlock ? 2 : 1); +} + QTEST_MAIN(tst_qquicktextdocument) #include "tst_qquicktextdocument.moc" diff --git a/tests/auto/quick/qquicktextedit/data/hAlignVisual.qml b/tests/auto/quick/qquicktextedit/data/hAlignVisual.qml index 1280a655f0..f532a9aa36 100644 --- a/tests/auto/quick/qquicktextedit/data/hAlignVisual.qml +++ b/tests/auto/quick/qquicktextedit/data/hAlignVisual.qml @@ -4,11 +4,11 @@ Rectangle { width: 200 height: 100 - Text { - objectName: "textItem" + TextEdit { + objectName: "textEditItem" text: "AA\nBBBBBBB\nCCCCCCCCCCCCCCCC" anchors.centerIn: parent - horizontalAlignment: Text.AlignLeft + horizontalAlignment: TextEdit.AlignLeft font.pointSize: 12 font.family: "Times New Roman" } diff --git a/tests/auto/quick/qquicktextedit/data/inFlickable.qml b/tests/auto/quick/qquicktextedit/data/inFlickable.qml index 7a896db29b..183ddd6701 100644 --- a/tests/auto/quick/qquicktextedit/data/inFlickable.qml +++ b/tests/auto/quick/qquicktextedit/data/inFlickable.qml @@ -1,6 +1,7 @@ import QtQuick 2.0 Flickable { + id: flick width: 320; height: 120; contentHeight: text.height TextEdit { id: text @@ -8,4 +9,10 @@ Flickable { font.pixelSize: 20 text: "several\nlines\nof\ntext\n-\ntry\nto\nflick" } + Text { + color: "red" + parent: flick // stay on top + anchors.right: parent.right + text: flick.contentY.toFixed(1) + } } diff --git a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp index 15f538ac3c..44745f8263 100644 --- a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp +++ b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp @@ -8,6 +8,8 @@ #include <QtQuick/QQuickTextDocument> #include <QtQuickTest/QtQuickTest> #include <QTextDocument> +#include <QtGui/qtextobject.h> +#include <QtGui/QTextTable> #include <QtQml/qqmlengine.h> #include <QtQml/qqmlcontext.h> #include <QtQml/qqmlexpression.h> @@ -43,6 +45,8 @@ DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD) Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests") +// #define DEBUG_WRITE_INPUT + static bool isPlatformWayland() { return !QGuiApplication::platformName().compare(QLatin1String("wayland"), Qt::CaseInsensitive); @@ -143,6 +147,8 @@ private slots: void largeTextObservesViewport(); void largeTextSelection(); void renderingAroundSelection(); + void largeTextTables_data(); + void largeTextTables(); void signal_editingfinished(); @@ -221,7 +227,6 @@ private: void simulateKey(QWindow *, int key, Qt::KeyboardModifiers modifiers = {}); bool isMainFontFixed(); - static bool hasWindowActivation(); QStringList standard; QStringList richText; @@ -972,8 +977,8 @@ void tst_qquicktextedit::hAlignVisual() view.showNormal(); QVERIFY(QTest::qWaitForWindowExposed(&view)); - QQuickText *text = view.rootObject()->findChild<QQuickText*>("textItem"); - QVERIFY(text != nullptr); + QQuickTextEdit *text = view.rootObject()->findChild<QQuickTextEdit*>("textEditItem"); + QVERIFY(text); // Try to check whether alignment works by checking the number of black // pixels in the thirds of the grabbed image. @@ -1000,7 +1005,7 @@ void tst_qquicktextedit::hAlignVisual() } { // HCenter Align - text->setHAlign(QQuickText::AlignHCenter); + text->setHAlign(QQuickTextEdit::AlignHCenter); QImage image = view.grabWindow(); const int left = numberOfNonWhitePixels(centeredSection1, centeredSection2, image); const int mid = numberOfNonWhitePixels(centeredSection2, centeredSection3, image); @@ -1010,7 +1015,7 @@ void tst_qquicktextedit::hAlignVisual() } { // Right Align - text->setHAlign(QQuickText::AlignRight); + text->setHAlign(QQuickTextEdit::AlignRight); QImage image = view.grabWindow(); const int left = numberOfNonWhitePixels(centeredSection1, centeredSection2, image); const int mid = numberOfNonWhitePixels(centeredSection2, centeredSection3, image); @@ -1022,36 +1027,36 @@ void tst_qquicktextedit::hAlignVisual() text->setWidth(200); { - // Left Align + // Right Align QImage image = view.grabWindow(); - int x = qCeil(text->implicitWidth() * view.devicePixelRatio()); - int left = numberOfNonWhitePixels(0, x, image); - int right = numberOfNonWhitePixels(x, image.width() - x, image); - QVERIFY2(left > 0, msgNotGreaterThan(left, 0).constData()); - QCOMPARE(right, 0); + const int x = image.width() - qCeil(text->implicitWidth() * view.devicePixelRatio()); + const int left = numberOfNonWhitePixels(0, x, image); + const int right = numberOfNonWhitePixels(x, image.width() - x, image); + QCOMPARE(left, 0); + QVERIFY2(right > 0, msgNotGreaterThan(left, 0).constData()); } { // HCenter Align - text->setHAlign(QQuickText::AlignHCenter); + text->setHAlign(QQuickTextEdit::AlignHCenter); QImage image = view.grabWindow(); - int x1 = qFloor(image.width() - text->implicitWidth() * view.devicePixelRatio()) / 2; - int x2 = image.width() - x1; - int left = numberOfNonWhitePixels(0, x1, image); - int mid = numberOfNonWhitePixels(x1, x2 - x1, image); - int right = numberOfNonWhitePixels(x2, image.width() - x2, image); + const int x1 = qFloor(image.width() - text->implicitWidth() * view.devicePixelRatio()) / 2; + const int x2 = image.width() - x1; + const int left = numberOfNonWhitePixels(0, x1, image); + const int mid = numberOfNonWhitePixels(x1, x2 - x1, image); + const int right = numberOfNonWhitePixels(x2, image.width(), image); QCOMPARE(left, 0); QVERIFY2(mid > 0, msgNotGreaterThan(left, 0).constData()); QCOMPARE(right, 0); } { - // Right Align - text->setHAlign(QQuickText::AlignRight); + // Left Align + text->setHAlign(QQuickTextEdit::AlignLeft); QImage image = view.grabWindow(); - int x = image.width() - qCeil(text->implicitWidth() * view.devicePixelRatio()); - int left = numberOfNonWhitePixels(0, x, image); - int right = numberOfNonWhitePixels(x, image.width() - x, image); - QCOMPARE(left, 0); - QVERIFY2(right > 0, msgNotGreaterThan(left, 0).constData()); + const int x = qCeil(text->implicitWidth() * view.devicePixelRatio()); + const int left = numberOfNonWhitePixels(0, x, image); + const int right = numberOfNonWhitePixels(x, image.width() - x, image); + QVERIFY2(left > 0, msgNotGreaterThan(left, 0).constData()); + QCOMPARE(right, 0); } } @@ -3385,11 +3390,6 @@ bool tst_qquicktextedit::isMainFontFixed() return ret; } -bool tst_qquicktextedit::hasWindowActivation() -{ - return (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)); -} - void tst_qquicktextedit::textInput() { QQuickView view(testFileUrl("inputMethodEvent.qml")); @@ -3951,6 +3951,105 @@ void tst_qquicktextedit::renderingAroundSelection() QTRY_COMPARE(textItem->sortedLinePositions, sortedLinePositions); } +struct OffsetAndExpectedBlocks { + int tableIndex; // which nested frame + qreal tableOffset; // fraction of that frame's height to scroll to + int minExpectedBlockCount; + + OffsetAndExpectedBlocks(int i, qreal o, int c) + : tableIndex(i), tableOffset(o), minExpectedBlockCount(c) {} +}; + +typedef QList<OffsetAndExpectedBlocks> OffsetAndExpectedBlocksList; + +void tst_qquicktextedit::largeTextTables_data() +{ + QTest::addColumn<int>("tables"); + QTest::addColumn<int>("tableCols"); + QTest::addColumn<int>("tableRows"); + QTest::addColumn<OffsetAndExpectedBlocksList>("steps"); + + QTest::newRow("one big table") << 1 << 3 << 70 + << OffsetAndExpectedBlocksList{ + OffsetAndExpectedBlocks(1, 0.75, 150), + OffsetAndExpectedBlocks(1, 0.5, 150)}; + QTest::newRow("short tables") << 5 << 3 << 10 + << OffsetAndExpectedBlocksList{ + OffsetAndExpectedBlocks(4, 0.75, 35), + OffsetAndExpectedBlocks(3, 0.25, 50), + OffsetAndExpectedBlocks(2, 0.75, 50)}; +} + +void tst_qquicktextedit::largeTextTables() // QTBUG-118636 +{ + QFETCH(int, tables); + QFETCH(int, tableCols); + QFETCH(int, tableRows); + QFETCH(OffsetAndExpectedBlocksList, steps); + + QStringList lines; + + lines << QLatin1String("<h1>") + QTest::currentDataTag() + "</h1>"; + for (int t = 0; t < tables; ++t) { + if (t > 0) + lines << QString("<p>table %1</p>").arg(t); + lines << "<table border='1'>"; + for (int r = 0; r < tableRows; ++r) { + lines << " <tr>"; + for (int c = 0; c < tableCols; ++c) + lines << QString(" <td>table %1 cell %2, %3</td>").arg(t).arg(c).arg(r); + lines << " </tr>"; + } + lines << "</table>"; + } + lines << "<p>here endeth the tables</p>"; + QString html = lines.join('\n'); + +#ifdef DEBUG_WRITE_INPUT + QFile f(QLatin1String("/tmp/") + QTest::currentDataTag() + ".html"); + f.open(QFile::WriteOnly); + f.write(html.toUtf8()); + f.close(); +#endif + + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("inFlickable.qml"))); + QQuickFlickable *flick = qmlobject_cast<QQuickFlickable *>(window.rootObject()); + QVERIFY(flick); + QQuickTextEdit *te = window.rootObject()->findChild<QQuickTextEdit *>(); + QVERIFY(te); + auto *tePriv = QQuickTextEditPrivate::get(te); + auto font = te->font(); + font.setPixelSize(10); + te->setFont(font); + + te->setTextFormat(QQuickTextEdit::RichText); + te->setText(html); + te->setFlag(QQuickItem::ItemObservesViewport); // this isn't "large text", but test viewporting anyway + + QTextDocument *doc = te->textDocument()->textDocument(); + QList<QTextFrame *> frames = doc->rootFrame()->childFrames(); + frames.prepend(doc->rootFrame()); + qCDebug(lcTests) << "blocks" << doc->blockCount() << "chars" << doc->characterCount() << "frames" << frames; + + for (const OffsetAndExpectedBlocks &oeb : steps) { + QCOMPARE_GT(frames.size(), oeb.tableIndex); + const QTextFrame *textFrame = frames.at(oeb.tableIndex); + const QTextCursor top = textFrame->firstCursorPosition(); + const qreal yTop = te->positionToRectangle(top.position()).top(); + const QTextCursor bottom = textFrame->lastCursorPosition(); + const qreal yBottom = te->positionToRectangle(bottom.position()).bottom(); + const qreal y = yTop + (yBottom - yTop) * oeb.tableOffset; + qCDebug(lcTests) << "frame" << textFrame << "goes from pos" << top.position() << "y" << yTop + << "to pos" << bottom.position() << "y" << yBottom << "; scrolling to" << y + << "which is at" << oeb.tableOffset << "of table height" << (yBottom - yTop); + flick->setContentY(y); + qCDebug(lcTests) << tePriv->renderedRegion << "rendered blocks" << tePriv->renderedBlockCount << ":" + << tePriv->firstBlockInViewport << "to" << tePriv->firstBlockPastViewport; + QTRY_COMPARE_GE(tePriv->renderedBlockCount, oeb.minExpectedBlockCount); + } +} + void tst_qquicktextedit::signal_editingfinished() { QQuickView *window = new QQuickView(nullptr); @@ -6483,8 +6582,8 @@ void tst_qquicktextedit::touchscreenDoesNotSelect() void tst_qquicktextedit::touchscreenSetsFocusAndMovesCursor() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION + QQuickView window; QVERIFY(QQuickTest::showView(window, testFileUrl("twoInAColumn.qml"))); window.requestActivate(); diff --git a/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp b/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp index 5e2d49afb8..c944406e10 100644 --- a/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp +++ b/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp @@ -5,6 +5,7 @@ #include <QtQuickTestUtils/private/qmlutils_p.h> #include <QtQuickTestUtils/private/testhttpserver_p.h> #include <QtQuickTestUtils/private/viewtestutils_p.h> +#include <QtQuickTestUtils/private/visualtestutils_p.h> #include <private/qinputmethod_p.h> #include <QtQml/qqmlengine.h> #include <QtQml/qqmlcomponent.h> @@ -212,7 +213,6 @@ private: #if QT_CONFIG(shortcut) void simulateKeys(QWindow *window, const QKeySequence &sequence); #endif - static bool hasWindowActivation(); QQmlEngine engine; QStringList standard; @@ -238,11 +238,6 @@ void tst_qquicktextinput::simulateKeys(QWindow *window, const QList<Key> &keys) } } -bool tst_qquicktextinput::hasWindowActivation() -{ - return (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)); -} - #if QT_CONFIG(shortcut) void tst_qquicktextinput::simulateKeys(QWindow *window, const QKeySequence &sequence) @@ -7191,17 +7186,15 @@ void tst_qquicktextinput::touchscreenDoesNotSelect() QTest::touchEvent(&window, touchscreen.data()).press(0, QPoint(x2,y), &window); QTest::touchEvent(&window, touchscreen.data()).release(0, QPoint(x2,y), &window); QQuickTouchUtils::flush(&window); - QCOMPARE(textInputObject->selectedText().isEmpty(), !expectDefaultSelectByMouse); - if (expectDefaultSelectByMouse) - QCOMPARE(textInputObject->cursorPosition(), cursorPos); - else - QCOMPARE_NE(textInputObject->cursorPosition(), cursorPos); + QCOMPARE(textInputObject->selectedText().isEmpty(), true); + QCOMPARE_NE(textInputObject->cursorPosition(), cursorPos); + QVERIFY(textInputObject->selectedText().isEmpty()); } void tst_qquicktextinput::touchscreenSetsFocusAndMovesCursor() { - if (!hasWindowActivation()) - QSKIP("Window activation is not supported"); + SKIP_IF_NO_WINDOW_ACTIVATION + QQuickView window; QVERIFY(QQuickTest::showView(window, testFileUrl("twoInAColumn.qml"))); window.requestActivate(); diff --git a/tests/auto/quick/qquickview_extra/data/qtbug_87228.qml b/tests/auto/quick/qquickview_extra/data/qtbug_87228.qml index ff10eba23d..9327daae6b 100644 --- a/tests/auto/quick/qquickview_extra/data/qtbug_87228.qml +++ b/tests/auto/quick/qquickview_extra/data/qtbug_87228.qml @@ -15,6 +15,8 @@ Item { Rectangle { ListView { objectName: "listView" + width: 100 + height: 100 delegate: Text { required property string desc text: desc diff --git a/tests/auto/quick/softwarerenderer/tst_softwarerenderer.cpp b/tests/auto/quick/softwarerenderer/tst_softwarerenderer.cpp index 2d2581a3fa..2124a8ee8f 100644 --- a/tests/auto/quick/softwarerenderer/tst_softwarerenderer.cpp +++ b/tests/auto/quick/softwarerenderer/tst_softwarerenderer.cpp @@ -56,10 +56,8 @@ void tst_SoftwareRenderer::renderTarget() rc.polishItems(); - rc.beginFrame(); rc.sync(); rc.render(); - rc.endFrame(); QImage content = window->grabWindow(); QString errorMessage; @@ -74,10 +72,8 @@ void tst_SoftwareRenderer::renderTarget() rc.polishItems(); - rc.beginFrame(); rc.sync(); rc.render(); - rc.endFrame(); content = window->grabWindow(); QVERIFY2(QQuickVisualTestUtils::compareImages(content, renderTarget2, &errorMessage), diff --git a/tests/auto/quick/touchmouse/tst_touchmouse.cpp b/tests/auto/quick/touchmouse/tst_touchmouse.cpp index d7820996be..179acbc728 100644 --- a/tests/auto/quick/touchmouse/tst_touchmouse.cpp +++ b/tests/auto/quick/touchmouse/tst_touchmouse.cpp @@ -244,6 +244,8 @@ private slots: void strayTouchDoesntAutograb(); + void noDoubleClickWithInterveningTouch(); + protected: bool eventFilter(QObject *, QEvent *event) override { @@ -1303,7 +1305,7 @@ void tst_TouchMouse::touchGrabCausesMouseUngrab() QVERIFY(leftItem); EventItem *rightItem = window.rootObject()->findChild<EventItem*>("rightItem"); - QVERIFY(leftItem); + QVERIFY(rightItem); // Send a touch to the leftItem. But leftItem accepts only mouse events, thus // a mouse event will be synthesized out of this touch and will get accepted by @@ -1660,6 +1662,58 @@ void tst_TouchMouse::strayTouchDoesntAutograb() // QTBUG-107867 QTest::touchEvent(&window, device).release(0, p1).release(1, p1); } +void tst_TouchMouse::noDoubleClickWithInterveningTouch() // QTBUG-116442 +{ + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("twosiblingitems.qml"))); + + EventItem *leftItem = window.rootObject()->findChild<EventItem*>("leftItem"); + QVERIFY(leftItem); + // simulate a MouseArea: don't accept touch + leftItem->setAcceptedMouseButtons(Qt::LeftButton); + leftItem->acceptMouse = true; + + EventItem *rightItem = window.rootObject()->findChild<EventItem*>("rightItem"); + QVERIFY(rightItem); + // simulate an item that reacts to either touch or mouse + rightItem->setAcceptedMouseButtons(Qt::LeftButton); + rightItem->acceptMouse = true; + rightItem->setAcceptTouchEvents(true); + rightItem->acceptTouch = true; + + const QPoint pLeft(80, 200); + const QPoint pRight(240, 200); + + // tap left + QTest::touchEvent(&window, device).press(1, pLeft, &window); + QTest::touchEvent(&window, device).release(1, pLeft, &window); + QQuickTouchUtils::flush(&window); + qCDebug(lcTests) << "left tap" << leftItem->eventList; + QCOMPARE(leftItem->eventList.size(), 3); + QCOMPARE(leftItem->eventList.at(0).type, QEvent::MouseButtonPress); + leftItem->eventList.clear(); + + // tap right + QTest::touchEvent(&window, device).press(1, pRight, &window); + QTest::touchEvent(&window, device).release(1, pRight, &window); + QQuickTouchUtils::flush(&window); + qCDebug(lcTests) << "right tap" << rightItem->eventList; + QCOMPARE(rightItem->eventList.size(), 2); + QCOMPARE(rightItem->eventList.at(0).type, QEvent::TouchBegin); + rightItem->eventList.clear(); + + // tap left again: this is NOT a double-click, even though it's within time and space limits + QTest::touchEvent(&window, device).press(3, pLeft, &window); + QTest::touchEvent(&window, device).release(3, pLeft, &window); + QQuickTouchUtils::flush(&window); + qCDebug(lcTests) << "left tap again" << leftItem->eventList; + QCOMPARE(leftItem->eventList.size(), 3); + QCOMPARE(leftItem->eventList.at(0).type, QEvent::MouseButtonPress); + QCOMPARE(leftItem->eventList.at(1).type, QEvent::MouseButtonRelease); + QCOMPARE(leftItem->eventList.at(2).type, QEvent::UngrabMouse); + leftItem->eventList.clear(); +} + QTEST_MAIN(tst_TouchMouse) #include "tst_touchmouse.moc" |
