File indexing completed on 2024-11-17 03:42:09

0001 /*
0002   This file is part of the KTextTemplate library
0003 
0004   SPDX-FileCopyrightText: 2010 Michael Jansen <kde@michael-jansen.biz>
0005   SPDX-FileCopyrightText: 2010 Stephen Kelly <steveire@gmail.com>
0006 
0007   SPDX-License-Identifier: LGPL-2.1-or-later
0008 
0009 */
0010 
0011 #include "engine.h"
0012 #include "ktexttemplate_paths.h"
0013 #include "metatype.h"
0014 #include "template.h"
0015 #include "test_macros.h"
0016 
0017 #include <QMetaType>
0018 #include <QQueue>
0019 #include <QStack>
0020 #include <QTest>
0021 #include <QVariant>
0022 #include <QVariantHash>
0023 
0024 #include <deque>
0025 #include <string>
0026 
0027 #include <memory>
0028 
0029 Q_DECLARE_SEQUENTIAL_CONTAINER_METATYPE(ThreeArray)
0030 
0031 Q_DECLARE_ASSOCIATIVE_CONTAINER_METATYPE(QtUnorderedMap)
0032 
0033 Q_DECLARE_SMART_POINTER_METATYPE(std::shared_ptr)
0034 
0035 Q_DECLARE_SEQUENTIAL_CONTAINER_METATYPE(std::deque)
0036 
0037 class TestGenericTypes : public QObject
0038 {
0039     Q_OBJECT
0040 
0041 private Q_SLOTS:
0042 
0043     void initTestCase();
0044 
0045     void testGenericClassType();
0046     void testSequentialContainer_Variant();
0047     void testAssociativeContainer_Variant();
0048     void testSequentialContainer_Type();
0049     void testAssociativeContainer_Type();
0050     void testSharedPointer();
0051     void testThirdPartySharedPointer();
0052     void testNestedContainers();
0053 
0054     void testCustomQObjectDerived();
0055 
0056     void propertyMacroTypes();
0057 
0058     void testUnregistered();
0059     void testPointerNonQObject();
0060     void testQGadget();
0061     void testGadgetMetaType();
0062 
0063 }; // class TestGenericTypes
0064 
0065 class Person
0066 {
0067 public:
0068     Person()
0069         : age(0)
0070     {
0071     }
0072     Person(std::string _name, int _age)
0073         : name(_name)
0074         , age(_age)
0075     {
0076         static auto _uid = 0;
0077         uid = ++_uid;
0078     }
0079 
0080     bool operator==(const Person &other) const
0081     {
0082         return uid == other.uid;
0083     }
0084 
0085     std::string name;
0086     int age;
0087     int uid;
0088 };
0089 
0090 class PersonGadget
0091 {
0092     Q_GADGET
0093     Q_PROPERTY(QString name MEMBER m_name)
0094 public:
0095     QString m_name;
0096     int m_age = 42;
0097 };
0098 
0099 int qHash(const Person &p)
0100 {
0101     return p.uid;
0102 }
0103 
0104 Q_DECLARE_METATYPE(Person)
0105 Q_DECLARE_METATYPE(PersonGadget)
0106 
0107 KTEXTTEMPLATE_BEGIN_LOOKUP(Person)
0108 if (property == QStringLiteral("name"))
0109     return QString::fromStdString(object.name);
0110 if (property == QStringLiteral("age"))
0111     return object.age;
0112 KTEXTTEMPLATE_END_LOOKUP
0113 
0114 KTEXTTEMPLATE_BEGIN_LOOKUP(PersonGadget)
0115 if (property == QStringLiteral("age"))
0116     return object.m_age;
0117 KTEXTTEMPLATE_END_LOOKUP
0118 
0119 class PersonObject : public QObject
0120 {
0121     Q_OBJECT
0122     Q_PROPERTY(QString name READ name)
0123     Q_PROPERTY(int age READ age)
0124 public:
0125     PersonObject(const QString &name, int age, QObject *parent = {})
0126         : QObject(parent)
0127         , m_name(name)
0128         , m_age(age)
0129     {
0130     }
0131 
0132     QString name() const
0133     {
0134         return m_name;
0135     }
0136     int age() const
0137     {
0138         return m_age;
0139     }
0140 
0141 private:
0142     const QString m_name;
0143     const int m_age; // Yeah, you wish...
0144 };
0145 
0146 void TestGenericTypes::initTestCase()
0147 {
0148     // Register the handler for our custom type
0149     KTextTemplate::registerMetaType<Person>();
0150     KTextTemplate::registerMetaType<PersonGadget>();
0151 }
0152 
0153 void TestGenericTypes::testGenericClassType()
0154 {
0155     KTextTemplate::Engine engine;
0156 
0157     engine.setPluginPaths({QStringLiteral(KTEXTTEMPLATE_PLUGIN_PATH)});
0158 
0159     auto t1 = engine.newTemplate(QStringLiteral("Person: \nName: {{p.name}}\nAge: {{p.age}}\nUnknown: {{p.unknown}}"), QStringLiteral("template1"));
0160 
0161     // Check it
0162     QVariantHash h;
0163     Person p("Grant Lee", 2);
0164     h.insert(QStringLiteral("p"), QVariant::fromValue(p));
0165     KTextTemplate::Context c(h);
0166     QCOMPARE(t1->render(&c), QStringLiteral("Person: \nName: Grant Lee\nAge: 2\nUnknown: "));
0167 }
0168 
0169 static QMap<int, Person> getPeople()
0170 {
0171     QMap<int, Person> people;
0172     people.insert(23, Person("Claire", 23));
0173     people.insert(32, Person("Grant", 32));
0174     people.insert(50, Person("Alan", 50));
0175     return people;
0176 }
0177 
0178 template<typename SequentialContainer>
0179 void insertPeopleVariants(KTextTemplate::Context &c)
0180 {
0181     auto people = getPeople();
0182     auto it = people.constBegin();
0183     const auto end = people.constEnd();
0184     SequentialContainer container;
0185     for (; it != end; ++it)
0186         container.push_back(QVariant::fromValue(it.value()));
0187     c.insert(QStringLiteral("people"), QVariant::fromValue(container));
0188 }
0189 
0190 template<typename AssociativeContainer>
0191 void insertAssociatedPeopleVariants(KTextTemplate::Context &c)
0192 {
0193     auto people = getPeople();
0194     auto it = people.constBegin();
0195     const auto end = people.constEnd();
0196     AssociativeContainer container;
0197     for (; it != end; ++it)
0198         container.insert(QString::number(it.key()), QVariant::fromValue(it.value()));
0199     c.insert(QStringLiteral("people"), QVariant::fromValue(container));
0200 }
0201 
0202 template<>
0203 void insertPeopleVariants<QMap<QString, QVariant>>(KTextTemplate::Context &c)
0204 {
0205     insertAssociatedPeopleVariants<QMap<QString, QVariant>>(c);
0206 }
0207 
0208 template<>
0209 void insertPeopleVariants<QHash<QString, QVariant>>(KTextTemplate::Context &c)
0210 {
0211     insertAssociatedPeopleVariants<QHash<QString, QVariant>>(c);
0212 }
0213 
0214 template<typename Container>
0215 void testSequentialIteration(KTextTemplate::Context &c)
0216 {
0217     KTextTemplate::Engine engine;
0218 
0219     engine.setPluginPaths({QStringLiteral(KTEXTTEMPLATE_PLUGIN_PATH)});
0220 
0221     {
0222         KTextTemplate::Template t1 =
0223             engine.newTemplate(QStringLiteral("{% for person in people %}{{ person.name }},{% endfor %}"), QStringLiteral("people_template"));
0224         QCOMPARE(t1->render(&c), QStringLiteral("Claire,Grant,Alan,"));
0225     }
0226 }
0227 
0228 template<typename Container>
0229 void testSequentialIndexing(KTextTemplate::Context &c)
0230 {
0231     KTextTemplate::Engine engine;
0232 
0233     engine.setPluginPaths({QStringLiteral(KTEXTTEMPLATE_PLUGIN_PATH)});
0234 
0235     {
0236         KTextTemplate::Template t1 =
0237             engine.newTemplate(QStringLiteral("{{ people.0.name }},{{ people.1.name }},{{ people.2.name }},"), QStringLiteral("people_template"));
0238         QCOMPARE(t1->render(&c), QStringLiteral("Claire,Grant,Alan,"));
0239     }
0240 }
0241 
0242 template<typename Container>
0243 struct SequentialContainerTester {
0244     static void iteration(KTextTemplate::Context &c)
0245     {
0246         testSequentialIteration<Container>(c);
0247     }
0248 
0249     static void indexing(KTextTemplate::Context &c)
0250     {
0251         testSequentialIndexing<Container>(c);
0252     }
0253 };
0254 
0255 template<typename T>
0256 struct SequentialContainerTester<QSet<T>> {
0257     static void iteration(KTextTemplate::Context &c)
0258     {
0259         KTextTemplate::Engine engine;
0260 
0261         engine.setPluginPaths({QStringLiteral(KTEXTTEMPLATE_PLUGIN_PATH)});
0262 
0263         KTextTemplate::Template t1 =
0264             engine.newTemplate(QStringLiteral("{% for person in people %}{{ person.name }},{% endfor %}"), QStringLiteral("people_template"));
0265         auto result = t1->render(&c);
0266         QStringList output{QStringLiteral("Claire,"), QStringLiteral("Grant,"), QStringLiteral("Alan,")};
0267         for (const QString &s : output) {
0268             QVERIFY(result.contains(s));
0269         }
0270 
0271         QCOMPARE(result.length(), output.join(QString()).length());
0272     }
0273 
0274     static void indexing(KTextTemplate::Context)
0275     {
0276     }
0277 };
0278 
0279 template<typename T>
0280 struct SequentialContainerTester<std::list<T>> {
0281     static void iteration(KTextTemplate::Context &c)
0282     {
0283         testSequentialIteration<std::list<T>>(c);
0284     }
0285 
0286     static void indexing(KTextTemplate::Context)
0287     {
0288     }
0289 };
0290 
0291 template<typename Container>
0292 void doTestSequentialContainer_Variant()
0293 {
0294     KTextTemplate::Context c;
0295 
0296     insertPeopleVariants<Container>(c);
0297 
0298     SequentialContainerTester<Container>::iteration(c);
0299     SequentialContainerTester<Container>::indexing(c);
0300 }
0301 
0302 template<typename Container>
0303 void testAssociativeValues(KTextTemplate::Context &c, bool unordered = {})
0304 {
0305     KTextTemplate::Engine engine;
0306 
0307     engine.setPluginPaths({QStringLiteral(KTEXTTEMPLATE_PLUGIN_PATH)});
0308 
0309     {
0310         KTextTemplate::Template t1 = engine.newTemplate(QStringLiteral("{% for person in people.values %}({{ person.name }}:{{ "
0311                                                                        "person.age }}),{% endfor %}"),
0312                                                         QStringLiteral("people_template"));
0313 
0314         auto result = t1->render(&c);
0315         if (!unordered)
0316             QCOMPARE(result, QStringLiteral("(Claire:23),(Grant:32),(Alan:50),"));
0317         else {
0318             QCOMPARE(result.size(), 33);
0319             QVERIFY(result.contains(QStringLiteral("(Claire:23),")));
0320             QVERIFY(result.contains(QStringLiteral("(Grant:32),")));
0321             QVERIFY(result.contains(QStringLiteral("(Alan:50),")));
0322         }
0323     }
0324 }
0325 
0326 template<typename Container>
0327 void testAssociativeItems(KTextTemplate::Context &c, bool unordered)
0328 {
0329     KTextTemplate::Engine engine;
0330 
0331     engine.setPluginPaths({QStringLiteral(KTEXTTEMPLATE_PLUGIN_PATH)});
0332 
0333     {
0334         KTextTemplate::Template t1 = engine.newTemplate(QStringLiteral("{% for item in people.items %}({{ item.1.name }}:{{ "
0335                                                                        "item.1.age }}),{% endfor %}"),
0336                                                         QStringLiteral("people_template"));
0337         auto result = t1->render(&c);
0338         if (!unordered)
0339             QCOMPARE(result, QStringLiteral("(Claire:23),(Grant:32),(Alan:50),"));
0340         else {
0341             QCOMPARE(result.size(), 33);
0342             QVERIFY(result.contains(QStringLiteral("(Claire:23),")));
0343             QVERIFY(result.contains(QStringLiteral("(Grant:32),")));
0344             QVERIFY(result.contains(QStringLiteral("(Alan:50),")));
0345         }
0346     }
0347 }
0348 
0349 template<typename Container>
0350 void doTestAssociativeContainer_Variant(bool unordered = {})
0351 {
0352     KTextTemplate::Engine engine;
0353 
0354     engine.setPluginPaths({QStringLiteral(KTEXTTEMPLATE_PLUGIN_PATH)});
0355 
0356     KTextTemplate::Context c;
0357 
0358     insertPeopleVariants<Container>(c);
0359     testAssociativeValues<Container>(c, unordered);
0360     testAssociativeItems<Container>(c, unordered);
0361 }
0362 
0363 void TestGenericTypes::testSequentialContainer_Variant()
0364 {
0365     doTestSequentialContainer_Variant<QVariantList>();
0366     doTestSequentialContainer_Variant<QList<QVariant>>();
0367     doTestSequentialContainer_Variant<QStack<QVariant>>();
0368     doTestSequentialContainer_Variant<QQueue<QVariant>>();
0369 }
0370 
0371 void TestGenericTypes::testAssociativeContainer_Variant()
0372 {
0373     doTestAssociativeContainer_Variant<QVariantMap>();
0374     doTestAssociativeContainer_Variant<QVariantHash>(true);
0375 }
0376 
0377 template<typename SequentialContainer>
0378 void insertPeople(KTextTemplate::Context &c)
0379 {
0380     auto people = getPeople();
0381     auto it = people.constBegin();
0382     const auto end = people.constEnd();
0383     SequentialContainer container;
0384     for (; it != end; ++it)
0385         container.insert(container.end(), it.value());
0386     c.insert(QStringLiteral("people"), QVariant::fromValue(container));
0387 }
0388 
0389 template<>
0390 void insertPeople<QSet<Person>>(KTextTemplate::Context &c)
0391 {
0392     auto people = getPeople();
0393     auto it = people.constBegin();
0394     const auto end = people.constEnd();
0395     QSet<Person> container;
0396     for (; it != end; ++it)
0397         container.insert(it.value());
0398     c.insert(QStringLiteral("people"), QVariant::fromValue(container));
0399 }
0400 
0401 template<>
0402 void insertPeople<ThreeArray<Person>>(KTextTemplate::Context &c)
0403 {
0404     auto people = getPeople();
0405     auto it = people.constBegin();
0406     ThreeArray<Person> container;
0407     for (auto i = 0; i < 3; ++i, ++it) {
0408         Q_ASSERT(it != people.constEnd());
0409         container[i] = it.value();
0410     }
0411     c.insert(QStringLiteral("people"), QVariant::fromValue(container));
0412 }
0413 
0414 template<typename AssociativeContainer>
0415 void insertAssociatedPeople(KTextTemplate::Context &c)
0416 {
0417     auto people = getPeople();
0418     auto it = people.constBegin();
0419     const auto end = people.constEnd();
0420     AssociativeContainer container;
0421     for (; it != end; ++it)
0422         container[QString::number(it.key())] = it.value();
0423     c.insert(QStringLiteral("people"), QVariant::fromValue(container));
0424 }
0425 
0426 template<typename AssociativeContainer>
0427 void insertAssociatedPeople_Number(KTextTemplate::Context &c)
0428 {
0429     auto people = getPeople();
0430     auto it = people.constBegin();
0431     const auto end = people.constEnd();
0432     AssociativeContainer container;
0433     for (; it != end; ++it)
0434         container[it.key()] = it.value();
0435     c.insert(QStringLiteral("people"), QVariant::fromValue(container));
0436 }
0437 
0438 template<typename Container>
0439 void doTestSequentialContainer_Type()
0440 {
0441     KTextTemplate::Context c;
0442 
0443     insertPeople<Container>(c);
0444 
0445     SequentialContainerTester<Container>::iteration(c);
0446     SequentialContainerTester<Container>::indexing(c);
0447 }
0448 
0449 template<typename Container>
0450 void doTestAssociativeContainer_Type(bool unordered = {})
0451 {
0452     KTextTemplate::Engine engine;
0453 
0454     engine.setPluginPaths({QStringLiteral(KTEXTTEMPLATE_PLUGIN_PATH)});
0455 
0456     KTextTemplate::Context c;
0457 
0458     insertAssociatedPeople<Container>(c);
0459     testAssociativeValues<Container>(c, unordered);
0460     testAssociativeItems<Container>(c, unordered);
0461 }
0462 
0463 template<typename Container>
0464 void doTestAssociativeContainer_Type_Number(bool unordered = {})
0465 {
0466     KTextTemplate::Engine engine;
0467 
0468     engine.setPluginPaths({QStringLiteral(KTEXTTEMPLATE_PLUGIN_PATH)});
0469 
0470     KTextTemplate::Context c;
0471 
0472     insertAssociatedPeople_Number<Container>(c);
0473     testAssociativeValues<Container>(c, unordered);
0474     testAssociativeItems<Container>(c, unordered);
0475 
0476     {
0477         KTextTemplate::Template t1 = engine.newTemplate(QStringLiteral("{{ people.23.name }}"), QStringLiteral("claire_template"));
0478         auto result = t1->render(&c);
0479         QCOMPARE(result, QStringLiteral("Claire"));
0480     }
0481 }
0482 
0483 void TestGenericTypes::testSequentialContainer_Type()
0484 {
0485     doTestSequentialContainer_Type<QList<Person>>();
0486     doTestSequentialContainer_Type<QList<Person>>();
0487     doTestSequentialContainer_Type<QStack<Person>>();
0488     doTestSequentialContainer_Type<QQueue<Person>>();
0489     doTestSequentialContainer_Type<QSet<Person>>();
0490     doTestSequentialContainer_Type<std::deque<Person>>();
0491     doTestSequentialContainer_Type<std::vector<Person>>();
0492     doTestSequentialContainer_Type<std::list<Person>>();
0493     doTestSequentialContainer_Type<ThreeArray<Person>>();
0494 }
0495 
0496 void TestGenericTypes::testAssociativeContainer_Type()
0497 {
0498     doTestAssociativeContainer_Type<QMap<QString, Person>>();
0499     doTestAssociativeContainer_Type_Number<QMap<qint16, Person>>();
0500     doTestAssociativeContainer_Type_Number<QMap<qint32, Person>>();
0501     doTestAssociativeContainer_Type_Number<QMap<qint64, Person>>();
0502     doTestAssociativeContainer_Type_Number<QMap<quint16, Person>>();
0503     doTestAssociativeContainer_Type_Number<QMap<quint32, Person>>();
0504     doTestAssociativeContainer_Type_Number<QMap<quint64, Person>>();
0505     doTestAssociativeContainer_Type<QHash<QString, Person>>(true);
0506     doTestAssociativeContainer_Type_Number<QHash<qint16, Person>>(true);
0507     doTestAssociativeContainer_Type_Number<QHash<qint32, Person>>(true);
0508     doTestAssociativeContainer_Type_Number<QHash<qint64, Person>>(true);
0509     doTestAssociativeContainer_Type_Number<QHash<quint16, Person>>(true);
0510     doTestAssociativeContainer_Type_Number<QHash<quint32, Person>>(true);
0511     doTestAssociativeContainer_Type_Number<QHash<quint64, Person>>(true);
0512 
0513     doTestAssociativeContainer_Type<std::map<QString, Person>>();
0514     doTestAssociativeContainer_Type_Number<std::map<qint16, Person>>();
0515     doTestAssociativeContainer_Type_Number<std::map<qint32, Person>>();
0516     doTestAssociativeContainer_Type_Number<std::map<qint64, Person>>();
0517     doTestAssociativeContainer_Type_Number<std::map<quint16, Person>>();
0518     doTestAssociativeContainer_Type_Number<std::map<quint32, Person>>();
0519     doTestAssociativeContainer_Type_Number<std::map<quint64, Person>>();
0520 
0521     doTestAssociativeContainer_Type<QtUnorderedMap<QString, Person>>(true);
0522     doTestAssociativeContainer_Type_Number<QtUnorderedMap<qint16, Person>>(true);
0523     doTestAssociativeContainer_Type_Number<QtUnorderedMap<qint32, Person>>(true);
0524     doTestAssociativeContainer_Type_Number<QtUnorderedMap<qint64, Person>>(true);
0525     doTestAssociativeContainer_Type_Number<QtUnorderedMap<quint16, Person>>(true);
0526     doTestAssociativeContainer_Type_Number<QtUnorderedMap<quint32, Person>>(true);
0527     doTestAssociativeContainer_Type_Number<QtUnorderedMap<quint64, Person>>(true);
0528 }
0529 
0530 void TestGenericTypes::testSharedPointer()
0531 {
0532     KTextTemplate::Engine engine;
0533 
0534     engine.setPluginPaths({QStringLiteral(KTEXTTEMPLATE_PLUGIN_PATH)});
0535 
0536     auto t1 = engine.newTemplate(QStringLiteral("{{ p.name }} {{ p.age }}"), QStringLiteral("template1"));
0537 
0538     // Check it
0539     QVariantHash h;
0540     QSharedPointer<PersonObject> p(new PersonObject(QStringLiteral("Grant Lee"), 2));
0541     h.insert(QStringLiteral("p"), QVariant::fromValue(p));
0542     KTextTemplate::Context c(h);
0543     QCOMPARE(t1->render(&c), QStringLiteral("Grant Lee 2"));
0544 }
0545 
0546 void TestGenericTypes::testThirdPartySharedPointer()
0547 {
0548     KTextTemplate::Engine engine;
0549 
0550     engine.setPluginPaths({QStringLiteral(KTEXTTEMPLATE_PLUGIN_PATH)});
0551 
0552     auto t1 = engine.newTemplate(QStringLiteral("{{ p.name }} {{ p.age }}"), QStringLiteral("template1"));
0553 
0554     // Check it
0555     QVariantHash h;
0556     std::shared_ptr<PersonObject> p(new PersonObject(QStringLiteral("Grant Lee"), 2));
0557     h.insert(QStringLiteral("p"), QVariant::fromValue(p));
0558     KTextTemplate::Context c(h);
0559     QCOMPARE(t1->render(&c), QStringLiteral("Grant Lee 2"));
0560 }
0561 
0562 using ListVectorInt = QList<QList<qint16>>;
0563 using MapListVectorInt = QMap<int, QList<QList<qint16>>>;
0564 using StackMapListVectorInt = QStack<QMap<int, QList<QList<qint16>>>>;
0565 
0566 static QList<qint16> getNumbers()
0567 {
0568     static auto n = 0;
0569     QList<qint16> nums;
0570     nums.push_back(++n);
0571     nums.push_back(++n);
0572     return nums;
0573 }
0574 
0575 static ListVectorInt getNumberLists()
0576 {
0577     ListVectorInt list;
0578     for (auto i = 0; i < 2; ++i) {
0579         list.append(getNumbers());
0580     }
0581     return list;
0582 }
0583 
0584 static MapListVectorInt getNumberListMap()
0585 {
0586     MapListVectorInt map;
0587     for (auto i = 0; i < 2; ++i) {
0588         map.insert(i, getNumberLists());
0589     }
0590     return map;
0591 }
0592 
0593 static StackMapListVectorInt getMapStack()
0594 {
0595     StackMapListVectorInt stack;
0596     for (auto i = 0; i < 2; ++i) {
0597         stack.push(getNumberListMap());
0598     }
0599     return stack;
0600 }
0601 
0602 void TestGenericTypes::testNestedContainers()
0603 {
0604     KTextTemplate::Engine engine;
0605 
0606     engine.setPluginPaths({QStringLiteral(KTEXTTEMPLATE_PLUGIN_PATH)});
0607 
0608     KTextTemplate::Context c;
0609     c.insert(QStringLiteral("stack"), QVariant::fromValue(getMapStack()));
0610 
0611 #if defined(Q_CC_MSVC)
0612 // MSVC doesn't like static string concatenations like L"foo" "bar", as
0613 // results from QStringLiteral, so use QLatin1String here instead.
0614 #define STRING_LITERAL QLatin1String
0615 #else
0616 #define STRING_LITERAL QStringLiteral
0617 #endif
0618     auto t1 = engine.newTemplate(STRING_LITERAL("{% for map in stack %}"
0619                                                 "(M {% for key, list in map.items %}"
0620                                                 "({{ key }} : (L {% for vector in list %}"
0621                                                 "(V {% for number in vector %}"
0622                                                 "{{ number }},"
0623                                                 "{% endfor %}),"
0624                                                 "{% endfor %}),"
0625                                                 "{% endfor %}),"
0626                                                 "{% endfor %}"),
0627                                  QStringLiteral("template1"));
0628 
0629 #undef STRING_LITERAL
0630 
0631     auto result = t1->render(&c);
0632 
0633     auto expectedResult = QStringLiteral(
0634         "(M (0 : (L (V 1,2,),(V 3,4,),),(1 : (L (V 5,6,),(V 7,8,),),),(M (0 : (L "
0635         "(V 9,10,),(V 11,12,),),(1 : (L (V 13,14,),(V 15,16,),),),");
0636 
0637     QCOMPARE(result, expectedResult);
0638 }
0639 
0640 class CustomObject : public QObject
0641 {
0642     Q_OBJECT
0643 public:
0644     explicit CustomObject(QObject *parent = {})
0645         : QObject(parent)
0646     {
0647     }
0648 };
0649 
0650 class OtherObject : public QObject
0651 {
0652     Q_OBJECT
0653     Q_PROPERTY(CustomObject *custom READ custom)
0654 public:
0655     explicit OtherObject(QObject *parent = {})
0656         : QObject(parent)
0657         , m_custom(new CustomObject(this))
0658     {
0659         m_custom->setProperty("nestedProp", QStringLiteral("nestedValue"));
0660     }
0661 
0662     CustomObject *custom()
0663     {
0664         return m_custom;
0665     }
0666 
0667 private:
0668     CustomObject *m_custom;
0669 };
0670 
0671 void TestGenericTypes::testCustomQObjectDerived()
0672 {
0673     KTextTemplate::Engine engine;
0674 
0675     engine.setPluginPaths({QStringLiteral(KTEXTTEMPLATE_PLUGIN_PATH)});
0676 
0677     auto customObject = new CustomObject(this);
0678     customObject->setProperty("someProp", QStringLiteral("propValue"));
0679 
0680     KTextTemplate::Context c;
0681     c.insert(QStringLiteral("custom"), QVariant::fromValue(customObject));
0682 
0683     {
0684         auto t1 = engine.newTemplate(QStringLiteral("{{ custom.someProp }}"), QStringLiteral("template1"));
0685 
0686         auto result = t1->render(&c);
0687         auto expectedResult = QStringLiteral("propValue");
0688 
0689         QCOMPARE(result, expectedResult);
0690     }
0691 
0692     auto other = new OtherObject(this);
0693 
0694     c.insert(QStringLiteral("other"), other);
0695 
0696     {
0697         auto t1 = engine.newTemplate(QStringLiteral("{{ other.custom.nestedProp }}"), QStringLiteral("template1"));
0698 
0699         auto result = t1->render(&c);
0700         auto expectedResult = QStringLiteral("nestedValue");
0701 
0702         QCOMPARE(result, expectedResult);
0703     }
0704 }
0705 
0706 struct UnregisteredType {
0707 };
0708 
0709 Q_DECLARE_METATYPE(UnregisteredType)
0710 
0711 struct RegisteredNotListType {
0712 };
0713 
0714 Q_DECLARE_METATYPE(RegisteredNotListType)
0715 
0716 KTEXTTEMPLATE_BEGIN_LOOKUP(RegisteredNotListType)
0717 Q_UNUSED(object)
0718 if (property == QStringLiteral("property"))
0719     return 42;
0720 KTEXTTEMPLATE_END_LOOKUP
0721 
0722 static QVariantList dummy(const UnregisteredType &)
0723 {
0724     return QVariantList{42};
0725 }
0726 
0727 QVariant dummyLookup(const QVariant &, const QString &)
0728 {
0729     return 42;
0730 }
0731 
0732 void TestGenericTypes::testUnregistered()
0733 {
0734     {
0735         UnregisteredType unregType;
0736         auto v = QVariant::fromValue(unregType);
0737 
0738         auto result = KTextTemplate::MetaType::lookup(v, QStringLiteral("property"));
0739         QVERIFY(!result.isValid());
0740 
0741         QVERIFY(!v.canConvert<QVariantList>());
0742     }
0743 
0744     KTextTemplate::registerMetaType<RegisteredNotListType>();
0745 
0746     {
0747         RegisteredNotListType nonListType;
0748         auto v = QVariant::fromValue(nonListType);
0749         auto result = KTextTemplate::MetaType::lookup(v, QStringLiteral("property"));
0750         QVERIFY(result.isValid());
0751         QVERIFY(!v.canConvert<QVariantList>());
0752     }
0753 
0754     {
0755         QMetaType::registerConverter<UnregisteredType, QVariantList>(&dummy);
0756         UnregisteredType unregType;
0757         auto v = QVariant::fromValue(unregType);
0758         auto result = KTextTemplate::MetaType::lookup(v, QStringLiteral("property"));
0759         QVERIFY(!result.isValid());
0760     }
0761 
0762     // Only do this in release mode?
0763     //   KTextTemplate::MetaType::registerLookUpOperator(0, dummyLookup);
0764     //   KTextTemplate::MetaType::registerToVariantListOperator(0, dummy);
0765 }
0766 
0767 Q_DECLARE_METATYPE(Person *)
0768 
0769 KTEXTTEMPLATE_BEGIN_LOOKUP_PTR(Person)
0770 if (property == QStringLiteral("name"))
0771     return QString::fromStdString(object->name);
0772 if (property == QStringLiteral("age"))
0773     return object->age;
0774 KTEXTTEMPLATE_END_LOOKUP
0775 
0776 void TestGenericTypes::testPointerNonQObject()
0777 {
0778     auto p = new Person("Adele", 21);
0779     auto v = QVariant::fromValue(p);
0780 
0781     KTextTemplate::registerMetaType<Person *>();
0782 
0783     auto result = KTextTemplate::MetaType::lookup(v, QStringLiteral("name"));
0784 
0785     QCOMPARE(result.value<QString>(), QStringLiteral("Adele"));
0786 
0787     delete p;
0788 }
0789 
0790 class CustomGadget
0791 {
0792     Q_GADGET
0793     Q_PROPERTY(int fortyTwo READ fortyTwo)
0794 public:
0795     int fortyTwo()
0796     {
0797         return 42;
0798     }
0799 };
0800 
0801 void TestGenericTypes::testQGadget()
0802 {
0803     CustomGadget g;
0804     auto v = QVariant::fromValue(g);
0805 
0806     auto result = KTextTemplate::MetaType::lookup(v, QStringLiteral("fortyTwo"));
0807 
0808     QCOMPARE(result.value<int>(), 42);
0809 }
0810 
0811 void TestGenericTypes::testGadgetMetaType()
0812 {
0813     KTextTemplate::Engine engine;
0814     engine.setPluginPaths({QStringLiteral(KTEXTTEMPLATE_PLUGIN_PATH)});
0815 
0816     auto t1 = engine.newTemplate(QStringLiteral("Person: \nName: {{p.name}}\nAge: {{p.age}}"), QStringLiteral("template1"));
0817 
0818     PersonGadget p;
0819     p.m_name = QStringLiteral("Some Name");
0820     KTextTemplate::Context c;
0821     c.insert(QStringLiteral("p"), QVariant::fromValue(p));
0822     QCOMPARE(t1->render(&c), QStringLiteral("Person: \nName: Some Name\nAge: 42"));
0823 }
0824 
0825 class ObjectWithProperties : public QObject
0826 {
0827     Q_OBJECT
0828     Q_PROPERTY(QList<int> numberList READ numberList)
0829     Q_PROPERTY(QList<CustomGadget> gadgetList READ gadgetList)
0830     Q_PROPERTY(QList<PersonObject *> personList READ personList)
0831     Q_PROPERTY(QList<QSharedPointer<PersonObject>> personPtrList READ personPtrList)
0832 
0833 public:
0834     ObjectWithProperties(QObject *parent = {})
0835         : QObject(parent)
0836     {
0837         m_numberList.push_back(42);
0838         m_numberList.push_back(7);
0839         m_gadgetList.push_back(CustomGadget{});
0840         m_gadgetList.push_back(CustomGadget{});
0841         m_personList.push_back(new PersonObject{QStringLiteral("Joe"), 20, this});
0842         m_personList.push_back(new PersonObject{QStringLiteral("Mike"), 22, this});
0843         m_personPtrList.push_back(QSharedPointer<PersonObject>::create(QStringLiteral("Niall"), 23));
0844         m_personPtrList.push_back(QSharedPointer<PersonObject>::create(QStringLiteral("Dave"), 24));
0845     }
0846 
0847     QList<int> numberList()
0848     {
0849         return m_numberList;
0850     }
0851     QList<CustomGadget> gadgetList()
0852     {
0853         return m_gadgetList;
0854     }
0855     QList<PersonObject *> personList()
0856     {
0857         return m_personList;
0858     }
0859     QList<QSharedPointer<PersonObject>> personPtrList()
0860     {
0861         return m_personPtrList;
0862     }
0863 
0864 private:
0865     QList<int> m_numberList;
0866     QList<CustomGadget> m_gadgetList;
0867     QList<PersonObject *> m_personList;
0868     QList<QSharedPointer<PersonObject>> m_personPtrList;
0869 };
0870 
0871 void TestGenericTypes::propertyMacroTypes()
0872 {
0873     KTextTemplate::Engine engine;
0874 
0875     qRegisterMetaType<QList<CustomGadget>>();
0876 
0877     engine.setPluginPaths({QStringLiteral(KTEXTTEMPLATE_PLUGIN_PATH)});
0878 
0879     auto objectWithProperties = new ObjectWithProperties(this);
0880 
0881     KTextTemplate::Context c;
0882     c.insert(QStringLiteral("obj"), objectWithProperties);
0883 
0884     {
0885         auto t1 = engine.newTemplate(QStringLiteral("{{ obj.numberList.0 }}--{{ obj.numberList.1 }}"), QStringLiteral("template1"));
0886 
0887         auto result = t1->render(&c);
0888         auto expectedResult = QStringLiteral("42--7");
0889 
0890         QCOMPARE(result, expectedResult);
0891     }
0892 
0893     {
0894         auto t1 = engine.newTemplate(QStringLiteral("{{ obj.gadgetList.0.fortyTwo }}--{{ obj.gadgetList.1.fortyTwo }}"), QStringLiteral("template1"));
0895 
0896         auto result = t1->render(&c);
0897         auto expectedResult = QStringLiteral("42--42");
0898 
0899         QCOMPARE(result, expectedResult);
0900     }
0901 
0902     {
0903         auto t1 = engine.newTemplate(QStringLiteral("{{ obj.personList.0.name }}({{ obj.personList.0.age }})"
0904                                                     "--{{ obj.personList.1.name }}({{ obj.personList.1.age }})"),
0905                                      QStringLiteral("template1"));
0906 
0907         auto result = t1->render(&c);
0908         auto expectedResult = QStringLiteral("Joe(20)--Mike(22)");
0909 
0910         QCOMPARE(result, expectedResult);
0911     }
0912 
0913     {
0914         auto t1 = engine.newTemplate(QStringLiteral("{{ obj.personPtrList.0.name }}({{ obj.personPtrList.0.age }})"
0915                                                     "--{{ obj.personPtrList.1.name }}({{ obj.personPtrList.1.age }})"),
0916                                      QStringLiteral("template1"));
0917 
0918         auto result = t1->render(&c);
0919         auto expectedResult = QStringLiteral("Niall(23)--Dave(24)");
0920 
0921         QCOMPARE(result, expectedResult);
0922     }
0923 }
0924 
0925 QTEST_MAIN(TestGenericTypes)
0926 #include "testgenerictypes.moc"