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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +30 -0
- data/lib/better_html.rb +53 -0
- data/lib/better_html/better_erb.rb +68 -0
- data/lib/better_html/better_erb/erubi_implementation.rb +50 -0
- data/lib/better_html/better_erb/erubis_implementation.rb +44 -0
- data/lib/better_html/better_erb/runtime_checks.rb +161 -0
- data/lib/better_html/better_erb/validated_output_buffer.rb +166 -0
- data/lib/better_html/errors.rb +22 -0
- data/lib/better_html/helpers.rb +5 -0
- data/lib/better_html/html_attributes.rb +26 -0
- data/lib/better_html/node_iterator.rb +144 -0
- data/lib/better_html/node_iterator/attribute.rb +34 -0
- data/lib/better_html/node_iterator/base.rb +27 -0
- data/lib/better_html/node_iterator/cdata.rb +8 -0
- data/lib/better_html/node_iterator/comment.rb +8 -0
- data/lib/better_html/node_iterator/content_node.rb +13 -0
- data/lib/better_html/node_iterator/element.rb +26 -0
- data/lib/better_html/node_iterator/html_erb.rb +78 -0
- data/lib/better_html/node_iterator/html_lodash.rb +101 -0
- data/lib/better_html/node_iterator/javascript_erb.rb +60 -0
- data/lib/better_html/node_iterator/location.rb +14 -0
- data/lib/better_html/node_iterator/text.rb +8 -0
- data/lib/better_html/node_iterator/token.rb +8 -0
- data/lib/better_html/railtie.rb +7 -0
- data/lib/better_html/test_helper/ruby_expr.rb +89 -0
- data/lib/better_html/test_helper/safe_erb_tester.rb +202 -0
- data/lib/better_html/test_helper/safe_lodash_tester.rb +121 -0
- data/lib/better_html/test_helper/safety_tester_base.rb +34 -0
- data/lib/better_html/tree.rb +113 -0
- data/lib/better_html/version.rb +3 -0
- data/lib/tasks/better_html_tasks.rake +4 -0
- data/test/better_html/better_erb/implementation_test.rb +402 -0
- data/test/better_html/helpers_test.rb +49 -0
- data/test/better_html/node_iterator/html_lodash_test.rb +132 -0
- data/test/better_html/node_iterator_test.rb +221 -0
- data/test/better_html/test_helper/ruby_expr_test.rb +206 -0
- data/test/better_html/test_helper/safe_erb_tester_test.rb +358 -0
- data/test/better_html/test_helper/safe_lodash_tester_test.rb +80 -0
- data/test/better_html/tree_test.rb +110 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +29 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +26 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +41 -0
- data/test/dummy/config/environments/production.rb +79 -0
- data/test/dummy/config/environments/test.rb +42 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +56 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/test_helper.rb +19 -0
- 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,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><bar /><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=\" '">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 "<!-- --> -->",
|
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></title></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
|