[plasma/union] /: autotests: Extract common testing code into its own header
Arjen Hiemstra <[email protected]> Wed, 5 Aug 2026 10:33:24 +0000 (UTC)
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit 8d7cb9dc2a1d2b99d490af4a4dd6179e8d6bab17 by Arjen Hiemstra. Committed on 05/08/2026 at 10:24. Pushed by ahiemstra into branch 'master'. autotests: Extract common testing code into its own header We need this in multiple cases, so let's keep a common implementation around. A +191 -0 autotests/TestInputPlugin.h [License: LGPL(3+eV) LGPL(v3.0) LGPL(v2.1)] M +2 -119 autotests/TestSelector.cpp M +4 -13 autotests/TestStyle.cpp M +5 -0 src/StyleRule.cpp M +2 -0 src/StyleRule.h https://invent.kde.org/plasma/union/-/commit/8d7cb9dc2a1d2b99d490af4a4dd6179e8d6bab17 diff --git a/autotests/TestInputPlugin.h b/autotests/TestInputPlugin.h new file mode 100644 index 00000000..4dd09bca --- /dev/null +++ b/autotests/TestInputPlugin.h @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +// SPDX-FileCopyrightText: 2026 Arjen Hiemstra <[email protected]> + +#pragma once + +#include <QFile> +#include <QJsonArray> + +#include <InputPlugin.h> +#include <Style.h> +#include <StyleLoader.h> +#include <StylePackage.h> + +using namespace Union; +using namespace Qt::StringLiterals; + +// Helper functions and classes for testing Style functionality. + +// Convenience wrapper around QFINDTESTDATA that returns a path rather than a string. +#define FINDTESTPATH(relative) std::filesystem::path(QFINDTESTDATA(relative).toStdString()) + +Element::State stateFromString(const QString &string) +{ + const auto metaEnum = QMetaEnum::fromType<Element::States>(); + + QByteArray value = string.toUtf8(); + + auto count = metaEnum.keyCount(); + for (int i = 0; i < count; ++i) { + if (qstrnicmp(metaEnum.key(i), value.data(), value.size()) == 0) { + return Element::State(metaEnum.value(i)); + } + } + + return Element::State::None; +} + +Union::SelectorList jsonToSelectorList(const QJsonArray &json) +{ + Union::SelectorList result; + for (const auto &entry : json) { + if (entry[u"universal"].toBool()) { + result.append(Selector::create<SelectorType::AnyElement>()); + continue; + } + + if (entry[u"child"].toBool()) { + result.append(Selector::create<SelectorType::ChildCombinator>()); + continue; + } + + if (entry[u"descendant"].toBool()) { + result.append(Selector::create<SelectorType::DescendantCombinator>()); + continue; + } + + if (auto type = entry[u"type"].toString(); !type.isEmpty()) { + result.append(Selector::create<SelectorType::Type>(type)); + } + + if (auto id = entry[u"id"].toString(); !id.isEmpty()) { + result.append(Selector::create<SelectorType::Id>(id)); + } + + if (auto states = entry[u"states"]; !states.isUndefined()) { + QJsonArray statesArray; + if (states.isArray()) { + statesArray = states.toArray(); + } else { + statesArray = QJsonArray{states}; + } + + for (const auto &state : statesArray) { + result.append(Selector::create<SelectorType::State>(stateFromString(state.toString()))); + } + } + + for (const auto &hint : entry[u"hints"].toArray()) { + result.append(Selector::create<SelectorType::Hint>(hint.toString())); + } + + for (const auto &exists : entry[u"attributes_exists"].toArray()) { + result.append(Selector::create<SelectorType::AttributeExists>(exists.toString())); + } + + for (const auto &[key, value] : entry[u"attributes_equals"].toObject().asKeyValueRange()) { + result.append(Selector::create<SelectorType::AttributeEquals>(std::make_pair(key.toString(), value.toVariant()))); + } + + for (const auto &[key, value] : entry[u"attributes_substring"].toObject().asKeyValueRange()) { + result.append(Selector::create<SelectorType::AttributeSubstringMatch>(std::make_pair(key.toString(), value.toString()))); + } + } + + return result; +} + +Union::Color variantToColor(const QVariant &variant) +{ + auto qcolor = variant.value<QColor>(); + return Union::Color::rgba(qcolor.red(), qcolor.green(), qcolor.blue(), qcolor.alpha()); +} + +static const QStringList KnownProperties{ + u"background-color"_s, +}; + +void setRuleProperty(const std::shared_ptr<Union::StyleRule> &rule, const QString &key, const QVariant &value) +{ + if (!KnownProperties.contains(key)) { + return; + } + + if (!rule->properties()) { + rule->setProperties(std::make_unique<Union::Properties::StylePropertyGroup>()); + } + auto properties = rule->properties(); + + if (key.startsWith(u"background")) { + if (!properties->background()) { + properties->setBackground(std::make_unique<Union::Properties::BackgroundPropertyGroup>()); + } + auto background = properties->background(); + + if (key == u"background-color") { + background->setColor(variantToColor(value)); + } + } +} + +struct TestStyleLoader : public Union::StyleLoader { + bool load(std::shared_ptr<Union::Style> style) override + { + auto stylePath = style->path() / "contents" / "test" / "style.json"; + + QFile jsonFile{stylePath}; + if (!jsonFile.open(QIODevice::ReadOnly)) { + qWarning() << "Failed reading test style data from" << stylePath.string() << jsonFile.errorString(); + return false; + } + + const auto json = QJsonDocument::fromJson(jsonFile.readAll())[u"rules"].toArray(); + if (json.isEmpty()) { + qWarning() << stylePath.string() << "does not contain valid JSON data"; + return false; + } + + for (const auto &jsonRule : json) { + auto rule = Union::StyleRule::create(); + + for (const auto &[key, value] : jsonRule.toObject().asKeyValueRange()) { + if (key == u"selectors") { + rule->setSelectors(jsonToSelectorList(value.toArray())); + } else { + setRuleProperty(rule, key.toString(), value.toVariant()); + } + } + + if (!rule->isEmpty()) { + style->insert(rule); + } + } + + return true; + } +}; + +struct TestInputPlugin : public Union::InputPlugin { + Union::StylePackage::Error validatePackage([[maybe_unused]] const Union::StylePackage &package) override + { + if (!std::filesystem::exists(package.path() / "contents" / "test")) { + return Union::StylePackage::Error::MissingFiles; + } + + if (!std::filesystem::exists(package.path() / "contents" / "test" / "style.json")) { + return Union::StylePackage::Error::MissingFiles; + } + + return Union::StylePackage::Error::None; + } + + Union::PackageHandler::Error createPackage([[maybe_unused]] const Union::StylePackage &package) override + { + return Union::PackageHandler::Error::None; + } + + std::shared_ptr<Union::Style> createStyle(const Union::StylePackage &package) const override + { + return Union::Style::create(package.path(), std::make_unique<TestStyleLoader>()); + } +}; diff --git a/autotests/TestSelector.cpp b/autotests/TestSelector.cpp index 4b3d1592..df79cf7a 100644 --- a/autotests/TestSelector.cpp +++ b/autotests/TestSelector.cpp @@ -8,25 +8,11 @@ #include <Element.h> #include <Selector.h> +#include "TestInputPlugin.h" + using namespace Union; using namespace Qt::StringLiterals; -Element::State stateFromString(const QString &string) -{ - const auto metaEnum = QMetaEnum::fromType<Element::States>(); - - QByteArray value = string.toUtf8(); - - auto count = metaEnum.keyCount(); - for (int i = 0; i < count; ++i) { - if (qstrnicmp(metaEnum.key(i), value.data(), value.size()) == 0) { - return Element::State(metaEnum.value(i)); - } - } - - return Element::State::None; -} - struct ElementProperties { QString type; QString id; @@ -97,109 +83,6 @@ ElementList structureFromJson(const QJsonArray &json) return result; } -struct SelectorProperties { - ElementProperties element; - bool universal = false; - bool child = false; - bool descendant = false; - QVariantList attributes_exists; - QVariantMap attributes_equals; - QVariantMap attributes_substring; -}; - -SelectorProperties jsonToSelectorProperties(const QJsonObject &json) -{ - SelectorProperties result; - - result.element = jsonToElementProperties(json); - - if (json.contains(u"universal")) { - result.universal = json.value(u"universal").toBool(); - } - - if (json.contains(u"child")) { - result.child = json.value(u"child").toBool(); - } - - if (json.contains(u"descendant")) { - result.descendant = json.value(u"descendant").toBool(); - } - - if (json.contains(u"attributes_exists")) { - result.attributes_exists = json.value(u"attributes_exists").toArray().toVariantList(); - } - - if (json.contains(u"attributes_equals")) { - result.attributes_equals = json.value(u"attributes_equals").toObject().toVariantMap(); - } - - if (json.contains(u"attribute_substring")) { - result.attributes_substring = json.value(u"attributes_substring").toObject().toVariantMap(); - } - - return result; -} - -SelectorList jsonToSelectorList(const QJsonArray &json) -{ - SelectorList result; - for (auto entry : json) { - auto properties = jsonToSelectorProperties(entry.toObject()); - - if (properties.universal) { - result.append(Selector::create<SelectorType::AnyElement>()); - continue; - } - - if (properties.child) { - result.append(Selector::create<SelectorType::ChildCombinator>()); - continue; - } - - if (properties.descendant) { - result.append(Selector::create<SelectorType::DescendantCombinator>()); - continue; - } - - if (!properties.element.type.isEmpty()) { - result.append(Selector::create<SelectorType::Type>(properties.element.type)); - } - - if (!properties.element.id.isEmpty()) { - result.append(Selector::create<SelectorType::Id>(properties.element.id)); - } - - if (properties.element.states != 0) { - QMetaEnum statesEnum = QMetaEnum::fromType<Element::States>(); - const auto count = statesEnum.keyCount(); - for (int i = 0; i < count; ++i) { - auto value = statesEnum.value(i); - if (properties.element.states & value) { - result.append(Selector::create<SelectorType::State>(Element::State(value))); - } - } - } - - for (const auto &hint : properties.element.hints) { - result.append(Selector::create<SelectorType::Hint>(hint)); - } - - for (const auto &exists : properties.attributes_exists) { - result.append(Selector::create<SelectorType::AttributeExists>(exists.toString())); - } - - for (auto [key, value] : properties.attributes_equals.asKeyValueRange()) { - result.append(Selector::create<SelectorType::AttributeEquals>(std::make_pair(key, value))); - } - - for (auto [key, value] : properties.attributes_substring.asKeyValueRange()) { - result.append(Selector::create<SelectorType::AttributeSubstringMatch>(std::make_pair(key, value.toString()))); - } - } - - return result; -} - struct TestData { QString name; SelectorList selectors; diff --git a/autotests/TestStyle.cpp b/autotests/TestStyle.cpp index 1c8a9de8..568b2b5e 100644 --- a/autotests/TestStyle.cpp +++ b/autotests/TestStyle.cpp @@ -6,27 +6,18 @@ #include <Style.h> #include <StyleLoader.h> +#include "TestInputPlugin.h" + using namespace Union; using namespace Qt::StringLiterals; -struct TestLoader : public StyleLoader { - bool load(std::shared_ptr<Style> style) override - { - auto testRule = StyleRule::create(); - testRule->setSelectors({Selector::create<SelectorType::Id>(u"test"_s)}); - style->insert(testRule); - - return true; - } -}; - class TestStyle : public QObject { Q_OBJECT private Q_SLOTS: void testLoad() { - auto style = Style::create(std::filesystem::path(), std::make_unique<TestLoader>()); + auto style = Style::create(std::filesystem::path(), std::make_unique<TestStyleLoader>()); QVERIFY(style->load()); @@ -37,7 +28,7 @@ private Q_SLOTS: void testMatches() { - auto style = Style::create(std::filesystem::path(), std::make_unique<TestLoader>()); + auto style = Style::create(std::filesystem::path(), std::make_unique<TestStyleLoader>()); QVERIFY(style->load()); QList<Element::Ptr> elements; diff --git a/src/StyleRule.cpp b/src/StyleRule.cpp index 89eda4ce..e948e6df 100644 --- a/src/StyleRule.cpp +++ b/src/StyleRule.cpp @@ -22,6 +22,11 @@ StyleRule::StyleRule(std::unique_ptr<StyleRulePrivate> &&d) StyleRule::~StyleRule() = default; +bool StyleRule::isEmpty() const +{ + return d->selectors.isEmpty() && d->properties->isEmpty(); +} + SelectorList StyleRule::selectors() const { return d->selectors; diff --git a/src/StyleRule.h b/src/StyleRule.h index d4a4e63d..fb056b17 100644 --- a/src/StyleRule.h +++ b/src/StyleRule.h @@ -39,6 +39,8 @@ public: StyleRule(std::unique_ptr<StyleRulePrivate> &&d); ~StyleRule() override; + bool isEmpty() const; + SelectorList selectors() const; void setSelectors(const SelectorList &selectors);