liquid 2.6.3 → 3.0.0.rc1

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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +42 -13
  3. data/README.md +27 -2
  4. data/lib/liquid.rb +11 -11
  5. data/lib/liquid/block.rb +75 -45
  6. data/lib/liquid/condition.rb +15 -11
  7. data/lib/liquid/context.rb +68 -29
  8. data/lib/liquid/document.rb +3 -3
  9. data/lib/liquid/drop.rb +17 -1
  10. data/lib/liquid/file_system.rb +17 -6
  11. data/lib/liquid/i18n.rb +39 -0
  12. data/lib/liquid/interrupts.rb +1 -1
  13. data/lib/liquid/lexer.rb +51 -0
  14. data/lib/liquid/locales/en.yml +22 -0
  15. data/lib/liquid/parser.rb +90 -0
  16. data/lib/liquid/standardfilters.rb +115 -52
  17. data/lib/liquid/strainer.rb +14 -4
  18. data/lib/liquid/tag.rb +42 -7
  19. data/lib/liquid/tags/assign.rb +10 -8
  20. data/lib/liquid/tags/break.rb +1 -1
  21. data/lib/liquid/tags/capture.rb +10 -8
  22. data/lib/liquid/tags/case.rb +13 -13
  23. data/lib/liquid/tags/comment.rb +9 -2
  24. data/lib/liquid/tags/continue.rb +1 -4
  25. data/lib/liquid/tags/cycle.rb +5 -7
  26. data/lib/liquid/tags/decrement.rb +3 -4
  27. data/lib/liquid/tags/for.rb +69 -36
  28. data/lib/liquid/tags/if.rb +52 -25
  29. data/lib/liquid/tags/ifchanged.rb +2 -2
  30. data/lib/liquid/tags/include.rb +8 -7
  31. data/lib/liquid/tags/increment.rb +4 -8
  32. data/lib/liquid/tags/raw.rb +3 -3
  33. data/lib/liquid/tags/table_row.rb +73 -0
  34. data/lib/liquid/tags/unless.rb +2 -4
  35. data/lib/liquid/template.rb +69 -10
  36. data/lib/liquid/utils.rb +13 -4
  37. data/lib/liquid/variable.rb +59 -8
  38. data/lib/liquid/version.rb +1 -1
  39. data/test/fixtures/en_locale.yml +9 -0
  40. data/test/{liquid → integration}/assign_test.rb +6 -0
  41. data/test/integration/blank_test.rb +106 -0
  42. data/test/{liquid → integration}/capture_test.rb +2 -2
  43. data/test/integration/context_test.rb +33 -0
  44. data/test/integration/drop_test.rb +245 -0
  45. data/test/{liquid → integration}/error_handling_test.rb +31 -2
  46. data/test/{liquid → integration}/filter_test.rb +7 -7
  47. data/test/{liquid → integration}/hash_ordering_test.rb +0 -0
  48. data/test/{liquid → integration}/output_test.rb +12 -12
  49. data/test/integration/parsing_quirks_test.rb +94 -0
  50. data/test/{liquid → integration}/security_test.rb +9 -9
  51. data/test/{liquid → integration}/standard_filter_test.rb +103 -33
  52. data/test/{liquid → integration}/tags/break_tag_test.rb +0 -0
  53. data/test/{liquid → integration}/tags/continue_tag_test.rb +0 -0
  54. data/test/{liquid → integration}/tags/for_tag_test.rb +78 -0
  55. data/test/{liquid → integration}/tags/if_else_tag_test.rb +1 -1
  56. data/test/integration/tags/include_tag_test.rb +212 -0
  57. data/test/{liquid → integration}/tags/increment_tag_test.rb +0 -0
  58. data/test/{liquid → integration}/tags/raw_tag_test.rb +1 -0
  59. data/test/{liquid → integration}/tags/standard_tag_test.rb +24 -22
  60. data/test/integration/tags/statements_test.rb +113 -0
  61. data/test/{liquid/tags/html_tag_test.rb → integration/tags/table_row_test.rb} +5 -5
  62. data/test/{liquid → integration}/tags/unless_else_tag_test.rb +0 -0
  63. data/test/{liquid → integration}/template_test.rb +66 -42
  64. data/test/integration/variable_test.rb +72 -0
  65. data/test/test_helper.rb +32 -7
  66. data/test/{liquid/block_test.rb → unit/block_unit_test.rb} +1 -1
  67. data/test/{liquid/condition_test.rb → unit/condition_unit_test.rb} +19 -1
  68. data/test/{liquid/context_test.rb → unit/context_unit_test.rb} +27 -19
  69. data/test/{liquid/file_system_test.rb → unit/file_system_unit_test.rb} +7 -1
  70. data/test/unit/i18n_unit_test.rb +37 -0
  71. data/test/unit/lexer_unit_test.rb +48 -0
  72. data/test/{liquid/module_ex_test.rb → unit/module_ex_unit_test.rb} +7 -7
  73. data/test/unit/parser_unit_test.rb +82 -0
  74. data/test/{liquid/regexp_test.rb → unit/regexp_unit_test.rb} +3 -3
  75. data/test/{liquid/strainer_test.rb → unit/strainer_unit_test.rb} +19 -1
  76. data/test/unit/tag_unit_test.rb +11 -0
  77. data/test/unit/tags/case_tag_unit_test.rb +10 -0
  78. data/test/unit/tags/for_tag_unit_test.rb +13 -0
  79. data/test/unit/tags/if_tag_unit_test.rb +8 -0
  80. data/test/unit/template_unit_test.rb +69 -0
  81. data/test/unit/tokenizer_unit_test.rb +29 -0
  82. data/test/{liquid/variable_test.rb → unit/variable_unit_test.rb} +17 -67
  83. metadata +117 -73
  84. data/lib/extras/liquid_view.rb +0 -51
  85. data/lib/liquid/htmltags.rb +0 -73
  86. data/test/liquid/drop_test.rb +0 -180
  87. data/test/liquid/parsing_quirks_test.rb +0 -52
  88. data/test/liquid/tags/include_tag_test.rb +0 -166
  89. data/test/liquid/tags/statements_test.rb +0 -134
