drnic-haml 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (190) hide show
  1. data/.yardopts +5 -0
  2. data/CONTRIBUTING +4 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +347 -0
  5. data/REVISION +1 -0
  6. data/Rakefile +371 -0
  7. data/VERSION +1 -0
  8. data/VERSION_NAME +1 -0
  9. data/bin/css2sass +7 -0
  10. data/bin/haml +9 -0
  11. data/bin/html2haml +7 -0
  12. data/bin/sass +8 -0
  13. data/extra/haml-mode.el +663 -0
  14. data/extra/sass-mode.el +205 -0
  15. data/extra/update_watch.rb +13 -0
  16. data/init.rb +8 -0
  17. data/lib/haml.rb +40 -0
  18. data/lib/haml/buffer.rb +307 -0
  19. data/lib/haml/engine.rb +301 -0
  20. data/lib/haml/error.rb +22 -0
  21. data/lib/haml/exec.rb +470 -0
  22. data/lib/haml/filters.rb +341 -0
  23. data/lib/haml/helpers.rb +560 -0
  24. data/lib/haml/helpers/action_view_extensions.rb +40 -0
  25. data/lib/haml/helpers/action_view_mods.rb +176 -0
  26. data/lib/haml/herb.rb +96 -0
  27. data/lib/haml/html.rb +308 -0
  28. data/lib/haml/precompiler.rb +997 -0
  29. data/lib/haml/shared.rb +78 -0
  30. data/lib/haml/template.rb +51 -0
  31. data/lib/haml/template/patch.rb +58 -0
  32. data/lib/haml/template/plugin.rb +71 -0
  33. data/lib/haml/util.rb +244 -0
  34. data/lib/haml/version.rb +64 -0
  35. data/lib/sass.rb +24 -0
  36. data/lib/sass/css.rb +423 -0
  37. data/lib/sass/engine.rb +491 -0
  38. data/lib/sass/environment.rb +79 -0
  39. data/lib/sass/error.rb +162 -0
  40. data/lib/sass/files.rb +133 -0
  41. data/lib/sass/plugin.rb +170 -0
  42. data/lib/sass/plugin/merb.rb +57 -0
  43. data/lib/sass/plugin/rails.rb +23 -0
  44. data/lib/sass/repl.rb +58 -0
  45. data/lib/sass/script.rb +55 -0
  46. data/lib/sass/script/bool.rb +17 -0
  47. data/lib/sass/script/color.rb +183 -0
  48. data/lib/sass/script/funcall.rb +50 -0
  49. data/lib/sass/script/functions.rb +199 -0
  50. data/lib/sass/script/lexer.rb +191 -0
  51. data/lib/sass/script/literal.rb +177 -0
  52. data/lib/sass/script/node.rb +14 -0
  53. data/lib/sass/script/number.rb +381 -0
  54. data/lib/sass/script/operation.rb +45 -0
  55. data/lib/sass/script/parser.rb +222 -0
  56. data/lib/sass/script/string.rb +12 -0
  57. data/lib/sass/script/unary_operation.rb +34 -0
  58. data/lib/sass/script/variable.rb +31 -0
  59. data/lib/sass/tree/comment_node.rb +84 -0
  60. data/lib/sass/tree/debug_node.rb +30 -0
  61. data/lib/sass/tree/directive_node.rb +70 -0
  62. data/lib/sass/tree/for_node.rb +48 -0
  63. data/lib/sass/tree/if_node.rb +54 -0
  64. data/lib/sass/tree/import_node.rb +69 -0
  65. data/lib/sass/tree/mixin_def_node.rb +29 -0
  66. data/lib/sass/tree/mixin_node.rb +48 -0
  67. data/lib/sass/tree/node.rb +252 -0
  68. data/lib/sass/tree/prop_node.rb +106 -0
  69. data/lib/sass/tree/root_node.rb +56 -0
  70. data/lib/sass/tree/rule_node.rb +220 -0
  71. data/lib/sass/tree/variable_node.rb +34 -0
  72. data/lib/sass/tree/while_node.rb +31 -0
  73. data/rails/init.rb +1 -0
  74. data/test/benchmark.rb +99 -0
  75. data/test/haml/engine_test.rb +1129 -0
  76. data/test/haml/helper_test.rb +282 -0
  77. data/test/haml/html2haml_test.rb +258 -0
  78. data/test/haml/markaby/standard.mab +52 -0
  79. data/test/haml/mocks/article.rb +6 -0
  80. data/test/haml/results/content_for_layout.xhtml +12 -0
  81. data/test/haml/results/eval_suppressed.xhtml +9 -0
  82. data/test/haml/results/filters.xhtml +62 -0
  83. data/test/haml/results/helpers.xhtml +93 -0
  84. data/test/haml/results/helpful.xhtml +10 -0
  85. data/test/haml/results/just_stuff.xhtml +68 -0
  86. data/test/haml/results/list.xhtml +12 -0
  87. data/test/haml/results/nuke_inner_whitespace.xhtml +40 -0
  88. data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
  89. data/test/haml/results/original_engine.xhtml +20 -0
  90. data/test/haml/results/partial_layout.xhtml +5 -0
  91. data/test/haml/results/partials.xhtml +21 -0
  92. data/test/haml/results/render_layout.xhtml +3 -0
  93. data/test/haml/results/silent_script.xhtml +74 -0
  94. data/test/haml/results/standard.xhtml +162 -0
  95. data/test/haml/results/tag_parsing.xhtml +23 -0
  96. data/test/haml/results/very_basic.xhtml +5 -0
  97. data/test/haml/results/whitespace_handling.xhtml +89 -0
  98. data/test/haml/rhtml/_av_partial_1.rhtml +12 -0
  99. data/test/haml/rhtml/_av_partial_2.rhtml +8 -0
  100. data/test/haml/rhtml/action_view.rhtml +62 -0
  101. data/test/haml/rhtml/standard.rhtml +54 -0
  102. data/test/haml/spec_test.rb +44 -0
  103. data/test/haml/template_test.rb +217 -0
  104. data/test/haml/templates/_av_partial_1.haml +9 -0
  105. data/test/haml/templates/_av_partial_1_ugly.haml +9 -0
  106. data/test/haml/templates/_av_partial_2.haml +5 -0
  107. data/test/haml/templates/_av_partial_2_ugly.haml +5 -0
  108. data/test/haml/templates/_layout.erb +3 -0
  109. data/test/haml/templates/_layout_for_partial.haml +3 -0
  110. data/test/haml/templates/_partial.haml +8 -0
  111. data/test/haml/templates/_text_area.haml +3 -0
  112. data/test/haml/templates/action_view.haml +47 -0
  113. data/test/haml/templates/action_view_ugly.haml +47 -0
  114. data/test/haml/templates/breakage.haml +8 -0
  115. data/test/haml/templates/content_for_layout.haml +8 -0
  116. data/test/haml/templates/eval_suppressed.haml +11 -0
  117. data/test/haml/templates/filters.haml +66 -0
  118. data/test/haml/templates/helpers.haml +95 -0
  119. data/test/haml/templates/helpful.haml +11 -0
  120. data/test/haml/templates/just_stuff.haml +83 -0
  121. data/test/haml/templates/list.haml +12 -0
  122. data/test/haml/templates/nuke_inner_whitespace.haml +32 -0
  123. data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
  124. data/test/haml/templates/original_engine.haml +17 -0
  125. data/test/haml/templates/partial_layout.haml +3 -0
  126. data/test/haml/templates/partialize.haml +1 -0
  127. data/test/haml/templates/partials.haml +12 -0
  128. data/test/haml/templates/render_layout.haml +2 -0
  129. data/test/haml/templates/silent_script.haml +40 -0
  130. data/test/haml/templates/standard.haml +42 -0
  131. data/test/haml/templates/standard_ugly.haml +42 -0
  132. data/test/haml/templates/tag_parsing.haml +21 -0
  133. data/test/haml/templates/very_basic.haml +4 -0
  134. data/test/haml/templates/whitespace_handling.haml +87 -0
  135. data/test/haml/util_test.rb +92 -0
  136. data/test/linked_rails.rb +12 -0
  137. data/test/sass/css2sass_test.rb +294 -0
  138. data/test/sass/engine_test.rb +956 -0
  139. data/test/sass/functions_test.rb +126 -0
  140. data/test/sass/more_results/more1.css +9 -0
  141. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  142. data/test/sass/more_results/more_import.css +29 -0
  143. data/test/sass/more_templates/_more_partial.sass +2 -0
  144. data/test/sass/more_templates/more1.sass +23 -0
  145. data/test/sass/more_templates/more_import.sass +11 -0
  146. data/test/sass/plugin_test.rb +229 -0
  147. data/test/sass/results/alt.css +4 -0
  148. data/test/sass/results/basic.css +9 -0
  149. data/test/sass/results/compact.css +5 -0
  150. data/test/sass/results/complex.css +87 -0
  151. data/test/sass/results/compressed.css +1 -0
  152. data/test/sass/results/expanded.css +19 -0
  153. data/test/sass/results/import.css +29 -0
  154. data/test/sass/results/line_numbers.css +49 -0
  155. data/test/sass/results/mixins.css +95 -0
  156. data/test/sass/results/multiline.css +24 -0
  157. data/test/sass/results/nested.css +22 -0
  158. data/test/sass/results/parent_ref.css +13 -0
  159. data/test/sass/results/script.css +16 -0
  160. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  161. data/test/sass/results/subdir/subdir.css +3 -0
  162. data/test/sass/results/units.css +11 -0
  163. data/test/sass/script_test.rb +261 -0
  164. data/test/sass/templates/_partial.sass +2 -0
  165. data/test/sass/templates/alt.sass +16 -0
  166. data/test/sass/templates/basic.sass +23 -0
  167. data/test/sass/templates/bork1.sass +2 -0
  168. data/test/sass/templates/bork2.sass +2 -0
  169. data/test/sass/templates/bork3.sass +2 -0
  170. data/test/sass/templates/compact.sass +17 -0
  171. data/test/sass/templates/complex.sass +307 -0
  172. data/test/sass/templates/compressed.sass +15 -0
  173. data/test/sass/templates/expanded.sass +17 -0
  174. data/test/sass/templates/import.sass +11 -0
  175. data/test/sass/templates/importee.sass +19 -0
  176. data/test/sass/templates/line_numbers.sass +13 -0
  177. data/test/sass/templates/mixins.sass +76 -0
  178. data/test/sass/templates/multiline.sass +20 -0
  179. data/test/sass/templates/nested.sass +25 -0
  180. data/test/sass/templates/nested_bork1.sass +2 -0
  181. data/test/sass/templates/nested_bork2.sass +2 -0
  182. data/test/sass/templates/nested_bork3.sass +2 -0
  183. data/test/sass/templates/parent_ref.sass +25 -0
  184. data/test/sass/templates/script.sass +101 -0
  185. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  186. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  187. data/test/sass/templates/subdir/subdir.sass +6 -0
  188. data/test/sass/templates/units.sass +11 -0
  189. data/test/test_helper.rb +44 -0
  190. metadata +298 -0
