diff options
Diffstat (limited to 'tests/auto')
30 files changed, 1476 insertions, 47 deletions
diff --git a/tests/auto/accessibility/accessibility.pro b/tests/auto/accessibility/accessibility.pro index d8d5bb95..4cc101fb 100644 --- a/tests/auto/accessibility/accessibility.pro +++ b/tests/auto/accessibility/accessibility.pro @@ -12,5 +12,7 @@ include (../shared/util.pri) TESTDATA = data/* OTHER_FILES += \ - data/*.qml + data/defaults\*.qml \ + data/ordering\*.qml \ + data/override*.qml diff --git a/tests/auto/accessibility/data/ordering/page.qml b/tests/auto/accessibility/data/ordering/page.qml new file mode 100644 index 00000000..5eeb1530 --- /dev/null +++ b/tests/auto/accessibility/data/ordering/page.qml @@ -0,0 +1,23 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 + +Page { + title: "Page" + Accessible.role: Accessible.Pane + + header: Label { + text: "Header" + } + + footer: Label { + text: "Footer" + } + + Label { + text: "Content item 1" + } + + Label { + text: "Content item 2" + } +} diff --git a/tests/auto/accessibility/tst_accessibility.cpp b/tests/auto/accessibility/tst_accessibility.cpp index 6e5a37df..30bd4757 100644 --- a/tests/auto/accessibility/tst_accessibility.cpp +++ b/tests/auto/accessibility/tst_accessibility.cpp @@ -60,6 +60,7 @@ private slots: void override_data(); void override(); + void ordering(); private: QQmlEngine engine; }; @@ -284,6 +285,35 @@ void tst_accessibility::override() Q_UNUSED(text) #endif } +template <typename Predicate> +void a11yDescendants(QAccessibleInterface *iface, Predicate pred) +{ + for (int i = 0; i < iface->childCount(); ++i) { + if (QAccessibleInterface *child = iface->child(i)) { + pred(child); + a11yDescendants(child, pred); + } + } +} + +void tst_accessibility::ordering() +{ + QQmlComponent component(&engine); + component.loadUrl(testFileUrl("ordering/page.qml")); + + QScopedPointer<QObject> object(component.create()); + QVERIFY2(!object.isNull(), qPrintable(component.errorString())); + +#if QT_CONFIG(accessibility) + QQuickItem *item = findItem(object.data()); + QVERIFY(item); + QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(item); + QVERIFY(iface); + QStringList strings; + a11yDescendants(iface, [&](QAccessibleInterface *iface) {strings << iface->text(QAccessible::Name);}); + QCOMPARE(strings.join(QLatin1String(", ")), "Header, Content item 1, Content item 2, Footer"); +#endif +} QTEST_MAIN(tst_accessibility) diff --git a/tests/auto/controls/data/tst_abstractbutton.qml b/tests/auto/controls/data/tst_abstractbutton.qml index da5642cc..6181e526 100644 --- a/tests/auto/controls/data/tst_abstractbutton.qml +++ b/tests/auto/controls/data/tst_abstractbutton.qml @@ -910,4 +910,53 @@ TestCase { compare(clickedSpy.count, 1) compare(doubleClickedSpy.count, 1) } + + // It should be possible to quickly click a button whose doubleClicked signal + // is not connected to anything. + function test_fastClick() { + let control = createTemporaryObject(button, testCase, { text: "Hello" }) + verify(control) + + let pressedSpy = signalSpy.createObject(control, { target: control, signalName: "pressed" }) + verify(pressedSpy.valid) + + let releasedSpy = signalSpy.createObject(control, { target: control, signalName: "released" }) + verify(releasedSpy.valid) + + let clickedSpy = signalSpy.createObject(control, { target: control, signalName: "clicked" }) + verify(clickedSpy.valid) + + // Can't listen to doubleClicked because it would cause it to be emitted. + // We instead just check that clicked is emitted twice. + + mouseDoubleClickSequence(control) + compare(pressedSpy.count, 2) + compare(releasedSpy.count, 2) + compare(clickedSpy.count, 2) + + let touch = touchEvent(control) + touch.press(0, control) + touch.commit() + compare(pressedSpy.count, 3) + compare(releasedSpy.count, 2) + compare(clickedSpy.count, 2) + + touch.release(0, control) + touch.commit() + compare(pressedSpy.count, 3) + compare(releasedSpy.count, 3) + compare(clickedSpy.count, 3) + + touch.press(0, control) + touch.commit() + compare(pressedSpy.count, 4) + compare(releasedSpy.count, 3) + compare(clickedSpy.count, 3) + + touch.release(0, control) + touch.commit() + compare(pressedSpy.count, 4) + compare(releasedSpy.count, 4) + compare(clickedSpy.count, 4) + } } diff --git a/tests/auto/controls/data/tst_action.qml b/tests/auto/controls/data/tst_action.qml index 0e41b7f3..7ed4aa11 100644 --- a/tests/auto/controls/data/tst_action.qml +++ b/tests/auto/controls/data/tst_action.qml @@ -194,4 +194,37 @@ TestCase { verify(container) compare(container.indirect.nativeText, container.direct.nativeText); } + + Component { + id: shortcutCleanup + Item { + property alias page: page + property alias action: action + property alias menu: menu + Item { + id: page + Action { + id: action + text: "action" + shortcut: "Insert" + } + Menu { + id: menu + MenuItem { action: action } + } + } + } + } + + function test_shortcutCleanup() { + { + var container = createTemporaryObject(shortcutCleanup, testCase); + verify(container) + container.action.shortcut = "Delete" + container.menu.open() + container.page.destroy() + tryVerify(function() { return !container.page }) + } + keyClick(Qt.Key_Delete, Qt.NoModifier) + } } diff --git a/tests/auto/controls/data/tst_combobox.qml b/tests/auto/controls/data/tst_combobox.qml index 1c58372f..9bbea26d 100644 --- a/tests/auto/controls/data/tst_combobox.qml +++ b/tests/auto/controls/data/tst_combobox.qml @@ -157,6 +157,8 @@ TestCase { verify(control.delegate) verify(control.indicator) verify(control.popup) + verify(control.acceptableInput) + compare(control.inputMethodHints, Qt.ImhNoPredictiveText) } function test_array() { @@ -948,6 +950,56 @@ TestCase { tryCompare(control.popup, "visible", false) } + Component { + id: reopenCombo + Window { + property alias innerCombo: innerCombo + visible: true + width: 300 + height: 300 + ComboBox { + id: innerCombo + model: 10 + anchors.verticalCenter: parent.verticalCenter + } + } + } + + // This test checks that when reopening the combobox that it is still appears at the same y position as + // previously + function test_reopen_popup() { + var control = createTemporaryObject(reopenCombo, testCase) + verify(control) + var y = 0; + for (var i = 0; i < 2; ++i) { + tryCompare(control.innerCombo.popup, "visible", false) + control.innerCombo.y = control.height - (control.innerCombo.popup.contentItem.height * 0.99) + var popupYSpy = createTemporaryObject(signalSpy, testCase, {target: control.innerCombo.popup, signalName: "yChanged"}) + verify(popupYSpy.valid) + mousePress(control.innerCombo) + compare(control.innerCombo.pressed, true) + compare(control.innerCombo.popup.visible, false) + mouseRelease(control.innerCombo) + compare(control.innerCombo.pressed, false) + compare(control.innerCombo.popup.visible, true) + if (control.innerCombo.popup.enter) + tryCompare(control.innerCombo.popup.enter, "running", false) + // Check on the second opening that it has the same y position as before + if (i !== 0) { + // y should not have changed again + verify(popupYSpy.count === 0) + verify(y === control.innerCombo.popup.y) + } else { + // In some cases on the initial show, y changes more than once + verify(popupYSpy.count >= 1) + y = control.innerCombo.popup.y + mouseClick(control.innerCombo) + compare(control.innerCombo.pressed, false) + tryCompare(control.innerCombo.popup, "visible", false) + } + } + } + function test_mouse() { var control = createTemporaryObject(comboBox, testCase, {model: 3, hoverEnabled: false}) verify(control) @@ -1496,7 +1548,7 @@ TestCase { control.editText = "" compare(control.acceptableInput, true) control.editText = "" - control.forceActiveFocus() + control.contentItem.forceActiveFocus() keyPress(Qt.Key_A) compare(control.editText, "") keyPress(Qt.Key_A) @@ -1533,7 +1585,7 @@ TestCase { compare(control.currentIndex, 0) compare(control.currentText, "first") - control.forceActiveFocus() + control.contentItem.forceActiveFocus() compare(control.activeFocus, true) control.selectAll() @@ -1556,7 +1608,7 @@ TestCase { var control = createTemporaryObject(comboBox, testCase, {editable: true, model: ["Banana", "Coco", "Coconut", "Apple", "Cocomuffin"]}) verify(control) - control.forceActiveFocus() + control.contentItem.forceActiveFocus() verify(control.activeFocus) var acceptCount = 0 @@ -1707,7 +1759,7 @@ TestCase { var control = createTemporaryObject(keysAttachedBox, testCase) verify(control) - control.forceActiveFocus() + control.contentItem.forceActiveFocus() verify(control.activeFocus) verify(!control.gotit) @@ -1962,7 +2014,7 @@ TestCase { // Give the first ComboBox focus and type in 0 to select "Item 10" (default is "Item 1"). waitForRendering(comboBox1) - comboBox1.forceActiveFocus() + comboBox1.contentItem.forceActiveFocus() verify(comboBox1.activeFocus) keyClick(Qt.Key_0) compare(comboBox1.editText, "Item 10") @@ -1980,7 +2032,7 @@ TestCase { // Give focus back to the first ComboBox, and try the same thing except // with non-existing text; the currentIndex should not change. - comboBox1.forceActiveFocus() + comboBox1.contentItem.forceActiveFocus() verify(comboBox1.activeFocus) keySequence(StandardKey.SelectAll) compare(comboBox1.contentItem.selectedText, "Item 10") @@ -1992,4 +2044,62 @@ TestCase { compare(comboBox1.currentIndex, 9) compare(currentIndexSpy.count, 1) } + + // QTBUG-61021: text line should not be focused by default + // It causes (e.g. on Android) showing virtual keyboard when it is not needed + function test_doNotFocusTextLineByDefault() { + var control = createTemporaryObject(comboBox, testCase) + // Focus not set after creating combobox + verify(!control.activeFocus) + verify(!control.contentItem.focus) + + // After setting focus on combobox, text line should not be focused + control.forceActiveFocus() + verify(control.activeFocus) + verify(!control.contentItem.focus) + + // Text line is focused after intentional setting focus on it + control.contentItem.forceActiveFocus() + verify(control.activeFocus) + verify(control.contentItem.focus) + } + + Component { + id: intValidatorComponent + IntValidator { + bottom: 0 + top: 255 + } + } + + function test_acceptableInput_QTBUG_94307() { + let items = [ + { text: "A" }, + { text: "2" }, + { text: "3" } + ] + let control = createTemporaryObject(comboBox, testCase, {model: items, editable: true}) + verify(control) + + verify(control.acceptableInput) + compare(control.displayText, "A") + + let acceptableInputSpy = signalSpy.createObject(control, {target: control, signalName: "acceptableInputChanged"}) + verify(acceptableInputSpy.valid) + + let intValidator = intValidatorComponent.createObject(testCase) + verify(intValidator) + + control.validator = intValidator + + compare(acceptableInputSpy.count, 1) + compare(control.displayText, "A") + compare(control.acceptableInput, false) + + control.currentIndex = 1 + + compare(acceptableInputSpy.count, 2) + compare(control.displayText, "2") + compare(control.acceptableInput, true) + } } diff --git a/tests/auto/controls/data/tst_dial.qml b/tests/auto/controls/data/tst_dial.qml index 26f30c33..e3ad2b2f 100644 --- a/tests/auto/controls/data/tst_dial.qml +++ b/tests/auto/controls/data/tst_dial.qml @@ -691,4 +691,19 @@ TestCase { compare(control.pressed, false); compare(control.position, data.expectedPosition); } + + function test_integerStepping() { + var dial = createTemporaryObject(dialComponent, testCase) + verify(dial) + + dial.from = 1 + dial.to = 8 + dial.stepSize = 1 + + for (let i = 1; i < 8; ++i) { + // compare as strings to avoid a fuzzy compare; we want an exact match + compare(""+dial.value, ""+1) + keyClick(Qt.Key_Right) + } + } } diff --git a/tests/auto/controls/data/tst_dialogbuttonbox.qml b/tests/auto/controls/data/tst_dialogbuttonbox.qml index a651713a..63cefa46 100644 --- a/tests/auto/controls/data/tst_dialogbuttonbox.qml +++ b/tests/auto/controls/data/tst_dialogbuttonbox.qml @@ -398,7 +398,7 @@ TestCase { // QTBUG-72886 function test_changeCustomButtonText(data) { - var control = createTemporaryObject(customButtonBox, testCase, {}) + var control = createTemporaryObject(data.component, testCase, {}) verify(control) var listView = control.contentItem @@ -418,6 +418,88 @@ TestCase { } Component { + id: customButtonBoxInDialog + + Dialog { + width: 300 + visible: true + + footer: DialogButtonBox { + objectName: "customButtonBoxInDialog" + alignment: Qt.AlignRight + + property alias okButton: okButton + + Button { + id: okButton + text: "OK" + + DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + } + } + } + } + + Component { + id: customButtonBoxTwoButtonsInDialog + + Dialog { + width: 300 + visible: true + + footer: DialogButtonBox { + objectName: "customButtonBoxTwoButtonsInDialog" + alignment: Qt.AlignRight + + property alias okButton: okButton + + Button { + id: okButton + text: "OK" + + DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + } + Button { + text: "Cancel" + + DialogButtonBox.buttonRole: DialogButtonBox.RejectRole + } + } + } + } + + function test_changeCustomButtonImplicitWidth_data() { + return [ + { tag: "oneButton", component: customButtonBoxInDialog }, + { tag: "twoButtons", component: customButtonBoxTwoButtonsInDialog }, + ] + } + + // QTBUG-102558 + function test_changeCustomButtonImplicitWidth(data) { + let dialog = createTemporaryObject(data.component, testCase, {}) + verify(dialog) + + let control = dialog.footer + verify(control) + + let listView = control.contentItem + waitForRendering(listView) + + let button = control.okButton + verify(button) + button.implicitWidth *= 1.5 + + // The button should never go outside of the box. + tryVerify(function() { return button.mapToItem(control, 0, 0).x >= 0 }, + 1000, "Expected left edge of button to be within left edge of DialogButtonBox (i.e. greater than or equal to 0)" + + ", but it's " + button.mapToItem(control, 0, 0).x) + tryVerify(function() { return button.mapToItem(control, 0, 0).x + button.width <= control.width }, + 1000, "Expected right edge of button to be within right edge of DialogButtonBox (i.e. less than or equal to " + + control.width + "), but it's " + (button.mapToItem(control, 0, 0).x + button.width)) + } + + Component { id: noRolesDialog Dialog { diff --git a/tests/auto/controls/data/tst_rangeslider.qml b/tests/auto/controls/data/tst_rangeslider.qml index 24f8a207..42f5bad5 100644 --- a/tests/auto/controls/data/tst_rangeslider.qml +++ b/tests/auto/controls/data/tst_rangeslider.qml @@ -159,6 +159,43 @@ TestCase { compare(control.first.position, 0.5) } + function test_setToFromUpdatesHandles() { + var control = createTemporaryObject(sliderComponent, testCase, { from: 0, to: 100, "first.value": 50, "second.value": 75 }) + verify(control) + + let firstPos = control.first.position + let secondPos = control.second.position + + var firstPosChangesSpy = signalSpy.createObject(control, {target: control.first, signalName: "positionChanged"}) + verify(firstPosChangesSpy.valid) + + var secondPosChangesSpy = signalSpy.createObject(control, {target: control.second, signalName: "positionChanged"}) + verify(secondPosChangesSpy.valid) + + // Increasing the 'to' value, so the positions of the handles should be + // moved to the left (become smaller) + control.to = 200; + compare(firstPosChangesSpy.count, 1) + compare(secondPosChangesSpy.count, 1) + verify(control.first.position < firstPos) + verify(control.second.position < secondPos) + + // resetting the values + control.to = 100 + firstPosChangesSpy.clear() + secondPosChangesSpy.clear() + firstPos = control.first.position + secondPos = control.second.position + + // Decreasing the 'from' value, so the positions of the handles should + // be moved to the right (become larger) + control.from = -100 + compare(firstPosChangesSpy.count, 1) + compare(secondPosChangesSpy.count, 1) + verify(control.first.position > firstPos) + verify(control.second.position > secondPos) + } + function test_setValues() { var control = createTemporaryObject(sliderComponent, testCase) verify(control) @@ -612,7 +649,7 @@ TestCase { } function test_overlappingHandles() { - var control = createTemporaryObject(sliderComponent, testCase, { orientation: data.orientation }) + var control = createTemporaryObject(sliderComponent, testCase) verify(control) // By default, we force the second handle to be after the first in @@ -1064,17 +1101,16 @@ TestCase { function test_valueAt_data() { return [ - { tag: "0.0..1.0", from: 0.0, to: 1.0, values: [0.0, 0.2, 0.5, 1.0] }, - { tag: "0..100", from: 0, to: 100, values: [0, 20, 50, 100] }, - { tag: "100..-100", from: 100, to: -100, values: [100, 60, 0, -100] }, - { tag: "-7..7", from: -7, to: 7, stepSize: 1.0, values: [-7.0, -4.0, 0.0, 7.0] }, - { tag: "-3..7", from: -3, to: 7, stepSize: 5.0, values: [-3.0, -3.0, 2.0, 7.0] }, + { tag: "0.0..1.0", properties: { from: 0.0, to: 1.0 }, values: [0.0, 0.2, 0.5, 1.0] }, + { tag: "0..100", properties: { from: 0, to: 100 }, values: [0, 20, 50, 100] }, + { tag: "100..-100", properties: { from: 100, to: -100 }, values: [100, 60, 0, -100] }, + { tag: "-7..7", properties: { from: -7, to: 7, stepSize: 1.0 }, values: [-7.0, -4.0, 0.0, 7.0] }, + { tag: "-3..7", properties: { from: -3, to: 7, stepSize: 5.0 }, values: [-3.0, -3.0, 2.0, 7.0] }, ] } function test_valueAt(data) { - var control = createTemporaryObject(sliderComponent, testCase, - { from: data.from, to: data.to, stepSize: data.stepSize }) + var control = createTemporaryObject(sliderComponent, testCase, data.properties) verify(control) compare(control.valueAt(0.0), data.values[0]) diff --git a/tests/auto/controls/data/tst_scrollbar.qml b/tests/auto/controls/data/tst_scrollbar.qml index 9d21fa8b..b018899e 100644 --- a/tests/auto/controls/data/tst_scrollbar.qml +++ b/tests/auto/controls/data/tst_scrollbar.qml @@ -189,6 +189,36 @@ TestCase { compare(horizontal.width, oldWidth) } + function test_attachTwice() { + let container = createTemporaryObject(flickable, testCase) + verify(container) + waitForRendering(container) + + container.ScrollBar.vertical = scrollBar.createObject(container, { objectName: "oldVerticalScrollBar" }) + verify(container.ScrollBar.vertical) + let oldVerticalScrollBar = findChild(container, "oldVerticalScrollBar") + verify(oldVerticalScrollBar) + verify(oldVerticalScrollBar.visible) + + container.ScrollBar.horizontal = scrollBar.createObject(container, { objectName: "oldHorizontalScrollBar" }) + verify(container.ScrollBar.horizontal) + let oldHorizontalScrollBar = findChild(container, "oldHorizontalScrollBar") + verify(oldHorizontalScrollBar) + verify(oldHorizontalScrollBar.visible) + + container.ScrollBar.vertical = scrollBar.createObject(container, { objectName: "newVerticalScrollBar" }) + let newVerticalScrollBar = findChild(container, "newVerticalScrollBar") + verify(newVerticalScrollBar) + verify(newVerticalScrollBar.visible) + verify(!oldVerticalScrollBar.visible) + + container.ScrollBar.horizontal = scrollBar.createObject(container, { objectName: "newHorizontalScrollBar" }) + let newHorizontalScrollBar = findChild(container, "newHorizontalScrollBar") + verify(newHorizontalScrollBar) + verify(newHorizontalScrollBar.visible) + verify(!oldHorizontalScrollBar.visible) + } + function test_mouse_data() { return [ { tag: "horizontal", properties: { visible: true, orientation: Qt.Horizontal, width: testCase.width } }, diff --git a/tests/auto/controls/data/tst_scrollview.qml b/tests/auto/controls/data/tst_scrollview.qml index 87c39509..cd493118 100644 --- a/tests/auto/controls/data/tst_scrollview.qml +++ b/tests/auto/controls/data/tst_scrollview.qml @@ -71,6 +71,11 @@ TestCase { } Component { + id: scrollBarComponent + ScrollBar {} + } + + Component { id: scrollableLabel ScrollView { Label { @@ -188,6 +193,15 @@ TestCase { } } } + Component { + id: scrollableTextAreaWithSibling + ScrollView { + Item { + } + TextArea { + } + } + } function test_scrollBars() { var control = createTemporaryObject(scrollView, testCase, {width: 200, height: 200}) @@ -502,4 +516,111 @@ TestCase { compare(control.contentWidth, flickable.contentWidth) compare(control.contentHeight, flickable.contentHeight) } + + function test_textAreaWithSibling() { + // Checks that it does not crash when the ScrollView is deleted + var control = createTemporaryObject(scrollableTextAreaWithSibling, testCase) + verify(control) + } + + Component { + id: zeroSizedContentItemComponent + ScrollView { + width: 100 + height: 100 + contentItem: Item {} + } + } + + function test_zeroSizedContentItem() { + ignoreWarning(/ScrollView only supports Flickable types as its contentItem/) + let control = createTemporaryObject(zeroSizedContentItemComponent, testCase) + verify(control) + + let verticalScrollBar = control.ScrollBar.vertical + verify(verticalScrollBar) + // Scrolling a ScrollView with a zero-sized contentItem shouldn't crash. + mouseDrag(verticalScrollBar, verticalScrollBar.width / 2, verticalScrollBar.height / 2, 0, 50) + + let horizontalScrollBar = control.ScrollBar.horizontal + verify(verticalScrollBar) + mouseDrag(horizontalScrollBar, horizontalScrollBar.width / 2, horizontalScrollBar.height / 2, 50, 0) + } + + function test_customScrollBars() { + let control = createTemporaryObject(scrollView, testCase) + verify(control) + control.ScrollBar.vertical.objectName = "oldVerticalScrollBar" + control.ScrollBar.horizontal.objectName = "oldHorizontalScrollBar" + + let oldVerticalScrollBar = control.ScrollBar.vertical + verify(oldVerticalScrollBar) + compare(oldVerticalScrollBar.objectName, "oldVerticalScrollBar") + + let oldHorizontalScrollBar = control.ScrollBar.horizontal + verify(oldHorizontalScrollBar) + compare(oldHorizontalScrollBar.objectName, "oldHorizontalScrollBar") + + // Create the new scroll bars imperatively so that we can easily access the old ones. + control.ScrollBar.vertical = scrollBarComponent.createObject(control, { objectName: "newVerticalScrollBar" }) + verify(control.ScrollBar.vertical) + let newVerticalScrollBar = findChild(control, "newVerticalScrollBar") + verify(newVerticalScrollBar) + verify(newVerticalScrollBar.visible) + verify(!oldVerticalScrollBar.visible) + + control.ScrollBar.horizontal = scrollBarComponent.createObject(control, { objectName: "newHorizontalScrollBar" }) + verify(control.ScrollBar.horizontal) + let newHorizontalScrollBar = findChild(control, "newHorizontalScrollBar") + verify(newHorizontalScrollBar) + verify(newHorizontalScrollBar.visible) + verify(!oldHorizontalScrollBar.visible) + } + + Component { + id: bindingToContentItemAndStandaloneFlickable + + Item { + width: 200 + height: 200 + + property alias scrollView: scrollView + + ScrollView { + id: scrollView + anchors.fill: parent + contentItem: listView + + property Item someBinding: contentItem + } + ListView { + id: listView + model: 10 + delegate: ItemDelegate { + text: modelData + width: listView.width + } + } + } + } + + // Tests that scroll bars show up for a ScrollView where + // - its contentItem is declared as a standalone, separate item + // - there is a binding to contentItem (which causes a default Flickable to be created) + function test_bindingToContentItemAndStandaloneFlickable() { + let root = createTemporaryObject(bindingToContentItemAndStandaloneFlickable, testCase) + verify(root) + + let control = root.scrollView + let verticalScrollBar = control.ScrollBar.vertical + let horizontalScrollBar = control.ScrollBar.horizontal + compare(verticalScrollBar.parent, control) + compare(horizontalScrollBar.parent, control) + verify(verticalScrollBar.visible) + verify(horizontalScrollBar.visible) + + mouseDrag(verticalScrollBar, verticalScrollBar.width / 2, verticalScrollBar.height / 2, 0, 50) + verify(verticalScrollBar.active) + verify(horizontalScrollBar.active) + } } diff --git a/tests/auto/controls/data/tst_slider.qml b/tests/auto/controls/data/tst_slider.qml index 280138c5..bf65e486 100644 --- a/tests/auto/controls/data/tst_slider.qml +++ b/tests/auto/controls/data/tst_slider.qml @@ -831,16 +831,16 @@ TestCase { function test_valueAt_data() { return [ - { tag: "0.0..1.0", from: 0.0, to: 1.0, values: [0.0, 0.2, 0.5, 1.0] }, - { tag: "0..100", from: 0, to: 100, values: [0, 20, 50, 100] }, - { tag: "100..-100", from: 100, to: -100, values: [100, 60, 0, -100] }, - { tag: "-7..7", from: -7, to: 7, stepSize: 1.0, values: [-7.0, -4.0, 0.0, 7.0] }, - { tag: "-3..7", from: -3, to: 7, stepSize: 5.0, values: [-3.0, -3.0, 2.0, 7.0] }, + { tag: "0.0..1.0", properties: { from: 0.0, to: 1.0 }, values: [0.0, 0.2, 0.5, 1.0] }, + { tag: "0..100", properties: { from: 0, to: 100 }, values: [0, 20, 50, 100] }, + { tag: "100..-100", properties: { from: 100, to: -100 }, values: [100, 60, 0, -100] }, + { tag: "-7..7", properties: { from: -7, to: 7, stepSize: 1.0 }, values: [-7.0, -4.0, 0.0, 7.0] }, + { tag: "-3..7", properties: { from: -3, to: 7, stepSize: 5.0 }, values: [-3.0, -3.0, 2.0, 7.0] }, ] } function test_valueAt(data) { - var control = createTemporaryObject(slider, testCase, {from: data.from, to: data.to, stepSize: data.stepSize}) + let control = createTemporaryObject(slider, testCase, data.properties) verify(control) compare(control.valueAt(0.0), data.values[0]) @@ -923,4 +923,39 @@ TestCase { touch.release(0, control, x0 + data.dx2, y0 + data.dy2).commit() } + + Component { + id: listViewWithPressDelayAndSliders + ListView { + width: 300 + height: 500 + model: 3 + pressDelay: 150 + delegate: Slider { + width: 300 + height: 150 + } + } + } + + function test_listViewWithPressDelay() { + var listView = createTemporaryObject(listViewWithPressDelayAndSliders, testCase, { width: parent.width, height: parent.height }) + verify(listView) + var control = listView.itemAtIndex(0) + verify(control) + var movedSpy = signalSpy.createObject(control, {target: control, signalName: "moved"}) + verify(movedSpy.valid) + + var touch = touchEvent(control) + var x0 = control.handle.x + control.handle.width * 0.5 + var y0 = control.handle.y + control.handle.height * 0.5 + touch.press(0, control, x0, y0).commit() + tryCompare(control, "pressed", true) + fuzzyCompare(control.value, 0, 0.01) + + touch.move(0, control, x0 + 100, y0).commit() + tryVerify(function() { return (control.value > 0.3); }) // around 0.35, depending on style + tryVerify(function() { return (movedSpy.count > 0); }) // ideally == 1, but in Material and Fusion it's 2 + touch.release(0) + } } diff --git a/tests/auto/controls/data/tst_spinbox.qml b/tests/auto/controls/data/tst_spinbox.qml index adb70a30..14ebba21 100644 --- a/tests/auto/controls/data/tst_spinbox.qml +++ b/tests/auto/controls/data/tst_spinbox.qml @@ -244,17 +244,17 @@ TestCase { function test_keys_data() { return [ - { tag: "1", from: 1, to: 10, value: 1, stepSize: 1, upSteps: [2,3,4], downSteps: [3,2,1,1] }, - { tag: "2", from: 1, to: 10, value: 10, stepSize: 2, upSteps: [10,10], downSteps: [8,6,4] }, - { tag: "25", from: 0, to: 100, value: 50, stepSize: 25, upSteps: [75,100,100], downSteps: [75,50,25,0,0] }, - { tag: "wrap1", wrap: true, from: 1, to: 10, value: 1, stepSize: 1, upSteps: [2,3], downSteps: [2,1,10,9] }, - { tag: "wrap2", wrap: true, from: 1, to: 10, value: 10, stepSize: 2, upSteps: [1,3,5], downSteps: [3,1,10,8,6] }, - { tag: "wrap25", wrap: true, from: 0, to: 100, value: 50, stepSize: 25, upSteps: [75,100,0,25], downSteps: [0,100,75] } + { tag: "1", properties: { from: 1, to: 10, value: 1, stepSize: 1 }, upSteps: [2,3,4], downSteps: [3,2,1,1] }, + { tag: "2", properties: { from: 1, to: 10, value: 10, stepSize: 2 }, upSteps: [10,10], downSteps: [8,6,4] }, + { tag: "25", properties: { from: 0, to: 100, value: 50, stepSize: 25 }, upSteps: [75,100,100], downSteps: [75,50,25,0,0] }, + { tag: "wrap1", properties: { wrap: true, from: 1, to: 10, value: 1, stepSize: 1 }, upSteps: [2,3], downSteps: [2,1,10,9] }, + { tag: "wrap2", properties: { wrap: true, from: 1, to: 10, value: 10, stepSize: 2 }, upSteps: [1,3,5], downSteps: [3,1,10,8,6] }, + { tag: "wrap25", properties: { wrap: true, from: 0, to: 100, value: 50, stepSize: 25 }, upSteps: [75,100,0,25], downSteps: [0,100,75] } ] } function test_keys(data) { - var control = createTemporaryObject(spinBox, testCase, {wrap: data.wrap, from: data.from, to: data.to, value: data.value, stepSize: data.stepSize}) + var control = createTemporaryObject(spinBox, testCase, data.properties) verify(control) var upPressedCount = 0 @@ -396,12 +396,12 @@ TestCase { function test_wheel_data() { return [ - { tag: "1", from: 1, to: 10, value: 1, stepSize: 1, upSteps: [2,3,4], downSteps: [3,2,1,1] }, - { tag: "2", from: 1, to: 10, value: 10, stepSize: 2, upSteps: [10,10], downSteps: [8,6,4] }, - { tag: "25", from: 0, to: 100, value: 50, stepSize: 25, upSteps: [75,100,100], downSteps: [75,50,25,0,0] }, - { tag: "wrap1", wrap: true, from: 1, to: 10, value: 1, stepSize: 1, upSteps: [2,3], downSteps: [2,1,10,9] }, - { tag: "wrap2", wrap: true, from: 1, to: 10, value: 10, stepSize: 2, upSteps: [1,3,5], downSteps: [3,1,10,8,6] }, - { tag: "wrap25", wrap: true, from: 0, to: 100, value: 50, stepSize: 25, upSteps: [75,100,0,25], downSteps: [0,100,75] } + { tag: "1", properties: { from: 1, to: 10, value: 1, stepSize: 1 }, upSteps: [2,3,4], downSteps: [3,2,1,1] }, + { tag: "2", properties: { from: 1, to: 10, value: 10, stepSize: 2 }, upSteps: [10,10], downSteps: [8,6,4] }, + { tag: "25", properties: { from: 0, to: 100, value: 50, stepSize: 25 }, upSteps: [75,100,100], downSteps: [75,50,25,0,0] }, + { tag: "wrap1", properties: { wrap: true, from: 1, to: 10, value: 1, stepSize: 1 }, upSteps: [2,3], downSteps: [2,1,10,9] }, + { tag: "wrap2", properties: { wrap: true, from: 1, to: 10, value: 10, stepSize: 2 }, upSteps: [1,3,5], downSteps: [3,1,10,8,6] }, + { tag: "wrap25", properties: { wrap: true, from: 0, to: 100, value: 50, stepSize: 25 }, upSteps: [75,100,0,25], downSteps: [0,100,75] } ] } @@ -409,7 +409,8 @@ TestCase { var ma = createTemporaryObject(mouseArea, testCase, {width: 100, height: 100}) verify(ma) - var control = spinBox.createObject(ma, {wrap: data.wrap, from: data.from, to: data.to, value: data.value, stepSize: data.stepSize, wheelEnabled: true}) + data.properties.wheelEnabled = true + var control = spinBox.createObject(ma, data.properties) verify(control) var valueModifiedCount = 0 diff --git a/tests/auto/controls/data/tst_splitview.qml b/tests/auto/controls/data/tst_splitview.qml index ae8179b2..aa167472 100644 --- a/tests/auto/controls/data/tst_splitview.qml +++ b/tests/auto/controls/data/tst_splitview.qml @@ -1838,8 +1838,7 @@ TestCase { var flickable = createTemporaryObject(flickableComponent, testCase) verify(flickable) - var control = threeSizedItemsComponent.createObject(flickable.contentItem, - { "orientation": data.orientation }) + var control = threeSizedItemsComponent.createObject(flickable.contentItem) verify(control) control.anchors.fill = undefined diff --git a/tests/auto/controls/data/tst_swipedelegate.qml b/tests/auto/controls/data/tst_swipedelegate.qml index d37ea42b..255dd881 100644 --- a/tests/auto/controls/data/tst_swipedelegate.qml +++ b/tests/auto/controls/data/tst_swipedelegate.qml @@ -702,7 +702,9 @@ TestCase { property alias removeAnimation: onRemoveAnimation - ListView.onRemove: SequentialAnimation { + ListView.onRemove: onRemoveAnimation.start() + + SequentialAnimation { id: onRemoveAnimation PropertyAction { @@ -1273,10 +1275,10 @@ TestCase { // When this happens, it will grab the mouse and hence we must clear // that action's pressed state so that it doesn't stay pressed after releasing. function test_dragSideAction() { - var listView = createTemporaryObject(removableDelegatesComponent, testCase); + let listView = createTemporaryObject(removableDelegatesComponent, testCase); verify(listView); - var control = listView.itemAt(0, 0); + let control = listView.itemAt(0, 0); verify(control); // Expose the side action. @@ -1284,15 +1286,43 @@ TestCase { verify(control.swipe.leftItem); tryCompare(control.swipe, "complete", true); - var pressedSpy = signalSpyComponent.createObject(control, + let pressedSpy = signalSpyComponent.createObject(control, { target: control.swipe.leftItem.SwipeDelegate, signalName: "pressedChanged" }); verify(pressedSpy); verify(pressedSpy.valid); + let movingHorizontallySpy = createTemporaryObject(signalSpyComponent, testCase, + { target: listView, signalName: "movingHorizontallyChanged" }) + verify(movingHorizontallySpy) + verify(movingHorizontallySpy.valid) + + let movingVerticallySpy = createTemporaryObject(signalSpyComponent, testCase, + { target: listView, signalName: "movingVerticallyChanged" }) + verify(movingVerticallySpy) + verify(movingVerticallySpy.valid) + + let flickingHorizontallySpy = createTemporaryObject(signalSpyComponent, testCase, + { target: listView, signalName: "flickingHorizontallyChanged" }) + verify(flickingHorizontallySpy) + verify(flickingHorizontallySpy.valid) + + let flickingVerticallySpy = createTemporaryObject(signalSpyComponent, testCase, + { target: listView, signalName: "flickingVerticallyChanged" }) + verify(flickingVerticallySpy) + verify(flickingVerticallySpy.valid) + + // Drag the ListView vertically; its contentY should change. mouseDrag(listView, 20, 20, 0, listView.height); compare(pressedSpy.count, 2); - verify(listView.contentY !== 0); + // Wait for it to stop moving. + tryCompare(listView, "flickingVertically", false) + + // 2 because it should change to true then false. + compare(movingHorizontallySpy.count, 0) + compare(movingVerticallySpy.count, 2) + compare(flickingHorizontallySpy.count, 0) + compare(flickingVerticallySpy.count, 2) compare(control.swipe.leftItem.SwipeDelegate.pressed, false); } @@ -1713,4 +1743,39 @@ TestCase { break; } } + + function test_resizeParent() { + let container = createTemporaryObject(itemComponent, testCase, { objectName: "container", width: 100, height: 200 }) + verify(container) + + let control = swipeDelegateComponent.createObject(container, { width: Qt.binding(function() { return container.width }) }) + verify(control) + + // Resize while closed. + container.width = 200 + compare(container.width, 200) + compare(control.width, 200) + compare(control.background.width, 200) + compare(control.contentItem.width, 200 - control.leftPadding - control.rightPadding) + + // Return to original size. + container.width = 100 + compare(control.width, 100) + compare(control.background.width, 100) + compare(control.contentItem.width, 100 - control.leftPadding - control.rightPadding) + + // Swipe to the left to open. + swipe(control, 0, -1.0) + // Nothing should have changed except positions. + compare(control.width, 100) + compare(control.background.width, 100) + compare(control.contentItem.width, 100 - control.leftPadding - control.rightPadding) + + // Resize while open. + container.width = 200 + // The items should fill the width as usual. + compare(control.width, 200) + compare(control.background.width, 200) + compare(control.contentItem.width, 200 - control.leftPadding - control.rightPadding) + } } diff --git a/tests/auto/controls/data/tst_swipeview.qml b/tests/auto/controls/data/tst_swipeview.qml index 5775491c..07d0cc0c 100644 --- a/tests/auto/controls/data/tst_swipeview.qml +++ b/tests/auto/controls/data/tst_swipeview.qml @@ -623,4 +623,67 @@ TestCase { compare(control.rectanglePressCount, 1) compare(control.rectangleReleaseCount, 1) } + + // We have a particular customer who came up with this hack to make SwipeView wrap around at the end. + // It's not advisible, and perhaps the test can be removed when we add a bool wrap property. + Component { + id: pathViewWorkaroundComponent + + SwipeView { + id: swipeView + anchors.left: parent.left + width: 100 + height: 100 + clip: true + Repeater { + id: repeater + objectName: "peter" + delegate: Rectangle { + id: rect + color: "#ffff00" + border.color: "black" + width: 100 + height: 100 + Text { + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + font.pixelSize: 24 + text: model.index + } + } + } + contentItem: PathView { + id: pathview + + preferredHighlightBegin: 0.5 + preferredHighlightEnd: 0.5 + model: swipeView.contentModel + + path: Path { + id: path + startX: (swipeView.width / 2 * -1) * (swipeView.count - 1) + startY: swipeView.height / 2 + PathLine { + relativeX: swipeView.width * swipeView.count + relativeY: 0 + } + } + } + } + } + + function test_child_pathview() { + const control = createTemporaryObject(pathViewWorkaroundComponent, testCase) + verify(control) + const repeater = control.children[0].children[0] + const spy = signalSpy.createObject(repeater, {target: repeater, signalName: "itemAdded"}) + repeater.model = 1 + tryCompare(spy, "count", 1) + const rect = repeater.itemAt(0) + tryCompare(rect, "visible", true) + if (Qt.platform.pluginName === "offscreen") + skip("grabImage() is not functional on the offscreen platform (QTBUG-63185)") + var image = grabImage(control) + compare(image.pixel(3, 3), "#ffff00") + } } diff --git a/tests/auto/controls/data/tst_switch.qml b/tests/auto/controls/data/tst_switch.qml index b3fab41c..10b6baa0 100644 --- a/tests/auto/controls/data/tst_switch.qml +++ b/tests/auto/controls/data/tst_switch.qml @@ -608,4 +608,27 @@ TestCase { mouseClick(control.indicator) verify(control.activeFocus) } + + Component { + id: deletionOrder1 + Item { + Image { id: innerImage } + Switch { indicator: innerImage } + } + } + + Component { + id: deletionOrder2 + Item { + Switch { indicator: innerImage } + Image { id: innerImage } + } + } + + function test_deletionOrder() { + var control1 = createTemporaryObject(deletionOrder1, testCase) + verify(control1) + var control2 = createTemporaryObject(deletionOrder2, testCase) + verify(control2) + } } diff --git a/tests/auto/controls/data/tst_tumbler.qml b/tests/auto/controls/data/tst_tumbler.qml index 5b3ef6e3..fd8b7d92 100644 --- a/tests/auto/controls/data/tst_tumbler.qml +++ b/tests/auto/controls/data/tst_tumbler.qml @@ -1257,4 +1257,25 @@ TestCase { tumbler.height *= 1.4 tryCompare(delegate, "displacement", 0) } + + //QTBUG-84426 + Component { + id: initialCurrentIndexTumbler + + Tumbler { + anchors.centerIn: parent + width: 60 + height: 200 + delegate: Text {text: modelData} + model: 10 + currentIndex: 4 + } + } + + function test_initialCurrentIndex() { + var tumbler = createTemporaryObject(initialCurrentIndexTumbler, testCase, {wrap: true}); + compare(tumbler.currentIndex, 4); + tumbler = createTemporaryObject(initialCurrentIndexTumbler, testCase, {wrap: false}); + compare(tumbler.currentIndex, 4); + } } diff --git a/tests/auto/qquickapplicationwindow/data/layoutLayout.qml b/tests/auto/qquickapplicationwindow/data/layoutLayout.qml new file mode 100644 index 00000000..24eeb57b --- /dev/null +++ b/tests/auto/qquickapplicationwindow/data/layoutLayout.qml @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.15 + +ApplicationWindow { + width: 200 + height: 200 + visible: true + + header: RowLayout { + Rectangle { color: "red"; implicitWidth: 20; implicitHeight: 20; Layout.fillWidth: true} + } + footer: ColumnLayout { + Rectangle { color: "green"; implicitWidth: 20; implicitHeight: 20; Layout.fillWidth: true} + } +} diff --git a/tests/auto/qquickapplicationwindow/tst_qquickapplicationwindow.cpp b/tests/auto/qquickapplicationwindow/tst_qquickapplicationwindow.cpp index 80124230..ebdc763a 100644 --- a/tests/auto/qquickapplicationwindow/tst_qquickapplicationwindow.cpp +++ b/tests/auto/qquickapplicationwindow/tst_qquickapplicationwindow.cpp @@ -76,6 +76,7 @@ private slots: void focusAfterPopupClosed(); void clearFocusOnDestruction(); void layout(); + void layoutLayout(); void componentComplete(); }; @@ -862,6 +863,42 @@ void tst_QQuickApplicationWindow::layout() QCOMPARE(content->height(), qreal(window->height())); } +void tst_QQuickApplicationWindow::layoutLayout() +{ + QQmlEngine engine; + QQmlComponent component(&engine); + component.loadUrl(testFileUrl("layoutLayout.qml")); + QScopedPointer<QObject> object(component.create()); + QVERIFY2(!object.isNull(), qPrintable(component.errorString())); + + QQuickApplicationWindow* window = qobject_cast<QQuickApplicationWindow*>(object.data()); + QVERIFY(window); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + QQuickItem *content = window->contentItem(); + QVERIFY(content); + QQuickItem *header = window->header(); + QVERIFY(header); + QQuickItem *footer = window->footer(); + QVERIFY(footer); + + QQuickItem *headerChild = header->findChild<QQuickItem*>(); + QVERIFY(headerChild); + QCOMPARE(header->x(), 0.0); + QCOMPARE(header->y(), -header->height()); + QCOMPARE(header->width(), qreal(window->width())); + QCOMPARE(headerChild->width(), qreal(window->width())); + QVERIFY(header->height() > 0); + + QQuickItem *footerChild = header->findChild<QQuickItem*>(); + QVERIFY(footerChild); + QCOMPARE(footer->x(), 0.0); + QCOMPARE(footer->y(), content->height()); + QCOMPARE(footer->width(), qreal(window->width())); + QCOMPARE(footerChild->width(), qreal(window->width())); + QVERIFY(footer->height() > 0.0); +} + class FriendlyApplicationWindow : public QQuickApplicationWindow { friend class tst_QQuickApplicationWindow; diff --git a/tests/auto/qquickcontrol/tst_qquickcontrol.cpp b/tests/auto/qquickcontrol/tst_qquickcontrol.cpp index c8d34756..df3b31c8 100644 --- a/tests/auto/qquickcontrol/tst_qquickcontrol.cpp +++ b/tests/auto/qquickcontrol/tst_qquickcontrol.cpp @@ -98,9 +98,12 @@ void tst_QQuickControl::flickable() QSignalSpy buttonClickedSpy(button, SIGNAL(clicked())); QVERIFY(buttonClickedSpy.isValid()); - QTest::touchEvent(window, touchDevice.data()).press(0, QPoint(button->width() / 2, button->height() / 2)); + QPoint p(button->width() / 2, button->height() / 2); + QTest::touchEvent(window, touchDevice.data()).press(0, p); QTRY_COMPARE(buttonPressedSpy.count(), 1); - QTest::touchEvent(window, touchDevice.data()).release(0, QPoint(button->width() / 2, button->height() / 2)); + p += QPoint(1, 1); // less than the drag threshold + QTest::touchEvent(window, touchDevice.data()).move(0, p); + QTest::touchEvent(window, touchDevice.data()).release(0, p); QTRY_COMPARE(buttonReleasedSpy.count(), 1); QTRY_COMPARE(buttonClickedSpy.count(), 1); } diff --git a/tests/auto/qquickdrawer/BLACKLIST b/tests/auto/qquickdrawer/BLACKLIST new file mode 100644 index 00000000..9f3f96be --- /dev/null +++ b/tests/auto/qquickdrawer/BLACKLIST @@ -0,0 +1,8 @@ +# See qtbase/src/testlib/qtestblacklist.cpp for format + +# QTBUG-77946 +[slider] +opensuse-leap + +[position] +opensuse-leap diff --git a/tests/auto/qquickheaderview/tst_qquickheaderview.cpp b/tests/auto/qquickheaderview/tst_qquickheaderview.cpp index 611e39cb..f335aa86 100644 --- a/tests/auto/qquickheaderview/tst_qquickheaderview.cpp +++ b/tests/auto/qquickheaderview/tst_qquickheaderview.cpp @@ -58,8 +58,10 @@ public: { } - int rowCount(const QModelIndex & = QModelIndex()) const override + int rowCount(const QModelIndex &index = QModelIndex()) const override { + if (index.isValid()) + return 0; return m_rows; } virtual void setRowCount(int count) @@ -70,8 +72,10 @@ public: endResetModel(); } - int columnCount(const QModelIndex & = QModelIndex()) const override + int columnCount(const QModelIndex &index = QModelIndex()) const override { + if (index.isValid()) + return 0; return m_cols; } virtual void setColumnCount(int count) diff --git a/tests/auto/qquickimaginestyle/data/tst_imagine.qml b/tests/auto/qquickimaginestyle/data/tst_imagine.qml index b9078d78..f4ba1c71 100644 --- a/tests/auto/qquickimaginestyle/data/tst_imagine.qml +++ b/tests/auto/qquickimaginestyle/data/tst_imagine.qml @@ -153,4 +153,60 @@ TestCase { // Shouldn't result in a crash. afterRenderingSpy.wait(1000) } + + Component { + id: invalidNinePatchImageProvider + Item { + width: 200 + height: 200 + property alias ninePatchImage: np + + NinePatchImage { + id: np + source : "qrc:/test-assets/button-background-1.png" + cache: false + visible: false + } + ShaderEffect { + width: 300 + height: 300 + property variant src: np + vertexShader: " + uniform highp mat4 qt_Matrix; + attribute highp vec4 qt_Vertex; + attribute highp vec2 qt_MultiTexCoord0; + varying highp vec2 coord; + void main() { + coord = qt_MultiTexCoord0; + gl_Position = qt_Matrix * qt_Vertex; + }" + fragmentShader: " + varying highp vec2 coord; + uniform sampler2D src; + uniform lowp float qt_Opacity; + void main() { + lowp vec4 tex = texture2D(src, coord); + gl_FragColor = vec4(vec3(dot(tex.rgb, + vec3(0.344, 0.5, 0.156))), + tex.a) * qt_Opacity; + }" + } + } + } + + function test_invalidNinePatchImageProvide() { + var container = createTemporaryObject(invalidNinePatchImageProvider, testCase) + verify(container); + + var afterRenderingSpy = signalSpyComponent.createObject(null, + { target: testCase.Window.window, signalName: "afterRendering" }) + verify(afterRenderingSpy.valid) + + afterRenderingSpy.wait(100) + container.ninePatchImage.source = "" + testCase.Window.window.update() + // Shouldn't result in a crash. + wait(10) + afterRenderingSpy.wait(100) + } } diff --git a/tests/auto/qquickpopup/BLACKLIST b/tests/auto/qquickpopup/BLACKLIST new file mode 100644 index 00000000..9246d301 --- /dev/null +++ b/tests/auto/qquickpopup/BLACKLIST @@ -0,0 +1,8 @@ +# See qtbase/src/testlib/qtestblacklist.cpp for format + +# QTBUG-94251 +[closePolicy] +opensuse-leap + +[cursorShape] +opensuse-leap diff --git a/tests/auto/qquickpopup/data/activeFocusAfterExit.qml b/tests/auto/qquickpopup/data/activeFocusAfterExit.qml new file mode 100644 index 00000000..06b0c068 --- /dev/null +++ b/tests/auto/qquickpopup/data/activeFocusAfterExit.qml @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 +import QtQuick.Controls 2.12 + +ApplicationWindow { + width: 400 + height: 400 + + property alias popup1: popup1 + property alias popup2: popup2 + property alias popup3: popup3 + + Popup { + id: popup1 + focus: true + z: 1 + } + + Popup { + id: popup2 + focus: false + z: 2 + } + + Popup { + id: popup3 + focus: true + z: 3 + } +} diff --git a/tests/auto/qquickpopup/data/activeFocusOnDelayedEnter.qml b/tests/auto/qquickpopup/data/activeFocusOnDelayedEnter.qml new file mode 100644 index 00000000..1ceea99c --- /dev/null +++ b/tests/auto/qquickpopup/data/activeFocusOnDelayedEnter.qml @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 +import QtQuick.Controls 2.12 + +ApplicationWindow { + width: 400 + height: 400 + + property alias popup1: popup1 + property alias popup2: popup2 + + Popup { + id: popup1 + focus: true + enter: Transition { + NumberAnimation { property: "opacity"; from: 0.0; to: 1.0; duration: 100 } + } + } + + Popup { + id: popup2 + focus: true + } +} diff --git a/tests/auto/qquickpopup/data/destroyDuringExitTransition.qml b/tests/auto/qquickpopup/data/destroyDuringExitTransition.qml new file mode 100644 index 00000000..ae72669f --- /dev/null +++ b/tests/auto/qquickpopup/data/destroyDuringExitTransition.qml @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.15 +import QtQuick.Controls 2.15 + +ApplicationWindow { + id: window + width: 400 + height: 400 + title: "destroyDuringExitTransition" + + property Dialog dialog1 + property Dialog dialog2 + + Component { + id: dlg + + Dialog { + dim: true + modal: true + closePolicy: Popup.CloseOnEscape + visible: true + + property alias button: button + + Column { + Text { + text: "button is " + (button.down ? "down" : "up") + } + + Button { + id: button + text: "Try to press this button" + } + } + } + } + + Component { + id: brokenDlg + Dialog { + dim: true + modal: true + closePolicy: Popup.CloseOnEscape + visible: true + + Text { + text: "Press Esc key to reject this dialog" + } + + exit: Transition { + NumberAnimation { property: "opacity"; from: 1.0; to: 0.0; duration: 100 } + } + } + } + + + Component.onCompleted: { + dialog1 = dlg.createObject(window) + dialog2 = brokenDlg.createObject(window) + + dialog2.onRejected.connect(function(){ + dialog2.destroy() + }) + } +} diff --git a/tests/auto/qquickpopup/data/modelessOnModalOnModeless.qml b/tests/auto/qquickpopup/data/modelessOnModalOnModeless.qml new file mode 100644 index 00000000..7f05cb67 --- /dev/null +++ b/tests/auto/qquickpopup/data/modelessOnModalOnModeless.qml @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.13 +import QtQuick.Controls 2.13 + +ApplicationWindow { + width: 400 + height: 400 + + property alias modelessPopup: modelessPopup + property alias button: button + property alias modalPopup: modalPopup + property alias tooltip: tooltip + + Popup { + id: modelessPopup + modal: false + closePolicy: Popup.NoAutoClose + width: 200 + height: 200 + anchors.centerIn: parent + + Button { + id: button + checkable: true + x: 0 + y: 0 + text: "Click me" + } + + Popup { + id: modalPopup + modal: true + closePolicy: Popup.NoAutoClose + width: 100 + height: 100 + anchors.centerIn: parent + + Popup { + id: tooltip + modal: false + closePolicy: Popup.NoAutoClose + width: 50 + height: 50 + anchors.centerIn: parent + } + } + } + +} diff --git a/tests/auto/qquickpopup/tst_qquickpopup.cpp b/tests/auto/qquickpopup/tst_qquickpopup.cpp index 41955d7d..54952d12 100644 --- a/tests/auto/qquickpopup/tst_qquickpopup.cpp +++ b/tests/auto/qquickpopup/tst_qquickpopup.cpp @@ -73,12 +73,15 @@ private slots: void activeFocusOnClose2(); void activeFocusOnClose3(); void activeFocusOnClosingSeveralPopups(); + void activeFocusAfterExit(); + void activeFocusOnDelayedEnter(); void hover_data(); void hover(); void wheel_data(); void wheel(); void parentDestroyed(); void nested(); + void modelessOnModalOnModeless(); void grabber(); void cursorShape(); void componentComplete(); @@ -96,6 +99,7 @@ private slots: void tabFence(); void invisibleToolTipOpen(); void centerInOverlayWithinStackViewItem(); + void destroyDuringExitTransition(); }; void tst_QQuickPopup::initTestCase() @@ -727,6 +731,79 @@ void tst_QQuickPopup::activeFocusOnClosingSeveralPopups() QTRY_COMPARE(button->hasActiveFocus(), true); } +void tst_QQuickPopup::activeFocusAfterExit() +{ + // Test that after closing a popup the highest one in z-order receives it instead. + QQuickApplicationHelper helper(this, QStringLiteral("activeFocusAfterExit.qml")); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickApplicationWindow *window = helper.appWindow; + window->show(); + window->requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(window)); + + QQuickPopup *popup1 = window->property("popup1").value<QQuickPopup*>(); + QVERIFY(popup1); + + QQuickPopup *popup2 = window->property("popup2").value<QQuickPopup*>(); + QVERIFY(popup2); + QSignalSpy closedSpy2(popup2, SIGNAL(closed())); + QVERIFY(closedSpy2.isValid()); + + QQuickPopup *popup3 = window->property("popup3").value<QQuickPopup*>(); + QVERIFY(popup3); + QSignalSpy closedSpy3(popup3, SIGNAL(closed())); + QVERIFY(closedSpy3.isValid()); + + popup1->open(); + QVERIFY(popup1->isVisible()); + QTRY_VERIFY(popup1->hasActiveFocus()); + + popup2->open(); + QVERIFY(popup2->isVisible()); + QTRY_VERIFY(!popup2->hasActiveFocus()); + + popup3->open(); + QVERIFY(popup3->isVisible()); + QTRY_VERIFY(popup3->hasActiveFocus()); + + popup3->close(); + closedSpy3.wait(); + QVERIFY(!popup3->isVisible()); + QTRY_VERIFY(!popup3->hasActiveFocus()); + QTRY_VERIFY(!popup2->hasActiveFocus()); + QTRY_VERIFY(popup1->hasActiveFocus()); + + popup2->close(); + closedSpy2.wait(); + QVERIFY(!popup2->isVisible()); + QTRY_VERIFY(!popup2->hasActiveFocus()); + QTRY_VERIFY(popup1->hasActiveFocus()); +} + +void tst_QQuickPopup::activeFocusOnDelayedEnter() +{ + // Test that after opening two popups, first of which has an animation, does not cause + // the first one to receive focus after the animation stops. + QQuickApplicationHelper helper(this, QStringLiteral("activeFocusOnDelayedEnter.qml")); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickApplicationWindow *window = helper.appWindow; + window->show(); + window->requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(window)); + + QQuickPopup *popup1 = window->property("popup1").value<QQuickPopup*>(); + QVERIFY(popup1); + QSignalSpy openedSpy(popup1, SIGNAL(opened())); + + QQuickPopup *popup2 = window->property("popup2").value<QQuickPopup*>(); + QVERIFY(popup2); + + popup1->open(); + popup2->open(); + openedSpy.wait(); + QTRY_VERIFY(popup2->hasActiveFocus()); +} + void tst_QQuickPopup::hover_data() { QTest::addColumn<QString>("source"); @@ -914,6 +991,46 @@ void tst_QQuickPopup::nested() QCOMPARE(modalPopup->isVisible(), true); } +void tst_QQuickPopup::modelessOnModalOnModeless() +{ + QQuickApplicationHelper helper(this, QStringLiteral("modelessOnModalOnModeless.qml")); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + QQuickPopup *modelessPopup = window->property("modelessPopup").value<QQuickPopup *>(); + QVERIFY(modelessPopup); + + QQuickButton *button = window->property("button").value<QQuickButton *>(); + QVERIFY(button); + QQuickPopup *modalPopup = window->property("modalPopup").value<QQuickPopup *>(); + QVERIFY(modalPopup); + QQuickPopup *tooltip = window->property("tooltip").value<QQuickPopup *>(); + QVERIFY(modalPopup); + + modelessPopup->open(); + QCOMPARE(modelessPopup->isVisible(), true); + QTRY_COMPARE(modelessPopup->isOpened(), true); + const auto buttonPoint = button->mapToScene(button->boundingRect().center()).toPoint(); + // click into the button, should not be blocked + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, buttonPoint); + QVERIFY(button->isChecked()); + modalPopup->open(); + QCOMPARE(modalPopup->isVisible(), true); + QTRY_COMPARE(modalPopup->isOpened(), true); + // click into the button, should be blocked + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, buttonPoint); + QVERIFY(button->isChecked()); + + tooltip->setVisible(true); + QCOMPARE(tooltip->isVisible(), true); + QTRY_COMPARE(tooltip->isOpened(), true); + // click into the button, should be blocked + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, buttonPoint); + QVERIFY(button->isChecked()); +} + // QTBUG-56697 void tst_QQuickPopup::grabber() { @@ -1429,6 +1546,35 @@ void tst_QQuickPopup::centerInOverlayWithinStackViewItem() // Shouldn't crash on exit. } +void tst_QQuickPopup::destroyDuringExitTransition() +{ + QQuickApplicationHelper helper(this, "destroyDuringExitTransition.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowActive(window)); + + QPointer<QQuickPopup> dialog2 = window->property("dialog2").value<QQuickPopup*>(); + QVERIFY(dialog2); + QTRY_COMPARE(dialog2->isVisible(), true); + + // Close the second dialog, destroying it before its exit transition can finish. + QTest::keyClick(window, Qt::Key_Escape); + QTRY_VERIFY(!dialog2); + + // Events should go through to the dialog underneath. + QQuickPopup *dialog1 = window->property("dialog1").value<QQuickPopup*>(); + QVERIFY(dialog1); + QQuickButton *button = dialog1->property("button").value<QQuickButton*>(); + QVERIFY(button); + const auto buttonClickPos = button->mapToScene(QPointF(button->width() / 2, button->height() / 2)).toPoint(); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, buttonClickPos); + QVERIFY(button->isDown()); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, buttonClickPos); + QVERIFY(!button->isDown()); +} + QTEST_QUICKCONTROLS_MAIN(tst_QQuickPopup) #include "tst_qquickpopup.moc" |