better_html 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +30 -0
  4. data/lib/better_html.rb +53 -0
  5. data/lib/better_html/better_erb.rb +68 -0
  6. data/lib/better_html/better_erb/erubi_implementation.rb +50 -0
  7. data/lib/better_html/better_erb/erubis_implementation.rb +44 -0
  8. data/lib/better_html/better_erb/runtime_checks.rb +161 -0
  9. data/lib/better_html/better_erb/validated_output_buffer.rb +166 -0
  10. data/lib/better_html/errors.rb +22 -0
  11. data/lib/better_html/helpers.rb +5 -0
  12. data/lib/better_html/html_attributes.rb +26 -0
  13. data/lib/better_html/node_iterator.rb +144 -0
  14. data/lib/better_html/node_iterator/attribute.rb +34 -0
  15. data/lib/better_html/node_iterator/base.rb +27 -0
  16. data/lib/better_html/node_iterator/cdata.rb +8 -0
  17. data/lib/better_html/node_iterator/comment.rb +8 -0
  18. data/lib/better_html/node_iterator/content_node.rb +13 -0
  19. data/lib/better_html/node_iterator/element.rb +26 -0
  20. data/lib/better_html/node_iterator/html_erb.rb +78 -0
  21. data/lib/better_html/node_iterator/html_lodash.rb +101 -0
  22. data/lib/better_html/node_iterator/javascript_erb.rb +60 -0
  23. data/lib/better_html/node_iterator/location.rb +14 -0
  24. data/lib/better_html/node_iterator/text.rb +8 -0
  25. data/lib/better_html/node_iterator/token.rb +8 -0
  26. data/lib/better_html/railtie.rb +7 -0
  27. data/lib/better_html/test_helper/ruby_expr.rb +89 -0
  28. data/lib/better_html/test_helper/safe_erb_tester.rb +202 -0
  29. data/lib/better_html/test_helper/safe_lodash_tester.rb +121 -0
  30. data/lib/better_html/test_helper/safety_tester_base.rb +34 -0
  31. data/lib/better_html/tree.rb +113 -0
  32. data/lib/better_html/version.rb +3 -0
  33. data/lib/tasks/better_html_tasks.rake +4 -0
  34. data/test/better_html/better_erb/implementation_test.rb +402 -0
  35. data/test/better_html/helpers_test.rb +49 -0
  36. data/test/better_html/node_iterator/html_lodash_test.rb +132 -0
  37. data/test/better_html/node_iterator_test.rb +221 -0
  38. data/test/better_html/test_helper/ruby_expr_test.rb +206 -0
  39. data/test/better_html/test_helper/safe_erb_tester_test.rb +358 -0
  40. data/test/better_html/test_helper/safe_lodash_tester_test.rb +80 -0
  41. data/test/better_html/tree_test.rb +110 -0
  42. data/test/dummy/README.rdoc +28 -0
  43. data/test/dummy/Rakefile +6 -0
  44. data/test/dummy/app/assets/javascripts/application.js +13 -0
  45. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  46. data/test/dummy/app/controllers/application_controller.rb +5 -0
  47. data/test/dummy/app/helpers/application_helper.rb +2 -0
  48. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  49. data/test/dummy/bin/bundle +3 -0
  50. data/test/dummy/bin/rails +4 -0
  51. data/test/dummy/bin/rake +4 -0
  52. data/test/dummy/bin/setup +29 -0
  53. data/test/dummy/config.ru +4 -0
  54. data/test/dummy/config/application.rb +26 -0
  55. data/test/dummy/config/boot.rb +5 -0
  56. data/test/dummy/config/database.yml +25 -0
  57. data/test/dummy/config/environment.rb +5 -0
  58. data/test/dummy/config/environments/development.rb +41 -0
  59. data/test/dummy/config/environments/production.rb +79 -0
  60. data/test/dummy/config/environments/test.rb +42 -0
  61. data/test/dummy/config/initializers/assets.rb +11 -0
  62. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  63. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  64. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  65. data/test/dummy/config/initializers/inflections.rb +16 -0
  66. data/test/dummy/config/initializers/mime_types.rb +4 -0
  67. data/test/dummy/config/initializers/session_store.rb +3 -0
  68. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  69. data/test/dummy/config/locales/en.yml +23 -0
  70. data/test/dummy/config/routes.rb +56 -0
  71. data/test/dummy/config/secrets.yml +22 -0
  72. data/test/dummy/public/404.html +67 -0
  73. data/test/dummy/public/422.html +67 -0
  74. data/test/dummy/public/500.html +66 -0
  75. data/test/dummy/public/favicon.ico +0 -0
  76. data/test/test_helper.rb +19 -0
  77. metadata +205 -0
