Cutelee  6.1.0
testbuiltins.cpp
1 /*
2  This file is part of the Cutelee template system.
3 
4  Copyright (c) 2009,2010 Stephen Kelly <steveire@gmail.com>
5 
6  This library is free software; you can redistribute it and/or
7  modify it under the terms of the GNU Lesser General Public
8  License as published by the Free Software Foundation; either version
9  2.1 of the Licence, or (at your option) any later version.
10 
11  This library is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  Lesser General Public License for more details.
15 
16  You should have received a copy of the GNU Lesser General Public
17  License along with this library. If not, see <http://www.gnu.org/licenses/>.
18 
19 */
20 
21 #ifndef BUILTINSTEST_H
22 #define BUILTINSTEST_H
23 
24 #include <QtCore/QDebug>
25 #include <QtCore/QFileInfo>
26 #include <QtTest/QTest>
27 
28 #include "cachingloaderdecorator.h"
29 #include "context.h"
30 #include "coverageobject.h"
31 #include "engine.h"
32 #include "filterexpression.h"
33 #include "cutelee_paths.h"
34 #include "template.h"
35 #include "util.h"
36 #include <metaenumvariable_p.h>
37 
39 
40 Q_DECLARE_METATYPE(Cutelee::Error)
41 
42 using namespace Cutelee;
43 
47 class OtherClass : public QObject
48 {
49  Q_OBJECT
50  Q_PROPERTY(QString method READ method CONSTANT)
51  Q_PROPERTY(Animals animals READ animals CONSTANT)
52 public:
53  enum Animals { Lions, Tigers, Bears };
54  Q_ENUM(Animals)
55 
56  OtherClass(QObject *parent = {}) : QObject(parent) {}
57  OtherClass(Animals animals, QObject *parent = nullptr) : QObject(parent), m_animals(animals) {}
58 
59  Animals animals() const { return m_animals; }
60 
61  QString method() const { return QStringLiteral("OtherClass::method"); }
62 
63 private:
64  Animals m_animals = Tigers;
65 };
66 
70 class SomeClass : public QObject
71 {
72  Q_OBJECT
73  Q_PROPERTY(QString method READ method CONSTANT)
74  Q_PROPERTY(QVariant otherClass READ otherClass CONSTANT)
75 
76 public:
77  enum FirstEnum { Employee, Employer, Manager };
78  Q_ENUM(FirstEnum)
79 
80  enum SecondEnum { Voter = 2, Consumer = 4, Citizen = 8 };
81  Q_ENUM(SecondEnum)
82 
83  SomeClass(QObject *parent = {})
84  : QObject(parent), m_other(new OtherClass(this))
85  {
86  }
87 
88  QString method() const { return QStringLiteral("SomeClass::method"); }
89 
90  QVariant otherClass() const { return QVariant::fromValue(m_other); }
91 
92  QString nonAccessibleMethod(const QString &str) { return str; }
93 
94 private:
95  QObject *m_other;
96 };
97 
102 {
103  Q_GADGET
104  Q_PROPERTY(PersonName personName READ personName)
105 public:
106  enum PersonName { Mike = 0, Natalie, Oliver };
107  Q_ENUM(PersonName)
108 
109  GadgetClass() {}
110  GadgetClass(PersonName pn) : m_personName(pn) {}
111 
112  PersonName personName() const { return m_personName; }
113 private:
114  PersonName m_personName = Oliver;
115 };
116 Q_DECLARE_METATYPE(GadgetClass)
117 
119 {
120 public:
122 
123  NoEscapeOutputStream(QTextStream *stream) : OutputStream(stream) {}
124 
125  std::shared_ptr<OutputStream> clone(QTextStream *stream) const override
126  {
127  return std::shared_ptr<NoEscapeOutputStream>(new NoEscapeOutputStream{stream});
128  }
129 
130  QString escape(const QString &input) const override { return input; }
131 };
132 
134 {
135 public:
137 
138  JSOutputStream(QTextStream *stream) : OutputStream(stream) {}
139 
140  std::shared_ptr<OutputStream> clone(QTextStream *stream) const override
141  {
142  return std::shared_ptr<JSOutputStream>(new JSOutputStream{stream});
143  }
144 
145  QString escape(const QString &input) const override
146  {
148  jsEscapes << std::pair<QString, QString>(QChar::fromLatin1('\\'),
149  QStringLiteral("\\u005C"))
150  << std::pair<QString, QString>(QChar::fromLatin1('\''),
151  QStringLiteral("\\u0027"))
152  << std::pair<QString, QString>(QChar::fromLatin1('\"'),
153  QStringLiteral("\\u0022"))
154  << std::pair<QString, QString>(QChar::fromLatin1('>'),
155  QStringLiteral("\\u003E"))
156  << std::pair<QString, QString>(QChar::fromLatin1('<'),
157  QStringLiteral("\\u003C"))
158  << std::pair<QString, QString>(QChar::fromLatin1('&'),
159  QStringLiteral("\\u0026"))
160  << std::pair<QString, QString>(QChar::fromLatin1('='),
161  QStringLiteral("\\u003D"))
162  << std::pair<QString, QString>(QChar::fromLatin1('-'),
163  QStringLiteral("\\u002D"))
164  << std::pair<QString, QString>(QChar::fromLatin1(';'),
165  QStringLiteral("\\u003B"))
166  << std::pair<QString, QString>(QChar(0x2028),
167  QStringLiteral("\\u2028"))
168  << std::pair<QString, QString>(QChar(0x2029),
169  QStringLiteral("\\u2029"));
170 
171  for (auto i = 0; i < 32; ++i) {
172  jsEscapes << std::pair<QString, QString>(
173  QChar(i),
174  QStringLiteral("\\u00")
175  + QStringLiteral("%1").arg(i, 2, 16, QLatin1Char('0')).toUpper());
176  }
177 
178  auto retString = input;
179  for (const std::pair<QString, QString> &escape : jsEscapes) {
180  retString = retString.replace(escape.first, escape.second);
181  }
182  return retString;
183  }
184 };
185 
187 {
188  Q_OBJECT
189 
190 private Q_SLOTS:
191  void initTestCase();
192 
193  void testObjects();
194 
195  void testTruthiness_data();
196  void testTruthiness();
197 
198  void testRenderAfterError();
199 
200  void testBasicSyntax_data();
201  void testBasicSyntax() { doTest(); }
202 
203  void testEnums_data();
204  void testEnums() { doTest(); }
205 
206  void testListIndex_data();
207  void testListIndex() { doTest(); }
208 
209  void testFilterSyntax_data();
210  void testFilterSyntax() { doTest(); }
211 
212  void testCommentSyntax_data();
213  void testCommentSyntax() { doTest(); }
214 
215  void testMultiline_data();
216  void testMultiline() { doTest(); }
217 
218  void testEscaping_data();
219  void testEscaping() { doTest(); }
220 
221  void testTypeAccessors_data();
222  void testTypeAccessors() { doTest(); }
223  void testTypeAccessorsUnordered_data();
224  void testTypeAccessorsUnordered();
225 
226  void testMultipleStates();
227  void testAlternativeEscaping();
228 
229  void testTemplatePathSafety_data();
230  void testTemplatePathSafety();
231 
232  void testMediaPathSafety_data();
233  void testMediaPathSafety();
234 
235  void testDynamicProperties_data();
236  void testDynamicProperties() { doTest(); }
237 
238  void testGarbageInput_data();
239  void testGarbageInput();
240 
241  void testInsignificantWhitespace_data();
242  void testInsignificantWhitespace();
243 
244  void cleanupTestCase();
245 
246 private:
247  Engine *m_engine;
248 
249  std::shared_ptr<InMemoryTemplateLoader> m_loader;
250 
251  Engine *getEngine();
252 
253  void doTest();
254 };
255 
256 void TestBuiltinSyntax::testObjects()
257 {
258  {
259  auto loader = std::shared_ptr<Cutelee::FileSystemTemplateLoader>(new Cutelee::FileSystemTemplateLoader);
260  loader->setTemplateDirs(
261  {QStringLiteral("/path/one"), QStringLiteral("/path/two")});
262 
263  auto cache
264  = std::shared_ptr<Cutelee::CachingLoaderDecorator>(new Cutelee::CachingLoaderDecorator{loader});
265  }
266 
267  Context c1, c2;
268  c1 = c1;
269  c1 = c2;
270  Context c3(c1);
271  Q_UNUSED(c3);
272 
273  FilterExpression f1, f2;
274  f1 = f1;
275  f1 = f2;
276  FilterExpression f3(f1);
277  Q_UNUSED(f3);
278 
279  Variable v1;
280  v1 = v1;
281  v1 = f1.variable();
282  Variable v3(v1);
283  Q_UNUSED(v3);
284  QVERIFY(!v1.isTrue(&c1));
285  QVERIFY(!v1.isLocalized());
286 
287  c1.setMutating(true);
288  QVERIFY(c1.isMutating());
289 
290  SafeString s1, s2;
291  s1 = s1;
292  s2 = s1;
293  SafeString s3(s1);
294  Q_UNUSED(s3);
295 
296 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
297  QMetaType::construct(qMetaTypeId<MetaEnumVariable>(), 0, 0);
298 #else
299  QMetaType(qMetaTypeId<MetaEnumVariable>()).construct(0, 0);
300 #endif
301 }
302 
303 void TestBuiltinSyntax::testTruthiness_data()
304 {
305  QTest::addColumn<QVariant>("input");
306  QTest::addColumn<bool>("expected");
307 
308  QTest::newRow("truthtest-01") << QVariant() << false;
309  QTest::newRow("truthtest-02") << QVariant(false) << false;
310  QTest::newRow("truthtest-03") << QVariant(true) << true;
311 
312  QTest::newRow("truthtest-04") << QVariant(0) << false;
313  QTest::newRow("truthtest-05") << QVariant(1) << true;
314 
315  {
316  auto falseV = QVariant::fromValue<int>(0);
317  QTest::newRow("truthtest-06") << falseV << false;
318  auto trueV = QVariant::fromValue<int>(1);
319  QTest::newRow("truthtest-07") << trueV << true;
320  }
321  {
322  auto falseV = QVariant::fromValue<uint>(0);
323  QTest::newRow("truthtest-08") << falseV << false;
324  auto trueV = QVariant::fromValue<uint>(1);
325  QTest::newRow("truthtest-09") << trueV << true;
326  }
327  {
328  auto falseV = QVariant::fromValue<qlonglong>(0);
329  QTest::newRow("truthtest-10") << falseV << false;
330  auto trueV = QVariant::fromValue<qlonglong>(1);
331  QTest::newRow("truthtest-11") << trueV << true;
332  }
333  {
334  auto falseV = QVariant::fromValue<qulonglong>(0);
335  QTest::newRow("truthtest-12") << falseV << false;
336  auto trueV = QVariant::fromValue<qulonglong>(1);
337  QTest::newRow("truthtest-13") << trueV << true;
338  }
339  {
340  auto falseV = QVariant::fromValue<double>(0);
341  QTest::newRow("truthtest-14") << falseV << false;
342  auto trueV = QVariant::fromValue<double>(1);
343  QTest::newRow("truthtest-15") << trueV << true;
344  }
345  {
346  auto falseV = QVariant::fromValue<float>(0);
347  QTest::newRow("truthtest-16") << falseV << false;
348  auto trueV = QVariant::fromValue<float>(1);
349  QTest::newRow("truthtest-17") << trueV << true;
350  }
351  {
352  auto falseV = QVariant::fromValue<char>(0);
353  QTest::newRow("truthtest-18") << falseV << false;
354  auto trueV = QVariant::fromValue<char>(1);
355  QTest::newRow("truthtest-19") << trueV << true;
356  }
357 
358  QTest::newRow("truthtest-20") << QVariant::fromValue(QString()) << false;
359  QTest::newRow("truthtest-21")
360  << QVariant::fromValue(QStringLiteral("")) << false;
361  QTest::newRow("truthtest-22")
362  << QVariant::fromValue(QStringLiteral("false")) << true;
363  QTest::newRow("truthtest-23")
364  << QVariant::fromValue(QStringLiteral("true")) << true;
365  QTest::newRow("truthtest-24")
366  << QVariant::fromValue(QStringLiteral("anystring")) << true;
367 
368  {
369  QVariantList l;
370  QTest::newRow("truthtest-25") << QVariant::fromValue(l) << false;
371  l.append(1);
372  QTest::newRow("truthtest-26") << QVariant::fromValue(l) << true;
373  }
374  {
375  QVariantHash h;
376  QTest::newRow("truthtest-27") << QVariant::fromValue(h) << false;
377  h.insert(QStringLiteral("value"), 1);
378  QTest::newRow("truthtest-28") << QVariant::fromValue(h) << true;
379  }
380  {
381  QVariantMap m;
382  QTest::newRow("truthtest-29") << QVariant::fromValue(m) << false;
383  m.insert(QStringLiteral("value"), 1);
384  QTest::newRow("truthtest-30") << QVariant::fromValue(m) << true;
385  }
386 
387  {
388  QTest::newRow("truthtest-31")
389  << QVariant::fromValue<QObject *>(nullptr) << false;
390  auto plainO = new QObject(this);
391  QTest::newRow("truthtest-32") << QVariant::fromValue(plainO) << true;
392  auto trueO = new QObject(this);
393  trueO->setProperty("__true__", true);
394  QTest::newRow("truthtest-33") << QVariant::fromValue(trueO) << true;
395  auto falseO = new QObject(this);
396  falseO->setProperty("__true__", false);
397  QTest::newRow("truthtest-34") << QVariant::fromValue(falseO) << false;
398  }
399 }
400 
401 void TestBuiltinSyntax::testTruthiness()
402 {
403  QFETCH(QVariant, input);
404  QFETCH(bool, expected);
405 
406  QVERIFY(variantIsTrue(input) == expected);
407 }
408 
409 void TestBuiltinSyntax::testRenderAfterError()
410 {
411 
412  Engine engine;
413  engine.setPluginPaths({QStringLiteral(CUTELEE_PLUGIN_PATH)});
414 
415  std::shared_ptr<InMemoryTemplateLoader> loader(new InMemoryTemplateLoader);
416  loader->setTemplate(QStringLiteral("template1"),
417  QStringLiteral("This template has an error {{ va>r }}"));
418  loader->setTemplate(QStringLiteral("template2"), QStringLiteral("Ok"));
419  loader->setTemplate(QStringLiteral("main"),
420  QStringLiteral("{% include template_var %}"));
421 
422  engine.addTemplateLoader(loader);
423 
424  Context c;
425  Template t;
426 
427  t = engine.loadByName(QStringLiteral("main"));
428 
429  c.insert(QStringLiteral("template_var"), QLatin1String("template1"));
430  auto output = t->render(&c);
431  QCOMPARE(output, QString());
432  QCOMPARE(t->error(), TagSyntaxError);
433 
434  c.insert(QStringLiteral("template_var"), QLatin1String("template2"));
435  QCOMPARE(t->render(&c), QLatin1String("Ok"));
436  QCOMPARE(t->error(), NoError);
437 }
438 
439 void TestBuiltinSyntax::initTestCase()
440 {
441  m_engine = getEngine();
442  m_loader
443  = std::shared_ptr<InMemoryTemplateLoader>(new InMemoryTemplateLoader());
444  m_engine->addTemplateLoader(m_loader);
445  QVERIFY(m_engine->templateLoaders().contains(m_loader));
446 }
447 
448 Engine *TestBuiltinSyntax::getEngine()
449 {
450  auto engine = new Engine(this);
451  engine->setPluginPaths({QStringLiteral(CUTELEE_PLUGIN_PATH)});
452  return engine;
453 }
454 
455 void TestBuiltinSyntax::cleanupTestCase() { delete m_engine; }
456 
457 void TestBuiltinSyntax::doTest()
458 {
459  QFETCH(QString, input);
460  QFETCH(Dict, dict);
461  QFETCH(QString, output);
462  QFETCH(Cutelee::Error, error);
463 
464  auto t = m_engine->newTemplate(input, QLatin1String(QTest::currentDataTag()));
465 
466  if (t->error() != NoError) {
467  if (t->error() != error)
468  qDebug() << t->errorString();
469  QCOMPARE(t->error(), error);
470  return;
471  }
472 
473  Context context(dict);
474 
475  auto result = t->render(&context);
476  if (t->error() != NoError) {
477  if (t->error() != error)
478  qDebug() << t->errorString();
479  QCOMPARE(t->error(), error);
480  return;
481  }
482 
483  QCOMPARE(t->error(), NoError);
484 
485  // Didn't catch any errors, so make sure I didn't expect any.
486  QCOMPARE(NoError, error);
487 
488  QCOMPARE(result, output);
489 }
490 
491 void TestBuiltinSyntax::testBasicSyntax_data()
492 {
493  QTest::addColumn<QString>("input");
494  QTest::addColumn<Dict>("dict");
495  QTest::addColumn<QString>("output");
496  QTest::addColumn<Cutelee::Error>("error");
497 
498  Dict dict;
499 
500  QTest::newRow("basic-syntax00") << QString() << dict << QString() << NoError;
501 
502  // Plain text should go through the template parser untouched
503  QTest::newRow("basic-syntax01")
504  << QStringLiteral("something cool") << dict
505  << QStringLiteral("something cool") << NoError;
506 
507  // Variables should be replaced with their value in the current
508  // context
509  dict.insert(QStringLiteral("headline"), QStringLiteral("Success"));
510  QTest::newRow("basic-syntax02") << QStringLiteral("{{ headline }}") << dict
511  << QStringLiteral("Success") << NoError;
512 
513  dict.clear();
514  dict.insert(QStringLiteral("first"), 1);
515  dict.insert(QStringLiteral("second"), 2);
516 
517  // More than one replacement variable is allowed in a template
518  QTest::newRow("basic-syntax03")
519  << QStringLiteral("{{ first }} --- {{ second }}") << dict
520  << QStringLiteral("1 --- 2") << NoError;
521 
522  dict.clear();
523  // Fail silently when a variable is not found in the current context
524  QTest::newRow("basic-syntax04") << QStringLiteral("as{{ missing }}df") << dict
525  << QStringLiteral("asdf") << NoError;
526 
527  // A variable may not contain more than one word
528  QTest::newRow("basic-syntax06") << QStringLiteral("{{ multi word variable }}")
529  << dict << QString() << TagSyntaxError;
530  // Raise TemplateSyntaxError for empty variable tags
531  QTest::newRow("basic-syntax07")
532  << QStringLiteral("{{ }}") << dict << QString() << EmptyVariableError;
533  QTest::newRow("basic-syntax08") << QStringLiteral("{{ }}") << dict
534  << QString() << EmptyVariableError;
535 
536  // Attribute syntax allows a template to call an object's attribute
537 
538  auto someClass = new SomeClass(this);
539  dict.insert(QStringLiteral("var"), QVariant::fromValue(someClass));
540 
541  QTest::newRow("basic-syntax09")
542  << QStringLiteral("{{ var.method }}") << dict
543  << QStringLiteral("SomeClass::method") << NoError;
544 
545  // Multiple levels of attribute access are allowed
546  QTest::newRow("basic-syntax10")
547  << QStringLiteral("{{ var.otherClass.method }}") << dict
548  << QStringLiteral("OtherClass::method") << NoError;
549 
550  // Fail silently when a variable's attribute isn't found
551  QTest::newRow("basic-syntax11")
552  << QStringLiteral("{{ var.blech }}") << dict << QString() << NoError;
553 
554  // TODO: Needed?
555  // Raise TemplateSyntaxError when trying to access a variable beginning with
556  // an underscore
557  // #C# {"var": SomeClass()}
558  dict.clear();
559  QVariantHash hash;
560  hash.insert(QStringLiteral("__dict__"), QStringLiteral("foo"));
561  dict.insert(QStringLiteral("var"), hash);
562  QTest::newRow("basic-syntax12") << QStringLiteral("{{ var.__dict__ }}")
563  << dict << QString() << TagSyntaxError;
564 
565  dict.clear();
566  // Raise TemplateSyntaxError when trying to access a variable containing an
567  // illegal character
568  QTest::newRow("basic-syntax13")
569  << QStringLiteral("{{ va>r }}") << dict << QString() << TagSyntaxError;
570  QTest::newRow("basic-syntax14")
571  << QStringLiteral("{{ (var.r) }}") << dict << QString() << TagSyntaxError;
572  QTest::newRow("basic-syntax15")
573  << QStringLiteral("{{ sp%am }}") << dict << QString() << TagSyntaxError;
574  QTest::newRow("basic-syntax16")
575  << QStringLiteral("{{ eggs! }}") << dict << QString() << TagSyntaxError;
576  QTest::newRow("basic-syntax17")
577  << QStringLiteral("{{ moo? }}") << dict << QString() << TagSyntaxError;
578  QTest::newRow("basic-syntax-error01")
579  << QStringLiteral("{{ moo:arg }}") << dict << QString() << TagSyntaxError;
580  QTest::newRow("basic-syntax-error02")
581  << QStringLiteral("{{ moo|cut:'foo':'bar' }}") << dict << QString()
582  << TagSyntaxError;
583 
584  // Attribute syntax allows a template to call a dictionary key's value
585 
586  hash.clear();
587  hash.insert(QStringLiteral("bar"), QStringLiteral("baz"));
588  dict.insert(QStringLiteral("foo"), hash);
589  QTest::newRow("basic-syntax18a") << QStringLiteral("{{ foo.bar }}") << dict
590  << QStringLiteral("baz") << NoError;
591 
592  // Fail silently when a variable's dictionary key isn't found
593  QTest::newRow("basic-syntax19a")
594  << QStringLiteral("{{ foo.spam }}") << dict << QString() << NoError;
595 
596  QVariantMap map;
597  map.insert(QStringLiteral("bar"), QStringLiteral("baz"));
598  dict.insert(QStringLiteral("foo"), map);
599  QTest::newRow("basic-syntax18b") << QStringLiteral("{{ foo.bar }}") << dict
600  << QStringLiteral("baz") << NoError;
601 
602  // Fail silently when a variable's dictionary key isn't found
603  QTest::newRow("basic-syntax19a")
604  << QStringLiteral("{{ foo.spam }}") << dict << QString() << NoError;
605 
606  dict.clear();
607  dict.insert(QStringLiteral("var"), QVariant::fromValue(someClass));
608  // Fail silently when attempting to access an unavailable method
609  QTest::newRow("basic-syntax20")
610  << QStringLiteral("{{ var.nonAccessibleMethod }}") << dict << QString()
611  << NoError;
612 
613  dict.clear();
614  // Don't get confused when parsing something that is almost, but not
615  // quite, a template tag.
616  QTest::newRow("basic-syntax21") << QStringLiteral("a {{ moo %} b") << dict
617  << QStringLiteral("a {{ moo %} b") << NoError;
618  QTest::newRow("basic-syntax22") << QStringLiteral("{{ moo #}") << dict
619  << QStringLiteral("{{ moo #}") << NoError;
620 
621  dict.insert(QStringLiteral("cow"), QStringLiteral("cow"));
622  // Will try to treat "moo #} {{ cow" as the variable. Not ideal, but
623  // costly to work around, so this triggers an error.
624  QTest::newRow("basic-syntax23") << QStringLiteral("{{ moo #} {{ cow }}")
625  << dict << QString() << TagSyntaxError;
626 
627  dict.clear();
628  // Embedded newlines make it not-a-tag.
629  QTest::newRow("basic-syntax24")
630  << "{{ moo\n }}" << dict << "{{ moo\n }}" << NoError;
631  // Literal strings are permitted inside variables, mostly for i18n
632  // purposes.
633  QTest::newRow("basic-syntax25")
634  << "{{ \"fred\" }}" << dict << QString::fromLatin1( "fred" ) << NoError;
635  QTest::newRow("basic-syntax26")
636  << "{{ \"\\\"fred\\\"\" }}" << dict << "\"fred\"" << NoError;
637  QTest::newRow("basic-syntax27")
638  << "{{ _(\"\\\"fred\\\"\") }}" << dict << "&quot;fred&quot;" << NoError;
639 
640  dict.clear();
641  hash.clear();
642  QVariantHash innerHash;
643  innerHash.insert(QStringLiteral("3"), QStringLiteral("d"));
644  hash.insert(QStringLiteral("2"), innerHash);
645  dict.insert(QStringLiteral("1"), hash);
646 
647  QTest::newRow("basic-syntax28") << QStringLiteral("{{ 1.2.3 }}") << dict
648  << QStringLiteral("d") << NoError;
649 
650  dict.clear();
651  hash.clear();
652  QVariantList list{QStringLiteral("a"), QStringLiteral("b"),
653  QStringLiteral("c"), QStringLiteral("d")};
654  hash.insert(QStringLiteral("2"), list);
655  dict.insert(QStringLiteral("1"), hash);
656  QTest::newRow("basic-syntax29") << QStringLiteral("{{ 1.2.3 }}") << dict
657  << QStringLiteral("d") << NoError;
658 
659  dict.clear();
660  list.clear();
661  QVariantList innerList{QStringLiteral("x"), QStringLiteral("x"),
662  QStringLiteral("x"), QStringLiteral("x")};
663  list.append(QVariant(innerList));
664  innerList = {QStringLiteral("y"), QStringLiteral("y"), QStringLiteral("y"),
665  QStringLiteral("y")};
666  list.append(QVariant(innerList));
667  innerList = {QStringLiteral("a"), QStringLiteral("b"), QStringLiteral("c"),
668  QStringLiteral("d")};
669  list.append(QVariant(innerList));
670  dict.insert(QStringLiteral("1"), list);
671 
672  QTest::newRow("basic-syntax30") << QStringLiteral("{{ 1.2.3 }}") << dict
673  << QStringLiteral("d") << NoError;
674 
675  dict.clear();
676  list.clear();
677  innerList = {QStringLiteral("x"), QStringLiteral("x"), QStringLiteral("x"),
678  QStringLiteral("x")};
679  list.append(QVariant(innerList));
680  innerList = {QStringLiteral("y"), QStringLiteral("y"), QStringLiteral("y"),
681  QStringLiteral("y")};
682  list.append(QVariant(innerList));
683  innerList = {QStringLiteral("a"), QStringLiteral("b"), QStringLiteral("c"),
684  QStringLiteral("d")};
685  list.append(QVariant(innerList));
686  dict.insert(QStringLiteral("1"), list);
687 
688  QTest::newRow("basic-syntax31") << QStringLiteral("{{ 1.2.3 }}") << dict
689  << QStringLiteral("d") << NoError;
690 
691  dict.clear();
692  list.clear();
693  hash.clear();
694  hash.insert(QStringLiteral("x"), QStringLiteral("x"));
695  list.append(hash);
696  hash.clear();
697  hash.insert(QStringLiteral("y"), QStringLiteral("y"));
698  list.append(hash);
699  hash.clear();
700  hash.insert(QStringLiteral("3"), QStringLiteral("d"));
701  list.append(hash);
702 
703  dict.insert(QStringLiteral("1"), list);
704 
705  QTest::newRow("basic-syntax32") << QStringLiteral("{{ 1.2.3 }}") << dict
706  << QStringLiteral("d") << NoError;
707 
708  dict.clear();
709 
710  dict.insert(QStringLiteral("1"), QStringLiteral("abc"));
711  QTest::newRow("basic-syntax33")
712  << QStringLiteral("{{ 1 }}") << dict << QStringLiteral("1") << NoError;
713  QTest::newRow("basic-syntax34") << QStringLiteral("{{ 1.2 }}") << dict
714  << QStringLiteral("1.2") << NoError;
715 
716  dict.clear();
717 
718  dict.insert(QStringLiteral("abc"), QStringLiteral("def"));
719 
720  QTest::newRow("basic-syntax35")
721  << QStringLiteral("{{ abc._something }} {{ abc._something|upper }}")
722  << dict << QString() << TagSyntaxError;
723 
724  QTest::newRow("basic-syntax36")
725  << "{{ \"fred }}" << dict << QString() << TagSyntaxError;
726  QTest::newRow("basic-syntax37")
727  << "{{ \'fred }}" << dict << QString() << TagSyntaxError;
728  QTest::newRow("basic-syntax38")
729  << "{{ \"fred\' }}" << dict << QString() << TagSyntaxError;
730  QTest::newRow("basic-syntax39")
731  << "{{ \'fred\" }}" << dict << QString() << TagSyntaxError;
732  QTest::newRow("basic-syntax40")
733  << "{{ _(\'fred }}" << dict << QString() << TagSyntaxError;
734  QTest::newRow("basic-syntax41")
735  << "{{ abc|removetags:_(\'fred }}" << dict << QString() << TagSyntaxError;
736 }
737 
738 void TestBuiltinSyntax::testEnums_data()
739 {
740  QTest::addColumn<QString>("input");
741  QTest::addColumn<Dict>("dict");
742  QTest::addColumn<QString>("output");
743  QTest::addColumn<Cutelee::Error>("error");
744 
745  Dict dict;
746 
747  auto otherClass = new OtherClass(this);
748  dict.insert(QStringLiteral("var"), QVariant::fromValue(otherClass));
749 
750  QTest::newRow("class-enums01") << QStringLiteral("{{ var.Lions }}") << dict
751  << QStringLiteral("0") << NoError;
752  QTest::newRow("class-enums02") << QStringLiteral("{{ var.Tigers }}") << dict
753  << QStringLiteral("1") << NoError;
754  QTest::newRow("class-enums03") << QStringLiteral("{{ var.Bears }}") << dict
755  << QStringLiteral("2") << NoError;
756  QTest::newRow("class-enums04")
757  << QStringLiteral("{{ var.Hamsters }}") << dict << QString() << NoError;
758  QTest::newRow("class-enums05")
759  << QStringLiteral("{{ var.Tigers.name }}") << dict
760  << QStringLiteral("Animals") << NoError;
761  QTest::newRow("class-enums06")
762  << QStringLiteral("{{ var.Tigers.scope }}") << dict
763  << QStringLiteral("OtherClass") << NoError;
764  QTest::newRow("class-enums07") << QStringLiteral("{{ var.Tigers.value }}")
765  << dict << QStringLiteral("1") << NoError;
766  QTest::newRow("class-enums08") << QStringLiteral("{{ var.Tigers.key }}")
767  << dict << QStringLiteral("Tigers") << NoError;
768  QTest::newRow("class-enums09") << QStringLiteral("{{ var.animals }}") << dict
769  << QStringLiteral("1") << NoError;
770  QTest::newRow("class-enums10")
771  << QStringLiteral("{{ var.animals.name }}") << dict
772  << QStringLiteral("Animals") << NoError;
773  QTest::newRow("class-enums11")
774  << QStringLiteral("{{ var.animals.scope }}") << dict
775  << QStringLiteral("OtherClass") << NoError;
776  QTest::newRow("class-enums12") << QStringLiteral("{{ var.animals.value }}")
777  << dict << QStringLiteral("1") << NoError;
778  QTest::newRow("class-enums13") << QStringLiteral("{{ var.animals.key }}")
779  << dict << QStringLiteral("Tigers") << NoError;
780  QTest::newRow("class-enums14") << QStringLiteral("{{ var.Animals.0 }}")
781  << dict << QStringLiteral("0") << NoError;
782  QTest::newRow("class-enums15") << QStringLiteral("{{ var.Animals.2 }}")
783  << dict << QStringLiteral("2") << NoError;
784  QTest::newRow("class-enums16")
785  << QStringLiteral("{{ var.Animals.3 }}") << dict << QString() << NoError;
786  QTest::newRow("class-enums17")
787  << QStringLiteral("{{ var.Animals.0.name }}") << dict
788  << QStringLiteral("Animals") << NoError;
789  QTest::newRow("class-enums18")
790  << QStringLiteral("{{ var.Animals.0.scope }}") << dict
791  << QStringLiteral("OtherClass") << NoError;
792  QTest::newRow("class-enums19") << QStringLiteral("{{ var.Animals.0.value }}")
793  << dict << QStringLiteral("0") << NoError;
794  QTest::newRow("class-enums20") << QStringLiteral("{{ var.Animals.0.key }}")
795  << dict << QStringLiteral("Lions") << NoError;
796  QTest::newRow("class-enums21") << QStringLiteral("{{ var.Animals.2.key }}")
797  << dict << QStringLiteral("Bears") << NoError;
798  QTest::newRow("class-enums22") << QStringLiteral("{{ var.Tigers.samba }}")
799  << dict << QString() << NoError;
800  QTest::newRow("class-enums23")
801  << QStringLiteral("{% with var.animals as result %}{{ result.key }},{{ "
802  "result }},{{ result.scope }}{% endwith %}")
803  << dict << QStringLiteral("Tigers,1,OtherClass") << NoError;
804  QTest::newRow("class-enums24")
805  << QStringLiteral("{% with var.Animals.2 as result %}{{ result.key }},{{ "
806  "result }},{{ result.scope }}{% endwith %}")
807  << dict << QStringLiteral("Bears,2,OtherClass") << NoError;
808  QTest::newRow("class-enums25")
809  << QStringLiteral("{% with var.Bears as result %}{{ result.key }},{{ "
810  "result }},{{ result.scope }}{% endwith %}")
811  << dict << QStringLiteral("Bears,2,OtherClass") << NoError;
812  QTest::newRow("class-enums26")
813  << QStringLiteral("{% with var.Animals as result %}{{ result.0.key }},{{ "
814  "result.1.key }},{{ result.2.key }}{% endwith %}")
815  << dict << QStringLiteral("Lions,Tigers,Bears") << NoError;
816 
817  dict.clear();
818 
819  auto someClass = new SomeClass(this);
820  dict.insert(QStringLiteral("var"), QVariant::fromValue(someClass));
821 
822  QTest::newRow("class-enums27") << QStringLiteral("{{ var.Employee }}") << dict
823  << QStringLiteral("0") << NoError;
824  QTest::newRow("class-enums28") << QStringLiteral("{{ var.Employer }}") << dict
825  << QStringLiteral("1") << NoError;
826  QTest::newRow("class-enums29") << QStringLiteral("{{ var.Manager }}") << dict
827  << QStringLiteral("2") << NoError;
828  QTest::newRow("class-enums30") << QStringLiteral("{{ var.Voter }}") << dict
829  << QStringLiteral("2") << NoError;
830  QTest::newRow("class-enums31") << QStringLiteral("{{ var.Consumer }}") << dict
831  << QStringLiteral("4") << NoError;
832  QTest::newRow("class-enums32") << QStringLiteral("{{ var.Citizen }}") << dict
833  << QStringLiteral("8") << NoError;
834  QTest::newRow("class-enums33")
835  << QStringLiteral("{{ var.FirstEnum }}") << dict << QString() << NoError;
836  QTest::newRow("class-enums34")
837  << QStringLiteral("{{ var.SecondEnum }}") << dict << QString() << NoError;
838 
839  QTest::newRow("class-enums35")
841  "{% with var.SecondEnum as result %}"
842  "{{ result.0 }},{{ result.1 }},{{ result.2 }},"
843  "{{ result.0.key }},{{ result.1.key }},{{ result.2.key }},"
844  "{{ result }},{{ result.scope }}"
845  "{% endwith %}")
846  << dict << QStringLiteral("2,4,8,Voter,Consumer,Citizen,,SomeClass")
847  << NoError;
848 
849  QTest::newRow("class-enums36")
850  << QStringLiteral("{% ifequal var.Employee 2 %}{% endifequal %}") << dict
851  << QString() << NoError;
852 
853  dict.insert(QStringLiteral("var"), QVariant::fromValue(otherClass));
854 
855  QTest::newRow("enums-loops01")
857  "{% for enum in var.Animals %}{% ifequal enum var.Tigers %}"
858  "<b>{{ enum.key }}</b>{% else %}{{ enum.key }}{% endifequal %},"
859  "{% empty %}No content{% endfor %}")
860  << dict << QStringLiteral("Lions,<b>Tigers</b>,Bears,") << NoError;
861 
862  QTest::newRow("enums-loops02")
863  << QString::fromLatin1("{% for enum in var.Tigers %}"
864  "{% ifequal enum result %}<b>{{ enum.key }}</b>"
865  "{% else %}{{ enum.key }}{% endifequal %},"
866  "{% empty %}No content"
867  "{% endfor %}")
868  << dict << QStringLiteral("No content") << NoError;
869 
870  QTest::newRow("enums-loops03")
871  << QString::fromLatin1("{% with var.animals as result %}"
872  "{% for enum in var.Animals %}"
873  "{% ifequal enum result %}<b>{{ enum.key }}</b>"
874  "{% else %}{{ enum.key }}{% endifequal %},"
875  "{% empty %}No content"
876  "{% endfor %}"
877  "{% endwith %}")
878  << dict << QStringLiteral("Lions,<b>Tigers</b>,Bears,") << NoError;
879 
880  QTest::newRow("enums-keycount01")
881  << QStringLiteral("{{ var.Animals.keyCount }}") << dict
882  << QStringLiteral("3") << NoError;
883  QTest::newRow("enums-keycount02")
884  << QStringLiteral("{{ var.Lions.keyCount }}") << dict
885  << QStringLiteral("3") << NoError;
886  QTest::newRow("enums-keycount02")
887  << QStringLiteral("{{ var.animals.keyCount }}") << dict
888  << QStringLiteral("3") << NoError;
889 
890  auto otherClass2 = new OtherClass(OtherClass::Lions, this);
891  dict.insert(QStringLiteral("var2"), QVariant::fromValue(otherClass2));
892 
893  auto otherClass3 = new OtherClass(this);
894  dict.insert(QStringLiteral("var3"), QVariant::fromValue(otherClass3));
895 
896  QTest::newRow("enums-compare01")
897  << QStringLiteral("{% if var.animals == var3.animals %}true{% else %}false{% endif %}") << dict
898  << QStringLiteral("true") << NoError;
899 
900  QTest::newRow("enums-compare02")
901  << QStringLiteral("{% if var.animals == var2.animals %}true{% else %}false{% endif %}") << dict
902  << QStringLiteral("false") << NoError;
903 
904  QTest::newRow("enums-compare03")
905  << QStringLiteral("{% if var.animals >= var3.animals %}true{% else %}false{% endif %}") << dict
906  << QStringLiteral("true") << NoError;
907 
908  QTest::newRow("enums-compare04")
909  << QStringLiteral("{% if var.animals >= var2.animals %}true{% else %}false{% endif %}") << dict
910  << QStringLiteral("true") << NoError;
911 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
912  QTest::newRow("enums-compare05")
913  << QStringLiteral("{% if var.animals > var3.animals %}true{% else %}false{% endif %}") << dict
914  << QStringLiteral("false") << NoError;
915 #endif
916 
917  QTest::newRow("enums-compare06")
918  << QStringLiteral("{% if var.animals > var2.animals %}true{% else %}false{% endif %}") << dict
919  << QStringLiteral("true") << NoError;
920 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
921  QTest::newRow("enums-compare07")
922  << QStringLiteral("{% if var.animals <= var3.animals %}true{% else %}false{% endif %}") << dict
923  << QStringLiteral("true") << NoError;
924 #endif
925  QTest::newRow("enums-compare08")
926  << QStringLiteral("{% if var.animals <= var2.animals %}true{% else %}false{% endif %}") << dict
927  << QStringLiteral("false") << NoError;
928 
929  QTest::newRow("enums-compare09")
930  << QStringLiteral("{% if var.animals < var3.animals %}true{% else %}false{% endif %}") << dict
931  << QStringLiteral("false") << NoError;
932 
933  QTest::newRow("enums-compare10")
934  << QStringLiteral("{% if var.animals < var2.animals %}true{% else %}false{% endif %}") << dict
935  << QStringLiteral("false") << NoError;
936 
937 #ifdef QT5_QT6_TODO
938  QTest::newRow("qt-enums01") << QStringLiteral("{{ Qt.AlignRight }}") << dict
939  << QStringLiteral("2") << NoError;
940  QTest::newRow("qt-enums02") << QStringLiteral("{{ Qt.AlignRight.scope }}")
941  << dict << QStringLiteral("Qt") << NoError;
942  QTest::newRow("qt-enums03") << QStringLiteral("{{ Qt.AlignRight.name }}")
943  << dict << QStringLiteral("Alignment") << NoError;
944  QTest::newRow("qt-enums04") << QStringLiteral("{{ Qt.AlignRight.value }}")
945  << dict << QStringLiteral("2") << NoError;
946  QTest::newRow("qt-enums05")
947  << QStringLiteral("{{ Qt.AlignRight.key }}") << dict
948  << QStringLiteral("AlignRight") << NoError;
949  QTest::newRow("qt-enums06")
950  << QStringLiteral("{{ Qt.Alignment.2.key }}") << dict
951  << QStringLiteral("AlignRight") << NoError;
952 #endif
953  QTest::newRow("qt-enums07") << QStringLiteral("{{ Qt.DoesNotExist }}") << dict
954  << QString() << NoError;
955  QTest::newRow("qt-enums08")
956  << QStringLiteral("{{ Qt }}") << dict << QString() << NoError;
957 
958  dict.clear();
959 
960  GadgetClass gadgetClasss;
961  dict.insert(QStringLiteral("var"), QVariant::fromValue(gadgetClasss));
962 
963  QTest::newRow("gadget-enums01") << QStringLiteral("{{ var.Mike }}") << dict
964  << QStringLiteral("0") << NoError;
965  QTest::newRow("gadget-enums02") << QStringLiteral("{{ var.Natalie }}") << dict
966  << QStringLiteral("1") << NoError;
967  QTest::newRow("gadget-enums03") << QStringLiteral("{{ var.Oliver }}") << dict
968  << QStringLiteral("2") << NoError;
969  QTest::newRow("gadget-enums04") << QStringLiteral("{{ var.Patricia }}") << dict
970  << QString() << NoError;
971  QTest::newRow("gadget-enums05") << QStringLiteral("{{ var.Natalie.name }}") << dict
972  << QStringLiteral("PersonName") << NoError;
973  QTest::newRow("gadget-enums06") << QStringLiteral("{{ var.Natalie.scope }}") << dict
974  << QStringLiteral("GadgetClass") << NoError;
975  QTest::newRow("gadget-enums07") << QStringLiteral("{{ var.Natalie.value }}") << dict
976  << QStringLiteral("1") << NoError;
977  QTest::newRow("gadget-enums08") << QStringLiteral("{{ var.Natalie.key }}") << dict
978  << QStringLiteral("Natalie") << NoError;
979  QTest::newRow("gadget-enums09") << QStringLiteral("{{ var.personName }}") << dict
980  << QStringLiteral("2") << NoError;
981  QTest::newRow("gadget-enums10") << QStringLiteral("{{ var.personName.name }}") << dict
982  << QStringLiteral("PersonName") << NoError;
983  QTest::newRow("gadget-enums11") << QStringLiteral("{{ var.personName.scope }}") << dict
984  << QStringLiteral("GadgetClass") << NoError;
985  QTest::newRow("gadget-enums12") << QStringLiteral("{{ var.personName.value }}") << dict
986  << QStringLiteral("2") << NoError;
987  QTest::newRow("gadget-enums13") << QStringLiteral("{{ var.personName.key }}") << dict
988  << QStringLiteral("Oliver") << NoError;
989  QTest::newRow("gadget-enums14") << QStringLiteral("{{ var.PersonName.0 }}") << dict
990  << QStringLiteral("0") << NoError;
991  QTest::newRow("gadget-enums15") << QStringLiteral("{{ var.PersonName.2 }}") << dict
992  << QStringLiteral("2") << NoError;
993  QTest::newRow("gadget-enums16") << QStringLiteral("{{ var.PersonName.3 }}") << dict
994  << QString() << NoError;
995  QTest::newRow("gadget-enums17") << QStringLiteral("{{ var.PersonName.0.name }}") << dict
996  << QStringLiteral("PersonName") << NoError;
997  QTest::newRow("gadget-enums18") << QStringLiteral("{{ var.PersonName.0.scope }}") << dict
998  << QStringLiteral("GadgetClass") << NoError;
999  QTest::newRow("gadget-enums19") << QStringLiteral("{{ var.PersonName.0.value }}")
1000  << dict << QStringLiteral("0") << NoError;
1001  QTest::newRow("gadget-enums20") << QStringLiteral("{{ var.PersonName.0.key }}")
1002  << dict << QStringLiteral("Mike") << NoError;
1003  QTest::newRow("gadget-enums21") << QStringLiteral("{{ var.PersonName.2.key }}")
1004  << dict << QStringLiteral("Oliver") << NoError;
1005  QTest::newRow("gadget-enums22") << QStringLiteral("{{ var.PersonName.samba }}")
1006  << dict << QString() << NoError;
1007  QTest::newRow("gadget-enums23")
1008  << QStringLiteral("{% with var.personName as result %}{{ result.key }},{{ "
1009  "result }},{{ result.scope }}{% endwith %}")
1010  << dict << QStringLiteral("Oliver,2,GadgetClass") << NoError;
1011  QTest::newRow("gadget-enums24")
1012  << QStringLiteral("{% with var.PersonName.2 as result %}{{ result.key }},{{ "
1013  "result }},{{ result.scope }}{% endwith %}")
1014  << dict << QStringLiteral("Oliver,2,GadgetClass") << NoError;
1015  QTest::newRow("gadget-enums25")
1016  << QStringLiteral("{% with var.Oliver as result %}{{ result.key }},{{ "
1017  "result }},{{ result.scope }}{% endwith %}")
1018  << dict << QStringLiteral("Oliver,2,GadgetClass") << NoError;
1019  QTest::newRow("gadget-enums26")
1020  << QStringLiteral("{% with var.PersonName as result %}{{ result.0.key }},{{ "
1021  "result.1.key }},{{ result.2.key }}{% endwith %}")
1022  << dict << QStringLiteral("Mike,Natalie,Oliver") << NoError;
1023 
1024  QTest::newRow("gadget-enums-loops01")
1026  "{% for enum in var.PersonName %}{% ifequal enum var.Natalie %}"
1027  "<b>{{ enum.key }}</b>{% else %}{{ enum.key }}{% endifequal %},"
1028  "{% empty %}No content{% endfor %}")
1029  << dict << QStringLiteral("Mike,<b>Natalie</b>,Oliver,") << NoError;
1030 
1031  QTest::newRow("gadget-enums-loops02")
1032  << QString::fromLatin1("{% for enum in var.Tigers %}"
1033  "{% ifequal enum result %}<b>{{ enum.key }}</b>"
1034  "{% else %}{{ enum.key }}{% endifequal %},"
1035  "{% empty %}No content"
1036  "{% endfor %}")
1037  << dict << QStringLiteral("No content") << NoError;
1038 
1039  QTest::newRow("gadget-enums-loops03")
1040  << QString::fromLatin1("{% with var.personName as result %}"
1041  "{% for enum in var.PersonName %}"
1042  "{% ifequal enum result %}<b>{{ enum.key }}</b>"
1043  "{% else %}{{ enum.key }}{% endifequal %},"
1044  "{% empty %}No content"
1045  "{% endfor %}"
1046  "{% endwith %}")
1047  << dict << QStringLiteral("Mike,Natalie,<b>Oliver</b>,") << NoError;
1048 
1049  QTest::newRow("gadget-enums-keycount01")
1050  << QStringLiteral("{{ var.PersonName.keyCount }}") << dict
1051  << QStringLiteral("3") << NoError;
1052  QTest::newRow("gadget-enums-keycount02")
1053  << QStringLiteral("{{ var.personName.keyCount }}") << dict
1054  << QStringLiteral("3") << NoError;
1055 
1056  GadgetClass gadgetClass2(GadgetClass::Natalie);
1057  dict.insert(QStringLiteral("var2"), QVariant::fromValue(gadgetClass2));
1058 
1059  GadgetClass gadgetClass3;
1060  dict.insert(QStringLiteral("var3"), QVariant::fromValue(gadgetClass3));
1061 
1062  QTest::newRow("gadget-enums-compare01")
1063  << QStringLiteral("{% if var.personName == var3.personName %}true{% else %}false{% endif %}") << dict
1064  << QStringLiteral("true") << NoError;
1065 
1066  QTest::newRow("gadget-enums-compare02")
1067  << QStringLiteral("{% if var.personName == var2.personName %}true{% else %}false{% endif %}") << dict
1068  << QStringLiteral("false") << NoError;
1069 
1070  QTest::newRow("gadget-enums-compare03")
1071  << QStringLiteral("{% if var.personName >= var3.personName %}true{% else %}false{% endif %}") << dict
1072  << QStringLiteral("true") << NoError;
1073 
1074  QTest::newRow("gadget-enums-compare04")
1075  << QStringLiteral("{% if var.personName >= var2.personName %}true{% else %}false{% endif %}") << dict
1076  << QStringLiteral("true") << NoError;
1077 
1078  QTest::newRow("gadget-enums-compare05")
1079  << QStringLiteral("{% if var.personName > var3.personName %}true{% else %}false{% endif %}") << dict
1080  << QStringLiteral("false") << NoError;
1081 
1082  QTest::newRow("gadget-enums-compare06")
1083  << QStringLiteral("{% if var.personName > var2.personName %}true{% else %}false{% endif %}") << dict
1084  << QStringLiteral("true") << NoError;
1085 
1086  QTest::newRow("gadget-enums-compare07")
1087  << QStringLiteral("{% if var.personName <= var3.personName %}true{% else %}false{% endif %}") << dict
1088  << QStringLiteral("true") << NoError;
1089 
1090  QTest::newRow("gadget-enums-compare08")
1091  << QStringLiteral("{% if var.personName <= var2.personName %}true{% else %}false{% endif %}") << dict
1092  << QStringLiteral("false") << NoError;
1093 
1094  QTest::newRow("gadget-enums-compare09")
1095  << QStringLiteral("{% if var.personName < var3.personName %}true{% else %}false{% endif %}") << dict
1096  << QStringLiteral("false") << NoError;
1097 
1098  QTest::newRow("gadget-enums-compare10")
1099  << QStringLiteral("{% if var.personName < var2.personName %}true{% else %}false{% endif %}") << dict
1100  << QStringLiteral("false") << NoError;
1101 }
1102 
1103 void TestBuiltinSyntax::testListIndex_data()
1104 {
1105 
1106  QTest::addColumn<QString>("input");
1107  QTest::addColumn<Dict>("dict");
1108  QTest::addColumn<QString>("output");
1109  QTest::addColumn<Cutelee::Error>("error");
1110 
1111  Dict dict;
1112 
1113  QVariantList l{QStringLiteral("first item"), QStringLiteral("second item")};
1114 
1115  dict.insert(QStringLiteral("var"), l);
1116 
1117  // List-index syntax allows a template to access a certain item of a
1118  // subscriptable object.
1119  QTest::newRow("list-index01") << QStringLiteral("{{ var.1 }}") << dict
1120  << QStringLiteral("second item") << NoError;
1121  // Fail silently when the list index is out of range.
1122  QTest::newRow("list-index02")
1123  << QStringLiteral("{{ var.5 }}") << dict << QString() << NoError;
1124 
1125  dict.clear();
1126  dict.insert(QStringLiteral("var"), QVariant());
1127 
1128  // Fail silently when the variable is not a subscriptable object.
1129  QTest::newRow("list-index03")
1130  << QStringLiteral("{{ var.1 }}") << dict << QString() << NoError;
1131 
1132  dict.clear();
1133  dict.insert(QStringLiteral("var"), QVariantHash());
1134  // Fail silently when variable is a dict without the specified key.
1135  QTest::newRow("list-index04")
1136  << QStringLiteral("{{ var.1 }}") << dict << QString() << NoError;
1137 
1138  dict.clear();
1139 
1140  QVariantHash hash;
1141  hash.insert(QStringLiteral("1"), QStringLiteral("hello"));
1142  dict.insert(QStringLiteral("var"), hash);
1143  // Dictionary lookup wins out when dict's key is a string.
1144  QTest::newRow("list-index05") << QStringLiteral("{{ var.1 }}") << dict
1145  << QStringLiteral("hello") << NoError;
1146 
1147  // QVariantHash can only use strings as keys, so list-index06 and
1148  // list-index07
1149  // are not valid.
1150 
1151  dict.clear();
1152 
1153  QStringList sl;
1154  sl.append(QStringLiteral("hello"));
1155  sl.append(QStringLiteral("world"));
1156  dict.insert(QStringLiteral("var"), sl);
1157  // QStringList lookup
1158  QTest::newRow("list-index08")
1159  << QStringLiteral("{{ var.0 }}, {{ var.1 }}!") << dict
1160  << QStringLiteral("hello, world!") << NoError;
1161 }
1162 
1163 void TestBuiltinSyntax::testFilterSyntax_data()
1164 {
1165  QTest::addColumn<QString>("input");
1166  QTest::addColumn<Dict>("dict");
1167  QTest::addColumn<QString>("output");
1168  QTest::addColumn<Cutelee::Error>("error");
1169 
1170  Dict dict;
1171 
1172  // Basic filter usage
1173  dict.insert(QStringLiteral("var"), QStringLiteral("Django is the greatest!"));
1174  QTest::newRow("filter-syntax01")
1175  << QStringLiteral("{{ var|upper }}") << dict
1176  << QStringLiteral("DJANGO IS THE GREATEST!") << NoError;
1177 
1178  // Chained filters
1179  QTest::newRow("filter-syntax02")
1180  << QStringLiteral("{{ var|upper|lower }}") << dict
1181  << QStringLiteral("django is the greatest!") << NoError;
1182 
1183  // Raise TemplateSyntaxError for space between a variable and filter pipe
1184  dict.clear();
1185  QTest::newRow("filter-syntax03") << QStringLiteral("{{ var |upper }}") << dict
1186  << QString() << TagSyntaxError;
1187 
1188  // Raise TemplateSyntaxError for space after a filter pipe
1189  QTest::newRow("filter-syntax04") << QStringLiteral("{{ var| upper }}") << dict
1190  << QString() << TagSyntaxError;
1191 
1192  // Raise TemplateSyntaxError for a nonexistent filter
1193  QTest::newRow("filter-syntax05") << QStringLiteral("{{ var|does_not_exist }}")
1194  << dict << QString() << UnknownFilterError;
1195 
1196  // Raise TemplateSyntaxError when trying to access a filter containing an
1197  // illegal character
1198  QTest::newRow("filter-syntax06") << QStringLiteral("{{ var|fil(ter) }}")
1199  << dict << QString() << UnknownFilterError;
1200 
1201  // Raise TemplateSyntaxError for invalid block tags
1202  QTest::newRow("filter-syntax07")
1203  << QStringLiteral("{% nothing_to_see_here %}") << dict << QString()
1204  << InvalidBlockTagError;
1205  // Raise TemplateSyntaxError for empty block tags
1206  QTest::newRow("filter-syntax08")
1207  << QStringLiteral("{% %}") << dict << QString() << EmptyBlockTagError;
1208 
1209  // Chained filters, with an argument to the first one
1210  dict.insert(QStringLiteral("var"), QStringLiteral("<b><i>Yes</i></b>"));
1211  QTest::newRow("filter-syntax09") << "{{ var|removetags:\"b i\"|upper|lower }}"
1212  << dict << QStringLiteral("yes") << NoError;
1213  // Literal string as argument is always "safe" from auto-escaping..
1214  dict.clear();
1215  dict.insert(QStringLiteral("var"), QVariant());
1216  QTest::newRow("filter-syntax10")
1217  << "{{ var|default_if_none:\" endquote\\\" hah\" }}" << dict
1218  << " endquote\" hah" << NoError;
1219  // Variable as argument
1220  dict.insert(QStringLiteral("var2"), QStringLiteral("happy"));
1221  QTest::newRow("filter-syntax11")
1222  << QStringLiteral("{{ var|default_if_none:var2 }}") << dict
1223  << QStringLiteral("happy") << NoError;
1224  // Default argument testing
1225  dict.clear();
1226  dict.insert(QStringLiteral("var"), true);
1227  QTest::newRow("filter-syntax12")
1228  << "{{ var|yesno:\"yup,nup,mup\" }} {{ var|yesno }}" << dict
1229  << QStringLiteral("yup yes") << NoError;
1230 
1231  // Fail silently for methods that raise an exception with a
1232  // "silent_variable_failure" attribute
1233  // dict.clear();
1234  // QObject *someClass = new SomeClass(this);
1235  // dict.insert( QStringLiteral("var"), QVariant::fromValue(someClass));
1236  // QTest::newRow("filter-syntax13") << QString::fromLatin1( "1{{
1237  // var.method3
1238  // }}2" ) << dict << QString::fromLatin1( "12" ) << NoError;
1239  // // In methods that raise an exception without a
1240  // // "silent_variable_attribute" set to True, the exception propagates
1241  // // #C# SomeOtherException)
1242  // QTest::newRow("filter-syntax14") << QString::fromLatin1( "var" ) <<
1243  // dict
1244  // << QString() << TagSyntaxError;
1245 
1246  // Escaped backslash in argument
1247  dict.clear();
1248  dict.insert(QStringLiteral("var"), QVariant());
1249  QTest::newRow("filter-syntax15") << "{{ var|default_if_none:\"foo\\bar\" }}"
1250  << dict << "foo\\bar" << NoError;
1251  // Escaped backslash using known escape char
1252  QTest::newRow("filter-syntax16") << "{{ var|default_if_none:\"foo\\now\" }}"
1253  << dict << "foo\\now" << NoError;
1254  // Empty strings can be passed as arguments to filters
1255  dict.clear();
1256  dict.insert(QStringLiteral("var"),
1257  QVariantList{QStringLiteral("a"), QStringLiteral("b"),
1258  QStringLiteral("c")});
1259  QTest::newRow("filter-syntax17")
1260  << "{{ var|join:\"\" }}" << dict << QStringLiteral("abc") << NoError;
1261 
1262  // Make sure that any unicode strings are converted to bytestrings
1263  // in the final output.
1264  // FAIL'filter-syntax18': (r'{{ var }}', {'var': UTF8Class()},
1265  // u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111'),
1266 
1267  // Numbers as filter arguments should work
1268  dict.clear();
1269  dict.insert(QStringLiteral("var"), QStringLiteral("hello world"));
1270  QTest::newRow("filter-syntax19")
1271  << QStringLiteral("{{ var|truncatewords:1 }}") << dict
1272  << QStringLiteral("hello ...") << NoError;
1273  // filters should accept empty string constants
1274  dict.clear();
1275  QTest::newRow("filter-syntax20") << "{{ \"\"|default_if_none:\"was none\" }}"
1276  << dict << QString() << NoError;
1277 
1278  QTest::newRow("filter-syntax21")
1279  << "{{ \"\"|default_if_none:|truncatewords }}" << dict << QString()
1280  << EmptyVariableError;
1281 }
1282 
1283 void TestBuiltinSyntax::testCommentSyntax_data()
1284 {
1285  QTest::addColumn<QString>("input");
1286  QTest::addColumn<Dict>("dict");
1287  QTest::addColumn<QString>("output");
1288  QTest::addColumn<Cutelee::Error>("error");
1289 
1290  Dict dict;
1291 
1292  QTest::newRow("comment-syntax01")
1293  << QStringLiteral("{# this is hidden #}hello") << dict
1294  << QStringLiteral("hello") << NoError;
1295  QTest::newRow("comment-syntax02")
1296  << QStringLiteral("{# this is hidden #}hello{# foo #}") << dict
1297  << QStringLiteral("hello") << NoError;
1298  // Comments can contain invalid stuff.
1299  QTest::newRow("comment-syntax03") << QStringLiteral("foo{# {% if %} #}")
1300  << dict << QStringLiteral("foo") << NoError;
1301  QTest::newRow("comment-syntax04")
1302  << QStringLiteral("foo{# {% endblock %} #}") << dict
1303  << QStringLiteral("foo") << NoError;
1304  QTest::newRow("comment-syntax05")
1305  << QStringLiteral("foo{# {% somerandomtag %} #}") << dict
1306  << QStringLiteral("foo") << NoError;
1307  QTest::newRow("comment-syntax06") << QStringLiteral("foo{# {% #}") << dict
1308  << QStringLiteral("foo") << NoError;
1309  QTest::newRow("comment-syntax07") << QStringLiteral("foo{# %} #}") << dict
1310  << QStringLiteral("foo") << NoError;
1311  QTest::newRow("comment-syntax08") << QStringLiteral("foo{# %} #}bar") << dict
1312  << QStringLiteral("foobar") << NoError;
1313  QTest::newRow("comment-syntax09") << QStringLiteral("foo{# {{ #}") << dict
1314  << QStringLiteral("foo") << NoError;
1315  QTest::newRow("comment-syntax10") << QStringLiteral("foo{# }} #}") << dict
1316  << QStringLiteral("foo") << NoError;
1317  QTest::newRow("comment-syntax11") << QStringLiteral("foo{# { #}") << dict
1318  << QStringLiteral("foo") << NoError;
1319  QTest::newRow("comment-syntax12") << QStringLiteral("foo{# } #}") << dict
1320  << QStringLiteral("foo") << NoError;
1321 }
1322 
1323 void TestBuiltinSyntax::testMultiline_data()
1324 {
1325  QTest::addColumn<QString>("input");
1326  QTest::addColumn<Dict>("dict");
1327  QTest::addColumn<QString>("output");
1328  QTest::addColumn<Cutelee::Error>("error");
1329 
1330  Dict dict;
1331 
1332  QTest::newRow("multiline01")
1333  << "Hello,\nboys.\nHow\nare\nyou\ngentlemen?" << dict
1334  << "Hello,\nboys.\nHow\nare\nyou\ngentlemen?" << NoError;
1335 }
1336 
1337 void TestBuiltinSyntax::testEscaping_data()
1338 {
1339  QTest::addColumn<QString>("input");
1340  QTest::addColumn<Dict>("dict");
1341  QTest::addColumn<QString>("output");
1342  QTest::addColumn<Cutelee::Error>("error");
1343 
1344  Dict dict;
1345 
1346  // html escaping is not to be confused with for example url escaping.
1347  dict.insert(QStringLiteral("var"), QStringLiteral("< > & \" \' # = % $"));
1348  QTest::newRow("escape01") << QStringLiteral("{{ var }}") << dict
1349  << "&lt; &gt; &amp; &quot; &#39; # = % $" << NoError;
1350 
1351  dict.clear();
1352  dict.insert(QStringLiteral("var"), QStringLiteral("this & that"));
1353  QTest::newRow("escape02") << QStringLiteral("{{ var }}") << dict
1354  << QStringLiteral("this &amp; that") << NoError;
1355 
1356  // Strings are compared unescaped.
1357  QTest::newRow("escape03")
1358  << "{% ifequal var \"this & that\" %}yes{% endifequal %}" << dict
1359  << QStringLiteral("yes") << NoError;
1360 
1361  // Arguments to filters are 'safe' and manipulate their input unescaped.
1362  QTest::newRow("escape04") << "{{ var|cut:\"&\" }}" << dict
1363  << QStringLiteral("this that") << NoError;
1364 
1365  dict.insert(QStringLiteral("varList"),
1366  QVariantList{QStringLiteral("Tom"), QStringLiteral("Dick"),
1367  QStringLiteral("Harry")});
1368  QTest::newRow("escape05") << "{{ varList|join:\" & \" }}" << dict
1369  << QStringLiteral("Tom & Dick & Harry") << NoError;
1370 
1371  // Unlike variable args.
1372  dict.insert(QStringLiteral("amp"), QStringLiteral(" & "));
1373  QTest::newRow("escape06")
1374  << QStringLiteral("{{ varList|join:amp }}") << dict
1375  << QStringLiteral("Tom &amp; Dick &amp; Harry") << NoError;
1376 
1377  // Literal strings are safe.
1378  QTest::newRow("escape07") << "{{ \"this & that\" }}" << dict
1379  << QStringLiteral("this & that") << NoError;
1380 
1381  // Iterating outputs safe characters.
1382  dict.clear();
1383  QVariantList list{QStringLiteral("K"), QStringLiteral("&"),
1384  QStringLiteral("R")};
1385  dict.insert(QStringLiteral("list"), list);
1386  QTest::newRow("escape08")
1387  << QStringLiteral("{% for letter in list %}{{ letter }},{% endfor %}")
1388  << dict << QStringLiteral("K,&amp;,R,") << NoError;
1389 
1390  dict.clear();
1391  // escape requirement survives lookup.
1392  QVariantHash hash;
1393  hash.insert(QStringLiteral("key"), QStringLiteral("this & that"));
1394  dict.insert(QStringLiteral("var"), hash);
1395  QTest::newRow("escape09") << QStringLiteral("{{ var.key }}") << dict
1396  << QStringLiteral("this &amp; that") << NoError;
1397 
1398  dict.clear();
1399 }
1400 
1401 void TestBuiltinSyntax::testMultipleStates()
1402 {
1403  auto engine1 = getEngine();
1404 
1405  auto loader1
1406  = std::shared_ptr<InMemoryTemplateLoader>(new InMemoryTemplateLoader());
1407 
1408  loader1->setTemplate(QStringLiteral("template1"),
1409  QStringLiteral("Template 1"));
1410  engine1->addTemplateLoader(loader1);
1411 
1412  auto t1 = engine1->newTemplate(QStringLiteral("{% include \"template1\" %}"),
1413  QStringLiteral("\"template1\""));
1414 
1415  auto engine2 = getEngine();
1416 
1417  auto loader2
1418  = std::shared_ptr<InMemoryTemplateLoader>(new InMemoryTemplateLoader());
1419 
1420  loader2->setTemplate(QStringLiteral("template2"),
1421  QStringLiteral("Template 2"));
1422 
1423  engine2->addTemplateLoader(loader2);
1424 
1425  auto t2 = engine2->newTemplate(QStringLiteral("{% include \"template2\" %}"),
1426  QStringLiteral("\"template2\""));
1427 
1428  auto engine3 = getEngine();
1429 
1430  auto loader3
1431  = std::shared_ptr<InMemoryTemplateLoader>(new InMemoryTemplateLoader());
1432 
1433  loader3->setTemplate(QStringLiteral("template3"),
1434  QStringLiteral("Template 3"));
1435 
1436  engine3->addTemplateLoader(loader3);
1437 
1438  auto t3 = engine3->newTemplate(QStringLiteral("{% include var %}"),
1439  QStringLiteral("var"));
1440 
1441  QVariantHash h;
1442  h.insert(QStringLiteral("var"), QStringLiteral("template3"));
1443  Context c(h);
1444  t1->render(&c);
1445 
1446  auto expected1 = QStringLiteral("Template 1");
1447  auto expected2 = QStringLiteral("Template 2");
1448  auto expected3 = QStringLiteral("Template 3");
1449  QCOMPARE(t1->render(&c), expected1);
1450  QCOMPARE(t2->render(&c), expected2);
1451  QCOMPARE(t3->render(&c), expected3);
1452 }
1453 
1454 void TestBuiltinSyntax::testAlternativeEscaping()
1455 {
1456  auto engine1 = getEngine();
1457 
1458  auto t1 = engine1->newTemplate(
1459  QStringLiteral("{{ var }} {% spaceless %}{{ var }}{% endspaceless %}"),
1460  QStringLiteral("\"template1\""));
1461 
1462  auto input = QStringLiteral("< > \r\n & \" \' # = % $");
1463 
1464  QVariantHash h;
1465  h.insert(QStringLiteral("var"), input);
1466  Context c(h);
1467 
1468  QString output;
1469  QTextStream ts(&output);
1470 
1471  NoEscapeOutputStream noEscapeOs(&ts);
1472 
1473  t1->render(&noEscapeOs, &c);
1474 
1475  QCOMPARE(output, QString(input + QLatin1String(" ") + input));
1476  output.clear();
1477 
1478  JSOutputStream jsOs(&ts);
1479 
1480  t1->render(&jsOs, &c);
1481 
1482  QString jsOutput(QStringLiteral(
1483  "\\u003C \\u003E \\u000D\\u000A \\u0026 \\u0022 \\u0027 # \\u003D % $"));
1484 
1485  jsOutput = jsOutput + QLatin1String(" ") + jsOutput;
1486 
1487  QCOMPARE(output, jsOutput);
1488 }
1489 
1490 void TestBuiltinSyntax::testTemplatePathSafety_data()
1491 {
1492  QTest::addColumn<QString>("inputPath");
1493  QTest::addColumn<QString>("output");
1494 
1495  QTest::newRow("template-path-safety01")
1496  << QStringLiteral("visible_file") << QStringLiteral("visible_file");
1497  QTest::newRow("template-path-safety02")
1498  << QStringLiteral("../invisible_file") << QString();
1499 }
1500 
1501 void TestBuiltinSyntax::testTemplatePathSafety()
1502 {
1503  QFETCH(QString, inputPath);
1504  QFETCH(QString, output);
1505 
1506  auto loader = new FileSystemTemplateLoader();
1507 
1508  loader->setTemplateDirs({QStringLiteral(".")});
1509 
1510  QFile f(inputPath);
1511  auto opened = f.open(QFile::WriteOnly | QFile::Text);
1512  QVERIFY(opened);
1513  f.write(inputPath.toUtf8());
1514  f.close();
1515 
1516  auto t = loader->loadByName(inputPath, m_engine);
1517  Context c;
1518  if (output.isEmpty())
1519  QVERIFY(!t);
1520  else
1521  QCOMPARE(t->render(&c), inputPath);
1522 
1523  delete loader;
1524  f.remove();
1525 }
1526 
1527 void TestBuiltinSyntax::testMediaPathSafety_data()
1528 {
1529  QTest::addColumn<QString>("inputPath");
1530  QTest::addColumn<QString>("output");
1531 
1532  QTest::newRow("media-path-safety01")
1533  << QStringLiteral("visible_file") << QStringLiteral("./visible_file");
1534  QTest::newRow("media-path-safety02")
1535  << QStringLiteral("../invisible_file") << QString();
1536 }
1537 
1538 void TestBuiltinSyntax::testMediaPathSafety()
1539 {
1540  QFETCH(QString, inputPath);
1541  QFETCH(QString, output);
1542 
1543  auto loader = new FileSystemTemplateLoader();
1544 
1545  loader->setTemplateDirs({QStringLiteral(".")});
1546 
1547  QFile f(inputPath);
1548  auto opened = f.open(QFile::WriteOnly | QFile::Text);
1549  QVERIFY(opened);
1550  f.write(inputPath.toUtf8());
1551  f.close();
1552 
1553  auto uri = loader->getMediaUri(inputPath);
1554  if (output.isEmpty())
1555  QVERIFY(uri.second.isEmpty());
1556  else
1557  QCOMPARE(QFileInfo(uri.first + uri.second).absoluteFilePath(),
1558  QFileInfo(output).absoluteFilePath());
1559 
1560  delete loader;
1561  f.remove();
1562 }
1563 
1564 void TestBuiltinSyntax::testTypeAccessorsUnordered()
1565 {
1566  QFETCH(QString, input);
1567  QFETCH(Dict, dict);
1568  QFETCH(QStringList, output);
1569  QFETCH(Cutelee::Error, error);
1570 
1571  auto t = m_engine->newTemplate(input, QLatin1String(QTest::currentDataTag()));
1572 
1573  Context context(dict);
1574 
1575  auto result = t->render(&context);
1576  if (t->error() != NoError) {
1577  if (t->error() != error)
1578  qDebug() << t->errorString();
1579  QCOMPARE(t->error(), error);
1580  return;
1581  }
1582 
1583  QCOMPARE(t->error(), NoError);
1584 
1585  // Didn't catch any errors, so make sure I didn't expect any.
1586  QCOMPARE(NoError, error);
1587 
1588  Q_FOREACH (const QString &s, output) {
1589  QVERIFY(result.contains(s));
1590  }
1591 
1592  QCOMPARE(result.length(), output.join(QString()).length());
1593 }
1594 
1595 void TestBuiltinSyntax::testTypeAccessorsUnordered_data()
1596 {
1597  QTest::addColumn<QString>("input");
1598  QTest::addColumn<Dict>("dict");
1599  QTest::addColumn<QStringList>("output");
1600  QTest::addColumn<Cutelee::Error>("error");
1601 
1602  Dict dict;
1603 
1604  QVariantHash itemsHash;
1605  itemsHash.insert(QStringLiteral("one"), 1);
1606  itemsHash.insert(QStringLiteral("two"), 2);
1607  itemsHash.insert(QStringLiteral("three"), 3);
1608 
1609  dict.insert(QStringLiteral("hash"), itemsHash);
1610 
1611  QTest::newRow("type-accessors-hash-unordered01")
1612  << QStringLiteral("{% for key,value in hash.items %}{{ key }}:{{ value "
1613  "}};{% endfor %}")
1614  << dict
1615  << QStringList{QStringLiteral("one:1;"), QStringLiteral("two:2;"),
1616  QStringLiteral("three:3;")}
1617  << NoError;
1618  QTest::newRow("type-accessors-hash-unordered02")
1619  << QStringLiteral("{% for key in hash.keys %}{{ key }};{% endfor %}")
1620  << dict
1621  << QStringList{QStringLiteral("one;"), QStringLiteral("two;"),
1622  QStringLiteral("three;")}
1623  << NoError;
1624  QTest::newRow("type-accessors-hash-unordered03")
1625  << QStringLiteral(
1626  "{% for value in hash.values %}{{ value }};{% endfor %}")
1627  << dict
1628  << QStringList{QStringLiteral("1;"), QStringLiteral("2;"),
1629  QStringLiteral("3;")}
1630  << NoError;
1631 }
1632 
1633 void TestBuiltinSyntax::testTypeAccessors_data()
1634 {
1635  QTest::addColumn<QString>("input");
1636  QTest::addColumn<Dict>("dict");
1637  QTest::addColumn<QString>("output");
1638  QTest::addColumn<Cutelee::Error>("error");
1639 
1640  Dict dict;
1641 
1642  QVariantHash itemsHash;
1643  itemsHash.insert(QStringLiteral("one"), 1);
1644  itemsHash.insert(QStringLiteral("two"), 2);
1645  itemsHash.insert(QStringLiteral("three"), 3);
1646 
1647  dict.insert(QStringLiteral("hash"), itemsHash);
1648 
1649  QTest::newRow("type-accessors-hash01")
1650  << QStringLiteral("{{ hash.items|length }}") << dict
1651  << QStringLiteral("3") << NoError;
1652  QTest::newRow("type-accessors-hash02")
1653  << QStringLiteral("{{ hash.keys|length }}") << dict << QStringLiteral("3")
1654  << NoError;
1655  QTest::newRow("type-accessors-hash03")
1656  << QStringLiteral("{{ hash.values|length }}") << dict
1657  << QStringLiteral("3") << NoError;
1658 
1659  dict.clear();
1660  dict.insert(QStringLiteral("str1"), QStringLiteral("my string"));
1661  dict.insert(QStringLiteral("str2"), QStringLiteral("mystring"));
1662 
1663  QTest::newRow("type-accessors-string01")
1664  << QStringLiteral("{{ str1.capitalize }}") << dict
1665  << QStringLiteral("My string") << NoError;
1666  QTest::newRow("type-accessors-string02")
1667  << QStringLiteral("{{ str2.capitalize }}") << dict
1668  << QStringLiteral("Mystring") << NoError;
1669 
1670  dict.clear();
1671  dict.insert(QStringLiteral("str1"), QStringLiteral("de24335fre"));
1672  dict.insert(QStringLiteral("str2"), QStringLiteral("de435f3.-5r"));
1673 
1674  QTest::newRow("type-accessors-string03")
1675  << QStringLiteral("{{ str1.isalnum }}") << dict << QStringLiteral("True")
1676  << NoError;
1677  QTest::newRow("type-accessors-string04")
1678  << QStringLiteral("{{ str2.isalnum }}") << dict << QStringLiteral("False")
1679  << NoError;
1680 
1681  dict.clear();
1682  dict.insert(QStringLiteral("str1"), QStringLiteral("24335"));
1683  dict.insert(QStringLiteral("str2"), QStringLiteral("de435f35r"));
1684  dict.insert(QStringLiteral("str3"), QStringLiteral("de435f3.-5r"));
1685 
1686  QTest::newRow("type-accessors-string05")
1687  << QStringLiteral("{{ str1.isdigit }}") << dict << QStringLiteral("True")
1688  << NoError;
1689  QTest::newRow("type-accessors-string06")
1690  << QStringLiteral("{{ str2.isdigit }}") << dict << QStringLiteral("False")
1691  << NoError;
1692  QTest::newRow("type-accessors-string07")
1693  << QStringLiteral("{{ str3.isdigit }}") << dict << QStringLiteral("False")
1694  << NoError;
1695 
1696  dict.clear();
1697  dict.insert(QStringLiteral("str"), QStringLiteral("MyString"));
1698  dict.insert(QStringLiteral("lowerStr"), QStringLiteral("mystring"));
1699 
1700  QTest::newRow("type-accessors-string08")
1701  << QStringLiteral("{{ str.islower }}") << dict << QStringLiteral("False")
1702  << NoError;
1703  QTest::newRow("type-accessors-string09")
1704  << QStringLiteral("{{ lowerStr.islower }}") << dict
1705  << QStringLiteral("True") << NoError;
1706 
1707  dict.clear();
1708  dict.insert(QStringLiteral("str1"), QStringLiteral(" "));
1709  dict.insert(QStringLiteral("str2"), QStringLiteral(" r "));
1710  dict.insert(QStringLiteral("str3"), QStringLiteral(" \t\nr "));
1711  dict.insert(QStringLiteral("str4"), QStringLiteral(" \t\n "));
1712 
1713  QTest::newRow("type-accessors-string10")
1714  << QStringLiteral("{{ str1.isspace }}") << dict << QStringLiteral("True")
1715  << NoError;
1716  QTest::newRow("type-accessors-string11")
1717  << QStringLiteral("{{ str2.isspace }}") << dict << QStringLiteral("False")
1718  << NoError;
1719  QTest::newRow("type-accessors-string12")
1720  << QStringLiteral("{{ str3.isspace }}") << dict << QStringLiteral("False")
1721  << NoError;
1722  QTest::newRow("type-accessors-string13")
1723  << QStringLiteral("{{ str4.isspace }}") << dict << QStringLiteral("True")
1724  << NoError;
1725 
1726  dict.clear();
1727  dict.insert(QStringLiteral("str1"), QStringLiteral("My String"));
1728  dict.insert(QStringLiteral("str2"), QStringLiteral("Mystring"));
1729  dict.insert(QStringLiteral("str3"), QStringLiteral("My string"));
1730  dict.insert(QStringLiteral("str4"), QStringLiteral("my string"));
1731 
1732  QTest::newRow("type-accessors-string14")
1733  << QStringLiteral("{{ str1.istitle }}") << dict << QStringLiteral("True")
1734  << NoError;
1735  QTest::newRow("type-accessors-string15")
1736  << QStringLiteral("{{ str2.istitle }}") << dict << QStringLiteral("True")
1737  << NoError;
1738  QTest::newRow("type-accessors-string16")
1739  << QStringLiteral("{{ str3.istitle }}") << dict << QStringLiteral("False")
1740  << NoError;
1741  QTest::newRow("type-accessors-string17")
1742  << QStringLiteral("{{ str4.istitle }}") << dict << QStringLiteral("False")
1743  << NoError;
1744 
1745  dict.clear();
1746  dict.insert(QStringLiteral("str"), QStringLiteral("MyString"));
1747  dict.insert(QStringLiteral("upperStr"), QStringLiteral("MYSTRING"));
1748 
1749  QTest::newRow("type-accessors-string18")
1750  << QStringLiteral("{{ str.isupper }}") << dict << QStringLiteral("False")
1751  << NoError;
1752  QTest::newRow("type-accessors-string19")
1753  << QStringLiteral("{{ upperStr.isupper }}") << dict
1754  << QStringLiteral("True") << NoError;
1755 
1756  dict.clear();
1757  dict.insert(QStringLiteral("str1"), QStringLiteral("My String"));
1758  dict.insert(QStringLiteral("str2"), QStringLiteral("MYSTRING"));
1759  dict.insert(QStringLiteral("str3"), QStringLiteral("MY STRING"));
1760 
1761  QTest::newRow("type-accessors-string20")
1762  << QStringLiteral("{{ str1.lower }}") << dict
1763  << QStringLiteral("my string") << NoError;
1764  QTest::newRow("type-accessors-string21")
1765  << QStringLiteral("{{ str2.lower }}") << dict
1766  << QStringLiteral("mystring") << NoError;
1767  QTest::newRow("type-accessors-string22")
1768  << QStringLiteral("{{ str3.lower }}") << dict
1769  << QStringLiteral("my string") << NoError;
1770 
1771  dict.clear();
1772  dict.insert(QStringLiteral("str"), QStringLiteral("one\ntwo three\nfour"));
1773 
1774  QTest::newRow("type-accessors-string23")
1775  << QStringLiteral(
1776  "{% for line in str.splitlines %}{{ line }};{% endfor %}")
1777  << dict << QStringLiteral("one;two three;four;") << NoError;
1778 
1779  dict.clear();
1780  dict.insert(QStringLiteral("str1"), QStringLiteral(" one"));
1781  dict.insert(QStringLiteral("str2"), QStringLiteral(" one "));
1782  dict.insert(QStringLiteral("str3"), QStringLiteral("one "));
1783  dict.insert(QStringLiteral("str4"), QStringLiteral(" "));
1784  dict.insert(QStringLiteral("str5"), QStringLiteral(""));
1785 
1786  QTest::newRow("type-accessors-string24")
1787  << QStringLiteral("{{ str1.strip }}") << dict << QStringLiteral("one")
1788  << NoError;
1789  QTest::newRow("type-accessors-string25")
1790  << QStringLiteral("{{ str2.strip }}") << dict << QStringLiteral("one")
1791  << NoError;
1792  QTest::newRow("type-accessors-string26")
1793  << QStringLiteral("{{ str3.strip }}") << dict << QStringLiteral("one")
1794  << NoError;
1795  QTest::newRow("type-accessors-string27")
1796  << QStringLiteral("{{ str4.strip }}") << dict << QString() << NoError;
1797  QTest::newRow("type-accessors-string28")
1798  << QStringLiteral("{{ str5.strip }}") << dict << QString() << NoError;
1799 
1800  dict.clear();
1801  dict.insert(QStringLiteral("str1"), QStringLiteral("My String"));
1802  dict.insert(QStringLiteral("str2"), QStringLiteral("mY sTRING"));
1803  dict.insert(QStringLiteral("str3"), QStringLiteral("My StrInG"));
1804  dict.insert(QStringLiteral("str4"), QStringLiteral("my string"));
1805  dict.insert(QStringLiteral("str5"), QStringLiteral("MY STRING"));
1806 
1807  // Yes, this really is a python built-in.
1808  QTest::newRow("type-accessors-string29")
1809  << QStringLiteral("{{ str1.swapcase }}") << dict
1810  << QStringLiteral("mY sTRING") << NoError;
1811  QTest::newRow("type-accessors-string30")
1812  << QStringLiteral("{{ str2.swapcase }}") << dict
1813  << QStringLiteral("My String") << NoError;
1814  QTest::newRow("type-accessors-string31")
1815  << QStringLiteral("{{ str3.swapcase }}") << dict
1816  << QStringLiteral("mY sTRiNg") << NoError;
1817  QTest::newRow("type-accessors-string32")
1818  << QStringLiteral("{{ str4.swapcase }}") << dict
1819  << QStringLiteral("MY STRING") << NoError;
1820  QTest::newRow("type-accessors-string33")
1821  << QStringLiteral("{{ str5.swapcase }}") << dict
1822  << QStringLiteral("my string") << NoError;
1823 
1824  dict.clear();
1825  dict.insert(QStringLiteral("str1"), QStringLiteral("My String"));
1826  dict.insert(QStringLiteral("str2"), QStringLiteral("mystring"));
1827  dict.insert(QStringLiteral("str3"), QStringLiteral("my string"));
1828  dict.insert(QStringLiteral("str4"), QStringLiteral("my String"));
1829  dict.insert(QStringLiteral("str5"), QStringLiteral("My string"));
1830  dict.insert(QStringLiteral("str6"), QStringLiteral("123"));
1831  dict.insert(QStringLiteral("str7"), QString());
1832 
1833  QTest::newRow("type-accessors-string34")
1834  << QStringLiteral("{{ str1.title }}") << dict
1835  << QStringLiteral("My String") << NoError;
1836  QTest::newRow("type-accessors-string35")
1837  << QStringLiteral("{{ str2.title }}") << dict
1838  << QStringLiteral("Mystring") << NoError;
1839  QTest::newRow("type-accessors-string36")
1840  << QStringLiteral("{{ str3.title }}") << dict
1841  << QStringLiteral("My String") << NoError;
1842  QTest::newRow("type-accessors-string37")
1843  << QStringLiteral("{{ str4.title }}") << dict
1844  << QStringLiteral("My String") << NoError;
1845  QTest::newRow("type-accessors-string38")
1846  << QStringLiteral("{{ str5.title }}") << dict
1847  << QStringLiteral("My String") << NoError;
1848 
1849  QTest::newRow("type-accessors-string39")
1850  << QStringLiteral("{{ str1.upper }}") << dict
1851  << QStringLiteral("MY STRING") << NoError;
1852  QTest::newRow("type-accessors-string40")
1853  << QStringLiteral("{{ str2.upper }}") << dict
1854  << QStringLiteral("MYSTRING") << NoError;
1855  QTest::newRow("type-accessors-string41")
1856  << QStringLiteral("{{ str3.upper }}") << dict
1857  << QStringLiteral("MY STRING") << NoError;
1858  QTest::newRow("type-accessors-string42")
1859  << QStringLiteral("{{ str3.dne }}") << dict << QString() << NoError;
1860  QTest::newRow("type-accessors-string43")
1861  << QStringLiteral("{{ str2.isalpha }}") << dict << QStringLiteral("True")
1862  << NoError;
1863  QTest::newRow("type-accessors-string44")
1864  << QStringLiteral("{{ str3.isalpha }}") << dict << QStringLiteral("False")
1865  << NoError;
1866  QTest::newRow("type-accessors-string45")
1867  << QStringLiteral("{{ str6.isalpha }}") << dict << QStringLiteral("False")
1868  << NoError;
1869  QTest::newRow("type-accessors-string46")
1870  << QStringLiteral("{{ str7.isalpha }}") << dict << QStringLiteral("False")
1871  << NoError;
1872 
1873  dict.clear();
1874 
1875 #define SON(obj) obj->setObjectName(QStringLiteral(#obj))
1876 
1877  auto obj1 = new QObject(this);
1878  SON(obj1);
1879  auto obj2 = new QObject(this);
1880  SON(obj2);
1881  obj2->setParent(obj1);
1882  auto obj3 = new QObject(this);
1883  obj3->setParent(obj2);
1884  SON(obj3);
1885  auto obj4 = new QObject(this);
1886  obj4->setParent(obj2);
1887  SON(obj4);
1888 
1889  dict.insert(QStringLiteral("object"), QVariant::fromValue(obj1));
1890 
1891  QTest::newRow("type-accessors-qobject01")
1892  << QStringLiteral("{{ object.objectName }}") << dict
1893  << QStringLiteral("obj1") << NoError;
1894 
1895  const QLatin1String objectDumper("<li>{{ object.objectName }}</li>"
1896  "{% if object.children %}"
1897  "<ul>"
1898  "{% for object in object.children %}"
1899  "{% include 'objectdumper.html' %}"
1900  "{% endfor %}"
1901  "</ul>"
1902  "{% endif %}");
1903 
1904  m_loader->setTemplate(QStringLiteral("objectdumper.html"), objectDumper);
1905 
1906  QTest::newRow("type-accessors-qobject02")
1907  << QStringLiteral("<ul>{% include 'objectdumper.html' %}</ul>") << dict
1908  << QString::fromLatin1("<ul>"
1909  "<li>obj1</li>"
1910  "<ul>"
1911  "<li>obj2</li>"
1912  "<ul>"
1913  "<li>obj3</li>"
1914  "<li>obj4</li>"
1915  "</ul>"
1916  "</ul>"
1917  "</ul>")
1918  << NoError;
1919 }
1920 
1921 void TestBuiltinSyntax::testDynamicProperties_data()
1922 {
1923  QTest::addColumn<QString>("input");
1924  QTest::addColumn<Dict>("dict");
1925  QTest::addColumn<QString>("output");
1926  QTest::addColumn<Cutelee::Error>("error");
1927 
1928  Dict dict;
1929 
1930  auto obj = new QObject(this);
1931  obj->setProperty("prop", 7);
1932  dict.insert(QStringLiteral("var"),
1933  QVariant::fromValue(static_cast<QObject *>(obj)));
1934 
1935  QTest::newRow("dynamic-properties01")
1936  << QStringLiteral("{{ var.prop }}") << dict << QStringLiteral("7")
1937  << NoError;
1938 }
1939 
1940 void TestBuiltinSyntax::testGarbageInput()
1941 {
1942  QFETCH(QString, input);
1943 
1944  auto t = m_engine->newTemplate(input, QLatin1String(QTest::currentDataTag()));
1945 
1946  Dict dict;
1947 
1948  Context context(dict);
1949 
1950  auto result = t->render(&context);
1951 
1952  QCOMPARE(t->error(), NoError);
1953 
1954  QCOMPARE(result, input);
1955 }
1956 
1957 void TestBuiltinSyntax::testGarbageInput_data()
1958 {
1959 
1960  QTest::addColumn<QString>("input");
1961 
1962  QTest::newRow("garbage-input01") << QStringLiteral("content %}");
1963  QTest::newRow("garbage-input02") << QStringLiteral(" content %}");
1964  QTest::newRow("garbage-input03") << QStringLiteral("content #}");
1965  QTest::newRow("garbage-input04") << QStringLiteral(" content #}");
1966  QTest::newRow("garbage-input05") << QStringLiteral("content }}");
1967  QTest::newRow("garbage-input06") << QStringLiteral(" content }}");
1968  QTest::newRow("garbage-input07") << QStringLiteral("% content %}");
1969  QTest::newRow("garbage-input08") << QStringLiteral("# content #}");
1970  QTest::newRow("garbage-input09") << QStringLiteral("{ content }}");
1971  QTest::newRow("garbage-input10") << QStringLiteral("{% content }");
1972  QTest::newRow("garbage-input11") << QStringLiteral("{% content %");
1973  QTest::newRow("garbage-input12") << QStringLiteral("{# content }");
1974  QTest::newRow("garbage-input13") << QStringLiteral("{# content #");
1975  QTest::newRow("garbage-input14") << QStringLiteral("{{ content }");
1976  QTest::newRow("garbage-input15") << QStringLiteral("{{ content }");
1977  QTest::newRow("garbage-input16") << QStringLiteral("{{ content %}");
1978  QTest::newRow("garbage-input17") << QStringLiteral("{% content }}");
1979  QTest::newRow("garbage-input18") << QStringLiteral("{{ content #}");
1980  QTest::newRow("garbage-input19") << QStringLiteral("{# content }}");
1981  QTest::newRow("garbage-input20") << QStringLiteral("{{ con #} tent #}");
1982  QTest::newRow("garbage-input21") << QStringLiteral("{{ con %} tent #}");
1983  QTest::newRow("garbage-input22") << QStringLiteral("{{ con #} tent %}");
1984  QTest::newRow("garbage-input23") << QStringLiteral("{{ con %} tent %}");
1985  QTest::newRow("garbage-input24") << QStringLiteral("{% con #} tent #}");
1986  QTest::newRow("garbage-input25") << QStringLiteral("{% con }} tent #}");
1987  QTest::newRow("garbage-input26") << QStringLiteral("{% con #} tent }}");
1988  QTest::newRow("garbage-input27") << QStringLiteral("{% con }} tent }}");
1989  QTest::newRow("garbage-input28") << QStringLiteral("{# con %} tent %}");
1990  QTest::newRow("garbage-input29") << QStringLiteral("{# con }} tent %}");
1991  QTest::newRow("garbage-input30") << QStringLiteral("{# con %} tent }}");
1992  QTest::newRow("garbage-input31") << QStringLiteral("{# con }} tent }}");
1993  QTest::newRow("garbage-input32") << QStringLiteral("{# con {# tent }}");
1994  QTest::newRow("garbage-input33") << QStringLiteral("{# con {% tent }}");
1995  QTest::newRow("garbage-input34") << QStringLiteral("{% con {% tent }}");
1996  QTest::newRow("garbage-input35") << QStringLiteral("{ { content }}");
1997  QTest::newRow("garbage-input36") << QStringLiteral("{ % content %}");
1998  QTest::newRow("garbage-input37") << QStringLiteral("{ # content #}");
1999  QTest::newRow("garbage-input38") << QStringLiteral("{\n{ content }}");
2000  QTest::newRow("garbage-input39") << QStringLiteral("{\n# content #}");
2001  QTest::newRow("garbage-input40") << QStringLiteral("{\n% content %}");
2002  QTest::newRow("garbage-input41") << QStringLiteral("{{\n content }}");
2003  QTest::newRow("garbage-input42") << QStringLiteral("{#\n content #}");
2004  QTest::newRow("garbage-input43") << QStringLiteral("{%\n content %}");
2005  QTest::newRow("garbage-input44") << QStringLiteral("{{ content \n}}");
2006  QTest::newRow("garbage-input45") << QStringLiteral("{# content \n#}");
2007  QTest::newRow("garbage-input46") << QStringLiteral("{% content \n%}");
2008  QTest::newRow("garbage-input47") << QStringLiteral("{{ content }\n}");
2009  QTest::newRow("garbage-input48") << QStringLiteral("{# content #\n}");
2010  QTest::newRow("garbage-input49") << QStringLiteral("{% content %\n}");
2011  QTest::newRow("garbage-input50") << QStringLiteral("{{ content } }");
2012  QTest::newRow("garbage-input51") << QStringLiteral("{% content % }");
2013  QTest::newRow("garbage-input52") << QStringLiteral("{# content # }");
2014  QTest::newRow("garbage-input53") << QStringLiteral("{ { content } }");
2015  QTest::newRow("garbage-input54") << QStringLiteral("{ % content % }");
2016  QTest::newRow("garbage-input55") << QStringLiteral("{ # content # }");
2017  QTest::newRow("garbage-input56") << QStringLiteral("{{ content }%");
2018  QTest::newRow("garbage-input57") << QStringLiteral("{# content #%");
2019  QTest::newRow("garbage-input58") << QStringLiteral("{% content %%");
2020  QTest::newRow("garbage-input59") << QStringLiteral("{{ content }A");
2021  QTest::newRow("garbage-input60") << QStringLiteral("{# content #A");
2022  QTest::newRow("garbage-input61") << QStringLiteral("{% content %A");
2023  QTest::newRow("garbage-input62") << QStringLiteral("{{ content A}");
2024  QTest::newRow("garbage-input63") << QStringLiteral("{# content A#");
2025  QTest::newRow("garbage-input64") << QStringLiteral("{% content A%");
2026  QTest::newRow("garbage-input65") << QStringLiteral("{# content A}");
2027  QTest::newRow("garbage-input66") << QStringLiteral("{% content A}");
2028  QTest::newRow("garbage-input67") << QStringLiteral("A{ content }}");
2029  QTest::newRow("garbage-input68") << QStringLiteral("A# content #}");
2030  QTest::newRow("garbage-input69") << QStringLiteral("A% content %}");
2031  QTest::newRow("garbage-input60") << QStringLiteral("{A content }}");
2032  QTest::newRow("garbage-input71") << QStringLiteral("{A content #}");
2033  QTest::newRow("garbage-input72") << QStringLiteral("{A content %}");
2034  QTest::newRow("garbage-input73") << QStringLiteral("{A content #}");
2035  QTest::newRow("garbage-input74") << QStringLiteral("{A content %}");
2036  QTest::newRow("garbage-input75") << QStringLiteral("{A content A}");
2037  QTest::newRow("garbage-input76") << QStringLiteral("}} content }}");
2038  QTest::newRow("garbage-input77") << QStringLiteral("}} content {{");
2039  QTest::newRow("garbage-input78") << QStringLiteral("#} content #}");
2040  QTest::newRow("garbage-input79") << QStringLiteral("#} content {#");
2041  QTest::newRow("garbage-input80") << QStringLiteral("%} content %}");
2042  QTest::newRow("garbage-input81") << QStringLiteral("%} content {%");
2043  QTest::newRow("garbage-input82") << QStringLiteral("#{ content }#");
2044  QTest::newRow("garbage-input83") << QStringLiteral("%{ content }%");
2045 }
2046 
2047 void TestBuiltinSyntax::testInsignificantWhitespace()
2048 {
2049  QFETCH(QString, input);
2050  QFETCH(Dict, dict);
2051  QFETCH(QString, stripped_output);
2052  QFETCH(QString, unstripped_output);
2053 
2054  Context context(dict);
2055 
2056  QVERIFY(!m_engine->smartTrimEnabled());
2057  m_engine->setSmartTrimEnabled(true);
2058  QVERIFY(m_engine->smartTrimEnabled());
2059 
2060  {
2061  auto t
2062  = m_engine->newTemplate(input, QLatin1String(QTest::currentDataTag()));
2063 
2064  auto result = t->render(&context);
2065 
2066  QCOMPARE(t->error(), NoError);
2067 
2068  QCOMPARE(result, stripped_output);
2069  }
2070  m_engine->setSmartTrimEnabled(false);
2071  {
2072  auto t
2073  = m_engine->newTemplate(input, QLatin1String(QTest::currentDataTag()));
2074 
2075  auto result = t->render(&context);
2076 
2077  QCOMPARE(t->error(), NoError);
2078 
2079  QCOMPARE(result, unstripped_output);
2080  }
2081 }
2082 
2083 void TestBuiltinSyntax::testInsignificantWhitespace_data()
2084 {
2085  QTest::addColumn<QString>("input");
2086  QTest::addColumn<Dict>("dict");
2087  QTest::addColumn<QString>("stripped_output");
2088  QTest::addColumn<QString>("unstripped_output");
2089 
2090  Dict dict;
2091 
2092  QTest::newRow("insignificant-whitespace01")
2093  << QStringLiteral("\n {% templatetag openblock %}\n") << dict
2094  << QStringLiteral("{%\n") << QStringLiteral("\n {%\n");
2095 
2096  QTest::newRow("insignificant-whitespace02")
2097  << QStringLiteral("\n{% templatetag openblock %}\n") << dict
2098  << QStringLiteral("{%\n") << QStringLiteral("\n{%\n");
2099 
2100  QTest::newRow("insignificant-whitespace03")
2101  << QStringLiteral("{% templatetag openblock %}\n") << dict
2102  << QStringLiteral("{%\n") << QStringLiteral("{%\n");
2103 
2104  QTest::newRow("insignificant-whitespace04")
2105  << QStringLiteral("\n\t \t {% templatetag openblock %}\n") << dict
2106  << QStringLiteral("{%\n") << QStringLiteral("\n\t \t {%\n");
2107 
2108  // Leading whitespace with text before single template tag
2109  QTest::newRow("insignificant-whitespace05")
2110  << QStringLiteral("\n some\ttext {% templatetag openblock %}\n") << dict
2111  << QStringLiteral("\n some\ttext {%\n")
2112  << QStringLiteral("\n some\ttext {%\n");
2113 
2114  // Leading line with text before single template tag
2115  QTest::newRow("insignificant-whitespace06")
2116  << QStringLiteral("\n some\ttext\n {% templatetag openblock %}\n") << dict
2117  << QStringLiteral("\n some\ttext{%\n")
2118  << QStringLiteral("\n some\ttext\n {%\n");
2119  QTest::newRow("insignificant-whitespace07")
2120  << QStringLiteral("\n some\ttext \n \t {% templatetag openblock %}\n")
2121  << dict << QStringLiteral("\n some\ttext {%\n")
2122  << QStringLiteral("\n some\ttext \n \t {%\n");
2123 
2124  // whitespace leading /before/ the newline is not stripped.
2125  QTest::newRow("insignificant-whitespace08")
2126  << QStringLiteral("\n some\ttext \t \n {% templatetag openblock %}\n")
2127  << dict << QStringLiteral("\n some\ttext \t {%\n")
2128  << QStringLiteral("\n some\ttext \t \n {%\n");
2129 
2130  // Multiple text lines before tag
2131  QTest::newRow("insignificant-whitespace09")
2132  << QStringLiteral("\n some\ntext \t \n {% templatetag openblock %}\n")
2133  << dict << QStringLiteral("\n some\ntext \t {%\n")
2134  << QStringLiteral("\n some\ntext \t \n {%\n");
2135  QTest::newRow("insignificant-whitespace10")
2136  << QStringLiteral(
2137  "\n some \t \n \t text \t \n {% templatetag openblock %}\n")
2138  << dict << QStringLiteral("\n some \t \n \t text \t {%\n")
2139  << QStringLiteral("\n some \t \n \t text \t \n {%\n");
2140 
2141  // Leading whitespace before tag, some text after
2142  QTest::newRow("insignificant-whitespace11")
2143  << QStringLiteral("\n \t {% templatetag openblock %} some text\n")
2144  << dict << QStringLiteral("\n \t {% some text\n")
2145  << QStringLiteral("\n \t {% some text\n");
2146 
2147  // Leading whitespace before tag, some text with trailing whitespace after
2148  QTest::newRow("insignificant-whitespace12")
2149  << QStringLiteral("\n \t {% templatetag openblock %} some text \t \n")
2150  << dict << QStringLiteral("\n \t {% some text \t \n")
2151  << QStringLiteral("\n \t {% some text \t \n");
2152 
2153  // Whitespace after tag is not removed
2154  QTest::newRow("insignificant-whitespace13")
2155  << QStringLiteral(
2156  "\n \t {% templatetag openblock %} \t \n \t some text \t \n")
2157  << dict << QStringLiteral("{% \t \n \t some text \t \n")
2158  << QStringLiteral("\n \t {% \t \n \t some text \t \n");
2159 
2160  // Multiple lines of leading whitespace. Only one leading newline is removed
2161  QTest::newRow("insignificant-whitespace14")
2162  << QStringLiteral("\n\n\n{% templatetag openblock %}\n some text\n")
2163  << dict << QStringLiteral("\n\n{%\n some text\n")
2164  << QStringLiteral("\n\n\n{%\n some text\n");
2165 
2166  // Trailing whitespace after tag
2167  QTest::newRow("insignificant-whitespace15")
2168  << QStringLiteral(
2169  "\n\n\n{% templatetag openblock %}\t \t \t\n some text\n")
2170  << dict << QStringLiteral("\n\n{%\t \t \t\n some text\n")
2171  << QStringLiteral("\n\n\n{%\t \t \t\n some text\n");
2172 
2173  // Removable newline followed by leading whitespace
2174  QTest::newRow("insignificant-whitespace16")
2175  << QStringLiteral(
2176  "\n\n\n\t \t \t{% templatetag openblock %}\n some text\n")
2177  << dict << QStringLiteral("\n\n{%\n some text\n")
2178  << QStringLiteral("\n\n\n\t \t \t{%\n some text\n");
2179 
2180  // Removable leading whitespace and trailing whitespace
2181  QTest::newRow("insignificant-whitespace17")
2182  << QStringLiteral(
2183  "\n\n\n\t \t \t{% templatetag openblock %}\t \t \t\n some text\n")
2184  << dict << QStringLiteral("\n\n{%\t \t \t\n some text\n")
2185  << QStringLiteral("\n\n\n\t \t \t{%\t \t \t\n some text\n");
2186 
2187  // Multiple lines of trailing whitespace. No trailing newline is removed.
2188  QTest::newRow("insignificant-whitespace18")
2189  << QStringLiteral("\n{% templatetag openblock %}\n\n\n some text\n")
2190  << dict << QStringLiteral("{%\n\n\n some text\n")
2191  << QStringLiteral("\n{%\n\n\n some text\n");
2192  QTest::newRow("insignificant-whitespace19")
2193  << QStringLiteral("\n{% templatetag openblock %}\t \n\n\n some text\n")
2194  << dict << QStringLiteral("{%\t \n\n\n some text\n")
2195  << QStringLiteral("\n{%\t \n\n\n some text\n");
2196 
2197  // Consecutive trimmed lines with tags strips one newline each
2198  QTest::newRow("insignificant-whitespace20")
2199  << QStringLiteral(
2200  "\n{% templatetag openblock %}\n{% templatetag openblock %}\n{% "
2201  "templatetag openblock %}\n some text\n")
2202  << dict << QStringLiteral("{%{%{%\n some text\n")
2203  << QStringLiteral("\n{%\n{%\n{%\n some text\n");
2204 
2205  // Consecutive trimmed lines with tags strips one newline each. Intermediate
2206  // newlines are preserved
2207  QTest::newRow("insignificant-whitespace21")
2208  << QStringLiteral(
2209  "\n\n{% templatetag openblock %}\n\n{% templatetag openblock "
2210  "%}\n\n{% templatetag openblock %}\n\n some text\n")
2211  << dict << QStringLiteral("\n{%\n{%\n{%\n\n some text\n")
2212  << QStringLiteral("\n\n{%\n\n{%\n\n{%\n\n some text\n");
2213 
2214  // Consecutive trimmed lines with tags strips one newline each. Leading
2215  // whitespace is stripped but trailing is not
2216  QTest::newRow("insignificant-whitespace22")
2217  << QStringLiteral("\n\n\t {% templatetag openblock %}\t \n\n\t {% "
2218  "templatetag openblock %}\t \n\n\t {% templatetag "
2219  "openblock %}\t \n some text\n")
2220  << dict << QStringLiteral("\n{%\t \n{%\t \n{%\t \n some text\n")
2221  << QStringLiteral("\n\n\t {%\t \n\n\t {%\t \n\n\t {%\t \n some text\n");
2222 
2223  // Consecutive trimmed lines with tags strips one newline each. Intermediate
2224  // whitespace is stripped
2225  QTest::newRow("insignificant-whitespace23")
2226  << QStringLiteral(
2227  "\n\t {% templatetag openblock %}\t \n\t {% templatetag openblock "
2228  "%}\t \n\t {% templatetag openblock %}\t \n some text\n")
2229  << dict << QStringLiteral("{%\t {%\t {%\t \n some text\n")
2230  << QStringLiteral("\n\t {%\t \n\t {%\t \n\t {%\t \n some text\n");
2231 
2232  // Intermediate whitespace on one line is preserved
2233  // Consecutive tags on one line do not have intermediate whitespace or
2234  // leading
2235  // whitespace stripped
2236  QTest::newRow("insignificant-whitespace24")
2237  << QStringLiteral(
2238  "\n\t {% templatetag openblock %}\t \t {% templatetag openblock "
2239  "%}\t \t {% templatetag openblock %}\t \n some text\n")
2240  << dict << QStringLiteral("\n\t {%\t \t {%\t \t {%\t \n some text\n")
2241  << QStringLiteral("\n\t {%\t \t {%\t \t {%\t \n some text\n");
2242 
2243  // Still, only one leading newline is removed.
2244  QTest::newRow("insignificant-whitespace25")
2245  << QStringLiteral(
2246  "\n\n {% templatetag openblock %}\n \t {% templatetag openblock "
2247  "%}\n \t {% templatetag openblock %}\n some text\n")
2248  << dict << QStringLiteral("\n{%{%{%\n some text\n")
2249  << QStringLiteral("\n\n {%\n \t {%\n \t {%\n some text\n");
2250 
2251  // Lines with {# comments #} have the same stripping behavior
2252  QTest::newRow("insignificant-whitespace26")
2253  << QStringLiteral("\n\n {% templatetag openblock %}\n \t {# some comment "
2254  "#}\n some text\n")
2255  << dict << QStringLiteral("\n{%\n some text\n")
2256  << QStringLiteral("\n\n {%\n \t \n some text\n");
2257 
2258  // Only {# comments #}
2259  QTest::newRow("insignificant-whitespace27")
2260  << QStringLiteral(
2261  "\n\n {# a comment #}\n \t {# some comment #}\n some text\n")
2262  << dict << QStringLiteral("\n\n some text\n")
2263  << QStringLiteral("\n\n \n \t \n some text\n");
2264 
2265  // Consecutive newlines with tags and comments
2266  QTest::newRow("insignificant-whitespace28")
2267  << QStringLiteral(
2268  "\n\t {% templatetag openblock %}\t \n\t {# some comment #}\t "
2269  "\n\t {% templatetag openblock %}\t \n some text\n")
2270  << dict << QStringLiteral("{%\t \t {%\t \n some text\n")
2271  << QStringLiteral("\n\t {%\t \n\t \t \n\t {%\t \n some text\n");
2272 
2273  dict.insert(QStringLiteral("spam"), QStringLiteral("ham"));
2274  // Lines with only {{ values }} have the same stripping behavior
2275  QTest::newRow("insignificant-whitespace29")
2276  << QStringLiteral("\n {% templatetag openblock %}\t\n \t {{ spam }}\t \n "
2277  "\t {% templatetag openblock %}\t \n some text\n")
2278  << dict << QStringLiteral("{%\tham\t {%\t \n some text\n")
2279  << QStringLiteral("\n {%\t\n \t ham\t \n \t {%\t \n some text\n");
2280  QTest::newRow("insignificant-whitespace30")
2281  << QStringLiteral(
2282  "\n\n {% templatetag openblock %}\t\n\n \t {{ spam }}\t \n\n \t "
2283  "{% templatetag openblock %}\t \n some text\n")
2284  << dict << QStringLiteral("\n{%\t\nham\t \n{%\t \n some text\n")
2285  << QStringLiteral("\n\n {%\t\n\n \t ham\t \n\n \t {%\t \n some text\n");
2286 
2287  // Leading whitespace not stripped when followed by anything. See
2288  // templatetag-whitespace24
2289  QTest::newRow("insignificant-whitespace31")
2290  << QStringLiteral("\n {% templatetag openblock %}\t \t {{ spam }}\t \t "
2291  "{% templatetag openblock %}\t \n some text\n")
2292  << dict << QStringLiteral("\n {%\t \t ham\t \t {%\t \n some text\n")
2293  << QStringLiteral("\n {%\t \t ham\t \t {%\t \n some text\n");
2294 
2295  // {{ value }} {% tag %} {{ value }} this time
2296  QTest::newRow("insignificant-whitespace32")
2297  << QStringLiteral("\n {{ spam }}\t\n \t {% templatetag openblock %}\t \n "
2298  "\t {{ spam }}\t \n some text\n")
2299  << dict << QStringLiteral("ham\t{%\t ham\t \n some text\n")
2300  << QStringLiteral("\n ham\t\n \t {%\t \n \t ham\t \n some text\n");
2301 
2302  // Invalid stuff is still invalid
2303  // Newlines inside begin-end tokens, even in {# comments #}, make it not a
2304  // tag.
2305  QTest::newRow("insignificant-whitespace33")
2306  << QStringLiteral(
2307  "\n\n {# \n{% templatetag openblock #}\t \n some text\n")
2308  << dict
2309  << QStringLiteral(
2310  "\n\n {# \n{% templatetag openblock #}\t \n some text\n")
2311  << QStringLiteral(
2312  "\n\n {# \n{% templatetag openblock #}\t \n some text\n");
2313 
2314  // Complete comment matching tags on one line are processed
2315  QTest::newRow("insignificant-whitespace34")
2316  << QStringLiteral(
2317  "\n\n {# \n{# templatetag openblock #}\t \n some text\n")
2318  << dict << QStringLiteral("\n\n {# \t \n some text\n")
2319  << QStringLiteral("\n\n {# \n\t \n some text\n");
2320  QTest::newRow("insignificant-whitespace35")
2321  << QStringLiteral(
2322  "\n\n {# \n{# templatetag openblock\n #}\t \n some text\n")
2323  << dict
2324  << QStringLiteral(
2325  "\n\n {# \n{# templatetag openblock\n #}\t \n some text\n")
2326  << QStringLiteral(
2327  "\n\n {# \n{# templatetag openblock\n #}\t \n some text\n");
2328  QTest::newRow("insignificant-whitespace36")
2329  << QStringLiteral("\n\n {# \n{{ some comment #}\t \n some text\n") << dict
2330  << QStringLiteral("\n\n {# \n{{ some comment #}\t \n some text\n")
2331  << QStringLiteral("\n\n {# \n{{ some comment #}\t \n some text\n");
2332  QTest::newRow("insignificant-whitespace37")
2333  << QStringLiteral(
2334  "\n\n {# \n \t {% templatetag openblock #}\t \n some text\n")
2335  << dict
2336  << QStringLiteral(
2337  "\n\n {# \n \t {% templatetag openblock #}\t \n some text\n")
2338  << QStringLiteral(
2339  "\n\n {# \n \t {% templatetag openblock #}\t \n some text\n");
2340  QTest::newRow("insignificant-whitespace38")
2341  << QStringLiteral("\n\n {# templatetag openblock #\n}\t \n some text\n")
2342  << dict
2343  << QStringLiteral("\n\n {# templatetag openblock #\n}\t \n some text\n")
2344  << QStringLiteral("\n\n {# templatetag openblock #\n}\t \n some text\n");
2345  QTest::newRow("insignificant-whitespace39")
2346  << QStringLiteral("\n\n {% templatetag openblock %\n}\t \n some text\n")
2347  << dict
2348  << QStringLiteral("\n\n {% templatetag openblock %\n}\t \n some text\n")
2349  << QStringLiteral("\n\n {% templatetag openblock %\n}\t \n some text\n");
2350  QTest::newRow("insignificant-whitespace40")
2351  << QStringLiteral("\n\n {{ templatetag openblock }\n}\t \n some text\n")
2352  << dict
2353  << QStringLiteral("\n\n {{ templatetag openblock }\n}\t \n some text\n")
2354  << QStringLiteral("\n\n {{ templatetag openblock }\n}\t \n some text\n");
2355  QTest::newRow("insignificant-whitespace41")
2356  << QStringLiteral(
2357  "\n\n {\n# {# templatetag openblock #}\t \n some text\n")
2358  << dict << QStringLiteral("\n\n {\n# \t \n some text\n")
2359  << QStringLiteral("\n\n {\n# \t \n some text\n");
2360  QTest::newRow("insignificant-whitespace42")
2361  << QStringLiteral("\n\n {\n {# templatetag openblock #}\t \n some text\n")
2362  << dict << QStringLiteral("\n\n {\t \n some text\n")
2363  << QStringLiteral("\n\n {\n \t \n some text\n");
2364  QTest::newRow("insignificant-whitespace43")
2365  << QStringLiteral("\n{{# foo #};{# bar #}\n") << dict
2366  << QStringLiteral("\n{;\n") << QStringLiteral("\n{;\n");
2367 
2368  QTest::newRow("insignificant-whitespace44")
2369  << QStringLiteral("\n{{ foo }} ") << dict << QString()
2370  << QStringLiteral("\n ");
2371 }
2372 
2373 QTEST_MAIN(TestBuiltinSyntax)
2374 #include "testbuiltins.moc"
2375 
2376 #endif
void setPluginPaths(const QStringList &dirs)
Definition: engine.cpp:87
bool variantIsTrue(const QVariant &variant)
Definition: util.cpp:39
The Template class is a tree of nodes which may be rendered.
Definition: template.h:94
QString escape(const QString &input) const override
QHash::iterator insert(const Key &key, const T &value)
void addTemplateLoader(std::shared_ptr< AbstractTemplateLoader > loader)
Definition: engine.cpp:68
bool isTrue(Context *c) const
Definition: variable.cpp:145
The Context class holds the context to render a Template with.
Definition: context.h:118
The Cutelee namespace holds all public Cutelee API.
Definition: Mainpage.dox:7
bool isLocalized() const
Definition: variable.cpp:147
Implements a loader decorator which caches compiled Template objects.
A container for static variables defined in Templates.
Definition: variable.h:52
void * construct(int type, const void *copy)
void clear()
void append(const T &value)
QChar fromLatin1(char c)
QString escape(const QString &input) const override
QString absoluteFilePath() const const
bool isEmpty() const const
QString render(Context *c) const
Definition: template.cpp:74
Template loadByName(const QString &name) const
Definition: engine.cpp:370
QString errorString() const
Definition: template.cpp:128
std::shared_ptr< OutputStream > clone(QTextStream *stream) const override
Cutelee::Engine is the main entry point for creating Cutelee Templates.
Definition: engine.h:120
Utility functions used throughout Cutelee.
A QString wrapper class for containing whether a string is safe or needs to be escaped.
Definition: safestring.h:91
The OutputStream class is used to render templates to a QTextStream.
Definition: outputstream.h:80
QVariant fromValue(const T &value)
std::shared_ptr< OutputStream > clone(QTextStream *stream) const override
QString & replace(int position, int n, QChar after)
QChar toUpper() const const
The InMemoryTemplateLoader loads Templates set dynamically in memory.
int length() const const
QString fromLatin1(const char *str, int size)
A FilterExpression object represents a filter expression in a template.
The FileSystemTemplateLoader loads Templates from the file system.
void insert(const QString &name, QObject *object)
Definition: context.cpp:145
Error error() const
Definition: template.cpp:122