fix: comprehensive boot warnings and exceptions — fixable silenced, unfixable diagnosed

Build system (5 gaps hardened):
- COOKBOOK_OFFLINE defaults to true (fork-mode)
- normalize_patch handles diff -ruN format
- New 'repo validate-patches' command (25/25 relibc patches)
- 14 patched Qt/Wayland/display recipes added to protected list
- relibc archive regenerated with current patch chain

Boot fixes (fixable):
- Full ISO EFI partition: 16 MiB → 1 MiB (matches mini, BIOS hardcoded 2 MiB offset)
- D-Bus system bus: absolute /usr/bin/dbus-daemon path (was skipped)
- redbear-sessiond: absolute /usr/bin/redbear-sessiond path (was skipped)
- daemon framework: silenced spurious INIT_NOTIFY warnings for oneshot_async services (P0-daemon-silence-init-notify.patch)
- udev-shim: demoted INIT_NOTIFY warning to INFO (expected for oneshot_async)
- relibc: comprehensive named semaphores (sem_open/close/unlink) replacing upstream todo!() stubs
- greeterd: Wayland socket timeout 15s → 30s (compositor DRM wait)
- greeter-ui: built and linked (header guard unification, sem_compat stubs removed)
- mc: un-ignored in both configs, fixed glib/libiconv/pcre2 transitive deps
- greeter config: removed stale keymapd dependency from display/greeter services
- prefix toolchain: relibc headers synced, _RELIBC_STDLIB_H guard unified

