haml 1.8.0 → 2.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 (89) hide show
  1. data/FAQ +138 -0
  2. data/MIT-LICENSE +1 -1
  3. data/{README → README.rdoc} +66 -3
  4. data/REVISION +1 -0
  5. data/Rakefile +115 -147
  6. data/VERSION +1 -1
  7. data/bin/css2sass +0 -0
  8. data/bin/haml +2 -1
  9. data/bin/html2haml +0 -0
  10. data/bin/sass +0 -0
  11. data/init.rb +6 -1
  12. data/lib/haml/buffer.rb +122 -64
  13. data/lib/haml/engine.rb +77 -46
  14. data/lib/haml/error.rb +15 -6
  15. data/lib/haml/exec.rb +61 -10
  16. data/lib/haml/filters.rb +229 -74
  17. data/lib/haml/helpers/action_view_extensions.rb +1 -1
  18. data/lib/haml/helpers/action_view_mods.rb +109 -24
  19. data/lib/haml/helpers.rb +137 -76
  20. data/lib/haml/html.rb +8 -8
  21. data/lib/haml/precompiler.rb +280 -153
  22. data/lib/haml/template/patch.rb +10 -3
  23. data/lib/haml/template/plugin.rb +61 -10
  24. data/lib/haml/template.rb +14 -9
  25. data/lib/haml.rb +483 -214
  26. data/lib/sass/constant/color.rb +13 -13
  27. data/lib/sass/constant/literal.rb +8 -7
  28. data/lib/sass/constant/nil.rb +9 -0
  29. data/lib/sass/constant/number.rb +10 -10
  30. data/lib/sass/constant/operation.rb +4 -4
  31. data/lib/sass/constant/string.rb +3 -3
  32. data/lib/sass/constant.rb +46 -77
  33. data/lib/sass/css.rb +130 -56
  34. data/lib/sass/engine.rb +131 -43
  35. data/lib/sass/plugin/merb.rb +48 -12
  36. data/lib/sass/plugin/rails.rb +10 -4
  37. data/lib/sass/plugin.rb +33 -10
  38. data/lib/sass/tree/attr_node.rb +5 -5
  39. data/lib/sass/tree/directive_node.rb +2 -7
  40. data/lib/sass/tree/node.rb +1 -12
  41. data/lib/sass/tree/rule_node.rb +39 -31
  42. data/lib/sass/tree/value_node.rb +1 -1
  43. data/lib/sass.rb +194 -19
  44. data/rails/init.rb +1 -0
  45. data/test/benchmark.rb +67 -80
  46. data/test/haml/engine_test.rb +368 -152
  47. data/test/haml/helper_test.rb +68 -16
  48. data/test/haml/html2haml_test.rb +3 -4
  49. data/test/haml/results/content_for_layout.xhtml +1 -2
  50. data/test/haml/results/eval_suppressed.xhtml +2 -4
  51. data/test/haml/results/filters.xhtml +38 -30
  52. data/test/haml/results/helpers.xhtml +4 -8
  53. data/test/haml/results/just_stuff.xhtml +8 -7
  54. data/test/haml/results/nuke_inner_whitespace.xhtml +40 -0
  55. data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
  56. data/test/haml/results/original_engine.xhtml +3 -7
  57. data/test/haml/results/partials.xhtml +1 -0
  58. data/test/haml/results/tag_parsing.xhtml +1 -6
  59. data/test/haml/results/very_basic.xhtml +2 -4
  60. data/test/haml/results/whitespace_handling.xhtml +13 -21
  61. data/test/haml/template_test.rb +42 -57
  62. data/test/haml/templates/_partial.haml +1 -0
  63. data/test/haml/templates/filters.haml +39 -21
  64. data/test/haml/templates/helpers.haml +10 -10
  65. data/test/haml/templates/just_stuff.haml +8 -3
  66. data/test/haml/templates/nuke_inner_whitespace.haml +32 -0
  67. data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
  68. data/test/haml/templates/partials.haml +1 -1
  69. data/test/haml/templates/tag_parsing.haml +0 -3
  70. data/test/haml/templates/whitespace_handling.haml +10 -10
  71. data/test/sass/engine_test.rb +97 -39
  72. data/test/sass/plugin_test.rb +4 -7
  73. data/test/sass/results/constants.css +2 -0
  74. data/test/sass/results/import.css +2 -2
  75. data/test/sass/results/mixins.css +95 -0
  76. data/test/sass/results/multiline.css +24 -0
  77. data/test/sass/templates/constants.sass +3 -0
  78. data/test/sass/templates/import.sass +4 -1
  79. data/test/sass/templates/importee.sass +4 -0
  80. data/test/sass/templates/mixins.sass +76 -0
  81. data/test/sass/templates/multiline.sass +20 -0
  82. data/test/test_helper.rb +18 -0
  83. metadata +70 -53
  84. data/lib/haml/helpers/action_view_mods.rb.rej +0 -30
  85. data/lib/haml/util.rb +0 -18
  86. data/lib/sass/constant.rb.rej +0 -42
  87. data/test/haml/runner.rb +0 -16
  88. data/test/profile.rb +0 -65
  89. data/test/sass/engine_test.rb.rej +0 -18
@@ -1,28 +1,82 @@
1
1
  #!/usr/bin/env ruby
