Cutelee 6.1.0
testgenerictypes.cpp
1/*
2 This file is part of the Cutelee template system.
3
4 Copyright (c) 2010 Michael Jansen <kde@michael-jansen.biz>
5 Copyright (c) 2010 Stephen Kelly <steveire@gmail.com>
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either version
10 2.1 of the Licence, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public
18 License along with this library. If not, see <http://www.gnu.org/licenses/>.
19
20*/
21
22#include "engine.h"
23#include "cutelee_paths.h"
24#include "metatype.h"
25#include "template.h"
26#include "test_macros.h"
27
28#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
29#include <QtCore/QLinkedList>
30#endif
31#include <QtCore/QMetaType>
32#include <QtCore/QQueue>
33#include <QtCore/QStack>
34#include <QtCore/QVariant>
35#include <QtCore/QVariantHash>
36#include <QJsonValue>
37#include <QJsonArray>
38#include <QJsonObject>
39#include <QJsonDocument>
40#include <QtTest/QTest>
41
42#include "coverageobject.h"
43#include <deque>
44#include <string>
45
46#include <memory>
47
48Q_DECLARE_SEQUENTIAL_CONTAINER_METATYPE(ThreeArray)
49
50Q_DECLARE_ASSOCIATIVE_CONTAINER_METATYPE(QtUnorderedMap)
51
52Q_DECLARE_SMART_POINTER_METATYPE(std::shared_ptr)
53
54Q_DECLARE_SEQUENTIAL_CONTAINER_METATYPE(std::deque)
55
57{
58 Q_OBJECT
59
60private Q_SLOTS:
61
62 void initTestCase();
63
64 void testGenericClassType();
65 void testSequentialContainer_Variant();
66 void testAssociativeContainer_Variant();
67 void testSequentialContainer_Type();
68 void testAssociativeContainer_Type();
69 void testSharedPointer();
70 void testThirdPartySharedPointer();
71 void testNestedContainers();
72
73 void testCustomQObjectDerived();
74
75 void propertyMacroTypes();
76
77 void testUnregistered();
78 void testPointerNonQObject();
79 void testQGadget();
80 void testGadgetMetaType();
81
82 void testJsonTypes();
83
84}; // class TestGenericTypes
85
86class Person
87{
88public:
89 Person() : age(0) {}
90 Person(std::string _name, int _age) : name(_name), age(_age)
91 {
92 static auto _uid = 0;
93 uid = ++_uid;
94 }
95
96 bool operator==(const Person &other) const { return uid == other.uid; }
97
98 std::string name;
99 int age;
100 int uid;
101};
102
104{
105 Q_GADGET
106 Q_PROPERTY(QString name MEMBER m_name)
107public:
108 QString m_name;
109 int m_age = 42;
110};
111
112int qHash(const Person &p) { return p.uid; }
113
114Q_DECLARE_METATYPE(Person)
115Q_DECLARE_METATYPE(PersonGadget)
116
118if (property == QStringLiteral("name"))
119 return QString::fromStdString(object.name);
120else if (property == QStringLiteral("age"))
121 return object.age;
123
125if (property == QStringLiteral("age"))
126 return object.m_age;
128
129class PersonObject : public QObject
130{
131 Q_OBJECT
132 Q_PROPERTY(QString name READ name CONSTANT)
133 Q_PROPERTY(int age READ age CONSTANT)
134public:
135 PersonObject(const QString &name, int age, QObject *parent = {})
136 : QObject(parent), m_name(name), m_age(age)
137 {
138 }
139
140 QString name() const { return m_name; }
141 int age() const { return m_age; }
142
143private:
144 const QString m_name;
145 const int m_age; // Yeah, you wish...
146};
147
148void TestGenericTypes::initTestCase()
149{
150 // Register the handler for our custom type
151 Cutelee::registerMetaType<Person>();
152 Cutelee::registerMetaType<PersonGadget>();
153}
154
155void TestGenericTypes::testGenericClassType()
156{
157 Cutelee::Engine engine;
158
159 engine.setPluginPaths({QStringLiteral(CUTELEE_PLUGIN_PATH)});
160
161 auto t1 = engine.newTemplate(
162 QStringLiteral(
163 "Person: \nName: {{p.name}}\nAge: {{p.age}}\nUnknown: {{p.unknown}}"),
164 QStringLiteral("template1"));
165
166 // Check it
167 QVariantHash h;
168 Person p("Grant Lee", 2);
169 h.insert(QStringLiteral("p"), QVariant::fromValue(p));
170 Cutelee::Context c(h);
171 QCOMPARE(t1->render(&c),
172 QStringLiteral("Person: \nName: Grant Lee\nAge: 2\nUnknown: "));
173}
174
175static QMap<int, Person> getPeople()
176{
177 QMap<int, Person> people;
178 people.insert(23, Person("Claire", 23));
179 people.insert(32, Person("Grant", 32));
180 people.insert(50, Person("Alan", 50));
181 return people;
182}
183
184template <typename SequentialContainer>
185void insertPeopleVariants(Cutelee::Context &c)
186{
187 auto people = getPeople();
188 auto it = people.constBegin();
189 const auto end = people.constEnd();
190 SequentialContainer container;
191 for (; it != end; ++it)
192 container.push_back(QVariant::fromValue(it.value()));
193 c.insert(QStringLiteral("people"), QVariant::fromValue(container));
194}
195
196template <typename AssociativeContainer>
197void insertAssociatedPeopleVariants(Cutelee::Context &c)
198{
199 auto people = getPeople();
200 auto it = people.constBegin();
201 const auto end = people.constEnd();
202 AssociativeContainer container;
203 for (; it != end; ++it)
204 container.insert(QString::number(it.key()),
205 QVariant::fromValue(it.value()));
206 c.insert(QStringLiteral("people"), QVariant::fromValue(container));
207}
208
209template <>
210void insertPeopleVariants<QMap<QString, QVariant>>(Cutelee::Context &c)
211{
212 insertAssociatedPeopleVariants<QMap<QString, QVariant>>(c);
213}
214
215template <>
216void insertPeopleVariants<QHash<QString, QVariant>>(Cutelee::Context &c)
217{
218 insertAssociatedPeopleVariants<QHash<QString, QVariant>>(c);
219}
220
221template <typename Container> void testSequentialIteration(Cutelee::Context &c)
222{
223 Cutelee::Engine engine;
224
225 engine.setPluginPaths({QStringLiteral(CUTELEE_PLUGIN_PATH)});
226
227 {
228 Cutelee::Template t1 = engine.newTemplate(
229 QStringLiteral(
230 "{% for person in people %}{{ person.name }},{% endfor %}"),
231 QStringLiteral("people_template"));
232 QCOMPARE(t1->render(&c), QStringLiteral("Claire,Grant,Alan,"));
233 }
234}
235
236template <typename Container> void testSequentialIndexing(Cutelee::Context &c)
237{
238 Cutelee::Engine engine;
239
240 engine.setPluginPaths({QStringLiteral(CUTELEE_PLUGIN_PATH)});
241
242 {
243 Cutelee::Template t1 = engine.newTemplate(
244 QStringLiteral(
245 "{{ people.0.name }},{{ people.1.name }},{{ people.2.name }},"),
246 QStringLiteral("people_template"));
247 QCOMPARE(t1->render(&c), QStringLiteral("Claire,Grant,Alan,"));
248 }
249}
250
251template <typename Container> struct SequentialContainerTester {
252 static void iteration(Cutelee::Context &c)
253 {
254 testSequentialIteration<Container>(c);
255 }
256
257 static void indexing(Cutelee::Context &c)
258 {
259 testSequentialIndexing<Container>(c);
260 }
261};
262
263template <typename T> struct SequentialContainerTester<QSet<T>> {
264 static void iteration(Cutelee::Context &c)
265 {
266 Cutelee::Engine engine;
267
268 engine.setPluginPaths({QStringLiteral(CUTELEE_PLUGIN_PATH)});
269
270 Cutelee::Template t1 = engine.newTemplate(
271 QStringLiteral(
272 "{% for person in people %}{{ person.name }},{% endfor %}"),
273 QStringLiteral("people_template"));
274 auto result = t1->render(&c);
275 QStringList output{QStringLiteral("Claire,"), QStringLiteral("Grant,"),
276 QStringLiteral("Alan,")};
277 Q_FOREACH (const QString &s, output) {
278 QVERIFY(result.contains(s));
279 }
280
281 QCOMPARE(result.length(), output.join(QString()).length());
282 }
283
284 static void indexing(Cutelee::Context) {}
285};
286
287#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
288template <typename T> struct SequentialContainerTester<QLinkedList<T>> {
289 static void iteration(Cutelee::Context &c)
290 {
291 testSequentialIteration<QLinkedList<T>>(c);
292 }
293
294 static void indexing(Cutelee::Context) {}
295};
296#endif
297
298template <typename T> struct SequentialContainerTester<std::list<T>> {
299 static void iteration(Cutelee::Context &c)
300 {
301 testSequentialIteration<std::list<T>>(c);
302 }
303
304 static void indexing(Cutelee::Context) {}
305};
306
307template <typename Container> void doTestSequentialContainer_Variant()
308{
310
311 insertPeopleVariants<Container>(c);
312
315}
316
317template <typename Container>
318void testAssociativeValues(Cutelee::Context &c, bool unordered = {})
319{
320#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
321 Cutelee::Engine engine;
322
323 engine.setPluginPaths({QStringLiteral(CUTELEE_PLUGIN_PATH)});
324
325 {
326 Cutelee::Template t1 = engine.newTemplate(
327 QStringLiteral("{% for person in people.values %}({{ person.name }}:{{ "
328 "person.age }}),{% endfor %}"),
329 QStringLiteral("people_template"));
330
331 auto result = t1->render(&c);
332 if (!unordered)
333 QCOMPARE(result, QStringLiteral("(Claire:23),(Grant:32),(Alan:50),"));
334 else {
335 QVERIFY(result.size() == 33);
336 QVERIFY(result.contains(QStringLiteral("(Claire:23),")));
337 QVERIFY(result.contains(QStringLiteral("(Grant:32),")));
338 QVERIFY(result.contains(QStringLiteral("(Alan:50),")));
339 }
340 }
341#endif
342}
343
344template <typename Container>
345void testAssociativeItems(Cutelee::Context &c, bool unordered)
346{
347#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
348 Cutelee::Engine engine;
349
350 engine.setPluginPaths({QStringLiteral(CUTELEE_PLUGIN_PATH)});
351
352 {
353 Cutelee::Template t1 = engine.newTemplate(
354 QStringLiteral("{% for item in people.items %}({{ item.1.name }}:{{ "
355 "item.1.age }}),{% endfor %}"),
356 QStringLiteral("people_template"));
357 auto result = t1->render(&c);
358 if (!unordered)
359 QCOMPARE(result, QStringLiteral("(Claire:23),(Grant:32),(Alan:50),"));
360 else {
361 QVERIFY(result.size() == 33);
362 QVERIFY(result.contains(QStringLiteral("(Claire:23),")));
363 QVERIFY(result.contains(QStringLiteral("(Grant:32),")));
364 QVERIFY(result.contains(QStringLiteral("(Alan:50),")));
365 }
366 }
367#endif
368}
369
370template <typename Container>
371void doTestAssociativeContainer_Variant(bool unordered = {})
372{
373 Cutelee::Engine engine;
374
375 engine.setPluginPaths({QStringLiteral(CUTELEE_PLUGIN_PATH)});
376
378
379 insertPeopleVariants<Container>(c);
380 testAssociativeValues<Container>(c, unordered);
381 testAssociativeItems<Container>(c, unordered);
382}
383
384void TestGenericTypes::testSequentialContainer_Variant()
385{
386 doTestSequentialContainer_Variant<QVariantList>();
387 doTestSequentialContainer_Variant<QVector<QVariant>>();
388 doTestSequentialContainer_Variant<QStack<QVariant>>();
389 doTestSequentialContainer_Variant<QQueue<QVariant>>();
390#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
391 doTestSequentialContainer_Variant<QLinkedList<QVariant>>();
392#endif
393}
394
395void TestGenericTypes::testAssociativeContainer_Variant()
396{
397 doTestAssociativeContainer_Variant<QVariantMap>();
398 doTestAssociativeContainer_Variant<QVariantHash>(true);
399}
400
401template <typename SequentialContainer> void insertPeople(Cutelee::Context &c)
402{
403 auto people = getPeople();
404 auto it = people.constBegin();
405 const auto end = people.constEnd();
406 SequentialContainer container;
407 for (; it != end; ++it)
408 container.insert(container.end(), it.value());
409 c.insert(QStringLiteral("people"), QVariant::fromValue(container));
410}
411
412template <> void insertPeople<QSet<Person>>(Cutelee::Context &c)
413{
414 auto people = getPeople();
415 auto it = people.constBegin();
416 const auto end = people.constEnd();
417 QSet<Person> container;
418 for (; it != end; ++it)
419 container.insert(it.value());
420 c.insert(QStringLiteral("people"), QVariant::fromValue(container));
421}
422
423template <> void insertPeople<ThreeArray<Person>>(Cutelee::Context &c)
424{
425 auto people = getPeople();
426 auto it = people.constBegin();
427 ThreeArray<Person> container;
428 for (auto i = 0; i < 3; ++i, ++it) {
429 Q_ASSERT(it != people.constEnd());
430 container[i] = it.value();
431 }
432 c.insert(QStringLiteral("people"), QVariant::fromValue(container));
433}
434
435template <typename AssociativeContainer>
436void insertAssociatedPeople(Cutelee::Context &c)
437{
438 auto people = getPeople();
439 auto it = people.constBegin();
440 const auto end = people.constEnd();
441 AssociativeContainer container;
442 for (; it != end; ++it)
443 container[QString::number(it.key())] = it.value();
444 c.insert(QStringLiteral("people"), QVariant::fromValue(container));
445}
446
447template <typename AssociativeContainer>
448void insertAssociatedPeople_Number(Cutelee::Context &c)
449{
450 auto people = getPeople();
451 auto it = people.constBegin();
452 const auto end = people.constEnd();
453 AssociativeContainer container;
454 for (; it != end; ++it)
455 container[it.key()] = it.value();
456 c.insert(QStringLiteral("people"), QVariant::fromValue(container));
457}
458
459template <typename Container> void doTestSequentialContainer_Type()
460{
462
463 insertPeople<Container>(c);
464
467}
468
469template <typename Container>
470void doTestAssociativeContainer_Type(bool unordered = {})
471{
472 Cutelee::Engine engine;
473
474 engine.setPluginPaths({QStringLiteral(CUTELEE_PLUGIN_PATH)});
475
477
478 insertAssociatedPeople<Container>(c);
479 testAssociativeValues<Container>(c, unordered);
480 testAssociativeItems<Container>(c, unordered);
481}
482
483template <typename Container>
484void doTestAssociativeContainer_Type_Number(bool unordered = {})
485{
486 Cutelee::Engine engine;
487
488 engine.setPluginPaths({QStringLiteral(CUTELEE_PLUGIN_PATH)});
489
491
492 insertAssociatedPeople_Number<Container>(c);
493 testAssociativeValues<Container>(c, unordered);
494 testAssociativeItems<Container>(c, unordered);
495
496 {
498 = engine.newTemplate(QStringLiteral("{{ people.23.name }}"),
499 QStringLiteral("claire_template"));
500 auto result = t1->render(&c);
501 QCOMPARE(result, QStringLiteral("Claire"));
502 }
503}
504
505void TestGenericTypes::testSequentialContainer_Type()
506{
507 doTestSequentialContainer_Type<QList<Person>>();
508 doTestSequentialContainer_Type<QVector<Person>>();
509 doTestSequentialContainer_Type<QStack<Person>>();
510 doTestSequentialContainer_Type<QQueue<Person>>();
511#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
512 doTestSequentialContainer_Type<QLinkedList<Person>>();
513#endif
514 doTestSequentialContainer_Type<QSet<Person>>();
515 doTestSequentialContainer_Type<std::deque<Person>>();
516 doTestSequentialContainer_Type<std::vector<Person>>();
517 doTestSequentialContainer_Type<std::list<Person>>();
518 doTestSequentialContainer_Type<ThreeArray<Person>>();
519}
520
521void TestGenericTypes::testAssociativeContainer_Type()
522{
523 doTestAssociativeContainer_Type<QMap<QString, Person>>();
524 doTestAssociativeContainer_Type_Number<QMap<qint16, Person>>();
525 doTestAssociativeContainer_Type_Number<QMap<qint32, Person>>();
526 doTestAssociativeContainer_Type_Number<QMap<qint64, Person>>();
527 doTestAssociativeContainer_Type_Number<QMap<quint16, Person>>();
528 doTestAssociativeContainer_Type_Number<QMap<quint32, Person>>();
529 doTestAssociativeContainer_Type_Number<QMap<quint64, Person>>();
530 doTestAssociativeContainer_Type<QHash<QString, Person>>(true);
531 doTestAssociativeContainer_Type_Number<QHash<qint16, Person>>(true);
532 doTestAssociativeContainer_Type_Number<QHash<qint32, Person>>(true);
533 doTestAssociativeContainer_Type_Number<QHash<qint64, Person>>(true);
534 doTestAssociativeContainer_Type_Number<QHash<quint16, Person>>(true);
535 doTestAssociativeContainer_Type_Number<QHash<quint32, Person>>(true);
536 doTestAssociativeContainer_Type_Number<QHash<quint64, Person>>(true);
537
538 doTestAssociativeContainer_Type<std::map<QString, Person>>();
539 doTestAssociativeContainer_Type_Number<std::map<qint16, Person>>();
540 doTestAssociativeContainer_Type_Number<std::map<qint32, Person>>();
541 doTestAssociativeContainer_Type_Number<std::map<qint64, Person>>();
542 doTestAssociativeContainer_Type_Number<std::map<quint16, Person>>();
543 doTestAssociativeContainer_Type_Number<std::map<quint32, Person>>();
544 doTestAssociativeContainer_Type_Number<std::map<quint64, Person>>();
545
546 doTestAssociativeContainer_Type<QtUnorderedMap<QString, Person>>(true);
547 doTestAssociativeContainer_Type_Number<QtUnorderedMap<qint16, Person>>(true);
548 doTestAssociativeContainer_Type_Number<QtUnorderedMap<qint32, Person>>(true);
549 doTestAssociativeContainer_Type_Number<QtUnorderedMap<qint64, Person>>(true);
550 doTestAssociativeContainer_Type_Number<QtUnorderedMap<quint16, Person>>(true);
551 doTestAssociativeContainer_Type_Number<QtUnorderedMap<quint32, Person>>(true);
552 doTestAssociativeContainer_Type_Number<QtUnorderedMap<quint64, Person>>(true);
553}
554
555void TestGenericTypes::testSharedPointer()
556{
557 Cutelee::Engine engine;
558
559 engine.setPluginPaths({QStringLiteral(CUTELEE_PLUGIN_PATH)});
560
561 auto t1 = engine.newTemplate(QStringLiteral("{{ p.name }} {{ p.age }}"),
562 QStringLiteral("template1"));
563
564 // Check it
565 QVariantHash h;
566 std::shared_ptr<PersonObject> p(
567 new PersonObject(QStringLiteral("Grant Lee"), 2));
568 h.insert(QStringLiteral("p"), QVariant::fromValue(p));
569 Cutelee::Context c(h);
570 QCOMPARE(t1->render(&c), QStringLiteral("Grant Lee 2"));
571}
572
573void TestGenericTypes::testThirdPartySharedPointer()
574{
575 Cutelee::Engine engine;
576
577 engine.setPluginPaths({QStringLiteral(CUTELEE_PLUGIN_PATH)});
578
579 auto t1 = engine.newTemplate(QStringLiteral("{{ p.name }} {{ p.age }}"),
580 QStringLiteral("template1"));
581
582 // Check it
583 QVariantHash h;
584 std::shared_ptr<PersonObject> p(
585 new PersonObject(QStringLiteral("Grant Lee"), 2));
586 h.insert(QStringLiteral("p"), QVariant::fromValue(p));
587 Cutelee::Context c(h);
588 QCOMPARE(t1->render(&c), QStringLiteral("Grant Lee 2"));
589}
590
591typedef QList<QVector<qint16>> ListVectorInt;
592typedef QMap<int, QList<QVector<qint16>>> MapListVectorInt;
593typedef QStack<QMap<int, QList<QVector<qint16>>>> StackMapListVectorInt;
594
595static QVector<qint16> getNumbers()
596{
597 static auto n = 0;
598 QVector<qint16> nums;
599 nums.push_back(++n);
600 nums.push_back(++n);
601 return nums;
602}
603
604static ListVectorInt getNumberLists()
605{
606 ListVectorInt list;
607 for (auto i = 0; i < 2; ++i) {
608 list.append(getNumbers());
609 }
610 return list;
611}
612
613static MapListVectorInt getNumberListMap()
614{
615 MapListVectorInt map;
616 for (auto i = 0; i < 2; ++i) {
617 map.insert(i, getNumberLists());
618 }
619 return map;
620}
621
622static StackMapListVectorInt getMapStack()
623{
624 StackMapListVectorInt stack;
625 for (auto i = 0; i < 2; ++i) {
626 stack.push(getNumberListMap());
627 }
628 return stack;
629}
630
631void TestGenericTypes::testNestedContainers()
632{
633#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
634 Cutelee::Engine engine;
635
636 engine.setPluginPaths({QStringLiteral(CUTELEE_PLUGIN_PATH)});
637
639 c.insert(QStringLiteral("stack"), QVariant::fromValue(getMapStack()));
640
641#if defined(Q_CC_MSVC)
642// MSVC doesn't like static string concatenations like L"foo" "bar", as
643// results from QStringLiteral, so use QLatin1String here instead.
644#define STRING_LITERAL QLatin1String
645#else
646#define STRING_LITERAL QStringLiteral
647#endif
648 auto t1 = engine.newTemplate(
649 STRING_LITERAL("{% for map in stack %}"
650 "(M {% for key, list in map.items %}"
651 "({{ key }} : (L {% for vector in list %}"
652 "(V {% for number in vector %}"
653 "{{ number }},"
654 "{% endfor %}),"
655 "{% endfor %}),"
656 "{% endfor %}),"
657 "{% endfor %}"),
658 QStringLiteral("template1"));
659
660#undef STRING_LITERAL
661
662 auto result = t1->render(&c);
663
664 auto expectedResult = QStringLiteral(
665 "(M (0 : (L (V 1,2,),(V 3,4,),),(1 : (L (V 5,6,),(V 7,8,),),),(M (0 : (L "
666 "(V 9,10,),(V 11,12,),),(1 : (L (V 13,14,),(V 15,16,),),),");
667
668 QCOMPARE(result, expectedResult);
669#endif
670}
671
672class CustomObject : public QObject
673{
674 Q_OBJECT
675public:
676 explicit CustomObject(QObject *parent = {}) : QObject(parent) {}
677};
678
679class OtherObject : public QObject
680{
681 Q_OBJECT
682 Q_PROPERTY(CustomObject *custom READ custom CONSTANT)
683public:
684 explicit OtherObject(QObject *parent = {})
685 : QObject(parent), m_custom(new CustomObject(this))
686 {
687 m_custom->setProperty("nestedProp", QStringLiteral("nestedValue"));
688 }
689
690 CustomObject *custom() { return m_custom; }
691
692private:
693 CustomObject *m_custom;
694};
695
696void TestGenericTypes::testCustomQObjectDerived()
697{
698 Cutelee::Engine engine;
699
700 engine.setPluginPaths({QStringLiteral(CUTELEE_PLUGIN_PATH)});
701
702 auto customObject = new CustomObject(this);
703 customObject->setProperty("someProp", QStringLiteral("propValue"));
704
706 c.insert(QStringLiteral("custom"), QVariant::fromValue(customObject));
707
708 {
709 auto t1 = engine.newTemplate(QStringLiteral("{{ custom.someProp }}"),
710 QStringLiteral("template1"));
711
712 auto result = t1->render(&c);
713 auto expectedResult = QStringLiteral("propValue");
714
715 QCOMPARE(result, expectedResult);
716 }
717
718 auto other = new OtherObject(this);
719
720 c.insert(QStringLiteral("other"), other);
721
722 {
723 auto t1
724 = engine.newTemplate(QStringLiteral("{{ other.custom.nestedProp }}"),
725 QStringLiteral("template1"));
726
727 auto result = t1->render(&c);
728 auto expectedResult = QStringLiteral("nestedValue");
729
730 QCOMPARE(result, expectedResult);
731 }
732}
733
735};
736
737Q_DECLARE_METATYPE(UnregisteredType)
738
741
742Q_DECLARE_METATYPE(RegisteredNotListType)
743
745Q_UNUSED(object)
746if (property == QStringLiteral("property"))
747 return 42;
749
750static QVariantList dummy(const UnregisteredType &) { return QVariantList{42}; }
751
752QVariant dummyLookup(const QVariant &, const QString &) { return 42; }
753
754void TestGenericTypes::testUnregistered()
755{
756
757 {
758 UnregisteredType unregType;
759 auto v = QVariant::fromValue(unregType);
760
761 auto result = Cutelee::MetaType::lookup(v, QStringLiteral("property"));
762 QVERIFY(!result.isValid());
763
764 QVERIFY(!v.canConvert<QVariantList>());
765 }
766
767 Cutelee::registerMetaType<RegisteredNotListType>();
768
769 {
770 RegisteredNotListType nonListType;
771 auto v = QVariant::fromValue(nonListType);
772 auto result = Cutelee::MetaType::lookup(v, QStringLiteral("property"));
773 QVERIFY(result.isValid());
774 QVERIFY(!v.canConvert<QVariantList>());
775 }
776
777 {
778 QMetaType::registerConverter<UnregisteredType, QVariantList>(&dummy);
779 UnregisteredType unregType;
780 auto v = QVariant::fromValue(unregType);
781 auto result = Cutelee::MetaType::lookup(v, QStringLiteral("property"));
782 QVERIFY(!result.isValid());
783 }
784
785 // Only do this in release mode?
786 // Cutelee::MetaType::registerLookUpOperator(0, dummyLookup);
787 // Cutelee::MetaType::registerToVariantListOperator(0, dummy);
788}
789
790Q_DECLARE_METATYPE(Person *)
791
793if (property == QStringLiteral("name"))
794 return QString::fromStdString(object->name);
795else if (property == QStringLiteral("age"))
796 return object->age;
798
799void TestGenericTypes::testPointerNonQObject()
800{
801 auto p = new Person("Adele", 21);
802 auto v = QVariant::fromValue(p);
803
804 Cutelee::registerMetaType<Person *>();
805
806 auto result = Cutelee::MetaType::lookup(v, QStringLiteral("name"));
807
808 QCOMPARE(result.toString(), QStringLiteral("Adele"));
809
810 delete p;
811}
812
814{
815 Q_GADGET
816 Q_PROPERTY(int fortyTwo READ fortyTwo)
817public:
818 int fortyTwo() { return 42; }
819};
820
821void TestGenericTypes::testQGadget()
822{
823 CustomGadget g;
824 auto v = QVariant::fromValue(g);
825
826 auto result = Cutelee::MetaType::lookup(v, QStringLiteral("fortyTwo"));
827
828 QCOMPARE(result.value<int>(), 42);
829}
830
831void TestGenericTypes::testGadgetMetaType()
832{
833 Cutelee::Engine engine;
834 engine.setPluginPaths({QStringLiteral(CUTELEE_PLUGIN_PATH)});
835
836 auto t1 = engine.newTemplate(
837 QStringLiteral("Person: \nName: {{p.name}}\nAge: {{p.age}}"),
838 QStringLiteral("template1"));
839
840 PersonGadget p;
841 p.m_name = QStringLiteral("Some Name");
843 c.insert(QStringLiteral("p"), QVariant::fromValue(p));
844 QCOMPARE(t1->render(&c),
845 QStringLiteral("Person: \nName: Some Name\nAge: 42"));
846}
847
848class ObjectWithProperties : public QObject
849{
850 Q_OBJECT
851 Q_PROPERTY(QList<int> numberList READ numberList CONSTANT)
852 Q_PROPERTY(QList<CustomGadget> gadgetList READ gadgetList CONSTANT)
853 Q_PROPERTY(QVector<PersonObject *> personList READ personList CONSTANT)
854 Q_PROPERTY(
855 QVector<QSharedPointer<PersonObject>> personPtrList READ personPtrList CONSTANT)
856
857public:
858 ObjectWithProperties(QObject *parent = {}) : QObject(parent)
859 {
860 m_numberList.push_back(42);
861 m_numberList.push_back(7);
862 m_gadgetList.push_back(CustomGadget{});
863 m_gadgetList.push_back(CustomGadget{});
864 m_personList.push_back(new PersonObject{QStringLiteral("Joe"), 20});
865 m_personList.push_back(new PersonObject{QStringLiteral("Mike"), 22});
866 m_personPtrList.push_back(
867 QSharedPointer<PersonObject>(new PersonObject{QStringLiteral("Niall"), 23}));
868 m_personPtrList.push_back(
869 QSharedPointer<PersonObject>(new PersonObject{QStringLiteral("Dave"), 24}));
870 }
871
872 QList<int> numberList() { return m_numberList; }
873 QList<CustomGadget> gadgetList() { return m_gadgetList; }
874 QVector<PersonObject *> personList() { return m_personList; }
875 QVector<QSharedPointer<PersonObject>> personPtrList()
876 {
877 return m_personPtrList;
878 }
879
880private:
881 QList<int> m_numberList;
882 QList<CustomGadget> m_gadgetList;
883 QVector<PersonObject *> m_personList;
884 QVector<QSharedPointer<PersonObject>> m_personPtrList;
885};
886
887void TestGenericTypes::propertyMacroTypes()
888{
889 Cutelee::Engine engine;
890
891 qRegisterMetaType<QList<CustomGadget>>();
892
893 engine.setPluginPaths({QStringLiteral(CUTELEE_PLUGIN_PATH)});
894
895 auto objectWithProperties = new ObjectWithProperties(this);
896
898 c.insert(QStringLiteral("obj"), objectWithProperties);
899
900 {
901 auto t1 = engine.newTemplate(
902 QStringLiteral("{{ obj.numberList.0 }}--{{ obj.numberList.1 }}"),
903 QStringLiteral("template1"));
904
905 auto result = t1->render(&c);
906 auto expectedResult = QStringLiteral("42--7");
907
908 QCOMPARE(result, expectedResult);
909 }
910
911 {
912 auto t1 = engine.newTemplate(
913 QStringLiteral(
914 "{{ obj.gadgetList.0.fortyTwo }}--{{ obj.gadgetList.1.fortyTwo }}"),
915 QStringLiteral("template1"));
916
917 auto result = t1->render(&c);
918 auto expectedResult = QStringLiteral("42--42");
919
920 QCOMPARE(result, expectedResult);
921 }
922
923 {
924 auto t1 = engine.newTemplate(
925 QStringLiteral(
926 "{{ obj.personList.0.name }}({{ obj.personList.0.age }})"
927 "--{{ obj.personList.1.name }}({{ obj.personList.1.age }})"),
928 QStringLiteral("template1"));
929
930 auto result = t1->render(&c);
931 auto expectedResult = QStringLiteral("Joe(20)--Mike(22)");
932
933 QCOMPARE(result, expectedResult);
934 }
935
936 {
937 auto t1 = engine.newTemplate(
938 QStringLiteral(
939 "{{ obj.personPtrList.0.name }}({{ obj.personPtrList.0.age }})"
940 "--{{ obj.personPtrList.1.name }}({{ obj.personPtrList.1.age }})"),
941 QStringLiteral("template1"));
942
943 auto result = t1->render(&c);
944 auto expectedResult = QStringLiteral("Niall(23)--Dave(24)");
945
946 QCOMPARE(result, expectedResult);
947 }
948}
949
950void TestGenericTypes::testJsonTypes()
951{
952 Cutelee::Engine engine;
953 engine.setPluginPaths({QStringLiteral(CUTELEE_PLUGIN_PATH)});
954
956
957 QJsonArray arr;
958 arr.push_back(QJsonObject({
959 {QStringLiteral("name"), QStringLiteral("Joe")},
960 {QStringLiteral("age"), 20}
961 }));
962 QJsonObject obj({
963 {QStringLiteral("name"), QStringLiteral("Mike")},
964 {QStringLiteral("age"), 22}
965 });
966 arr.push_back(obj);
967
968 c.insert(QStringLiteral("arr"), arr);
969 c.insert(QStringLiteral("obj"), obj);
970
971 {
972 auto t = engine.newTemplate(
973 QStringLiteral("{{ arr.count }}"),
974 QStringLiteral("template"));
975
976 auto result = t->render(&c);
977 auto expectedResult = QStringLiteral("2");
978
979 QCOMPARE(result, expectedResult);
980 }
981
982 {
983 auto t = engine.newTemplate(
984 QStringLiteral("{{ arr.1.name }}({{ arr.1.age }})"),
985 QStringLiteral("template"));
986
987 auto result = t->render(&c);
988 auto expectedResult = QStringLiteral("Mike(22)");
989
990 QCOMPARE(result, expectedResult);
991 }
992
993 {
994 auto t = engine.newTemplate(
995 QStringLiteral("{% for person in arr %}{{ person.name }}({{ person.age }})\n{% endfor %}"),
996 QStringLiteral("template"));
997
998 auto result = t->render(&c);
999 auto expectedResult = QStringLiteral("Joe(20)\nMike(22)\n");
1000
1001 QCOMPARE(result, expectedResult);
1002 }
1003
1004 {
1005 auto t = engine.newTemplate(
1006 QStringLiteral("{% for name,age in arr %}{{ name }}({{ age }})\n{% endfor %}"),
1007 QStringLiteral("template"));
1008
1009 auto result = t->render(&c);
1010 auto expectedResult = QStringLiteral("Joe(20)\nMike(22)\n");
1011
1012 QCOMPARE(result, expectedResult);
1013 }
1014
1015 {
1016 auto t = engine.newTemplate(
1017 QStringLiteral("{{ obj.count }}"),
1018 QStringLiteral("template"));
1019
1020 auto result = t->render(&c);
1021 auto expectedResult = QStringLiteral("2");
1022
1023 QCOMPARE(result, expectedResult);
1024 }
1025
1026 {
1027 auto t = engine.newTemplate(
1028 QStringLiteral("{{ obj.name }}({{ obj.age }})"),
1029 QStringLiteral("template"));
1030
1031 auto result = t->render(&c);
1032 auto expectedResult = QStringLiteral("Mike(22)");
1033
1034 QCOMPARE(result, expectedResult);
1035 }
1036
1037 {
1038 auto t = engine.newTemplate(
1039 QStringLiteral("{% for key in obj.keys %}{{ key }}\n{% endfor %}"),
1040 QStringLiteral("template"));
1041
1042 auto result = t->render(&c);
1043
1044 QVERIFY(result == QStringLiteral("name\nage\n") || result == QStringLiteral("age\nname\n"));
1045 }
1046
1047 {
1048 auto t = engine.newTemplate(
1049 QStringLiteral("{% for val in obj.values %}{{ val }}\n{% endfor %}"),
1050 QStringLiteral("template"));
1051
1052 auto result = t->render(&c);
1053
1054 QVERIFY(result == QStringLiteral("Mike\n22\n") || result == QStringLiteral("22\nMike\n"));
1055 }
1056
1057 {
1058 auto t = engine.newTemplate(
1059 QStringLiteral("{% for item in obj.items %}{{ item.0 }}:{{ item.1 }}\n{% endfor %}"),
1060 QStringLiteral("template"));
1061
1062 auto result = t->render(&c);
1063
1064 QVERIFY(result == QStringLiteral("age:22\nname:Mike\n") || result == QStringLiteral("name:Mike\nage:22\n"));
1065 }
1066
1067 QJsonDocument arrDoc(arr);
1068 c.insert(QStringLiteral("arrDoc"), arrDoc);
1069
1070 {
1071 auto t = engine.newTemplate(
1072 QStringLiteral("{{ arrDoc.count }}"),
1073 QStringLiteral("template"));
1074
1075 auto result = t->render(&c);
1076 auto expectedResult = QStringLiteral("2");
1077
1078 QCOMPARE(result, expectedResult);
1079 }
1080
1081 {
1082 auto t = engine.newTemplate(
1083 QStringLiteral("{{ arrDoc.1.name }}({{ arrDoc.1.age }})"),
1084 QStringLiteral("template"));
1085
1086 auto result = t->render(&c);
1087 auto expectedResult = QStringLiteral("Mike(22)");
1088
1089 QCOMPARE(result, expectedResult);
1090 }
1091
1092 {
1093 auto t = engine.newTemplate(
1094 QStringLiteral("{% for person in arrDoc %}{{ person.name }}({{ person.age }})\n{% endfor %}"),
1095 QStringLiteral("template"));
1096
1097 auto result = t->render(&c);
1098 auto expectedResult = QStringLiteral("Joe(20)\nMike(22)\n");
1099
1100 QCOMPARE(result, expectedResult);
1101 }
1102
1103 {
1104 auto t = engine.newTemplate(
1105 QStringLiteral("{% for name,age in arrDoc %}{{ name }}({{ age }})\n{% endfor %}"),
1106 QStringLiteral("template"));
1107
1108 auto result = t->render(&c);
1109 auto expectedResult = QStringLiteral("Joe(20)\nMike(22)\n");
1110
1111 QCOMPARE(result, expectedResult);
1112 }
1113
1114 QJsonDocument objDoc(obj);
1115 c.insert(QStringLiteral("objDoc"), objDoc);
1116
1117 {
1118 auto t = engine.newTemplate(
1119 QStringLiteral("{{ objDoc.count }}"),
1120 QStringLiteral("template"));
1121
1122 auto result = t->render(&c);
1123 auto expectedResult = QStringLiteral("2");
1124
1125 QCOMPARE(result, expectedResult);
1126 }
1127
1128 {
1129 auto t = engine.newTemplate(
1130 QStringLiteral("{{ objDoc.name }}({{ objDoc.age }})"),
1131 QStringLiteral("template"));
1132
1133 auto result = t->render(&c);
1134 auto expectedResult = QStringLiteral("Mike(22)");
1135
1136 QCOMPARE(result, expectedResult);
1137 }
1138
1139 {
1140 auto t = engine.newTemplate(
1141 QStringLiteral("{% for key in objDoc.keys %}{{ key }}\n{% endfor %}"),
1142 QStringLiteral("template"));
1143
1144 auto result = t->render(&c);
1145
1146 QVERIFY(result == QStringLiteral("name\nage\n") || result == QStringLiteral("age\nname\n"));
1147 }
1148
1149 {
1150 auto t = engine.newTemplate(
1151 QStringLiteral("{% for val in objDoc.values %}{{ val }}\n{% endfor %}"),
1152 QStringLiteral("template"));
1153
1154 auto result = t->render(&c);
1155
1156 QVERIFY(result == QStringLiteral("Mike\n22\n") || result == QStringLiteral("22\nMike\n"));
1157 }
1158
1159 {
1160 auto t = engine.newTemplate(
1161 QStringLiteral("{% for item in objDoc.items %}{{ item.0 }}:{{ item.1 }}\n{% endfor %}"),
1162 QStringLiteral("template"));
1163
1164 auto result = t->render(&c);
1165
1166 QVERIFY(result == QStringLiteral("age:22\nname:Mike\n") || result == QStringLiteral("name:Mike\nage:22\n"));
1167 }
1168
1169 QJsonObject emptyObj;
1170 c.insert(QStringLiteral("emptyObj"), emptyObj);
1171
1172 {
1173 auto t = engine.newTemplate(
1174 QStringLiteral("{{ emptyObj.name }}"),
1175 QStringLiteral("template"));
1176
1177 auto result = t->render(&c);
1178 auto expectedResult = QStringLiteral("");
1179
1180 QCOMPARE(result, expectedResult);
1181 }
1182
1183 QJsonArray emptyArr;
1184 c.insert(QStringLiteral("emptyArr"), emptyArr);
1185
1186 {
1187 auto t = engine.newTemplate(
1188 QStringLiteral("{{ emptyArr.1 }}"),
1189 QStringLiteral("template"));
1190
1191 auto result = t->render(&c);
1192 auto expectedResult = QStringLiteral("");
1193
1194 QCOMPARE(result, expectedResult);
1195 }
1196
1197 QJsonDocument emptyDoc;
1198 c.insert(QStringLiteral("emptyDoc"), emptyDoc);
1199
1200 {
1201 auto t = engine.newTemplate(
1202 QStringLiteral("{{ emptyDoc }}"),
1203 QStringLiteral("template"));
1204
1205 auto result = t->render(&c);
1206 auto expectedResult = QStringLiteral("");
1207
1208 QCOMPARE(result, expectedResult);
1209 }
1210
1211 c.insert(QStringLiteral("valBool"), QJsonValue(true));
1212
1213 {
1214 auto t = engine.newTemplate(
1215 QStringLiteral("{{ valBool }}"),
1216 QStringLiteral("template"));
1217
1218 auto result = t->render(&c);
1219 auto expectedResult = QStringLiteral("true");
1220
1221 QCOMPARE(result, expectedResult);
1222 }
1223
1224 c.insert(QStringLiteral("valDouble"), QJsonValue(15));
1225
1226 {
1227 auto t = engine.newTemplate(
1228 QStringLiteral("{{ valDouble }}"),
1229 QStringLiteral("template"));
1230
1231 auto result = t->render(&c);
1232 auto expectedResult = QStringLiteral("15");
1233
1234 QCOMPARE(result, expectedResult);
1235 }
1236
1237 c.insert(QStringLiteral("valString"), QJsonValue(QStringLiteral("Sapere aude")));
1238
1239 {
1240 auto t = engine.newTemplate(
1241 QStringLiteral("{{ valString }}"),
1242 QStringLiteral("template"));
1243
1244 auto result = t->render(&c);
1245 auto expectedResult = QStringLiteral("Sapere aude");
1246
1247 QCOMPARE(result, expectedResult);
1248 }
1249
1250 c.insert(QStringLiteral("valArray"), QJsonValue(arr));
1251
1252 {
1253 auto t = engine.newTemplate(
1254 QStringLiteral("{% for person in valArray %}{{ person.name }}({{ person.age }})\n{% endfor %}"),
1255 QStringLiteral("template"));
1256
1257 auto result = t->render(&c);
1258 auto expectedResult = QStringLiteral("Joe(20)\nMike(22)\n");
1259
1260 QCOMPARE(result, expectedResult);
1261 }
1262
1263 c.insert(QStringLiteral("valObj"), QJsonValue(obj));
1264
1265 {
1266 auto t = engine.newTemplate(
1267 QStringLiteral("{{ valObj.name }}({{ valObj.age }})"),
1268 QStringLiteral("template"));
1269
1270 auto result = t->render(&c);
1271 auto expectedResult = QStringLiteral("Mike(22)");
1272
1273 QCOMPARE(result, expectedResult);
1274 }
1275
1276 c.insert(QStringLiteral("valNull"), QJsonValue());
1277
1278 {
1279 auto t = engine.newTemplate(
1280 QStringLiteral("{{ valNull }}"),
1281 QStringLiteral("template"));
1282
1283 auto result = t->render(&c);
1284 auto expectedResult = QStringLiteral("");
1285
1286 QCOMPARE(result, expectedResult);
1287 }
1288}
1289
1290QTEST_MAIN(TestGenericTypes)
1291#include "testgenerictypes.moc"
The Context class holds the context to render a Template with.
Definition context.h:119
void insert(const QString &name, QObject *object)
Definition context.cpp:145
Cutelee::Engine is the main entry point for creating Cutelee Templates.
Definition engine.h:121
void setPluginPaths(const QStringList &dirs)
Definition engine.cpp:87
Template newTemplate(const QString &content, const QString &name) const
Definition engine.cpp:391
The Template class is a tree of nodes which may be rendered.
Definition template.h:95
QString render(Context *c) const
Definition template.cpp:74
#define CUTELEE_BEGIN_LOOKUP(Type)
Definition metatype.h:213
#define CUTELEE_END_LOOKUP
Definition metatype.h:239
#define CUTELEE_BEGIN_LOOKUP_PTR(Type)
Definition metatype.h:226