liquid 4.0.3 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +54 -0
  3. data/README.md +6 -0
  4. data/lib/liquid/block.rb +31 -14
  5. data/lib/liquid/block_body.rb +166 -54
  6. data/lib/liquid/condition.rb +41 -20
  7. data/lib/liquid/context.rb +107 -52
  8. data/lib/liquid/document.rb +47 -9
  9. data/lib/liquid/drop.rb +4 -2
  10. data/lib/liquid/errors.rb +20 -18
  11. data/lib/liquid/expression.rb +29 -34
  12. data/lib/liquid/extensions.rb +2 -0
  13. data/lib/liquid/file_system.rb +6 -4
  14. data/lib/liquid/forloop_drop.rb +11 -4
  15. data/lib/liquid/i18n.rb +5 -3
  16. data/lib/liquid/interrupts.rb +3 -1
  17. data/lib/liquid/lexer.rb +30 -23
  18. data/lib/liquid/locales/en.yml +3 -1
  19. data/lib/liquid/parse_context.rb +20 -4
  20. data/lib/liquid/parse_tree_visitor.rb +2 -2
  21. data/lib/liquid/parser.rb +30 -18
  22. data/lib/liquid/parser_switching.rb +17 -3
  23. data/lib/liquid/partial_cache.rb +24 -0
  24. data/lib/liquid/profiler/hooks.rb +26 -14
  25. data/lib/liquid/profiler.rb +67 -86
  26. data/lib/liquid/range_lookup.rb +13 -3
  27. data/lib/liquid/register.rb +6 -0
  28. data/lib/liquid/resource_limits.rb +47 -8
  29. data/lib/liquid/standardfilters.rb +95 -46
  30. data/lib/liquid/static_registers.rb +44 -0
  31. data/lib/liquid/strainer_factory.rb +36 -0
  32. data/lib/liquid/strainer_template.rb +53 -0
  33. data/lib/liquid/tablerowloop_drop.rb +6 -4
  34. data/lib/liquid/tag/disableable.rb +22 -0
  35. data/lib/liquid/tag/disabler.rb +21 -0
  36. data/lib/liquid/tag.rb +28 -6
  37. data/lib/liquid/tags/assign.rb +24 -10
  38. data/lib/liquid/tags/break.rb +8 -3
  39. data/lib/liquid/tags/capture.rb +11 -8
  40. data/lib/liquid/tags/case.rb +40 -27
  41. data/lib/liquid/tags/comment.rb +5 -3
  42. data/lib/liquid/tags/continue.rb +8 -3
  43. data/lib/liquid/tags/cycle.rb +25 -14
  44. data/lib/liquid/tags/decrement.rb +6 -3
  45. data/lib/liquid/tags/echo.rb +34 -0
  46. data/lib/liquid/tags/for.rb +68 -44
  47. data/lib/liquid/tags/if.rb +39 -23
  48. data/lib/liquid/tags/ifchanged.rb +11 -10
  49. data/lib/liquid/tags/include.rb +34 -47
  50. data/lib/liquid/tags/increment.rb +7 -3
  51. data/lib/liquid/tags/raw.rb +14 -11
  52. data/lib/liquid/tags/render.rb +84 -0
  53. data/lib/liquid/tags/table_row.rb +23 -19
  54. data/lib/liquid/tags/unless.rb +23 -15
  55. data/lib/liquid/template.rb +53 -72
  56. data/lib/liquid/template_factory.rb +9 -0
  57. data/lib/liquid/tokenizer.rb +18 -10
  58. data/lib/liquid/usage.rb +8 -0
  59. data/lib/liquid/utils.rb +13 -3
  60. data/lib/liquid/variable.rb +46 -41
  61. data/lib/liquid/variable_lookup.rb +11 -6
  62. data/lib/liquid/version.rb +2 -1
  63. data/lib/liquid.rb +17 -5
  64. data/test/integration/assign_test.rb +74 -5
  65. data/test/integration/blank_test.rb +11 -8
  66. data/test/integration/block_test.rb +47 -1
  67. data/test/integration/capture_test.rb +18 -10
  68. data/test/integration/context_test.rb +609 -5
  69. data/test/integration/document_test.rb +4 -2
  70. data/test/integration/drop_test.rb +67 -83
  71. data/test/integration/error_handling_test.rb +73 -61
  72. data/test/integration/expression_test.rb +46 -0
  73. data/test/integration/filter_test.rb +53 -42
  74. data/test/integration/hash_ordering_test.rb +5 -3
  75. data/test/integration/output_test.rb +26 -24
  76. data/test/integration/parsing_quirks_test.rb +19 -7
  77. data/test/integration/{render_profiling_test.rb → profiler_test.rb} +84 -25
  78. data/test/integration/security_test.rb +30 -21
  79. data/test/integration/standard_filter_test.rb +385 -281
  80. data/test/integration/tag/disableable_test.rb +59 -0
  81. data/test/integration/tag_test.rb +45 -0
  82. data/test/integration/tags/break_tag_test.rb +4 -2
  83. data/test/integration/tags/continue_tag_test.rb +4 -2
  84. data/test/integration/tags/echo_test.rb +13 -0
  85. data/test/integration/tags/for_tag_test.rb +107 -51
  86. data/test/integration/tags/if_else_tag_test.rb +5 -3
  87. data/test/integration/tags/include_tag_test.rb +70 -54
  88. data/test/integration/tags/increment_tag_test.rb +4 -2
  89. data/test/integration/tags/liquid_tag_test.rb +116 -0
  90. data/test/integration/tags/raw_tag_test.rb +14 -11
  91. data/test/integration/tags/render_tag_test.rb +213 -0
  92. data/test/integration/tags/standard_tag_test.rb +38 -31
  93. data/test/integration/tags/statements_test.rb +23 -21
  94. data/test/integration/tags/table_row_test.rb +2 -0
  95. data/test/integration/tags/unless_else_tag_test.rb +4 -2
  96. data/test/integration/template_test.rb +132 -124
  97. data/test/integration/trim_mode_test.rb +78 -44
  98. data/test/integration/variable_test.rb +74 -32
  99. data/test/test_helper.rb +113 -22
  100. data/test/unit/block_unit_test.rb +19 -24
  101. data/test/unit/condition_unit_test.rb +79 -77
  102. data/test/unit/file_system_unit_test.rb +6 -4
  103. data/test/unit/i18n_unit_test.rb +7 -5
  104. data/test/unit/lexer_unit_test.rb +11 -9
  105. data/test/{integration → unit}/parse_tree_visitor_test.rb +16 -2
  106. data/test/unit/parser_unit_test.rb +37 -35
  107. data/test/unit/partial_cache_unit_test.rb +128 -0
  108. data/test/unit/regexp_unit_test.rb +17 -15
  109. data/test/unit/static_registers_unit_test.rb +156 -0
  110. data/test/unit/strainer_factory_unit_test.rb +100 -0
  111. data/test/unit/strainer_template_unit_test.rb +82 -0
  112. data/test/unit/tag_unit_test.rb +5 -3
  113. data/test/unit/tags/case_tag_unit_test.rb +3 -1
  114. data/test/unit/tags/for_tag_unit_test.rb +4 -2
  115. data/test/unit/tags/if_tag_unit_test.rb +3 -1
  116. data/test/unit/template_factory_unit_test.rb +12 -0
  117. data/test/unit/template_unit_test.rb +19 -10
  118. data/test/unit/tokenizer_unit_test.rb +26 -19
  119. data/test/unit/variable_unit_test.rb +51 -49
  120. metadata +76 -50
  121. data/lib/liquid/strainer.rb +0 -66
  122. data/lib/liquid/truffle.rb +0 -5
  123. data/test/truffle/truffle_test.rb +0 -9
  124. data/test/unit/context_unit_test.rb +0 -489
  125. data/test/unit/strainer_unit_test.rb +0 -164
