haml 1.8.0 → 2.0.0

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 (82) hide show
  1. data/FAQ +138 -0
  2. data/MIT-LICENSE +1 -1
  3. data/{README → README.rdoc} +66 -3
  4. data/Rakefile +111 -147
  5. data/VERSION +1 -1
  6. data/bin/css2sass +0 -0
  7. data/bin/haml +0 -0
  8. data/bin/html2haml +0 -0
  9. data/bin/sass +0 -0
  10. data/init.rb +6 -1
  11. data/lib/haml/buffer.rb +121 -63
  12. data/lib/haml/engine.rb +67 -44
  13. data/lib/haml/error.rb +16 -6
  14. data/lib/haml/exec.rb +37 -7
  15. data/lib/haml/filters.rb +213 -68
  16. data/lib/haml/helpers/action_view_extensions.rb +1 -1
  17. data/lib/haml/helpers/action_view_mods.rb +57 -9
  18. data/lib/haml/helpers.rb +105 -61
  19. data/lib/haml/html.rb +6 -6
  20. data/lib/haml/precompiler.rb +268 -132
  21. data/lib/haml/template/patch.rb +9 -2
  22. data/lib/haml/template/plugin.rb +61 -10
  23. data/lib/haml/template.rb +3 -6
  24. data/lib/haml.rb +464 -201
  25. data/lib/sass/constant/color.rb +13 -13
  26. data/lib/sass/constant/literal.rb +7 -7
  27. data/lib/sass/constant/number.rb +9 -9
  28. data/lib/sass/constant/operation.rb +4 -4
  29. data/lib/sass/constant/string.rb +3 -3
  30. data/lib/sass/constant.rb +22 -22
  31. data/lib/sass/css.rb +104 -31
  32. data/lib/sass/engine.rb +120 -39
  33. data/lib/sass/error.rb +1 -1
  34. data/lib/sass/plugin/merb.rb +48 -12
  35. data/lib/sass/plugin/rails.rb +10 -4
  36. data/lib/sass/plugin.rb +22 -3
  37. data/lib/sass/tree/attr_node.rb +5 -5
  38. data/lib/sass/tree/directive_node.rb +2 -7
  39. data/lib/sass/tree/node.rb +1 -12
  40. data/lib/sass/tree/rule_node.rb +39 -31
  41. data/lib/sass/tree/value_node.rb +1 -1
  42. data/lib/sass.rb +157 -12
  43. data/test/benchmark.rb +67 -80
  44. data/test/haml/engine_test.rb +318 -81
  45. data/test/haml/helper_test.rb +52 -16
  46. data/test/haml/results/content_for_layout.xhtml +1 -2
  47. data/test/haml/results/eval_suppressed.xhtml +2 -4
  48. data/test/haml/results/filters.xhtml +44 -15
  49. data/test/haml/results/helpers.xhtml +2 -3
  50. data/test/haml/results/just_stuff.xhtml +2 -6
  51. data/test/haml/results/nuke_inner_whitespace.xhtml +34 -0
  52. data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
  53. data/test/haml/results/original_engine.xhtml +3 -7
  54. data/test/haml/results/partials.xhtml +1 -0
  55. data/test/haml/results/tag_parsing.xhtml +1 -6
  56. data/test/haml/results/very_basic.xhtml +2 -4
  57. data/test/haml/results/whitespace_handling.xhtml +13 -21
  58. data/test/haml/template_test.rb +8 -15
  59. data/test/haml/templates/_partial.haml +1 -0
  60. data/test/haml/templates/filters.haml +48 -7
  61. data/test/haml/templates/helpers.haml +9 -9
  62. data/test/haml/templates/just_stuff.haml +1 -2
  63. data/test/haml/templates/nuke_inner_whitespace.haml +26 -0
  64. data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
  65. data/test/haml/templates/tag_parsing.haml +0 -3
  66. data/test/haml/test_helper.rb +15 -0
  67. data/test/sass/engine_test.rb +84 -34
  68. data/test/sass/plugin_test.rb +1 -1
  69. data/test/sass/results/import.css +2 -2
  70. data/test/sass/results/mixins.css +95 -0
  71. data/test/sass/results/multiline.css +24 -0
  72. data/test/sass/templates/import.sass +4 -1
  73. data/test/sass/templates/importee.sass +4 -0
  74. data/test/sass/templates/mixins.sass +76 -0
  75. data/test/sass/templates/multiline.sass +20 -0
  76. metadata +65 -53
  77. data/lib/haml/helpers/action_view_mods.rb.rej +0 -30
  78. data/lib/haml/util.rb +0 -18
  79. data/lib/sass/constant.rb.rej +0 -42
  80. data/test/haml/runner.rb +0 -16
  81. data/test/profile.rb +0 -65
  82. data/test/sass/engine_test.rb.rej +0 -18
