liquid-4-0-2 4.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +235 -0
  3. data/LICENSE +20 -0
  4. data/README.md +108 -0
  5. data/lib/liquid.rb +80 -0
  6. data/lib/liquid/block.rb +77 -0
  7. data/lib/liquid/block_body.rb +142 -0
  8. data/lib/liquid/condition.rb +151 -0
  9. data/lib/liquid/context.rb +226 -0
  10. data/lib/liquid/document.rb +27 -0
  11. data/lib/liquid/drop.rb +78 -0
  12. data/lib/liquid/errors.rb +56 -0
  13. data/lib/liquid/expression.rb +49 -0
  14. data/lib/liquid/extensions.rb +74 -0
  15. data/lib/liquid/file_system.rb +73 -0
  16. data/lib/liquid/forloop_drop.rb +42 -0
  17. data/lib/liquid/i18n.rb +39 -0
  18. data/lib/liquid/interrupts.rb +16 -0
  19. data/lib/liquid/lexer.rb +55 -0
  20. data/lib/liquid/locales/en.yml +26 -0
  21. data/lib/liquid/parse_context.rb +38 -0
  22. data/lib/liquid/parse_tree_visitor.rb +42 -0
  23. data/lib/liquid/parser.rb +90 -0
  24. data/lib/liquid/parser_switching.rb +31 -0
  25. data/lib/liquid/profiler.rb +158 -0
  26. data/lib/liquid/profiler/hooks.rb +23 -0
  27. data/lib/liquid/range_lookup.rb +37 -0
  28. data/lib/liquid/resource_limits.rb +23 -0
  29. data/lib/liquid/standardfilters.rb +485 -0
  30. data/lib/liquid/strainer.rb +66 -0
  31. data/lib/liquid/tablerowloop_drop.rb +62 -0
  32. data/lib/liquid/tag.rb +43 -0
  33. data/lib/liquid/tags/assign.rb +59 -0
  34. data/lib/liquid/tags/break.rb +18 -0
  35. data/lib/liquid/tags/capture.rb +38 -0
  36. data/lib/liquid/tags/case.rb +94 -0
  37. data/lib/liquid/tags/comment.rb +16 -0
  38. data/lib/liquid/tags/continue.rb +18 -0
  39. data/lib/liquid/tags/cycle.rb +65 -0
  40. data/lib/liquid/tags/decrement.rb +35 -0
  41. data/lib/liquid/tags/for.rb +203 -0
  42. data/lib/liquid/tags/if.rb +122 -0
  43. data/lib/liquid/tags/ifchanged.rb +18 -0
  44. data/lib/liquid/tags/include.rb +124 -0
  45. data/lib/liquid/tags/increment.rb +31 -0
  46. data/lib/liquid/tags/raw.rb +47 -0
  47. data/lib/liquid/tags/table_row.rb +62 -0
  48. data/lib/liquid/tags/unless.rb +30 -0
  49. data/lib/liquid/template.rb +254 -0
  50. data/lib/liquid/tokenizer.rb +31 -0
  51. data/lib/liquid/utils.rb +83 -0
  52. data/lib/liquid/variable.rb +148 -0
  53. data/lib/liquid/variable_lookup.rb +88 -0
  54. data/lib/liquid/version.rb +4 -0
  55. data/test/fixtures/en_locale.yml +9 -0
  56. data/test/integration/assign_test.rb +48 -0
  57. data/test/integration/blank_test.rb +106 -0
  58. data/test/integration/block_test.rb +12 -0
  59. data/test/integration/capture_test.rb +50 -0
  60. data/test/integration/context_test.rb +32 -0
  61. data/test/integration/document_test.rb +19 -0
  62. data/test/integration/drop_test.rb +273 -0
  63. data/test/integration/error_handling_test.rb +260 -0
  64. data/test/integration/filter_test.rb +178 -0
  65. data/test/integration/hash_ordering_test.rb +23 -0
  66. data/test/integration/output_test.rb +123 -0
  67. data/test/integration/parse_tree_visitor_test.rb +247 -0
  68. data/test/integration/parsing_quirks_test.rb +122 -0
  69. data/test/integration/render_profiling_test.rb +154 -0
  70. data/test/integration/security_test.rb +80 -0
  71. data/test/integration/standard_filter_test.rb +698 -0
  72. data/test/integration/tags/break_tag_test.rb +15 -0
  73. data/test/integration/tags/continue_tag_test.rb +15 -0
  74. data/test/integration/tags/for_tag_test.rb +410 -0
  75. data/test/integration/tags/if_else_tag_test.rb +188 -0
  76. data/test/integration/tags/include_tag_test.rb +245 -0
  77. data/test/integration/tags/increment_tag_test.rb +23 -0
  78. data/test/integration/tags/raw_tag_test.rb +31 -0
  79. data/test/integration/tags/standard_tag_test.rb +296 -0
  80. data/test/integration/tags/statements_test.rb +111 -0
  81. data/test/integration/tags/table_row_test.rb +64 -0
  82. data/test/integration/tags/unless_else_tag_test.rb +26 -0
  83. data/test/integration/template_test.rb +332 -0
  84. data/test/integration/trim_mode_test.rb +529 -0
  85. data/test/integration/variable_test.rb +96 -0
  86. data/test/test_helper.rb +116 -0
  87. data/test/unit/block_unit_test.rb +58 -0
  88. data/test/unit/condition_unit_test.rb +166 -0
  89. data/test/unit/context_unit_test.rb +489 -0
  90. data/test/unit/file_system_unit_test.rb +35 -0
  91. data/test/unit/i18n_unit_test.rb +37 -0
  92. data/test/unit/lexer_unit_test.rb +51 -0
  93. data/test/unit/parser_unit_test.rb +82 -0
  94. data/test/unit/regexp_unit_test.rb +44 -0
  95. data/test/unit/strainer_unit_test.rb +164 -0
  96. data/test/unit/tag_unit_test.rb +21 -0
  97. data/test/unit/tags/case_tag_unit_test.rb +10 -0
  98. data/test/unit/tags/for_tag_unit_test.rb +13 -0
  99. data/test/unit/tags/if_tag_unit_test.rb +8 -0
  100. data/test/unit/template_unit_test.rb +78 -0
  101. data/test/unit/tokenizer_unit_test.rb +55 -0
  102. data/test/unit/variable_unit_test.rb +162 -0
  103. metadata +224 -0
