merbjedi-haml 2.1.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 (177) hide show
  1. data/FAQ +138 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +332 -0
  4. data/REVISION +1 -0
  5. data/Rakefile +184 -0
  6. data/VERSION +1 -0
  7. data/bin/css2sass +7 -0
  8. data/bin/haml +9 -0
  9. data/bin/html2haml +7 -0
  10. data/bin/sass +8 -0
  11. data/extra/haml-mode.el +434 -0
  12. data/extra/sass-mode.el +98 -0
  13. data/init.rb +8 -0
  14. data/lib/haml.rb +1025 -0
  15. data/lib/haml/buffer.rb +255 -0
  16. data/lib/haml/engine.rb +268 -0
  17. data/lib/haml/error.rb +22 -0
  18. data/lib/haml/exec.rb +395 -0
  19. data/lib/haml/filters.rb +276 -0
  20. data/lib/haml/helpers.rb +465 -0
  21. data/lib/haml/helpers/action_view_extensions.rb +45 -0
  22. data/lib/haml/helpers/action_view_mods.rb +181 -0
  23. data/lib/haml/html.rb +218 -0
  24. data/lib/haml/precompiler.rb +896 -0
  25. data/lib/haml/shared.rb +45 -0
  26. data/lib/haml/template.rb +51 -0
  27. data/lib/haml/template/patch.rb +58 -0
  28. data/lib/haml/template/plugin.rb +72 -0
  29. data/lib/haml/util.rb +77 -0
  30. data/lib/haml/version.rb +47 -0
  31. data/lib/sass.rb +1062 -0
  32. data/lib/sass/css.rb +388 -0
  33. data/lib/sass/engine.rb +501 -0
  34. data/lib/sass/environment.rb +33 -0
  35. data/lib/sass/error.rb +35 -0
  36. data/lib/sass/plugin.rb +203 -0
  37. data/lib/sass/plugin/merb.rb +56 -0
  38. data/lib/sass/plugin/rails.rb +24 -0
  39. data/lib/sass/repl.rb +44 -0
  40. data/lib/sass/script.rb +38 -0
  41. data/lib/sass/script/bool.rb +13 -0
  42. data/lib/sass/script/color.rb +97 -0
  43. data/lib/sass/script/funcall.rb +28 -0
  44. data/lib/sass/script/functions.rb +122 -0
  45. data/lib/sass/script/lexer.rb +144 -0
  46. data/lib/sass/script/literal.rb +60 -0
  47. data/lib/sass/script/number.rb +231 -0
  48. data/lib/sass/script/operation.rb +30 -0
  49. data/lib/sass/script/parser.rb +142 -0
  50. data/lib/sass/script/string.rb +42 -0
  51. data/lib/sass/script/unary_operation.rb +21 -0
  52. data/lib/sass/script/variable.rb +20 -0
  53. data/lib/sass/tree/attr_node.rb +64 -0
  54. data/lib/sass/tree/comment_node.rb +30 -0
  55. data/lib/sass/tree/debug_node.rb +22 -0
  56. data/lib/sass/tree/directive_node.rb +50 -0
  57. data/lib/sass/tree/file_node.rb +27 -0
  58. data/lib/sass/tree/for_node.rb +29 -0
  59. data/lib/sass/tree/if_node.rb +27 -0
  60. data/lib/sass/tree/mixin_def_node.rb +18 -0
  61. data/lib/sass/tree/mixin_node.rb +34 -0
  62. data/lib/sass/tree/node.rb +97 -0
  63. data/lib/sass/tree/rule_node.rb +120 -0
  64. data/lib/sass/tree/variable_node.rb +24 -0
  65. data/lib/sass/tree/while_node.rb +20 -0
  66. data/rails/init.rb +1 -0
  67. data/test/benchmark.rb +99 -0
  68. data/test/haml/engine_test.rb +852 -0
  69. data/test/haml/helper_test.rb +224 -0
  70. data/test/haml/html2haml_test.rb +92 -0
  71. data/test/haml/markaby/standard.mab +52 -0
  72. data/test/haml/mocks/article.rb +6 -0
  73. data/test/haml/results/content_for_layout.xhtml +15 -0
  74. data/test/haml/results/eval_suppressed.xhtml +9 -0
  75. data/test/haml/results/filters.xhtml +62 -0
  76. data/test/haml/results/helpers.xhtml +93 -0
  77. data/test/haml/results/helpful.xhtml +10 -0
  78. data/test/haml/results/just_stuff.xhtml +68 -0
  79. data/test/haml/results/list.xhtml +12 -0
  80. data/test/haml/results/nuke_inner_whitespace.xhtml +40 -0
  81. data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
  82. data/test/haml/results/original_engine.xhtml +20 -0
  83. data/test/haml/results/partial_layout.xhtml +5 -0
  84. data/test/haml/results/partials.xhtml +21 -0
  85. data/test/haml/results/render_layout.xhtml +3 -0
  86. data/test/haml/results/silent_script.xhtml +74 -0
  87. data/test/haml/results/standard.xhtml +42 -0
  88. data/test/haml/results/tag_parsing.xhtml +23 -0
  89. data/test/haml/results/very_basic.xhtml +5 -0
  90. data/test/haml/results/whitespace_handling.xhtml +89 -0
  91. data/test/haml/rhtml/_av_partial_1.rhtml +12 -0
  92. data/test/haml/rhtml/_av_partial_2.rhtml +8 -0
  93. data/test/haml/rhtml/action_view.rhtml +62 -0
  94. data/test/haml/rhtml/standard.rhtml +54 -0
  95. data/test/haml/template_test.rb +204 -0
  96. data/test/haml/templates/_av_partial_1.haml +9 -0
  97. data/test/haml/templates/_av_partial_1_ugly.haml +9 -0
  98. data/test/haml/templates/_av_partial_2.haml +5 -0
  99. data/test/haml/templates/_av_partial_2_ugly.haml +5 -0
  100. data/test/haml/templates/_layout.erb +3 -0
  101. data/test/haml/templates/_layout_for_partial.haml +3 -0
  102. data/test/haml/templates/_partial.haml +8 -0
  103. data/test/haml/templates/_text_area.haml +3 -0
  104. data/test/haml/templates/action_view.haml +47 -0
  105. data/test/haml/templates/action_view_ugly.haml +47 -0
  106. data/test/haml/templates/breakage.haml +8 -0
  107. data/test/haml/templates/content_for_layout.haml +10 -0
  108. data/test/haml/templates/eval_suppressed.haml +11 -0
  109. data/test/haml/templates/filters.haml +66 -0
  110. data/test/haml/templates/helpers.haml +95 -0
  111. data/test/haml/templates/helpful.haml +11 -0
  112. data/test/haml/templates/just_stuff.haml +83 -0
  113. data/test/haml/templates/list.haml +12 -0
  114. data/test/haml/templates/nuke_inner_whitespace.haml +32 -0
  115. data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
  116. data/test/haml/templates/original_engine.haml +17 -0
  117. data/test/haml/templates/partial_layout.haml +3 -0
  118. data/test/haml/templates/partialize.haml +1 -0
  119. data/test/haml/templates/partials.haml +12 -0
  120. data/test/haml/templates/render_layout.haml +2 -0
  121. data/test/haml/templates/silent_script.haml +40 -0
  122. data/test/haml/templates/standard.haml +42 -0
  123. data/test/haml/templates/standard_ugly.haml +42 -0
  124. data/test/haml/templates/tag_parsing.haml +21 -0
  125. data/test/haml/templates/very_basic.haml +4 -0
  126. data/test/haml/templates/whitespace_handling.haml +87 -0
  127. data/test/linked_rails.rb +12 -0
  128. data/test/sass/css2sass_test.rb +193 -0
  129. data/test/sass/engine_test.rb +752 -0
  130. data/test/sass/functions_test.rb +96 -0
  131. data/test/sass/more_results/more1.css +9 -0
  132. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  133. data/test/sass/more_results/more_import.css +29 -0
  134. data/test/sass/more_templates/_more_partial.sass +2 -0
  135. data/test/sass/more_templates/more1.sass +23 -0
  136. data/test/sass/more_templates/more_import.sass +11 -0
  137. data/test/sass/plugin_test.rb +208 -0
  138. data/test/sass/results/alt.css +4 -0
  139. data/test/sass/results/basic.css +9 -0
  140. data/test/sass/results/compact.css +5 -0
  141. data/test/sass/results/complex.css +87 -0
  142. data/test/sass/results/compressed.css +1 -0
  143. data/test/sass/results/expanded.css +19 -0
  144. data/test/sass/results/import.css +29 -0
  145. data/test/sass/results/line_numbers.css +49 -0
  146. data/test/sass/results/mixins.css +95 -0
  147. data/test/sass/results/multiline.css +24 -0
  148. data/test/sass/results/nested.css +22 -0
  149. data/test/sass/results/parent_ref.css +13 -0
  150. data/test/sass/results/script.css +16 -0
  151. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  152. data/test/sass/results/subdir/subdir.css +3 -0
  153. data/test/sass/results/units.css +11 -0
  154. data/test/sass/script_test.rb +152 -0
  155. data/test/sass/templates/_partial.sass +2 -0
  156. data/test/sass/templates/alt.sass +16 -0
  157. data/test/sass/templates/basic.sass +23 -0
  158. data/test/sass/templates/bork.sass +2 -0
  159. data/test/sass/templates/bork2.sass +2 -0
  160. data/test/sass/templates/compact.sass +17 -0
  161. data/test/sass/templates/complex.sass +309 -0
  162. data/test/sass/templates/compressed.sass +15 -0
  163. data/test/sass/templates/expanded.sass +17 -0
  164. data/test/sass/templates/import.sass +11 -0
  165. data/test/sass/templates/importee.sass +19 -0
  166. data/test/sass/templates/line_numbers.sass +13 -0
  167. data/test/sass/templates/mixins.sass +76 -0
  168. data/test/sass/templates/multiline.sass +20 -0
  169. data/test/sass/templates/nested.sass +25 -0
  170. data/test/sass/templates/parent_ref.sass +25 -0
  171. data/test/sass/templates/script.sass +101 -0
  172. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  173. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  174. data/test/sass/templates/subdir/subdir.sass +6 -0
  175. data/test/sass/templates/units.sass +11 -0
  176. data/test/test_helper.rb +21 -0
  177. metadata +273 -0
