From d0547a681225a42051b30209c042392678b5ce66 Mon Sep 17 00:00:00 2001 From: Andrea Marchesini Date: Tue, 2 Aug 2022 10:38:25 +0200 Subject: [PATCH 1/4] Fix the condition and introduce a reduce() method (#4109) --- src/addonmanager.cpp | 26 +++++++++ src/addonmanager.h | 2 + src/addons/addon.cpp | 77 +++++++++++++++++++-------- src/ui/developerMenu/ViewMessages.qml | 4 ++ 4 files changed, 88 insertions(+), 21 deletions(-) diff --git a/src/addonmanager.cpp b/src/addonmanager.cpp index 3cc97ec74..6ee822225 100644 --- a/src/addonmanager.cpp +++ b/src/addonmanager.cpp @@ -596,3 +596,29 @@ Addon* AddonManager::pick(QJSValue filterCallback) const { return nullptr; } + +QJSValue AddonManager::reduce(QJSValue callback, QJSValue initialValue) const { + if (!callback.isCallable()) { + logger.error() << "AddonManager.reduce must receive a callable JS value"; + return initialValue; + } + + QJSEngine* engine = QmlEngineHolder::instance()->engine(); + Q_ASSERT(engine); + + QJSValue reducedValue = initialValue; + + for (QMap::const_iterator i(m_addons.constBegin()); + i != m_addons.constEnd(); ++i) { + if (!i.value().m_addon || !i.value().m_addon->enabled()) { + continue; + } + + QJSValueList arguments; + arguments.append(engine->toScriptValue(i.value().m_addon)); + arguments.append(reducedValue); + reducedValue = callback.call(arguments); + } + + return reducedValue; +} diff --git a/src/addonmanager.h b/src/addonmanager.h index cc1bd6a36..2fc3e674e 100644 --- a/src/addonmanager.h +++ b/src/addonmanager.h @@ -23,6 +23,8 @@ class AddonManager final : public QAbstractListModel { public: Q_INVOKABLE Addon* pick(QJSValue filterCallback) const; + Q_INVOKABLE QJSValue reduce(QJSValue callback, QJSValue initialValue) const; + enum ModelRoles { AddonRole = Qt::UserRole + 1, }; diff --git a/src/addons/addon.cpp b/src/addons/addon.cpp index 10ba45c4a..fd81ef822 100644 --- a/src/addons/addon.cpp +++ b/src/addons/addon.cpp @@ -39,7 +39,9 @@ bool evaluateConditionsSettingsOp(const QString& op, bool result) { struct ConditionCallback { QString m_key; - std::function m_callback; + std::function m_staticCallback; + std::function + m_dynamicCallback; }; QList s_conditionCallbacks{ @@ -62,6 +64,9 @@ QList s_conditionCallbacks{ } return true; + }, + [](QObject*, const QJsonValue&) -> AddonConditionWatcher* { + return nullptr; }}, {"platforms", @@ -78,6 +83,9 @@ QList s_conditionCallbacks{ } return true; + }, + [](QObject*, const QJsonValue&) -> AddonConditionWatcher* { + return nullptr; }}, {"settings", @@ -131,6 +139,9 @@ QList s_conditionCallbacks{ } return true; + }, + [](QObject*, const QJsonValue&) -> AddonConditionWatcher* { + return nullptr; }}, {"env", @@ -151,6 +162,9 @@ QList s_conditionCallbacks{ logger.info() << "Unknown env value:" << env; return false; + }, + [](QObject*, const QJsonValue&) -> AddonConditionWatcher* { + return nullptr; }}, {"min_client_version", @@ -166,6 +180,9 @@ QList s_conditionCallbacks{ } return true; + }, + [](QObject*, const QJsonValue&) -> AddonConditionWatcher* { + return nullptr; }}, {"max_client_version", @@ -181,6 +198,33 @@ QList s_conditionCallbacks{ } return true; + }, + [](QObject*, const QJsonValue&) -> AddonConditionWatcher* { + return nullptr; + }}, + + {"locales", + [](const QJsonValue&) -> bool { + // dynamic condition + return true; + }, + [](QObject* parent, const QJsonValue& value) -> AddonConditionWatcher* { + QStringList locales; + for (const QJsonValue& v : value.toArray()) { + locales.append(v.toString().toLower()); + } + + return AddonConditionWatcherLocales::maybeCreate(parent, locales); + }}, + + {"trigger_time", + [](const QJsonValue&) -> bool { + // dynamic condition + return true; + }, + [](QObject* parent, const QJsonValue& value) -> AddonConditionWatcher* { + return AddonConditionWatcherTriggerTimeSecs::maybeCreate( + parent, value.toInteger()); }}, }; @@ -317,25 +361,16 @@ void Addon::retranslate() { void Addon::maybeCreateConditionWatchers(const QJsonObject& conditions) { QList watcherList; - { - QStringList locales; - for (const QJsonValue& value : conditions["locales"].toArray()) { - locales.append(value.toString().toLower()); - } - - AddonConditionWatcher* tmpWatcher = - AddonConditionWatcherLocales::maybeCreate(this, locales); - if (tmpWatcher) { - watcherList.append(tmpWatcher); - } - } - - { - AddonConditionWatcher* tmpWatcher = - AddonConditionWatcherTriggerTimeSecs::maybeCreate( - this, conditions["trigger_time"].toInteger()); - if (tmpWatcher) { - watcherList.append(tmpWatcher); + for (const QString& key : conditions.keys()) { + for (const ConditionCallback& condition : s_conditionCallbacks) { + if (condition.m_key == key) { + AddonConditionWatcher* conditionWatcher = + condition.m_dynamicCallback(this, conditions[key]); + if (conditionWatcher) { + watcherList.append(conditionWatcher); + } + break; + } } } @@ -370,7 +405,7 @@ bool Addon::evaluateConditions(const QJsonObject& conditions) { bool found = false; for (const ConditionCallback& condition : s_conditionCallbacks) { if (condition.m_key == key) { - if (!condition.m_callback(conditions[key])) { + if (!condition.m_staticCallback(conditions[key])) { return false; } found = true; diff --git a/src/ui/developerMenu/ViewMessages.qml b/src/ui/developerMenu/ViewMessages.qml index 2028f8e86..56dac026b 100644 --- a/src/ui/developerMenu/ViewMessages.qml +++ b/src/ui/developerMenu/ViewMessages.qml @@ -42,6 +42,10 @@ Item { filterCallback: obj => obj.addon.type === "message" } + Text { + text: "Unread messages: " + VPNAddonManager.reduce((addon, initialValue) => initialValue + (addon.type === "message" ? 1 : 0), 0); + } + Repeater { model: messagesModel delegate: VPNCheckBoxRow { From 061fc1e055af8da3d2a35fe66a8604a8c13a722c Mon Sep 17 00:00:00 2001 From: Andrea Marchesini Date: Tue, 2 Aug 2022 10:39:09 +0200 Subject: [PATCH 2/4] Filter and sortable model (#4094) --- src/addonmanager.cpp | 1 + src/commands/commandui.cpp | 8 +-- src/crashreporter/crashreporter.cpp | 2 + src/crashreporter/crashreporter.h | 4 +- src/crashreporter/crashui.cpp | 4 +- src/filterproxymodel.cpp | 88 ++++++++++++++++++++++++++--- src/filterproxymodel.h | 34 ++++++++++- src/inspector/inspectorhandler.cpp | 8 ++- src/inspector/inspectorutils.cpp | 8 ++- src/models/featuremodel.cpp | 1 + src/qmlengineholder.cpp | 14 +++-- src/qmlengineholder.h | 13 +++-- tests/qml/CMakeLists.txt | 9 +++ tests/qml/helper.cpp | 8 +++ tests/qml/qml.pro | 12 +++- tests/qml/tst_filterableModel.qml | 85 ++++++++++++++++++++++++++++ tests/unit/testaddon.cpp | 5 +- tests/unit/testthemes.cpp | 9 ++- 18 files changed, 280 insertions(+), 33 deletions(-) create mode 100644 tests/qml/tst_filterableModel.qml diff --git a/src/addonmanager.cpp b/src/addonmanager.cpp index 6ee822225..fb2e8985d 100644 --- a/src/addonmanager.cpp +++ b/src/addonmanager.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include diff --git a/src/commands/commandui.cpp b/src/commands/commandui.cpp index dd09759c0..0109ee4a2 100644 --- a/src/commands/commandui.cpp +++ b/src/commands/commandui.cpp @@ -10,7 +10,6 @@ #include "closeeventhandler.h" #include "commandlineparser.h" #include "constants.h" -#include "filterproxymodel.h" #include "fontloader.h" #include "iaphandler.h" #include "imageproviderfactory.h" @@ -35,6 +34,7 @@ #include #include +#include #include #ifdef MVPN_DEBUG @@ -203,8 +203,8 @@ int CommandUI::run(QStringList& tokens) { } #endif // This object _must_ live longer than MozillaVPN to avoid shutdown crashes. - QmlEngineHolder* engineHolder = new QmlEngineHolder(); - QQmlApplicationEngine* engine = QmlEngineHolder::instance()->engine(); + QQmlApplicationEngine* engine = new QQmlApplicationEngine(); + QmlEngineHolder engineHolder(engine); // TODO pending #3398 QQmlContext* ctx = engine->rootContext(); @@ -565,7 +565,7 @@ int CommandUI::run(QStringList& tokens) { engine->load(url); NotificationHandler* notificationHandler = - NotificationHandler::create(engineHolder); + NotificationHandler::create(&engineHolder); QObject::connect(vpn.controller(), &Controller::stateChanged, notificationHandler, diff --git a/src/crashreporter/crashreporter.cpp b/src/crashreporter/crashreporter.cpp index 91c018ed1..659672c63 100644 --- a/src/crashreporter/crashreporter.cpp +++ b/src/crashreporter/crashreporter.cpp @@ -4,11 +4,13 @@ #include "crashreporter.h" #include "crashui.h" +#include "qmlengineholder.h" using namespace std; CrashReporter::CrashReporter(QObject* parent) : QObject(parent) { m_ui = make_unique(); + new QmlEngineHolder(&m_engine); } bool CrashReporter::shouldPromptUser() { diff --git a/src/crashreporter/crashreporter.h b/src/crashreporter/crashreporter.h index 57e77ca25..b5b387b01 100644 --- a/src/crashreporter/crashreporter.h +++ b/src/crashreporter/crashreporter.h @@ -6,10 +6,10 @@ #define CRASHREPORTER_H #include +#include #include #include "crashui.h" -#include "qmlengineholder.h" #include "settingsholder.h" #include "localizer.h" @@ -29,7 +29,7 @@ class CrashReporter : public QObject { private: std::unique_ptr m_ui; - QmlEngineHolder m_engineHolder; + QQmlApplicationEngine m_engine; SettingsHolder settings; Localizer localizer; }; diff --git a/src/crashreporter/crashui.cpp b/src/crashreporter/crashui.cpp index 7ac85f90d..080fdb069 100644 --- a/src/crashreporter/crashui.cpp +++ b/src/crashreporter/crashui.cpp @@ -12,6 +12,7 @@ #include "qmlengineholder.h" #include "theme.h" +#include #include #include @@ -62,7 +63,8 @@ void CrashUI::initialize() { }); const QUrl url(QML_MAIN); - QmlEngineHolder::instance()->engine()->load(url); + qobject_cast(QmlEngineHolder::instance()->engine()) + ->load(url); m_initialized = true; } } diff --git a/src/filterproxymodel.cpp b/src/filterproxymodel.cpp index 5ccce8b70..7f08a8515 100644 --- a/src/filterproxymodel.cpp +++ b/src/filterproxymodel.cpp @@ -23,6 +23,24 @@ void FilterProxyModel::setFilterCallback(QJSValue filterCallback) { } m_filterCallback = filterCallback; + invalidate(); + + emit filterCallbackChanged(); +} + +QJSValue FilterProxyModel::sortCallback() const { return m_sortCallback; } + +void FilterProxyModel::setSortCallback(QJSValue sortCallback) { + if (!sortCallback.isCallable()) { + logger.error() + << "FilterProxyModel.sortCallback must be a JS callable value"; + return; + } + + m_sortCallback = sortCallback; + invalidate(); + + emit sortCallbackChanged(); } QAbstractListModel* FilterProxyModel::source() const { @@ -39,9 +57,19 @@ void FilterProxyModel::setSource(QAbstractListModel* sourceModel) { } } +QVariant FilterProxyModel::get(int pos) const { + QModelIndex i = index(pos, 0); + QJSValue value = dataToJSValue(this, i); + return QVariant::fromValue(value); +} + bool FilterProxyModel::filterAcceptsRow( int source_row, const QModelIndex& source_parent) const { - if (m_filterCallback.isNull()) { + if (!m_completed) { + return false; + } + + if (m_filterCallback.isNull() || m_filterCallback.isUndefined()) { logger.debug() << "No filter callback set!"; return true; } @@ -58,6 +86,56 @@ bool FilterProxyModel::filterAcceptsRow( return false; } + QJSValue value = dataToJSValue(sourceModel(), index); + + QJSValueList arguments; + arguments.append(value); + + QJSValue retValue = m_filterCallback.call(arguments); + return retValue.toBool(); +} + +bool FilterProxyModel::lessThan(const QModelIndex& left, + const QModelIndex& right) const { + if (!m_completed) { + return false; + } + + if (m_sortCallback.isNull() || m_sortCallback.isUndefined()) { + return QSortFilterProxyModel::lessThan(left, right); + } + + Q_ASSERT(m_sortCallback.isCallable()); + + if (!QmlEngineHolder::exists()) { + logger.error() << "Something bad is happening. Are we shutting down?"; + return false; + } + + QJSValue valueA = dataToJSValue(sourceModel(), left); + QJSValue valueB = dataToJSValue(sourceModel(), right); + + QJSValueList arguments; + arguments.append(valueA); + arguments.append(valueB); + + QJSValue retValue = m_sortCallback.call(arguments); + return retValue.toBool(); +} + +void FilterProxyModel::classBegin() {} + +void FilterProxyModel::componentComplete() { + m_completed = true; + invalidate(); + + if (m_sortCallback.isCallable()) { + sort(0); + } +} + +QJSValue FilterProxyModel::dataToJSValue(const QAbstractItemModel* model, + const QModelIndex& index) const { QJSEngine* engine = QmlEngineHolder::instance()->engine(); Q_ASSERT(engine); @@ -67,13 +145,9 @@ bool FilterProxyModel::filterAcceptsRow( for (QHash::const_iterator i = m_sourceModelRoleNames.constBegin(); i != m_sourceModelRoleNames.constEnd(); ++i) { - QVariant data = sourceModel()->data(index, i.key()); + QVariant data = model->data(index, i.key()); value.setProperty(QString(i.value()), engine->toScriptValue(data)); } - QJSValueList arguments; - arguments.append(value); - - QJSValue retValue = m_filterCallback.call(arguments); - return retValue.toBool(); + return value; } diff --git a/src/filterproxymodel.h b/src/filterproxymodel.h index 4eec6baf8..0bd5e9590 100644 --- a/src/filterproxymodel.h +++ b/src/filterproxymodel.h @@ -8,15 +8,20 @@ #include #include #include +#include #include -class FilterProxyModel : public QSortFilterProxyModel { +class FilterProxyModel : public QSortFilterProxyModel, public QQmlParserStatus { Q_OBJECT Q_DISABLE_COPY_MOVE(FilterProxyModel) + Q_INTERFACES(QQmlParserStatus) + QML_NAMED_ELEMENT(VPNFilterProxyModel) - Q_PROPERTY( - QJSValue filterCallback READ filterCallback WRITE setFilterCallback) + Q_PROPERTY(QJSValue filterCallback READ filterCallback WRITE setFilterCallback + NOTIFY filterCallbackChanged) + Q_PROPERTY(QJSValue sortCallback READ sortCallback WRITE setSortCallback + NOTIFY sortCallbackChanged) Q_PROPERTY(QAbstractListModel* source READ source WRITE setSource) public: @@ -24,22 +29,45 @@ class FilterProxyModel : public QSortFilterProxyModel { virtual ~FilterProxyModel() = default; + Q_INVOKABLE QVariant get(int pos) const; + + signals: + void filterCallbackChanged(); + void sortCallbackChanged(); + public: QJSValue filterCallback() const; void setFilterCallback(QJSValue filterCallback); + QJSValue sortCallback() const; + void setSortCallback(QJSValue sortCallback); + QAbstractListModel* source() const; void setSource(QAbstractListModel* sourceModel); + QJSValue dataToJSValue(const QAbstractItemModel* model, + const QModelIndex& index) const; + // QSortFilterProxyModel methods bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override; + bool lessThan(const QModelIndex& left, + const QModelIndex& right) const override; + + // QQmlParserStatus + + void classBegin() override; + void componentComplete() override; + private: mutable QJSValue m_filterCallback; + mutable QJSValue m_sortCallback; QHash m_sourceModelRoleNames; + + bool m_completed = false; }; #endif // FILTERPROXYMODEL_H diff --git a/src/inspector/inspectorhandler.cpp b/src/inspector/inspectorhandler.cpp index 1338b51f3..009fa22c4 100644 --- a/src/inspector/inspectorhandler.cpp +++ b/src/inspector/inspectorhandler.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -1115,7 +1116,12 @@ QJsonObject InspectorHandler::getViewTree() { QJsonObject out; out["type"] = "qml_tree"; - QQmlApplicationEngine* engine = QmlEngineHolder::instance()->engine(); + QQmlApplicationEngine* engine = qobject_cast( + QmlEngineHolder::instance()->engine()); + if (!engine) { + return out; + } + QJsonArray viewRoots; for (auto& root : engine->rootObjects()) { QQuickWindow* window = qobject_cast(root); diff --git a/src/inspector/inspectorutils.cpp b/src/inspector/inspectorutils.cpp index d1499265a..d387d9142 100644 --- a/src/inspector/inspectorutils.cpp +++ b/src/inspector/inspectorutils.cpp @@ -5,6 +5,7 @@ #include "inspectorutils.h" #include "qmlengineholder.h" +#include #include // static @@ -13,7 +14,12 @@ QObject* InspectorUtils::findObject(const QString& name) { Q_ASSERT(!parts.isEmpty()); QQuickItem* parent = nullptr; - QQmlApplicationEngine* engine = QmlEngineHolder::instance()->engine(); + QQmlApplicationEngine* engine = qobject_cast( + QmlEngineHolder::instance()->engine()); + if (!engine) { + return nullptr; + } + for (QObject* rootObject : engine->rootObjects()) { if (!rootObject) { continue; diff --git a/src/models/featuremodel.cpp b/src/models/featuremodel.cpp index 0c41d061a..275d7aa8a 100644 --- a/src/models/featuremodel.cpp +++ b/src/models/featuremodel.cpp @@ -18,6 +18,7 @@ #include #include #include +#include namespace { FeatureModel* s_instance = nullptr; diff --git a/src/qmlengineholder.cpp b/src/qmlengineholder.cpp index 6a043fbb5..43920bcf5 100644 --- a/src/qmlengineholder.cpp +++ b/src/qmlengineholder.cpp @@ -6,17 +6,19 @@ #include "leakdetector.h" #include "logger.h" -#include +#include #include +#include namespace { Logger logger(LOG_MAIN, "QmlEngineHolder"); QmlEngineHolder* s_instance = nullptr; } // namespace -QmlEngineHolder::QmlEngineHolder() { +QmlEngineHolder::QmlEngineHolder(QQmlEngine* engine) : m_engine(engine) { MVPN_COUNT_CTOR(QmlEngineHolder); + Q_ASSERT(engine); Q_ASSERT(!s_instance); s_instance = this; } @@ -38,7 +40,7 @@ QmlEngineHolder* QmlEngineHolder::instance() { bool QmlEngineHolder::exists() { return !!s_instance; } QNetworkAccessManager* QmlEngineHolder::networkAccessManager() { - return m_engine.networkAccessManager(); + return m_engine->networkAccessManager(); } void QmlEngineHolder::clearCacheInternal() { @@ -50,7 +52,11 @@ void QmlEngineHolder::clearCacheInternal() { } QWindow* QmlEngineHolder::window() const { - QObject* rootObject = m_engine.rootObjects().first(); + QQmlApplicationEngine* engine = + qobject_cast(m_engine); + if (!engine) return nullptr; + + QObject* rootObject = engine->rootObjects().first(); return qobject_cast(rootObject); } diff --git a/src/qmlengineholder.h b/src/qmlengineholder.h index 9a6073936..440d9baa4 100644 --- a/src/qmlengineholder.h +++ b/src/qmlengineholder.h @@ -7,22 +7,25 @@ #include "networkmanager.h" -#include - +class QQmlEngine; class QWindow; class QmlEngineHolder final : public NetworkManager { Q_DISABLE_COPY_MOVE(QmlEngineHolder) public: - QmlEngineHolder(); + explicit QmlEngineHolder(QQmlEngine* engine); ~QmlEngineHolder(); static QmlEngineHolder* instance(); static bool exists(); - QQmlApplicationEngine* engine() { return &m_engine; } + QQmlEngine* engine() { return m_engine; } + +#ifdef UNIT_TEST + void replaceEngine(QQmlEngine* engine) { m_engine = engine; } +#endif QNetworkAccessManager* networkAccessManager() override; @@ -34,7 +37,7 @@ class QmlEngineHolder final : public NetworkManager { void clearCacheInternal() override; private: - QQmlApplicationEngine m_engine; + QQmlEngine* m_engine = nullptr; }; #endif // QMLENGINEHOLDER_H diff --git a/tests/qml/CMakeLists.txt b/tests/qml/CMakeLists.txt index 7305675d6..715a9a42f 100644 --- a/tests/qml/CMakeLists.txt +++ b/tests/qml/CMakeLists.txt @@ -34,6 +34,8 @@ target_sources(qml_tests PRIVATE ${MVPN_SOURCE_DIR}/controller.h ${MVPN_SOURCE_DIR}/externalophandler.cpp ${MVPN_SOURCE_DIR}/externalophandler.h + ${MVPN_SOURCE_DIR}/filterproxymodel.cpp + ${MVPN_SOURCE_DIR}/filterproxymodel.h ${MVPN_SOURCE_DIR}/hawkauth.cpp ${MVPN_SOURCE_DIR}/hawkauth.h ${MVPN_SOURCE_DIR}/hkdf.cpp @@ -67,6 +69,8 @@ target_sources(qml_tests PRIVATE ${MVPN_SOURCE_DIR}/update/versionapi.h ${MVPN_SOURCE_DIR}/update/webupdater.cpp ${MVPN_SOURCE_DIR}/update/webupdater.h + ${MVPN_SOURCE_DIR}/qmlengineholder.cpp + ${MVPN_SOURCE_DIR}/qmlengineholder.h ) # VPN Client UI resources @@ -98,3 +102,8 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") list(APPEND QML_TEST_ARGS -platform offscreen) endif() add_test(NAME qml_tests COMMAND qml_tests ${QML_TEST_ARGS}) + +qt6_add_qml_module(qml_tests + URI Mozilla.VPN.qmlcomponents + VERSION 1.0 +) diff --git a/tests/qml/helper.cpp b/tests/qml/helper.cpp index 762ad565d..833e29ec5 100644 --- a/tests/qml/helper.cpp +++ b/tests/qml/helper.cpp @@ -3,6 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "helper.h" +#include "../../src/qmlengineholder.h" + #include #include @@ -60,6 +62,12 @@ void TestHelper::qmlEngineAvailable(QQmlEngine* engine) { Glean::Initialize(engine); engine->addImportPath("qrc:///"); + if (!QmlEngineHolder::exists()) { + new QmlEngineHolder(engine); + } else { + QmlEngineHolder::instance()->replaceEngine(engine); + } + qmlRegisterSingletonType( "TestHelper", 1, 0, "TestHelper", [this](QQmlEngine*, QJSEngine*) -> QObject* { diff --git a/tests/qml/qml.pro b/tests/qml/qml.pro index 2078efbc1..18439f1e8 100644 --- a/tests/qml/qml.pro +++ b/tests/qml/qml.pro @@ -45,6 +45,7 @@ SOURCES += \ ../unit/mocinspectorhandler.cpp \ ../../src/closeeventhandler.cpp \ ../../src/externalophandler.cpp \ + ../../src/filterproxymodel.cpp \ ../../src/hawkauth.cpp \ ../../src/hkdf.cpp \ ../../src/ipaddress.cpp \ @@ -60,7 +61,8 @@ SOURCES += \ ../../src/theme.cpp \ ../../src/update/updater.cpp \ ../../src/update/versionapi.cpp \ - ../../src/update/webupdater.cpp + ../../src/update/webupdater.cpp \ + ../../src/qmlengineholder.cpp HEADERS += \ helper.h \ @@ -68,6 +70,7 @@ HEADERS += \ ../../src/constants.h \ ../../src/controller.h \ ../../src/externalophandler.h \ + ../../src/filterproxymodel.h \ ../../src/hawkauth.h \ ../../src/hkdf.h \ ../../src/ipaddress.h \ @@ -84,7 +87,8 @@ HEADERS += \ ../../src/theme.h \ ../../src/update/updater.h \ ../../src/update/versionapi.h \ - ../../src/update/webupdater.h + ../../src/update/webupdater.h \ + ../../src/qmlengineholder.h OBJECTS_DIR = .obj MOC_DIR = .moc @@ -101,3 +105,7 @@ win* { RESOURCES += qt6winhack.qrc } } + +CONFIG += qmltypes +QML_IMPORT_NAME = Mozilla.VPN.qmlcomponents +QML_IMPORT_MAJOR_VERSION = 1.0 diff --git a/tests/qml/tst_filterableModel.qml b/tests/qml/tst_filterableModel.qml new file mode 100644 index 000000000..f5f0c5946 --- /dev/null +++ b/tests/qml/tst_filterableModel.qml @@ -0,0 +1,85 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import QtQuick 2.3 +import QtTest 1.0 + +import Mozilla.VPN 1.0 +import Mozilla.VPN.qmlcomponents 1.0 + +Item { + width: 600 + height: 800 + + ListModel { + id: fruitModel + + ListElement { + name: "Apple" + cost: 2.45 + } + ListElement { + name: "Orange" + cost: 3.25 + } + ListElement { + name: "Banana" + cost: 1.95 + } + } + + VPNFilterProxyModel { + id: testModel_filterAndSort + source: fruitModel + filterCallback: (fruit) => fruit.cost < 3 + sortCallback: (a, b) => a.cost < b.cost + } + + VPNFilterProxyModel { + id: testModel_filter + source: fruitModel + filterCallback: (fruit) => fruit.cost < 3 + } + + VPNFilterProxyModel { + id: testModel_sort + source: fruitModel + sortCallback: (a, b) => a.cost < b.cost + } + + TestCase { + name: "VPNFilterPRoxyModel" + when: windowShown + + function test_source() { + compare(fruitModel.rowCount(), 3, "FruitModel count"); + compare(fruitModel.get(0).name, "Apple"); + compare(fruitModel.get(1).name, "Orange"); + compare(fruitModel.get(2).name, "Banana"); + } + + function test_filterAndSort() { + compare(testModel_filterAndSort.rowCount(), 2, "TestModel count"); + compare(testModel_filterAndSort.source, fruitModel, "The filter.source"); + compare(testModel_filterAndSort.get(0).name, "Banana"); + compare(testModel_filterAndSort.get(1).name, "Apple"); + } + + function test_filter() { + compare(testModel_filter.rowCount(), 2, "TestModel count"); + compare(testModel_filter.source, fruitModel, "The filter.source"); + compare(testModel_filter.get(0).name, "Apple"); + compare(testModel_filter.get(1).name, "Banana"); + } + + function test_sort() { + compare(testModel_sort.rowCount(), 3, "TestModel count"); + compare(testModel_sort.source, fruitModel, "The filter.source"); + compare(testModel_sort.get(0).name, "Banana"); + compare(testModel_sort.get(1).name, "Apple"); + compare(testModel_sort.get(2).name, "Orange"); + } + } +} + diff --git a/tests/unit/testaddon.cpp b/tests/unit/testaddon.cpp index f1fd35d61..7d5b8dfe0 100644 --- a/tests/unit/testaddon.cpp +++ b/tests/unit/testaddon.cpp @@ -15,6 +15,8 @@ #include "../../src/tutorial/tutorial.h" #include "helper.h" +#include + void TestAddon::conditions_data() { QTest::addColumn("conditions"); QTest::addColumn("result"); @@ -520,7 +522,8 @@ void TestAddon::tutorial_create() { content["highlighted"].toBool() ? false : content["advanced"].toBool(); QCOMPARE(tutorial->property("advanced").toBool(), isAdvanced); - QmlEngineHolder qml; + QQmlApplicationEngine engine; + QmlEngineHolder qml(&engine); QSignalSpy signalSpy(tm, &Tutorial::playingChanged); diff --git a/tests/unit/testthemes.cpp b/tests/unit/testthemes.cpp index cbd985eab..97bc27acb 100644 --- a/tests/unit/testthemes.cpp +++ b/tests/unit/testthemes.cpp @@ -7,6 +7,8 @@ #include "../../src/settingsholder.h" #include "../../src/theme.h" +#include + void TestThemes::loadTheme_data() { QTest::addColumn("theme"); QTest::addColumn("expected"); @@ -30,7 +32,8 @@ void TestThemes::loadTheme() { settingsHolder.setTheme(theme); } - QmlEngineHolder qml; + QQmlApplicationEngine engine; + QmlEngineHolder qml(&engine); Theme t; t.initialize(qml.engine()); @@ -47,7 +50,9 @@ void TestThemes::loadTheme() { void TestThemes::model() { SettingsHolder settingsHolder; - QmlEngineHolder qml; + + QQmlApplicationEngine engine; + QmlEngineHolder qml(&engine); Theme t; t.initialize(qml.engine()); From 04936c9614d67898a2771c414d8fbd2ea7f2c039 Mon Sep 17 00:00:00 2001 From: Andrea Marchesini Date: Wed, 3 Aug 2022 08:27:11 +0200 Subject: [PATCH 3/4] Fix a crash for the device registration (#4126) --- src/mozillavpn.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mozillavpn.cpp b/src/mozillavpn.cpp index 98f1f9001..59b4a28cf 100644 --- a/src/mozillavpn.cpp +++ b/src/mozillavpn.cpp @@ -673,14 +673,14 @@ void MozillaVPN::completeActivation() { --deviceCount; } - if (deviceCount >= m_private->m_user.maxDevices()) { + if (deviceCount >= m_private->m_user.maxDevices() && + option == DeviceNotFound) { maybeStateMain(); return; } // Here we add the current device. - if (m_private->m_keys.privateKey().isEmpty() || - !m_private->m_deviceModel.hasCurrentDevice(keys())) { + if (option != DeviceStillValid) { addCurrentDeviceAndRefreshData(); } else { // Let's fetch the account and the servers. From e0ce5422f2eb75440cebadf4e40a5144f50b5d7a Mon Sep 17 00:00:00 2001 From: Sebastian Streich Date: Wed, 3 Aug 2022 15:50:16 +0200 Subject: [PATCH 4/4] Functional Tests: Use artifact passing instead of caching (#4133) * Use artifact passing instead of caching * make it executeable --- .github/workflows/functional_tests.yaml | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/.github/workflows/functional_tests.yaml b/.github/workflows/functional_tests.yaml index 637feb5e0..772bf714a 100644 --- a/.github/workflows/functional_tests.yaml +++ b/.github/workflows/functional_tests.yaml @@ -30,14 +30,6 @@ jobs: auth_header="$(git config --local --get http.https://github.com/.extraheader)" git submodule sync --recursive git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 - - - name: Cache build - id: cache-build - uses: actions/cache@v2 - with: - path: build/ - key: ${{ github.sha }} - - name: Install dependecies if: steps.cache-build.outputs.cache-hit != 'true' run: | @@ -64,6 +56,10 @@ jobs: cmake -S $(pwd) -B build/cmake -DBUILD_DUMMY=ON \ -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_EXE_LINKER_FLAGS=--coverage cmake --build build/cmake -j$(nproc) + - uses: actions/upload-artifact@v3 + with: + name: test-client-${{ github.sha }} + path: build/ - name: Generate tasklist id: testGen @@ -73,7 +69,6 @@ jobs: for test in $(find tests/functional -name 'test*.js' | sort); do printf '{"name": "%s", "path": "%s"}' $(basename ${test%.js} | sed -n 's/test//p') $test done | jq -s -c - - name: Check tests shell: bash env: @@ -94,13 +89,10 @@ jobs: steps: - name: Clone repository uses: actions/checkout@v2 - - - name: Cache build - id: cache-build - uses: actions/cache@v2 + - uses: actions/download-artifact@v3 with: + name: test-client-${{ github.sha }} path: build/ - key: ${{ github.sha }} - name: Install dependecies run: | @@ -111,7 +103,9 @@ jobs: - name: Check build shell: bash - run: ./build/cmake/src/mozillavpn -v + run: | + chmod +x ./build/cmake/src/mozillavpn + ./build/cmake/src/mozillavpn -v - name: Running ${{matrix.test.name}} Tests id: runTests