liquid 2.6.3 → 3.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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