@@ -58,19 +58,48 @@ class ErrorHandlingTest < Test::Unit::TestCase
58
58
 
59
59
  def test_missing_endtag_parse_time_error
60
60
  assert_raise(Liquid::SyntaxError) do
61
- template = Liquid::Template.parse(' {% for a in b %} ... ')
61
+ Liquid::Template.parse(' {% for a in b %} ... ')
62
62
  end
63
63
  end
64
64
 
65
65
  def test_unrecognized_operator
66
+ with_error_mode(:strict) do
67
+ assert_raise(SyntaxError) do
68
+ Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ')
69
+ end
70
+ end
71
+ end
72
+
73
+ def test_lax_unrecognized_operator
66
74
  assert_nothing_raised do
67
- template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ')
75
+ template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :error_mode => :lax)
68
76
  assert_equal ' Liquid error: Unknown operator =! ', template.render
69
77
  assert_equal 1, template.errors.size
70
78
  assert_equal Liquid::ArgumentError, template.errors.first.class
71
79
  end
72
80
  end
73
81
 
82
+ def test_strict_error_messages
83
+ err = assert_raise(SyntaxError) do
84
+ Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :error_mode => :strict)
85
+ end
86
+ assert_equal 'Unexpected character = in "1 =! 2"', err.message
87
+
88
+ err = assert_raise(SyntaxError) do
89
+ Liquid::Template.parse('{{%%%}}', :error_mode => :strict)
90
+ end
91
+ assert_equal 'Unexpected character % in "{{%%%}}"', err.message
92
+ end
93
+
94
+ def test_warnings
95
+ template = Liquid::Template.parse('{% if ~~~ %}{{%%%}}{% else %}{{ hello. }}{% endif %}', :error_mode => :warn)
96
+ assert_equal 3, template.warnings.size
97
+ assert_equal 'Unexpected character ~ in "~~~"', template.warnings[0].message
98
+ assert_equal 'Unexpected character % in "{{%%%}}"', template.warnings[1].message
99
+ assert_equal 'Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].message
100
+ assert_equal '', template.render
101
+ end
102
+
74
103
  # Liquid should not catch Exceptions that are not subclasses of StandardError, like Interrupt and NoMemoryError