@@ -0,0 +1,220 @@
1
+ require 'pathname'
2
+
3
+ module Sass::Tree
4
+ class RuleNode < Node
5
+ # The character used to include the parent selector
6
+ PARENT = '&'
7
+
8
+ # The CSS selectors for this rule.
9
+ # Each string is a selector line, and the lines are meant to be separated by commas.
10
+ # For example,
11
+ #
12
+ # foo, bar, baz,
13
+ # bip, bop, bup
14
+ #
15
+ # would be
16
+ #
17
+ # ["foo, bar, baz",
18
+ # "bip, bop, bup"]
19
+ #
20
+ # @return [Array<String>]
21
+ attr_accessor :rules
22
+
23
+ # The CSS selectors for this rule,
24
+ # parsed for commas and parent-references.
25
+ # It's only set once {Tree::Node#perform} has been called.
26
+ #
27
+ # It's an array of arrays of arrays.
28
+ # The first level of arrays represents distinct lines in the Sass file;
29
+ # the second level represents comma-separated selectors;
30
+ # the third represents structure within those selectors,
31
+ # currently only parent-refs (represented by `:parent`).
32
+ # For example,
33
+ #
34
+ # &.foo, bar, baz,
35
+ # bip, &.bop, bup
36
+ #
37
+ # would be
38
+ #
39
+ # [[[:parent, "foo"], ["bar"], ["baz"]],
40
+ # [["bip"], [:parent, "bop"], ["bup"]]]
41
+ #
42
+ # @return [Array<Array<Array<String|Symbol>>>]
43
+ attr_accessor :parsed_rules
44
+
45
+ # @param rule [String] The first CSS rule. See \{#rules}
46
+ def initialize(rule)
47
+ @rules = [rule]
48
+ super()
49
+ end
50
+
51
+ # Compares the contents of two rules.
52
+ #
53
+ # @param other [Object] The object to compare with
54
+ # @return [Boolean] Whether or not this node and the other object
55
+ # are the same
56
+ def ==(other)
57
+ self.class == other.class && rules == other.rules && super
58
+ end
59
+
60
+ # Adds another {RuleNode}'s rules to this one's.
61
+ #
62
+ # @param node [RuleNode] The other node
63
+ def add_rules(node)
64
+ @rules += node.rules
65
+ end
66
+
67
+ # @return [Boolean] Whether or not this rule is continued on the next line
68
+ def continued?
69
+ @rules.last[-1] == ?,
70
+ end
71
+
72
+ protected
73
+
74
+ # Computes the CSS for the rule.
75
+ #
76
+ # @param tabs [Fixnum] The level of indentation for the CSS
77
+ # @param super_rules [Array<Array<String>>] The rules for the parent node
78
+ # (see \{#rules}), or `nil` if there are no parents
79
+ # @return [String] The resulting CSS
80
+ # @raise [Sass::SyntaxError] if the rule has no parents but uses `&`
81
+ def _to_s(tabs, super_rules = nil)
82
+ resolved_rules = resolve_parent_refs(super_rules)
83
+
84
+ properties = []
85
+ sub_rules = []
86
+
87
+ rule_separator = style == :compressed ? ',' : ', '
88
+ line_separator = [:nested, :expanded].include?(style) ? ",\n" : rule_separator
89
+ rule_indent = ' ' * (tabs - 1)
90
+ per_rule_indent, total_indent = [:nested, :expanded].include?(style) ? [rule_indent, ''] : ['', rule_indent]
91
+
92
+ total_rule = total_indent + resolved_rules.map do |line|
93
+ per_rule_indent + line.join(rule_separator)
94
+ end.join(line_separator)
95
+
96
+ children.each do |child|
97
+ next if child.invisible?
98
+ if child.is_a? RuleNode
99
+ sub_rules << child
100
+ else
101
+ properties << child
102
+ end
103
+ end
104
+
105
+ to_return = ''
106
+ if !properties.empty?
107
+ old_spaces = ' ' * (tabs - 1)
108
+ spaces = ' ' * tabs
109
+ if @options[:line_comments] && style != :compressed
110
+ to_return << "#{old_spaces}/* line #{line}"
111
+
112
+ if filename
113
+ relative_filename = if @options[:css_filename]
114
+ begin
115
+ Pathname.new(filename).relative_path_from(
116
+ Pathname.new(File.dirname(@options[:css_filename]))).to_s
117
+ rescue ArgumentError
118
+ nil
119
+ end
120
+ end
121
+ relative_filename ||= filename
122
+ to_return << ", #{relative_filename}"
123
+ end
124
+
125
+ to_return << " */\n"
126
+ end
127
+
128
+ if style == :compact
129
+ properties = properties.map { |a| a.to_s(1) }.select{|a| a && a.length > 0}.join(' ')
130
+ to_return << "#{total_rule} { #{properties} }\n"
131
+ elsif style == :compressed
132
+ properties = properties.map { |a| a.to_s(1) }.select{|a| a && a.length > 0}.join(';')
133
+ to_return << "#{total_rule}{#{properties}}"
134
+ else
135
+ properties = properties.map { |a| a.to_s(tabs + 1) }.select{|a| a && a.length > 0}.join("\n")
136
+ end_props = (style == :expanded ? "\n" + old_spaces : ' ')
137
+ to_return << "#{total_rule} {\n#{properties}#{end_props}}\n"
138
+ end
139
+ end
140
+
141
+ tabs += 1 unless properties.empty? || style != :nested
142
+ sub_rules.each do |sub|
143
+ to_return << sub.to_s(tabs, resolved_rules)
144
+ end
145
+
146
+ to_return
147
+ end
148
+
149
+ # Runs any SassScript that may be embedded in the rule,
150
+ # and parses the selectors for commas.
151
+ #
152
+ # @param environment [Sass::Environment] The lexical environment containing
153
+ # variable and mixin values
154
+ def perform!(environment)
155
+ @parsed_rules = @rules.map {|r| parse_selector(interpolate(r, environment))}
156
+ super
157
+ end
158
+
159
+ private
160
+
161
+ def resolve_parent_refs(super_rules)
162
+ if super_rules.nil?
163
+ return @parsed_rules.map do |line|
164
+ line.map do |rule|
165
+ if rule.include?(:parent)
166
+ raise Sass::SyntaxError.new("Base-level rules cannot contain the parent-selector-referencing character '#{PARENT}'.")
167
+ end
168
+
169
+ rule.join
170
+ end.compact
171
+ end
172
+ end
173
+
174
+ new_rules = []
175
+ super_rules.each do |super_line|
176
+ @parsed_rules.each do |line|
177
+ new_rules << []
178
+
179
+ super_line.each do |super_rule|
180
+ line.each do |rule|
181
+ rule = [:parent, " ", *rule] unless rule.include?(:parent)
182
+
183
+ new_rules.last << rule.map do |segment|
184
+ next segment unless segment == :parent
185
+ super_rule
186
+ end.join
187
+ end
188
+ end
189
+ end
190
+ end
191
+ new_rules
192
+ end
193
+
194
+ def parse_selector(text)
195
+ scanner = StringScanner.new(text)
196
+ rules = [[]]
197
+
198
+ while scanner.rest?
199
+ rules.last << scanner.scan(/[^",&]*/)
200
+ case scanner.scan(/./)
201
+ when '&'; rules.last << :parent
202
+ when ','
203
+ scanner.scan(/\s*/)
204
+ rules << [] if scanner.rest?
205
+ when '"'
206
+ rules.last << '"' << scanner.scan(/([^"\\]|\\.)*/)
207
+ # We don't want to enforce that strings are closed,
208
+ # but we do want to consume quotes or trailing backslashes.
209
+ rules.last << scanner.scan(/./) if scanner.rest?
210
+ end
211
+ end
212
+
213
+ rules.map! do |l|
214
+ Haml::Util.merge_adjacent_strings(l).reject {|r| r.is_a?(String) && r.empty?}
215
+ end
216
+
217
+ rules
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,34 @@
1
+ module Sass
2
+ module Tree
3
+ # A dynamic node representing a variable definition.
4
+ #
5
+ # @see Sass::Tree
6
+ class VariableNode < Node
7
+ # @param name [String] The name of the variable
8
+ # @param expr [Script::Node] The parse tree for the initial variable value
9
+ # @param guarded [Boolean] Whether this is a guarded variable assignment (`||=`)
10
+ def initialize(name, expr, guarded)
11
+ @name = name
12
+ @expr = expr
13
+ @guarded = guarded
14
+ super()
15
+ end
16
+
17
+ protected
18
+
19
+ # Loads the new variable value into the environment.
20
+ #
21
+ # @param environment [Sass::Environment] The lexical environment containing
22
+ # variable and mixin values
23
+ def _perform(environment)
24
+ if @guarded && environment.var(@name).nil?
25
+ environment.set_var(@name, @expr.perform(environment))
26
+ elsif !@guarded
27
+ environment.set_var(@name, @expr.perform(environment))
28
+ end
29
+
30
+ []
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ require 'sass/tree/node'
2
+
3
+ module Sass::Tree
4
+ # A dynamic node representing a Sass `@while` loop.
5
+ #
6
+ # @see Sass::Tree
7
+ class WhileNode < Node
8
+ # @param expr [Script::Node] The parse tree for the continue expression
9
+ def initialize(expr)
10
+ @expr = expr
11
+ super()
12
+ end
13
+
14
+ protected
15
+
16
+ # Runs the child nodes until the continue expression becomes false.
17
+ #
18
+ # @param environment [Sass::Environment] The lexical environment containing
19
+ # variable and mixin values
20
+ # @return [Array<Tree::Node>] The resulting static nodes
21
+ # @see Sass::Tree
22
+ def _perform(environment)
23
+ children = []
24
+ new_environment = Sass::Environment.new(environment)
25
+ while @expr.perform(environment).to_bool
26
+ children += perform_children(new_environment)
27
+ end
28
+ children
29
+ end
30
+ end
31
+ 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,1129 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+ require File.dirname(__FILE__) + '/../test_helper'
4
+
5
+ class EngineTest < Test::Unit::TestCase
6
+ # A map of erroneous Haml documents to the error messages they should produce.
7
+ # The error messages may be arrays;
8
+ # if so, the second element should be the line number that should be reported for the error.
9
+ # If this isn't provided, the tests will assume the line number should be the last line of the document.
10
+ EXCEPTION_MAP = {
11
+ "!!!\n a" => "Illegal nesting: nesting within a header command is illegal.",
12
+ "a\n b" => "Illegal nesting: nesting within plain text is illegal.",
13
+ "/ a\n b" => "Illegal nesting: nesting within a tag that already has content is illegal.",
14
+ "% a" => 'Invalid tag: "% a".',
15
+ "%p a\n b" => "Illegal nesting: content can't be both given on the same line as %p and nested within it.",
16
+ "%p=" => "There's no Ruby code for = to evaluate.",
17
+ "%p~" => "There's no Ruby code for ~ to evaluate.",
18
+ "~" => "There's no Ruby code for ~ to evaluate.",
19
+ "=" => "There's no Ruby code for = to evaluate.",
20
+ "%p/\n a" => "Illegal nesting: nesting within a self-closing tag is illegal.",
21
+ ":a\n b" => ['Filter "a" is not defined.', 1],
22
+ ":a= b" => 'Invalid filter name ":a= b".',
23
+ "." => "Illegal element: classes and ids must have values.",
24
+ ".#" => "Illegal element: classes and ids must have values.",
25
+ ".{} a" => "Illegal element: classes and ids must have values.",
26
+ ".() a" => "Illegal element: classes and ids must have values.",
27
+ ".= a" => "Illegal element: classes and ids must have values.",
28
+ "%p..a" => "Illegal element: classes and ids must have values.",
29
+ "%a/ b" => "Self-closing tags can't have content.",
30
+ "%p{:a => 'b',\n:c => 'd'}/ e" => ["Self-closing tags can't have content.", 2],
31
+ "%p{:a => 'b',\n:c => 'd'}=" => ["There's no Ruby code for = to evaluate.", 2],
32
+ "%p.{:a => 'b',\n:c => 'd'} e" => ["Illegal element: classes and ids must have values.", 1],
33
+ "%p{:a => 'b',\n:c => 'd',\n:e => 'f'}\n%p/ a" => ["Self-closing tags can't have content.", 4],
34
+ "%p{:a => 'b',\n:c => 'd',\n:e => 'f'}\n- raise 'foo'" => ["foo", 4],
35
+ "%p{:a => 'b',\n:c => raise('foo'),\n:e => 'f'}" => ["foo", 2],
36
+ "%p{:a => 'b',\n:c => 'd',\n:e => raise('foo')}" => ["foo", 3],
37
+ " %p foo" => "Indenting at the beginning of the document is illegal.",
38
+ " %p foo" => "Indenting at the beginning of the document is illegal.",
39
+ "- end" => "You don't need to use \"- end\" in Haml. Use indentation instead:\n- if foo?\n %strong Foo!\n- else\n Not foo.",
40
+ " \n\t\n %p foo" => ["Indenting at the beginning of the document is illegal.", 3],
41
+ "\n\n %p foo" => ["Indenting at the beginning of the document is illegal.", 3],
42
+ "%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],
43
+ "%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],
44
+ "%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],
45
+ "%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],
46
+ "%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],
47
+ "%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],
48
+ "%p\n foo\n%p\n bar" => ["The line was indented 2 levels deeper than the previous line.", 4],
49
+ "%p\n foo\n %p\n bar" => ["The line was indented 3 levels deeper than the previous line.", 4],
50
+ "%p\n \tfoo" => ["Indentation can't use both tabs and spaces.", 2],
51
+ "%p(" => "Invalid attribute list: \"(\".",
52
+ "%p(foo=\nbar)" => ["Invalid attribute list: \"(foo=\".", 1],
53
+ "%p(foo=)" => "Invalid attribute list: \"(foo=)\".",
54
+ "%p(foo 'bar')" => "Invalid attribute list: \"(foo 'bar')\".",
55
+ "%p(foo 'bar'\nbaz='bang')" => ["Invalid attribute list: \"(foo 'bar'\".", 1],
56
+ "%p(foo='bar'\nbaz 'bang'\nbip='bop')" => ["Invalid attribute list: \"(foo='bar' baz 'bang'\".", 2],
57
+ "%p{:foo => 'bar' :bar => 'baz'}" => :compile,
58
+ "%p{:foo => }" => :compile,
59
+ "%p{=> 'bar'}" => :compile,
60
+ "%p{:foo => 'bar}" => :compile,
61
+ "%p{'foo => 'bar'}" => :compile,
62
+ "%p{:foo => 'bar\"}" => :compile,
63
+
64
+ # Regression tests
65
+ "- raise 'foo'\n\n\n\nbar" => ["foo", 1],
66
+ "= 'foo'\n-raise 'foo'" => ["foo", 2],
67
+ "\n\n\n- raise 'foo'" => ["foo", 4],
68
+ "%p foo |\n bar |\n baz |\nbop\n- raise 'foo'" => ["foo", 5],
69
+ "foo\n\n\n bar" => ["Illegal nesting: nesting within plain text is illegal.", 4],
70
+ "%p/\n\n bar" => ["Illegal nesting: nesting within a self-closing tag is illegal.", 3],
71
+ "%p foo\n\n bar" => ["Illegal nesting: content can't be both given on the same line as %p and nested within it.", 3],
72
+ "/ foo\n\n bar" => ["Illegal nesting: nesting within a tag that already has content is illegal.", 3],
73
+ "!!!\n\n bar" => ["Illegal nesting: nesting within a header command is illegal.", 3],
74
+ "foo\n:ruby\n 1\n 2\n 3\n- raise 'foo'" => ["foo", 6],
75
+ }
76
+
77
+ User = Struct.new('User', :id)
78
+ class CustomHamlClass < Struct.new(:id)
79
+ def haml_object_ref
80
+ "my_thing"
81
+ end
82
+ end
83
+
84
+ def render(text, options = {}, &block)
85
+ scope = options.delete(:scope) || Object.new
86
+ locals = options.delete(:locals) || {}
87
+ engine(text, options).to_html(scope, locals, &block)
88
+ end
89
+
90
+ def engine(text, options = {})
91
+ unless options[:filename]
92
+ # use caller method name as fake filename. useful for debugging
93
+ i = -1
94
+ caller[i+=1] =~ /`(.+?)'/ until $1 and $1.index('test_') == 0
95
+ options[:filename] = "(#{$1})"
96
+ end
97
+ Haml::Engine.new(text, options)
98
+ end
99
+
100
+ def test_empty_render
101
+ assert_equal "", render("")
102
+ end
103
+
104
+ def test_flexible_tabulation
105
+ assert_equal("<p>\n foo\n</p>\n<q>\n bar\n <a>\n baz\n </a>\n</q>\n",
106
+ render("%p\n foo\n%q\n bar\n %a\n baz"))
107
+ assert_equal("<p>\n foo\n</p>\n<q>\n bar\n <a>\n baz\n </a>\n</q>\n",
108
+ render("%p\n\tfoo\n%q\n\tbar\n\t%a\n\t\tbaz"))
109
+ assert_equal("<p>\n \t \t bar\n baz\n</p>\n",
110
+ render("%p\n :plain\n \t \t bar\n baz"))
111
+ end
112
+
113
+ def test_empty_render_should_remain_empty
114
+ assert_equal('', render(''))
115
+ end
116
+
117
+ def test_attributes_should_render_correctly
118
+ assert_equal("<div class='atlantis' style='ugly'></div>", render(".atlantis{:style => 'ugly'}").chomp)
119
+ end
120
+
121
+ def test_css_id_as_attribute_should_be_appended_with_underscore
122
+ assert_equal("<div id='my_id_1'></div>", render("#my_id{:id => '1'}").chomp)
123
+ assert_equal("<div id='my_id_1'></div>", render("#my_id{:id => 1}").chomp)
124
+ end
125
+
126
+ def test_ruby_code_should_work_inside_attributes
127
+ author = 'hcatlin'
128
+ assert_equal("<p class='3'>foo</p>", render("%p{:class => 1+2} foo").chomp)
129
+ end
130
+
131
+ def test_nil_should_render_empty_tag
132
+ assert_equal("<div class='no_attributes'></div>",
133
+ render(".no_attributes{:nil => nil}").chomp)
134
+ end
135
+
136
+ def test_strings_should_get_stripped_inside_tags
137
+ assert_equal("<div class='stripped'>This should have no spaces in front of it</div>",
138
+ render(".stripped This should have no spaces in front of it").chomp)
139
+ end
140
+
141
+ def test_one_liner_should_be_one_line
142
+ assert_equal("<p>Hello</p>", render('%p Hello').chomp)
143
+ end
144
+
145
+ def test_one_liner_with_newline_shouldnt_be_one_line
146
+ assert_equal("<p>\n foo\n bar\n</p>", render('%p= "foo\nbar"').chomp)
147
+ end
148
+
149
+ def test_multi_render
150
+ engine = engine("%strong Hi there!")
151
+ assert_equal("<strong>Hi there!</strong>\n", engine.to_html)
152
+ assert_equal("<strong>Hi there!</strong>\n", engine.to_html)
153
+ assert_equal("<strong>Hi there!</strong>\n", engine.to_html)
154
+ end
155
+
156
+ def test_interpolation
157
+ assert_equal("<p>Hello World</p>\n", render('%p Hello #{who}', :locals => {:who => 'World'}))
158
+ assert_equal("<p>\n Hello World\n</p>\n", render("%p\n Hello \#{who}", :locals => {:who => 'World'}))
159
+ end
160
+
161
+ def test_interpolation_in_the_middle_of_a_string
162
+ assert_equal("\"title 'Title'. \"\n",
163
+ render("\"title '\#{\"Title\"}'. \""))
164
+ end
165
+
166
+ def test_interpolation_at_the_beginning_of_a_line
167
+ assert_equal("<p>2</p>\n", render('%p #{1 + 1}'))
168
+ assert_equal("<p>\n 2\n</p>\n", render("%p\n \#{1 + 1}"))
169
+ end
170
+
171
+ def test_nil_tag_value_should_render_as_empty
172
+ assert_equal("<p></p>\n", render("%p= nil"))
173
+ end
174
+
175
+ def test_tag_with_failed_if_should_render_as_empty
176
+ assert_equal("<p></p>\n", render("%p= 'Hello' if false"))
177
+ end
178
+
179
+ def test_static_attributes_with_empty_attr
180
+ assert_equal("<img alt='' src='/foo.png' />\n", render("%img{:src => '/foo.png', :alt => ''}"))
181
+ end
182
+
183
+ def test_dynamic_attributes_with_empty_attr
184
+ assert_equal("<img alt='' src='/foo.png' />\n", render("%img{:width => nil, :src => '/foo.png', :alt => String.new}"))
185
+ end
186
+
187
+ def test_attribute_hash_with_newlines
188
+ assert_equal("<p a='b' c='d'>foop</p>\n", render("%p{:a => 'b',\n :c => 'd'} foop"))
189
+ assert_equal("<p a='b' c='d'>\n foop\n</p>\n", render("%p{:a => 'b',\n :c => 'd'}\n foop"))
190
+ assert_equal("<p a='b' c='d' />\n", render("%p{:a => 'b',\n :c => 'd'}/"))
191
+ assert_equal("<p a='b' c='d' e='f'></p>\n", render("%p{:a => 'b',\n :c => 'd',\n :e => 'f'}"))
192
+ end
193
+
194
+ def test_attr_hashes_not_modified
195
+ hash = {:color => 'red'}
196
+ assert_equal(<<HTML, render(<<HAML, :locals => {:hash => hash}))
197
+ <div color='red'></div>
198
+ <div class='special' color='red'></div>
199
+ <div color='red'></div>
200
+ HTML
201
+ %div{hash}
202
+ .special{hash}
203
+ %div{hash}
204
+ HAML
205
+ assert_equal(hash, {:color => 'red'})
206
+ end
207
+
208
+ def test_end_of_file_multiline
209
+ assert_equal("<p>0</p>\n<p>1</p>\n<p>2</p>\n", render("- for i in (0...3)\n %p= |\n i |"))
210
+ end
211
+
212
+ def test_cr_newline
213
+ 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"))
214
+ end
215
+
216
+ def test_textareas
217
+ assert_equal("<textarea>Foo&#x000A; bar&#x000A; baz</textarea>\n",
218
+ render('%textarea= "Foo\n bar\n baz"'))
219
+
220
+ assert_equal("<pre>Foo&#x000A; bar&#x000A; baz</pre>\n",
221
+ render('%pre= "Foo\n bar\n baz"'))
222
+
223
+ assert_equal("<textarea>#{'a' * 100}</textarea>\n",
224
+ render("%textarea #{'a' * 100}"))
225
+
226
+ assert_equal("<p>\n <textarea>Foo\n Bar\n Baz</textarea>\n</p>\n", render(<<SOURCE))
227
+ %p
228
+ %textarea
229
+ Foo
230
+ Bar
231
+ Baz
232
+ SOURCE
233
+ end
234
+
235
+ def test_boolean_attributes
236
+ assert_equal("<p bar baz='true' foo='bar'></p>\n",
237
+ render("%p{:foo => 'bar', :bar => true, :baz => 'true'}", :format => :html4))
238
+ assert_equal("<p bar='bar' baz='true' foo='bar'></p>\n",
239
+ render("%p{:foo => 'bar', :bar => true, :baz => 'true'}", :format => :xhtml))
240
+
241
+ assert_equal("<p baz='false' foo='bar'></p>\n",
242
+ render("%p{:foo => 'bar', :bar => false, :baz => 'false'}", :format => :html4))
243
+ assert_equal("<p baz='false' foo='bar'></p>\n",
244
+ render("%p{:foo => 'bar', :bar => false, :baz => 'false'}", :format => :xhtml))
245
+ end
246
+
247
+ def test_both_whitespace_nukes_work_together
248
+ assert_equal(<<RESULT, render(<<SOURCE))
249
+ <p><q>Foo
250
+ Bar</q></p>
251
+ RESULT
252
+ %p
253
+ %q><= "Foo\\nBar"
254
+ SOURCE
255
+ end
256
+
257
+ # Regression tests
258
+
259
+ def test_whitespace_nuke_with_both_newlines
260
+ assert_equal("<p>foo</p>\n", render('%p<= "\nfoo\n"'))
261
+ assert_equal(<<HTML, render(<<HAML))
262
+ <p>
263
+ <p>foo</p>
264
+ </p>
265
+ HTML
266
+ %p
267
+ %p<= "\\nfoo\\n"
268
+ HAML
269
+ end
270
+
271
+ def test_both_case_indentation_work_with_deeply_nested_code
272
+ result = <<RESULT
273
+ <h2>
274
+ other
275
+ </h2>
276
+ RESULT
277
+ assert_equal(result, render(<<HAML))
278
+ - case 'other'
279
+ - when 'test'
280
+ %h2
281
+ hi
282
+ - when 'other'
283
+ %h2
284
+ other
285
+ HAML
286
+ assert_equal(result, render(<<HAML))
287
+ - case 'other'
288
+ - when 'test'
289
+ %h2
290
+ hi
291
+ - when 'other'
292
+ %h2
293
+ other
294
+ HAML
295
+ end
296
+
297
+ def test_equals_block_with_ugly
298
+ assert_equal("foo\n", render(<<HAML, :ugly => true))
299
+ = capture_haml do
300
+ foo
301
+ HAML
302
+ end
303
+
304
+ def test_plain_equals_with_ugly
305
+ assert_equal("foo\nbar\n", render(<<HAML, :ugly => true))
306
+ = "foo"
307
+ bar
308
+ HAML
309
+ end
310
+
311
+ def test_inline_if
312
+ assert_equal(<<HTML, render(<<HAML))
313
+ <p>One</p>
314
+ <p></p>
315
+ <p>Three</p>
316
+ HTML
317
+ - for name in ["One", "Two", "Three"]
318
+ %p= name unless name == "Two"
319
+ HAML
320
+ end
321
+
322
+ def test_end_with_method_call
323
+ assert_equal(<<HTML, render(<<HAML))
324
+ 2|3|4
325
+ b-a-r
326
+ HTML
327
+ = [1, 2, 3].map do |i|
328
+ - i + 1
329
+ - end.join("|")
330
+ = "bar".gsub(/./) do |s|
331
+ - s + "-"
332
+ - end.gsub(/-$/) do |s|
333
+ - ''
334
+ HAML
335
+ end
336
+
337
+ def test_silent_end_with_stuff
338
+ assert_equal(<<HTML, render(<<HAML))
339
+ e
340
+ d
341
+ c
342
+ b
343
+ a
344
+ HTML
345
+ - str = "abcde"
346
+ - if true
347
+ = str.slice!(-1).chr
348
+ - end until str.empty?
349
+ HAML
350
+
351
+ assert_equal(<<HTML, render(<<HAML))
352
+ <p>hi!</p>
353
+ HTML
354
+ - if true
355
+ %p hi!
356
+ - end if "foo".gsub(/f/) do
357
+ - "z"
358
+ - end + "bar"
359
+ HAML
360
+ end
361
+
362
+ def test_multiline_with_colon_after_filter
363
+ assert_equal(<<HTML, render(<<HAML))
364
+ Foo
365
+ Bar
366
+ HTML
367
+ :plain
368
+ Foo
369
+ = { :a => "Bar", |
370
+ :b => "Baz" }[:a] |
371
+ HAML
372
+ assert_equal(<<HTML, render(<<HAML))
373
+
374
+ Bar
375
+ HTML
376
+ :plain
377
+ = { :a => "Bar", |
378
+ :b => "Baz" }[:a] |
379
+ HAML
380
+ end
381
+
382
+ def test_multiline_in_filter
383
+ assert_equal(<<HTML, render(<<HAML))
384
+ Foo |
385
+ Bar |
386
+ Baz
387
+ HTML
388
+ :plain
389
+ Foo |
390
+ Bar |
391
+ Baz
392
+ HAML
393
+ end
394
+
395
+ def test_curly_brace
396
+ assert_equal(<<HTML, render(<<HAML))
397
+ Foo { Bar
398
+ HTML
399
+ == Foo { Bar
400
+ HAML
401
+ end
402
+
403
+ def test_escape_html
404
+ html = <<HTML
405
+ &amp;
406
+ &
407
+ &amp;
408
+ HTML
409
+
410
+ assert_equal(html, render(<<HAML, :escape_html => true))
411
+ &= "&"
412
+ != "&"
413
+ = "&"
414
+ HAML
415
+
416
+ assert_equal(html, render(<<HAML, :escape_html => true))
417
+ &~ "&"
418
+ !~ "&"
419
+ ~ "&"
420
+ HAML
421
+
422
+ assert_equal(html, render(<<HAML, :escape_html => true))
423
+ & \#{"&"}
424
+ ! \#{"&"}
425
+ \#{"&"}
426
+ HAML
427
+
428
+ assert_equal(html, render(<<HAML, :escape_html => true))
429
+ &== \#{"&"}
430
+ !== \#{"&"}
431
+ == \#{"&"}
432
+ HAML
433
+
434
+ tag_html = <<HTML
435
+ <p>&amp;</p>
436
+ <p>&</p>
437
+ <p>&amp;</p>
438
+ HTML
439
+
440
+ assert_equal(tag_html, render(<<HAML, :escape_html => true))
441
+ %p&= "&"
442
+ %p!= "&"
443
+ %p= "&"
444
+ HAML
445
+
446
+ assert_equal(tag_html, render(<<HAML, :escape_html => true))
447
+ %p&~ "&"
448
+ %p!~ "&"
449
+ %p~ "&"
450
+ HAML
451
+
452
+ assert_equal(tag_html, render(<<HAML, :escape_html => true))
453
+ %p& \#{"&"}
454
+ %p! \#{"&"}
455
+ %p \#{"&"}
456
+ HAML
457
+
458
+ assert_equal(tag_html, render(<<HAML, :escape_html => true))
459
+ %p&== \#{"&"}
460
+ %p!== \#{"&"}
461
+ %p== \#{"&"}
462
+ HAML
463
+ end
464
+
465
+ def test_new_attrs_with_hash
466
+ assert_equal("<a href='#'></a>\n", render('%a(href="#")'))
467
+ end
468
+
469
+ def test_javascript_filter_with_dynamic_interp_and_escape_html
470
+ assert_equal(<<HTML, render(<<HAML, :escape_html => true))
471
+ <script type='text/javascript'>
472
+ //<![CDATA[
473
+ & < > &
474
+ //]]>
475
+ </script>
476
+ HTML
477
+ :javascript
478
+ & < > \#{"&"}
479
+ HAML
480
+ end
481
+
482
+ def test_silent_script_with_hyphen_case
483
+ assert_equal("", render("- 'foo-case-bar-case'"))
484
+ end
485
+
486
+ def test_silent_script_with_hyphen_end
487
+ assert_equal("", render("- 'foo-end-bar-end'"))
488
+ end
489
+
490
+ def test_silent_script_with_hyphen_end_and_block
491
+ assert_equal(<<HTML, render(<<HAML))
492
+ <p>foo-end</p>
493
+ <p>bar-end</p>
494
+ HTML
495
+ - "foo-end-bar-end".gsub(/\\w+-end/) do |s|
496
+ %p= s
497
+ HAML
498
+ end
499
+
500
+ # HTML escaping tests
501
+
502
+ def test_ampersand_equals_should_escape
503
+ assert_equal("<p>\n foo &amp; bar\n</p>\n", render("%p\n &= 'foo & bar'", :escape_html => false))
504
+ end
505
+
506
+ def test_ampersand_equals_inline_should_escape
507
+ assert_equal("<p>foo &amp; bar</p>\n", render("%p&= 'foo & bar'", :escape_html => false))
508
+ end
509
+
510
+ def test_ampersand_equals_should_escape_before_preserve
511
+ assert_equal("<textarea>foo&#x000A;bar</textarea>\n", render('%textarea&= "foo\nbar"', :escape_html => false))
512
+ end
513
+
514
+ def test_bang_equals_should_not_escape
515
+ assert_equal("<p>\n foo & bar\n</p>\n", render("%p\n != 'foo & bar'", :escape_html => true))
516
+ end
517
+
518
+ def test_bang_equals_inline_should_not_escape
519
+ assert_equal("<p>foo & bar</p>\n", render("%p!= 'foo & bar'", :escape_html => true))
520
+ end
521
+
522
+ def test_static_attributes_should_be_escaped
523
+ assert_equal("<img class='atlantis' style='ugly&amp;stupid' />\n",
524
+ render("%img.atlantis{:style => 'ugly&stupid'}"))
525
+ assert_equal("<div class='atlantis' style='ugly&amp;stupid'>foo</div>\n",
526
+ render(".atlantis{:style => 'ugly&stupid'} foo"))
527
+ assert_equal("<p class='atlantis' style='ugly&amp;stupid'>foo</p>\n",
528
+ render("%p.atlantis{:style => 'ugly&stupid'}= 'foo'"))
529
+ assert_equal("<p class='atlantis' style='ugly&#x000A;stupid'></p>\n",
530
+ render("%p.atlantis{:style => \"ugly\\nstupid\"}"))
531
+ end
532
+
533
+ def test_dynamic_attributes_should_be_escaped
534
+ assert_equal("<img alt='' src='&amp;foo.png' />\n",
535
+ render("%img{:width => nil, :src => '&foo.png', :alt => String.new}"))
536
+ assert_equal("<p alt='' src='&amp;foo.png'>foo</p>\n",
537
+ render("%p{:width => nil, :src => '&foo.png', :alt => String.new} foo"))
538
+ assert_equal("<div alt='' src='&amp;foo.png'>foo</div>\n",
539
+ render("%div{:width => nil, :src => '&foo.png', :alt => String.new}= 'foo'"))
540
+ assert_equal("<img alt='' src='foo&#x000A;.png' />\n",
541
+ render("%img{:width => nil, :src => \"foo\\n.png\", :alt => String.new}"))
542
+ end
543
+
544
+ def test_string_double_equals_should_be_esaped
545
+ assert_equal("<p>4&amp;3</p>\n", render("%p== \#{2+2}&\#{2+1}", :escape_html => true))
546
+ assert_equal("<p>4&3</p>\n", render("%p== \#{2+2}&\#{2+1}", :escape_html => false))
547
+ end
548
+
549
+ def test_escaped_inline_string_double_equals
550
+ assert_equal("<p>4&amp;3</p>\n", render("%p&== \#{2+2}&\#{2+1}", :escape_html => true))
551
+ assert_equal("<p>4&amp;3</p>\n", render("%p&== \#{2+2}&\#{2+1}", :escape_html => false))
552
+ end
553
+
554
+ def test_unescaped_inline_string_double_equals
555
+ assert_equal("<p>4&3</p>\n", render("%p!== \#{2+2}&\#{2+1}", :escape_html => true))
556
+ assert_equal("<p>4&3</p>\n", render("%p!== \#{2+2}&\#{2+1}", :escape_html => false))
557
+ end
558
+
559
+ def test_escaped_string_double_equals
560
+ assert_equal("<p>\n 4&amp;3\n</p>\n", render("%p\n &== \#{2+2}&\#{2+1}", :escape_html => true))
561
+ assert_equal("<p>\n 4&amp;3\n</p>\n", render("%p\n &== \#{2+2}&\#{2+1}", :escape_html => false))
562
+ end
563
+
564
+ def test_unescaped_string_double_equals
565
+ assert_equal("<p>\n 4&3\n</p>\n", render("%p\n !== \#{2+2}&\#{2+1}", :escape_html => true))
566
+ assert_equal("<p>\n 4&3\n</p>\n", render("%p\n !== \#{2+2}&\#{2+1}", :escape_html => false))
567
+ end
568
+
569
+ def test_string_interpolation_should_be_esaped
570
+ assert_equal("<p>4&amp;3</p>\n", render("%p \#{2+2}&\#{2+1}", :escape_html => true))
571
+ assert_equal("<p>4&3</p>\n", render("%p \#{2+2}&\#{2+1}", :escape_html => false))
572
+ end
573
+
574
+ def test_escaped_inline_string_interpolation
575
+ assert_equal("<p>4&amp;3</p>\n", render("%p& \#{2+2}&\#{2+1}", :escape_html => true))
576
+ assert_equal("<p>4&amp;3</p>\n", render("%p& \#{2+2}&\#{2+1}", :escape_html => false))
577
+ end
578
+
579
+ def test_unescaped_inline_string_interpolation
580
+ assert_equal("<p>4&3</p>\n", render("%p! \#{2+2}&\#{2+1}", :escape_html => true))
581
+ assert_equal("<p>4&3</p>\n", render("%p! \#{2+2}&\#{2+1}", :escape_html => false))
582
+ end
583
+
584
+ def test_escaped_string_interpolation
585
+ assert_equal("<p>\n 4&amp;3\n</p>\n", render("%p\n & \#{2+2}&\#{2+1}", :escape_html => true))
586
+ assert_equal("<p>\n 4&amp;3\n</p>\n", render("%p\n & \#{2+2}&\#{2+1}", :escape_html => false))
587
+ end
588
+
589
+ def test_unescaped_string_interpolation
590
+ assert_equal("<p>\n 4&3\n</p>\n", render("%p\n ! \#{2+2}&\#{2+1}", :escape_html => true))
591
+ assert_equal("<p>\n 4&3\n</p>\n", render("%p\n ! \#{2+2}&\#{2+1}", :escape_html => false))
592
+ end
593
+
594
+ def test_scripts_should_respect_escape_html_option
595
+ assert_equal("<p>\n foo &amp; bar\n</p>\n", render("%p\n = 'foo & bar'", :escape_html => true))
596
+ assert_equal("<p>\n foo & bar\n</p>\n", render("%p\n = 'foo & bar'", :escape_html => false))
597
+ end
598
+
599
+ def test_inline_scripts_should_respect_escape_html_option
600
+ assert_equal("<p>foo &amp; bar</p>\n", render("%p= 'foo & bar'", :escape_html => true))
601
+ assert_equal("<p>foo & bar</p>\n", render("%p= 'foo & bar'", :escape_html => false))
602
+ end
603
+
604
+ def test_script_ending_in_comment_should_render_when_html_is_escaped
605
+ assert_equal("foo&amp;bar\n", render("= 'foo&bar' #comment", :escape_html => true))
606
+ end
607
+
608
+ def test_script_with_if_shouldnt_output
609
+ assert_equal(<<HTML, render(<<HAML))
610
+ <p>foo</p>
611
+ <p></p>
612
+ HTML
613
+ %p= "foo"
614
+ %p= "bar" if false
615
+ HAML
616
+ end
617
+
618
+ # Options tests
619
+
620
+ def test_filename_and_line
621
+ begin
622
+ render("\n\n = abc", :filename => 'test', :line => 2)
623
+ rescue Exception => e
624
+ assert_kind_of Haml::SyntaxError, e
625
+ assert_match(/test:4/, e.backtrace.first)
626
+ end
627
+
628
+ begin
629
+ render("\n\n= 123\n\n= nil[]", :filename => 'test', :line => 2)
630
+ rescue Exception => e
631
+ assert_kind_of NoMethodError, e
632
+ assert_match(/test:6/, e.backtrace.first)
633
+ end
634
+ end
635
+
636
+ def test_stop_eval
637
+ assert_equal("", render("= 'Hello'", :suppress_eval => true))
638
+ assert_equal("", render("- haml_concat 'foo'", :suppress_eval => true))
639
+ assert_equal("<div id='foo' yes='no' />\n", render("#foo{:yes => 'no'}/", :suppress_eval => true))
640
+ assert_equal("<div id='foo' />\n", render("#foo{:yes => 'no', :call => a_function() }/", :suppress_eval => true))
641
+ assert_equal("<div />\n", render("%div[1]/", :suppress_eval => true))
642
+ assert_equal("", render(":ruby\n Kernel.puts 'hello'", :suppress_eval => true))
643
+ end
644
+
645
+ def test_doctypes
646
+ assert_equal('<!DOCTYPE html>',
647
+ render('!!!', :format => :html5).strip)
648
+ assert_equal('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
649
+ render('!!! strict').strip)
650
+ assert_equal('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
651
+ render('!!! frameset').strip)
652
+ assert_equal('<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">',
653
+ render('!!! mobile').strip)
654
+ assert_equal('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',
655
+ render('!!! basic').strip)
656
+ assert_equal('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
657
+ render('!!! transitional').strip)
658
+ assert_equal('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
659
+ render('!!!').strip)
660
+ assert_equal('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">',
661
+ render('!!! strict', :format => :html4).strip)
662
+ assert_equal('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">',
663
+ render('!!! frameset', :format => :html4).strip)
664
+ assert_equal('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',
665
+ render('!!! transitional', :format => :html4).strip)
666
+ assert_equal('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',
667
+ render('!!!', :format => :html4).strip)
668
+ end
669
+
670
+ def test_attr_wrapper
671
+ assert_equal("<p strange=*attrs*></p>\n", render("%p{ :strange => 'attrs'}", :attr_wrapper => '*'))
672
+ assert_equal("<p escaped='quo\"te'></p>\n", render("%p{ :escaped => 'quo\"te'}", :attr_wrapper => '"'))
673
+ assert_equal("<p escaped=\"quo'te\"></p>\n", render("%p{ :escaped => 'quo\\'te'}", :attr_wrapper => '"'))
674
+ assert_equal("<p escaped=\"q'uo&quot;te\"></p>\n", render("%p{ :escaped => 'q\\'uo\"te'}", :attr_wrapper => '"'))
675
+ assert_equal("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n", render("!!! XML", :attr_wrapper => '"'))
676
+ end
677
+
678
+ def test_attrs_parsed_correctly
679
+ assert_equal("<p boom=>biddly='bar =&gt; baz'></p>\n", render("%p{'boom=>biddly' => 'bar => baz'}"))
680
+ assert_equal("<p foo,bar='baz, qux'></p>\n", render("%p{'foo,bar' => 'baz, qux'}"))
681
+ assert_equal("<p escaped='quo&#x000A;te'></p>\n", render("%p{ :escaped => \"quo\\nte\"}"))
682
+ assert_equal("<p escaped='quo4te'></p>\n", render("%p{ :escaped => \"quo\#{2 + 2}te\"}"))
683
+ end
684
+
685
+ def test_correct_parsing_with_brackets
686
+ assert_equal("<p class='foo'>{tada} foo</p>\n", render("%p{:class => 'foo'} {tada} foo"))
687
+ assert_equal("<p class='foo'>deep {nested { things }}</p>\n", render("%p{:class => 'foo'} deep {nested { things }}"))
688
+ assert_equal("<p class='bar foo'>{a { d</p>\n", render("%p{{:class => 'foo'}, :class => 'bar'} {a { d"))
689
+ assert_equal("<p foo='bar'>a}</p>\n", render("%p{:foo => 'bar'} a}"))
690
+
691
+ foo = []
692
+ foo[0] = Struct.new('Foo', :id).new
693
+ assert_equal("<p class='struct_foo' id='struct_foo_new'>New User]</p>\n",
694
+ render("%p[foo[0]] New User]", :locals => {:foo => foo}))
695
+ assert_equal("<p class='prefix_struct_foo' id='prefix_struct_foo_new'>New User]</p>\n",
696
+ render("%p[foo[0], :prefix] New User]", :locals => {:foo => foo}))
697
+
698
+ foo[0].id = 1
699
+ assert_equal("<p class='struct_foo' id='struct_foo_1'>New User]</p>\n",
700
+ render("%p[foo[0]] New User]", :locals => {:foo => foo}))
701
+ assert_equal("<p class='prefix_struct_foo' id='prefix_struct_foo_1'>New User]</p>\n",
702
+ render("%p[foo[0], :prefix] New User]", :locals => {:foo => foo}))
703
+ end
704
+
705
+ def test_empty_attrs
706
+ assert_equal("<p attr=''>empty</p>\n", render("%p{ :attr => '' } empty"))
707
+ assert_equal("<p attr=''>empty</p>\n", render("%p{ :attr => x } empty", :locals => {:x => ''}))
708
+ end
709
+
710
+ def test_nil_attrs
711
+ assert_equal("<p>nil</p>\n", render("%p{ :attr => nil } nil"))
712
+ assert_equal("<p>nil</p>\n", render("%p{ :attr => x } nil", :locals => {:x => nil}))
713
+ end
714
+
715
+ def test_nil_id_with_syntactic_id
716
+ assert_equal("<p id='foo'>nil</p>\n", render("%p#foo{:id => nil} nil"))
717
+ assert_equal("<p id='foo_bar'>nil</p>\n", render("%p#foo{{:id => 'bar'}, :id => nil} nil"))
718
+ assert_equal("<p id='foo_bar'>nil</p>\n", render("%p#foo{{:id => nil}, :id => 'bar'} nil"))
719
+ end
720
+
721
+ def test_nil_class_with_syntactic_class
722
+ assert_equal("<p class='foo'>nil</p>\n", render("%p.foo{:class => nil} nil"))
723
+ assert_equal("<p class='bar foo'>nil</p>\n", render("%p.bar.foo{:class => nil} nil"))
724
+ assert_equal("<p class='bar foo'>nil</p>\n", render("%p.foo{{:class => 'bar'}, :class => nil} nil"))
725
+ assert_equal("<p class='bar foo'>nil</p>\n", render("%p.foo{{:class => nil}, :class => 'bar'} nil"))
726
+ end
727
+
728
+ def test_locals
729
+ assert_equal("<p>Paragraph!</p>\n", render("%p= text", :locals => { :text => "Paragraph!" }))
730
+ end
731
+
732
+ def test_dynamic_attrs_shouldnt_register_as_literal_values
733
+ assert_equal("<p a='b2c'></p>\n", render('%p{:a => "b#{1 + 1}c"}'))
734
+ assert_equal("<p a='b2c'></p>\n", render("%p{:a => 'b' + (1 + 1).to_s + 'c'}"))
735
+ end
736
+
737
+ def test_dynamic_attrs_with_self_closed_tag
738
+ assert_equal("<a b='2' />\nc\n", render("%a{'b' => 1 + 1}/\n= 'c'\n"))
739
+ end
740
+
741
+ EXCEPTION_MAP.each do |key, value|
742
+ define_method("test_exception (#{key.inspect})") do
743
+ begin
744
+ render(key, :filename => __FILE__)
745
+ rescue Exception => err
746
+ value = [value] unless value.is_a?(Array)
747
+ expected_message, line_no = value
748
+ line_no ||= key.split("\n").length
749
+
750
+ if expected_message == :compile
751
+ assert_match(/^compile error\n/, err.message, "Line: #{key}")
752
+ else
753
+ assert_equal(expected_message, err.message, "Line: #{key}")
754
+ end
755
+
756
+ assert_match(/^#{Regexp.escape(__FILE__)}:#{line_no}/, err.backtrace[0], "Line: #{key}")
757
+ else
758
+ assert(false, "Exception not raised for\n#{key}")
759
+ end
760
+ end
761
+ end
762
+
763
+ def test_exception_line
764
+ render("a\nb\n!!!\n c\nd")
765
+ rescue Haml::SyntaxError => e
766
+ assert_equal("(test_exception_line):4", e.backtrace[0])
767
+ else
768
+ assert(false, '"a\nb\n!!!\n c\nd" doesn\'t produce an exception')
769
+ end
770
+
771
+ def test_exception
772
+ render("%p\n hi\n %a= undefined\n= 12")
773
+ rescue Exception => e
774
+ assert_match("(test_exception):3", e.backtrace[0])
775
+ else
776
+ # Test failed... should have raised an exception
777
+ assert(false)
778
+ end
779
+
780
+ def test_compile_error
781
+ render("a\nb\n- fee)\nc")
782
+ rescue Exception => e
783
+ assert_match(/\(test_compile_error\):3: syntax error/i, e.message)
784
+ else
785
+ assert(false,
786
+ '"a\nb\n- fee)\nc" doesn\'t produce an exception!')
787
+ end
788
+
789
+ def test_unbalanced_brackets
790
+ render('foo #{1 + 5} foo #{6 + 7 bar #{8 + 9}')
791
+ rescue Haml::SyntaxError => e
792
+ assert_equal("Unbalanced brackets.", e.message)
793
+ end
794
+
795
+ def test_balanced_conditional_comments
796
+ assert_equal("<!--[if !(IE 6)|(IE 7)]> Bracket: ] <![endif]-->\n",
797
+ render("/[if !(IE 6)|(IE 7)] Bracket: ]"))
798
+ end
799
+
800
+ def test_empty_filter
801
+ assert_equal(<<END, render(':javascript'))
802
+ <script type='text/javascript'>
803
+ //<![CDATA[
804
+
805
+ //]]>
806
+ </script>
807
+ END
808
+ end
809
+
810
+ def test_ugly_filter
811
+ assert_equal(<<END, render(":sass\n #foo\n bar: baz", :ugly => true))
812
+ #foo {
813
+ bar: baz; }
814
+ END
815
+ end
816
+
817
+ def test_local_assigns_dont_modify_class
818
+ assert_equal("bar\n", render("= foo", :locals => {:foo => 'bar'}))
819
+ assert_equal(nil, defined?(foo))
820
+ end
821
+
822
+ def test_object_ref_with_nil_id
823
+ user = User.new
824
+ assert_equal("<p class='struct_user' id='struct_user_new'>New User</p>\n",
825
+ render("%p[user] New User", :locals => {:user => user}))
826
+ end
827
+
828
+ def test_object_ref_before_attrs
829
+ user = User.new 42
830
+ assert_equal("<p class='struct_user' id='struct_user_42' style='width: 100px;'>New User</p>\n",
831
+ render("%p[user]{:style => 'width: 100px;'} New User", :locals => {:user => user}))
832
+ end
833
+
834
+ def test_object_ref_with_custom_haml_class
835
+ custom = CustomHamlClass.new 42
836
+ assert_equal("<p class='my_thing' id='my_thing_42' style='width: 100px;'>My Thing</p>\n",
837
+ render("%p[custom]{:style => 'width: 100px;'} My Thing", :locals => {:custom => custom}))
838
+ end
839
+
840
+ def test_non_literal_attributes
841
+ assert_equal("<p a1='foo' a2='bar' a3='baz' />\n",
842
+ render("%p{a2, a1, :a3 => 'baz'}/",
843
+ :locals => {:a1 => {:a1 => 'foo'}, :a2 => {:a2 => 'bar'}}))
844
+ end
845
+
846
+ def test_render_should_accept_a_binding_as_scope
847
+ string = "This is a string!"
848
+ string.instance_variable_set("@var", "Instance variable")
849
+ b = string.instance_eval do
850
+ var = "Local variable"
851
+ binding
852
+ end
853
+
854
+ assert_equal("<p>THIS IS A STRING!</p>\n<p>Instance variable</p>\n<p>Local variable</p>\n",
855
+ render("%p= upcase\n%p= @var\n%p= var", :scope => b))
856
+ end
857
+
858
+ def test_yield_should_work_with_binding
859
+ assert_equal("12\nFOO\n", render("= yield\n= upcase", :scope => "foo".instance_eval{binding}) { 12 })
860
+ end
861
+
862
+ def test_yield_should_work_with_def_method
863
+ s = "foo"
864
+ engine("= yield\n= upcase").def_method(s, :render)
865
+ assert_equal("12\nFOO\n", s.render { 12 })
866
+ end
867
+
868
+ def test_def_method_with_module
869
+ engine("= yield\n= upcase").def_method(String, :render_haml)
870
+ assert_equal("12\nFOO\n", "foo".render_haml { 12 })
871
+ end
872
+
873
+ def test_def_method_locals
874
+ obj = Object.new
875
+ engine("%p= foo\n.bar{:baz => baz}= boom").def_method(obj, :render, :foo, :baz, :boom)
876
+ assert_equal("<p>1</p>\n<div baz='2' class='bar'>3</div>\n", obj.render(:foo => 1, :baz => 2, :boom => 3))
877
+ end
878
+
879
+ def test_render_proc_locals
880
+ proc = engine("%p= foo\n.bar{:baz => baz}= boom").render_proc(Object.new, :foo, :baz, :boom)
881
+ assert_equal("<p>1</p>\n<div baz='2' class='bar'>3</div>\n", proc[:foo => 1, :baz => 2, :boom => 3])
882
+ end
883
+
884
+ def test_render_proc_with_binding
885
+ assert_equal("FOO\n", engine("= upcase").render_proc("foo".instance_eval{binding}).call)
886
+ end
887
+
888
+ def test_ugly_true
889
+ assert_equal("<div id='outer'>\n<div id='inner'>\n<p>hello world</p>\n</div>\n</div>\n",
890
+ render("#outer\n #inner\n %p hello world", :ugly => true))
891
+
892
+ assert_equal("<p>#{'s' * 75}</p>\n",
893
+ render("%p #{'s' * 75}", :ugly => true))
894
+
895
+ assert_equal("<p>#{'s' * 75}</p>\n",
896
+ render("%p= 's' * 75", :ugly => true))
897
+ end
898
+
899
+ def test_auto_preserve_unless_ugly
900
+ assert_equal("<pre>foo&#x000A;bar</pre>\n", render('%pre="foo\nbar"'))
901
+ assert_equal("<pre>foo\nbar</pre>\n", render("%pre\n foo\n bar"))
902
+ assert_equal("<pre>foo\nbar</pre>\n", render('%pre="foo\nbar"', :ugly => true))
903
+ assert_equal("<pre>foo\nbar</pre>\n", render("%pre\n foo\n bar", :ugly => true))
904
+ end
905
+
906
+ def test_xhtml_output_option
907
+ assert_equal "<p>\n <br />\n</p>\n", render("%p\n %br", :format => :xhtml)
908
+ assert_equal "<a />\n", render("%a/", :format => :xhtml)
909
+ end
910
+
911
+ def test_arbitrary_output_option
912
+ assert_raise(Haml::Error, "Invalid output format :html1") { engine("%br", :format => :html1) }
913
+ end
914
+
915
+ def test_static_hashes
916
+ assert_equal("<a b='a =&gt; b'></a>\n", render("%a{:b => 'a => b'}", :suppress_eval => true))
917
+ assert_equal("<a b='a, b'></a>\n", render("%a{:b => 'a, b'}", :suppress_eval => true))
918
+ assert_equal("<a b='a\tb'></a>\n", render('%a{:b => "a\tb"}', :suppress_eval => true))
919
+ assert_equal("<a b='a\#{foo}b'></a>\n", render('%a{:b => "a\\#{foo}b"}', :suppress_eval => true))
920
+ end
921
+
922
+ def test_dynamic_hashes_with_suppress_eval
923
+ assert_equal("<a></a>\n", render('%a{:b => "a #{1 + 1} b", :c => "d"}', :suppress_eval => true))
924
+ end
925
+
926
+ # HTML 4.0
927
+
928
+ def test_html_has_no_self_closing_tags
929
+ assert_equal "<p>\n <br>\n</p>\n", render("%p\n %br", :format => :html4)
930
+ assert_equal "<br>\n", render("%br/", :format => :html4)
931
+ end
932
+
933
+ def test_html_renders_empty_node_with_closing_tag
934
+ assert_equal "<div class='foo'></div>\n", render(".foo", :format => :html4)
935
+ end
936
+
937
+ def test_html_doesnt_add_slash_to_self_closing_tags
938
+ assert_equal "<a>\n", render("%a/", :format => :html4)
939
+ assert_equal "<a foo='2'>\n", render("%a{:foo => 1 + 1}/", :format => :html4)
940
+ assert_equal "<meta>\n", render("%meta", :format => :html4)
941
+ assert_equal "<meta foo='2'>\n", render("%meta{:foo => 1 + 1}", :format => :html4)
942
+ end
943
+
944
+ def test_html_ignores_xml_prolog_declaration
945
+ assert_equal "", render('!!! XML', :format => :html4)
946
+ end
947
+
948
+ def test_html_has_different_doctype
949
+ assert_equal %{<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n},
950
+ render('!!!', :format => :html4)
951
+ end
952
+
953
+ # because anything before the doctype triggers quirks mode in IE
954
+ def test_xml_prolog_and_doctype_dont_result_in_a_leading_whitespace_in_html
955
+ assert_no_match(/^\s+/, render("!!! xml\n!!!", :format => :html4))
956
+ end
957
+
958
+ # HTML5
959
+ def test_html5_doctype
960
+ assert_equal %{<!DOCTYPE html>\n}, render('!!!', :format => :html5)
961
+ end
962
+
963
+ # New attributes
964
+
965
+ def test_basic_new_attributes
966
+ assert_equal("<a>bar</a>\n", render("%a() bar"))
967
+ assert_equal("<a href='foo'>bar</a>\n", render("%a(href='foo') bar"))
968
+ assert_equal("<a b='c' c='d' d='e'>baz</a>\n", render(%q{%a(b="c" c='d' d="e") baz}))
969
+ end
970
+
971
+ def test_new_attribute_ids
972
+ assert_equal("<div id='foo_bar'></div>\n", render("#foo(id='bar')"))
973
+ assert_equal("<div id='foo_bar_baz'></div>\n", render("#foo{:id => 'bar'}(id='baz')"))
974
+ assert_equal("<div id='foo_baz_bar'></div>\n", render("#foo(id='baz'){:id => 'bar'}"))
975
+ foo = User.new(42)
976
+ assert_equal("<div class='struct_user' id='foo_baz_bar_struct_user_42'></div>\n",
977
+ render("#foo(id='baz'){:id => 'bar'}[foo]", :locals => {:foo => foo}))
978
+ assert_equal("<div class='struct_user' id='foo_baz_bar_struct_user_42'></div>\n",
979
+ render("#foo(id='baz')[foo]{:id => 'bar'}", :locals => {:foo => foo}))
980
+ assert_equal("<div class='struct_user' id='foo_baz_bar_struct_user_42'></div>\n",
981
+ render("#foo[foo](id='baz'){:id => 'bar'}", :locals => {:foo => foo}))
982
+ assert_equal("<div class='struct_user' id='foo_bar_baz_struct_user_42'></div>\n",
983
+ render("#foo[foo]{:id => 'bar'}(id='baz')", :locals => {:foo => foo}))
984
+ end
985
+
986
+ def test_new_attribute_classes
987
+ assert_equal("<div class='bar foo'></div>\n", render(".foo(class='bar')"))
988
+ assert_equal("<div class='bar baz foo'></div>\n", render(".foo{:class => 'bar'}(class='baz')"))
989
+ assert_equal("<div class='bar baz foo'></div>\n", render(".foo(class='baz'){:class => 'bar'}"))
990
+ foo = User.new(42)
991
+ assert_equal("<div class='bar baz foo struct_user' id='struct_user_42'></div>\n",
992
+ render(".foo(class='baz'){:class => 'bar'}[foo]", :locals => {:foo => foo}))
993
+ assert_equal("<div class='bar baz foo struct_user' id='struct_user_42'></div>\n",
994
+ render(".foo[foo](class='baz'){:class => 'bar'}", :locals => {:foo => foo}))
995
+ assert_equal("<div class='bar baz foo struct_user' id='struct_user_42'></div>\n",
996
+ render(".foo[foo]{:class => 'bar'}(class='baz')", :locals => {:foo => foo}))
997
+ end
998
+
999
+ def test_dynamic_new_attributes
1000
+ assert_equal("<a href='12'>bar</a>\n", render("%a(href=foo) bar", :locals => {:foo => 12}))
1001
+ assert_equal("<a b='12' c='13' d='14'>bar</a>\n", render("%a(b=b c='13' d=d) bar", :locals => {:b => 12, :d => 14}))
1002
+ end
1003
+
1004
+ def test_new_attribute_interpolation
1005
+ assert_equal("<a href='12'>bar</a>\n", render('%a(href="1#{1 + 1}") bar'))
1006
+ assert_equal("<a href='2: 2, 3: 3'>bar</a>\n", render(%q{%a(href='2: #{1 + 1}, 3: #{foo}') bar}, :locals => {:foo => 3}))
1007
+ assert_equal(%Q{<a href='1\#{1 + 1}'>bar</a>\n}, render('%a(href="1\#{1 + 1}") bar'))
1008
+ end
1009
+
1010
+ def test_truthy_new_attributes
1011
+ assert_equal("<a href='href'>bar</a>\n", render("%a(href) bar"))
1012
+ assert_equal("<a bar='baz' href>bar</a>\n", render("%a(href bar='baz') bar", :format => :html5))
1013
+ assert_equal("<a href='href'>bar</a>\n", render("%a(href=true) bar"))
1014
+ assert_equal("<a>bar</a>\n", render("%a(href=false) bar"))
1015
+ end
1016
+
1017
+ def test_new_attribute_parsing
1018
+ assert_equal("<a a2='b2'>bar</a>\n", render("%a(a2=b2) bar", :locals => {:b2 => 'b2'}))
1019
+ assert_equal(%Q{<a a='foo"bar'>bar</a>\n}, render(%q{%a(a="#{'foo"bar'}") bar})) #'
1020
+ assert_equal(%Q{<a a="foo'bar">bar</a>\n}, render(%q{%a(a="#{"foo'bar"}") bar})) #'
1021
+ assert_equal(%Q{<a a='foo"bar'>bar</a>\n}, render(%q{%a(a='foo"bar') bar}))
1022
+ assert_equal(%Q{<a a="foo'bar">bar</a>\n}, render(%q{%a(a="foo'bar") bar}))
1023
+ assert_equal("<a a:b='foo'>bar</a>\n", render("%a(a:b='foo') bar"))
1024
+ assert_equal("<a a='foo' b='bar'>bar</a>\n", render("%a(a = 'foo' b = 'bar') bar"))
1025
+ assert_equal("<a a='foo' b='bar'>bar</a>\n", render("%a(a = foo b = bar) bar", :locals => {:foo => 'foo', :bar => 'bar'}))
1026
+ assert_equal("<a a='foo'>(b='bar')</a>\n", render("%a(a='foo')(b='bar')"))
1027
+ assert_equal("<a a='foo)bar'>baz</a>\n", render("%a(a='foo)bar') baz"))
1028
+ assert_equal("<a a='foo'>baz</a>\n", render("%a( a = 'foo' ) baz"))
1029
+ end
1030
+
1031
+ def test_new_attribute_escaping
1032
+ assert_equal(%Q{<a a='foo " bar'>bar</a>\n}, render(%q{%a(a="foo \" bar") bar}))
1033
+ assert_equal(%Q{<a a='foo \\" bar'>bar</a>\n}, render(%q{%a(a="foo \\\\\" bar") bar}))
1034
+
1035
+ assert_equal(%Q{<a a="foo ' bar">bar</a>\n}, render(%q{%a(a='foo \' bar') bar}))
1036
+ assert_equal(%Q{<a a="foo \\' bar">bar</a>\n}, render(%q{%a(a='foo \\\\\' bar') bar}))
1037
+
1038
+ assert_equal(%Q{<a a='foo \\ bar'>bar</a>\n}, render(%q{%a(a="foo \\\\ bar") bar}))
1039
+ assert_equal(%Q{<a a='foo \#{1 + 1} bar'>bar</a>\n}, render(%q{%a(a="foo \#{1 + 1} bar") bar}))
1040
+ end
1041
+
1042
+ def test_multiline_new_attribute
1043
+ assert_equal("<a a='b' c='d'>bar</a>\n", render("%a(a='b'\n c='d') bar"))
1044
+ assert_equal("<a a='b' b='c' c='d' d='e' e='f' f='j'>bar</a>\n",
1045
+ render("%a(a='b' b='c'\n c='d' d=e\n e='f' f='j') bar", :locals => {:e => 'e'}))
1046
+ end
1047
+
1048
+ def test_new_and_old_attributes
1049
+ assert_equal("<a a='b' c='d'>bar</a>\n", render("%a(a='b'){:c => 'd'} bar"))
1050
+ assert_equal("<a a='b' c='d'>bar</a>\n", render("%a{:c => 'd'}(a='b') bar"))
1051
+ assert_equal("<a a='b' c='d'>bar</a>\n", render("%a(c='d'){:a => 'b'} bar"))
1052
+ assert_equal("<a a='b' c='d'>bar</a>\n", render("%a{:a => 'b'}(c='d') bar"))
1053
+
1054
+ assert_equal("<a a='d'>bar</a>\n", render("%a{:a => 'b'}(a='d') bar"))
1055
+ assert_equal("<a a='b'>bar</a>\n", render("%a(a='d'){:a => 'b'} bar"))
1056
+
1057
+ assert_equal("<a a='b' b='c' c='d' d='e'>bar</a>\n",
1058
+ render("%a{:a => 'b',\n:b => 'c'}(c='d'\nd='e') bar"))
1059
+ end
1060
+
1061
+ # Encodings
1062
+
1063
+ unless Haml::Util.ruby1_8?
1064
+ def test_default_encoding
1065
+ assert_equal(Encoding.find("utf-8"), render(<<HAML.encode("us-ascii")).encoding)
1066
+ HTML
1067
+ %p bar
1068
+ %p foo
1069
+ HAML
1070
+ end
1071
+
1072
+ def test_convert_template_render
1073
+ assert_equal(<<HTML, render(<<HAML.encode("iso-8859-1"), :encoding => "utf-8"))
1074
+ <p>bâr</p>
1075
+ <p>föö</p>
1076
+ HTML
1077
+ %p bâr
1078
+ %p föö
1079
+ HAML
1080
+ end
1081
+
1082
+ def test_convert_template_render_proc
1083
+ assert_converts_template_properly {|e| e.render_proc.call}
1084
+ end
1085
+
1086
+ def test_convert_template_render
1087
+ assert_converts_template_properly {|e| e.render}
1088
+ end
1089
+
1090
+ def test_convert_template_def_method
1091
+ assert_converts_template_properly do |e|
1092
+ o = Object.new
1093
+ e.def_method(o, :render)
1094
+ o.render
1095
+ end
1096
+ end
1097
+
1098
+ def test_encoding_error
1099
+ render("foo\nbar\nb\xFEaz".force_encoding("utf-8"))
1100
+ assert(false, "Expected exception")
1101
+ rescue Haml::Error => e
1102
+ assert_equal(3, e.line)
1103
+ assert_equal('Invalid UTF-8 character "\xFE"', e.message)
1104
+ end
1105
+
1106
+ def test_ascii_incompatible_encoding_error
1107
+ template = "foo\nbar\nb_z".encode("utf-16le")
1108
+ template[9] = "\xFE".force_encoding("utf-16le")
1109
+ render(template)
1110
+ assert(false, "Expected exception")
1111
+ rescue Haml::Error => e
1112
+ assert_equal(3, e.line)
1113
+ assert_equal('Invalid UTF-16LE character "\xFE"', e.message)
1114
+ end
1115
+ end
1116
+
1117
+ private
1118
+
1119
+ def assert_converts_template_properly
1120
+ engine = Haml::Engine.new(<<HAML.encode("iso-8859-1"), :encoding => "utf-8")
1121
+ %p bâr
1122
+ %p föö
1123
+ HAML
1124
+ assert_equal(<<HTML, yield(engine))
1125
+ <p>bâr</p>
1126
+ <p>föö</p>
1127
+ HTML
1128
+ end
1129
+ end