diff --git a/README.md b/README.md index c1ec5bace825b5334ed198f96136b08f31f851a2..48349e995c666ffba3b421b41c21959e0e697816 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ Test results can be viewed with any JUnit compatible viewer. For example, you ca ```bash npm i -g xunit-viewer -xunit-viewer -r output.junit.xml +xunit-viewer -r output.junitxml -s -w ``` It'll create a `report.html` file that can be opened in a web browser. For more information about `xunit-viewer` please visit the [GitHub repo][xunit-viewer]. diff --git a/cicd/stages/build.yml b/cicd/stages/build.yml index 74aaa52fa0690226f5137626f072aaaebb18477e..86cb904d4f9a71c4c363812a7becb19e678987ca 100644 --- a/cicd/stages/build.yml +++ b/cicd/stages/build.yml @@ -3,23 +3,27 @@ .build-components: &build-components - pushd 3rdparty/qtquickdesigner-components - | - cmake \ - -S . \ - -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_TOOLCHAIN_FILE=${QDS_CI_JOB_QT_PATH}/lib/cmake/Qt6/qt.toolchain.cmake \ - -DANDROID_SDK_ROOT=${DOCKER_ENV_ANDROID_SDK_ROOT} \ - -DANDROID_NDK_ROOT=${DOCKER_ENV_ANDROID_NDK_ROOT} \ - -DQT_HOST_PATH=${DOCKER_ENV_QT_PATH_LINUX_GCC_64} \ - -DFLOWVIEW_AUTO_QMLDIR=ON \ - -DCMAKE_INSTALL_PREFIX=${QDS_CI_JOB_QT_PATH} - - cmake --build . - - cmake --install . + for arch in ${QDS_CI_JOB_ARCHS}; do + QT_DIR_WITH_ARCH=${DOCKER_ENV_QT_PATH_WITH_VERSION}/${arch} + BUILD_PATH=${QDS_CI_JOB_BUILD_PATH}/components/${arch} + cmake \ + -S . \ + -G Ninja \ + -B ${BUILD_PATH} \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_TOOLCHAIN_FILE=${QT_DIR_WITH_ARCH}/lib/cmake/Qt6/qt.toolchain.cmake \ + -DANDROID_SDK_ROOT=${DOCKER_ENV_ANDROID_SDK_ROOT} \ + -DANDROID_NDK_ROOT=${DOCKER_ENV_ANDROID_NDK_ROOT} \ + -DQT_HOST_PATH=${DOCKER_ENV_QT_PATH_LINUX_GCC_64} \ + -DFLOWVIEW_AUTO_QMLDIR=ON \ + -DCMAKE_INSTALL_PREFIX=${QT_DIR_WITH_ARCH} + cmake --build ${BUILD_PATH} --target install + rm -rf ${BUILD_PATH} + done - popd -.build-android-apps: &build-android-apps +.build-app: &build-app - export GOOGLE_PLAY_APP_VERSION=$(( $(git tag --list | wc -l) +11 )) - # where is this magic "$(git tag --list | wc -l) +11" coming from? # "git tag --list | wc -l" counts the number of tags in the repository. # The "+11" is a magic number which is the last GOOGLE_PLAY_APP_VERSION # that was released before starting to add tags to the repository. After @@ -39,9 +43,11 @@ -DCMAKE_INSTALL_PREFIX=${QDS_CI_JOB_QT_PATH} \ -DANDROID_OPENSSL_PATH=${QDS_CI_JOB_OPENSSL_PATH} \ -DGOOGLE_PLAY_APP_VERSION=${GOOGLE_PLAY_APP_VERSION} \ + -DQT_ANDROID_BUILD_ALL_ABIS=ON \ + -DQT_ANDROID_MULTI_ABI_FORWARD_VARS="GOOGLE_PLAY_APP_VERSION;ANDROID_OPENSSL_PATH" \ -DBUILD_EXAMPLES=OFF - cat ${QDS_CI_JOB_BUILD_PATH}/src/dynamic_imports.qml - - cmake --build ${QDS_CI_JOB_BUILD_PATH} --target aab + - cmake --build ${QDS_CI_JOB_BUILD_PATH} --target ${QDS_CI_JOB_CMAKE_BUILD_TARGET} .copy-and-sign-apks: ©-and-sign-apks - QDS_CI_KEYSTORE_PATH=$(pwd)/android_release.keystore @@ -57,68 +63,54 @@ - | if [[ -n ${CI_COMMIT_TAG} ]]; then + QDS_CI_KEYSTORE_PATH=$(pwd)/android_release.keystore + echo ${QDS_VAR_KEYSTORE} | base64 -d > ${QDS_CI_KEYSTORE_PATH} cp -r ${QDS_CI_JOB_BUILD_PATH}/src/android-build/build/outputs/bundle/release/* ${QDS_CI_JOB_ARTIFACTS_PATH_APP} - /usr/bin/jarsigner -keystore ${CI_PROJECT_DIR}/cicd/android/android_release.keystore ${QDS_CI_JOB_ARTIFACTS_PATH_APP}/android-build-release.aab designviewer -storepass ${QDS_VAR_PASS} + /usr/bin/jarsigner -keystore ${QDS_CI_KEYSTORE_PATH} ${QDS_CI_JOB_ARTIFACTS_PATH_APP}/android-build-release.aab ${QDS_VAR_KEYSTORE_ALIAS} -storepass ${QDS_VAR_PASS} fi -build-android: +.build-common: extends: .pipeline_common stage: build - parallel: - matrix: - - QDS_CI_JOB_TARGET_ARCH: "arm64_v8a" - QDS_CI_JOB_OPENSSL_PATH: "/opt/openssl/ssl_3/arm64-v8a" - - QDS_CI_JOB_TARGET_ARCH: "armv7" - QDS_CI_JOB_OPENSSL_PATH: "/opt/openssl/ssl_3/armeabi-v7a" - - QDS_CI_JOB_TARGET_ARCH: "x86" - QDS_CI_JOB_OPENSSL_PATH: "/opt/openssl/ssl_3/x86" - - QDS_CI_JOB_TARGET_ARCH: "x86_64" - QDS_CI_JOB_OPENSSL_PATH: "/opt/openssl/ssl_3/x86_64" - variables: - QDS_CI_JOB_BUILD_PATH: "${QDS_CI_CACHE_PATH}/${QDS_CI_JOB_TARGET_ARCH}/build" - QDS_CI_JOB_ARTIFACTS_PATH: ${QDS_CI_ARTIFACTS_PATH}/${QDS_CI_JOB_TARGET_ARCH} - QDS_CI_JOB_ARTIFACTS_PATH_APP: ${QDS_CI_JOB_ARTIFACTS_PATH}/app - QDS_CI_JOB_ARTIFACTS_PATH_TEST: ${QDS_CI_JOB_ARTIFACTS_PATH}/test artifacts: - name: qt_ui_viewer-${CI_JOB_ID}-qt${QDS_CI_QT_VERSION}-${QDS_CI_JOB_TARGET_ARCH} + name: qt_ui_viewer-${CI_JOB_ID}-qt${QDS_CI_QT_VERSION}-${QDS_CI_JOB_TARGET_PLATFORM} expose_as: "build-artifacts" paths: - ${QDS_CI_ARTIFACTS_PATH} expire_in: 1 week + +build-android: + extends: .build-common + variables: + QDS_CI_JOB_TARGET_PLATFORM: "android" + QDS_CI_JOB_ARCHS: android_arm64_v8a android_armv7 android_x86 android_x86_64 + QDS_CI_JOB_OPENSSL_PATH: "/opt/openssl/ssl_3/arm64-v8a" + QDS_CI_JOB_BUILD_PATH: "${CI_PROJECT_DIR}/${QDS_CI_JOB_TARGET_PLATFORM}/build" + QDS_CI_JOB_ARTIFACTS_PATH: ${QDS_CI_ARTIFACTS_PATH}/${QDS_CI_JOB_TARGET_PLATFORM} + QDS_CI_JOB_ARTIFACTS_PATH_APP: ${QDS_CI_JOB_ARTIFACTS_PATH}/app + QDS_CI_JOB_ARTIFACTS_PATH_TEST: ${QDS_CI_JOB_ARTIFACTS_PATH}/test + QDS_CI_JOB_CMAKE_BUILD_TARGET: "aab" script: - mkdir -p ${QDS_CI_JOB_ARTIFACTS_PATH_APP} ${QDS_CI_JOB_ARTIFACTS_PATH_TEST} - - export QDS_CI_JOB_QT_PATH="${DOCKER_ENV_QT_PATH_WITH_VERSION}/android_${QDS_CI_JOB_TARGET_ARCH}" + - export QDS_CI_JOB_QT_PATH="${DOCKER_ENV_QT_PATH_WITH_VERSION}/android_arm64_v8a" - *build-components - - *build-android-apps + - *build-app - *copy-and-sign-apks - *copy-and-sign-aab build-desktop: - extends: .pipeline_common - stage: build + extends: .build-common variables: - QDS_CI_JOB_TARGET_ARCH: "gcc_64" - QDS_CI_JOB_BUILD_PATH: "${QDS_CI_CACHE_PATH}/${QDS_CI_JOB_TARGET_ARCH}/build" - QDS_CI_JOB_ARTIFACTS_PATH: ${QDS_CI_ARTIFACTS_PATH}/${QDS_CI_JOB_TARGET_ARCH} - artifacts: - name: qt_ui_viewer-${CI_JOB_ID}-qt${QDS_CI_QT_VERSION}-${QDS_CI_JOB_TARGET_ARCH} - expose_as: "build-artifacts" - paths: - - ${QDS_CI_ARTIFACTS_PATH} - expire_in: 1 week + QDS_CI_JOB_TARGET_PLATFORM: "desktop" + QDS_CI_JOB_ARCHS: gcc_64 + QDS_CI_JOB_BUILD_PATH: "${CI_PROJECT_DIR}/${QDS_CI_JOB_TARGET_PLATFORM}/build" + QDS_CI_JOB_ARTIFACTS_PATH: ${QDS_CI_ARTIFACTS_PATH}/${QDS_CI_JOB_TARGET_PLATFORM} + QDS_CI_JOB_CMAKE_BUILD_TARGET: "all" script: - mkdir -p ${QDS_CI_JOB_ARTIFACTS_PATH} - - export QDS_CI_JOB_QT_PATH="${DOCKER_ENV_QT_PATH_WITH_VERSION}/${QDS_CI_JOB_TARGET_ARCH}" - - *build-components + - export QDS_CI_JOB_QT_PATH="${DOCKER_ENV_QT_PATH_LINUX_GCC_64}" - apt-get update - apt-get install -y libpulse-dev - - | - cmake \ - -S . \ - -B ${QDS_CI_JOB_BUILD_PATH} \ - -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_PREFIX_PATH=${QDS_CI_JOB_QT_PATH} \ - -DBUILD_EXAMPLES=OFF - - cat ${QDS_CI_JOB_BUILD_PATH}/src/dynamic_imports.qml - - cmake --build ${QDS_CI_JOB_BUILD_PATH} + - *build-components + - *build-app + - cp -r ${QDS_CI_JOB_BUILD_PATH}/src//qtuiviewer ${QDS_CI_JOB_ARTIFACTS_PATH} diff --git a/cicd/stages/release.yml b/cicd/stages/release.yml index e0deaef999bb86d763a81be3e2b9fafc85aa7510..171f8adab7f91d3bef5d6469e3eacf0c29eb61e3 100644 --- a/cicd/stages/release.yml +++ b/cicd/stages/release.yml @@ -5,7 +5,7 @@ create-packages: rules: - if: $CI_COMMIT_TAG needs: - - job: test-x86_64 + - job: test-android optional: false artifacts: true - job: build-android @@ -29,7 +29,7 @@ create-packages: cd "${arch}/app" tar -czf "${arch}.tar.gz" * echo "Uploading ${arch}.tar.gz to GitLab Package Registry" - curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file "${arch}.tar.gz" ${QDS_PACKAGE_URL}/qt-ui-viewer-${CI_COMMIT_TAG}-qt${QDS_CI_QT_VERSION}-${arch}.tar.gz + curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file "${arch}.tar.gz" ${QDS_PACKAGE_URL}/qt-ui-viewer-${CI_COMMIT_TAG}-qt${QDS_CI_QT_VERSION}-multiarch.tar.gz cd ${current_dir} done artifacts: diff --git a/cicd/stages/test.yml b/cicd/stages/test.yml index 1fd17286d5fd738f01f8410cb4f179c369ed857b..f51db76960d153cc424d09a75fdc958cdd1f33f3 100644 --- a/cicd/stages/test.yml +++ b/cicd/stages/test.yml @@ -1,7 +1,18 @@ -# todo: run the tests for all architectures -test-x86_64: +.test-common: stage: test extends: .pipeline_common + variables: + GIT_SUBMODULE_STRATEGY: none + QDS_CI_JOB_TEST_RESULTS_PATH: ${CI_PROJECT_DIR}/test + artifacts: + paths: + - ${QDS_CI_JOB_TEST_RESULTS_PATH}/ + reports: + junit: ${QDS_CI_JOB_TEST_RESULTS_PATH}/test.junit.xml + expire_in: 1 week + +test-android: + extends: .test-common tags: - qds-blade-server-shell needs: @@ -11,9 +22,6 @@ test-x86_64: - job: build-desktop optional: false artifacts: true - variables: - GIT_SUBMODULE_STRATEGY: none - QDS_CI_JOB_TEST_RESULTS_PATH: ${CI_PROJECT_DIR}/test script: - | if [[ ${QDS_CI_SKIP_TESTS} == "false" ]]; then @@ -35,7 +43,7 @@ test-x86_64: /opt/android/emulator/emulator -avd test -no-boot-anim -no-window -no-audio -no-snapshot -accel on & 2>&1 > /dev/null sleep 30 fi - - cd ${QDS_CI_ARTIFACTS_PATH}/x86_64/test || exit 1 + - cd ${QDS_CI_ARTIFACTS_PATH}/android/test || exit 1 - mkdir -p ${QDS_CI_JOB_TEST_RESULTS_PATH} - adb install -r -g android-build-release.apk - adb shell pm clear io.qt.qtdesignviewer.test @@ -71,9 +79,27 @@ test-x86_64: - mv output.junit.xml ${QDS_CI_JOB_TEST_RESULTS_PATH}/test.junit.xml - chmod +x ${CI_PROJECT_DIR}/tests/scripts/get_test_result.sh - ${CI_PROJECT_DIR}/tests/scripts/get_test_result.sh ${QDS_CI_JOB_TEST_RESULTS_PATH}/test.junit.xml - artifacts: - paths: - - ${QDS_CI_JOB_TEST_RESULTS_PATH}/ - reports: - junit: ${QDS_CI_JOB_TEST_RESULTS_PATH}/test.junit.xml - expire_in: 1 week + + +# it's not working as of because of the headless mode +# possible bugs: +# - https://bugreports.qt.io/browse/QTBUG-124467 +# - https://bugreports.qt.io/browse/QTBUG-126508 +.test-desktop: + extends: .test-common + needs: + - job: build-desktop + optional: false + artifacts: true + script: + - | + if [[ ${QDS_CI_SKIP_TESTS} == "false" ]]; then + echo "Running tests"; + else + echo "Skipping tests"; + exit 0; + fi + - cd ${QDS_CI_ARTIFACTS_PATH}/gcc_64 || exit 1 + - apt update; apt install -y libxcb-cursor0 + - ./qtuiviewer_test + - cp output.junitxml ${QDS_CI_JOB_TEST_RESULTS_PATH}/test.junit.xml diff --git a/src/backend/backend.cpp b/src/backend/backend.cpp index 78f62a70ed7a021b6a2b097af263a24d6987e768..fe564c38de0f8edaf413a4cf08a05e26460bc659 100644 --- a/src/backend/backend.cpp +++ b/src/backend/backend.cpp @@ -173,7 +173,7 @@ void Backend::initProjectManager() void Backend::initDsManager() { qDebug() << "Design Studio Manager thread started. Initializing Design Studio Manager"; - m_dsManager.reset(new DesignStudioManager(m_settings.deviceUuid(), this)); + m_dsManager.reset(new DesignStudioManager(m_settings.deviceUuid())); connect(m_dsManager.get(), &DesignStudioManager::projectReceived, this, &Backend::runProject); @@ -259,8 +259,10 @@ void Backend::runProject(const QString &id, const QByteArray &projectData) if (!retVal) QMetaObject::invokeMethod(m_dsManager.get(), "sendProjectStopped", Q_ARG(QString, id)); - else - QMetaObject::invokeMethod(m_dsManager.get(), "sendProjectRunning", Q_ARG(QString, id)); + else { + QMetaObject::invokeMethod(m_projectManager.get(), "showAppWindow"); + QMetaObject::invokeMethod(m_dsManager.get(), "sendProjectStarted", Q_ARG(QString, id)); + } emit popupClose(); }); diff --git a/src/backend/dsconnector/ds.cpp b/src/backend/dsconnector/ds.cpp index ba39bd50bd26659bf347fb1c51380f4aa37518ae..08c24200d58c368d9d7a4c6e2bf05f9d87f0ae05 100644 --- a/src/backend/dsconnector/ds.cpp +++ b/src/backend/dsconnector/ds.cpp @@ -19,7 +19,7 @@ using namespace Qt::Literals; constexpr auto deviceInfo = "deviceInfo"_L1; constexpr auto projectReceivingProgress = "projectReceivingProgress"_L1; constexpr auto projectStarting = "projectStarting"_L1; -constexpr auto projectRunning = "projectRunning"_L1; +constexpr auto projectStarted = "projectRunning"_L1; constexpr auto projectStopped = "projectStopped"_L1; constexpr auto projectLogs = "projectLogs"_L1; }; // namespace PackageToDesignStudio @@ -247,9 +247,9 @@ void DesignStudio::sendProjectStarting() sendData(PackageToDesignStudio::projectStarting); } -void DesignStudio::sendProjectRunning() +void DesignStudio::sendProjectStarted() { - sendData(PackageToDesignStudio::projectRunning); + sendData(PackageToDesignStudio::projectStarted); } void DesignStudio::sendProjectStopped() diff --git a/src/backend/dsconnector/ds.h b/src/backend/dsconnector/ds.h index 069c9880a39c3ae778b64ea909f5080ede9c7fdb..5034d3c1f9ff63073e970a367ca347a1845d5fce 100644 --- a/src/backend/dsconnector/ds.h +++ b/src/backend/dsconnector/ds.h @@ -23,7 +23,7 @@ public: // Send data void sendDeviceInfo(); void sendProjectStarting(); - void sendProjectRunning(); + void sendProjectStarted(); void sendProjectStopped(); void sendProjectLogs(const QString &logs); diff --git a/src/backend/dsconnector/dsmanager.cpp b/src/backend/dsconnector/dsmanager.cpp index 1c1e6028c5af3b51b281d0ced2695ebaa61c0540..9ca02241fcd11f3aab78bd4145a30568383140cb 100644 --- a/src/backend/dsconnector/dsmanager.cpp +++ b/src/backend/dsconnector/dsmanager.cpp @@ -9,26 +9,32 @@ #include <QJsonObject> #include <QUdpSocket> -DesignStudioManager::DesignStudioManager(const QString &deviceUuid, QObject *parent) +DesignStudioManager::DesignStudioManager(const QString &deviceUuid, + const quint16 port, + QObject *parent) : QObject(parent) + , m_port(port) , m_deviceUuid(deviceUuid) , m_webSocketServer("DesignStudio", QWebSocketServer::NonSecureMode) {} -void DesignStudioManager::init() +bool DesignStudioManager::init() { - static bool initialized = false; - if (initialized) { - return; + if (m_webSocketServer.isListening()) { + qWarning() << "TCP server is already running"; + return false; + } + + if (!m_webSocketServer.listen(QHostAddress::Any, m_port)) { + qWarning() << "Failed to start TCP server on port" << m_port; + return false; } - initialized = true; - m_webSocketServer.listen(QHostAddress::Any, 40000); connect(&m_webSocketServer, &QWebSocketServer::newConnection, this, &DesignStudioManager::incomingConnection); - qDebug() << "TCP server listening on port 40000"; + qDebug() << "TCP server listening on port" << m_webSocketServer.serverPort(); m_discoveryTimer.setInterval(10000); connect(&m_discoveryTimer, &QTimer::timeout, [this]() { @@ -51,6 +57,18 @@ void DesignStudioManager::init() udpSocket.writeDatagram(datagram, QHostAddress("10.0.2.2"), port); }); m_discoveryTimer.start(); + + return true; +} + +QString DesignStudioManager::webSocketServerName() const +{ + return m_webSocketServer.serverName(); +} + +int DesignStudioManager::webSocketServerPort() const +{ + return m_webSocketServer.serverPort(); } void DesignStudioManager::incomingConnection() @@ -64,6 +82,7 @@ void DesignStudioManager::incomingConnection() &QObject::deleteLater); connect(designStudio.data(), &DesignStudio::disconnected, this, [this, designStudio]() { m_designStudios.removeOne(designStudio); + emit designStudioDisconnected(designStudio->id(), designStudio->ipv4Addr()); qDebug() << "Remaining Design Studios:" << m_designStudios.size(); if (m_designStudios.isEmpty()) emit allDesignStudiosDisconnected(); @@ -106,11 +125,11 @@ void DesignStudioManager::sendProjectStarting(const QString &id) } } -void DesignStudioManager::sendProjectRunning(const QString &id) +void DesignStudioManager::sendProjectStarted(const QString &id) { for (const auto &designStudio : m_designStudios) { if (designStudio->id() == id) - designStudio->sendProjectRunning(); + designStudio->sendProjectStarted(); } } diff --git a/src/backend/dsconnector/dsmanager.h b/src/backend/dsconnector/dsmanager.h index 661407a4c9c7e9e2f0e680879d3b6e2a715404c7..7c8fa08cb376381ef2638d021794c6cb8844c12d 100644 --- a/src/backend/dsconnector/dsmanager.h +++ b/src/backend/dsconnector/dsmanager.h @@ -11,13 +11,17 @@ class DesignStudioManager : public QObject { Q_OBJECT public: - explicit DesignStudioManager(const QString &deviceUuid, QObject *parent = nullptr); + explicit DesignStudioManager(const QString &deviceUuid, + const quint16 port = 40000, + QObject *parent = nullptr); - void init(); + bool init(); + QString webSocketServerName() const; + int webSocketServerPort() const; public slots: void sendProjectStarting(const QString &id); - void sendProjectRunning(const QString &id); + void sendProjectStarted(const QString &id); void sendProjectStopped(const QString &id); void sendProjectLogs(const QString &id, const QString &logs); QString getDesignStudioIp(const QString &id) const; @@ -28,6 +32,7 @@ private: // Network QWebSocketServer m_webSocketServer; QTimer m_discoveryTimer; + const quint16 m_port; // Connected Design Studios QList<QSharedPointer<DesignStudio>> m_designStudios; diff --git a/src/backend/main.cpp b/src/backend/main.cpp index 237cac5ef5b9e9c8f6130742d5eab4640c675c42..3c4bc110027bd544fc218c6dff8c3c604804fb66 100644 --- a/src/backend/main.cpp +++ b/src/backend/main.cpp @@ -33,8 +33,6 @@ int main(int argc, char *argv[]) { Logger::instance(); - - qDebug() << "Starting Qt Design Viewer"; qputenv("QML_COMPAT_RESOLVE_URLS_ON_ASSIGNMENT", "1"); QApplication app(argc, argv); @@ -52,6 +50,7 @@ int main(int argc, char *argv[]) #ifdef Q_OS_ANDROID view.showMaximized(); #else + QApplication::setWindowIcon(QIcon(QStringLiteral(":/images/appicon.svg"))); view.show(); #endif diff --git a/src/backend/projectmanager.cpp b/src/backend/projectmanager.cpp index 016e710ce2bdcad478c1f14d07b46a5254ba542e..90a1e042850a368fdbb0499a1587e8f64ec0c178 100644 --- a/src/backend/projectmanager.cpp +++ b/src/backend/projectmanager.cpp @@ -287,6 +287,8 @@ bool ProjectManager::runProjectInternal(const QByteArray &project) qDebug() << "Setting up the quickWindow"; m_quickWindow.reset(qobject_cast<QQuickWindow *>(topLevel)); + m_quickWindow->setVisible(false); + if (m_quickWindow) { qDebug() << "Running with incubator controller"; m_qmlEngine->setIncubationController(m_quickWindow->incubationController()); @@ -306,7 +308,7 @@ bool ProjectManager::runProjectInternal(const QByteArray &project) view->setResizeMode(QQuickView::SizeViewToRootObject); m_quickWindow->setBaseSize(QSize(contentItem->width(), contentItem->height())); } - showAppWindow(); + return true; } @@ -344,7 +346,7 @@ void ProjectManager::orientateWindow(Qt::ScreenOrientation orientation) << m_quickWindow->width() << Qt::endl << "-- Child size: " << childItem->height() << " x " << childItem->width() << Qt::endl << "-- Child pos: " << childItem->x() << ", " << childItem->y() << Qt::endl - << "-- Child scale: " << childItem->scale(); + << "-- Child scale: " << childItem->scale() << Qt::endl; const QSizeF newContentSize = childItem->size().scaled(screenGeometry.size().toSizeF(), Qt::AspectRatioMode::KeepAspectRatio); @@ -369,12 +371,15 @@ void ProjectManager::showAppWindow() qDebug("Initializing and showing the QML app window"); QScreen *screen = QGuiApplication::primaryScreen(); - connect(screen, &QScreen::orientationChanged, this, &ProjectManager::orientateWindow); + connect(screen, &QScreen::orientationChanged, this, [this](Qt::ScreenOrientation orientation) { + orientateWindow(orientation); + }); + orientateWindow(screen->orientation()); connect(m_quickWindow.data(), &QQuickWindow::closing, this, [this, screen]() { qDebug() << "QML app window is closing"; - disconnect(screen, &QScreen::orientationChanged, this, &ProjectManager::orientateWindow); + disconnect(screen, &QScreen::orientationChanged, this, nullptr); emit closingProject(m_sessionId); m_sessionId.clear(); }); @@ -385,11 +390,16 @@ void ProjectManager::showAppWindow() void ProjectManager::stopProject() { - if (!m_quickWindow || !m_quickWindow->isVisible()) + if (!m_quickWindow) return; qDebug("Stopping the QML app window"); m_quickWindow->close(); + + m_qmlComponent.reset(); + m_qmlEngine.reset(); + m_quickWindow.reset(); + m_sessionId.clear(); } QString ProjectManager::sessionId() const diff --git a/src/backend/projectmanager.h b/src/backend/projectmanager.h index ae12e3cf6ca38d8f2b3d90c2a1ead4646d0c3f82..269552b51543df46d9e87d4039f5521c0fbd1e1e 100644 --- a/src/backend/projectmanager.h +++ b/src/backend/projectmanager.h @@ -41,8 +41,11 @@ public slots: const bool autoScaleProject, const QString &sessionId); void stopProject(); + void showAppWindow(); private: + friend class TestProjectManager; + // Member variables QByteArray m_projectData; QString m_projectPath; @@ -67,11 +70,9 @@ private: QString getMainQmlFile(const QString &projectPath, const QString &qmlProjectFileContent); QStringList getImportPaths(const QString &projectPath, const QString &qmlProjectFileContent); bool isQt6Project(const QString &qmlProjectFileContent); - void parseQmlProjectFile(const QString &fileName, QString *mainFile, QStringList *importPaths); // window management void orientateWindow(Qt::ScreenOrientation orientation); - void showAppWindow(); signals: void closingProject(const QString &sessionId); diff --git a/src/backend/settings.cpp b/src/backend/settings.cpp index cb0459635b8144597c123f8f55deb54b501c696e..408d0dbf16b3a3eee248260fd3ff6d6221833fcb 100644 --- a/src/backend/settings.cpp +++ b/src/backend/settings.cpp @@ -35,6 +35,18 @@ Settings::Settings() } } +bool Settings::clearSettings() +{ + const auto backup = m_settings; + m_settings = QJsonObject(); + const bool rV = saveSettings(); + if (rV) + applyDefaultSettings(); + else + m_settings = backup; + return rV; +} + void Settings::applyDefaultSettings() { m_settings["autoScale"] = true; diff --git a/src/backend/settings.h b/src/backend/settings.h index eb87bb22cc7544ea9c136543a790a72edcba49ad..739dfd778f6089db4e4fa217775cd99854a0622a 100644 --- a/src/backend/settings.h +++ b/src/backend/settings.h @@ -10,6 +10,8 @@ class Settings public: Settings(); + bool clearSettings(); + void setAutoScaleProject(const bool &enabled); void setDeviceUuid(const QString &deviceUuid); void setKeepScreenOn(const bool &enabled); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5c840f42ec52fd632dfd1a2e44302d27e6681ada..71d08408603380646ec972a02fe6c1faf0c8b063 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,10 +1,13 @@ set(TARGET_NAME ${PROJECT_NAME}_test) -find_package(Qt6 REQUIRED COMPONENTS Test Core Quick Gui Multimedia WebSockets) +find_package(Qt6 REQUIRED COMPONENTS Test Core Quick Gui Widgets Multimedia WebSockets) enable_testing(true) qt_add_executable(${TARGET_NAME} tst_settings.cpp tst_settings.h + tst_dsmanager.cpp tst_dsmanager.h + tst_projectmanager.cpp tst_projectmanager.h + mock_ds.cpp mock_ds.h main.cpp ) @@ -12,10 +15,18 @@ add_test(NAME ${TARGET_NAME} COMMAND qtuiviewer_test) target_link_libraries( ${TARGET_NAME} PRIVATE - Qt::Test Qt::Core Qt::Quick Qt::Gui Qt::Multimedia Qt::WebSockets + Qt::Test Qt::Core Qt::Quick Qt::Gui Qt::Multimedia Qt::WebSockets Qt::Widgets qtuiviewerlib ) +qt_add_resources(${TARGET_NAME} "resources" + PREFIX + "/" + FILES + "data/test_project/test_project_success.qmlrc" + "data/test_project/test_project_fail.qmlrc" +) + set_target_properties(${TARGET_NAME} PROPERTIES QT_ANDROID_PACKAGE_NAME "io.qt.qtdesignviewer.test") set_property(TARGET ${TARGET_NAME} APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android diff --git a/tests/android/AndroidManifest.xml b/tests/android/AndroidManifest.xml index 294377be7cc1a00cfcb489e580c19b38562e86d0..440774b83a60da6f13c16189e9fdb4bc379dffba 100644 --- a/tests/android/AndroidManifest.xml +++ b/tests/android/AndroidManifest.xml @@ -5,7 +5,7 @@ <!-- %%INSERT_FEATURES --> <supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:smallScreens="true" /> - <application android:icon="@mipmap/app_icon" android:debuggable="true" + <application android:icon="@mipmap/ic_launcher" android:debuggable="true" android:name="org.qtproject.qt.android.bindings.QtApplication" android:extractNativeLibs="true" android:hardwareAccelerated="true" android:label="Qt UI Viewer Test" diff --git a/tests/android/res/drawable/ic_launcher_background.xml b/tests/android/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..e3c2454e06fb6ae5cb0f94109492d3b473966a16 --- /dev/null +++ b/tests/android/res/drawable/ic_launcher_background.xml @@ -0,0 +1,26 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + <group android:scaleX="0.99" + android:scaleY="0.99" + android:translateX="0.54" + android:translateY="0.54"> + <path + android:pathData="M0,13.5V108H94.5L108,94.5V0H13.5L0,13.5Z"> + <aapt:attr name="android:fillColor"> + <gradient + android:startX="104.99" + android:startY="-26.62" + android:endX="11.55" + android:endY="126.08" + android:type="linear"> + <item android:offset="0.14" android:color="#FF014B57"/> + <item android:offset="1" android:color="#FF0F7080"/> + </gradient> + </aapt:attr> + </path> + </group> +</vector> diff --git a/tests/android/res/drawable/ic_launcher_foreground.xml b/tests/android/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000000000000000000000000000000000000..f09532519c052b3af27d99d52b060696fe593a5d --- /dev/null +++ b/tests/android/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,18 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + <group android:scaleX="0.66" + android:scaleY="0.66" + android:translateX="17.37" + android:translateY="17.04"> + <group> + <clip-path + android:pathData="M0.25,0.13h107.75v107.75h-107.75z"/> + <path + android:pathData="M28.84,62.54C28.84,67.29 31.25,69.66 36.08,69.66C40.91,69.66 43.33,67.29 43.33,62.54V35.42H51.59V62.36C51.59,67.35 50.29,71.02 47.69,73.37C45.14,75.69 41.27,76.84 36.08,76.84C30.89,76.84 27,75.69 24.41,73.37C21.85,71.02 20.58,67.35 20.58,62.36V35.42H28.84V62.54ZM82.13,35.42H90.81L81.35,76.13H66.09L56.63,35.42H65.31L72.38,68.94H75.07L82.13,35.42Z" + android:fillColor="#2CDE85"/> + </group> + </group> +</vector> diff --git a/tests/android/res/mipmap-anydpi-v26/ic_launcher.xml b/tests/android/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000000000000000000000000000000000000..bbd3e021239ce758474da78cfc2ca3cf85ed0d91 --- /dev/null +++ b/tests/android/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background"/> + <foreground android:drawable="@drawable/ic_launcher_foreground"/> +</adaptive-icon> \ No newline at end of file diff --git a/tests/android/res/mipmap-anydpi-v26/ic_launcher_round.xml b/tests/android/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000000000000000000000000000000000000..bbd3e021239ce758474da78cfc2ca3cf85ed0d91 --- /dev/null +++ b/tests/android/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background"/> + <foreground android:drawable="@drawable/ic_launcher_foreground"/> +</adaptive-icon> \ No newline at end of file diff --git a/tests/android/res/mipmap-hdpi/app_icon.png b/tests/android/res/mipmap-hdpi/app_icon.png deleted file mode 100644 index 74a188d073456264ab840da9314a48447fc95fcf..0000000000000000000000000000000000000000 Binary files a/tests/android/res/mipmap-hdpi/app_icon.png and /dev/null differ diff --git a/tests/android/res/mipmap-hdpi/ic_launcher.webp b/tests/android/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..45b059ae192f60f60ed2303339c36c294dbbfc3e Binary files /dev/null and b/tests/android/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/tests/android/res/mipmap-hdpi/ic_launcher_round.webp b/tests/android/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..6478dcebfebd3dd0c861fa21138c69bc168fffc9 Binary files /dev/null and b/tests/android/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/tests/android/res/mipmap-ldpi/app_icon.png b/tests/android/res/mipmap-ldpi/app_icon.png deleted file mode 100644 index a52520e2630446955333139ca069431c38b75376..0000000000000000000000000000000000000000 Binary files a/tests/android/res/mipmap-ldpi/app_icon.png and /dev/null differ diff --git a/tests/android/res/mipmap-mdpi/app_icon.png b/tests/android/res/mipmap-mdpi/app_icon.png deleted file mode 100644 index 36a356c0cec907702d1ecd5562586ca8ee72325c..0000000000000000000000000000000000000000 Binary files a/tests/android/res/mipmap-mdpi/app_icon.png and /dev/null differ diff --git a/tests/android/res/mipmap-mdpi/ic_launcher.webp b/tests/android/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..f023c33ba54629232b36c2efda70367edc74dd99 Binary files /dev/null and b/tests/android/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/tests/android/res/mipmap-mdpi/ic_launcher_round.webp b/tests/android/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..a3cc2d08d9da07471aba193ffefba9328d3299aa Binary files /dev/null and b/tests/android/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/tests/android/res/mipmap-xhdpi/app_icon.png b/tests/android/res/mipmap-xhdpi/app_icon.png deleted file mode 100644 index e1f2e9dc1f2653cdb78f3392df37664615f811aa..0000000000000000000000000000000000000000 Binary files a/tests/android/res/mipmap-xhdpi/app_icon.png and /dev/null differ diff --git a/tests/android/res/mipmap-xhdpi/ic_launcher.webp b/tests/android/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..0c33762a480bca1d329d2b90252297fdb106b8e6 Binary files /dev/null and b/tests/android/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/tests/android/res/mipmap-xhdpi/ic_launcher_round.webp b/tests/android/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..6599d9b307275916df8e1da8a74c6ad9917fbf02 Binary files /dev/null and b/tests/android/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/tests/android/res/mipmap-xxhdpi/app_icon.png b/tests/android/res/mipmap-xxhdpi/app_icon.png deleted file mode 100644 index 652a8e9b7e4e7ebb31424805025b1a2bce8e708d..0000000000000000000000000000000000000000 Binary files a/tests/android/res/mipmap-xxhdpi/app_icon.png and /dev/null differ diff --git a/tests/android/res/mipmap-xxhdpi/ic_launcher.webp b/tests/android/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..bb3c5ea66b3356bf2195068b3dc4a6d8b6466a12 Binary files /dev/null and b/tests/android/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/tests/android/res/mipmap-xxhdpi/ic_launcher_round.webp b/tests/android/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..001d3b0de54aa94df573dc740f0d45ea2e8f245b Binary files /dev/null and b/tests/android/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/tests/android/res/mipmap-xxxhdpi/app_icon.png b/tests/android/res/mipmap-xxxhdpi/app_icon.png deleted file mode 100644 index 7a404f6cbb8e0acc99545092319bea31c384ecca..0000000000000000000000000000000000000000 Binary files a/tests/android/res/mipmap-xxxhdpi/app_icon.png and /dev/null differ diff --git a/tests/android/res/mipmap-xxxhdpi/ic_launcher.webp b/tests/android/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..c60e7e17d4c43dfa63500e0bf30802ddfc1fbd46 Binary files /dev/null and b/tests/android/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/tests/android/res/mipmap-xxxhdpi/ic_launcher_round.webp b/tests/android/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..a41bd7a2b444bd7108df57d4334afc50bc5ae025 Binary files /dev/null and b/tests/android/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/tests/android/res/values/strings.xml b/tests/android/res/values/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..004d0188e87976edca44fd1c4e4b3cd1c8469571 --- /dev/null +++ b/tests/android/res/values/strings.xml @@ -0,0 +1,18 @@ +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ https://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<resources> + <string name="app_name">Qt UI Viewer</string> +</resources> diff --git a/tests/data/test_project/Generated/Quick3DAssets.txt b/tests/data/test_project/Generated/Quick3DAssets.txt new file mode 100644 index 0000000000000000000000000000000000000000..84c843f100d41b805bc1dab082525ad2228526b7 --- /dev/null +++ b/tests/data/test_project/Generated/Quick3DAssets.txt @@ -0,0 +1 @@ +Imported 3D assets and components imported from bundles will be created in this folder. diff --git a/tests/data/test_project/qtquickcontrols2.conf b/tests/data/test_project/qtquickcontrols2.conf new file mode 100644 index 0000000000000000000000000000000000000000..2ed8ad76d0505e144850b96195cc1aeba3421010 --- /dev/null +++ b/tests/data/test_project/qtquickcontrols2.conf @@ -0,0 +1,8 @@ +[Controls] +Style=Basic + +[Material] +Theme=Light + +[Universal] +Theme=System diff --git a/tests/data/test_project/temp.qrc b/tests/data/test_project/temp.qrc new file mode 100644 index 0000000000000000000000000000000000000000..16e081ed0fbbfa80ac705031e03855684fc22121 --- /dev/null +++ b/tests/data/test_project/temp.qrc @@ -0,0 +1,13 @@ + +<RCC> +<qresource> +<file>qtquickcontrols2.conf</file> +<file>test_project.qmlproject</file> +<file>test_project/Constants.qml</file> +<file>test_project/EventListModel.qml</file> +<file>test_project/EventListSimulator.qml</file> +<file>test_project/qmldir</file> +<file>test_projectContent/App.qml</file> +<file>test_projectContent/Screen01.ui.qml</file> +</qresource> +</RCC> \ No newline at end of file diff --git a/tests/data/test_project/test_project.qmlproject b/tests/data/test_project/test_project.qmlproject new file mode 100644 index 0000000000000000000000000000000000000000..9d4155129ea4bd710713d68fcd3e3a8ad51d1706 --- /dev/null +++ b/tests/data/test_project/test_project.qmlproject @@ -0,0 +1,122 @@ +import QmlProject + +Project { + mainFile: "test_projectContent/App.qml" + mainUiFile: "test_projectContent/Screen01.ui.qml" + + /* Include .qml, .js, and image files from current directory and subdirectories */ + QmlFiles { + directory: "test_project" + } + + QmlFiles { + directory: "test_projectContent" + } + + QmlFiles { + directory: "Generated" + } + + JavaScriptFiles { + directory: "test_project" + } + + JavaScriptFiles { + directory: "test_projectContent" + } + + ImageFiles { + directory: "test_projectContent" + } + + ImageFiles { + directory: "Generated" + } + + Files { + filter: "*.conf" + files: ["qtquickcontrols2.conf"] + } + + Files { + filter: "qmldir" + directory: "." + } + + Files { + filter: "*.ttf;*.otf" + } + + Files { + filter: "*.wav;*.mp3" + } + + Files { + filter: "*.mp4" + } + + Files { + filter: "*.glsl;*.glslv;*.glslf;*.vsh;*.fsh;*.vert;*.frag" + } + + Files { + filter: "*.qsb" + } + + Files { + filter: "*.json" + } + + Files { + filter: "*.mesh" + directory: "Generated" + } + + Files { + filter: "*.qad" + directory: "Generated" + } + + Environment { + QT_QUICK_CONTROLS_CONF: "qtquickcontrols2.conf" + QML_COMPAT_RESOLVE_URLS_ON_ASSIGNMENT: "1" + QT_LOGGING_RULES: "qt.qml.connections=false" + QT_ENABLE_HIGHDPI_SCALING: "0" + /* Useful for debugging + QSG_VISUALIZE=batches + QSG_VISUALIZE=clip + QSG_VISUALIZE=changes + QSG_VISUALIZE=overdraw + */ + } + + qt6Project: true + + /* List of plugin directories passed to QML runtime */ + importPaths: [ "." ] + + /* Required for deployment */ + targetDirectory: "/opt/UntitledProject49" + + + qdsVersion: "4.7" + + quickVersion: "6.8" + + /* If any modules the project imports require widgets (e.g. QtCharts), widgetApp must be true */ + widgetApp: true + + /* args: Specifies command line arguments for qsb tool to generate shaders. + files: Specifies target files for qsb tool. If path is included, it must be relative to this file. + Wildcard '*' can be used in the file name part of the path. + e.g. files: [ "UntitledProject49Content/shaders/*.vert", "*.frag" ] */ + ShaderTool { + args: "-s --glsl \"100 es,120,150\" --hlsl 50 --msl 12" + files: [ "test_projectContent/shaders/*" ] + } + + multilanguageSupport: true + supportedLanguages: ["en"] + primaryLanguage: "en" + +} diff --git a/tests/data/test_project/test_project/Constants.qml b/tests/data/test_project/test_project/Constants.qml new file mode 100644 index 0000000000000000000000000000000000000000..e134a568b8534f24a27ca3ddf8f936bc07002109 --- /dev/null +++ b/tests/data/test_project/test_project/Constants.qml @@ -0,0 +1,27 @@ +pragma Singleton +import QtQuick +import QtQuick.Studio.Application + +QtObject { + readonly property int width: 1920 + readonly property int height: 1080 + + property string relativeFontDirectory: "fonts" + + /* Edit this comment to add your custom font */ + readonly property font font: Qt.font({ + family: Qt.application.font.family, + pixelSize: Qt.application.font.pixelSize + }) + readonly property font largeFont: Qt.font({ + family: Qt.application.font.family, + pixelSize: Qt.application.font.pixelSize * 1.6 + }) + + readonly property color backgroundColor: "#EAEAEA" + + + property StudioApplication application: StudioApplication { + fontPath: Qt.resolvedUrl("../UntitledProject49Content/" + relativeFontDirectory) + } +} diff --git a/tests/data/test_project/test_project/EventListModel.qml b/tests/data/test_project/test_project/EventListModel.qml new file mode 100644 index 0000000000000000000000000000000000000000..00c70659823285878690ed15d1da57df13141d5a --- /dev/null +++ b/tests/data/test_project/test_project/EventListModel.qml @@ -0,0 +1,12 @@ +import QtQuick + +ListModel { + id: eventListModel + + ListElement { + eventId: "enterPressed" + eventDescription: "Emitted when pressing the enter button" + shortcut: "Return" + parameters: "Enter" + } +} diff --git a/tests/data/test_project/test_project/EventListSimulator.qml b/tests/data/test_project/test_project/EventListSimulator.qml new file mode 100644 index 0000000000000000000000000000000000000000..d26ae6d50718a28b22720b417f0c7800d24f4aca --- /dev/null +++ b/tests/data/test_project/test_project/EventListSimulator.qml @@ -0,0 +1,22 @@ +import QtQuick +import QtQuick.Studio.EventSimulator +import QtQuick.Studio.EventSystem + +QtObject { + id: simulator + property bool active: true + + property Timer __timer: Timer { + id: timer + interval: 100 + onTriggered: { + EventSimulator.show() + } + } + + Component.onCompleted: { + EventSystem.init(Qt.resolvedUrl("EventListModel.qml")) + if (simulator.active) + timer.start() + } +} diff --git a/tests/data/test_project/test_project/designer/plugin.metainfo b/tests/data/test_project/test_project/designer/plugin.metainfo new file mode 100644 index 0000000000000000000000000000000000000000..cef86901ea6ac8c39cf4adcb59c504ed1dc7ddf9 --- /dev/null +++ b/tests/data/test_project/test_project/designer/plugin.metainfo @@ -0,0 +1,13 @@ +MetaInfo { + Type { + name: "UntitledProject49.EventListSimulator" + icon: ":/qtquickplugin/images/item-icon16.png" + + Hints { + visibleInNavigator: true + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: false + canBeDroppedInView3D: false + } + } +} diff --git a/tests/data/test_project/test_project/qmldir b/tests/data/test_project/test_project/qmldir new file mode 100644 index 0000000000000000000000000000000000000000..3fc49f80682d63bfc2487ea5e0fcc753c6fc0c1d --- /dev/null +++ b/tests/data/test_project/test_project/qmldir @@ -0,0 +1,4 @@ +module UntitledProject49 +singleton Constants 1.0 Constants.qml +EventListSimulator 1.0 EventListSimulator.qml +EventListModel 1.0 EventListModel.qml diff --git a/tests/data/test_project/test_projectContent/App.qml b/tests/data/test_project/test_projectContent/App.qml new file mode 100644 index 0000000000000000000000000000000000000000..cadefcd82742d20df0e68beeb0fbe9721307b73e --- /dev/null +++ b/tests/data/test_project/test_projectContent/App.qml @@ -0,0 +1,16 @@ +import QtQuick +import test_project + +Window { + width: mainScreen.width + height: mainScreen.height + + visible: true + title: "test_project" + + Screen01 { + id: mainScreen + } + +} + diff --git a/tests/data/test_project/test_projectContent/Screen01.ui.qml b/tests/data/test_project/test_projectContent/Screen01.ui.qml new file mode 100644 index 0000000000000000000000000000000000000000..ac24a0f9b4014761409eccd6e2850aa41be17ecd --- /dev/null +++ b/tests/data/test_project/test_projectContent/Screen01.ui.qml @@ -0,0 +1,254 @@ + + +/* +This is a UI file (.ui.qml) that is intended to be edited in Qt Design Studio only. +It is supposed to be strictly declarative and only uses a subset of QML. If you edit +this file manually, you might introduce QML code that is not supported by Qt Design Studio. +Check out https://doc.qt.io/qtcreator/creator-quick-ui-forms.html for details on .ui.qml files. +*/ +import QtQuick +import QtQuick.Controls +import test_project +import QtQuick.Studio.DesignEffects +import QtQuick.Studio.Components +import QtQuick.Studio.Effects +import QtQuick.Studio.LogicHelper +import QtQuick.Studio.MultiText +import QtCharts +import QtGraphs +import QtQuick.Layouts +import QtQuick3D +import QtQuick.Controls.FluentWinUI3 +import QtQuick.Effects +import QtQuick.Studio.Utils +import QtQuick.Studio.Application +import QtQuick.Timeline.BlendTrees +import QtQuick.VectorImage +import QtQuick.Window +import QtQuick3D.AssetUtils +import QtQuick3D.Effects +import QtQuick3D.Helpers +import QtQuick3D.Particles3D +import QtQuick3D.Physics +import QtQuick.Timeline +import QtNetwork +import QtQuick3D.Physics.Helpers +import QtMultimedia +import QtQuick3D.SpatialAudio +import QtQuick3D.Xr +import QtQuickUltralite.Extras +import QtQuickUltralite.Studio.Components +import QtQuickUltralite.Profiling +import QtQuickUltralite.Layers +import FlowView +import test + +Rectangle { + id: rectangle + width: Constants.width + height: Constants.height + + color: Constants.backgroundColor + + Button { + id: button + text: qsTr("Press me") + anchors.verticalCenter: parent.verticalCenter + checkable: true + anchors.horizontalCenter: parent.horizontalCenter + + Connections { + target: button + onClicked: animation.start() + } + + DesignEffect { + backgroundLayer: button + backgroundBlurRadius: 5 + layerBlurRadius: 2 + effects: [ + DesignDropShadow {}, + DesignDropShadow {} + ] + } + } + + Text { + id: label + text: qsTr("Hello UntitledProject49") + anchors.top: button.bottom + font.family: Constants.font.family + anchors.topMargin: 45 + anchors.horizontalCenter: parent.horizontalCenter + + SequentialAnimation { + id: animation + + ColorAnimation { + id: colorAnimation1 + target: rectangle + property: "color" + to: "#2294c6" + from: Constants.backgroundColor + } + + ColorAnimation { + id: colorAnimation2 + target: rectangle + property: "color" + to: Constants.backgroundColor + from: "#2294c6" + } + } + } + + Button { + id: button1 + x: 930 + y: 466 + text: qsTr("Button") + } + + CheckBox { + id: checkBox + x: 918 + y: 417 + text: qsTr("Check Box") + } + + ComboBox { + id: comboBox + x: 900 + y: 563 + } + + RadioButton { + id: radioButton + x: 897 + y: 632 + text: qsTr("Radio Button") + } + + RadioDelegate { + id: radioDelegate + x: 881 + y: 677 + text: qsTr("Radio Delegate") + } + + RoundButton { + id: roundButton + x: 864 + y: 490 + text: "+" + } + + Slider { + id: slider + x: 859 + y: 375 + value: 0.5 + } + + RangeSlider { + id: rangeSlider + x: 859 + y: 333 + second.value: 0.75 + first.value: 0.25 + } + + Switch { + id: _switch + x: 845 + y: 438 + text: qsTr("Switch") + } + + Button { + id: button2 + x: 1090 + y: 473 + text: qsTr("Button") + } + + ArcItem { + id: arc + x: 1090 + y: 333 + fillColor: "#00000000" + } + + EllipseItem { + id: ellipse + x: 628 + y: 333 + } + + BorderItem { + id: _border + x: 628 + y: 508 + adjustBorderRadius: true + } + + FlipableItem { + id: flipable + x: 1066 + y: 532 + } + + TriangleItem { + id: triangle + x: 669 + y: 677 + } + + Item { + id: __materialLibrary__ + } + + ChartView { + id: bar + x: 1257 + y: 340 + width: 300 + height: 300 + BarSeries { + name: "BarSeries" + BarSet { + values: [2, 2, 3] + label: "Set1" + } + + BarSet { + values: [5, 1, 2] + label: "Set2" + } + + BarSet { + values: [3, 5, 8] + label: "Set3" + } + } + } + + DesignEffect { + visible: true + effects: [ + DesignInnerShadow {}, + DesignDropShadow {} + ] + } + states: [ + State { + name: "clicked" + when: button.checked + + PropertyChanges { + target: label + text: qsTr("Button Checked") + } + } + ] +} diff --git a/tests/data/test_project/test_projectContent/fonts/fonts.txt b/tests/data/test_project/test_projectContent/fonts/fonts.txt new file mode 100644 index 0000000000000000000000000000000000000000..ab96122067450783de407d6200171a97ab3849be --- /dev/null +++ b/tests/data/test_project/test_projectContent/fonts/fonts.txt @@ -0,0 +1 @@ +Fonts in this folder are loaded automatically. diff --git a/tests/data/test_project/test_projectContent/images/images.txt b/tests/data/test_project/test_projectContent/images/images.txt new file mode 100644 index 0000000000000000000000000000000000000000..f8b999661ea6fb6c57246fba02c15a578bd64cf5 --- /dev/null +++ b/tests/data/test_project/test_projectContent/images/images.txt @@ -0,0 +1 @@ +Default folder for image assets. diff --git a/tests/data/test_project/test_project_fail.qmlrc b/tests/data/test_project/test_project_fail.qmlrc new file mode 100644 index 0000000000000000000000000000000000000000..ff815586b54fb2e038e38fb1965bac390af59b65 Binary files /dev/null and b/tests/data/test_project/test_project_fail.qmlrc differ diff --git a/tests/data/test_project/test_project_success.qmlrc b/tests/data/test_project/test_project_success.qmlrc new file mode 100644 index 0000000000000000000000000000000000000000..1b124cb3c43f3d6c054af130f5a5286e8da78273 Binary files /dev/null and b/tests/data/test_project/test_project_success.qmlrc differ diff --git a/tests/main.cpp b/tests/main.cpp index 7076f505a61f06f47306f4445c6e3ec18f270a90..dad15107f916ea9193b0e29ede6c19ce1dbbc84e 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -1,17 +1,20 @@ // Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include <QGuiApplication> +#include <QApplication> #include <QTemporaryFile> #include <QTest> +#include "tst_dsmanager.h" +#include "tst_projectmanager.h" #include "tst_settings.h" -#define DEBUG_LINE qDebug() << "Debugline:" << __LINE__ +#define DEBUG_LINE qDebug() << "Debug (" << __LINE__ << "):" int main(int argc, char *argv[]) { - QGuiApplication app(argc, argv); + qputenv("QML_COMPAT_RESOLVE_URLS_ON_ASSIGNMENT", "1"); + QApplication app(argc, argv); QStringList initialArgs = app.arguments(); DEBUG_LINE << "Arguments:" << initialArgs; @@ -33,14 +36,14 @@ int main(int argc, char *argv[]) } if (outputFileName.isEmpty()) { - qDebug() << "Output file name not provided. Using the default name: output.junitxml"; + DEBUG_LINE << "Output file name not provided. Using the default name: output.junitxml"; outputFileName = "output.junitxml"; } QFile outputFile{outputFileName}; - if (!outputFile.open(QIODevice::Append)) { + if (!outputFile.open(QIODevice::Truncate | QIODevice::WriteOnly)) { DEBUG_LINE << "Failed to open the output file"; - return 1; + return -1; } int status = 0; @@ -48,8 +51,11 @@ int main(int argc, char *argv[]) // temporary file to store the individual test results. // we'll append the results to the output file. QTemporaryFile file; - file.open(); - DEBUG_LINE << "Temporary file:" << file.fileName(); + if (!file.open()) { + DEBUG_LINE << "Failed to open the temporary file"; + status = -1; + return; + } QStringList args{"-o", QString(file.fileName()).append(",junitxml")}; args.prepend(appName); @@ -60,15 +66,24 @@ int main(int argc, char *argv[]) // append the test results to the output file file.readLine(); // skip the first line because it's the xml header - outputFile.write(file.readAll()); + const int rV = outputFile.write(file.readAll()); + if (rV == -1) { + DEBUG_LINE << "Failed to write the test results to the output file"; + status = -1; + return; + } }; outputFile.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); outputFile.write("<testsuites>\n"); runTest(new TestSettings); + runTest(new TestDesignStudioManager); + runTest(new TestProjectManager); outputFile.write("</testsuites>\n"); + outputFile.flush(); + outputFile.close(); DEBUG_LINE << "Test suite finished with status:" << status; return status; diff --git a/tests/mock_ds.cpp b/tests/mock_ds.cpp new file mode 100644 index 0000000000000000000000000000000000000000..14955957688e4a164fbbb2bac1cd89b1efccea5e --- /dev/null +++ b/tests/mock_ds.cpp @@ -0,0 +1,129 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "mock_ds.h" + +#include <QJsonParseError> +#include <QJsonObject> +#include <QJsonDocument> + +namespace PackageToDevice { +using namespace Qt::Literals; +constexpr auto designStudioReady = "designStudioReady"_L1; +constexpr auto projectData = "projectData"_L1; +constexpr auto stopRunningProject = "stopRunningProject"_L1; +}; // namespace PackageToDevice + +namespace PackageFromDevice { +using namespace Qt::Literals; +constexpr auto deviceInfo = "deviceInfo"_L1; +constexpr auto projectReceivingProgress = "projectReceivingProgress"_L1; +constexpr auto projectStarting = "projectStarting"_L1; +constexpr auto projectStarted = "projectRunning"_L1; +constexpr auto projectStopped = "projectStopped"_L1; +constexpr auto projectLogs = "projectLogs"_L1; +}; // namespace PackageFromDevice + +MockDS::MockDS(const QString dsID, + const QString &serverAddr, + const QString &serverName, + QObject *parent) + : QObject(parent) + , m_dsID(dsID) + , m_serverAddr(serverAddr) +{ + m_socket.reset(new QWebSocket(serverName, QWebSocketProtocol::VersionLatest, this)); + QObject::connect(m_socket.data(), + &QWebSocket::textMessageReceived, + this, + &MockDS::textMessageReceived); + connect(); +} + +MockDS::~MockDS() +{ + m_socket->close(); +} + +void MockDS::connect() +{ + m_socket->open(QUrl(m_serverAddr)); +} + +void MockDS::disconnect() +{ + m_socket->close(); + m_socket->abort(); +} + +bool MockDS::isConnected() const +{ + return m_socket->isValid(); +} + +bool MockDS::sendTextMessage(const QLatin1String &dataType, const QJsonValue &data) +{ + if (!isConnected()) + return false; + + QJsonObject message; + message["dataType"] = dataType; + message["data"] = data; + const QString jsonMessage = QString::fromLatin1( + QJsonDocument(message).toJson(QJsonDocument::Compact)); + m_socket->sendTextMessage(jsonMessage); + + return true; +} + +bool MockDS::sendDesignStudioReady() +{ + QJsonObject data; + data["designStudioID"] = m_dsID; + data["commVersion"] = 1; + return sendTextMessage(PackageToDevice::designStudioReady, data); +} + +bool MockDS::sendProjectData(const QByteArray &data, const QString &qtVersion) +{ + QJsonObject projectInfo; + projectInfo["projectSize"] = data.size(); + projectInfo["qtVersion"] = qtVersion; + + const bool rV = sendTextMessage(PackageToDevice::projectData, projectInfo); + if (!rV) + return false; + + const int dataSent = m_socket->sendBinaryMessage(data); + return dataSent == data.size(); +} + +bool MockDS::sendProjectStopped() +{ + return sendTextMessage(PackageToDevice::stopRunningProject, {}); +} + +void MockDS::textMessageReceived(const QString &message){ + qDebug () << "Message received: " << message; + QJsonParseError jsonError; + const QJsonDocument jsonDoc = QJsonDocument::fromJson(message.toLatin1(), &jsonError); + + const QJsonObject jsonObj = jsonDoc.object(); + const QString dataType = jsonObj.value("dataType").toString(); + if (dataType == PackageFromDevice::deviceInfo) { + QJsonObject deviceInfo = jsonObj.value("data").toObject(); + emit deviceInfoReady(); + } else if (dataType == PackageFromDevice::projectStarted) { + emit projectStarted(); + } else if (dataType == PackageFromDevice::projectStopped) { + emit projectStopped(); + } else if (dataType == PackageFromDevice::projectLogs) { + const QString logs = jsonObj.value("data").toString(); + emit projectLogsReceived(logs); + } else if (dataType == PackageFromDevice::projectStarting) { + emit projectStarting(); + } else if (dataType == PackageFromDevice::projectReceivingProgress) { + const int progress = jsonObj.value("data").toInt(); + emit projectSendingProgress(progress); + } +} diff --git a/tests/mock_ds.h b/tests/mock_ds.h new file mode 100644 index 0000000000000000000000000000000000000000..1214accc59019b2f276f5f984febfb20fe228bfb --- /dev/null +++ b/tests/mock_ds.h @@ -0,0 +1,43 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include <QWebSocket> + +/** + * @brief This is a mock class to simulate the Design Studio connection. + * This one acts like a client and the DesignStudioManager acts like a server. + * This can be used to test the data coming from the Design Studio or the vice versa. + */ +class MockDS : public QObject +{ + Q_OBJECT +public: + MockDS(const QString dsID, const QString &serverAddr, const QString &serverName, QObject *parent = nullptr); + ~MockDS(); + + void connect(); + void disconnect(); + bool isConnected() const; + bool sendDesignStudioReady(); + bool sendProjectData(const QByteArray &data, const QString &qtVersion); + bool sendProjectStopped(); + +private: + QScopedPointer<QWebSocket> m_socket; + const QString m_dsID; + const QString m_serverAddr; + +private: + void textMessageReceived(const QString &message); + bool sendTextMessage(const QLatin1String &dataType, const QJsonValue &data); + +signals: + void projectStarted(); + void projectStarting(); + void projectStopped(); + void projectLogsReceived(const QString &logs); + void deviceInfoReady(); + void projectSendingProgress(const int percentage); +}; diff --git a/tests/tst_dsmanager.cpp b/tests/tst_dsmanager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d84669a4a839e6c67fa505f48f4f0003c8befa3f --- /dev/null +++ b/tests/tst_dsmanager.cpp @@ -0,0 +1,221 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "tst_dsmanager.h" + +#include <QTest> +#include <QSignalSpy> + +#define DEVICE_ID "1234567890" +#define DS_ID "0987654321" +#define CONNECTION_TIMEOUT 1000 + +TestObjects::TestObjects(const quint16 port, QObject *parent) + : QObject(parent) +{ + m_dsManager.reset(new DesignStudioManager(DEVICE_ID, port)); + m_dsManager->init(); + + m_mockDS.reset(new MockDS(DS_ID, QString("ws://localhost:%1").arg(port), "DesignStudio")); + + const bool rV = QTest::qWaitFor([&]() { return m_mockDS->isConnected(); }, CONNECTION_TIMEOUT); + QVERIFY(rV == true); + + QSignalSpy spy(m_mockDS.data(), &MockDS::deviceInfoReady); + QVERIFY(m_mockDS->sendDesignStudioReady() == true); + QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 1000); + + m_mockDS2.reset(new MockDS(DS_ID "2", QString("ws://localhost:%1").arg(port), "DesignStudio")); + const bool rV2 = QTest::qWaitFor([&]() { return m_mockDS2->isConnected(); }, CONNECTION_TIMEOUT); + QVERIFY(rV2 == true); + + QSignalSpy spy2(m_mockDS2.data(), &MockDS::deviceInfoReady); + QVERIFY(m_mockDS2->sendDesignStudioReady() == true); + QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 1000); +} + +DesignStudioManager *TestObjects::designStudioManager() +{ + return m_dsManager.data(); +} + +MockDS *TestObjects::mockDS() +{ + return m_mockDS.data(); +} + +MockDS *TestObjects::mockDS2() +{ + return m_mockDS2.data(); +} + +void TestDesignStudioManager::initTestCase() +{ + QFile file(":/data/test_project/test_project_success.qmlrc"); + qDebug() << "Reading file:" << file.fileName(); + QVERIFY(file.open(QIODevice::ReadOnly)); + + m_projectData = file.readAll(); + QVERIFY(m_projectData.size() > 0); + + m_dsManager.reset(new DesignStudioManager(DEVICE_ID)); + m_dsManager->init(); + + const QString serverName = m_dsManager->webSocketServerName(); + const int serverPort = m_dsManager->webSocketServerPort(); + // Important note: + // If the following 2 parameters change by a design decision, + // they have to be updated on Qt Design Studio side as well. + QVERIFY2(serverName == "DesignStudio", + "Default server name is not correct. Update the test and Design Studio side."); + QVERIFY2(serverPort == 40000, + "Default server port is not correct. Update the test and Design Studio side."); + + const QString serverAddr = QString("ws://localhost:%1").arg(serverPort); + + m_mockDS.reset(new MockDS(DS_ID, serverAddr, serverName)); + const bool rV = QTest::qWaitFor([&]() { return m_mockDS->isConnected(); }, CONNECTION_TIMEOUT); + QVERIFY(rV == true); + + QSignalSpy spy(m_mockDS.data(), &MockDS::deviceInfoReady); + QVERIFY(m_mockDS->sendDesignStudioReady() == true); + QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 1000); +} + +void TestDesignStudioManager::testSendProjectStarting() +{ + QSignalSpy spy(m_mockDS.data(), &MockDS::projectStarting); + m_dsManager->sendProjectStarting(DS_ID); + QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 1000); +} + +void TestDesignStudioManager::testSendProjectStarted() +{ + QSignalSpy spy(m_mockDS.data(), &MockDS::projectStarted); + m_dsManager->sendProjectStarted(DS_ID); + QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 1000); +} + +void TestDesignStudioManager::testSendProjectStopped() +{ + QSignalSpy spy(m_mockDS.data(), &MockDS::projectStopped); + m_dsManager->sendProjectStopped(DS_ID); + QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 1000); +} + +void TestDesignStudioManager::testSendProjectLogs() +{ + QSignalSpy spy(m_mockDS.data(), &MockDS::projectLogsReceived); + m_dsManager->sendProjectLogs(DS_ID, "Test logs"); + QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 1000); +} + +void TestDesignStudioManager::testProjectReceivingProgress() +{ + TestObjects testObjects(40001); + const QString qtVersion = "6.8.0"; + + QSignalSpy spy(testObjects.mockDS(), &MockDS::projectSendingProgress); + QVERIFY(testObjects.mockDS()->sendProjectData(m_projectData, qtVersion) == true); + QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 1000); +} + +void TestDesignStudioManager::testGetDesignStudioIp() +{ + TestObjects testObjects(40001); + const QString ipAddr = testObjects.designStudioManager()->getDesignStudioIp(DS_ID); + const QString failMessage = QString("Design Studio IP is not correct. Expected: %1, Got: %2") + .arg("::1 or ::ffff:127.0.0.1") + .arg(ipAddr); + + if (ipAddr != "::1" && ipAddr != "::ffff:127.0.0.1") + QFAIL(qPrintable(failMessage)); + + QVERIFY(testObjects.designStudioManager()->getDesignStudioIp("non-existing-id").isEmpty()); +} + +void TestDesignStudioManager::testProjectVersionMismatch() +{ + TestObjects testObjects(40002); + const QString qtVersion = "5.0.0"; + + QSignalSpy spy(testObjects.designStudioManager(), &DesignStudioManager::projectVersionMismatch); + QVERIFY(testObjects.mockDS()->sendProjectData(m_projectData, qtVersion) == true); + QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 1000); +} + +void TestDesignStudioManager::testProjectIncoming() +{ + TestObjects testObjects(40003); + const QString qtVersion = "6.8.0"; + + QSignalSpy spy(testObjects.designStudioManager(), &DesignStudioManager::projectIncoming); + QVERIFY(testObjects.mockDS()->sendProjectData(m_projectData, qtVersion) == true); + QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 1000); +} + +void TestDesignStudioManager::testProjectIncomingProgress() +{ + TestObjects testObjects(40004); + + const QString qtVersion = "6.8.0"; + QByteArray bigProjectdata; + bigProjectdata.fill('a', 1024 * 1024 * 10); + + QSignalSpy spy(testObjects.designStudioManager(), &DesignStudioManager::projectIncomingProgress); + QVERIFY(testObjects.mockDS()->sendProjectData(bigProjectdata, qtVersion) == true); + QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 1000); +} + +void TestDesignStudioManager::testProjectReceived() +{ + TestObjects testObjects(40005); + const QString qtVersion = "6.8.0"; + + QSignalSpy spy(testObjects.designStudioManager(), &DesignStudioManager::projectReceived); + QVERIFY(testObjects.mockDS()->sendProjectData(m_projectData, qtVersion) == true); + QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 1000); +} + +void TestDesignStudioManager::testDesignStudioConnected() +{ + TestObjects testObjects(40006); + + QSignalSpy spy(testObjects.designStudioManager(), &DesignStudioManager::designStudioConnected); + testObjects.mockDS()->disconnect(); + testObjects.mockDS()->connect(); + QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 1000); +} + +void TestDesignStudioManager::testDesignStudioDisconnected() +{ + TestObjects testObjects(40007); + + QSignalSpy spy(testObjects.designStudioManager(), + &DesignStudioManager::designStudioDisconnected); + testObjects.mockDS()->disconnect(); + QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 1000); +} + +void TestDesignStudioManager::testAllDesignStudiosDisconnected() +{ + TestObjects testObjects(40008); + + QSignalSpy spy(testObjects.designStudioManager(), + &DesignStudioManager::designStudioDisconnected); + QSignalSpy spy2(testObjects.designStudioManager(), + &DesignStudioManager::allDesignStudiosDisconnected); + testObjects.mockDS()->disconnect(); + testObjects.mockDS2()->disconnect(); + spy.wait(1000); + QCOMPARE(spy.count(), 2); + QCOMPARE(spy2.count(), 1); +} + +void TestDesignStudioManager::testProjectStopRequested() +{ + TestObjects testObjects(40009); + + QSignalSpy spy(testObjects.designStudioManager(), &DesignStudioManager::projectStopRequested); + testObjects.mockDS()->sendProjectStopped(); + QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 1000); +} diff --git a/tests/tst_dsmanager.h b/tests/tst_dsmanager.h new file mode 100644 index 0000000000000000000000000000000000000000..e573518f0e3ecaa15aeaf49a52dabc68199a2f5b --- /dev/null +++ b/tests/tst_dsmanager.h @@ -0,0 +1,54 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include <QObject> + +#include "backend/dsconnector/dsmanager.h" +#include "mock_ds.h" + +class TestObjects : public QObject +{ + Q_OBJECT +public: + TestObjects(const quint16 port = 40000, QObject *parent = nullptr); + DesignStudioManager *designStudioManager(); + MockDS *mockDS(); + MockDS *mockDS2(); + +private: + QScopedPointer<DesignStudioManager> m_dsManager; + QScopedPointer<MockDS> m_mockDS; + QScopedPointer<MockDS> m_mockDS2; +}; + +class TestDesignStudioManager : public QObject +{ + Q_OBJECT +private slots: + void initTestCase(); + + // to DesignStudio + void testSendProjectStarted(); + void testSendProjectStarting(); + void testSendProjectStopped(); + void testSendProjectLogs(); + void testProjectReceivingProgress(); + void testGetDesignStudioIp(); + + // to Android + void testProjectVersionMismatch(); + void testProjectIncoming(); + void testProjectIncomingProgress(); + void testProjectReceived(); + void testDesignStudioConnected(); + void testDesignStudioDisconnected(); + void testAllDesignStudiosDisconnected(); + void testProjectStopRequested(); + +private: + QScopedPointer<DesignStudioManager> m_dsManager; + QScopedPointer<MockDS> m_mockDS; + QByteArray m_projectData; +}; diff --git a/tests/tst_projectmanager.cpp b/tests/tst_projectmanager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1be6c918c8a96810aa469cd5b1931f98ec2049e7 --- /dev/null +++ b/tests/tst_projectmanager.cpp @@ -0,0 +1,211 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "tst_projectmanager.h" + +#include <QQuickItem> +#include <QSignalSpy> +#include <QTest> + +void TestProjectManager::initTestCase() +{ + QFile file(":/data/test_project/test_project_success.qmlrc"); + QVERIFY(file.open(QIODevice::ReadOnly)); + + m_projectThatCanRun = file.readAll(); + QVERIFY(m_projectThatCanRun.size() > 0); + + file.setFileName(":/data/test_project/test_project_fail.qmlrc"); + QVERIFY(file.open(QIODevice::ReadOnly)); + + m_projectThatCannotRun = file.readAll(); + QVERIFY(m_projectThatCannotRun.size() > 0); +} + +void TestProjectManager::testRunProject() +{ + ProjectManager projectManager; + bool result = projectManager.runProject(m_projectThatCanRun, true, "sessionId"); + + QVERIFY(projectManager.sessionId() == "sessionId"); + QVERIFY(result); + + result = projectManager.runProject(m_projectThatCannotRun, true, "sessionId"); + QVERIFY(!result); + QVERIFY(projectManager.sessionId().isEmpty()); +} + +void TestProjectManager::testStopProject() +{ + ProjectManager projectManager; + const bool result = projectManager.runProject(m_projectThatCanRun, true, "sessionId"); + QVERIFY(result); + + projectManager.stopProject(); + QVERIFY(projectManager.sessionId().isEmpty()); + QVERIFY(projectManager.m_quickWindow.isNull()); + QVERIFY(projectManager.m_qmlEngine.isNull()); + QVERIFY(projectManager.m_qmlComponent.isNull()); +} + +void TestProjectManager::testRegisterResource() +{ + ProjectManager projectManager; + const QString resourcePath = ":/data/test_project_register"; + bool result = projectManager.registerResource(m_projectThatCanRun, resourcePath); + QVERIFY(result); + + QFile file(resourcePath + "/test_project.qmlproject"); + QVERIFY(file.open(QIODevice::ReadOnly)); + QByteArray data = file.readAll(); + QVERIFY(data.size() > 0); + QVERIFY(data.contains("import QmlProject")); + QVERIFY(data.contains("mainFile: \"test_projectContent/App.qml\"")); + + file.close(); + file.setFileName(resourcePath + "/qtquickcontrols2.conf"); + QVERIFY(file.open(QIODevice::ReadOnly)); + data = file.readAll(); + QVERIFY(data.size() > 0); + QVERIFY(data.contains("Style=Basic")); +} + +void TestProjectManager::testUnregisterResource() +{ + ProjectManager projectManager; + const QString resourcePath = ":/data/test_project_unregister"; + bool result = projectManager.registerResource(m_projectThatCanRun, resourcePath); + QVERIFY(result); + + result = projectManager.unregisterResource(m_projectThatCanRun, resourcePath); + QVERIFY(result); + + const QFile file(resourcePath + "/test_project.qmlproject"); + QVERIFY(!file.exists()); +} + +void TestProjectManager::testCopyResourceToFs() +{ + ProjectManager projectManager; + const QString resourcePath = ":/data/test_project_copy"; + bool result = projectManager.registerResource(m_projectThatCanRun, resourcePath); + QVERIFY(result); + + const QString destinationPath = QDir::tempPath() + "/test_project_copy"; + result = projectManager.copyResourceToFs(resourcePath, destinationPath); + QVERIFY(result); + + const QStringList sourceFiles = QDir(resourcePath).entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + const QStringList destFiles = QDir(destinationPath).entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + + QVERIFY(sourceFiles.size() == destFiles.size()); + QCOMPARE(sourceFiles, destFiles); +} + +void TestProjectManager::testUnpackProject() +{ + ProjectManager projectManager; + const QString projectPath = projectManager.unpackProject(m_projectThatCanRun); + QVERIFY(!projectPath.isEmpty()); + QVERIFY(QFile::exists(projectPath + "/test_project.qmlproject")); + QVERIFY(QFile::exists(projectPath + "/qtquickcontrols2.conf")); +} + +void TestProjectManager::testRunProjectInternal() +{ + ProjectManager projectManager; + + const bool result = projectManager.runProjectInternal(m_projectThatCanRun); + + QVERIFY(result); + QVERIFY(projectManager.m_qmlEngine); + QVERIFY(projectManager.m_qmlComponent); + QVERIFY(projectManager.m_quickWindow); + QVERIFY(projectManager.m_quickWindow->isVisible() == false); +} + +void TestProjectManager::testFindFile() +{ + ProjectManager projectManager; + const QString projectPath = projectManager.unpackProject(m_projectThatCanRun); + QVERIFY(!projectPath.isEmpty()); + + const QString qmlProjectFile = projectManager.findFile(projectPath, "*.qmlproject", false); + QVERIFY(!qmlProjectFile.isEmpty()); + QVERIFY(qmlProjectFile.contains("test_project.qmlproject")); +} + +void TestProjectManager::testReadQmlProjectFile() +{ + ProjectManager projectManager; + const QString projectPath = projectManager.unpackProject(m_projectThatCanRun); + QVERIFY(!projectPath.isEmpty()); + + const QString qmlProjectFile = projectManager.findFile(projectPath, "*.qmlproject", false); + QVERIFY(!qmlProjectFile.isEmpty()); + + const QString qmlProjectFileContent = projectManager.readQmlProjectFile(qmlProjectFile); + QVERIFY(!qmlProjectFileContent.isEmpty()); + QVERIFY(qmlProjectFileContent.contains("mainFile: \"test_projectContent/App.qml\"")); +} + +void TestProjectManager::testGetMainQmlFile() +{ + ProjectManager projectManager; + const QString projectPath = projectManager.unpackProject(m_projectThatCanRun); + QVERIFY(!projectPath.isEmpty()); + + const QString qmlProjectFile = projectManager.findFile(projectPath, "*.qmlproject", false); + QVERIFY(!qmlProjectFile.isEmpty()); + + const QString qmlProjectFileContent = projectManager.readQmlProjectFile(qmlProjectFile); + QVERIFY(!qmlProjectFileContent.isEmpty()); + + const QString mainQmlFile = projectManager.getMainQmlFile(projectPath, qmlProjectFileContent); + QVERIFY(!mainQmlFile.isEmpty()); + QCOMPARE(mainQmlFile, projectPath + "/test_projectContent/App.qml"); +} + +void TestProjectManager::testGetImportPaths() +{ + ProjectManager projectManager; + const QString projectPath = projectManager.unpackProject(m_projectThatCanRun); + QVERIFY(!projectPath.isEmpty()); + + const QString qmlProjectFile = projectManager.findFile(projectPath, "*.qmlproject", false); + QVERIFY(!qmlProjectFile.isEmpty()); + + const QString qmlProjectFileContent = projectManager.readQmlProjectFile(qmlProjectFile); + QVERIFY(!qmlProjectFileContent.isEmpty()); + + const QStringList importPaths = projectManager.getImportPaths(projectPath, qmlProjectFileContent); + QVERIFY(importPaths.size() == 1); + QCOMPARE(importPaths.at(0), projectPath + "/."); +} + +void TestProjectManager::testIsQt6Project() +{ + ProjectManager projectManager; + const QString projectPath = projectManager.unpackProject(m_projectThatCanRun); + QVERIFY(!projectPath.isEmpty()); + + const QString qmlProjectFile = projectManager.findFile(projectPath, "*.qmlproject", false); + QVERIFY(!qmlProjectFile.isEmpty()); + + const QString qmlProjectFileContent = projectManager.readQmlProjectFile(qmlProjectFile); + QVERIFY(!qmlProjectFileContent.isEmpty()); + + const bool result = projectManager.isQt6Project(qmlProjectFileContent); + QVERIFY(result); +} + +void TestProjectManager::testShowAppWindow() +{ + ProjectManager projectManager; + + projectManager.runProject(m_projectThatCanRun, true, "sessionId"); + QVERIFY(projectManager.m_quickWindow->isVisible() == false); + + projectManager.showAppWindow(); + QVERIFY(projectManager.m_quickWindow->isVisible()); +} diff --git a/tests/tst_projectmanager.h b/tests/tst_projectmanager.h new file mode 100644 index 0000000000000000000000000000000000000000..a9c5b603ccfd8eeb06b8e8f202ec21ba1ba274dc --- /dev/null +++ b/tests/tst_projectmanager.h @@ -0,0 +1,40 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include <QObject> + +#include "backend/projectmanager.h" + +class TestProjectManager : public QObject +{ + Q_OBJECT +private slots: + void initTestCase(); + + // public methods + void testRunProject(); + void testStopProject(); + + // protected methods - resource management + void testRegisterResource(); + void testUnregisterResource(); + void testCopyResourceToFs(); + void testUnpackProject(); + + // protected methods - project management + void testRunProjectInternal(); + void testFindFile(); + void testReadQmlProjectFile(); + void testGetMainQmlFile(); + void testGetImportPaths(); + void testIsQt6Project(); + + // protected methods - window management + void testShowAppWindow(); + +private: + QByteArray m_projectThatCanRun; + QByteArray m_projectThatCannotRun; +}; diff --git a/tests/tst_settings.cpp b/tests/tst_settings.cpp index a8922db75f2fa151bc0ebf1f2c22d21a9cbc91ba..f860d33ad0bc335e3df55be7c594f455d3ceeb3f 100644 --- a/tests/tst_settings.cpp +++ b/tests/tst_settings.cpp @@ -5,22 +5,31 @@ #include <QTest> -#include "backend/settings.h" +void TestSettings::initTestCase() +{ + QVERIFY(m_settings.clearSettings()); +} void TestSettings::testAutoScaleProject() { - Settings settings; - QVERIFY(settings.autoScaleProject()); + QVERIFY(m_settings.autoScaleProject()); - settings.setAutoScaleProject(false); - QVERIFY(!settings.autoScaleProject()); + m_settings.setAutoScaleProject(false); + QVERIFY(!m_settings.autoScaleProject()); } void TestSettings::testDeviceUuid() { - Settings settings; - QVERIFY(settings.deviceUuid().isEmpty()); + QVERIFY(m_settings.deviceUuid().isEmpty()); + + m_settings.setDeviceUuid("1234567890"); + QCOMPARE(m_settings.deviceUuid(), QString("1234567890")); +} + +void TestSettings::testKeepScreenOn() +{ + QVERIFY(!m_settings.keepScreenOn()); - settings.setDeviceUuid("1234567890"); - QCOMPARE(settings.deviceUuid(), QString("1234567890")); + m_settings.setKeepScreenOn(true); + QVERIFY(m_settings.keepScreenOn()); } diff --git a/tests/tst_settings.h b/tests/tst_settings.h index 6c70c64d27b737a0e0585207ffe57696af200960..92cfed7aa462d7383c7bdd5d50b383b11cff1d93 100644 --- a/tests/tst_settings.h +++ b/tests/tst_settings.h @@ -5,11 +5,18 @@ #include <QObject> +#include "backend/settings.h" + class TestSettings : public QObject { Q_OBJECT private slots: + void initTestCase(); void testAutoScaleProject(); void testDeviceUuid(); + void testKeepScreenOn(); + +private: + Settings m_settings; };