haml 2.0.10 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of haml might be problematic. Click here for more details.

Files changed (107) hide show
  1. data/.yardopts +5 -0
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +347 -0
  4. data/Rakefile +124 -19
  5. data/VERSION +1 -1
  6. data/VERSION_NAME +1 -0
  7. data/extra/haml-mode.el +397 -78
  8. data/extra/sass-mode.el +148 -36
  9. data/extra/update_watch.rb +13 -0
  10. data/lib/haml.rb +15 -993
  11. data/lib/haml/buffer.rb +131 -84
  12. data/lib/haml/engine.rb +129 -97
  13. data/lib/haml/error.rb +7 -7
  14. data/lib/haml/exec.rb +127 -42
  15. data/lib/haml/filters.rb +107 -42
  16. data/lib/haml/helpers.rb +210 -156
  17. data/lib/haml/helpers/action_view_extensions.rb +34 -39
  18. data/lib/haml/helpers/action_view_mods.rb +132 -139
  19. data/lib/haml/html.rb +77 -65
  20. data/lib/haml/precompiler.rb +404 -213
  21. data/lib/haml/shared.rb +78 -0
  22. data/lib/haml/template.rb +14 -14
  23. data/lib/haml/template/patch.rb +2 -2
  24. data/lib/haml/template/plugin.rb +2 -3
  25. data/lib/haml/util.rb +211 -6
  26. data/lib/haml/version.rb +30 -13
  27. data/lib/sass.rb +7 -856
  28. data/lib/sass/css.rb +169 -161
  29. data/lib/sass/engine.rb +344 -328
  30. data/lib/sass/environment.rb +79 -0
  31. data/lib/sass/error.rb +33 -11
  32. data/lib/sass/files.rb +139 -0
  33. data/lib/sass/plugin.rb +160 -117
  34. data/lib/sass/plugin/merb.rb +7 -6
  35. data/lib/sass/plugin/rails.rb +5 -6
  36. data/lib/sass/repl.rb +58 -0
  37. data/lib/sass/script.rb +59 -0
  38. data/lib/sass/script/bool.rb +17 -0
  39. data/lib/sass/script/color.rb +183 -0
  40. data/lib/sass/script/funcall.rb +50 -0
  41. data/lib/sass/script/functions.rb +198 -0
  42. data/lib/sass/script/lexer.rb +178 -0
  43. data/lib/sass/script/literal.rb +177 -0
  44. data/lib/sass/script/node.rb +14 -0
  45. data/lib/sass/script/number.rb +381 -0
  46. data/lib/sass/script/operation.rb +45 -0
  47. data/lib/sass/script/parser.rb +172 -0
  48. data/lib/sass/script/string.rb +12 -0
  49. data/lib/sass/script/unary_operation.rb +34 -0
  50. data/lib/sass/script/variable.rb +31 -0
  51. data/lib/sass/tree/comment_node.rb +73 -10
  52. data/lib/sass/tree/debug_node.rb +30 -0
  53. data/lib/sass/tree/directive_node.rb +42 -17
  54. data/lib/sass/tree/file_node.rb +41 -0
  55. data/lib/sass/tree/for_node.rb +48 -0
  56. data/lib/sass/tree/if_node.rb +54 -0
  57. data/lib/sass/tree/mixin_def_node.rb +29 -0
  58. data/lib/sass/tree/mixin_node.rb +48 -0
  59. data/lib/sass/tree/node.rb +214 -11
  60. data/lib/sass/tree/prop_node.rb +109 -0
  61. data/lib/sass/tree/rule_node.rb +178 -51
  62. data/lib/sass/tree/variable_node.rb +34 -0
  63. data/lib/sass/tree/while_node.rb +31 -0
  64. data/test/haml/engine_test.rb +331 -36
  65. data/test/haml/helper_test.rb +12 -1
  66. data/test/haml/results/content_for_layout.xhtml +0 -3
  67. data/test/haml/results/filters.xhtml +2 -0
  68. data/test/haml/results/list.xhtml +1 -1
  69. data/test/haml/template_test.rb +7 -2
  70. data/test/haml/templates/content_for_layout.haml +0 -2
  71. data/test/haml/templates/list.haml +1 -1
  72. data/test/haml/util_test.rb +92 -0
  73. data/test/sass/css2sass_test.rb +69 -24
  74. data/test/sass/engine_test.rb +586 -64
  75. data/test/sass/functions_test.rb +125 -0
  76. data/test/sass/more_results/more1.css +9 -0
  77. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  78. data/test/sass/more_results/more_import.css +29 -0
  79. data/test/sass/more_templates/_more_partial.sass +2 -0
  80. data/test/sass/more_templates/more1.sass +23 -0
  81. data/test/sass/more_templates/more_import.sass +11 -0
  82. data/test/sass/plugin_test.rb +81 -28
  83. data/test/sass/results/line_numbers.css +49 -0
  84. data/test/sass/results/{constants.css → script.css} +4 -4
  85. data/test/sass/results/subdir/subdir.css +2 -0
  86. data/test/sass/results/units.css +11 -0
  87. data/test/sass/script_test.rb +258 -0
  88. data/test/sass/templates/import.sass +1 -1
  89. data/test/sass/templates/importee.sass +7 -2
  90. data/test/sass/templates/line_numbers.sass +13 -0
  91. data/test/sass/templates/{constants.sass → script.sass} +11 -10
  92. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  93. data/test/sass/templates/subdir/subdir.sass +2 -2
  94. data/test/sass/templates/units.sass +11 -0
  95. data/test/test_helper.rb +14 -0
  96. metadata +77 -19
  97. data/FAQ +0 -138
  98. data/README.rdoc +0 -319
  99. data/lib/sass/constant.rb +0 -216
  100. data/lib/sass/constant/color.rb +0 -101
  101. data/lib/sass/constant/literal.rb +0 -54
  102. data/lib/sass/constant/nil.rb +0 -9
  103. data/lib/sass/constant/number.rb +0 -87
  104. data/lib/sass/constant/operation.rb +0 -30
  105. data/lib/sass/constant/string.rb +0 -22
  106. data/lib/sass/tree/attr_node.rb +0 -57
  107. data/lib/sass/tree/value_node.rb +0 -20
