QtQuickView statuslistener CI tests

This test adds basic loading status tests for QtQuickView on Android.
The test uses a primary QtQuickView which has the TestCases, and a
Communicator QML_ELEMENT C++ class to load other QML files into a
secondary QtQuickView contained in the hosting layout. The hosting
activity listens to QtQuickView status changed signals, and sends those
directly to the Communicator object, which emits a signal with the
status information to the TestCases.

Task-number: QTBUG-133439
Change-Id: I01a3a1dc3124036d8290a248b9b2d1bb4010033d
Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
This commit is contained in:
Petri Virkkunen 2025-03-03 13:39:04 +02:00
parent 58272fe764
commit e4971b51f8
15 changed files with 427 additions and 0 deletions

View File

@ -1,2 +1,3 @@
add_subdirectory(basic) add_subdirectory(basic)
add_subdirectory(signallistener) add_subdirectory(signallistener)
add_subdirectory(statuslistener)

View File

@ -0,0 +1,24 @@
# 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.22)
project(tst_qtquickview_statuslistener LANGUAGES CXX)
find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
find_package(Qt6 REQUIRED COMPONENTS Quick)
endif()
qt_standard_project_setup()
# Dummy test - just so this can be detected by ctest.
# Actual test implementation is under ${CMAKE_CURRENT_SOURCE_DIR}/android/qml/
# Add a source file, otherwise no test target is actually created.
qt_internal_add_test(tst_qtquickview_statuslistener
SOURCES
empty.cpp
)
set_target_properties(tst_qtquickview_statuslistener
PROPERTIES
QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android
)

View File

@ -0,0 +1,34 @@
<?xml version="1.0"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.qtproject.qt.android.qtquickview_statuslistener"
android:installLocation="auto"
android:versionCode="-- %%INSERT_VERSION_CODE%% --"
android:versionName="-- %%INSERT_VERSION_NAME%% --">
<supports-screens
android:anyDensity="true"
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true" />
<application
android:name="org.qtproject.qt.android.bindings.QtApplication"
android:hardwareAccelerated="true"
android:label="-- %%INSERT_APP_NAME%% --"
android:icon="-- %%INSERT_APP_ICON%% --"
android:requestLegacyExternalStorage="true"
android:allowBackup="true"
android:fullBackupOnly="false">
<activity
android:name="org.qtproject.qt.android.qtquickview_statuslistener.TestActivity"
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
android:launchMode="singleTop"
android:screenOrientation="unspecified"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,67 @@
plugins {
id 'com.android.application' version '8.3.0'
id 'org.qtproject.qt.gradleplugin' version '1.2'
}
def home = System.properties['user.home']
def ndkPath = System.getenv('ANDROID_NDK_ROOT')
QtBuild {
qtPath = file(home + '/work/install')
projectPath = file('qml/')
qtKitDir = file(home + '/work/install/target')
extraCMakeArguments = ['-DANDROID_NDK_ROOT=' + ndkPath]
}
android {
/*******************************************************
* The following variables:
* - androidBuildToolsVersion,
* - androidCompileSdkVersion
* - qtAndroidDir - holds the path to qt android files
* needed to build any Qt application
* on Android.
* - qtGradlePluginType - whether to build an app or a library
*
* are defined in gradle.properties file. This file is
* updated by QtCreator and androiddeployqt tools.
* Changing them manually might break the compilation!
*******************************************************/
namespace androidPackageName
compileSdkVersion androidCompileSdkVersion
buildToolsVersion androidBuildToolsVersion
ndkVersion androidNdkVersion
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = [qtAndroidDir + '/src', 'src', 'java']
res.srcDirs = [qtAndroidDir + '/res', 'res']
}
}
tasks.withType(JavaCompile) {
options.incremental = true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
abortOnError false
}
// Do not compress Qt binary resources file
aaptOptions {
noCompress 'rcc'
}
defaultConfig {
resConfig "en"
minSdkVersion qtMinSdkVersion
targetSdkVersion qtTargetSdkVersion
ndk.abiFilters = qtTargetAbiList.split(",")
}
}

View File

@ -0,0 +1,39 @@
# Copyright (C) 2025 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.22)
project(tst_qtquickview_statuslistener_qml LANGUAGES CXX)
find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
find_package(Qt6 REQUIRED COMPONENTS Quick QuickTest QuickTestPrivate)
qt_standard_project_setup()
qt_add_executable(tst_qtquickview_statuslistener_qml
tst_qtquickview.cpp)
set_source_files_properties(TestView.qml PROPERTIES QT_QML_GENERATE_JAVA_CLASS TRUE)
set_source_files_properties(Invalid.qml PROPERTIES
QT_QML_SKIP_QMLDIR_ENTRY TRUE
QT_QML_SKIP_QMLLINT TRUE
QT_QML_SKIP_CACHEGEN TRUE)
qt_add_qml_module(tst_qtquickview_statuslistener_qml
URI TestViewModule
VERSION 1.0
QML_FILES
Valid.qml
Invalid.qml
TestView.qml
SOURCES
testactivitycommunicator.h testactivitycommunicator.cpp
)
set_target_properties(tst_qtquickview_statuslistener_qml
PROPERTIES
QT_ANDROID_PACKAGE_NAME "org.qtproject.qt.android.tst_qtquickview_statuslistener_qml")
target_link_libraries(tst_qtquickview_statuslistener_qml PRIVATE
Qt::Core Qt::Quick Qt::QuickTest Qt::QuickTestPrivate
)

View File

@ -0,0 +1,9 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
import QtQuick
Item {
id: root
Hello, this is some text in the middle of your otherwise nicely formed QML file.
}

View File

