liquid 2.6.1 → 4.0.3

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 (130) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +194 -29
  3. data/{MIT-LICENSE → LICENSE} +0 -0
  4. data/README.md +60 -2
  5. data/lib/liquid.rb +25 -14
  6. data/lib/liquid/block.rb +47 -96
  7. data/lib/liquid/block_body.rb +143 -0
  8. data/lib/liquid/condition.rb +70 -39
  9. data/lib/liquid/context.rb +116 -157
  10. data/lib/liquid/document.rb +19 -9
  11. data/lib/liquid/drop.rb +31 -14
  12. data/lib/liquid/errors.rb +54 -10
  13. data/lib/liquid/expression.rb +49 -0
  14. data/lib/liquid/extensions.rb +19 -7
  15. data/lib/liquid/file_system.rb +25 -14
  16. data/lib/liquid/forloop_drop.rb +42 -0
  17. data/lib/liquid/i18n.rb +39 -0
  18. data/lib/liquid/interrupts.rb +2 -3
  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 +311 -77
  30. data/lib/liquid/strainer.rb +39 -26
  31. data/lib/liquid/tablerowloop_drop.rb +62 -0
  32. data/lib/liquid/tag.rb +28 -11
  33. data/lib/liquid/tags/assign.rb +34 -10
  34. data/lib/liquid/tags/break.rb +1 -4
  35. data/lib/liquid/tags/capture.rb +11 -9
  36. data/lib/liquid/tags/case.rb +37 -22
  37. data/lib/liquid/tags/comment.rb +10 -3
  38. data/lib/liquid/tags/continue.rb +1 -4
  39. data/lib/liquid/tags/cycle.rb +20 -14
  40. data/lib/liquid/tags/decrement.rb +4 -8
  41. data/lib/liquid/tags/for.rb +121 -60
  42. data/lib/liquid/tags/if.rb +73 -30
  43. data/lib/liquid/tags/ifchanged.rb +3 -5
  44. data/lib/liquid/tags/include.rb +77 -46
  45. data/lib/liquid/tags/increment.rb +4 -8
  46. data/lib/liquid/tags/raw.rb +35 -10
  47. data/lib/liquid/tags/table_row.rb +62 -0
  48. data/lib/liquid/tags/unless.rb +6 -9
  49. data/lib/liquid/template.rb +130 -32
  50. data/lib/liquid/tokenizer.rb +31 -0
  51. data/lib/liquid/truffle.rb +5 -0
  52. data/lib/liquid/utils.rb +57 -4
  53. data/lib/liquid/variable.rb +121 -30
  54. data/lib/liquid/variable_lookup.rb +88 -0
  55. data/lib/liquid/version.rb +2 -1
  56. data/test/fixtures/en_locale.yml +9 -0
  57. data/test/integration/assign_test.rb +48 -0
  58. data/test/integration/blank_test.rb +106 -0
  59. data/test/integration/block_test.rb +12 -0
  60. data/test/{liquid → integration}/capture_test.rb +13 -3
  61. data/test/integration/context_test.rb +32 -0
  62. data/test/integration/document_test.rb +19 -0
  63. data/test/integration/drop_test.rb +273 -0
  64. data/test/integration/error_handling_test.rb +260 -0
  65. data/test/integration/filter_test.rb +178 -0
  66. data/test/integration/hash_ordering_test.rb +23 -0
  67. data/test/integration/output_test.rb +123 -0
  68. data/test/integration/parse_tree_visitor_test.rb +247 -0
  69. data/test/integration/parsing_quirks_test.rb +122 -0
  70. data/test/integration/render_profiling_test.rb +154 -0
  71. data/test/integration/security_test.rb +80 -0
  72. data/test/integration/standard_filter_test.rb +776 -0
  73. data/test/{liquid → integration}/tags/break_tag_test.rb +2 -3
  74. data/test/{liquid → integration}/tags/continue_tag_test.rb +1 -2
  75. data/test/integration/tags/for_tag_test.rb +410 -0
  76. data/test/integration/tags/if_else_tag_test.rb +188 -0
  77. data/test/integration/tags/include_tag_test.rb +253 -0
  78. data/test/integration/tags/increment_tag_test.rb +23 -0
  79. data/test/{liquid → integration}/tags/raw_tag_test.rb +9 -2
  80. data/test/integration/tags/standard_tag_test.rb +296 -0
  81. data/test/integration/tags/statements_test.rb +111 -0
  82. data/test/{liquid/tags/html_tag_test.rb → integration/tags/table_row_test.rb} +25 -24
  83. data/test/integration/tags/unless_else_tag_test.rb +26 -0
  84. data/test/integration/template_test.rb +332 -0
  85. data/test/integration/trim_mode_test.rb +529 -0
  86. data/test/integration/variable_test.rb +96 -0
  87. data/test/test_helper.rb +106 -19
  88. data/test/truffle/truffle_test.rb +9 -0
  89. data/test/{liquid/block_test.rb → unit/block_unit_test.rb} +9 -9
  90. data/test/unit/condition_unit_test.rb +166 -0
  91. data/test/{liquid/context_test.rb → unit/context_unit_test.rb} +85 -74
  92. data/test/unit/file_system_unit_test.rb +35 -0
  93. data/test/unit/i18n_unit_test.rb +37 -0
  94. data/test/unit/lexer_unit_test.rb +51 -0
  95. data/test/unit/parser_unit_test.rb +82 -0
  96. data/test/{liquid/regexp_test.rb → unit/regexp_unit_test.rb} +4 -4
  97. data/test/unit/strainer_unit_test.rb +164 -0
  98. data/test/unit/tag_unit_test.rb +21 -0
  99. data/test/unit/tags/case_tag_unit_test.rb +10 -0
  100. data/test/unit/tags/for_tag_unit_test.rb +13 -0
  101. data/test/unit/tags/if_tag_unit_test.rb +8 -0
  102. data/test/unit/template_unit_test.rb +78 -0
  103. data/test/unit/tokenizer_unit_test.rb +55 -0
  104. data/test/unit/variable_unit_test.rb +162 -0
  105. metadata +157 -77
  106. data/lib/extras/liquid_view.rb +0 -51
  107. data/lib/liquid/htmltags.rb +0 -74
  108. data/lib/liquid/module_ex.rb +0 -62
  109. data/test/liquid/assign_test.rb +0 -21
  110. data/test/liquid/condition_test.rb +0 -127
  111. data/test/liquid/drop_test.rb +0 -180
  112. data/test/liquid/error_handling_test.rb +0 -81
  113. data/test/liquid/file_system_test.rb +0 -29
  114. data/test/liquid/filter_test.rb +0 -125
  115. data/test/liquid/hash_ordering_test.rb +0 -25
  116. data/test/liquid/module_ex_test.rb +0 -87
  117. data/test/liquid/output_test.rb +0 -116
  118. data/test/liquid/parsing_quirks_test.rb +0 -52
  119. data/test/liquid/security_test.rb +0 -64
  120. data/test/liquid/standard_filter_test.rb +0 -251
  121. data/test/liquid/strainer_test.rb +0 -52
  122. data/test/liquid/tags/for_tag_test.rb +0 -297
  123. data/test/liquid/tags/if_else_tag_test.rb +0 -166
  124. data/test/liquid/tags/include_tag_test.rb +0 -166
  125. data/test/liquid/tags/increment_tag_test.rb +0 -24
  126. data/test/liquid/tags/standard_tag_test.rb +0 -295
  127. data/test/liquid/tags/statements_test.rb +0 -134
  128. data/test/liquid/tags/unless_else_tag_test.rb +0 -26
  129. data/test/liquid/template_test.rb +0 -146
  130. data/test/liquid/variable_test.rb +0 -186