@@ -0,0 +1,109 @@
1
+ module Sass::Tree
2
+ # A static node reprenting a CSS property.
3
+ #
4
+ # @see Sass::Tree
5
+ class PropNode < Node
6
+ # The name of the property.
7
+ #
8
+ # @return [String]
9
+ attr_accessor :name
10
+
11
+ # The value of the property,
12
+ # either a plain string or a SassScript parse tree.
13
+ #
14
+ # @return [String, Script::Node]
15
+ attr_accessor :value
16
+
17
+ # @param name [String] See \{#name}
18
+ # @param value [String] See \{#value}
19
+ # @param prop_syntax [Symbol] `:new` if this property uses `a: b`-style syntax,
20
+ # `:old` if it uses `:a b`-style syntax
21
+ def initialize(name, value, prop_syntax)
22
+ @name = name
23
+ @value = value
24
+ @prop_syntax = prop_syntax
25
+ super()
26
+ end
27
+
28
+ # Compares the names and values of two properties.
29
+ #
30
+ # @param other [Object] The object to compare with
31
+ # @return [Boolean] Whether or not this node and the other object
32
+ # are the same
33
+ def ==(other)
34
+ self.class == other.class && name == other.name && value == other.value && super
35
+ end
36
+
37
+ # Computes the CSS for the property.
38
+ #
39
+ # @param tabs [Fixnum] The level of indentation for the CSS
40
+ # @param parent_name [String] The name of the parent property (e.g. `text`) or nil
41
+ # @return [String] The resulting CSS
42
+ # @raise [Sass::SyntaxError] if the property uses invalid syntax
43
+ def to_s(tabs, parent_name = nil)
44
+ if @options[:property_syntax] == :old && @prop_syntax == :new
45
+ raise Sass::SyntaxError.new("Illegal property syntax: can't use new syntax when :property_syntax => :old is set.")
46
+ elsif @options[:property_syntax] == :new && @prop_syntax == :old
47
+ raise Sass::SyntaxError.new("Illegal property syntax: can't use old syntax when :property_syntax => :new is set.")
48
+ end
49
+
50
+ if value[-1] == ?;
51
+ raise Sass::SyntaxError.new("Invalid property: #{declaration.dump} (no \";\" required at end-of-line).", @line)
52
+ end
53
+ real_name = name
54
+ real_name = "#{parent_name}-#{real_name}" if parent_name
55
+
56
+ if value.empty? && children.empty?
57
+ raise Sass::SyntaxError.new("Invalid property: #{declaration.dump} (no value).", @line)
58
+ end
59
+
60
+ join_string = case style
61
+ when :compact; ' '
62
+ when :compressed; ''
63
+ else "\n"
64
+ end
65
+ spaces = ' ' * (tabs - 1)
66
+ to_return = ''
67
+ if !value.empty?
68
+ to_return << "#{spaces}#{real_name}:#{style == :compressed ? '' : ' '}#{value};#{join_string}"
69
+ end
70
+
71
+ children.each do |kid|
72
+ next if kid.invisible?
73
+ to_return << kid.to_s(tabs, real_name) << join_string
74
+ end
75
+
76
+ (style == :compressed && parent_name) ? to_return : to_return[0...-1]
77
+ end
78
+
79
+ protected
80
+
81
+ # Runs any SassScript that may be embedded in the property.
82
+ #
83
+ # @param environment [Sass::Environment] The lexical environment containing
84
+ # variable and mixin values
85
+ def perform!(environment)
86
+ @name = interpolate(@name, environment)
87
+ @value = @value.is_a?(String) ? interpolate(@value, environment) : @value.perform(environment).to_s
88
+ super
89
+ end
90
+
91
+ # Returns an error message if the given child node is invalid,
92
+ # and false otherwise.
93
+ #
94
+ # {PropNode} only allows other {PropNode}s and {CommentNode}s as children.
95
+ # @param child [Tree::Node] A potential child node
96
+ # @return [String] An error message if the child is invalid, or nil otherwise
97
+ def invalid_child?(child)
98
+ if !child.is_a?(PropNode) && !child.is_a?(CommentNode)
99
+ "Illegal nesting: Only properties may be nested beneath properties."
100
+ end
101
+ end
102
+
103
+ private
104
+
105
+ def declaration
106
+ @prop_syntax == :new ? "#{name}: #{value}" : ":#{name} #{value}"
107
+ end
108
+ end
109
+ end
@@ -1,93 +1,220 @@
1
- require 'sass/tree/node'
2
- require 'sass/tree/attr_node'
1
+ require 'pathname'
3
2
 