@@ -1,15 +1,45 @@
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 Sass 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
+
36
+ # Regression tests
37
+ "- raise 'foo'\n\n\n\nbar" => ["foo", 1],
38
+ "= 'foo'\n-raise 'foo'" => ["foo", 2],
39
+ "\n\n\n- raise 'foo'" => ["foo", 4],
40
+ }
41
+
42
+ User = Struct.new('User', :id)
13
43
 
14
44
  def render(text, options = {}, &block)
15
45
  scope = options.delete(:scope) || Object.new
@@ -22,7 +52,7 @@ class EngineTest < Test::Unit::TestCase
22
52
  end
23
53
 
24
54
  def test_attributes_should_render_correctly
25
- assert_equal("<div class='atlantis' style='ugly'>\n</div>", render(".atlantis{:style => 'ugly'}").chomp)
55
+ assert_equal("<div class='atlantis' style='ugly'></div>", render(".atlantis{:style => 'ugly'}").chomp)
26
56
  end
27
57
 
28
58
  def test_ruby_code_should_work_inside_attributes
@@ -31,7 +61,7 @@ class EngineTest < Test::Unit::TestCase
31
61
  end
32
62
 
33
63
  def test_nil_should_render_empty_tag
34
- assert_equal("<div class='no_attributes'>\n</div>",
64
+ assert_equal("<div class='no_attributes'></div>",
35
65
  render(".no_attributes{:nil => nil}").chomp)
36
66
  end
37
67
 
@@ -44,20 +74,8 @@ class EngineTest < Test::Unit::TestCase
44
74
  assert_equal("<p>Hello</p>", render('%p Hello').chomp)
45
75
  end
46
76
 
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'}))
77
+ def test_one_liner_with_newline_shouldnt_be_one_line
78
+ assert_equal("<p>\n foo\n bar\n</p>", render('%p= "foo\nbar"').chomp)
61
79
  end
62
80
 
63
81
  def test_multi_render
@@ -93,36 +111,190 @@ class EngineTest < Test::Unit::TestCase
93
111
  assert_equal("<img alt='' src='/foo.png' />\n", render("%img{:width => nil, :src => '/foo.png', :alt => String.new}"))
94
112
  end
95
113
 
114
+ def test_end_of_file_multiline
115
+ assert_equal("<p>0</p>\n<p>1</p>\n<p>2</p>\n", render("- for i in (0...3)\n %p= |\n i |"))
116
+ end
117
+
118
+ def test_cr_newline
119
+ 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"))
120
+ end
121
+
122
+ def test_textareas
123
+ assert_equal("<textarea>Foo&#x000A; bar&#x000A; baz</textarea>\n",
124
+ render('%textarea= "Foo\n bar\n baz"'))
125
+
126
+ assert_equal("<pre>Foo&#x000A; bar&#x000A; baz</pre>\n",
127
+ render('%pre= "Foo\n bar\n baz"'))
128
+
129
+ assert_equal("<textarea>#{'a' * 100}</textarea>\n",
130
+ render("%textarea #{'a' * 100}"))
131
+
132
+ assert_equal("<p>\n <textarea>Foo\n Bar\n Baz</textarea>\n</p>\n", render(<<SOURCE))
133
+ %p
134
+ %textarea
135
+ Foo
136
+ Bar
137
+ Baz
138
+ SOURCE
139
+ end
140
+
141
+ def test_boolean_attributes
142
+ assert_equal("<p bar baz='true' foo='bar'></p>\n",
143
+ render("%p{:foo => 'bar', :bar => true, :baz => 'true'}", :format => :html4))
144
+ assert_equal("<p bar='bar' baz='true' foo='bar'></p>\n",
145
+ render("%p{:foo => 'bar', :bar => true, :baz => 'true'}", :format => :xhtml))
146
+
147
+ assert_equal("<p baz='false' foo='bar'></p>\n",
148
+ render("%p{:foo => 'bar', :bar => false, :baz => 'false'}", :format => :html4))
149
+ assert_equal("<p baz='false' foo='bar'></p>\n",
150
+ render("%p{:foo => 'bar', :bar => false, :baz => 'false'}", :format => :xhtml))
151
+ end
152
+
153
+ def test_both_whitespace_nukes_work_together
154
+ assert_equal(<<RESULT, render(<<SOURCE))
155
+ <p><q>Foo
156
+ Bar</q></p>
157
+ RESULT
158
+ %p
159
+ %q><= "Foo\\nBar"
160
+ SOURCE
161
+ end
162
+
163
+ # HTML escaping tests
164
+
165
+ def test_ampersand_equals_should_escape
166
+ assert_equal("<p>\n foo &amp; bar\n</p>\n", render("%p\n &= 'foo & bar'", :escape_html => false))
167
+ end
168
+
169
+ def test_ampersand_equals_inline_should_escape
170
+ assert_equal("<p>foo &amp; bar</p>\n", render("%p&= 'foo & bar'", :escape_html => false))
171
+ end
172
+
173
+ def test_bang_equals_should_not_escape
174
+ assert_equal("<p>\n foo & bar\n</p>\n", render("%p\n != 'foo & bar'", :escape_html => true))
175
+ end
176
+
177
+ def test_bang_equals_inline_should_not_escape
178
+ assert_equal("<p>foo & bar</p>\n", render("%p!= 'foo & bar'", :escape_html => true))
179
+ end
180
+
181
+ def test_static_attributes_should_be_escaped
182
+ assert_equal("<img class='atlantis' style='ugly&amp;stupid' />\n",
183
+ render("%img.atlantis{:style => 'ugly&stupid'}", :escape_html => true))
184
+ assert_equal("<div class='atlantis' style='ugly&amp;stupid'>foo</div>\n",
185
+ render(".atlantis{:style => 'ugly&stupid'} foo", :escape_html => true))
186
+ assert_equal("<p class='atlantis' style='ugly&amp;stupid'>foo</p>\n",
187
+ render("%p.atlantis{:style => 'ugly&stupid'}= 'foo'", :escape_html => true))
188
+ end
189
+
190
+ def test_dynamic_attributes_should_be_escaped
191
+ assert_equal("<img alt='' src='/foo.png' />\n",
192
+ render("%img{:width => nil, :src => '/foo.png', :alt => String.new}", :escape_html => true))
193
+ assert_equal("<p alt='' src='/foo.png'>foo</p>\n",
194
+ render("%p{:width => nil, :src => '/foo.png', :alt => String.new} foo", :escape_html => true))
195
+ assert_equal("<div alt='' src='/foo.png'>foo</div>\n",
196
+ render("%div{:width => nil, :src => '/foo.png', :alt => String.new}= 'foo'", :escape_html => true))
197
+ end
198
+
199
+ def test_string_interpolation_should_be_esaped
200
+ assert_equal("<p>4&amp;3</p>\n", render("%p== #{2+2}&#{2+1}", :escape_html => true))
201
+ assert_equal("<p>4&3</p>\n", render("%p== #{2+2}&#{2+1}", :escape_html => false))
202
+ end
203
+
204
+ def test_escaped_inline_string_interpolation
205
+ assert_equal("<p>4&amp;3</p>\n", render("%p&== #{2+2}&#{2+1}", :escape_html => true))
206
+ assert_equal("<p>4&amp;3</p>\n", render("%p&== #{2+2}&#{2+1}", :escape_html => false))
207
+ end
208
+
209
+ def test_unescaped_inline_string_interpolation
210
+ assert_equal("<p>4&3</p>\n", render("%p!== #{2+2}&#{2+1}", :escape_html => true))
211
+ assert_equal("<p>4&3</p>\n", render("%p!== #{2+2}&#{2+1}", :escape_html => false))
212
+ end
213
+
214
+ def test_escaped_string_interpolation
215
+ assert_equal("<p>\n 4&amp;3\n</p>\n", render("%p\n &== #{2+2}&#{2+1}", :escape_html => true))
216
+ assert_equal("<p>\n 4&amp;3\n</p>\n", render("%p\n &== #{2+2}&#{2+1}", :escape_html => false))
217
+ end
218
+
219
+ def test_unescaped_string_interpolation
220
+ assert_equal("<p>\n 4&3\n</p>\n", render("%p\n !== #{2+2}&#{2+1}", :escape_html => true))
221
+ assert_equal("<p>\n 4&3\n</p>\n", render("%p\n !== #{2+2}&#{2+1}", :escape_html => false))
222
+ end
223
+
224
+ def test_scripts_should_respect_escape_html_option
225
+ assert_equal("<p>\n foo &amp; bar\n</p>\n", render("%p\n = 'foo & bar'", :escape_html => true))
226
+ assert_equal("<p>\n foo & bar\n</p>\n", render("%p\n = 'foo & bar'", :escape_html => false))
227
+ end
228
+
229
+ def test_inline_scripts_should_respect_escape_html_option
230
+ assert_equal("<p>foo &amp; bar</p>\n", render("%p= 'foo & bar'", :escape_html => true))
231
+ assert_equal("<p>foo & bar</p>\n", render("%p= 'foo & bar'", :escape_html => false))
232
+ end
233
+
234
+ def test_script_ending_in_comment_should_render_when_html_is_escaped
235
+ assert_equal("foo&amp;bar\n", render("= 'foo&bar' #comment", :escape_html => true))
236
+ end
237
+
96
238
  # Options tests
97
239
 
240
+ def test_filename_and_line
241
+ begin
242
+ render("\n\n = abc", :filename => 'test', :line => 2)
243
+ rescue Exception => e
244
+ assert_kind_of Haml::SyntaxError, e
245
+ assert_match /test:4/, e.backtrace.first
246
+ end
247
+
248
+ begin
249
+ render("\n\n= 123\n\n= nil[]", :filename => 'test', :line => 2)
250
+ rescue Exception => e
251
+ assert_kind_of NoMethodError, e
252
+ assert_match /test:6/, e.backtrace.first
253
+ end
254
+ end
255
+
98
256
  def test_stop_eval
99
257
  assert_equal("", render("= 'Hello'", :suppress_eval => true))
100
258
  assert_equal("", render("- puts 'foo'", :suppress_eval => true))
101
259
  assert_equal("<div id='foo' yes='no' />\n", render("#foo{:yes => 'no'}/", :suppress_eval => true))
102
260
  assert_equal("<div id='foo' />\n", render("#foo{:yes => 'no', :call => a_function() }/", :suppress_eval => true))
103
261
  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!")
262
+ assert_equal("", render(":ruby\n puts 'hello'", :suppress_eval => true))
112
263
  end
113
264
 
114
265
  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 => '"'))