@@ -1,8 +1,598 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_helper'
2
4
 
5
+ class HundredCentes
6
+ def to_liquid
7
+ 100
8
+ end
9
+ end
10
+
11
+ class CentsDrop < Liquid::Drop
12
+ def amount
13
+ HundredCentes.new
14
+ end
15
+
16
+ def non_zero?
17
+ true
18
+ end
19
+ end
20
+
21
+ class ContextSensitiveDrop < Liquid::Drop
22
+ def test
23
+ @context['test']
24
+ end
25
+ end
26
+
27
+ class Category < Liquid::Drop
28
+ attr_accessor :name
29
+
30
+ def initialize(name)
31
+ @name = name
32
+ end
33
+
34
+ def to_liquid
35
+ CategoryDrop.new(self)
36
+ end
37
+ end
38
+
39
+ class CategoryDrop
40
+ attr_accessor :category, :context
41
+ def initialize(category)
42
+ @category = category
43
+ end
44
+ end
45
+
46
+ class CounterDrop < Liquid::Drop
47
+ def count
48
+ @count ||= 0
49
+ @count += 1
50
+ end
51
+ end
52
+
53
+ class ArrayLike
54
+ def fetch(index)
55
+ end
56
+
57
+ def [](index)
58
+ @counts ||= []
59
+ @counts[index] ||= 0
60
+ @counts[index] += 1
61
+ end
62
+
63
+ def to_liquid
64
+ self
65
+ end
66
+ end
67
+
3
68
  class ContextTest < Minitest::Test