4
3
  module Sass::Tree
5
- class RuleNode < ValueNode
4
+ class RuleNode < Node
6
5
  # The character used to include the parent selector
7
6
  PARENT = '&'
8
7
 
9
- alias_method :rule, :value
10
- alias_method :rule=, :value=
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
11
22
 
12
- def ==(other)
13
- self.class == other.class && rules == other.rules && super
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()
14
49
  end
15
50
 
16
- def rules
17
- Array(rule)
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
18
58
  end
19
59
 
60
+ # Adds another {RuleNode}'s rules to this one's.
61
+ #
62
+ # @param node [RuleNode] The other node
20
63
  def add_rules(node)
21
- self.rule = rules
22
- self.rule += node.rules
64
+ @rules += node.rules
23
65
  end
24
66
 
67
+ # @return [Boolean] Whether or not this rule is continued on the next line
25
68
  def continued?
26
- rule[-1] == ?,
69
+ @rules.last[-1] == ?,
27
70
  end
28
71
 
72
+ # Computes the CSS for the rule.
73
+ #
74
+ # @param tabs [Fixnum] The level of indentation for the CSS
75
+ # @param super_rules [Array<Array<String>>] The rules for the parent node
76
+ # (see \{#rules}), or `nil` if there are no parents
77
+ # @return [String] The resulting CSS
78
+ # @raise [Sass::SyntaxError] if the rule has no parents but uses `&`
29
79
  def to_s(tabs, super_rules = nil)
30
- attributes = []
80
+ resolved_rules = resolve_parent_refs(super_rules)
81
+
82
+ properties = []
31
83
  sub_rules = []
32
84
 
33
- rule_split = /\s*,\s*/
34
- rule_separator = @style == :compressed ? ',' : ', '
35
- line_separator = [:nested, :expanded].include?(@style) ? ",\n" : rule_separator
85
+ rule_separator = style == :compressed ? ',' : ', '
86
+ line_separator = [:nested, :expanded].include?(style) ? ",\n" : rule_separator
36
87
  rule_indent = ' ' * (tabs - 1)
37
- total_rule = if super_rules
38
- super_rules.split(",\n").map do |super_line|
39
- super_line.strip.split(rule_split).map do |super_rule|
40
- self.rules.map do |line|
41
- rule_indent + line.gsub(/,$/, '').split(rule_split).map do |rule|
42
- if rule.include?(PARENT)
43
- rule.gsub(PARENT, super_rule)
44
- else
45
- "#{super_rule} #{rule}"
46
- end
47
- end.join(rule_separator)
48
- end.join(line_separator)
49
- end.join(rule_separator)
50
- end.join(line_separator)
51
- elsif self.rules.any? { |r| r.include?(PARENT) }
52
- raise Sass::SyntaxError.new("Base-level rules cannot contain the parent-selector-referencing character '#{PARENT}'.", line)
53
- else
54
- per_rule_indent, total_indent = [:nested, :expanded].include?(@style) ? [rule_indent, ''] : ['', rule_indent]
55
- total_indent + self.rules.map do |r|
56
- per_rule_indent + r.gsub(/,$/, '').gsub(rule_split, rule_separator).rstrip
57
- end.join(line_separator)
58
- end
88
+ per_rule_indent, total_indent = [:nested, :expanded].include?(style) ? [rule_indent, ''] : ['', rule_indent]
89
+
90
+ total_rule = total_indent + resolved_rules.map do |line|
91
+ per_rule_indent + line.join(rule_separator)
92
+ end.join(line_separator)
59
93
 
60
94
  children.each do |child|
95
+ next if child.invisible?
61
96
  if child.is_a? RuleNode
62
97
  sub_rules << child
63
98
  else
64
- attributes << child
99
+ properties << child
65
100
  end
66
101
  end
67
102
 
68
103
  to_return = ''
69
- if !attributes.empty?
104
+ if !properties.empty?
70
105
  old_spaces = ' ' * (tabs - 1)
71
106
  spaces = ' ' * tabs
72
- if @style == :compact
73
- attributes = attributes.map { |a| a.to_s(1) }.join(' ')
74
- to_return << "#{total_rule} { #{attributes} }\n"
75
- elsif @style == :compressed
76
- attributes = attributes.map { |a| a.to_s(1) }.join(';')
77
- to_return << "#{total_rule}{#{attributes}}"
107
+ if @options[:line_comments] && style != :compressed
108
+ to_return << "#{old_spaces}/* line #{line}"
109
+
110
+ if filename
111
+ relative_filename = if @options[:css_filename]
112
+ begin
113
+ Pathname.new(filename).relative_path_from(
114
+ Pathname.new(File.dirname(@options[:css_filename]))).to_s
115
+ rescue ArgumentError
116
+ nil
117
+ end
118
+ end
119
+ relative_filename ||= filename
120
+ to_return << ", #{relative_filename}"
121
+ end
122
+
123
+ to_return << " */\n"
124
+ end
125
+
126
+ if style == :compact
127
+ properties = properties.map { |a| a.to_s(1) }.select{|a| a && a.length > 0}.join(' ')
128
+ to_return << "#{total_rule} { #{properties} }\n"
129
+ elsif style == :compressed
130
+ properties = properties.map { |a| a.to_s(1) }.select{|a| a && a.length > 0}.join(';')
131
+ to_return << "#{total_rule}{#{properties}}"
78
132
  else