266
+ assert_equal("<p strange=*attrs*></p>\n", render("%p{ :strange => 'attrs'}", :attr_wrapper => '*'))
267
+ assert_equal("<p escaped='quo\"te'></p>\n", render("%p{ :escaped => 'quo\"te'}", :attr_wrapper => '"'))
268
+ assert_equal("<p escaped=\"quo'te\"></p>\n", render("%p{ :escaped => 'quo\\'te'}", :attr_wrapper => '"'))
269
+ assert_equal("<p escaped=\"q'uo&quot;te\"></p>\n", render("%p{ :escaped => 'q\\'uo\"te'}", :attr_wrapper => '"'))
118
270
  assert_equal("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n", render("!!! XML", :attr_wrapper => '"'))
119
271
  end
120
272
 
121
273
  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\"}"))
274
+ assert_equal("<p boom=>biddly='bar =&gt; baz'></p>\n", render("%p{'boom=>biddly' => 'bar => baz'}"))
275
+ assert_equal("<p foo,bar='baz, qux'></p>\n", render("%p{'foo,bar' => 'baz, qux'}"))
276
+ assert_equal("<p escaped='quo\nte'></p>\n", render("%p{ :escaped => \"quo\\nte\"}"))
277
+ assert_equal("<p escaped='quo4te'></p>\n", render("%p{ :escaped => \"quo\#{2 + 2}te\"}"))
278
+ end
279
+
280
+ def test_correct_parsing_with_brackets
281
+ assert_equal("<p class='foo'>{tada} foo</p>\n", render("%p{:class => 'foo'} {tada} foo"))
282
+ assert_equal("<p class='foo'>deep {nested { things }}</p>\n", render("%p{:class => 'foo'} deep {nested { things }}"))
283
+ assert_equal("<p class='bar foo'>{a { d</p>\n", render("%p{{:class => 'foo'}, :class => 'bar'} {a { d"))
284
+ assert_equal("<p foo='bar'>a}</p>\n", render("%p{:foo => 'bar'} a}"))
285
+
286
+ foo = []
287
+ foo[0] = Struct.new('Foo', :id).new
288
+ assert_equal("<p class='struct_foo' id='struct_foo_new'>New User]</p>\n",
289
+ render("%p[foo[0]] New User]", :locals => {:foo => foo}))
290
+ assert_equal("<p class='prefix_struct_foo' id='prefix_struct_foo_new'>New User]</p>\n",
291
+ render("%p[foo[0], :prefix] New User]", :locals => {:foo => foo}))
292
+
293
+ foo[0].id = 1
294
+ assert_equal("<p class='struct_foo' id='struct_foo_1'>New User]</p>\n",
295
+ render("%p[foo[0]] New User]", :locals => {:foo => foo}))
296
+ assert_equal("<p class='prefix_struct_foo' id='prefix_struct_foo_1'>New User]</p>\n",
297
+ render("%p[foo[0], :prefix] New User]", :locals => {:foo => foo}))
126
298
  end