@@ -0,0 +1,178 @@
1
+ require 'test_helper'
2
+
3
+ module MoneyFilter
4
+ def money(input)
5
+ sprintf(' %d$ ', input)
6
+ end
7
+
8
+ def money_with_underscore(input)
9
+ sprintf(' %d$ ', input)
10
+ end
11
+ end
12
+
13
+ module CanadianMoneyFilter
14
+ def money(input)
15
+ sprintf(' %d$ CAD ', input)
16
+ end
17
+ end
18
+
19
+ module SubstituteFilter
20
+ def substitute(input, params = {})
21
+ input.gsub(/%\{(\w+)\}/) { |match| params[$1] }
22
+ end
23
+ end
24
+
25
+ class FiltersTest < Minitest::Test
26
+ include Liquid
27
+
28
+ module OverrideObjectMethodFilter
29
+ def tap(input)
30
+ "tap overridden"
31
+ end
32
+ end
33
+
34
+ def setup
35
+ @context = Context.new
36
+ end
37
+
38
+ def test_local_filter
39
+ @context['var'] = 1000
40
+ @context.add_filters(MoneyFilter)
41
+
42
+ assert_equal ' 1000$ ', Template.parse("{{var | money}}").render(@context)
43
+ end
44
+
45
+ def test_underscore_in_filter_name
46
+ @context['var'] = 1000
47
+ @context.add_filters(MoneyFilter)
48
+ assert_equal ' 1000$ ', Template.parse("{{var | money_with_underscore}}").render(@context)
49
+ end
50
+
51
+ def test_second_filter_overwrites_first
52
+ @context['var'] = 1000
53
+ @context.add_filters(MoneyFilter)
54
+ @context.add_filters(CanadianMoneyFilter)
55
+
56
+ assert_equal ' 1000$ CAD ', Template.parse("{{var | money}}").render(@context)
57
+ end
58
+
59
+ def test_size
60
+ @context['var'] = 'abcd'
61
+ @context.add_filters(MoneyFilter)
62
+
63
+ assert_equal '4', Template.parse("{{var | size}}").render(@context)
64
+ end
65
+
66
+ def test_join
67
+ @context['var'] = [1, 2, 3, 4]
68
+
69
+ assert_equal "1 2 3 4", Template.parse("{{var | join}}").render(@context)
70
+ end
71
+
72
+ def test_sort
73
+ @context['value'] = 3
74
+ @context['numbers'] = [2, 1, 4, 3]
75
+ @context['words'] = ['expected', 'as', 'alphabetic']
76
+ @context['arrays'] = ['flower', 'are']
77
+ @context['case_sensitive'] = ['sensitive', 'Expected', 'case']
78
+
79
+ assert_equal '1 2 3 4', Template.parse("{{numbers | sort | join}}").render(@context)
80
+ assert_equal 'alphabetic as expected', Template.parse("{{words | sort | join}}").render(@context)
81
+ assert_equal '3', Template.parse("{{value | sort}}").render(@context)
82
+ assert_equal 'are flower', Template.parse("{{arrays | sort | join}}").render(@context)
83
+ assert_equal 'Expected case sensitive', Template.parse("{{case_sensitive | sort | join}}").render(@context)
84
+ end
85
+
86
+ def test_sort_natural
87
+ @context['words'] = ['case', 'Assert', 'Insensitive']
88
+ @context['hashes'] = [{ 'a' => 'A' }, { 'a' => 'b' }, { 'a' => 'C' }]
89
+ @context['objects'] = [TestObject.new('A'), TestObject.new('b'), TestObject.new('C')]
90
+
91
+ # Test strings
92
+ assert_equal 'Assert case Insensitive', Template.parse("{{words | sort_natural | join}}").render(@context)
93
+
94
+ # Test hashes
95
+ assert_equal 'A b C', Template.parse("{{hashes | sort_natural: 'a' | map: 'a' | join}}").render(@context)
96
+
97
+ # Test objects
98
+ assert_equal 'A b C', Template.parse("{{objects | sort_natural: 'a' | map: 'a' | join}}").render(@context)
99
+ end
100
+
101
+ def test_compact
102
+ @context['words'] = ['a', nil, 'b', nil, 'c']
103
+ @context['hashes'] = [{ 'a' => 'A' }, { 'a' => nil }, { 'a' => 'C' }]
104
+ @context['objects'] = [TestObject.new('A'), TestObject.new(nil), TestObject.new('C')]
105
+
106
+ # Test strings
107
+ assert_equal 'a b c', Template.parse("{{words | compact | join}}").render(@context)
108
+
109
+ # Test hashes
110
+ assert_equal 'A C', Template.parse("{{hashes | compact: 'a' | map: 'a' | join}}").render(@context)
111
+
112
+ # Test objects
113
+ assert_equal 'A C', Template.parse("{{objects | compact: 'a' | map: 'a' | join}}").render(@context)
114
+ end
115
+
116
+ def test_strip_html
117
+ @context['var'] = "<b>bla blub</a>"
118
+
119
+ assert_equal "bla blub", Template.parse("{{ var | strip_html }}").render(@context)
120
+ end
121
+
122
+ def test_strip_html_ignore_comments_with_html
123
+ @context['var'] = "<!-- split and some <ul> tag --><b>bla blub</a>"
124
+
125
+ assert_equal "bla blub", Template.parse("{{ var | strip_html }}").render(@context)
126
+ end
127
+
128
+ def test_capitalize
129
+ @context['var'] = "blub"
130
+
131
+ assert_equal "Blub", Template.parse("{{ var | capitalize }}").render(@context)
132
+ end
133
+
134
+ def test_nonexistent_filter_is_ignored
135
+ @context['var'] = 1000
136
+
137
+ assert_equal '1000', Template.parse("{{ var | xyzzy }}").render(@context)
138
+ end
139
+
140
+ def test_filter_with_keyword_arguments
141
+ @context['surname'] = 'john'
142
+ @context['input'] = 'hello %{first_name}, %{last_name}'
143
+ @context.add_filters(SubstituteFilter)
144
+ output = Template.parse(%({{ input | substitute: first_name: surname, last_name: 'doe' }})).render(@context)
145
+ assert_equal 'hello john, doe', output
146
+ end
147
+
148
+ def test_override_object_method_in_filter
149
+ assert_equal "tap overridden", Template.parse("{{var | tap}}").render!({ 'var' => 1000 }, filters: [OverrideObjectMethodFilter])
150
+
151
+ # tap still treated as a non-existent filter
152
+ assert_equal "1000", Template.parse("{{var | tap}}").render!({ 'var' => 1000 })
153
+ end
154
+ end
155
+
156
+ class FiltersInTemplate < Minitest::Test
157
+ include Liquid
158
+
159
+ def test_local_global
160
+ with_global_filter(MoneyFilter) do
161
+ assert_equal " 1000$ ", Template.parse("{{1000 | money}}").render!(nil, nil)
162
+ assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, filters: CanadianMoneyFilter)
163
+ assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, filters: [CanadianMoneyFilter])
164
+ end
165
+ end
166
+
167
+ def test_local_filter_with_deprecated_syntax
168
+ assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, CanadianMoneyFilter)
169
+ assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, [CanadianMoneyFilter])
170
+ end
171
+ end # FiltersTest
172
+
173
+ class TestObject < Liquid::Drop
174
+ attr_accessor :a
175
+ def initialize(a)
176
+ @a = a
177
+ end
178
+ end
@@ -0,0 +1,23 @@
1
+ require 'test_helper'
2
+
3
+ class HashOrderingTest < Minitest::Test
4
+ module MoneyFilter
5
+ def money(input)
6
+ sprintf(' %d$ ', input)
7
+ end
8
+ end
9
+
10
+ module CanadianMoneyFilter
11
+ def money(input)
12
+ sprintf(' %d$ CAD ', input)
13
+ end
14
+ end
15
+
16
+ include Liquid
17
+
18
+ def test_global_register_order
19
+ with_global_filter(MoneyFilter, CanadianMoneyFilter) do
20
+ assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, nil)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,123 @@
1
+ require 'test_helper'
2
+
3
+ module FunnyFilter
4
+ def make_funny(input)
5
+ 'LOL'
6
+ end
7
+
8
+ def cite_funny(input)
9
+ "LOL: #{input}"
10
+ end
11
+
12
+ def add_smiley(input, smiley = ":-)")
13
+ "#{input} #{smiley}"
14
+ end
15
+
16
+ def add_tag(input, tag = "p", id = "foo")
17
+ %(<#{tag} id="#{id}">#{input}</#{tag}>)
18
+ end
19
+
20
+ def paragraph(input)
21
+ "<p>#{input}</p>"
22
+ end
23
+
24
+ def link_to(name, url)
25
+ %(<a href="#{url}">#{name}</a>)
26
+ end
27
+ end
28
+
29
+ class OutputTest < Minitest::Test
30
+ include Liquid
31
+
32
+ def setup
33
+ @assigns = {
34
+ 'best_cars' => 'bmw',
35
+ 'car' => { 'bmw' => 'good', 'gm' => 'bad' }
36
+ }
37
+ end
38
+
39
+ def test_variable
40
+ text = %( {{best_cars}} )
41
+
42
+ expected = %( bmw )
43
+ assert_equal expected, Template.parse(text).render!(@assigns)
44
+ end
45
+
46
+ def test_variable_traversing_with_two_brackets
47
+ text = %({{ site.data.menu[include.menu][include.locale] }})
48
+ assert_equal "it works!", Template.parse(text).render!(
49
+ "site" => { "data" => { "menu" => { "foo" => { "bar" => "it works!" } } } },
50
+ "include" => { "menu" => "foo", "locale" => "bar" }
51
+ )
52
+ end
53
+
54
+ def test_variable_traversing
55
+ text = %( {{car.bmw}} {{car.gm}} {{car.bmw}} )
56
+
57
+ expected = %( good bad good )
58
+ assert_equal expected, Template.parse(text).render!(@assigns)
59
+ end
60
+
61
+ def test_variable_piping
62
+ text = %( {{ car.gm | make_funny }} )
63
+ expected = %( LOL )
64
+
65
+ assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
66
+ end
67
+
68
+ def test_variable_piping_with_input
69
+ text = %( {{ car.gm | cite_funny }} )
70
+ expected = %( LOL: bad )
71
+
72
+ assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
73
+ end
74
+
75
+ def test_variable_piping_with_args
76
+ text = %! {{ car.gm | add_smiley : ':-(' }} !
77
+ expected = %| bad :-( |
78
+
79
+ assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
80
+ end
81
+
82
+ def test_variable_piping_with_no_args
83
+ text = %( {{ car.gm | add_smiley }} )
84
+ expected = %| bad :-) |
85
+
86
+ assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
87
+ end
88
+
89
+ def test_multiple_variable_piping_with_args
90
+ text = %! {{ car.gm | add_smiley : ':-(' | add_smiley : ':-('}} !
91
+ expected = %| bad :-( :-( |
92
+
93
+ assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
94
+ end
95
+
96
+ def test_variable_piping_with_multiple_args
97
+ text = %( {{ car.gm | add_tag : 'span', 'bar'}} )
98
+ expected = %( <span id="bar">bad</span> )
99
+
100
+ assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
101
+ end
102
+
103
+ def test_variable_piping_with_variable_args
104
+ text = %( {{ car.gm | add_tag : 'span', car.bmw}} )
105
+ expected = %( <span id="good">bad</span> )
106
+
107
+ assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
108
+ end
109
+
110
+ def test_multiple_pipings
111
+ text = %( {{ best_cars | cite_funny | paragraph }} )
112
+ expected = %( <p>LOL: bmw</p> )
113
+
114
+ assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
115
+ end
116
+
117
+ def test_link_to
118
+ text = %( {{ 'Typo' | link_to: 'http://typo.leetsoft.com' }} )
119
+ expected = %( <a href="http://typo.leetsoft.com">Typo</a> )
120
+
121
+ assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
122
+ end
123
+ end # OutputTest
@@ -0,0 +1,247 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class ParseTreeVisitorTest < Minitest::Test
6
+ include Liquid
7
+
8
+ def test_variable
9
+ assert_equal(
10
+ ["test"],
11
+ visit(%({{ test }}))
12
+ )
13
+ end
14
+
15
+ def test_varible_with_filter
16
+ assert_equal(
17
+ ["test", "infilter"],
18
+ visit(%({{ test | split: infilter }}))
19
+ )
20
+ end
21
+
22
+ def test_dynamic_variable
23
+ assert_equal(
24
+ ["test", "inlookup"],
25
+ visit(%({{ test[inlookup] }}))
26
+ )
27
+ end
28
+
29
+ def test_if_condition
30
+ assert_equal(
31
+ ["test"],
32
+ visit(%({% if test %}{% endif %}))
33
+ )
34
+ end
35
+
36
+ def test_complex_if_condition
37
+ assert_equal(
38
+ ["test"],
39
+ visit(%({% if 1 == 1 and 2 == test %}{% endif %}))
40
+ )
41
+ end
42
+
43
+ def test_if_body
44
+ assert_equal(
45
+ ["test"],
46
+ visit(%({% if 1 == 1 %}{{ test }}{% endif %}))
47
+ )
48
+ end
49
+
50
+ def test_unless_condition
51
+ assert_equal(
52
+ ["test"],
53
+ visit(%({% unless test %}{% endunless %}))
54
+ )
55
+ end
56
+
57
+ def test_complex_unless_condition
58
+ assert_equal(
59
+ ["test"],
60
+ visit(%({% unless 1 == 1 and 2 == test %}{% endunless %}))
61
+ )
62
+ end
63
+
64
+ def test_unless_body
65
+ assert_equal(
66
+ ["test"],
67
+ visit(%({% unless 1 == 1 %}{{ test }}{% endunless %}))
68
+ )
69
+ end
70
+
71
+ def test_elsif_condition
72
+ assert_equal(
73
+ ["test"],
74
+ visit(%({% if 1 == 1 %}{% elsif test %}{% endif %}))
75
+ )
76
+ end
77
+
78
+ def test_complex_elsif_condition
79
+ assert_equal(
80
+ ["test"],
81
+ visit(%({% if 1 == 1 %}{% elsif 1 == 1 and 2 == test %}{% endif %}))
82
+ )
83
+ end
84
+
85
+ def test_elsif_body
86
+ assert_equal(
87
+ ["test"],
88
+ visit(%({% if 1 == 1 %}{% elsif 2 == 2 %}{{ test }}{% endif %}))
89
+ )
90
+ end
91
+
92
+ def test_else_body
93
+ assert_equal(
94
+ ["test"],
95
+ visit(%({% if 1 == 1 %}{% else %}{{ test }}{% endif %}))
96
+ )
97
+ end
98
+
99
+ def test_case_left
100
+ assert_equal(
101
+ ["test"],
102
+ visit(%({% case test %}{% endcase %}))
103
+ )
104
+ end
105
+
106
+ def test_case_condition
107
+ assert_equal(
108
+ ["test"],
109
+ visit(%({% case 1 %}{% when test %}{% endcase %}))
110
+ )
111
+ end
112
+
113
+ def test_case_when_body
114
+ assert_equal(
115
+ ["test"],
116
+ visit(%({% case 1 %}{% when 2 %}{{ test }}{% endcase %}))
117
+ )
118
+ end
119
+
120
+ def test_case_else_body
121
+ assert_equal(
122
+ ["test"],
123
+ visit(%({% case 1 %}{% else %}{{ test }}{% endcase %}))
124
+ )
125
+ end
126
+
127
+ def test_for_in
128
+ assert_equal(
129
+ ["test"],
130
+ visit(%({% for x in test %}{% endfor %}))
131
+ )
132
+ end
133
+
134
+ def test_for_limit
135
+ assert_equal(
136
+ ["test"],
137
+ visit(%({% for x in (1..5) limit: test %}{% endfor %}))
138
+ )
139
+ end
140
+
141
+ def test_for_offset
142
+ assert_equal(
143
+ ["test"],
144
+ visit(%({% for x in (1..5) offset: test %}{% endfor %}))
145
+ )
146
+ end
147
+
148
+ def test_for_body
149
+ assert_equal(
150
+ ["test"],
151
+ visit(%({% for x in (1..5) %}{{ test }}{% endfor %}))
152
+ )
153
+ end
154
+
155
+ def test_tablerow_in
156
+ assert_equal(
157
+ ["test"],
158
+ visit(%({% tablerow x in test %}{% endtablerow %}))
159
+ )
160
+ end
161
+
162
+ def test_tablerow_limit
163
+ assert_equal(
164
+ ["test"],
165
+ visit(%({% tablerow x in (1..5) limit: test %}{% endtablerow %}))
166
+ )
167
+ end
168
+
169
+ def test_tablerow_offset
170
+ assert_equal(
171
+ ["test"],
172
+ visit(%({% tablerow x in (1..5) offset: test %}{% endtablerow %}))
173
+ )
174
+ end
175
+
176
+ def test_tablerow_body
177
+ assert_equal(
178
+ ["test"],
179
+ visit(%({% tablerow x in (1..5) %}{{ test }}{% endtablerow %}))
180
+ )
181
+ end
182
+
183
+ def test_cycle
184
+ assert_equal(
185
+ ["test"],
186
+ visit(%({% cycle test %}))
187
+ )
188
+ end
189
+
190
+ def test_assign
191
+ assert_equal(
192
+ ["test"],
193
+ visit(%({% assign x = test %}))
194
+ )
195
+ end
196
+
197
+ def test_capture
198
+ assert_equal(
199
+ ["test"],
200
+ visit(%({% capture x %}{{ test }}{% endcapture %}))
201
+ )
202
+ end
203
+
204
+ def test_include
205
+ assert_equal(
206
+ ["test"],
207
+ visit(%({% include test %}))
208
+ )
209
+ end
210
+
211
+ def test_include_with
212
+ assert_equal(
213
+ ["test"],
214
+ visit(%({% include "hai" with test %}))
215
+ )
216
+ end
217
+
218
+ def test_include_for
219
+ assert_equal(
220
+ ["test"],
221
+ visit(%({% include "hai" for test %}))
222
+ )
223
+ end
224
+
225
+ def test_preserve_tree_structure
226
+ assert_equal(
227
+ [[nil, [
228
+ [nil, [[nil, [["other", []]]]]],
229
+ ["test", []],
230
+ ["xs", []]
231
+ ]]],
232
+ traversal(%({% for x in xs offset: test %}{{ other }}{% endfor %})).visit
233
+ )
234
+ end
235
+
236
+ private
237
+
238
+ def traversal(template)
239
+ ParseTreeVisitor
240
+ .for(Template.parse(template).root)
241
+ .add_callback_for(VariableLookup, &:name)
242
+ end
243
+
244
+ def visit(template)
245
+ traversal(template).visit.flatten.compact
246
+ end
247
+ end