79
- attributes = attributes.map { |a| a.to_s(tabs + 1) }.join("\n")
80
- end_attrs = (@style == :expanded ? "\n" + old_spaces : ' ')
81
- to_return << "#{total_rule} {\n#{attributes}#{end_attrs}}\n"
133
+ properties = properties.map { |a| a.to_s(tabs + 1) }.select{|a| a && a.length > 0}.join("\n")
134
+ end_props = (style == :expanded ? "\n" + old_spaces : ' ')
135
+ to_return << "#{total_rule} {\n#{properties}#{end_props}}\n"
82
136
  end
83
137
  end
84
138
 
85
- tabs += 1 unless attributes.empty? || @style != :nested
139
+ tabs += 1 unless properties.empty? || style != :nested
86
140
  sub_rules.each do |sub|
87
- to_return << sub.to_s(tabs, total_rule)
141
+ to_return << sub.to_s(tabs, resolved_rules)
88
142
  end
89
143
 
90
144
  to_return
91
145
  end
146
+
147
+ protected
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}'.", self.line)
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
92
219
  end
93
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
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
2
3
  require File.dirname(__FILE__) + '/../test_helper'
3
4
 
4
5
  class EngineTest < Test::Unit::TestCase
@@ -17,39 +18,53 @@ class EngineTest < Test::Unit::TestCase
17
18
  "~" => "There's no Ruby code for ~ to evaluate.",
18
19
  "=" => "There's no Ruby code for = to evaluate.",
19
20
  "%p/\n a" => "Illegal nesting: nesting within a self-closing tag is illegal.",
20
- "%p\n\ta" => <<END.strip,
21
- A tab character was used for indentation. Haml must be indented using two spaces.
22
- Are you sure you have soft tabs enabled in your editor?
23
- END
24
- "%p\n a" => "1 space was used for indentation. Haml must be indented using two spaces.",
25
- "%p\n a" => "3 spaces were used for indentation. Haml must be indented using two spaces.",
26
- "%p\n a" => "4 spaces were used for indentation. Haml must be indented using two spaces.",
27
21
  ":a\n b" => ['Filter "a" is not defined.', 1],
28
22
  ":a= b" => 'Invalid filter name ":a= b".',
29
23
  "." => "Illegal element: classes and ids must have values.",
30
24
  ".#" => "Illegal element: classes and ids must have values.",
31
25
  ".{} a" => "Illegal element: classes and ids must have values.",
26
+ ".() a" => "Illegal element: classes and ids must have values.",
32
27
  ".= a" => "Illegal element: classes and ids must have values.",
33
28
  "%p..a" => "Illegal element: classes and ids must have values.",
34
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],
35
37
  " %p foo" => "Indenting at the beginning of the document is illegal.",
36
38
  " %p foo" => "Indenting at the beginning of the document is illegal.",
37
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.",
38
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],
39
57
 
40
58
  # Regression tests
41
59
  "- raise 'foo'\n\n\n\nbar" => ["foo", 1],
42
60
  "= 'foo'\n-raise 'foo'" => ["foo", 2],
43
61
  "\n\n\n- raise 'foo'" => ["foo", 4],
62
+ "%p foo |\n bar |\n baz |\nbop\n- raise 'foo'" => ["foo", 5],
44
63
  "foo\n\n\n bar" => ["Illegal nesting: nesting within plain text is illegal.", 4],
45
64
  "%p/\n\n bar" => ["Illegal nesting: nesting within a self-closing tag is illegal.", 3],
46
65
  "%p foo\n\n bar" => ["Illegal nesting: content can't be both given on the same line as %p and nested within it.", 3],
47
66
  "/ foo\n\n bar" => ["Illegal nesting: nesting within a tag that already has content is illegal.", 3],
48
67
  "!!!\n\n bar" => ["Illegal nesting: nesting within a header command is illegal.", 3],
49
- "foo\n\n\n\tbar" => [<<END.strip, 4],
50
- A tab character was used for indentation. Haml must be indented using two spaces.
51
- Are you sure you have soft tabs enabled in your editor?
52
- END
53
68
  "foo\n:ruby\n 1\n 2\n 3\n- raise 'foo'" => ["foo", 6],
54
69
  }
55
70
 
@@ -70,7 +85,20 @@ END
70
85
  end
71
86
  Haml::Engine.new(text, options)
72
87
  end
73
-
88
+
89
+ def test_empty_render
90
+ assert_equal "", render("")
91
+ end
92
+
93
+ def test_flexible_tabulation
94
+ assert_equal("<p>\n foo\n</p>\n<q>\n bar\n <a>\n baz\n </a>\n</q>\n",
95
+ render("%p\n foo\n%q\n bar\n %a\n baz"))
96
+ assert_equal("<p>\n foo\n</p>\n<q>\n bar\n <a>\n baz\n </a>\n</q>\n",
97
+ render("%p\n\tfoo\n%q\n\tbar\n\t%a\n\t\tbaz"))
98
+ assert_equal("<p>\n \t \t bar\n baz\n</p>\n",
99
+ render("%p\n :plain\n \t \t bar\n baz"))
100
+ end
101
+
74
102
  def test_empty_render_should_remain_empty
75
103
  assert_equal('', render(''))
76
104
  end
@@ -109,14 +137,19 @@ END
109
137
  assert_equal("<strong>Hi there!</strong>\n", engine.to_html)
