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.
- data/FAQ +138 -0
- data/MIT-LICENSE +1 -1
- data/{README → README.rdoc} +66 -3
- data/REVISION +1 -0
- data/Rakefile +115 -147
- data/VERSION +1 -1
- data/bin/css2sass +0 -0
- data/bin/haml +2 -1
- data/bin/html2haml +0 -0
- data/bin/sass +0 -0
- data/init.rb +6 -1
- data/lib/haml/buffer.rb +122 -64
- data/lib/haml/engine.rb +77 -46
- data/lib/haml/error.rb +15 -6
- data/lib/haml/exec.rb +61 -10
- data/lib/haml/filters.rb +229 -74
- data/lib/haml/helpers/action_view_extensions.rb +1 -1
- data/lib/haml/helpers/action_view_mods.rb +109 -24
- data/lib/haml/helpers.rb +137 -76
- data/lib/haml/html.rb +8 -8
- data/lib/haml/precompiler.rb +280 -153
- data/lib/haml/template/patch.rb +10 -3
- data/lib/haml/template/plugin.rb +61 -10
- data/lib/haml/template.rb +14 -9
- data/lib/haml.rb +483 -214
- data/lib/sass/constant/color.rb +13 -13
- data/lib/sass/constant/literal.rb +8 -7
- data/lib/sass/constant/nil.rb +9 -0
- data/lib/sass/constant/number.rb +10 -10
- data/lib/sass/constant/operation.rb +4 -4
- data/lib/sass/constant/string.rb +3 -3
- data/lib/sass/constant.rb +46 -77
- data/lib/sass/css.rb +130 -56
- data/lib/sass/engine.rb +131 -43
- data/lib/sass/plugin/merb.rb +48 -12
- data/lib/sass/plugin/rails.rb +10 -4
- data/lib/sass/plugin.rb +33 -10
- data/lib/sass/tree/attr_node.rb +5 -5
- data/lib/sass/tree/directive_node.rb +2 -7
- data/lib/sass/tree/node.rb +1 -12
- data/lib/sass/tree/rule_node.rb +39 -31
- data/lib/sass/tree/value_node.rb +1 -1
- data/lib/sass.rb +194 -19
- data/rails/init.rb +1 -0
- data/test/benchmark.rb +67 -80
- data/test/haml/engine_test.rb +368 -152
- data/test/haml/helper_test.rb +68 -16
- data/test/haml/html2haml_test.rb +3 -4
- data/test/haml/results/content_for_layout.xhtml +1 -2
- data/test/haml/results/eval_suppressed.xhtml +2 -4
- data/test/haml/results/filters.xhtml +38 -30
- data/test/haml/results/helpers.xhtml +4 -8
- data/test/haml/results/just_stuff.xhtml +8 -7
- data/test/haml/results/nuke_inner_whitespace.xhtml +40 -0
- data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
- data/test/haml/results/original_engine.xhtml +3 -7
- data/test/haml/results/partials.xhtml +1 -0
- data/test/haml/results/tag_parsing.xhtml +1 -6
- data/test/haml/results/very_basic.xhtml +2 -4
- data/test/haml/results/whitespace_handling.xhtml +13 -21
- data/test/haml/template_test.rb +42 -57
- data/test/haml/templates/_partial.haml +1 -0
- data/test/haml/templates/filters.haml +39 -21
- data/test/haml/templates/helpers.haml +10 -10
- data/test/haml/templates/just_stuff.haml +8 -3
- data/test/haml/templates/nuke_inner_whitespace.haml +32 -0
- data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
- data/test/haml/templates/partials.haml +1 -1
- data/test/haml/templates/tag_parsing.haml +0 -3
- data/test/haml/templates/whitespace_handling.haml +10 -10
- data/test/sass/engine_test.rb +97 -39
- data/test/sass/plugin_test.rb +4 -7
- data/test/sass/results/constants.css +2 -0
- data/test/sass/results/import.css +2 -2
- data/test/sass/results/mixins.css +95 -0
- data/test/sass/results/multiline.css +24 -0
- data/test/sass/templates/constants.sass +3 -0
- data/test/sass/templates/import.sass +4 -1
- data/test/sass/templates/importee.sass +4 -0
- data/test/sass/templates/mixins.sass +76 -0
- data/test/sass/templates/multiline.sass +20 -0
- data/test/test_helper.rb +18 -0
- metadata +70 -53
- data/lib/haml/helpers/action_view_mods.rb.rej +0 -30
- data/lib/haml/util.rb +0 -18
- data/lib/sass/constant.rb.rej +0 -42
- data/test/haml/runner.rb +0 -16
- data/test/profile.rb +0 -65
- data/test/sass/engine_test.rb.rej +0 -18
data/test/haml/engine_test.rb
CHANGED
|
@@ -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
|
-
|
|
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'
|
|
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'
|
|
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
|
|
48
|
-
assert_equal("<
|
|
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 =
|
|
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
 bar
 baz</textarea>\n",
|
|
148
|
+
render('%textarea= "Foo\n bar\n baz"'))
|
|
149
|
+
|
|
150
|
+
assert_equal("<pre>Foo
 bar
 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 & 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 & 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
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&stupid' />\n",
|
|
211
|
+
render("%img.atlantis{:style => 'ugly&stupid'}"))
|
|
212
|
+
assert_equal("<div class='atlantis' style='ugly&stupid'>foo</div>\n",
|
|
213
|
+
render(".atlantis{:style => 'ugly&stupid'} foo"))
|
|
214
|
+
assert_equal("<p class='atlantis' style='ugly&stupid'>foo</p>\n",
|
|
215
|
+
render("%p.atlantis{:style => 'ugly&stupid'}= 'foo'"))
|
|
216
|
+
assert_equal("<p class='atlantis' style='ugly
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='&foo.png' />\n",
|
|
222
|
+
render("%img{:width => nil, :src => '&foo.png', :alt => String.new}"))
|
|
223
|
+
assert_equal("<p alt='' src='&foo.png'>foo</p>\n",
|
|
224
|
+
render("%p{:width => nil, :src => '&foo.png', :alt => String.new} foo"))
|
|
225
|
+
assert_equal("<div alt='' src='&foo.png'>foo</div>\n",
|
|
226
|
+
render("%div{:width => nil, :src => '&foo.png', :alt => String.new}= 'foo'"))
|
|
227
|
+
assert_equal("<img alt='' src='foo
.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&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&3</p>\n", render("%p&== #{2+2}&#{2+1}", :escape_html => true))
|
|
238
|
+
assert_equal("<p>4&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&3\n</p>\n", render("%p\n &== #{2+2}&#{2+1}", :escape_html => true))
|
|
248
|
+
assert_equal("<p>\n 4&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 & 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 & 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&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
|
|
116
|
-
assert_equal("<p escaped='quo\"te'
|
|
117
|
-
assert_equal("<p escaped=\"
|
|
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"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
|
|
123
|
-
assert_equal("<p foo,bar='baz, qux'
|
|
124
|
-
assert_equal("<p escaped='quo
|
|
125
|
-
assert_equal("<p escaped='quo4te'
|
|
306
|
+
assert_equal("<p boom=>biddly='bar => 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
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
|
|
139
|
-
assert_equal("<p>
|
|
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
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
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'
|
|
156
|
-
assert_equal("<p a='b2c'
|
|
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
|
|
160
|
-
|
|
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
|
|
169
|
-
|
|
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(
|
|
178
|
-
rescue Exception =>
|
|
179
|
-
|
|
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, "#{
|
|
381
|
+
assert(false, "Exception not raised for\n#{key}")
|
|
182
382
|
end
|
|
183
383
|
end
|
|
184
384
|
end
|
|
185
385
|
|
|
186
|
-
def
|
|
386
|
+
def test_exception_line
|
|
187
387
|
render("a\nb\n!!!\n c\nd")
|
|
188
388
|
rescue Haml::SyntaxError => e
|
|
189
|
-
assert_equal(
|
|
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("(
|
|
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\(
|
|
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
|
|
222
|
-
|
|
223
|
-
|
|
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
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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",
|
|
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
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
|