/*
  This file is part of the KTextTemplate library

  SPDX-FileCopyrightText: 2009, 2010 Stephen Kelly <steveire@gmail.com>

  SPDX-License-Identifier: LGPL-2.1-or-later

*/

#ifndef FILTERSTEST_H
#define FILTERSTEST_H

#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QTest>

#include "context.h"
#include "engine.h"
#include "ktexttemplate_paths.h"
#include "template.h"
#include <util.h>

using Dict = QHash<QString, QVariant>;

Q_DECLARE_METATYPE(KTextTemplate::Error)

using namespace KTextTemplate;

class TestFilters : public QObject
{
    Q_OBJECT

private Q_SLOTS:
    void initTestCase();
    void cleanupTestCase();

    void testDateBasedFilters_data();
    void testDateBasedFilters()
    {
        doTest();
    }

    void testStringFilters_data();
    void testStringFilters()
    {
        doTest();
    }

    void testListFilters_data();
    void testListFilters()
    {
        doTest();
    }

    void testLogicFilters_data();
    void testLogicFilters()
    {
        doTest();
    }

    void testMiscFilters_data();
    void testMiscFilters()
    {
        doTest();
    }

    void testIntegerFilters_data();
    void testIntegerFilters()
    {
        doTest();
    }

private:
    void doTest();

    QSharedPointer<InMemoryTemplateLoader> loader;
    Engine *m_engine = nullptr;
};

void TestFilters::initTestCase()
{
    m_engine = new Engine(this);

    loader = QSharedPointer<InMemoryTemplateLoader>(new InMemoryTemplateLoader());
    m_engine->addTemplateLoader(loader);

    auto appDirPath = QFileInfo(QCoreApplication::applicationDirPath()).absoluteDir().path();
    m_engine->setPluginPaths({
        QStringLiteral(KTEXTTEMPLATE_PLUGIN_PATH),
        appDirPath + QStringLiteral("/tests/") // For testtags.qs
    });
}

void TestFilters::cleanupTestCase()
{
    delete m_engine;
}

void TestFilters::doTest()
{
    QFETCH(QString, input);
    QFETCH(Dict, dict);
    QFETCH(QString, output);
    QFETCH(KTextTemplate::Error, error);

    auto t = m_engine->newTemplate(input, QLatin1String(QTest::currentDataTag()));

    Context context(dict);

    auto result = t->render(&context);

    if (t->error() != NoError) {
        if (t->error() != error)
            qDebug() << t->errorString();
        QCOMPARE(t->error(), error);
        return;
    }

    // Didn't catch any errors, so make sure I didn't expect any.
    QCOMPARE(NoError, error);

    QCOMPARE(t->error(), NoError);

    QCOMPARE(result, output);
}

void TestFilters::testDateBasedFilters_data()
{
    QTest::addColumn<QString>("input");
    QTest::addColumn<Dict>("dict");
    QTest::addColumn<QString>("output");
    QTest::addColumn<KTextTemplate::Error>("error");

    Dict dict;
    auto now = QDateTime::currentDateTimeUtc();

    dict.insert(QStringLiteral("a"), now.addSecs(-70));

    QTest::newRow("filter-timesince01") << QStringLiteral("{{ a|timesince }}") << dict << QStringLiteral("1 minute") << NoError;

    dict.clear();

    dict.insert(QStringLiteral("a"), now.addDays(-1).addSecs(-60));

    QTest::newRow("filter-timesince02") << QStringLiteral("{{ a|timesince }}") << dict << QStringLiteral("1 day") << NoError;

    dict.clear();

    dict.insert(QStringLiteral("a"), now.addSecs(-1 * 60 * 60).addSecs(-1 * 25 * 60).addSecs(-1 * 10));
    QTest::newRow("filter-timesince03") << QStringLiteral("{{ a|timesince }}") << dict << QStringLiteral("1 hour, 25 minutes") << NoError;

    dict.clear();

    //  Compare to a given parameter

    dict.insert(QStringLiteral("a"), now.addDays(-2));
    dict.insert(QStringLiteral("b"), now.addDays(-1));

    QTest::newRow("filter-timesince04") << QStringLiteral("{{ a|timesince:b }}") << dict << QStringLiteral("1 day") << NoError;

    dict.clear();

    dict.insert(QStringLiteral("a"), now.addDays(-2).addSecs(-60));
    dict.insert(QStringLiteral("b"), now.addDays(-2));

    QTest::newRow("filter-timesince05") << QStringLiteral("{{ a|timesince:b }}") << dict << QStringLiteral("1 minute") << NoError;

    dict.clear();

    //  Check that timezone is respected

    //  {"a":now_tz - timedelta(hours=8), "b":now_tz
    //   QTest::newRow( "filter-timesince06" ) << QString::fromLatin1( "{{
    //   a|timesince:b }}" ) << dict << QString::fromLatin1( "8 hours" ) <<
    //   NoError;

    dict.insert(QStringLiteral("earlier"), now.addDays(-7));
    QTest::newRow("filter-timesince07") << QStringLiteral("{{ earlier|timesince }}") << dict << QStringLiteral("1 week") << NoError;

    dict.clear();

    dict.insert(QStringLiteral("now"), now);
    dict.insert(QStringLiteral("earlier"), now.addDays(-7));

    QTest::newRow("filter-timesince08") << QStringLiteral("{{ earlier|timesince:now }}") << dict << QStringLiteral("1 week") << NoError;

    dict.clear();

    dict.insert(QStringLiteral("later"), now.addDays(7));

    QTest::newRow("filter-timesince09") << QStringLiteral("{{ later|timesince }}") << dict << QStringLiteral("0 minutes") << NoError;

    dict.clear();

    dict.insert(QStringLiteral("now"), now);
    dict.insert(QStringLiteral("later"), now.addDays(7));

    QTest::newRow("filter-timesince10") << QStringLiteral("{{ later|timesince:now }}") << dict << QStringLiteral("0 minutes") << NoError;

    //  Ensures that differing timezones are calculated correctly

    //  {"a": now
    //   QTest::newRow( "filter-timesince11" ) << QString::fromLatin1( "{{
    //   a|timesince }}" ) << dict << QString::fromLatin1( "0 minutes" ) <<
    //   NoError;

    //  {"a": now_tz
    //   QTest::newRow( "filter-timesince12" ) << QString::fromLatin1( "{{
    //   a|timesince }}" ) << dict << QString::fromLatin1( "0 minutes" ) <<
    //   NoError;

    //  {"a": now_tz_i
    //   QTest::newRow( "filter-timesince13" ) << QString::fromLatin1( "{{
    //   a|timesince }}" ) << dict << QString::fromLatin1( "0 minutes" ) <<
    //   NoError;

    //  {"a": now_tz, "b": now_tz_i
    //   QTest::newRow( "filter-timesince14" ) << QString::fromLatin1( "{{
    //   a|timesince:b }}" ) << dict << QString::fromLatin1( "0 minutes" ) <<
    //   NoError;

    //  {"a": now, "b": now_tz_i
    //   QTest::newRow( "filter-timesince15" ) << QString::fromLatin1( "{{
    //   a|timesince:b }}" ) << dict << QString() << NoError;

    //  {"a": now_tz_i, "b": now
    //   QTest::newRow( "filter-timesince16" ) << QString::fromLatin1( "{{
    //   a|timesince:b }}" ) << dict << QString() << NoError;

    dict.clear();

    dict.insert(QStringLiteral("a"), now);
    dict.insert(QStringLiteral("b"), now);

    QTest::newRow("filter-timesince17") << QStringLiteral("{{ a|timesince:b }}") << dict << QStringLiteral("0 minutes") << NoError;

    dict.clear();

    dict.insert(QStringLiteral("a"), now);
    dict.insert(QStringLiteral("b"), now.addDays(1));

    QTest::newRow("filter-timesince18") << QStringLiteral("{{ a|timesince:b }}") << dict << QStringLiteral("1 day") << NoError;

    dict.clear();
    QTest::newRow("filter-timesince19") << QStringLiteral("{{xx|timesince}}") << dict << QStringLiteral("") << NoError;
    QTest::newRow("filter-timesince20") << QStringLiteral("{{|timesince}}") << dict << QStringLiteral("") << NoError;

    //  Default compare with datetime.now()

    dict.clear();
    dict.insert(QStringLiteral("a"), now.addSecs(130));

    QTest::newRow("filter-timeuntil01") << QStringLiteral("{{ a|timeuntil }}") << dict << QStringLiteral("2 minutes") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), now.addDays(1).addSecs(10));

    QTest::newRow("filter-timeuntil02") << QStringLiteral("{{ a|timeuntil }}") << dict << QStringLiteral("1 day") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), now.addSecs(60 * 60 * 8).addSecs(610));

    QTest::newRow("filter-timeuntil03") << QStringLiteral("{{ a|timeuntil }}") << dict << QStringLiteral("8 hours, 10 minutes") << NoError;

    //  Compare to a given parameter

    dict.clear();
    dict.insert(QStringLiteral("a"), now.addDays(-1));
    dict.insert(QStringLiteral("b"), now.addDays(-2));

    QTest::newRow("filter-timeuntil04") << QStringLiteral("{{ a|timeuntil:b }}") << dict << QStringLiteral("1 day") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), now.addDays(-1));
    dict.insert(QStringLiteral("b"), now.addDays(-1).addSecs(-60));

    QTest::newRow("filter-timeuntil05") << QStringLiteral("{{ a|timeuntil:b }}") << dict << QStringLiteral("1 minute") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("earlier"), now.addDays(-7));

    QTest::newRow("filter-timeuntil06") << QStringLiteral("{{ earlier|timeuntil }}") << dict << QStringLiteral("0 minutes") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("now"), now);
    dict.insert(QStringLiteral("earlier"), now.addDays(-7));

    QTest::newRow("filter-timeuntil07") << QStringLiteral("{{ earlier|timeuntil:now }}") << dict << QStringLiteral("0 minutes") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("later"), now.addDays(7).addSecs(5));

    QTest::newRow("filter-timeuntil08") << QStringLiteral("{{ later|timeuntil }}") << dict << QStringLiteral("1 week") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("now"), now);
    dict.insert(QStringLiteral("later"), now.addDays(7));

    QTest::newRow("filter-timeuntil09") << QStringLiteral("{{ later|timeuntil:now }}") << dict << QStringLiteral("1 week") << NoError;

    //  Ensures that differing timezones are calculated correctly
    //
    //   //  {"a": now_tz_i
    //   QTest::newRow( "filter-timeuntil10" ) << QString::fromLatin1( "{{
    //   a|timeuntil }}" ) << dict << QString::fromLatin1( "0 minutes" ) <<
    //   NoError;
    //
    //   //  {"a": now_tz_i, "b": now_tz
    //   QTest::newRow( "filter-timeuntil11" ) << QString::fromLatin1( "{{
    //   a|timeuntil:b }}" ) << dict << QString::fromLatin1( "0 minutes" ) <<
    //   NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), now);
    dict.insert(QStringLiteral("b"), now);
    QTest::newRow("filter-timeuntil12") << QStringLiteral("{{ a|timeuntil:b }}") << dict << QStringLiteral("0 minutes") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), now);
    dict.insert(QStringLiteral("b"), now.addDays(-1));

    QTest::newRow("filter-timeuntil13") << QStringLiteral("{{ a|timeuntil:b }}") << dict << QStringLiteral("1 day") << NoError;

    dict.clear();
    QTest::newRow("filter-timeuntil14") << QStringLiteral("{{xx|timeuntil}}") << dict << QStringLiteral("") << NoError;
    QTest::newRow("filter-timeuntil15") << QStringLiteral("{{|timeuntil}}") << dict << QStringLiteral("") << NoError;

    QDateTime d(QDate(2008, 1, 1), {});

    dict.clear();
    dict.insert(QStringLiteral("d"), d);

    QTest::newRow("date01") << "{{ d|date:\"MM\" }}" << dict << QStringLiteral("01") << NoError;
    QTest::newRow("date02") << QStringLiteral("{{ d|date }}") << dict << d.toString(QStringLiteral("MMM. d, yyyy")) << NoError;

    dict.clear();
    dict.insert(QStringLiteral("d"), QStringLiteral("fail_string"));
    QTest::newRow("date03") << "{{ d|date:\"MM\" }}" << dict << QString() << NoError;
}

