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 
48 Q_DECLARE_SEQUENTIAL_CONTAINER_METATYPE(ThreeArray)
49 
50 Q_DECLARE_ASSOCIATIVE_CONTAINER_METATYPE(QtUnorderedMap)
51 
52 Q_DECLARE_SMART_POINTER_METATYPE(std::shared_ptr)
53 
54 Q_DECLARE_SEQUENTIAL_CONTAINER_METATYPE(std::deque)
55 
57 {
58  Q_OBJECT
59 
60 private 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 
86 class Person
87 {
88 public:
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)
107 public:
108  QString m_name;
109  int m_age = 42;
110 };
111 
112 int qHash(const Person &p) { return p.uid; }
113 
114 Q_DECLARE_METATYPE(Person)
115 Q_DECLARE_METATYPE(PersonGadget)
116 
118 if (property == QStringLiteral("name"))
119  return QString::fromStdString(object.name);
120 else if (property == QStringLiteral("age"))
121  return object.age;
123 
125 if (property == QStringLiteral("age"))
126  return object.m_age;
128 
129 class PersonObject : public QObject
130 {
131  Q_OBJECT
132  Q_PROPERTY(QString name READ name CONSTANT)
133  Q_PROPERTY(int age READ age CONSTANT)
134 public:
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 
143 private:
144  const QString m_name;
145  const int m_age; // Yeah, you wish...
146 };
147 
148 void TestGenericTypes::initTestCase()
149 {
150  // Register the handler for our custom type
151  Cutelee::registerMetaType<Person>();
152  Cutelee::registerMetaType<PersonGadget>();
153 }
154 
155 void 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 
175 static 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 
184 template <typename SequentialContainer>
185 void 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 
196 template <typename AssociativeContainer>
197 void 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 
209 template <>
210 void insertPeopleVariants<QMap<QString, QVariant>>(Cutelee::Context &c)
211 {
212  insertAssociatedPeopleVariants<QMap<QString, QVariant>>(c);
213 }
214 
215 template <>
216 void insertPeopleVariants<QHash<QString, QVariant>>(Cutelee::Context &c)
217 {
218  insertAssociatedPeopleVariants<QHash<QString, QVariant>>(c);
219 }
220 
221 template <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 
236 template <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 
251 template <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 
263 template <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)
288 template <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 
298 template <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 
307 template <typename Container> void doTestSequentialContainer_Variant()
308 {
310 
311  insertPeopleVariants<Container>(c);
312 
315 }
316 
317 template <typename Container>
318 void 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 
344 template <typename Container>
345 void 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 
370 template <typename Container>
371 void 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 
384 void 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 
395 void TestGenericTypes::testAssociativeContainer_Variant()
396 {
397  doTestAssociativeContainer_Variant<QVariantMap>();
398  doTestAssociativeContainer_Variant<QVariantHash>(true);
399 }
400 
401 template <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 
412 template <> 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 
423 template <> 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 
435 template <typename AssociativeContainer>
436 void 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 
447 template <typename AssociativeContainer>
448 void 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 
459 template <typename Container> void doTestSequentialContainer_Type()
460 {
462 
463  insertPeople<Container>(c);
464 
467 }
468 
469 template <typename Container>
470 void 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 
483 template <typename Container>
484 void 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 
505 void 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 
521 void 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 
555 void 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 
573 void 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 
591 typedef QList<QVector<qint16>> ListVectorInt;
592 typedef QMap<int, QList<QVector<qint16>>> MapListVectorInt;
593 typedef QStack<QMap<int, QList<QVector<qint16>>>> StackMapListVectorInt;
594 
595 static 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 
604 static ListVectorInt getNumberLists()
605 {
606  ListVectorInt list;
607  for (auto i = 0; i < 2; ++i) {
608  list.append(getNumbers());
609  }
610  return list;
611 }
612 
613 static 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 
622 static StackMapListVectorInt getMapStack()
623 {
624  StackMapListVectorInt stack;
625  for (auto i = 0; i < 2; ++i) {
626  stack.push(getNumberListMap());
627  }
628  return stack;
629 }
630 
631 void 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 
672 class CustomObject : public QObject
673 {
674  Q_OBJECT
675 public:
676  explicit CustomObject(QObject *parent = {}) : QObject(parent) {}
677 };
678 
679 class OtherObject : public QObject
680 {
681  Q_OBJECT
682  Q_PROPERTY(CustomObject *custom READ custom CONSTANT)
683 public:
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 
692 private:
693  CustomObject *m_custom;
694 };
695 
696 void 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 
737 Q_DECLARE_METATYPE(UnregisteredType)
738 
740 };
741 
742 Q_DECLARE_METATYPE(RegisteredNotListType)
743 
745 Q_UNUSED(object)
746 if (property == QStringLiteral("property"))
747  return 42;
749 
750 static QVariantList dummy(const UnregisteredType &) { return QVariantList{42}; }
751 
752 QVariant dummyLookup(const QVariant &, const QString &) { return 42; }
753 
754 void 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 
790 Q_DECLARE_METATYPE(Person *)
791 
793 if (property == QStringLiteral("name"))
794  return QString::fromStdString(object->name);
795 else if (property == QStringLiteral("age"))
796  return object->age;
798 
799 void 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)
817 public:
818  int fortyTwo() { return 42; }
819 };
820 
821 void 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 
831 void 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 
848 class 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 
857 public:
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 
880 private:
881  QList<int> m_numberList;
882  QList<CustomGadget> m_gadgetList;
883  QVector<PersonObject *> m_personList;
884  QVector<QSharedPointer<PersonObject>> m_personPtrList;
885 };
886 
887 void 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 
950 void 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 
1290 QTEST_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