110
138
  end
111
139
 
112
- def test_double_equals
113
- assert_equal("<p>Hello World</p>\n", render('%p== Hello #{who}', :locals => {:who => 'World'}))
114
- assert_equal("<p>\n Hello World\n</p>\n", render("%p\n == Hello \#{who}", :locals => {:who => 'World'}))
140
+ def test_interpolation
141
+ assert_equal("<p>Hello World</p>\n", render('%p Hello #{who}', :locals => {:who => 'World'}))
142
+ assert_equal("<p>\n Hello World\n</p>\n", render("%p\n Hello \#{who}", :locals => {:who => 'World'}))
115
143
  end
116
144
 
117
- def test_double_equals_in_the_middle_of_a_string
145
+ def test_interpolation_in_the_middle_of_a_string
118
146
  assert_equal("\"title 'Title'. \"\n",
119
- render("== \"title '\#{\"Title\"}'. \""))
147
+ render("\"title '\#{\"Title\"}'. \""))
148
+ end
149
+
150
+ def test_interpolation_at_the_beginning_of_a_line
151
+ assert_equal("<p>2</p>\n", render('%p #{1 + 1}'))
152
+ assert_equal("<p>\n 2\n</p>\n", render("%p\n \#{1 + 1}"))
120
153
  end
121
154
 
122
155
  def test_nil_tag_value_should_render_as_empty
@@ -135,6 +168,13 @@ END
135
168
  assert_equal("<img alt='' src='/foo.png' />\n", render("%img{:width => nil, :src => '/foo.png', :alt => String.new}"))
136
169
  end
137
170
 
171
+ def test_attribute_hash_with_newlines
172
+ assert_equal("<p a='b' c='d'>foop</p>\n", render("%p{:a => 'b',\n :c => 'd'} foop"))
173
+ assert_equal("<p a='b' c='d'>\n foop\n</p>\n", render("%p{:a => 'b',\n :c => 'd'}\n foop"))
174
+ assert_equal("<p a='b' c='d' />\n", render("%p{:a => 'b',\n :c => 'd'}/"))
175
+ assert_equal("<p a='b' c='d' e='f'></p>\n", render("%p{:a => 'b',\n :c => 'd',\n :e => 'f'}"))
176
+ end
177
+
138
178
  def test_attr_hashes_not_modified
139
179
  hash = {:color => 'red'}
140
180
  assert_equal(<<HTML, render(<<HAML, :locals => {:hash => hash}))
@@ -188,8 +228,19 @@ SOURCE
188
228
  render("%p{:foo => 'bar', :bar => false, :baz => 'false'}", :format => :xhtml))
189
229
  end
190
230
 
231
+ def test_both_whitespace_nukes_work_together
232
+ assert_equal(<<RESULT, render(<<SOURCE))
233
+ <p><q>Foo
234
+ Bar</q></p>
235
+ RESULT
236
+ %p
237
+ %q><= "Foo\\nBar"
238
+ SOURCE
239
+ end
240
+
241
+ # Regression tests
242
+
191
243
  def test_whitespace_nuke_with_both_newlines
192
- # Regression test
193
244
  assert_equal("<p>foo</p>\n", render('%p<= "\nfoo\n"'))
194
245
  assert_equal(<<HTML, render(<<HAML))
195
246
  <p>
@@ -201,17 +252,6 @@ HTML
201
252
  HAML
202
253
  end
203
254
 
204
- def test_both_whitespace_nukes_work_together
205
- assert_equal(<<RESULT, render(<<SOURCE))
206
- <p><q>Foo
207
- Bar</q></p>
208
- RESULT
209
- %p
210
- %q><= "Foo\\nBar"
211
- SOURCE
212
- end
213
-
214
- # Mostly a regression test
215
255
  def test_both_case_indentation_work_with_deeply_nested_code
216
256
  result = <<RESULT
217
257
  <h2>
@@ -238,6 +278,87 @@ HAML
238
278
  HAML
239
279
  end
240
280
 
