From e2216d9a9bc8ab8224f58d009d84c8a273ada7ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Fri, 9 Aug 2024 13:56:12 +0200 Subject: [PATCH] Improve tests for extension metadata. --- include/uibase/extensions/extension.h | 5 ++ src/extension.cpp | 32 ++++++----- tests/CMakeLists.txt | 2 + .../extensions/mo2-example-extension/icon.png | Bin 0 -> 26498 bytes .../mo2-example-extension/metadata.json | 22 ++++++++ tests/test_extensions.cpp | 52 +++++------------- tests/test_main.cpp | 11 ++-- tests/test_strings.cpp | 7 +++ tests/test_utils.cpp | 31 +++++++++++ tests/test_utils.h | 27 +++++++++ tests/translations/extract_translations.py | 46 ++++++++++++++++ tests/translations/tests_en.qm | Bin 141 -> 228 bytes tests/translations/tests_en.ts | 15 ++++- tests/translations/tests_fr.qm | Bin 147 -> 416 bytes tests/translations/tests_fr.ts | 15 ++++- 15 files changed, 207 insertions(+), 58 deletions(-) create mode 100644 tests/data/extensions/mo2-example-extension/icon.png create mode 100644 tests/data/extensions/mo2-example-extension/metadata.json create mode 100644 tests/test_utils.cpp create mode 100644 tests/test_utils.h create mode 100644 tests/translations/extract_translations.py diff --git a/include/uibase/extensions/extension.h b/include/uibase/extensions/extension.h index c26f2bad..60b145e6 100644 --- a/include/uibase/extensions/extension.h +++ b/include/uibase/extensions/extension.h @@ -159,6 +159,11 @@ class QDLLEXPORT IExtension class QDLLEXPORT ExtensionFactory { public: + // load metadata from the given file, throws InvalidExtensionMetaDataException if the + // file does not exist or is invalid + // + static ExtensionMetaData loadMetaData(std::filesystem::path const& path); + // load an extension from the given directory, return a null-pointer if the extension // could not be load // diff --git a/src/extension.cpp b/src/extension.cpp index 761db63a..bc50efdc 100644 --- a/src/extension.cpp +++ b/src/extension.cpp @@ -101,7 +101,7 @@ ExtensionMetaData::ExtensionMetaData(std::filesystem::path const& path, // TODO: name of the key // translation context - m_TranslationContext = jsonData["translationContext"].toString(""); + m_TranslationContext = jsonData["translation-context"].toString(""); if (jsonData.contains("icon")) { const QFileInfo icon{QDir(path), jsonData["icon"].toString()}; @@ -178,23 +178,21 @@ IExtension::IExtension(std::filesystem::path const& path, ExtensionMetaData&& me : m_Path{path}, m_MetaData{std::move(metadata)} {} -std::unique_ptr -ExtensionFactory::loadExtension(std::filesystem::path const& directory) +ExtensionMetaData ExtensionFactory::loadMetaData(std::filesystem::path const& path) { - const auto metadataPath = directory / METADATA_FILENAME; - - if (!exists(metadataPath)) { - log::warn("missing extension metadata in '{}'", directory.native()); - return nullptr; + if (!exists(path)) { + throw InvalidExtensionMetaDataException( + std::format("metadata file '{}' not found", path)); } // load the meta data QJsonParseError jsonError; QJsonDocument jsonMetaData; { - QFile file(metadataPath); + QFile file(path); if (!file.open(QFile::ReadOnly)) { - return {}; + throw InvalidExtensionMetaDataException( + std::format("failed to open metadata file '{}'", path)); } const auto jsonContent = file.readAll(); @@ -202,14 +200,18 @@ ExtensionFactory::loadExtension(std::filesystem::path const& directory) } if (jsonMetaData.isNull()) { - log::warn("failed to read metadata from '{}': {}", metadataPath.native(), - jsonError.errorString()); - return nullptr; + throw InvalidExtensionMetaDataException( + std::format("invalid metadata file '{}': {}", path, jsonError.errorString())); } + return ExtensionMetaData(path.parent_path(), jsonMetaData.object()); +} + +std::unique_ptr +ExtensionFactory::loadExtension(std::filesystem::path const& directory) +{ try { - return loadExtension(directory, - ExtensionMetaData(directory, jsonMetaData.object())); + return loadExtension(directory, loadMetaData(directory / METADATA_FILENAME)); } catch (InvalidExtensionMetaDataException const& ex) { log::warn("failed to load extension from '{}': invalid metadata ({})", directory.native(), ex.what()); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b9845270..08390a87 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,6 +3,8 @@ cmake_minimum_required(VERSION 3.16) add_executable(uibase-tests EXCLUDE_FROM_ALL) target_sources(uibase-tests PRIVATE + test_utils.h + test_utils.cpp test_main.cpp test_formatters.cpp test_ifiletree.cpp diff --git a/tests/data/extensions/mo2-example-extension/icon.png b/tests/data/extensions/mo2-example-extension/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2b18c97bb2e77d2965cc8d3707a2a5b5d2bc8b01 GIT binary patch literal 26498 zcmeI4X-HI27>2*eG#9kUvdzTEtf0~%ZJn0MDYdjkB#pwE(L`*)0i!Z2r+&Dh77=Be z+Mf)x30ufEn-)wljPOTk8Hu7)8L*0WtY8JVCCR}+;TbFu2} zrE@1k$d+Uxj~VipA~eFMizs12s74*RKc=IxY`43f(wmm9yD{#F!;CM+C(a=OnI{6H zNB9<9EQ(Y3K1|%0eWS?wt|i3fk#F{=LtgD)8>_v{ecu`ir1$>EjsvanJ<3z^KP?|i z++KK+`F`0J4|XoSUVE}fv|F-dwb)VRUZ>#g<1;VW@wWGduu;6dvLN4$w~Z?0RNkhP z*d?O<85gi=0f|5nLji#%(gRWjmjc-ohQnBZW%8I9x@Dd_-S{+`mpAVhX{0)DpXFnH z{kg=P;=A9Km9MXNcGp;9L=j_kk?NMr&+E_Oy^MK50i(!})Ekd7ihT&kjRGJP<039m zMhPiUMsX>SDv&A|3n-&_Oboq@!srjz4is@EKoQ2T7z-%8cuZiZ8?yn71x%QTn1E$+ zDPWnfOvVB%lg9)s6PC$XfMtr9fMs$iV41_(GWmKXOYOAfHFi{*mAsayJhgVVB6_?$ z_vSe>Ba$eswPP&NfM455D$Rb7Vo%2AW~00yOgh}ce$K=FJ{as-ST=|CGkCLAO1kj9 zT2=2MdDPUbwT*l2^e&P=1)U!jGBL5}cO`AVz|4b9h@ncfqW*T=lyv<>I@&c5v^3Yj zd+h5>>ps`0F;y8vutu&b!5X;~u*P9+jaW!)3k1Xg;((!mQ5EhD7*)Zj3S$AKqlgKV zj$8^@CM=V&0L$bt0n3DCG8SN&A|_y&TnbnwER(SS%j7Wu%YS#5aIg79~Ac*tO95r$1+xK%^tm z83~AVTSO41BC*{IV*wB0ikLugETq6v7%l}?3*jML#=_rzn~3GzyKHk17H9QfB`T72EYIq00UqE41fVJ00zJS m7ytwREdzr${!hJJu6Jv9*_7Y7W84+_zCu^8*O*r6j{X3~U0Qem literal 0 HcmV?d00001 diff --git a/tests/data/extensions/mo2-example-extension/metadata.json b/tests/data/extensions/mo2-example-extension/metadata.json new file mode 100644 index 00000000..cccb7948 --- /dev/null +++ b/tests/data/extensions/mo2-example-extension/metadata.json @@ -0,0 +1,22 @@ +{ + "id": "mo2-example-extension", + "name": "Example Extension for UI Base Tests", + "version": "1.0.0", + "description": "ModOrganizer2 example extension for UI Base Tests.", + "author": { + "name": "Mod Organizer 2", + "homepage": "https://www.modorganizer.org/" + }, + "icon": "icon.png", + "contributors": ["AL", "AnyOldName3", "Holt59", "Silarn"], + "type": "plugin", + "translation-context": "mo2-example-extension", + "content": { + "plugins": { + "autodetect": true + }, + "translations": { + "autodetect": "translations" + } + } +} diff --git a/tests/test_extensions.cpp b/tests/test_extensions.cpp index 4e767282..322fbf34 100644 --- a/tests/test_extensions.cpp +++ b/tests/test_extensions.cpp @@ -9,46 +9,24 @@ #include -using namespace MOBase; +#include "test_utils.h" -class TestMetaData : public ExtensionMetaData -{ -public: - TestMetaData(std::filesystem::path const& path, QByteArray const& metadata) - : ExtensionMetaData(path, QJsonDocument::fromJson(metadata).object()) - {} -}; +using namespace MOBase; TEST(ExtensionsTest, MetaData) { - const auto metadata = TestMetaData({}, R"({ - "id": "mo2-game-bethesda", - "name": "Elder Scrolls & Fallout Games", - "version": "1.0.0", - "description": "ModOrganizer2 support for The Elder Scrolls & Fallout games.", - "author": { - "name": "Mod Organizer 2", - "homepage": "https://www.modorganizer.org/" - }, - "icon": "./tests/icon.png", - "contributors": [ - "AL", - "AnyOldName3", - "Holt59", - "Silarn" - ], - "type": "game", - "content": { - "plugins": { - "autodetect": true - }, - "translations": { - "autodetect": "translations" - } - } -})"); - - EXPECT_EQ("mo2-game-bethesda", metadata.identifier()); - EXPECT_EQ("Elder Scrolls & Fallout Games", metadata.name()); + mo2::tests::TranslationHelper tr; + + const auto metadata = ExtensionFactory::loadMetaData( + "./tests/data/extensions/mo2-example-extension/metadata.json"); + + tr.switchLanguage("en"); + EXPECT_EQ("mo2-example-extension", metadata.identifier()); + EXPECT_EQ("Example Extension for UI Base Tests", metadata.name()); + + tr.switchLanguage("fr"); + EXPECT_EQ("mo2-example-extension", metadata.identifier()); + EXPECT_EQ("Extension Démo pour les tests UI Base", metadata.name()); + EXPECT_FALSE(metadata.icon().isNull()); } diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 0399b433..ff015903 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -3,13 +3,16 @@ #include #include +#include + int main(int argc, char** argv) { QCoreApplication app(argc, argv); - QTranslator translator; - if (translator.load("tests_fr", "tests/translations")) { - app.installTranslator(&translator); - } + + MOBase::log::createDefault({.name = "./mo2-tests.logs", + .maxLevel = MOBase::log::Levels::Info, + .pattern = ""}); + testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } diff --git a/tests/test_strings.cpp b/tests/test_strings.cpp index 56e75c9b..841ebb0c 100644 --- a/tests/test_strings.cpp +++ b/tests/test_strings.cpp @@ -9,6 +9,8 @@ #include +#include "test_utils.h" + using namespace MOBase; TEST(StringsTest, IEquals) @@ -37,6 +39,11 @@ TEST(StringsTest, IReplaceAll) // this is more a tests of the tests TEST(StringsTest, Translation) { + mo2::tests::TranslationHelper tr; + ASSERT_EQ("Translate to French", + QCoreApplication::translate("uibase-tests", "Translate to French")); + + tr.switchLanguage("fr"); ASSERT_EQ("Traduction en Français", QCoreApplication::translate("uibase-tests", "Translate to French")); } diff --git a/tests/test_utils.cpp b/tests/test_utils.cpp new file mode 100644 index 00000000..19a897ae --- /dev/null +++ b/tests/test_utils.cpp @@ -0,0 +1,31 @@ +#include "test_utils.h" + +#include + +namespace mo2::tests +{ +TranslationHelper::TranslationHelper() {} + +TranslationHelper::~TranslationHelper() +{ + release(); +} + +void TranslationHelper::release() +{ + if (m_Translator) { + QCoreApplication::removeTranslator(m_Translator.get()); + m_Translator.reset(); + } +} + +void TranslationHelper::switchLanguage(const QString& lang) +{ + m_Translator = std::make_unique(); + if (m_Translator->load("tests_" + lang, "tests/translations")) { + QCoreApplication::installTranslator(m_Translator.get()); + } else { + m_Translator.reset(); + } +} +} // namespace mo2::tests diff --git a/tests/test_utils.h b/tests/test_utils.h new file mode 100644 index 00000000..9362be50 --- /dev/null +++ b/tests/test_utils.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include +#include + +namespace mo2::tests +{ + +class TranslationHelper +{ +public: + // create a new translation helper that can be used to switch language during tests + TranslationHelper(); + ~TranslationHelper(); + + // switch to the given language (should be available) + void switchLanguage(const QString& lang); + +private: + std::unique_ptr m_Translator; + + void release(); +}; + +} // namespace mo2::tests diff --git a/tests/translations/extract_translations.py b/tests/translations/extract_translations.py new file mode 100644 index 00000000..745749a4 --- /dev/null +++ b/tests/translations/extract_translations.py @@ -0,0 +1,46 @@ +import json +from pathlib import Path + +from PyQt6.lupdate.source_file import SourceFile +from PyQt6.lupdate.translation_file import TranslationFile +from PyQt6.lupdate.translations import Context, Message + +folder = Path(__file__).parent + +tr_files = [ + TranslationFile(path, no_obsolete=False, no_summary=False, verbose=True) + for path in folder.glob("*.ts") +] + +for metadata_path in folder.parent.joinpath("data", "extensions").glob("**/*.json"): + with open(metadata_path, "rb") as fp: + metadata = json.load(fp) + + if "translation-context" not in metadata: + continue + + source = SourceFile(filename=metadata_path) + + context = Context(name=metadata["translation-context"]) + + for key in ("name", "description"): + if key not in metadata: + continue + + context.messages.append( + Message( + filename=metadata_path, + line_nr=-1, + source=metadata[key], + comment=None, + numerus=None, + ) + ) + + source.contexts.append(context) + + for tr_file in tr_files: + tr_file.update(source) + +for tr_file in tr_files: + tr_file.write() diff --git a/tests/translations/tests_en.qm b/tests/translations/tests_en.qm index d5ca50195d8899565e374e2405b25297d85c8ced..837385dd47bd54d5df1a9cb58a454032e56e1b1d 100644 GIT binary patch literal 228 zcmcE7ks@*G{hX<16=n7(EZlo{IRgU&YieG6Xs{EICBU33)eNK=*h(Mg0V&^1AhDA9 zKM-($g@J^!YeiyiK~AcIYeh+FUU6oAoUB~szOL=aY->dP@QOQzL9P! rL@khps%1nq%*Z!C#lI*$F)yTsvJn;G+Gaj*&RS@nHU)X=m1DB delta 118 zcmaFD*vmLUE}w(_`_dl_K#&QjUj}gh#`?7kD-_$2S}GNq%tS~$$TK`29!w! z$|N&naDX(i0kLpMQDR + + mo2-example-extension + + + Example Extension for UI Base Tests + + + + + ModOrganizer2 example extension for UI Base Tests. + + + uibase-tests Translate to French - Translate to French + Translate to French diff --git a/tests/translations/tests_fr.qm b/tests/translations/tests_fr.qm index 80958d6e11a850ede8d6266c2a39254a0abce8ba..fc1ef2fa025b1ef4296ff4fe04aeaceb897b7b63 100644 GIT binary patch literal 416 zcmb7g41rcoK_?D0DgJTsW8iJI7Di&g?c4s|%hkj4`y+sjiMu>KkC}R5v3pI& z=6q>rcR3Jf$aIDTH7Y0!JaSmN-~=Bim<1(u4#zZO$J3jxr#5+v0X0U-mu|HOCQye(SH8>Mc44C<{qE9N-Jrp8EtV3ZY!3@E9P;pQ)FA;g ujQC?p48sWv_8cODtpShz4}LVX*~m>(lP}aovI3<4Ii~;5nj5a^guxe1>tk#H delta 124 zcmZ3$JehHVTqy_p_oY7=fFKiy6PSU74nqh-5kn$F3PUMFGD8VaBA+3TL4hF^OuB*P z@)({2Rb(<0bAYt60kLpMQDR + + mo2-example-extension + + + Example Extension for UI Base Tests + Extension Démo pour les tests UI Base + + + + ModOrganizer2 example extension for UI Base Tests. + Exemple d'extension ModOrganizer2 pour les tests UI Base. + + uibase-tests Translate to French - Traduction en Français + Traduction en Français