assert_valid_html 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
|
|
1
|
+
class ActionController::TestCase
|
2
|
+
def assert_valid_html
|
3
|
+
return unless @response.headers['Content-Type'].match(/html/)
|
4
|
+
|
5
|
+
validator = AssertValidHtml::Validator.new(@response.body)
|
6
|
+
assert_block(validator.message){ validator.valid? }
|
7
|
+
end
|
8
|
+
|
9
|
+
alias_method :assert_valid_markup, :assert_valid_html
|
10
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require "open3"
|
2
|
+
|
3
|
+
module AssertValidHtml
|
4
|
+
class Validator
|
5
|
+
ValidationError = Struct.new(:message, :line, :context)
|
6
|
+
|
7
|
+
ALLOWED_ATTRIBUTES = %w[
|
8
|
+
media hreflang rel target value charset autofocus placeholder form
|
9
|
+
required disabled autocomplete min max multiple pattern step list
|
10
|
+
novalidate formaction formenctype formmethod formnovalidate formtarget
|
11
|
+
type label contextmenu scoped async manifest sizes reversed sandbox
|
12
|
+
seamless srcdoc contenteditable draggable hidden role data-\\S* aria-\\S*
|
13
|
+
spellcheck
|
14
|
+
]
|
15
|
+
|
16
|
+
ALLOWED_ELEMENTS = %w[
|
17
|
+
section article aside hgroup header footer nav figure figcaption video
|
18
|
+
audio source embed progress meter time ruby rt rp wbr canvas command
|
19
|
+
details datalist keygen output
|
20
|
+
]
|
21
|
+
|
22
|
+
IGNORED = [
|
23
|
+
/Warning: trimming empty/,
|
24
|
+
/lacks "summary" attribute/,
|
25
|
+
/<meta> lacks "content" attribute/, # HTML5
|
26
|
+
/proprietary attribute "xmlns:fb"/, # Facebook
|
27
|
+
/<\/?fb:/ # //
|
28
|
+
]
|
29
|
+
|
30
|
+
def self.ignore(regexp)
|
31
|
+
ignored << regexp
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.ignored
|
35
|
+
@ignored ||= IGNORED.dup
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.ignored_regexp
|
39
|
+
Regexp.new((ignored + [
|
40
|
+
%r{proprietary attribute "(?:#{ALLOWED_ATTRIBUTES.join("|")})"},
|
41
|
+
%r{discarding unexpected </?(?:#{ALLOWED_ELEMENTS.join("|")})>}
|
42
|
+
]).join("|"))
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(html)
|
46
|
+
@html = html
|
47
|
+
@lines = html.split(/\n/)
|
48
|
+
end
|
49
|
+
|
50
|
+
def valid?
|
51
|
+
errors.empty?
|
52
|
+
end
|
53
|
+
|
54
|
+
def errors
|
55
|
+
@errors ||= (
|
56
|
+
ignored = self.class.ignored_regexp
|
57
|
+
tidy(@html).split(/\n/).select {|w| w =~ /Warning/ && w !~ ignored }
|
58
|
+
).inject([]){ |array, error|
|
59
|
+
line, message = parse_tidy_message(error)
|
60
|
+
array << ValidationError.new(message, line, context(line))
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
def context(line)
|
65
|
+
top = [0, line - 6].max
|
66
|
+
bottom = [[@lines.length - 1, 0].max, line + 4].min
|
67
|
+
(top..bottom).to_a.zip(@lines[top..bottom]).map{ |number, text|
|
68
|
+
"%s %3d | %s" % [number + 1 == line ? "*" : " ", number + 1, text]
|
69
|
+
}.join("\n")
|
70
|
+
end
|
71
|
+
|
72
|
+
def message
|
73
|
+
if valid?
|
74
|
+
"HTML is valid"
|
75
|
+
else
|
76
|
+
(["HTML is invalid"] + errors.map{ |e| e.message + "\n" + e.context }).join("\n\n")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
def parse_tidy_message(error)
|
82
|
+
m = error.match(/^line (\d+) column (\d+) - (.*)/)
|
83
|
+
return m[1].to_i, "#{m[3]} at line #{m[1]} column #{m[2]}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def tidy(html)
|
87
|
+
stdin, stdout, stderr = Open3.popen3("tidy -q -e -utf8")
|
88
|
+
stdin << html
|
89
|
+
stdin.close
|
90
|
+
stderr.read
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: assert_valid_html
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Paul Battley
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-11-17 00:00:00 +00:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description:
|
23
|
+
email: pbattley@gmail.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- lib/assert_valid_html.rb
|
32
|
+
- lib/assert_valid_html/validator.rb
|
33
|
+
- lib/assert_valid_html/test.rb
|
34
|
+
- lib/assert_valid_html/version.rb
|
35
|
+
- lib/assert_valid_html/railtie.rb
|
36
|
+
has_rdoc: true
|
37
|
+
homepage:
|
38
|
+
licenses: []
|
39
|
+
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
hash: 3
|
51
|
+
segments:
|
52
|
+
- 0
|
53
|
+
version: "0"
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
requirements: []
|
64
|
+
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 1.3.7
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: Check HTML validity without using an external web service
|
70
|
+
test_files: []
|
71
|
+
|