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.
- checksums.yaml +5 -5
- data/History.md +194 -29
- data/{MIT-LICENSE → LICENSE} +0 -0
- data/README.md +60 -2
- data/lib/liquid.rb +25 -14
- data/lib/liquid/block.rb +47 -96
- data/lib/liquid/block_body.rb +143 -0
- data/lib/liquid/condition.rb +70 -39
- data/lib/liquid/context.rb +116 -157
- data/lib/liquid/document.rb +19 -9
- data/lib/liquid/drop.rb +31 -14
- data/lib/liquid/errors.rb +54 -10
- data/lib/liquid/expression.rb +49 -0
- data/lib/liquid/extensions.rb +19 -7
- data/lib/liquid/file_system.rb +25 -14
- data/lib/liquid/forloop_drop.rb +42 -0
- data/lib/liquid/i18n.rb +39 -0
- data/lib/liquid/interrupts.rb +2 -3
- data/lib/liquid/lexer.rb +55 -0
- data/lib/liquid/locales/en.yml +26 -0
- data/lib/liquid/parse_context.rb +38 -0
- data/lib/liquid/parse_tree_visitor.rb +42 -0
- data/lib/liquid/parser.rb +90 -0
- data/lib/liquid/parser_switching.rb +31 -0
- data/lib/liquid/profiler.rb +158 -0
- data/lib/liquid/profiler/hooks.rb +23 -0
- data/lib/liquid/range_lookup.rb +37 -0
- data/lib/liquid/resource_limits.rb +23 -0
- data/lib/liquid/standardfilters.rb +311 -77
- data/lib/liquid/strainer.rb +39 -26
- data/lib/liquid/tablerowloop_drop.rb +62 -0
- data/lib/liquid/tag.rb +28 -11
- data/lib/liquid/tags/assign.rb +34 -10
- data/lib/liquid/tags/break.rb +1 -4
- data/lib/liquid/tags/capture.rb +11 -9
- data/lib/liquid/tags/case.rb +37 -22
- data/lib/liquid/tags/comment.rb +10 -3
- data/lib/liquid/tags/continue.rb +1 -4
- data/lib/liquid/tags/cycle.rb +20 -14
- data/lib/liquid/tags/decrement.rb +4 -8
- data/lib/liquid/tags/for.rb +121 -60
- data/lib/liquid/tags/if.rb +73 -30
- data/lib/liquid/tags/ifchanged.rb +3 -5
- data/lib/liquid/tags/include.rb +77 -46
- data/lib/liquid/tags/increment.rb +4 -8
- data/lib/liquid/tags/raw.rb +35 -10
- data/lib/liquid/tags/table_row.rb +62 -0
- data/lib/liquid/tags/unless.rb +6 -9
- data/lib/liquid/template.rb +130 -32
- data/lib/liquid/tokenizer.rb +31 -0
- data/lib/liquid/truffle.rb +5 -0
- data/lib/liquid/utils.rb +57 -4
- data/lib/liquid/variable.rb +121 -30
- data/lib/liquid/variable_lookup.rb +88 -0
- data/lib/liquid/version.rb +2 -1
- data/test/fixtures/en_locale.yml +9 -0
- data/test/integration/assign_test.rb +48 -0
- data/test/integration/blank_test.rb +106 -0
- data/test/integration/block_test.rb +12 -0
- data/test/{liquid → integration}/capture_test.rb +13 -3
- data/test/integration/context_test.rb +32 -0
- data/test/integration/document_test.rb +19 -0
- data/test/integration/drop_test.rb +273 -0
- data/test/integration/error_handling_test.rb +260 -0
- data/test/integration/filter_test.rb +178 -0
- data/test/integration/hash_ordering_test.rb +23 -0
- data/test/integration/output_test.rb +123 -0
- data/test/integration/parse_tree_visitor_test.rb +247 -0
- data/test/integration/parsing_quirks_test.rb +122 -0
- data/test/integration/render_profiling_test.rb +154 -0
- data/test/integration/security_test.rb +80 -0
- data/test/integration/standard_filter_test.rb +776 -0
- data/test/{liquid → integration}/tags/break_tag_test.rb +2 -3
- data/test/{liquid → integration}/tags/continue_tag_test.rb +1 -2
- data/test/integration/tags/for_tag_test.rb +410 -0
- data/test/integration/tags/if_else_tag_test.rb +188 -0
- data/test/integration/tags/include_tag_test.rb +253 -0
- data/test/integration/tags/increment_tag_test.rb +23 -0
- data/test/{liquid → integration}/tags/raw_tag_test.rb +9 -2
- data/test/integration/tags/standard_tag_test.rb +296 -0
- data/test/integration/tags/statements_test.rb +111 -0
- data/test/{liquid/tags/html_tag_test.rb → integration/tags/table_row_test.rb} +25 -24
- data/test/integration/tags/unless_else_tag_test.rb +26 -0
- data/test/integration/template_test.rb +332 -0
- data/test/integration/trim_mode_test.rb +529 -0
- data/test/integration/variable_test.rb +96 -0
- data/test/test_helper.rb +106 -19
- data/test/truffle/truffle_test.rb +9 -0
- data/test/{liquid/block_test.rb → unit/block_unit_test.rb} +9 -9
- data/test/unit/condition_unit_test.rb +166 -0
- data/test/{liquid/context_test.rb → unit/context_unit_test.rb} +85 -74
- data/test/unit/file_system_unit_test.rb +35 -0
- data/test/unit/i18n_unit_test.rb +37 -0
- data/test/unit/lexer_unit_test.rb +51 -0
- data/test/unit/parser_unit_test.rb +82 -0
- data/test/{liquid/regexp_test.rb → unit/regexp_unit_test.rb} +4 -4
- data/test/unit/strainer_unit_test.rb +164 -0
- data/test/unit/tag_unit_test.rb +21 -0
- data/test/unit/tags/case_tag_unit_test.rb +10 -0
- data/test/unit/tags/for_tag_unit_test.rb +13 -0
- data/test/unit/tags/if_tag_unit_test.rb +8 -0
- data/test/unit/template_unit_test.rb +78 -0
- data/test/unit/tokenizer_unit_test.rb +55 -0
- data/test/unit/variable_unit_test.rb +162 -0
- metadata +157 -77
- data/lib/extras/liquid_view.rb +0 -51
- data/lib/liquid/htmltags.rb +0 -74
- data/lib/liquid/module_ex.rb +0 -62
- data/test/liquid/assign_test.rb +0 -21
- data/test/liquid/condition_test.rb +0 -127
- data/test/liquid/drop_test.rb +0 -180
- data/test/liquid/error_handling_test.rb +0 -81
- data/test/liquid/file_system_test.rb +0 -29
- data/test/liquid/filter_test.rb +0 -125
- data/test/liquid/hash_ordering_test.rb +0 -25
- data/test/liquid/module_ex_test.rb +0 -87
- data/test/liquid/output_test.rb +0 -116
- data/test/liquid/parsing_quirks_test.rb +0 -52
- data/test/liquid/security_test.rb +0 -64
- data/test/liquid/standard_filter_test.rb +0 -251
- data/test/liquid/strainer_test.rb +0 -52
- data/test/liquid/tags/for_tag_test.rb +0 -297
- data/test/liquid/tags/if_else_tag_test.rb +0 -166
- data/test/liquid/tags/include_tag_test.rb +0 -166
- data/test/liquid/tags/increment_tag_test.rb +0 -24
- data/test/liquid/tags/standard_tag_test.rb +0 -295
- data/test/liquid/tags/statements_test.rb +0 -134
- data/test/liquid/tags/unless_else_tag_test.rb +0 -26
- data/test/liquid/template_test.rb +0 -146
- 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
|
data/test/test_helper.rb
CHANGED
@@ -1,29 +1,116 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
4
|
-
require '
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
14
|
-
|
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
|
-
|
19
|
-
|
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
|
-
|
23
|
-
|
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
|
-
|
63
|
+
globals.each do |global|
|
64
|
+
Liquid::Template.register_filter(global)
|
26
65
|
end
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
class
|
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
|
-
|
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
|
-
|
50
|
-
|
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
|
-
|
56
|
-
|
57
|
-
|
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
|
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
|
-
|
98
|
-
|
97
|
+
assert_nil @context['nil']
|
98
|
+
assert_nil @context['nil']
|
99
99
|
end
|
100
100
|
|
101
101
|
def test_variables_not_existing
|
102
|
-
|
102
|
+
assert_nil @context['does_not_exist']
|
103
103
|
end
|
104
104
|
|
105
105
|
def test_scoping
|
106
|
-
|
107
|
-
|
108
|
-
@context.pop
|
109
|
-
end
|
106
|
+
@context.push
|
107
|
+
@context.pop
|
110
108
|
|
111
|
-
|
109
|
+
assert_raises(Liquid::ContextError) do
|
112
110
|
@context.pop
|
113
111
|
end
|
114
112
|
|
115
|
-
|
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
|
-
|
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
|
-
|
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(
|
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(
|
318
|
+
@context.merge("cents" => { 'amount' => HundredCentes.new })
|
349
319
|
assert_equal 100, @context['cents.amount']
|
350
320
|
|
351
|
-
@context.merge(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
358
|
+
@context.merge("cents" => { "cents" => CentsDrop.new })
|
389
359
|
assert_equal 100, @context['cents.cents.amount']
|
390
360
|
|
391
|
-
@context.merge(
|
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'] =
|
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' }
|
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 }
|
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
|