summaryrefslogtreecommitdiffstats
path: root/tests/manual
diff options
context:
space:
mode:
Diffstat (limited to 'tests/manual')
-rw-r--r--tests/manual/sandboxed_file_access/CMakeLists.txt71
-rw-r--r--tests/manual/sandboxed_file_access/Info.plist.ios90
-rw-r--r--tests/manual/sandboxed_file_access/Info.plist.macos68
-rw-r--r--tests/manual/sandboxed_file_access/app.entitlements10
-rw-r--r--tests/manual/sandboxed_file_access/tst_sandboxed_file_access.cpp422
-rw-r--r--tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp8
6 files changed, 668 insertions, 1 deletions
diff --git a/tests/manual/sandboxed_file_access/CMakeLists.txt b/tests/manual/sandboxed_file_access/CMakeLists.txt
new file mode 100644
index 00000000000..8df09401cf9
--- /dev/null
+++ b/tests/manual/sandboxed_file_access/CMakeLists.txt
@@ -0,0 +1,71 @@
+# Copyright (C) 2025 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_manual_sandboxed_file_access LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
+qt_standard_project_setup()
+
+qt_add_executable(tst_manual_sandboxed_file_access
+ tst_sandboxed_file_access.cpp
+)
+
+target_link_libraries(tst_manual_sandboxed_file_access PRIVATE
+ Qt::CorePrivate
+ Qt::Widgets
+ Qt::Test
+)
+
+enable_language(OBJCXX)
+set_source_files_properties(tst_sandboxed_file_access.cpp PROPERTIES LANGUAGE OBJCXX)
+
+if(MACOS)
+ target_sources(tst_manual_sandboxed_file_access PRIVATE app.entitlements)
+ set_target_properties(tst_manual_sandboxed_file_access PROPERTIES
+ MACOSX_BUNDLE TRUE
+ MACOSX_BUNDLE_GUI_IDENTIFIER "io.qt.dev.tst-manual-sandboxed-file-access"
+ XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/app.entitlements"
+ XCODE_ATTRIBUTE_COPY_PHASE_STRIP FALSE
+ )
+ if(NOT CMAKE_GENERATOR STREQUAL "Xcode")
+ set_target_properties(tst_manual_sandboxed_file_access PROPERTIES
+ RESOURCE "${CMAKE_CURRENT_SOURCE_DIR}/app.entitlements"
+ )
+ endif()
+
+ set(platform_plugin "${QT6_INSTALL_PREFIX}/${QT6_INSTALL_PLUGINS}/platforms/libqcocoa.dylib")
+ target_sources(tst_manual_sandboxed_file_access PRIVATE ${platform_plugin})
+ set_source_files_properties(${platform_plugin}
+ PROPERTIES
+ MACOSX_PACKAGE_LOCATION PlugIns/platforms
+ )
+
+ target_compile_definitions(tst_manual_sandboxed_file_access PRIVATE
+ QTEST_THROW_ON_FAIL
+ QTEST_THROW_ON_SKIP
+ )
+endif()
+
+if(IOS)
+ set(plist_path "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.ios")
+else()
+ set(plist_path "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.macos")
+endif()
+
+set_target_properties(tst_manual_sandboxed_file_access
+ PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${plist_path}")
+
+install(TARGETS tst_manual_sandboxed_file_access
+ BUNDLE DESTINATION .
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+)
+
+qt_generate_deploy_app_script(
+ TARGET tst_manual_sandboxed_file_access
+ OUTPUT_SCRIPT deploy_script
+ NO_UNSUPPORTED_PLATFORM_ERROR
+)
+install(SCRIPT ${deploy_script})
diff --git a/tests/manual/sandboxed_file_access/Info.plist.ios b/tests/manual/sandboxed_file_access/Info.plist.ios
new file mode 100644
index 00000000000..c6072cffa92
--- /dev/null
+++ b/tests/manual/sandboxed_file_access/Info.plist.ios
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>BuildMachineOSBuild</key>
+ <string>25B78</string>
+ <key>CFBundleAllowMixedLocalizations</key>
+ <true/>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleDisplayName</key>
+ <string>tst_manual_sandboxed_file_access</string>
+ <key>CFBundleExecutable</key>
+ <string>tst_manual_sandboxed_file_access</string>
+ <key>CFBundleIdentifier</key>
+ <string>io.qt.fb.tst-manual-sandboxed-file-access</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>tst_manual_sandboxed_file_access</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>6.0</string>
+ <key>CFBundleSupportedPlatforms</key>
+ <array>
+ <string>iPhoneOS</string>
+ </array>
+ <key>CFBundleVersion</key>
+ <string>6.0.0</string>
+ <key>DTCompiler</key>
+ <string>com.apple.compilers.llvm.clang.1_0</string>
+ <key>DTPlatformBuild</key>
+ <string>23B77</string>
+ <key>DTPlatformName</key>
+ <string>iphoneos</string>
+ <key>DTPlatformVersion</key>
+ <string>26.1</string>
+ <key>DTSDKBuild</key>
+ <string>23B77</string>
+ <key>DTSDKName</key>
+ <string>iphoneos26.1</string>
+ <key>DTXcode</key>
+ <string>2610</string>
+ <key>DTXcodeBuild</key>
+ <string>17B55</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>MinimumOSVersion</key>
+ <string>17</string>
+ <key>NOTE</key>
+ <string>This file was generated by Qt's default CMake support.</string>
+ <key>UIDeviceFamily</key>
+ <array>
+ <integer>1</integer>
+ <integer>2</integer>
+ </array>
+ <key>UILaunchStoryboardName</key>
+ <string>LaunchScreen</string>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>arm64</string>
+ </array>
+ <key>UISupportedInterfaceOrientations</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationPortraitUpsideDown</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+ <key>CFBundleDocumentTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSItemContentTypes</key>
+ <array>
+ <string>public.text</string>
+ </array>
+ <!-- These two don't seem to be needed to make things work -->
+ <key>LSHandlerRank</key>
+ <string>Default</string>
+ <key>CFBundleTypeName</key>
+ <string>Text files</string>
+ </dict>
+ </array>
+ <key>UISupportsDocumentBrowser</key>
+ <true/>
+</dict>
+</plist>
diff --git a/tests/manual/sandboxed_file_access/Info.plist.macos b/tests/manual/sandboxed_file_access/Info.plist.macos
new file mode 100644
index 00000000000..81a93f0353f
--- /dev/null
+++ b/tests/manual/sandboxed_file_access/Info.plist.macos
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>BuildMachineOSBuild</key>
+ <string>25B78</string>
+ <key>CFBundleAllowMixedLocalizations</key>
+ <true/>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>tst_manual_sandboxed_file_access</string>
+ <key>CFBundleIdentifier</key>
+ <string>io.qt.dev.tst-manual-sandboxed-file-access</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>tst_manual_sandboxed_file_access</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>6.0</string>
+ <key>CFBundleSupportedPlatforms</key>
+ <array>
+ <string>MacOSX</string>
+ </array>
+ <key>CFBundleVersion</key>
+ <string>6.0.0</string>
+ <key>DTCompiler</key>
+ <string>com.apple.compilers.llvm.clang.1_0</string>
+ <key>DTPlatformBuild</key>
+ <string>25B74</string>
+ <key>DTPlatformName</key>
+ <string>macosx</string>
+ <key>DTPlatformVersion</key>
+ <string>26.1</string>
+ <key>DTSDKBuild</key>
+ <string>25B74</string>
+ <key>DTSDKName</key>
+ <string>macosx26.1</string>
+ <key>DTXcode</key>
+ <string>2610</string>
+ <key>DTXcodeBuild</key>
+ <string>17B55</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>13</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+ <key>NSSupportsAutomaticGraphicsSwitching</key>
+ <true/>
+ <key>CFBundleDocumentTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSItemContentTypes</key>
+ <array>
+ <string>public.text</string>
+ </array>
+ <!-- These two don't seem to be needed to make things work -->
+ <key>LSHandlerRank</key>
+ <string>Default</string>
+ <key>CFBundleTypeName</key>
+ <string>Text files</string>
+ </dict>
+ </array>
+</dict>
+</plist>
diff --git a/tests/manual/sandboxed_file_access/app.entitlements b/tests/manual/sandboxed_file_access/app.entitlements
new file mode 100644
index 00000000000..6d968edb4f8
--- /dev/null
+++ b/tests/manual/sandboxed_file_access/app.entitlements
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>com.apple.security.app-sandbox</key>
+ <true/>
+ <key>com.apple.security.files.user-selected.read-write</key>
+ <true/>
+</dict>
+</plist>
diff --git a/tests/manual/sandboxed_file_access/tst_sandboxed_file_access.cpp b/tests/manual/sandboxed_file_access/tst_sandboxed_file_access.cpp
new file mode 100644
index 00000000000..18381ce0c8c
--- /dev/null
+++ b/tests/manual/sandboxed_file_access/tst_sandboxed_file_access.cpp
@@ -0,0 +1,422 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtCore>
+#include <QtWidgets>
+#include <QtTest>
+
+#include <Foundation/Foundation.h>
+
+#if defined(Q_OS_MACOS) && defined(QT_BUILD_INTERNAL)
+#include <private/qcore_mac_p.h>
+Q_CONSTRUCTOR_FUNCTION(qt_mac_ensureResponsible);
+#endif
+
+class tst_SandboxedFileAccess : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+
+ void alwaysAccessibleLocations();
+
+ void standardPaths_data();
+ void standardPaths();
+
+ void readSingleFile();
+ void writeSingleFile();
+ void writeSingleFileNonCanonical();
+
+ void removeFile();
+ void trashFile();
+
+ void readFileAfterRestart();
+
+ void directoryAccess();
+
+ void securityScopedTargetFile();
+
+ void fileOpenEvent();
+
+private:
+ void writeFile(const QString &fileName);
+ QByteArray readFile(const QString &fileName);
+
+ QString getFileName(QFileDialog::AcceptMode, QFileDialog::FileMode,
+ const QString &action = QString(), const QString &fileName = QString());
+
+ QString sandboxPath() const
+ {
+ return QStandardPaths::standardLocations(QStandardPaths::HomeLocation).first();
+ }
+
+ QString bundlePath() const
+ {
+ QString path = QCoreApplication::applicationDirPath();
+#if defined(Q_OS_MACOS)
+ path.remove("/Contents/MacOS");
+#endif
+ return path;
+ }
+
+ QStringList m_persistedFileNames;
+ QPointer<QWidget> m_widget;
+};
+
+void tst_SandboxedFileAccess::initTestCase()
+{
+ qDebug() << "đŸ“Ļ App bundle" << bundlePath();
+ qDebug() << "🔐 App container" << sandboxPath();
+
+ m_widget = new QWidget;
+ m_widget->show();
+ QVERIFY(QTest::qWaitForWindowExposed(m_widget));
+}
+
+void tst_SandboxedFileAccess::cleanupTestCase()
+{
+ NSURL *appSupportDir = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)).toNSURL();
+ NSURL *bookmarksFile = [appSupportDir URLByAppendingPathComponent:@"SecurityScopedBookmarks.plist"];
+ NSError *error = nullptr;
+ NSMutableDictionary *bookmarks = [[NSDictionary dictionaryWithContentsOfURL:bookmarksFile
+ error:&error] mutableCopy];
+ for (NSString *path in bookmarks.allKeys) {
+ if (m_persistedFileNames.contains(QString::fromNSString(path))) {
+ qDebug() << "Keeping knowledge of persisted path" << path;
+ continue;
+ }
+ qDebug() << "Wiping knowledge of path" << path;
+ [bookmarks removeObjectForKey:path];
+ }
+ [bookmarks writeToURL:bookmarksFile error:&error];
+
+ qGuiApp->quit();
+}
+
+void tst_SandboxedFileAccess::alwaysAccessibleLocations()
+{
+ readFile(QCoreApplication::applicationFilePath());
+
+ // The documents location is inside the sandbox and writable on both iOS and macOS
+ auto documents = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
+ writeFile(documents + "/test-writable-file.txt");
+}
+
+void tst_SandboxedFileAccess::standardPaths_data()
+{
+ QTest::addColumn<QStandardPaths::StandardLocation>("location");
+ auto standardLocations = QMetaEnum::fromType<QStandardPaths::StandardLocation>();
+ for (int i = 0; i < standardLocations.keyCount(); ++i)
+ QTest::newRow(standardLocations.key(i)) << QStandardPaths::StandardLocation(standardLocations.value(i));
+}
+
+void tst_SandboxedFileAccess::standardPaths()
+{
+ QFETCH(QStandardPaths::StandardLocation, location);
+ auto writableLocation = QStandardPaths::writableLocation(location);
+
+ if (writableLocation.isEmpty())
+ QSKIP("There's no writable location for this location");
+
+ QFileInfo info(writableLocation);
+ if (info.isSymLink() && !info.symLinkTarget().startsWith(sandboxPath()))
+ QSKIP("This location is a symlink to outside the sandbox and requires access");
+
+ QVERIFY(QDir().mkpath(writableLocation));
+
+#if !defined(Q_OS_MACOS)
+ QEXPECT_FAIL("HomeLocation", "The sandbox root is not writable on iOS", Abort);
+#endif
+ writeFile(writableLocation + QString("/test-writable-file-%1.txt").arg(QTest::currentDataTag()));
+}
+
+void tst_SandboxedFileAccess::readSingleFile()
+{
+ QString filePath = getFileName(QFileDialog::AcceptOpen,
+ QFileDialog::ExistingFile, "Choose file to read");
+ readFile(filePath);
+
+ {
+ QFile file(QCoreApplication::applicationFilePath());
+ QVERIFY(file.open(QFile::ReadOnly));
+ QByteArray plistContent = file.read(100);
+ file.close();
+
+ // Check that setFileName can target a security scoped file
+ file.setFileName(filePath);
+ QVERIFY(file.open(QFile::ReadOnly));
+ QVERIFY(file.isReadable());
+ QCOMPARE_NE(file.read(100), plistContent);
+ }
+
+ QDir dir;
+ QString fileName;
+
+ {
+ QFileInfo info(filePath);
+ dir = info.path();
+ fileName = info.fileName();
+ QVERIFY(dir.exists());
+ QVERIFY(!fileName.isEmpty());
+ }
+
+ // Check that we're able to access files via non-canonical paths
+ readFile(dir.absolutePath() + "/../" + dir.dirName() + "/" + fileName);
+}
+
+QByteArray tst_SandboxedFileAccess::readFile(const QString &fileName)
+{
+ QFile file(fileName);
+ QVERIFY(file.exists());
+ QVERIFY(file.open(QFile::ReadOnly));
+ QVERIFY(file.isReadable());
+ QByteArray data = file.read(100);
+ QVERIFY(!data.isEmpty());
+ return data;
+}
+
+void tst_SandboxedFileAccess::writeSingleFile()
+{
+ QString filePath = getFileName(QFileDialog::AcceptSave, QFileDialog::AnyFile,
+ "Choose a file to write", "write-single-file.txt");
+ writeFile(filePath);
+ readFile(filePath);
+}
+
+void tst_SandboxedFileAccess::writeSingleFileNonCanonical()
+{
+ QString filePath = getFileName(QFileDialog::AcceptSave, QFileDialog::AnyFile,
+ "Choose a file to write", "write-single-file-non-canonical.txt");
+ QDir dir;
+ QString fileName;
+
+ {
+ QFileInfo info(filePath);
+ dir = info.path();
+ fileName = info.fileName();
+ QVERIFY(dir.exists());
+ QVERIFY(!fileName.isEmpty());
+ }
+
+ writeFile(dir.absolutePath() + "/../" + dir.dirName() + "/" + fileName);
+ readFile(filePath);
+}
+
+void tst_SandboxedFileAccess::writeFile(const QString &fileName)
+{
+ QFile file(fileName);
+ QVERIFY(file.open(QFile::WriteOnly));
+ QVERIFY(file.isWritable());
+ QVERIFY(file.write("Hello world"));
+}
+
+void tst_SandboxedFileAccess::removeFile()
+{
+ QString fileName = getFileName(QFileDialog::AcceptSave, QFileDialog::AnyFile,
+ "Choose a file to write and then remove", "write-and-remove-file.txt");
+ writeFile(fileName);
+
+ {
+ QFile file(fileName);
+ QVERIFY(file.remove());
+ }
+}
+
+void tst_SandboxedFileAccess::trashFile()
+{
+ QString fileName = getFileName(QFileDialog::AcceptSave, QFileDialog::AnyFile,
+ "Choose a file to write and then trash", "write-and-trash-file.txt");
+ writeFile(fileName);
+
+ {
+ QFile file(fileName);
+ QVERIFY(file.moveToTrash());
+ }
+}
+
+void tst_SandboxedFileAccess::readFileAfterRestart()
+{
+ // Every other restart of the app will save a file or load a previously saved file
+
+ QSettings settings;
+ QString savedFile = settings.value("savedFile").toString();
+ if (savedFile.isEmpty()) {
+ QString filePath = getFileName(QFileDialog::AcceptSave, QFileDialog::AnyFile,
+ "Choose a file to write for reading after restart", "write-and-read-after-restart.txt");
+ qDebug() << "Writing" << filePath << "and saving to preferences";
+ writeFile(filePath);
+ settings.setValue("savedFile", filePath);
+ m_persistedFileNames << filePath;
+ } else {
+ qDebug() << "Loading" << savedFile << "from preferences";
+ settings.remove("savedFile"); // Remove up front, in case this fails
+ readFile(savedFile);
+ QFile file(savedFile);
+ QVERIFY(file.remove());
+ }
+}
+
+void tst_SandboxedFileAccess::directoryAccess()
+{
+ // Every other restart of the app will re-establish access to the folder,
+ // or re-use previous access.
+
+ QSettings settings;
+ QString directory = settings.value("savedDirectory").toString();
+ if (directory.isEmpty()) {
+ directory = getFileName(QFileDialog::AcceptOpen, QFileDialog::Directory,
+ "Choose a directory we can create some files in");
+ auto canonical = QFileInfo(directory).canonicalFilePath();
+ QVERIFY(!canonical.isEmpty());
+ directory = canonical;
+ settings.setValue("savedDirectory", directory);
+ m_persistedFileNames << QFileInfo(directory).canonicalFilePath();
+ } else {
+ settings.remove("savedDirectory");
+ }
+ settings.sync();
+
+ QString fileInDir;
+
+ {
+ QDir dir(directory);
+ QVERIFY(dir.exists());
+ QVERIFY(dir.isReadable());
+ fileInDir = dir.filePath("file-in-dir.txt");
+ }
+
+ writeFile(fileInDir);
+ readFile(fileInDir);
+
+ {
+ QDir dir(directory);
+ QVERIFY(dir.count() > 0);
+ QVERIFY(dir.entryList().contains("file-in-dir.txt"));
+ }
+
+ {
+ QDir dir(directory);
+ QVERIFY(dir.mkdir("subdirectory"));
+ QVERIFY(dir.entryList().contains("subdirectory"));
+ fileInDir = dir.filePath("subdirectory/file-in-subdir.txt");
+ }
+
+ writeFile(fileInDir);
+ readFile(fileInDir);
+
+ // Check that we can write to a non-canonical path within the directory
+ // we have access to, and then read it from the canonical path.
+ writeFile(directory + "/subdirectory/../non-existing-non-canonical.txt");
+ readFile(directory + "/non-existing-non-canonical.txt");
+
+ {
+ QDir dir(directory);
+ QVERIFY(dir.cd("subdirectory"));
+ dir.removeRecursively();
+ }
+}
+
+void tst_SandboxedFileAccess::securityScopedTargetFile()
+{
+ // This is a non-security scoped file
+ auto documents = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
+ QString sourceFilePath = documents + "/test-security-scoped-target-file.txt";
+ writeFile(sourceFilePath);
+ QFile sourceFile(sourceFilePath);
+
+ QString directory = getFileName(QFileDialog::AcceptOpen, QFileDialog::Directory,
+ "Choose a directory we can link/copy some to");
+
+ QString subDirectory;
+ {
+ QDir dir(directory);
+ QVERIFY(dir.mkdir("subdirectory"));
+ QVERIFY(dir.entryList().contains("subdirectory"));
+ subDirectory = dir.filePath("subdirectory");
+ }
+
+ QVERIFY(sourceFile.copy(subDirectory + "/copied-file.txt"));
+ QVERIFY(sourceFile.link(subDirectory + "/linked-file.txt"));
+ QVERIFY(sourceFile.rename(subDirectory + "/renamed-file.txt"));
+
+ {
+ QDir dir(directory);
+ QVERIFY(dir.cd("subdirectory"));
+ dir.removeRecursively();
+ }
+}
+
+void tst_SandboxedFileAccess::fileOpenEvent()
+{
+ struct OpenEventFilter : public QObject
+ {
+ bool eventFilter(QObject *watched, QEvent *event) override
+ {
+ if (event->type() == QEvent::FileOpen) {
+ QFileOpenEvent *openEvent = static_cast<QFileOpenEvent *>(event);
+ fileName = openEvent->file();
+ }
+
+ return QObject::eventFilter(watched, event);
+ }
+
+ QString fileName;
+ };
+
+ OpenEventFilter openEventFilter;
+ qGuiApp->installEventFilter(&openEventFilter);
+
+ m_widget->setLayout(new QVBoxLayout);
+ QLabel label;
+ label.setWordWrap(true);
+ m_widget->layout()->addWidget(&label);
+#if defined(Q_OS_MACOS)
+ label.setText("Drag a text file to the app's Dock icon, or open in the app via Finder's 'Open With' menu");
+#else
+ label.setText("Open the Files app, and choose 'Open With' or share a text document with this app");
+#endif
+ label.show();
+
+ QTRY_VERIFY_WITH_TIMEOUT(!openEventFilter.fileName.isNull(), 30s);
+ label.setText("Got file: " + openEventFilter.fileName);
+
+ readFile(openEventFilter.fileName);
+
+ QTest::qWait(3000);
+}
+
+QString tst_SandboxedFileAccess::getFileName(QFileDialog::AcceptMode acceptMode, QFileDialog::FileMode fileMode,
+ const QString &action, const QString &fileName)
+{
+ QFileDialog dialog(m_widget);
+ dialog.setAcceptMode(acceptMode);
+ dialog.setFileMode(fileMode);
+ dialog.setWindowTitle(action);
+ dialog.setLabelText(QFileDialog::Accept, action);
+ dialog.selectFile(fileName);
+ if (!action.isEmpty())
+ qDebug() << "â„šī¸" << action;
+ dialog.exec();
+ auto selectedFiles = dialog.selectedFiles();
+ return selectedFiles.count() ? selectedFiles.first() : QString();
+}
+
+int main(int argc, char** argv)
+{
+ QApplication app(argc, argv);
+
+ tst_SandboxedFileAccess testObject;
+
+ // Run tests with QApp running
+ int testExecResult = 0;
+ QMetaObject::invokeMethod(&testObject, [&]{
+ testExecResult = QTest::qExec(&testObject, argc, argv);
+ }, Qt::QueuedConnection);
+
+ [[maybe_unused]] int appExecResult = app.exec();
+ return testExecResult;
+}
+
+#include "tst_sandboxed_file_access.moc"
diff --git a/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp b/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp
index 1e49847c97f..484c28a484b 100644
--- a/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp
+++ b/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp
@@ -9,6 +9,12 @@
#include <emscripten.h>
#include <emscripten/threading.h>
+#if QT_CONFIG(wasm_jspi)
+# define QT_WASM_EMSCRIPTEN_ASYNC ,emscripten::async()
+#else
+# define QT_WASM_EMSCRIPTEN_ASYNC
+#endif
+
namespace QtWasmTest {
namespace {
QObject *g_testObject = nullptr;
@@ -127,7 +133,7 @@ void passTest()
EMSCRIPTEN_BINDINGS(qtwebtestrunner) {
emscripten::function("cleanupTestCase", &cleanupTestCase);
emscripten::function("getTestFunctions", &getTestFunctions);
- emscripten::function("runTestFunction", &runTestFunction, emscripten::async());
+ emscripten::function("runTestFunction", &runTestFunction QT_WASM_EMSCRIPTEN_ASYNC);
emscripten::function("qtWasmFail", &failTest);
emscripten::function("qtWasmPass", &passTest);
}