@@ -0,0 +1,96 @@
1
+ require 'test_helper'
2
+
3
+ class VariableTest < Minitest::Test
4
+ include Liquid
5
+
6
+ def test_simple_variable
7
+ template = Template.parse(%({{test}}))
8
+ assert_equal 'worked', template.render!('test' => 'worked')
9
+ assert_equal 'worked wonderfully', template.render!('test' => 'worked wonderfully')
10
+ end
11
+
12
+ def test_variable_render_calls_to_liquid
13
+ assert_template_result 'foobar', '{{ foo }}', 'foo' => ThingWithToLiquid.new
14
+ end
15
+
16
+ def test_simple_with_whitespaces
17
+ template = Template.parse(%( {{ test }} ))
18
+ assert_equal ' worked ', template.render!('test' => 'worked')
19
+ assert_equal ' worked wonderfully ', template.render!('test' => 'worked wonderfully')
20
+ end
21
+
22
+ def test_ignore_unknown
23
+ template = Template.parse(%({{ test }}))
24
+ assert_equal '', template.render!
25
+ end
26
+
27
+ def test_using_blank_as_variable_name
28
+ template = Template.parse("{% assign foo = blank %}{{ foo }}")
29
+ assert_equal '', template.render!
30
+ end
31
+
32
+ def test_using_empty_as_variable_name
33
+ template = Template.parse("{% assign foo = empty %}{{ foo }}")
34
+ assert_equal '', template.render!
35
+ end
36
+
37
+ def test_hash_scoping
38
+ template = Template.parse(%({{ test.test }}))
39
+ assert_equal 'worked', template.render!('test' => { 'test' => 'worked' })
40
+ end
41
+
42
+ def test_false_renders_as_false
43
+ assert_equal 'false', Template.parse("{{ foo }}").render!('foo' => false)
44
+ assert_equal 'false', Template.parse("{{ false }}").render!
45
+ end
46
+
47
+ def test_nil_renders_as_empty_string
48
+ assert_equal '', Template.parse("{{ nil }}").render!
49
+ assert_equal 'cat', Template.parse("{{ nil | append: 'cat' }}").render!
50
+ end
51
+
52
+ def test_preset_assigns
53
+ template = Template.parse(%({{ test }}))
54
+ template.assigns['test'] = 'worked'
55
+ assert_equal 'worked', template.render!
56
+ end
57
+
58
+ def test_reuse_parsed_template
59
+ template = Template.parse(%({{ greeting }} {{ name }}))
60
+ template.assigns['greeting'] = 'Goodbye'
61
+ assert_equal 'Hello Tobi', template.render!('greeting' => 'Hello', 'name' => 'Tobi')
62
+ assert_equal 'Hello ', template.render!('greeting' => 'Hello', 'unknown' => 'Tobi')
63
+ assert_equal 'Hello Brian', template.render!('greeting' => 'Hello', 'name' => 'Brian')
64
+ assert_equal 'Goodbye Brian', template.render!('name' => 'Brian')
65
+ assert_equal({ 'greeting' => 'Goodbye' }, template.assigns)
66
+ end
67
+
68
+ def test_assigns_not_polluted_from_template
69
+ template = Template.parse(%({{ test }}{% assign test = 'bar' %}{{ test }}))
70
+ template.assigns['test'] = 'baz'
71
+ assert_equal 'bazbar', template.render!
72
+ assert_equal 'bazbar', template.render!
73
+ assert_equal 'foobar', template.render!('test' => 'foo')
74
+ assert_equal 'bazbar', template.render!
75
+ end
76
+
77
+ def test_hash_with_default_proc
78
+ template = Template.parse(%(Hello {{ test }}))
79
+ assigns = Hash.new { |h, k| raise "Unknown variable '#{k}'" }
80
+ assigns['test'] = 'Tobi'
81
+ assert_equal 'Hello Tobi', template.render!(assigns)
82
+ assigns.delete('test')
83
+ e = assert_raises(RuntimeError) do
84
+ template.render!(assigns)
85
+ end
86
+ assert_equal "Unknown variable 'test'", e.message
87
+ end
88
+
89
+ def test_multiline_variable
90
+ assert_equal 'worked', Template.parse("{{\ntest\n}}").render!('test' => 'worked')
91
+ end
92
+
93
+ def test_render_symbol
94
+ assert_template_result 'bar', '{{ foo }}', 'foo' => :bar
95
+ end
96
+ end
@@ -1,29 +1,116 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'test/unit'
4
- require 'test/unit/assertions'
5
- begin
6
- require 'ruby-debug'
7
- rescue LoadError
8
- puts "Couldn't load ruby-debug. gem install ruby-debug if you need it."
3
+ ENV["MT_NO_EXPECTATIONS"] = "1"
4
+ require 'minitest/autorun'
5
+
6
+ $LOAD_PATH.unshift(File.join(File.expand_path(__dir__), '..', 'lib'))
7
+ require 'liquid.rb'
8
+ require 'liquid/profiler'
9
+
10
+ mode = :strict
11
+ if env_mode = ENV['LIQUID_PARSER_MODE']
12
+ puts "-- #{env_mode.upcase} ERROR MODE"
13
+ mode = env_mode.to_sym
14
+ end
15
+ Liquid::Template.error_mode = mode
16
+
17
+ if ENV['LIQUID-C'] == '1'
18
+ puts "-- LIQUID C"
19
+ require 'liquid/c'
20
+ end
21
+
22
+ if Minitest.const_defined?('Test')
23
+ # We're on Minitest 5+. Nothing to do here.
24
+ else
25
+ # Minitest 4 doesn't have Minitest::Test yet.
26
+ Minitest::Test = MiniTest::Unit::TestCase
9
27
  end