127
299
 
128
300
  def test_empty_attrs
@@ -135,61 +307,51 @@ class EngineTest < Test::Unit::TestCase
135
307
  assert_equal("<p>nil</p>\n", render("%p{ :attr => x } nil", :locals => {:x => nil}))
136
308
  end
137
309
 
138
- def test_locals
139
- assert_equal("<p>Paragraph!</p>\n", render("%p= text", :locals => { :text => "Paragraph!" }))
310
+ def test_nil_id_with_syntactic_id
311
+ assert_equal("<p id='foo'>nil</p>\n", render("%p#foo{:id => nil} nil"))
312
+ assert_equal("<p id='foo_bar'>nil</p>\n", render("%p#foo{{:id => 'bar'}, :id => nil} nil"))
313
+ assert_equal("<p id='foo_bar'>nil</p>\n", render("%p#foo{{:id => nil}, :id => 'bar'} nil"))
140
314
  end
141
315
 
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)
316
+ def test_nil_class_with_syntactic_class
317
+ assert_equal("<p class='foo'>nil</p>\n", render("%p.foo{:class => nil} nil"))
318
+ assert_equal("<p class='bar foo'>nil</p>\n", render("%p.bar.foo{:class => nil} nil"))
319
+ assert_equal("<p class='bar foo'>nil</p>\n", render("%p.foo{{:class => 'bar'}, :class => nil} nil"))
320
+ assert_equal("<p class='bar foo'>nil</p>\n", render("%p.foo{{:class => nil}, :class => 'bar'} nil"))
321
+ end
150
322
 