void TestFilters::testStringFilters_data()
{
    QTest::addColumn<QString>("input");
    QTest::addColumn<Dict>("dict");
    QTest::addColumn<QString>("output");
    QTest::addColumn<KTextTemplate::Error>("error");

    Dict dict;

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("<a>\'"));
    dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(QStringLiteral("<a>\'"))));

    QTest::newRow("filter-addslash01") << QStringLiteral(
        "{% autoescape off %}{{ a|addslashes }} {{ "
        "b|addslashes }}{% endautoescape %}")
                                       << dict << R"(<a>\' <a>\')" << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("<a>\'"));
    dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(QStringLiteral("<a>\'"))));

    QTest::newRow("filter-addslash02") << QStringLiteral("{{ a|addslashes }} {{ b|addslashes }}") << dict << R"(&lt;a&gt;\&#39; <a>\')" << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("fred>"));
    dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(QStringLiteral("fred&gt;"))));

    QTest::newRow("filter-capfirst01") << QStringLiteral(
        "{% autoescape off %}{{ a|capfirst }} {{ b|capfirst "
        "}}{% endautoescape %}") << dict
                                       << QStringLiteral("Fred> Fred&gt;") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("fred>"));
    dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(QStringLiteral("fred&gt;"))));

    QTest::newRow("filter-capfirst02") << QStringLiteral("{{ a|capfirst }} {{ b|capfirst }}") << dict << QStringLiteral("Fred&gt; Fred&gt;") << NoError;

    //  Note that applying fix_ampsersands in autoescape mode leads to
    //  double escaping.

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("a&b"));
    dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(QStringLiteral("a&b"))));

    QTest::newRow("filter-fix_ampersands01") << QStringLiteral(
        "{% autoescape off %}{{ a|fix_ampersands }} {{ "
        "b|fix_ampersands }}{% endautoescape %}")
                                             << dict << QStringLiteral("a&amp;b a&amp;b") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("a&b"));
    dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(QStringLiteral("a&b"))));

    QTest::newRow("filter-fix_ampersands02") << QStringLiteral("{{ a|fix_ampersands }} {{ b|fix_ampersands }}") << dict << QStringLiteral("a&amp;amp;b a&amp;b")
                                             << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("1.42"));
    dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(QStringLiteral("1.42"))));

    QTest::newRow("filter-floatformat01") << QStringLiteral(
        "{% autoescape off %}{{ a|floatformat }} {{ "
        "b|floatformat }}{% endautoescape %}")
                                          << dict << QStringLiteral("1.4 1.4") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("1.42"));
    dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(QStringLiteral("1.42"))));

    QTest::newRow("filter-floatformat02") << QStringLiteral("{{ a|floatformat }} {{ b|floatformat }}") << dict << QStringLiteral("1.4 1.4") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), double(1234.54321));
    dict.insert(QStringLiteral("b"), int(1234));

    QTest::newRow("filter-floatformat03") << QStringLiteral("{{ a|floatformat }} {{ b|floatformat }}") << dict << QStringLiteral("1234.5 1234.0") << NoError;
    QTest::newRow("filter-floatformat04") << QStringLiteral("{{ a|floatformat:2 }} {{ b|floatformat:2 }}") << dict << QStringLiteral("1234.54 1234.00")
                                          << NoError;
    QTest::newRow("filter-floatformat04") << QStringLiteral("{{ a|floatformat:0 }} {{ b|floatformat:0 }}") << dict << QStringLiteral("1235 1234") << NoError;

    //  The contents of "linenumbers" is escaped according to the current
    //  autoescape setting.

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("one\n<two>\nthree"));
    dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(QStringLiteral("one\n&lt;two&gt;\nthree"))));

    QTest::newRow("filter-linenumbers01") << QStringLiteral("{{ a|linenumbers }} {{ b|linenumbers }}") << dict
                                          << "1. one\n2. &lt;two&gt;\n3. three 1. one\n2. &lt;two&gt;\n3. three" << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("one\n<two>\nthree"));
    dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(QStringLiteral("one\n&lt;two&gt;\nthree"))));
    QTest::newRow("filter-linenumbers02") << QStringLiteral(
        "{% autoescape off %}{{ a|linenumbers }} {{ "
        "b|linenumbers }}{% endautoescape %}")
                                          << dict << "1. one\n2. <two>\n3. three 1. one\n2. &lt;two&gt;\n3. three" << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("Apple & banana"));
    dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(QStringLiteral("Apple &amp; banana"))));

    QTest::newRow("filter-lower01") << QStringLiteral("{% autoescape off %}{{ a|lower }} {{ b|lower }}{% endautoescape %}") << dict
                                    << QStringLiteral("apple & banana apple &amp; banana") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("Apple & banana"));
    dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(QStringLiteral("Apple &amp; banana"))));

    QTest::newRow("filter-lower02") << QStringLiteral("{{ a|lower }} {{ b|lower }}") << dict << QStringLiteral("apple &amp; banana apple &amp; banana")
                                    << NoError;

    //  The make_list filter can destroy existing escaping, so the results are
    //  escaped.

    dict.clear();
    dict.insert(QStringLiteral("a"), markSafe(QStringLiteral("&")));

    QTest::newRow("filter-make_list01") << QStringLiteral("{% autoescape off %}{{ a|make_list }}{% endautoescape %}") << dict << "[u\'&\']" << NoError;
    QTest::newRow("filter-make_list02") << QStringLiteral("{{ a|make_list }}") << dict << QStringLiteral("[u&#39;&amp;&#39;]") << NoError;

    QTest::newRow("filter-make_list03") << QStringLiteral(
        "{% autoescape off %}{{ a|make_list|stringformat:\"%1\"|safe }}{% "
        "endautoescape %}") << dict << QStringLiteral("[u\'&\']")
                                        << NoError;
    QTest::newRow("filter-make_list04") << QStringLiteral("{{ a|make_list|stringformat:\"%1\"|safe }}") << dict << QStringLiteral("[u\'&\']") << NoError;

    //  Running slugify on a pre-escaped string leads to odd behaviour,
    //  but the result is still safe.

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("a & b"));
    dict.insert(QStringLiteral("b"), markSafe(QStringLiteral("a &amp; b")));

    QTest::newRow("filter-slugify01") << QStringLiteral("{% autoescape off %}{{ a|slugify }} {{ b|slugify }}{% endautoescape %}") << dict
                                      << QStringLiteral("a-b a-amp-b") << NoError;
    QTest::newRow("filter-slugify02") << QStringLiteral("{{ a|slugify }} {{ b|slugify }}") << dict << QStringLiteral("a-b a-amp-b") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("Schöne Grüße"));

    QTest::newRow("filter-slugify03") << QStringLiteral("{{ a|slugify }}") << dict << QStringLiteral("schone-grue") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("testing\r\njavascript \'string\" <b>escaping</b>"));
    QTest::newRow("escapejs01") << QStringLiteral("{{ a|escapejs }}") << dict
                                << "testing\\u000D\\u000Ajavascript \\u0027string\\u0022 "
                                   "\\u003Cb\\u003Eescaping\\u003C/b\\u003E"
                                << NoError;
    QTest::newRow("escapejs02") << QStringLiteral("{% autoescape off %}{{ a|escapejs }}{% endautoescape %}") << dict
                                << "testing\\u000D\\u000Ajavascript \\u0027string\\u0022 "
                                   "\\u003Cb\\u003Eescaping\\u003C/b\\u003E"
                                << NoError;

    //  Notice that escaping is applied *after* any filters, so the string
    //  formatting here only needs to deal with pre-escaped characters.

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("a<b"));
    dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(QStringLiteral("a<b"))));

    QTest::newRow("filter-stringformat01") << "{% autoescape off %}.{{ a|stringformat:\"%1\" }}. .{{ "
                                              "b|stringformat:\"%2\" }}.{% endautoescape %}"
                                           << dict << QStringLiteral(".a<b. .a<b.") << NoError;
    QTest::newRow("filter-stringformat02") << R"(.{{ a|stringformat:"%1" }}. .{{ b|stringformat:"%2" }}.)" << dict << QStringLiteral(".a&lt;b. .a<b.")
                                           << NoError;
    QTest::newRow("filter-stringformat03") << ".{{ a|stringformat:\"foo %1 bar\" }}. .{{ b|stringformat:\"baz %2 "
                                              "bat\" }}."
                                           << dict << QStringLiteral(".foo a&lt;b bar. .baz a<b bat.") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("path"), QStringLiteral("www.kde.org"));
    QTest::newRow("filter-stringformat04") << "{% with path|stringformat:\"<a href=\\\"%1\\\">%1</a>\"|safe as "
                                              "result %}{{ result }}{% endwith %}"
                                           << dict << "<a href=\"www.kde.org\">www.kde.org</a>" << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("JOE\'S CRAB SHACK"));
    QTest::newRow("filter-title01") << "{{ a|title }}" << dict << QStringLiteral("Joe&#39;s Crab Shack") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("555 WEST 53RD STREET"));
    QTest::newRow("filter-title02") << "{{ a|title }}" << dict << QStringLiteral("555 West 53rd Street") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("alpha & bravo"));
    dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(QStringLiteral("alpha &amp; bravo"))));

    QTest::newRow("filter-truncatewords01") << "{% autoescape off %}{{ a|truncatewords:\"2\" }} {{ "
                                               "b|truncatewords:\"2\"}}{% endautoescape %}"
                                            << dict << QStringLiteral("alpha & ... alpha &amp; ...") << NoError;

    QTest::newRow("filter-truncatewords02") << R"({{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}})" << dict
                                            << QStringLiteral("alpha &amp; ... alpha &amp; ...") << NoError;

    //  The "upper" filter messes up entities (which are case-sensitive),
    //  so it's not safe for non-escaping purposes.

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("a & b"));
    dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(QStringLiteral("a &amp; b"))));

    QTest::newRow("filter-upper01") << QStringLiteral("{% autoescape off %}{{ a|upper }} {{ b|upper }}{% endautoescape %}") << dict
                                    << QStringLiteral("A & B A &AMP; B") << NoError;
    QTest::newRow("filter-upper02") << QStringLiteral("{{ a|upper }} {{ b|upper }}") << dict << QStringLiteral("A &amp; B A &amp;AMP; B") << NoError;

    //   //  {"a": "http://example.com/?x=&y=", "b":
    //   mark_safe("http://example.com?x=&amp;y=")
    //   QTest::newRow( "filter-urlize01") << QString::fromLatin1( "{%
    //   autoescape
    //   off %}{{ a|urlize }} {{ b|urlize }}{% endautoescape %}" ) << dict <<
    //   "<a
    //   href=\"http://example.com/?x=&y=\"
    //   rel=\"nofollow\">http://example.com/?x=&y=</a> <a
    //   href=\"http://example.com?x=&amp;y=\"
    //   rel=\"nofollow\">http://example.com?x=&amp;y=</a>" << NoError;
    //
    //   //  {"a": "http://example.com/?x=&y=", "b":
    //   mark_safe("http://example.com?x=&amp;y=")
    //   QTest::newRow( "filter-urlize02") << QString::fromLatin1( "{{ a|urlize
    //   }}
    //   {{ b|urlize }}" ) << dict << "<a href=\"http://example.com/?x=&amp;y=\"
    //   rel=\"nofollow\">http://example.com/?x=&amp;y=</a> <a
    //   href=\"http://example.com?x=&amp;y=\"
    //   rel=\"nofollow\">http://example.com?x=&amp;y=</a>" << NoError;
    //
    //   //  {"a": mark_safe("a &amp; b")
    //   QTest::newRow( "filter-urlize03") << QString::fromLatin1( "{%
    //   autoescape
    //   off %}{{ a|urlize }}{% endautoescape %}" ) << dict <<
    //   QString::fromLatin1( "a &amp; b" ) << NoError;
    //
    //   //  {"a": mark_safe("a &amp; b")
    //   QTest::newRow( "filter-urlize04") << QString::fromLatin1( "{{ a|urlize
    //   }}" ) << dict << QString::fromLatin1( "a &amp; b" ) << NoError;
    //
    //   //  This will lead to a nonsense result, but at least it won't be
    //
    //   //  exploitable for XSS purposes when auto-escaping is on.
    //
    //   //  {"a": "<script>alert(\"foo\")</script>"
    //   QTest::newRow( "filter-urlize05") << QString::fromLatin1( "{%
    //   autoescape
    //   off %}{{ a|urlize }}{% endautoescape %}" ) << dict <<
    //   "<script>alert(\"foo\")</script>" << NoError;
    //
    //   //  {"a": "<script>alert(\"foo\")</script>"
    //   QTest::newRow( "filter-urlize06") << QString::fromLatin1( "{{ a|urlize
    //   }}" ) << dict << QString::fromLatin1(
    //   "&lt;script&gt;alert(&#39;foo&#39;)&lt;/script&gt;" ) << NoError;
    //
    //   //  mailto: testing for urlize
    //
    //   //  {"a": "Email me at me@example.com"
    //   QTest::newRow( "filter-urlize07") << QString::fromLatin1( "{{ a|urlize
    //   }}" ) << dict << "Email me at <a
    //   href=\"mailto:me@example.com\">me@example.com</a>" << NoError;
    //
    //   //  {"a": "Email me at <me@example.com>"
    //   QTest::newRow( "filter-urlize08") << QString::fromLatin1( "{{ a|urlize
    //   }}" ) << dict << "Email me at &lt;<a
    //   href=\"mailto:me@example.com\">me@example.com</a>&gt;" << NoError;
    //
    //   //  {"a": "\"Unsafe\" http://example.com/x=&y=", "b":
    //   mark_safe("&quot;Safe&quot; http://example.com?x=&amp;y=")
    //   QTest::newRow( "filter-urlizetrunc01") << "{% autoescape off %}{{
    //   a|urlizetrunc:\"8\" }} {{ b|urlizetrunc:\"8\" }}{% endautoescape %}" <<
    //   dict << "\"Unsafe\" <a href=\"http://example.com/x=&y=\"
    //   rel=\"nofollow\">http:...</a> &quot;Safe&quot; <a
    //   href=\"http://example.com?x=&amp;y=\" rel=\"nofollow\">http:...</a>" <<
    //   NoError;
    //
    //   //  {"a": "\"Unsafe\" http://example.com/x=&y=", "b":
    //   mark_safe("&quot;Safe&quot; http://example.com?x=&amp;y=")
    //   QTest::newRow( "filter-urlizetrunc02") << "{{ a|urlizetrunc:\"8\" }} {{
    //   b|urlizetrunc:\"8\" }}" << dict << "&quot;Unsafe&quot; <a
    //   href=\"http://example.com/x=&amp;y=\" rel=\"nofollow\">http:...</a>
    //   &quot;Safe&quot; <a href=\"http://example.com?x=&amp;y=\"
    //   rel=\"nofollow\">http:...</a>" << NoError;

    //   //  Ensure iriencode keeps safe strings:
    //
    //   //  {"url": "?test=1&me=2"
    //   QTest::newRow( "filter-iriencode01") << QString::fromLatin1( "{{
    //   url|iriencode }}" ) << dict << QString::fromLatin1( "?test=1&amp;me=2"
    //   )
    //   << NoError;
    //
    //   //  {"url": "?test=1&me=2"
    //   QTest::newRow( "filter-iriencode02") << QString::fromLatin1( "{%
    //   autoescape off %}{{ url|iriencode }}{% endautoescape %}" ) << dict <<
    //   QString::fromLatin1( "?test=1&me=2" ) << NoError;
    //
    //   //  {"url": mark_safe("?test=1&me=2")
    //   QTest::newRow( "filter-iriencode03") << QString::fromLatin1( "{{
    //   url|iriencode }}" ) << dict << QString::fromLatin1( "?test=1&me=2" ) <<
    //   NoError;
    //
    //   //  {"url": mark_safe("?test=1&me=2")
    //   QTest::newRow( "filter-iriencode04") << QString::fromLatin1( "{%
    //   autoescape off %}{{ url|iriencode }}{% endautoescape %}" ) << dict <<
    //   QString::fromLatin1( "?test=1&me=2" ) << NoError;
    //
    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("a & b"));
    dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(QStringLiteral("a &amp; b"))));

    QTest::newRow("filter-wordcount01") << QStringLiteral(
        "{% autoescape off %}{{ a|wordcount }} {{ b|wordcount "
        "}}{% endautoescape %}") << dict << QStringLiteral("3 3")
                                        << NoError;

    QTest::newRow("filter-wordcount02") << QStringLiteral("{{ a|wordcount }} {{ b|wordcount }}") << dict << QStringLiteral("3 3") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("a & b"));
    dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(QStringLiteral("a & b"))));

    QTest::newRow("filter-wordwrap01") << QStringLiteral(
        "{% autoescape off %}{{ a|wordwrap:3 }} {{ "
        "b|wordwrap:3 }}{% endautoescape %}")
                                       << dict << "a &\nb a &\nb" << NoError;

    QTest::newRow("filter-wordwrap02") << QStringLiteral("{{ a|wordwrap:3 }} {{ b|wordwrap:3 }}") << dict << "a &amp;\nb a &\nb" << NoError;

    dict.clear();
    QTest::newRow("filter-wordwrap03") << QStringLiteral("{{xx|wordwrap}}") << dict << "" << NoError;

    QTest::newRow("filter-wordwrap04") << QStringLiteral("{{|wordwrap}}") << dict << "" << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("a&b"));
    dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(QStringLiteral("a&b"))));

    QTest::newRow("filter-ljust01") << "{% autoescape off %}.{{ a|ljust:\"5\" }}. .{{ b|ljust:\"5\" }}.{% "
                                       "endautoescape %}"
                                    << dict << QStringLiteral(".a&b  . .a&b  .") << NoError;

    QTest::newRow("filter-ljust02") << R"(.{{ a|ljust:"5" }}. .{{ b|ljust:"5" }}.)" << dict << QStringLiteral(".a&amp;b  . .a&b  .") << NoError;

    QTest::newRow("filter-rjust01") << "{% autoescape off %}.{{ a|rjust:\"5\" }}. .{{ b|rjust:\"5\" }}.{% "
                                       "endautoescape %}"
                                    << dict << QStringLiteral(".  a&b. .  a&b.") << NoError;

    QTest::newRow("filter-rjust02") << R"(.{{ a|rjust:"5" }}. .{{ b|rjust:"5" }}.)" << dict << QStringLiteral(".  a&amp;b. .  a&b.") << NoError;

    QTest::newRow("filter-center01") << "{% autoescape off %}.{{ a|center:\"5\" }}. .{{ b|center:\"5\" "
                                        "}}.{% "
                                        "endautoescape %}"
                                     << dict << QStringLiteral(". a&b . . a&b .") << NoError;

    QTest::newRow("filter-center02") << R"(.{{ a|center:"5" }}. .{{ b|center:"5" }}.)" << dict << QStringLiteral(". a&amp;b . . a&b .") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("x&y"));
    dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(QStringLiteral("x&amp;y"))));

    QTest::newRow("filter-cut01") << "{% autoescape off %}{{ a|cut:\"x\" }} {{ "
                                     "b|cut:\"x\" }}{% endautoescape %}"
                                  << dict << QStringLiteral("&y &amp;y") << NoError;
    QTest::newRow("filter-cut02") << R"({{ a|cut:"x" }} {{ b|cut:"x" }})" << dict << QStringLiteral("&amp;y &amp;y") << NoError;
    QTest::newRow("filter-cut03") << "{% autoescape off %}{{ a|cut:\"&\" }} {{ "
                                     "b|cut:\"&\" }}{% endautoescape %}"
                                  << dict << QStringLiteral("xy xamp;y") << NoError;
    QTest::newRow("filter-cut04") << R"({{ a|cut:"&" }} {{ b|cut:"&" }})" << dict << QStringLiteral("xy xamp;y") << NoError;

    //  Passing ";" to cut can break existing HTML entities, so those strings
    //  are auto-escaped.

    QTest::newRow("filter-cut05") << "{% autoescape off %}{{ a|cut:\";\" }} {{ "
                                     "b|cut:\";\" }}{% endautoescape %}"
                                  << dict << QStringLiteral("x&y x&ampy") << NoError;
    QTest::newRow("filter-cut06") << R"({{ a|cut:";" }} {{ b|cut:";" }})" << dict << QStringLiteral("x&amp;y x&amp;ampy") << NoError;

    //  The "escape" filter works the same whether autoescape is on or off,
    //  but it has no effect on strings already marked as safe.

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("x&y"));
    dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(QStringLiteral("x&y"))));

    QTest::newRow("filter-escape01") << QStringLiteral("{{ a|escape }} {{ b|escape }}") << dict << QStringLiteral("x&amp;y x&y") << NoError;
    QTest::newRow("filter-escape02") << QStringLiteral("{% autoescape off %}{{ a|escape }} {{ b|escape }}{% endautoescape %}") << dict
                                     << QStringLiteral("x&amp;y x&y") << NoError;

    //  It is only applied once, regardless of the number of times it
    //  appears in a chain.

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("x&y"));

    QTest::newRow("filter-escape03") << QStringLiteral("{% autoescape off %}{{ a|escape|escape }}{% endautoescape %}") << dict << QStringLiteral("x&amp;y")
                                     << NoError;
    QTest::newRow("filter-escape04") << QStringLiteral("{{ a|escape|escape }}") << dict << QStringLiteral("x&amp;y") << NoError;

    //  Force_escape is applied immediately. It can be used to provide
    //  double-escaping, for example.

    QTest::newRow("filter-force-escape01") << QStringLiteral("{% autoescape off %}{{ a|force_escape }}{% endautoescape %}") << dict << QStringLiteral("x&amp;y")
                                           << NoError;
    QTest::newRow("filter-force-escape02") << QStringLiteral("{{ a|force_escape }}") << dict << QStringLiteral("x&amp;y") << NoError;
    QTest::newRow("filter-force-escape03") << QStringLiteral(
        "{% autoescape off %}{{ a|force_escape|force_escape "
        "}}{% endautoescape %}") << dict << QStringLiteral("x&amp;amp;y")
                                           << NoError;
    QTest::newRow("filter-force-escape04") << QStringLiteral("{{ a|force_escape|force_escape }}") << dict << QStringLiteral("x&amp;amp;y") << NoError;

    //  Because the result of force_escape is "safe", an additional
    //  escape filter has no effect.

    QTest::newRow("filter-force-escape05") << QStringLiteral("{% autoescape off %}{{ a|force_escape|escape }}{% endautoescape %}") << dict
                                           << QStringLiteral("x&amp;y") << NoError;
    QTest::newRow("filter-force-escape06") << QStringLiteral("{{ a|force_escape|escape }}") << dict << QStringLiteral("x&amp;y") << NoError;
    QTest::newRow("filter-force-escape07") << QStringLiteral("{% autoescape off %}{{ a|escape|force_escape }}{% endautoescape %}") << dict
                                           << QStringLiteral("x&amp;y") << NoError;
    QTest::newRow("filter-force-escape08") << QStringLiteral("{{ a|escape|force_escape }}") << dict << QStringLiteral("x&amp;y") << NoError;

    //  The contents in "linebreaks" and "linebreaksbr" are escaped
    //  according to the current autoescape setting.

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("x&\ny"));
    dict.insert(QStringLiteral("b"), markSafe(QStringLiteral("x&\ny")));

    QTest::newRow("filter-linebreaks01") << QStringLiteral("{{ a|linebreaks }} {{ b|linebreaks }}") << dict
                                         << QStringLiteral("<p>x&amp;<br />y</p> <p>x&<br />y</p>") << NoError;
    QTest::newRow("filter-linebreaks02") << QStringLiteral(
        "{% autoescape off %}{{ a|linebreaks }} {{ "
        "b|linebreaks }}{% endautoescape %}")
                                         << dict << QStringLiteral("<p>x&<br />y</p> <p>x&<br />y</p>") << NoError;
    QTest::newRow("filter-linebreaksbr01") << QStringLiteral("{{ a|linebreaksbr }} {{ b|linebreaksbr }}") << dict << QStringLiteral("x&amp;<br />y x&<br />y")
                                           << NoError;
    QTest::newRow("filter-linebreaksbr02") << QStringLiteral(
        "{% autoescape off %}{{ a|linebreaksbr }} {{ "
        "b|linebreaksbr }}{% endautoescape %}")
                                           << dict << QStringLiteral("x&<br />y x&<br />y") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("<b>hello</b>"));

    QTest::newRow("filter-safe01") << QStringLiteral("{{ a }} -- {{ a|safe }}") << dict << QStringLiteral("&lt;b&gt;hello&lt;/b&gt; -- <b>hello</b>")
                                   << NoError;
    QTest::newRow("filter-safe02") << QStringLiteral("{% autoescape off %}{{ a }} -- {{ a|safe }}{% endautoescape %}") << dict
                                   << QStringLiteral("<b>hello</b> -- <b>hello</b>") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QVariantList{QStringLiteral("&"), QStringLiteral("<")});

    QTest::newRow("filter-safeseq01") << R"({{ a|join:", " }} -- {{ a|safeseq|join:", " }})" << dict << QStringLiteral("&amp;, &lt; -- &, <") << NoError;
    QTest::newRow("filter-safeseq02") << "{% autoescape off %}{{ a|join:\", \" "
                                         "}} -- {{ a|safeseq|join:\", \" "
                                         "}}{% endautoescape %}"
                                      << dict << QStringLiteral("&, < -- &, <") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("<a>x</a> <p><b>y</b></p>"));
    dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(QStringLiteral("<a>x</a> <p><b>y</b></p>"))));

    QTest::newRow("filter-removetags01") << R"({{ a|removetags:"a b" }} {{ b|removetags:"a b" }})" << dict
                                         << QStringLiteral("x &lt;p&gt;y&lt;/p&gt; x <p>y</p>") << NoError;
    QTest::newRow("filter-removetags02") << "{% autoescape off %}{{ a|removetags:\"a b\" }} {{ b|removetags:\"a "
                                            "b\" }}{% endautoescape %}"
                                         << dict << QStringLiteral("x <p>y</p> x <p>y</p>") << NoError;
    QTest::newRow("filter-striptags01") << QStringLiteral("{{ a|striptags }} {{ b|striptags }}") << dict << QStringLiteral("x y x y") << NoError;
    QTest::newRow("filter-striptags02") << QStringLiteral(
        "{% autoescape off %}{{ a|striptags }} {{ b|striptags "
        "}}{% endautoescape %}") << dict << QStringLiteral("x y x y")
                                        << NoError;

    dict.clear();
    dict.insert(QStringLiteral("fs_int_mib"), 1048576);

    QTest::newRow("filter-filesizeformat01") << QStringLiteral("{{ fs_int_mib|filesizeformat }}") << dict << QStringLiteral("1.05 MB") << NoError;

    QTest::newRow("filter-filesizeformat02") << QStringLiteral("{{ fs_int_mib|filesizeformat:\"2\" }}") << dict << QStringLiteral("1.00 MiB") << NoError;

    QTest::newRow("filter-filesizeformat03") << QStringLiteral("{{ fs_int_mib|filesizeformat:\"10,3\" }}") << dict << QStringLiteral("1.049 MB") << NoError;

    QTest::newRow("filter-filesizeformat04") << QStringLiteral("{{ fs_int_mib|filesizeformat:\"10,2,1024\" }}") << dict << QStringLiteral("1.07 GB") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("fs_float_mib"), 1024.5);

    QTest::newRow("filter-filesizeformat05") << QStringLiteral("{{ fs_float_mib|filesizeformat:\"10,2,1024\" }}") << dict << QStringLiteral("1.05 MB")
                                             << NoError;

    dict.clear();
    dict.insert(QStringLiteral("fs_string_mib"), QStringLiteral("1024.5"));

    QTest::newRow("filter-filesizeformat06") << QStringLiteral("{{ fs_string_mib|filesizeformat:\"10,2,1024\" }}") << dict << QStringLiteral("1.05 MB")
                                             << NoError;

    dict.clear();
    dict.insert(QStringLiteral("fs_bytes"), 999);
    dict.insert(QStringLiteral("fs_kb"), 1000);
    dict.insert(QStringLiteral("fs_10kb"), 10 * 1000);
    dict.insert(QStringLiteral("fs_1000kb"), 1000 * 1000 - 1);
    dict.insert(QStringLiteral("fs_mb"), 1000 * 1000);
    dict.insert(QStringLiteral("fs_50mb"), 1000 * 1000 * 50);
    dict.insert(QStringLiteral("fs_1000mb"), 1000 * 1000 * 1000 - 1);
    dict.insert(QStringLiteral("fs_gb"), 1000 * 1000 * 1000);
    dict.insert(QStringLiteral("fs_tb"), static_cast<double>(1000.0 * 1000.0 * 1000.0 * 1000.0));
    dict.insert(QStringLiteral("fs_pb"), static_cast<double>(1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0));
    dict.insert(QStringLiteral("fs_eb"), static_cast<double>(1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 2000.0));
    dict.insert(QStringLiteral("fs_zb"), static_cast<double>(1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0));
    dict.insert(QStringLiteral("fs_yb"), static_cast<double>(1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0));
    dict.insert(QStringLiteral("fs_2000yb"), static_cast<double>(1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 2000.0));
    dict.insert(QStringLiteral("fs_0b1"), 0.1);
    dict.insert(QStringLiteral("fs_0b2"), QString(QChar(0x03B1)));
    dict.insert(QStringLiteral("fs_neg_1"), -100);
    dict.insert(QStringLiteral("fs_neg_2"), -1000 * 1000 * 50);

    // fixes tests on MSVC2013
    QString fsInput;
    fsInput = QStringLiteral(
        "{{ fs_bytes|filesizeformat }} {{ fs_kb|filesizeformat }} {{ "
        "fs_10kb|filesizeformat }} {{ fs_1000kb|filesizeformat }} ");
    fsInput += QStringLiteral(
        "{{ fs_mb|filesizeformat }} {{ fs_50mb|filesizeformat }} {{ "
        "fs_1000mb|filesizeformat }} {{ fs_gb|filesizeformat }} ");
    fsInput += QStringLiteral(
        "{{ fs_tb|filesizeformat }} {{ fs_pb|filesizeformat }} {{ "
        "fs_eb|filesizeformat }} {{ fs_zb|filesizeformat }} ");
    fsInput += QStringLiteral(
        "{{ fs_yb|filesizeformat }} {{ fs_2000yb|filesizeformat }} {{ "
        "fs_0b1|filesizeformat }} {{ fs_0b2|filesizeformat }} ");
    fsInput += QStringLiteral("{{ fs_neg_1|filesizeformat }} {{ fs_neg_2|filesizeformat }}");

    QString fsExpect;
    fsExpect = QStringLiteral("999 bytes 1.00 KB 10.00 KB 1000.00 KB ");
    fsExpect += QStringLiteral("1.00 MB 50.00 MB 1000.00 MB 1.00 GB ");
    fsExpect += QStringLiteral("1.00 TB 1.00 PB 2.00 EB 1.00 ZB ");
    fsExpect += QStringLiteral("1.00 YB 2000.00 YB 0 bytes 0 bytes ");
    fsExpect += QStringLiteral("-100 bytes -50.00 MB");

    QTest::newRow("filter-filesizeformat07") << fsInput << dict << fsExpect << NoError;
}