10
- require File.join(File.dirname(__FILE__), '..', 'lib', 'liquid')
11
28
 
29
+ module Minitest
30
+ class Test
31
+ def fixture(name)
32
+ File.join(File.expand_path(__dir__), "fixtures", name)
33
+ end
34
+ end
35
+
36
+ module Assertions
37
+ include Liquid
38
+
39
+ def assert_template_result(expected, template, assigns = {}, message = nil)
40
+ assert_equal expected, Template.parse(template).render!(assigns), message
41
+ end
12
42
 
13
- module Test
14
- module Unit
15
- module Assertions
16
- include Liquid
43
+ def assert_template_result_matches(expected, template, assigns = {}, message = nil)
44
+ return assert_template_result(expected, template, assigns, message) unless expected.is_a? Regexp
17
45
 
18
- def assert_template_result(expected, template, assigns = {}, message = nil)
19
- assert_equal expected, Template.parse(template).render(assigns)
46
+ assert_match expected, Template.parse(template).render!(assigns), message
47
+ end
48
+
49
+ def assert_match_syntax_error(match, template, assigns = {})
50
+ exception = assert_raises(Liquid::SyntaxError) do
51
+ Template.parse(template).render(assigns)
20
52
  end
53
+ assert_match match, exception.message
54
+ end
21
55
 
22
- def assert_template_result_matches(expected, template, assigns = {}, message = nil)
23
- return assert_template_result(expected, template, assigns, message) unless expected.is_a? Regexp
56
+ def with_global_filter(*globals)
57
+ original_global_strainer = Liquid::Strainer.class_variable_get(:@@global_strainer)
58
+ Liquid::Strainer.class_variable_set(:@@global_strainer, Class.new(Liquid::Strainer) do
59
+ @filter_methods = Set.new
60
+ end)
61
+ Liquid::Strainer.class_variable_get(:@@strainer_class_cache).clear
24
62
 
25
- assert_match expected, Template.parse(template).render(assigns)
63
+ globals.each do |global|
64
+ Liquid::Template.register_filter(global)
26
65
  end
