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

0001 /*
0002   This file is part of the KTextTemplate library
0003 
0004   SPDX-FileCopyrightText: 2009, 2010 Stephen Kelly <steveire@gmail.com>
0005 
0006   SPDX-License-Identifier: LGPL-2.1-or-later
0007 
0008 */
0009 
0010 #ifndef BUILTINSTEST_H
0011 #define BUILTINSTEST_H
0012 
0013 #include <QDebug>
0014 #include <QFileInfo>
0015 #include <QTest>
0016 
0017 #include "cachingloaderdecorator.h"
0018 #include "context.h"
0019 #include "engine.h"
0020 #include "filterexpression.h"
0021 #include "ktexttemplate_paths.h"
0022 #include "template.h"
0023 #include "util.h"
0024 #include <metaenumvariable_p.h>
0025 
0026 using Dict = QHash<QString, QVariant>;
0027 
0028 Q_DECLARE_METATYPE(KTextTemplate::Error)
0029 
0030 using namespace KTextTemplate;
0031 
0032 /**
0033   For use with tests.
0034 */
0035 class OtherClass : public QObject
0036 {
0037     Q_OBJECT
0038     Q_PROPERTY(QString method READ method)
0039     Q_PROPERTY(Animals animals READ animals)
0040 public:
0041     enum Animals { Lions, Tigers, Bears };
0042     Q_ENUMS(Animals)
0043 
0044     OtherClass(QObject *parent = {})
0045         : QObject(parent)
0046     {
0047     }
0048     OtherClass(Animals animals, QObject *parent = {})
0049         : QObject(parent)
0050         , m_animals(animals)
0051     {
0052     }
0053 
0054     Animals animals() const
0055     {
0056         return m_animals;
0057     }
0058 
0059     QString method() const
0060     {
0061         return QStringLiteral("OtherClass::method");
0062     }
0063 
0064 private:
0065     Animals m_animals = Tigers;
0066 };
0067 
0068 /**
0069   For use with tests.
0070 */
0071 class SomeClass : public QObject
0072 {
0073     Q_OBJECT
0074     Q_PROPERTY(QString method READ method)
0075     Q_PROPERTY(QVariant otherClass READ otherClass)
0076 
0077 public:
0078     enum FirstEnum { Employee, Employer, Manager };
0079 
0080     enum SecondEnum { Voter = 2, Consumer = 4, Citizen = 8 };
0081 
0082     Q_ENUMS(FirstEnum SecondEnum)
0083 
0084     SomeClass(QObject *parent = {})
0085         : QObject(parent)
0086         , m_other(new OtherClass(this))
0087     {
0088     }
0089 
0090     QString method() const
0091     {
0092         return QStringLiteral("SomeClass::method");
0093     }
0094 
0095     QVariant otherClass() const
0096     {
0097         return QVariant::fromValue(m_other);
0098     }
0099 
0100     QString nonAccessibleMethod(const QString &str)
0101     {
0102         return str;
0103     }
0104 
0105 private:
0106     QObject *m_other;
0107 };
0108 
0109 /**
0110   For use with tests.
0111  */
0112 class GadgetClass
0113 {
0114     Q_GADGET
0115     Q_PROPERTY(PersonName personName READ personName)
0116 public:
0117     enum PersonName { Mike = 0, Natalie, Oliver };
0118     Q_ENUM(PersonName)
0119 
0120     GadgetClass() = default;
0121     GadgetClass(PersonName pn)
0122         : m_personName(pn)
0123     {
0124     }
0125 
0126     PersonName personName() const
0127     {
0128         return m_personName;
0129     }
0130 
0131 private:
0132     PersonName m_personName = Oliver;
0133 };
0134 Q_DECLARE_METATYPE(GadgetClass)
0135 
0136 class NoEscapeOutputStream : public OutputStream
0137 {
0138 public:
0139     NoEscapeOutputStream()
0140     {
0141     }
0142 
0143     NoEscapeOutputStream(QTextStream *stream)
0144         : OutputStream(stream)
0145     {
0146     }
0147 
0148     QSharedPointer<OutputStream> clone(QTextStream *stream) const override
0149     {
0150         return QSharedPointer<NoEscapeOutputStream>::create(stream);
0151     }
0152 
0153     QString escape(const QString &input) const override
0154     {
0155         return input;
0156     }
0157 };
0158 
0159 class JSOutputStream : public OutputStream
0160 {
0161 public:
0162     JSOutputStream()
0163     {
0164     }
0165 
0166     JSOutputStream(QTextStream *stream)
0167         : OutputStream(stream)
0168     {
0169     }
0170 
0171     QSharedPointer<OutputStream> clone(QTextStream *stream) const override
0172     {
0173         return QSharedPointer<JSOutputStream>::create(stream);
0174     }
0175 
0176     QString escape(const QString &input) const override
0177     {
0178         QList<std::pair<QString, QString>> jsEscapes;
0179         jsEscapes << std::pair<QString, QString>(QChar::fromLatin1('\\'), QStringLiteral("\\u005C"))
0180                   << std::pair<QString, QString>(QChar::fromLatin1('\''), QStringLiteral("\\u0027"))
0181                   << std::pair<QString, QString>(QChar::fromLatin1('\"'), QStringLiteral("\\u0022"))
0182                   << std::pair<QString, QString>(QChar::fromLatin1('>'), QStringLiteral("\\u003E"))
0183                   << std::pair<QString, QString>(QChar::fromLatin1('<'), QStringLiteral("\\u003C"))
0184                   << std::pair<QString, QString>(QChar::fromLatin1('&'), QStringLiteral("\\u0026"))
0185                   << std::pair<QString, QString>(QChar::fromLatin1('='), QStringLiteral("\\u003D"))
0186                   << std::pair<QString, QString>(QChar::fromLatin1('-'), QStringLiteral("\\u002D"))
0187                   << std::pair<QString, QString>(QChar::fromLatin1(';'), QStringLiteral("\\u003B"))
0188                   << std::pair<QString, QString>(QChar(0x2028), QStringLiteral("\\u2028"))
0189                   << std::pair<QString, QString>(QChar(0x2029), QStringLiteral("\\u2029"));
0190 
0191         for (auto i = 0; i < 32; ++i) {
0192             jsEscapes << std::pair<QString, QString>(QChar(i), QStringLiteral("\\u00") + QStringLiteral("%1").arg(i, 2, 16, QLatin1Char('0')).toUpper());
0193         }
0194 
0195         auto retString = input;
0196         for (const auto &escape : std::as_const(jsEscapes)) {
0197             retString = retString.replace(escape.first, escape.second);
0198         }
0199         return retString;
0200     }
0201 };
0202 
0203 class TestBuiltinSyntax : public QObject
0204 {
0205     Q_OBJECT
0206 
0207 private Q_SLOTS:
0208     void initTestCase();
0209 
0210     void testObjects();
0211 
0212     void testTruthiness_data();
0213     void testTruthiness();
0214 
0215     void testRenderAfterError();
0216 
0217     void testBasicSyntax_data();
0218     void testBasicSyntax()
0219     {
0220         doTest();
0221     }
0222 
0223     void testEnums_data();
0224     void testEnums()
0225     {
0226         doTest();
0227     }
0228 
0229     void testListIndex_data();
0230     void testListIndex()
0231     {
0232         doTest();
0233     }
0234 
0235     void testFilterSyntax_data();
0236     void testFilterSyntax()
0237     {
0238         doTest();
0239     }
0240 
0241     void testCommentSyntax_data();
0242     void testCommentSyntax()
0243     {
0244         doTest();
0245     }
0246 
0247     void testMultiline_data();
0248     void testMultiline()
0249     {
0250         doTest();
0251     }
0252 
0253     void testEscaping_data();
0254     void testEscaping()
0255     {
0256         doTest();
0257     }
0258 
0259     void testTypeAccessors_data();
0260     void testTypeAccessors()
0261     {
0262         doTest();
0263     }
0264     void testTypeAccessorsUnordered_data();
0265     void testTypeAccessorsUnordered();
0266 
0267     void testMultipleStates();
0268     void testAlternativeEscaping();
0269 
0270     void testTemplatePathSafety_data();
0271     void testTemplatePathSafety();
0272 
0273     void testMediaPathSafety_data();
0274     void testMediaPathSafety();
0275 
0276     void testDynamicProperties_data();
0277     void testDynamicProperties()
0278     {
0279         doTest();
0280     }
0281 
0282     void testGarbageInput_data();
0283     void testGarbageInput();
0284 
0285     void testInsignificantWhitespace_data();
0286     void testInsignificantWhitespace();
0287 
0288     void cleanupTestCase();
0289 
0290 private:
0291     Engine *m_engine;
0292 
0293     QSharedPointer<InMemoryTemplateLoader> m_loader;
0294 
0295     Engine *getEngine();
0296 
0297     void doTest();
0298 };
0299 
0300 void TestBuiltinSyntax::testObjects()
0301 {
0302     {
0303         auto loader = QSharedPointer<KTextTemplate::FileSystemTemplateLoader>::create();
0304         loader->setTemplateDirs({QStringLiteral("/path/one"), QStringLiteral("/path/two")});
0305 
0306         auto cache = QSharedPointer<KTextTemplate::CachingLoaderDecorator>::create(loader);
0307     }
0308 
0309     Context c1, c2;
0310     c1 = c1;
0311     c1 = c2;
0312     Context c3(c1);
0313     Q_UNUSED(c3);
0314 
0315     FilterExpression f1, f2;
0316     f1 = f1;
0317     f1 = f2;
0318     FilterExpression f3(f1);
0319     Q_UNUSED(f3);
0320 
0321     Variable v1;
0322     v1 = v1;
0323     v1 = f1.variable();
0324     Variable v3(v1);
0325     Q_UNUSED(v3);
0326     QVERIFY(!v1.isTrue(&c1));
0327     QVERIFY(!v1.isLocalized());
0328 
0329     c1.setMutating(true);
0330     QVERIFY(c1.isMutating());
0331 
0332     SafeString s1, s2;
0333     s1 = s1;
0334     s2 = s1;
0335     SafeString s3(s1);
0336     Q_UNUSED(s3);
0337 
0338     QMetaType{qMetaTypeId<MetaEnumVariable>()}.create(nullptr);
0339 }
0340 
0341 void TestBuiltinSyntax::testTruthiness_data()
0342 {
0343     QTest::addColumn<QVariant>("input");
0344     QTest::addColumn<bool>("expected");
0345 
0346     QTest::newRow("truthtest-01") << QVariant() << false;
0347     QTest::newRow("truthtest-02") << QVariant(false) << false;
0348     QTest::newRow("truthtest-03") << QVariant(true) << true;
0349 
0350     QTest::newRow("truthtest-04") << QVariant(0) << false;
0351     QTest::newRow("truthtest-05") << QVariant(1) << true;
0352 
0353     {
0354         auto falseV = QVariant::fromValue<int>(0);
0355         QTest::newRow("truthtest-06") << falseV << false;
0356         auto trueV = QVariant::fromValue<int>(1);
0357         QTest::newRow("truthtest-07") << trueV << true;
0358     }
0359     {
0360         auto falseV = QVariant::fromValue<uint>(0);
0361         QTest::newRow("truthtest-08") << falseV << false;
0362         auto trueV = QVariant::fromValue<uint>(1);
0363         QTest::newRow("truthtest-09") << trueV << true;
0364     }
0365     {
0366         auto falseV = QVariant::fromValue<qlonglong>(0);
0367         QTest::newRow("truthtest-10") << falseV << false;
0368         auto trueV = QVariant::fromValue<qlonglong>(1);
0369         QTest::newRow("truthtest-11") << trueV << true;
0370     }
0371     {
0372         auto falseV = QVariant::fromValue<qulonglong>(0);
0373         QTest::newRow("truthtest-12") << falseV << false;
0374         auto trueV = QVariant::fromValue<qulonglong>(1);
0375         QTest::newRow("truthtest-13") << trueV << true;
0376     }
0377     {
0378         auto falseV = QVariant::fromValue<double>(0);
0379         QTest::newRow("truthtest-14") << falseV << false;
0380         auto trueV = QVariant::fromValue<double>(1);
0381         QTest::newRow("truthtest-15") << trueV << true;
0382     }
0383     {
0384         auto falseV = QVariant::fromValue<float>(0);
0385         QTest::newRow("truthtest-16") << falseV << false;
0386         auto trueV = QVariant::fromValue<float>(1);
0387         QTest::newRow("truthtest-17") << trueV << true;
0388     }
0389     {
0390         auto falseV = QVariant::fromValue<char>(0);
0391         QTest::newRow("truthtest-18") << falseV << false;
0392         auto trueV = QVariant::fromValue<char>(1);
0393         QTest::newRow("truthtest-19") << trueV << true;
0394     }
0395 
0396     QTest::newRow("truthtest-20") << QVariant::fromValue(QString()) << false;
0397     QTest::newRow("truthtest-21") << QVariant::fromValue(QStringLiteral("")) << false;
0398     QTest::newRow("truthtest-22") << QVariant::fromValue(QStringLiteral("false")) << true;
0399     QTest::newRow("truthtest-23") << QVariant::fromValue(QStringLiteral("true")) << true;
0400     QTest::newRow("truthtest-24") << QVariant::fromValue(QStringLiteral("anystring")) << true;
0401 
0402     {
0403         QVariantList l;
0404         QTest::newRow("truthtest-25") << QVariant::fromValue(l) << false;
0405         l.append(1);
0406         QTest::newRow("truthtest-26") << QVariant::fromValue(l) << true;
0407     }
0408     {
0409         QVariantHash h;
0410         QTest::newRow("truthtest-27") << QVariant::fromValue(h) << false;
0411         h.insert(QStringLiteral("value"), 1);
0412         QTest::newRow("truthtest-28") << QVariant::fromValue(h) << true;
0413     }
0414 
0415     {
0416         QTest::newRow("truthtest-29") << QVariant::fromValue<QObject *>(nullptr) << false;
0417         auto plainO = new QObject(this);
0418         QTest::newRow("truthtest-30") << QVariant::fromValue(plainO) << true;
0419         auto trueO = new QObject(this);
0420         trueO->setProperty("__true__", true);
0421         QTest::newRow("truthtest-31") << QVariant::fromValue(trueO) << true;
0422         auto falseO = new QObject(this);
0423         falseO->setProperty("__true__", false);
0424         QTest::newRow("truthtest-32") << QVariant::fromValue(falseO) << false;
0425     }
0426 }
0427 
0428 void TestBuiltinSyntax::testTruthiness()
0429 {
0430     QFETCH(QVariant, input);
0431     QFETCH(bool, expected);
0432 
0433     QVERIFY(variantIsTrue(input) == expected);
0434 }
0435 
0436 void TestBuiltinSyntax::testRenderAfterError()
0437 {
0438     Engine engine;
0439     engine.setPluginPaths({QStringLiteral(KTEXTTEMPLATE_PLUGIN_PATH)});
0440 
0441     QSharedPointer<InMemoryTemplateLoader> loader(new InMemoryTemplateLoader);
0442     loader->setTemplate(QStringLiteral("template1"), QStringLiteral("This template has an error {{ va>r }}"));
0443     loader->setTemplate(QStringLiteral("template2"), QStringLiteral("Ok"));
0444     loader->setTemplate(QStringLiteral("main"), QStringLiteral("{% include template_var %}"));
0445 
0446     engine.addTemplateLoader(loader);
0447 
0448     Context c;
0449     Template t;
0450 
0451     t = engine.loadByName(QStringLiteral("main"));
0452 
0453     c.insert(QStringLiteral("template_var"), QLatin1String("template1"));
0454     auto output = t->render(&c);
0455     QCOMPARE(output, QString());
0456     QCOMPARE(t->error(), TagSyntaxError);
0457 
0458     c.insert(QStringLiteral("template_var"), QLatin1String("template2"));
0459     QCOMPARE(t->render(&c), QLatin1String("Ok"));
0460     QCOMPARE(t->error(), NoError);
0461 }
0462 
0463 void TestBuiltinSyntax::initTestCase()
0464 {
0465     m_engine = getEngine();
0466     m_loader = QSharedPointer<InMemoryTemplateLoader>(new InMemoryTemplateLoader());
0467     m_engine->addTemplateLoader(m_loader);
0468     QVERIFY(m_engine->templateLoaders().contains(m_loader));
0469 }
0470 
0471 Engine *TestBuiltinSyntax::getEngine()
0472 {
0473     auto engine = new Engine(this);
0474     engine->setPluginPaths({QStringLiteral(KTEXTTEMPLATE_PLUGIN_PATH)});
0475     return engine;
0476 }
0477 
0478 void TestBuiltinSyntax::cleanupTestCase()
0479 {
0480     delete m_engine;
0481 }
0482 
0483 void TestBuiltinSyntax::doTest()
0484 {
0485     QFETCH(QString, input);
0486     QFETCH(Dict, dict);
0487     QFETCH(QString, output);
0488     QFETCH(KTextTemplate::Error, error);
0489 
0490     auto t = m_engine->newTemplate(input, QLatin1String(QTest::currentDataTag()));
0491 
0492     if (t->error() != NoError) {
0493         if (t->error() != error)
0494             qDebug() << t->errorString();
0495         QCOMPARE(t->error(), error);
0496         return;
0497     }
0498 
0499     Context context(dict);
0500 
0501     auto result = t->render(&context);
0502     if (t->error() != NoError) {
0503         if (t->error() != error)
0504             qDebug() << t->errorString();
0505         QCOMPARE(t->error(), error);
0506         return;
0507     }
0508 
0509     QCOMPARE(t->error(), NoError);
0510 
0511     // Didn't catch any errors, so make sure I didn't expect any.
0512     QCOMPARE(NoError, error);
0513 
0514     QCOMPARE(result, output);
0515 }
0516 
0517 void TestBuiltinSyntax::testBasicSyntax_data()
0518 {
0519     QTest::addColumn<QString>("input");
0520     QTest::addColumn<Dict>("dict");
0521     QTest::addColumn<QString>("output");
0522     QTest::addColumn<KTextTemplate::Error>("error");
0523 
0524     Dict dict;
0525 
0526     QTest::newRow("basic-syntax00") << QString() << dict << QString() << NoError;
0527 
0528     // Plain text should go through the template parser untouched
0529     QTest::newRow("basic-syntax01") << QStringLiteral("something cool") << dict << QStringLiteral("something cool") << NoError;
0530 
0531     // Variables should be replaced with their value in the current
0532     // context
0533     dict.insert(QStringLiteral("headline"), QStringLiteral("Success"));
0534     QTest::newRow("basic-syntax02") << QStringLiteral("{{ headline }}") << dict << QStringLiteral("Success") << NoError;
0535 
0536     dict.clear();
0537     dict.insert(QStringLiteral("first"), 1);
0538     dict.insert(QStringLiteral("second"), 2);
0539 
0540     // More than one replacement variable is allowed in a template
0541     QTest::newRow("basic-syntax03") << QStringLiteral("{{ first }} --- {{ second }}") << dict << QStringLiteral("1 --- 2") << NoError;
0542 
0543     dict.clear();
0544     // Fail silently when a variable is not found in the current context
0545     QTest::newRow("basic-syntax04") << QStringLiteral("as{{ missing }}df") << dict << QStringLiteral("asdf") << NoError;
0546 
0547     // A variable may not contain more than one word
0548     QTest::newRow("basic-syntax06") << QStringLiteral("{{ multi word variable }}") << dict << QString() << TagSyntaxError;
0549     // Raise TemplateSyntaxError for empty variable tags
0550     QTest::newRow("basic-syntax07") << QStringLiteral("{{ }}") << dict << QString() << EmptyVariableError;
0551     QTest::newRow("basic-syntax08") << QStringLiteral("{{        }}") << dict << QString() << EmptyVariableError;
0552 
0553     // Attribute syntax allows a template to call an object's attribute
0554 
0555     auto someClass = new SomeClass(this);
0556     dict.insert(QStringLiteral("var"), QVariant::fromValue(someClass));
0557 
0558     QTest::newRow("basic-syntax09") << QStringLiteral("{{ var.method }}") << dict << QStringLiteral("SomeClass::method") << NoError;
0559 
0560     // Multiple levels of attribute access are allowed
0561     QTest::newRow("basic-syntax10") << QStringLiteral("{{ var.otherClass.method }}") << dict << QStringLiteral("OtherClass::method") << NoError;
0562 
0563     // Fail silently when a variable's attribute isn't found
0564     QTest::newRow("basic-syntax11") << QStringLiteral("{{ var.blech }}") << dict << QString() << NoError;
0565 
0566     // TODO: Needed?
0567     // Raise TemplateSyntaxError when trying to access a variable beginning with
0568     // an underscore
0569     // #C# {"var": SomeClass()}
0570     dict.clear();
0571     QVariantHash hash;
0572     hash.insert(QStringLiteral("__dict__"), QStringLiteral("foo"));
0573     dict.insert(QStringLiteral("var"), hash);
0574     QTest::newRow("basic-syntax12") << QStringLiteral("{{ var.__dict__ }}") << dict << QString() << TagSyntaxError;
0575 
0576     dict.clear();
0577     // Raise TemplateSyntaxError when trying to access a variable containing an
0578     // illegal character
0579     QTest::newRow("basic-syntax13") << QStringLiteral("{{ va>r }}") << dict << QString() << TagSyntaxError;
0580     QTest::newRow("basic-syntax14") << QStringLiteral("{{ (var.r) }}") << dict << QString() << TagSyntaxError;
0581     QTest::newRow("basic-syntax15") << QStringLiteral("{{ sp%am }}") << dict << QString() << TagSyntaxError;
0582     QTest::newRow("basic-syntax16") << QStringLiteral("{{ eggs! }}") << dict << QString() << TagSyntaxError;
0583     QTest::newRow("basic-syntax17") << QStringLiteral("{{ moo? }}") << dict << QString() << TagSyntaxError;
0584     QTest::newRow("basic-syntax-error01") << QStringLiteral("{{ moo:arg }}") << dict << QString() << TagSyntaxError;
0585     QTest::newRow("basic-syntax-error02") << QStringLiteral("{{ moo|cut:'foo':'bar' }}") << dict << QString() << TagSyntaxError;
0586 
0587     // Attribute syntax allows a template to call a dictionary key's value
0588 
0589     hash.clear();
0590     hash.insert(QStringLiteral("bar"), QStringLiteral("baz"));
0591     dict.insert(QStringLiteral("foo"), hash);
0592     QTest::newRow("basic-syntax18") << QStringLiteral("{{ foo.bar }}") << dict << QStringLiteral("baz") << NoError;
0593 
0594     // Fail silently when a variable's dictionary key isn't found
0595     QTest::newRow("basic-syntax19") << QStringLiteral("{{ foo.spam }}") << dict << QString() << NoError;
0596 
0597     dict.clear();
0598     dict.insert(QStringLiteral("var"), QVariant::fromValue(someClass));
0599     // Fail silently when attempting to access an unavailable method
0600     QTest::newRow("basic-syntax20") << QStringLiteral("{{ var.nonAccessibleMethod }}") << dict << QString() << NoError;
0601 
0602     dict.clear();
0603     // Don't get confused when parsing something that is almost, but not
0604     // quite, a template tag.
0605     QTest::newRow("basic-syntax21") << QStringLiteral("a {{ moo %} b") << dict << QStringLiteral("a {{ moo %} b") << NoError;
0606     QTest::newRow("basic-syntax22") << QStringLiteral("{{ moo #}") << dict << QStringLiteral("{{ moo #}") << NoError;
0607 
0608     dict.insert(QStringLiteral("cow"), QStringLiteral("cow"));
0609     // Will try to treat "moo #} {{ cow" as the variable. Not ideal, but
0610     // costly to work around, so this triggers an error.
0611     QTest::newRow("basic-syntax23") << QStringLiteral("{{ moo #} {{ cow }}") << dict << QString() << TagSyntaxError;
0612 
0613     dict.clear();
0614     // Embedded newlines make it not-a-tag.
0615     QTest::newRow("basic-syntax24") << "{{ moo\n }}" << dict << "{{ moo\n }}" << NoError;
0616     // Literal strings are permitted inside variables, mostly for i18n
0617     // purposes.
0618     QTest::newRow("basic-syntax25") << "{{ \"fred\" }}" << dict << QStringLiteral("fred") << NoError;
0619     QTest::newRow("basic-syntax26") << R"({{ "\"fred\"" }})" << dict << "\"fred\"" << NoError;
0620     QTest::newRow("basic-syntax27") << R"({{ _("\"fred\"") }})" << dict << "&quot;fred&quot;" << NoError;
0621 
0622     dict.clear();
0623     hash.clear();
0624     QVariantHash innerHash;
0625     innerHash.insert(QStringLiteral("3"), QStringLiteral("d"));
0626     hash.insert(QStringLiteral("2"), innerHash);
0627     dict.insert(QStringLiteral("1"), hash);
0628 
0629     QTest::newRow("basic-syntax28") << QStringLiteral("{{ 1.2.3 }}") << dict << QStringLiteral("d") << NoError;
0630 
0631     dict.clear();
0632     hash.clear();
0633     QVariantList list{QStringLiteral("a"), QStringLiteral("b"), QStringLiteral("c"), QStringLiteral("d")};
0634     hash.insert(QStringLiteral("2"), list);
0635     dict.insert(QStringLiteral("1"), hash);
0636     QTest::newRow("basic-syntax29") << QStringLiteral("{{ 1.2.3 }}") << dict << QStringLiteral("d") << NoError;
0637 
0638     dict.clear();
0639     list.clear();
0640     QVariantList innerList{QStringLiteral("x"), QStringLiteral("x"), QStringLiteral("x"), QStringLiteral("x")};
0641     list.append(QVariant(innerList));
0642     innerList = {QStringLiteral("y"), QStringLiteral("y"), QStringLiteral("y"), QStringLiteral("y")};
0643     list.append(QVariant(innerList));
0644     innerList = {QStringLiteral("a"), QStringLiteral("b"), QStringLiteral("c"), QStringLiteral("d")};
0645     list.append(QVariant(innerList));
0646     dict.insert(QStringLiteral("1"), list);
0647 
0648     QTest::newRow("basic-syntax30") << QStringLiteral("{{ 1.2.3 }}") << dict << QStringLiteral("d") << NoError;
0649 
0650     dict.clear();
0651     list.clear();
0652     innerList = {QStringLiteral("x"), QStringLiteral("x"), QStringLiteral("x"), QStringLiteral("x")};
0653     list.append(QVariant(innerList));
0654     innerList = {QStringLiteral("y"), QStringLiteral("y"), QStringLiteral("y"), QStringLiteral("y")};
0655     list.append(QVariant(innerList));
0656     innerList = {QStringLiteral("a"), QStringLiteral("b"), QStringLiteral("c"), QStringLiteral("d")};
0657     list.append(QVariant(innerList));
0658     dict.insert(QStringLiteral("1"), list);
0659 
0660     QTest::newRow("basic-syntax31") << QStringLiteral("{{ 1.2.3 }}") << dict << QStringLiteral("d") << NoError;
0661 
0662     dict.clear();
0663     list.clear();
0664     hash.clear();
0665     hash.insert(QStringLiteral("x"), QStringLiteral("x"));
0666     list.append(hash);
0667     hash.clear();
0668     hash.insert(QStringLiteral("y"), QStringLiteral("y"));
0669     list.append(hash);
0670     hash.clear();
0671     hash.insert(QStringLiteral("3"), QStringLiteral("d"));
0672     list.append(hash);
0673 
0674     dict.insert(QStringLiteral("1"), list);
0675 
0676     QTest::newRow("basic-syntax32") << QStringLiteral("{{ 1.2.3 }}") << dict << QStringLiteral("d") << NoError;
0677 
0678     dict.clear();
0679 
0680     dict.insert(QStringLiteral("1"), QStringLiteral("abc"));
0681     QTest::newRow("basic-syntax33") << QStringLiteral("{{ 1 }}") << dict << QStringLiteral("1") << NoError;
0682     QTest::newRow("basic-syntax34") << QStringLiteral("{{ 1.2 }}") << dict << QStringLiteral("1.2") << NoError;
0683 
0684     dict.clear();
0685 
0686     dict.insert(QStringLiteral("abc"), QStringLiteral("def"));
0687 
0688     QTest::newRow("basic-syntax35") << QStringLiteral("{{ abc._something }} {{ abc._something|upper }}") << dict << QString() << TagSyntaxError;
0689 
0690     QTest::newRow("basic-syntax36") << "{{ \"fred }}" << dict << QString() << TagSyntaxError;
0691     QTest::newRow("basic-syntax37") << "{{ \'fred }}" << dict << QString() << TagSyntaxError;
0692     QTest::newRow("basic-syntax38") << "{{ \"fred\' }}" << dict << QString() << TagSyntaxError;
0693     QTest::newRow("basic-syntax39") << "{{ \'fred\" }}" << dict << QString() << TagSyntaxError;
0694     QTest::newRow("basic-syntax40") << "{{ _(\'fred }}" << dict << QString() << TagSyntaxError;
0695     QTest::newRow("basic-syntax41") << "{{ abc|removetags:_(\'fred }}" << dict << QString() << TagSyntaxError;
0696 }
0697 
0698 void TestBuiltinSyntax::testEnums_data()
0699 {
0700     QTest::addColumn<QString>("input");
0701     QTest::addColumn<Dict>("dict");
0702     QTest::addColumn<QString>("output");
0703     QTest::addColumn<KTextTemplate::Error>("error");
0704 
0705     Dict dict;
0706 
0707     auto otherClass = new OtherClass(this);
0708     dict.insert(QStringLiteral("var"), QVariant::fromValue(otherClass));
0709 
0710     QTest::newRow("class-enums01") << QStringLiteral("{{ var.Lions }}") << dict << QStringLiteral("0") << NoError;
0711     QTest::newRow("class-enums02") << QStringLiteral("{{ var.Tigers }}") << dict << QStringLiteral("1") << NoError;
0712     QTest::newRow("class-enums03") << QStringLiteral("{{ var.Bears }}") << dict << QStringLiteral("2") << NoError;
0713     QTest::newRow("class-enums04") << QStringLiteral("{{ var.Hamsters }}") << dict << QString() << NoError;
0714     QTest::newRow("class-enums05") << QStringLiteral("{{ var.Tigers.name }}") << dict << QStringLiteral("Animals") << NoError;
0715     QTest::newRow("class-enums06") << QStringLiteral("{{ var.Tigers.scope }}") << dict << QStringLiteral("OtherClass") << NoError;
0716     QTest::newRow("class-enums07") << QStringLiteral("{{ var.Tigers.value }}") << dict << QStringLiteral("1") << NoError;
0717     QTest::newRow("class-enums08") << QStringLiteral("{{ var.Tigers.key }}") << dict << QStringLiteral("Tigers") << NoError;
0718     QTest::newRow("class-enums09") << QStringLiteral("{{ var.animals }}") << dict << QStringLiteral("1") << NoError;
0719     QTest::newRow("class-enums10") << QStringLiteral("{{ var.animals.name }}") << dict << QStringLiteral("Animals") << NoError;
0720     QTest::newRow("class-enums11") << QStringLiteral("{{ var.animals.scope }}") << dict << QStringLiteral("OtherClass") << NoError;
0721     QTest::newRow("class-enums12") << QStringLiteral("{{ var.animals.value }}") << dict << QStringLiteral("1") << NoError;
0722     QTest::newRow("class-enums13") << QStringLiteral("{{ var.animals.key }}") << dict << QStringLiteral("Tigers") << NoError;
0723     QTest::newRow("class-enums14") << QStringLiteral("{{ var.Animals.0 }}") << dict << QStringLiteral("0") << NoError;
0724     QTest::newRow("class-enums15") << QStringLiteral("{{ var.Animals.2 }}") << dict << QStringLiteral("2") << NoError;
0725     QTest::newRow("class-enums16") << QStringLiteral("{{ var.Animals.3 }}") << dict << QString() << NoError;
0726     QTest::newRow("class-enums17") << QStringLiteral("{{ var.Animals.0.name }}") << dict << QStringLiteral("Animals") << NoError;
0727     QTest::newRow("class-enums18") << QStringLiteral("{{ var.Animals.0.scope }}") << dict << QStringLiteral("OtherClass") << NoError;
0728     QTest::newRow("class-enums19") << QStringLiteral("{{ var.Animals.0.value }}") << dict << QStringLiteral("0") << NoError;
0729     QTest::newRow("class-enums20") << QStringLiteral("{{ var.Animals.0.key }}") << dict << QStringLiteral("Lions") << NoError;
0730     QTest::newRow("class-enums21") << QStringLiteral("{{ var.Animals.2.key }}") << dict << QStringLiteral("Bears") << NoError;
0731     QTest::newRow("class-enums22") << QStringLiteral("{{ var.Tigers.samba }}") << dict << QString() << NoError;
0732     QTest::newRow("class-enums23") << QStringLiteral(
0733         "{% with var.animals as result %}{{ result.key }},{{ "
0734         "result }},{{ result.scope }}{% endwith %}")
0735                                    << dict << QStringLiteral("Tigers,1,OtherClass") << NoError;
0736     QTest::newRow("class-enums24") << QStringLiteral(
0737         "{% with var.Animals.2 as result %}{{ result.key }},{{ "
0738         "result }},{{ result.scope }}{% endwith %}")
0739                                    << dict << QStringLiteral("Bears,2,OtherClass") << NoError;
0740     QTest::newRow("class-enums25") << QStringLiteral(
0741         "{% with var.Bears as result %}{{ result.key }},{{ "
0742         "result }},{{ result.scope }}{% endwith %}")
0743                                    << dict << QStringLiteral("Bears,2,OtherClass") << NoError;
0744     QTest::newRow("class-enums26") << QStringLiteral(
0745         "{% with var.Animals as result %}{{ result.0.key }},{{ "
0746         "result.1.key }},{{ result.2.key }}{% endwith %}")
0747                                    << dict << QStringLiteral("Lions,Tigers,Bears") << NoError;
0748 
0749     dict.clear();
0750 
0751     auto someClass = new SomeClass(this);
0752     dict.insert(QStringLiteral("var"), QVariant::fromValue(someClass));
0753 
0754     QTest::newRow("class-enums27") << QStringLiteral("{{ var.Employee }}") << dict << QStringLiteral("0") << NoError;
0755     QTest::newRow("class-enums28") << QStringLiteral("{{ var.Employer }}") << dict << QStringLiteral("1") << NoError;
0756     QTest::newRow("class-enums29") << QStringLiteral("{{ var.Manager }}") << dict << QStringLiteral("2") << NoError;
0757     QTest::newRow("class-enums30") << QStringLiteral("{{ var.Voter }}") << dict << QStringLiteral("2") << NoError;
0758     QTest::newRow("class-enums31") << QStringLiteral("{{ var.Consumer }}") << dict << QStringLiteral("4") << NoError;
0759     QTest::newRow("class-enums32") << QStringLiteral("{{ var.Citizen }}") << dict << QStringLiteral("8") << NoError;
0760     QTest::newRow("class-enums33") << QStringLiteral("{{ var.FirstEnum }}") << dict << QString() << NoError;
0761     QTest::newRow("class-enums34") << QStringLiteral("{{ var.SecondEnum }}") << dict << QString() << NoError;
0762 
0763     QTest::newRow("class-enums35") << QString::fromLatin1(
0764         "{% with var.SecondEnum as result %}"
0765         "{{ result.0 }},{{ result.1 }},{{ result.2 }},"
0766         "{{ result.0.key }},{{ result.1.key }},{{ result.2.key }},"
0767         "{{ result }},{{ result.scope }}"
0768         "{% endwith %}") << dict << QStringLiteral("2,4,8,Voter,Consumer,Citizen,,SomeClass")
0769                                    << NoError;
0770 
0771     QTest::newRow("class-enums36") << QStringLiteral("{% ifequal var.Employee 2 %}{% endifequal %}") << dict << QString() << NoError;
0772 
0773     dict.insert(QStringLiteral("var"), QVariant::fromValue(otherClass));
0774 
0775     QTest::newRow("enums-loops01") << QString::fromLatin1(
0776         "{% for enum in var.Animals %}{% ifequal enum var.Tigers %}"
0777         "<b>{{ enum.key }}</b>{% else %}{{ enum.key }}{% endifequal %},"
0778         "{% empty %}No content{% endfor %}")
0779                                    << dict << QStringLiteral("Lions,<b>Tigers</b>,Bears,") << NoError;
0780 
0781     QTest::newRow("enums-loops02") << QString::fromLatin1(
0782         "{% for enum in var.Tigers %}"
0783         "{% ifequal enum result %}<b>{{ enum.key }}</b>"
0784         "{% else %}{{ enum.key }}{% endifequal %},"
0785         "{% empty %}No content"
0786         "{% endfor %}") << dict << QStringLiteral("No content")
0787                                    << NoError;
0788 
0789     QTest::newRow("enums-loops03") << QString::fromLatin1(
0790         "{% with var.animals as result %}"
0791         "{% for enum in var.Animals %}"
0792         "{% ifequal enum result %}<b>{{ enum.key }}</b>"
0793         "{% else %}{{ enum.key }}{% endifequal %},"
0794         "{% empty %}No content"
0795         "{% endfor %}"
0796         "{% endwith %}") << dict << QStringLiteral("Lions,<b>Tigers</b>,Bears,")
0797                                    << NoError;
0798 
0799     QTest::newRow("enums-keycount01") << QStringLiteral("{{ var.Animals.keyCount }}") << dict << QStringLiteral("3") << NoError;
0800     QTest::newRow("enums-keycount02") << QStringLiteral("{{ var.Lions.keyCount }}") << dict << QStringLiteral("3") << NoError;
0801     QTest::newRow("enums-keycount02") << QStringLiteral("{{ var.animals.keyCount }}") << dict << QStringLiteral("3") << NoError;
0802 
0803     auto otherClass2 = new OtherClass(OtherClass::Lions, this);
0804     dict.insert(QStringLiteral("var2"), QVariant::fromValue(otherClass2));
0805 
0806     auto otherClass3 = new OtherClass(this);
0807     dict.insert(QStringLiteral("var3"), QVariant::fromValue(otherClass3));
0808 
0809     QTest::newRow("enums-compare01") << QStringLiteral("{% if var.animals == var3.animals %}true{% else %}false{% endif %}") << dict << QStringLiteral("true")
0810                                      << NoError;
0811 
0812     QTest::newRow("enums-compare02") << QStringLiteral("{% if var.animals == var2.animals %}true{% else %}false{% endif %}") << dict << QStringLiteral("false")
0813                                      << NoError;
0814 
0815     QTest::newRow("enums-compare03") << QStringLiteral("{% if var.animals >= var3.animals %}true{% else %}false{% endif %}") << dict << QStringLiteral("true")
0816                                      << NoError;
0817 
0818     QTest::newRow("enums-compare04") << QStringLiteral("{% if var.animals >= var2.animals %}true{% else %}false{% endif %}") << dict << QStringLiteral("true")
0819                                      << NoError;
0820 
0821     QTest::newRow("enums-compare05") << QStringLiteral("{% if var.animals > var3.animals %}true{% else %}false{% endif %}") << dict << QStringLiteral("false")
0822                                      << NoError;
0823 
0824     QTest::newRow("enums-compare06") << QStringLiteral("{% if var.animals > var2.animals %}true{% else %}false{% endif %}") << dict << QStringLiteral("true")
0825                                      << NoError;
0826 
0827     QTest::newRow("enums-compare07") << QStringLiteral("{% if var.animals <= var3.animals %}true{% else %}false{% endif %}") << dict << QStringLiteral("true")
0828                                      << NoError;
0829 
0830     QTest::newRow("enums-compare08") << QStringLiteral("{% if var.animals <= var2.animals %}true{% else %}false{% endif %}") << dict << QStringLiteral("false")
0831                                      << NoError;
0832 
0833     QTest::newRow("enums-compare09") << QStringLiteral("{% if var.animals < var3.animals %}true{% else %}false{% endif %}") << dict << QStringLiteral("false")
0834                                      << NoError;
0835 
0836     QTest::newRow("enums-compare10") << QStringLiteral("{% if var.animals < var2.animals %}true{% else %}false{% endif %}") << dict << QStringLiteral("false")
0837                                      << NoError;
0838 
0839     QTest::newRow("qt-enums01") << QStringLiteral("{{ Qt.AlignRight }}") << dict << QStringLiteral("2") << NoError;
0840     QTest::newRow("qt-enums02") << QStringLiteral("{{ Qt.AlignRight.scope }}") << dict << QStringLiteral("Qt") << NoError;
0841     QTest::newRow("qt-enums03") << QStringLiteral("{{ Qt.AlignRight.name }}") << dict << QStringLiteral("Alignment") << NoError;
0842     QTest::newRow("qt-enums04") << QStringLiteral("{{ Qt.AlignRight.value }}") << dict << QStringLiteral("2") << NoError;
0843     QTest::newRow("qt-enums05") << QStringLiteral("{{ Qt.AlignRight.key }}") << dict << QStringLiteral("AlignRight") << NoError;
0844     QTest::newRow("qt-enums06") << QStringLiteral("{{ Qt.Alignment.2.key }}") << dict << QStringLiteral("AlignRight") << NoError;
0845     QTest::newRow("qt-enums07") << QStringLiteral("{{ Qt.DoesNotExist }}") << dict << QString() << NoError;
0846     QTest::newRow("qt-enums08") << QStringLiteral("{{ Qt }}") << dict << QString() << NoError;
0847 
0848     dict.clear();
0849 
0850     GadgetClass gadgetClasss;
0851     dict.insert(QStringLiteral("var"), QVariant::fromValue(gadgetClasss));
0852 
0853     QTest::newRow("gadget-enums01") << QStringLiteral("{{ var.Mike }}") << dict << QStringLiteral("0") << NoError;
0854     QTest::newRow("gadget-enums02") << QStringLiteral("{{ var.Natalie }}") << dict << QStringLiteral("1") << NoError;
0855     QTest::newRow("gadget-enums03") << QStringLiteral("{{ var.Oliver }}") << dict << QStringLiteral("2") << NoError;
0856     QTest::newRow("gadget-enums04") << QStringLiteral("{{ var.Patricia }}") << dict << QString() << NoError;
0857     QTest::newRow("gadget-enums05") << QStringLiteral("{{ var.Natalie.name }}") << dict << QStringLiteral("PersonName") << NoError;
0858     QTest::newRow("gadget-enums06") << QStringLiteral("{{ var.Natalie.scope }}") << dict << QStringLiteral("GadgetClass") << NoError;
0859     QTest::newRow("gadget-enums07") << QStringLiteral("{{ var.Natalie.value }}") << dict << QStringLiteral("1") << NoError;
0860     QTest::newRow("gadget-enums08") << QStringLiteral("{{ var.Natalie.key }}") << dict << QStringLiteral("Natalie") << NoError;
0861     QTest::newRow("gadget-enums09") << QStringLiteral("{{ var.personName }}") << dict << QStringLiteral("2") << NoError;
0862     QTest::newRow("gadget-enums10") << QStringLiteral("{{ var.personName.name }}") << dict << QStringLiteral("PersonName") << NoError;
0863     QTest::newRow("gadget-enums11") << QStringLiteral("{{ var.personName.scope }}") << dict << QStringLiteral("GadgetClass") << NoError;
0864     QTest::newRow("gadget-enums12") << QStringLiteral("{{ var.personName.value }}") << dict << QStringLiteral("2") << NoError;
0865     QTest::newRow("gadget-enums13") << QStringLiteral("{{ var.personName.key }}") << dict << QStringLiteral("Oliver") << NoError;
0866     QTest::newRow("gadget-enums14") << QStringLiteral("{{ var.PersonName.0 }}") << dict << QStringLiteral("0") << NoError;
0867     QTest::newRow("gadget-enums15") << QStringLiteral("{{ var.PersonName.2 }}") << dict << QStringLiteral("2") << NoError;
0868     QTest::newRow("gadget-enums16") << QStringLiteral("{{ var.PersonName.3 }}") << dict << QString() << NoError;
0869     QTest::newRow("gadget-enums17") << QStringLiteral("{{ var.PersonName.0.name }}") << dict << QStringLiteral("PersonName") << NoError;
0870     QTest::newRow("gadget-enums18") << QStringLiteral("{{ var.PersonName.0.scope }}") << dict << QStringLiteral("GadgetClass") << NoError;
0871     QTest::newRow("gadget-enums19") << QStringLiteral("{{ var.PersonName.0.value }}") << dict << QStringLiteral("0") << NoError;
0872     QTest::newRow("gadget-enums20") << QStringLiteral("{{ var.PersonName.0.key }}") << dict << QStringLiteral("Mike") << NoError;
0873     QTest::newRow("gadget-enums21") << QStringLiteral("{{ var.PersonName.2.key }}") << dict << QStringLiteral("Oliver") << NoError;
0874     QTest::newRow("gadget-enums22") << QStringLiteral("{{ var.PersonName.samba }}") << dict << QString() << NoError;
0875     QTest::newRow("gadget-enums23") << QStringLiteral(
0876         "{% with var.personName as result %}{{ result.key }},{{ "
0877         "result }},{{ result.scope }}{% endwith %}")
0878                                     << dict << QStringLiteral("Oliver,2,GadgetClass") << NoError;
0879     QTest::newRow("gadget-enums24") << QStringLiteral(
0880         "{% with var.PersonName.2 as result %}{{ result.key }},{{ "
0881         "result }},{{ result.scope }}{% endwith %}")
0882                                     << dict << QStringLiteral("Oliver,2,GadgetClass") << NoError;
0883     QTest::newRow("gadget-enums25") << QStringLiteral(
0884         "{% with var.Oliver as result %}{{ result.key }},{{ "
0885         "result }},{{ result.scope }}{% endwith %}")
0886                                     << dict << QStringLiteral("Oliver,2,GadgetClass") << NoError;
0887     QTest::newRow("gadget-enums26") << QStringLiteral(
0888         "{% with var.PersonName as result %}{{ result.0.key }},{{ "
0889         "result.1.key }},{{ result.2.key }}{% endwith %}")
0890                                     << dict << QStringLiteral("Mike,Natalie,Oliver") << NoError;
0891 
0892     QTest::newRow("gadget-enums-loops01") << QString::fromLatin1(
0893         "{% for enum in var.PersonName %}{% ifequal enum var.Natalie %}"
0894         "<b>{{ enum.key }}</b>{% else %}{{ enum.key }}{% endifequal %},"
0895         "{% empty %}No content{% endfor %}")
0896                                           << dict << QStringLiteral("Mike,<b>Natalie</b>,Oliver,") << NoError;
0897 
0898     QTest::newRow("gadget-enums-loops02") << QString::fromLatin1(
0899         "{% for enum in var.Tigers %}"
0900         "{% ifequal enum result %}<b>{{ enum.key }}</b>"
0901         "{% else %}{{ enum.key }}{% endifequal %},"
0902         "{% empty %}No content"
0903         "{% endfor %}") << dict << QStringLiteral("No content")
0904                                           << NoError;
0905 
0906     QTest::newRow("gadget-enums-loops03") << QString::fromLatin1(
0907         "{% with var.personName as result %}"
0908         "{% for enum in var.PersonName %}"
0909         "{% ifequal enum result %}<b>{{ enum.key }}</b>"
0910         "{% else %}{{ enum.key }}{% endifequal %},"
0911         "{% empty %}No content"
0912         "{% endfor %}"
0913         "{% endwith %}") << dict << QStringLiteral("Mike,Natalie,<b>Oliver</b>,")
0914                                           << NoError;
0915 
0916     QTest::newRow("gadget-enums-keycount01") << QStringLiteral("{{ var.PersonName.keyCount }}") << dict << QStringLiteral("3") << NoError;
0917     QTest::newRow("gadget-enums-keycount02") << QStringLiteral("{{ var.personName.keyCount }}") << dict << QStringLiteral("3") << NoError;
0918 
0919     GadgetClass gadgetClass2(GadgetClass::Natalie);
0920     dict.insert(QStringLiteral("var2"), QVariant::fromValue(gadgetClass2));
0921 
0922     GadgetClass gadgetClass3;
0923     dict.insert(QStringLiteral("var3"), QVariant::fromValue(gadgetClass3));
0924 
0925     QTest::newRow("gadget-enums-compare01") << QStringLiteral(
0926         "{% if var.personName == var3.personName %}true{% else "
0927         "%}false{% endif %}") << dict << QStringLiteral("true")
0928                                             << NoError;
0929 
0930     QTest::newRow("gadget-enums-compare02") << QStringLiteral(
0931         "{% if var.personName == var2.personName %}true{% else "
0932         "%}false{% endif %}") << dict << QStringLiteral("false")
0933                                             << NoError;
0934 
0935     QTest::newRow("gadget-enums-compare03") << QStringLiteral(
0936         "{% if var.personName >= var3.personName %}true{% else "
0937         "%}false{% endif %}") << dict << QStringLiteral("true")
0938                                             << NoError;
0939 
0940     QTest::newRow("gadget-enums-compare04") << QStringLiteral(
0941         "{% if var.personName >= var2.personName %}true{% else "
0942         "%}false{% endif %}") << dict << QStringLiteral("true")
0943                                             << NoError;
0944 
0945     QTest::newRow("gadget-enums-compare05") << QStringLiteral("{% if var.personName > var3.personName %}true{% else %}false{% endif %}") << dict
0946                                             << QStringLiteral("false") << NoError;
0947 
0948     QTest::newRow("gadget-enums-compare06") << QStringLiteral("{% if var.personName > var2.personName %}true{% else %}false{% endif %}") << dict
0949                                             << QStringLiteral("true") << NoError;
0950 
0951     QTest::newRow("gadget-enums-compare07") << QStringLiteral(
0952         "{% if var.personName <= var3.personName %}true{% else "
0953         "%}false{% endif %}") << dict << QStringLiteral("true")
0954                                             << NoError;
0955 
0956     QTest::newRow("gadget-enums-compare08") << QStringLiteral(
0957         "{% if var.personName <= var2.personName %}true{% else "
0958         "%}false{% endif %}") << dict << QStringLiteral("false")
0959                                             << NoError;
0960 
0961     QTest::newRow("gadget-enums-compare09") << QStringLiteral("{% if var.personName < var3.personName %}true{% else %}false{% endif %}") << dict
0962                                             << QStringLiteral("false") << NoError;
0963 
0964     QTest::newRow("gadget-enums-compare10") << QStringLiteral("{% if var.personName < var2.personName %}true{% else %}false{% endif %}") << dict
0965                                             << QStringLiteral("false") << NoError;
0966 }
0967 
0968 void TestBuiltinSyntax::testListIndex_data()
0969 {
0970     QTest::addColumn<QString>("input");
0971     QTest::addColumn<Dict>("dict");
0972     QTest::addColumn<QString>("output");
0973     QTest::addColumn<KTextTemplate::Error>("error");
0974 
0975     Dict dict;
0976 
0977     QVariantList l{QStringLiteral("first item"), QStringLiteral("second item")};
0978 
0979     dict.insert(QStringLiteral("var"), l);
0980 
0981     // List-index syntax allows a template to access a certain item of a
0982     // subscriptable object.
0983     QTest::newRow("list-index01") << QStringLiteral("{{ var.1 }}") << dict << QStringLiteral("second item") << NoError;
0984     // Fail silently when the list index is out of range.
0985     QTest::newRow("list-index02") << QStringLiteral("{{ var.5 }}") << dict << QString() << NoError;
0986 
0987     dict.clear();
0988     dict.insert(QStringLiteral("var"), QVariant());
0989 
0990     // Fail silently when the variable is not a subscriptable object.
0991     QTest::newRow("list-index03") << QStringLiteral("{{ var.1 }}") << dict << QString() << NoError;
0992 
0993     dict.clear();
0994     dict.insert(QStringLiteral("var"), QVariantHash());
0995     // Fail silently when variable is a dict without the specified key.
0996     QTest::newRow("list-index04") << QStringLiteral("{{ var.1 }}") << dict << QString() << NoError;
0997 
0998     dict.clear();
0999 
1000     QVariantHash hash;
1001     hash.insert(QStringLiteral("1"), QStringLiteral("hello"));
1002     dict.insert(QStringLiteral("var"), hash);
1003     // Dictionary lookup wins out when dict's key is a string.
1004     QTest::newRow("list-index05") << QStringLiteral("{{ var.1 }}") << dict << QStringLiteral("hello") << NoError;
1005 
1006     // QVariantHash can only use strings as keys, so list-index06 and
1007     // list-index07
1008     // are not valid.
1009 
1010     dict.clear();
1011 
1012     QStringList sl;
1013     sl.append(QStringLiteral("hello"));
1014     sl.append(QStringLiteral("world"));
1015     dict.insert(QStringLiteral("var"), sl);
1016     // QStringList lookup
1017     QTest::newRow("list-index08") << QStringLiteral("{{ var.0 }}, {{ var.1 }}!") << dict << QStringLiteral("hello, world!") << NoError;
1018 }
1019 
1020 void TestBuiltinSyntax::testFilterSyntax_data()
1021 {
1022     QTest::addColumn<QString>("input");
1023     QTest::addColumn<Dict>("dict");
1024     QTest::addColumn<QString>("output");
1025     QTest::addColumn<KTextTemplate::Error>("error");
1026 
1027     Dict dict;
1028 
1029     // Basic filter usage
1030     dict.insert(QStringLiteral("var"), QStringLiteral("Django is the greatest!"));
1031     QTest::newRow("filter-syntax01") << QStringLiteral("{{ var|upper }}") << dict << QStringLiteral("DJANGO IS THE GREATEST!") << NoError;
1032 
1033     // Chained filters
1034     QTest::newRow("filter-syntax02") << QStringLiteral("{{ var|upper|lower }}") << dict << QStringLiteral("django is the greatest!") << NoError;
1035 
1036     // Raise TemplateSyntaxError for space between a variable and filter pipe
1037     dict.clear();
1038     QTest::newRow("filter-syntax03") << QStringLiteral("{{ var |upper }}") << dict << QString() << TagSyntaxError;
1039 
1040     // Raise TemplateSyntaxError for space after a filter pipe
1041     QTest::newRow("filter-syntax04") << QStringLiteral("{{ var| upper }}") << dict << QString() << TagSyntaxError;
1042 
1043     // Raise TemplateSyntaxError for a nonexistent filter
1044     QTest::newRow("filter-syntax05") << QStringLiteral("{{ var|does_not_exist }}") << dict << QString() << UnknownFilterError;
1045 
1046     // Raise TemplateSyntaxError when trying to access a filter containing an
1047     // illegal character
1048     QTest::newRow("filter-syntax06") << QStringLiteral("{{ var|fil(ter) }}") << dict << QString() << UnknownFilterError;
1049 
1050     // Raise TemplateSyntaxError for invalid block tags
1051     QTest::newRow("filter-syntax07") << QStringLiteral("{% nothing_to_see_here %}") << dict << QString() << InvalidBlockTagError;
1052     // Raise TemplateSyntaxError for empty block tags
1053     QTest::newRow("filter-syntax08") << QStringLiteral("{% %}") << dict << QString() << EmptyBlockTagError;
1054 
1055     // Chained filters, with an argument to the first one
1056     dict.insert(QStringLiteral("var"), QStringLiteral("<b><i>Yes</i></b>"));
1057     QTest::newRow("filter-syntax09") << "{{ var|removetags:\"b i\"|upper|lower }}" << dict << QStringLiteral("yes") << NoError;
1058     // Literal string as argument is always "safe" from auto-escaping..
1059     dict.clear();
1060     dict.insert(QStringLiteral("var"), QVariant());
1061     QTest::newRow("filter-syntax10") << R"({{ var|default_if_none:" endquote\" hah" }})" << dict << " endquote\" hah" << NoError;
1062     // Variable as argument
1063     dict.insert(QStringLiteral("var2"), QStringLiteral("happy"));
1064     QTest::newRow("filter-syntax11") << QStringLiteral("{{ var|default_if_none:var2 }}") << dict << QStringLiteral("happy") << NoError;
1065     // Default argument testing
1066     dict.clear();
1067     dict.insert(QStringLiteral("var"), true);
1068     QTest::newRow("filter-syntax12") << "{{ var|yesno:\"yup,nup,mup\" }} {{ var|yesno }}" << dict << QStringLiteral("yup yes") << NoError;
1069 
1070     // Fail silently for methods that raise an exception with a
1071     // "silent_variable_failure" attribute
1072     //   dict.clear();
1073     //   QObject *someClass = new SomeClass(this);
1074     //   dict.insert( QStringLiteral("var"), QVariant::fromValue(someClass));
1075     //   QTest::newRow("filter-syntax13") << QString::fromLatin1( "1{{
1076     //   var.method3
1077     //   }}2" ) << dict << QString::fromLatin1( "12" ) << NoError;
1078     //   // In methods that raise an exception without a
1079     //   // "silent_variable_attribute" set to True, the exception propagates
1080     //   // #C# SomeOtherException)
1081     //   QTest::newRow("filter-syntax14") << QString::fromLatin1( "var" ) <<
1082     //   dict
1083     //   << QString() << TagSyntaxError;
1084 
1085     // Escaped backslash in argument
1086     dict.clear();
1087     dict.insert(QStringLiteral("var"), QVariant());
1088     QTest::newRow("filter-syntax15") << R"({{ var|default_if_none:"foo\bar" }})" << dict << "foo\\bar" << NoError;
1089     // Escaped backslash using known escape char
1090     QTest::newRow("filter-syntax16") << R"({{ var|default_if_none:"foo\now" }})" << dict << "foo\\now" << NoError;
1091     // Empty strings can be passed as arguments to filters
1092     dict.clear();
1093     dict.insert(QStringLiteral("var"), QVariantList{QStringLiteral("a"), QStringLiteral("b"), QStringLiteral("c")});
1094     QTest::newRow("filter-syntax17") << "{{ var|join:\"\" }}" << dict << QStringLiteral("abc") << NoError;
1095 
1096     // Make sure that any unicode strings are converted to bytestrings
1097     // in the final output.
1098     //   FAIL'filter-syntax18': (r'{{ var }}', {'var': UTF8Class()},
1099     //   u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111'),
1100 
1101     // Numbers as filter arguments should work
1102     dict.clear();
1103     dict.insert(QStringLiteral("var"), QStringLiteral("hello world"));
1104     QTest::newRow("filter-syntax19") << QStringLiteral("{{ var|truncatewords:1 }}") << dict << QStringLiteral("hello ...") << NoError;
1105     // filters should accept empty string constants
1106     dict.clear();
1107     QTest::newRow("filter-syntax20") << R"({{ ""|default_if_none:"was none" }})" << dict << QString() << NoError;
1108 
1109     QTest::newRow("filter-syntax21") << "{{ \"\"|default_if_none:|truncatewords }}" << dict << QString() << EmptyVariableError;
1110 }
1111 
1112 void TestBuiltinSyntax::testCommentSyntax_data()
1113 {
1114     QTest::addColumn<QString>("input");
1115     QTest::addColumn<Dict>("dict");
1116     QTest::addColumn<QString>("output");
1117     QTest::addColumn<KTextTemplate::Error>("error");
1118 
1119     Dict dict;
1120 
1121     QTest::newRow("comment-syntax01") << QStringLiteral("{# this is hidden #}hello") << dict << QStringLiteral("hello") << NoError;
1122     QTest::newRow("comment-syntax02") << QStringLiteral("{# this is hidden #}hello{# foo #}") << dict << QStringLiteral("hello") << NoError;
1123     // Comments can contain invalid stuff.
1124     QTest::newRow("comment-syntax03") << QStringLiteral("foo{#  {% if %}  #}") << dict << QStringLiteral("foo") << NoError;
1125     QTest::newRow("comment-syntax04") << QStringLiteral("foo{#  {% endblock %}  #}") << dict << QStringLiteral("foo") << NoError;
1126     QTest::newRow("comment-syntax05") << QStringLiteral("foo{#  {% somerandomtag %}  #}") << dict << QStringLiteral("foo") << NoError;
1127     QTest::newRow("comment-syntax06") << QStringLiteral("foo{# {% #}") << dict << QStringLiteral("foo") << NoError;
1128     QTest::newRow("comment-syntax07") << QStringLiteral("foo{# %} #}") << dict << QStringLiteral("foo") << NoError;
1129     QTest::newRow("comment-syntax08") << QStringLiteral("foo{# %} #}bar") << dict << QStringLiteral("foobar") << NoError;
1130     QTest::newRow("comment-syntax09") << QStringLiteral("foo{# {{ #}") << dict << QStringLiteral("foo") << NoError;
1131     QTest::newRow("comment-syntax10") << QStringLiteral("foo{# }} #}") << dict << QStringLiteral("foo") << NoError;
1132     QTest::newRow("comment-syntax11") << QStringLiteral("foo{# { #}") << dict << QStringLiteral("foo") << NoError;
1133     QTest::newRow("comment-syntax12") << QStringLiteral("foo{# } #}") << dict << QStringLiteral("foo") << NoError;
1134 }
1135 
1136 void TestBuiltinSyntax::testMultiline_data()
1137 {
1138     QTest::addColumn<QString>("input");
1139     QTest::addColumn<Dict>("dict");
1140     QTest::addColumn<QString>("output");
1141     QTest::addColumn<KTextTemplate::Error>("error");
1142 
1143     Dict dict;
1144 
1145     QTest::newRow("multiline01") << "Hello,\nboys.\nHow\nare\nyou\ngentlemen?" << dict << "Hello,\nboys.\nHow\nare\nyou\ngentlemen?" << NoError;
1146 }
1147 
1148 void TestBuiltinSyntax::testEscaping_data()
1149 {
1150     QTest::addColumn<QString>("input");
1151     QTest::addColumn<Dict>("dict");
1152     QTest::addColumn<QString>("output");
1153     QTest::addColumn<KTextTemplate::Error>("error");
1154 
1155     Dict dict;
1156 
1157     // html escaping is not to be confused with for example url escaping.
1158     dict.insert(QStringLiteral("var"), QStringLiteral("< > & \" \' # = % $"));
1159     QTest::newRow("escape01") << QStringLiteral("{{ var }}") << dict << "&lt; &gt; &amp; &quot; &#39; # = % $" << NoError;
1160 
1161     dict.clear();
1162     dict.insert(QStringLiteral("var"), QStringLiteral("this & that"));
1163     QTest::newRow("escape02") << QStringLiteral("{{ var }}") << dict << QStringLiteral("this &amp; that") << NoError;
1164 
1165     // Strings are compared unescaped.
1166     QTest::newRow("escape03") << "{% ifequal var \"this & that\" %}yes{% endifequal %}" << dict << QStringLiteral("yes") << NoError;
1167 
1168     // Arguments to filters are 'safe' and manipulate their input unescaped.
1169     QTest::newRow("escape04") << "{{ var|cut:\"&\" }}" << dict << QStringLiteral("this  that") << NoError;
1170 
1171     dict.insert(QStringLiteral("varList"), QVariantList{QStringLiteral("Tom"), QStringLiteral("Dick"), QStringLiteral("Harry")});
1172     QTest::newRow("escape05") << "{{ varList|join:\" & \" }}" << dict << QStringLiteral("Tom & Dick & Harry") << NoError;
1173 
1174     // Unlike variable args.
1175     dict.insert(QStringLiteral("amp"), QStringLiteral(" & "));
1176     QTest::newRow("escape06") << QStringLiteral("{{ varList|join:amp }}") << dict << QStringLiteral("Tom &amp; Dick &amp; Harry") << NoError;
1177 
1178     // Literal strings are safe.
1179     QTest::newRow("escape07") << "{{ \"this & that\" }}" << dict << QStringLiteral("this & that") << NoError;
1180 
1181     // Iterating outputs safe characters.
1182     dict.clear();
1183     QVariantList list{QStringLiteral("K"), QStringLiteral("&"), QStringLiteral("R")};
1184     dict.insert(QStringLiteral("list"), list);
1185     QTest::newRow("escape08") << QStringLiteral("{% for letter in list %}{{ letter }},{% endfor %}") << dict << QStringLiteral("K,&amp;,R,") << NoError;
1186 
1187     dict.clear();
1188     // escape requirement survives lookup.
1189     QVariantHash hash;
1190     hash.insert(QStringLiteral("key"), QStringLiteral("this & that"));
1191     dict.insert(QStringLiteral("var"), hash);
1192     QTest::newRow("escape09") << QStringLiteral("{{ var.key }}") << dict << QStringLiteral("this &amp; that") << NoError;
1193 
1194     dict.clear();
1195 }
1196 
1197 void TestBuiltinSyntax::testMultipleStates()
1198 {
1199     auto engine1 = getEngine();
1200 
1201     auto loader1 = QSharedPointer<InMemoryTemplateLoader>(new InMemoryTemplateLoader());
1202 
1203     loader1->setTemplate(QStringLiteral("template1"), QStringLiteral("Template 1"));
1204     engine1->addTemplateLoader(loader1);
1205 
1206     auto t1 = engine1->newTemplate(QStringLiteral("{% include \"template1\" %}"), QStringLiteral("\"template1\""));
1207 
1208     auto engine2 = getEngine();
1209 
1210     auto loader2 = QSharedPointer<InMemoryTemplateLoader>(new InMemoryTemplateLoader());
1211 
1212     loader2->setTemplate(QStringLiteral("template2"), QStringLiteral("Template 2"));
1213 
1214     engine2->addTemplateLoader(loader2);
1215 
1216     auto t2 = engine2->newTemplate(QStringLiteral("{% include \"template2\" %}"), QStringLiteral("\"template2\""));
1217 
1218     auto engine3 = getEngine();
1219 
1220     auto loader3 = QSharedPointer<InMemoryTemplateLoader>(new InMemoryTemplateLoader());
1221 
1222     loader3->setTemplate(QStringLiteral("template3"), QStringLiteral("Template 3"));
1223 
1224     engine3->addTemplateLoader(loader3);
1225 
1226     auto t3 = engine3->newTemplate(QStringLiteral("{% include var %}"), QStringLiteral("var"));
1227 
1228     QVariantHash h;
1229     h.insert(QStringLiteral("var"), QStringLiteral("template3"));
1230     Context c(h);
1231     t1->render(&c);
1232 
1233     auto expected1 = QStringLiteral("Template 1");
1234     auto expected2 = QStringLiteral("Template 2");
1235     auto expected3 = QStringLiteral("Template 3");
1236     QCOMPARE(t1->render(&c), expected1);
1237     QCOMPARE(t2->render(&c), expected2);
1238     QCOMPARE(t3->render(&c), expected3);
1239 }
1240 
1241 void TestBuiltinSyntax::testAlternativeEscaping()
1242 {
1243     auto engine1 = getEngine();
1244 
1245     auto t1 = engine1->newTemplate(QStringLiteral("{{ var }} {% spaceless %}{{ var }}{% endspaceless %}"), QStringLiteral("\"template1\""));
1246 
1247     auto input = QStringLiteral("< > \r\n & \" \' # = % $");
1248 
1249     QVariantHash h;
1250     h.insert(QStringLiteral("var"), input);
1251     Context c(h);
1252 
1253     QString output;
1254     QTextStream ts(&output);
1255 
1256     NoEscapeOutputStream noEscapeOs(&ts);
1257 
1258     t1->render(&noEscapeOs, &c);
1259 
1260     QCOMPARE(output, QString(input + QLatin1String(" ") + input));
1261     output.clear();
1262 
1263     JSOutputStream jsOs(&ts);
1264 
1265     t1->render(&jsOs, &c);
1266 
1267     QString jsOutput(QStringLiteral("\\u003C \\u003E \\u000D\\u000A \\u0026 \\u0022 \\u0027 # \\u003D % $"));
1268 
1269     jsOutput = jsOutput + QLatin1String(" ") + jsOutput;
1270 
1271     QCOMPARE(output, jsOutput);
1272 }
1273 
1274 void TestBuiltinSyntax::testTemplatePathSafety_data()
1275 {
1276     QTest::addColumn<QString>("inputPath");
1277     QTest::addColumn<QString>("output");
1278 
1279     QTest::newRow("template-path-safety01") << QStringLiteral("visible_file") << QStringLiteral("visible_file");
1280     QTest::newRow("template-path-safety02") << QStringLiteral("../invisible_file") << QString();
1281 }
1282 
1283 void TestBuiltinSyntax::testTemplatePathSafety()
1284 {
1285     QFETCH(QString, inputPath);
1286     QFETCH(QString, output);
1287 
1288     auto loader = new FileSystemTemplateLoader();
1289 
1290     loader->setTemplateDirs({QStringLiteral(".")});
1291 
1292     QFile f(inputPath);
1293     auto opened = f.open(QFile::WriteOnly | QFile::Text);
1294     QVERIFY(opened);
1295     f.write(inputPath.toUtf8());
1296     f.close();
1297 
1298     auto t = loader->loadByName(inputPath, m_engine);
1299     Context c;
1300     if (output.isEmpty())
1301         QVERIFY(!t);
1302     else
1303         QCOMPARE(t->render(&c), inputPath);
1304 
1305     delete loader;
1306     f.remove();
1307 }
1308 
1309 void TestBuiltinSyntax::testMediaPathSafety_data()
1310 {
1311     QTest::addColumn<QString>("inputPath");
1312     QTest::addColumn<QString>("output");
1313 
1314     QTest::newRow("media-path-safety01") << QStringLiteral("visible_file") << QStringLiteral("./visible_file");
1315     QTest::newRow("media-path-safety02") << QStringLiteral("../invisible_file") << QString();
1316 }
1317 
1318 void TestBuiltinSyntax::testMediaPathSafety()
1319 {
1320     QFETCH(QString, inputPath);
1321     QFETCH(QString, output);
1322 
1323     auto loader = new FileSystemTemplateLoader();
1324 
1325     loader->setTemplateDirs({QStringLiteral(".")});
1326 
1327     QFile f(inputPath);
1328     auto opened = f.open(QFile::WriteOnly | QFile::Text);
1329     QVERIFY(opened);
1330     f.write(inputPath.toUtf8());
1331     f.close();
1332 
1333     auto uri = loader->getMediaUri(inputPath);
1334     if (output.isEmpty())
1335         QVERIFY(uri.second.isEmpty());
1336     else
1337         QCOMPARE(QFileInfo(uri.first + uri.second).absoluteFilePath(), QFileInfo(output).absoluteFilePath());
1338 
1339     delete loader;
1340     f.remove();
1341 }
1342 
1343 void TestBuiltinSyntax::testTypeAccessorsUnordered()
1344 {
1345     QFETCH(QString, input);
1346     QFETCH(Dict, dict);
1347     QFETCH(QStringList, output);
1348     QFETCH(KTextTemplate::Error, error);
1349 
1350     auto t = m_engine->newTemplate(input, QLatin1String(QTest::currentDataTag()));
1351 
1352     Context context(dict);
1353 
1354     auto result = t->render(&context);
1355     if (t->error() != NoError) {
1356         if (t->error() != error)
1357             qDebug() << t->errorString();
1358         QCOMPARE(t->error(), error);
1359         return;
1360     }
1361 
1362     QCOMPARE(t->error(), NoError);
1363 
1364     // Didn't catch any errors, so make sure I didn't expect any.
1365     QCOMPARE(NoError, error);
1366 
1367     for (const QString &s : output) {
1368         QVERIFY(result.contains(s));
1369     }
1370 
1371     QCOMPARE(result.length(), output.join(QString()).length());
1372 }
1373 
1374 void TestBuiltinSyntax::testTypeAccessorsUnordered_data()
1375 {
1376     QTest::addColumn<QString>("input");
1377     QTest::addColumn<Dict>("dict");
1378     QTest::addColumn<QStringList>("output");
1379     QTest::addColumn<KTextTemplate::Error>("error");
1380 
1381     Dict dict;
1382 
1383     QVariantHash itemsHash;
1384     itemsHash.insert(QStringLiteral("one"), 1);
1385     itemsHash.insert(QStringLiteral("two"), 2);
1386     itemsHash.insert(QStringLiteral("three"), 3);
1387 
1388     dict.insert(QStringLiteral("hash"), itemsHash);
1389 
1390     QTest::newRow("type-accessors-hash-unordered01") << QStringLiteral(
1391         "{% for key,value in hash.items %}{{ key }}:{{ value "
1392         "}};{% endfor %}") << dict << QStringList{QStringLiteral("one:1;"), QStringLiteral("two:2;"), QStringLiteral("three:3;")}
1393                                                      << NoError;
1394     QTest::newRow("type-accessors-hash-unordered02") << QStringLiteral("{% for key in hash.keys %}{{ key }};{% endfor %}") << dict
1395                                                      << QStringList{QStringLiteral("one;"), QStringLiteral("two;"), QStringLiteral("three;")} << NoError;
1396     QTest::newRow("type-accessors-hash-unordered03") << QStringLiteral("{% for value in hash.values %}{{ value }};{% endfor %}") << dict
1397                                                      << QStringList{QStringLiteral("1;"), QStringLiteral("2;"), QStringLiteral("3;")} << NoError;
1398 }
1399 
1400 void TestBuiltinSyntax::testTypeAccessors_data()
1401 {
1402     QTest::addColumn<QString>("input");
1403     QTest::addColumn<Dict>("dict");
1404     QTest::addColumn<QString>("output");
1405     QTest::addColumn<KTextTemplate::Error>("error");
1406 
1407     Dict dict;
1408 
1409     QVariantHash itemsHash;
1410     itemsHash.insert(QStringLiteral("one"), 1);
1411     itemsHash.insert(QStringLiteral("two"), 2);
1412     itemsHash.insert(QStringLiteral("three"), 3);
1413 
1414     dict.insert(QStringLiteral("hash"), itemsHash);
1415 
1416     QTest::newRow("type-accessors-hash01") << QStringLiteral("{{ hash.items|length }}") << dict << QStringLiteral("3") << NoError;
1417     QTest::newRow("type-accessors-hash02") << QStringLiteral("{{ hash.keys|length }}") << dict << QStringLiteral("3") << NoError;
1418     QTest::newRow("type-accessors-hash03") << QStringLiteral("{{ hash.values|length }}") << dict << QStringLiteral("3") << NoError;
1419 
1420     dict.clear();
1421     dict.insert(QStringLiteral("str1"), QStringLiteral("my string"));
1422     dict.insert(QStringLiteral("str2"), QStringLiteral("mystring"));
1423 
1424     QTest::newRow("type-accessors-string01") << QStringLiteral("{{ str1.capitalize }}") << dict << QStringLiteral("My string") << NoError;
1425     QTest::newRow("type-accessors-string02") << QStringLiteral("{{ str2.capitalize }}") << dict << QStringLiteral("Mystring") << NoError;
1426 
1427     dict.clear();
1428     dict.insert(QStringLiteral("str1"), QStringLiteral("de24335fre"));
1429     dict.insert(QStringLiteral("str2"), QStringLiteral("de435f3.-5r"));
1430 
1431     QTest::newRow("type-accessors-string03") << QStringLiteral("{{ str1.isalnum }}") << dict << QStringLiteral("True") << NoError;
1432     QTest::newRow("type-accessors-string04") << QStringLiteral("{{ str2.isalnum }}") << dict << QStringLiteral("False") << NoError;
1433 
1434     dict.clear();
1435     dict.insert(QStringLiteral("str1"), QStringLiteral("24335"));
1436     dict.insert(QStringLiteral("str2"), QStringLiteral("de435f35r"));
1437     dict.insert(QStringLiteral("str3"), QStringLiteral("de435f3.-5r"));
1438 
1439     QTest::newRow("type-accessors-string05") << QStringLiteral("{{ str1.isdigit }}") << dict << QStringLiteral("True") << NoError;
1440     QTest::newRow("type-accessors-string06") << QStringLiteral("{{ str2.isdigit }}") << dict << QStringLiteral("False") << NoError;
1441     QTest::newRow("type-accessors-string07") << QStringLiteral("{{ str3.isdigit }}") << dict << QStringLiteral("False") << NoError;
1442 
1443     dict.clear();
1444     dict.insert(QStringLiteral("str"), QStringLiteral("MyString"));
1445     dict.insert(QStringLiteral("lowerStr"), QStringLiteral("mystring"));
1446 
1447     QTest::newRow("type-accessors-string08") << QStringLiteral("{{ str.islower }}") << dict << QStringLiteral("False") << NoError;
1448     QTest::newRow("type-accessors-string09") << QStringLiteral("{{ lowerStr.islower }}") << dict << QStringLiteral("True") << NoError;
1449 
1450     dict.clear();
1451     dict.insert(QStringLiteral("str1"), QStringLiteral("        "));
1452     dict.insert(QStringLiteral("str2"), QStringLiteral("     r  "));
1453     dict.insert(QStringLiteral("str3"), QStringLiteral(" \t\nr  "));
1454     dict.insert(QStringLiteral("str4"), QStringLiteral(" \t\n   "));
1455 
1456     QTest::newRow("type-accessors-string10") << QStringLiteral("{{ str1.isspace }}") << dict << QStringLiteral("True") << NoError;
1457     QTest::newRow("type-accessors-string11") << QStringLiteral("{{ str2.isspace }}") << dict << QStringLiteral("False") << NoError;
1458     QTest::newRow("type-accessors-string12") << QStringLiteral("{{ str3.isspace }}") << dict << QStringLiteral("False") << NoError;
1459     QTest::newRow("type-accessors-string13") << QStringLiteral("{{ str4.isspace }}") << dict << QStringLiteral("True") << NoError;
1460 
1461     dict.clear();
1462     dict.insert(QStringLiteral("str1"), QStringLiteral("My String"));
1463     dict.insert(QStringLiteral("str2"), QStringLiteral("Mystring"));
1464     dict.insert(QStringLiteral("str3"), QStringLiteral("My string"));
1465     dict.insert(QStringLiteral("str4"), QStringLiteral("my string"));
1466 
1467     QTest::newRow("type-accessors-string14") << QStringLiteral("{{ str1.istitle }}") << dict << QStringLiteral("True") << NoError;
1468     QTest::newRow("type-accessors-string15") << QStringLiteral("{{ str2.istitle }}") << dict << QStringLiteral("True") << NoError;
1469     QTest::newRow("type-accessors-string16") << QStringLiteral("{{ str3.istitle }}") << dict << QStringLiteral("False") << NoError;
1470     QTest::newRow("type-accessors-string17") << QStringLiteral("{{ str4.istitle }}") << dict << QStringLiteral("False") << NoError;
1471 
1472     dict.clear();
1473     dict.insert(QStringLiteral("str"), QStringLiteral("MyString"));
1474     dict.insert(QStringLiteral("upperStr"), QStringLiteral("MYSTRING"));
1475 
1476     QTest::newRow("type-accessors-string18") << QStringLiteral("{{ str.isupper }}") << dict << QStringLiteral("False") << NoError;
1477     QTest::newRow("type-accessors-string19") << QStringLiteral("{{ upperStr.isupper }}") << dict << QStringLiteral("True") << NoError;
1478 
1479     dict.clear();
1480     dict.insert(QStringLiteral("str1"), QStringLiteral("My String"));
1481     dict.insert(QStringLiteral("str2"), QStringLiteral("MYSTRING"));
1482     dict.insert(QStringLiteral("str3"), QStringLiteral("MY STRING"));
1483 
1484     QTest::newRow("type-accessors-string20") << QStringLiteral("{{ str1.lower }}") << dict << QStringLiteral("my string") << NoError;
1485     QTest::newRow("type-accessors-string21") << QStringLiteral("{{ str2.lower }}") << dict << QStringLiteral("mystring") << NoError;
1486     QTest::newRow("type-accessors-string22") << QStringLiteral("{{ str3.lower }}") << dict << QStringLiteral("my string") << NoError;
1487 
1488     dict.clear();
1489     dict.insert(QStringLiteral("str"), QStringLiteral("one\ntwo three\nfour"));
1490 
1491     QTest::newRow("type-accessors-string23") << QStringLiteral("{% for line in str.splitlines %}{{ line }};{% endfor %}") << dict
1492                                              << QStringLiteral("one;two three;four;") << NoError;
1493 
1494     dict.clear();
1495     dict.insert(QStringLiteral("str1"), QStringLiteral("          one"));
1496     dict.insert(QStringLiteral("str2"), QStringLiteral("     one     "));
1497     dict.insert(QStringLiteral("str3"), QStringLiteral("one          "));
1498     dict.insert(QStringLiteral("str4"), QStringLiteral("             "));
1499     dict.insert(QStringLiteral("str5"), QStringLiteral(""));
1500 
1501     QTest::newRow("type-accessors-string24") << QStringLiteral("{{ str1.strip }}") << dict << QStringLiteral("one") << NoError;
1502     QTest::newRow("type-accessors-string25") << QStringLiteral("{{ str2.strip }}") << dict << QStringLiteral("one") << NoError;
1503     QTest::newRow("type-accessors-string26") << QStringLiteral("{{ str3.strip }}") << dict << QStringLiteral("one") << NoError;
1504     QTest::newRow("type-accessors-string27") << QStringLiteral("{{ str4.strip }}") << dict << QString() << NoError;
1505     QTest::newRow("type-accessors-string28") << QStringLiteral("{{ str5.strip }}") << dict << QString() << NoError;
1506 
1507     dict.clear();
1508     dict.insert(QStringLiteral("str1"), QStringLiteral("My String"));
1509     dict.insert(QStringLiteral("str2"), QStringLiteral("mY sTRING"));
1510     dict.insert(QStringLiteral("str3"), QStringLiteral("My StrInG"));
1511     dict.insert(QStringLiteral("str4"), QStringLiteral("my string"));
1512     dict.insert(QStringLiteral("str5"), QStringLiteral("MY STRING"));
1513 
1514     // Yes, this really is a python built-in.
1515     QTest::newRow("type-accessors-string29") << QStringLiteral("{{ str1.swapcase }}") << dict << QStringLiteral("mY sTRING") << NoError;
1516     QTest::newRow("type-accessors-string30") << QStringLiteral("{{ str2.swapcase }}") << dict << QStringLiteral("My String") << NoError;
1517     QTest::newRow("type-accessors-string31") << QStringLiteral("{{ str3.swapcase }}") << dict << QStringLiteral("mY sTRiNg") << NoError;
1518     QTest::newRow("type-accessors-string32") << QStringLiteral("{{ str4.swapcase }}") << dict << QStringLiteral("MY STRING") << NoError;
1519     QTest::newRow("type-accessors-string33") << QStringLiteral("{{ str5.swapcase }}") << dict << QStringLiteral("my string") << NoError;
1520 
1521     dict.clear();
1522     dict.insert(QStringLiteral("str1"), QStringLiteral("My String"));
1523     dict.insert(QStringLiteral("str2"), QStringLiteral("mystring"));
1524     dict.insert(QStringLiteral("str3"), QStringLiteral("my string"));
1525     dict.insert(QStringLiteral("str4"), QStringLiteral("my String"));
1526     dict.insert(QStringLiteral("str5"), QStringLiteral("My string"));
1527     dict.insert(QStringLiteral("str6"), QStringLiteral("123"));
1528     dict.insert(QStringLiteral("str7"), QString());
1529 
1530     QTest::newRow("type-accessors-string34") << QStringLiteral("{{ str1.title }}") << dict << QStringLiteral("My String") << NoError;
1531     QTest::newRow("type-accessors-string35") << QStringLiteral("{{ str2.title }}") << dict << QStringLiteral("Mystring") << NoError;
1532     QTest::newRow("type-accessors-string36") << QStringLiteral("{{ str3.title }}") << dict << QStringLiteral("My String") << NoError;
1533     QTest::newRow("type-accessors-string37") << QStringLiteral("{{ str4.title }}") << dict << QStringLiteral("My String") << NoError;
1534     QTest::newRow("type-accessors-string38") << QStringLiteral("{{ str5.title }}") << dict << QStringLiteral("My String") << NoError;
1535 
1536     QTest::newRow("type-accessors-string39") << QStringLiteral("{{ str1.upper }}") << dict << QStringLiteral("MY STRING") << NoError;
1537     QTest::newRow("type-accessors-string40") << QStringLiteral("{{ str2.upper }}") << dict << QStringLiteral("MYSTRING") << NoError;
1538     QTest::newRow("type-accessors-string41") << QStringLiteral("{{ str3.upper }}") << dict << QStringLiteral("MY STRING") << NoError;
1539     QTest::newRow("type-accessors-string42") << QStringLiteral("{{ str3.dne }}") << dict << QString() << NoError;
1540     QTest::newRow("type-accessors-string43") << QStringLiteral("{{ str2.isalpha }}") << dict << QStringLiteral("True") << NoError;
1541     QTest::newRow("type-accessors-string44") << QStringLiteral("{{ str3.isalpha }}") << dict << QStringLiteral("False") << NoError;
1542     QTest::newRow("type-accessors-string45") << QStringLiteral("{{ str6.isalpha }}") << dict << QStringLiteral("False") << NoError;
1543     QTest::newRow("type-accessors-string46") << QStringLiteral("{{ str7.isalpha }}") << dict << QStringLiteral("False") << NoError;
1544 
1545     dict.clear();
1546 
1547 #define SON(obj) obj->setObjectName(QStringLiteral(#obj))
1548 
1549     auto obj1 = new QObject(this);
1550     SON(obj1);
1551     auto obj2 = new QObject(this);
1552     SON(obj2);
1553     obj2->setParent(obj1);
1554     auto obj3 = new QObject(this);
1555     obj3->setParent(obj2);
1556     SON(obj3);
1557     auto obj4 = new QObject(this);
1558     obj4->setParent(obj2);
1559     SON(obj4);
1560 
1561     dict.insert(QStringLiteral("object"), QVariant::fromValue(obj1));
1562 
1563     QTest::newRow("type-accessors-qobject01") << QStringLiteral("{{ object.objectName }}") << dict << QStringLiteral("obj1") << NoError;
1564 
1565     const QLatin1String objectDumper(
1566         "<li>{{ object.objectName }}</li>"
1567         "{% if object.children %}"
1568         "<ul>"
1569         "{% for object in object.children %}"
1570         "{% include 'objectdumper.html' %}"
1571         "{% endfor %}"
1572         "</ul>"
1573         "{% endif %}");
1574 
1575     m_loader->setTemplate(QStringLiteral("objectdumper.html"), objectDumper);
1576 
1577     QTest::newRow("type-accessors-qobject02") << QStringLiteral("<ul>{% include 'objectdumper.html' %}</ul>") << dict
1578                                               << QString::fromLatin1(
1579                                                      "<ul>"
1580                                                      "<li>obj1</li>"
1581                                                      "<ul>"
1582                                                      "<li>obj2</li>"
1583                                                      "<ul>"
1584                                                      "<li>obj3</li>"
1585                                                      "<li>obj4</li>"
1586                                                      "</ul>"
1587                                                      "</ul>"
1588                                                      "</ul>")
1589                                               << NoError;
1590 }
1591 
1592 void TestBuiltinSyntax::testDynamicProperties_data()
1593 {
1594     QTest::addColumn<QString>("input");
1595     QTest::addColumn<Dict>("dict");
1596     QTest::addColumn<QString>("output");
1597     QTest::addColumn<KTextTemplate::Error>("error");
1598 
1599     Dict dict;
1600 
1601     auto obj = new QObject(this);
1602     obj->setProperty("prop", 7);
1603     dict.insert(QStringLiteral("var"), QVariant::fromValue(static_cast<QObject *>(obj)));
1604 
1605     QTest::newRow("dynamic-properties01") << QStringLiteral("{{ var.prop }}") << dict << QStringLiteral("7") << NoError;
1606 }
1607 
1608 void TestBuiltinSyntax::testGarbageInput()
1609 {
1610     QFETCH(QString, input);
1611 
1612     auto t = m_engine->newTemplate(input, QLatin1String(QTest::currentDataTag()));
1613 
1614     Dict dict;
1615 
1616     Context context(dict);
1617 
1618     auto result = t->render(&context);
1619 
1620     QCOMPARE(t->error(), NoError);
1621 
1622     QCOMPARE(result, input);
1623 }
1624 
1625 void TestBuiltinSyntax::testGarbageInput_data()
1626 {
1627     QTest::addColumn<QString>("input");
1628 
1629     Dict dict;
1630 
1631     QTest::newRow("garbage-input01") << QStringLiteral("content %}");
1632     QTest::newRow("garbage-input02") << QStringLiteral(" content %}");
1633     QTest::newRow("garbage-input03") << QStringLiteral("content #}");
1634     QTest::newRow("garbage-input04") << QStringLiteral(" content #}");
1635     QTest::newRow("garbage-input05") << QStringLiteral("content }}");
1636     QTest::newRow("garbage-input06") << QStringLiteral(" content }}");
1637     QTest::newRow("garbage-input07") << QStringLiteral("% content %}");
1638     QTest::newRow("garbage-input08") << QStringLiteral("# content #}");
1639     QTest::newRow("garbage-input09") << QStringLiteral("{ content }}");
1640     QTest::newRow("garbage-input10") << QStringLiteral("{% content }");
1641     QTest::newRow("garbage-input11") << QStringLiteral("{% content %");
1642     QTest::newRow("garbage-input12") << QStringLiteral("{# content }");
1643     QTest::newRow("garbage-input13") << QStringLiteral("{# content #");
1644     QTest::newRow("garbage-input14") << QStringLiteral("{{ content }");
1645     QTest::newRow("garbage-input15") << QStringLiteral("{{ content }");
1646     QTest::newRow("garbage-input16") << QStringLiteral("{{ content %}");
1647     QTest::newRow("garbage-input17") << QStringLiteral("{% content }}");
1648     QTest::newRow("garbage-input18") << QStringLiteral("{{ content #}");
1649     QTest::newRow("garbage-input19") << QStringLiteral("{# content }}");
1650     QTest::newRow("garbage-input20") << QStringLiteral("{{ con #} tent #}");
1651     QTest::newRow("garbage-input21") << QStringLiteral("{{ con %} tent #}");
1652     QTest::newRow("garbage-input22") << QStringLiteral("{{ con #} tent %}");
1653     QTest::newRow("garbage-input23") << QStringLiteral("{{ con %} tent %}");
1654     QTest::newRow("garbage-input24") << QStringLiteral("{% con #} tent #}");
1655     QTest::newRow("garbage-input25") << QStringLiteral("{% con }} tent #}");
1656     QTest::newRow("garbage-input26") << QStringLiteral("{% con #} tent }}");
1657     QTest::newRow("garbage-input27") << QStringLiteral("{% con }} tent }}");
1658     QTest::newRow("garbage-input28") << QStringLiteral("{# con %} tent %}");
1659     QTest::newRow("garbage-input29") << QStringLiteral("{# con }} tent %}");
1660     QTest::newRow("garbage-input30") << QStringLiteral("{# con %} tent }}");
1661     QTest::newRow("garbage-input31") << QStringLiteral("{# con }} tent }}");
1662     QTest::newRow("garbage-input32") << QStringLiteral("{# con {# tent }}");
1663     QTest::newRow("garbage-input33") << QStringLiteral("{# con {% tent }}");
1664     QTest::newRow("garbage-input34") << QStringLiteral("{% con {% tent }}");
1665     QTest::newRow("garbage-input35") << QStringLiteral("{ { content }}");
1666     QTest::newRow("garbage-input36") << QStringLiteral("{ % content %}");
1667     QTest::newRow("garbage-input37") << QStringLiteral("{ # content #}");
1668     QTest::newRow("garbage-input38") << QStringLiteral("{\n{ content }}");
1669     QTest::newRow("garbage-input39") << QStringLiteral("{\n# content #}");
1670     QTest::newRow("garbage-input40") << QStringLiteral("{\n% content %}");
1671     QTest::newRow("garbage-input41") << QStringLiteral("{{\n content }}");
1672     QTest::newRow("garbage-input42") << QStringLiteral("{#\n content #}");
1673     QTest::newRow("garbage-input43") << QStringLiteral("{%\n content %}");
1674     QTest::newRow("garbage-input44") << QStringLiteral("{{ content \n}}");
1675     QTest::newRow("garbage-input45") << QStringLiteral("{# content \n#}");
1676     QTest::newRow("garbage-input46") << QStringLiteral("{% content \n%}");
1677     QTest::newRow("garbage-input47") << QStringLiteral("{{ content }\n}");
1678     QTest::newRow("garbage-input48") << QStringLiteral("{# content #\n}");
1679     QTest::newRow("garbage-input49") << QStringLiteral("{% content %\n}");
1680     QTest::newRow("garbage-input50") << QStringLiteral("{{ content } }");
1681     QTest::newRow("garbage-input51") << QStringLiteral("{% content % }");
1682     QTest::newRow("garbage-input52") << QStringLiteral("{# content # }");
1683     QTest::newRow("garbage-input53") << QStringLiteral("{ { content } }");
1684     QTest::newRow("garbage-input54") << QStringLiteral("{ % content % }");
1685     QTest::newRow("garbage-input55") << QStringLiteral("{ # content # }");
1686     QTest::newRow("garbage-input56") << QStringLiteral("{{ content }%");
1687     QTest::newRow("garbage-input57") << QStringLiteral("{# content #%");
1688     QTest::newRow("garbage-input58") << QStringLiteral("{% content %%");
1689     QTest::newRow("garbage-input59") << QStringLiteral("{{ content }A");
1690     QTest::newRow("garbage-input60") << QStringLiteral("{# content #A");
1691     QTest::newRow("garbage-input61") << QStringLiteral("{% content %A");
1692     QTest::newRow("garbage-input62") << QStringLiteral("{{ content A}");
1693     QTest::newRow("garbage-input63") << QStringLiteral("{# content A#");
1694     QTest::newRow("garbage-input64") << QStringLiteral("{% content A%");
1695     QTest::newRow("garbage-input65") << QStringLiteral("{# content A}");
1696     QTest::newRow("garbage-input66") << QStringLiteral("{% content A}");
1697     QTest::newRow("garbage-input67") << QStringLiteral("A{ content }}");
1698     QTest::newRow("garbage-input68") << QStringLiteral("A# content #}");
1699     QTest::newRow("garbage-input69") << QStringLiteral("A% content %}");
1700     QTest::newRow("garbage-input60") << QStringLiteral("{A content }}");
1701     QTest::newRow("garbage-input71") << QStringLiteral("{A content #}");
1702     QTest::newRow("garbage-input72") << QStringLiteral("{A content %}");
1703     QTest::newRow("garbage-input73") << QStringLiteral("{A content #}");
1704     QTest::newRow("garbage-input74") << QStringLiteral("{A content %}");
1705     QTest::newRow("garbage-input75") << QStringLiteral("{A content A}");
1706     QTest::newRow("garbage-input76") << QStringLiteral("}} content }}");
1707     QTest::newRow("garbage-input77") << QStringLiteral("}} content {{");
1708     QTest::newRow("garbage-input78") << QStringLiteral("#} content #}");
1709     QTest::newRow("garbage-input79") << QStringLiteral("#} content {#");
1710     QTest::newRow("garbage-input80") << QStringLiteral("%} content %}");
1711     QTest::newRow("garbage-input81") << QStringLiteral("%} content {%");
1712     QTest::newRow("garbage-input82") << QStringLiteral("#{ content }#");
1713     QTest::newRow("garbage-input83") << QStringLiteral("%{ content }%");
1714 }
1715 
1716 void TestBuiltinSyntax::testInsignificantWhitespace()
1717 {
1718     QFETCH(QString, input);
1719     QFETCH(Dict, dict);
1720     QFETCH(QString, stripped_output);
1721     QFETCH(QString, unstripped_output);
1722 
1723     Context context(dict);
1724 
1725     QVERIFY(!m_engine->smartTrimEnabled());
1726     m_engine->setSmartTrimEnabled(true);
1727     QVERIFY(m_engine->smartTrimEnabled());
1728 
1729     {
1730         auto t = m_engine->newTemplate(input, QLatin1String(QTest::currentDataTag()));
1731 
1732         auto result = t->render(&context);
1733 
1734         QCOMPARE(t->error(), NoError);
1735 
1736         QCOMPARE(result, stripped_output);
1737     }
1738     m_engine->setSmartTrimEnabled(false);
1739     {
1740         auto t = m_engine->newTemplate(input, QLatin1String(QTest::currentDataTag()));
1741 
1742         auto result = t->render(&context);
1743 
1744         QCOMPARE(t->error(), NoError);
1745 
1746         QCOMPARE(result, unstripped_output);
1747     }
1748 }
1749 
1750 void TestBuiltinSyntax::testInsignificantWhitespace_data()
1751 {
1752     QTest::addColumn<QString>("input");
1753     QTest::addColumn<Dict>("dict");
1754     QTest::addColumn<QString>("stripped_output");
1755     QTest::addColumn<QString>("unstripped_output");
1756 
1757     Dict dict;
1758 
1759     QTest::newRow("insignificant-whitespace01") << QStringLiteral("\n {% templatetag openblock %}\n") << dict << QStringLiteral("{%\n")
1760                                                 << QStringLiteral("\n {%\n");
1761 
1762     QTest::newRow("insignificant-whitespace02") << QStringLiteral("\n{% templatetag openblock %}\n") << dict << QStringLiteral("{%\n")
1763                                                 << QStringLiteral("\n{%\n");
1764 
1765     QTest::newRow("insignificant-whitespace03") << QStringLiteral("{% templatetag openblock %}\n") << dict << QStringLiteral("{%\n") << QStringLiteral("{%\n");
1766 
1767     QTest::newRow("insignificant-whitespace04") << QStringLiteral("\n\t \t {% templatetag openblock %}\n") << dict << QStringLiteral("{%\n")
1768                                                 << QStringLiteral("\n\t \t {%\n");
1769 
1770     // Leading whitespace with text before single template tag
1771     QTest::newRow("insignificant-whitespace05") << QStringLiteral("\n some\ttext {% templatetag openblock %}\n") << dict << QStringLiteral("\n some\ttext {%\n")
1772                                                 << QStringLiteral("\n some\ttext {%\n");
1773 
1774     // Leading line with text before single template tag
1775     QTest::newRow("insignificant-whitespace06") << QStringLiteral("\n some\ttext\n {% templatetag openblock %}\n") << dict
1776                                                 << QStringLiteral("\n some\ttext{%\n") << QStringLiteral("\n some\ttext\n {%\n");
1777     QTest::newRow("insignificant-whitespace07") << QStringLiteral("\n some\ttext \n \t {% templatetag openblock %}\n") << dict
1778                                                 << QStringLiteral("\n some\ttext {%\n") << QStringLiteral("\n some\ttext \n \t {%\n");
1779 
1780     // whitespace leading /before/ the newline is not stripped.
1781     QTest::newRow("insignificant-whitespace08") << QStringLiteral("\n some\ttext \t \n {% templatetag openblock %}\n") << dict
1782                                                 << QStringLiteral("\n some\ttext \t {%\n") << QStringLiteral("\n some\ttext \t \n {%\n");
1783 
1784     // Multiple text lines before tag
1785     QTest::newRow("insignificant-whitespace09") << QStringLiteral("\n some\ntext \t \n {% templatetag openblock %}\n") << dict
1786                                                 << QStringLiteral("\n some\ntext \t {%\n") << QStringLiteral("\n some\ntext \t \n {%\n");
1787     QTest::newRow("insignificant-whitespace10") << QStringLiteral("\n some \t \n \t text \t \n {% templatetag openblock %}\n") << dict
1788                                                 << QStringLiteral("\n some \t \n \t text \t {%\n") << QStringLiteral("\n some \t \n \t text \t \n {%\n");
1789 
1790     // Leading whitespace before tag, some text after
1791     QTest::newRow("insignificant-whitespace11") << QStringLiteral("\n   \t {% templatetag openblock %} some text\n") << dict
1792                                                 << QStringLiteral("\n   \t {% some text\n") << QStringLiteral("\n   \t {% some text\n");
1793 
1794     // Leading whitespace before tag, some text with trailing whitespace after
1795     QTest::newRow("insignificant-whitespace12") << QStringLiteral("\n   \t {% templatetag openblock %} some text  \t \n") << dict
1796                                                 << QStringLiteral("\n   \t {% some text  \t \n") << QStringLiteral("\n   \t {% some text  \t \n");
1797 
1798     // Whitespace after tag is not removed
1799     QTest::newRow("insignificant-whitespace13") << QStringLiteral("\n \t {% templatetag openblock %} \t \n \t some text  \t \n") << dict
1800                                                 << QStringLiteral("{% \t \n \t some text  \t \n") << QStringLiteral("\n \t {% \t \n \t some text  \t \n");
1801 
1802     // Multiple lines of leading whitespace. Only one leading newline is removed
1803     QTest::newRow("insignificant-whitespace14") << QStringLiteral("\n\n\n{% templatetag openblock %}\n some text\n") << dict
1804                                                 << QStringLiteral("\n\n{%\n some text\n") << QStringLiteral("\n\n\n{%\n some text\n");
1805 
1806     // Trailing whitespace after tag
1807     QTest::newRow("insignificant-whitespace15") << QStringLiteral("\n\n\n{% templatetag openblock %}\t \t \t\n some text\n") << dict
1808                                                 << QStringLiteral("\n\n{%\t \t \t\n some text\n") << QStringLiteral("\n\n\n{%\t \t \t\n some text\n");
1809 
1810     // Removable newline followed by leading whitespace
1811     QTest::newRow("insignificant-whitespace16") << QStringLiteral("\n\n\n\t \t \t{% templatetag openblock %}\n some text\n") << dict
1812                                                 << QStringLiteral("\n\n{%\n some text\n") << QStringLiteral("\n\n\n\t \t \t{%\n some text\n");
1813 
1814     // Removable leading whitespace and trailing whitespace
1815     QTest::newRow("insignificant-whitespace17") << QStringLiteral("\n\n\n\t \t \t{% templatetag openblock %}\t \t \t\n some text\n") << dict
1816                                                 << QStringLiteral("\n\n{%\t \t \t\n some text\n") << QStringLiteral("\n\n\n\t \t \t{%\t \t \t\n some text\n");
1817 
1818     // Multiple lines of trailing whitespace. No trailing newline is removed.
1819     QTest::newRow("insignificant-whitespace18") << QStringLiteral("\n{% templatetag openblock %}\n\n\n some text\n") << dict
1820                                                 << QStringLiteral("{%\n\n\n some text\n") << QStringLiteral("\n{%\n\n\n some text\n");
1821     QTest::newRow("insignificant-whitespace19") << QStringLiteral("\n{% templatetag openblock %}\t \n\n\n some text\n") << dict
1822                                                 << QStringLiteral("{%\t \n\n\n some text\n") << QStringLiteral("\n{%\t \n\n\n some text\n");
1823 
1824     // Consecutive trimmed lines with tags strips one newline each
1825     QTest::newRow("insignificant-whitespace20") << QStringLiteral(
1826         "\n{% templatetag openblock %}\n{% templatetag openblock %}\n{% "
1827         "templatetag openblock %}\n some text\n")
1828                                                 << dict << QStringLiteral("{%{%{%\n some text\n") << QStringLiteral("\n{%\n{%\n{%\n some text\n");
1829 
1830     // Consecutive trimmed lines with tags strips one newline each. Intermediate
1831     // newlines are preserved
1832     QTest::newRow("insignificant-whitespace21") << QStringLiteral(
1833         "\n\n{% templatetag openblock %}\n\n{% templatetag openblock "
1834         "%}\n\n{% templatetag openblock %}\n\n some text\n")
1835                                                 << dict << QStringLiteral("\n{%\n{%\n{%\n\n some text\n")
1836                                                 << QStringLiteral("\n\n{%\n\n{%\n\n{%\n\n some text\n");
1837 
1838     // Consecutive trimmed lines with tags strips one newline each. Leading
1839     // whitespace is stripped but trailing is not
1840     QTest::newRow("insignificant-whitespace22") << QStringLiteral(
1841         "\n\n\t {% templatetag openblock %}\t \n\n\t {% "
1842         "templatetag openblock %}\t \n\n\t {% templatetag "
1843         "openblock %}\t \n some text\n") << dict << QStringLiteral("\n{%\t \n{%\t \n{%\t \n some text\n")
1844                                                 << QStringLiteral("\n\n\t {%\t \n\n\t {%\t \n\n\t {%\t \n some text\n");
1845 
1846     // Consecutive trimmed lines with tags strips one newline each. Intermediate
1847     // whitespace is stripped
1848     QTest::newRow("insignificant-whitespace23") << QStringLiteral(
1849         "\n\t {% templatetag openblock %}\t \n\t {% templatetag openblock "
1850         "%}\t \n\t {% templatetag openblock %}\t \n some text\n")
1851                                                 << dict << QStringLiteral("{%\t {%\t {%\t \n some text\n")
1852                                                 << QStringLiteral("\n\t {%\t \n\t {%\t \n\t {%\t \n some text\n");
1853 
1854     // Intermediate whitespace on one line is preserved
1855     // Consecutive tags on one line do not have intermediate whitespace or
1856     // leading
1857     // whitespace stripped
1858     QTest::newRow("insignificant-whitespace24") << QStringLiteral(
1859         "\n\t {% templatetag openblock %}\t \t {% templatetag openblock "
1860         "%}\t \t {% templatetag openblock %}\t \n some text\n")
1861                                                 << dict << QStringLiteral("\n\t {%\t \t {%\t \t {%\t \n some text\n")
1862                                                 << QStringLiteral("\n\t {%\t \t {%\t \t {%\t \n some text\n");
1863 
1864     // Still, only one leading newline is removed.
1865     QTest::newRow("insignificant-whitespace25") << QStringLiteral(
1866         "\n\n {% templatetag openblock %}\n \t {% templatetag openblock "
1867         "%}\n \t {% templatetag openblock %}\n some text\n")
1868                                                 << dict << QStringLiteral("\n{%{%{%\n some text\n") << QStringLiteral("\n\n {%\n \t {%\n \t {%\n some text\n");
1869 
1870     // Lines with {# comments #} have the same stripping behavior
1871     QTest::newRow("insignificant-whitespace26") << QStringLiteral(
1872         "\n\n {% templatetag openblock %}\n \t {# some comment "
1873         "#}\n some text\n") << dict << QStringLiteral("\n{%\n some text\n")
1874                                                 << QStringLiteral("\n\n {%\n \t \n some text\n");
1875 
1876     // Only {# comments #}
1877     QTest::newRow("insignificant-whitespace27") << QStringLiteral("\n\n {# a comment #}\n \t {# some comment #}\n some text\n") << dict
1878                                                 << QStringLiteral("\n\n some text\n") << QStringLiteral("\n\n \n \t \n some text\n");
1879 
1880     // Consecutive newlines with tags and comments
1881     QTest::newRow("insignificant-whitespace28") << QStringLiteral(
1882         "\n\t {% templatetag openblock %}\t \n\t {# some comment #}\t "
1883         "\n\t {% templatetag openblock %}\t \n some text\n")
1884                                                 << dict << QStringLiteral("{%\t \t {%\t \n some text\n")
1885                                                 << QStringLiteral("\n\t {%\t \n\t \t \n\t {%\t \n some text\n");
1886 
1887     dict.insert(QStringLiteral("spam"), QStringLiteral("ham"));
1888     // Lines with only {{ values }} have the same stripping behavior
1889     QTest::newRow("insignificant-whitespace29") << QStringLiteral(
1890         "\n {% templatetag openblock %}\t\n \t {{ spam }}\t \n "
1891         "\t {% templatetag openblock %}\t \n some text\n")
1892                                                 << dict << QStringLiteral("{%\tham\t {%\t \n some text\n")
1893                                                 << QStringLiteral("\n {%\t\n \t ham\t \n \t {%\t \n some text\n");
1894     QTest::newRow("insignificant-whitespace30") << QStringLiteral(
1895         "\n\n {% templatetag openblock %}\t\n\n \t {{ spam }}\t \n\n \t "
1896         "{% templatetag openblock %}\t \n some text\n")
1897                                                 << dict << QStringLiteral("\n{%\t\nham\t \n{%\t \n some text\n")
1898                                                 << QStringLiteral("\n\n {%\t\n\n \t ham\t \n\n \t {%\t \n some text\n");
1899 
1900     // Leading whitespace not stripped when followed by anything. See
1901     // templatetag-whitespace24
1902     QTest::newRow("insignificant-whitespace31") << QStringLiteral(
1903         "\n {% templatetag openblock %}\t \t {{ spam }}\t \t "
1904         "{% templatetag openblock %}\t \n some text\n")
1905                                                 << dict << QStringLiteral("\n {%\t \t ham\t \t {%\t \n some text\n")
1906                                                 << QStringLiteral("\n {%\t \t ham\t \t {%\t \n some text\n");
1907 
1908     // {{ value }} {% tag %} {{ value }} this time
1909     QTest::newRow("insignificant-whitespace32") << QStringLiteral(
1910         "\n {{ spam }}\t\n \t {% templatetag openblock %}\t \n "
1911         "\t {{ spam }}\t \n some text\n") << dict
1912                                                 << QStringLiteral("ham\t{%\t ham\t \n some text\n")
1913                                                 << QStringLiteral("\n ham\t\n \t {%\t \n \t ham\t \n some text\n");
1914 
1915     // Invalid stuff is still invalid
1916     // Newlines inside begin-end tokens, even in {# comments #}, make it not a
1917     // tag.
1918     QTest::newRow("insignificant-whitespace33") << QStringLiteral("\n\n {# \n{% templatetag openblock #}\t \n some text\n") << dict
1919                                                 << QStringLiteral("\n\n {# \n{% templatetag openblock #}\t \n some text\n")
1920                                                 << QStringLiteral("\n\n {# \n{% templatetag openblock #}\t \n some text\n");
1921 
1922     // Complete comment matching tags on one line are processed
1923     QTest::newRow("insignificant-whitespace34") << QStringLiteral("\n\n {# \n{# templatetag openblock #}\t \n some text\n") << dict
1924                                                 << QStringLiteral("\n\n {# \t \n some text\n") << QStringLiteral("\n\n {# \n\t \n some text\n");
1925     QTest::newRow("insignificant-whitespace35") << QStringLiteral("\n\n {# \n{# templatetag openblock\n #}\t \n some text\n") << dict
1926                                                 << QStringLiteral("\n\n {# \n{# templatetag openblock\n #}\t \n some text\n")
1927                                                 << QStringLiteral("\n\n {# \n{# templatetag openblock\n #}\t \n some text\n");
1928     QTest::newRow("insignificant-whitespace36") << QStringLiteral("\n\n {# \n{{ some comment #}\t \n some text\n") << dict
1929                                                 << QStringLiteral("\n\n {# \n{{ some comment #}\t \n some text\n")
1930                                                 << QStringLiteral("\n\n {# \n{{ some comment #}\t \n some text\n");
1931     QTest::newRow("insignificant-whitespace37") << QStringLiteral("\n\n {# \n \t {% templatetag openblock #}\t \n some text\n") << dict
1932                                                 << QStringLiteral("\n\n {# \n \t {% templatetag openblock #}\t \n some text\n")
1933                                                 << QStringLiteral("\n\n {# \n \t {% templatetag openblock #}\t \n some text\n");
1934     QTest::newRow("insignificant-whitespace38") << QStringLiteral("\n\n {# templatetag openblock #\n}\t \n some text\n") << dict
1935                                                 << QStringLiteral("\n\n {# templatetag openblock #\n}\t \n some text\n")
1936                                                 << QStringLiteral("\n\n {# templatetag openblock #\n}\t \n some text\n");
1937     QTest::newRow("insignificant-whitespace39") << QStringLiteral("\n\n {% templatetag openblock %\n}\t \n some text\n") << dict
1938                                                 << QStringLiteral("\n\n {% templatetag openblock %\n}\t \n some text\n")
1939                                                 << QStringLiteral("\n\n {% templatetag openblock %\n}\t \n some text\n");
1940     QTest::newRow("insignificant-whitespace40") << QStringLiteral("\n\n {{ templatetag openblock }\n}\t \n some text\n") << dict
1941                                                 << QStringLiteral("\n\n {{ templatetag openblock }\n}\t \n some text\n")
1942                                                 << QStringLiteral("\n\n {{ templatetag openblock }\n}\t \n some text\n");
1943     QTest::newRow("insignificant-whitespace41") << QStringLiteral("\n\n {\n# {# templatetag openblock #}\t \n some text\n") << dict
1944                                                 << QStringLiteral("\n\n {\n# \t \n some text\n") << QStringLiteral("\n\n {\n# \t \n some text\n");
1945     QTest::newRow("insignificant-whitespace42") << QStringLiteral("\n\n {\n {# templatetag openblock #}\t \n some text\n") << dict
1946                                                 << QStringLiteral("\n\n {\t \n some text\n") << QStringLiteral("\n\n {\n \t \n some text\n");
1947     QTest::newRow("insignificant-whitespace43") << QStringLiteral("\n{{# foo #};{# bar #}\n") << dict << QStringLiteral("\n{;\n") << QStringLiteral("\n{;\n");
1948 
1949     QTest::newRow("insignificant-whitespace44") << QStringLiteral("\n{{ foo }} ") << dict << QString() << QStringLiteral("\n ");
1950 }
1951 
1952 QTEST_MAIN(TestBuiltinSyntax)
1953 #include "testbuiltins.moc"
1954 
1955 #endif