void TestFilters::testListFilters_data()
{
    QTest::addColumn<QString>("input");
    QTest::addColumn<Dict>("dict");
    QTest::addColumn<QString>("output");
    QTest::addColumn<KTextTemplate::Error>("error");

    Dict dict;

    dict.insert(QStringLiteral("a"), QVariantList{QStringLiteral("a&b"), QStringLiteral("x")});
    dict.insert(QStringLiteral("b"), QVariantList{QVariant::fromValue(markSafe(QStringLiteral("a&b"))), QStringLiteral("x")});

    QTest::newRow("filter-first01") << QStringLiteral("{{ a|first }} {{ b|first }}") << dict << QStringLiteral("a&amp;b a&b") << NoError;
    QTest::newRow("filter-first02") << QStringLiteral("{% autoescape off %}{{ a|first }} {{ b|first }}{% endautoescape %}") << dict << QStringLiteral("a&b a&b")
                                    << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QVariantList{QStringLiteral("x"), QStringLiteral("a&b")});
    dict.insert(QStringLiteral("b"), QVariantList{QStringLiteral("x"), QVariant::fromValue(markSafe(QStringLiteral("a&b")))});

    QTest::newRow("filter-last01") << QStringLiteral("{{ a|last }} {{ b|last }}") << dict << QStringLiteral("a&amp;b a&b") << NoError;
    QTest::newRow("filter-last02") << QStringLiteral("{% autoescape off %}{{ a|last }} {{ b|last }}{% endautoescape %}") << dict << QStringLiteral("a&b a&b")
                                   << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QVariantList{QStringLiteral("a&b"), QStringLiteral("a&b")});
    dict.insert(QStringLiteral("b"),
                QVariantList() << QVariant::fromValue(markSafe(QStringLiteral("a&b"))) << QVariant::fromValue(markSafe(QStringLiteral("a&b"))));
    QTest::newRow("filter-random01") << QStringLiteral("{{ a|random }} {{ b|random }}") << dict << QStringLiteral("a&amp;b a&b") << NoError;
    QTest::newRow("filter-random02") << QStringLiteral("{% autoescape off %}{{ a|random }} {{ b|random }}{% endautoescape %}") << dict
                                     << QStringLiteral("a&b a&b") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("empty_list"), QVariantList());
    QTest::newRow("filter-random03") << QStringLiteral("{{empty_list|random}}") << dict << QStringLiteral("") << NoError;
    QTest::newRow("filter-random04") << QStringLiteral("{{|random}}") << dict << QStringLiteral("") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("a&b"));
    dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(QStringLiteral("a&b"))));

    QTest::newRow("filter-slice01") << R"({{ a|slice:"1:3" }} {{ b|slice:"1:3" }})" << dict << QStringLiteral("&amp;b &b") << NoError;
    QTest::newRow("filter-slice02") << "{% autoescape off %}{{ a|slice:\"1:3\" }} {{ b|slice:\"1:3\" }}{% "
                                       "endautoescape %}"
                                    << dict << QStringLiteral("&b &b") << NoError;

    dict.clear();
    QTest::newRow("filter-slice03") << "{{xx|slice}}" << dict << QStringLiteral("") << NoError;
    QTest::newRow("filter-slice04") << "{{|slice}}" << dict << QStringLiteral("") << NoError;

    dict.clear();
    QVariantList sublist{QStringLiteral("<y")};
    dict.insert(QStringLiteral("a"), QVariantList{QStringLiteral("x>"), QVariant(sublist)});

    QTest::newRow("filter-unordered_list01") << QStringLiteral("{{ a|unordered_list }}") << dict << "\t<li>x&gt;\n\t<ul>\n\t\t<li>&lt;y</li>\n\t</ul>\n\t</li>"
                                             << NoError;
    QTest::newRow("filter-unordered_list02") << QStringLiteral("{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}") << dict
                                             << "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>" << NoError;

    dict.clear();
    sublist = {markSafe(QStringLiteral("<y"))};
    dict.insert(QStringLiteral("a"), QVariantList{QStringLiteral("x>"), QVariant(sublist)});

    QTest::newRow("filter-unordered_list03") << QStringLiteral("{{ a|unordered_list }}") << dict << "\t<li>x&gt;\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"
                                             << NoError;
    QTest::newRow("filter-unordered_list04") << QStringLiteral("{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}") << dict
                                             << "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>" << NoError;

    dict.clear();
    sublist = {QStringLiteral("<y")};
    dict.insert(QStringLiteral("a"), QVariantList{QStringLiteral("x>"), QVariant(sublist)});

    QTest::newRow("filter-unordered_list05") << QStringLiteral("{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}") << dict
                                             << "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>" << NoError;

    //  length filter.
    dict.clear();
    dict.insert(QStringLiteral("list"), QVariantList{QStringLiteral("4"), QVariant(), true, QVariantHash()});

    QTest::newRow("length01") << QStringLiteral("{{ list|length }}") << dict << QStringLiteral("4") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("list"), QVariantList());

    QTest::newRow("length02") << QStringLiteral("{{ list|length }}") << dict << QStringLiteral("0") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("string"), QStringLiteral(""));

    QTest::newRow("length03") << QStringLiteral("{{ string|length }}") << dict << QStringLiteral("0") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("string"), QStringLiteral("django"));

    QTest::newRow("length04") << QStringLiteral("{{ string|length }}") << dict << QStringLiteral("6") << NoError;

    //  Invalid uses that should fail silently.

    dict.clear();
    dict.insert(QStringLiteral("int"), 7);

    QTest::newRow("length05") << QStringLiteral("{{ int|length }}") << dict << QString() << NoError;

    dict.clear();
    dict.insert(QStringLiteral("None"), QVariant());

    QTest::newRow("length06") << QStringLiteral("{{ None|length }}") << dict << QString() << NoError;

    //  length_is filter.

    dict.clear();
    dict.insert(QStringLiteral("some_list"), QVariantList{QStringLiteral("4"), QVariant(), true, QVariantHash()});

    QTest::newRow("length_is01") << "{% if some_list|length_is:\"4\" %}Four{% endif %}" << dict << QStringLiteral("Four") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("some_list"), QVariantList{QStringLiteral("4"), QVariant(), true, QVariantHash(), 17});

    QTest::newRow("length_is02") << "{% if some_list|length_is:\"4\" %}Four{% else %}Not Four{% endif %}" << dict << QStringLiteral("Not Four") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("mystring"), QStringLiteral("word"));

    QTest::newRow("length_is03") << "{% if mystring|length_is:\"4\" %}Four{% endif %}" << dict << QStringLiteral("Four") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("mystring"), QStringLiteral("Python"));

    QTest::newRow("length_is04") << "{% if mystring|length_is:\"4\" %}Four{% else %}Not Four{% endif %}" << dict << QStringLiteral("Not Four") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("mystring"), QStringLiteral(""));

    QTest::newRow("length_is05") << "{% if mystring|length_is:\"4\" %}Four{% else %}Not Four{% endif %}" << dict << QStringLiteral("Not Four") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("var"), QStringLiteral("django"));

    QTest::newRow("length_is06") << QStringLiteral("{% with var|length as my_length %}{{ my_length }}{% endwith %}") << dict << QStringLiteral("6") << NoError;

    //  Boolean return value from length_is should not be coerced to a string

    dict.clear();
    QTest::newRow("length_is07") << "{% if \"X\"|length_is:0 %}Length is 0{% "
                                    "else %}Length not 0{% endif %}"
                                 << dict << QStringLiteral("Length not 0") << NoError;
    QTest::newRow("length_is08") << "{% if \"X\"|length_is:1 %}Length is 1{% "
                                    "else %}Length not 1{% endif %}"
                                 << dict << QStringLiteral("Length is 1") << NoError;

    //  Invalid uses that should fail silently.

    dict.clear();
    dict.insert(QStringLiteral("var"), QStringLiteral("django"));

    QTest::newRow("length_is09") << "{{ var|length_is:\"fish\" }}" << dict << QString() << NoError;

    dict.clear();
    dict.insert(QStringLiteral("int"), 7);

    QTest::newRow("length_is10") << "{{ int|length_is:\"1\" }}" << dict << QString() << NoError;

    dict.clear();
    dict.insert(QStringLiteral("none"), QVariant());

    QTest::newRow("length_is11") << "{{ none|length_is:\"1\" }}" << dict << QString() << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QVariantList{QStringLiteral("alpha"), QStringLiteral("beta & me")});

    QTest::newRow("join01") << "{{ a|join:\", \" }}" << dict << QStringLiteral("alpha, beta &amp; me") << NoError;
    QTest::newRow("join02") << "{% autoescape off %}{{ a|join:\", \" }}{% endautoescape %}" << dict << QStringLiteral("alpha, beta & me") << NoError;
    QTest::newRow("join03") << "{{ a|join:\" &amp; \" }}" << dict << QStringLiteral("alpha &amp; beta &amp; me") << NoError;
    QTest::newRow("join04") << "{% autoescape off %}{{ a|join:\" &amp; \" }}{% endautoescape %}" << dict << QStringLiteral("alpha &amp; beta & me") << NoError;

    // Test that joining with unsafe joiners don't result in unsafe strings
    // (#11377)
    dict.insert(QStringLiteral("var"), QStringLiteral(" & "));
    QTest::newRow("join05") << "{{ a|join:var }}" << dict << QStringLiteral("alpha &amp; beta &amp; me") << NoError;
    dict.insert(QStringLiteral("var"), KTextTemplate::markSafe(QStringLiteral(" & ")));
    QTest::newRow("join06") << "{{ a|join:var }}" << dict << QStringLiteral("alpha & beta &amp; me") << NoError;
    dict.insert(QStringLiteral("a"), QVariantList{QStringLiteral("Alpha"), QStringLiteral("Beta & Me")});
    dict.insert(QStringLiteral("var"), QStringLiteral(" & "));
    QTest::newRow("join07") << "{{ a|join:var|lower }}" << dict << QStringLiteral("alpha &amp; beta &amp; me") << NoError;

    dict.insert(QStringLiteral("a"), QVariantList{QStringLiteral("Alpha"), QStringLiteral("Beta & Me")});
    dict.insert(QStringLiteral("var"), KTextTemplate::markSafe(QStringLiteral(" & ")));
    QTest::newRow("join08") << "{{ a|join:var|lower }}" << dict << QStringLiteral("alpha & beta &amp; me") << NoError;

    // arguments to filters are intended to be used unescaped.
    //   dict.clear();
    //   dict.insert( QStringLiteral("a"), QVariantList() <<
    //   QString::fromLatin1( "alpha" ) << QString::fromLatin1( "beta & me" ) );
    //   dict.insert( QStringLiteral("var"), QStringLiteral(" & ") );
    //
    //   QTest::newRow( "join05" ) << QString::fromLatin1( "{{ a|join:var }}" )
    //   <<
    //   dict << QString::fromLatin1( "alpha &amp; beta &amp; me" ) << NoError;
    //
    //   dict.clear();
    //   dict.insert( QStringLiteral("a"), QVariantList() <<
    //   QString::fromLatin1( "alpha" ) << QString::fromLatin1( "beta & me" ) );
    //   dict.insert( QStringLiteral("var"), QVariant::fromValue( markSafe(
    //   QString::fromLatin1( " & " ) ) ) );
    //
    //   QTest::newRow( "join06" ) << QString::fromLatin1( "{{ a|join:var }}" )
    //   <<
    //   dict << QString::fromLatin1( "alpha & beta &amp; me" ) << NoError;
    //
    //   dict.clear();
    //   dict.insert( QStringLiteral("a"), QVariantList() <<
    //   QString::fromLatin1( "Alpha" ) << QString::fromLatin1( "Beta & me" ) );
    //   dict.insert( QStringLiteral("var"), QStringLiteral(" & ") );
    //
    //   QTest::newRow( "join07" ) << QString::fromLatin1( "{{ a|join:var|lower
    //   }}" ) << dict << QString::fromLatin1( "alpha &amp; beta &amp; me" ) <<
    //   NoError;
    //
    //   dict.clear();
    //   dict.insert( QStringLiteral("a"), QVariantList() <<
    //   QString::fromLatin1( "Alpha" ) << QString::fromLatin1( "Beta & me" ) );
    //   dict.insert( QStringLiteral("var"), QVariant::fromValue( markSafe(
    //   QString::fromLatin1( " & " ) ) ) );
    //
    //   QTest::newRow( "join08" ) << QString::fromLatin1( "{{ a|join:var|lower
    //   }}" ) << dict << QString::fromLatin1( "alpha & beta &amp; me" ) <<
    //   NoError;

    dict.clear();

    QVariantList mapList;
    const auto cities = QStringList{QStringLiteral("London"), QStringLiteral("Berlin"), QStringLiteral("Paris"), QStringLiteral("Dublin")};
    for (const QString &city : cities) {
        QVariantHash map;
        map.insert(QStringLiteral("city"), city);
        mapList << map;
    }

    dict.insert(QStringLiteral("mapList"), mapList);

    QTest::newRow("dictsort01") << "{% with mapList|dictsort:'city' as result %}{% for item in result "
                                   "%}{{ item.city }},{% endfor %}{% endwith %}"
                                << dict << "Berlin,Dublin,London,Paris," << NoError;

    {
        // Test duplication works
        QVariantHash map;
        map.insert(QStringLiteral("city"), QStringLiteral("Berlin"));
        mapList << map;
    }
    dict.insert(QStringLiteral("mapList"), mapList);

    QTest::newRow("dictsort02") << "{% with mapList|dictsort:'city' as result %}{% for item in result "
                                   "%}{{ item.city }},{% endfor %}{% endwith %}"
                                << dict << "Berlin,Berlin,Dublin,London,Paris," << NoError;

    dict.clear();

    QVariantList listList;

    const auto countries = QStringList{QStringLiteral("England"), QStringLiteral("Germany"), QStringLiteral("France"), QStringLiteral("Ireland")};

    const auto languages = QStringList{QStringLiteral("English"), QStringLiteral("German"), QStringLiteral("French"), QStringLiteral("Irish")};

    for (auto i = 0; i < cities.size(); ++i) {
        listList << QVariant(QVariantList{cities.at(i), countries.at(i), languages.at(i)});
    }

    dict.insert(QStringLiteral("listList"), listList);

    QTest::newRow("dictsort03") << "{% with listList|dictsort:'0' as result %}{% for item in result "
                                   "%}{{ "
                                   "item.0 }};{{ item.1 }};{{ item.2 }},{% endfor %}{% endwith %}"
                                << dict
                                << "Berlin;Germany;German,Dublin;Ireland;Irish,London;England;"
                                   "English,Paris;France;French,"
                                << NoError;

    QTest::newRow("dictsort04") << "{% with listList|dictsort:'1' as result %}{% for item in result "
                                   "%}{{ "
                                   "item.0 }};{{ item.1 }};{{ item.2 }},{% endfor %}{% endwith %}"
                                << dict
                                << "London;England;English,Paris;France;French,Berlin;Germany;"
                                   "German,Dublin;Ireland;Irish,"
                                << NoError;
}