2
-
3
- require 'rubygems'
4
- require 'active_support'
5
- require 'action_controller'
6
- require 'action_view'
7
-
8
- require 'test/unit'
9
- require File.dirname(__FILE__) + '/../../lib/haml'
10
- require 'haml/engine'
2
+ require File.dirname(__FILE__) + '/../test_helper'
11
3
 
12
4
  class EngineTest < Test::Unit::TestCase
5
+ # A map of erroneous Haml documents to the error messages they should produce.
6
+ # The error messages may be arrays;
7
+ # if so, the second element should be the line number that should be reported for the error.
8
+ # If this isn't provided, the tests will assume the line number should be the last line of the document.
9
+ EXCEPTION_MAP = {
10
+ "!!!\n a" => "Illegal nesting: nesting within a header command is illegal.",
11
+ "a\n b" => "Illegal nesting: nesting within plain text is illegal.",
12
+ "/ a\n b" => "Illegal nesting: nesting within a tag that already has content is illegal.",
13
+ "% a" => 'Invalid tag: "% a".',
14
+ "%p a\n b" => "Illegal nesting: content can't be both given on the same line as %p and nested within it.",
15
+ "%p=" => "There's no Ruby code for = to evaluate.",
16
+ "%p~" => "There's no Ruby code for ~ to evaluate.",
17
+ "~" => "There's no Ruby code for ~ to evaluate.",
18
+ "=" => "There's no Ruby code for = to evaluate.",
19
+ "%p/\n a" => "Illegal nesting: nesting within a self-closing tag is illegal.",
20
+ "%p\n\ta" => <<END.strip,
21
+ A tab character was used for indentation. Haml must be indented using two spaces.
22
+ Are you sure you have soft tabs enabled in your editor?
23
+ END
24
+ "%p\n a" => "1 space was used for indentation. Haml must be indented using two spaces.",
25
+ "%p\n a" => "3 spaces were used for indentation. Haml must be indented using two spaces.",
26
+ "%p\n a" => "4 spaces were used for indentation. Haml must be indented using two spaces.",
27
+ ":a\n b" => ['Filter "a" is not defined.', 1],
28
+ ":a= b" => 'Invalid filter name ":a= b".',
29
+ "." => "Illegal element: classes and ids must have values.",
30
+ ".#" => "Illegal element: classes and ids must have values.",
31
+ ".{} a" => "Illegal element: classes and ids must have values.",
32
+ ".= a" => "Illegal element: classes and ids must have values.",
33
+ "%p..a" => "Illegal element: classes and ids must have values.",
34
+ "%a/ b" => "Self-closing tags can't have content.",
35
+ " %p foo" => "Indenting at the beginning of the document is illegal.",
36
+ " %p foo" => "Indenting at the beginning of the document is illegal.",
37
+ "- end" => "You don't need to use \"- end\" in Haml. Use indentation instead:\n- if foo?\n %strong Foo!\n- else\n Not foo.",
38
+ " \n\t\n %p foo" => ["Indenting at the beginning of the document is illegal.", 3],
39
+
40
+ # Regression tests
41
+ "- raise 'foo'\n\n\n\nbar" => ["foo", 1],
42
+ "= 'foo'\n-raise 'foo'" => ["foo", 2],
43
+ "\n\n\n- raise 'foo'" => ["foo", 4],
44
+ "foo\n\n\n bar" => ["Illegal nesting: nesting within plain text is illegal.", 4],
45
+ "%p/\n\n bar" => ["Illegal nesting: nesting within a self-closing tag is illegal.", 3],
46
+ "%p foo\n\n bar" => ["Illegal nesting: content can't be both given on the same line as %p and nested within it.", 3],
47
+ "/ foo\n\n bar" => ["Illegal nesting: nesting within a tag that already has content is illegal.", 3],
48
+ "!!!\n\n bar" => ["Illegal nesting: nesting within a header command is illegal.", 3],
49
+ "foo\n\n\n\tbar" => [<<END.strip, 4],
50
+ A tab character was used for indentation. Haml must be indented using two spaces.
51
+ Are you sure you have soft tabs enabled in your editor?
52
+ END
53
+ "foo\n:ruby\n 1\n 2\n 3\n- raise 'foo'" => ["foo", 6],
54
+ }
55
+
56
+ User = Struct.new('User', :id)
13
57
 
14
58
  def render(text, options = {}, &block)
15
59
  scope = options.delete(:scope) || Object.new
16
60
  locals = options.delete(:locals) || {}
17
- Haml::Engine.new(text, options).to_html(scope, locals, &block)
61
+ engine(text, options).to_html(scope, locals, &block)
18
62
  end
