1from collections import namedtuple
2
3import pytest
4
5from jinja2 import Environment
6from jinja2.utils import Markup
7
8
9async def make_aiter(iter):
10    for item in iter:
11        yield item
12
13
14def mark_dualiter(parameter, factory):
15    def decorator(f):
16        return pytest.mark.parametrize(
17            parameter, [lambda: factory(), lambda: make_aiter(factory())]
18        )(f)
19
20    return decorator
21
22
23@pytest.fixture
24def env_async():
25    return Environment(enable_async=True)
26
27
28@mark_dualiter("foo", lambda: range(10))
29def test_first(env_async, foo):
30    tmpl = env_async.from_string("{{ foo()|first }}")
31    out = tmpl.render(foo=foo)
32    assert out == "0"
33
34
35@mark_dualiter(
36    "items",
37    lambda: [
38        {"foo": 1, "bar": 2},
39        {"foo": 2, "bar": 3},
40        {"foo": 1, "bar": 1},
41        {"foo": 3, "bar": 4},
42    ],
43)
44def test_groupby(env_async, items):
45    tmpl = env_async.from_string(
46        """
47    {%- for grouper, list in items()|groupby('foo') -%}
48        {{ grouper }}{% for x in list %}: {{ x.foo }}, {{ x.bar }}{% endfor %}|
49    {%- endfor %}"""
50    )
51    assert tmpl.render(items=items).split("|") == [
52        "1: 1, 2: 1, 1",
53        "2: 2, 3",
54        "3: 3, 4",
55        "",
56    ]
57
58
59@mark_dualiter("items", lambda: [("a", 1), ("a", 2), ("b", 1)])
60def test_groupby_tuple_index(env_async, items):
61    tmpl = env_async.from_string(
62        """
63    {%- for grouper, list in items()|groupby(0) -%}
64        {{ grouper }}{% for x in list %}:{{ x.1 }}{% endfor %}|
65    {%- endfor %}"""
66    )
67    assert tmpl.render(items=items) == "a:1:2|b:1|"
68
69
70def make_articles():
71    Date = namedtuple("Date", "day,month,year")
72    Article = namedtuple("Article", "title,date")
73    return [
74        Article("aha", Date(1, 1, 1970)),
75        Article("interesting", Date(2, 1, 1970)),
76        Article("really?", Date(3, 1, 1970)),
77        Article("totally not", Date(1, 1, 1971)),
78    ]
79
80
81@mark_dualiter("articles", make_articles)
82def test_groupby_multidot(env_async, articles):
83    tmpl = env_async.from_string(
84        """
85    {%- for year, list in articles()|groupby('date.year') -%}
86        {{ year }}{% for x in list %}[{{ x.title }}]{% endfor %}|
87    {%- endfor %}"""
88    )
89    assert tmpl.render(articles=articles).split("|") == [
90        "1970[aha][interesting][really?]",
91        "1971[totally not]",
92        "",
93    ]
94
95
96@mark_dualiter("int_items", lambda: [1, 2, 3])
97def test_join_env_int(env_async, int_items):
98    tmpl = env_async.from_string('{{ items()|join("|") }}')
99    out = tmpl.render(items=int_items)
100    assert out == "1|2|3"
101
102
103@mark_dualiter("string_items", lambda: ["<foo>", Markup("<span>foo</span>")])
104def test_join_string_list(string_items):
105    env2 = Environment(autoescape=True, enable_async=True)
106    tmpl = env2.from_string('{{ ["<foo>", "<span>foo</span>"|safe]|join }}')
107    assert tmpl.render(items=string_items) == "&lt;foo&gt;<span>foo</span>"
108
109
110def make_users():
111    User = namedtuple("User", "username")
112    return map(User, ["foo", "bar"])
113
114
115@mark_dualiter("users", make_users)
116def test_join_attribute(env_async, users):
117    tmpl = env_async.from_string("""{{ users()|join(', ', 'username') }}""")
118    assert tmpl.render(users=users) == "foo, bar"
119
120
121@mark_dualiter("items", lambda: [1, 2, 3, 4, 5])
122def test_simple_reject(env_async, items):
123    tmpl = env_async.from_string('{{ items()|reject("odd")|join("|") }}')
124    assert tmpl.render(items=items) == "2|4"
125
126
127@mark_dualiter("items", lambda: [None, False, 0, 1, 2, 3, 4, 5])
128def test_bool_reject(env_async, items):
129    tmpl = env_async.from_string('{{ items()|reject|join("|") }}')
130    assert tmpl.render(items=items) == "None|False|0"
131
132
133@mark_dualiter("items", lambda: [1, 2, 3, 4, 5])
134def test_simple_select(env_async, items):
135    tmpl = env_async.from_string('{{ items()|select("odd")|join("|") }}')
136    assert tmpl.render(items=items) == "1|3|5"
137
138
139@mark_dualiter("items", lambda: [None, False, 0, 1, 2, 3, 4, 5])
140def test_bool_select(env_async, items):
141    tmpl = env_async.from_string('{{ items()|select|join("|") }}')
142    assert tmpl.render(items=items) == "1|2|3|4|5"
143
144
145def make_users():
146    User = namedtuple("User", "name,is_active")
147    return [
148        User("john", True),
149        User("jane", True),
150        User("mike", False),
151    ]
152
153
154@mark_dualiter("users", make_users)
155def test_simple_select_attr(env_async, users):
156    tmpl = env_async.from_string(
157        '{{ users()|selectattr("is_active")|map(attribute="name")|join("|") }}'
158    )
159    assert tmpl.render(users=users) == "john|jane"
160
161
162@mark_dualiter("items", lambda: list("123"))
163def test_simple_map(env_async, items):
164    tmpl = env_async.from_string('{{ items()|map("int")|sum }}')
165    assert tmpl.render(items=items) == "6"
166
167
168def test_map_sum(env_async):  # async map + async filter
169    tmpl = env_async.from_string('{{ [[1,2], [3], [4,5,6]]|map("sum")|list }}')
170    assert tmpl.render() == "[3, 3, 15]"
171
172
173@mark_dualiter("users", make_users)
174def test_attribute_map(env_async, users):
175    tmpl = env_async.from_string('{{ users()|map(attribute="name")|join("|") }}')
176    assert tmpl.render(users=users) == "john|jane|mike"
177
178
179def test_empty_map(env_async):
180    tmpl = env_async.from_string('{{ none|map("upper")|list }}')
181    assert tmpl.render() == "[]"
182
183
184@mark_dualiter("items", lambda: [1, 2, 3, 4, 5, 6])
185def test_sum(env_async, items):
186    tmpl = env_async.from_string("""{{ items()|sum }}""")
187    assert tmpl.render(items=items) == "21"
188
189
190@mark_dualiter("items", lambda: [{"value": 23}, {"value": 1}, {"value": 18}])
191def test_sum_attributes(env_async, items):
192    tmpl = env_async.from_string("""{{ items()|sum('value') }}""")
193    assert tmpl.render(items=items)
194
195
196def test_sum_attributes_nested(env_async):
197    tmpl = env_async.from_string("""{{ values|sum('real.value') }}""")
198    assert (
199        tmpl.render(
200            values=[
201                {"real": {"value": 23}},
202                {"real": {"value": 1}},
203                {"real": {"value": 18}},
204            ]
205        )
206        == "42"
207    )
208
209
210def test_sum_attributes_tuple(env_async):
211    tmpl = env_async.from_string("""{{ values.items()|sum('1') }}""")
212    assert tmpl.render(values={"foo": 23, "bar": 1, "baz": 18}) == "42"
213
214
215@mark_dualiter("items", lambda: range(10))
216def test_slice(env_async, items):
217    tmpl = env_async.from_string(
218        "{{ items()|slice(3)|list }}|{{ items()|slice(3, 'X')|list }}"
219    )
220    out = tmpl.render(items=items)
221    assert out == (
222        "[[0, 1, 2, 3], [4, 5, 6], [7, 8, 9]]|"
223        "[[0, 1, 2, 3], [4, 5, 6, 'X'], [7, 8, 9, 'X']]"
224    )
225