27
- end # Assertions
28
- end # Unit
29
- end # Test
66
+ yield
67
+ ensure
68
+ Liquid::Strainer.class_variable_get(:@@strainer_class_cache).clear
69
+ Liquid::Strainer.class_variable_set(:@@global_strainer, original_global_strainer)
70
+ end
71
+
72
+ def with_taint_mode(mode)
73
+ old_mode = Liquid::Template.taint_mode
74
+ Liquid::Template.taint_mode = mode
75
+ yield
76
+ ensure
77
+ Liquid::Template.taint_mode = old_mode
78
+ end
79
+
80
+ def with_error_mode(mode)
81
+ old_mode = Liquid::Template.error_mode
82
+ Liquid::Template.error_mode = mode
83
+ yield
84
+ ensure
85
+ Liquid::Template.error_mode = old_mode
86
+ end
87
+ end
88
+ end
89
+
90
+ class ThingWithToLiquid
91
+ def to_liquid
92
+ 'foobar'
93
+ end
94
+ end
95
+
96
+ class ErrorDrop < Liquid::Drop
97
+ def standard_error
98
+ raise Liquid::StandardError, 'standard error'
99
+ end
100
+
101
+ def argument_error
102
+ raise Liquid::ArgumentError, 'argument error'
103
+ end
104
+
105
+ def syntax_error
106
+ raise Liquid::SyntaxError, 'syntax error'
107
+ end
108
+
109
+ def runtime_error
110
+ raise 'runtime error'
111
+ end
112
+
113
+ def exception
114
+ raise Exception, 'exception'
115
+ end
116
+ end
@@ -0,0 +1,9 @@
1
+ require 'test_helper'
2
+
3
+ class TruffleTest < Minitest::Test
4
+ include Liquid
5
+
6
+ def test_truffle_works
7
+
8
+ end
9
+ end
@@ -1,6 +1,6 @@
1
1
  require 'test_helper'
2
2
 
3
- class BlockTest < Test::Unit::TestCase
3
+ class BlockUnitTest < Minitest::Test
4
4
  include Liquid
5
5
 
6
6
  def test_blankspace
@@ -34,7 +34,7 @@ class BlockTest < Test::Unit::TestCase
34
34
  template = Liquid::Template.parse(" {{funk}} {{so}} {{brother}} ")
35
35
  assert_equal 7, template.root.nodelist.size
36
36
  assert_equal [String, Variable, String, Variable, String, Variable, String],
37
- block_types(template.root.nodelist)
37
+ block_types(template.root.nodelist)
38
38
  end
39
39
 
40
40
  def test_with_block
@@ -45,14 +45,14 @@ class BlockTest < Test::Unit::TestCase
45
45
 
46
46
  def test_with_custom_tag
47
47
  Liquid::Template.register_tag("testtag", Block)
48
-
49
- assert_nothing_thrown do
50
- template = Liquid::Template.parse( "{% testtag %} {% endtesttag %}")
51
- end
48
+ assert Liquid::Template.parse("{% testtag %} {% endtesttag %}")
49
+ ensure
50
+ Liquid::Template.tags.delete('testtag')
52
51
  end
53
52
 
54
53
  private
55
- def block_types(nodelist)
56
- nodelist.collect { |node| node.class }
57
- end
54
+
55
+ def block_types(nodelist)
56
+ nodelist.collect(&:class)
57
+ end
58
58
  end # VariableTest
