diff options
Diffstat (limited to 'tests/auto/quick')
21 files changed, 750 insertions, 15 deletions
diff --git a/tests/auto/quick/pointerhandlers/mousearea_interop/BLACKLIST b/tests/auto/quick/pointerhandlers/mousearea_interop/BLACKLIST index 74d29b4ac3..cb290fe641 100644 --- a/tests/auto/quick/pointerhandlers/mousearea_interop/BLACKLIST +++ b/tests/auto/quick/pointerhandlers/mousearea_interop/BLACKLIST @@ -1,7 +1,3 @@ # QTBUG-95938 [dragHandlerInSiblingStealingGrabFromMouseAreaViaTouch] opensuse-leap - -# QTBUG-95938 -[dragHandlerInSiblingStealingGrabFromMouseAreaViaMouse] -opensuse-leap diff --git a/tests/auto/quick/pointerhandlers/mousearea_interop/tst_mousearea_interop.cpp b/tests/auto/quick/pointerhandlers/mousearea_interop/tst_mousearea_interop.cpp index 556acbc828..6331a2d390 100644 --- a/tests/auto/quick/pointerhandlers/mousearea_interop/tst_mousearea_interop.cpp +++ b/tests/auto/quick/pointerhandlers/mousearea_interop/tst_mousearea_interop.cpp @@ -43,6 +43,7 @@ void tst_MouseAreaInterop::createView(QScopedPointer<QQuickView> &window, const window.reset(new QQuickView); window->setSource(testFileUrl(fileName)); QTRY_COMPARE(window->status(), QQuickView::Ready); + window.data()->setFlag(Qt::FramelessWindowHint); QQuickViewTestUtils::centerOnScreen(window.data()); QQuickViewTestUtils::moveMouseAway(window.data()); @@ -58,6 +59,10 @@ void tst_MouseAreaInterop::dragHandlerInSiblingStealingGrabFromMouseAreaViaMouse createView(windowPtr, "dragTakeOverFromSibling.qml"); QQuickView * window = windowPtr.data(); + const QPoint pos = QGuiApplication::primaryScreen()->availableGeometry().topLeft(); + window->setFramePosition(pos); + QTRY_COMPARE(window->framePosition(), pos); + QPointer<QQuickPointerHandler> handler = window->rootObject()->findChild<QQuickPointerHandler*>(); QVERIFY(handler); QQuickMouseArea *ma = window->rootObject()->findChild<QQuickMouseArea*>(); @@ -108,6 +113,10 @@ void tst_MouseAreaInterop::dragHandlerInSiblingStealingGrabFromMouseAreaViaTouch QQuickView * window = windowPtr.data(); auto devPriv = QPointingDevicePrivate::get(touchDevice); + const QPoint pos = QGuiApplication::primaryScreen()->availableGeometry().topLeft(); + window->setFramePosition(pos); + QTRY_COMPARE(window->framePosition(), pos); + QPointer<QQuickPointerHandler> handler = window->rootObject()->findChild<QQuickPointerHandler*>(); QVERIFY(handler); QQuickMouseArea *ma = window->rootObject()->findChild<QQuickMouseArea*>(); diff --git a/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp b/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp index b3b2259319..bff6ca9733 100644 --- a/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp +++ b/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp @@ -394,14 +394,25 @@ void tst_HoverHandler::movingItemWithHoverHandler() QTRY_COMPARE(paddleHH->isHovered(), true); // TODO check the cursor shape after fixing QTBUG-53987 + const auto &deliveryTargets = + QQuickPointerHandlerPrivate::deviceDeliveryTargets(QPointingDevice::primaryPointingDevice()); + const auto targetsCount = deliveryTargets.size(); + qCDebug(lcPointerTests) << "deviceDeliveryTargets before paddle movement" << deliveryTargets; paddle->setX(100); QTRY_COMPARE(paddleHH->isHovered(), false); + // QQuickDeliveryAgentPrivate::deliverHoverEvent() clears the deviceDeliveryTargets list, + // and then each HoverHandler's QQuickPointerHandler::handlePointerEvent() adds itself again. + // As long as we visit the same handlers each time, the list should not grow. (QTBUG-135975) + qCDebug(lcPointerTests) << "deviceDeliveryTargets after paddle movement" << deliveryTargets; + QCOMPARE(deliveryTargets.size(), targetsCount); paddle->setX(p.x() - paddle->width() / 2); QTRY_COMPARE(paddleHH->isHovered(), true); + QCOMPARE(deliveryTargets.size(), targetsCount); paddle->setX(540); QTRY_COMPARE(paddleHH->isHovered(), false); + QCOMPARE(deliveryTargets.size(), targetsCount); } void tst_HoverHandler::margin() // QTBUG-85303 diff --git a/tests/auto/quick/qquickanimations/data/animationInstantiator.qml b/tests/auto/quick/qquickanimations/data/animationInstantiator.qml new file mode 100644 index 0000000000..48206073d0 --- /dev/null +++ b/tests/auto/quick/qquickanimations/data/animationInstantiator.qml @@ -0,0 +1,40 @@ +import QtQuick + +Item { + id: root + property int v: 0 + + Transition { + id: transition + NumberAnimation {} + } + + Instantiator { + id: instantiator + + delegate: NumberAnimation {} + + onObjectAdded: (index, object) => { + transition.animations.splice(index, 0, object) + } + + onObjectRemoved: (index, object) => { + // NB: This is a logic error. The indices of other entries change when we remove one + // from the middle, but Instantiator will still report the old indices. + // We still want to do this, in order ot exercise the code that nulls dangling + // pointers. + transition.animations.splice(index, 1) + } + } + + Timer { + interval: 2 + running: true + repeat: true + onTriggered: { + instantiator.model = (++v % 2) ? 0 : 10 + } + } +} + + diff --git a/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp b/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp index ce5473c8a5..083efc9213 100644 --- a/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp +++ b/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp @@ -102,6 +102,7 @@ private slots: void restartNestedAnimationGroupWhenDirty(); void targetsDeletedNotRemoved(); void alwaysRunToEndSetFalseRestartBug(); + void animationInstantiator(); }; #define QTIMED_COMPARE(lhs, rhs) do { \ @@ -2331,6 +2332,16 @@ void tst_qquickanimations::alwaysRunToEndSetFalseRestartBug() QCOMPARE(sequential.isRunning(), false); } +void tst_qquickanimations::animationInstantiator() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("animationInstantiator.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QTRY_VERIFY(o->property("v").toInt() > 10); +} + QTEST_MAIN(tst_qquickanimations) #include "tst_qquickanimations.moc" diff --git a/tests/auto/quick/qquickapplication/BLACKLIST b/tests/auto/quick/qquickapplication/BLACKLIST deleted file mode 100644 index b8bc4363f1..0000000000 --- a/tests/auto/quick/qquickapplication/BLACKLIST +++ /dev/null @@ -1,2 +0,0 @@ -[state] -opensuse-leap # QTBUG-122031 diff --git a/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp b/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp index 0bac4baaea..aed74dce10 100644 --- a/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp +++ b/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp @@ -165,7 +165,8 @@ void tst_qquickapplication::state() // Otherwise, the application can only be in two states, Active and Inactive. These are // triggered by window activation. window.show(); - window.requestActivate(); + if (QGuiApplication::platformName().toLower() != QLatin1String("xcb")) + window.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&window)); QCOMPARE(QGuiApplication::focusWindow(), &window); QCOMPARE(Qt::ApplicationState(item->property("state").toInt()), Qt::ApplicationActive); diff --git a/tests/auto/quick/qquicklistview/BLACKLIST b/tests/auto/quick/qquicklistview/BLACKLIST index a686534d20..e463fc0b45 100644 --- a/tests/auto/quick/qquicklistview/BLACKLIST +++ b/tests/auto/quick/qquicklistview/BLACKLIST @@ -1,6 +1,3 @@ -#QTBUG-53863 -[populateTransitions] -opensuse-42.1 # QTBUG-75202 [contentHeightWithDelayRemove] macos ci diff --git a/tests/auto/quick/qquickloader/data/invalidateContext.qml b/tests/auto/quick/qquickloader/data/invalidateContext.qml new file mode 100644 index 0000000000..b5a28f5d0f --- /dev/null +++ b/tests/auto/quick/qquickloader/data/invalidateContext.qml @@ -0,0 +1,28 @@ +import QtQuick + +Item { + Loader { + active: loaderActive + sourceComponent: comp + } + + Component { + id: comp + Column { + Repeater { + id: repeater + model: cppModel + + Component.onCompleted: console.log("Repeater constructed"); + Component.onDestruction: console.log("Repeater destroyed"); + + delegate: Text { + text: { + console.log("updating text"); + return display + rootData.getValue(); + } + } + } + } + } +} diff --git a/tests/auto/quick/qquickloader/tst_qquickloader.cpp b/tests/auto/quick/qquickloader/tst_qquickloader.cpp index 389f037166..afc49fe74d 100644 --- a/tests/auto/quick/qquickloader/tst_qquickloader.cpp +++ b/tests/auto/quick/qquickloader/tst_qquickloader.cpp @@ -117,6 +117,8 @@ private slots: void stackOverflow(); void stackOverflow2(); void boundComponent(); + + void invalidateContext(); }; Q_DECLARE_METATYPE(QList<QQmlError>) @@ -1585,6 +1587,60 @@ void tst_QQuickLoader::boundComponent() QCOMPARE(o->objectName(), QStringLiteral("loaded")); } +class CppModel : public QAbstractListModel +{ +public: + virtual int rowCount(const QModelIndex &) const override {return 1;} + virtual QVariant data(const QModelIndex &, int) const override { return "test"; } + + void reset () {beginResetModel(); endResetModel();} +}; + +class RootData : public QObject +{ + Q_OBJECT +public: + RootData() : object(std::make_unique<QObject>()) + { + object->setObjectName("objectName"); + } + + Q_INVOKABLE QString getValue() const { return object->objectName(); } + + void deleteObject() { object.reset(); } + +private: + std::unique_ptr<QObject> object; +}; + +void tst_QQuickLoader::invalidateContext() +{ + CppModel model; + RootData rootData; + + QQmlEngine engine; + QQmlContext *rootContext = engine.rootContext(); + rootContext->setContextProperty("cppModel", &model); + rootContext->setContextProperty("loaderActive", true); + rootContext->setContextProperty("rootData", &rootData); + + QQmlComponent component(&engine, testFileUrl("invalidateContext.qml"));\ + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + + QTest::ignoreMessage(QtDebugMsg, "Repeater constructed"); + QTest::ignoreMessage(QtDebugMsg, "updating text"); + QScopedPointer<QObject> o(component.create()); + + QTest::ignoreMessage(QtDebugMsg, "Repeater destroyed"); + rootContext->setContextProperty("loaderActive", false); + + // The object should never be used anymore, as loader is inactive + rootData.deleteObject(); + + // Should not trigger any re-evaluation (which would crash in getValue()) + model.reset(); +} + QTEST_MAIN(tst_QQuickLoader) #include "tst_qquickloader.moc" diff --git a/tests/auto/quick/qquickmultipointtoucharea/data/inFlickableWithScale.qml b/tests/auto/quick/qquickmultipointtoucharea/data/inFlickableWithScale.qml new file mode 100644 index 0000000000..9af4c36241 --- /dev/null +++ b/tests/auto/quick/qquickmultipointtoucharea/data/inFlickableWithScale.qml @@ -0,0 +1,27 @@ +import QtQuick + +Rectangle { + id: root + width: 240 + height: 320 + + property bool gestureStarted: false + + Flickable { + id: flick + anchors.fill: parent + anchors.margins: 50 + clip: true + contentWidth: width + contentHeight: height * 2 + + scale: 0.5 + + MultiPointTouchArea { + anchors.fill: parent + onGestureStarted: (gesture) => { + root.gestureStarted = true + } + } + } +} diff --git a/tests/auto/quick/qquickmultipointtoucharea/tst_qquickmultipointtoucharea.cpp b/tests/auto/quick/qquickmultipointtoucharea/tst_qquickmultipointtoucharea.cpp index 81e57bfd12..6fe27ebe6f 100644 --- a/tests/auto/quick/qquickmultipointtoucharea/tst_qquickmultipointtoucharea.cpp +++ b/tests/auto/quick/qquickmultipointtoucharea/tst_qquickmultipointtoucharea.cpp @@ -40,6 +40,7 @@ private slots: void inFlickable(); void inFlickable2(); void inFlickableWithPressDelay(); + void inFlickableWithScale(); void inMouseArea(); void mouseAsTouchpoint(); void invisible(); @@ -860,6 +861,35 @@ void tst_QQuickMultiPointTouchArea::inFlickableWithPressDelay() // QTBUG-78818 QTRY_VERIFY(!flickable->isMoving()); } +// QTBUG-131886 +void tst_QQuickMultiPointTouchArea::inFlickableWithScale() +{ + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("inFlickableWithScale.qml"))); + + QQuickFlickable *flickable = window.rootObject()->findChild<QQuickFlickable*>(); + QVERIFY(flickable != nullptr); + QQuickMultiPointTouchArea *mpta = window.rootObject()->findChild<QQuickMultiPointTouchArea*>(); + QVERIFY(mpta != nullptr); + + QSignalSpy gestureStartedSpy(mpta, &QQuickMultiPointTouchArea::gestureStarted); + + // press and drag over dragThreshold emits gestureStarted even when scaled + QPoint p1(120, 160); // size is 240 x 320, press in the center + QTest::touchEvent(&window, device).press(0, p1); + QQuickTouchUtils::flush(&window); + + p1 += QPoint(0, dragThreshold); + QTest::touchEvent(&window, device).move(0, p1); + QQuickTouchUtils::flush(&window); + + QCOMPARE(gestureStartedSpy.size(), 1); + + QTest::touchEvent(&window, device).release(0, p1); + QQuickTouchUtils::flush(&window); +} + // QTBUG-31047 void tst_QQuickMultiPointTouchArea::inMouseArea() { diff --git a/tests/auto/quick/qquickshape/data/changeElementsImperatively.qml b/tests/auto/quick/qquickshape/data/changeElementsImperatively.qml new file mode 100644 index 0000000000..129be7222a --- /dev/null +++ b/tests/auto/quick/qquickshape/data/changeElementsImperatively.qml @@ -0,0 +1,21 @@ +import QtQuick +import QtQuick.Shapes + +Shape { + width: 640 + height: 480 + + Component.onCompleted: { + bottomPathLine.x = 20 + } + + ShapePath { + PathLine {} + PathLine { + id: bottomPathLine + } + PathArc {} + PathLine {} + PathArc {} + } +} diff --git a/tests/auto/quick/qquickshape/tst_qquickshape.cpp b/tests/auto/quick/qquickshape/tst_qquickshape.cpp index a9e15939d3..c9e7af5ee7 100644 --- a/tests/auto/quick/qquickshape/tst_qquickshape.cpp +++ b/tests/auto/quick/qquickshape/tst_qquickshape.cpp @@ -62,6 +62,7 @@ private slots: void multilineDataTypes_data(); void multilineDataTypes(); void multilineStronglyTyped(); + void changeElementsImperatively(); private: QVector<QPolygonF> m_lowPolyLogo; @@ -674,6 +675,13 @@ void tst_QQuickShape::multilineStronglyTyped() } } +void tst_QQuickShape::changeElementsImperatively() +{ + QQuickView window; + // Shouldn't crash. + QVERIFY(QQuickTest::showView(window, testFileUrl("changeElementsImperatively.qml"))); +} + QTEST_MAIN(tst_QQuickShape) #include "tst_qquickshape.moc" diff --git a/tests/auto/quick/qquicktext/BLACKLIST b/tests/auto/quick/qquicktext/BLACKLIST index a4e9c44eab..3c64442e2b 100644 --- a/tests/auto/quick/qquicktext/BLACKLIST +++ b/tests/auto/quick/qquicktext/BLACKLIST @@ -1,5 +1,4 @@ -[fontSizeMode] -opensuse-42.1 + [contentSize] windows gcc msvc-2019 diff --git a/tests/auto/quick/qquicktextedit/data/readOnly.qml b/tests/auto/quick/qquicktextedit/data/readOnly.qml index 085adba5fb..4aea6a78ab 100644 --- a/tests/auto/quick/qquicktextedit/data/readOnly.qml +++ b/tests/auto/quick/qquicktextedit/data/readOnly.qml @@ -1,12 +1,38 @@ -import QtQuick 2.0 +import QtQuick Rectangle { property variant myInput: input width: 800; height: 600; color: "blue" - TextEdit { id: input; focus: true + property bool activateSelectAllShortcut: false + property bool activateCopyShortcut: false + property bool activatePageupShortcut: false + property bool acceptShortcutOverride: true + + Shortcut { + sequences: [StandardKey.SelectAll] + onActivated: { activateSelectAllShortcut = true } + } + + Shortcut { + sequences: [StandardKey.Copy] + onActivated: { activateCopyShortcut = true } + } + + Shortcut { + sequences: [StandardKey.MoveToPreviousPage] + onActivated: { activatePageupShortcut = true } + } + + TextEdit { + id: input + + focus: true readOnly: true text: "I am the very model of a modern major general.\n" + Keys.onShortcutOverride: event => { + event.accepted = acceptShortcutOverride + } } } diff --git a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp index 399627c4f1..31945b3776 100644 --- a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp +++ b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp @@ -3256,11 +3256,53 @@ void tst_qquicktextedit::readOnly() QString initial = edit->text(); for (int k=Qt::Key_0; k<=Qt::Key_Z; k++) simulateKey(&window, k); + simulateKey(&window, Qt::Key_Return); simulateKey(&window, Qt::Key_Space); simulateKey(&window, Qt::Key_Escape); QCOMPARE(edit->text(), initial); + // Read-only shall support Selection and Copy by default + QCOMPARE(window.rootObject()->property("acceptShortcutOverride").toBool(), true); + simulateKeys(&window, QKeySequence(QKeySequence::SelectAll)); + QCOMPARE(window.rootObject()->property("activateSelectAllShortcut").toBool(), false); + QCOMPARE(edit->cursorPosition(), edit->text().size()); + QCOMPARE(edit->selectedText(), edit->text()); +#ifndef QT_NO_CLIPBOARD + // Copy the selected text into the clipboard + if (PlatformQuirks::isClipboardAvailable()) { + if (auto *clipBoard = QGuiApplication::clipboard()) { + simulateKeys(&window, QKeySequence(QKeySequence::Copy)); + QCOMPARE(window.rootObject()->property("activateCopyShortcut").toBool(), false); + QCOMPARE(clipBoard->text(), edit->text()); + clipBoard->clear(); + } + } +#endif + edit->setCursorPosition(0); + + window.rootObject()->setProperty("acceptShortcutOverride", false); + QCOMPARE(window.rootObject()->property("acceptShortcutOverride").toBool(), false); + simulateKeys(&window, QKeySequence(QKeySequence::SelectAll)); + QCOMPARE(window.rootObject()->property("activateSelectAllShortcut").toBool(), true); + QCOMPARE(edit->cursorPosition(), 0); + QCOMPARE(edit->selectedText(), ""); +#ifndef QT_NO_CLIPBOARD + // Copy the selected text into the clipboard + if (PlatformQuirks::isClipboardAvailable()) { + if (auto *clipBoard = QGuiApplication::clipboard()) { + simulateKeys(&window, QKeySequence(QKeySequence::Copy)); + QCOMPARE(window.rootObject()->property("activateCopyShortcut").toBool(), true); + QCOMPARE(clipBoard->text(), ""); + } + } +#endif + simulateKeys(&window, QKeySequence(QKeySequence::MoveToPreviousPage)); + QCOMPARE(window.rootObject()->property("activatePageupShortcut").toBool(), true); + + window.rootObject()->setProperty("acceptShortcutOverride", true); + QCOMPARE(window.rootObject()->property("acceptShortcutOverride").toBool(), true); + edit->setCursorPosition(3); edit->setReadOnly(false); QCOMPARE(edit->isReadOnly(), false); diff --git a/tests/auto/quick/qquicktreeview/data/editdelegate.qml b/tests/auto/quick/qquicktreeview/data/editdelegate.qml new file mode 100644 index 0000000000..f8f908807f --- /dev/null +++ b/tests/auto/quick/qquicktreeview/data/editdelegate.qml @@ -0,0 +1,91 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Window + +Item { + width: 800 + height: 600 + + property alias treeView: treeView + property alias textInput: textInput + + TextInput { + id: textInput + width: 100 + height: 10 + } + + TreeView { + id: treeView + anchors.fill: parent + clip: true + + property Item editItem: null + property var editIndex + + selectionModel: ItemSelectionModel {} + + delegate: Rectangle { + id: root + implicitWidth: 100 + implicitHeight: 50 + clip: true + color: current || selected ? "lightgreen" : "white" + + property alias text: label.text + + readonly property real indent: 20 + readonly property real padding: 5 + + // Assigned to by TreeView: + required property TreeView treeView + required property bool isTreeNode + required property bool expanded + required property int hasChildren + required property int depth + required property bool current + required property bool selected + required property bool editing + + Text { + id: indicator + visible: root.isTreeNode && root.hasChildren + x: padding + (root.depth * root.indent) + text: root.expanded ? "▼" : "▶" + } + + Text { + id: label + x: padding + (root.isTreeNode ? (root.depth + 1) * root.indent : 0) + width: root.width - root.padding - x + clip: true + text: model.display + } + + TableView.editDelegate: TextInput { + id: editRoot + anchors.fill: parent + text: display + horizontalAlignment: TextInput.AlignHCenter + verticalAlignment: TextInput.AlignVCenter + activeFocusOnTab: true + + required property bool editing + + Component.onCompleted: { + treeView.editItem = editRoot + treeView.editIndex = treeView.index(row, column) + selectAll() + } + + Component.onDestruction: { + treeView.editItem = null + treeView.editIndex = treeView.index(-1, -1) + } + } + } + } + +} diff --git a/tests/auto/quick/qquicktreeview/testmodel.cpp b/tests/auto/quick/qquicktreeview/testmodel.cpp index a6a756ba68..231df4c5b0 100644 --- a/tests/auto/quick/qquicktreeview/testmodel.cpp +++ b/tests/auto/quick/qquicktreeview/testmodel.cpp @@ -129,7 +129,6 @@ bool TestModel::insertRows(int position, int rows, const QModelIndex &parent) return true; } - void insertColumnsRecursive(TreeItem *item, int row, int pos, int cols) { for (int col = 0; col < cols; col++) @@ -158,3 +157,11 @@ bool TestModel::insertColumns(int position, int cols, const QModelIndex &parent) endInsertColumns(); return true; } + +Qt::ItemFlags TestModel::flags(const QModelIndex &index) const +{ + if (m_editableIndices.contains(index)) + return Qt::ItemIsSelectable|Qt::ItemIsEnabled|Qt::ItemIsEditable; + + return Qt::ItemIsSelectable|Qt::ItemIsEnabled; +} diff --git a/tests/auto/quick/qquicktreeview/testmodel.h b/tests/auto/quick/qquicktreeview/testmodel.h index 182211e050..b7d10fb468 100644 --- a/tests/auto/quick/qquicktreeview/testmodel.h +++ b/tests/auto/quick/qquicktreeview/testmodel.h @@ -36,11 +36,14 @@ public: bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &index) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; int maxDepth() { return 4; } bool insertRows(int position, int rows, const QModelIndex &parent) override; bool insertColumns(int position, int cols, const QModelIndex &parent) override; + QList<QModelIndex> m_editableIndices; + private: QScopedPointer<TreeItem> m_rootItem; int m_columnCount = 5; diff --git a/tests/auto/quick/qquicktreeview/tst_qquicktreeview.cpp b/tests/auto/quick/qquicktreeview/tst_qquicktreeview.cpp index e16797c7fa..e0ddc3d731 100644 --- a/tests/auto/quick/qquicktreeview/tst_qquicktreeview.cpp +++ b/tests/auto/quick/qquicktreeview/tst_qquicktreeview.cpp @@ -7,6 +7,7 @@ #include <QtQuick/qquickview.h> #include <QtQuick/private/qquicktreeview_p.h> #include <QtQuick/private/qquicktreeview_p_p.h> +#include <QtQuick/private/qquicktextinput_p.h> #include <QtQml/qqmlengine.h> #include <QtQml/qqmlcontext.h> @@ -88,6 +89,10 @@ private slots: void sortTreeModel(); void sortTreeModelDynamic_data(); void sortTreeModelDynamic(); + void editUsingEditTriggers_data(); + void editUsingEditTriggers(); + void editOnNonEditableCell_data(); + void editOnNonEditableCell(); }; tst_qquicktreeview::tst_qquicktreeview() @@ -1204,6 +1209,335 @@ void tst_qquicktreeview::sortTreeModelDynamic() } } +void tst_qquicktreeview::editUsingEditTriggers_data() +{ + QTest::addColumn<QQuickTreeView::EditTriggers>("editTriggers"); + QTest::addColumn<bool>("interactive"); + + QTest::newRow("NoEditTriggers") << QQuickTreeView::EditTriggers(QQuickTreeView::NoEditTriggers); + QTest::newRow("SingleTapped") << QQuickTreeView::EditTriggers(QQuickTreeView::SingleTapped); + QTest::newRow("DoubleTapped") << QQuickTreeView::EditTriggers(QQuickTreeView::DoubleTapped); + QTest::newRow("SelectedTapped") << QQuickTreeView::EditTriggers(QQuickTreeView::SelectedTapped); + QTest::newRow("EditKeyPressed") << QQuickTreeView::EditTriggers(QQuickTreeView::EditKeyPressed); + QTest::newRow("AnyKeyPressed") << QQuickTreeView::EditTriggers(QQuickTreeView::AnyKeyPressed); + QTest::newRow("DoubleTapped | EditKeyPressed") + << QQuickTreeView::EditTriggers(QQuickTreeView::DoubleTapped | QQuickTreeView::EditKeyPressed); + QTest::newRow("SingleTapped | AnyKeyPressed") + << QQuickTreeView::EditTriggers(QQuickTreeView::SingleTapped | QQuickTreeView::AnyKeyPressed); +} + +void tst_qquicktreeview::editUsingEditTriggers() +{ + // Check that you can start to edit in treeView + // using the available edit triggers. + QFETCH(QQuickTreeView::EditTriggers, editTriggers); + LOAD_TREEVIEW("editdelegate.qml"); + + TestModel testModel; + treeView->setModel(QVariant::fromValue(&testModel)); + treeView->forceActiveFocus(); + treeView->expand(0); + + WAIT_UNTIL_POLISHED; + + QCOMPARE(treeView->editTriggers(), QQuickTreeView::EditKeyPressed); + treeView->setEditTriggers(editTriggers); + + const char kEditItem[] = "editItem"; + const char kEditIndex[] = "editIndex"; + + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + + const QPoint cell1(0, 0); + const QPoint cell2(1, 1); + const QModelIndex index1 = treeView->modelIndex(cell1); + const QModelIndex index2 = treeView->modelIndex(cell2); + const auto item1 = treeView->itemAtCell(cell1); + const auto item2 = treeView->itemAtCell(cell2); + QVERIFY(item1); + QVERIFY(item2); + + testModel.m_editableIndices = { index1, index2 }; + + QQuickWindow *window = treeView->window(); + + const QPoint localPos = QPoint(item1->width() - 1, item1->height() - 1); + const QPoint localPosOutside = QPoint(treeView->contentWidth() + 10, treeView->contentHeight() + 10); + const QPoint tapPos1 = window->contentItem()->mapFromItem(item1, localPos).toPoint(); + const QPoint tapPos2 = window->contentItem()->mapFromItem(item2, localPos).toPoint(); + const QPoint tapOutsideContentItem = window->contentItem()->mapFromItem(item2, localPosOutside).toPoint(); + + if (editTriggers & QQuickTreeView::SingleTapped) { + // edit cell 1 + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos1); + QCOMPARE(treeView->selectionModel()->currentIndex(), index1); + const auto editItem1 = treeView->property(kEditItem).value<QQuickItem *>(); + QVERIFY(editItem1); + QVERIFY(editItem1->hasActiveFocus()); + QCOMPARE(treeView->property(kEditIndex).value<QModelIndex>(), index1); + + // edit cell 2 (without closing the previous edit session first) + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos2); + QCOMPARE(treeView->selectionModel()->currentIndex(), index2); + const auto editItem2 = treeView->property(kEditItem).value<QQuickItem *>(); + QVERIFY(editItem2); + QVERIFY(editItem2->hasActiveFocus()); + QCOMPARE(treeView->property(kEditIndex).value<QModelIndex>(), index2); + + // single tap outside content item should close the editor + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapOutsideContentItem); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + QCOMPARE(treeView->selectionModel()->currentIndex(), index2); + } + + if (editTriggers & QQuickTreeView::DoubleTapped) { + // edit cell 1 + QTest::mouseDClick(window, Qt::LeftButton, Qt::NoModifier, tapPos1); + QCOMPARE(treeView->selectionModel()->currentIndex(), index1); + const auto editItem1 = treeView->property(kEditItem).value<QQuickItem *>(); + QVERIFY(editItem1); + QVERIFY(editItem1->hasActiveFocus()); + QCOMPARE(treeView->property(kEditIndex).value<QModelIndex>(), index1); + + // edit cell 2 (without closing the previous edit session first) + QTest::mouseDClick(window, Qt::LeftButton, Qt::NoModifier, tapPos2); + QCOMPARE(treeView->selectionModel()->currentIndex(), index2); + const auto editItem2 = treeView->property(kEditItem).value<QQuickItem *>(); + QVERIFY(editItem2); + QVERIFY(editItem2->hasActiveFocus()); + QCOMPARE(treeView->property(kEditIndex).value<QModelIndex>(), index2); + + // single tap outside the edit item should close the editor + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos1); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + QCOMPARE(treeView->selectionModel()->currentIndex(), index1); + + if (!(editTriggers & QQuickTreeView::SingleTapped)) { + // single tap on a cell should not open the editor + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos1); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + } + + // single tap outside content item should make sure editing ends + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapOutsideContentItem); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + } + + if (editTriggers & QQuickTreeView::SelectedTapped) { + // select cell first, then tap on it + treeView->selectionModel()->setCurrentIndex(index1, QItemSelectionModel::Select); + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos1); + QCOMPARE(treeView->selectionModel()->currentIndex(), index1); + const auto editItem1 = treeView->property(kEditItem).value<QQuickItem *>(); + QVERIFY(editItem1); + QVERIFY(editItem1->hasActiveFocus()); + QCOMPARE(treeView->property(kEditIndex).value<QModelIndex>(), index1); + + // tap on a non-selected cell. This should close the editor, and move + // the current index, but not begin to edit the cell. + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos2); + QCOMPARE(treeView->selectionModel()->currentIndex(), index2); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + + // tap on a non-selected cell while no editor is active + treeView->selectionModel()->setCurrentIndex(index1, QItemSelectionModel::NoUpdate); + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos2); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + QCOMPARE(treeView->selectionModel()->currentIndex(), index2); + + // tap on the current cell. This alone should not start an edit (unless it's also selected) + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos1); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + } + + if (editTriggers & QQuickTreeView::EditKeyPressed) { + treeView->selectionModel()->setCurrentIndex(index1, QItemSelectionModel::NoUpdate); + QTest::keyClick(window, Qt::Key_Return); + const auto editItem1 = treeView->property(kEditItem).value<QQuickItem *>(); + QVERIFY(editItem1); + QVERIFY(editItem1->hasActiveFocus()); + QCOMPARE(treeView->property(kEditIndex).value<QModelIndex>(), index1); + + // Pressing escape should close the editor + QTest::keyClick(window, Qt::Key_Escape); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + QCOMPARE(treeView->selectionModel()->currentIndex(), index1); + + // Pressing Enter to open the editor again + QTest::keyClick(window, Qt::Key_Enter); + const auto editItem2 = treeView->property(kEditItem).value<QQuickItem *>(); + QVERIFY(editItem2); + QVERIFY(editItem2->hasActiveFocus()); + QCOMPARE(treeView->property(kEditIndex).value<QModelIndex>(), index1); + + // single tap outside the edit item should close the editor + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos2); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + } + + if (editTriggers & QQuickTreeView::AnyKeyPressed) { + // Pressing key x should start to edit. And in case of AnyKeyPressed, we + // also replay the key event to the focus object. + treeView->selectionModel()->setCurrentIndex(index1, QItemSelectionModel::NoUpdate); + QTest::keyClick(window, Qt::Key_X); + QCOMPARE(treeView->selectionModel()->currentIndex(), index1); + QCOMPARE(treeView->property(kEditIndex).value<QModelIndex>(), index1); + auto textInput1 = treeView->property(kEditItem).value<QQuickTextInput *>(); + QVERIFY(textInput1); + QVERIFY(textInput1->hasActiveFocus()); + QCOMPARE(textInput1->text(), "x"); + + // Pressing escape should close the editor + QTest::keyClick(window, Qt::Key_Escape); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + QCOMPARE(treeView->selectionModel()->currentIndex(), index1); + + // Pressing a modifier key alone should not open the editor + QTest::keyClick(window, Qt::Key_Shift); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QTest::keyClick(window, Qt::Key_Control); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QTest::keyClick(window, Qt::Key_Alt); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QTest::keyClick(window, Qt::Key_Meta); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + + // Pressing enter should also start to edit. But this is a + // special case, we don't replay enter into the focus object. + treeView->selectionModel()->setCurrentIndex(index1, QItemSelectionModel::NoUpdate); + QTest::keyClick(window, Qt::Key_Enter); + QCOMPARE(treeView->selectionModel()->currentIndex(), index1); + QCOMPARE(treeView->property(kEditIndex).value<QModelIndex>(), index1); + auto textInput2 = treeView->property(kEditItem).value<QQuickTextInput *>(); + QVERIFY(textInput2); + QVERIFY(textInput2->hasActiveFocus()); + + if (!(editTriggers & QQuickTreeView::SingleTapped)) { + // single tap outside the edit item should close the editor + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos2); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + } + + // single tap outside content item should make sure editing ends + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapOutsideContentItem); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + } + + if (editTriggers == QQuickTreeView::NoEditTriggers) { + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos1); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + QTest::mouseDClick(window, Qt::LeftButton, Qt::NoModifier, tapPos1); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + treeView->selectionModel()->setCurrentIndex(index1, QItemSelectionModel::NoUpdate); + QTest::keyClick(window, Qt::Key_Return); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + QTest::keyClick(window, Qt::Key_Enter); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + QTest::keyClick(window, Qt::Key_X); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + } +} + +void tst_qquicktreeview::editOnNonEditableCell_data() +{ + QTest::addColumn<QQuickTreeView::EditTriggers>("editTriggers"); + + QTest::newRow("SingleTapped") << QQuickTreeView::EditTriggers(QQuickTreeView::SingleTapped); + QTest::newRow("DoubleTapped") << QQuickTreeView::EditTriggers(QQuickTreeView::DoubleTapped); + QTest::newRow("SelectedTapped") << QQuickTreeView::EditTriggers(QQuickTreeView::SelectedTapped); + QTest::newRow("EditKeyPressed") << QQuickTreeView::EditTriggers(QQuickTreeView::EditKeyPressed); + QTest::newRow("AnyKeyPressed") << QQuickTreeView::EditTriggers(QQuickTreeView::EditKeyPressed); +} + +void tst_qquicktreeview::editOnNonEditableCell() +{ + // Check that the user cannot edit a non-editable cell from the edit triggers. + // Note: we don't want TreeView to print out warnings in this case, since + // the user is not doing anything wrong. We only want to print out warnings if + // the application is calling edit() explicitly on a cell that cannot be edited + // (separate test below). + QFETCH(QQuickTreeView::EditTriggers, editTriggers); + LOAD_TREEVIEW("editdelegate.qml"); + + TestModel testModel; + treeView->setModel(QVariant::fromValue(&testModel)); + treeView->forceActiveFocus(); + treeView->expand(0); + + WAIT_UNTIL_POLISHED; + + const char kEditItem[] = "editItem"; + const char kEditIndex[] = "editIndex"; + + const QPoint cell(1, 1); + const QModelIndex index1 = treeView->modelIndex(cell); + const auto item = treeView->itemAtCell(cell); + QVERIFY(item); + + QQuickWindow *window = treeView->window(); + + const QPoint localPos = QPoint(item->width() - 1, item->height() - 1); + const QPoint tapPos = window->contentItem()->mapFromItem(item, localPos).toPoint(); + + if (editTriggers & QQuickTreeView::SingleTapped) { + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + } + + if (editTriggers & QQuickTreeView::DoubleTapped) { + QTest::mouseDClick(window, Qt::LeftButton, Qt::NoModifier, tapPos); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + } + + if (editTriggers & QQuickTreeView::SelectedTapped) { + // select cell first, then tap on it + treeView->selectionModel()->setCurrentIndex(index1, QItemSelectionModel::NoUpdate); + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + } + + if (editTriggers & QQuickTreeView::EditKeyPressed) { + treeView->selectionModel()->setCurrentIndex(index1, QItemSelectionModel::NoUpdate); + QTest::keyClick(window, Qt::Key_Enter); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + QTest::keyClick(window, Qt::Key_Return); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + } + + if (editTriggers & QQuickTreeView::AnyKeyPressed) { + treeView->selectionModel()->setCurrentIndex(index1, QItemSelectionModel::NoUpdate); + QTest::keyClick(window, Qt::Key_X); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + QTest::keyClick(window, Qt::Key_Enter); + QVERIFY(!treeView->property(kEditItem).value<QQuickItem *>()); + QVERIFY(!treeView->property(kEditIndex).value<QModelIndex>().isValid()); + } +} + QTEST_MAIN(tst_qquicktreeview) #include "tst_qquicktreeview.moc" |