75
104
  def test_exceptions_propagate
76
105
  assert_raise Exception do
@@ -67,12 +67,12 @@ class FiltersTest < Test::Unit::TestCase
67
67
  @context['value'] = 3
68
68
  @context['numbers'] = [2,1,4,3]
69
69
  @context['words'] = ['expected', 'as', 'alphabetic']
70
- @context['arrays'] = [['flattened'], ['are']]
70
+ @context['arrays'] = ['flower', 'are']
71
71
 
72
72
  assert_equal [1,2,3,4], Variable.new("numbers | sort").render(@context)
73
73
  assert_equal ['alphabetic', 'as', 'expected'], Variable.new("words | sort").render(@context)
74
74
  assert_equal [3], Variable.new("value | sort").render(@context)
75
- assert_equal ['are', 'flattened'], Variable.new("arrays | sort").render(@context)
75
+ assert_equal ['are', 'flower'], Variable.new("arrays | sort").render(@context)
76
76
  end
77
77
 
78
78
  def test_strip_html
@@ -113,13 +113,13 @@ class FiltersInTemplate < Test::Unit::TestCase
113
113
  def test_local_global
114
114
  Template.register_filter(MoneyFilter)
115
115
 
116
- assert_equal " 1000$ ", Template.parse("{{1000 | money}}").render(nil, nil)
117
- assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, :filters => CanadianMoneyFilter)
118
- assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, :filters => [CanadianMoneyFilter])
116
+ assert_equal " 1000$ ", Template.parse("{{1000 | money}}").render!(nil, nil)
117
+ assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, :filters => CanadianMoneyFilter)
118
+ assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, :filters => [CanadianMoneyFilter])
119
119
  end
120
120
 
121
121
  def test_local_filter_with_deprecated_syntax
122
- assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, CanadianMoneyFilter)
123
- assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, [CanadianMoneyFilter])
122
+ assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, CanadianMoneyFilter)
123
+ assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, [CanadianMoneyFilter])
124
124
  end
125
125
  end # FiltersTest
File without changes
@@ -41,76 +41,76 @@ class OutputTest < Test::Unit::TestCase
41
41
  text = %| {{best_cars}} |
42
42
 
43
43
  expected = %| bmw |
44
- assert_equal expected, Template.parse(text).render(@assigns)
44
+ assert_equal expected, Template.parse(text).render!(@assigns)
45
45
  end
46
46
 
47
47
  def test_variable_traversing
48
48
  text = %| {{car.bmw}} {{car.gm}} {{car.bmw}} |
49
49
 
50
50
  expected = %| good bad good |
51
- assert_equal expected, Template.parse(text).render(@assigns)
51
+ assert_equal expected, Template.parse(text).render!(@assigns)
52
52
  end
53
53
 
54
54
  def test_variable_piping
55
55
  text = %( {{ car.gm | make_funny }} )
56
56
  expected = %| LOL |
57
57
 
58
- assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
58
+ assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
59
59
  end
60
60
 
61
61
  def test_variable_piping_with_input
62
62
  text = %( {{ car.gm | cite_funny }} )
63
63
  expected = %| LOL: bad |
64
64
 
65
- assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
65
+ assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
66
66
  end
67
67
 
68
68
  def test_variable_piping_with_args
69
69
  text = %! {{ car.gm | add_smiley : ':-(' }} !
70
70
  expected = %| bad :-( |
71
71
 
72
- assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
72
+ assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
73
73
  end
74
74
 
75
75
  def test_variable_piping_with_no_args
76
76
  text = %! {{ car.gm | add_smiley }} !
77
77
  expected = %| bad :-) |
78
78
 
79
- assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
79
+ assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
80
80
  end
81
81
 
82
82
  def test_multiple_variable_piping_with_args
83
83
  text = %! {{ car.gm | add_smiley : ':-(' | add_smiley : ':-('}} !