@@ -0,0 +1,166 @@
1
+ require 'test_helper'
2
+
3
+ class ConditionUnitTest < Minitest::Test
4
+ include Liquid
5
+
6
+ def setup
7
+ @context = Liquid::Context.new
8
+ end
9
+
10
+ def test_basic_condition
11
+ assert_equal false, Condition.new(1, '==', 2).evaluate
12
+ assert_equal true, Condition.new(1, '==', 1).evaluate
13
+ end
14
+
15
+ def test_default_operators_evalute_true
16
+ assert_evaluates_true 1, '==', 1
17
+ assert_evaluates_true 1, '!=', 2
18
+ assert_evaluates_true 1, '<>', 2
19
+ assert_evaluates_true 1, '<', 2
20
+ assert_evaluates_true 2, '>', 1
21
+ assert_evaluates_true 1, '>=', 1
22
+ assert_evaluates_true 2, '>=', 1
23
+ assert_evaluates_true 1, '<=', 2
24
+ assert_evaluates_true 1, '<=', 1
25
+ # negative numbers
26
+ assert_evaluates_true 1, '>', -1
27
+ assert_evaluates_true -1, '<', 1
28
+ assert_evaluates_true 1.0, '>', -1.0
29
+ assert_evaluates_true -1.0, '<', 1.0
30
+ end
31
+
32
+ def test_default_operators_evalute_false
33
+ assert_evaluates_false 1, '==', 2
34
+ assert_evaluates_false 1, '!=', 1
35
+ assert_evaluates_false 1, '<>', 1
36
+ assert_evaluates_false 1, '<', 0
37
+ assert_evaluates_false 2, '>', 4
38
+ assert_evaluates_false 1, '>=', 3
39
+ assert_evaluates_false 2, '>=', 4
40
+ assert_evaluates_false 1, '<=', 0
41
+ assert_evaluates_false 1, '<=', 0
42
+ end
43
+
44
+ def test_contains_works_on_strings
45
+ assert_evaluates_true 'bob', 'contains', 'o'
46
+ assert_evaluates_true 'bob', 'contains', 'b'
47
+ assert_evaluates_true 'bob', 'contains', 'bo'
48
+ assert_evaluates_true 'bob', 'contains', 'ob'
49
+ assert_evaluates_true 'bob', 'contains', 'bob'
50
+
51
+ assert_evaluates_false 'bob', 'contains', 'bob2'
52
+ assert_evaluates_false 'bob', 'contains', 'a'
53
+ assert_evaluates_false 'bob', 'contains', '---'
54
+ end
55
+
56
+ def test_invalid_comparation_operator
57
+ assert_evaluates_argument_error 1, '~~', 0
58
+ end
59
+
60
+ def test_comparation_of_int_and_str
61
+ assert_evaluates_argument_error '1', '>', 0
62
+ assert_evaluates_argument_error '1', '<', 0
63
+ assert_evaluates_argument_error '1', '>=', 0
64
+ assert_evaluates_argument_error '1', '<=', 0
65
+ end
66
+
67
+ def test_hash_compare_backwards_compatibility
68
+ assert_nil Condition.new({}, '>', 2).evaluate
69
+ assert_nil Condition.new(2, '>', {}).evaluate
70
+ assert_equal false, Condition.new({}, '==', 2).evaluate
71
+ assert_equal true, Condition.new({ 'a' => 1 }, '==', { 'a' => 1 }).evaluate
72
+ assert_equal true, Condition.new({ 'a' => 2 }, 'contains', 'a').evaluate
73
+ end
74
+
75
+ def test_contains_works_on_arrays
76
+ @context = Liquid::Context.new
77
+ @context['array'] = [1, 2, 3, 4, 5]
78
+ array_expr = VariableLookup.new("array")
79
+
80
+ assert_evaluates_false array_expr, 'contains', 0
81
+ assert_evaluates_true array_expr, 'contains', 1
82
+ assert_evaluates_true array_expr, 'contains', 2
83
+ assert_evaluates_true array_expr, 'contains', 3
84
+ assert_evaluates_true array_expr, 'contains', 4
85
+ assert_evaluates_true array_expr, 'contains', 5
86
+ assert_evaluates_false array_expr, 'contains', 6
87
+ assert_evaluates_false array_expr, 'contains', "1"
88
+ end
89
+
90
+ def test_contains_returns_false_for_nil_operands
91
+ @context = Liquid::Context.new
92
+ assert_evaluates_false VariableLookup.new('not_assigned'), 'contains', '0'
93
+ assert_evaluates_false 0, 'contains', VariableLookup.new('not_assigned')
94
+ end
95
+
96
+ def test_contains_return_false_on_wrong_data_type
97
+ assert_evaluates_false 1, 'contains', 0
98
+ end
99
+
100
+ def test_contains_with_string_left_operand_coerces_right_operand_to_string
101
+ assert_evaluates_true ' 1 ', 'contains', 1
102
+ assert_evaluates_false ' 1 ', 'contains', 2
103
+ end
104
+
105
+ def test_or_condition
106
+ condition = Condition.new(1, '==', 2)
107
+
108
+ assert_equal false, condition.evaluate
109
+
110
+ condition.or Condition.new(2, '==', 1)
111
+
112
+ assert_equal false, condition.evaluate
113
+
114
+ condition.or Condition.new(1, '==', 1)
115
+
116
+ assert_equal true, condition.evaluate
117
+ end
118
+
119
+ def test_and_condition
120
+ condition = Condition.new(1, '==', 1)
121
+
122
+ assert_equal true, condition.evaluate
123
+
124
+ condition.and Condition.new(2, '==', 2)
125
+
126
+ assert_equal true, condition.evaluate
127
+
128
+ condition.and Condition.new(2, '==', 1)
129
+
130
+ assert_equal false, condition.evaluate
131
+ end
132
+
133
+ def test_should_allow_custom_proc_operator
134
+ Condition.operators['starts_with'] = proc { |cond, left, right| left =~ %r{^#{right}} }
135
+
136
+ assert_evaluates_true 'bob', 'starts_with', 'b'
137
+ assert_evaluates_false 'bob', 'starts_with', 'o'
138
+ ensure
139
+ Condition.operators.delete 'starts_with'
140
+ end
141
+
142
+ def test_left_or_right_may_contain_operators
143
+ @context = Liquid::Context.new
144
+ @context['one'] = @context['another'] = "gnomeslab-and-or-liquid"
145
+
146
+ assert_evaluates_true VariableLookup.new("one"), '==', VariableLookup.new("another")
147
+ end
148
+
149
+ private
150
+
151
+ def assert_evaluates_true(left, op, right)
152
+ assert Condition.new(left, op, right).evaluate(@context),
153
+ "Evaluated false: #{left} #{op} #{right}"
154
+ end
155
+
156
+ def assert_evaluates_false(left, op, right)
157
+ assert !Condition.new(left, op, right).evaluate(@context),
158
+ "Evaluated true: #{left} #{op} #{right}"
159
+ end
160
+
161
+ def assert_evaluates_argument_error(left, op, right)
162
+ assert_raises(Liquid::ArgumentError) do
163
+ Condition.new(left, op, right).evaluate(@context)
164
+ end
165
+ end
166
+ end # ConditionTest
@@ -63,7 +63,7 @@ class ArrayLike
63
63
  end
64
64
  end
65
65
 
66
- class ContextTest < Test::Unit::TestCase
66
+ class ContextUnitTest < Minitest::Test
67
67
  include Liquid
68
68
 
69
69
  def setup
@@ -94,25 +94,23 @@ class ContextTest < Test::Unit::TestCase
94
94
  assert_equal false, @context['bool']
95
95
 
96
96
  @context['nil'] = nil
97
- assert_equal nil, @context['nil']
98
- assert_equal nil, @context['nil']
97
+ assert_nil @context['nil']
98
+ assert_nil @context['nil']
99
99
  end
100
100
 
101
101
  def test_variables_not_existing
102
- assert_equal nil, @context['does_not_exist']
102
+ assert_nil @context['does_not_exist']
103
103
  end
104
104
 
105
105
  def test_scoping
106
- assert_nothing_raised do
107
- @context.push
108
- @context.pop
109
- end
106
+ @context.push
107
+ @context.pop
110
108
 
111
- assert_raise(Liquid::ContextError) do
109
+ assert_raises(Liquid::ContextError) do
112
110
  @context.pop
113
111
  end
114
112
 
115
- assert_raise(Liquid::ContextError) do
113
+ assert_raises(Liquid::ContextError) do
116
114
  @context.push
117
115
  @context.pop
118
116
  @context.pop
@@ -120,30 +118,25 @@ class ContextTest < Test::Unit::TestCase
120
118
  end
121
119
 
122
120
  def test_length_query
123
-
124
- @context['numbers'] = [1,2,3,4]
121
+ @context['numbers'] = [1, 2, 3, 4]
125
122
 
126
123
  assert_equal 4, @context['numbers.size']
127
124
 
128
- @context['numbers'] = {1 => 1,2 => 2,3 => 3,4 => 4}
125
+ @context['numbers'] = { 1 => 1, 2 => 2, 3 => 3, 4 => 4 }
129
126
 
130
127
  assert_equal 4, @context['numbers.size']
131
128
 
132
- @context['numbers'] = {1 => 1,2 => 2,3 => 3,4 => 4, 'size' => 1000}
129
+ @context['numbers'] = { 1 => 1, 2 => 2, 3 => 3, 4 => 4, 'size' => 1000 }
133
130
 
134
131
  assert_equal 1000, @context['numbers.size']
135
-
136
132
  end
137
133
 
138
134
  def test_hyphenated_variable
139
-
140
135
  @context['oh-my'] = 'godz'
141
136
  assert_equal 'godz', @context['oh-my']
142
-
143
137
  end
144
138
 
145
139
  def test_add_filter
146
-
147
140
  filter = Module.new do
148
141
  def hi(output)
149
142
  output + ' hi!'
@@ -159,29 +152,9 @@ class ContextTest < Test::Unit::TestCase
159
152
 
160
153
  context.add_filters(filter)
161
154
  assert_equal 'hi? hi!', context.invoke(:hi, 'hi?')
162
-
163
- end
164
-
165
- def test_override_global_filter
166
- global = Module.new do
167
- def notice(output)
168
- "Global #{output}"
169
- end
170
- end
171
-
172
- local = Module.new do
173
- def notice(output)
174
- "Local #{output}"
175
- end
176
- end
177
-
178
- Template.register_filter(global)
179
- assert_equal 'Global test', Template.parse("{{'test' | notice }}").render
180
- assert_equal 'Local test', Template.parse("{{'test' | notice }}").render({}, :filters => [local])
181
155
  end
182
156
 
183
157
  def test_only_intended_filters_make_it_there
184
-
185
158
  filter = Module.new do
186
159
  def hi(output)
187
160
  output + ' hi!'
@@ -208,11 +181,11 @@ class ContextTest < Test::Unit::TestCase
208
181
  @context['test'] = 'test'
209
182
  assert_equal 'test', @context['test']
210
183
  @context.pop
211
- assert_equal nil, @context['test']
184
+ assert_nil @context['test']
212
185
  end
213
186
 
214
187
  def test_hierachical_data
215
- @context['hash'] = {"name" => 'tobi'}
188
+ @context['hash'] = { "name" => 'tobi' }
216
189
  assert_equal 'tobi', @context['hash.name']
217
190
  assert_equal 'tobi', @context['hash["name"]']
218
191
  end
@@ -241,7 +214,7 @@ class ContextTest < Test::Unit::TestCase
241
214
  end
242
215
 
243
216
  def test_array_notation
244
- @context['test'] = [1,2,3,4,5]
217
+ @context['test'] = [1, 2, 3, 4, 5]
245
218
 
246
219
  assert_equal 1, @context['test[0]']
247
220
  assert_equal 2, @context['test[1]']
@@ -251,21 +224,21 @@ class ContextTest < Test::Unit::TestCase
251
224
  end
252
225
 
253
226
  def test_recoursive_array_notation
254
- @context['test'] = {'test' => [1,2,3,4,5]}
227
+ @context['test'] = { 'test' => [1, 2, 3, 4, 5] }
255
228
 
256
229
  assert_equal 1, @context['test.test[0]']
257
230
 
258
- @context['test'] = [{'test' => 'worked'}]
231
+ @context['test'] = [{ 'test' => 'worked' }]
259
232
 
260
233
  assert_equal 'worked', @context['test[0].test']
261
234
  end
262
235
 
263
236
  def test_hash_to_array_transition
264
237
  @context['colors'] = {
265
- 'Blue' => ['003366','336699', '6699CC', '99CCFF'],
266
- 'Green' => ['003300','336633', '669966', '99CC99'],
267
- 'Yellow' => ['CC9900','FFCC00', 'FFFF99', 'FFFFCC'],
268
- 'Red' => ['660000','993333', 'CC6666', 'FF9999']
238
+ 'Blue' => ['003366', '336699', '6699CC', '99CCFF'],
239
+ 'Green' => ['003300', '336633', '669966', '99CC99'],
240
+ 'Yellow' => ['CC9900', 'FFCC00', 'FFFF99', 'FFFFCC'],
241
+ 'Red' => ['660000', '993333', 'CC6666', 'FF9999']
269
242
  }
270
243
 
271
244
  assert_equal '003366', @context['colors.Blue[0]']
@@ -273,12 +246,12 @@ class ContextTest < Test::Unit::TestCase
273
246
  end
274
247
 
275
248
  def test_try_first
276
- @context['test'] = [1,2,3,4,5]
249
+ @context['test'] = [1, 2, 3, 4, 5]
277
250
 
278
251
  assert_equal 1, @context['test.first']
279
252
  assert_equal 5, @context['test.last']
280
253
 
281
- @context['test'] = {'test' => [1,2,3,4,5]}
254
+ @context['test'] = { 'test' => [1, 2, 3, 4, 5] }
282
255
 
283
256
  assert_equal 1, @context['test.test.first']
284
257
  assert_equal 5, @context['test.test.last']
@@ -289,8 +262,8 @@ class ContextTest < Test::Unit::TestCase
289
262
  end
290
263
 
291
264
  def test_access_hashes_with_hash_notation
292
- @context['products'] = {'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
293
- @context['product'] = {'variants' => [ {'title' => 'draft151cm'}, {'title' => 'element151cm'} ]}
265
+ @context['products'] = { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
266
+ @context['product'] = { 'variants' => [ { 'title' => 'draft151cm' }, { 'title' => 'element151cm' } ] }
294
267
 
295
268
  assert_equal 5, @context['products["count"]']
296
269
  assert_equal 'deepsnow', @context['products["tags"][0]']
@@ -310,85 +283,82 @@ class ContextTest < Test::Unit::TestCase
310
283
  end
311
284
 
312
285
  def test_access_hashes_with_hash_access_variables
313
-
314
286
  @context['var'] = 'tags'
315
- @context['nested'] = {'var' => 'tags'}
316
- @context['products'] = {'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
287
+ @context['nested'] = { 'var' => 'tags' }
288
+ @context['products'] = { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
317
289
 
318
290
  assert_equal 'deepsnow', @context['products[var].first']
319
291
  assert_equal 'freestyle', @context['products[nested.var].last']
320
292
  end
321
293
 
322
294
  def test_hash_notation_only_for_hash_access
323
- @context['array'] = [1,2,3,4,5]
324
- @context['hash'] = {'first' => 'Hello'}
295
+ @context['array'] = [1, 2, 3, 4, 5]
296
+ @context['hash'] = { 'first' => 'Hello' }
325
297
 
326
298
  assert_equal 1, @context['array.first']
327
- assert_equal nil, @context['array["first"]']
299
+ assert_nil @context['array["first"]']
328
300
  assert_equal 'Hello', @context['hash["first"]']
329
301
  end
330
302
 
331
303
  def test_first_can_appear_in_middle_of_callchain
332
-
333
- @context['product'] = {'variants' => [ {'title' => 'draft151cm'}, {'title' => 'element151cm'} ]}
304
+ @context['product'] = { 'variants' => [ { 'title' => 'draft151cm' }, { 'title' => 'element151cm' } ] }
334
305
 
335
306
  assert_equal 'draft151cm', @context['product.variants[0].title']
336
307
  assert_equal 'element151cm', @context['product.variants[1].title']
337
308
  assert_equal 'draft151cm', @context['product.variants.first.title']
338
309
  assert_equal 'element151cm', @context['product.variants.last.title']
339
-
340
310
  end
341
311
 
342
312
  def test_cents
343
- @context.merge( "cents" => HundredCentes.new )
313
+ @context.merge("cents" => HundredCentes.new)
344
314
  assert_equal 100, @context['cents']
345
315
  end
346
316
 
347
317
  def test_nested_cents
348
- @context.merge( "cents" => { 'amount' => HundredCentes.new} )
318
+ @context.merge("cents" => { 'amount' => HundredCentes.new })
349
319
  assert_equal 100, @context['cents.amount']
350
320
 
351
- @context.merge( "cents" => { 'cents' => { 'amount' => HundredCentes.new} } )
321
+ @context.merge("cents" => { 'cents' => { 'amount' => HundredCentes.new } })
352
322
  assert_equal 100, @context['cents.cents.amount']
353
323
  end
354
324
 
355
325
  def test_cents_through_drop
356
- @context.merge( "cents" => CentsDrop.new )
326
+ @context.merge("cents" => CentsDrop.new)
357
327
  assert_equal 100, @context['cents.amount']
358
328
  end
359
329
 
360
330
  def test_nested_cents_through_drop
361
- @context.merge( "vars" => {"cents" => CentsDrop.new} )
331
+ @context.merge("vars" => { "cents" => CentsDrop.new })
362
332
  assert_equal 100, @context['vars.cents.amount']
363
333
  end
364
334
 
365
335
  def test_drop_methods_with_question_marks
366
- @context.merge( "cents" => CentsDrop.new )
336
+ @context.merge("cents" => CentsDrop.new)
367
337
  assert @context['cents.non_zero?']
368
338
  end
369
339
 
370
340
  def test_context_from_within_drop
371
- @context.merge( "test" => '123', "vars" => ContextSensitiveDrop.new )
341
+ @context.merge("test" => '123', "vars" => ContextSensitiveDrop.new)
372
342
  assert_equal '123', @context['vars.test']
373
343
  end
374
344
 
375
345
  def test_nested_context_from_within_drop
376
- @context.merge( "test" => '123', "vars" => {"local" => ContextSensitiveDrop.new } )
346
+ @context.merge("test" => '123', "vars" => { "local" => ContextSensitiveDrop.new })
377
347
  assert_equal '123', @context['vars.local.test']
378
348
  end
379
349
 
380
350
  def test_ranges
381
- @context.merge( "test" => '5' )
351
+ @context.merge("test" => '5')
382
352
  assert_equal (1..5), @context['(1..5)']
383
353
  assert_equal (1..5), @context['(1..test)']
384
354
  assert_equal (5..5), @context['(test..test)']
385
355
  end
386
356
 
387
357
  def test_cents_through_drop_nestedly
388
- @context.merge( "cents" => {"cents" => CentsDrop.new} )
358
+ @context.merge("cents" => { "cents" => CentsDrop.new })
389
359
  assert_equal 100, @context['cents.cents.amount']
390
360
 
391
- @context.merge( "cents" => { "cents" => {"cents" => CentsDrop.new}} )
361
+ @context.merge("cents" => { "cents" => { "cents" => CentsDrop.new } })
392
362
  assert_equal 100, @context['cents.cents.cents.amount']
393
363
  end
394
364
 
@@ -409,7 +379,7 @@ class ContextTest < Test::Unit::TestCase
409
379
  end
410
380
 
411
381
  def test_proc_as_variable
412
- @context['dynamic'] = Proc.new { 'Hello' }
382
+ @context['dynamic'] = proc { 'Hello' }
413
383
 
414
384
  assert_equal 'Hello', @context['dynamic']
415
385
  end
@@ -427,7 +397,7 @@ class ContextTest < Test::Unit::TestCase
427
397
  end
428
398
 
429
399
  def test_array_containing_lambda_as_variable
430
- @context['dynamic'] = [1,2, proc { 'Hello' } ,4,5]
400
+ @context['dynamic'] = [1, 2, proc { 'Hello' }, 4, 5]
431
401
 
432
402
  assert_equal 'Hello', @context['dynamic[2]']
433
403
  end
@@ -453,7 +423,7 @@ class ContextTest < Test::Unit::TestCase
453
423
  end
454
424
 
455
425
  def test_lambda_in_array_is_called_once
456
- @context['callcount'] = [1,2, proc { @global ||= 0; @global += 1; @global.to_s } ,4,5]
426
+ @context['callcount'] = [1, 2, proc { @global ||= 0; @global += 1; @global.to_s }, 4, 5]
457
427
 
458
428
  assert_equal '1', @context['callcount[2]']
459
429
  assert_equal '1', @context['callcount[2]']
@@ -475,4 +445,45 @@ class ContextTest < Test::Unit::TestCase
475
445
  assert_kind_of CategoryDrop, @context['category']
476
446
  assert_equal @context, @context['category'].context
477
447
  end
448
+
449
+ def test_interrupt_avoids_object_allocations
450
+ assert_no_object_allocations do
451
+ @context.interrupt?
452
+ end
453
+ end
454
+
455
+ def test_context_initialization_with_a_proc_in_environment
456
+ contx = Context.new([test: ->(c) { c['poutine'] }], { test: :foo })
457
+
458
+ assert contx
459
+ assert_nil contx['poutine']
460
+ end
461
+
462
+ def test_apply_global_filter
463
+ global_filter_proc = ->(output) { "#{output} filtered" }
464
+
465
+ context = Context.new
466
+ context.global_filter = global_filter_proc
467
+
468
+ assert_equal 'hi filtered', context.apply_global_filter('hi')
469
+ end
470
+
471
+ def test_apply_global_filter_when_no_global_filter_exist
472
+ context = Context.new
473
+ assert_equal 'hi', context.apply_global_filter('hi')
474
+ end
475
+
476
+ private
477
+
478
+ def assert_no_object_allocations
479
+ unless RUBY_ENGINE == 'ruby'
480
+ skip "stackprof needed to count object allocations"
481
+ end
482
+ require 'stackprof'
483
+
484
+ profile = StackProf.run(mode: :object) do
485
+ yield
486
+ end
487
+ assert_equal 0, profile[:samples]
488
+ end
478
489
  end # ContextTest