Cutelee  6.1.0
testfilters.cpp
1 /*
2  This file is part of the Cutelee template system.
3 
4  Copyright (c) 2009,2010 Stephen Kelly <steveire@gmail.com>
5 
6  This library is free software; you can redistribute it and/or
7  modify it under the terms of the GNU Lesser General Public
8  License as published by the Free Software Foundation; either version
9  2.1 of the Licence, or (at your option) any later version.
10 
11  This library is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  Lesser General Public License for more details.
15 
16  You should have received a copy of the GNU Lesser General Public
17  License along with this library. If not, see <http://www.gnu.org/licenses/>.
18 
19 */
20 
21 #ifndef FILTERSTEST_H
22 #define FILTERSTEST_H
23 
24 #include <QtCore/QDebug>
25 #include <QtCore/QDir>
26 #include <QtCore/QFileInfo>
27 #include <QtCore/QJsonDocument>
28 #include <QtCore/QJsonObject>
29 #include <QtCore/QJsonArray>
30 #include <QtTest/QTest>
31 
32 #include "context.h"
33 #include "coverageobject.h"
34 #include "engine.h"
35 #include "cutelee_paths.h"
36 
37 #include "template.h"
38 #include <util.h>
39 
40 typedef QHash<QString, QVariant> Dict;
41 
42 Q_DECLARE_METATYPE(Cutelee::Error)
43 
44 using namespace Cutelee;
45 
47 {
48  Q_OBJECT
49 
50 private Q_SLOTS:
51  void initTestCase();
52  void cleanupTestCase();
53 
54  void testDateBasedFilters_data();
55  void testDateBasedFilters() { doTest(); }
56 
57  void testStringFilters_data();
58  void testStringFilters() { doTest(); }
59 
60  void testListFilters_data();
61  void testListFilters() { doTest(); }
62 
63  void testLogicFilters_data();
64  void testLogicFilters() { doTest(); }
65 
66  void testMiscFilters_data();
67  void testMiscFilters() { doTest(); }
68 
69  void testIntegerFilters_data();
70  void testIntegerFilters() { doTest(); }
71 
72 private:
73  void doTest();
74 
75  std::shared_ptr<InMemoryTemplateLoader> loader;
76  Engine *m_engine;
77 };
78 
79 void TestFilters::initTestCase()
80 {
81  m_engine = new Engine(this);
82 
83  loader = std::shared_ptr<InMemoryTemplateLoader>(new InMemoryTemplateLoader());
84  m_engine->addTemplateLoader(loader);
85 
86  auto appDirPath
87  = QFileInfo(QCoreApplication::applicationDirPath()).absoluteDir().path();
88  m_engine->setPluginPaths({
89  QStringLiteral(CUTELEE_PLUGIN_PATH),
90  appDirPath + QStringLiteral("/tests/") // For testtags.qs
91  });
92 }
93 
94 void TestFilters::cleanupTestCase() { delete m_engine; }
95 
96 void TestFilters::doTest()
97 {
98  QFETCH(QString, input);
99  QFETCH(Dict, dict);
100  QFETCH(QString, output);
101  QFETCH(Cutelee::Error, error);
102 
103  auto t = m_engine->newTemplate(input, QLatin1String(QTest::currentDataTag()));
104 
105  Context context(dict);
106 
107  auto result = t->render(&context);
108 
109  if (t->error() != NoError) {
110  if (t->error() != error)
111  qDebug() << t->errorString();
112  QCOMPARE(t->error(), error);
113  return;
114  }
115 
116  // Didn't catch any errors, so make sure I didn't expect any.
117  QCOMPARE(NoError, error);
118 
119  QCOMPARE(t->error(), NoError);
120 
121  QCOMPARE(result, output);
122 }
123 
124 void TestFilters::testDateBasedFilters_data()
125 {
126  QTest::addColumn<QString>("input");
127  QTest::addColumn<Dict>("dict");
128  QTest::addColumn<QString>("output");
129  QTest::addColumn<Cutelee::Error>("error");
130 
131  Dict dict;
132  auto now = QDateTime::currentDateTimeUtc();
133 
134  dict.insert(QStringLiteral("a"), now.addSecs(-70));
135 
136  QTest::newRow("filter-timesince01")
137  << QStringLiteral("{{ a|timesince }}") << dict
138  << QStringLiteral("1 minute") << NoError;
139 
140  dict.clear();
141 
142  dict.insert(QStringLiteral("a"), now.addDays(-1).addSecs(-60));
143 
144  QTest::newRow("filter-timesince02")
145  << QStringLiteral("{{ a|timesince }}") << dict << QStringLiteral("1 day")
146  << NoError;
147 
148  dict.clear();
149 
150  dict.insert(QStringLiteral("a"),
151  now.addSecs(-1 * 60 * 60).addSecs(-1 * 25 * 60).addSecs(-1 * 10));
152  QTest::newRow("filter-timesince03")
153  << QStringLiteral("{{ a|timesince }}") << dict
154  << QStringLiteral("1 hour, 25 minutes") << NoError;
155 
156  dict.clear();
157 
158  // Compare to a given parameter
159 
160  dict.insert(QStringLiteral("a"), now.addDays(-2));
161  dict.insert(QStringLiteral("b"), now.addDays(-1));
162 
163  QTest::newRow("filter-timesince04")
164  << QStringLiteral("{{ a|timesince:b }}") << dict
165  << QStringLiteral("1 day") << NoError;
166 
167  dict.clear();
168 
169  dict.insert(QStringLiteral("a"), now.addDays(-2).addSecs(-60));
170  dict.insert(QStringLiteral("b"), now.addDays(-2));
171 
172  QTest::newRow("filter-timesince05")
173  << QStringLiteral("{{ a|timesince:b }}") << dict
174  << QStringLiteral("1 minute") << NoError;
175 
176  dict.clear();
177 
178  // Check that timezone is respected
179 
180  // {"a":now_tz - timedelta(hours=8), "b":now_tz
181  // QTest::newRow( "filter-timesince06" ) << QString::fromLatin1( "{{
182  // a|timesince:b }}" ) << dict << QString::fromLatin1( "8 hours" ) <<
183  // NoError;
184 
185  dict.insert(QStringLiteral("earlier"), now.addDays(-7));
186  QTest::newRow("filter-timesince07")
187  << QStringLiteral("{{ earlier|timesince }}") << dict
188  << QStringLiteral("1 week") << NoError;
189 
190  dict.clear();
191 
192  dict.insert(QStringLiteral("now"), now);
193  dict.insert(QStringLiteral("earlier"), now.addDays(-7));
194 
195  QTest::newRow("filter-timesince08")
196  << QStringLiteral("{{ earlier|timesince:now }}") << dict
197  << QStringLiteral("1 week") << NoError;
198 
199  dict.clear();
200 
201  dict.insert(QStringLiteral("later"), now.addDays(7));
202 
203  QTest::newRow("filter-timesince09")
204  << QStringLiteral("{{ later|timesince }}") << dict
205  << QStringLiteral("0 minutes") << NoError;
206 
207  dict.clear();
208 
209  dict.insert(QStringLiteral("now"), now);
210  dict.insert(QStringLiteral("later"), now.addDays(7));
211 
212  QTest::newRow("filter-timesince10")
213  << QStringLiteral("{{ later|timesince:now }}") << dict
214  << QStringLiteral("0 minutes") << NoError;
215 
216  // Ensures that differing timezones are calculated correctly
217 
218  // {"a": now
219  // QTest::newRow( "filter-timesince11" ) << QString::fromLatin1( "{{
220  // a|timesince }}" ) << dict << QString::fromLatin1( "0 minutes" ) <<
221  // NoError;
222 
223  // {"a": now_tz
224  // QTest::newRow( "filter-timesince12" ) << QString::fromLatin1( "{{
225  // a|timesince }}" ) << dict << QString::fromLatin1( "0 minutes" ) <<
226  // NoError;
227 
228  // {"a": now_tz_i
229  // QTest::newRow( "filter-timesince13" ) << QString::fromLatin1( "{{
230  // a|timesince }}" ) << dict << QString::fromLatin1( "0 minutes" ) <<
231  // NoError;
232 
233  // {"a": now_tz, "b": now_tz_i
234  // QTest::newRow( "filter-timesince14" ) << QString::fromLatin1( "{{
235  // a|timesince:b }}" ) << dict << QString::fromLatin1( "0 minutes" ) <<
236  // NoError;
237 
238  // {"a": now, "b": now_tz_i
239  // QTest::newRow( "filter-timesince15" ) << QString::fromLatin1( "{{
240  // a|timesince:b }}" ) << dict << QString() << NoError;
241 
242  // {"a": now_tz_i, "b": now
243  // QTest::newRow( "filter-timesince16" ) << QString::fromLatin1( "{{
244  // a|timesince:b }}" ) << dict << QString() << NoError;
245 
246  dict.clear();
247 
248  dict.insert(QStringLiteral("a"), now);
249  dict.insert(QStringLiteral("b"), now);
250 
251  QTest::newRow("filter-timesince17")
252  << QStringLiteral("{{ a|timesince:b }}") << dict
253  << QStringLiteral("0 minutes") << NoError;
254 
255  dict.clear();
256 
257  dict.insert(QStringLiteral("a"), now);
258  dict.insert(QStringLiteral("b"), now.addDays(1));
259 
260  QTest::newRow("filter-timesince18")
261  << QStringLiteral("{{ a|timesince:b }}") << dict
262  << QStringLiteral("1 day") << NoError;
263 
264  dict.clear();
265  QTest::newRow("filter-timesince19") << QStringLiteral("{{xx|timesince}}")
266  << dict << QStringLiteral("") << NoError;
267  QTest::newRow("filter-timesince20") << QStringLiteral("{{|timesince}}")
268  << dict << QStringLiteral("") << NoError;
269 
270  // Default compare with datetime.now()
271 
272  dict.clear();
273  dict.insert(QStringLiteral("a"), now.addSecs(130));
274 
275  QTest::newRow("filter-timeuntil01")
276  << QStringLiteral("{{ a|timeuntil }}") << dict
277  << QStringLiteral("2 minutes") << NoError;
278 
279  dict.clear();
280  dict.insert(QStringLiteral("a"), now.addDays(1).addSecs(10));
281 
282  QTest::newRow("filter-timeuntil02")
283  << QStringLiteral("{{ a|timeuntil }}") << dict << QStringLiteral("1 day")
284  << NoError;
285 
286  dict.clear();
287  dict.insert(QStringLiteral("a"), now.addSecs(60 * 60 * 8).addSecs(610));
288 
289  QTest::newRow("filter-timeuntil03")
290  << QStringLiteral("{{ a|timeuntil }}") << dict
291  << QStringLiteral("8 hours, 10 minutes") << NoError;
292 
293  // Compare to a given parameter
294 
295  dict.clear();
296  dict.insert(QStringLiteral("a"), now.addDays(-1));
297  dict.insert(QStringLiteral("b"), now.addDays(-2));
298 
299  QTest::newRow("filter-timeuntil04")
300  << QStringLiteral("{{ a|timeuntil:b }}") << dict
301  << QStringLiteral("1 day") << NoError;
302 
303  dict.clear();
304  dict.insert(QStringLiteral("a"), now.addDays(-1));
305  dict.insert(QStringLiteral("b"), now.addDays(-1).addSecs(-60));
306 
307  QTest::newRow("filter-timeuntil05")
308  << QStringLiteral("{{ a|timeuntil:b }}") << dict
309  << QStringLiteral("1 minute") << NoError;
310 
311  dict.clear();
312  dict.insert(QStringLiteral("earlier"), now.addDays(-7));
313 
314  QTest::newRow("filter-timeuntil06")
315  << QStringLiteral("{{ earlier|timeuntil }}") << dict
316  << QStringLiteral("0 minutes") << NoError;
317 
318  dict.clear();
319  dict.insert(QStringLiteral("now"), now);
320  dict.insert(QStringLiteral("earlier"), now.addDays(-7));
321 
322  QTest::newRow("filter-timeuntil07")
323  << QStringLiteral("{{ earlier|timeuntil:now }}") << dict
324  << QStringLiteral("0 minutes") << NoError;
325 
326  dict.clear();
327  dict.insert(QStringLiteral("later"), now.addDays(7).addSecs(5));
328 
329  QTest::newRow("filter-timeuntil08")
330  << QStringLiteral("{{ later|timeuntil }}") << dict
331  << QStringLiteral("1 week") << NoError;
332 
333  dict.clear();
334  dict.insert(QStringLiteral("now"), now);
335  dict.insert(QStringLiteral("later"), now.addDays(7));
336 
337  QTest::newRow("filter-timeuntil09")
338  << QStringLiteral("{{ later|timeuntil:now }}") << dict
339  << QStringLiteral("1 week") << NoError;
340 
341  // Ensures that differing timezones are calculated correctly
342  //
343  // // {"a": now_tz_i
344  // QTest::newRow( "filter-timeuntil10" ) << QString::fromLatin1( "{{
345  // a|timeuntil }}" ) << dict << QString::fromLatin1( "0 minutes" ) <<
346  // NoError;
347  //
348  // // {"a": now_tz_i, "b": now_tz
349  // QTest::newRow( "filter-timeuntil11" ) << QString::fromLatin1( "{{
350  // a|timeuntil:b }}" ) << dict << QString::fromLatin1( "0 minutes" ) <<
351  // NoError;
352 
353  dict.clear();
354  dict.insert(QStringLiteral("a"), now);
355  dict.insert(QStringLiteral("b"), now);
356  QTest::newRow("filter-timeuntil12")
357  << QStringLiteral("{{ a|timeuntil:b }}") << dict
358  << QStringLiteral("0 minutes") << NoError;
359 
360  dict.clear();
361  dict.insert(QStringLiteral("a"), now);
362  dict.insert(QStringLiteral("b"), now.addDays(-1));
363 
364  QTest::newRow("filter-timeuntil13")
365  << QStringLiteral("{{ a|timeuntil:b }}") << dict
366  << QStringLiteral("1 day") << NoError;
367 
368  dict.clear();
369  QTest::newRow("filter-timeuntil14") << QStringLiteral("{{xx|timeuntil}}")
370  << dict << QStringLiteral("") << NoError;
371  QTest::newRow("filter-timeuntil15") << QStringLiteral("{{|timeuntil}}")
372  << dict << QStringLiteral("") << NoError;
373 
374  QDate d(2008, 1, 1);
375 
376  dict.clear();
377  dict.insert(QStringLiteral("d"), d);
378 
379  QTest::newRow("date01") << "{{ d|date:\"MM\" }}" << dict
380  << QStringLiteral("01") << NoError;
381  QTest::newRow("date02") << QStringLiteral("{{ d|date }}") << dict
382  << d.toString(QStringLiteral("MMM. d, yyyy"))
383  << NoError;
384 
385  dict.clear();
386  dict.insert(QStringLiteral("d"), QStringLiteral("fail_string"));
387  QTest::newRow("date03") << "{{ d|date:\"MM\" }}" << dict << QString()
388  << NoError;
389 }
390 
391 void TestFilters::testStringFilters_data()
392 {
393  QTest::addColumn<QString>("input");
394  QTest::addColumn<Dict>("dict");
395  QTest::addColumn<QString>("output");
396  QTest::addColumn<Cutelee::Error>("error");
397 
398  Dict dict;
399 
400  dict.clear();
401  dict.insert(QStringLiteral("a"), QStringLiteral("<a>\'"));
402  dict.insert(QStringLiteral("b"),
403  QVariant::fromValue(markSafe(QStringLiteral("<a>\'"))));
404 
405  QTest::newRow("filter-addslash01")
406  << QStringLiteral("{% autoescape off %}{{ a|addslashes }} {{ "
407  "b|addslashes }}{% endautoescape %}")
408  << dict << "<a>\\\' <a>\\\'" << NoError;
409 
410  dict.clear();
411  dict.insert(QStringLiteral("a"), QStringLiteral("<a>\'"));
412  dict.insert(QStringLiteral("b"),
413  QVariant::fromValue(markSafe(QStringLiteral("<a>\'"))));
414 
415  QTest::newRow("filter-addslash02")
416  << QStringLiteral("{{ a|addslashes }} {{ b|addslashes }}") << dict
417  << "&lt;a&gt;\\&#39; <a>\\\'" << NoError;
418 
419  dict.clear();
420  dict.insert(QStringLiteral("a"), QStringLiteral("fred>"));
421  dict.insert(QStringLiteral("b"),
422  QVariant::fromValue(markSafe(QStringLiteral("fred&gt;"))));
423 
424  QTest::newRow("filter-capfirst01")
425  << QStringLiteral("{% autoescape off %}{{ a|capfirst }} {{ b|capfirst "
426  "}}{% endautoescape %}")
427  << dict << QStringLiteral("Fred> Fred&gt;") << NoError;
428 
429  dict.clear();
430  dict.insert(QStringLiteral("a"), QStringLiteral("fred>"));
431  dict.insert(QStringLiteral("b"),
432  QVariant::fromValue(markSafe(QStringLiteral("fred&gt;"))));
433 
434  QTest::newRow("filter-capfirst02")
435  << QStringLiteral("{{ a|capfirst }} {{ b|capfirst }}") << dict
436  << QStringLiteral("Fred&gt; Fred&gt;") << NoError;
437 
438  // Note that applying fix_ampsersands in autoescape mode leads to
439  // double escaping.
440 
441  dict.clear();
442  dict.insert(QStringLiteral("a"), QStringLiteral("a&b"));
443  dict.insert(QStringLiteral("b"),
444  QVariant::fromValue(markSafe(QStringLiteral("a&b"))));
445 
446  QTest::newRow("filter-fix_ampersands01")
447  << QStringLiteral("{% autoescape off %}{{ a|fix_ampersands }} {{ "
448  "b|fix_ampersands }}{% endautoescape %}")
449  << dict << QStringLiteral("a&amp;b a&amp;b") << NoError;
450 
451  dict.clear();
452  dict.insert(QStringLiteral("a"), QStringLiteral("a&b"));
453  dict.insert(QStringLiteral("b"),
454  QVariant::fromValue(markSafe(QStringLiteral("a&b"))));
455 
456  QTest::newRow("filter-fix_ampersands02")
457  << QStringLiteral("{{ a|fix_ampersands }} {{ b|fix_ampersands }}") << dict
458  << QStringLiteral("a&amp;amp;b a&amp;b") << NoError;
459 
460  dict.clear();
461  dict.insert(QStringLiteral("a"), QStringLiteral("1.42"));
462  dict.insert(QStringLiteral("b"),
463  QVariant::fromValue(markSafe(QStringLiteral("1.42"))));
464 
465  QTest::newRow("filter-floatformat01")
466  << QStringLiteral("{% autoescape off %}{{ a|floatformat }} {{ "
467  "b|floatformat }}{% endautoescape %}")
468  << dict << QStringLiteral("1.4 1.4") << NoError;
469 
470  dict.clear();
471  dict.insert(QStringLiteral("a"), QStringLiteral("1.42"));
472  dict.insert(QStringLiteral("b"),
473  QVariant::fromValue(markSafe(QStringLiteral("1.42"))));
474 
475  QTest::newRow("filter-floatformat02")
476  << QStringLiteral("{{ a|floatformat }} {{ b|floatformat }}") << dict
477  << QStringLiteral("1.4 1.4") << NoError;
478 
479  dict.clear();
480  dict.insert(QStringLiteral("a"), double(1234.54321));
481  dict.insert(QStringLiteral("b"), int(1234));
482 
483  QTest::newRow("filter-floatformat03")
484  << QStringLiteral("{{ a|floatformat }} {{ b|floatformat }}") << dict
485  << QStringLiteral("1234.5 1234.0") << NoError;
486  QTest::newRow("filter-floatformat04")
487  << QStringLiteral("{{ a|floatformat:2 }} {{ b|floatformat:2 }}") << dict
488  << QStringLiteral("1234.54 1234.00") << NoError;
489  QTest::newRow("filter-floatformat04")
490  << QStringLiteral("{{ a|floatformat:0 }} {{ b|floatformat:0 }}") << dict
491  << QStringLiteral("1235 1234") << NoError;
492 
493  // The contents of "linenumbers" is escaped according to the current
494  // autoescape setting.
495 
496  dict.clear();
497  dict.insert(QStringLiteral("a"), QStringLiteral("one\n<two>\nthree"));
498  dict.insert(
499  QStringLiteral("b"),
500  QVariant::fromValue(markSafe(QStringLiteral("one\n&lt;two&gt;\nthree"))));
501 
502  QTest::newRow("filter-linenumbers01")
503  << QStringLiteral("{{ a|linenumbers }} {{ b|linenumbers }}") << dict
504  << "1. one\n2. &lt;two&gt;\n3. three 1. one\n2. &lt;two&gt;\n3. three"
505  << NoError;
506 
507  dict.clear();
508  dict.insert(QStringLiteral("a"), QStringLiteral("one\n<two>\nthree"));
509  dict.insert(
510  QStringLiteral("b"),
511  QVariant::fromValue(markSafe(QStringLiteral("one\n&lt;two&gt;\nthree"))));
512  QTest::newRow("filter-linenumbers02")
513  << QStringLiteral("{% autoescape off %}{{ a|linenumbers }} {{ "
514  "b|linenumbers }}{% endautoescape %}")
515  << dict << "1. one\n2. <two>\n3. three 1. one\n2. &lt;two&gt;\n3. three"
516  << NoError;
517 
518  dict.clear();
519  dict.insert(QStringLiteral("a"), QStringLiteral("Apple & banana"));
520  dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(
521  QStringLiteral("Apple &amp; banana"))));
522 
523  QTest::newRow("filter-lower01") << QStringLiteral(
524  "{% autoescape off %}{{ a|lower }} {{ b|lower }}{% endautoescape %}")
525  << dict
526  << QStringLiteral(
527  "apple & banana apple &amp; banana")
528  << NoError;
529 
530  dict.clear();
531  dict.insert(QStringLiteral("a"), QStringLiteral("Apple & banana"));
532  dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(
533  QStringLiteral("Apple &amp; banana"))));
534 
535  QTest::newRow("filter-lower02")
536  << QStringLiteral("{{ a|lower }} {{ b|lower }}") << dict
537  << QStringLiteral("apple &amp; banana apple &amp; banana") << NoError;
538 
539  // The make_list filter can destroy existing escaping, so the results are
540  // escaped.
541 
542  dict.clear();
543  dict.insert(QStringLiteral("a"), markSafe(QStringLiteral("&")));
544 
545  QTest::newRow("filter-make_list01") << QStringLiteral(
546  "{% autoescape off %}{{ a|make_list }}{% endautoescape %}")
547  << dict << "[u\'&\']" << NoError;
548  QTest::newRow("filter-make_list02")
549  << QStringLiteral("{{ a|make_list }}") << dict
550  << QStringLiteral("[u&#39;&amp;&#39;]") << NoError;
551 
552  QTest::newRow("filter-make_list03") << QStringLiteral(
553  "{% autoescape off %}{{ a|make_list|stringformat:\"%1\"|safe }}{% "
554  "endautoescape %}") << dict << QStringLiteral("[u\'&\']")
555  << NoError;
556  QTest::newRow("filter-make_list04")
557  << QStringLiteral("{{ a|make_list|stringformat:\"%1\"|safe }}") << dict
558  << QStringLiteral("[u\'&\']") << NoError;
559 
560  // Running slugify on a pre-escaped string leads to odd behaviour,
561  // but the result is still safe.
562 
563  dict.clear();
564  dict.insert(QStringLiteral("a"), QStringLiteral("a & b"));
565  dict.insert(QStringLiteral("b"), markSafe(QStringLiteral("a &amp; b")));
566 
567  QTest::newRow("filter-slugify01") << QStringLiteral(
568  "{% autoescape off %}{{ a|slugify }} {{ b|slugify }}{% endautoescape %}")
569  << dict << QStringLiteral("a-b a-amp-b")
570  << NoError;
571  QTest::newRow("filter-slugify02")
572  << QStringLiteral("{{ a|slugify }} {{ b|slugify }}") << dict
573  << QStringLiteral("a-b a-amp-b") << NoError;
574 
575  dict.clear();
576  dict.insert(QStringLiteral("a"), QStringLiteral("Schöne Grüße"));
577 
578  QTest::newRow("filter-slugify03") << QStringLiteral("{{ a|slugify }}") << dict
579  << QStringLiteral("schone-grue") << NoError;
580 
581  dict.clear();
582  dict.insert(
583  QStringLiteral("a"),
584  QStringLiteral("testing\r\njavascript \'string\" <b>escaping</b>"));
585  QTest::newRow("escapejs01")
586  << QStringLiteral("{{ a|escapejs }}") << dict
587  << "testing\\u000D\\u000Ajavascript \\u0027string\\u0022 "
588  "\\u003Cb\\u003Eescaping\\u003C/b\\u003E"
589  << NoError;
590  QTest::newRow("escapejs02")
591  << QStringLiteral(
592  "{% autoescape off %}{{ a|escapejs }}{% endautoescape %}")
593  << dict
594  << "testing\\u000D\\u000Ajavascript \\u0027string\\u0022 "
595  "\\u003Cb\\u003Eescaping\\u003C/b\\u003E"
596  << NoError;
597 
598  // Notice that escaping is applied *after* any filters, so the string
599  // formatting here only needs to deal with pre-escaped characters.
600 
601  dict.clear();
602  dict.insert(QStringLiteral("a"), QStringLiteral("a<b"));
603  dict.insert(QStringLiteral("b"),
604  QVariant::fromValue(markSafe(QStringLiteral("a<b"))));
605 
606  QTest::newRow("filter-stringformat01")
607  << "{% autoescape off %}.{{ a|stringformat:\"%1\" }}. .{{ "
608  "b|stringformat:\"%2\" }}.{% endautoescape %}"
609  << dict << QStringLiteral(".a<b. .a<b.") << NoError;
610  QTest::newRow("filter-stringformat02")
611  << ".{{ a|stringformat:\"%1\" }}. .{{ b|stringformat:\"%2\" }}." << dict
612  << QStringLiteral(".a&lt;b. .a<b.") << NoError;
613  QTest::newRow("filter-stringformat03")
614  << ".{{ a|stringformat:\"foo %1 bar\" }}. .{{ b|stringformat:\"baz %2 "
615  "bat\" }}."
616  << dict << QStringLiteral(".foo a&lt;b bar. .baz a<b bat.") << NoError;
617 
618  dict.clear();
619  dict.insert(QStringLiteral("path"), QStringLiteral("www.cutelee.org"));
620  QTest::newRow("filter-stringformat04")
621  << "{% with path|stringformat:\"<a href=\\\"%1\\\">%1</a>\"|safe as "
622  "result %}{{ result }}{% endwith %}"
623  << dict << "<a href=\"www.cutelee.org\">www.cutelee.org</a>" << NoError;
624 
625  dict.clear();
626  dict.insert(QStringLiteral("a"), QStringLiteral("JOE\'S CRAB SHACK"));
627  QTest::newRow("filter-title01")
628  << "{{ a|title }}" << dict << QStringLiteral("Joe&#39;s Crab Shack")
629  << NoError;
630 
631  dict.clear();
632  dict.insert(QStringLiteral("a"), QStringLiteral("555 WEST 53RD STREET"));
633  QTest::newRow("filter-title02")
634  << "{{ a|title }}" << dict << QStringLiteral("555 West 53rd Street")
635  << NoError;
636 
637  dict.clear();
638  dict.insert(QStringLiteral("a"), QStringLiteral("alpha & bravo"));
639  dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(
640  QStringLiteral("alpha &amp; bravo"))));
641 
642  QTest::newRow("filter-truncatewords01")
643  << "{% autoescape off %}{{ a|truncatewords:\"2\" }} {{ "
644  "b|truncatewords:\"2\"}}{% endautoescape %}"
645  << dict << QStringLiteral("alpha & ... alpha &amp; ...") << NoError;
646 
647  QTest::newRow("filter-truncatewords02")
648  << "{{ a|truncatewords:\"2\" }} {{ b|truncatewords:\"2\"}}" << dict
649  << QStringLiteral("alpha &amp; ... alpha &amp; ...") << NoError;
650 
651  // The "upper" filter messes up entities (which are case-sensitive),
652  // so it's not safe for non-escaping purposes.
653 
654  dict.clear();
655  dict.insert(QStringLiteral("a"), QStringLiteral("a & b"));
656  dict.insert(QStringLiteral("b"),
657  QVariant::fromValue(markSafe(QStringLiteral("a &amp; b"))));
658 
659  QTest::newRow("filter-upper01") << QStringLiteral(
660  "{% autoescape off %}{{ a|upper }} {{ b|upper }}{% endautoescape %}")
661  << dict << QStringLiteral("A & B A &AMP; B")
662  << NoError;
663  QTest::newRow("filter-upper02")
664  << QStringLiteral("{{ a|upper }} {{ b|upper }}") << dict
665  << QStringLiteral("A &amp; B A &amp;AMP; B") << NoError;
666 
667  // // {"a": "http://example.com/?x=&y=", "b":
668  // mark_safe("http://example.com?x=&amp;y=")
669  // QTest::newRow( "filter-urlize01") << QString::fromLatin1( "{%
670  // autoescape
671  // off %}{{ a|urlize }} {{ b|urlize }}{% endautoescape %}" ) << dict <<
672  // "<a
673  // href=\"http://example.com/?x=&y=\"
674  // rel=\"nofollow\">http://example.com/?x=&y=</a> <a
675  // href=\"http://example.com?x=&amp;y=\"
676  // rel=\"nofollow\">http://example.com?x=&amp;y=</a>" << NoError;
677  //
678  // // {"a": "http://example.com/?x=&y=", "b":
679  // mark_safe("http://example.com?x=&amp;y=")
680  // QTest::newRow( "filter-urlize02") << QString::fromLatin1( "{{ a|urlize
681  // }}
682  // {{ b|urlize }}" ) << dict << "<a href=\"http://example.com/?x=&amp;y=\"
683  // rel=\"nofollow\">http://example.com/?x=&amp;y=</a> <a
684  // href=\"http://example.com?x=&amp;y=\"
685  // rel=\"nofollow\">http://example.com?x=&amp;y=</a>" << NoError;
686  //
687  // // {"a": mark_safe("a &amp; b")
688  // QTest::newRow( "filter-urlize03") << QString::fromLatin1( "{%
689  // autoescape
690  // off %}{{ a|urlize }}{% endautoescape %}" ) << dict <<
691  // QString::fromLatin1( "a &amp; b" ) << NoError;
692  //
693  // // {"a": mark_safe("a &amp; b")
694  // QTest::newRow( "filter-urlize04") << QString::fromLatin1( "{{ a|urlize
695  // }}" ) << dict << QString::fromLatin1( "a &amp; b" ) << NoError;
696  //
697  // // This will lead to a nonsense result, but at least it won't be
698  //
699  // // exploitable for XSS purposes when auto-escaping is on.
700  //
701  // // {"a": "<script>alert(\"foo\")</script>"
702  // QTest::newRow( "filter-urlize05") << QString::fromLatin1( "{%
703  // autoescape
704  // off %}{{ a|urlize }}{% endautoescape %}" ) << dict <<
705  // "<script>alert(\"foo\")</script>" << NoError;
706  //
707  // // {"a": "<script>alert(\"foo\")</script>"
708  // QTest::newRow( "filter-urlize06") << QString::fromLatin1( "{{ a|urlize
709  // }}" ) << dict << QString::fromLatin1(
710  // "&lt;script&gt;alert(&#39;foo&#39;)&lt;/script&gt;" ) << NoError;
711  //
712  // // mailto: testing for urlize
713  //
714  // // {"a": "Email me at me@example.com"
715  // QTest::newRow( "filter-urlize07") << QString::fromLatin1( "{{ a|urlize
716  // }}" ) << dict << "Email me at <a
717  // href=\"mailto:me@example.com\">me@example.com</a>" << NoError;
718  //
719  // // {"a": "Email me at <me@example.com>"
720  // QTest::newRow( "filter-urlize08") << QString::fromLatin1( "{{ a|urlize
721  // }}" ) << dict << "Email me at &lt;<a
722  // href=\"mailto:me@example.com\">me@example.com</a>&gt;" << NoError;
723  //
724  // // {"a": "\"Unsafe\" http://example.com/x=&y=", "b":
725  // mark_safe("&quot;Safe&quot; http://example.com?x=&amp;y=")
726  // QTest::newRow( "filter-urlizetrunc01") << "{% autoescape off %}{{
727  // a|urlizetrunc:\"8\" }} {{ b|urlizetrunc:\"8\" }}{% endautoescape %}" <<
728  // dict << "\"Unsafe\" <a href=\"http://example.com/x=&y=\"
729  // rel=\"nofollow\">http:...</a> &quot;Safe&quot; <a
730  // href=\"http://example.com?x=&amp;y=\" rel=\"nofollow\">http:...</a>" <<
731  // NoError;
732  //
733  // // {"a": "\"Unsafe\" http://example.com/x=&y=", "b":
734  // mark_safe("&quot;Safe&quot; http://example.com?x=&amp;y=")
735  // QTest::newRow( "filter-urlizetrunc02") << "{{ a|urlizetrunc:\"8\" }} {{
736  // b|urlizetrunc:\"8\" }}" << dict << "&quot;Unsafe&quot; <a
737  // href=\"http://example.com/x=&amp;y=\" rel=\"nofollow\">http:...</a>
738  // &quot;Safe&quot; <a href=\"http://example.com?x=&amp;y=\"
739  // rel=\"nofollow\">http:...</a>" << NoError;
740 
741  // // Ensure iriencode keeps safe strings:
742  //
743  // // {"url": "?test=1&me=2"
744  // QTest::newRow( "filter-iriencode01") << QString::fromLatin1( "{{
745  // url|iriencode }}" ) << dict << QString::fromLatin1( "?test=1&amp;me=2"
746  // )
747  // << NoError;
748  //
749  // // {"url": "?test=1&me=2"
750  // QTest::newRow( "filter-iriencode02") << QString::fromLatin1( "{%
751  // autoescape off %}{{ url|iriencode }}{% endautoescape %}" ) << dict <<
752  // QString::fromLatin1( "?test=1&me=2" ) << NoError;
753  //
754  // // {"url": mark_safe("?test=1&me=2")
755  // QTest::newRow( "filter-iriencode03") << QString::fromLatin1( "{{
756  // url|iriencode }}" ) << dict << QString::fromLatin1( "?test=1&me=2" ) <<
757  // NoError;
758  //
759  // // {"url": mark_safe("?test=1&me=2")
760  // QTest::newRow( "filter-iriencode04") << QString::fromLatin1( "{%
761  // autoescape off %}{{ url|iriencode }}{% endautoescape %}" ) << dict <<
762  // QString::fromLatin1( "?test=1&me=2" ) << NoError;
763  //
764  dict.clear();
765  dict.insert(QStringLiteral("a"), QStringLiteral("a & b"));
766  dict.insert(QStringLiteral("b"),
767  QVariant::fromValue(markSafe(QStringLiteral("a &amp; b"))));
768 
769  QTest::newRow("filter-wordcount01")
770  << QStringLiteral("{% autoescape off %}{{ a|wordcount }} {{ b|wordcount "
771  "}}{% endautoescape %}")
772  << dict << QStringLiteral("3 3") << NoError;
773 
774  QTest::newRow("filter-wordcount02")
775  << QStringLiteral("{{ a|wordcount }} {{ b|wordcount }}") << dict
776  << QStringLiteral("3 3") << NoError;
777 
778  dict.clear();
779  dict.insert(QStringLiteral("a"), QStringLiteral("a & b"));
780  dict.insert(QStringLiteral("b"),
781  QVariant::fromValue(markSafe(QStringLiteral("a & b"))));
782 
783  QTest::newRow("filter-wordwrap01")
784  << QStringLiteral("{% autoescape off %}{{ a|wordwrap:3 }} {{ "
785  "b|wordwrap:3 }}{% endautoescape %}")
786  << dict << "a &\nb a &\nb" << NoError;
787 
788  QTest::newRow("filter-wordwrap02")
789  << QStringLiteral("{{ a|wordwrap:3 }} {{ b|wordwrap:3 }}") << dict
790  << "a &amp;\nb a &\nb" << NoError;
791 
792  dict.clear();
793  QTest::newRow("filter-wordwrap03")
794  << QStringLiteral("{{xx|wordwrap}}") << dict << "" << NoError;
795 
796  QTest::newRow("filter-wordwrap04")
797  << QStringLiteral("{{|wordwrap}}") << dict << "" << NoError;
798 
799  dict.clear();
800  dict.insert(QStringLiteral("a"), QStringLiteral("a&b"));
801  dict.insert(QStringLiteral("b"),
802  QVariant::fromValue(markSafe(QStringLiteral("a&b"))));
803 
804  QTest::newRow("filter-ljust01")
805  << "{% autoescape off %}.{{ a|ljust:\"5\" }}. .{{ b|ljust:\"5\" }}.{% "
806  "endautoescape %}"
807  << dict << QStringLiteral(".a&b . .a&b .") << NoError;
808 
809  QTest::newRow("filter-ljust02")
810  << ".{{ a|ljust:\"5\" }}. .{{ b|ljust:\"5\" }}." << dict
811  << QStringLiteral(".a&amp;b . .a&b .") << NoError;
812 
813  QTest::newRow("filter-rjust01")
814  << "{% autoescape off %}.{{ a|rjust:\"5\" }}. .{{ b|rjust:\"5\" }}.{% "
815  "endautoescape %}"
816  << dict << QStringLiteral(". a&b. . a&b.") << NoError;
817 
818  QTest::newRow("filter-rjust02")
819  << ".{{ a|rjust:\"5\" }}. .{{ b|rjust:\"5\" }}." << dict
820  << QStringLiteral(". a&amp;b. . a&b.") << NoError;
821 
822  QTest::newRow("filter-center01")
823  << "{% autoescape off %}.{{ a|center:\"5\" }}. .{{ b|center:\"5\" "
824  "}}.{% "
825  "endautoescape %}"
826  << dict << QStringLiteral(". a&b . . a&b .") << NoError;
827 
828  QTest::newRow("filter-center02")
829  << ".{{ a|center:\"5\" }}. .{{ b|center:\"5\" }}." << dict
830  << QStringLiteral(". a&amp;b . . a&b .") << NoError;
831 
832  dict.clear();
833  dict.insert(QStringLiteral("a"), QStringLiteral("x&y"));
834  dict.insert(QStringLiteral("b"),
835  QVariant::fromValue(markSafe(QStringLiteral("x&amp;y"))));
836 
837  QTest::newRow("filter-cut01")
838  << "{% autoescape off %}{{ a|cut:\"x\" }} {{ "
839  "b|cut:\"x\" }}{% endautoescape %}"
840  << dict << QStringLiteral("&y &amp;y") << NoError;
841  QTest::newRow("filter-cut02") << "{{ a|cut:\"x\" }} {{ b|cut:\"x\" }}" << dict
842  << QStringLiteral("&amp;y &amp;y") << NoError;
843  QTest::newRow("filter-cut03")
844  << "{% autoescape off %}{{ a|cut:\"&\" }} {{ "
845  "b|cut:\"&\" }}{% endautoescape %}"
846  << dict << QStringLiteral("xy xamp;y") << NoError;
847  QTest::newRow("filter-cut04") << "{{ a|cut:\"&\" }} {{ b|cut:\"&\" }}" << dict
848  << QStringLiteral("xy xamp;y") << NoError;
849 
850  // Passing ";" to cut can break existing HTML entities, so those strings
851  // are auto-escaped.
852 
853  QTest::newRow("filter-cut05")
854  << "{% autoescape off %}{{ a|cut:\";\" }} {{ "
855  "b|cut:\";\" }}{% endautoescape %}"
856  << dict << QStringLiteral("x&y x&ampy") << NoError;
857  QTest::newRow("filter-cut06")
858  << "{{ a|cut:\";\" }} {{ b|cut:\";\" }}" << dict
859  << QStringLiteral("x&amp;y x&amp;ampy") << NoError;
860 
861  // The "escape" filter works the same whether autoescape is on or off,
862  // but it has no effect on strings already marked as safe.
863 
864  dict.clear();
865  dict.insert(QStringLiteral("a"), QStringLiteral("x&y"));
866  dict.insert(QStringLiteral("b"),
867  QVariant::fromValue(markSafe(QStringLiteral("x&y"))));
868 
869  QTest::newRow("filter-escape01")
870  << QStringLiteral("{{ a|escape }} {{ b|escape }}") << dict
871  << QStringLiteral("x&amp;y x&y") << NoError;
872  QTest::newRow("filter-escape02") << QStringLiteral(
873  "{% autoescape off %}{{ a|escape }} {{ b|escape }}{% endautoescape %}")
874  << dict << QStringLiteral("x&amp;y x&y")
875  << NoError;
876 
877  // It is only applied once, regardless of the number of times it
878  // appears in a chain.
879 
880  dict.clear();
881  dict.insert(QStringLiteral("a"), QStringLiteral("x&y"));
882 
883  QTest::newRow("filter-escape03")
884  << QStringLiteral(
885  "{% autoescape off %}{{ a|escape|escape }}{% endautoescape %}")
886  << dict << QStringLiteral("x&amp;y") << NoError;
887  QTest::newRow("filter-escape04")
888  << QStringLiteral("{{ a|escape|escape }}") << dict
889  << QStringLiteral("x&amp;y") << NoError;
890 
891 
892  // Force_escape is applied immediately. It can be used to provide
893  // double-escaping, for example.
894 
895  QTest::newRow("filter-force-escape01")
896  << QStringLiteral(
897  "{% autoescape off %}{{ a|force_escape }}{% endautoescape %}")
898  << dict << QStringLiteral("x&amp;y") << NoError;
899  QTest::newRow("filter-force-escape02")
900  << QStringLiteral("{{ a|force_escape }}") << dict
901  << QStringLiteral("x&amp;y") << NoError;
902 
903  QTest::newRow("filter-force-escape03")
904  << QStringLiteral("{% autoescape off %}{{ a|force_escape|force_escape "
905  "}}{% endautoescape %}")
906  << dict << QStringLiteral("x&amp;amp;y") << NoError;
907  QTest::newRow("filter-force-escape04")
908  << QStringLiteral("{{ a|force_escape|force_escape }}") << dict
909  << QStringLiteral("x&amp;amp;y") << NoError;
910 
911  // Because the result of force_escape is "safe", an additional
912  // escape filter has no effect.
913 
914  QTest::newRow("filter-force-escape05") << QStringLiteral(
915  "{% autoescape off %}{{ a|force_escape|escape }}{% endautoescape %}")
916  << dict << QStringLiteral("x&amp;y")
917  << NoError;
918  QTest::newRow("filter-force-escape06")
919  << QStringLiteral("{{ a|force_escape|escape }}") << dict
920  << QStringLiteral("x&amp;y") << NoError;
921  QTest::newRow("filter-force-escape07") << QStringLiteral(
922  "{% autoescape off %}{{ a|escape|force_escape }}{% endautoescape %}")
923  << dict << QStringLiteral("x&amp;y")
924  << NoError;
925  QTest::newRow("filter-force-escape08")
926  << QStringLiteral("{{ a|escape|force_escape }}") << dict
927  << QStringLiteral("x&amp;y") << NoError;
928 
929  // The contents in "linebreaks" and "linebreaksbr" are escaped
930  // according to the current autoescape setting.
931 
932  dict.clear();
933  dict.insert(QStringLiteral("a"), QStringLiteral("x&\ny"));
934  dict.insert(QStringLiteral("b"), markSafe(QStringLiteral("x&\ny")));
935 
936  QTest::newRow("filter-linebreaks01")
937  << QStringLiteral("{{ a|linebreaks }} {{ b|linebreaks }}") << dict
938  << QStringLiteral("<p>x&amp;<br />y</p> <p>x&<br />y</p>") << NoError;
939  QTest::newRow("filter-linebreaks02")
940  << QStringLiteral("{% autoescape off %}{{ a|linebreaks }} {{ "
941  "b|linebreaks }}{% endautoescape %}")
942  << dict << QStringLiteral("<p>x&<br />y</p> <p>x&<br />y</p>") << NoError;
943  QTest::newRow("filter-linebreaksbr01")
944  << QStringLiteral("{{ a|linebreaksbr }} {{ b|linebreaksbr }}") << dict
945  << QStringLiteral("x&amp;<br />y x&<br />y") << NoError;
946  QTest::newRow("filter-linebreaksbr02")
947  << QStringLiteral("{% autoescape off %}{{ a|linebreaksbr }} {{ "
948  "b|linebreaksbr }}{% endautoescape %}")
949  << dict << QStringLiteral("x&<br />y x&<br />y") << NoError;
950 
951  dict.clear();
952  dict.insert(QStringLiteral("a"), QStringLiteral("<b>hello</b>"));
953 
954  QTest::newRow("filter-safe01")
955  << QStringLiteral("{{ a }} -- {{ a|safe }}") << dict
956  << QStringLiteral("&lt;b&gt;hello&lt;/b&gt; -- <b>hello</b>") << NoError;
957  QTest::newRow("filter-safe02")
958  << QStringLiteral(
959  "{% autoescape off %}{{ a }} -- {{ a|safe }}{% endautoescape %}")
960  << dict << QStringLiteral("<b>hello</b> -- <b>hello</b>") << NoError;
961 
962  dict.clear();
963  dict.insert(QStringLiteral("a"),
964  QVariantList{QStringLiteral("&"), QStringLiteral("<")});
965 
966  QTest::newRow("filter-safeseq01")
967  << "{{ a|join:\", \" }} -- {{ a|safeseq|join:\", \" }}" << dict
968  << QStringLiteral("&amp;, &lt; -- &, <") << NoError;
969  QTest::newRow("filter-safeseq02")
970  << "{% autoescape off %}{{ a|join:\", \" "
971  "}} -- {{ a|safeseq|join:\", \" "
972  "}}{% endautoescape %}"
973  << dict << QStringLiteral("&, < -- &, <") << NoError;
974 
975  dict.clear();
976  dict.insert(QStringLiteral("a"), QStringLiteral("<a>x</a> <p><b>y</b></p>"));
977  dict.insert(QStringLiteral("b"), QVariant::fromValue(markSafe(QStringLiteral(
978  "<a>x</a> <p><b>y</b></p>"))));
979 
980  QTest::newRow("filter-removetags01")
981  << "{{ a|removetags:\"a b\" }} {{ b|removetags:\"a b\" }}" << dict
982  << QStringLiteral("x &lt;p&gt;y&lt;/p&gt; x <p>y</p>") << NoError;
983  QTest::newRow("filter-removetags02")
984  << "{% autoescape off %}{{ a|removetags:\"a b\" }} {{ b|removetags:\"a "
985  "b\" }}{% endautoescape %}"
986  << dict << QStringLiteral("x <p>y</p> x <p>y</p>") << NoError;
987  QTest::newRow("filter-striptags01")
988  << QStringLiteral("{{ a|striptags }} {{ b|striptags }}") << dict
989  << QStringLiteral("x y x y") << NoError;
990  QTest::newRow("filter-striptags02")
991  << QStringLiteral("{% autoescape off %}{{ a|striptags }} {{ b|striptags "
992  "}}{% endautoescape %}")
993  << dict << QStringLiteral("x y x y") << NoError;
994 
995  dict.clear();
996  dict.insert(QStringLiteral("fs_int_mib"), 1048576);
997 
998  QTest::newRow("filter-filesizeformat01")
999  << QStringLiteral("{{ fs_int_mib|filesizeformat }}") << dict
1000  << QStringLiteral("1.05 MB") << NoError;
1001 
1002  QTest::newRow("filter-filesizeformat02")
1003  << QStringLiteral("{{ fs_int_mib|filesizeformat:\"2\" }}") << dict
1004  << QStringLiteral("1.00 MiB") << NoError;
1005 
1006  QTest::newRow("filter-filesizeformat03")
1007  << QStringLiteral("{{ fs_int_mib|filesizeformat:\"10,3\" }}") << dict
1008  << QStringLiteral("1.049 MB") << NoError;
1009 
1010  QTest::newRow("filter-filesizeformat04")
1011  << QStringLiteral("{{ fs_int_mib|filesizeformat:\"10,2,1024\" }}") << dict
1012  << QStringLiteral("1.07 GB") << NoError;
1013 
1014  dict.clear();
1015  dict.insert(QStringLiteral("fs_float_mib"), 1024.5);
1016 
1017  QTest::newRow("filter-filesizeformat05")
1018  << QStringLiteral("{{ fs_float_mib|filesizeformat:\"10,2,1024\" }}") << dict
1019  << QStringLiteral("1.05 MB") << NoError;
1020 
1021  dict.clear();
1022  dict.insert(QStringLiteral("fs_string_mib"), QStringLiteral("1024.5"));
1023 
1024  QTest::newRow("filter-filesizeformat06")
1025  << QStringLiteral("{{ fs_string_mib|filesizeformat:\"10,2,1024\" }}") << dict
1026  << QStringLiteral("1.05 MB") << NoError;
1027 
1028  dict.clear();
1029  dict.insert(QStringLiteral("fs_bytes"), 999);
1030  dict.insert(QStringLiteral("fs_kb"), 1000);
1031  dict.insert(QStringLiteral("fs_10kb"), 10 * 1000);
1032  dict.insert(QStringLiteral("fs_1000kb"), 1000 * 1000 -1);
1033  dict.insert(QStringLiteral("fs_mb"), 1000 * 1000);
1034  dict.insert(QStringLiteral("fs_50mb"), 1000 * 1000 * 50);
1035  dict.insert(QStringLiteral("fs_1000mb"), 1000 * 1000 * 1000 - 1);
1036  dict.insert(QStringLiteral("fs_gb"), 1000 * 1000 * 1000);
1037  dict.insert(QStringLiteral("fs_tb"), static_cast<double>(1000.0 * 1000.0 * 1000.0 * 1000.0));
1038  dict.insert(QStringLiteral("fs_pb"), static_cast<double>(1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0));
1039  dict.insert(QStringLiteral("fs_eb"), static_cast<double>(1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 2000.0));
1040  dict.insert(QStringLiteral("fs_zb"), static_cast<double>(1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0));
1041  dict.insert(QStringLiteral("fs_yb"), static_cast<double>(1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0));
1042  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));
1043  dict.insert(QStringLiteral("fs_0b1"), 0.1);
1044  dict.insert(QStringLiteral("fs_0b2"), QString(QChar(0x03B1)));
1045  dict.insert(QStringLiteral("fs_neg_1"), -100);
1046  dict.insert(QStringLiteral("fs_neg_2"), -1000 * 1000 *50);
1047 
1048  // fixes tests on MSVC2013
1049  QString fsInput;
1050  fsInput = QStringLiteral("{{ fs_bytes|filesizeformat }} {{ fs_kb|filesizeformat }} {{ fs_10kb|filesizeformat }} {{ fs_1000kb|filesizeformat }} ");
1051  fsInput += QStringLiteral("{{ fs_mb|filesizeformat }} {{ fs_50mb|filesizeformat }} {{ fs_1000mb|filesizeformat }} {{ fs_gb|filesizeformat }} ");
1052  fsInput += QStringLiteral("{{ fs_tb|filesizeformat }} {{ fs_pb|filesizeformat }} {{ fs_eb|filesizeformat }} {{ fs_zb|filesizeformat }} ");
1053  fsInput += QStringLiteral("{{ fs_yb|filesizeformat }} {{ fs_2000yb|filesizeformat }} {{ fs_0b1|filesizeformat }} {{ fs_0b2|filesizeformat }} ");
1054  fsInput += QStringLiteral("{{ fs_neg_1|filesizeformat }} {{ fs_neg_2|filesizeformat }}");
1055 
1056  QString fsExpect;
1057  fsExpect = QStringLiteral("999 bytes 1.00 KB 10.00 KB 1000.00 KB ");
1058  fsExpect += QStringLiteral("1.00 MB 50.00 MB 1000.00 MB 1.00 GB ");
1059  fsExpect += QStringLiteral("1.00 TB 1.00 PB 2.00 EB 1.00 ZB ");
1060  fsExpect += QStringLiteral("1.00 YB 2000.00 YB 0 bytes 0 bytes ");
1061  fsExpect += QStringLiteral("-100 bytes -50.00 MB");
1062 
1063  QTest::newRow("filter-filesizeformat07")
1064  << fsInput
1065  << dict
1066  << fsExpect
1067  << NoError;
1068 
1069  const QString jsonSK = QStringLiteral("jsondata"); // stash/dict key
1070  const QString jsonObjectExpect = QStringLiteral(R"(<script id="hello-data" type="application/json">{"hello":"world"}</script>)");
1071  const QString jsonArrayExpect = QStringLiteral(R"(<script id="hello-data" type="application/json">["hello","world"]</script>)");
1072 
1073  const QString jsonInput = QStringLiteral(R"({{ jsondata|json_script:"hello-data" }})");
1074 
1075  const QJsonObject jsonObject{{QStringLiteral("hello"),QStringLiteral("world")}};
1076  dict.clear();
1077  dict.insert(jsonSK, jsonObject);
1078  QTest::newRow("filter-json_script-JsonObj") << jsonInput << dict << jsonObjectExpect << NoError;
1079 
1080  const QJsonDocument jsonDoc(jsonObject);
1081  dict.clear();
1082  dict.insert(jsonSK, jsonDoc);
1083  QTest::newRow("filter-json_script-JsonDoc") << jsonInput << dict << jsonObjectExpect << NoError;
1084 
1085  const QJsonArray jsonArray{QStringLiteral("hello"), QStringLiteral("world")};
1086  dict.clear();
1087  dict.insert(jsonSK, jsonArray);
1088  QTest::newRow("filter-json_script-JsonArr") << jsonInput << dict << jsonArrayExpect << NoError;
1089 
1090  const QVariantHash jsonVarHash{{QStringLiteral("hello"),QStringLiteral("world")}};
1091  dict.clear();
1092  dict.insert(jsonSK, jsonVarHash);
1093  QTest::newRow("filter-json_script-VarHash") << jsonInput << dict << jsonObjectExpect << NoError;
1094 
1095  const QVariantMap jsonVarMap{{QStringLiteral("hello"),QStringLiteral("world")}};
1096  dict.clear();
1097  dict.insert(jsonSK, jsonVarMap);
1098  QTest::newRow("filter-json_script-VarMap") << jsonInput << dict << jsonObjectExpect << NoError;
1099 
1100  const QVariantList jsonVarList{QStringLiteral("hello"), QStringLiteral("world")};
1101  dict.clear();
1102  dict.insert(jsonSK, jsonVarList);
1103  QTest::newRow("filter-json_script-VarList") << jsonInput << dict << jsonArrayExpect << NoError;
1104 
1105  const QStringList jsonStringList{QStringLiteral("hello"), QStringLiteral("world")};
1106  dict.clear();
1107  dict.insert(jsonSK, jsonStringList);
1108  QTest::newRow("filter-json_script-StringList") << jsonInput << dict << jsonArrayExpect << NoError;
1109 
1110  const QJsonObject jsonEscapeInput{{QStringLiteral("hello"),QStringLiteral("world</script>&amp;")}};
1111  const QString jsonEscapeExpect = QStringLiteral(R"(<script id="hello-data" type="application/json">{"hello":"world\\u003C/script\\u003E\\u0026amp;"}</script>)");
1112  dict.clear();
1113  dict.insert(jsonSK, jsonEscapeInput);
1114  QTest::newRow("filter-json_script-escape") << jsonInput << dict << jsonEscapeExpect << NoError;
1115 }
1116 
1117 void TestFilters::testListFilters_data()
1118 {
1119  QTest::addColumn<QString>("input");
1120  QTest::addColumn<Dict>("dict");
1121  QTest::addColumn<QString>("output");
1122  QTest::addColumn<Cutelee::Error>("error");
1123 
1124  Dict dict;
1125 
1126  dict.insert(QStringLiteral("a"),
1127  QVariantList{QStringLiteral("a&b"), QStringLiteral("x")});
1128  dict.insert(QStringLiteral("b"),
1129  QVariantList{QVariant::fromValue(markSafe(QStringLiteral("a&b"))),
1130  QStringLiteral("x")});
1131 
1132  QTest::newRow("filter-first01")
1133  << QStringLiteral("{{ a|first }} {{ b|first }}") << dict
1134  << QStringLiteral("a&amp;b a&b") << NoError;
1135  QTest::newRow("filter-first02") << QStringLiteral(
1136  "{% autoescape off %}{{ a|first }} {{ b|first }}{% endautoescape %}")
1137  << dict << QStringLiteral("a&b a&b")
1138  << NoError;
1139 
1140  dict.clear();
1141  dict.insert(QStringLiteral("a"),
1142  QVariantList{QStringLiteral("x"), QStringLiteral("a&b")});
1143  dict.insert(QStringLiteral("b"),
1144  QVariantList{QStringLiteral("x"), QVariant::fromValue(markSafe(
1145  QStringLiteral("a&b")))});
1146 
1147  QTest::newRow("filter-last01")
1148  << QStringLiteral("{{ a|last }} {{ b|last }}") << dict
1149  << QStringLiteral("a&amp;b a&b") << NoError;
1150  QTest::newRow("filter-last02")
1151  << QStringLiteral(
1152  "{% autoescape off %}{{ a|last }} {{ b|last }}{% endautoescape %}")
1153  << dict << QStringLiteral("a&b a&b") << NoError;
1154 
1155  dict.clear();
1156  dict.insert(QStringLiteral("a"),
1157  QVariantList{QStringLiteral("a&b"), QStringLiteral("a&b")});
1158  dict.insert(QStringLiteral("b"),
1159  QVariantList()
1160  << QVariant::fromValue(markSafe(QStringLiteral("a&b")))
1161  << QVariant::fromValue(markSafe(QStringLiteral("a&b"))));
1162  QTest::newRow("filter-random01")
1163  << QStringLiteral("{{ a|random }} {{ b|random }}") << dict
1164  << QStringLiteral("a&amp;b a&b") << NoError;
1165  QTest::newRow("filter-random02") << QStringLiteral(
1166  "{% autoescape off %}{{ a|random }} {{ b|random }}{% endautoescape %}")
1167  << dict << QStringLiteral("a&b a&b")
1168  << NoError;
1169 
1170  dict.clear();
1171  dict.insert(QStringLiteral("empty_list"), QVariantList());
1172  QTest::newRow("filter-random03") << QStringLiteral("{{empty_list|random}}")
1173  << dict << QStringLiteral("") << NoError;
1174  QTest::newRow("filter-random04")
1175  << QStringLiteral("{{|random}}") << dict << QStringLiteral("") << NoError;
1176 
1177  dict.clear();
1178  dict.insert(QStringLiteral("a"), QStringLiteral("a&b"));
1179  dict.insert(QStringLiteral("b"),
1180  QVariant::fromValue(markSafe(QStringLiteral("a&b"))));
1181 
1182  QTest::newRow("filter-slice01")
1183  << "{{ a|slice:\"1:3\" }} {{ b|slice:\"1:3\" }}" << dict
1184  << QStringLiteral("&amp;b &b") << NoError;
1185  QTest::newRow("filter-slice02")
1186  << "{% autoescape off %}{{ a|slice:\"1:3\" }} {{ b|slice:\"1:3\" }}{% "
1187  "endautoescape %}"
1188  << dict << QStringLiteral("&b &b") << NoError;
1189 
1190  dict.clear();
1191  QTest::newRow("filter-slice03")
1192  << "{{xx|slice}}" << dict << QStringLiteral("") << NoError;
1193  QTest::newRow("filter-slice04")
1194  << "{{|slice}}" << dict << QStringLiteral("") << NoError;
1195 
1196  dict.clear();
1197  QVariantList sublist{QStringLiteral("<y")};
1198  dict.insert(QStringLiteral("a"),
1199  QVariantList{QStringLiteral("x>"), QVariant(sublist)});
1200 
1201  QTest::newRow("filter-unordered_list01")
1202  << QStringLiteral("{{ a|unordered_list }}") << dict
1203  << "\t<li>x&gt;\n\t<ul>\n\t\t<li>&lt;y</li>\n\t</ul>\n\t</li>" << NoError;
1204  QTest::newRow("filter-unordered_list02")
1205  << QStringLiteral(
1206  "{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}")
1207  << dict << "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"
1208  << NoError;
1209 
1210  dict.clear();
1211  sublist = {markSafe(QStringLiteral("<y"))};
1212  dict.insert(QStringLiteral("a"),
1213  QVariantList{QStringLiteral("x>"), QVariant(sublist)});
1214 
1215  QTest::newRow("filter-unordered_list03")
1216  << QStringLiteral("{{ a|unordered_list }}") << dict
1217  << "\t<li>x&gt;\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>" << NoError;
1218  QTest::newRow("filter-unordered_list04")
1219  << QStringLiteral(
1220  "{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}")
1221  << dict << "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"
1222  << NoError;
1223 
1224  dict.clear();
1225  sublist = {QStringLiteral("<y")};
1226  dict.insert(QStringLiteral("a"),
1227  QVariantList{QStringLiteral("x>"), QVariant(sublist)});
1228 
1229  QTest::newRow("filter-unordered_list05")
1230  << QStringLiteral(
1231  "{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}")
1232  << dict << "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"
1233  << NoError;
1234 
1235  // length filter.
1236  dict.clear();
1237  dict.insert(
1238  QStringLiteral("list"),
1239  QVariantList{QStringLiteral("4"), QVariant(), true, QVariantHash()});
1240 
1241  QTest::newRow("length01") << QStringLiteral("{{ list|length }}") << dict
1242  << QStringLiteral("4") << NoError;
1243 
1244  dict.clear();
1245  dict.insert(QStringLiteral("list"), QVariantList());
1246 
1247  QTest::newRow("length02") << QStringLiteral("{{ list|length }}") << dict
1248  << QStringLiteral("0") << NoError;
1249 
1250  dict.clear();
1251  dict.insert(QStringLiteral("string"), QStringLiteral(""));
1252 
1253  QTest::newRow("length03") << QStringLiteral("{{ string|length }}") << dict
1254  << QStringLiteral("0") << NoError;
1255 
1256  dict.clear();
1257  dict.insert(QStringLiteral("string"), QStringLiteral("django"));
1258 
1259  QTest::newRow("length04") << QStringLiteral("{{ string|length }}") << dict
1260  << QStringLiteral("6") << NoError;
1261 
1262  // Invalid uses that should fail silently.
1263 
1264  dict.clear();
1265  dict.insert(QStringLiteral("int"), 7);
1266 
1267  QTest::newRow("length05")
1268  << QStringLiteral("{{ int|length }}") << dict << QString() << NoError;
1269 
1270  dict.clear();
1271  dict.insert(QStringLiteral("None"), QVariant());
1272 
1273  QTest::newRow("length06")
1274  << QStringLiteral("{{ None|length }}") << dict << QString() << NoError;
1275 
1276  // length_is filter.
1277 
1278  dict.clear();
1279  dict.insert(
1280  QStringLiteral("some_list"),
1281  QVariantList{QStringLiteral("4"), QVariant(), true, QVariantHash()});
1282 
1283  QTest::newRow("length_is01")
1284  << "{% if some_list|length_is:\"4\" %}Four{% endif %}" << dict
1285  << QStringLiteral("Four") << NoError;
1286 
1287  dict.clear();
1288  dict.insert(
1289  QStringLiteral("some_list"),
1290  QVariantList{QStringLiteral("4"), QVariant(), true, QVariantHash(), 17});
1291 
1292  QTest::newRow("length_is02")
1293  << "{% if some_list|length_is:\"4\" %}Four{% else %}Not Four{% endif %}"
1294  << dict << QStringLiteral("Not Four") << NoError;
1295 
1296  dict.clear();
1297  dict.insert(QStringLiteral("mystring"), QStringLiteral("word"));
1298 
1299  QTest::newRow("length_is03")
1300  << "{% if mystring|length_is:\"4\" %}Four{% endif %}" << dict
1301  << QStringLiteral("Four") << NoError;
1302 
1303  dict.clear();
1304  dict.insert(QStringLiteral("mystring"), QStringLiteral("Python"));
1305 
1306  QTest::newRow("length_is04")
1307  << "{% if mystring|length_is:\"4\" %}Four{% else %}Not Four{% endif %}"
1308  << dict << QStringLiteral("Not Four") << NoError;
1309 
1310  dict.clear();
1311  dict.insert(QStringLiteral("mystring"), QStringLiteral(""));
1312 
1313  QTest::newRow("length_is05")
1314  << "{% if mystring|length_is:\"4\" %}Four{% else %}Not Four{% endif %}"
1315  << dict << QStringLiteral("Not Four") << NoError;
1316 
1317  dict.clear();
1318  dict.insert(QStringLiteral("var"), QStringLiteral("django"));
1319 
1320  QTest::newRow("length_is06") << QStringLiteral(
1321  "{% with var|length as my_length %}{{ my_length }}{% endwith %}")
1322  << dict << QStringLiteral("6") << NoError;
1323 
1324  // Boolean return value from length_is should not be coerced to a string
1325 
1326  dict.clear();
1327  QTest::newRow("length_is07")
1328  << "{% if \"X\"|length_is:0 %}Length is 0{% "
1329  "else %}Length not 0{% endif %}"
1330  << dict << QStringLiteral("Length not 0") << NoError;
1331  QTest::newRow("length_is08")
1332  << "{% if \"X\"|length_is:1 %}Length is 1{% "
1333  "else %}Length not 1{% endif %}"
1334  << dict << QStringLiteral("Length is 1") << NoError;
1335 
1336  // Invalid uses that should fail silently.
1337 
1338  dict.clear();
1339  dict.insert(QStringLiteral("var"), QStringLiteral("django"));
1340 
1341  QTest::newRow("length_is09")
1342  << "{{ var|length_is:\"fish\" }}" << dict << QString() << NoError;
1343 
1344  dict.clear();
1345  dict.insert(QStringLiteral("int"), 7);
1346 
1347  QTest::newRow("length_is10")
1348  << "{{ int|length_is:\"1\" }}" << dict << QString() << NoError;
1349 
1350  dict.clear();
1351  dict.insert(QStringLiteral("none"), QVariant());
1352 
1353  QTest::newRow("length_is11")
1354  << "{{ none|length_is:\"1\" }}" << dict << QString() << NoError;
1355 
1356  dict.clear();
1357  dict.insert(QStringLiteral("a"), QVariantList{QStringLiteral("alpha"),
1358  QStringLiteral("beta & me")});
1359 
1360  QTest::newRow("join01") << "{{ a|join:\", \" }}" << dict
1361  << QStringLiteral("alpha, beta &amp; me") << NoError;
1362  QTest::newRow("join02")
1363  << "{% autoescape off %}{{ a|join:\", \" }}{% endautoescape %}" << dict
1364  << QStringLiteral("alpha, beta & me") << NoError;
1365  QTest::newRow("join03") << "{{ a|join:\" &amp; \" }}" << dict
1366  << QStringLiteral("alpha &amp; beta &amp; me")
1367  << NoError;
1368  QTest::newRow("join04")
1369  << "{% autoescape off %}{{ a|join:\" &amp; \" }}{% endautoescape %}"
1370  << dict << QStringLiteral("alpha &amp; beta & me") << NoError;
1371 
1372  // Test that joining with unsafe joiners don't result in unsafe strings
1373  // (#11377)
1374  dict.insert(QStringLiteral("var"), QStringLiteral(" & "));
1375  QTest::newRow("join05") << "{{ a|join:var }}" << dict
1376  << QStringLiteral("alpha &amp; beta &amp; me")
1377  << NoError;
1378  dict.insert(QStringLiteral("var"), Cutelee::markSafe(QStringLiteral(" & ")));
1379  QTest::newRow("join06") << "{{ a|join:var }}" << dict
1380  << QStringLiteral("alpha & beta &amp; me") << NoError;
1381  dict.insert(QStringLiteral("a"), QVariantList{QStringLiteral("Alpha"),
1382  QStringLiteral("Beta & Me")});
1383  dict.insert(QStringLiteral("var"), QStringLiteral(" & "));
1384  QTest::newRow("join07") << "{{ a|join:var|lower }}" << dict
1385  << QStringLiteral("alpha &amp; beta &amp; me")
1386  << NoError;
1387 
1388  dict.insert(QStringLiteral("a"), QVariantList{QStringLiteral("Alpha"),
1389  QStringLiteral("Beta & Me")});
1390  dict.insert(QStringLiteral("var"), Cutelee::markSafe(QStringLiteral(" & ")));
1391  QTest::newRow("join08") << "{{ a|join:var|lower }}" << dict
1392  << QStringLiteral("alpha & beta &amp; me") << NoError;
1393 
1394  // arguments to filters are intended to be used unescaped.
1395  // dict.clear();
1396  // dict.insert( QStringLiteral("a"), QVariantList() <<
1397  // QString::fromLatin1( "alpha" ) << QString::fromLatin1( "beta & me" ) );
1398  // dict.insert( QStringLiteral("var"), QStringLiteral(" & ") );
1399  //
1400  // QTest::newRow( "join05" ) << QString::fromLatin1( "{{ a|join:var }}" )
1401  // <<
1402  // dict << QString::fromLatin1( "alpha &amp; beta &amp; me" ) << NoError;
1403  //
1404  // dict.clear();
1405  // dict.insert( QStringLiteral("a"), QVariantList() <<
1406  // QString::fromLatin1( "alpha" ) << QString::fromLatin1( "beta & me" ) );
1407  // dict.insert( QStringLiteral("var"), QVariant::fromValue( markSafe(
1408  // QString::fromLatin1( " & " ) ) ) );
1409  //
1410  // QTest::newRow( "join06" ) << QString::fromLatin1( "{{ a|join:var }}" )
1411  // <<
1412  // dict << QString::fromLatin1( "alpha & beta &amp; me" ) << NoError;
1413  //
1414  // dict.clear();
1415  // dict.insert( QStringLiteral("a"), QVariantList() <<
1416  // QString::fromLatin1( "Alpha" ) << QString::fromLatin1( "Beta & me" ) );
1417  // dict.insert( QStringLiteral("var"), QStringLiteral(" & ") );
1418  //
1419  // QTest::newRow( "join07" ) << QString::fromLatin1( "{{ a|join:var|lower
1420  // }}" ) << dict << QString::fromLatin1( "alpha &amp; beta &amp; me" ) <<
1421  // NoError;
1422  //
1423  // dict.clear();
1424  // dict.insert( QStringLiteral("a"), QVariantList() <<
1425  // QString::fromLatin1( "Alpha" ) << QString::fromLatin1( "Beta & me" ) );
1426  // dict.insert( QStringLiteral("var"), QVariant::fromValue( markSafe(
1427  // QString::fromLatin1( " & " ) ) ) );
1428  //
1429  // QTest::newRow( "join08" ) << QString::fromLatin1( "{{ a|join:var|lower
1430  // }}" ) << dict << QString::fromLatin1( "alpha & beta &amp; me" ) <<
1431  // NoError;
1432 
1433  dict.clear();
1434 
1435  QVariantList mapList;
1436  const auto cities
1437  = QStringList{QStringLiteral("London"), QStringLiteral("Berlin"),
1438  QStringLiteral("Paris"), QStringLiteral("Dublin")};
1439  Q_FOREACH (const QString &city, cities) {
1440  QVariantHash map;
1441  map.insert(QStringLiteral("city"), city);
1442  mapList << map;
1443  }
1444 
1445  dict.insert(QStringLiteral("mapList"), mapList);
1446 
1447  QTest::newRow("dictsort01")
1448  << "{% with mapList|dictsort:'city' as result %}{% for item in result "
1449  "%}{{ item.city }},{% endfor %}{% endwith %}"
1450  << dict << "Berlin,Dublin,London,Paris," << NoError;
1451 
1452  {
1453  // Test duplication works
1454  QVariantHash map;
1455  map.insert(QStringLiteral("city"), QStringLiteral("Berlin"));
1456  mapList << map;
1457  }
1458  dict.insert(QStringLiteral("mapList"), mapList);
1459 
1460  QTest::newRow("dictsort02")
1461  << "{% with mapList|dictsort:'city' as result %}{% for item in result "
1462  "%}{{ item.city }},{% endfor %}{% endwith %}"
1463  << dict << "Berlin,Berlin,Dublin,London,Paris," << NoError;
1464 
1465  dict.clear();
1466 
1467  QVariantList listList;
1468 
1469  const auto countries
1470  = QStringList{QStringLiteral("England"), QStringLiteral("Germany"),
1471  QStringLiteral("France"), QStringLiteral("Ireland")};
1472 
1473  const auto languages
1474  = QStringList{QStringLiteral("English"), QStringLiteral("German"),
1475  QStringLiteral("French"), QStringLiteral("Irish")};
1476 
1477  for (auto i = 0; i < cities.size(); ++i) {
1478  listList << QVariant(
1479  QVariantList{cities.at(i), countries.at(i), languages.at(i)});
1480  }
1481 
1482  dict.insert(QStringLiteral("listList"), listList);
1483 
1484  QTest::newRow("dictsort03")
1485  << "{% with listList|dictsort:'0' as result %}{% for item in result "
1486  "%}{{ "
1487  "item.0 }};{{ item.1 }};{{ item.2 }},{% endfor %}{% endwith %}"
1488  << dict
1489  << "Berlin;Germany;German,Dublin;Ireland;Irish,London;England;"
1490  "English,Paris;France;French,"
1491  << NoError;
1492 
1493  QTest::newRow("dictsort04")
1494  << "{% with listList|dictsort:'1' as result %}{% for item in result "
1495  "%}{{ "
1496  "item.0 }};{{ item.1 }};{{ item.2 }},{% endfor %}{% endwith %}"
1497  << dict
1498  << "London;England;English,Paris;France;French,Berlin;Germany;"
1499  "German,Dublin;Ireland;Irish,"
1500  << NoError;
1501 }
1502 
1503 void TestFilters::testLogicFilters_data()
1504 {
1505  QTest::addColumn<QString>("input");
1506  QTest::addColumn<Dict>("dict");
1507  QTest::addColumn<QString>("output");
1508  QTest::addColumn<Cutelee::Error>("error");
1509 
1510  Dict dict;
1511 
1512  // Literal string arguments to the default filter are always treated as
1513  // safe strings, regardless of the auto-escaping state.
1514 
1515  // Note: we have to use {"a": ""} here, otherwise the invalid template
1516  // variable string interferes with the test result.
1517 
1518  dict.insert(QStringLiteral("a"), QStringLiteral(""));
1519 
1520  QTest::newRow("filter-default01")
1521  << "{{ a|default:\"x<\" }}" << dict << QStringLiteral("x<") << NoError;
1522  QTest::newRow("filter-default02")
1523  << "{% autoescape off %}{{ a|default:\"x<\" }}{% endautoescape %}" << dict
1524  << QStringLiteral("x<") << NoError;
1525 
1526  dict.clear();
1527  dict.insert(QStringLiteral("a"),
1528  QVariant::fromValue(markSafe(QStringLiteral("x>"))));
1529 
1530  QTest::newRow("filter-default03")
1531  << "{{ a|default:\"x<\" }}" << dict << QStringLiteral("x>") << NoError;
1532  QTest::newRow("filter-default04")
1533  << "{% autoescape off %}{{ a|default:\"x<\" }}{% endautoescape %}" << dict
1534  << QStringLiteral("x>") << NoError;
1535 
1536  dict.clear();
1537  dict.insert(QStringLiteral("a"), QVariant());
1538 
1539  QTest::newRow("filter-default_if_none01")
1540  << "{{ a|default:\"x<\" }}" << dict << QStringLiteral("x<") << NoError;
1541  QTest::newRow("filter-default_if_none02")
1542  << "{% autoescape off %}{{ a|default:\"x<\" }}{% endautoescape %}" << dict
1543  << QStringLiteral("x<") << NoError;
1544 }
1545 
1546 void TestFilters::testMiscFilters_data()
1547 {
1548  QTest::addColumn<QString>("input");
1549  QTest::addColumn<Dict>("dict");
1550  QTest::addColumn<QString>("output");
1551  QTest::addColumn<Cutelee::Error>("error");
1552 
1553  Dict dict;
1554 
1555  //
1556  // // {"a": "<1-800-call-me>", "b": mark_safe("<1-800-call-me>")
1557  // QTest::newRow( "filter-phone2numeric01") << QString::fromLatin1( "{{
1558  // a|phone2numeric }} {{ b|phone2numeric }}" ) << dict <<
1559  // QString::fromLatin1( "&lt;1-800-2255-63&gt; <1-800-2255-63>" ) <<
1560  // NoError;
1561  //
1562  // // {"a": "<1-800-call-me>", "b": mark_safe("<1-800-call-me>")
1563  // QTest::newRow( "filter-phone2numeric02") << QString::fromLatin1( "{%
1564  // autoescape off %}{{ a|phone2numeric }} {{ b|phone2numeric }}{%
1565  // endautoescape %}" ) << dict << QString::fromLatin1( "<1-800-2255-63>
1566  // <1-800-2255-63>" ) << NoError;
1567  //
1568  // Chaining a bunch of safeness-preserving filters should not alter
1569  // the safe status either way.
1570 
1571  dict.insert(QStringLiteral("a"), QStringLiteral("a < b"));
1572  dict.insert(QStringLiteral("b"),
1573  QVariant::fromValue(markSafe(QStringLiteral("a < b"))));
1574 
1575  QTest::newRow("chaining01")
1576  << "{{ a|capfirst|center:\"7\" }}.{{ b|capfirst|center:\"7\" }}" << dict
1577  << QStringLiteral(" A &lt; b . A < b ") << NoError;
1578  QTest::newRow("chaining02")
1579  << "{% autoescape off %}{{ a|capfirst|center:\"7\" }}.{{ "
1580  "b|capfirst|center:\"7\" }}{% endautoescape %}"
1581  << dict << QStringLiteral(" A < b . A < b ") << NoError;
1582 
1583  // Using a filter that forces a string back to unsafe:
1584 
1585  QTest::newRow("chaining03")
1586  << "{{ a|cut:\"b\"|capfirst }}.{{ b|cut:\"b\"|capfirst }}" << dict
1587  << QStringLiteral("A &lt; .A < ") << NoError;
1588  QTest::newRow("chaining04")
1589  << "{% autoescape off %}{{ a|cut:\"b\"|capfirst }}.{{ "
1590  "b|cut:\"b\"|capfirst }}{% endautoescape %}"
1591  << dict << QStringLiteral("A < .A < ") << NoError;
1592 
1593  dict.clear();
1594  dict.insert(QStringLiteral("a"), QStringLiteral("a < b"));
1595 
1596  // Using a filter that forces safeness does not lead to double-escaping
1597 
1598  QTest::newRow("chaining05") << QStringLiteral("{{ a|escape|capfirst }}")
1599  << dict << QStringLiteral("A &lt; b") << NoError;
1600  QTest::newRow("chaining06") << QStringLiteral(
1601  "{% autoescape off %}{{ a|escape|capfirst }}{% endautoescape %}")
1602  << dict << QStringLiteral("A &lt; b") << NoError;
1603 
1604  // Force to safe, then back (also showing why using force_escape too
1605  // early in a chain can lead to unexpected results).
1606 
1607  QTest::newRow("chaining07") << "{{ a|force_escape|cut:\";\" }}" << dict
1608  << QStringLiteral("a &amp;lt b") << NoError;
1609  QTest::newRow("chaining08")
1610  << "{% autoescape off %}{{ a|force_escape|cut:\";\" }}{% endautoescape "
1611  "%}"
1612  << dict << QStringLiteral("a &lt b") << NoError;
1613  QTest::newRow("chaining09") << "{{ a|cut:\";\"|force_escape }}" << dict
1614  << QStringLiteral("a &lt; b") << NoError;
1615  QTest::newRow("chaining10")
1616  << "{% autoescape off %}{{ a|cut:\";\"|force_escape }}{% endautoescape "
1617  "%}"
1618  << dict << QStringLiteral("a &lt; b") << NoError;
1619  QTest::newRow("chaining11")
1620  << "{{ a|cut:\"b\"|safe }}" << dict << QStringLiteral("a < ") << NoError;
1621  QTest::newRow("chaining12")
1622  << "{% autoescape off %}{{ a|cut:\"b\"|safe }}{% endautoescape %}" << dict
1623  << QStringLiteral("a < ") << NoError;
1624  QTest::newRow("chaining13") << QStringLiteral("{{ a|safe|force_escape }}")
1625  << dict << QStringLiteral("a &lt; b") << NoError;
1626  QTest::newRow("chaining14") << QStringLiteral(
1627  "{% autoescape off %}{{ a|safe|force_escape }}{% endautoescape %}")
1628  << dict << QStringLiteral("a &lt; b") << NoError;
1629 
1630  // // Filters decorated with stringfilter still respect is_safe.
1631  //
1632  // // {"unsafe": UnsafeClass()
1633  // QTest::newRow( "autoescape-stringfilter01") << QString::fromLatin1( "{{
1634  // unsafe|capfirst }}" ) << dict << QString::fromLatin1( "You &amp; me" )
1635  // <<
1636  // NoError;
1637  //
1638  // // {"unsafe": UnsafeClass()
1639  // QTest::newRow( "autoescape-stringfilter02") << QString::fromLatin1( "{%
1640  // autoescape off %}{{ unsafe|capfirst }}{% endautoescape %}" ) << dict <<
1641  // QString::fromLatin1( "You & me" ) << NoError;
1642  //
1643  // // {"safe": SafeClass()
1644  // QTest::newRow( "autoescape-stringfilter03") << QString::fromLatin1( "{{
1645  // safe|capfirst }}" ) << dict << QString::fromLatin1( "You &gt; me" ) <<
1646  // NoError;
1647  //
1648  // // {"safe": SafeClass()
1649  // QTest::newRow( "autoescape-stringfilter04") << QString::fromLatin1( "{%
1650  // autoescape off %}{{ safe|capfirst }}{% endautoescape %}" ) << dict <<
1651  // QString::fromLatin1( "You &gt; me" ) << NoError;
1652  //
1653 }
1654 
1655 void TestFilters::testIntegerFilters_data()
1656 {
1657  QTest::addColumn<QString>("input");
1658  QTest::addColumn<Dict>("dict");
1659  QTest::addColumn<QString>("output");
1660  QTest::addColumn<Cutelee::Error>("error");
1661 
1662  Dict dict;
1663 
1664  dict.insert(QStringLiteral("i"), 2000);
1665 
1666  QTest::newRow("add01") << QStringLiteral("{{ i|add:5 }}") << dict
1667  << QStringLiteral("2005") << NoError;
1668  QTest::newRow("add02") << QStringLiteral("{{ i|add:\"napis\" }}") << dict
1669  << QStringLiteral("2000") << NoError;
1670 
1671  dict.clear();
1672  dict.insert(QStringLiteral("i"), QStringLiteral("not_an_int"));
1673 
1674  QTest::newRow("add03") << QStringLiteral("{{ i|add:16 }}") << dict
1675  << QStringLiteral("not_an_int") << NoError;
1676  QTest::newRow("add04") << QStringLiteral("{{ i|add:\"16\" }}") << dict
1677  << QStringLiteral("not_an_int16") << NoError;
1678 
1679  dict.clear();
1680  dict.insert(QStringLiteral("l1"), QVariantList{1, 2});
1681  dict.insert(QStringLiteral("l2"), QVariantList{3, 4});
1682 
1683  QTest::newRow("add05") << QStringLiteral("{{ l1|add:l2 }}") << dict
1684  << QStringLiteral("[1, 2, 3, 4]") << NoError;
1685  // QTest::newRow( "add06" ) << QString::fromLatin1( "{{ t1|add:t2 }}" ) <<
1686  // dict << QString::fromLatin1( "2005" ) << NoError;
1687 
1688  // QTest::newRow( "add07" ) << QString::fromLatin1( "{{ d|add:t }}" ) <<
1689  // dict
1690  // << QString::fromLatin1( "2005" ) << NoError;
1691 
1692  QTest::newRow("add08") << QStringLiteral("{{ 1|add:2 }}") << dict
1693  << QStringLiteral("3") << NoError;
1694 
1695  QTest::newRow("filter-getdigit01") << QStringLiteral("{{ 123|get_digit:1 }}")
1696  << dict << QStringLiteral("3") << NoError;
1697  QTest::newRow("filter-getdigit02") << QStringLiteral("{{ 123|get_digit:2 }}")
1698  << dict << QStringLiteral("2") << NoError;
1699  QTest::newRow("filter-getdigit03") << QStringLiteral("{{ 123|get_digit:3 }}")
1700  << dict << QStringLiteral("1") << NoError;
1701  QTest::newRow("filter-getdigit04")
1702  << QStringLiteral("{{ 123|get_digit:4 }}") << dict
1703  << QStringLiteral("123") << NoError;
1704 }
1705 
1706 QTEST_MAIN(TestFilters)
1707 #include "testfilters.moc"
1708 
1709 #endif
The Context class holds the context to render a Template with.
Definition: context.h:119
Cutelee::Engine is the main entry point for creating Cutelee Templates.
Definition: engine.h:121
The InMemoryTemplateLoader loads Templates set dynamically in memory.
The Cutelee namespace holds all public Cutelee API.
Definition: Mainpage.dox:8
Cutelee::SafeString markSafe(const Cutelee::SafeString &input)
Definition: util.cpp:90
Utility functions used throughout Cutelee.