19
-
63
+
64
+ def engine(text, options = {})
65
+ unless options[:filename]
66
+ # use caller method name as fake filename. useful for debugging
67
+ i = -1
68
+ caller[i+=1] =~ /`(.+?)'/ until $1 and $1.index('test_') == 0
69
+ options[:filename] = "(#{$1})"
70
+ end
71
+ Haml::Engine.new(text, options)
72
+ end
73
+
20
74
  def test_empty_render_should_remain_empty
21
75
  assert_equal('', render(''))
22
76
  end
23
77
 
24
78
  def test_attributes_should_render_correctly
25
- assert_equal("<div class='atlantis' style='ugly'>\n</div>", render(".atlantis{:style => 'ugly'}").chomp)
79
+ assert_equal("<div class='atlantis' style='ugly'></div>", render(".atlantis{:style => 'ugly'}").chomp)
26
80
  end
27
81
 
28
82
  def test_ruby_code_should_work_inside_attributes
@@ -31,7 +85,7 @@ class EngineTest < Test::Unit::TestCase
31
85
  end
32
86
 
33
87
  def test_nil_should_render_empty_tag
34
- assert_equal("<div class='no_attributes'>\n</div>",
88
+ assert_equal("<div class='no_attributes'></div>",
35
89
  render(".no_attributes{:nil => nil}").chomp)
36
90
  end
37
91
 
@@ -44,24 +98,12 @@ class EngineTest < Test::Unit::TestCase
44
98
  assert_equal("<p>Hello</p>", render('%p Hello').chomp)
45
99
  end
46
100
 
47
- def test_long_liner_should_not_print_on_one_line
48
- assert_equal("<div>\n #{'x' * 51}\n</div>", render("%div #{'x' * 51}").chomp)
49
- end
50
-
51
- def test_non_prerendered_one_liner
52
- assert_equal("<p class='awesome'>One line</p>\n", render("%p{:class => c} One line", :locals => {:c => 'awesome'}))
53
- end
54
-
55
- def test_non_prerendered_script_one_liner
56
- assert_equal("<p class='awesome'>One line</p>\n", render("%p{:class => c}= 'One line'", :locals => {:c => 'awesome'}))
57
- end
58
-
59
- def test_non_prerendered_long_script_one_liner
60
- assert_equal("<p class='awesome'>\n #{'x' * 60}\n</p>\n", render("%p{:class => c}= 'x' * 60", :locals => {:c => 'awesome'}))
101
+ def test_one_liner_with_newline_shouldnt_be_one_line
102
+ assert_equal("<p>\n foo\n bar\n</p>", render('%p= "foo\nbar"').chomp)
61
103
  end
62
104
 
63
105
  def test_multi_render
64
- engine = Haml::Engine.new("%strong Hi there!")
106
+ engine = engine("%strong Hi there!")
65
107
  assert_equal("<strong>Hi there!</strong>\n", engine.to_html)
66
108
  assert_equal("<strong>Hi there!</strong>\n", engine.to_html)
67
109
  assert_equal("<strong>Hi there!</strong>\n", engine.to_html)
@@ -93,36 +135,198 @@ class EngineTest < Test::Unit::TestCase
93
135
  assert_equal("<img alt='' src='/foo.png' />\n", render("%img{:width => nil, :src => '/foo.png', :alt => String.new}"))
94
136
  end
95
137
 
138
+ def test_end_of_file_multiline
139
+ assert_equal("<p>0</p>\n<p>1</p>\n<p>2</p>\n", render("- for i in (0...3)\n %p= |\n i |"))
140
+ end
141
+
142
+ def test_cr_newline
143
+ assert_equal("<p>foo</p>\n<p>bar</p>\n<p>baz</p>\n<p>boom</p>\n", render("%p foo\r%p bar\r\n%p baz\n\r%p boom"))
144
+ end
145
+
146
+ def test_textareas
147
+ assert_equal("<textarea>Foo&#x000A; bar&#x000A; baz</textarea>\n",
148
+ render('%textarea= "Foo\n bar\n baz"'))
149
+
150
+ assert_equal("<pre>Foo&#x000A; bar&#x000A; baz</pre>\n",
151
+ render('%pre= "Foo\n bar\n baz"'))
152
+
153
+ assert_equal("<textarea>#{'a' * 100}</textarea>\n",
154
+ render("%textarea #{'a' * 100}"))
155
+
156
+ assert_equal("<p>\n <textarea>Foo\n Bar\n Baz</textarea>\n</p>\n", render(<<SOURCE))
157
+ %p
158
+ %textarea
159
+ Foo
160
+ Bar
161
+ Baz
162
+ SOURCE
163
+ end
164
+
165
+ def test_boolean_attributes
166
+ assert_equal("<p bar baz='true' foo='bar'></p>\n",
167
+ render("%p{:foo => 'bar', :bar => true, :baz => 'true'}", :format => :html4))
168
+ assert_equal("<p bar='bar' baz='true' foo='bar'></p>\n",
169
+ render("%p{:foo => 'bar', :bar => true, :baz => 'true'}", :format => :xhtml))
170
+
171
+ assert_equal("<p baz='false' foo='bar'></p>\n",
172
+ render("%p{:foo => 'bar', :bar => false, :baz => 'false'}", :format => :html4))
173
+ assert_equal("<p baz='false' foo='bar'></p>\n",
174
+ render("%p{:foo => 'bar', :bar => false, :baz => 'false'}", :format => :xhtml))
175
+ end
176
+
177
+ def test_both_whitespace_nukes_work_together
178
+ assert_equal(<<RESULT, render(<<SOURCE))
179
+ <p><q>Foo
180
+ Bar</q></p>
181
+ RESULT
182
+ %p
183
+ %q><= "Foo\\nBar"
184
+ SOURCE
185
+ end
186
+
187
+ # HTML escaping tests
188
+
189
+ def test_ampersand_equals_should_escape
190
+ assert_equal("<p>\n foo &amp; bar\n</p>\n", render("%p\n &= 'foo & bar'", :escape_html => false))
191
+ end
192
+
193
+ def test_ampersand_equals_inline_should_escape
194
+ assert_equal("<p>foo &amp; bar</p>\n", render("%p&= 'foo & bar'", :escape_html => false))
195
+ end
196
+
197
+ def test_ampersand_equals_should_escape_before_preserve
198
+ assert_equal("<textarea>foo&#x000A;bar</textarea>\n", render('%textarea&= "foo\nbar"', :escape_html => false))
199
+ end
200
+
201
+ def test_bang_equals_should_not_escape
202
+ assert_equal("<p>\n foo & bar\n</p>\n", render("%p\n != 'foo & bar'", :escape_html => true))
203
+ end
204
+
205
+ def test_bang_equals_inline_should_not_escape
206
+ assert_equal("<p>foo & bar</p>\n", render("%p!= 'foo & bar'", :escape_html => true))
207
+ end
208
+
209
+ def test_static_attributes_should_be_escaped
210
+ assert_equal("<img class='atlantis' style='ugly&amp;stupid' />\n",
211
+ render("%img.atlantis{:style => 'ugly&stupid'}"))
212
+ assert_equal("<div class='atlantis' style='ugly&amp;stupid'>foo</div>\n",
213
+ render(".atlantis{:style => 'ugly&stupid'} foo"))
214
+ assert_equal("<p class='atlantis' style='ugly&amp;stupid'>foo</p>\n",
215
+ render("%p.atlantis{:style => 'ugly&stupid'}= 'foo'"))
216
+ assert_equal("<p class='atlantis' style='ugly&#x000A;stupid'></p>\n",
217
+ render("%p.atlantis{:style => \"ugly\\nstupid\"}"))
218
+ end
219
+
220
+ def test_dynamic_attributes_should_be_escaped
221
+ assert_equal("<img alt='' src='&amp;foo.png' />\n",
222
+ render("%img{:width => nil, :src => '&foo.png', :alt => String.new}"))
223
+ assert_equal("<p alt='' src='&amp;foo.png'>foo</p>\n",
224
+ render("%p{:width => nil, :src => '&foo.png', :alt => String.new} foo"))
225
+ assert_equal("<div alt='' src='&amp;foo.png'>foo</div>\n",
226
+ render("%div{:width => nil, :src => '&foo.png', :alt => String.new}= 'foo'"))
227
+ assert_equal("<img alt='' src='foo&#x000A;.png' />\n",
228
+ render("%img{:width => nil, :src => \"foo\\n.png\", :alt => String.new}"))
229
+ end
230
+
231
+ def test_string_interpolation_should_be_esaped
232
+ assert_equal("<p>4&amp;3</p>\n", render("%p== #{2+2}&#{2+1}", :escape_html => true))
233
+ assert_equal("<p>4&3</p>\n", render("%p== #{2+2}&#{2+1}", :escape_html => false))
234
+ end
235
+
236
+ def test_escaped_inline_string_interpolation
237
+ assert_equal("<p>4&amp;3</p>\n", render("%p&== #{2+2}&#{2+1}", :escape_html => true))
238
+ assert_equal("<p>4&amp;3</p>\n", render("%p&== #{2+2}&#{2+1}", :escape_html => false))
239
+ end
240
+
241
+ def test_unescaped_inline_string_interpolation
242
+ assert_equal("<p>4&3</p>\n", render("%p!== #{2+2}&#{2+1}", :escape_html => true))
243
+ assert_equal("<p>4&3</p>\n", render("%p!== #{2+2}&#{2+1}", :escape_html => false))
244
+ end
245
+
246
+ def test_escaped_string_interpolation
247
+ assert_equal("<p>\n 4&amp;3\n</p>\n", render("%p\n &== #{2+2}&#{2+1}", :escape_html => true))
248
+ assert_equal("<p>\n 4&amp;3\n</p>\n", render("%p\n &== #{2+2}&#{2+1}", :escape_html => false))
249
+ end
250
+
251
+ def test_unescaped_string_interpolation
252
+ assert_equal("<p>\n 4&3\n</p>\n", render("%p\n !== #{2+2}&#{2+1}", :escape_html => true))
253
+ assert_equal("<p>\n 4&3\n</p>\n", render("%p\n !== #{2+2}&#{2+1}", :escape_html => false))
254
+ end
255
+
256
+ def test_scripts_should_respect_escape_html_option
257
+ assert_equal("<p>\n foo &amp; bar\n</p>\n", render("%p\n = 'foo & bar'", :escape_html => true))
258
+ assert_equal("<p>\n foo & bar\n</p>\n", render("%p\n = 'foo & bar'", :escape_html => false))
259
+ end
260
+
261
+ def test_inline_scripts_should_respect_escape_html_option
262
+ assert_equal("<p>foo &amp; bar</p>\n", render("%p= 'foo & bar'", :escape_html => true))
263
+ assert_equal("<p>foo & bar</p>\n", render("%p= 'foo & bar'", :escape_html => false))
264
+ end
265
+
266
+ def test_script_ending_in_comment_should_render_when_html_is_escaped
267
+ assert_equal("foo&amp;bar\n", render("= 'foo&bar' #comment", :escape_html => true))
268
+ end
269
+
96
270
  # Options tests
97
271
 
272
+ def test_filename_and_line
273
+ begin
274
+ render("\n\n = abc", :filename => 'test', :line => 2)
275
+ rescue Exception => e
276
+ assert_kind_of Haml::SyntaxError, e
277
+ assert_match(/test:4/, e.backtrace.first)
278
+ end
279
+
280
+ begin
281
+ render("\n\n= 123\n\n= nil[]", :filename => 'test', :line => 2)
282
+ rescue Exception => e
283
+ assert_kind_of NoMethodError, e
284
+ assert_match(/test:6/, e.backtrace.first)
285
+ end
286
+ end
287
+
98
288
  def test_stop_eval
99
289
  assert_equal("", render("= 'Hello'", :suppress_eval => true))
100
290
  assert_equal("", render("- puts 'foo'", :suppress_eval => true))
101
291
  assert_equal("<div id='foo' yes='no' />\n", render("#foo{:yes => 'no'}/", :suppress_eval => true))
102
292
  assert_equal("<div id='foo' />\n", render("#foo{:yes => 'no', :call => a_function() }/", :suppress_eval => true))
103
293
  assert_equal("<div />\n", render("%div[1]/", :suppress_eval => true))
104
-
105
- begin
106
- assert_equal("", render(":ruby\n puts 'hello'", :suppress_eval => true))
107
- rescue Haml::HamlError => err
108
- caught = true
109
- assert_equal('"ruby" filter is not defined!', err.message)
110
- end
111
- assert(caught, "Rendering a ruby filter without evaluating didn't throw an error!")
294
+ assert_equal("", render(":ruby\n puts 'hello'", :suppress_eval => true))
112
295
  end
113
296
 
114
297
  def test_attr_wrapper
115
- assert_equal("<p strange=*attrs*>\n</p>\n", render("%p{ :strange => 'attrs'}", :attr_wrapper => '*'))
116
- assert_equal("<p escaped='quo\"te'>\n</p>\n", render("%p{ :escaped => 'quo\"te'}", :attr_wrapper => '"'))
117
- assert_equal("<p escaped=\"q'uo&quot;te\">\n</p>\n", render("%p{ :escaped => 'q\\'uo\"te'}", :attr_wrapper => '"'))
298
+ assert_equal("<p strange=*attrs*></p>\n", render("%p{ :strange => 'attrs'}", :attr_wrapper => '*'))
299
+ assert_equal("<p escaped='quo\"te'></p>\n", render("%p{ :escaped => 'quo\"te'}", :attr_wrapper => '"'))
300
+ assert_equal("<p escaped=\"quo'te\"></p>\n", render("%p{ :escaped => 'quo\\'te'}", :attr_wrapper => '"'))
301
+ assert_equal("<p escaped=\"q'uo&quot;te\"></p>\n", render("%p{ :escaped => 'q\\'uo\"te'}", :attr_wrapper => '"'))
118
302
  assert_equal("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n", render("!!! XML", :attr_wrapper => '"'))
119
303
  end
120
304
 
121
305
  def test_attrs_parsed_correctly
122
- assert_equal("<p boom=>biddly='bar => baz'>\n</p>\n", render("%p{'boom=>biddly' => 'bar => baz'}"))
123
- assert_equal("<p foo,bar='baz, qux'>\n</p>\n", render("%p{'foo,bar' => 'baz, qux'}"))
124
- assert_equal("<p escaped='quo\nte'>\n</p>\n", render("%p{ :escaped => \"quo\\nte\"}"))
125
- assert_equal("<p escaped='quo4te'>\n</p>\n", render("%p{ :escaped => \"quo\#{2 + 2}te\"}"))
306
+ assert_equal("<p boom=>biddly='bar =&gt; baz'></p>\n", render("%p{'boom=>biddly' => 'bar => baz'}"))
307
+ assert_equal("<p foo,bar='baz, qux'></p>\n", render("%p{'foo,bar' => 'baz, qux'}"))
308
+ assert_equal("<p escaped='quo&#x000A;te'></p>\n", render("%p{ :escaped => \"quo\\nte\"}"))
309
+ assert_equal("<p escaped='quo4te'></p>\n", render("%p{ :escaped => \"quo\#{2 + 2}te\"}"))
310
+ end
311
+
312
+ def test_correct_parsing_with_brackets
313
+ assert_equal("<p class='foo'>{tada} foo</p>\n", render("%p{:class => 'foo'} {tada} foo"))
314
+ assert_equal("<p class='foo'>deep {nested { things }}</p>\n", render("%p{:class => 'foo'} deep {nested { things }}"))
315
+ assert_equal("<p class='bar foo'>{a { d</p>\n", render("%p{{:class => 'foo'}, :class => 'bar'} {a { d"))
316
+ assert_equal("<p foo='bar'>a}</p>\n", render("%p{:foo => 'bar'} a}"))
317
+
318
+ foo = []
319
+ foo[0] = Struct.new('Foo', :id).new
320
+ assert_equal("<p class='struct_foo' id='struct_foo_new'>New User]</p>\n",
321
+ render("%p[foo[0]] New User]", :locals => {:foo => foo}))
322
+ assert_equal("<p class='prefix_struct_foo' id='prefix_struct_foo_new'>New User]</p>\n",
323
+ render("%p[foo[0], :prefix] New User]", :locals => {:foo => foo}))
324
+
325
+ foo[0].id = 1
326
+ assert_equal("<p class='struct_foo' id='struct_foo_1'>New User]</p>\n",
327
+ render("%p[foo[0]] New User]", :locals => {:foo => foo}))
328
+ assert_equal("<p class='prefix_struct_foo' id='prefix_struct_foo_1'>New User]</p>\n",
329
+ render("%p[foo[0], :prefix] New User]", :locals => {:foo => foo}))
126
330
  end
127
331
 
128
332
  def test_empty_attrs
@@ -135,61 +339,54 @@ class EngineTest < Test::Unit::TestCase
135
339
  assert_equal("<p>nil</p>\n", render("%p{ :attr => x } nil", :locals => {:x => nil}))
136
340
  end
137
341
 
138
- def test_locals
139
- assert_equal("<p>Paragraph!</p>\n", render("%p= text", :locals => { :text => "Paragraph!" }))
342
+ def test_nil_id_with_syntactic_id
343
+ assert_equal("<p id='foo'>nil</p>\n", render("%p#foo{:id => nil} nil"))
344
+ assert_equal("<p id='foo_bar'>nil</p>\n", render("%p#foo{{:id => 'bar'}, :id => nil} nil"))
345
+ assert_equal("<p id='foo_bar'>nil</p>\n", render("%p#foo{{:id => nil}, :id => 'bar'} nil"))
140
346
  end
141
347
 
142
- def test_deprecated_locals_option
143
- Kernel.module_eval do
144
- def warn_with_stub(msg); end
145
- alias_method :warn_without_stub, :warn
146
- alias_method :warn, :warn_with_stub
147
- end
148
-
149
- assert_equal("<p>Paragraph!</p>\n", Haml::Engine.new("%p= text", :locals => { :text => "Paragraph!" }).render)
348
+ def test_nil_class_with_syntactic_class
349
+ assert_equal("<p class='foo'>nil</p>\n", render("%p.foo{:class => nil} nil"))
350
+ assert_equal("<p class='bar foo'>nil</p>\n", render("%p.bar.foo{:class => nil} nil"))
351
+ assert_equal("<p class='bar foo'>nil</p>\n", render("%p.foo{{:class => 'bar'}, :class => nil} nil"))
352
+ assert_equal("<p class='bar foo'>nil</p>\n", render("%p.foo{{:class => nil}, :class => 'bar'} nil"))
353
+ end
150
354
 
151
- Kernel.module_eval { alias_method :warn, :warn_without_stub }
355
+ def test_locals
356
+ assert_equal("<p>Paragraph!</p>\n", render("%p= text", :locals => { :text => "Paragraph!" }))
152
357
  end
153
358
 
154
359
  def test_dynamic_attrs_shouldnt_register_as_literal_values
155
- assert_equal("<p a='b2c'>\n</p>\n", render('%p{:a => "b#{1 + 1}c"}'))
156
- assert_equal("<p a='b2c'>\n</p>\n", render("%p{:a => 'b' + (1 + 1).to_s + 'c'}"))
360
+ assert_equal("<p a='b2c'></p>\n", render('%p{:a => "b#{1 + 1}c"}'))
361
+ assert_equal("<p a='b2c'></p>\n", render("%p{:a => 'b' + (1 + 1).to_s + 'c'}"))
157
362
  end
158
363
 
159
- def test_rec_merge
160
- hash1 = {1=>2, 3=>{5=>7, 8=>9}}
161
- hash2 = {4=>5, 3=>{5=>2, 16=>12}}
162
- hash3 = {1=>2, 4=>5, 3=>{5=>2, 8=>9, 16=>12}}
163
-
164
- hash1.rec_merge!(hash2)
165
- assert_equal(hash3, hash1)
364
+ def test_dynamic_attrs_with_self_closed_tag
365
+ assert_equal("<a b='2' />\nc\n", render("%a{'b' => 1 + 1}/\n= 'c'\n"))
166
366
  end
167
367
 
168
- def test_syntax_errors
169
- errs = [ "!!!\n a", "a\n b", "a\n:foo\nb", "/ a\n b",
170
- "% a", "%p a\n b", "a\n%p=\nb", "%p=\n a",
171
- "a\n%p~\nb", "a\n~\nb", "a\n~\n b", "%p~\n b", "%p/\n a",
172
- "%p\n \t%a b", "%a\n b\nc", "%a\n b\nc",
173
- ":notafilter\n This isn't\n a filter!",
174
- ".{} a", "\#{} a", ".= 'foo'", "%a/ b", "%p..class", "%p..#." ]
175
- errs.each do |err|
368
+ def test_exceptions
369
+ EXCEPTION_MAP.each do |key, value|
176
370
  begin
177
- render(err)
178
- rescue Exception => e
179
- assert(e.is_a?(Haml::Error), "#{err.dump} doesn't produce Haml::SyntaxError")
371
+ render(key, :filename => "(exception test for #{key.inspect})")
372
+ rescue Exception => err
373
+ value = [value] unless value.is_a?(Array)
374
+ expected_message, line_no = value
375
+ line_no ||= key.split("\n").length
376
+ line_reported = err.backtrace[0].gsub(/\(.+\):/, '').to_i
377
+
378
+ assert_equal(expected_message, err.message, "Line: #{key}")
379
+ assert_equal(line_no, line_reported, "Line: #{key}")
180
380
  else
181
- assert(false, "#{err.dump} doesn't produce an exception")
381
+ assert(false, "Exception not raised for\n#{key}")
182
382
  end
183
383
  end
184
384
  end
185
385
 
186
- def test_syntax_error
386
+ def test_exception_line
187
387
  render("a\nb\n!!!\n c\nd")
188
388
  rescue Haml::SyntaxError => e
189
- assert_equal(e.message, "Illegal Nesting: Nesting within a header command is illegal.")
190
- assert_equal("(haml):3", e.backtrace[0])
191
- rescue Exception => e
192
- assert(false, '"a\nb\n!!!\n c\nd" doesn\'t produce a Haml::SyntaxError')
389
+ assert_equal("(test_exception_line):4", e.backtrace[0])
193
390
  else
194
391
  assert(false, '"a\nb\n!!!\n c\nd" doesn\'t produce an exception')
195
392
  end
@@ -197,7 +394,7 @@ class EngineTest < Test::Unit::TestCase
197
394
  def test_exception
198
395
  render("%p\n hi\n %a= undefined\n= 12")
199
396
  rescue Exception => e
200
- assert_match("(haml):3", e.backtrace[0])
397
+ assert_match("(test_exception):3", e.backtrace[0])
201
398
  else
202
399
  # Test failed... should have raised an exception
203
400
  assert(false)
@@ -206,7 +403,7 @@ class EngineTest < Test::Unit::TestCase
206
403
  def test_compile_error
207
404
  render("a\nb\n- fee)\nc")
208
405
  rescue Exception => e
209
- assert_match(/^compile error\n\(haml\):3: syntax error/i, e.message)
406
+ assert_match(/^compile error\n\(test_compile_error\):3: syntax error/i, e.message)
210
407
  else
211
408
  assert(false,
212
409
  '"a\nb\n- fee)\nc" doesn\'t produce an exception!')
@@ -218,74 +415,26 @@ class EngineTest < Test::Unit::TestCase
218
415
  assert_equal("Unbalanced brackets.", e.message)
219
416
  end
220
417
 
221
- def test_no_bluecloth
222
- Kernel.module_eval do
223
- def gem_original_require_with_bluecloth(file)
224
- raise LoadError if file == 'bluecloth'
225
- gem_original_require_without_bluecloth(file)
226
- end
227
- alias_method :gem_original_require_without_bluecloth, :gem_original_require
228
- alias_method :gem_original_require, :gem_original_require_with_bluecloth
229
- end
230
-
231
- begin
232
- assert_equal("<h1>Foo</h1>\t<p>- a\n- b</p>\n",
233
- Haml::Engine.new(":markdown\n Foo\n ===\n - a\n - b").to_html)
234
- rescue Haml::HamlError => e
235
- if e.message == "Can't run Markdown filter; required 'bluecloth' or 'redcloth', but none were found"
236
- puts "\nCouldn't require 'bluecloth' or 'redcloth'; skipping a test."
237
- else
238
- raise e
239
- end
240
- end
241
-
242
- Kernel.module_eval do
243
- alias_method :gem_original_require, :gem_original_require_without_bluecloth
244
- end
418
+ def test_balanced_conditional_comments
419
+ assert_equal("<!--[if !(IE 6)|(IE 7)]> Bracket: ] <![endif]-->\n",
420
+ render("/[if !(IE 6)|(IE 7)] Bracket: ]"))
245
421
  end
246
422
 
247
- def test_no_redcloth
248
- Kernel.module_eval do
249
- def gem_original_require_with_redcloth(file)
250
- raise LoadError if file == 'redcloth'
251
- gem_original_require_without_redcloth(file)
252
- end
253
- alias_method :gem_original_require_without_redcloth, :gem_original_require
254
- alias_method :gem_original_require, :gem_original_require_with_redcloth
255
- end
256
-
257
- begin
258
- Haml::Engine.new(":redcloth\n _foo_").to_html
259
- rescue Haml::HamlError
260
- else
261
- assert(false, "No exception raised!")
262
- end
263
-
264
- Kernel.module_eval do
265
- alias_method :gem_original_require, :gem_original_require_without_redcloth
266
- end
423
+ def test_empty_filter
424
+ assert_equal(<<END, render(':javascript'))
425
+ <script type='text/javascript'>
426
+ //<![CDATA[
427
+
428
+ //]]>
429
+ </script>
430
+ END
267
431
  end
268
432
 
269
- def test_no_redcloth_or_bluecloth
270
- Kernel.module_eval do
271
- def gem_original_require_with_redcloth_and_bluecloth(file)
272
- raise LoadError if file == 'redcloth' || file == 'bluecloth'
273
- gem_original_require_without_redcloth_and_bluecloth(file)
274
- end
275
- alias_method :gem_original_require_without_redcloth_and_bluecloth, :gem_original_require
276
- alias_method :gem_original_require, :gem_original_require_with_redcloth_and_bluecloth
277
- end
278
-
279
- begin
280
- Haml::Engine.new(":markdown\n _foo_").to_html
281
- rescue Haml::HamlError
282
- else
283
- assert(false, "No exception raised!")
284
- end
285
-
286
- Kernel.module_eval do
287
- alias_method :gem_original_require, :gem_original_require_without_redcloth_and_bluecloth
288
- end
433
+ def test_ugly_filter
434
+ assert_equal(<<END, render(":sass\n #foo\n bar: baz", :ugly => true))
435
+ #foo {
436
+ bar: baz; }
437
+ END
289
438
  end
290
439
 
291
440
  def test_local_assigns_dont_modify_class
@@ -294,11 +443,17 @@ class EngineTest < Test::Unit::TestCase
294
443
  end
295
444
 
296
445
  def test_object_ref_with_nil_id
297
- user = Struct.new('User', :id).new
446
+ user = User.new
298
447
  assert_equal("<p class='struct_user' id='struct_user_new'>New User</p>\n",
299
448
  render("%p[user] New User", :locals => {:user => user}))
300
449
  end
301
450
 
451
+ def test_object_ref_before_attrs
452
+ user = User.new 42
453
+ assert_equal("<p class='struct_user' id='struct_user_42' style='width: 100px;'>New User</p>\n",
454
+ render("%p[user]{:style => 'width: 100px;'} New User", :locals => {:user => user}))
455
+ end
456
+
302
457
  def test_non_literal_attributes
303
458
  assert_equal("<p a1='foo' a2='bar' a3='baz' />\n",
304
459
  render("%p{a2, a1, :a3 => 'baz'}/",
@@ -323,27 +478,88 @@ class EngineTest < Test::Unit::TestCase
323
478
 
324
479
  def test_yield_should_work_with_def_method
325
480
  s = "foo"
326
- Haml::Engine.new("= yield\n= upcase").def_method(s, :render)
481
+ engine("= yield\n= upcase").def_method(s, :render)
327
482
  assert_equal("12\nFOO\n", s.render { 12 })
328
483
  end
329
484
 
330
485
  def test_def_method_with_module
331
- Haml::Engine.new("= yield\n= upcase").def_method(String, :render_haml)
486
+ engine("= yield\n= upcase").def_method(String, :render_haml)
332
487
  assert_equal("12\nFOO\n", "foo".render_haml { 12 })
333
488
  end
334
489
 
335
490
  def test_def_method_locals
336
491
  obj = Object.new
337
- Haml::Engine.new("%p= foo\n.bar{:baz => baz}= boom").def_method(obj, :render, :foo, :baz, :boom)
492
+ engine("%p= foo\n.bar{:baz => baz}= boom").def_method(obj, :render, :foo, :baz, :boom)
338
493
  assert_equal("<p>1</p>\n<div baz='2' class='bar'>3</div>\n", obj.render(:foo => 1, :baz => 2, :boom => 3))
339
494
  end
340
495
 
341
496
  def test_render_proc_locals
342
- proc = Haml::Engine.new("%p= foo\n.bar{:baz => baz}= boom").render_proc(Object.new, :foo, :baz, :boom)
497
+ proc = engine("%p= foo\n.bar{:baz => baz}= boom").render_proc(Object.new, :foo, :baz, :boom)
343
498
  assert_equal("<p>1</p>\n<div baz='2' class='bar'>3</div>\n", proc[:foo => 1, :baz => 2, :boom => 3])
344
499
  end
345
500
 
346
501
  def test_render_proc_with_binding
347
- assert_equal("FOO\n", Haml::Engine.new("= upcase").render_proc("foo".instance_eval{binding}).call)
502
+ assert_equal("FOO\n", engine("= upcase").render_proc("foo".instance_eval{binding}).call)
503
+ end
504
+
505
+ def test_ugly_true
506
+ assert_equal("<div id='outer'>\n<div id='inner'>\n<p>hello world</p>\n</div>\n</div>\n",
507
+ render("#outer\n #inner\n %p hello world", :ugly => true))
508
+
509
+ assert_equal("<p>#{'s' * 75}</p>\n",
510
+ render("%p #{'s' * 75}", :ugly => true))
511
+
512
+ assert_equal("<p>#{'s' * 75}</p>\n",
513
+ render("%p= 's' * 75", :ugly => true))
514
+ end
515
+
516
+ def test_auto_preserve_unless_ugly
517
+ assert_equal("<pre>foo&#x000A;bar</pre>\n", render('%pre="foo\nbar"'))
518
+ assert_equal("<pre>foo\nbar</pre>\n", render("%pre\n foo\n bar"))
519
+ assert_equal("<pre>foo\nbar</pre>\n", render('%pre="foo\nbar"', :ugly => true))
520
+ assert_equal("<pre>foo\nbar</pre>\n", render("%pre\n foo\n bar", :ugly => true))
521
+ end
522
+
523
+ def test_xhtml_output_option
524
+ assert_equal "<p>\n <br />\n</p>\n", render("%p\n %br", :format => :xhtml)
525
+ assert_equal "<a />\n", render("%a/", :format => :xhtml)
526
+ end
527
+
528
+ def test_arbitrary_output_option
529
+ assert_raise(Haml::Error, "Invalid output format :html1") { engine("%br", :format => :html1) }
530
+ end
531
+
532
+ # HTML 4.0
533
+
534
+ def test_html_has_no_self_closing_tags
535
+ assert_equal "<p>\n <br>\n</p>\n", render("%p\n %br", :format => :html4)
536
+ assert_equal "<br>\n", render("%br/", :format => :html4)
537
+ end
538
+
539
+ def test_html_renders_empty_node_with_closing_tag
540
+ assert_equal "<div class='foo'></div>\n", render(".foo", :format => :html4)
541
+ end
542
+
543
+ def test_html_ignores_explicit_self_closing_declaration
544
+ assert_equal "<a></a>\n", render("%a/", :format => :html4)
545
+ end
546
+
547
+ def test_html_ignores_xml_prolog_declaration
548
+ assert_equal "", render('!!! XML', :format => :html4)
549
+ end
550
+
551
+ def test_html_has_different_doctype
552
+ assert_equal %{<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n},
553
+ render('!!!', :format => :html4)
554
+ end
555
+
556
+ # because anything before the doctype triggers quirks mode in IE
557
+ def test_xml_prolog_and_doctype_dont_result_in_a_leading_whitespace_in_html
558
+ assert_no_match(/^\s+/, render("!!! xml\n!!!", :format => :html4))
559
+ end
560
+
561
+ # HTML5
562
+ def test_html5_doctype
563
+ assert_equal %{<!DOCTYPE html>\n}, render('!!!', :format => :html5)
348
564
  end
349
565
  end