281
+ def test_equals_block_with_ugly
282
+ assert_equal("foo\n", render(<<HAML, :ugly => true))
283
+ = capture_haml do
284
+ foo
285
+ HAML
286
+ end
287
+
288
+ def test_plain_equals_with_ugly
289
+ assert_equal("foo\nbar\n", render(<<HAML, :ugly => true))
290
+ = "foo"
291
+ bar
292
+ HAML
293
+ end
294
+
295
+ def test_inline_if
296
+ assert_equal(<<HTML, render(<<HAML))
297
+ <p>One</p>
298
+ <p></p>
299
+ <p>Three</p>
300
+ HTML
301
+ - for name in ["One", "Two", "Three"]
302
+ %p= name unless name == "Two"
303
+ HAML
304
+ end
305
+
306
+ def test_end_with_method_call
307
+ assert_equal(<<HTML, render(<<HAML))
308
+ 2|3|4
309
+ b-a-r
310
+ HTML
311
+ = [1, 2, 3].map do |i|
312
+ - i + 1
313
+ - end.join("|")
314
+ = "bar".gsub(/./) do |s|
315
+ - s + "-"
316
+ - end.gsub(/-$/) do |s|
317
+ - ''
318
+ HAML
319
+ end
320
+
321
+ def test_multiline_with_colon_after_filter
322
+ assert_equal(<<HTML, render(<<HAML))
323
+ Foo
324
+ Bar
325
+ HTML
326
+ :plain
327
+ Foo
328
+ = { :a => "Bar", |
329
+ :b => "Baz" }[:a] |
330
+ HAML
331
+ assert_equal(<<HTML, render(<<HAML))
332
+
333
+ Bar
334
+ HTML
335
+ :plain
336
+ = { :a => "Bar", |
337
+ :b => "Baz" }[:a] |
338
+ HAML
339
+ end
340
+
341
+ def test_multiline_in_filter
342
+ assert_equal(<<HTML, render(<<HAML))
343
+ Foo |
344
+ Bar |
345
+ Baz
346
+ HTML
347
+ :plain
348
+ Foo |
349
+ Bar |
350
+ Baz
351
+ HAML
352
+ end
353
+
354
+ def test_curly_brace
355
+ assert_equal(<<HTML, render(<<HAML))
356
+ Foo { Bar
357
+ HTML
358
+ == Foo { Bar
359
+ HAML
360
+ end
361
+
241
362
  # HTML escaping tests
242
363
 
243
364
  def test_ampersand_equals_should_escape
@@ -281,32 +402,57 @@ HAML
281
402
  assert_equal("<img alt='' src='foo&#x000A;.png' />\n",
282
403
  render("%img{:width => nil, :src => \"foo\\n.png\", :alt => String.new}"))
283
404
  end
284
-
285
- def test_string_interpolation_should_be_esaped
405
+
406
+ def test_string_double_equals_should_be_esaped
286
407
  assert_equal("<p>4&amp;3</p>\n", render("%p== \#{2+2}&\#{2+1}", :escape_html => true))
287
408
  assert_equal("<p>4&3</p>\n", render("%p== \#{2+2}&\#{2+1}", :escape_html => false))
288
409
  end
289
410
 
290
- def test_escaped_inline_string_interpolation
411
+ def test_escaped_inline_string_double_equals
291
412
  assert_equal("<p>4&amp;3</p>\n", render("%p&== \#{2+2}&\#{2+1}", :escape_html => true))
292
413
  assert_equal("<p>4&amp;3</p>\n", render("%p&== \#{2+2}&\#{2+1}", :escape_html => false))
293
414
  end
294
415
 
295
- def test_unescaped_inline_string_interpolation
416
+ def test_unescaped_inline_string_double_equals
296
417
  assert_equal("<p>4&3</p>\n", render("%p!== \#{2+2}&\#{2+1}", :escape_html => true))
297
418
  assert_equal("<p>4&3</p>\n", render("%p!== \#{2+2}&\#{2+1}", :escape_html => false))
298
419
  end
299
420
 
300
- def test_escaped_string_interpolation
421
+ def test_escaped_string_double_equals
301
422
  assert_equal("<p>\n 4&amp;3\n</p>\n", render("%p\n &== \#{2+2}&\#{2+1}", :escape_html => true))
302
423
  assert_equal("<p>\n 4&amp;3\n</p>\n", render("%p\n &== \#{2+2}&\#{2+1}", :escape_html => false))
303
424
  end
304
425
 
305
- def test_unescaped_string_interpolation
426
+ def test_unescaped_string_double_equals
306
427
  assert_equal("<p>\n 4&3\n</p>\n", render("%p\n !== \#{2+2}&\#{2+1}", :escape_html => true))
307
428
  assert_equal("<p>\n 4&3\n</p>\n", render("%p\n !== \#{2+2}&\#{2+1}", :escape_html => false))
308
429
  end
309
430
 
431
+ def test_string_interpolation_should_be_esaped
432
+ assert_equal("<p>4&amp;3</p>\n", render("%p \#{2+2}&\#{2+1}", :escape_html => true))
433
+ assert_equal("<p>4&3</p>\n", render("%p \#{2+2}&\#{2+1}", :escape_html => false))
434
+ end
435
+
436
+ def test_escaped_inline_string_interpolation
437
+ assert_equal("<p>4&amp;3</p>\n", render("%p& \#{2+2}&\#{2+1}", :escape_html => true))
438
+ assert_equal("<p>4&amp;3</p>\n", render("%p& \#{2+2}&\#{2+1}", :escape_html => false))
439
+ end
440
+
441
+ def test_unescaped_inline_string_interpolation
442
+ assert_equal("<p>4&3</p>\n", render("%p! \#{2+2}&\#{2+1}", :escape_html => true))
443
+ assert_equal("<p>4&3</p>\n", render("%p! \#{2+2}&\#{2+1}", :escape_html => false))
444
+ end
445
+
446
+ def test_escaped_string_interpolation
447
+ assert_equal("<p>\n 4&amp;3\n</p>\n", render("%p\n & \#{2+2}&\#{2+1}", :escape_html => true))
448
+ assert_equal("<p>\n 4&amp;3\n</p>\n", render("%p\n & \#{2+2}&\#{2+1}", :escape_html => false))
449
+ end
450
+
451
+ def test_unescaped_string_interpolation
452
+ assert_equal("<p>\n 4&3\n</p>\n", render("%p\n ! \#{2+2}&\#{2+1}", :escape_html => true))
453
+ assert_equal("<p>\n 4&3\n</p>\n", render("%p\n ! \#{2+2}&\#{2+1}", :escape_html => false))
454
+ end
455
+
310
456
  def test_scripts_should_respect_escape_html_option
311
457
  assert_equal("<p>\n foo &amp; bar\n</p>\n", render("%p\n = 'foo & bar'", :escape_html => true))
312
458
  assert_equal("<p>\n foo & bar\n</p>\n", render("%p\n = 'foo & bar'", :escape_html => false))
@@ -499,7 +645,7 @@ HAML
499
645
  end
500
646
 
501
647
  def test_unbalanced_brackets
502
- render('== #{1 + 5} foo #{6 + 7 bar #{8 + 9}')
648
+ render('foo #{1 + 5} foo #{6 + 7 bar #{8 + 9}')
503
649
  rescue Haml::SyntaxError => e
504
650
  assert_equal("Unbalanced brackets.", e.message)
505
651
  end
@@ -665,4 +811,153 @@ END
665
811
  def test_html5_doctype
666
812
  assert_equal %{<!DOCTYPE html>\n}, render('!!!', :format => :html5)
667
813
  end
814
+
815
+ # New attributes
816
+
817
+ def test_basic_new_attributes
818
+ assert_equal("<a>bar</a>\n", render("%a() bar"))
819
+ assert_equal("<a href='foo'>bar</a>\n", render("%a(href='foo') bar"))
820
+ assert_equal("<a b='c' c='d' d='e'>baz</a>\n", render(%q{%a(b="c" c='d' d="e") baz}))
821
+ end
822
+
823
+ def test_new_attribute_ids
824
+ assert_equal("<div id='foo_bar'></div>\n", render("#foo(id='bar')"))
825
+ assert_equal("<div id='foo_bar_baz'></div>\n", render("#foo{:id => 'bar'}(id='baz')"))
826
+ assert_equal("<div id='foo_baz_bar'></div>\n", render("#foo(id='baz'){:id => 'bar'}"))
827
+ foo = User.new(42)
828
+ assert_equal("<div class='struct_user' id='foo_baz_bar_struct_user_42'></div>\n",
829
+ render("#foo(id='baz'){:id => 'bar'}[foo]", :locals => {:foo => foo}))
830
+ assert_equal("<div class='struct_user' id='foo_baz_bar_struct_user_42'></div>\n",
831
+ render("#foo(id='baz')[foo]{:id => 'bar'}", :locals => {:foo => foo}))
832
+ assert_equal("<div class='struct_user' id='foo_baz_bar_struct_user_42'></div>\n",
833
+ render("#foo[foo](id='baz'){:id => 'bar'}", :locals => {:foo => foo}))
834
+ assert_equal("<div class='struct_user' id='foo_bar_baz_struct_user_42'></div>\n",
835
+ render("#foo[foo]{:id => 'bar'}(id='baz')", :locals => {:foo => foo}))
836
+ end
837
+
838
+ def test_new_attribute_classes
839
+ assert_equal("<div class='bar foo'></div>\n", render(".foo(class='bar')"))
840
+ assert_equal("<div class='bar baz foo'></div>\n", render(".foo{:class => 'bar'}(class='baz')"))
841
+ assert_equal("<div class='bar baz foo'></div>\n", render(".foo(class='baz'){:class => 'bar'}"))
842
+ foo = User.new(42)
843
+ assert_equal("<div class='bar baz foo struct_user' id='struct_user_42'></div>\n",
844
+ render(".foo(class='baz'){:class => 'bar'}[foo]", :locals => {:foo => foo}))
845
+ assert_equal("<div class='bar baz foo struct_user' id='struct_user_42'></div>\n",
846
+ render(".foo[foo](class='baz'){:class => 'bar'}", :locals => {:foo => foo}))
847
+ assert_equal("<div class='bar baz foo struct_user' id='struct_user_42'></div>\n",
848
+ render(".foo[foo]{:class => 'bar'}(class='baz')", :locals => {:foo => foo}))
849
+ end
850
+
851
+ def test_dynamic_new_attributes
852
+ assert_equal("<a href='12'>bar</a>\n", render("%a(href=foo) bar", :locals => {:foo => 12}))
853
+ 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}))
854
+ end
855
+
856
+ def test_new_attribute_interpolation
857
+ assert_equal("<a href='12'>bar</a>\n", render('%a(href="1#{1 + 1}") bar'))
858
+ assert_equal("<a href='2: 2, 3: 3'>bar</a>\n", render(%q{%a(href='2: #{1 + 1}, 3: #{foo}') bar}, :locals => {:foo => 3}))
859
+ assert_equal(%Q{<a href='1\#{1 + 1}'>bar</a>\n}, render('%a(href="1\#{1 + 1}") bar'))
860
+ end
861
+
862
+ def test_truthy_new_attributes
863
+ assert_equal("<a href='href'>bar</a>\n", render("%a(href) bar"))
864
+ assert_equal("<a bar='baz' href>bar</a>\n", render("%a(href bar='baz') bar", :format => :html5))
865
+ assert_equal("<a href='href'>bar</a>\n", render("%a(href=true) bar"))
866
+ assert_equal("<a>bar</a>\n", render("%a(href=false) bar"))
867
+ end
868
+
869
+ def test_new_attribute_parsing
870
+ assert_equal("<a a2='b2'>bar</a>\n", render("%a(a2=b2) bar", :locals => {:b2 => 'b2'}))
871
+ assert_equal(%Q{<a a='foo"bar'>bar</a>\n}, render(%q{%a(a="#{'foo"bar'}") bar})) #'
872
+ assert_equal(%Q{<a a="foo'bar">bar</a>\n}, render(%q{%a(a="#{"foo'bar"}") bar})) #'
873
+ assert_equal(%Q{<a a='foo"bar'>bar</a>\n}, render(%q{%a(a='foo"bar') bar}))
874
+ assert_equal(%Q{<a a="foo'bar">bar</a>\n}, render(%q{%a(a="foo'bar") bar}))
875
+ assert_equal("<a a:b='foo'>bar</a>\n", render("%a(a:b='foo') bar"))
876
+ assert_equal("<a a='foo' b='bar'>bar</a>\n", render("%a(a = 'foo' b = 'bar') bar"))
877
+ assert_equal("<a a='foo' b='bar'>bar</a>\n", render("%a(a = foo b = bar) bar", :locals => {:foo => 'foo', :bar => 'bar'}))
878
+ assert_equal("<a a='foo'>(b='bar')</a>\n", render("%a(a='foo')(b='bar')"))
879
+ assert_equal("<a a='foo)bar'>baz</a>\n", render("%a(a='foo)bar') baz"))
880
+ assert_equal("<a a='foo'>baz</a>\n", render("%a( a = 'foo' ) baz"))
881
+ end
882
+
883
+ def test_new_attribute_escaping
884
+ assert_equal(%Q{<a a='foo " bar'>bar</a>\n}, render(%q{%a(a="foo \" bar") bar}))
885
+ assert_equal(%Q{<a a='foo \\" bar'>bar</a>\n}, render(%q{%a(a="foo \\\\\" bar") bar}))
886
+
887
+ assert_equal(%Q{<a a="foo ' bar">bar</a>\n}, render(%q{%a(a='foo \' bar') bar}))
888
+ assert_equal(%Q{<a a="foo \\' bar">bar</a>\n}, render(%q{%a(a='foo \\\\\' bar') bar}))
889
+
890
+ assert_equal(%Q{<a a='foo \\ bar'>bar</a>\n}, render(%q{%a(a="foo \\\\ bar") bar}))
891
+ assert_equal(%Q{<a a='foo \#{1 + 1} bar'>bar</a>\n}, render(%q{%a(a="foo \#{1 + 1} bar") bar}))
892
+ end
893
+
894
+ def test_multiline_new_attribute
895
+ assert_equal("<a a='b' c='d'>bar</a>\n", render("%a(a='b'\n c='d') bar"))
896
+ assert_equal("<a a='b' b='c' c='d' d='e' e='f' f='j'>bar</a>\n",
897
+ render("%a(a='b' b='c'\n c='d' d=e\n e='f' f='j') bar", :locals => {:e => 'e'}))
898
+ end
899
+
900
+ def test_new_and_old_attributes
901
+ assert_equal("<a a='b' c='d'>bar</a>\n", render("%a(a='b'){:c => 'd'} bar"))
902
+ assert_equal("<a a='b' c='d'>bar</a>\n", render("%a{:c => 'd'}(a='b') bar"))
903
+ assert_equal("<a a='b' c='d'>bar</a>\n", render("%a(c='d'){:a => 'b'} bar"))
904
+ assert_equal("<a a='b' c='d'>bar</a>\n", render("%a{:a => 'b'}(c='d') bar"))
905
+
906
+ assert_equal("<a a='d'>bar</a>\n", render("%a{:a => 'b'}(a='d') bar"))
907
+ assert_equal("<a a='b'>bar</a>\n", render("%a(a='d'){:a => 'b'} bar"))
908
+
909
+ assert_equal("<a a='b' b='c' c='d' d='e'>bar</a>\n",
910
+ render("%a{:a => 'b',\n:b => 'c'}(c='d'\nd='e') bar"))
911
+ end
912
+
913
+ # Encodings
914
+
915
+ unless Haml::Util.ruby1_8?
916
+ def test_default_encoding
917
+ assert_equal(Encoding.find("utf-8"), render(<<HAML.encode("us-ascii")).encoding)
918
+ HTML
919
+ %p bar
920
+ %p foo
921
+ HAML
922
+ end
923
+
924
+ def test_convert_template_render
925
+ assert_equal(<<HTML, render(<<HAML.encode("iso-8859-1"), :encoding => "utf-8"))
926
+ <p>bâr</p>
927
+ <p>föö</p>
928
+ HTML
929
+ %p bâr
930
+ %p föö
931
+ HAML
932
+ end
933
+
934
+ def test_convert_template_render_proc
935
+ assert_converts_template_properly {|e| e.render_proc.call}
936
+ end
937
+
938
+ def test_convert_template_render
939
+ assert_converts_template_properly {|e| e.render}
940
+ end
941
+
942
+ def test_convert_template_def_method
943
+ assert_converts_template_properly do |e|
944
+ o = Object.new
945
+ e.def_method(o, :render)
946
+ o.render
947
+ end
948
+ end
949
+ end
950
+
951
+ private
952
+
953
+ def assert_converts_template_properly
954
+ engine = Haml::Engine.new(<<HAML.encode("iso-8859-1"), :encoding => "utf-8")
955
+ %p bâr
956
+ %p föö
957
+ HAML
958
+ assert_equal(<<HTML, yield(engine))
959
+ <p>bâr</p>
960
+ <p>föö</p>
961
+ HTML
962
+ end
668
963
  end