Unfixable (diagnosed, upstream):
- i2c-hidd: abort on no-I2C-hardware (QEMU) — process::exit → relibc abort
- kded6/greeter-ui: page fault 0x8 — Qt library null deref
- Thread panics fd != -1 — Rust std library on Redox
- DHCP timeout / eth0 MAC — QEMU user-mode networking
- hwrngd/thermald — no hardware RNG/thermal in VM
- live preload allocation — BIOS memory fragmentation, continues on demand
This commit is contained in:
2026-05-05 20:20:37 +01:00
parent a5f97b6632
commit f31522130f
81834 changed files with 11051982 additions and 108 deletions
@@ -0,0 +1,3 @@
# QTBUG-92094
[tabFocusNavigation]
*
@@ -0,0 +1,49 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
cmake_minimum_required(VERSION 3.16)
project(tst_qquickfolderdialogimpl LANGUAGES CXX)
find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
endif()
# Collect test data
file(GLOB_RECURSE test_data_glob
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
data/*)
list(APPEND test_data ${test_data_glob})
qt_internal_add_test(tst_qquickfolderdialogimpl
SOURCES
tst_qquickfolderdialogimpl.cpp
DEFINES
QQC2_IMPORT_PATH="${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/quickcontrols"
LIBRARIES
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
Qt::Qml
Qt::QmlPrivate
Qt::QuickControls2
Qt::QuickControls2Private
Qt::QuickControlsTestUtilsPrivate
Qt::QuickDialogs2Private
Qt::QuickDialogs2QuickImplPrivate
Qt::QuickDialogs2UtilsPrivate
Qt::QuickPrivate
Qt::QuickTemplates2Private
Qt::QuickTest
Qt::QuickTestUtilsPrivate
Qt::TestPrivate
TESTDATA ${test_data}
)
qt_internal_extend_target(tst_qquickfolderdialogimpl CONDITION ANDROID OR IOS
DEFINES
QT_QMLTEST_DATADIR=":/data"
)
qt_internal_extend_target(tst_qquickfolderdialogimpl CONDITION NOT ANDROID AND NOT IOS
DEFINES
QT_QMLTEST_DATADIR="${CMAKE_CURRENT_SOURCE_DIR}/data"
)
@@ -0,0 +1,20 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
import QtQuick
import QtQuick.Controls
import QtQuick.Dialogs
ApplicationWindow {
id: window
width: 640
height: 480
property alias dialog: dialog
FolderDialog {
id: dialog
acceptLabel: "AcceptTest"
rejectLabel: "RejectTest"
}
}
@@ -0,0 +1,21 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
import QtQuick
import QtQuick.Controls
import QtQuick.Dialogs
ApplicationWindow {
id: window
width: 640
height: 480
property alias dialog: dialog
required property url initialFolder
FolderDialog {
id: dialog
objectName: "FolderDialog"
currentFolder: window.initialFolder
}
}
@@ -0,0 +1,18 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
import QtQuick
import QtQuick.Controls
import QtQuick.Dialogs
ApplicationWindow {
width: 640
height: 480
property alias dialog: dialog
FolderDialog {
id: dialog
title: "Test Title"
}
}
@@ -0,0 +1,38 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
import QtQuick
import QtQuick.Controls
import QtQuick.Dialogs
ApplicationWindow {
id: rootWindow
width: 100
height: 100
property alias dialog: dialog
property alias rootMArea: rootMouseArea
property alias childMArea: childMouseArea
property alias childWindow: childWindow
MouseArea {
id: rootMouseArea
anchors.fill: parent
}
ApplicationWindow {
id: childWindow
width: 100
height: 100
MouseArea {
id: childMouseArea
anchors.fill: parent
}
}
FolderDialog {
id: dialog
objectName: "FolderDialog"
}
}
@@ -0,0 +1,26 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
import QtQuick
import QtQuick.Controls
import QtQuick.Dialogs
ApplicationWindow {
width: 640
height: 480
property alias dialog: dialog
function doneAccepted() {
dialog.done(FileDialog.Accepted)
}
function doneRejected() {
dialog.done(FileDialog.Rejected)
}
FolderDialog {
id: dialog
objectName: "FolderDialog"
}
}
@@ -0,0 +1,19 @@
// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
import QtQuick
import QtQuick.Controls
import QtQuick.Dialogs
ApplicationWindow {
width: 480
height: 640
property alias dialog: dialog
FolderDialog {
id: dialog
objectName: "FolderDialog"
flags: Qt.Window | Qt.FramelessWindowHint
}
}
@@ -0,0 +1,10 @@
// This file exists for the sole purpose for qmlimportscanner to find
// which modules it needs to extract for deployment.
// Otherwise, it fails to find the imports that are expressed in C++.
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt.labs.folderlistmodel
QtObject { }
@@ -0,0 +1,949 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtTest/qtest.h>
#include <QtTest/qsignalspy.h>
#include <QtQml/qqmlfile.h>
#include <QtQuick/private/qquicklistview_p.h>
#include <QtQuick/private/qquickmousearea_p.h>
#include <QtQuickTest/quicktest.h>
#include <QtQuickControlsTestUtils/private/controlstestutils_p.h>
#include <QtQuickControlsTestUtils/private/dialogstestutils_p.h>
#include <QtQuickDialogs2/private/qquickfolderdialog_p.h>
#include <QtQuickDialogs2QuickImpl/private/qquickplatformfolderdialog_p.h>
#include <QtQuickDialogs2QuickImpl/private/qquickfiledialogdelegate_p.h>
#include <QtQuickDialogs2QuickImpl/private/qquickfolderbreadcrumbbar_p.h>
#include <QtQuickDialogs2QuickImpl/private/qquickfolderbreadcrumbbar_p_p.h>
#include <QtQuickDialogs2QuickImpl/private/qquickfolderdialogimpl_p.h>
#include <QtQuickTemplates2/private/qquickapplicationwindow_p.h>
#include <QtQuickTemplates2/private/qquickcombobox_p.h>
#include <QtQuickTemplates2/private/qquickdialogbuttonbox_p.h>
#include <QtQuickTemplates2/private/qquickdialogbuttonbox_p_p.h>
#include <QtQuickTemplates2/private/qquicklabel_p.h>
#include <QtQuickTemplates2/private/qquickoverlay_p.h>
#include <QtQuickControls2/qquickstyle.h>
using namespace QQuickVisualTestUtils;
using namespace QQuickDialogTestUtils;
using namespace QQuickControlsTestUtils;
class tst_QQuickFolderDialogImpl : public QQmlDataTest
{
Q_OBJECT
public:
tst_QQuickFolderDialogImpl();
static void initMain()
{
// We need to set this attribute.
QCoreApplication::setAttribute(Qt::AA_DontUseNativeDialogs);
// We also don't want to run this for every style, as each one will have
// different ways of implementing the dialogs.
// For now we only test one style.
QQuickStyle::setStyle("Basic");
}
private slots:
void initTestCase() override;
void cleanupTestCase();
void defaults();
void chooseFolderViaStandardButtons();
void bindCurrentFolder_data();
void bindCurrentFolder();
void changeFolderViaDoubleClick();
void changeFolderViaTextEdit();
void changeFolderViaEnter();
void cancelDialogWhileTextEditHasFocus();
void goUp();
void goUpWhileTextEditHasFocus();
void goIntoLargeFolder();
void clickOnBreadcrumb();
void keyAndShortcutHandling();
void tabFocusNavigation();
void acceptRejectLabel();
void bindTitle();
void itemsDisabledWhenNecessary();
void done_data();
void done();
void checkModality_data();
void checkModality();
void checkFrameless();
private:
QTemporaryDir tempDir;
QString tempDirCanonicalPath;
QScopedPointer<QFile> tempFile1;
QString tempFile1CanonicalPath;
QScopedPointer<QFile> tempFile2;
QString tempFile2CanonicalPath;
QDir tempSubDir1;
QString tempSubDir1CanonicalPath;
QDir tempSubSubDir;
QScopedPointer<QFile> tempSubFile1;
QScopedPointer<QFile> tempSubFile2;
QDir tempSubDir2;
QString tempSubDir2CanonicalPath;
QDir oldCurrentDir;
};
tst_QQuickFolderDialogImpl::tst_QQuickFolderDialogImpl()
: QQmlDataTest(QT_QMLTEST_DATADIR)
{
}
void tst_QQuickFolderDialogImpl::initTestCase()
{
QQmlDataTest::initTestCase();
QVERIFY(tempDir.isValid());
tempDirCanonicalPath = QFileInfo(tempDir.path()).canonicalFilePath();
// QTEST_QUICKCONTROLS_MAIN constructs the test case object once,
// and then calls qRun() for each style, and qRun() calls initTestCase().
// So, we need to check if we've already made the temporary directory.
// Note that this is only necessary if the test is run with more than one style.
if (!QDir(tempDirCanonicalPath).isEmpty())
return;
/*
Create a couple of files within the temporary directory. The structure is:
[temp directory]
├── sub-dir-1
│ ├── sub-sub-dir
│ ├── sub-file1.txt
│ └── sub-file2.txt
├── sub-dir-2
├── file1.txt
└── file2.txt
*/
tempSubDir1 = QDir(tempDirCanonicalPath);
QVERIFY2(tempSubDir1.mkdir("sub-dir-1"), qPrintable(QString::fromLatin1(
"Failed to make sub-directory \"sub-dir-1\" in %1. Permissions are: %2")
.arg(tempDirCanonicalPath, QDebug::toString(QFileInfo(tempDirCanonicalPath).permissions()))));
QVERIFY(tempSubDir1.cd("sub-dir-1"));
tempSubDir1CanonicalPath = tempSubDir1.canonicalPath();
QVERIFY(!tempSubDir1CanonicalPath.isEmpty());
tempSubSubDir = QDir(tempSubDir1CanonicalPath);
QVERIFY2(tempSubSubDir.mkdir("sub-sub-dir"), qPrintable(QString::fromLatin1(
"Failed to make sub-directory \"sub-sub-dir\" in %1. Permissions are: %2")
.arg(tempSubDir1CanonicalPath, QDebug::toString(QFileInfo(tempSubDir1CanonicalPath).permissions()))));
QVERIFY(tempSubSubDir.cd("sub-sub-dir"));
tempSubFile1.reset(new QFile(tempSubDir1CanonicalPath + "/sub-file1.txt"));
QVERIFY(tempSubFile1->open(QIODevice::ReadWrite));
tempSubFile2.reset(new QFile(tempSubDir1CanonicalPath + "/sub-file2.txt"));
QVERIFY(tempSubFile2->open(QIODevice::ReadWrite));
tempSubDir2 = QDir(tempDirCanonicalPath);
QVERIFY2(tempSubDir2.mkdir("sub-dir-2"), qPrintable(QString::fromLatin1(
"Failed to make sub-directory \"sub-dir-2\" in %1. Permissions are: %2")
.arg(tempDirCanonicalPath, QDebug::toString(QFileInfo(tempDirCanonicalPath).permissions()))));
QVERIFY(tempSubDir2.cd("sub-dir-2"));
tempSubDir2CanonicalPath = tempSubDir2.canonicalPath();
QVERIFY(!tempSubDir2CanonicalPath.isEmpty());
tempFile1.reset(new QFile(tempDirCanonicalPath + "/file1.txt"));
QVERIFY(tempFile1->open(QIODevice::ReadWrite));
tempFile2.reset(new QFile(tempDirCanonicalPath + "/file2.txt"));
QVERIFY(tempFile2->open(QIODevice::ReadWrite));
// Ensure that each test starts off in the temporary directory.
oldCurrentDir = QDir::current();
QDir::setCurrent(tempDirCanonicalPath);
}
void tst_QQuickFolderDialogImpl::cleanupTestCase()
{
// Just in case...
QDir::setCurrent(oldCurrentDir.path());
}
void tst_QQuickFolderDialogImpl::defaults()
{
QTest::failOnWarning(QRegularExpression(".*"));
QQuickApplicationHelper helper(this, "folderDialog.qml");
QVERIFY2(helper.ready, helper.failureMessage());
QQuickWindow *window = helper.window;
window->show();
window->requestActivate();
QVERIFY(QTest::qWaitForWindowExposed(window));
QQuickFolderDialog *dialog = window->property("dialog").value<QQuickFolderDialog*>();
QVERIFY(dialog);
COMPARE_URL(dialog->currentFolder(), QUrl::fromLocalFile(QDir().absolutePath()));
// The first file in the directory should be selected, but not until the dialog is actually open,
// as QQuickFileDialogImpl hasn't been created yet.
COMPARE_URL(dialog->selectedFolder(), QUrl());
QCOMPARE(dialog->title(), QString());
dialog->open();
QQuickFolderDialogImpl *quickDialog = window->findChild<QQuickFolderDialogImpl*>();
QVERIFY(quickDialog);
QTRY_VERIFY(quickDialog->isOpened());
QVERIFY(quickDialog);
COMPARE_URL(quickDialog->selectedFolder(), QUrl::fromLocalFile(tempSubDir1CanonicalPath));
COMPARE_URL(quickDialog->currentFolder(), QUrl::fromLocalFile(QDir().absolutePath()));
COMPARE_URL(dialog->selectedFolder(), QUrl::fromLocalFile(tempSubDir1CanonicalPath));
COMPARE_URL(dialog->currentFolder(), QUrl::fromLocalFile(QDir().absolutePath()));
QCOMPARE(quickDialog->title(), QString());
}
typedef DialogTestHelper<QQuickFolderDialog, QQuickFolderDialogImpl> FolderDialogTestHelper;
class FolderDialogSignalHelper
{
public:
FolderDialogSignalHelper(const FolderDialogTestHelper &dialogTestHelper) :
dialogSelectedFolderChangedSpy(dialogTestHelper.dialog, SIGNAL(selectedFolderChanged())),
quickDialogSelectedFolderChangedSpy(dialogTestHelper.quickDialog, SIGNAL(selectedFolderChanged(QUrl))),
dialogCurrentFolderChangedSpy(dialogTestHelper.dialog, SIGNAL(currentFolderChanged())),
quickDialogCurrentFolderChangedSpy(dialogTestHelper.quickDialog, SIGNAL(currentFolderChanged(QUrl)))
{
if (!dialogSelectedFolderChangedSpy.isValid())
errorMessage = "selectedFolderChanged signal of dialog is not valid";
if (!quickDialogSelectedFolderChangedSpy.isValid())
errorMessage = "selectedFolderChanged signal of quickDialog is not valid";
if (!dialogCurrentFolderChangedSpy.isValid())
errorMessage = "currentFolderChanged signal of dialog is not valid";
if (!quickDialogCurrentFolderChangedSpy.isValid())
errorMessage = "currentFolderChanged signal of quickDialog is not valid";
}
QSignalSpy dialogSelectedFolderChangedSpy;
QSignalSpy quickDialogSelectedFolderChangedSpy;
QSignalSpy dialogCurrentFolderChangedSpy;
QSignalSpy quickDialogCurrentFolderChangedSpy;
QByteArray errorMessage;
};
void tst_QQuickFolderDialogImpl::chooseFolderViaStandardButtons()
{
// Open the dialog.
FolderDialogTestHelper dialogHelper(this, "folderDialog.qml");
QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage());
QVERIFY(dialogHelper.waitForWindowActive());
QVERIFY(dialogHelper.openDialog());
QTRY_VERIFY(dialogHelper.isQuickDialogOpen());
// Select the delegate by clicking once.
const FolderDialogSignalHelper signalHelper(dialogHelper);
QVERIFY2(signalHelper.errorMessage.isEmpty(), signalHelper.errorMessage);
auto folderDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("folderDialogListView");
QVERIFY(folderDialogListView);
QQuickFileDialogDelegate *delegate = nullptr;
QTRY_VERIFY(findViewDelegateItem(folderDialogListView, 1, delegate));
COMPARE_URL(delegate->file(), QUrl::fromLocalFile(tempSubDir2CanonicalPath));
QVERIFY(clickButton(delegate));
// currentFolder shouldn't change just from a single click.
COMPARE_URL(dialogHelper.quickDialog->currentFolder(), QUrl::fromLocalFile(tempDirCanonicalPath));
COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempDirCanonicalPath));
COMPARE_URL(dialogHelper.quickDialog->selectedFolder(), QUrl::fromLocalFile(tempSubDir2CanonicalPath));
COMPARE_URL(dialogHelper.dialog->selectedFolder(), QUrl::fromLocalFile(tempSubDir2CanonicalPath));
// Only selectedFile-related signals should be emitted.
QCOMPARE(signalHelper.dialogSelectedFolderChangedSpy.size(), 1);
QCOMPARE(signalHelper.quickDialogSelectedFolderChangedSpy.size(), 1);
QCOMPARE(signalHelper.dialogCurrentFolderChangedSpy.size(), 0);
QCOMPARE(signalHelper.quickDialogCurrentFolderChangedSpy.size(), 0);
// Click the "Open" button.
QVERIFY(dialogHelper.quickDialog->footer());
auto dialogButtonBox = qobject_cast<QQuickDialogButtonBox*>(dialogHelper.quickDialog->footer());
QVERIFY(dialogButtonBox);
QQuickAbstractButton* openButton = findDialogButton(dialogButtonBox, "Open");
QVERIFY(openButton);
QVERIFY(clickButton(openButton));
COMPARE_URL(dialogHelper.dialog->selectedFolder(), QUrl::fromLocalFile(tempSubDir2CanonicalPath));
COMPARE_URL(dialogHelper.quickDialog->selectedFolder(), QUrl::fromLocalFile(tempSubDir2CanonicalPath));
QCOMPARE(signalHelper.dialogSelectedFolderChangedSpy.size(), 1);
QCOMPARE(signalHelper.quickDialogSelectedFolderChangedSpy.size(), 1);
QCOMPARE(signalHelper.dialogCurrentFolderChangedSpy.size(), 0);
QCOMPARE(signalHelper.quickDialogCurrentFolderChangedSpy.size(), 0);
QTRY_VERIFY(!dialogHelper.quickDialog->isVisible());
QVERIFY(!dialogHelper.dialog->isVisible());
}
void tst_QQuickFolderDialogImpl::bindCurrentFolder_data()
{
QTest::addColumn<QUrl>("initialFolder");
QTest::addColumn<QUrl>("expectedFolder");
QTest::addColumn<QStringList>("expectedVisibleFiles");
const auto currentDirUrl = QUrl::fromLocalFile(QDir::current().canonicalPath());
const auto tempSubDirUrl = QUrl::fromLocalFile(tempSubDir1CanonicalPath);
const auto tempSubFile1Url = QUrl::fromLocalFile(tempSubFile1->fileName());
const QStringList currentDirFiles = { tempSubDir1CanonicalPath, tempSubDir2CanonicalPath };
const QStringList tempSubDirFiles = { tempSubSubDir.path() };
// Setting the folder to "sub-dir-1" should result in "sub-sub-dir" being visible.
QTest::addRow("sub-dir-1") << tempSubDirUrl << tempSubDirUrl << tempSubDirFiles;
// Setting a file as the folder shouldn't work, and the dialog shouldn't change its folder.
QTest::addRow("sub-dir/sub-file1.txt") << tempSubFile1Url << currentDirUrl << currentDirFiles;
}
void tst_QQuickFolderDialogImpl::bindCurrentFolder()
{
QFETCH(QUrl, initialFolder);
QFETCH(QUrl, expectedFolder);
QFETCH(QStringList, expectedVisibleFiles);
// Open the dialog.
FolderDialogTestHelper dialogHelper(this, "bindCurrentFolder.qml", {},
{{ "initialFolder", initialFolder }});
QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage());
QVERIFY(dialogHelper.waitForWindowActive());
QVERIFY(dialogHelper.openDialog());
QTRY_VERIFY(dialogHelper.isQuickDialogOpen());
COMPARE_URL(dialogHelper.dialog->currentFolder(), expectedFolder);
auto folderDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("folderDialogListView");
QVERIFY(folderDialogListView);
QString failureMessage;
// Even waiting for ListView polish and that the FolderListModel's status is ready aren't enough
// on Windows, apparently, as sometimes there just aren't any delegates by the time we do the check.
// So, we use QTRY_VERIFY2 each time we call this function just to be safe.
QTRY_VERIFY2(verifyFileDialogDelegates(folderDialogListView, expectedVisibleFiles, failureMessage), qPrintable(failureMessage));
// Check that the breadcrumb bar is correct by constructing the expected files from the expectedFolder.
auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>();
QVERIFY(breadcrumbBar);
QVERIFY2(verifyBreadcrumbDelegates(breadcrumbBar, expectedFolder, failureMessage), qPrintable(failureMessage));
}
void tst_QQuickFolderDialogImpl::changeFolderViaDoubleClick()
{
// Open the dialog.
FolderDialogTestHelper dialogHelper(this, "folderDialog.qml");
QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage());
QVERIFY(dialogHelper.waitForWindowActive());
QVERIFY(dialogHelper.openDialog());
QTRY_VERIFY(dialogHelper.isQuickDialogOpen());
// Double-click on the "sub-dir-2" delegate.
const FolderDialogSignalHelper signalHelper(dialogHelper);
QVERIFY2(signalHelper.errorMessage.isEmpty(), signalHelper.errorMessage);
auto folderDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("folderDialogListView");
QVERIFY(folderDialogListView);
QQuickFileDialogDelegate *delegate = nullptr;
QTRY_VERIFY(findViewDelegateItem(folderDialogListView, 1, delegate));
COMPARE_URL(delegate->file(), QUrl::fromLocalFile(tempSubDir2CanonicalPath))
QVERIFY(doubleClickButton(delegate));
COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempSubDir2CanonicalPath))
COMPARE_URL(dialogHelper.dialog->selectedFolder(), QUrl());
// selectedFolder is set to the folder when clicked and then set to an empty URL after
// the double click.
QCOMPARE(signalHelper.dialogSelectedFolderChangedSpy.size(), 2);
QCOMPARE(signalHelper.quickDialogSelectedFolderChangedSpy.size(), 2);
QCOMPARE(signalHelper.dialogCurrentFolderChangedSpy.size(), 1);
QCOMPARE(signalHelper.quickDialogCurrentFolderChangedSpy.size(), 1);
// Since we only changed the current folder, the dialog should still be open.
QVERIFY(dialogHelper.dialog->isVisible());
dialogHelper.dialog->close();
QVERIFY(!dialogHelper.dialog->isVisible());
QTRY_VERIFY(!dialogHelper.quickDialog->isVisible());
}
void tst_QQuickFolderDialogImpl::changeFolderViaTextEdit()
{
// Open the dialog.
FolderDialogTestHelper dialogHelper(this, "folderDialog.qml");
QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage());
QVERIFY(dialogHelper.waitForWindowActive());
QVERIFY(dialogHelper.openDialog());
QTRY_VERIFY(dialogHelper.isQuickDialogOpen());
QVERIFY(dialogHelper.waitForPopupWindowActiveAndPolished());
#if QT_CONFIG(shortcut)
// Get the text edit visible with Ctrl+L.
const auto editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L);
QTest::keySequence(dialogHelper.popupWindow(), editPathKeySequence);
auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>();
QVERIFY(breadcrumbBar);
QVERIFY(breadcrumbBar->textField()->isVisible());
QTRY_VERIFY(breadcrumbBar->textField()->hasActiveFocus());
QCOMPARE(breadcrumbBar->textField()->text(), dialogHelper.dialog->currentFolder().toLocalFile());
QCOMPARE(breadcrumbBar->textField()->selectedText(), breadcrumbBar->textField()->text());
// Enter the path to the folder in the text edit.
enterText(dialogHelper.popupWindow(), tempSubDir2CanonicalPath);
QCOMPARE(breadcrumbBar->textField()->text(), tempSubDir2CanonicalPath);
#endif
// Hit enter to accept.
QTest::keyClick(dialogHelper.popupWindow(), Qt::Key_Return);
COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempSubDir2CanonicalPath));
COMPARE_URL(dialogHelper.quickDialog->currentFolder(), QUrl::fromLocalFile(tempSubDir2CanonicalPath));
// We changed into a directory with no folders, so the selected folder should be invalid.
COMPARE_URL(dialogHelper.dialog->selectedFolder(), QUrl());
COMPARE_URL(dialogHelper.quickDialog->selectedFolder(), QUrl());
QTRY_VERIFY(dialogHelper.dialog->isVisible());
// Close the dialog.
dialogHelper.dialog->close();
QVERIFY(!dialogHelper.dialog->isVisible());
QTRY_VERIFY(!dialogHelper.quickDialog->isVisible());
}
void tst_QQuickFolderDialogImpl::changeFolderViaEnter()
{
// Open the dialog.
FolderDialogTestHelper dialogHelper(this, "folderDialog.qml");
QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage());
QVERIFY(dialogHelper.waitForWindowActive());
QVERIFY(dialogHelper.openDialog());
QTRY_VERIFY(dialogHelper.isQuickDialogOpen());
QVERIFY(dialogHelper.waitForPopupWindowActiveAndPolished());
// The first delegate in the view should be selected and have focus.
COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempDirCanonicalPath));
auto folderDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("folderDialogListView");
QVERIFY(folderDialogListView);
QQuickFileDialogDelegate *subDir1Delegate = nullptr;
QTRY_VERIFY(findViewDelegateItem(folderDialogListView, 0, subDir1Delegate));
COMPARE_URL(subDir1Delegate->file(), QUrl::fromLocalFile(tempSubDir1CanonicalPath));
QTRY_VERIFY_ACTIVE_FOCUS(subDir1Delegate);
// Select the delegate by pressing enter.
const FolderDialogSignalHelper signalHelper(dialogHelper);
QVERIFY2(signalHelper.errorMessage.isEmpty(), signalHelper.errorMessage);
QTest::keyClick(dialogHelper.popupWindow(), Qt::Key_Return);
COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempSubDir1CanonicalPath));
COMPARE_URL(dialogHelper.dialog->selectedFolder(), QUrl::fromLocalFile(tempSubSubDir.path()));
QCOMPARE(signalHelper.dialogSelectedFolderChangedSpy.size(), 1);
QCOMPARE(signalHelper.quickDialogSelectedFolderChangedSpy.size(), 1);
QCOMPARE(signalHelper.dialogCurrentFolderChangedSpy.size(), 1);
QCOMPARE(signalHelper.quickDialogCurrentFolderChangedSpy.size(), 1);
// Since we only changed the current folder, the dialog should still be open.
QVERIFY(dialogHelper.dialog->isVisible());
dialogHelper.dialog->close();
QVERIFY(!dialogHelper.dialog->isVisible());
QTRY_VERIFY(!dialogHelper.quickDialog->isVisible());
}
void tst_QQuickFolderDialogImpl::cancelDialogWhileTextEditHasFocus()
{
// Open the dialog.
FolderDialogTestHelper dialogHelper(this, "folderDialog.qml");
QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage());
QVERIFY(dialogHelper.waitForWindowActive());
QVERIFY(dialogHelper.openDialog());
QTRY_VERIFY(dialogHelper.isQuickDialogOpen());
QVERIFY(dialogHelper.waitForPopupWindowActiveAndPolished());
#if QT_CONFIG(shortcut)
// Get the text edit visible with Ctrl+L.
const auto editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L);
QTest::keySequence(dialogHelper.popupWindow(), editPathKeySequence);
#endif
auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>();
QVERIFY(breadcrumbBar);
QVERIFY(breadcrumbBar->textField()->isVisible());
QCOMPARE(breadcrumbBar->textField()->text(), dialogHelper.dialog->currentFolder().toLocalFile());
QCOMPARE(breadcrumbBar->textField()->selectedText(), breadcrumbBar->textField()->text());
// Close it via the cancel button.
auto dialogButtonBox = qobject_cast<QQuickDialogButtonBox*>(dialogHelper.quickDialog->footer());
QVERIFY(dialogButtonBox);
const QString cancelText = QQuickDialogButtonBoxPrivate::buttonText(QPlatformDialogHelper::Cancel);
auto cancelButton = findDialogButton(dialogButtonBox, cancelText);
QVERIFY(cancelButton);
QVERIFY(clickButton(cancelButton));
// Open it again. The text field should not be visible, but the breadcrumb bar itself should be.
dialogHelper.dialog->open();
QVERIFY(dialogHelper.dialog->isVisible());
QTRY_VERIFY(dialogHelper.quickDialog->isOpened());
QVERIFY(breadcrumbBar->isVisible());
// The ListView that contains the breadcrumb delegates should be visible.
QVERIFY(breadcrumbBar->contentItem()->isVisible());
QVERIFY(!breadcrumbBar->textField()->isVisible());
}
void tst_QQuickFolderDialogImpl::goUp()
{
// Open the dialog, starting off in "sub-dir-1".
FolderDialogTestHelper dialogHelper(this, "bindCurrentFolder.qml", {},
{{ "initialFolder", QUrl::fromLocalFile(tempSubDir1CanonicalPath) }});
QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage());
QVERIFY(dialogHelper.waitForWindowActive());
QVERIFY(dialogHelper.openDialog());
QTRY_VERIFY(dialogHelper.isQuickDialogOpen());
// Go up a directory via the button next to the breadcrumb bar.
auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>();
QVERIFY(breadcrumbBar);
auto barListView = qobject_cast<QQuickListView*>(breadcrumbBar->contentItem());
QVERIFY(barListView);
if (QQuickTest::qIsPolishScheduled(barListView))
QVERIFY(QQuickTest::qWaitForPolish(barListView));
QVERIFY(clickButton(breadcrumbBar->upButton()));
COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempDirCanonicalPath));
// The previous directory that we were in should now be selected (matches e.g. Windows and Ubuntu).
COMPARE_URL(dialogHelper.dialog->selectedFolder(), QUrl::fromLocalFile(tempSubDir1CanonicalPath));
auto folderDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("folderDialogListView");
QVERIFY(folderDialogListView);
QQuickFileDialogDelegate *subDirDelegate = nullptr;
QTRY_VERIFY(findViewDelegateItem(folderDialogListView, 0, subDirDelegate));
QCOMPARE(subDirDelegate->isHighlighted(), true);
#if QT_CONFIG(shortcut)
// Go up a directory via the keyboard shortcut.
const auto goUpKeySequence = QKeySequence(Qt::ALT | Qt::Key_Up);
QTest::keySequence(dialogHelper.window(), goUpKeySequence);
QDir tempParentDir(tempDirCanonicalPath);
QVERIFY(tempParentDir.cdUp());
COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempParentDir.path()));
COMPARE_URL(dialogHelper.dialog->selectedFolder(), QUrl::fromLocalFile(tempDirCanonicalPath));
#endif
}
void tst_QQuickFolderDialogImpl::goUpWhileTextEditHasFocus()
{
// Open the dialog, starting off in "sub-dir-1".
FolderDialogTestHelper dialogHelper(this, "bindCurrentFolder.qml", {},
{{ "initialFolder", QUrl::fromLocalFile(tempSubDir1CanonicalPath) }});
QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage());
QVERIFY(dialogHelper.waitForWindowActive());
QVERIFY(dialogHelper.openDialog());
QTRY_VERIFY(dialogHelper.isQuickDialogOpen());
QVERIFY(dialogHelper.waitForPopupWindowActiveAndPolished());
// Go up a directory via the button next to the breadcrumb bar.
auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>();
QVERIFY(breadcrumbBar);
auto barListView = qobject_cast<QQuickListView*>(breadcrumbBar->contentItem());
QVERIFY(barListView);
if (QQuickTest::qIsPolishScheduled(barListView))
QVERIFY(QQuickTest::qWaitForPolish(barListView));
QVERIFY(clickButton(breadcrumbBar->upButton()));
// The path should have changed to the parent directory.
COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempDirCanonicalPath));
// The text edit should be hidden when it loses focus.
QVERIFY(!breadcrumbBar->textField()->hasActiveFocus());
QVERIFY(!breadcrumbBar->textField()->isVisible());
// The focus should be given to the first delegate.
QTRY_VERIFY(dialogHelper.popupWindow()->activeFocusItem());
auto folderDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("folderDialogListView");
QVERIFY(folderDialogListView);
QQuickFileDialogDelegate *firstDelegate = nullptr;
QTRY_VERIFY(findViewDelegateItem(folderDialogListView, 0, firstDelegate));
QCOMPARE(dialogHelper.popupWindow()->activeFocusItem(), firstDelegate);
}
void tst_QQuickFolderDialogImpl::goIntoLargeFolder()
{
// Create a throwaway directory with a lot of folders within it...
QTemporaryDir anotherTempDir;
QVERIFY(anotherTempDir.isValid());
for (int i = 0; i < 30; ++i) {
QDir newDir(anotherTempDir.path());
QVERIFY(newDir.exists());
// Pad with zeroes so that the directories are ordered as we expect.
QVERIFY(newDir.mkdir(QString::fromLatin1("dir%1").arg(i, 2, 10, QLatin1Char('0'))));
}
// ... and within one of those folders, more folders.
QDir dir20(anotherTempDir.path() + "/dir20");
QVERIFY(dir20.exists());
for (int i = 0; i < 30; ++i) {
QDir newDir(dir20.path());
QVERIFY(newDir.exists());
QVERIFY(newDir.mkdir(QString::fromLatin1("subdir%1").arg(i, 2, 10, QLatin1Char('0'))));
}
// Open the dialog. Start off in the throwaway directory.
FolderDialogTestHelper dialogHelper(this, "bindCurrentFolder.qml", {},
{{ "initialFolder", QUrl::fromLocalFile(anotherTempDir.path()) }});
QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage());
QVERIFY(dialogHelper.waitForWindowActive());
QVERIFY(dialogHelper.openDialog());
QTRY_VERIFY(dialogHelper.isQuickDialogOpen());
// If the screen is so tall that the contentItem is not vertically larger than the view,
// then the test makes no sense.
auto folderDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("folderDialogListView");
QVERIFY(folderDialogListView);
if (QQuickTest::qIsPolishScheduled(folderDialogListView))
QVERIFY(QQuickTest::qWaitForPolish(folderDialogListView));
// Just to be safe, make sure it's at least twice as big.
if (folderDialogListView->contentItem()->height() < folderDialogListView->height() * 2) {
QSKIP(qPrintable(QString::fromLatin1("Expected height of folderDialogListView's contentItem (%1)" \
" to be at least twice as big as the ListView's height (%2)")
.arg(folderDialogListView->contentItem()->height()).arg(folderDialogListView->height())));
}
// Scroll down to dir20. The view should be somewhere past the middle.
QVERIFY(QMetaObject::invokeMethod(folderDialogListView, "positionViewAtIndex", Qt::DirectConnection,
Q_ARG(int, 20), Q_ARG(int, QQuickItemView::PositionMode::Center)));
QVERIFY(folderDialogListView->contentY() > 0);
// Go into it. The view should start at the top of the directory, not at the same contentY
// that it had in the previous directory.
QQuickFileDialogDelegate *dir20Delegate = nullptr;
QTRY_VERIFY(findViewDelegateItem(folderDialogListView, 20, dir20Delegate));
COMPARE_URL(dir20Delegate->file(), QUrl::fromLocalFile(dir20.path()));
QVERIFY(doubleClickButton(dir20Delegate));
COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(dir20.path()));
COMPARE_URL(dialogHelper.quickDialog->currentFolder(), QUrl::fromLocalFile(dir20.path()));
QCOMPARE(folderDialogListView->contentY(), 0);
}
void tst_QQuickFolderDialogImpl::clickOnBreadcrumb()
{
// Open the dialog, starting off in "sub-dir-1".
FolderDialogTestHelper dialogHelper(this, "bindCurrentFolder.qml", {},
{{ "initialFolder", QUrl::fromLocalFile(tempSubDir1CanonicalPath) }});
QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage());
QVERIFY(dialogHelper.waitForWindowActive());
QVERIFY(dialogHelper.openDialog());
QTRY_VERIFY(dialogHelper.isQuickDialogOpen());
// Find the breadcrumb bar delegate for the temp directory (parent of "sub-dir-1").
auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>();
QVERIFY(breadcrumbBar);
auto breadcrumbBarListView = qobject_cast<QQuickListView*>(breadcrumbBar->contentItem());
QVERIFY(breadcrumbBarListView);
QQuickAbstractButton *breadcrumbDelegate = nullptr;
// The following breadcrumb bar has 5 delegates:
// [ / > tmp > deadbeef ]
// We can assume that, regardless of which system we're running on, we'll have
// at least 3: one for the temp directory, and one for the > separator, and one for "sub-dir-1".
QVERIFY(breadcrumbBarListView->count() >= 3);
// - 1 to make it zero-based, and another 2 to get to the second-last directory, which is the parent of "sub-dir-1".
QTRY_VERIFY(findViewDelegateItem(breadcrumbBarListView, breadcrumbBarListView->count() - 3, breadcrumbDelegate));
QVERIFY(clickButton(breadcrumbDelegate));
QCOMPARE(dialogHelper.quickDialog->currentFolder(), QUrl::fromLocalFile(tempDirCanonicalPath));
QCOMPARE(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempDirCanonicalPath));
QCOMPARE(dialogHelper.quickDialog->selectedFolder(), QUrl::fromLocalFile(tempSubDir1CanonicalPath));
QCOMPARE(dialogHelper.dialog->selectedFolder(), QUrl::fromLocalFile(tempSubDir1CanonicalPath));
}
void tst_QQuickFolderDialogImpl::keyAndShortcutHandling()
{
// Open the dialog.
FolderDialogTestHelper dialogHelper(this, "folderDialog.qml");
QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage());
QVERIFY(dialogHelper.waitForWindowActive());
QVERIFY(dialogHelper.openDialog());
QTRY_VERIFY(dialogHelper.isQuickDialogOpen());
QVERIFY(dialogHelper.waitForPopupWindowActiveAndPolished());
#if QT_CONFIG(shortcut)
// Get the text edit visible with Ctrl+L.
const auto editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L);
QTest::keySequence(dialogHelper.popupWindow(), editPathKeySequence);
#endif
auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>();
QVERIFY(breadcrumbBar);
QTRY_VERIFY_ACTIVE_FOCUS(breadcrumbBar->textField());
QVERIFY(breadcrumbBar->textField()->isVisible());
QCOMPARE(breadcrumbBar->textField()->text(), dialogHelper.dialog->currentFolder().toLocalFile());
QCOMPARE(breadcrumbBar->textField()->selectedText(), breadcrumbBar->textField()->text());
#if QT_CONFIG(shortcut)
// Ctrl+L shouldn't hide it.
QTest::keySequence(dialogHelper.popupWindow(), editPathKeySequence);
QVERIFY(breadcrumbBar->textField()->isVisible());
#endif
// Cancel it with the escape key.
QTest::keyClick(dialogHelper.popupWindow(), Qt::Key_Escape);
QVERIFY(!breadcrumbBar->textField()->isVisible());
QVERIFY(dialogHelper.dialog->isVisible());
#if QT_CONFIG(shortcut)
// Make it visible.
QTest::keySequence(dialogHelper.popupWindow(), editPathKeySequence);
QVERIFY(breadcrumbBar->textField()->isVisible());
#endif
// Cancel it with the escape key again.
QTest::keyClick(dialogHelper.popupWindow(), Qt::Key_Escape);
QVERIFY(!breadcrumbBar->textField()->isVisible());
QVERIFY(dialogHelper.dialog->isVisible());
// Pressing escape now should close the dialog.
QTest::keyClick(dialogHelper.popupWindow(), Qt::Key_Escape);
QVERIFY(!dialogHelper.dialog->isVisible());
QTRY_VERIFY(!dialogHelper.quickDialog->isVisible());
}
void tst_QQuickFolderDialogImpl::tabFocusNavigation()
{
// Open the dialog.
FolderDialogTestHelper dialogHelper(this, "folderDialog.qml");
QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage());
QVERIFY(dialogHelper.waitForWindowActive());
QVERIFY(dialogHelper.openDialog());
QTRY_VERIFY(dialogHelper.isQuickDialogOpen());
QVERIFY(dialogHelper.waitForPopupWindowActiveAndPolished());
QList<QQuickItem*> expectedFocusItems;
// The initial focus should be on the first delegate.
auto folderDialogListView = dialogHelper.quickDialog->findChild<QQuickListView*>("folderDialogListView");
QVERIFY(folderDialogListView);
QQuickFileDialogDelegate *firstDelegate = nullptr;
QTRY_VERIFY(findViewDelegateItem(folderDialogListView, 0, firstDelegate));
expectedFocusItems << firstDelegate;
// Next, the left-most dialog button.
auto dialogButtonBox = qobject_cast<QQuickDialogButtonBox*>(dialogHelper.quickDialog->footer());
QVERIFY(dialogButtonBox);
QCOMPARE(dialogButtonBox->count(), 2);
auto leftMostButton = qobject_cast<QQuickAbstractButton*>(dialogButtonBox->itemAt(0));
QVERIFY(leftMostButton);
expectedFocusItems << leftMostButton;
// Then, the right-most dialog button.
auto rightMostButton = qobject_cast<QQuickAbstractButton*>(dialogButtonBox->itemAt(1));
QVERIFY(rightMostButton);
expectedFocusItems << rightMostButton;
// Then, the up button.
auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>();
QVERIFY(breadcrumbBar);
expectedFocusItems << breadcrumbBar->upButton();
// Finally, add each bread crumb delegate.
for (int i = 0; i < folderDialogListView->count(); ++i) {
QQuickFileDialogDelegate *delegate = nullptr;
QTRY_VERIFY(findViewDelegateItem(folderDialogListView, i, delegate));
expectedFocusItems << delegate;
}
// Tab through each item, checking the focus after each.
for (auto expectedFocusItem : std::as_const(expectedFocusItems)) {
// Check the focus item first so that we account for the first item.
// Print detailed failure message as workaround for QTBUG-92102.
QVERIFY2(dialogHelper.popupWindow()->activeFocusItem() == expectedFocusItem, qPrintable(QString::fromLatin1(
"\n Actual: %1\n Expected: %2").arg(QDebug::toString(dialogHelper.popupWindow()->activeFocusItem()))
.arg(QDebug::toString(expectedFocusItem))));
if (expectedFocusItem != expectedFocusItems.last())
QTest::keyClick(dialogHelper.popupWindow(), Qt::Key_Tab);
}
// Ensure the order is reversed when shift-tabbing.
std::reverse(expectedFocusItems.begin(), expectedFocusItems.end());
// We know the first (last) item has focus already, so skip it.
expectedFocusItems.removeFirst();
for (auto expectedFocusItem : std::as_const(expectedFocusItems)) {
QTest::keyClick(dialogHelper.popupWindow(), Qt::Key_Tab, Qt::ShiftModifier);
QCOMPARE(dialogHelper.popupWindow()->activeFocusItem(), expectedFocusItem);
}
}
void tst_QQuickFolderDialogImpl::acceptRejectLabel()
{
// Open the dialog.
FolderDialogTestHelper dialogHelper(this, "acceptRejectLabel.qml");
QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage());
QVERIFY(dialogHelper.waitForWindowActive());
QVERIFY(dialogHelper.openDialog());
QTRY_VERIFY(dialogHelper.isQuickDialogOpen());
// Check that the accept and reject buttons' labels have changed.
auto dialogButtonBox = qobject_cast<QQuickDialogButtonBox*>(dialogHelper.quickDialog->footer());
QVERIFY(dialogButtonBox);
QVERIFY(findDialogButton(dialogButtonBox, "AcceptTest"));
QVERIFY(findDialogButton(dialogButtonBox, "RejectTest"));
// Close the dialog.
dialogHelper.dialog->close();
QVERIFY(!dialogHelper.dialog->isVisible());
QTRY_VERIFY(!dialogHelper.quickDialog->isVisible());
// Reset back to the default text.
dialogHelper.dialog->resetAcceptLabel();
dialogHelper.dialog->resetRejectLabel();
// Re-open the dialog.
dialogHelper.dialog->open();
QVERIFY(dialogHelper.dialog->isVisible());
QTRY_VERIFY(dialogHelper.quickDialog->isOpened());
// Check that the defaults are back.
const QString openText = QQuickDialogButtonBoxPrivate::buttonText(QPlatformDialogHelper::Open);
QVERIFY2(findDialogButton(dialogButtonBox, openText), qPrintable(QString::fromLatin1(
"Failed to find dialog button with text \"%1\"").arg(openText)));
const QString cancelText = QQuickDialogButtonBoxPrivate::buttonText(QPlatformDialogHelper::Cancel);
QVERIFY2(findDialogButton(dialogButtonBox, cancelText), qPrintable(QString::fromLatin1(
"Failed to find dialog button with text \"%1\"").arg(cancelText)));
}
void tst_QQuickFolderDialogImpl::bindTitle()
{
// Open the dialog.
FolderDialogTestHelper dialogHelper(this, "bindTitle.qml");
QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage());
QVERIFY(dialogHelper.waitForWindowActive());
QVERIFY(dialogHelper.openDialog());
QTRY_VERIFY(dialogHelper.isQuickDialogOpen());
// Open the dialog and check that the correct title is displayed.
const QString expectedTitle = QLatin1String("Test Title");
QCOMPARE(dialogHelper.dialog->title(), expectedTitle);
QCOMPARE(dialogHelper.quickDialog->title(), expectedTitle);
auto header = dialogHelper.quickDialog->header();
QVERIFY(header);
auto dialogTitleBarLabel = dialogHelper.quickDialog->header()->findChild<QQuickLabel*>("dialogTitleBarLabel");
QVERIFY(dialogTitleBarLabel);
QCOMPARE(dialogTitleBarLabel->text(), expectedTitle);
}
void tst_QQuickFolderDialogImpl::itemsDisabledWhenNecessary()
{
// Open the dialog.
FolderDialogTestHelper dialogHelper(this, "bindCurrentFolder.qml", {},
{{ "initialFolder", QUrl::fromLocalFile(tempSubDir2CanonicalPath) }});
QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage());
QVERIFY(dialogHelper.waitForWindowActive());
QVERIFY(dialogHelper.openDialog());
QTRY_VERIFY(dialogHelper.isQuickDialogOpen());
COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempSubDir2CanonicalPath));
COMPARE_URL(dialogHelper.quickDialog->currentFolder(), QUrl::fromLocalFile(tempSubDir2CanonicalPath));
QVERIFY(dialogHelper.waitForPopupWindowActiveAndPolished());
// We opened it in a folder that has no files, so the Open button should be disabled.
QVERIFY(dialogHelper.quickDialog->footer());
auto dialogButtonBox = qobject_cast<QQuickDialogButtonBox*>(dialogHelper.quickDialog->footer());
QVERIFY(dialogButtonBox);
QQuickAbstractButton* openButton = findDialogButton(dialogButtonBox, "Open");
QVERIFY(openButton);
QCOMPARE(openButton->isEnabled(), false);
// Now go up. The Open button should now be enabled.
auto breadcrumbBar = dialogHelper.quickDialog->findChild<QQuickFolderBreadcrumbBar*>();
QVERIFY(breadcrumbBar);
QVERIFY(clickButton(breadcrumbBar->upButton()));
QCOMPARE(openButton->isEnabled(), true);
COMPARE_URL(dialogHelper.dialog->currentFolder(), QUrl::fromLocalFile(tempDirCanonicalPath));
COMPARE_URL(dialogHelper.quickDialog->currentFolder(), QUrl::fromLocalFile(tempDirCanonicalPath));
#if QT_CONFIG(shortcut)
// Get the text edit visible with Ctrl+L. The Open button should now be disabled.
const auto editPathKeySequence = QKeySequence(Qt::CTRL | Qt::Key_L);
QTest::keySequence(dialogHelper.popupWindow(), editPathKeySequence);
QVERIFY(breadcrumbBar->textField()->isVisible());
QCOMPARE(openButton->isEnabled(), false);
#endif
// Hide it with the escape key. The Open button should now be enabled.
QTest::keyClick(dialogHelper.popupWindow(), Qt::Key_Escape);
QVERIFY(!breadcrumbBar->textField()->isVisible());
QCOMPARE(openButton->isEnabled(), true);
}
void tst_QQuickFolderDialogImpl::done_data()
{
QTest::addColumn<QQuickAbstractDialog::StandardCode>("result");
QTest::newRow("Accepted") << QQuickAbstractDialog::Accepted;
QTest::newRow("Rejected") << QQuickAbstractDialog::Rejected;
}
void tst_QQuickFolderDialogImpl::done()
{
QFETCH(QQuickAbstractDialog::StandardCode, result);
// Open the dialog.
FolderDialogTestHelper dialogHelper(this, "folderDialog.qml");
QVERIFY2(dialogHelper.isWindowInitialized(), dialogHelper.failureMessage());
QVERIFY(dialogHelper.waitForWindowActive());
QVERIFY(dialogHelper.openDialog());
QTRY_VERIFY(dialogHelper.isQuickDialogOpen());
// Call the "done" slot manually.
switch (result) {
case QQuickAbstractDialog::Accepted:
QVERIFY(QMetaObject::invokeMethod(dialogHelper.window(), "doneAccepted"));
break;
case QQuickAbstractDialog::Rejected:
QVERIFY(QMetaObject::invokeMethod(dialogHelper.window(), "doneRejected"));
break;
}
QVERIFY(!dialogHelper.dialog->isVisible());
QTRY_VERIFY(!dialogHelper.quickDialog->isVisible());
QCOMPARE(dialogHelper.dialog->result(), result);
}
void tst_QQuickFolderDialogImpl::checkModality_data()
{
QTest::addColumn<Qt::WindowModality>("modality");
QTest::addColumn<QFileDialogOptions::FileDialogOption>("options");
QTest::addColumn<int>("expectedRootWindowChildCount");
QTest::addColumn<int>("expectedChildWindowClickCount");
QTest::newRow("nonModal") << Qt::NonModal << QFileDialogOptions::DontUseNativeDialog << 1 << 1;
QTest::newRow("windowModal") << Qt::WindowModal << QFileDialogOptions::DontUseNativeDialog << 0 << 1;
// Verify application modal case only for the platform which supports multiple windows
if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::MultipleWindows))
QTest::newRow("applicationModal") << Qt::ApplicationModal << QFileDialogOptions::DontUseNativeDialog << 0 << 0;
}
void tst_QQuickFolderDialogImpl::checkModality()
{
QFETCH(Qt::WindowModality, modality);
QFETCH(QFileDialogOptions::FileDialogOption, options);
QFETCH(int, expectedRootWindowChildCount);
QFETCH(int, expectedChildWindowClickCount);
FolderDialogTestHelper dialogHelper(this, "checkModality.qml");
dialogHelper.dialog->setModality(modality);
QCOMPARE(dialogHelper.dialog->modality(), modality);
dialogHelper.dialog->setOptions(options);
QCOMPARE(dialogHelper.dialog->options(), options);
OPEN_QUICK_DIALOG();
QVERIFY(dialogHelper.waitForPopupWindowActiveAndPolished());
auto *childWindow = dialogHelper.window()->property("childWindow").value<QQuickWindow *>();
QVERIFY(childWindow);
// Setting the child window transient parent as root window won't allow it to receive mouse
// events, causing WindowModal case not to be tested. Thus, resetting the transient parent.
if (modality == Qt::WindowModal)
childWindow->setTransientParent(nullptr);
const auto *rootMouseArea = dialogHelper.window()->property("rootMArea").value<QQuickMouseArea *>();
QVERIFY(rootMouseArea);
QSignalSpy rmaMouseSpy(rootMouseArea, &QQuickMouseArea::clicked);
QTest::mouseClick(dialogHelper.window(), Qt::LeftButton, Qt::NoModifier, QPoint(5, 5));
QCOMPARE(rmaMouseSpy.size(), expectedRootWindowChildCount);
const auto *childMouseArea = dialogHelper.window()->property("childMArea").value<QQuickMouseArea *>();
QVERIFY(childMouseArea);
QSignalSpy cmaMouseSpy(childMouseArea, &QQuickMouseArea::clicked);
QTest::mouseClick(childWindow, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5));
QCOMPARE(cmaMouseSpy.size(), expectedChildWindowClickCount);
}
void tst_QQuickFolderDialogImpl::checkFrameless()
{
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
QSKIP("Frameless window is not supported on Android/IOS");
#endif
FolderDialogTestHelper dialogHelper(this, "folderDialogFrameless.qml");
OPEN_QUICK_DIALOG();
QVERIFY(dialogHelper.waitForPopupWindowActiveAndPolished());
QVERIFY(dialogHelper.popupWindow()->flags().testFlag(Qt::FramelessWindowHint));
}
QTEST_MAIN(tst_QQuickFolderDialogImpl)
#include "tst_qquickfolderdialogimpl.moc"