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.
- data/.yardopts +5 -0
- data/CONTRIBUTING +4 -0
- data/MIT-LICENSE +20 -0
- data/README.md +347 -0
- data/REVISION +1 -0
- data/Rakefile +371 -0
- data/VERSION +1 -0
- data/VERSION_NAME +1 -0
- data/bin/css2sass +7 -0
- data/bin/haml +9 -0
- data/bin/html2haml +7 -0
- data/bin/sass +8 -0
- data/extra/haml-mode.el +663 -0
- data/extra/sass-mode.el +205 -0
- data/extra/update_watch.rb +13 -0
- data/init.rb +8 -0
- data/lib/haml.rb +40 -0
- data/lib/haml/buffer.rb +307 -0
- data/lib/haml/engine.rb +301 -0
- data/lib/haml/error.rb +22 -0
- data/lib/haml/exec.rb +470 -0
- data/lib/haml/filters.rb +341 -0
- data/lib/haml/helpers.rb +560 -0
- data/lib/haml/helpers/action_view_extensions.rb +40 -0
- data/lib/haml/helpers/action_view_mods.rb +176 -0
- data/lib/haml/herb.rb +96 -0
- data/lib/haml/html.rb +308 -0
- data/lib/haml/precompiler.rb +997 -0
- data/lib/haml/shared.rb +78 -0
- data/lib/haml/template.rb +51 -0
- data/lib/haml/template/patch.rb +58 -0
- data/lib/haml/template/plugin.rb +71 -0
- data/lib/haml/util.rb +244 -0
- data/lib/haml/version.rb +64 -0
- data/lib/sass.rb +24 -0
- data/lib/sass/css.rb +423 -0
- data/lib/sass/engine.rb +491 -0
- data/lib/sass/environment.rb +79 -0
- data/lib/sass/error.rb +162 -0
- data/lib/sass/files.rb +133 -0
- data/lib/sass/plugin.rb +170 -0
- data/lib/sass/plugin/merb.rb +57 -0
- data/lib/sass/plugin/rails.rb +23 -0
- data/lib/sass/repl.rb +58 -0
- data/lib/sass/script.rb +55 -0
- data/lib/sass/script/bool.rb +17 -0
- data/lib/sass/script/color.rb +183 -0
- data/lib/sass/script/funcall.rb +50 -0
- data/lib/sass/script/functions.rb +199 -0
- data/lib/sass/script/lexer.rb +191 -0
- data/lib/sass/script/literal.rb +177 -0
- data/lib/sass/script/node.rb +14 -0
- data/lib/sass/script/number.rb +381 -0
- data/lib/sass/script/operation.rb +45 -0
- data/lib/sass/script/parser.rb +222 -0
- data/lib/sass/script/string.rb +12 -0
- data/lib/sass/script/unary_operation.rb +34 -0
- data/lib/sass/script/variable.rb +31 -0
- data/lib/sass/tree/comment_node.rb +84 -0
- data/lib/sass/tree/debug_node.rb +30 -0
- data/lib/sass/tree/directive_node.rb +70 -0
- data/lib/sass/tree/for_node.rb +48 -0
- data/lib/sass/tree/if_node.rb +54 -0
- data/lib/sass/tree/import_node.rb +69 -0
- data/lib/sass/tree/mixin_def_node.rb +29 -0
- data/lib/sass/tree/mixin_node.rb +48 -0
- data/lib/sass/tree/node.rb +252 -0
- data/lib/sass/tree/prop_node.rb +106 -0
- data/lib/sass/tree/root_node.rb +56 -0
- data/lib/sass/tree/rule_node.rb +220 -0
- data/lib/sass/tree/variable_node.rb +34 -0
- data/lib/sass/tree/while_node.rb +31 -0
- data/rails/init.rb +1 -0
- data/test/benchmark.rb +99 -0
- data/test/haml/engine_test.rb +1129 -0
- data/test/haml/helper_test.rb +282 -0
- data/test/haml/html2haml_test.rb +258 -0
- data/test/haml/markaby/standard.mab +52 -0
- data/test/haml/mocks/article.rb +6 -0
- data/test/haml/results/content_for_layout.xhtml +12 -0
- data/test/haml/results/eval_suppressed.xhtml +9 -0
- data/test/haml/results/filters.xhtml +62 -0
- data/test/haml/results/helpers.xhtml +93 -0
- data/test/haml/results/helpful.xhtml +10 -0
- data/test/haml/results/just_stuff.xhtml +68 -0
- data/test/haml/results/list.xhtml +12 -0
- data/test/haml/results/nuke_inner_whitespace.xhtml +40 -0
- data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
- data/test/haml/results/original_engine.xhtml +20 -0
- data/test/haml/results/partial_layout.xhtml +5 -0
- data/test/haml/results/partials.xhtml +21 -0
- data/test/haml/results/render_layout.xhtml +3 -0
- data/test/haml/results/silent_script.xhtml +74 -0
- data/test/haml/results/standard.xhtml +162 -0
- data/test/haml/results/tag_parsing.xhtml +23 -0
- data/test/haml/results/very_basic.xhtml +5 -0
- data/test/haml/results/whitespace_handling.xhtml +89 -0
- data/test/haml/rhtml/_av_partial_1.rhtml +12 -0
- data/test/haml/rhtml/_av_partial_2.rhtml +8 -0
- data/test/haml/rhtml/action_view.rhtml +62 -0
- data/test/haml/rhtml/standard.rhtml +54 -0
- data/test/haml/spec_test.rb +44 -0
- data/test/haml/template_test.rb +217 -0
- data/test/haml/templates/_av_partial_1.haml +9 -0
- data/test/haml/templates/_av_partial_1_ugly.haml +9 -0
- data/test/haml/templates/_av_partial_2.haml +5 -0
- data/test/haml/templates/_av_partial_2_ugly.haml +5 -0
- data/test/haml/templates/_layout.erb +3 -0
- data/test/haml/templates/_layout_for_partial.haml +3 -0
- data/test/haml/templates/_partial.haml +8 -0
- data/test/haml/templates/_text_area.haml +3 -0
- data/test/haml/templates/action_view.haml +47 -0
- data/test/haml/templates/action_view_ugly.haml +47 -0
- data/test/haml/templates/breakage.haml +8 -0
- data/test/haml/templates/content_for_layout.haml +8 -0
- data/test/haml/templates/eval_suppressed.haml +11 -0
- data/test/haml/templates/filters.haml +66 -0
- data/test/haml/templates/helpers.haml +95 -0
- data/test/haml/templates/helpful.haml +11 -0
- data/test/haml/templates/just_stuff.haml +83 -0
- data/test/haml/templates/list.haml +12 -0
- data/test/haml/templates/nuke_inner_whitespace.haml +32 -0
- data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
- data/test/haml/templates/original_engine.haml +17 -0
- data/test/haml/templates/partial_layout.haml +3 -0
- data/test/haml/templates/partialize.haml +1 -0
- data/test/haml/templates/partials.haml +12 -0
- data/test/haml/templates/render_layout.haml +2 -0
- data/test/haml/templates/silent_script.haml +40 -0
- data/test/haml/templates/standard.haml +42 -0
- data/test/haml/templates/standard_ugly.haml +42 -0
- data/test/haml/templates/tag_parsing.haml +21 -0
- data/test/haml/templates/very_basic.haml +4 -0
- data/test/haml/templates/whitespace_handling.haml +87 -0
- data/test/haml/util_test.rb +92 -0
- data/test/linked_rails.rb +12 -0
- data/test/sass/css2sass_test.rb +294 -0
- data/test/sass/engine_test.rb +956 -0
- data/test/sass/functions_test.rb +126 -0
- data/test/sass/more_results/more1.css +9 -0
- data/test/sass/more_results/more1_with_line_comments.css +26 -0
- data/test/sass/more_results/more_import.css +29 -0
- data/test/sass/more_templates/_more_partial.sass +2 -0
- data/test/sass/more_templates/more1.sass +23 -0
- data/test/sass/more_templates/more_import.sass +11 -0
- data/test/sass/plugin_test.rb +229 -0
- data/test/sass/results/alt.css +4 -0
- data/test/sass/results/basic.css +9 -0
- data/test/sass/results/compact.css +5 -0
- data/test/sass/results/complex.css +87 -0
- data/test/sass/results/compressed.css +1 -0
- data/test/sass/results/expanded.css +19 -0
- data/test/sass/results/import.css +29 -0
- data/test/sass/results/line_numbers.css +49 -0
- data/test/sass/results/mixins.css +95 -0
- data/test/sass/results/multiline.css +24 -0
- data/test/sass/results/nested.css +22 -0
- data/test/sass/results/parent_ref.css +13 -0
- data/test/sass/results/script.css +16 -0
- data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
- data/test/sass/results/subdir/subdir.css +3 -0
- data/test/sass/results/units.css +11 -0
- data/test/sass/script_test.rb +261 -0
- data/test/sass/templates/_partial.sass +2 -0
- data/test/sass/templates/alt.sass +16 -0
- data/test/sass/templates/basic.sass +23 -0
- data/test/sass/templates/bork1.sass +2 -0
- data/test/sass/templates/bork2.sass +2 -0
- data/test/sass/templates/bork3.sass +2 -0
- data/test/sass/templates/compact.sass +17 -0
- data/test/sass/templates/complex.sass +307 -0
- data/test/sass/templates/compressed.sass +15 -0
- data/test/sass/templates/expanded.sass +17 -0
- data/test/sass/templates/import.sass +11 -0
- data/test/sass/templates/importee.sass +19 -0
- data/test/sass/templates/line_numbers.sass +13 -0
- data/test/sass/templates/mixins.sass +76 -0
- data/test/sass/templates/multiline.sass +20 -0
- data/test/sass/templates/nested.sass +25 -0
- data/test/sass/templates/nested_bork1.sass +2 -0
- data/test/sass/templates/nested_bork2.sass +2 -0
- data/test/sass/templates/nested_bork3.sass +2 -0
- data/test/sass/templates/parent_ref.sass +25 -0
- data/test/sass/templates/script.sass +101 -0
- data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
- data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
- data/test/sass/templates/subdir/subdir.sass +6 -0
- data/test/sass/templates/units.sass +11 -0
- data/test/test_helper.rb +44 -0
- 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
|
data/rails/init.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Kernel.load File.join(File.dirname(__FILE__), '..', 'init.rb')
|
data/test/benchmark.rb
ADDED
|
@@ -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
 bar
 baz</textarea>\n",