4
69
  include Liquid
5
70
 
71
+ def setup
72
+ @context = Liquid::Context.new
73
+ end
74
+
75
+ def test_variables
76
+ @context['string'] = 'string'
77
+ assert_equal('string', @context['string'])
78
+
79
+ @context['num'] = 5
80
+ assert_equal(5, @context['num'])
81
+
82
+ @context['time'] = Time.parse('2006-06-06 12:00:00')
83
+ assert_equal(Time.parse('2006-06-06 12:00:00'), @context['time'])
84
+
85
+ @context['date'] = Date.today
86
+ assert_equal(Date.today, @context['date'])
87
+
88
+ now = Time.now
89
+ @context['datetime'] = now
90
+ assert_equal(now, @context['datetime'])
91
+
92
+ @context['bool'] = true
93
+ assert_equal(true, @context['bool'])
94
+
95
+ @context['bool'] = false
96
+ assert_equal(false, @context['bool'])
97
+
98
+ @context['nil'] = nil
99
+ assert_nil(@context['nil'])
100
+ assert_nil(@context['nil'])
101
+ end
102
+
103
+ def test_variables_not_existing
104
+ assert_nil(@context['does_not_exist'])
105
+ end
106
+
107
+ def test_scoping
108
+ @context.push
109
+ @context.pop
110
+
111
+ assert_raises(Liquid::ContextError) do
112
+ @context.pop
113
+ end
114
+
115
+ assert_raises(Liquid::ContextError) do
116
+ @context.push
117
+ @context.pop
118
+ @context.pop
119
+ end
120
+ end
121
+
122
+ def test_length_query
123
+ @context['numbers'] = [1, 2, 3, 4]
124
+
125
+ assert_equal(4, @context['numbers.size'])
126
+
127
+ @context['numbers'] = { 1 => 1, 2 => 2, 3 => 3, 4 => 4 }
128
+
129
+ assert_equal(4, @context['numbers.size'])
130
+
131
+ @context['numbers'] = { 1 => 1, 2 => 2, 3 => 3, 4 => 4, 'size' => 1000 }
132
+
133
+ assert_equal(1000, @context['numbers.size'])
134
+ end
135
+
136
+ def test_hyphenated_variable
137
+ @context['oh-my'] = 'godz'
138
+ assert_equal('godz', @context['oh-my'])
139
+ end
140
+
141
+ def test_add_filter
142
+ filter = Module.new do
143
+ def hi(output)
144
+ output + ' hi!'
145
+ end
146
+ end
147
+
148
+ context = Context.new
149
+ context.add_filters(filter)
150
+ assert_equal('hi? hi!', context.invoke(:hi, 'hi?'))
151
+
152
+ context = Context.new
153
+ assert_equal('hi?', context.invoke(:hi, 'hi?'))
154
+
155
+ context.add_filters(filter)
156
+ assert_equal('hi? hi!', context.invoke(:hi, 'hi?'))
157
+ end
158
+
159
+ def test_only_intended_filters_make_it_there
160
+ filter = Module.new do
161
+ def hi(output)
162
+ output + ' hi!'
163
+ end
164
+ end
165
+
166
+ context = Context.new
167
+ assert_equal("Wookie", context.invoke("hi", "Wookie"))
168
+
169
+ context.add_filters(filter)
170
+ assert_equal("Wookie hi!", context.invoke("hi", "Wookie"))
171
+ end
172
+
173
+ def test_add_item_in_outer_scope
174
+ @context['test'] = 'test'
175
+ @context.push
176
+ assert_equal('test', @context['test'])
177
+ @context.pop
178
+ assert_equal('test', @context['test'])
179
+ end
180
+
181
+ def test_add_item_in_inner_scope
182
+ @context.push
183
+ @context['test'] = 'test'
184
+ assert_equal('test', @context['test'])
185
+ @context.pop
186
+ assert_nil(@context['test'])
187
+ end
188
+
189
+ def test_hierachical_data
190
+ @context['hash'] = { "name" => 'tobi' }
191
+ assert_equal('tobi', @context['hash.name'])
192
+ assert_equal('tobi', @context['hash["name"]'])
193
+ end
194
+
195
+ def test_keywords
196
+ assert_equal(true, @context['true'])
197
+ assert_equal(false, @context['false'])
198
+ end
199
+
200
+ def test_digits
201
+ assert_equal(100, @context['100'])
202
+ assert_equal(100.00, @context['100.00'])
203
+ end
204
+
205
+ def test_strings
206
+ assert_equal("hello!", @context['"hello!"'])
207
+ assert_equal("hello!", @context["'hello!'"])
208
+ end
209
+
210
+ def test_merge
211
+ @context.merge("test" => "test")
212
+ assert_equal('test', @context['test'])
213
+ @context.merge("test" => "newvalue", "foo" => "bar")
214
+ assert_equal('newvalue', @context['test'])
215
+ assert_equal('bar', @context['foo'])
216
+ end
217
+
218
+ def test_array_notation
219
+ @context['test'] = [1, 2, 3, 4, 5]
220
+
221
+ assert_equal(1, @context['test[0]'])
222
+ assert_equal(2, @context['test[1]'])
223
+ assert_equal(3, @context['test[2]'])
224
+ assert_equal(4, @context['test[3]'])
225
+ assert_equal(5, @context['test[4]'])
226
+ end
227
+
228
+ def test_recoursive_array_notation
229
+ @context['test'] = { 'test' => [1, 2, 3, 4, 5] }
230
+
231
+ assert_equal(1, @context['test.test[0]'])
232
+
233
+ @context['test'] = [{ 'test' => 'worked' }]
234
+
235
+ assert_equal('worked', @context['test[0].test'])
236
+ end
237
+
238
+ def test_hash_to_array_transition
239
+ @context['colors'] = {
240
+ 'Blue' => ['003366', '336699', '6699CC', '99CCFF'],
241
+ 'Green' => ['003300', '336633', '669966', '99CC99'],
242
+ 'Yellow' => ['CC9900', 'FFCC00', 'FFFF99', 'FFFFCC'],
243
+ 'Red' => ['660000', '993333', 'CC6666', 'FF9999'],
244
+ }
245
+
246
+ assert_equal('003366', @context['colors.Blue[0]'])
247
+ assert_equal('FF9999', @context['colors.Red[3]'])
248
+ end
249
+
250
+ def test_try_first
251
+ @context['test'] = [1, 2, 3, 4, 5]
252
+
253
+ assert_equal(1, @context['test.first'])
254
+ assert_equal(5, @context['test.last'])
255
+
256
+ @context['test'] = { 'test' => [1, 2, 3, 4, 5] }
257
+
258
+ assert_equal(1, @context['test.test.first'])
259
+ assert_equal(5, @context['test.test.last'])
260
+
261
+ @context['test'] = [1]
262
+ assert_equal(1, @context['test.first'])
263
+ assert_equal(1, @context['test.last'])
264
+ end
265
+
266
+ def test_access_hashes_with_hash_notation
267
+ @context['products'] = { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
268
+ @context['product'] = { 'variants' => [{ 'title' => 'draft151cm' }, { 'title' => 'element151cm' }] }
269
+
270
+ assert_equal(5, @context['products["count"]'])
271
+ assert_equal('deepsnow', @context['products["tags"][0]'])
272
+ assert_equal('deepsnow', @context['products["tags"].first'])
273
+ assert_equal('draft151cm', @context['product["variants"][0]["title"]'])
274
+ assert_equal('element151cm', @context['product["variants"][1]["title"]'])
275
+ assert_equal('draft151cm', @context['product["variants"][0]["title"]'])
276
+ assert_equal('element151cm', @context['product["variants"].last["title"]'])
277
+ end
278
+
279
+ def test_access_variable_with_hash_notation
280
+ @context['foo'] = 'baz'
281
+ @context['bar'] = 'foo'
282
+
283
+ assert_equal('baz', @context['["foo"]'])
284
+ assert_equal('baz', @context['[bar]'])
285
+ end
286
+
287
+ def test_access_hashes_with_hash_access_variables
288
+ @context['var'] = 'tags'
289
+ @context['nested'] = { 'var' => 'tags' }
290
+ @context['products'] = { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
291
+
292
+ assert_equal('deepsnow', @context['products[var].first'])
293
+ assert_equal('freestyle', @context['products[nested.var].last'])
294
+ end
295
+
296
+ def test_hash_notation_only_for_hash_access
297
+ @context['array'] = [1, 2, 3, 4, 5]
298
+ @context['hash'] = { 'first' => 'Hello' }
299
+
300
+ assert_equal(1, @context['array.first'])
301
+ assert_nil(@context['array["first"]'])
302
+ assert_equal('Hello', @context['hash["first"]'])
303
+ end
304
+
305
+ def test_first_can_appear_in_middle_of_callchain
306
+ @context['product'] = { 'variants' => [{ 'title' => 'draft151cm' }, { 'title' => 'element151cm' }] }
307
+
308
+ assert_equal('draft151cm', @context['product.variants[0].title'])
309
+ assert_equal('element151cm', @context['product.variants[1].title'])
310
+ assert_equal('draft151cm', @context['product.variants.first.title'])
311
+ assert_equal('element151cm', @context['product.variants.last.title'])
312
+ end
313
+
314
+ def test_cents
315
+ @context.merge("cents" => HundredCentes.new)
316
+ assert_equal(100, @context['cents'])
317
+ end
318
+
319
+ def test_nested_cents
320
+ @context.merge("cents" => { 'amount' => HundredCentes.new })
321
+ assert_equal(100, @context['cents.amount'])
322
+
323
+ @context.merge("cents" => { 'cents' => { 'amount' => HundredCentes.new } })
324
+ assert_equal(100, @context['cents.cents.amount'])
325
+ end
326
+
327
+ def test_cents_through_drop
328
+ @context.merge("cents" => CentsDrop.new)
329
+ assert_equal(100, @context['cents.amount'])
330
+ end
331
+
332
+ def test_nested_cents_through_drop
333
+ @context.merge("vars" => { "cents" => CentsDrop.new })
334
+ assert_equal(100, @context['vars.cents.amount'])
335
+ end
336
+
337
+ def test_drop_methods_with_question_marks
338
+ @context.merge("cents" => CentsDrop.new)
339
+ assert(@context['cents.non_zero?'])
340
+ end
341
+
342
+ def test_context_from_within_drop
343
+ @context.merge("test" => '123', "vars" => ContextSensitiveDrop.new)
344
+ assert_equal('123', @context['vars.test'])
345
+ end
346
+
347
+ def test_nested_context_from_within_drop
348
+ @context.merge("test" => '123', "vars" => { "local" => ContextSensitiveDrop.new })
349
+ assert_equal('123', @context['vars.local.test'])
350
+ end
351
+
352
+ def test_ranges
353
+ @context.merge("test" => '5')
354
+ assert_equal((1..5), @context['(1..5)'])
355
+ assert_equal((1..5), @context['(1..test)'])
356
+ assert_equal((5..5), @context['(test..test)'])
357
+ end
358
+
359
+ def test_cents_through_drop_nestedly
360
+ @context.merge("cents" => { "cents" => CentsDrop.new })
361
+ assert_equal(100, @context['cents.cents.amount'])
362
+
363
+ @context.merge("cents" => { "cents" => { "cents" => CentsDrop.new } })
364
+ assert_equal(100, @context['cents.cents.cents.amount'])
365
+ end
366
+
367
+ def test_drop_with_variable_called_only_once
368
+ @context['counter'] = CounterDrop.new
369
+
370
+ assert_equal(1, @context['counter.count'])
371
+ assert_equal(2, @context['counter.count'])
372
+ assert_equal(3, @context['counter.count'])
373
+ end
374
+
375
+ def test_drop_with_key_called_only_once
376
+ @context['counter'] = CounterDrop.new
377
+
378
+ assert_equal(1, @context['counter["count"]'])
379
+ assert_equal(2, @context['counter["count"]'])
380
+ assert_equal(3, @context['counter["count"]'])
381
+ end
382
+
383
+ def test_proc_as_variable
384
+ @context['dynamic'] = proc { 'Hello' }
385
+
386
+ assert_equal('Hello', @context['dynamic'])
387
+ end
388
+
389
+ def test_lambda_as_variable
390
+ @context['dynamic'] = proc { 'Hello' }
391
+
392
+ assert_equal('Hello', @context['dynamic'])
393
+ end
394
+
395
+ def test_nested_lambda_as_variable
396
+ @context['dynamic'] = { "lambda" => proc { 'Hello' } }
397
+
398
+ assert_equal('Hello', @context['dynamic.lambda'])
399
+ end
400
+
401
+ def test_array_containing_lambda_as_variable
402
+ @context['dynamic'] = [1, 2, proc { 'Hello' }, 4, 5]
403
+
404
+ assert_equal('Hello', @context['dynamic[2]'])
405
+ end
406
+
407
+ def test_lambda_is_called_once
408
+ @context['callcount'] = proc {
409
+ @global ||= 0
410
+ @global += 1
411
+ @global.to_s
412
+ }
413
+
414
+ assert_equal('1', @context['callcount'])
415
+ assert_equal('1', @context['callcount'])
416
+ assert_equal('1', @context['callcount'])
417
+
418
+ @global = nil
419
+ end
420
+
421
+ def test_nested_lambda_is_called_once
422
+ @context['callcount'] = { "lambda" => proc {
423
+ @global ||= 0
424
+ @global += 1
425
+ @global.to_s
426
+ } }
427
+
428
+ assert_equal('1', @context['callcount.lambda'])
429
+ assert_equal('1', @context['callcount.lambda'])
430
+ assert_equal('1', @context['callcount.lambda'])
431
+
432
+ @global = nil
433
+ end
434
+
435
+ def test_lambda_in_array_is_called_once
436
+ @context['callcount'] = [1, 2, proc {
437
+ @global ||= 0
438
+ @global += 1
439
+ @global.to_s
440
+ }, 4, 5]
441
+
442
+ assert_equal('1', @context['callcount[2]'])
443
+ assert_equal('1', @context['callcount[2]'])
444
+ assert_equal('1', @context['callcount[2]'])
445
+
446
+ @global = nil
447
+ end
448
+
449
+ def test_access_to_context_from_proc
450
+ @context.registers[:magic] = 345392
451
+
452
+ @context['magic'] = proc { @context.registers[:magic] }
453
+
454
+ assert_equal(345392, @context['magic'])
455
+ end
456
+
457
+ def test_to_liquid_and_context_at_first_level
458
+ @context['category'] = Category.new("foobar")
459
+ assert_kind_of(CategoryDrop, @context['category'])
460
+ assert_equal(@context, @context['category'].context)
461
+ end
462
+
463
+ def test_interrupt_avoids_object_allocations
464
+ @context.interrupt? # ruby 3.0.0 allocates on the first call
465
+ assert_no_object_allocations do
466
+ @context.interrupt?
467
+ end
468
+ end
469
+
470
+ def test_context_initialization_with_a_proc_in_environment
471
+ contx = Context.new([test: ->(c) { c['poutine'] }], test: :foo)
472
+
473
+ assert(contx)
474
+ assert_nil(contx['poutine'])
475
+ end
476
+
477
+ def test_apply_global_filter
478
+ global_filter_proc = ->(output) { "#{output} filtered" }
479
+
480
+ context = Context.new
481
+ context.global_filter = global_filter_proc
482
+
483
+ assert_equal('hi filtered', context.apply_global_filter('hi'))
484
+ end
485
+
486
+ def test_static_environments_are_read_with_lower_priority_than_environments
487
+ context = Context.build(
488
+ static_environments: { 'shadowed' => 'static', 'unshadowed' => 'static' },
489
+ environments: { 'shadowed' => 'dynamic' }
490
+ )
491
+
492
+ assert_equal('dynamic', context['shadowed'])
493
+ assert_equal('static', context['unshadowed'])
494
+ end
495
+
496
+ def test_apply_global_filter_when_no_global_filter_exist
497
+ context = Context.new
498
+ assert_equal('hi', context.apply_global_filter('hi'))
499
+ end
500
+
501
+ def test_new_isolated_subcontext_does_not_inherit_variables
502
+ super_context = Context.new
503
+ super_context['my_variable'] = 'some value'
504
+ subcontext = super_context.new_isolated_subcontext
505
+
506
+ assert_nil(subcontext['my_variable'])
507
+ end
508
+
509
+ def test_new_isolated_subcontext_inherits_static_environment
510
+ super_context = Context.build(static_environments: { 'my_environment_value' => 'my value' })
511
+ subcontext = super_context.new_isolated_subcontext
512
+
513
+ assert_equal('my value', subcontext['my_environment_value'])
514
+ end
515
+
516
+ def test_new_isolated_subcontext_inherits_resource_limits
517
+ resource_limits = ResourceLimits.new({})
518
+ super_context = Context.new({}, {}, {}, false, resource_limits)
519
+ subcontext = super_context.new_isolated_subcontext
520
+ assert_equal(resource_limits, subcontext.resource_limits)
521
+ end
522
+
523
+ def test_new_isolated_subcontext_inherits_exception_renderer
524
+ super_context = Context.new
525
+ super_context.exception_renderer = ->(_e) { 'my exception message' }
526
+ subcontext = super_context.new_isolated_subcontext
527
+ assert_equal('my exception message', subcontext.handle_error(Liquid::Error.new))
528
+ end
529
+
530
+ def test_new_isolated_subcontext_does_not_inherit_non_static_registers
531
+ registers = {
532
+ my_register: :my_value,
533
+ }
534
+ super_context = Context.new({}, {}, StaticRegisters.new(registers))
535
+ super_context.registers[:my_register] = :my_alt_value
536
+ subcontext = super_context.new_isolated_subcontext
537
+ assert_equal(:my_value, subcontext.registers[:my_register])
538
+ end
539
+
540
+ def test_new_isolated_subcontext_inherits_static_registers
541
+ super_context = Context.build(registers: { my_register: :my_value })
542
+ subcontext = super_context.new_isolated_subcontext
543
+ assert_equal(:my_value, subcontext.registers[:my_register])
544
+ end
545
+
546
+ def test_new_isolated_subcontext_registers_do_not_pollute_context
547
+ super_context = Context.build(registers: { my_register: :my_value })
548
+ subcontext = super_context.new_isolated_subcontext
549
+ subcontext.registers[:my_register] = :my_alt_value
550
+ assert_equal(:my_value, super_context.registers[:my_register])
551
+ end
552
+
553
+ def test_new_isolated_subcontext_inherits_filters
554
+ my_filter = Module.new do
555
+ def my_filter(*)
556
+ 'my filter result'
557
+ end
558
+ end
559
+
560
+ super_context = Context.new
561
+ super_context.add_filters([my_filter])
562
+ subcontext = super_context.new_isolated_subcontext
563
+ template = Template.parse('{{ 123 | my_filter }}')
564
+ assert_equal('my filter result', template.render(subcontext))
565
+ end
566
+
567
+ def test_disables_tag_specified
568
+ context = Context.new
569
+ context.with_disabled_tags(%w(foo bar)) do
570
+ assert_equal(true, context.tag_disabled?("foo"))
571
+ assert_equal(true, context.tag_disabled?("bar"))
572
+ assert_equal(false, context.tag_disabled?("unknown"))
573
+ end
574
+ end
575
+
576
+ def test_disables_nested_tags
577
+ context = Context.new
578
+ context.with_disabled_tags(["foo"]) do
579
+ context.with_disabled_tags(["foo"]) do
580
+ assert_equal(true, context.tag_disabled?("foo"))
581
+ assert_equal(false, context.tag_disabled?("bar"))
582
+ end
583
+ context.with_disabled_tags(["bar"]) do
584
+ assert_equal(true, context.tag_disabled?("foo"))
585
+ assert_equal(true, context.tag_disabled?("bar"))
586
+ context.with_disabled_tags(["foo"]) do
587
+ assert_equal(true, context.tag_disabled?("foo"))
588
+ assert_equal(true, context.tag_disabled?("bar"))
589
+ end
590
+ end
591
+ assert_equal(true, context.tag_disabled?("foo"))
592
+ assert_equal(false, context.tag_disabled?("bar"))
593
+ end
594
+ end
595
+
6
596
  def test_override_global_filter
7
597
  global = Module.new do
8
598
  def notice(output)
@@ -17,16 +607,30 @@ class ContextTest < Minitest::Test
17
607
  end
18
608
 
19
609
  with_global_filter(global) do
20
- assert_equal 'Global test', Template.parse("{{'test' | notice }}").render!
21
- assert_equal 'Local test', Template.parse("{{'test' | notice }}").render!({}, filters: [local])
610
+ assert_equal('Global test', Template.parse("{{'test' | notice }}").render!)
611
+ assert_equal('Local test', Template.parse("{{'test' | notice }}").render!({}, filters: [local]))
22
612
  end
23
613
  end
24
614
 
25
615
  def test_has_key_will_not_add_an_error_for_missing_keys
26
- with_error_mode :strict do
616
+ with_error_mode(:strict) do
27
617
  context = Context.new
28
618
  context.key?('unknown')
29
- assert_empty context.errors
619
+ assert_empty(context.errors)
30
620
  end
31
621
  end
32
- end
622
+
623
+ private
624
+
625
+ def assert_no_object_allocations
626
+ unless RUBY_ENGINE == 'ruby'
627
+ skip("stackprof needed to count object allocations")
628
+ end
629
+ require 'stackprof'
630
+
631
+ profile = StackProf.run(mode: :object) do
632
+ yield
633
+ end
634
+ assert_equal(0, profile[:samples])
635
+ end
636
+ end # ContextTest
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_helper'
2
4
 
3
5
  class DocumentTest < Minitest::Test
@@ -7,13 +9,13 @@ class DocumentTest < Minitest::Test
7
9
  exc = assert_raises(SyntaxError) do
8
10
  Template.parse("{% else %}")
9
11
  end
10
- assert_equal exc.message, "Liquid syntax error: Unexpected outer 'else' tag"
12
+ assert_equal(exc.message, "Liquid syntax error: Unexpected outer 'else' tag")
11
13
  end
12
14
 
13
15
  def test_unknown_tag
14
16
  exc = assert_raises(SyntaxError) do
15
17
  Template.parse("{% foo %}")
16
18
  end
17
- assert_equal exc.message, "Liquid syntax error: Unknown tag 'foo'"
19
+ assert_equal(exc.message, "Liquid syntax error: Unknown tag 'foo'")
18
20
  end
19
21
  end