@@ -0,0 +1,24 @@
1
+ module Sass
2
+ module Tree
3
+ class VariableNode < Node
4
+ def initialize(name, expr, guarded, options)
5
+ @name = name
6
+ @expr = expr
7
+ @guarded = guarded
8
+ super(options)
9
+ end
10
+
11
+ protected
12
+
13
+ def _perform(environment)
14
+ if @guarded && environment.var(@name).nil?
15
+ environment.set_var(@name, @expr.perform(environment))
16
+ elsif !@guarded
17
+ environment.set_var(@name, @expr.perform(environment))
18
+ end
19
+
20
+ []
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ require 'sass/tree/node'
2
+
3
+ module Sass::Tree
4
+ class WhileNode < Node
5
+ def initialize(expr, options)
6
+ @expr = expr
7
+ super(options)
8
+ end
9
+
10
+ private
11
+
12
+ def _perform(environment)
13
+ children = []
14
+ while @expr.perform(environment).to_bool
15
+ children += perform_children(Sass::Environment.new(environment))
16
+ end
17
+ children
18
+ end
19
+ end
20
+ end
@@ -0,0 +1 @@
1
+ Kernel.load File.join(File.dirname(__FILE__), '..', 'init.rb')
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ times = (ARGV.first || 1000).to_i
4
+
5
+ if times == 0 # Invalid parameter
6
+ puts <<END
7
+ ruby #$0 [times=1000]
8
+ Benchmark Haml against various other templating languages and Sass
9
+ on its own.
10
+ END
11
+ exit 1
12
+ end
13
+
14
+ require File.dirname(__FILE__) + '/../lib/haml'
15
+ require File.dirname(__FILE__) + '/linked_rails'
16
+ %w[sass rubygems erb erubis markaby active_support action_controller
17
+ action_view action_pack haml/template rbench].each {|dep| require(dep)}
18
+
19
+ def view
20
+ unless Haml::Util.has?(:instance_method, ActionView::Base, :finder)
21
+ return ActionView::Base.new(File.dirname(__FILE__), {})
22
+ end
23
+
24
+ # Rails >=2.1.0
25
+ base = ActionView::Base.new
26
+ base.finder.append_view_path(File.dirname(__FILE__))
27
+ base
28
+ end
29
+
30
+ def render(view, file)
31
+ view.render :file => file
32
+ end
33
+
34
+ RBench.run(times) do
35
+ column :haml, :title => "Haml"
36
+ column :haml_ugly, :title => "Haml :ugly"
37
+ column :erb, :title => "ERB"
38
+ column :erubis, :title => "Erubis"
39
+
40
+ template_name = 'standard'
41
+ directory = File.dirname(__FILE__) + '/haml'
42
+ haml_template = File.read("#{directory}/templates/#{template_name}.haml")
43
+ erb_template = File.read("#{directory}/rhtml/#{template_name}.rhtml")
44
+ markaby_template = File.read("#{directory}/markaby/#{template_name}.mab")
45
+
46
+ report "Cached" do
47
+ obj = Object.new
48
+
49
+ Haml::Engine.new(haml_template).def_method(obj, :haml)
50
+ Haml::Engine.new(haml_template, :ugly => true).def_method(obj, :haml_ugly)
51
+ Erubis::Eruby.new(erb_template).def_method(obj, :erubis)
52
+ obj.instance_eval("def erb; #{ERB.new(erb_template, nil, '-').src}; end")
53
+
54
+ haml { obj.haml }
55
+ haml_ugly { obj.haml_ugly }
56
+ erb { obj.erb }
57
+ erubis { obj.erubis }
58
+ end
59
+
60
+ report "ActionView" do
61
+ @base = view
62
+
63
+ @base.unmemoize_all
64
+ Haml::Template.options[:ugly] = false
65
+ # To cache the template
66
+ render @base, 'haml/templates/standard'
67
+ render @base, 'haml/rhtml/standard'
68
+
69
+ haml { render @base, 'haml/templates/standard' }
70
+ erb { render @base, 'haml/rhtml/standard' }
71
+
72
+ Haml::Template.options[:ugly] = true
73
+ render @base, 'haml/templates/standard_ugly'
74
+ haml_ugly { render @base, 'haml/templates/standard_ugly' }
75
+ end
76
+
77
+ report "ActionView with deep partials" do
78
+ @base = view
79
+
80
+ @base.unmemoize_all
81
+ Haml::Template.options[:ugly] = false
82
+ # To cache the template
83
+ render @base, 'haml/templates/action_view'
84
+ render @base, 'haml/rhtml/action_view'
85
+
86
+ haml { render @base, 'haml/templates/action_view' }
87
+ erb { render @base, 'haml/rhtml/action_view' }
88
+
89
+ Haml::Template.options[:ugly] = true
90
+ render @base, 'haml/templates/action_view_ugly'
91
+ haml_ugly { render @base, 'haml/templates/action_view_ugly' }
92
+ end
93
+ end
94
+
95
+ RBench.run(times) do
96
+ sass_template = File.read("#{File.dirname(__FILE__)}/sass/templates/complex.sass")
97
+
98
+ report("Sass") { Sass::Engine.new(sass_template).render }
99
+ end
@@ -0,0 +1,852 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../test_helper'
3
+
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
+ ":a\n b" => ['Filter "a" is not defined.', 1],
21
+ ":a= b" => 'Invalid filter name ":a= b".',
22
+ "." => "Illegal element: classes and ids must have values.",
23
+ ".#" => "Illegal element: classes and ids must have values.",
24
+ ".{} a" => "Illegal element: classes and ids must have values.",
25
+ ".= a" => "Illegal element: classes and ids must have values.",
26
+ "%p..a" => "Illegal element: classes and ids must have values.",
27
+ "%a/ b" => "Self-closing tags can't have content.",
28
+ "%p{:a => 'b',\n:c => 'd'}/ e" => ["Self-closing tags can't have content.", 2],
29
+ "%p{:a => 'b',\n:c => 'd'}=" => ["There's no Ruby code for = to evaluate.", 2],
30
+ "%p.{:a => 'b',\n:c => 'd'} e" => ["Illegal element: classes and ids must have values.", 1],
31
+ "%p{:a => 'b',\n:c => 'd',\n:e => 'f'}\n%p/ a" => ["Self-closing tags can't have content.", 4],
32
+ "%p{:a => 'b',\n:c => 'd',\n:e => 'f'}\n- raise 'foo'" => ["foo", 4],
33
+ "%p{:a => 'b',\n:c => raise('foo'),\n:e => 'f'}" => ["foo", 2],
34
+ "%p{:a => 'b',\n:c => 'd',\n:e => raise('foo')}" => ["foo", 3],
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
+ "\n\n %p foo" => ["Indenting at the beginning of the document is illegal.", 3],
40
+ "%p\n foo\n foo" => ["Inconsistent indentation: 1 space was used for indentation, but the rest of the document was indented using 2 spaces.", 3],
41
+ "%p\n foo\n%p\n foo" => ["Inconsistent indentation: 1 space was used for indentation, but the rest of the document was indented using 2 spaces.", 4],
42
+ "%p\n\t\tfoo\n\tfoo" => ["Inconsistent indentation: 1 tab was used for indentation, but the rest of the document was indented using 2 tabs.", 3],
43
+ "%p\n foo\n foo" => ["Inconsistent indentation: 3 spaces were used for indentation, but the rest of the document was indented using 2 spaces.", 3],
44
+ "%p\n foo\n %p\n bar" => ["Inconsistent indentation: 3 spaces were used for indentation, but the rest of the document was indented using 2 spaces.", 4],
45
+ "%p\n :plain\n bar\n \t baz" => ['Inconsistent indentation: " \t " was used for indentation, but the rest of the document was indented using 2 spaces.', 4],
46
+ "%p\n foo\n%p\n bar" => ["The line was indented 2 levels deeper than the previous line.", 4],
47
+ "%p\n foo\n %p\n bar" => ["The line was indented 3 levels deeper than the previous line.", 4],
48
+ "%p\n \tfoo" => ["Indentation can't use both tabs and spaces.", 2],
49
+
50
+ # Regression tests
51
+ "- raise 'foo'\n\n\n\nbar" => ["foo", 1],
52
+ "= 'foo'\n-raise 'foo'" => ["foo", 2],
53
+ "\n\n\n- raise 'foo'" => ["foo", 4],
54
+ "%p foo |\n bar |\n baz |\nbop\n- raise 'foo'" => ["foo", 5],
55
+ "foo\n\n\n bar" => ["Illegal nesting: nesting within plain text is illegal.", 4],
56
+ "%p/\n\n bar" => ["Illegal nesting: nesting within a self-closing tag is illegal.", 3],
57
+ "%p foo\n\n bar" => ["Illegal nesting: content can't be both given on the same line as %p and nested within it.", 3],
58
+ "%p \#{'hello''}\n\n bar" => ["Illegal nesting: content can't be both given on the same line as %p and nested within it.", 3],
59
+ "/ foo\n\n bar" => ["Illegal nesting: nesting within a tag that already has content is illegal.", 3],
60
+ "!!!\n\n bar" => ["Illegal nesting: nesting within a header command is illegal.", 3],
61
+ "foo\n:ruby\n 1\n 2\n 3\n- raise 'foo'" => ["foo", 6],
62
+ }
63
+
64
+ User = Struct.new('User', :id)
65
+
66
+ def render(text, options = {}, &block)
67
+ scope = options.delete(:scope) || Object.new
68
+ locals = options.delete(:locals) || {}
69
+ engine(text, options).to_html(scope, locals, &block)
70
+ end
71
+
72
+ def engine(text, options = {})
73
+ unless options[:filename]
74
+ # use caller method name as fake filename. useful for debugging
75
+ i = -1
76
+ caller[i+=1] =~ /`(.+?)'/ until $1 and $1.index('test_') == 0
77
+ options[:filename] = "(#{$1})"
78
+ end
79
+ Haml::Engine.new(text, options)
80
+ end
81
+
82
+ def test_empty_render
83
+ assert_equal "", render("")
84
+ end
85
+
86
+ def test_flexible_tabulation
87
+ assert_equal("<p>\n foo\n</p>\n<q>\n bar\n <a>\n baz\n </a>\n</q>\n",
88
+ render("%p\n foo\n%q\n bar\n %a\n baz"))
89
+ assert_equal("<p>\n foo\n</p>\n<q>\n bar\n <a>\n baz\n </a>\n</q>\n",
90
+ render("%p\n\tfoo\n%q\n\tbar\n\t%a\n\t\tbaz"))
91
+ assert_equal("<p>\n \t \t bar\n baz\n</p>\n",
92
+ render("%p\n :plain\n \t \t bar\n baz"))
93
+ end
94
+
95
+ def test_empty_render_should_remain_empty
96
+ assert_equal('', render(''))
97
+ end
98
+
99
+ def test_attributes_should_render_correctly
100
+ assert_equal("<div class='atlantis' style='ugly'></div>", render(".atlantis{:style => 'ugly'}").chomp)
101
+ end
102
+
103
+ def test_ruby_code_should_work_inside_attributes
104
+ author = 'hcatlin'
105
+ assert_equal("<p class='3'>foo</p>", render("%p{:class => 1+2} foo").chomp)
106
+ end
107
+
108
+ def test_nil_should_render_empty_tag
109
+ assert_equal("<div class='no_attributes'></div>",
110
+ render(".no_attributes{:nil => nil}").chomp)
111
+ end
112
+
113
+ def test_strings_should_get_stripped_inside_tags
114
+ assert_equal("<div class='stripped'>This should have no spaces in front of it</div>",
115
+ render(".stripped This should have no spaces in front of it").chomp)
116
+ end
117
+
118
+ def test_shorthand_one_liners
119
+ assert_equal("<p>Hello World</p>\n", render("%p Hello World"))
120
+ assert_equal("<p>Hello World</p>\n", render("%p= 'Hello World'"))
121
+ assert_equal("<div class='welcome'>Hello World</div>\n", render(".welcome Hello World"))
122
+ assert_equal("<div class='welcome'>Hello World</div>\n", render(".welcome= 'Hello World'"))
123
+ assert_equal("<div id='welcome'>Hello World</div>\n", render("#welcome Hello World"))
124
+ assert_equal("<div id='welcome'>Hello World</div>\n", render("#welcome= 'Hello World'"))
125
+ end
126
+
127
+ def test_one_liner_should_be_one_line
128
+ assert_equal("<p>Hello</p>", render('%p Hello').chomp)
129
+ end
130
+
131
+ def test_one_liner_eval_with_interpolation
132
+ assert_equal("<div class='welcome'>Hello WORLD</div>", render(%{.welcome= "Hello \#{'WORLD'}"}).chomp)
133
+ assert_equal("<div id='welcome'>Hello WORLD</div>", render(%{#welcome= "Hello \#{'WORLD'}"}).chomp)
134
+ end
135
+
136
+ def test_one_liner_with_newline_shouldnt_be_one_line
137
+ assert_equal("<p>\n foo\n bar\n</p>", render('%p= "foo\nbar"').chomp)
138
+ end
139
+
140
+ def test_multi_render
141
+ engine = engine("%strong Hi there!")
142
+ assert_equal("<strong>Hi there!</strong>\n", engine.to_html)
143
+ assert_equal("<strong>Hi there!</strong>\n", engine.to_html)
144
+ assert_equal("<strong>Hi there!</strong>\n", engine.to_html)
145
+ end
146
+
147
+ def test_double_equals
148
+ assert_equal("<p>Hello World</p>\n", render('%p== Hello #{who}', :locals => {:who => 'World'}))
149
+ assert_equal("<p>\n Hello World\n</p>\n", render("%p\n == Hello \#{who}", :locals => {:who => 'World'}))
150
+ end
151
+
152
+ def test_double_equals_in_the_middle_of_a_string
153
+ assert_equal("\"title 'Title'. \"\n",
154
+ render("== \"title '\#{\"Title\"}'. \""))
155
+ end
156
+
157
+ def test_implicit_interpolation
158
+ assert_equal("Hello World\n", render('Hello #{who}', :locals => {:who => 'World'}))
159
+ end
160
+
161
+ def test_implicit_interpolation_without_end
162
+ assert_equal("Hello \#{who\n", render('Hello #{who', :locals => {:who => 'World'}))
163
+ assert_equal("<p>Hello \#{who</p>\n", render('%p Hello #{who', :locals => {:who => 'World'}))
164
+ end
165
+
166
+ def test_implicit_interpolation_at_beginning
167
+ assert_equal("World\n", render('#{who}', :locals => {:who => 'World'}))
168
+ end
169
+
170
+ def test_escaped_hash
171
+ assert_equal("Hello \#{who}\n", render('Hello \\#{who}'))
172
+ end
173
+
174
+ def test_implicit_interpolation_with_tag
175
+ assert_equal("<p>Hello World</p>\n", render('%p Hello #{who}', :locals => {:who => 'World'}))
176
+ assert_equal(<<HTML, render(<<HAML, :locals => {:who => 'World'}))
177
+ <p>
178
+ Hello World
179
+ </p>
180
+ HTML
181
+ %p
182
+ == Hello \#{who}
183
+ HAML
184
+ end
185
+
186
+ def test_implicit_interpolation_at_beginning_with_tag
187
+ assert_equal("<p>World</p>\n", render('%p #{who}', :locals => {:who => 'World'}))
188
+ assert_equal(<<HTML, render(<<HAML, :locals => {:who => 'World'}))
189
+ <p>
190
+ World
191
+ </p>
192
+ HTML
193
+ %p
194
+ \#{who}
195
+ HAML
196
+ end
197
+
198
+ def test_explicit_interpolation
199
+ assert_equal("<p>Hello World</p>\n", render('%p== Hello #{who}', :locals => {:who => 'World'}))
200
+ end
201
+
202
+ def test_escaped_actions
203
+ assert_equal("- Hello World\n", render('\\- Hello World'))
204
+ assert_equal("= Hello World\n", render('\\= Hello World'))
205
+ assert_equal("~ Hello World\n", render('\\~ Hello World'))
206
+ assert_equal("== Hello World\n", render('\\== Hello World'))
207
+ end
208
+
209
+ def test_escaped_actions_with_implicit_interpolation
210
+ assert_equal("= Hello World\n", render('\\= Hello #{who}', :locals => {:who => 'World'}))
211
+ end
212
+
213
+ def test_nil_tag_value_should_render_as_empty
214
+ assert_equal("<p></p>\n", render("%p= nil"))
215
+ end
216
+
217
+ def test_tag_with_failed_if_should_render_as_empty
218
+ assert_equal("<p></p>\n", render("%p= 'Hello' if false"))
219
+ end
220
+
221
+ def test_static_attributes_with_empty_attr
222
+ assert_equal("<img alt='' src='/foo.png' />\n", render("%img{:src => '/foo.png', :alt => ''}"))
223
+ end
224
+
225
+ def test_dynamic_attributes_with_empty_attr
226
+ assert_equal("<img alt='' src='/foo.png' />\n", render("%img{:width => nil, :src => '/foo.png', :alt => String.new}"))
227
+ end
228
+
229
+ def test_attribute_hash_with_newlines
230
+ assert_equal("<p a='b' c='d'>foop</p>\n", render("%p{:a => 'b',\n :c => 'd'} foop"))
231
+ assert_equal("<p a='b' c='d'>\n foop\n</p>\n", render("%p{:a => 'b',\n :c => 'd'}\n foop"))
232
+ assert_equal("<p a='b' c='d' />\n", render("%p{:a => 'b',\n :c => 'd'}/"))
233
+ assert_equal("<p a='b' c='d' e='f'></p>\n", render("%p{:a => 'b',\n :c => 'd',\n :e => 'f'}"))
234
+ end
235
+
236
+ def test_attr_hashes_not_modified
237
+ hash = {:color => 'red'}
238
+ assert_equal(<<HTML, render(<<HAML, :locals => {:hash => hash}))
239
+ <div color='red'></div>
240
+ <div class='special' color='red'></div>
241
+ <div color='red'></div>
242
+ HTML
243
+ %div{hash}
244
+ .special{hash}
245
+ %div{hash}
246
+ HAML
247
+ assert_equal(hash, {:color => 'red'})
248
+ end
249
+
250
+ def test_end_of_file_multiline
251
+ assert_equal("<p>0</p>\n<p>1</p>\n<p>2</p>\n", render("- for i in (0...3)\n %p= |\n i |"))
252
+ end
253
+
254
+ def test_cr_newline
255
+ 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"))
256
+ end
257
+
258
+ def test_textareas
259
+ assert_equal("<textarea>Foo&#x000A; bar&#x000A; baz</textarea>\n",
260
+ render('%textarea= "Foo\n bar\n baz"'))
261
+
262
+ assert_equal("<pre>Foo&#x000A; bar&#x000A; baz</pre>\n",
263
+ render('%pre= "Foo\n bar\n baz"'))
264
+
265
+ assert_equal("<textarea>#{'a' * 100}</textarea>\n",
266
+ render("%textarea #{'a' * 100}"))
267
+
268
+ assert_equal("<p>\n <textarea>Foo\n Bar\n Baz</textarea>\n</p>\n", render(<<SOURCE))
269
+ %p
270
+ %textarea
271
+ Foo
272
+ Bar
273
+ Baz
274
+ SOURCE
275
+ end
276
+
277
+ def test_boolean_attributes
278
+ assert_equal("<p bar baz='true' foo='bar'></p>\n",
279
+ render("%p{:foo => 'bar', :bar => true, :baz => 'true'}", :format => :html4))
280
+ assert_equal("<p bar='bar' baz='true' foo='bar'></p>\n",
281
+ render("%p{:foo => 'bar', :bar => true, :baz => 'true'}", :format => :xhtml))
282
+
283
+ assert_equal("<p baz='false' foo='bar'></p>\n",
284
+ render("%p{:foo => 'bar', :bar => false, :baz => 'false'}", :format => :html4))
285
+ assert_equal("<p baz='false' foo='bar'></p>\n",
286
+ render("%p{:foo => 'bar', :bar => false, :baz => 'false'}", :format => :xhtml))
287
+ end
288
+
289
+ def test_both_whitespace_nukes_work_together
290
+ assert_equal(<<RESULT, render(<<SOURCE))
291
+ <p><q>Foo
292
+ Bar</q></p>
293
+ RESULT
294
+ %p
295
+ %q><= "Foo\\nBar"
296
+ SOURCE
297
+ end #=>
298
+
299
+ # Regression tests
300
+
301
+ def test_whitespace_nuke_with_both_newlines
302
+ assert_equal("<p>foo</p>\n", render('%p<= "\nfoo\n"'))
303
+ assert_equal(<<HTML, render(<<HAML))
304
+ <p>
305
+ <p>foo</p>
306
+ </p>
307
+ HTML
308
+ %p
309
+ %p<= "\\nfoo\\n"
310
+ HAML
311
+ end
312
+
313
+ def test_both_case_indentation_work_with_deeply_nested_code
314
+ result = <<RESULT
315
+ <h2>
316
+ other
317
+ </h2>
318
+ RESULT
319
+ assert_equal(result, render(<<HAML))
320
+ - case 'other'
321
+ - when 'test'
322
+ %h2
323
+ hi
324
+ - when 'other'
325
+ %h2
326
+ other
327
+ HAML
328
+ assert_equal(result, render(<<HAML))
329
+ - case 'other'
330
+ - when 'test'
331
+ %h2
332
+ hi
333
+ - when 'other'
334
+ %h2
335
+ other
336
+ HAML
337
+ end
338
+
339
+ def test_equals_block_with_ugly
340
+ assert_equal("foo\n", render(<<HAML, :ugly => true))
341
+ = capture_haml do
342
+ foo
343
+ HAML
344
+ end
345
+
346
+ def test_plain_equals_with_ugly
347
+ assert_equal("foo\nbar\n", render(<<HAML, :ugly => true))
348
+ = "foo"
349
+ bar
350
+ HAML
351
+ end
352
+
353
+ def test_ampersand_equals_should_escape
354
+ assert_equal("<p>\n foo &amp; bar\n</p>\n", render("%p\n &= 'foo & bar'", :escape_html => false))
355
+ end
356
+
357
+ def test_ampersand_with_implicit_interpolation
358
+ assert_equal(<<HTML, render(<<HAML, :locals => {:complete => "bar"}, :escape_html => false))
359
+ <p>
360
+ foo &amp; bar
361
+ </p>
362
+ HTML
363
+ %p
364
+ & foo & \#{complete}
365
+ HAML
366
+ end
367
+
368
+ def test_ampersand_fallback
369
+ assert_equal(<<HTML, render(<<HAML, :escape_html => false))
370
+ <p>
371
+ &amp;
372
+ </p>
373
+ HTML
374
+ %p
375
+ &amp;
376
+ HAML
377
+ end
378
+
379
+ def test_ampersand_fallback_with_implicit_interpolation
380
+ assert_equal(<<HTML, render(<<HAML, :locals => {:complete => "bar"}, :escape_html => false))
381
+ <p>
382
+ &amp; foo bar
383
+ </p>
384
+ HTML
385
+ %p
386
+ &amp; foo \#{complete}
387
+ HAML
388
+ end
389
+
390
+ def test_bang_with_implicit_interpolation
391
+ assert_equal(<<HTML, render(<<HAML, :locals => {:complete => "bar"}, :escape_html => true))
392
+ <p>
393
+ foo & bar
394
+ </p>
395
+ HTML
396
+ %p
397
+ ! foo & \#{complete}
398
+ HAML
399
+ end
400
+
401
+ def test_bang_fallback
402
+ assert_equal(<<HTML, render(<<HAML, :locals => {:complete => "bar"}, :escape_html => false))
403
+ <p>
404
+ !bang
405
+ </p>
406
+ HTML
407
+ %p
408
+ !bang
409
+ HAML
410
+ end
411
+
412
+ def test_bang_fallback_with_implicit_interpolation
413
+ assert_equal(<<HTML, render(<<HAML, :locals => {:complete => "bar"}, :escape_html => false))
414
+ <p>
415
+ !bang foo bar
416
+ </p>
417
+ HTML
418
+ %p
419
+ !bang foo \#{complete}
420
+ HAML
421
+ end
422
+
423
+ def test_escape_with_implicit_interpolation
424
+ assert_equal(<<HTML, render(<<HAML, :locals => {:complete => "bar"}))
425
+ <p>
426
+ = foo bar
427
+ </p>
428
+ HTML
429
+ %p
430
+ \\= foo \#{complete}
431
+ HAML
432
+ end
433
+
434
+ def test_ampersand_equals_inline_should_escape
435
+ assert_equal("<p>foo &amp; bar</p>\n", render("%p&= 'foo & bar'", :escape_html => false))
436
+ end
437
+
438
+ def test_ampersand_interpolation_inline_should_escape
439
+ assert_equal("<p>foo &amp; bar</p>\n", render("%p&== foo & \#{complete}", :locals => {:complete => "bar"}, :escape_html => false))
440
+ end
441
+
442
+ def test_ampersand_implicit_interpolation_inline_should_escape
443
+ assert_equal("<p>foo bar</p>\n", render("%p& foo \#{complete}", :locals => {:complete => "bar"}, :escape_html => false))
444
+ end
445
+
446
+ def test_ampersand_equals_should_escape_before_preserve
447
+ assert_equal("<textarea>foo&#x000A;bar</textarea>\n", render('%textarea&= "foo\nbar"', :escape_html => false))
448
+ end
449
+
450
+ def test_bang_equals_should_not_escape
451
+ assert_equal("<p>\n foo & bar\n</p>\n", render("%p\n != 'foo & bar'", :escape_html => true))
452
+ end
453
+
454
+ def test_bang_equals_inline_should_not_escape
455
+ assert_equal("<p>foo & bar</p>\n", render("%p!= 'foo & bar'", :escape_html => true))
456
+ end
457
+
458
+ def test_static_attributes_should_be_escaped
459
+ assert_equal("<img class='atlantis' style='ugly&amp;stupid' />\n",
460
+ render("%img.atlantis{:style => 'ugly&stupid'}"))
461
+ assert_equal("<div class='atlantis' style='ugly&amp;stupid'>foo</div>\n",
462
+ render(".atlantis{:style => 'ugly&stupid'} foo"))
463
+ assert_equal("<p class='atlantis' style='ugly&amp;stupid'>foo</p>\n",
464
+ render("%p.atlantis{:style => 'ugly&stupid'}= 'foo'"))
465
+ assert_equal("<p class='atlantis' style='ugly&#x000A;stupid'></p>\n",
466
+ render("%p.atlantis{:style => \"ugly\\nstupid\"}"))
467
+ end
468
+
469
+ def test_dynamic_attributes_should_be_escaped
470
+ assert_equal("<img alt='' src='&amp;foo.png' />\n",
471
+ render("%img{:width => nil, :src => '&foo.png', :alt => String.new}"))
472
+ assert_equal("<p alt='' src='&amp;foo.png'>foo</p>\n",
473
+ render("%p{:width => nil, :src => '&foo.png', :alt => String.new} foo"))
474
+ assert_equal("<div alt='' src='&amp;foo.png'>foo</div>\n",
475
+ render("%div{:width => nil, :src => '&foo.png', :alt => String.new}= 'foo'"))
476
+ assert_equal("<img alt='' src='foo&#x000A;.png' />\n",
477
+ render("%img{:width => nil, :src => \"foo\\n.png\", :alt => String.new}"))
478
+ end
479
+
480
+ def test_string_interpolation_should_be_esaped
481
+ assert_equal("<p>4&amp;3</p>\n", render("%p== \#{2+2}&\#{2+1}", :escape_html => true))
482
+ assert_equal("<p>4&3</p>\n", render("%p== \#{2+2}&\#{2+1}", :escape_html => false))
483
+ end
484
+
485
+ def test_escaped_inline_string_interpolation
486
+ assert_equal("<p>4&amp;3</p>\n", render("%p&== \#{2+2}&\#{2+1}", :escape_html => true))
487
+ assert_equal("<p>4&amp;3</p>\n", render("%p&== \#{2+2}&\#{2+1}", :escape_html => false))
488
+ end
489
+
490
+ def test_unescaped_inline_string_interpolation
491
+ assert_equal("<p>4&3</p>\n", render("%p!== \#{2+2}&\#{2+1}", :escape_html => true))
492
+ assert_equal("<p>4&3</p>\n", render("%p!== \#{2+2}&\#{2+1}", :escape_html => false))
493
+ end
494
+
495
+ def test_escaped_string_interpolation
496
+ assert_equal("<p>\n 4&amp;3\n</p>\n", render("%p\n &== \#{2+2}&\#{2+1}", :escape_html => true))
497
+ assert_equal("<p>\n 4&amp;3\n</p>\n", render("%p\n &== \#{2+2}&\#{2+1}", :escape_html => false))
498
+ end
499
+
500
+ def test_unescaped_string_interpolation
501
+ assert_equal("<p>\n 4&3\n</p>\n", render("%p\n !== \#{2+2}&\#{2+1}", :escape_html => true))
502
+ assert_equal("<p>\n 4&3\n</p>\n", render("%p\n !== \#{2+2}&\#{2+1}", :escape_html => false))
503
+ end
504
+
505
+ def test_scripts_should_respect_escape_html_option
506
+ assert_equal("<p>\n foo &amp; bar\n</p>\n", render("%p\n = 'foo & bar'", :escape_html => true))
507
+ assert_equal("<p>\n foo & bar\n</p>\n", render("%p\n = 'foo & bar'", :escape_html => false))
508
+ end
509
+
510
+ def test_inline_scripts_should_respect_escape_html_option
511
+ assert_equal("<p>foo &amp; bar</p>\n", render("%p= 'foo & bar'", :escape_html => true))
512
+ assert_equal("<p>foo & bar</p>\n", render("%p= 'foo & bar'", :escape_html => false))
513
+ end
514
+
515
+ def test_script_ending_in_comment_should_render_when_html_is_escaped
516
+ assert_equal("foo&amp;bar\n", render("= 'foo&bar' #comment", :escape_html => true))
517
+ end
518
+
519
+ def test_script_with_if_shouldnt_output
520
+ assert_equal(<<HTML, render(<<HAML))
521
+ <p>foo</p>
522
+ <p></p>
523
+ HTML
524
+ %p= "foo"
525
+ %p= "bar" if false
526
+ HAML
527
+ end
528
+
529
+ # Options tests
530
+
531
+ def test_filename_and_line
532
+ begin
533
+ render("\n\n = abc", :filename => 'test', :line => 2)
534
+ rescue Exception => e
535
+ assert_kind_of Haml::SyntaxError, e
536
+ assert_match(/test:4/, e.backtrace.first)
537
+ end
538
+
539
+ begin
540
+ render("\n\n= 123\n\n= nil[]", :filename => 'test', :line => 2)
541
+ rescue Exception => e
542
+ assert_kind_of NoMethodError, e
543
+ assert_match(/test:6/, e.backtrace.first)
544
+ end
545
+ end
546
+
547
+ def test_stop_eval
548
+ assert_equal("", render("= 'Hello'", :suppress_eval => true))
549
+ assert_equal("", render("- haml_concat 'foo'", :suppress_eval => true))
550
+ assert_equal("<div id='foo' yes='no' />\n", render("#foo{:yes => 'no'}/", :suppress_eval => true))
551
+ assert_equal("<div id='foo' />\n", render("#foo{:yes => 'no', :call => a_function() }/", :suppress_eval => true))
552
+ assert_equal("<div />\n", render("%div[1]/", :suppress_eval => true))
553
+ assert_equal("", render(":ruby\n Kernel.puts 'hello'", :suppress_eval => true))
554
+ end
555
+
556
+ def test_doctypes
557
+ assert_equal('<!DOCTYPE html>',
558
+ render('!!!', :format => :html5).strip)
559
+ assert_equal('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
560
+ render('!!! strict').strip)
561
+ assert_equal('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
562
+ render('!!! frameset').strip)
563
+ assert_equal('<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">',
564
+ render('!!! mobile').strip)
565
+ assert_equal('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',
566
+ render('!!! basic').strip)
567
+ assert_equal('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
568
+ render('!!! transitional').strip)
569
+ assert_equal('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
570
+ render('!!!').strip)
571
+ assert_equal('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">',
572
+ render('!!! strict', :format => :html4).strip)
573
+ assert_equal('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">',
574
+ render('!!! frameset', :format => :html4).strip)
575
+ assert_equal('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',
576
+ render('!!! transitional', :format => :html4).strip)
577
+ assert_equal('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',
578
+ render('!!!', :format => :html4).strip)
579
+ end
580
+
581
+ def test_attr_wrapper
582
+ assert_equal("<p strange=*attrs*></p>\n", render("%p{ :strange => 'attrs'}", :attr_wrapper => '*'))
583
+ assert_equal("<p escaped='quo\"te'></p>\n", render("%p{ :escaped => 'quo\"te'}", :attr_wrapper => '"'))
584
+ assert_equal("<p escaped=\"quo'te\"></p>\n", render("%p{ :escaped => 'quo\\'te'}", :attr_wrapper => '"'))
585
+ assert_equal("<p escaped=\"q'uo&quot;te\"></p>\n", render("%p{ :escaped => 'q\\'uo\"te'}", :attr_wrapper => '"'))
586
+ assert_equal("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n", render("!!! XML", :attr_wrapper => '"'))
587
+ end
588
+
589
+ def test_attrs_parsed_correctly
590
+ assert_equal("<p boom=>biddly='bar =&gt; baz'></p>\n", render("%p{'boom=>biddly' => 'bar => baz'}"))
591
+ assert_equal("<p foo,bar='baz, qux'></p>\n", render("%p{'foo,bar' => 'baz, qux'}"))
592
+ assert_equal("<p escaped='quo&#x000A;te'></p>\n", render("%p{ :escaped => \"quo\\nte\"}"))
593
+ assert_equal("<p escaped='quo4te'></p>\n", render("%p{ :escaped => \"quo\#{2 + 2}te\"}"))
594
+ end
595
+
596
+ def test_correct_parsing_with_brackets
597
+ assert_equal("<p class='foo'>{tada} foo</p>\n", render("%p{:class => 'foo'} {tada} foo"))
598
+ assert_equal("<p class='foo'>deep {nested { things }}</p>\n", render("%p{:class => 'foo'} deep {nested { things }}"))
599
+ assert_equal("<p class='bar foo'>{a { d</p>\n", render("%p{{:class => 'foo'}, :class => 'bar'} {a { d"))
600
+ assert_equal("<p foo='bar'>a}</p>\n", render("%p{:foo => 'bar'} a}"))
601
+
602
+ foo = []
603
+ foo[0] = Struct.new('Foo', :id).new
604
+ assert_equal("<p class='struct_foo' id='struct_foo_new'>New User]</p>\n",
605
+ render("%p[foo[0]] New User]", :locals => {:foo => foo}))
606
+ assert_equal("<p class='prefix_struct_foo' id='prefix_struct_foo_new'>New User]</p>\n",
607
+ render("%p[foo[0], :prefix] New User]", :locals => {:foo => foo}))
608
+
609
+ foo[0].id = 1
610
+ assert_equal("<p class='struct_foo' id='struct_foo_1'>New User]</p>\n",
611
+ render("%p[foo[0]] New User]", :locals => {:foo => foo}))
612
+ assert_equal("<p class='prefix_struct_foo' id='prefix_struct_foo_1'>New User]</p>\n",
613
+ render("%p[foo[0], :prefix] New User]", :locals => {:foo => foo}))
614
+ end
615
+
616
+ def test_empty_attrs
617
+ assert_equal("<p attr=''>empty</p>\n", render("%p{ :attr => '' } empty"))
618
+ assert_equal("<p attr=''>empty</p>\n", render("%p{ :attr => x } empty", :locals => {:x => ''}))
619
+ end
620
+
621
+ def test_nil_attrs
622
+ assert_equal("<p>nil</p>\n", render("%p{ :attr => nil } nil"))
623
+ assert_equal("<p>nil</p>\n", render("%p{ :attr => x } nil", :locals => {:x => nil}))
624
+ end
625
+
626
+ def test_nil_id_with_syntactic_id
627
+ assert_equal("<p id='foo'>nil</p>\n", render("%p#foo{:id => nil} nil"))
628
+ assert_equal("<p id='foo_bar'>nil</p>\n", render("%p#foo{{:id => 'bar'}, :id => nil} nil"))
629
+ assert_equal("<p id='foo_bar'>nil</p>\n", render("%p#foo{{:id => nil}, :id => 'bar'} nil"))
630
+ end
631
+
632
+ def test_nil_class_with_syntactic_class
633
+ assert_equal("<p class='foo'>nil</p>\n", render("%p.foo{:class => nil} nil"))
634
+ assert_equal("<p class='bar foo'>nil</p>\n", render("%p.bar.foo{:class => nil} nil"))
635
+ assert_equal("<p class='bar foo'>nil</p>\n", render("%p.foo{{:class => 'bar'}, :class => nil} nil"))
636
+ assert_equal("<p class='bar foo'>nil</p>\n", render("%p.foo{{:class => nil}, :class => 'bar'} nil"))
637
+ end
638
+
639
+ def test_locals
640
+ assert_equal("<p>Paragraph!</p>\n", render("%p= text", :locals => { :text => "Paragraph!" }))
641
+ end
642
+
643
+ def test_dynamic_attrs_shouldnt_register_as_literal_values
644
+ assert_equal("<p a='b2c'></p>\n", render('%p{:a => "b#{1 + 1}c"}'))
645
+ assert_equal("<p a='b2c'></p>\n", render("%p{:a => 'b' + (1 + 1).to_s + 'c'}"))
646
+ end
647
+
648
+ def test_dynamic_attrs_with_self_closed_tag
649
+ assert_equal("<a b='2' />\nc\n", render("%a{'b' => 1 + 1}/\n= 'c'\n"))
650
+ end
651
+
652
+ def test_exceptions
653
+ EXCEPTION_MAP.each do |key, value|
654
+ begin
655
+ render(key, :filename => "(exception test for #{key.inspect})")
656
+ rescue Exception => err
657
+ value = [value] unless value.is_a?(Array)
658
+ expected_message, line_no = value
659
+ line_no ||= key.split("\n").length
660
+ line_reported = err.backtrace[0].gsub(/\(.+\):/, '').to_i
661
+
662
+ assert_equal(expected_message, err.message, "Line: #{key}")
663
+ assert_equal(line_no, line_reported, "Line: #{key}")
664
+ else
665
+ assert(false, "Exception not raised for\n#{key}")
666
+ end
667
+ end
668
+ end
669
+
670
+ def test_exception_line
671
+ render("a\nb\n!!!\n c\nd")
672
+ rescue Haml::SyntaxError => e
673
+ assert_equal("(test_exception_line):4", e.backtrace[0])
674
+ else
675
+ assert(false, '"a\nb\n!!!\n c\nd" doesn\'t produce an exception')
676
+ end
677
+
678
+ def test_exception
679
+ render("%p\n hi\n %a= undefined\n= 12")
680
+ rescue Exception => e
681
+ assert_match("(test_exception):3", e.backtrace[0])
682
+ else
683
+ # Test failed... should have raised an exception
684
+ assert(false)
685
+ end
686
+
687
+ def test_compile_error
688
+ render("a\nb\n- fee)\nc")
689
+ rescue Exception => e
690
+ assert_match(/\(test_compile_error\):3: syntax error/i, e.message)
691
+ else
692
+ assert(false,
693
+ '"a\nb\n- fee)\nc" doesn\'t produce an exception!')
694
+ end
695
+
696
+ def test_unbalanced_brackets
697
+ render('== #{1 + 5} foo #{6 + 7 bar #{8 + 9}')
698
+ rescue Haml::SyntaxError => e
699
+ assert_equal("Unbalanced brackets.", e.message)
700
+ end
701
+
702
+ def test_balanced_conditional_comments
703
+ assert_equal("<!--[if !(IE 6)|(IE 7)]> Bracket: ] <![endif]-->\n",
704
+ render("/[if !(IE 6)|(IE 7)] Bracket: ]"))
705
+ end
706
+
707
+ def test_empty_filter
708
+ assert_equal(<<END, render(':javascript'))
709
+ <script type='text/javascript'>
710
+ //<![CDATA[
711
+
712
+ //]]>
713
+ </script>
714
+ END
715
+ end
716
+
717
+ def test_ugly_filter
718
+ assert_equal(<<END, render(":sass\n #foo\n bar: baz", :ugly => true))
719
+ #foo {
720
+ bar: baz; }
721
+ END
722
+ end
723
+
724
+ def test_local_assigns_dont_modify_class
725
+ assert_equal("bar\n", render("= foo", :locals => {:foo => 'bar'}))
726
+ assert_equal(nil, defined?(foo))
727
+ end
728
+
729
+ def test_object_ref_with_nil_id
730
+ user = User.new
731
+ assert_equal("<p class='struct_user' id='struct_user_new'>New User</p>\n",
732
+ render("%p[user] New User", :locals => {:user => user}))
733
+ end
734
+
735
+ def test_object_ref_before_attrs
736
+ user = User.new 42
737
+ assert_equal("<p class='struct_user' id='struct_user_42' style='width: 100px;'>New User</p>\n",
738
+ render("%p[user]{:style => 'width: 100px;'} New User", :locals => {:user => user}))
739
+ end
740
+
741
+ def test_non_literal_attributes
742
+ assert_equal("<p a1='foo' a2='bar' a3='baz' />\n",
743
+ render("%p{a2, a1, :a3 => 'baz'}/",
744
+ :locals => {:a1 => {:a1 => 'foo'}, :a2 => {:a2 => 'bar'}}))
745
+ end
746
+
747
+ def test_render_should_accept_a_binding_as_scope
748
+ string = "This is a string!"
749
+ string.instance_variable_set("@var", "Instance variable")
750
+ b = string.instance_eval do
751
+ var = "Local variable"
752
+ binding
753
+ end
754
+
755
+ assert_equal("<p>THIS IS A STRING!</p>\n<p>Instance variable</p>\n<p>Local variable</p>\n",
756
+ render("%p= upcase\n%p= @var\n%p= var", :scope => b))
757
+ end
758
+
759
+ def test_yield_should_work_with_binding
760
+ assert_equal("12\nFOO\n", render("= yield\n= upcase", :scope => "foo".instance_eval{binding}) { 12 })
761
+ end
762
+
763
+ def test_yield_should_work_with_def_method
764
+ s = "foo"
765
+ engine("= yield\n= upcase").def_method(s, :render)
766
+ assert_equal("12\nFOO\n", s.render { 12 })
767
+ end
768
+
769
+ def test_def_method_with_module
770
+ engine("= yield\n= upcase").def_method(String, :render_haml)
771
+ assert_equal("12\nFOO\n", "foo".render_haml { 12 })
772
+ end
773
+
774
+ def test_def_method_locals
775
+ obj = Object.new
776
+ engine("%p= foo\n.bar{:baz => baz}= boom").def_method(obj, :render, :foo, :baz, :boom)
777
+ assert_equal("<p>1</p>\n<div baz='2' class='bar'>3</div>\n", obj.render(:foo => 1, :baz => 2, :boom => 3))
778
+ end
779
+
780
+ def test_render_proc_locals
781
+ proc = engine("%p= foo\n.bar{:baz => baz}= boom").render_proc(Object.new, :foo, :baz, :boom)
782
+ assert_equal("<p>1</p>\n<div baz='2' class='bar'>3</div>\n", proc[:foo => 1, :baz => 2, :boom => 3])
783
+ end
784
+
785
+ def test_render_proc_with_binding
786
+ assert_equal("FOO\n", engine("= upcase").render_proc("foo".instance_eval{binding}).call)
787
+ end
788
+
789
+ def test_ugly_true
790
+ assert_equal("<div id='outer'>\n<div id='inner'>\n<p>hello world</p>\n</div>\n</div>\n",
791
+ render("#outer\n #inner\n %p hello world", :ugly => true))
792
+
793
+ assert_equal("<p>#{'s' * 75}</p>\n",
794
+ render("%p #{'s' * 75}", :ugly => true))
795
+
796
+ assert_equal("<p>#{'s' * 75}</p>\n",
797
+ render("%p= 's' * 75", :ugly => true))
798
+ end
799
+
800
+ def test_auto_preserve_unless_ugly
801
+ assert_equal("<pre>foo&#x000A;bar</pre>\n", render('%pre="foo\nbar"'))
802
+ assert_equal("<pre>foo\nbar</pre>\n", render("%pre\n foo\n bar"))
803
+ assert_equal("<pre>foo\nbar</pre>\n", render('%pre="foo\nbar"', :ugly => true))
804
+ assert_equal("<pre>foo\nbar</pre>\n", render("%pre\n foo\n bar", :ugly => true))
805
+ end
806
+
807
+ def test_xhtml_output_option
808
+ assert_equal "<p>\n <br />\n</p>\n", render("%p\n %br", :format => :xhtml)
809
+ assert_equal "<a />\n", render("%a/", :format => :xhtml)
810
+ end
811
+
812
+ def test_arbitrary_output_option
813
+ assert_raise(Haml::Error, "Invalid output format :html1") { engine("%br", :format => :html1) }
814
+ end
815
+
816
+ # HTML 4.0
817
+
818
+ def test_html_has_no_self_closing_tags
819
+ assert_equal "<p>\n <br>\n</p>\n", render("%p\n %br", :format => :html4)
820
+ assert_equal "<br>\n", render("%br/", :format => :html4)
821
+ end
822
+
823
+ def test_html_renders_empty_node_with_closing_tag
824
+ assert_equal "<div class='foo'></div>\n", render(".foo", :format => :html4)
825
+ end
826
+
827
+ def test_html_doesnt_add_slash_to_self_closing_tags
828
+ assert_equal "<a>\n", render("%a/", :format => :html4)
829
+ assert_equal "<a foo='2'>\n", render("%a{:foo => 1 + 1}/", :format => :html4)
830
+ assert_equal "<meta>\n", render("%meta", :format => :html4)
831
+ assert_equal "<meta foo='2'>\n", render("%meta{:foo => 1 + 1}", :format => :html4)
832
+ end
833
+
834
+ def test_html_ignores_xml_prolog_declaration
835
+ assert_equal "", render('!!! XML', :format => :html4)
836
+ end
837
+
838
+ def test_html_has_different_doctype
839
+ assert_equal %{<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n},
840
+ render('!!!', :format => :html4)
841
+ end
842
+
843
+ # because anything before the doctype triggers quirks mode in IE
844
+ def test_xml_prolog_and_doctype_dont_result_in_a_leading_whitespace_in_html
845
+ assert_no_match(/^\s+/, render("!!! xml\n!!!", :format => :html4))
846
+ end
847
+
848
+ # HTML5
849
+ def test_html5_doctype
850
+ assert_equal %{<!DOCTYPE html>\n}, render('!!!', :format => :html5)
851
+ end
852
+ end