|
|
218
|
+
render('%textarea= "Foo\n bar\n baz"'))
|
|
219
|
+
|
|
220
|
+
assert_equal("<pre>Foo
 bar
 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
|
+
&
|
|
406
|
+
&
|
|
407
|
+
&
|
|
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>&</p>
|
|
436
|
+
<p>&</p>
|
|
437
|
+
<p>&</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 & 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 & 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
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&stupid' />\n",
|
|
524
|
+
render("%img.atlantis{:style => 'ugly&stupid'}"))
|
|
525
|
+
assert_equal("<div class='atlantis' style='ugly&stupid'>foo</div>\n",
|
|
526
|
+
render(".atlantis{:style => 'ugly&stupid'} foo"))
|
|
527
|
+
assert_equal("<p class='atlantis' style='ugly&stupid'>foo</p>\n",
|
|
528
|
+
render("%p.atlantis{:style => 'ugly&stupid'}= 'foo'"))
|
|
529
|
+
assert_equal("<p class='atlantis' style='ugly
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='&foo.png' />\n",
|
|
535
|
+
render("%img{:width => nil, :src => '&foo.png', :alt => String.new}"))
|
|
536
|
+
assert_equal("<p alt='' src='&foo.png'>foo</p>\n",
|
|
537
|
+
render("%p{:width => nil, :src => '&foo.png', :alt => String.new} foo"))
|
|
538
|
+
assert_equal("<div alt='' src='&foo.png'>foo</div>\n",
|
|
539
|
+
render("%div{:width => nil, :src => '&foo.png', :alt => String.new}= 'foo'"))
|
|
540
|
+
assert_equal("<img alt='' src='foo
.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&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&3</p>\n", render("%p&== \#{2+2}&\#{2+1}", :escape_html => true))
|
|
551
|
+
assert_equal("<p>4&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&3\n</p>\n", render("%p\n &== \#{2+2}&\#{2+1}", :escape_html => true))
|
|
561
|
+
assert_equal("<p>\n 4&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&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&3</p>\n", render("%p& \#{2+2}&\#{2+1}", :escape_html => true))
|
|
576
|
+
assert_equal("<p>4&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&3\n</p>\n", render("%p\n & \#{2+2}&\#{2+1}", :escape_html => true))
|
|
586
|
+
assert_equal("<p>\n 4&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 & 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 & 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&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"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 => 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
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
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 => 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
|