drnic-haml 2.3.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 (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