IRIS YANG | 3121357 | 2020-08-18 13:17:02 +0000 | [diff] [blame] | 1 | import random |
| 2 | from collections import namedtuple |
| 3 | |
| 4 | import pytest |
| 5 | |
| 6 | from jinja2 import Environment |
| 7 | from jinja2 import Markup |
| 8 | from jinja2 import StrictUndefined |
| 9 | from jinja2 import UndefinedError |
| 10 | |
| 11 | |
| 12 | class Magic: |
| 13 | def __init__(self, value): |
| 14 | self.value = value |
| 15 | |
| 16 | def __str__(self): |
| 17 | return str(self.value) |
| 18 | |
| 19 | |
| 20 | class Magic2: |
| 21 | def __init__(self, value1, value2): |
| 22 | self.value1 = value1 |
| 23 | self.value2 = value2 |
| 24 | |
| 25 | def __str__(self): |
| 26 | return f"({self.value1},{self.value2})" |
| 27 | |
| 28 | |
| 29 | class TestFilter: |
| 30 | def test_filter_calling(self, env): |
| 31 | rv = env.call_filter("sum", [1, 2, 3]) |
| 32 | assert rv == 6 |
| 33 | |
| 34 | def test_capitalize(self, env): |
| 35 | tmpl = env.from_string('{{ "foo bar"|capitalize }}') |
| 36 | assert tmpl.render() == "Foo bar" |
| 37 | |
| 38 | def test_center(self, env): |
| 39 | tmpl = env.from_string('{{ "foo"|center(9) }}') |
| 40 | assert tmpl.render() == " foo " |
| 41 | |
| 42 | def test_default(self, env): |
| 43 | tmpl = env.from_string( |
| 44 | "{{ missing|default('no') }}|{{ false|default('no') }}|" |
| 45 | "{{ false|default('no', true) }}|{{ given|default('no') }}" |
| 46 | ) |
| 47 | assert tmpl.render(given="yes") == "no|False|no|yes" |
| 48 | |
| 49 | @pytest.mark.parametrize( |
| 50 | "args,expect", |
| 51 | ( |
| 52 | ("", "[('aa', 0), ('AB', 3), ('b', 1), ('c', 2)]"), |
| 53 | ("true", "[('AB', 3), ('aa', 0), ('b', 1), ('c', 2)]"), |
| 54 | ('by="value"', "[('aa', 0), ('b', 1), ('c', 2), ('AB', 3)]"), |
| 55 | ("reverse=true", "[('c', 2), ('b', 1), ('AB', 3), ('aa', 0)]"), |
| 56 | ), |
| 57 | ) |
| 58 | def test_dictsort(self, env, args, expect): |
| 59 | t = env.from_string(f"{{{{ foo|dictsort({args}) }}}}") |
| 60 | out = t.render(foo={"aa": 0, "b": 1, "c": 2, "AB": 3}) |
| 61 | assert out == expect |
| 62 | |
| 63 | def test_batch(self, env): |
| 64 | tmpl = env.from_string("{{ foo|batch(3)|list }}|{{ foo|batch(3, 'X')|list }}") |
| 65 | out = tmpl.render(foo=list(range(10))) |
| 66 | assert out == ( |
| 67 | "[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]|" |
| 68 | "[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 'X', 'X']]" |
| 69 | ) |
| 70 | |
| 71 | def test_slice(self, env): |
| 72 | tmpl = env.from_string("{{ foo|slice(3)|list }}|{{ foo|slice(3, 'X')|list }}") |
| 73 | out = tmpl.render(foo=list(range(10))) |
| 74 | assert out == ( |
| 75 | "[[0, 1, 2, 3], [4, 5, 6], [7, 8, 9]]|" |
| 76 | "[[0, 1, 2, 3], [4, 5, 6, 'X'], [7, 8, 9, 'X']]" |
| 77 | ) |
| 78 | |
| 79 | def test_escape(self, env): |
| 80 | tmpl = env.from_string("""{{ '<">&'|escape }}""") |
| 81 | out = tmpl.render() |
| 82 | assert out == "<">&" |
| 83 | |
| 84 | @pytest.mark.parametrize( |
| 85 | ("chars", "expect"), [(None, "..stays.."), (".", " ..stays"), (" .", "stays")] |
| 86 | ) |
| 87 | def test_trim(self, env, chars, expect): |
| 88 | tmpl = env.from_string("{{ foo|trim(chars) }}") |
| 89 | out = tmpl.render(foo=" ..stays..", chars=chars) |
| 90 | assert out == expect |
| 91 | |
| 92 | def test_striptags(self, env): |
| 93 | tmpl = env.from_string("""{{ foo|striptags }}""") |
| 94 | out = tmpl.render( |
| 95 | foo=' <p>just a small \n <a href="#">' |
| 96 | "example</a> link</p>\n<p>to a webpage</p> " |
| 97 | "<!-- <p>and some commented stuff</p> -->" |
| 98 | ) |
| 99 | assert out == "just a small example link to a webpage" |
| 100 | |
| 101 | def test_filesizeformat(self, env): |
| 102 | tmpl = env.from_string( |
| 103 | "{{ 100|filesizeformat }}|" |
| 104 | "{{ 1000|filesizeformat }}|" |
| 105 | "{{ 1000000|filesizeformat }}|" |
| 106 | "{{ 1000000000|filesizeformat }}|" |
| 107 | "{{ 1000000000000|filesizeformat }}|" |
| 108 | "{{ 100|filesizeformat(true) }}|" |
| 109 | "{{ 1000|filesizeformat(true) }}|" |
| 110 | "{{ 1000000|filesizeformat(true) }}|" |
| 111 | "{{ 1000000000|filesizeformat(true) }}|" |
| 112 | "{{ 1000000000000|filesizeformat(true) }}" |
| 113 | ) |
| 114 | out = tmpl.render() |
| 115 | assert out == ( |
| 116 | "100 Bytes|1.0 kB|1.0 MB|1.0 GB|1.0 TB|100 Bytes|" |
| 117 | "1000 Bytes|976.6 KiB|953.7 MiB|931.3 GiB" |
| 118 | ) |
| 119 | |
| 120 | def test_filesizeformat_issue59(self, env): |
| 121 | tmpl = env.from_string( |
| 122 | "{{ 300|filesizeformat }}|" |
| 123 | "{{ 3000|filesizeformat }}|" |
| 124 | "{{ 3000000|filesizeformat }}|" |
| 125 | "{{ 3000000000|filesizeformat }}|" |
| 126 | "{{ 3000000000000|filesizeformat }}|" |
| 127 | "{{ 300|filesizeformat(true) }}|" |
| 128 | "{{ 3000|filesizeformat(true) }}|" |
| 129 | "{{ 3000000|filesizeformat(true) }}" |
| 130 | ) |
| 131 | out = tmpl.render() |
| 132 | assert out == ( |
| 133 | "300 Bytes|3.0 kB|3.0 MB|3.0 GB|3.0 TB|300 Bytes|2.9 KiB|2.9 MiB" |
| 134 | ) |
| 135 | |
| 136 | def test_first(self, env): |
| 137 | tmpl = env.from_string("{{ foo|first }}") |
| 138 | out = tmpl.render(foo=list(range(10))) |
| 139 | assert out == "0" |
| 140 | |
| 141 | @pytest.mark.parametrize( |
| 142 | ("value", "expect"), (("42", "42.0"), ("abc", "0.0"), ("32.32", "32.32")) |
| 143 | ) |
| 144 | def test_float(self, env, value, expect): |
| 145 | t = env.from_string("{{ value|float }}") |
| 146 | assert t.render(value=value) == expect |
| 147 | |
| 148 | def test_float_default(self, env): |
| 149 | t = env.from_string("{{ value|float(default=1.0) }}") |
| 150 | assert t.render(value="abc") == "1.0" |
| 151 | |
| 152 | def test_format(self, env): |
| 153 | tmpl = env.from_string("{{ '%s|%s'|format('a', 'b') }}") |
| 154 | out = tmpl.render() |
| 155 | assert out == "a|b" |
| 156 | |
| 157 | @staticmethod |
| 158 | def _test_indent_multiline_template(env, markup=False): |
| 159 | text = "\n".join(["", "foo bar", '"baz"', ""]) |
| 160 | if markup: |
| 161 | text = Markup(text) |
| 162 | t = env.from_string("{{ foo|indent(2, false, false) }}") |
| 163 | assert t.render(foo=text) == '\n foo bar\n "baz"\n' |
| 164 | t = env.from_string("{{ foo|indent(2, false, true) }}") |
| 165 | assert t.render(foo=text) == '\n foo bar\n "baz"\n ' |
| 166 | t = env.from_string("{{ foo|indent(2, true, false) }}") |
| 167 | assert t.render(foo=text) == ' \n foo bar\n "baz"\n' |
| 168 | t = env.from_string("{{ foo|indent(2, true, true) }}") |
| 169 | assert t.render(foo=text) == ' \n foo bar\n "baz"\n ' |
| 170 | |
| 171 | def test_indent(self, env): |
| 172 | self._test_indent_multiline_template(env) |
| 173 | t = env.from_string('{{ "jinja"|indent }}') |
| 174 | assert t.render() == "jinja" |
| 175 | t = env.from_string('{{ "jinja"|indent(first=true) }}') |
| 176 | assert t.render() == " jinja" |
| 177 | t = env.from_string('{{ "jinja"|indent(blank=true) }}') |
| 178 | assert t.render() == "jinja" |
| 179 | |
| 180 | def test_indent_markup_input(self, env): |
| 181 | """ |
| 182 | Tests cases where the filter input is a Markup type |
| 183 | """ |
| 184 | self._test_indent_multiline_template(env, markup=True) |
| 185 | |
| 186 | @pytest.mark.parametrize( |
| 187 | ("value", "expect"), |
| 188 | ( |
| 189 | ("42", "42"), |
| 190 | ("abc", "0"), |
| 191 | ("32.32", "32"), |
| 192 | ("12345678901234567890", "12345678901234567890"), |
| 193 | ), |
| 194 | ) |
| 195 | def test_int(self, env, value, expect): |
| 196 | t = env.from_string("{{ value|int }}") |
| 197 | assert t.render(value=value) == expect |
| 198 | |
| 199 | @pytest.mark.parametrize( |
| 200 | ("value", "base", "expect"), |
| 201 | (("0x4d32", 16, "19762"), ("011", 8, "9"), ("0x33Z", 16, "0"),), |
| 202 | ) |
| 203 | def test_int_base(self, env, value, base, expect): |
| 204 | t = env.from_string("{{ value|int(base=base) }}") |
| 205 | assert t.render(value=value, base=base) == expect |
| 206 | |
| 207 | def test_int_default(self, env): |
| 208 | t = env.from_string("{{ value|int(default=1) }}") |
| 209 | assert t.render(value="abc") == "1" |
| 210 | |
| 211 | def test_int_special_method(self, env): |
| 212 | class IntIsh: |
| 213 | def __int__(self): |
| 214 | return 42 |
| 215 | |
| 216 | t = env.from_string("{{ value|int }}") |
| 217 | assert t.render(value=IntIsh()) == "42" |
| 218 | |
| 219 | def test_join(self, env): |
| 220 | tmpl = env.from_string('{{ [1, 2, 3]|join("|") }}') |
| 221 | out = tmpl.render() |
| 222 | assert out == "1|2|3" |
| 223 | |
| 224 | env2 = Environment(autoescape=True) |
| 225 | tmpl = env2.from_string('{{ ["<foo>", "<span>foo</span>"|safe]|join }}') |
| 226 | assert tmpl.render() == "<foo><span>foo</span>" |
| 227 | |
| 228 | def test_join_attribute(self, env): |
| 229 | User = namedtuple("User", "username") |
| 230 | tmpl = env.from_string("""{{ users|join(', ', 'username') }}""") |
| 231 | assert tmpl.render(users=map(User, ["foo", "bar"])) == "foo, bar" |
| 232 | |
| 233 | def test_last(self, env): |
| 234 | tmpl = env.from_string("""{{ foo|last }}""") |
| 235 | out = tmpl.render(foo=list(range(10))) |
| 236 | assert out == "9" |
| 237 | |
| 238 | def test_length(self, env): |
| 239 | tmpl = env.from_string("""{{ "hello world"|length }}""") |
| 240 | out = tmpl.render() |
| 241 | assert out == "11" |
| 242 | |
| 243 | def test_lower(self, env): |
| 244 | tmpl = env.from_string("""{{ "FOO"|lower }}""") |
| 245 | out = tmpl.render() |
| 246 | assert out == "foo" |
| 247 | |
| 248 | def test_pprint(self, env): |
| 249 | from pprint import pformat |
| 250 | |
| 251 | tmpl = env.from_string("""{{ data|pprint }}""") |
| 252 | data = list(range(1000)) |
| 253 | assert tmpl.render(data=data) == pformat(data) |
| 254 | |
| 255 | def test_random(self, env, request): |
| 256 | # restore the random state when the test ends |
| 257 | state = random.getstate() |
| 258 | request.addfinalizer(lambda: random.setstate(state)) |
| 259 | # generate the random values from a known seed |
| 260 | random.seed("jinja") |
| 261 | expected = [random.choice("1234567890") for _ in range(10)] |
| 262 | |
| 263 | # check that the random sequence is generated again by a template |
| 264 | # ensures that filter result is not constant folded |
| 265 | random.seed("jinja") |
| 266 | t = env.from_string('{{ "1234567890"|random }}') |
| 267 | |
| 268 | for value in expected: |
| 269 | assert t.render() == value |
| 270 | |
| 271 | def test_reverse(self, env): |
| 272 | tmpl = env.from_string( |
| 273 | "{{ 'foobar'|reverse|join }}|{{ [1, 2, 3]|reverse|list }}" |
| 274 | ) |
| 275 | assert tmpl.render() == "raboof|[3, 2, 1]" |
| 276 | |
| 277 | def test_string(self, env): |
| 278 | x = [1, 2, 3, 4, 5] |
| 279 | tmpl = env.from_string("""{{ obj|string }}""") |
| 280 | assert tmpl.render(obj=x) == str(x) |
| 281 | |
| 282 | def test_title(self, env): |
| 283 | tmpl = env.from_string("""{{ "foo bar"|title }}""") |
| 284 | assert tmpl.render() == "Foo Bar" |
| 285 | tmpl = env.from_string("""{{ "foo's bar"|title }}""") |
| 286 | assert tmpl.render() == "Foo's Bar" |
| 287 | tmpl = env.from_string("""{{ "foo bar"|title }}""") |
| 288 | assert tmpl.render() == "Foo Bar" |
| 289 | tmpl = env.from_string("""{{ "f bar f"|title }}""") |
| 290 | assert tmpl.render() == "F Bar F" |
| 291 | tmpl = env.from_string("""{{ "foo-bar"|title }}""") |
| 292 | assert tmpl.render() == "Foo-Bar" |
| 293 | tmpl = env.from_string("""{{ "foo\tbar"|title }}""") |
| 294 | assert tmpl.render() == "Foo\tBar" |
| 295 | tmpl = env.from_string("""{{ "FOO\tBAR"|title }}""") |
| 296 | assert tmpl.render() == "Foo\tBar" |
| 297 | tmpl = env.from_string("""{{ "foo (bar)"|title }}""") |
| 298 | assert tmpl.render() == "Foo (Bar)" |
| 299 | tmpl = env.from_string("""{{ "foo {bar}"|title }}""") |
| 300 | assert tmpl.render() == "Foo {Bar}" |
| 301 | tmpl = env.from_string("""{{ "foo [bar]"|title }}""") |
| 302 | assert tmpl.render() == "Foo [Bar]" |
| 303 | tmpl = env.from_string("""{{ "foo <bar>"|title }}""") |
| 304 | assert tmpl.render() == "Foo <Bar>" |
| 305 | |
| 306 | class Foo: |
| 307 | def __str__(self): |
| 308 | return "foo-bar" |
| 309 | |
| 310 | tmpl = env.from_string("""{{ data|title }}""") |
| 311 | out = tmpl.render(data=Foo()) |
| 312 | assert out == "Foo-Bar" |
| 313 | |
| 314 | def test_truncate(self, env): |
| 315 | tmpl = env.from_string( |
| 316 | '{{ data|truncate(15, true, ">>>") }}|' |
| 317 | '{{ data|truncate(15, false, ">>>") }}|' |
| 318 | "{{ smalldata|truncate(15) }}" |
| 319 | ) |
| 320 | out = tmpl.render(data="foobar baz bar" * 1000, smalldata="foobar baz bar") |
| 321 | assert out == "foobar baz b>>>|foobar baz>>>|foobar baz bar" |
| 322 | |
| 323 | def test_truncate_very_short(self, env): |
| 324 | tmpl = env.from_string( |
| 325 | '{{ "foo bar baz"|truncate(9) }}|{{ "foo bar baz"|truncate(9, true) }}' |
| 326 | ) |
| 327 | out = tmpl.render() |
| 328 | assert out == "foo bar baz|foo bar baz" |
| 329 | |
| 330 | def test_truncate_end_length(self, env): |
| 331 | tmpl = env.from_string('{{ "Joel is a slug"|truncate(7, true) }}') |
| 332 | out = tmpl.render() |
| 333 | assert out == "Joel..." |
| 334 | |
| 335 | def test_upper(self, env): |
| 336 | tmpl = env.from_string('{{ "foo"|upper }}') |
| 337 | assert tmpl.render() == "FOO" |
| 338 | |
| 339 | def test_urlize(self, env): |
| 340 | tmpl = env.from_string('{{ "foo http://www.example.com/ bar"|urlize }}') |
| 341 | assert tmpl.render() == ( |
| 342 | 'foo <a href="http://www.example.com/" rel="noopener">' |
| 343 | "http://www.example.com/</a> bar" |
| 344 | ) |
| 345 | |
| 346 | def test_urlize_rel_policy(self): |
| 347 | env = Environment() |
| 348 | env.policies["urlize.rel"] = None |
| 349 | tmpl = env.from_string('{{ "foo http://www.example.com/ bar"|urlize }}') |
| 350 | assert tmpl.render() == ( |
| 351 | 'foo <a href="http://www.example.com/">http://www.example.com/</a> bar' |
| 352 | ) |
| 353 | |
| 354 | def test_urlize_target_parameter(self, env): |
| 355 | tmpl = env.from_string( |
| 356 | '{{ "foo http://www.example.com/ bar"|urlize(target="_blank") }}' |
| 357 | ) |
| 358 | assert ( |
| 359 | tmpl.render() |
| 360 | == 'foo <a href="http://www.example.com/" rel="noopener" target="_blank">' |
| 361 | "http://www.example.com/</a> bar" |
| 362 | ) |
| 363 | |
| 364 | def test_wordcount(self, env): |
| 365 | tmpl = env.from_string('{{ "foo bar baz"|wordcount }}') |
| 366 | assert tmpl.render() == "3" |
| 367 | |
| 368 | strict_env = Environment(undefined=StrictUndefined) |
| 369 | t = strict_env.from_string("{{ s|wordcount }}") |
| 370 | with pytest.raises(UndefinedError): |
| 371 | t.render() |
| 372 | |
| 373 | def test_block(self, env): |
| 374 | tmpl = env.from_string("{% filter lower|escape %}<HEHE>{% endfilter %}") |
| 375 | assert tmpl.render() == "<hehe>" |
| 376 | |
| 377 | def test_chaining(self, env): |
| 378 | tmpl = env.from_string("""{{ ['<foo>', '<bar>']|first|upper|escape }}""") |
| 379 | assert tmpl.render() == "<FOO>" |
| 380 | |
| 381 | def test_sum(self, env): |
| 382 | tmpl = env.from_string("""{{ [1, 2, 3, 4, 5, 6]|sum }}""") |
| 383 | assert tmpl.render() == "21" |
| 384 | |
| 385 | def test_sum_attributes(self, env): |
| 386 | tmpl = env.from_string("""{{ values|sum('value') }}""") |
| 387 | assert tmpl.render(values=[{"value": 23}, {"value": 1}, {"value": 18}]) == "42" |
| 388 | |
| 389 | def test_sum_attributes_nested(self, env): |
| 390 | tmpl = env.from_string("""{{ values|sum('real.value') }}""") |
| 391 | assert ( |
| 392 | tmpl.render( |
| 393 | values=[ |
| 394 | {"real": {"value": 23}}, |
| 395 | {"real": {"value": 1}}, |
| 396 | {"real": {"value": 18}}, |
| 397 | ] |
| 398 | ) |
| 399 | == "42" |
| 400 | ) |
| 401 | |
| 402 | def test_sum_attributes_tuple(self, env): |
| 403 | tmpl = env.from_string("""{{ values.items()|sum('1') }}""") |
| 404 | assert tmpl.render(values={"foo": 23, "bar": 1, "baz": 18}) == "42" |
| 405 | |
| 406 | def test_abs(self, env): |
| 407 | tmpl = env.from_string("""{{ -1|abs }}|{{ 1|abs }}""") |
| 408 | assert tmpl.render() == "1|1", tmpl.render() |
| 409 | |
| 410 | def test_round_positive(self, env): |
| 411 | tmpl = env.from_string( |
| 412 | "{{ 2.7|round }}|{{ 2.1|round }}|" |
| 413 | "{{ 2.1234|round(3, 'floor') }}|" |
| 414 | "{{ 2.1|round(0, 'ceil') }}" |
| 415 | ) |
| 416 | assert tmpl.render() == "3.0|2.0|2.123|3.0", tmpl.render() |
| 417 | |
| 418 | def test_round_negative(self, env): |
| 419 | tmpl = env.from_string( |
| 420 | "{{ 21.3|round(-1)}}|" |
| 421 | "{{ 21.3|round(-1, 'ceil')}}|" |
| 422 | "{{ 21.3|round(-1, 'floor')}}" |
| 423 | ) |
| 424 | assert tmpl.render() == "20.0|30.0|20.0", tmpl.render() |
| 425 | |
| 426 | def test_xmlattr(self, env): |
| 427 | tmpl = env.from_string( |
| 428 | "{{ {'foo': 42, 'bar': 23, 'fish': none, " |
| 429 | "'spam': missing, 'blub:blub': '<?>'}|xmlattr }}" |
| 430 | ) |
| 431 | out = tmpl.render().split() |
| 432 | assert len(out) == 3 |
| 433 | assert 'foo="42"' in out |
| 434 | assert 'bar="23"' in out |
| 435 | assert 'blub:blub="<?>"' in out |
| 436 | |
| 437 | def test_sort1(self, env): |
| 438 | tmpl = env.from_string("{{ [2, 3, 1]|sort }}|{{ [2, 3, 1]|sort(true) }}") |
| 439 | assert tmpl.render() == "[1, 2, 3]|[3, 2, 1]" |
| 440 | |
| 441 | def test_sort2(self, env): |
| 442 | tmpl = env.from_string('{{ "".join(["c", "A", "b", "D"]|sort) }}') |
| 443 | assert tmpl.render() == "AbcD" |
| 444 | |
| 445 | def test_sort3(self, env): |
| 446 | tmpl = env.from_string("""{{ ['foo', 'Bar', 'blah']|sort }}""") |
| 447 | assert tmpl.render() == "['Bar', 'blah', 'foo']" |
| 448 | |
| 449 | def test_sort4(self, env): |
| 450 | tmpl = env.from_string("""{{ items|sort(attribute='value')|join }}""") |
| 451 | assert tmpl.render(items=map(Magic, [3, 2, 4, 1])) == "1234" |
| 452 | |
| 453 | def test_sort5(self, env): |
| 454 | tmpl = env.from_string("""{{ items|sort(attribute='value.0')|join }}""") |
| 455 | assert tmpl.render(items=map(Magic, [[3], [2], [4], [1]])) == "[1][2][3][4]" |
| 456 | |
| 457 | def test_sort6(self, env): |
| 458 | tmpl = env.from_string("""{{ items|sort(attribute='value1,value2')|join }}""") |
| 459 | assert ( |
| 460 | tmpl.render( |
| 461 | items=map( |
| 462 | lambda x: Magic2(x[0], x[1]), [(3, 1), (2, 2), (2, 1), (2, 5)] |
| 463 | ) |
| 464 | ) |
| 465 | == "(2,1)(2,2)(2,5)(3,1)" |
| 466 | ) |
| 467 | |
| 468 | def test_sort7(self, env): |
| 469 | tmpl = env.from_string("""{{ items|sort(attribute='value2,value1')|join }}""") |
| 470 | assert ( |
| 471 | tmpl.render( |
| 472 | items=map( |
| 473 | lambda x: Magic2(x[0], x[1]), [(3, 1), (2, 2), (2, 1), (2, 5)] |
| 474 | ) |
| 475 | ) |
| 476 | == "(2,1)(3,1)(2,2)(2,5)" |
| 477 | ) |
| 478 | |
| 479 | def test_sort8(self, env): |
| 480 | tmpl = env.from_string( |
| 481 | """{{ items|sort(attribute='value1.0,value2.0')|join }}""" |
| 482 | ) |
| 483 | assert ( |
| 484 | tmpl.render( |
| 485 | items=map( |
| 486 | lambda x: Magic2(x[0], x[1]), |
| 487 | [([3], [1]), ([2], [2]), ([2], [1]), ([2], [5])], |
| 488 | ) |
| 489 | ) |
| 490 | == "([2],[1])([2],[2])([2],[5])([3],[1])" |
| 491 | ) |
| 492 | |
| 493 | def test_unique(self, env): |
| 494 | t = env.from_string('{{ "".join(["b", "A", "a", "b"]|unique) }}') |
| 495 | assert t.render() == "bA" |
| 496 | |
| 497 | def test_unique_case_sensitive(self, env): |
| 498 | t = env.from_string('{{ "".join(["b", "A", "a", "b"]|unique(true)) }}') |
| 499 | assert t.render() == "bAa" |
| 500 | |
| 501 | def test_unique_attribute(self, env): |
| 502 | t = env.from_string("{{ items|unique(attribute='value')|join }}") |
| 503 | assert t.render(items=map(Magic, [3, 2, 4, 1, 2])) == "3241" |
| 504 | |
| 505 | @pytest.mark.parametrize( |
| 506 | "source,expect", |
| 507 | ( |
| 508 | ('{{ ["a", "B"]|min }}', "a"), |
| 509 | ('{{ ["a", "B"]|min(case_sensitive=true) }}', "B"), |
| 510 | ("{{ []|min }}", ""), |
| 511 | ('{{ ["a", "B"]|max }}', "B"), |
| 512 | ('{{ ["a", "B"]|max(case_sensitive=true) }}', "a"), |
| 513 | ("{{ []|max }}", ""), |
| 514 | ), |
| 515 | ) |
| 516 | def test_min_max(self, env, source, expect): |
| 517 | t = env.from_string(source) |
| 518 | assert t.render() == expect |
| 519 | |
| 520 | @pytest.mark.parametrize("name,expect", (("min", "1"), ("max", "9"),)) |
| 521 | def test_min_max_attribute(self, env, name, expect): |
| 522 | t = env.from_string("{{ items|" + name + '(attribute="value") }}') |
| 523 | assert t.render(items=map(Magic, [5, 1, 9])) == expect |
| 524 | |
| 525 | def test_groupby(self, env): |
| 526 | tmpl = env.from_string( |
| 527 | """ |
| 528 | {%- for grouper, list in [{'foo': 1, 'bar': 2}, |
| 529 | {'foo': 2, 'bar': 3}, |
| 530 | {'foo': 1, 'bar': 1}, |
| 531 | {'foo': 3, 'bar': 4}]|groupby('foo') -%} |
| 532 | {{ grouper }}{% for x in list %}: {{ x.foo }}, {{ x.bar }}{% endfor %}| |
| 533 | {%- endfor %}""" |
| 534 | ) |
| 535 | assert tmpl.render().split("|") == ["1: 1, 2: 1, 1", "2: 2, 3", "3: 3, 4", ""] |
| 536 | |
| 537 | def test_groupby_tuple_index(self, env): |
| 538 | tmpl = env.from_string( |
| 539 | """ |
| 540 | {%- for grouper, list in [('a', 1), ('a', 2), ('b', 1)]|groupby(0) -%} |
| 541 | {{ grouper }}{% for x in list %}:{{ x.1 }}{% endfor %}| |
| 542 | {%- endfor %}""" |
| 543 | ) |
| 544 | assert tmpl.render() == "a:1:2|b:1|" |
| 545 | |
| 546 | def test_groupby_multidot(self, env): |
| 547 | Date = namedtuple("Date", "day,month,year") |
| 548 | Article = namedtuple("Article", "title,date") |
| 549 | articles = [ |
| 550 | Article("aha", Date(1, 1, 1970)), |
| 551 | Article("interesting", Date(2, 1, 1970)), |
| 552 | Article("really?", Date(3, 1, 1970)), |
| 553 | Article("totally not", Date(1, 1, 1971)), |
| 554 | ] |
| 555 | tmpl = env.from_string( |
| 556 | """ |
| 557 | {%- for year, list in articles|groupby('date.year') -%} |
| 558 | {{ year }}{% for x in list %}[{{ x.title }}]{% endfor %}| |
| 559 | {%- endfor %}""" |
| 560 | ) |
| 561 | assert tmpl.render(articles=articles).split("|") == [ |
| 562 | "1970[aha][interesting][really?]", |
| 563 | "1971[totally not]", |
| 564 | "", |
| 565 | ] |
| 566 | |
| 567 | def test_filtertag(self, env): |
| 568 | tmpl = env.from_string( |
| 569 | "{% filter upper|replace('FOO', 'foo') %}foobar{% endfilter %}" |
| 570 | ) |
| 571 | assert tmpl.render() == "fooBAR" |
| 572 | |
| 573 | def test_replace(self, env): |
| 574 | env = Environment() |
| 575 | tmpl = env.from_string('{{ string|replace("o", 42) }}') |
| 576 | assert tmpl.render(string="<foo>") == "<f4242>" |
| 577 | env = Environment(autoescape=True) |
| 578 | tmpl = env.from_string('{{ string|replace("o", 42) }}') |
| 579 | assert tmpl.render(string="<foo>") == "<f4242>" |
| 580 | tmpl = env.from_string('{{ string|replace("<", 42) }}') |
| 581 | assert tmpl.render(string="<foo>") == "42foo>" |
| 582 | tmpl = env.from_string('{{ string|replace("o", ">x<") }}') |
| 583 | assert tmpl.render(string=Markup("foo")) == "f>x<>x<" |
| 584 | |
| 585 | def test_forceescape(self, env): |
| 586 | tmpl = env.from_string("{{ x|forceescape }}") |
| 587 | assert tmpl.render(x=Markup("<div />")) == "<div />" |
| 588 | |
| 589 | def test_safe(self, env): |
| 590 | env = Environment(autoescape=True) |
| 591 | tmpl = env.from_string('{{ "<div>foo</div>"|safe }}') |
| 592 | assert tmpl.render() == "<div>foo</div>" |
| 593 | tmpl = env.from_string('{{ "<div>foo</div>" }}') |
| 594 | assert tmpl.render() == "<div>foo</div>" |
| 595 | |
| 596 | @pytest.mark.parametrize( |
| 597 | ("value", "expect"), |
| 598 | [ |
| 599 | ("Hello, world!", "Hello%2C%20world%21"), |
| 600 | ("Hello, world\u203d", "Hello%2C%20world%E2%80%BD"), |
| 601 | ({"f": 1}, "f=1"), |
| 602 | ([("f", 1), ("z", 2)], "f=1&z=2"), |
| 603 | ({"\u203d": 1}, "%E2%80%BD=1"), |
| 604 | ({0: 1}, "0=1"), |
| 605 | ([("a b/c", "a b/c")], "a+b%2Fc=a+b%2Fc"), |
| 606 | ("a b/c", "a%20b/c"), |
| 607 | ], |
| 608 | ) |
| 609 | def test_urlencode(self, value, expect): |
| 610 | e = Environment(autoescape=True) |
| 611 | t = e.from_string("{{ value|urlencode }}") |
| 612 | assert t.render(value=value) == expect |
| 613 | |
| 614 | def test_simple_map(self, env): |
| 615 | env = Environment() |
| 616 | tmpl = env.from_string('{{ ["1", "2", "3"]|map("int")|sum }}') |
| 617 | assert tmpl.render() == "6" |
| 618 | |
| 619 | def test_map_sum(self, env): |
| 620 | tmpl = env.from_string('{{ [[1,2], [3], [4,5,6]]|map("sum")|list }}') |
| 621 | assert tmpl.render() == "[3, 3, 15]" |
| 622 | |
| 623 | def test_attribute_map(self, env): |
| 624 | User = namedtuple("User", "name") |
| 625 | env = Environment() |
| 626 | users = [ |
| 627 | User("john"), |
| 628 | User("jane"), |
| 629 | User("mike"), |
| 630 | ] |
| 631 | tmpl = env.from_string('{{ users|map(attribute="name")|join("|") }}') |
| 632 | assert tmpl.render(users=users) == "john|jane|mike" |
| 633 | |
| 634 | def test_empty_map(self, env): |
| 635 | env = Environment() |
| 636 | tmpl = env.from_string('{{ none|map("upper")|list }}') |
| 637 | assert tmpl.render() == "[]" |
| 638 | |
| 639 | def test_map_default(self, env): |
| 640 | Fullname = namedtuple("Fullname", "firstname,lastname") |
| 641 | Firstname = namedtuple("Firstname", "firstname") |
| 642 | env = Environment() |
| 643 | tmpl = env.from_string( |
| 644 | '{{ users|map(attribute="lastname", default="smith")|join(", ") }}' |
| 645 | ) |
| 646 | users = [ |
| 647 | Fullname("john", "lennon"), |
| 648 | Fullname("jane", "edwards"), |
| 649 | Fullname("jon", None), |
| 650 | Firstname("mike"), |
| 651 | ] |
| 652 | assert tmpl.render(users=users) == "lennon, edwards, None, smith" |
| 653 | |
| 654 | def test_simple_select(self, env): |
| 655 | env = Environment() |
| 656 | tmpl = env.from_string('{{ [1, 2, 3, 4, 5]|select("odd")|join("|") }}') |
| 657 | assert tmpl.render() == "1|3|5" |
| 658 | |
| 659 | def test_bool_select(self, env): |
| 660 | env = Environment() |
| 661 | tmpl = env.from_string('{{ [none, false, 0, 1, 2, 3, 4, 5]|select|join("|") }}') |
| 662 | assert tmpl.render() == "1|2|3|4|5" |
| 663 | |
| 664 | def test_simple_reject(self, env): |
| 665 | env = Environment() |
| 666 | tmpl = env.from_string('{{ [1, 2, 3, 4, 5]|reject("odd")|join("|") }}') |
| 667 | assert tmpl.render() == "2|4" |
| 668 | |
| 669 | def test_bool_reject(self, env): |
| 670 | env = Environment() |
| 671 | tmpl = env.from_string('{{ [none, false, 0, 1, 2, 3, 4, 5]|reject|join("|") }}') |
| 672 | assert tmpl.render() == "None|False|0" |
| 673 | |
| 674 | def test_simple_select_attr(self, env): |
| 675 | User = namedtuple("User", "name,is_active") |
| 676 | env = Environment() |
| 677 | users = [ |
| 678 | User("john", True), |
| 679 | User("jane", True), |
| 680 | User("mike", False), |
| 681 | ] |
| 682 | tmpl = env.from_string( |
| 683 | '{{ users|selectattr("is_active")|map(attribute="name")|join("|") }}' |
| 684 | ) |
| 685 | assert tmpl.render(users=users) == "john|jane" |
| 686 | |
| 687 | def test_simple_reject_attr(self, env): |
| 688 | User = namedtuple("User", "name,is_active") |
| 689 | env = Environment() |
| 690 | users = [ |
| 691 | User("john", True), |
| 692 | User("jane", True), |
| 693 | User("mike", False), |
| 694 | ] |
| 695 | tmpl = env.from_string( |
| 696 | '{{ users|rejectattr("is_active")|map(attribute="name")|join("|") }}' |
| 697 | ) |
| 698 | assert tmpl.render(users=users) == "mike" |
| 699 | |
| 700 | def test_func_select_attr(self, env): |
| 701 | User = namedtuple("User", "id,name") |
| 702 | env = Environment() |
| 703 | users = [ |
| 704 | User(1, "john"), |
| 705 | User(2, "jane"), |
| 706 | User(3, "mike"), |
| 707 | ] |
| 708 | tmpl = env.from_string( |
| 709 | '{{ users|selectattr("id", "odd")|map(attribute="name")|join("|") }}' |
| 710 | ) |
| 711 | assert tmpl.render(users=users) == "john|mike" |
| 712 | |
| 713 | def test_func_reject_attr(self, env): |
| 714 | User = namedtuple("User", "id,name") |
| 715 | env = Environment() |
| 716 | users = [ |
| 717 | User(1, "john"), |
| 718 | User(2, "jane"), |
| 719 | User(3, "mike"), |
| 720 | ] |
| 721 | tmpl = env.from_string( |
| 722 | '{{ users|rejectattr("id", "odd")|map(attribute="name")|join("|") }}' |
| 723 | ) |
| 724 | assert tmpl.render(users=users) == "jane" |
| 725 | |
| 726 | def test_json_dump(self): |
| 727 | env = Environment(autoescape=True) |
| 728 | t = env.from_string("{{ x|tojson }}") |
| 729 | assert t.render(x={"foo": "bar"}) == '{"foo": "bar"}' |
| 730 | assert t.render(x="\"ba&r'") == r'"\"ba\u0026r\u0027"' |
| 731 | assert t.render(x="<bar>") == r'"\u003cbar\u003e"' |
| 732 | |
| 733 | def my_dumps(value, **options): |
| 734 | assert options == {"foo": "bar"} |
| 735 | return "42" |
| 736 | |
| 737 | env.policies["json.dumps_function"] = my_dumps |
| 738 | env.policies["json.dumps_kwargs"] = {"foo": "bar"} |
| 739 | assert t.render(x=23) == "42" |
| 740 | |
| 741 | def test_wordwrap(self, env): |
| 742 | env.newline_sequence = "\n" |
| 743 | t = env.from_string("{{ s|wordwrap(20) }}") |
| 744 | result = t.render(s="Hello!\nThis is Jinja saying something.") |
| 745 | assert result == "Hello!\nThis is Jinja saying\nsomething." |