mirror of
https://git.jami.net/savoirfairelinux/jami-client-qt.git
synced 2025-09-10 03:53:23 +02:00
testing: add a way to test individual QML components
This is a WIP and is intended to be adapted continuously to support more and more UI elements and reduce the time spent debugging components. Some components will require additional configuration (e.g. the conversation ID must be set), which may require additional changes. Change-Id: Iaa5d49693f874202439e746a274da4911adf7d15
This commit is contained in:
parent
97e477416a
commit
ae53d92c2e
9 changed files with 188 additions and 52 deletions
65
src/app/ComponentTestWindow.qml
Normal file
65
src/app/ComponentTestWindow.qml
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Savoir-faire Linux Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
import net.jami.Adapters 1.1
|
||||
|
||||
// A window into which we can load a QML file for testing.
|
||||
ApplicationWindow {
|
||||
id: appWindow
|
||||
visible: true
|
||||
width: testWidth || loader.implicitWidth || 800
|
||||
height: testHeight || loader.implicitHeight || 600
|
||||
title: testComponentURI
|
||||
|
||||
// WARNING: The following currently must be maintained in tandem with MainApplicationWindow.qml
|
||||
// Used to manage full screen mode and save/restore window geometry.
|
||||
readonly property bool useFrameless: false
|
||||
property bool isRTL: UtilsAdapter.isRTL
|
||||
LayoutMirroring.enabled: isRTL
|
||||
LayoutMirroring.childrenInherit: isRTL
|
||||
property LayoutManager layoutManager: LayoutManager {
|
||||
appContainer: null
|
||||
}
|
||||
// Used to manage dynamic view loading and unloading.
|
||||
property ViewManager viewManager: ViewManager {}
|
||||
// Used to manage the view stack and the current view.
|
||||
property ViewCoordinator viewCoordinator: ViewCoordinator {}
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
source: Qt.resolvedUrl(testComponentURI)
|
||||
onStatusChanged: {
|
||||
console.log("Status changed to:", loader.status)
|
||||
if (loader.status == Loader.Error || loader.status == Loader.Null) {
|
||||
console.error("Couldn't load component:", source)
|
||||
Qt.exit(1);
|
||||
} else if (loader.status == Loader.Ready) {
|
||||
console.info("Loaded component:", source);
|
||||
// If any of the dimensions are not set, set them to the appWindow's dimensions
|
||||
item.width = item.width || Qt.binding(() => appWindow.width);
|
||||
item.height = item.height || Qt.binding(() => appWindow.height);
|
||||
viewCoordinator.init(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Closing this window should always exit the application.
|
||||
onClosing: Qt.quit()
|
||||
}
|
|
@ -41,6 +41,20 @@ QtObject {
|
|||
// Used to store if a OngoingCallPage component is fullscreened.
|
||||
property bool isCallFullscreen: false
|
||||
|
||||
// QWK: Provide spacing for widgets that may be occluded by the system buttons.
|
||||
property QtObject qwkSystemButtonSpacing: QtObject {
|
||||
id: qwkSystemButtonSpacing
|
||||
readonly property bool isMacOS: Qt.platform.os.toString() === "osx"
|
||||
// macOS buttons are on the left.
|
||||
readonly property real left: {
|
||||
appWindow.useFrameless && isMacOS && viewCoordinator.isInSinglePaneMode ? 80 : 0
|
||||
}
|
||||
// Windows and Linux buttons are on the right.
|
||||
readonly property real right: {
|
||||
appWindow.useFrameless && !isMacOS && !root.isFullscreen ? sysBtnsLoader.width + 24 : 0
|
||||
}
|
||||
}
|
||||
|
||||
// Restore a visible windowed mode.
|
||||
function restoreApp() {
|
||||
if (isHidden) {
|
||||
|
|
|
@ -41,14 +41,11 @@ import QWindowKit
|
|||
ApplicationWindow {
|
||||
id: appWindow
|
||||
|
||||
readonly property bool useFrameless: UtilsAdapter.getAppValue(Settings.Key.UseFramelessWindow)
|
||||
property bool isRTL: UtilsAdapter.isRTL
|
||||
|
||||
LayoutMirroring.enabled: isRTL
|
||||
LayoutMirroring.childrenInherit: isRTL
|
||||
|
||||
// This needs to be set from the start.
|
||||
readonly property bool useFrameless: UtilsAdapter.getAppValue(Settings.Key.UseFramelessWindow)
|
||||
|
||||
onActiveFocusItemChanged: {
|
||||
focusOverlay.margin = -5;
|
||||
if (activeFocusItem && ((activeFocusItem.focusReason === Qt.TabFocusReason) || (activeFocusItem.focusReason === Qt.BacktabFocusReason))) {
|
||||
|
@ -94,16 +91,10 @@ ApplicationWindow {
|
|||
id: layoutManager
|
||||
appContainer: fullscreenContainer
|
||||
}
|
||||
|
||||
// Used to manage dynamic view loading and unloading.
|
||||
ViewManager {
|
||||
id: viewManager
|
||||
}
|
||||
|
||||
property ViewManager viewManager: ViewManager {}
|
||||
// Used to manage the view stack and the current view.
|
||||
ViewCoordinator {
|
||||
id: viewCoordinator
|
||||
}
|
||||
property ViewCoordinator viewCoordinator: ViewCoordinator {}
|
||||
|
||||
// Used to prevent the window from being visible until the
|
||||
// window geometry has been restored and the view stack has
|
||||
|
@ -234,17 +225,6 @@ ApplicationWindow {
|
|||
anchors.fill: parent
|
||||
}
|
||||
|
||||
// QWK: Provide spacing for widgets that may be occluded by the system buttons.
|
||||
QtObject {
|
||||
id: qwkSystemButtonSpacing
|
||||
readonly property bool isMacOS: Qt.platform.os.toString() === "osx"
|
||||
readonly property bool isFullscreen: layoutManager.isFullScreen
|
||||
// macOS buttons are on the left.
|
||||
readonly property real left: useFrameless && isMacOS && viewCoordinator.isInSinglePaneMode ? 80 : 0
|
||||
// Windows and Linux buttons are on the right.
|
||||
readonly property real right: useFrameless && !isMacOS && !isFullscreen ? sysBtnsLoader.width + 24 : 0
|
||||
}
|
||||
|
||||
// QWK: Window Title bar
|
||||
Item {
|
||||
id: titleBar
|
||||
|
|
|
@ -335,49 +335,59 @@ MainApplication::parseArguments()
|
|||
}
|
||||
}
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
parser_.addHelpOption();
|
||||
parser_.addVersionOption();
|
||||
|
||||
QCommandLineOption webDebugOption(QStringList() << "remote-debugging-port",
|
||||
"Web debugging port.",
|
||||
"port");
|
||||
parser.addOption(webDebugOption);
|
||||
parser_.addOption(webDebugOption);
|
||||
|
||||
QCommandLineOption minimizedOption({"m", "minimized"}, "Start minimized.");
|
||||
parser.addOption(minimizedOption);
|
||||
parser_.addOption(minimizedOption);
|
||||
|
||||
QCommandLineOption debugOption({"d", "debug"}, "Debug out.");
|
||||
parser.addOption(debugOption);
|
||||
parser_.addOption(debugOption);
|
||||
|
||||
QCommandLineOption logFileOption({"f", "file"}, "Debug to <file>.", "file");
|
||||
parser.addOption(logFileOption);
|
||||
parser_.addOption(logFileOption);
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
QCommandLineOption updateUrlOption({"u", "url"}, "<url> for debugging version queries.", "url");
|
||||
parser.addOption(updateUrlOption);
|
||||
parser_.addOption(updateUrlOption);
|
||||
|
||||
#endif
|
||||
QCommandLineOption terminateOption({"t", "term"}, "Terminate all instances.");
|
||||
parser.addOption(terminateOption);
|
||||
parser_.addOption(terminateOption);
|
||||
|
||||
QCommandLineOption muteDaemonOption({"q", "quiet"}, "Mute daemon logging. (only if debug)");
|
||||
parser.addOption(muteDaemonOption);
|
||||
parser_.addOption(muteDaemonOption);
|
||||
|
||||
parser.process(*this);
|
||||
#ifdef QT_DEBUG
|
||||
// In debug mode, add an option to test a specific QML component via its name.
|
||||
// e.g. ./jami --test AccountComboBox
|
||||
parser_.addOption(QCommandLineOption("test", "Test a QML component via its name.", "uri"));
|
||||
// We may need to force the test window dimensions in the case that the component to test
|
||||
// does not specify its own dimensions and is dependent on parent/sibling dimensions.
|
||||
// e.g. ./jami --test AccountComboBox -w 200
|
||||
parser_.addOption(QCommandLineOption("width", "Width for the test window.", "width"));
|
||||
parser_.addOption(QCommandLineOption("height", "Height for the test window.", "height"));
|
||||
#endif
|
||||
|
||||
runOptions_[Option::StartMinimized] = parser.isSet(minimizedOption);
|
||||
runOptions_[Option::Debug] = parser.isSet(debugOption);
|
||||
if (parser.isSet(logFileOption)) {
|
||||
auto logFileValue = parser.value(logFileOption);
|
||||
parser_.process(*this);
|
||||
|
||||
runOptions_[Option::StartMinimized] = parser_.isSet(minimizedOption);
|
||||
runOptions_[Option::Debug] = parser_.isSet(debugOption);
|
||||
if (parser_.isSet(logFileOption)) {
|
||||
auto logFileValue = parser_.value(logFileOption);
|
||||
auto logFile = logFileValue.isEmpty() ? Utils::getDebugFilePath() : logFileValue;
|
||||
qputenv("JAMI_LOG_FILE", logFile.toStdString().c_str());
|
||||
}
|
||||
#ifdef Q_OS_WINDOWS
|
||||
runOptions_[Option::UpdateUrl] = parser.value(updateUrlOption);
|
||||
runOptions_[Option::UpdateUrl] = parser_.value(updateUrlOption);
|
||||
#endif
|
||||
runOptions_[Option::TerminationRequested] = parser.isSet(terminateOption);
|
||||
runOptions_[Option::MuteDaemon] = parser.isSet(muteDaemonOption);
|
||||
runOptions_[Option::TerminationRequested] = parser_.isSet(terminateOption);
|
||||
runOptions_[Option::MuteDaemon] = parser_.isSet(muteDaemonOption);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -393,6 +403,35 @@ MainApplication::setApplicationFont()
|
|||
setFont(font);
|
||||
}
|
||||
|
||||
QString
|
||||
findResource(const QString& targetBasename, const QString& basePath = ":/")
|
||||
{
|
||||
QDir dir(basePath);
|
||||
// List all entries in the directory excluding special entries '.' and '..'
|
||||
QStringList entries = dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot,
|
||||
QDir::DirsFirst);
|
||||
|
||||
Q_FOREACH (const QString& entry, entries) {
|
||||
QString fullPath = basePath + "/" + entry;
|
||||
QFileInfo fileInfo(fullPath);
|
||||
|
||||
if (fileInfo.isDir()) {
|
||||
// Recursively search in subdirectories
|
||||
QString found = findResource(targetBasename, fullPath);
|
||||
if (!found.isEmpty()) {
|
||||
return found; // Return the first match found in any subdirectory
|
||||
}
|
||||
} else if (fileInfo.isFile()
|
||||
&& fileInfo.fileName().contains(targetBasename, Qt::CaseInsensitive)) {
|
||||
// Match found, return the full path but remove the leading ":/".
|
||||
return fileInfo.absoluteFilePath().mid(2);
|
||||
}
|
||||
}
|
||||
|
||||
// No match found in this directory or its subdirectories
|
||||
return QString();
|
||||
}
|
||||
|
||||
void
|
||||
MainApplication::initQmlLayer()
|
||||
{
|
||||
|
@ -406,7 +445,30 @@ MainApplication::initQmlLayer()
|
|||
&screenInfo_,
|
||||
this);
|
||||
|
||||
engine_->load(QUrl(QStringLiteral("qrc:/MainApplicationWindow.qml")));
|
||||
QUrl url;
|
||||
if (parser_.isSet("test")) {
|
||||
// List the QML files in the project source tree.
|
||||
const auto targetTestComponent = findResource(parser_.value("test"));
|
||||
if (targetTestComponent.isEmpty()) {
|
||||
C_FATAL << "Failed to find QML component:" << parser_.value("test");
|
||||
}
|
||||
engine_->rootContext()->setContextProperty("testComponentURI", targetTestComponent);
|
||||
// Log the width and height values for the test window.
|
||||
const auto testWidth = parser_.isSet("width") ? parser_.value("width").toInt() : 0;
|
||||
const auto testHeight = parser_.isSet("height") ? parser_.value("height").toInt() : 0;
|
||||
engine_->rootContext()->setContextProperty("testWidth", testWidth);
|
||||
engine_->rootContext()->setContextProperty("testHeight", testHeight);
|
||||
url = u"qrc:/ComponentTestWindow.qml"_qs;
|
||||
} else {
|
||||
url = u"qrc:/MainApplicationWindow.qml"_qs;
|
||||
}
|
||||
QObject::connect(
|
||||
engine_.get(),
|
||||
&QQmlApplicationEngine::objectCreationFailed,
|
||||
this,
|
||||
[url]() { C_FATAL << "Failed to load QML component:" << url; },
|
||||
Qt::QueuedConnection);
|
||||
engine_->load(url);
|
||||
|
||||
// Report the render interface used.
|
||||
C_DBG << "Main window loaded using" << getRenderInterfaceString();
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <QQmlEngine>
|
||||
#include <QScreen>
|
||||
#include <QWindow>
|
||||
#include <QCommandLineParser>
|
||||
|
||||
#include <memory>
|
||||
|
||||
|
@ -122,6 +123,6 @@ private:
|
|||
SystemTray* systemTray_;
|
||||
AppSettingsManager* settingsManager_;
|
||||
PreviewEngine* previewEngine_;
|
||||
|
||||
ScreenInfo screenInfo_;
|
||||
QCommandLineParser parser_;
|
||||
};
|
||||
|
|
|
@ -29,7 +29,7 @@ Label {
|
|||
|
||||
property alias popup: comboBoxPopup
|
||||
|
||||
width: parent ? parent.width : o
|
||||
width: parent ? parent.width : 0
|
||||
height: JamiTheme.accountListItemHeight
|
||||
|
||||
property bool inSettings: viewCoordinator.currentViewName === "SettingsView"
|
||||
|
|
|
@ -75,8 +75,8 @@ Rectangle {
|
|||
|
||||
anchors.fill: parent
|
||||
// QWK: spacing
|
||||
anchors.leftMargin: qwkSystemButtonSpacing.left
|
||||
anchors.rightMargin: 10 + qwkSystemButtonSpacing.right
|
||||
anchors.leftMargin: layoutManager.qwkSystemButtonSpacing.left
|
||||
anchors.rightMargin: 10 + layoutManager.qwkSystemButtonSpacing.right
|
||||
spacing: 16
|
||||
|
||||
JamiPushButton { QWKSetParentHitTestVisible {}
|
||||
|
|
|
@ -131,8 +131,8 @@ Item {
|
|||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
// QWK: spacing
|
||||
anchors.leftMargin: qwkSystemButtonSpacing.left
|
||||
anchors.rightMargin: qwkSystemButtonSpacing.right
|
||||
anchors.leftMargin: layoutManager.qwkSystemButtonSpacing.left
|
||||
anchors.rightMargin: layoutManager.qwkSystemButtonSpacing.right
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
|
|
|
@ -19,6 +19,8 @@ import QtQuick
|
|||
import QtQuick.Controls
|
||||
import QtTest
|
||||
|
||||
import net.jami.Adapters 1.1
|
||||
|
||||
import "../../../src/app/"
|
||||
|
||||
// The purpose of this component is to fake the ApplicationWindow and prevent
|
||||
|
@ -38,11 +40,23 @@ Item {
|
|||
|
||||
Component.onCompleted: viewCoordinator.init(this)
|
||||
|
||||
// These are our fake app management objects. The caveat is that they
|
||||
// must be maintained in sync with the actual objects in the app for now.
|
||||
// The benefit, is that this should be the single place where we need to
|
||||
// sync them.
|
||||
property int visibility: 0
|
||||
Binding {
|
||||
tw.visibility: uut.Window.window.visibility
|
||||
when: QTestRootObject.windowShown
|
||||
}
|
||||
|
||||
// WARNING: The following currently must be maintained in tandem with MainApplicationWindow.qml
|
||||
// Used to manage full screen mode and save/restore window geometry.
|
||||
property bool isRTL: UtilsAdapter.isRTL
|
||||
LayoutMirroring.enabled: isRTL
|
||||
LayoutMirroring.childrenInherit: isRTL
|
||||
property LayoutManager layoutManager: LayoutManager {
|
||||
appContainer: null
|
||||
}
|
||||
// Used to manage dynamic view loading and unloading.
|
||||
property ViewManager viewManager: ViewManager {}
|
||||
// Used to manage the view stack and the current view.
|
||||
property ViewCoordinator viewCoordinator: ViewCoordinator {}
|
||||
property QtObject appWindow: QtObject {
|
||||
property bool useFrameless: false
|
||||
|
|
Loading…
Add table
Reference in a new issue