void TestFilters::testLogicFilters_data()
{
    QTest::addColumn<QString>("input");
    QTest::addColumn<Dict>("dict");
    QTest::addColumn<QString>("output");
    QTest::addColumn<KTextTemplate::Error>("error");

    Dict dict;

    //  Literal string arguments to the default filter are always treated as
    //  safe strings, regardless of the auto-escaping state.

    //  Note: we have to use {"a": ""} here, otherwise the invalid template
    //  variable string interferes with the test result.

    dict.insert(QStringLiteral("a"), QStringLiteral(""));

    QTest::newRow("filter-default01") << "{{ a|default:\"x<\" }}" << dict << QStringLiteral("x<") << NoError;
    QTest::newRow("filter-default02") << "{% autoescape off %}{{ a|default:\"x<\" }}{% endautoescape %}" << dict << QStringLiteral("x<") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QVariant::fromValue(markSafe(QStringLiteral("x>"))));

    QTest::newRow("filter-default03") << "{{ a|default:\"x<\" }}" << dict << QStringLiteral("x>") << NoError;
    QTest::newRow("filter-default04") << "{% autoescape off %}{{ a|default:\"x<\" }}{% endautoescape %}" << dict << QStringLiteral("x>") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QVariant());

    QTest::newRow("filter-default_if_none01") << "{{ a|default:\"x<\" }}" << dict << QStringLiteral("x<") << NoError;
    QTest::newRow("filter-default_if_none02") << "{% autoescape off %}{{ a|default:\"x<\" }}{% endautoescape %}" << dict << QStringLiteral("x<") << NoError;
}