@@ -0,0 +1,358 @@
1
+ require 'test_helper'
2
+ require 'better_html/test_helper/safe_erb_tester'
3
+
4
+ module BetterHtml
5
+ module TestHelper
6
+ class SafeErbTesterTest < ActiveSupport::TestCase
7
+ setup do
8
+ BetterHtml.config
9
+ .stubs(:javascript_safe_methods)
10
+ .returns(['j', 'escape_javascript', 'to_json'])
11
+ end
12
+
13
+ test "string without interpolation is safe" do
14
+ errors = parse(<<-EOF).errors
15
+ <a onclick="alert('<%= "something" %>')">
16
+ EOF
17
+
18
+ assert_equal 1, errors.size
19
+ assert_equal '<%= "something" %>', errors.first.token.text
20
+ assert_equal "erb interpolation in javascript attribute must call '(...).to_json'", errors.first.message
21
+ end
22
+
23
+ test "string with interpolation" do
24
+ errors = parse(<<-EOF).errors
25
+ <a onclick="<%= "hello \#{name}" %>">
26
+ EOF
27
+
28
+ assert_equal 1, errors.size
29
+ assert_equal '<%= "hello #{name}" %>', errors.first.token.text
30
+ assert_equal "erb interpolation in javascript attribute must call '(...).to_json'", errors.first.message
31
+ end
32
+
33
+ test "string with interpolation and ternary" do
34
+ errors = parse(<<-EOF).errors
35
+ <a onclick="<%= "hello \#{foo ? bar : baz}" if bla? %>">
36
+ EOF
37
+
38
+ assert_equal 2, errors.size
39
+
40
+ assert_equal '<%= "hello #{foo ? bar : baz}" if bla? %>', errors.first.token.text
41
+ assert_equal "erb interpolation in javascript attribute must call '(...).to_json'", errors.first.message
42
+
43
+ assert_equal '<%= "hello #{foo ? bar : baz}" if bla? %>', errors.first.token.text
44
+ assert_equal "erb interpolation in javascript attribute must call '(...).to_json'", errors.first.message
45
+ end
46
+
47
+ test "plain erb tag in html attribute" do
48
+ errors = parse(<<-EOF).errors
49
+ <a onclick="method(<%= unsafe %>)">
50
+ EOF
51
+
52
+ assert_equal 1, errors.size
53
+ assert_equal '<%= unsafe %>', errors.first.token.text
54
+ assert_equal "erb interpolation in javascript attribute must call '(...).to_json'", errors.first.message
55
+ end
56
+
57
+ test "to_json is safe in html attribute" do
58
+ errors = parse(<<-EOF).errors
59
+ <a onclick="method(<%= unsafe.to_json %>)">
60
+ EOF
61
+ assert_predicate errors, :empty?
62
+ end
63
+
64
+ test "ternary with safe javascript escaping" do
65
+ errors = parse(<<-EOF).errors
66
+ <a onclick="method(<%= foo ? bar.to_json : j(baz) %>)">
67
+ EOF
68
+ assert_predicate errors, :empty?
69
+ end
70
+
71
+ test "ternary with unsafe javascript escaping" do
72
+ errors = parse(<<-EOF).errors
73
+ <a onclick="method(<%= foo ? bar : j(baz) %>)">
74
+ EOF
75
+
76
+ assert_equal 1, errors.size
77
+ assert_equal '<%= foo ? bar : j(baz) %>', errors.first.token.text
78
+ assert_equal "erb interpolation in javascript attribute must call '(...).to_json'", errors.first.message
79
+ end
80
+
81
+ test "j is safe in html attribute" do
82
+ errors = parse(<<-EOF).errors
83
+ <a onclick="method('<%= j unsafe %>')">
84
+ EOF
85
+ assert_predicate errors, :empty?
86
+ end
87
+
88
+ test "j() is safe in html attribute" do
89
+ errors = parse(<<-EOF).errors
90
+ <a onclick="method('<%= j(unsafe) %>')">
91
+ EOF
92
+ assert_predicate errors, :empty?
93
+ end
94
+
95
+ test "escape_javascript is safe in html attribute" do
96
+ errors = parse(<<-EOF).errors
97
+ <a onclick="method(<%= escape_javascript unsafe %>)">
98
+ EOF
99
+ assert_predicate errors, :empty?
100
+ end
101
+
102
+ test "escape_javascript() is safe in html attribute" do
103
+ errors = parse(<<-EOF).errors
104
+ <a onclick="method(<%= escape_javascript(unsafe) %>)">
105
+ EOF
106
+ assert_predicate errors, :empty?
107
+ end
108
+
109
+ test "html_safe is never safe in html attribute, even non javascript attributes like href" do
110
+ errors = parse(<<-EOF).errors
111
+ <a href="<%= unsafe.html_safe %>">
112
+ EOF
113
+
114
+ assert_equal 1, errors.size
115
+ assert_equal '<%= unsafe.html_safe %>', errors.first.token.text
116
+ assert_equal "erb interpolation with '<%= (...).html_safe %>' inside html attribute is never safe", errors.first.message
117
+ end
118
+
119
+ test "html_safe is never safe in html attribute, even with to_json" do
120
+ errors = parse(<<-EOF).errors
121
+ <a onclick="method(<%= unsafe.to_json.html_safe %>)">
122
+ EOF
123
+
124
+ assert_equal 1, errors.size
125
+ assert_equal '<%= unsafe.to_json.html_safe %>', errors.first.token.text
126
+ assert_equal "erb interpolation with '<%= (...).html_safe %>' inside html attribute is never safe", errors.first.message
127
+ end
128
+
129
+ test "<%== is never safe in html attribute, even non javascript attributes like href" do
130
+ errors = parse(<<-EOF).errors
131
+ <a href="<%== unsafe %>">
132
+ EOF
133
+
134
+ assert_equal 1, errors.size
135
+ assert_equal '<%== unsafe %>', errors.first.token.text
136
+ assert_includes "erb interpolation with '<%==' inside html attribute is never safe", errors.first.message
137
+ end
138
+
139
+ test "<%== is never safe in html attribute, even with to_json" do
140
+ errors = parse(<<-EOF).errors
141
+ <a onclick="method(<%== unsafe.to_json %>)">
142
+ EOF
143
+
144
+ assert_equal 1, errors.size
145
+ assert_equal '<%== unsafe.to_json %>', errors.first.token.text
146
+ assert_includes "erb interpolation with '<%==' inside html attribute is never safe", errors.first.message
147
+ end
148
+
149
+ test "raw is never safe in html attribute, even non javascript attributes like href" do
150
+ errors = parse(<<-EOF).errors
151
+ <a href="<%= raw unsafe %>">
152
+ EOF
153
+
154
+ assert_equal 1, errors.size
155
+ assert_equal '<%= raw unsafe %>', errors.first.token.text
156
+ assert_equal "erb interpolation with '<%= raw(...) %>' inside html attribute is never safe", errors.first.message
157
+ end
158
+
159
+ test "raw is never safe in html attribute, even with to_json" do
160
+ errors = parse(<<-EOF).errors
161
+ <a onclick="method(<%= raw unsafe.to_json %>)">
162
+ EOF
163
+
164
+ assert_equal 1, errors.size
165
+ assert_equal '<%= raw unsafe.to_json %>', errors.first.token.text
166
+ assert_equal "erb interpolation with '<%= raw(...) %>' inside html attribute is never safe", errors.first.message
167
+ end
168
+
169
+ test "unsafe erb in <script> tag without type" do
170
+ errors = parse(<<-EOF).errors
171
+ <script>
172
+ if (a < 1) { <%= unsafe %> }
173
+ </script>
174
+ EOF
175
+
176
+ assert_equal 1, errors.size
177
+ assert_equal '<%= unsafe %>', errors.first.token.text
178
+ assert_equal "erb interpolation in javascript tag must call '(...).to_json'", errors.first.message
179
+ end
180
+
181
+ test "unsafe erb in javascript template" do
182
+ errors = parse(<<-JS, template_language: :javascript).errors
183
+ if (a < 1) { <%= unsafe %> }
184
+ JS
185
+
186
+ assert_equal 1, errors.size
187
+ assert_equal '<%= unsafe %>', errors.first.token.text
188
+ assert_equal "erb interpolation in javascript tag must call '(...).to_json'", errors.first.message
189
+ end
190
+
191
+ test "<script> tag without calls is unsafe" do
192
+ errors = parse(<<-EOF).errors
193
+ <script type="text/javascript">
194
+ if (a < 1) { <%= "unsafe" %> }
195
+ </script>
196
+ EOF
197
+
198
+ assert_equal 1, errors.size
199
+ assert_equal '<%= "unsafe" %>', errors.first.token.text
200
+ assert_equal "erb interpolation in javascript tag must call '(...).to_json'", errors.first.message
201
+ end
202
+
203
+ test "javascript template without calls is unsafe" do
204
+ errors = parse(<<-JS, template_language: :javascript).errors
205
+ if (a < 1) { <%= "unsafe" %> }
206
+ JS
207
+
208
+ assert_equal 1, errors.size
209
+ assert_equal '<%= "unsafe" %>', errors.first.token.text
210
+ assert_equal "erb interpolation in javascript tag must call '(...).to_json'", errors.first.message
211
+ end
212
+
213
+ test "unsafe erb in javascript_tag" do
214
+ errors = parse(<<-EOF).errors
215
+ <%= javascript_tag do %>
216
+ if (a < 1) { <%= unsafe %> }
217
+ <% end %>
218
+ EOF
219
+
220
+ assert_equal 1, errors.size
221
+ assert_equal '<%= javascript_tag do %>', errors.first.token.text
222
+ assert_includes "'javascript_tag do' syntax is deprecated; use inline <script> instead", errors.first.message
223
+ end
224
+
225
+ test "unsafe erb in <script> tag with text/javascript content type" do
226
+ errors = parse(<<-EOF).errors
227
+ <script type="text/javascript">
228
+ if (a < 1) { <%= unsafe %> }
229
+ </script>
230
+ EOF
231
+
232
+ assert_equal 1, errors.size
233
+ assert_equal '<%= unsafe %>', errors.first.token.text
234
+ assert_equal "erb interpolation in javascript tag must call '(...).to_json'", errors.first.message
235
+ end
236
+
237
+ test "<script> tag with non executable content type is ignored" do
238
+ errors = parse(<<-EOF).errors
239
+ <script type="text/html">
240
+ <a onclick="<%= unsafe %>">
241
+ </script>
242
+ EOF
243
+
244
+ assert_predicate errors, :empty?
245
+ end
246
+
247
+ test "statements not allowed in script tags" do
248
+ errors = parse(<<-EOF).errors
249
+ <script type="text/javascript">
250
+ <% if foo? %>
251
+ bla
252
+ <% end %>
253
+ </script>
254
+ EOF
255
+
256
+ assert_equal 1, errors.size
257
+ assert_equal "<% if foo? \n%>", errors.first.token.text
258
+ assert_equal "erb statement not allowed here; did you mean '<%=' ?", errors.first.message
259
+ end
260
+
261
+ test "statements not allowed in javascript template" do
262
+ errors = parse(<<-JS, template_language: :javascript).errors
263
+ <% if foo %>
264
+ bla
265
+ <% end %>
266
+ JS
267
+
268
+ assert_equal 1, errors.size
269
+ assert_equal "<% if foo \n%>", errors.first.token.text
270
+ assert_equal "erb statement not allowed here; did you mean '<%=' ?", errors.first.message
271
+ end
272
+
273
+ test "script tag without content" do
274
+ errors = parse(<<-EOF).errors
275
+ <script type="text/javascript"></script>
276
+ EOF
277
+
278
+ assert_predicate errors, :empty?
279
+ end
280
+
281
+ test "statement after script regression" do
282
+ errors = parse(<<-EOF).errors
283
+ <script type="text/javascript">
284
+ foo()
285
+ </script>
286
+ <% if condition? %>
287
+ EOF
288
+
289
+ assert_predicate errors, :empty?
290
+ end
291
+
292
+ test "<script> with to_json is safe" do
293
+ errors = parse(<<-EOF).errors
294
+ <script type="text/javascript">
295
+ <%= unsafe.to_json %>
296
+ </script>
297
+ EOF
298
+
299
+ assert_predicate errors, :empty?
300
+ end
301
+
302
+ test "javascript template with to_json is safe" do
303
+ errors = parse(<<-JS, template_language: :javascript).errors
304
+ <%= unsafe.to_json %>
305
+ JS
306
+
307
+ assert_predicate errors, :empty?
308
+ end
309
+
310
+ test "<script> with raw and to_json is safe" do
311
+ errors = parse(<<-EOF).errors
312
+ <script type="text/javascript">
313
+ <%= raw unsafe.to_json %>
314
+ </script>
315
+ EOF
316
+
317
+ assert_predicate errors, :empty?
318
+ end
319
+
320
+ test "javascript template with raw and to_json is safe" do
321
+ errors = parse(<<-JS, template_language: :javascript).errors
322
+ <%= raw unsafe.to_json %>
323
+ JS
324
+
325
+ assert_predicate errors, :empty?
326
+ end
327
+
328
+ test "end statements are allowed in script tags" do
329
+ errors = parse(<<-EOF).errors
330
+ <script type="text/template">
331
+ <%= ui_form do %>
332
+ <div></div>
333
+ <% end %>
334
+ </script>
335
+ EOF
336
+
337
+ assert_predicate errors, :empty?
338
+ end
339
+
340
+ test "statements are allowed in text/html tags" do
341
+ errors = parse(<<-EOF).errors
342
+ <script type="text/html">
343
+ <% if condition? %>
344
+ <div></div>
345
+ <% end %>
346
+ </script>
347
+ EOF
348
+
349
+ assert_predicate errors, :empty?
350
+ end
351
+
352
+ private
353
+ def parse(data, template_language: :html)
354
+ SafeErbTester::Tester.new(data, template_language: template_language)
355
+ end
356
+ end
357
+ end
358
+ end
@@ -0,0 +1,80 @@
1
+ require 'test_helper'
2
+ require 'better_html/test_helper/safe_lodash_tester'
3
+
4
+ module BetterHtml
5
+ module TestHelper
6
+ class SafeLodashTesterTest < ActiveSupport::TestCase
7
+ test "interpolate in attribute not allowed" do
8
+ errors = parse(<<-EOF).errors
9
+ <div class="[%! foo %]">
10
+ EOF
11
+
12
+ assert_equal 1, errors.size
13
+ assert_equal '[%! foo %]', errors.first.token.text
14
+ assert_equal "lodash interpolation with '[%!' inside html attribute is never safe", errors.first.message
15
+ end
16
+
17
+ test "escape in attribute is allowed" do
18
+ errors = parse(<<-EOF).errors
19
+ <div class="[%= foo %]">
20
+ EOF
21
+
22
+ assert_predicate errors, :empty?
23
+ end
24
+
25
+ test "escape in javascript attribute not allowed" do
26
+ errors = parse(<<-EOF).errors
27
+ <div onclick="[%= foo %]">
28
+ EOF
29
+
30
+ assert_equal 1, errors.size
31
+ assert_equal '[%= foo %]', errors.first.token.text
32
+ assert_equal "lodash interpolation in javascript attribute `onclick` must call `JSON.stringify(foo)`", errors.first.message
33
+ end
34
+
35
+ test "escape in javascript attribute with JSON.stringify is allowed" do
36
+ errors = parse(<<-EOF).errors
37
+ <div onclick="[%= JSON.stringify(foo) %]">
38
+ EOF
39
+
40
+ assert_predicate errors, :empty?
41
+ end
42
+
43
+ test "script tag is not allowed" do
44
+ errors = parse(<<-EOF).errors
45
+ <script type="text/javascript"></script>
46
+ EOF
47
+
48
+ assert_equal 1, errors.size
49
+ assert_equal 'script', errors.first.token.text
50
+ assert_equal "No script tags allowed nested in lodash templates", errors.first.message
51
+ end
52
+
53
+ test "statement not allowed in attribute name" do
54
+ errors = parse(<<-EOF).errors
55
+ <div class[% if (foo) %]="foo">
56
+ EOF
57
+
58
+ assert_equal 1, errors.size
59
+ assert_equal '[% if (foo) %]', errors.first.token.text
60
+ assert_equal "javascript statement not allowed here; did you mean '[%=' ?", errors.first.message
61
+ end
62
+
63
+ test "statement not allowed in attribute value" do
64
+ errors = parse(<<-EOF).errors
65
+ <div class="foo[% if (foo) %]">
66
+ EOF
67
+
68
+ assert_equal 1, errors.size
69
+ assert_equal '[% if (foo) %]', errors.first.token.text
70
+ assert_equal "javascript statement not allowed here; did you mean '[%=' ?", errors.first.message
71
+ end
72
+
73
+ private
74
+
75
+ def parse(data)
76
+ SafeLodashTester::Tester.new(data)
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,110 @@
1
+ require 'test_helper'
2
+
3
+ module BetterHtml
4
+ class TreeTest < ActiveSupport::TestCase
5
+ test "simple node" do
6
+ tree = Tree.new("<div></div>")
7
+ assert_predicate tree.errors, :empty?
8
+
9
+ assert_equal 1, tree.root.size
10
+ assert_equal 'div', tree.root[0].name
11
+ assert_equal true, tree.root[0].closed?
12
+ assert_equal false, tree.root[0].self_closing?
13
+ end
14
+
15
+ test "simple self-closing node" do
16
+ tree = Tree.new("<meta />")
17
+ assert_predicate tree.errors, :empty?
18
+
19
+ assert_equal 1, tree.root.size
20
+ assert_equal 'meta', tree.root[0].name
21
+ assert_equal true, tree.root[0].closed?
22
+ assert_equal true, tree.root[0].self_closing?
23
+ end
24
+
25
+ test "mismatched closing tag" do
26
+ tree = Tree.new("<div></p></div>")
27
+ assert_equal 1, tree.errors.size
28
+ assert_equal "mismatched </p> in <div> element", tree.errors[0].message
29
+
30
+ assert_equal 1, tree.root.size
31
+ assert_equal 'div', tree.root[0].name
32
+ assert_equal true, tree.root[0].closed?
33
+ assert_equal false, tree.root[0].self_closing?
34
+ end
35
+
36
+ test "mismatched closing tag at root" do
37
+ tree = Tree.new("</p>")
38
+ assert_equal 1, tree.errors.size
39
+ assert_equal "mismatched </p> at root of tree", tree.errors[0].message
40
+
41
+ assert_predicate tree.root, :empty?
42
+ end
43
+
44
+ test "node with text content" do
45
+ tree = Tree.new("<div>text</div>")
46
+ assert_predicate tree.errors, :empty?
47
+
48
+ assert_equal 1, tree.root.size
49
+ div = tree.root[0]
50
+ assert_equal 1, div.size
51
+ assert_equal 'text', div[0].content
52
+ end
53
+
54
+ test "extract erb from text node" do
55
+ tree = Tree.new("<div>before<%= foo %>after</div>")
56
+ assert_predicate tree.errors, :empty?
57
+
58
+ assert_equal 1, tree.root.size
59
+ div = tree.root[0]
60
+ assert_equal 1, div.size
61
+ text = div[0]
62
+ assert_equal 3, text.content_parts.size
63
+ assert_equal 'before', text.content_parts[0].text
64
+ assert_equal '<%= foo %>', text.content_parts[1].text
65
+ assert_equal ' foo ', text.content_parts[1].code
66
+ assert_equal 'after', text.content_parts[2].text
67
+ end
68
+
69
+ test "closing tag for void element" do
70
+ tree = Tree.new("<br></br>")
71
+ assert_equal 1, tree.errors.size
72
+ assert_equal "end of tag for void element: </br>", tree.errors[0].message
73
+
74
+ assert_equal 1, tree.root.size
75
+ assert_equal 'br', tree.root[0].name
76
+ assert_equal true, tree.root[0].closed?
77
+ assert_equal false, tree.root[0].self_closing?
78
+ assert_equal true, tree.root[0].void?
79
+ end
80
+
81
+ test "properly self-closed void element" do
82
+ tree = Tree.new("<br/>")
83
+ assert_predicate tree.errors, :empty?
84
+
85
+ assert_equal 1, tree.root.size
86
+ assert_equal 'br', tree.root[0].name
87
+ assert_equal true, tree.root[0].closed?
88
+ assert_equal true, tree.root[0].self_closing?
89
+ assert_equal true, tree.root[0].void?
90
+ end
91
+
92
+ test "void elements are nested properly" do
93
+ tree = Tree.new("<div><hr>test</hr></div>")
94
+ assert_equal 1, tree.errors.size
95
+ assert_equal "end of tag for void element: </hr>", tree.errors[0].message
96
+
97
+ assert_equal 1, tree.root.size
98
+ div = tree.root[0]
99
+ assert_equal 2, div.size
100
+ assert_equal true, div[0].element?
101
+ assert_equal true, div[1].text?
102
+ end
103
+
104
+ test "parser errors are bubbled up" do
105
+ tree = Tree.new("<>")
106
+ assert_equal 1, tree.errors.size
107
+ assert_equal "expected '/' or tag name", tree.errors[0].message
108
+ end
109
+ end
110
+ end