151
- Kernel.module_eval { alias_method :warn, :warn_without_stub }
323
+ def test_locals
324
+ assert_equal("<p>Paragraph!</p>\n", render("%p= text", :locals => { :text => "Paragraph!" }))
152
325
  end
153
326
 
154
327
  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'}"))
328
+ assert_equal("<p a='b2c'></p>\n", render('%p{:a => "b#{1 + 1}c"}'))
329
+ assert_equal("<p a='b2c'></p>\n", render("%p{:a => 'b' + (1 + 1).to_s + 'c'}"))
157
330
  end
158
331
 
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)
332
+ def test_dynamic_attrs_with_self_closed_tag
333
+ assert_equal("<a b='2' />\nc\n", render("%a{'b' => 1 + 1}/\n= 'c'\n"))
166
334
  end
167
335
 
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|
336
+ def test_exceptions
337
+ EXCEPTION_MAP.each do |key, value|
176
338
  begin
177
- render(err)
178
- rescue Exception => e
179
- assert(e.is_a?(Haml::Error), "#{err.dump} doesn't produce Haml::SyntaxError")
339
+ render(key)
340
+ rescue Exception => err
341
+ value = [value] unless value.is_a?(Array)
342
+
343
+ assert_equal(value.first, err.message, "Line: #{key}")
344
+ assert_equal(value[1] || key.split("\n").length, err.backtrace[0].gsub('(haml):', '').to_i, "Line: #{key}")
180
345
  else
181
- assert(false, "#{err.dump} doesn't produce an exception")
346
+ assert(false, "Exception not raised for\n#{key}")
182
347
  end
183
348
  end
184
349
  end
185
350
 