void TestFilters::testMiscFilters_data()
{
    QTest::addColumn<QString>("input");
    QTest::addColumn<Dict>("dict");
    QTest::addColumn<QString>("output");
    QTest::addColumn<KTextTemplate::Error>("error");

    Dict dict;

    //
    //   //  {"a": "<1-800-call-me>", "b": mark_safe("<1-800-call-me>")
    //   QTest::newRow( "filter-phone2numeric01") << QString::fromLatin1( "{{
    //   a|phone2numeric }} {{ b|phone2numeric }}" ) << dict <<
    //   QString::fromLatin1( "&lt;1-800-2255-63&gt; <1-800-2255-63>" ) <<
    //   NoError;
    //
    //   //  {"a": "<1-800-call-me>", "b": mark_safe("<1-800-call-me>")
    //   QTest::newRow( "filter-phone2numeric02") << QString::fromLatin1( "{%
    //   autoescape off %}{{ a|phone2numeric }} {{ b|phone2numeric }}{%
    //   endautoescape %}" ) << dict << QString::fromLatin1( "<1-800-2255-63>
    //   <1-800-2255-63>" ) << NoError;
    //
    //  Chaining a bunch of safeness-preserving filters should not alter
    //  the safe status either way.

    dict.insert(QStringLiteral("a"), QStringLiteral("a < b"));
    dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(QStringLiteral("a < b"))));

    QTest::newRow("chaining01") << R"({{ a|capfirst|center:"7" }}.{{ b|capfirst|center:"7" }})" << dict << QStringLiteral(" A &lt; b . A < b ") << NoError;
    QTest::newRow("chaining02") << "{% autoescape off %}{{ a|capfirst|center:\"7\" }}.{{ "
                                   "b|capfirst|center:\"7\" }}{% endautoescape %}"
                                << dict << QStringLiteral(" A < b . A < b ") << NoError;

    //  Using a filter that forces a string back to unsafe:

    QTest::newRow("chaining03") << R"({{ a|cut:"b"|capfirst }}.{{ b|cut:"b"|capfirst }})" << dict << QStringLiteral("A &lt; .A < ") << NoError;
    QTest::newRow("chaining04") << "{% autoescape off %}{{ a|cut:\"b\"|capfirst }}.{{ "
                                   "b|cut:\"b\"|capfirst }}{% endautoescape %}"
                                << dict << QStringLiteral("A < .A < ") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("a"), QStringLiteral("a < b"));

    //  Using a filter that forces safeness does not lead to double-escaping

    QTest::newRow("chaining05") << QStringLiteral("{{ a|escape|capfirst }}") << dict << QStringLiteral("A &lt; b") << NoError;
    QTest::newRow("chaining06") << QStringLiteral("{% autoescape off %}{{ a|escape|capfirst }}{% endautoescape %}") << dict << QStringLiteral("A &lt; b")
                                << NoError;

    //  Force to safe, then back (also showing why using force_escape too
    //  early in a chain can lead to unexpected results).

    QTest::newRow("chaining07") << "{{ a|force_escape|cut:\";\" }}" << dict << QStringLiteral("a &amp;lt b") << NoError;
    QTest::newRow("chaining08") << "{% autoescape off %}{{ a|force_escape|cut:\";\" }}{% endautoescape "
                                   "%}"
                                << dict << QStringLiteral("a &lt b") << NoError;
    QTest::newRow("chaining09") << "{{ a|cut:\";\"|force_escape }}" << dict << QStringLiteral("a &lt; b") << NoError;
    QTest::newRow("chaining10") << "{% autoescape off %}{{ a|cut:\";\"|force_escape }}{% endautoescape "
                                   "%}"
                                << dict << QStringLiteral("a &lt; b") << NoError;
    QTest::newRow("chaining11") << "{{ a|cut:\"b\"|safe }}" << dict << QStringLiteral("a < ") << NoError;
    QTest::newRow("chaining12") << "{% autoescape off %}{{ a|cut:\"b\"|safe }}{% endautoescape %}" << dict << QStringLiteral("a < ") << NoError;
    QTest::newRow("chaining13") << QStringLiteral("{{ a|safe|force_escape }}") << dict << QStringLiteral("a &lt; b") << NoError;
    QTest::newRow("chaining14") << QStringLiteral("{% autoescape off %}{{ a|safe|force_escape }}{% endautoescape %}") << dict << QStringLiteral("a &lt; b")
                                << NoError;

    //   //  Filters decorated with stringfilter still respect is_safe.
    //
    //   //  {"unsafe": UnsafeClass()
    //   QTest::newRow( "autoescape-stringfilter01") << QString::fromLatin1( "{{
    //   unsafe|capfirst }}" ) << dict << QString::fromLatin1( "You &amp; me" )
    //   <<
    //   NoError;
    //
    //   //  {"unsafe": UnsafeClass()
    //   QTest::newRow( "autoescape-stringfilter02") << QString::fromLatin1( "{%
    //   autoescape off %}{{ unsafe|capfirst }}{% endautoescape %}" ) << dict <<
    //   QString::fromLatin1( "You & me" ) << NoError;
    //
    //   //  {"safe": SafeClass()
    //   QTest::newRow( "autoescape-stringfilter03") << QString::fromLatin1( "{{
    //   safe|capfirst }}" ) << dict << QString::fromLatin1( "You &gt; me" ) <<
    //   NoError;
    //
    //   //  {"safe": SafeClass()
    //   QTest::newRow( "autoescape-stringfilter04") << QString::fromLatin1( "{%
    //   autoescape off %}{{ safe|capfirst }}{% endautoescape %}" ) << dict <<
    //   QString::fromLatin1( "You &gt; me" ) << NoError;
    //
}