@ -0,0 +1,45 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
import QtQuick
import QtTest
Item {
id: root
TestActivityCommunicator {
id: communicator
}
SignalSpy {
id: qtQuickViewStatusSpy
target: communicator
signalName: "qtQuickViewStatusChanged"
}
TestCase {
id: validQmlFile
name: "Valid QML file"
function test_valid() {
qtQuickViewStatusSpy.clear()
communicator.loadQtQuickView("qrc:/TestViewModule/Valid.qml");
qtQuickViewStatusSpy.wait()
compare(qtQuickViewStatusSpy.count, 1)
compare(qtQuickViewStatusSpy.signalArguments[0][0], 1)
}
}
TestCase {
id: invalidQmlFile
name: "Invalid QML file"
function test_invalid() {
qtQuickViewStatusSpy.clear()
communicator.loadQtQuickView("qrc:/TestViewModule/Invalid.qml");
qtQuickViewStatusSpy.wait()
compare(qtQuickViewStatusSpy.count, 1)
compare(qtQuickViewStatusSpy.signalArguments[0][0], 3)
}
}
}

View File

@ -0,0 +1,20 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
import QtQuick
import QtTest
Item {
id: root
Rectangle {
anchors.fill: parent
color: "#00FF00"
Text {
anchors.centerIn: parent
text: "QML"
color: "black"
}
}
}

View File

@ -0,0 +1,39 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "testactivitycommunicator.h"
using namespace QtJniTypes;
static TestActivityCommunicator *s_instance;
TestActivityCommunicator::TestActivityCommunicator(QObject *parent)
: QObject{ parent },
m_activity(TestActivity::callStaticMethod<TestActivity>("instance"))
{
s_instance = this;
TestActivity::registerNativeMethods({
Q_JNI_NATIVE_SCOPED_METHOD(jni_onQtQuickViewStatusChanged, TestActivityCommunicator)
});
}
TestActivityCommunicator::~TestActivityCommunicator()
{
s_instance = nullptr;
}
TestActivityCommunicator *TestActivityCommunicator::instance()
{
return s_instance;
}
void TestActivityCommunicator::loadQtQuickView(const QString &qmlUri)
{
m_activity.callMethod<void>("loadQtQuickView", qmlUri);
}
void TestActivityCommunicator::jni_onQtQuickViewStatusChanged(JNIEnv *, jclass, jint status)
{
Q_ASSERT(s_instance);
emit s_instance->qtQuickViewStatusChanged(status);
}

View File

@ -0,0 +1,37 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef TESTACTIVITYCOMMUNICATOR_H
#define TESTACTIVITYCOMMUNICATOR_H
#include <QtCore/qobject.h>
#include <QtQml/qqmlengine.h>
#include <QtCore/qjnitypes.h>
Q_DECLARE_JNI_CLASS(TestActivity,
"org/qtproject/qt/android/qtquickview_statuslistener/TestActivity")
class TestActivityCommunicator : public QObject
{
Q_OBJECT
QML_ELEMENT
public:
explicit TestActivityCommunicator(QObject *parent = nullptr);
~TestActivityCommunicator();
static TestActivityCommunicator *instance();
public slots:
void loadQtQuickView(const QString &qmlUri);
signals:
void qtQuickViewStatusChanged(int status);
private:
QtJniTypes::TestActivity m_activity;
static void jni_onQtQuickViewStatusChanged(JNIEnv *, jclass, jint status);
Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_onQtQuickViewStatusChanged)
};
#endif // TESTACTIVITYCOMMUNICATOR_H

View File

@ -0,0 +1,6 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtQuickTest/6.10.0/QtQuickTest/private/quickforandroidtest_p.h>
QUICK_FOR_ANDROID_TEST_MAIN("tst_qtquickview_statuslistener")

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="@+id/qmlFrame"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
</FrameLayout>
<FrameLayout
android:id="@+id/qmlFrame2"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
</FrameLayout>
</LinearLayout>

View File

@ -0,0 +1,15 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}

View File

@ -0,0 +1,65 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
package org.qtproject.qt.android.qtquickview_statuslistener;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.FrameLayout;
import org.qtproject.qt.android.QtQmlStatus;
import org.qtproject.qt.android.QtQmlStatusChangeListener;
import org.qtproject.qt.android.QtQuickView;
import org.qtproject.qt.android.QtQuickViewContent;
import org.qtproject.qt.android.tst_qtquickview_statuslistener_qml.TestViewModule.TestView;
public class TestActivity extends Activity implements QtQmlStatusChangeListener
{
native void jni_onQtQuickViewStatusChanged(int status);
private final TestView m_testView = new TestView();
private QtQuickView m_quickView;
private QtQuickView m_testingView;
private static TestActivity m_instance;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
m_instance = this;
m_quickView = new QtQuickView(this);
final FrameLayout qmlFrame = findViewById(R.id.qmlFrame);
final ViewGroup.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
qmlFrame.addView(m_quickView, params);
m_quickView.loadContent(m_testView);
}
public void loadQtQuickView(String qmlUri)
{
runOnUiThread(() -> {
FrameLayout parent = findViewById(R.id.qmlFrame2);
if (m_testingView != null)
parent.removeView(m_testingView);
m_testingView = new QtQuickView(this, qmlUri, "tst_qtquickview_statuslistener_qml");
m_testingView.setStatusChangeListener(new QtQmlStatusChangeListener() {
@Override
public void onStatusChanged(QtQmlStatus status) {
jni_onQtQuickViewStatusChanged(status.ordinal());
}
});
parent.addView(m_testingView,
new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
});
}
public TestView testView() { return m_testView; }
public static TestActivity instance() { return m_instance; }
}

View File

@ -0,0 +1,4 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Left empty on purpose.