186
- def test_syntax_error
351
+ def test_exception_line
187
352
  render("a\nb\n!!!\n c\nd")
188
353
  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')
354
+ assert_equal("(haml):4", e.backtrace[0])
193
355
  else
194
356
  assert(false, '"a\nb\n!!!\n c\nd" doesn\'t produce an exception')
195
357
  end
@@ -218,6 +380,11 @@ class EngineTest < Test::Unit::TestCase
218
380
  assert_equal("Unbalanced brackets.", e.message)
219
381
  end
220
382
 
383
+ def test_balanced_conditional_comments
384
+ assert_equal("<!--[if !(IE 6)|(IE 7)]> Bracket: ] <![endif]-->\n",
385
+ render("/[if !(IE 6)|(IE 7)] Bracket: ]"))
386
+ end
387
+
221
388
  def test_no_bluecloth
222
389
  Kernel.module_eval do
223
390
  def gem_original_require_with_bluecloth(file)
@@ -231,7 +398,7 @@ class EngineTest < Test::Unit::TestCase
231
398
  begin
232
399
  assert_equal("<h1>Foo</h1>\t<p>- a\n- b</p>\n",
233
400
  Haml::Engine.new(":markdown\n Foo\n ===\n - a\n - b").to_html)
234
- rescue Haml::HamlError => e
401
+ rescue Haml::Error => e
235
402
  if e.message == "Can't run Markdown filter; required 'bluecloth' or 'redcloth', but none were found"
236
403
  puts "\nCouldn't require 'bluecloth' or 'redcloth'; skipping a test."
237
404
  else
@@ -256,7 +423,7 @@ class EngineTest < Test::Unit::TestCase
256
423
 
257
424
  begin
258
425
  Haml::Engine.new(":redcloth\n _foo_").to_html
259
- rescue Haml::HamlError
426
+ rescue Haml::Error
260
427
  else
261
428
  assert(false, "No exception raised!")
262
429
  end
@@ -278,7 +445,7 @@ class EngineTest < Test::Unit::TestCase
278
445
 
279
446
  begin
280
447
  Haml::Engine.new(":markdown\n _foo_").to_html
281
- rescue Haml::HamlError
448
+ rescue Haml::Error
282
449
  else
283
450
  assert(false, "No exception raised!")
284
451
  end
@@ -288,17 +455,33 @@ class EngineTest < Test::Unit::TestCase
288
455
  end
289
456
  end
290
457
 
458
+ def test_empty_filter
459
+ assert_equal(<<END, render(':javascript'))
460
+ <script type='text/javascript'>
461
+ //<![CDATA[
462
+
463
+ //]]>
464
+ </script>
465
+ END
466
+ end
467
+
291
468
  def test_local_assigns_dont_modify_class
292
469
  assert_equal("bar\n", render("= foo", :locals => {:foo => 'bar'}))
293
470
  assert_equal(nil, defined?(foo))
294
471
  end
295
472
 
296
473
  def test_object_ref_with_nil_id
297
- user = Struct.new('User', :id).new
474
+ user = User.new
298
475
  assert_equal("<p class='struct_user' id='struct_user_new'>New User</p>\n",
299
476
  render("%p[user] New User", :locals => {:user => user}))
300
477
  end
301
478
 
479
+ def test_object_ref_before_attrs
480
+ user = User.new 42
481
+ assert_equal("<p class='struct_user' id='struct_user_42' style='width: 100px;'>New User</p>\n",
482
+ render("%p[user]{:style => 'width: 100px;'} New User", :locals => {:user => user}))
483
+ end
484
+
302
485
  def test_non_literal_attributes