void TestFilters::testIntegerFilters_data()
{
    QTest::addColumn<QString>("input");
    QTest::addColumn<Dict>("dict");
    QTest::addColumn<QString>("output");
    QTest::addColumn<KTextTemplate::Error>("error");

    Dict dict;

    dict.insert(QStringLiteral("i"), 2000);

    QTest::newRow("add01") << QStringLiteral("{{ i|add:5 }}") << dict << QStringLiteral("2005") << NoError;
    QTest::newRow("add02") << QStringLiteral("{{ i|add:\"napis\" }}") << dict << QStringLiteral("2000") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("i"), QStringLiteral("not_an_int"));

    QTest::newRow("add03") << QStringLiteral("{{ i|add:16 }}") << dict << QStringLiteral("not_an_int") << NoError;
    QTest::newRow("add04") << QStringLiteral("{{ i|add:\"16\" }}") << dict << QStringLiteral("not_an_int16") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("l1"), QVariantList{1, 2});
    dict.insert(QStringLiteral("l2"), QVariantList{3, 4});

    QTest::newRow("add05") << QStringLiteral("{{ l1|add:l2 }}") << dict << QStringLiteral("[1, 2, 3, 4]") << NoError;
    // QTest::newRow( "add06" ) << QString::fromLatin1( "{{ t1|add:t2 }}" ) <<
    // dict << QString::fromLatin1( "2005" ) << NoError;

    // QTest::newRow( "add07" ) << QString::fromLatin1( "{{ d|add:t }}" ) <<
    // dict
    // << QString::fromLatin1( "2005" ) << NoError;

    QTest::newRow("add08") << QStringLiteral("{{ 1|add:2 }}") << dict << QStringLiteral("3") << NoError;
    dict.clear();
    dict.insert(QStringLiteral("l1"), QStringList{QStringLiteral("one"), QStringLiteral("two")});
    dict.insert(QStringLiteral("l2"), QStringList{QStringLiteral("three"), QStringLiteral("four")});

    QTest::newRow("add09") << QStringLiteral("{{ l1|add:l2|join:\", \" }}") << dict << QStringLiteral("one, two, three, four") << NoError;

    QTest::newRow("filter-getdigit01") << QStringLiteral("{{ 123|get_digit:1 }}") << dict << QStringLiteral("3") << NoError;
    QTest::newRow("filter-getdigit02") << QStringLiteral("{{ 123|get_digit:2 }}") << dict << QStringLiteral("2") << NoError;
    QTest::newRow("filter-getdigit03") << QStringLiteral("{{ 123|get_digit:3 }}") << dict << QStringLiteral("1") << NoError;
    QTest::newRow("filter-getdigit04") << QStringLiteral("{{ 123|get_digit:4 }}") << dict << QStringLiteral("123") << NoError;
}

QTEST_MAIN(TestFilters)
#include "testfilters.moc"

#endif
