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,34 @@
1
+ module BetterHtml
2
+ module TestHelper
3
+ module SafetyTesterBase
4
+
5
+ class SafetyError < InterpolatorError
6
+ attr_reader :token
7
+
8
+ def initialize(token, message)
9
+ @token = token
10
+ super(message)
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def format_safety_error(data, error)
17
+ loc = error.token.location
18
+ s = "On line #{loc.line}\n"
19
+ s << "#{error.message}\n"
20
+ line = extract_line(data, loc.line)
21
+ s << "#{line}\n"
22
+ length = [[loc.stop - loc.start, line.length - loc.column].min, 1].max
23
+ s << "#{' ' * loc.column}#{'^' * length}\n\n"
24
+ s
25
+ end
26
+
27
+ def extract_line(data, line)
28
+ line = data.lines[line-1]
29
+ line.nil? ? "" : line.gsub(/\n$/, '')
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,113 @@
1
+ require 'better_html/node_iterator'
2
+
3
+ module BetterHtml
4
+ class Tree
5
+ attr_reader :errors
6
+ attr_reader :root
7
+
8
+ cattr_accessor :void_elements
9
+ self.void_elements = %w(area base br col embed hr img
10
+ input keygen link menuitem meta param source track wbr)
11
+
12
+ def initialize(data, **options)
13
+ @data = data
14
+ @errors = Errors.new
15
+ @nodes = BetterHtml::NodeIterator.new(data, **options.slice(:template_language))
16
+ @root = TreeRoot.new
17
+ construct!
18
+ @nodes.parser_errors&.each do |error|
19
+ @errors.add(error)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ class TreeError < HtmlError
26
+ attr_reader :token
27
+
28
+ def initialize(token, message)
29
+ @token = token
30
+ super(message)
31
+ end
32
+ end
33
+
34
+ def add_error(token, message)
35
+ @errors.add(TreeError.new(token, message))
36
+ end
37
+
38
+ def construct!
39
+ current = @root
40
+ @nodes.each do |node|
41
+ case node.node_type
42
+ when :text, :comment, :cdata
43
+ current << node
44
+ when :element
45
+ if node.closing?
46
+ if void_elements.include?(node.name)
47
+ add_error(node.name_parts.first,
48
+ "end of tag for void element: </#{node.name}>")
49
+ elsif current.root?
50
+ add_error(node.name_parts.first,
51
+ "mismatched </#{node.name}> at root of tree")
52
+ else
53
+ if node.name == current.name
54
+ current.end_node = node
55
+ current = current.parent
56
+ else
57
+ add_error(node.name_parts.first,
58
+ "mismatched </#{node.name}> in <#{current.name}> element")
59
+ end
60
+ end
61
+ else
62
+ element = Element.new(parent: current, start_node: node)
63
+ current << element
64
+ current = element unless element.closed?
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ class NodeContainer
71
+ attr_accessor :content_nodes
72
+ delegate :each, :[], :each_with_index, :<<, :push,
73
+ :size, :empty?, :any?, to: :content_nodes
74
+
75
+ def root?
76
+ false
77
+ end
78
+
79
+ def initialize
80
+ @content_nodes = []
81
+ end
82
+ end
83
+
84
+ class TreeRoot < NodeContainer
85
+ def root?
86
+ true
87
+ end
88
+ end
89
+
90
+ class Element < NodeContainer
91
+ attr_reader :parent
92
+ attr_accessor :start_node
93
+ attr_accessor :end_node
94
+
95
+ delegate :name, :attributes, :self_closing?, to: :start_node
96
+ delegate :element?, :text?, :comment?, :cdata?, to: :start_node
97
+
98
+ def initialize(parent:, start_node:)
99
+ super()
100
+ @parent = parent
101
+ @start_node = start_node
102
+ end
103
+
104
+ def closed?
105
+ void? || end_node.present? || self_closing?
106
+ end
107
+
108
+ def void?
109
+ BetterHtml::Tree.void_elements.include?(name)
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,3 @@
1
+ module BetterHtml
2
+ VERSION = "0.0.3"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :better_html do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,402 @@
1
+ require 'test_helper'
2
+ require 'ostruct'
3
+ require 'better_html/better_erb'
4
+ require 'json'
5
+
6
+ class BetterHtml::BetterErb::ImplementationTest < ActiveSupport::TestCase
7
+ test "simple template rendering" do
8
+ assert_equal "<foo>some value<foo>",
9
+ render("<foo><%= bar %><foo>", { bar: 'some value' })
10
+ end
11
+
12
+ test "html_safe interpolation" do
13
+ assert_equal "<foo><bar /><foo>",
14
+ render("<foo><%= bar %><foo>", { bar: '<bar />'.html_safe })
15
+ end
16
+
17
+ test "non html_safe interpolation" do
18
+ assert_equal "<foo>&lt;bar /&gt;<foo>",
19
+ render("<foo><%= bar %><foo>", { bar: '<bar />' })
20
+ end
21
+
22
+ test "interpolate non-html_safe inside attribute is escaped" do
23
+ assert_equal "<a href=\" &#39;&quot;&gt;x \">",
24
+ render("<a href=\"<%= value %>\">", { value: ' \'">x ' })
25
+ end
26
+
27
+ test "interpolate html_safe inside attribute is magically force-escaped" do
28
+ e = assert_raises(BetterHtml::UnsafeHtmlError) do
29
+ render("<a href=\"<%= value %>\">", { value: ' \'">x '.html_safe })
30
+ end
31
+ assert_equal "Detected invalid characters as part of the interpolation "\
32
+ "into a quoted attribute value. The value cannot contain the character \".", e.message
33
+ end
34
+
35
+ test "interpolate html_safe inside single quoted attribute" do
36
+ BetterHtml.config.stubs(:allow_single_quoted_attributes).returns(true)
37
+ e = assert_raises(BetterHtml::UnsafeHtmlError) do
38
+ render("<a href=\'<%= value %>\'>", { value: ' \'">x '.html_safe })
39
+ end
40
+ assert_equal "Detected invalid characters as part of the interpolation "\
41
+ "into a quoted attribute value. The value cannot contain the character '.", e.message
42
+ end
43
+
44
+ test "interpolate in attribute name" do
45
+ assert_equal "<a data-safe-foo>",
46
+ render("<a data-<%= value %>-foo>", { value: "safe" })
47
+ end
48
+
49
+ test "interpolate in attribute name with unsafe value with spaces" do
50
+ e = assert_raises(BetterHtml::UnsafeHtmlError) do
51
+ render("<a data-<%= value %>-foo>", { value: "un safe" })
52
+ end
53
+ assert_equal "Detected invalid characters as part of the interpolation "\
54
+ "into a attribute name around 'data-<%= value %>'.", e.message
55
+ end
56
+
57
+ test "interpolate in attribute name with unsafe value with equal sign" do
58
+ e = assert_raises(BetterHtml::UnsafeHtmlError) do
59
+ render("<a data-<%= value %>-foo>", { value: "un=safe" })
60
+ end
61
+ assert_equal "Detected invalid characters as part of the "\
62
+ "interpolation into a attribute name around 'data-<%= value %>'.", e.message
63
+ end
64
+
65
+ test "interpolate in attribute name with unsafe value with quote" do
66
+ e = assert_raises(BetterHtml::UnsafeHtmlError) do
67
+ render("<a data-<%= value %>-foo>", { value: "un\"safe" })
68
+ end
69
+ assert_equal "Detected invalid characters as part of the "\
70
+ "interpolation into a attribute name around 'data-<%= value %>'.", e.message
71
+ end
72
+
73
+ test "interpolate after an attribute name without a value" do
74
+ assert_equal '<a data-foo foo="bar">',
75
+ render("<a data-foo <%= html_attributes(foo: 'bar') %>>")
76
+ end
77
+
78
+ test "interpolate after an attribute name with equal sign" do
79
+ BetterHtml.config.stubs(:allow_unquoted_attributes).returns(true)
80
+ e = assert_raises(BetterHtml::DontInterpolateHere) do
81
+ render("<a data-foo= <%= html_attributes(foo: 'bar') %>>")
82
+ end
83
+ assert_equal "Do not interpolate without quotes after "\
84
+ "attribute around 'data-foo=<%= html_attributes(foo: 'bar') %>'.", e.message
85
+ end
86
+
87
+ test "interpolate after an attribute value" do
88
+ e = assert_raises(BetterHtml::DontInterpolateHere) do
89
+ render("<a foo=\"xx\"<%= html_attributes(foo: 'bar') %>>")
90
+ end
91
+ assert_equal "Add a space after this attribute value. "\
92
+ "Instead of <a foo=\"xx\"<%= html_attributes(foo: 'bar') %>>"\
93
+ " try <a foo=\"xx\" <%= html_attributes(foo: 'bar') %>>.", e.message
94
+ end
95
+
96
+ test "interpolate in attribute without quotes" do
97
+ BetterHtml.config.stubs(:allow_unquoted_attributes).returns(true)
98
+ e = assert_raises(BetterHtml::DontInterpolateHere) do
99
+ render("<a href=<%= value %>>", { value: "un safe" })
100
+ end
101
+ assert_equal "Do not interpolate without quotes after "\
102
+ "attribute around 'href=<%= value %>'.", e.message
103
+ end
104
+
105
+ test "interpolate in attribute after value" do
106
+ BetterHtml.config.stubs(:allow_unquoted_attributes).returns(true)
107
+ e = assert_raises(BetterHtml::DontInterpolateHere) do
108
+ render("<a href=something<%= value %>>", { value: "" })
109
+ end
110
+ assert_equal "Do not interpolate without quotes around this "\
111
+ "attribute value. Instead of <a href=something<%= value %>> "\
112
+ "try <a href=\"something<%= value %>\">.", e.message
113
+ end
114
+
115
+ test "interpolate in tag name" do
116
+ assert_equal "<tag-safe-foo>",
117
+ render("<tag-<%= value %>-foo>", { value: "safe" })
118
+ end
119
+
120
+ test "interpolate in tag name with space" do
121
+ e = assert_raises(BetterHtml::UnsafeHtmlError) do
122
+ render("<tag-<%= value %>-foo>", { value: "un safe" })
123
+ end
124
+ assert_equal "Detected invalid characters as part of the interpolation "\
125
+ "into a tag name around: <tag-<%= value %>>.", e.message
126
+ end
127
+
128
+ test "interpolate in tag name with slash" do
129
+ e = assert_raises(BetterHtml::UnsafeHtmlError) do
130
+ render("<tag-<%= value %>-foo>", { value: "un/safe" })
131
+ end
132
+ assert_equal "Detected invalid characters as part of the interpolation "\
133
+ "into a tag name around: <tag-<%= value %>>.", e.message
134
+ end
135
+
136
+ test "interpolate in tag name with end of tag" do
137
+ e = assert_raises(BetterHtml::UnsafeHtmlError) do
138
+ render("<tag-<%= value %>-foo>", { value: "><script>" })
139
+ end
140
+ assert_equal "Detected invalid characters as part of the interpolation "\
141
+ "into a tag name around: <tag-<%= value %>>.", e.message
142
+ end
143
+
144
+ test "interpolate in comment" do
145
+ assert_equal "<!-- safe -->",
146
+ render("<!-- <%= value %> -->", { value: "safe" })
147
+ end
148
+
149
+ test "interpolate in comment with end-of-comment" do
150
+ e = assert_raises(BetterHtml::UnsafeHtmlError) do
151
+ render("<!-- <%= value %> -->", { value: "-->".html_safe })
152
+ end
153
+ assert_equal "Detected invalid characters as part of the interpolation "\
154
+ "into a html comment around: <!-- <%= value %>.", e.message
155
+ end
156
+
157
+ test "non html_safe interpolation into comment tag" do
158
+ assert_equal "<!-- --&gt; -->",
159
+ render("<!-- <%= value %> -->", value: '-->')
160
+ end
161
+
162
+ test "interpolate in script tag" do
163
+ assert_equal "<script> foo safe bar<script>",
164
+ render("<script> foo <%= value %> bar<script>", { value: "safe" })
165
+ end
166
+
167
+ test "interpolate in script tag with start of comment" do
168
+ skip "skip for now; causing problems"
169
+ e = assert_raises(BetterHtml::UnsafeHtmlError) do
170
+ render("<script> foo <%= value %> bar<script>", { value: "<!--".html_safe })
171
+ end
172
+ assert_equal "Detected invalid characters as part of the interpolation "\
173
+ "into a script tag around: <script> foo <%= value %>. "\
174
+ "A script tag cannot contain <script or </script anywhere inside of it.", e.message
175
+ end
176
+
177
+ test "interpolate in script tag with start of script" do
178
+ e = assert_raises(BetterHtml::UnsafeHtmlError) do
179
+ render("<script> foo <%= value %> bar<script>", { value: "<script".html_safe })
180
+ end
181
+ assert_equal "Detected invalid characters as part of the interpolation "\
182
+ "into a script tag around: <script> foo <%= value %>. "\
183
+ "A script tag cannot contain <script or </script anywhere inside of it.", e.message
184
+ end
185
+
186
+ test "interpolate in script tag with raw interpolation" do
187
+ assert_equal "<script> x = \"foo\" </script>",
188
+ render("<script> x = <%== value %> </script>", { value: JSON.dump("foo") })
189
+ end
190
+
191
+ test "interpolate in script tag with start of script case insensitive" do
192
+ e = assert_raises(BetterHtml::UnsafeHtmlError) do
193
+ render("<script> foo <%= value %> bar<script>", { value: "<ScRIpT".html_safe })
194
+ end
195
+ assert_equal "Detected invalid characters as part of the interpolation "\
196
+ "into a script tag around: <script> foo <%= value %>. "\
197
+ "A script tag cannot contain <script or </script anywhere inside of it.", e.message
198
+ end
199
+
200
+ test "interpolate in script tag with end of script" do
201
+ e = assert_raises(BetterHtml::UnsafeHtmlError) do
202
+ render("<script> foo <%= value %> bar<script>", { value: "</script".html_safe })
203
+ end
204
+ assert_equal "Detected invalid characters as part of the interpolation "\
205
+ "into a script tag around: <script> foo <%= value %>. "\
206
+ "A script tag cannot contain <script or </script anywhere inside of it.", e.message
207
+ end
208
+
209
+ test "interpolate html_attributes" do
210
+ assert_equal "<a foo=\"bar\">",
211
+ render("<a <%= html_attributes(foo: 'bar') %>>")
212
+ end
213
+
214
+ test "interpolate without html_attributes" do
215
+ e = assert_raises(BetterHtml::DontInterpolateHere) do
216
+ render("<a <%= 'foo=\"bar\"' %>>")
217
+ end
218
+ assert_equal "Do not interpolate String in a tag. Instead "\
219
+ "of <a <%= 'foo=\"bar\"' %>> please try <a <%= html_attributes(attr: value) %>>.", e.message
220
+ end
221
+
222
+ test "non html_safe interpolation into rawtext tag" do
223
+ assert_equal "<title>&lt;/title&gt;</title>",
224
+ render("<title><%= value %></title>", value: '</title>')
225
+ end
226
+
227
+ test "html_safe interpolation into rawtext tag" do
228
+ assert_equal "<title><safe></title>",
229
+ render("<title><%= value %></title>", value: '<safe>'.html_safe)
230
+ end
231
+
232
+ test "html_safe interpolation terminating the current tag" do
233
+ e = assert_raises(BetterHtml::UnsafeHtmlError) do
234
+ render("<title><%= value %></title>", value: '</title>'.html_safe)
235
+ end
236
+ assert_equal "Detected invalid characters as part of the interpolation "\
237
+ "into a title tag around: <title><%= value %>.", e.message
238
+ end
239
+
240
+ test "interpolate block in middle of tag" do
241
+ e = assert_raises(BetterHtml::DontInterpolateHere) do
242
+ render(<<-HTML)
243
+ <a href="" <%= something do %>
244
+ foo
245
+ <% end %>
246
+ HTML
247
+ end
248
+ assert_equal "Ruby statement not allowed.\n"\
249
+ "In 'tag' on line 1 column 19:\n"\
250
+ " <a href=\"\" <%= something do %>\n"\
251
+ " ^^^^^^^^^^^^^^^^^^^", e.message
252
+ end
253
+
254
+ test "interpolate with output block is valid syntax" do
255
+ assert_nothing_raised do
256
+ render(<<-HTML)
257
+ <%= capture do %>
258
+ <foo>
259
+ <% end %>
260
+ HTML
261
+ end
262
+ end
263
+
264
+ test "interpolate with statement block is valid syntax" do
265
+ assert_nothing_raised do
266
+ render(<<-HTML)
267
+ <% capture do %>
268
+ <foo>
269
+ <% end %>
270
+ HTML
271
+ end
272
+ end
273
+
274
+ test "can interpolate method calls without parenthesis" do
275
+ assert_equal "<div>foo</div>",
276
+ render("<div><%= send 'value' %></div>", value: 'foo')
277
+ end
278
+
279
+ test "tag names are validated against tag_name_pattern regexp" do
280
+ e = assert_raises(BetterHtml::HtmlError) do
281
+ render("<foo~bar></foo~bar>")
282
+ end
283
+ assert_equal "Invalid tag name \"foo~bar\" does not match regular expression /\\A[a-z0-9\\-\\:]+\\z/\n"\
284
+ "On line 1 column 1:\n"\
285
+ "<foo~bar></foo~bar>\n"\
286
+ " ^^^^^^^", e.message
287
+ end
288
+
289
+ test "attribute names are validated against attribute_name_pattern regexp" do
290
+ e = assert_raises(BetterHtml::HtmlError) do
291
+ render("<foo bar_baz=\"1\">")
292
+ end
293
+ assert_equal "Invalid attribute name \"bar_baz\" does not match regular expression #{BetterHtml.config.partial_attribute_name_pattern.inspect}\n"\
294
+ "On line 1 column 5:\n"\
295
+ "<foo bar_baz=\"1\">\n"\
296
+ " ^^^^^^^", e.message
297
+ end
298
+
299
+ test "single quotes are disallowed when allow_single_quoted_attributes=false" do
300
+ BetterHtml.config.stubs(:allow_single_quoted_attributes).returns(false)
301
+ e = assert_raises(BetterHtml::HtmlError) do
302
+ render("<foo bar='1'>")
303
+ end
304
+ assert_equal "Single-quoted attributes are not allowed\n"\
305
+ "On line 1 column 9:\n"\
306
+ "<foo bar='1'>\n"\
307
+ " ^", e.message
308
+ end
309
+
310
+ test "single quotes are allowed when allow_single_quoted_attributes=true" do
311
+ BetterHtml.config.stubs(:allow_single_quoted_attributes).returns(true)
312
+ assert_nothing_raised do
313
+ render("<foo bar='1'>")
314
+ end
315
+ end
316
+
317
+ test "unquoted values are disallowed when allow_unquoted_attributes=false" do
318
+ BetterHtml.config.stubs(:allow_unquoted_attributes).returns(false)
319
+ e = assert_raises(BetterHtml::HtmlError) do
320
+ render("<foo bar=1>")
321
+ end
322
+ assert_equal "Unquoted attribute values are not allowed\n"\
323
+ "On line 1 column 9:\n"\
324
+ "<foo bar=1>\n"\
325
+ " ^", e.message
326
+ end
327
+
328
+ test "unquoted values are allowed when allow_unquoted_attributes=true" do
329
+ BetterHtml.config.stubs(:allow_unquoted_attributes).returns(true)
330
+ assert_nothing_raised do
331
+ render("<foo bar=1>")
332
+ end
333
+ end
334
+
335
+ test "capture works as intended" do
336
+ output = render(<<-HTML)
337
+ <%- foo = capture do -%>
338
+ <foo>
339
+ <%- end -%>
340
+ <bar><%= foo %></bar>
341
+ HTML
342
+
343
+ assert_equal " <bar> <foo>\n</bar>\n", output
344
+ end
345
+
346
+ test "validate! raises when tag is not terminated at end of document" do
347
+ document = compile("<foo")
348
+ e = assert_raises(BetterHtml::HtmlError) do
349
+ document.validate!
350
+ end
351
+ assert_equal "Detected an open tag at the end of this document.", e.message
352
+ end
353
+
354
+ test "validate! raises when rawtext tag is not terminated at end of document" do
355
+ document = compile("<script>")
356
+ e = assert_raises(BetterHtml::HtmlError) do
357
+ document.validate!
358
+ end
359
+ assert_equal "Detected an open tag at the end of this document.", e.message
360
+ end
361
+
362
+ test "validate! raises parsing error for attribute name" do
363
+ document = compile("<foo bar~baz=\"1\">")
364
+ e = assert_raises(BetterHtml::HtmlError) do
365
+ document.validate!
366
+ end
367
+ assert_equal "expected whitespace, '>', attribute name or value\n"\
368
+ "On line 1 column 8:\n"\
369
+ "<foo bar~baz=\"1\">\n"\
370
+ " ^^^^^^^^^", e.message
371
+ end
372
+
373
+ private
374
+
375
+ class ViewContext < OpenStruct
376
+ include(ActionView::Helpers)
377
+ include(BetterHtml::Helpers)
378
+ attr_accessor :output_buffer
379
+
380
+ def get_binding
381
+ binding
382
+ end
383
+ end
384
+
385
+ def render(source, locals={})
386
+ context = ViewContext.new(locals)
387
+ impl = compile(source)
388
+ if ActionView.version < Gem::Version.new("5.1")
389
+ impl.result(context.get_binding)
390
+ else
391
+ impl.evaluate(context)
392
+ end
393
+ end
394
+
395
+ def compile(source)
396
+ if ActionView.version < Gem::Version.new("5.1")
397
+ BetterHtml::BetterErb::ErubisImplementation.new(source)
398
+ else
399
+ BetterHtml::BetterErb::ErubiImplementation.new(source)
400
+ end
401
+ end
402
+ end