303
486
  assert_equal("<p a1='foo' a2='bar' a3='baz' />\n",
304
487
  render("%p{a2, a1, :a3 => 'baz'}/",
@@ -346,4 +529,58 @@ class EngineTest < Test::Unit::TestCase
346
529
  def test_render_proc_with_binding
347
530
  assert_equal("FOO\n", Haml::Engine.new("= upcase").render_proc("foo".instance_eval{binding}).call)
348
531
  end
532
+
533
+ def test_ugly_true
534
+ assert_equal("<div id='outer'>\n<div id='inner'>\n<p>hello world</p>\n</div>\n</div>\n",
535
+ render("#outer\n #inner\n %p hello world", :ugly => true))
536
+
537
+ assert_equal("<p>#{'s' * 75}</p>\n",
538
+ render("%p #{'s' * 75}", :ugly => true))
539
+
540
+ assert_equal("<p>#{'s' * 75}</p>\n",
541
+ render("%p= 's' * 75", :ugly => true))
542
+ end
543
+
544
+ def test_xhtml_output_option
545
+ assert_equal "<p>\n <br />\n</p>\n", render("%p\n %br", :format => :xhtml)
546
+ assert_equal "<a />\n", render("%a/", :format => :xhtml)
547
+ end
548
+
549
+ def test_arbitrary_output_option
550
+ assert_raise(Haml::Error, "Invalid output format :html1") { Haml::Engine.new("%br", :format => :html1) }
551
+ end
552
+
553
+ # HTML 4.0
554
+
555
+ def test_html_has_no_self_closing_tags
556
+ assert_equal "<p>\n <br>\n</p>\n", render("%p\n %br", :format => :html4)
557
+ assert_equal "<br>\n", render("%br/", :format => :html4)
558
+ end
559
+
560
+ def test_html_renders_empty_node_with_closing_tag
561
+ assert_equal "<div class='foo'></div>\n", render(".foo", :format => :html4)
562
+ end
563
+
564
+ def test_html_ignores_explicit_self_closing_declaration
565
+ assert_equal "<a></a>\n", render("%a/", :format => :html4)
566
+ end
567
+
568
+ def test_html_ignores_xml_prolog_declaration
569
+ assert_equal "", render('!!! XML', :format => :html4)
570
+ end
571
+
572
+ def test_html_has_different_doctype
573
+ assert_equal %{<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n},
574
+ render('!!!', :format => :html4)
575
+ end
576
+
577
+ # because anything before the doctype triggers quirks mode in IE
578
+ def test_xml_prolog_and_doctype_dont_result_in_a_leading_whitespace_in_html
579
+ assert_no_match /^\s+/, render("!!! xml\n!!!", :format => :html4)
580
+ end
581
+
582
+ # HTML5
583
+ def test_html5_doctype
584
+ assert_equal %{<!DOCTYPE html>\n}, render('!!!', :format => :html5)
585
+ end
349
586
  end
@@ -1,20 +1,15 @@
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'
2
+ require File.dirname(__FILE__) + '/test_helper'
10
3
  require 'haml/template'
11
4
 
12
5
  class HelperTest < Test::Unit::TestCase
13
6
  include Haml::Helpers
7
+ Post = Struct.new('Post', :body)
14
8
 
15
9
  def setup
16
10
  @base = ActionView::Base.new
17
11
  @base.controller = ActionController::Base.new
12
+ @base.instance_variable_set('@post', Post.new("Foo bar\nbaz"))
18
13
  end
19
14
 
20
15
  def render(text, options = {})
@@ -27,14 +22,14 @@ class HelperTest < Test::Unit::TestCase
27
22
  end
28
23
 
29
24
  def test_flatten
30
- assert_equal(flatten("FooBar"), "FooBar")
25
+ assert_equal("FooBar", flatten("FooBar"))
31
26
 
32
- assert_equal(flatten("Foo\rBar"), "FooBar")
27
+ assert_equal("FooBar", flatten("Foo\rBar"))
33
28
 
34
- assert_equal(flatten("Foo\nBar"), "Foo&#x000A;Bar")
29
+ assert_equal("Foo&#x000A;Bar", flatten("Foo\nBar"))
35
30
 
36
- assert_equal(flatten("Hello\nWorld!\nYOU ARE \rFLAT?\n\rOMGZ!"),
37
- "Hello&#x000A;World!&#x000A;YOU ARE FLAT?&#x000A;OMGZ!")
31
+ assert_equal("Hello&#x000A;World!&#x000A;YOU ARE FLAT?&#x000A;OMGZ!",
32
+ flatten("Hello\nWorld!\nYOU ARE \rFLAT?\n\rOMGZ!"))
38
33
  end
39
34
 
40
35
  def test_list_of_should_render_correctly
@@ -85,10 +80,33 @@ class HelperTest < Test::Unit::TestCase
85
80
  should_be = "<form action=\"foo\" method=\"post\">\n <p>bar</p>\n <strong>baz</strong>\n</form>\n"
86
81
  assert_equal(should_be, result)
87
82
  end
83
+
84
+ def test_text_area
85
+ assert_equal(%(<textarea id="body" name="body">Foo&#x000A;Bar&#x000A; Baz&#x000A; Boom</textarea>\n),
86
+ render('= text_area_tag "body", "Foo\nBar\n Baz\n Boom"', :action_view))
87
+
88
+ assert_equal(%(<textarea cols="40" id="post_body" name="post[body]" rows="20">Foo bar&#x000A;baz</textarea>\n),
89
+ render('= text_area :post, :body', :action_view))
90
+
91
+ assert_equal(%(<pre>Foo bar&#x000A; baz</pre>\n),
92
+ render('= content_tag "pre", "Foo bar\n baz"', :action_view))
93
+ end
88
94
 
89
95
  def test_capture_haml
90
96
  assert_equal("\"<p>13</p>\\n\"\n", render("- foo = capture_haml(13) do |a|\n %p= a\n= foo.dump"))
91
97
  end
98
+
99
+ def test_haml_tag_attribute_html_escaping
100
+ assert_equal("<p id='foo&amp;bar'>baz</p>\n", render("%p{:id => 'foo&bar'} baz", :escape_html => true))
101
+ end
102
+
103
+ def test_haml_tag_autoclosed_tags_are_closed
104
+ assert_equal("<br class='foo' />\n", render("- haml_tag :br, :class => 'foo'"))
105
+ end
106
+
107
+ def test_haml_tag_non_autoclosed_tags_arent_closed
108
+ assert_equal("<p>\n</p>\n", render("- haml_tag :p"))
109
+ end
92
110
 
93
111
  def test_is_haml
94
112
  assert(!ActionView::Base.new.is_haml?)
@@ -123,12 +141,12 @@ class HelperTest < Test::Unit::TestCase
123
141
  end
124
142
 
125
143
  def test_find_and_preserve_with_block
126
- assert_equal("<pre>&#x000A; Foo&#x000A; Bar&#x000A;</pre>\nFoo\nBar\n",
144
+ assert_equal("<pre>Foo&#x000A;Bar</pre>\nFoo\nBar\n",
127
145
  render("= find_and_preserve do\n %pre\n Foo\n Bar\n Foo\n Bar"))
128
146
  end
129
147
 
130
148
  def test_preserve_with_block
131
- assert_equal("<pre>&#x000A; Foo&#x000A; Bar&#x000A;</pre>&#x000A;Foo&#x000A;Bar&#x000A;\n",
149
+ assert_equal("<pre>Foo&#x000A;Bar</pre>&#x000A;Foo&#x000A;Bar\n",
132
150
  render("= preserve do\n %pre\n Foo\n Bar\n Foo\n Bar"))
133
151
  end
134
152
 
@@ -140,12 +158,30 @@ class HelperTest < Test::Unit::TestCase
140
158
  context.init_haml_helpers
141
159
 
142
160
  result = context.capture_haml do
143
- context.open :p, :attr => "val" do
161
+ context.haml_tag :p, :attr => "val" do
144
162
  context.puts "Blah"
145
163
  end
146
164
  end
147
165
 
148
166
  assert_equal("<p attr='val'>\n Blah\n</p>\n", result)
149
167
  end
168
+
169
+ def test_non_haml
170
+ assert_equal("false\n", render("= non_haml { is_haml? }"))
171
+ end
172
+
173
+ class ActsLikeTag
174
+ # We want to be able to have people include monkeypatched ActionView helpers
175
+ # without redefining is_haml?.
176
+ # This is accomplished via Object#is_haml?, and this is a test for it.
177
+ include ActionView::Helpers::TagHelper
178
+ def to_s
179
+ content_tag :p, 'some tag content'
180
+ end
181
+ end
182
+
183
+ def test_random_class_includes_tag_helper
184
+ assert_equal "<p>some tag content</p>", ActsLikeTag.new.to_s
185
+ end
150
186
  end
151
187
 
@@ -1,7 +1,6 @@
1
1
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
2
  <html>
3
- <head>
4
- </head>
3
+ <head></head>
5
4
  <body>
6
5
  <div id='content'>
7
6
  Lorem ipsum dolor sit amet
@@ -1,7 +1,5 @@
1
- <p>
2
- </p>
3
- <p>
4
- </p>
1
+ <p></p>
2
+ <p></p>
5
3
  <h1>Me!</h1>
6
4
  <div id='foo'>
7
5
  <p id='bar'>All</p>