84
84
  expected = %| bad :-( :-( |
85
85
 
86
- assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
86
+ assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
87
87
  end
88
88
 
89
- def test_variable_piping_with_args
89
+ def test_variable_piping_with_multiple_args
90
90
  text = %! {{ car.gm | add_tag : 'span', 'bar'}} !
91
91
  expected = %| <span id="bar">bad</span> |
92
92
 
93
- assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
93
+ assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
94
94
  end
95
95
 
96
96
  def test_variable_piping_with_variable_args
97
97
  text = %! {{ car.gm | add_tag : 'span', car.bmw}} !
98
98
  expected = %| <span id="good">bad</span> |
99
99
 
100
- assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
100
+ assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
101
101
  end
102
102
 
103
103
  def test_multiple_pipings
104
104
  text = %( {{ best_cars | cite_funny | paragraph }} )
105
105
  expected = %| <p>LOL: bmw</p> |
106
106
 
107
- assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
107
+ assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
108
108
  end
109
109
 
110
110
  def test_link_to
111
111
  text = %( {{ 'Typo' | link_to: 'http://typo.leetsoft.com' }} )
112
112
  expected = %| <a href="http://typo.leetsoft.com">Typo</a> |
113
113
 
114
- assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
114
+ assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
115
115
  end
116
116
  end # OutputTest
@@ -0,0 +1,94 @@
1
+ require 'test_helper'
2
+
3
+ class ParsingQuirksTest < Test::Unit::TestCase
4
+ include Liquid
5
+
6
+ def test_parsing_css
7
+ text = " div { font-weight: bold; } "
8
+ assert_equal text, Template.parse(text).render!
9
+ end
10
+
11
+ def test_raise_on_single_close_bracet
12
+ assert_raise(SyntaxError) do
13
+ Template.parse("text {{method} oh nos!")
14
+ end
15
+ end
16
+
17
+ def test_raise_on_label_and_no_close_bracets
18
+ assert_raise(SyntaxError) do
19
+ Template.parse("TEST {{ ")
20
+ end
21
+ end
22
+
23
+ def test_raise_on_label_and_no_close_bracets_percent
24
+ assert_raise(SyntaxError) do
25
+ Template.parse("TEST {% ")
26
+ end
27
+ end
28
+
29
+ def test_error_on_empty_filter
30
+ assert_nothing_raised do
31
+ Template.parse("{{test}}")
32
+ Template.parse("{{|test}}")
33
+ end
34
+ with_error_mode(:strict) do
35
+ assert_raise(SyntaxError) do
36
+ Template.parse("{{test |a|b|}}")
37
+ end
38
+ end
39
+ end
40
+
41
+ def test_meaningless_parens_error
42
+ with_error_mode(:strict) do
43
+ assert_raise(SyntaxError) do
44
+ markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
45
+ Template.parse("{% if #{markup} %} YES {% endif %}")
46
+ end
47
+ end
48
+ end
49
+
50
+ def test_unexpected_characters_syntax_error
51
+ with_error_mode(:strict) do
52
+ assert_raise(SyntaxError) do
53
+ markup = "true && false"
54
+ Template.parse("{% if #{markup} %} YES {% endif %}")
55
+ end
56
+ assert_raise(SyntaxError) do
57
+ markup = "false || true"
58
+ Template.parse("{% if #{markup} %} YES {% endif %}")
59
+ end
60
+ end
61
+ end
62
+
63
+ def test_no_error_on_lax_empty_filter
64
+ assert_nothing_raised do
65
+ Template.parse("{{test |a|b|}}", :error_mode => :lax)
66
+ Template.parse("{{test}}", :error_mode => :lax)
67
+ Template.parse("{{|test|}}", :error_mode => :lax)
68
+ end
69
+ end
70
+
71
+ def test_meaningless_parens_lax
72
+ with_error_mode(:lax) do
73
+ assigns = {'b' => 'bar', 'c' => 'baz'}
74
+ markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
75
+ assert_template_result(' YES ',"{% if #{markup} %} YES {% endif %}", assigns)
76
+ end
77
+ end
78
+
79
+ def test_unexpected_characters_silently_eat_logic_lax
80
+ with_error_mode(:lax) do
81
+ markup = "true && false"
82
+ assert_template_result(' YES ',"{% if #{markup} %} YES {% endif %}")
83
+ markup = "false || true"
84
+ assert_template_result('',"{% if #{markup} %} YES {% endif %}")
85
+ end
86
+ end
87
+
88
+ def test_raise_on_invalid_tag_delimiter
89
+ assert_raise(Liquid::SyntaxError) do
90
+ Template.new.parse('{% end %}')
91
+ end
92
+ end
93
+
94
+ end # ParsingQuirksTest
@@ -13,14 +13,14 @@ class SecurityTest < Test::Unit::TestCase
13
13
  text = %( {{ '1+1' | instance_eval }} )
14
14
  expected = %| 1+1 |
15
15
 
16
- assert_equal expected, Template.parse(text).render(@assigns)
16
+ assert_equal expected, Template.parse(text).render!(@assigns)
17
17
  end
18
18
 
19
19
  def test_no_existing_instance_eval
20
20
  text = %( {{ '1+1' | __instance_eval__ }} )
21
21
  expected = %| 1+1 |
22
22
 
23
- assert_equal expected, Template.parse(text).render(@assigns)
23
+ assert_equal expected, Template.parse(text).render!(@assigns)
24
24
  end
25
25
 
26
26
 
@@ -28,7 +28,7 @@ class SecurityTest < Test::Unit::TestCase
28
28
  text = %( {{ '1+1' | instance_eval }} )
29
29
  expected = %| 1+1 |
30
30
 
31
- assert_equal expected, Template.parse(text).render(@assigns)
31
+ assert_equal expected, Template.parse(text).render!(@assigns)
32
32
  end
33
33
 
34
34
 
@@ -36,7 +36,7 @@ class SecurityTest < Test::Unit::TestCase
36
36
  text = %( {{ '1+1' | add_one | instance_eval }} )
37
37
  expected = %| 1+1 + 1 |
38
38
 
39
- assert_equal expected, Template.parse(text).render(@assigns, :filters => SecurityFilter)
39
+ assert_equal expected, Template.parse(text).render!(@assigns, :filters => SecurityFilter)
40
40
  end
41
41
 
42
42
  def test_does_not_add_filters_to_symbol_table
@@ -47,17 +47,17 @@ class SecurityTest < Test::Unit::TestCase
47
47
  template = Template.parse(test)
48
48
  assert_equal [], (Symbol.all_symbols - current_symbols)
49
49
 
50
- template.render
50
+ template.render!
51
51
  assert_equal [], (Symbol.all_symbols - current_symbols)
52
52
  end
53
53
 
54
54
  def test_does_not_add_drop_methods_to_symbol_table
55
55
  current_symbols = Symbol.all_symbols
56
56
 
57
- drop = Drop.new
58
- drop.invoke_drop("custom_method_1")
59
- drop.invoke_drop("custom_method_2")
60
- drop.invoke_drop("custom_method_3")
57
+ assigns = { 'drop' => Drop.new }
58
+ assert_equal "", Template.parse("{{ drop.custom_method_1 }}", assigns).render!
59
+ assert_equal "", Template.parse("{{ drop.custom_method_2 }}", assigns).render!
60
+ assert_equal "", Template.parse("{{ drop.custom_method_3 }}", assigns).render!
61
61
 
62
62
  assert_equal [], (Symbol.all_symbols - current_symbols)
63
63
  end
@@ -7,6 +7,8 @@ class Filters
7
7
  end
8
8
 
9
9
  class TestThing
10
+ attr_reader :foo
11
+
10
12
  def initialize
11
13
  @foo = 0
12
14
  end
@@ -15,6 +17,10 @@ class TestThing
15
17
  "woot: #{@foo}"
16
18
  end
17
19
 
20
+ def [](whatever)
21
+ to_s
22
+ end
23
+
18
24
  def to_liquid
19
25
  @foo += 1
20
26
  self
@@ -27,6 +33,14 @@ class TestDrop < Liquid::Drop
27
33
  end
28
34
  end
29
35
 
36
+ class TestEnumerable < Liquid::Drop
37
+ include Enumerable
38
+
39
+ def each(&block)
40
+ [ { "foo" => 1, "bar" => 2 }, { "foo" => 2, "bar" => 1 }, { "foo" => 3, "bar" => 3 } ].each(&block)
41
+ end
42
+ end
43
+
30
44
  class StandardFiltersTest < Test::Unit::TestCase
31
45
  include Liquid
32
46
 
@@ -50,11 +64,6 @@ class StandardFiltersTest < Test::Unit::TestCase
50
64
  assert_equal '', @filters.upcase(nil)
51
65
  end
52
66
 
53
- def test_upcase
54
- assert_equal 'TESTING', @filters.upcase("Testing")
55
- assert_equal '', @filters.upcase(nil)
56
- end
57
-
58
67
  def test_truncate
59
68
  assert_equal '1234...', @filters.truncate('1234567890', 7)
60
69
  assert_equal '1234567890', @filters.truncate('1234567890', 20)
@@ -63,7 +72,7 @@ class StandardFiltersTest < Test::Unit::TestCase
63
72
  assert_equal "测试...", @filters.truncate("测试测试测试测试", 5)
64
73
  end
65
74
 
66
- def test_strip
75
+ def test_split
67
76
  assert_equal ['12','34'], @filters.split('12~34', '~')
68
77
  assert_equal ['A? ',' ,Z'], @filters.split('A? ~ ~ ~ ,Z', '~ ~ ~')
69
78
  assert_equal ['A?Z'], @filters.split('A?Z', '~')
@@ -77,7 +86,7 @@ class StandardFiltersTest < Test::Unit::TestCase
77
86
  end
78
87
 
79
88
  def test_escape_once
80
- assert_equal '&lt;strong&gt;', @filters.escape_once(@filters.escape('<strong>'))
89
+ assert_equal '&lt;strong&gt;Hulk&lt;/strong&gt;', @filters.escape_once('&lt;strong&gt;Hulk</strong>')
81
90
  end
82
91
 
83
92
  def test_truncatewords
@@ -108,10 +117,25 @@ class StandardFiltersTest < Test::Unit::TestCase
108
117
  assert_equal [{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], @filters.sort([{"a" => 4}, {"a" => 3}, {"a" => 1}, {"a" => 2}], "a")
109
118
  end
110
119
 
120
+ def test_legacy_sort_hash
121
+ assert_equal [{a:1, b:2}], @filters.sort({a:1, b:2})
122
+ end
123
+
124
+ def test_numerical_vs_lexicographical_sort
125
+ assert_equal [2, 10], @filters.sort([10, 2])
126
+ assert_equal [{"a" => 2}, {"a" => 10}], @filters.sort([{"a" => 10}, {"a" => 2}], "a")
127
+ assert_equal ["10", "2"], @filters.sort(["10", "2"])
128
+ assert_equal [{"a" => "10"}, {"a" => "2"}], @filters.sort([{"a" => "10"}, {"a" => "2"}], "a")
129
+ end
130
+
111
131
  def test_reverse
112
132
  assert_equal [4,3,2,1], @filters.reverse([1,2,3,4])
113
133
  end
114
134
 
135
+ def test_legacy_reverse_hash
136
+ assert_equal [{a:1, b:2}], @filters.reverse(a:1, b:2)
137
+ end
138
+
115
139
  def test_map
116
140
  assert_equal [1,2,3,4], @filters.map([{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], 'a')
117
141
  assert_template_result 'abc', "{{ ary | map:'foo' | map:'bar' }}",
@@ -119,20 +143,45 @@ class StandardFiltersTest < Test::Unit::TestCase
119
143
  end
120
144
 
121
145
  def test_map_doesnt_call_arbitrary_stuff
122
- assert_equal "", Liquid::Template.parse('{{ "foo" | map: "__id__" }}').render
123
- assert_equal "", Liquid::Template.parse('{{ "foo" | map: "inspect" }}').render
146
+ assert_template_result "", '{{ "foo" | map: "__id__" }}'
147
+ assert_template_result "", '{{ "foo" | map: "inspect" }}'
124
148
  end
125
149
 
126
150
  def test_map_calls_to_liquid
127
151
  t = TestThing.new
128
- assert_equal "woot: 1", Liquid::Template.parse('{{ foo }}').render("foo" => t)
152
+ assert_template_result "woot: 1", '{{ foo | map: "whatever" }}', "foo" => [t]
153
+ end
154
+
155
+ def test_map_on_hashes
156
+ assert_template_result "4217", '{{ thing | map: "foo" | map: "bar" }}',
157
+ "thing" => { "foo" => [ { "bar" => 42 }, { "bar" => 17 } ] }
158
+ end
159
+
160
+ def test_legacy_map_on_hashes_with_dynamic_key
161
+ template = "{% assign key = 'foo' %}{{ thing | map: key | map: 'bar' }}"
162
+ hash = { "foo" => { "bar" => 42 } }
163
+ assert_template_result "42", template, "thing" => hash
164
+ end
165
+
166
+ def test_sort_calls_to_liquid
167
+ t = TestThing.new
168
+ Liquid::Template.parse('{{ foo | sort: "whatever" }}').render("foo" => [t])
169
+ assert t.foo > 0
129
170
  end
130
171
 
131
172
  def test_map_over_proc
132
173
  drop = TestDrop.new
133
174
  p = Proc.new{ drop }
134
175
  templ = '{{ procs | map: "test" }}'
135
- assert_equal "testfoo", Liquid::Template.parse(templ).render("procs" => [p])
176
+ assert_template_result "testfoo", templ, "procs" => [p]
177
+ end
178
+
179
+ def test_map_works_on_enumerables
180
+ assert_template_result "123", '{{ foo | map: "foo" }}', "foo" => TestEnumerable.new
181
+ end
182
+
183
+ def test_sort_works_on_enumerables
184
+ assert_template_result "213", '{{ foo | sort: "bar" | map: "foo" }}', "foo" => TestEnumerable.new
136
185
  end
137
186
 
138
187
  def test_date
@@ -157,13 +206,10 @@ class StandardFiltersTest < Test::Unit::TestCase
157
206
 
158
207
  assert_equal nil, @filters.date(nil, "%B")
159
208
 
160
- with_timezone("UTC") do
161
- assert_equal "07/05/2006", @filters.date(1152098955, "%m/%d/%Y")
162
- assert_equal "07/05/2006", @filters.date("1152098955", "%m/%d/%Y")
163
- end
209
+ assert_equal "07/05/2006", @filters.date(1152098955, "%m/%d/%Y")
210
+ assert_equal "07/05/2006", @filters.date("1152098955", "%m/%d/%Y")
164
211
  end
165
212
 
166
-
167
213
  def test_first_last
168
214
  assert_equal 1, @filters.first([1,2,3])
169
215
  assert_equal 3, @filters.last([1,2,3])
@@ -187,6 +233,21 @@ class StandardFiltersTest < Test::Unit::TestCase
187
233
  assert_template_result 'foobar', "{{ 'foo|bar' | remove: '|' }}"
188
234
  end
189
235
 
236
+ def test_strip
237
+ assert_template_result 'ab c', "{{ source | strip }}", 'source' => " ab c "
238
+ assert_template_result 'ab c', "{{ source | strip }}", 'source' => " \tab c \n \t"
239
+ end
240
+
241
+ def test_lstrip
242
+ assert_template_result 'ab c ', "{{ source | lstrip }}", 'source' => " ab c "
243
+ assert_template_result "ab c \n \t", "{{ source | lstrip }}", 'source' => " \tab c \n \t"
244
+ end
245
+
246
+ def test_rstrip
247
+ assert_template_result " ab c", "{{ source | rstrip }}", 'source' => " ab c "
248
+ assert_template_result " \tab c", "{{ source | rstrip }}", 'source' => " \tab c \n \t"
249
+ end
250
+
190
251
  def test_strip_newlines
191
252
  assert_template_result 'abc', "{{ source | strip_newlines }}", 'source' => "a\nb\nc"
192
253
  assert_template_result 'abc', "{{ source | strip_newlines }}", 'source' => "a\r\nb\nc"
@@ -210,9 +271,6 @@ class StandardFiltersTest < Test::Unit::TestCase
210
271
  assert_template_result "12", "{{ 3 | times:4 }}"
211
272
  assert_template_result "0", "{{ 'foo' | times:4 }}"
212
273
 
213
- # Ruby v1.9.2-rc1, or higher, backwards compatible Float test
214
- assert_match(/(6\.3)|(6\.(0{13})1)/, Template.parse("{{ '2.1' | times:3 }}").render)
215
-
216
274
  assert_template_result "6", "{{ '2.1' | times:3 | replace: '.','-' | plus:0}}"
217
275
 
218
276
  assert_template_result "7.25", "{{ 0.0725 | times:100 }}"
@@ -222,11 +280,8 @@ class StandardFiltersTest < Test::Unit::TestCase
222
280
  assert_template_result "4", "{{ 12 | divided_by:3 }}"
223
281
  assert_template_result "4", "{{ 14 | divided_by:3 }}"
224
282
 
225
- # Ruby v1.9.2-rc1, or higher, backwards compatible Float test
226
- assert_match(/4\.(6{13,14})7/, Template.parse("{{ 14 | divided_by:'3.0' }}").render)
227
-
228
283
  assert_template_result "5", "{{ 15 | divided_by:3 }}"
229
- assert_template_result "Liquid error: divided by 0", "{{ 5 | divided_by:0 }}"
284
+ assert_equal "Liquid error: divided by 0", Template.parse("{{ 5 | divided_by:0 }}").render
230
285
 
231
286
  assert_template_result "0.5", "{{ 2.0 | divided_by:4 }}"
232
287
  end
@@ -235,6 +290,22 @@ class StandardFiltersTest < Test::Unit::TestCase
235
290
  assert_template_result "1", "{{ 3 | modulo:2 }}"
236
291
  end
237
292
 
293
+ def test_round
294
+ assert_template_result "5", "{{ input | round }}", 'input' => 4.6
295
+ assert_template_result "4", "{{ '4.3' | round }}"
296
+ assert_template_result "4.56", "{{ input | round: 2 }}", 'input' => 4.5612
297
+ end
298
+
299
+ def test_ceil
300
+ assert_template_result "5", "{{ input | ceil }}", 'input' => 4.6
301
+ assert_template_result "5", "{{ '4.3' | ceil }}"
302
+ end
303
+
304
+ def test_floor
305
+ assert_template_result "4", "{{ input | floor }}", 'input' => 4.6
306
+ assert_template_result "4", "{{ '4.3' | floor }}"
307
+ end
308
+
238
309
  def test_append
239
310
  assigns = {'a' => 'bc', 'b' => 'd' }
240
311
  assert_template_result('bcd',"{{ a | append: 'd'}}",assigns)
@@ -247,17 +318,16 @@ class StandardFiltersTest < Test::Unit::TestCase
247
318
  assert_template_result('abc',"{{ a | prepend: b}}",assigns)
248
319
  end
249
320
 
250
- def test_cannot_access_private_methods
251
- assert_template_result('a',"{{ 'a' | to_number }}")
321
+ def test_default
322
+ assert_equal "foo", @filters.default("foo", "bar")
323
+ assert_equal "bar", @filters.default(nil, "bar")
324
+ assert_equal "bar", @filters.default("", "bar")
325
+ assert_equal "bar", @filters.default(false, "bar")
326
+ assert_equal "bar", @filters.default([], "bar")
327
+ assert_equal "bar", @filters.default({}, "bar")
252
328
  end
253
329
 
254
- private
255
-
256
- def with_timezone(tz)
257
- old_tz = ENV['TZ']
258
- ENV['TZ'] = tz
259
- yield
260
- ensure
261
- ENV['TZ'] = old_tz
330
+ def test_cannot_access_private_methods
331
+ assert_template_result('a',"{{ 'a' | to_number }}")
262
332
  end
263
333
  end # StandardFiltersTest