Use explicit latin1 in the tests.
[grantlee:grantlee.git] / tests / testfilters.cpp
1 /*
2   This file is part of the Grantlee 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 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   Library 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 <QtTest>
25 #include <QtCore/QObject>
26
27 #include "template.h"
28 #include "engine.h"
29 #include "context.h"
30 #include <util.h>
31 #include "grantlee_paths.h"
32
33
34 typedef QHash<QString, QVariant> Dict;
35
36 Q_DECLARE_METATYPE( Dict )
37 Q_DECLARE_METATYPE( Grantlee::Error )
38
39 using namespace Grantlee;
40
41 class TestFilters : public QObject
42 {
43   Q_OBJECT
44
45 private Q_SLOTS:
46   void initTestCase();
47   void cleanupTestCase();
48
49   void testDateBasedFilters_data();
50   void testDateBasedFilters() {
51     doTest();
52   }
53
54   void testStringFilters_data();
55   void testStringFilters() {
56     doTest();
57   }
58
59   void testListFilters_data();
60   void testListFilters() {
61     doTest();
62   }
63
64   void testLogicFilters_data();
65   void testLogicFilters() {
66     doTest();
67   }
68
69   void testMiscFilters_data();
70   void testMiscFilters() {
71     doTest();
72   }
73
74   void testIntegerFilters_data();
75   void testIntegerFilters() {
76     doTest();
77   }
78
79
80 private:
81
82   void doTest();
83
84   InMemoryTemplateLoader::Ptr loader;
85   Engine *m_engine;
86
87 };
88
89 void TestFilters::initTestCase()
90 {
91   m_engine = new Engine( this );
92
93   loader = InMemoryTemplateLoader::Ptr( new InMemoryTemplateLoader() );
94   m_engine->addTemplateLoader( loader );
95
96   QString appDirPath = QFileInfo( QCoreApplication::applicationDirPath() ).absoluteDir().path();
97   m_engine->setPluginPaths( QStringList() << QLatin1String( GRANTLEE_PLUGIN_PATH )
98                            << appDirPath + QLatin1String( "/tests/" ) // For testtags.qs
99                          );
100 }
101
102 void TestFilters::cleanupTestCase()
103 {
104   delete m_engine;
105 }
106
107 void TestFilters::doTest()
108 {
109   QFETCH( QString, input );
110   QFETCH( Dict, dict );
111   QFETCH( QString, output );
112   QFETCH( Grantlee::Error, error );
113
114   Template t = m_engine->newTemplate( input, QLatin1String( QTest::currentDataTag() ) );
115
116   Context context( dict );
117
118   QString result = t->render( &context );
119
120   if ( t->error() != NoError ) {
121     if ( t->error() != error )
122       qDebug() << t->errorString();
123     QCOMPARE( t->error(), error );
124     return;
125   }
126
127   // Didn't catch any errors, so make sure I didn't expect any.
128   QCOMPARE( NoError, error );
129
130   QCOMPARE( t->error(), NoError );
131
132   QCOMPARE( result, output );
133 }
134
135 void TestFilters::testDateBasedFilters_data()
136 {
137   QTest::addColumn<QString>( "input" );
138   QTest::addColumn<Dict>( "dict" );
139   QTest::addColumn<QString>( "output" );
140   QTest::addColumn<Grantlee::Error>( "error" );
141
142   Dict dict;
143
144   dict.insert( QLatin1String( "a" ), QDateTime::currentDateTime().addSecs( -70 ) );
145
146   QTest::newRow( "filter-timesince01" ) << QString::fromLatin1( "{{ a|timesince }}" ) << dict << QString::fromLatin1( "1 minute" ) << NoError;
147
148   dict.clear();
149
150   dict.insert( QLatin1String( "a" ), QDateTime::currentDateTime().addDays( -1 ).addSecs( -60 ) );
151
152   QTest::newRow( "filter-timesince02" ) << QString::fromLatin1( "{{ a|timesince }}" ) << dict << QString::fromLatin1( "1 day" ) << NoError;
153
154   dict.clear();
155
156   dict.insert( QLatin1String( "a" ), QDateTime::currentDateTime().addSecs( -1 * 60 * 60 ).addSecs( -1 * 25 * 60 ).addSecs( -1 * 10 ) );
157   QTest::newRow( "filter-timesince03" ) << QString::fromLatin1( "{{ a|timesince }}" ) << dict << QString::fromLatin1( "1 hour, 25 minutes" ) << NoError;
158
159   dict.clear();
160
161   //  Compare to a given parameter
162
163   dict.insert( QLatin1String( "a" ), QDateTime::currentDateTime().addDays( -2 ) );
164   dict.insert( QLatin1String( "b" ), QDateTime::currentDateTime().addDays( -1 ) );
165
166   QTest::newRow( "filter-timesince04" ) << QString::fromLatin1( "{{ a|timesince:b }}" ) << dict << QString::fromLatin1( "1 day" ) << NoError;
167
168   dict.clear();
169
170   dict.insert( QLatin1String( "a" ), QDateTime::currentDateTime().addDays( -2 ).addSecs( -60 ) );
171   dict.insert( QLatin1String( "b" ), QDateTime::currentDateTime().addDays( -2 ) );
172
173   QTest::newRow( "filter-timesince05" ) << QString::fromLatin1( "{{ a|timesince:b }}" ) << dict << QString::fromLatin1( "1 minute" ) << NoError;
174
175   dict.clear();
176
177   //  Check that timezone is respected
178
179   //  {"a":now_tz - timedelta(hours=8), "b":now_tz
180 //   QTest::newRow( "filter-timesince06" ) << QString::fromLatin1( "{{ a|timesince:b }}" ) << dict << QString::fromLatin1( "8 hours" ) << NoError;
181
182   dict.insert( QLatin1String( "earlier" ), QDateTime::currentDateTime().addDays( -7 ) );
183   QTest::newRow( "filter-timesince07" ) << QString::fromLatin1( "{{ earlier|timesince }}" ) << dict << QString::fromLatin1( "1 week" ) << NoError;
184
185   dict.clear();
186
187   dict.insert( QLatin1String( "now" ), QDateTime::currentDateTime() );
188   dict.insert( QLatin1String( "earlier" ), QDateTime::currentDateTime().addDays( -7 ) );
189
190   QTest::newRow( "filter-timesince08" ) << QString::fromLatin1( "{{ earlier|timesince:now }}" ) << dict << QString::fromLatin1( "1 week" ) << NoError;
191
192   dict.clear();
193
194   dict.insert( QLatin1String( "later" ), QDateTime::currentDateTime().addDays( 7 ) );
195
196   QTest::newRow( "filter-timesince09" ) << QString::fromLatin1( "{{ later|timesince }}" ) << dict << QString::fromLatin1( "0 minutes" ) << NoError;
197
198   dict.clear();
199
200   dict.insert( QLatin1String( "now" ), QDateTime::currentDateTime() );
201   dict.insert( QLatin1String( "later" ), QDateTime::currentDateTime().addDays( 7 ) );
202
203   QTest::newRow( "filter-timesince10" ) << QString::fromLatin1( "{{ later|timesince:now }}" ) << dict << QString::fromLatin1( "0 minutes" ) << NoError;
204
205   //  Ensures that differing timezones are calculated correctly
206
207   //  {"a": now
208 //   QTest::newRow( "filter-timesince11" ) << QString::fromLatin1( "{{ a|timesince }}" ) << dict << QString::fromLatin1( "0 minutes" ) << NoError;
209
210   //  {"a": now_tz
211 //   QTest::newRow( "filter-timesince12" ) << QString::fromLatin1( "{{ a|timesince }}" ) << dict << QString::fromLatin1( "0 minutes" ) << NoError;
212
213   //  {"a": now_tz_i
214 //   QTest::newRow( "filter-timesince13" ) << QString::fromLatin1( "{{ a|timesince }}" ) << dict << QString::fromLatin1( "0 minutes" ) << NoError;
215
216   //  {"a": now_tz, "b": now_tz_i
217 //   QTest::newRow( "filter-timesince14" ) << QString::fromLatin1( "{{ a|timesince:b }}" ) << dict << QString::fromLatin1( "0 minutes" ) << NoError;
218
219   //  {"a": now, "b": now_tz_i
220 //   QTest::newRow( "filter-timesince15" ) << QString::fromLatin1( "{{ a|timesince:b }}" ) << dict << QString() << NoError;
221
222   //  {"a": now_tz_i, "b": now
223 //   QTest::newRow( "filter-timesince16" ) << QString::fromLatin1( "{{ a|timesince:b }}" ) << dict << QString() << NoError;
224
225   dict.clear();
226
227   dict.insert( QLatin1String( "a" ), QDateTime::currentDateTime() );
228   dict.insert( QLatin1String( "b" ), QDateTime::currentDateTime() );
229
230   QTest::newRow( "filter-timesince17" ) << QString::fromLatin1( "{{ a|timesince:b }}" ) << dict << QString::fromLatin1( "0 minutes" ) << NoError;
231
232   dict.clear();
233
234   dict.insert( QLatin1String( "a" ), QDateTime::currentDateTime() );
235   dict.insert( QLatin1String( "b" ), QDateTime::currentDateTime().addDays( 1 ) );
236
237   QTest::newRow( "filter-timesince18" ) << QString::fromLatin1( "{{ a|timesince:b }}" ) << dict << QString::fromLatin1( "1 day" ) << NoError;
238
239   //  Default compare with datetime.now()
240
241   dict.clear();
242   dict.insert( QLatin1String( "a" ), QDateTime::currentDateTime().addSecs( 130 ) );
243
244   QTest::newRow( "filter-timeuntil01" ) << QString::fromLatin1( "{{ a|timeuntil }}" ) << dict << QString::fromLatin1( "2 minutes" ) << NoError;
245
246   dict.clear();
247   dict.insert( QLatin1String( "a" ), QDateTime::currentDateTime().addDays( 1 ).addSecs( 10 ) );
248
249   QTest::newRow( "filter-timeuntil02" ) << QString::fromLatin1( "{{ a|timeuntil }}" ) << dict << QString::fromLatin1( "1 day" ) << NoError;
250
251   dict.clear();
252   dict.insert( QLatin1String( "a" ), QDateTime::currentDateTime().addSecs( 60 * 60 * 8 ).addSecs( 610 ) );
253
254   QTest::newRow( "filter-timeuntil03" ) << QString::fromLatin1( "{{ a|timeuntil }}" ) << dict << QString::fromLatin1( "8 hours, 10 minutes" ) << NoError;
255
256   //  Compare to a given parameter
257
258   dict.clear();
259   dict.insert( QLatin1String( "a" ), QDateTime::currentDateTime().addDays( -1 ) );
260   dict.insert( QLatin1String( "b" ), QDateTime::currentDateTime().addDays( -2 ) );
261
262   QTest::newRow( "filter-timeuntil04" ) << QString::fromLatin1( "{{ a|timeuntil:b }}" ) << dict << QString::fromLatin1( "1 day" ) << NoError;
263
264   dict.clear();
265   dict.insert( QLatin1String( "a" ), QDateTime::currentDateTime().addDays( -1 ) );
266   dict.insert( QLatin1String( "b" ), QDateTime::currentDateTime().addDays( -1 ).addSecs( -60 ) );
267
268   QTest::newRow( "filter-timeuntil05" ) << QString::fromLatin1( "{{ a|timeuntil:b }}" ) << dict << QString::fromLatin1( "1 minute" ) << NoError;
269
270   dict.clear();
271   dict.insert( QLatin1String( "earlier" ), QDateTime::currentDateTime().addDays( -7 ) );
272
273   QTest::newRow( "filter-timeuntil06" ) << QString::fromLatin1( "{{ earlier|timeuntil }}" ) << dict << QString::fromLatin1( "0 minutes" ) << NoError;
274
275   dict.clear();
276   dict.insert( QLatin1String( "now" ), QDateTime::currentDateTime() );
277   dict.insert( QLatin1String( "earlier" ), QDateTime::currentDateTime().addDays( -7 ) );
278
279   QTest::newRow( "filter-timeuntil07" ) << QString::fromLatin1( "{{ earlier|timeuntil:now }}" ) << dict << QString::fromLatin1( "0 minutes" ) << NoError;
280
281   dict.clear();
282   dict.insert( QLatin1String( "later" ), QDateTime::currentDateTime().addDays( 7 ) );
283
284   QTest::newRow( "filter-timeuntil08" ) << QString::fromLatin1( "{{ later|timeuntil }}" ) << dict << QString::fromLatin1( "1 week" ) << NoError;
285
286   dict.clear();
287   dict.insert( QLatin1String( "now" ), QDateTime::currentDateTime() );
288   dict.insert( QLatin1String( "later" ), QDateTime::currentDateTime().addDays( 7 ) );
289
290   QTest::newRow( "filter-timeuntil09" ) << QString::fromLatin1( "{{ later|timeuntil:now }}" ) << dict << QString::fromLatin1( "1 week" ) << NoError;
291
292   //  Ensures that differing timezones are calculated correctly
293 //
294 //   //  {"a": now_tz_i
295 //   QTest::newRow( "filter-timeuntil10" ) << QString::fromLatin1( "{{ a|timeuntil }}" ) << dict << QString::fromLatin1( "0 minutes" ) << NoError;
296 //
297 //   //  {"a": now_tz_i, "b": now_tz
298 //   QTest::newRow( "filter-timeuntil11" ) << QString::fromLatin1( "{{ a|timeuntil:b }}" ) << dict << QString::fromLatin1( "0 minutes" ) << NoError;
299
300   dict.clear();
301   dict.insert( QLatin1String( "a" ), QDateTime::currentDateTime() );
302   dict.insert( QLatin1String( "b" ), QDateTime::currentDateTime() );
303   QTest::newRow( "filter-timeuntil12" ) << QString::fromLatin1( "{{ a|timeuntil:b }}" ) << dict << QString::fromLatin1( "0 minutes" ) << NoError;
304
305   dict.clear();
306   dict.insert( QLatin1String( "a" ), QDateTime::currentDateTime() );
307   dict.insert( QLatin1String( "b" ), QDateTime::currentDateTime().addDays( -1 ) );
308
309   QTest::newRow( "filter-timeuntil13" ) << QString::fromLatin1( "{{ a|timeuntil:b }}" ) << dict << QString::fromLatin1( "1 day" ) << NoError;
310
311   QDateTime d( QDate( 2008, 1, 1 ) );
312
313   dict.clear();
314   dict.insert( QLatin1String( "d" ), d );
315
316   QTest::newRow( "date01" ) << "{{ d|date:\"MM\" }}" << dict << QString::fromLatin1( "01" ) << NoError;
317   QTest::newRow( "date02" ) << QString::fromLatin1( "{{ d|date }}" ) << dict << QString::fromLatin1( "Jan. 1, 2008" ) << NoError;
318
319   dict.clear();
320   dict.insert( QLatin1String( "d" ), QLatin1String( "fail_string" ) );
321   QTest::newRow( "date03" ) << "{{ d|date:\"MM\" }}" << dict << QString() << NoError;
322
323 }
324
325 void TestFilters::testStringFilters_data()
326 {
327   QTest::addColumn<QString>( "input" );
328   QTest::addColumn<Dict>( "dict" );
329   QTest::addColumn<QString>( "output" );
330   QTest::addColumn<Grantlee::Error>( "error" );
331
332   Dict dict;
333
334   dict.clear();
335   dict.insert( QLatin1String( "a" ), QLatin1String( "<a>\'" ) );
336   dict.insert( QLatin1String( "b" ), QVariant::fromValue( markSafe( QString::fromLatin1( "<a>\'" ) ) ) );
337
338   QTest::newRow( "filter-addslash01" ) << QString::fromLatin1( "{% autoescape off %}{{ a|addslashes }} {{ b|addslashes }}{% endautoescape %}" ) << dict << "<a>\\\' <a>\\\'" << NoError;
339
340   dict.clear();
341   dict.insert( QLatin1String( "a" ), QLatin1String( "<a>\'" ) );
342   dict.insert( QLatin1String( "b" ), QVariant::fromValue( markSafe( QString::fromLatin1( "<a>\'" ) ) ) );
343
344   QTest::newRow( "filter-addslash02" ) << QString::fromLatin1( "{{ a|addslashes }} {{ b|addslashes }}" ) << dict << "&lt;a&gt;\\&#39; <a>\\\'" << NoError;
345
346   dict.clear();
347   dict.insert( QLatin1String( "a" ), QLatin1String( "fred>" ) );
348   dict.insert( QLatin1String( "b" ), QVariant::fromValue( markSafe( QString::fromLatin1( "fred&gt;" ) ) ) );
349
350   QTest::newRow( "filter-capfirst01" ) << QString::fromLatin1( "{% autoescape off %}{{ a|capfirst }} {{ b|capfirst }}{% endautoescape %}" ) << dict << QString::fromLatin1( "Fred> Fred&gt;" ) << NoError;
351
352   dict.clear();
353   dict.insert( QLatin1String( "a" ), QLatin1String( "fred>" ) );
354   dict.insert( QLatin1String( "b" ), QVariant::fromValue( markSafe( QString::fromLatin1( "fred&gt;" ) ) ) );
355
356   QTest::newRow( "filter-capfirst02" ) << QString::fromLatin1( "{{ a|capfirst }} {{ b|capfirst }}" ) << dict << QString::fromLatin1( "Fred&gt; Fred&gt;" ) << NoError;
357
358   //  Note that applying fix_ampsersands in autoescape mode leads to
359   //  double escaping.
360
361   dict.clear();
362   dict.insert( QLatin1String( "a" ), QLatin1String( "a&b" ) );
363   dict.insert( QLatin1String( "b" ), QVariant::fromValue( markSafe( QString::fromLatin1( "a&b" ) ) ) );
364
365   QTest::newRow( "filter-fix_ampersands01" ) << QString::fromLatin1( "{% autoescape off %}{{ a|fix_ampersands }} {{ b|fix_ampersands }}{% endautoescape %}" ) << dict << QString::fromLatin1( "a&amp;b a&amp;b" ) << NoError;
366
367   dict.clear();
368   dict.insert( QLatin1String( "a" ), QLatin1String( "a&b" ) );
369   dict.insert( QLatin1String( "b" ), QVariant::fromValue( markSafe( QString::fromLatin1( "a&b" ) ) ) );
370
371   QTest::newRow( "filter-fix_ampersands02" ) << QString::fromLatin1( "{{ a|fix_ampersands }} {{ b|fix_ampersands }}" ) << dict << QString::fromLatin1( "a&amp;amp;b a&amp;b" ) << NoError;
372
373   dict.clear();
374   dict.insert( QLatin1String( "a" ), QLatin1String( "1.42" ) );
375   dict.insert( QLatin1String( "b" ), QVariant::fromValue( markSafe( QString::fromLatin1( "1.42" ) ) ) );
376
377   QTest::newRow( "filter-floatformat01" ) << QString::fromLatin1( "{% autoescape off %}{{ a|floatformat }} {{ b|floatformat }}{% endautoescape %}" ) << dict << QString::fromLatin1( "1.4 1.4" ) << NoError;
378
379   dict.clear();
380   dict.insert( QLatin1String( "a" ), QLatin1String( "1.42" ) );
381   dict.insert( QLatin1String( "b" ), QVariant::fromValue( markSafe( QString::fromLatin1( "1.42" ) ) ) );
382
383   QTest::newRow( "filter-floatformat02" ) << QString::fromLatin1( "{{ a|floatformat }} {{ b|floatformat }}" ) << dict << QString::fromLatin1( "1.4 1.4" ) << NoError;
384
385   //  The contents of "linenumbers" is escaped according to the current
386   //  autoescape setting.
387
388   dict.clear();
389   dict.insert( QLatin1String( "a" ), QLatin1String( "one\n<two>\nthree" ) );
390   dict.insert( QLatin1String( "b" ), QVariant::fromValue( markSafe( QString::fromLatin1( "one\n&lt;two&gt;\nthree" ) ) ) );
391
392   QTest::newRow( "filter-linenumbers01" ) << QString::fromLatin1( "{{ a|linenumbers }} {{ b|linenumbers }}" ) << dict << "1. one\n2. &lt;two&gt;\n3. three 1. one\n2. &lt;two&gt;\n3. three" << NoError;
393
394   dict.clear();
395   dict.insert( QLatin1String( "a" ), QLatin1String( "one\n<two>\nthree" ) );
396   dict.insert( QLatin1String( "b" ), QVariant::fromValue( markSafe( QString::fromLatin1( "one\n&lt;two&gt;\nthree" ) ) ) );
397   QTest::newRow( "filter-linenumbers02" ) << QString::fromLatin1( "{% autoescape off %}{{ a|linenumbers }} {{ b|linenumbers }}{% endautoescape %}" ) << dict << "1. one\n2. <two>\n3. three 1. one\n2. &lt;two&gt;\n3. three" << NoError;
398
399   dict.clear();
400   dict.insert( QLatin1String( "a" ), QLatin1String( "Apple & banana" ) );
401   dict.insert( QLatin1String( "b" ), QVariant::fromValue( markSafe( QString::fromLatin1( "Apple &amp; banana" ) ) ) );
402
403   QTest::newRow( "filter-lower01" ) << QString::fromLatin1( "{% autoescape off %}{{ a|lower }} {{ b|lower }}{% endautoescape %}" ) << dict << QString::fromLatin1( "apple & banana apple &amp; banana" ) << NoError;
404
405   dict.clear();
406   dict.insert( QLatin1String( "a" ), QLatin1String( "Apple & banana" ) );
407   dict.insert( QLatin1String( "b" ), QVariant::fromValue( markSafe( QString::fromLatin1( "Apple &amp; banana" ) ) ) );
408
409   QTest::newRow( "filter-lower02" ) << QString::fromLatin1( "{{ a|lower }} {{ b|lower }}" ) << dict << QString::fromLatin1( "apple &amp; banana apple &amp; banana" ) << NoError;
410
411   //  The make_list filter can destroy existing escaping, so the results are
412   //  escaped.
413
414   dict.clear();
415   dict.insert( QLatin1String( "a" ), markSafe( QString::fromLatin1( "&" ) ) );
416
417   QTest::newRow( "filter-make_list01" ) << QString::fromLatin1( "{% autoescape off %}{{ a|make_list }}{% endautoescape %}" ) << dict << "[u\'&\']" << NoError;
418   QTest::newRow( "filter-make_list02" ) << QString::fromLatin1( "{{ a|make_list }}" ) << dict << QString::fromLatin1( "[u&#39;&amp;&#39;]" ) << NoError;
419
420   QTest::newRow( "filter-make_list03" ) << QString::fromLatin1( "{% autoescape off %}{{ a|make_list|stringformat:\"%1\"|safe }}{% endautoescape %}" ) << dict << QString::fromLatin1( "[u\'&\']" ) << NoError;
421   QTest::newRow( "filter-make_list04" ) << QString::fromLatin1( "{{ a|make_list|stringformat:\"%1\"|safe }}" ) << dict << QString::fromLatin1( "[u\'&\']" ) << NoError;
422
423   //  Running slugify on a pre-escaped string leads to odd behaviour,
424   //  but the result is still safe.
425
426   dict.clear();
427   dict.insert( QLatin1String( "a" ), QLatin1String( "a & b" ) );
428   dict.insert( QLatin1String( "b" ), markSafe( QString::fromLatin1( "a &amp; b" ) ) );
429
430   QTest::newRow( "filter-slugify01" ) << QString::fromLatin1( "{% autoescape off %}{{ a|slugify }} {{ b|slugify }}{% endautoescape %}" ) << dict << QString::fromLatin1( "a-b a-amp-b" ) << NoError;
431   QTest::newRow( "filter-slugify02" ) << QString::fromLatin1( "{{ a|slugify }} {{ b|slugify }}" ) << dict << QString::fromLatin1( "a-b a-amp-b" ) << NoError;
432
433   dict.clear();
434   dict.insert( QLatin1String( "a" ), QString::fromUtf8( "Schöne Grüße" ) );
435
436   QTest::newRow( "filter-slugify03" ) << QString::fromLatin1( "{{ a|slugify }}" ) << dict << QString::fromLatin1( "schone-grue" ) << NoError;
437
438
439   dict.clear();
440   dict.insert( QLatin1String( "a" ), QLatin1String( "testing\r\njavascript \'string\" <b>escaping</b>" ) );
441   QTest::newRow( "escapejs01" ) << QString::fromLatin1( "{{ a|escapejs }}" ) << dict << "testing\\x0D\\x0Ajavascript \\x27string\\x22 \\x3Cb\\x3Eescaping\\x3C/b\\x3E" << NoError;
442   QTest::newRow( "escapejs02" ) << QString::fromLatin1( "{% autoescape off %}{{ a|escapejs }}{% endautoescape %}" ) << dict << "testing\\x0D\\x0Ajavascript \\x27string\\x22 \\x3Cb\\x3Eescaping\\x3C/b\\x3E" << NoError;
443
444   //  Notice that escaping is applied *after* any filters, so the string
445   //  formatting here only needs to deal with pre-escaped characters.
446
447   dict.clear();
448   dict.insert( QLatin1String( "a" ), QLatin1String( "a<b" ) );
449   dict.insert( QLatin1String( "b" ), QVariant::fromValue( markSafe( QString::fromLatin1( "a<b" ) ) ) );
450
451   QTest::newRow( "filter-stringformat01" ) << "{% autoescape off %}.{{ a|stringformat:\"%1\" }}. .{{ b|stringformat:\"%2\" }}.{% endautoescape %}" << dict << QString::fromLatin1( ".a<b. .a<b." ) << NoError;
452   QTest::newRow( "filter-stringformat02" ) << ".{{ a|stringformat:\"%1\" }}. .{{ b|stringformat:\"%2\" }}." << dict << QString::fromLatin1( ".a&lt;b. .a<b." ) << NoError;
453   QTest::newRow( "filter-stringformat03" ) << ".{{ a|stringformat:\"foo %1 bar\" }}. .{{ b|stringformat:\"baz %2 bat\" }}." << dict << QString::fromLatin1( ".foo a&lt;b bar. .baz a<b bat." ) << NoError;
454
455   dict.clear();
456   dict.insert( QLatin1String( "path" ), QLatin1String( "www.grantlee.org" ) );
457   QTest::newRow( "filter-stringformat04" ) << "{% with path|stringformat:\"<a href=\\\"%1\\\">%1</a>\"|safe as result %}{{ result }}{% endwith %}" << dict << "<a href=\"www.grantlee.org\">www.grantlee.org</a>" << NoError;
458
459
460
461   //  XXX No test for "title" filter; needs an actual object.
462
463
464   dict.clear();
465   dict.insert( QLatin1String( "a" ), QLatin1String( "alpha & bravo" ) );
466   dict.insert( QLatin1String( "b" ), QVariant::fromValue( markSafe( QString::fromLatin1( "alpha &amp; bravo" ) ) ) );
467
468   QTest::newRow( "filter-truncatewords01" ) << "{% autoescape off %}{{ a|truncatewords:\"2\" }} {{ b|truncatewords:\"2\"}}{% endautoescape %}" << dict << QString::fromLatin1( "alpha & ... alpha &amp; ..." ) << NoError;
469
470   QTest::newRow( "filter-truncatewords02" ) << "{{ a|truncatewords:\"2\" }} {{ b|truncatewords:\"2\"}}" << dict << QString::fromLatin1( "alpha &amp; ... alpha &amp; ..." ) << NoError;
471
472   //  The "upper" filter messes up entities (which are case-sensitive),
473   //  so it's not safe for non-escaping purposes.
474
475   dict.clear();
476   dict.insert( QLatin1String( "a" ), QLatin1String( "a & b" ) );
477   dict.insert( QLatin1String( "b" ), QVariant::fromValue( markSafe( QString::fromLatin1( "a &amp; b" ) ) ) );
478
479   QTest::newRow( "filter-upper01" ) << QString::fromLatin1( "{% autoescape off %}{{ a|upper }} {{ b|upper }}{% endautoescape %}" ) << dict << QString::fromLatin1( "A & B A &AMP; B" ) << NoError;
480   QTest::newRow( "filter-upper02" ) << QString::fromLatin1( "{{ a|upper }} {{ b|upper }}" ) << dict << QString::fromLatin1( "A &amp; B A &amp;AMP; B" ) << NoError;
481
482 //   //  {"a": "http://example.com/?x=&y=", "b": mark_safe("http://example.com?x=&amp;y=")
483 //   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;
484 //
485 //   //  {"a": "http://example.com/?x=&y=", "b": mark_safe("http://example.com?x=&amp;y=")
486 //   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;
487 //
488 //   //  {"a": mark_safe("a &amp; b")
489 //   QTest::newRow( "filter-urlize03") << QString::fromLatin1( "{% autoescape off %}{{ a|urlize }}{% endautoescape %}" ) << dict << QString::fromLatin1( "a &amp; b" ) << NoError;
490 //
491 //   //  {"a": mark_safe("a &amp; b")
492 //   QTest::newRow( "filter-urlize04") << QString::fromLatin1( "{{ a|urlize }}" ) << dict << QString::fromLatin1( "a &amp; b" ) << NoError;
493 //
494 //   //  This will lead to a nonsense result, but at least it won't be
495 //
496 //   //  exploitable for XSS purposes when auto-escaping is on.
497 //
498 //   //  {"a": "<script>alert(\"foo\")</script>"
499 //   QTest::newRow( "filter-urlize05") << QString::fromLatin1( "{% autoescape off %}{{ a|urlize }}{% endautoescape %}" ) << dict << "<script>alert(\"foo\")</script>" << NoError;
500 //
501 //   //  {"a": "<script>alert(\"foo\")</script>"
502 //   QTest::newRow( "filter-urlize06") << QString::fromLatin1( "{{ a|urlize }}" ) << dict << QString::fromLatin1( "&lt;script&gt;alert(&#39;foo&#39;)&lt;/script&gt;" ) << NoError;
503 //
504 //   //  mailto: testing for urlize
505 //
506 //   //  {"a": "Email me at me@example.com"
507 //   QTest::newRow( "filter-urlize07") << QString::fromLatin1( "{{ a|urlize }}" ) << dict << "Email me at <a href=\"mailto:me@example.com\">me@example.com</a>" << NoError;
508 //
509 //   //  {"a": "Email me at <me@example.com>"
510 //   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;
511 //
512 //   //  {"a": "\"Unsafe\" http://example.com/x=&y=", "b": mark_safe("&quot;Safe&quot; http://example.com?x=&amp;y=")
513 //   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;
514 //
515 //   //  {"a": "\"Unsafe\" http://example.com/x=&y=", "b": mark_safe("&quot;Safe&quot; http://example.com?x=&amp;y=")
516 //   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;
517
518 //   //  Ensure iriencode keeps safe strings:
519 //
520 //   //  {"url": "?test=1&me=2"
521 //   QTest::newRow( "filter-iriencode01") << QString::fromLatin1( "{{ url|iriencode }}" ) << dict << QString::fromLatin1( "?test=1&amp;me=2" ) << NoError;
522 //
523 //   //  {"url": "?test=1&me=2"
524 //   QTest::newRow( "filter-iriencode02") << QString::fromLatin1( "{% autoescape off %}{{ url|iriencode }}{% endautoescape %}" ) << dict << QString::fromLatin1( "?test=1&me=2" ) << NoError;
525 //
526 //   //  {"url": mark_safe("?test=1&me=2")
527 //   QTest::newRow( "filter-iriencode03") << QString::fromLatin1( "{{ url|iriencode }}" ) << dict << QString::fromLatin1( "?test=1&me=2" ) << NoError;
528 //
529 //   //  {"url": mark_safe("?test=1&me=2")
530 //   QTest::newRow( "filter-iriencode04") << QString::fromLatin1( "{% autoescape off %}{{ url|iriencode }}{% endautoescape %}" ) << dict << QString::fromLatin1( "?test=1&me=2" ) << NoError;
531 //
532   dict.clear();
533   dict.insert( QLatin1String( "a" ), QLatin1String( "a & b" ) );
534   dict.insert( QLatin1String( "b" ), QVariant::fromValue( markSafe( QString::fromLatin1( "a &amp; b" ) ) ) );
535
536   QTest::newRow( "filter-wordcount01" ) << QString::fromLatin1( "{% autoescape off %}{{ a|wordcount }} {{ b|wordcount }}{% endautoescape %}" ) << dict << QString::fromLatin1( "3 3" ) << NoError;
537
538   QTest::newRow( "filter-wordcount02" ) << QString::fromLatin1( "{{ a|wordcount }} {{ b|wordcount }}" ) << dict << QString::fromLatin1( "3 3" ) << NoError;
539
540   dict.clear();
541   dict.insert( QLatin1String( "a" ), QLatin1String( "a & b" ) );
542   dict.insert( QLatin1String( "b" ), QVariant::fromValue( markSafe( QString::fromLatin1( "a & b" ) ) ) );
543
544   QTest::newRow( "filter-wordwrap01" ) << QString::fromLatin1( "{% autoescape off %}{{ a|wordwrap:3 }} {{ b|wordwrap:3 }}{% endautoescape %}" ) << dict << "a &\nb a &\nb" << NoError;
545
546   QTest::newRow( "filter-wordwrap02" ) << QString::fromLatin1( "{{ a|wordwrap:3 }} {{ b|wordwrap:3 }}" ) << dict << "a &amp;\nb a &\nb" << NoError;
547
548   dict.clear();
549   dict.insert( QLatin1String( "a" ), QLatin1String( "a&b" ) );
550   dict.insert( QLatin1String( "b" ), QVariant::fromValue( markSafe( QString::fromLatin1( "a&b" ) ) ) );
551
552   QTest::newRow( "filter-ljust01" ) << "{% autoescape off %}.{{ a|ljust:\"5\" }}. .{{ b|ljust:\"5\" }}.{% endautoescape %}" << dict << QString::fromLatin1( ".a&b  . .a&b  ." ) << NoError;
553
554   QTest::newRow( "filter-ljust02" ) << ".{{ a|ljust:\"5\" }}. .{{ b|ljust:\"5\" }}." << dict << QString::fromLatin1( ".a&amp;b  . .a&b  ." ) << NoError;
555
556   QTest::newRow( "filter-rjust01" ) << "{% autoescape off %}.{{ a|rjust:\"5\" }}. .{{ b|rjust:\"5\" }}.{% endautoescape %}" << dict << QString::fromLatin1( ".  a&b. .  a&b." ) << NoError;
557
558   QTest::newRow( "filter-rjust02" ) << ".{{ a|rjust:\"5\" }}. .{{ b|rjust:\"5\" }}." << dict << QString::fromLatin1( ".  a&amp;b. .  a&b." ) << NoError;
559
560   QTest::newRow( "filter-center01" ) << "{% autoescape off %}.{{ a|center:\"5\" }}. .{{ b|center:\"5\" }}.{% endautoescape %}" << dict << QString::fromLatin1( ". a&b . . a&b ." ) << NoError;
561
562   QTest::newRow( "filter-center02" ) << ".{{ a|center:\"5\" }}. .{{ b|center:\"5\" }}." << dict << QString::fromLatin1( ". a&amp;b . . a&b ." ) << NoError;
563
564   dict.clear();
565   dict.insert( QLatin1String( "a" ), QLatin1String( "x&y" ) );
566   dict.insert( QLatin1String( "b" ), QVariant::fromValue( markSafe( QString::fromLatin1( "x&amp;y" ) ) ) );
567
568   QTest::newRow( "filter-cut01" ) << "{% autoescape off %}{{ a|cut:\"x\" }} {{ b|cut:\"x\" }}{% endautoescape %}" << dict << QString::fromLatin1( "&y &amp;y" ) << NoError;
569   QTest::newRow( "filter-cut02" ) << "{{ a|cut:\"x\" }} {{ b|cut:\"x\" }}" << dict << QString::fromLatin1( "&amp;y &amp;y" ) << NoError;
570   QTest::newRow( "filter-cut03" ) << "{% autoescape off %}{{ a|cut:\"&\" }} {{ b|cut:\"&\" }}{% endautoescape %}" << dict << QString::fromLatin1( "xy xamp;y" ) << NoError;
571   QTest::newRow( "filter-cut04" ) << "{{ a|cut:\"&\" }} {{ b|cut:\"&\" }}" << dict << QString::fromLatin1( "xy xamp;y" ) << NoError;
572
573   //  Passing ";" to cut can break existing HTML entities, so those strings
574   //  are auto-escaped.
575
576   QTest::newRow( "filter-cut05" ) << "{% autoescape off %}{{ a|cut:\";\" }} {{ b|cut:\";\" }}{% endautoescape %}" << dict << QString::fromLatin1( "x&y x&ampy" ) << NoError;
577   QTest::newRow( "filter-cut06" ) << "{{ a|cut:\";\" }} {{ b|cut:\";\" }}" << dict << QString::fromLatin1( "x&amp;y x&amp;ampy" ) << NoError;
578
579   //  The "escape" filter works the same whether autoescape is on or off,
580   //  but it has no effect on strings already marked as safe.
581
582   dict.clear();
583   dict.insert( QLatin1String( "a" ), QLatin1String( "x&y" ) );
584   dict.insert( QLatin1String( "b" ), QVariant::fromValue( markSafe( QString::fromLatin1( "x&y" ) ) ) );
585
586   QTest::newRow( "filter-escape01" ) << QString::fromLatin1( "{{ a|escape }} {{ b|escape }}" ) << dict << QString::fromLatin1( "x&amp;y x&y" ) << NoError;
587   QTest::newRow( "filter-escape02" ) << QString::fromLatin1( "{% autoescape off %}{{ a|escape }} {{ b|escape }}{% endautoescape %}" ) << dict << QString::fromLatin1( "x&amp;y x&y" ) << NoError;
588
589   //  It is only applied once, regardless of the number of times it
590   //  appears in a chain.
591
592   dict.clear();
593   dict.insert( QLatin1String( "a" ), QLatin1String( "x&y" ) );
594
595   QTest::newRow( "filter-escape03" ) << QString::fromLatin1( "{% autoescape off %}{{ a|escape|escape }}{% endautoescape %}" ) << dict << QString::fromLatin1( "x&amp;y" ) << NoError;
596   QTest::newRow( "filter-escape04" ) << QString::fromLatin1( "{{ a|escape|escape }}" ) << dict << QString::fromLatin1( "x&amp;y" ) << NoError;
597
598   //  Force_escape is applied immediately. It can be used to provide
599   //  double-escaping, for example.
600
601   QTest::newRow( "filter-force-escape01" ) << QString::fromLatin1( "{% autoescape off %}{{ a|force_escape }}{% endautoescape %}" ) << dict << QString::fromLatin1( "x&amp;y" ) << NoError;
602   QTest::newRow( "filter-force-escape02" ) << QString::fromLatin1( "{{ a|force_escape }}" ) << dict << QString::fromLatin1( "x&amp;y" ) << NoError;
603   QTest::newRow( "filter-force-escape03" ) << QString::fromLatin1( "{% autoescape off %}{{ a|force_escape|force_escape }}{% endautoescape %}" ) << dict << QString::fromLatin1( "x&amp;amp;y" ) << NoError;
604   QTest::newRow( "filter-force-escape04" ) << QString::fromLatin1( "{{ a|force_escape|force_escape }}" ) << dict << QString::fromLatin1( "x&amp;amp;y" ) << NoError;
605
606   //  Because the result of force_escape is "safe", an additional
607   //  escape filter has no effect.
608
609   QTest::newRow( "filter-force-escape05" ) << QString::fromLatin1( "{% autoescape off %}{{ a|force_escape|escape }}{% endautoescape %}" ) << dict << QString::fromLatin1( "x&amp;y" ) << NoError;
610   QTest::newRow( "filter-force-escape06" ) << QString::fromLatin1( "{{ a|force_escape|escape }}" ) << dict << QString::fromLatin1( "x&amp;y" ) << NoError;
611   QTest::newRow( "filter-force-escape07" ) << QString::fromLatin1( "{% autoescape off %}{{ a|escape|force_escape }}{% endautoescape %}" ) << dict << QString::fromLatin1( "x&amp;y" ) << NoError;
612   QTest::newRow( "filter-force-escape08" ) << QString::fromLatin1( "{{ a|escape|force_escape }}" ) << dict << QString::fromLatin1( "x&amp;y" ) << NoError;
613
614   //  The contents in "linebreaks" and "linebreaksbr" are escaped
615   //  according to the current autoescape setting.
616
617   dict.clear();
618   dict.insert( QLatin1String( "a" ), QLatin1String( "x&\ny" ) );
619   dict.insert( QLatin1String( "b" ), markSafe( QString::fromLatin1( "x&\ny" ) ) );
620
621   QTest::newRow( "filter-linebreaks01" ) << QString::fromLatin1( "{{ a|linebreaks }} {{ b|linebreaks }}" ) << dict << QString::fromLatin1( "<p>x&amp;<br />y</p> <p>x&<br />y</p>" ) << NoError;
622   QTest::newRow( "filter-linebreaks02" ) << QString::fromLatin1( "{% autoescape off %}{{ a|linebreaks }} {{ b|linebreaks }}{% endautoescape %}" ) << dict << QString::fromLatin1( "<p>x&<br />y</p> <p>x&<br />y</p>" ) << NoError;
623   QTest::newRow( "filter-linebreaksbr01" ) << QString::fromLatin1( "{{ a|linebreaksbr }} {{ b|linebreaksbr }}" ) << dict << QString::fromLatin1( "x&amp;<br />y x&<br />y" ) << NoError;
624   QTest::newRow( "filter-linebreaksbr02" ) << QString::fromLatin1( "{% autoescape off %}{{ a|linebreaksbr }} {{ b|linebreaksbr }}{% endautoescape %}" ) << dict << QString::fromLatin1( "x&<br />y x&<br />y" ) << NoError;
625
626   dict.clear();
627   dict.insert( QLatin1String( "a" ), QLatin1String( "<b>hello</b>" ) );
628
629   QTest::newRow( "filter-safe01" ) << QString::fromLatin1( "{{ a }} -- {{ a|safe }}" ) << dict << QString::fromLatin1( "&lt;b&gt;hello&lt;/b&gt; -- <b>hello</b>" ) << NoError;
630   QTest::newRow( "filter-safe02" ) << QString::fromLatin1( "{% autoescape off %}{{ a }} -- {{ a|safe }}{% endautoescape %}" ) << dict << QString::fromLatin1( "<b>hello</b> -- <b>hello</b>" ) << NoError;
631
632   dict.clear();
633   dict.insert( QLatin1String( "a" ), QVariantList() << QString::fromLatin1( "&" ) << QString::fromLatin1( "<" ) );
634
635   QTest::newRow( "filter-safeseq01" ) << "{{ a|join:\", \" }} -- {{ a|safeseq|join:\", \" }}" << dict << QString::fromLatin1( "&amp;, &lt; -- &, <" ) << NoError;
636   QTest::newRow( "filter-safeseq02" ) << "{% autoescape off %}{{ a|join:\", \" }} -- {{ a|safeseq|join:\", \" }}{% endautoescape %}" << dict << QString::fromLatin1( "&, < -- &, <" ) << NoError;
637
638   dict.clear();
639   dict.insert( QLatin1String( "a" ), QLatin1String( "<a>x</a> <p><b>y</b></p>" ) );
640   dict.insert( QLatin1String( "b" ), QVariant::fromValue( markSafe( QString::fromLatin1( "<a>x</a> <p><b>y</b></p>" ) ) ) );
641
642   QTest::newRow( "filter-removetags01" ) << "{{ a|removetags:\"a b\" }} {{ b|removetags:\"a b\" }}" << dict << QString::fromLatin1( "x &lt;p&gt;y&lt;/p&gt; x <p>y</p>" ) << NoError;
643   QTest::newRow( "filter-removetags02" ) << "{% autoescape off %}{{ a|removetags:\"a b\" }} {{ b|removetags:\"a b\" }}{% endautoescape %}" << dict << QString::fromLatin1( "x <p>y</p> x <p>y</p>" ) << NoError;
644   QTest::newRow( "filter-striptags01" ) << QString::fromLatin1( "{{ a|striptags }} {{ b|striptags }}" ) << dict << QString::fromLatin1( "x y x y" ) << NoError;
645   QTest::newRow( "filter-striptags02" ) << QString::fromLatin1( "{% autoescape off %}{{ a|striptags }} {{ b|striptags }}{% endautoescape %}" ) << dict << QString::fromLatin1( "x y x y" ) << NoError;
646
647 }
648
649 void TestFilters::testListFilters_data()
650 {
651   QTest::addColumn<QString>( "input" );
652   QTest::addColumn<Dict>( "dict" );
653   QTest::addColumn<QString>( "output" );
654   QTest::addColumn<Grantlee::Error>( "error" );
655
656   Dict dict;
657
658   dict.insert( QLatin1String( "a" ), QVariantList() << QString::fromLatin1( "a&b" ) << QString::fromLatin1( "x" ) );
659   dict.insert( QLatin1String( "b" ), QVariantList() << QVariant::fromValue( markSafe( QString::fromLatin1( "a&b" ) ) ) << QString::fromLatin1( "x" ) );
660
661   QTest::newRow( "filter-first01" ) << QString::fromLatin1( "{{ a|first }} {{ b|first }}" ) << dict << QString::fromLatin1( "a&amp;b a&b" ) << NoError;
662   QTest::newRow( "filter-first02" ) << QString::fromLatin1( "{% autoescape off %}{{ a|first }} {{ b|first }}{% endautoescape %}" ) << dict << QString::fromLatin1( "a&b a&b" ) << NoError;
663
664   dict.clear();
665   dict.insert( QLatin1String( "a" ), QVariantList() << QString::fromLatin1( "x" ) << QString::fromLatin1( "a&b" ) );
666   dict.insert( QLatin1String( "b" ), QVariantList() << QString::fromLatin1( "x" ) << QVariant::fromValue( markSafe( QString::fromLatin1( "a&b" ) ) ) );
667
668   QTest::newRow( "filter-last01" ) << QString::fromLatin1( "{{ a|last }} {{ b|last }}" ) << dict << QString::fromLatin1( "a&amp;b a&b" ) << NoError;
669   QTest::newRow( "filter-last02" ) << QString::fromLatin1( "{% autoescape off %}{{ a|last }} {{ b|last }}{% endautoescape %}" ) << dict << QString::fromLatin1( "a&b a&b" ) << NoError;
670
671   dict.clear();
672   dict.insert( QLatin1String( "a" ), QVariantList() << QString::fromLatin1( "a&b" ) << QString::fromLatin1( "a&b" ) );
673   dict.insert( QLatin1String( "b" ), QVariantList() << QVariant::fromValue( markSafe( QString::fromLatin1( "a&b" ) ) ) << QVariant::fromValue( markSafe( QString::fromLatin1( "a&b" ) ) ) );
674   QTest::newRow( "filter-random01" ) << QString::fromLatin1( "{{ a|random }} {{ b|random }}" ) << dict << QString::fromLatin1( "a&amp;b a&b" ) << NoError;
675   QTest::newRow( "filter-random02" ) << QString::fromLatin1( "{% autoescape off %}{{ a|random }} {{ b|random }}{% endautoescape %}" ) << dict << QString::fromLatin1( "a&b a&b" ) << NoError;
676
677   dict.clear();
678   dict.insert( QLatin1String( "a" ), QLatin1String( "a&b" ) );
679   dict.insert( QLatin1String( "b" ), QVariant::fromValue( markSafe( QString::fromLatin1( "a&b" ) ) ) );
680
681   QTest::newRow( "filter-slice01" ) << "{{ a|slice:\"1:3\" }} {{ b|slice:\"1:3\" }}" << dict << QString::fromLatin1( "&amp;b &b" ) << NoError;
682   QTest::newRow( "filter-slice02" ) << "{% autoescape off %}{{ a|slice:\"1:3\" }} {{ b|slice:\"1:3\" }}{% endautoescape %}" << dict << QString::fromLatin1( "&b &b" ) << NoError;
683
684   dict.clear();
685   QVariantList sublist;
686   sublist << QVariant( QLatin1String( "<y" ) );
687   dict.insert( QLatin1String( "a" ), QVariantList() << QString::fromLatin1( "x>" ) << QVariant( sublist ) );
688
689   QTest::newRow( "filter-unordered_list01" ) << QString::fromLatin1( "{{ 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;
690   QTest::newRow( "filter-unordered_list02" ) << QString::fromLatin1( "{% 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;
691
692   dict.clear();
693   sublist.clear();
694   sublist << markSafe( QString::fromLatin1( "<y" ) );
695   dict.insert( QLatin1String( "a" ), QVariantList() << QString::fromLatin1( "x>" ) << QVariant( sublist ) );
696
697   QTest::newRow( "filter-unordered_list03" ) << QString::fromLatin1( "{{ a|unordered_list }}" ) << dict << "\t<li>x&gt;\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>" << NoError;
698   QTest::newRow( "filter-unordered_list04" ) << QString::fromLatin1( "{% 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;
699
700   dict.clear();
701   sublist.clear();
702   sublist << QVariant( QLatin1String( "<y" ) );
703   dict.insert( QLatin1String( "a" ), QVariantList() << QString::fromLatin1( "x>" ) << QVariant( sublist ) );
704
705   QTest::newRow( "filter-unordered_list05" ) << QString::fromLatin1( "{% 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;
706
707   //  length filter.
708   dict.clear();
709   dict.insert( QLatin1String( "list" ), QVariantList() << QString::fromLatin1( "4" ) << QVariant() << true << QVariantHash() );
710
711   QTest::newRow( "length01" ) << QString::fromLatin1( "{{ list|length }}" ) << dict << QString::fromLatin1( "4" ) << NoError;
712
713   dict.clear();
714   dict.insert( QLatin1String( "list" ), QVariantList() );
715
716   QTest::newRow( "length02" ) << QString::fromLatin1( "{{ list|length }}" ) << dict << QString::fromLatin1( "0" ) << NoError;
717
718   dict.clear();
719   dict.insert( QLatin1String( "string" ), QLatin1String( "" ) );
720
721   QTest::newRow( "length03" ) << QString::fromLatin1( "{{ string|length }}" ) << dict << QString::fromLatin1( "0" ) << NoError;
722
723   dict.clear();
724   dict.insert( QLatin1String( "string" ), QLatin1String( "django" ) );
725
726   QTest::newRow( "length04" ) << QString::fromLatin1( "{{ string|length }}" ) << dict << QString::fromLatin1( "6" ) << NoError;
727
728   //  Invalid uses that should fail silently.
729
730   dict.clear();
731   dict.insert( QLatin1String( "int" ), 7 );
732
733   QTest::newRow( "length05" ) << QString::fromLatin1( "{{ int|length }}" ) << dict << QString() << NoError;
734
735   dict.clear();
736   dict.insert( QLatin1String( "None" ), QVariant() );
737
738   QTest::newRow( "length06" ) << QString::fromLatin1( "{{ None|length }}" ) << dict << QString() << NoError;
739
740   //  length_is filter.
741
742   dict.clear();
743   dict.insert( QLatin1String( "some_list" ), QVariantList() << QString::fromLatin1( "4" ) << QVariant() << true << QVariantHash() );
744
745   QTest::newRow( "length_is01" ) << "{% if some_list|length_is:\"4\" %}Four{% endif %}" << dict << QString::fromLatin1( "Four" ) << NoError;
746
747   dict.clear();
748   dict.insert( QLatin1String( "some_list" ), QVariantList() << QString::fromLatin1( "4" ) << QVariant() << true << QVariantHash() << 17 );
749
750   QTest::newRow( "length_is02" ) << "{% if some_list|length_is:\"4\" %}Four{% else %}Not Four{% endif %}" << dict << QString::fromLatin1( "Not Four" ) << NoError;
751
752   dict.clear();
753   dict.insert( QLatin1String( "mystring" ), QLatin1String( "word" ) );
754
755   QTest::newRow( "length_is03" ) << "{% if mystring|length_is:\"4\" %}Four{% endif %}" << dict << QString::fromLatin1( "Four" ) << NoError;
756
757   dict.clear();
758   dict.insert( QLatin1String( "mystring" ), QLatin1String( "Python" ) );
759
760   QTest::newRow( "length_is04" ) << "{% if mystring|length_is:\"4\" %}Four{% else %}Not Four{% endif %}" << dict << QString::fromLatin1( "Not Four" ) << NoError;
761
762   dict.clear();
763   dict.insert( QLatin1String( "mystring" ), QLatin1String( "" ) );
764
765   QTest::newRow( "length_is05" ) << "{% if mystring|length_is:\"4\" %}Four{% else %}Not Four{% endif %}" << dict << QString::fromLatin1( "Not Four" ) << NoError;
766
767   dict.clear();
768   dict.insert( QLatin1String( "var" ), QLatin1String( "django" ) );
769
770   QTest::newRow( "length_is06" ) << QString::fromLatin1( "{% with var|length as my_length %}{{ my_length }}{% endwith %}" ) << dict << QString::fromLatin1( "6" ) << NoError;
771
772   //  Boolean return value from length_is should not be coerced to a string
773
774   dict.clear();
775   QTest::newRow( "length_is07" ) << "{% if \"X\"|length_is:0 %}Length is 0{% else %}Length not 0{% endif %}" << dict << QString::fromLatin1( "Length not 0" ) << NoError;
776   QTest::newRow( "length_is08" ) << "{% if \"X\"|length_is:1 %}Length is 1{% else %}Length not 1{% endif %}" << dict << QString::fromLatin1( "Length is 1" ) << NoError;
777
778   //  Invalid uses that should fail silently.
779
780   dict.clear();
781   dict.insert( QLatin1String( "var" ), QLatin1String( "django" ) );
782
783   QTest::newRow( "length_is09" ) << "{{ var|length_is:\"fish\" }}" << dict << QString() << NoError;
784
785   dict.clear();
786   dict.insert( QLatin1String( "int" ), 7 );
787
788   QTest::newRow( "length_is10" ) << "{{ int|length_is:\"1\" }}" << dict << QString() << NoError;
789
790   dict.clear();
791   dict.insert( QLatin1String( "none" ), QVariant() );
792
793   QTest::newRow( "length_is11" ) << "{{ none|length_is:\"1\" }}" << dict << QString() << NoError;
794
795   dict.clear();
796   dict.insert( QLatin1String( "a" ), QVariantList() << QString::fromLatin1( "alpha" ) << QString::fromLatin1( "beta & me" ) );
797
798   QTest::newRow( "join01" ) << "{{ a|join:\", \" }}" << dict << QString::fromLatin1( "alpha, beta &amp; me" ) << NoError;
799   QTest::newRow( "join02" ) << "{% autoescape off %}{{ a|join:\", \" }}{% endautoescape %}" << dict << QString::fromLatin1( "alpha, beta & me" ) << NoError;
800   QTest::newRow( "join03" ) << "{{ a|join:\" &amp; \" }}" << dict << QString::fromLatin1( "alpha &amp; beta &amp; me" ) << NoError;
801   QTest::newRow( "join04" ) << "{% autoescape off %}{{ a|join:\" &amp; \" }}{% endautoescape %}" << dict << QString::fromLatin1( "alpha &amp; beta & me" ) << NoError;
802
803   // arguments to filters are intended to be used unescaped.
804 //   dict.clear();
805 //   dict.insert( QLatin1String( "a" ), QVariantList() << QString::fromLatin1( "alpha" ) << QString::fromLatin1( "beta & me" ) );
806 //   dict.insert( QLatin1String( "var" ), QLatin1String( " & " ) );
807 //
808 //   QTest::newRow( "join05" ) << QString::fromLatin1( "{{ a|join:var }}" ) << dict << QString::fromLatin1( "alpha &amp; beta &amp; me" ) << NoError;
809 //
810 //   dict.clear();
811 //   dict.insert( QLatin1String( "a" ), QVariantList() << QString::fromLatin1( "alpha" ) << QString::fromLatin1( "beta & me" ) );
812 //   dict.insert( QLatin1String( "var" ), QVariant::fromValue( markSafe( QString::fromLatin1( " & " ) ) ) );
813 //
814 //   QTest::newRow( "join06" ) << QString::fromLatin1( "{{ a|join:var }}" ) << dict << QString::fromLatin1( "alpha & beta &amp; me" ) << NoError;
815 //
816 //   dict.clear();
817 //   dict.insert( QLatin1String( "a" ), QVariantList() << QString::fromLatin1( "Alpha" ) << QString::fromLatin1( "Beta & me" ) );
818 //   dict.insert( QLatin1String( "var" ), QLatin1String( " & " ) );
819 //
820 //   QTest::newRow( "join07" ) << QString::fromLatin1( "{{ a|join:var|lower }}" ) << dict << QString::fromLatin1( "alpha &amp; beta &amp; me" ) << NoError;
821 //
822 //   dict.clear();
823 //   dict.insert( QLatin1String( "a" ), QVariantList() << QString::fromLatin1( "Alpha" ) << QString::fromLatin1( "Beta & me" ) );
824 //   dict.insert( QLatin1String( "var" ), QVariant::fromValue( markSafe( QString::fromLatin1( " & " ) ) ) );
825 //
826 //   QTest::newRow( "join08" ) << QString::fromLatin1( "{{ a|join:var|lower }}" ) << dict << QString::fromLatin1( "alpha & beta &amp; me" ) << NoError;
827 }
828
829 void TestFilters::testLogicFilters_data()
830 {
831   QTest::addColumn<QString>( "input" );
832   QTest::addColumn<Dict>( "dict" );
833   QTest::addColumn<QString>( "output" );
834   QTest::addColumn<Grantlee::Error>( "error" );
835
836   Dict dict;
837
838   //  Literal string arguments to the default filter are always treated as
839   //  safe strings, regardless of the auto-escaping state.
840
841   //  Note: we have to use {"a": ""} here, otherwise the invalid template
842   //  variable string interferes with the test result.
843
844   dict.insert( QLatin1String( "a" ), QLatin1String( "" ) );
845
846   QTest::newRow( "filter-default01" ) << "{{ a|default:\"x<\" }}" << dict << QString::fromLatin1( "x<" ) << NoError;
847   QTest::newRow( "filter-default02" ) << "{% autoescape off %}{{ a|default:\"x<\" }}{% endautoescape %}" << dict << QString::fromLatin1( "x<" ) << NoError;
848
849   dict.clear();
850   dict.insert( QLatin1String( "a" ), QVariant::fromValue( markSafe( QString::fromLatin1( "x>" ) ) ) );
851
852   QTest::newRow( "filter-default03" ) << "{{ a|default:\"x<\" }}" << dict << QString::fromLatin1( "x>" ) << NoError;
853   QTest::newRow( "filter-default04" ) << "{% autoescape off %}{{ a|default:\"x<\" }}{% endautoescape %}" << dict << QString::fromLatin1( "x>" ) << NoError;
854
855   dict.clear();
856   dict.insert( QLatin1String( "a" ), QVariant() );
857
858   QTest::newRow( "filter-default_if_none01" ) << "{{ a|default:\"x<\" }}" << dict << QString::fromLatin1( "x<" ) << NoError;
859   QTest::newRow( "filter-default_if_none02" ) << "{% autoescape off %}{{ a|default:\"x<\" }}{% endautoescape %}" << dict << QString::fromLatin1( "x<" ) << NoError;
860
861 }
862
863
864 void TestFilters::testMiscFilters_data()
865 {
866   QTest::addColumn<QString>( "input" );
867   QTest::addColumn<Dict>( "dict" );
868   QTest::addColumn<QString>( "output" );
869   QTest::addColumn<Grantlee::Error>( "error" );
870
871   Dict dict;
872
873 //
874 //   //  {"a": "<1-800-call-me>", "b": mark_safe("<1-800-call-me>")
875 //   QTest::newRow( "filter-phone2numeric01") << QString::fromLatin1( "{{ a|phone2numeric }} {{ b|phone2numeric }}" ) << dict << QString::fromLatin1( "&lt;1-800-2255-63&gt; <1-800-2255-63>" ) << NoError;
876 //
877 //   //  {"a": "<1-800-call-me>", "b": mark_safe("<1-800-call-me>")
878 //   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;
879 //
880   //  Chaining a bunch of safeness-preserving filters should not alter
881   //  the safe status either way.
882
883   dict.insert( QLatin1String( "a" ), QLatin1String( "a < b" ) );
884   dict.insert( QLatin1String( "b" ), QVariant::fromValue( markSafe( QString::fromLatin1( "a < b" ) ) ) );
885
886   QTest::newRow( "chaining01" ) << "{{ a|capfirst|center:\"7\" }}.{{ b|capfirst|center:\"7\" }}" << dict << QString::fromLatin1( " A &lt; b . A < b " ) << NoError;
887   QTest::newRow( "chaining02" ) << "{% autoescape off %}{{ a|capfirst|center:\"7\" }}.{{ b|capfirst|center:\"7\" }}{% endautoescape %}" << dict << QString::fromLatin1( " A < b . A < b " ) << NoError;
888
889   //  Using a filter that forces a string back to unsafe:
890
891   QTest::newRow( "chaining03" ) << "{{ a|cut:\"b\"|capfirst }}.{{ b|cut:\"b\"|capfirst }}" << dict << QString::fromLatin1( "A &lt; .A < " ) << NoError;
892   QTest::newRow( "chaining04" ) << "{% autoescape off %}{{ a|cut:\"b\"|capfirst }}.{{ b|cut:\"b\"|capfirst }}{% endautoescape %}" << dict << QString::fromLatin1( "A < .A < " ) << NoError;
893
894   dict.clear();
895   dict.insert( QLatin1String( "a" ), QLatin1String( "a < b" ) );
896
897
898   //  Using a filter that forces safeness does not lead to double-escaping
899
900   QTest::newRow( "chaining05" ) << QString::fromLatin1( "{{ a|escape|capfirst }}" ) << dict << QString::fromLatin1( "A &lt; b" ) << NoError;
901   QTest::newRow( "chaining06" ) << QString::fromLatin1( "{% autoescape off %}{{ a|escape|capfirst }}{% endautoescape %}" ) << dict << QString::fromLatin1( "A &lt; b" ) << NoError;
902
903   //  Force to safe, then back (also showing why using force_escape too
904   //  early in a chain can lead to unexpected results).
905
906   QTest::newRow( "chaining07" ) << "{{ a|force_escape|cut:\";\" }}" << dict << QString::fromLatin1( "a &amp;lt b" ) << NoError;
907   QTest::newRow( "chaining08" ) << "{% autoescape off %}{{ a|force_escape|cut:\";\" }}{% endautoescape %}" << dict << QString::fromLatin1( "a &lt b" ) << NoError;
908   QTest::newRow( "chaining09" ) << "{{ a|cut:\";\"|force_escape }}" << dict << QString::fromLatin1( "a &lt; b" ) << NoError;
909   QTest::newRow( "chaining10" ) << "{% autoescape off %}{{ a|cut:\";\"|force_escape }}{% endautoescape %}" << dict << QString::fromLatin1( "a &lt; b" ) << NoError;
910   QTest::newRow( "chaining11" ) << "{{ a|cut:\"b\"|safe }}" << dict << QString::fromLatin1( "a < " ) << NoError;
911   QTest::newRow( "chaining12" ) << "{% autoescape off %}{{ a|cut:\"b\"|safe }}{% endautoescape %}" << dict << QString::fromLatin1( "a < " ) << NoError;
912   QTest::newRow( "chaining13" ) << QString::fromLatin1( "{{ a|safe|force_escape }}" ) << dict << QString::fromLatin1( "a &lt; b" ) << NoError;
913   QTest::newRow( "chaining14" ) << QString::fromLatin1( "{% autoescape off %}{{ a|safe|force_escape }}{% endautoescape %}" ) << dict << QString::fromLatin1( "a &lt; b" ) << NoError;
914
915
916 //   //  Filters decorated with stringfilter still respect is_safe.
917 //
918 //   //  {"unsafe": UnsafeClass()
919 //   QTest::newRow( "autoescape-stringfilter01") << QString::fromLatin1( "{{ unsafe|capfirst }}" ) << dict << QString::fromLatin1( "You &amp; me" ) << NoError;
920 //
921 //   //  {"unsafe": UnsafeClass()
922 //   QTest::newRow( "autoescape-stringfilter02") << QString::fromLatin1( "{% autoescape off %}{{ unsafe|capfirst }}{% endautoescape %}" ) << dict << QString::fromLatin1( "You & me" ) << NoError;
923 //
924 //   //  {"safe": SafeClass()
925 //   QTest::newRow( "autoescape-stringfilter03") << QString::fromLatin1( "{{ safe|capfirst }}" ) << dict << QString::fromLatin1( "You &gt; me" ) << NoError;
926 //
927 //   //  {"safe": SafeClass()
928 //   QTest::newRow( "autoescape-stringfilter04") << QString::fromLatin1( "{% autoescape off %}{{ safe|capfirst }}{% endautoescape %}" ) << dict << QString::fromLatin1( "You &gt; me" ) << NoError;
929 //
930 }
931
932 void TestFilters::testIntegerFilters_data()
933 {
934   QTest::addColumn<QString>( "input" );
935   QTest::addColumn<Dict>( "dict" );
936   QTest::addColumn<QString>( "output" );
937   QTest::addColumn<Grantlee::Error>( "error" );
938
939   Dict dict;
940
941   QTest::newRow( "filter-add01" ) << QString::fromLatin1( "{{ 1|add:2 }}" ) << dict << QString::fromLatin1( "3" ) << NoError;
942
943   QTest::newRow( "filter-getdigit01" ) << QString::fromLatin1( "{{ 123|get_digit:1 }}" ) << dict << QString::fromLatin1( "3" ) << NoError;
944   QTest::newRow( "filter-getdigit02" ) << QString::fromLatin1( "{{ 123|get_digit:2 }}" ) << dict << QString::fromLatin1( "2" ) << NoError;
945   QTest::newRow( "filter-getdigit03" ) << QString::fromLatin1( "{{ 123|get_digit:3 }}" ) << dict << QString::fromLatin1( "1" ) << NoError;
946   QTest::newRow( "filter-getdigit04" ) << QString::fromLatin1( "{{ 123|get_digit:4 }}" ) << dict << QString::fromLatin1( "123" ) << NoError;
947
948 }
949
950 QTEST_MAIN( TestFilters )
951 #include "testfilters.moc"
952
953 #endif
954