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
38typedef QHash<QString, QVariant> Dict;
39
40Q_DECLARE_METATYPE(Cutelee::Error)
41
42using namespace Cutelee;
43
47class OtherClass : public QObject
48{
50 Q_PROPERTY(QString method READ method CONSTANT)
51 Q_PROPERTY(Animals animals READ animals CONSTANT)
52public:
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
63private:
64 Animals m_animals = Tigers;
65};
66
70class SomeClass : public QObject
71{
73 Q_PROPERTY(QString method READ method CONSTANT)
74 Q_PROPERTY(QVariant otherClass READ otherClass CONSTANT)
75
76public:
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
94private:
95 QObject *m_other;
96};
97
101class GadgetClass
102{
103 Q_GADGET
104 Q_PROPERTY(PersonName personName READ personName)
105public:
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; }
113private:
114 PersonName m_personName = Oliver;
115};
116Q_DECLARE_METATYPE(GadgetClass)
117
118class NoEscapeOutputStream : public OutputStream
119{
120public:
121 NoEscapeOutputStream() : OutputStream() {}
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
133class JSOutputStream : public OutputStream
134{
135public:
136 JSOutputStream() : OutputStream() {}
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
186class TestBuiltinSyntax : public CoverageObject
187{
189
190private 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
246private:
247 Engine *m_engine;
248
249 std::shared_ptr<InMemoryTemplateLoader> m_loader;
250
251 Engine *getEngine();
252
253 void doTest();
254};
255
256void 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
303void 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
401void TestBuiltinSyntax::testTruthiness()
402{
403 QFETCH(QVariant, input);
404 QFETCH(bool, expected);
405
406 QVERIFY(variantIsTrue(input) == expected);
407}
408
409void 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
439void 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
448Engine *TestBuiltinSyntax::getEngine()
449{
450 auto engine = new Engine(this);
451 engine->setPluginPaths({QStringLiteral(CUTELEE_PLUGIN_PATH)});
452 return engine;
453}
454
455void TestBuiltinSyntax::cleanupTestCase() { delete m_engine; }
456
457void 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
491void 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
738void 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
1103void 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
1163void 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
1283void 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
1323void 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
1337void 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
1401void 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
1454void 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
1490void 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
1501void 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
1527void 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
1538void 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
1564void 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
1595void 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
1633void 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
1921void 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
1940void 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
1957void 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
2047void 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
2083void 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
2373QTEST_MAIN(TestBuiltinSyntax)
2374#include "testbuiltins.moc"
2375
2376#endif
void insert(const QString &name, QObject *object)
Definition context.cpp:145
Cutelee::Engine is the main entry point for creating Cutelee Templates.
Definition engine.h:121
Template loadByName(const QString &name) const
Definition engine.cpp:370
void setPluginPaths(const QStringList &dirs)
Definition engine.cpp:87
void addTemplateLoader(std::shared_ptr< AbstractTemplateLoader > loader)
Definition engine.cpp:68
The FileSystemTemplateLoader loads Templates from the file system.
QString errorString() const
Definition template.cpp:128
QString render(Context *c) const
Definition template.cpp:74
Error error() const
Definition template.cpp:122
bool isTrue(Context *c) const
Definition variable.cpp:145
bool isLocalized() const
Definition variable.cpp:147
std::shared_ptr< OutputStream > clone(QTextStream *stream) const override
QString escape(const QString &input) const override
std::shared_ptr< OutputStream > clone(QTextStream *stream) const override
QString escape(const QString &input) const override
The Cutelee namespace holds all public Cutelee API.
Definition Mainpage.dox:8
bool variantIsTrue(const QVariant &variant)
Definition util.cpp:39
QChar fromLatin1(char c)
void clear()
iterator insert(const Key &key, const T &value)
void append(const T &value)
void * construct(int type, const void *copy)
QObject(QObject *parent)
Q_ENUM(...)
Q_OBJECTQ_OBJECT
Q_PROPERTY(...)
Q_SLOTSQ_SLOTS
QObject * parent() const const
void clear()
QString fromLatin1(const char *str, int size)
bool isEmpty() const const
QVariant fromValue(const T &value)
Utility functions used throughout Cutelee.