be_valid_asset 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE.txt +20 -0
- data/README.markdown +158 -0
- data/Rakefile +17 -0
- data/lib/be_valid_asset.rb +33 -0
- data/lib/be_valid_asset/be_valid_base.rb +98 -0
- data/lib/be_valid_asset/be_valid_css.rb +70 -0
- data/lib/be_valid_asset/be_valid_feed.rb +77 -0
- data/lib/be_valid_asset/be_valid_xhtml.rb +75 -0
- data/spec/be_valid_asset/be_valid_css_spec.rb +195 -0
- data/spec/be_valid_asset/be_valid_feed_spec.rb +178 -0
- data/spec/be_valid_asset/be_valid_xhtml_spec.rb +290 -0
- data/spec/files/invalid.css +43 -0
- data/spec/files/invalid.html +15 -0
- data/spec/files/invalid2.html +16 -0
- data/spec/files/invalid_feed.xml +37 -0
- data/spec/files/valid-1.css +31 -0
- data/spec/files/valid-2.css +42 -0
- data/spec/files/valid-3.css +48 -0
- data/spec/files/valid.css +42 -0
- data/spec/files/valid.html +15 -0
- data/spec/files/valid_feed.xml +34 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +32 -0
- metadata +88 -0
data/MIT-LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Unboxed Consulting
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOa AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SaALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
be\_valid\_asset
|
2
|
+
==============
|
3
|
+
|
4
|
+
Provides `be_valid_xhtml`, `be_valid_css` and `be_valid_feed` matchers for rspec controller and view tests.
|
5
|
+
|
6
|
+
Installation
|
7
|
+
------------
|
8
|
+
|
9
|
+
To use be\_valid\_asset in your project, install the gem:
|
10
|
+
|
11
|
+
gem install unboxed-be_valid_asset -s http://gems.github.com
|
12
|
+
|
13
|
+
or as a plugin
|
14
|
+
|
15
|
+
./script/plugin install git://github.com/unboxed/be_valid_asset.git
|
16
|
+
|
17
|
+
Add the following to the `spec/support/be_valid_asset.rb`:
|
18
|
+
(for older version of RSpec you'll need to require it from `spec_helper.rb`)
|
19
|
+
|
20
|
+
include BeValidAsset
|
21
|
+
|
22
|
+
# BeValidAsset::Configuration.display_invalid_content = true
|
23
|
+
BeValidAsset::Configuration.enable_caching = true
|
24
|
+
BeValidAsset::Configuration.cache_path = File.join(RAILS_ROOT, %w(tmp be_valid_asset_cache))
|
25
|
+
|
26
|
+
See below for details of the configuration options available
|
27
|
+
|
28
|
+
Usage
|
29
|
+
-----
|
30
|
+
|
31
|
+
### (X)HTML validation
|
32
|
+
|
33
|
+
It can be used to test either an ActionController Response object as follows:
|
34
|
+
|
35
|
+
describe FooController do
|
36
|
+
integrate_views
|
37
|
+
|
38
|
+
describe "GET 'index'" do
|
39
|
+
it "should have valid markup" do
|
40
|
+
get 'index'
|
41
|
+
response.should be_valid_xhtml
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
or
|
47
|
+
|
48
|
+
describe "/index.html" do
|
49
|
+
it "should be valid XHTML" do
|
50
|
+
render 'home/index', :layout => true
|
51
|
+
response.should be_valid_xhtml
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
or to test a string:
|
56
|
+
|
57
|
+
it "should be valid xhtml" do
|
58
|
+
html = File.read(File.join(RAILS_ROOT, %w(public index.html)))
|
59
|
+
html.should be_valid_xhtml
|
60
|
+
end
|
61
|
+
|
62
|
+
It is also possible to validate an xhtml fragment. This assumes xhtml-1.0 strict.
|
63
|
+
|
64
|
+
it "should be valid xhtml" do
|
65
|
+
string = "<p>This is an html fragment</p>"
|
66
|
+
string.should be_valid_xhtml_fragment
|
67
|
+
end
|
68
|
+
|
69
|
+
### CSS validation
|
70
|
+
|
71
|
+
CSS files can be validated as follows:
|
72
|
+
|
73
|
+
it "should be valid CSS" do
|
74
|
+
css = File.read(File.join(RAILS_ROOT, %w(public stylesheets main.css)))
|
75
|
+
css.should be_valid_css
|
76
|
+
end
|
77
|
+
|
78
|
+
be\_valid\_css takes an optional parameter specifying the css profile to test against. It defaults to testing against CSS 2.1. It can be set to any of the profiles supported by the CSS validator (e.g. css1, css2, css21, css3). There are also the following shortcut methods:
|
79
|
+
|
80
|
+
* `be_valid_css1` => CSS 1.0
|
81
|
+
* `be_valid_css2` => CSS 2.1
|
82
|
+
* `be_valid_css3` => CSS 3.0
|
83
|
+
|
84
|
+
### Feed validation
|
85
|
+
|
86
|
+
RSS and Atom feeds can be validated from a response, or a string, in the same way as for xhtml or CSS. e.g.
|
87
|
+
|
88
|
+
describe FooController do
|
89
|
+
integrate_views
|
90
|
+
|
91
|
+
describe "GET 'index.rss'" do
|
92
|
+
it "should be valid" do
|
93
|
+
get 'index.rss'
|
94
|
+
response.should be_valid_feed
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
There are also aliased methods `be_valid_rss` and `be_valid_atom` that do the same thing.
|
100
|
+
|
101
|
+
Environment Variables
|
102
|
+
---------------------
|
103
|
+
|
104
|
+
### Disabling network tests
|
105
|
+
|
106
|
+
If the environment variable `NONET` is set to true, then all tests with no cached response available will be marked as pending.
|
107
|
+
|
108
|
+
### http_proxy
|
109
|
+
|
110
|
+
If you need to use a proxy server to access the validator service, set the environment variable http_proxy.
|
111
|
+
|
112
|
+
Configuration
|
113
|
+
-------------
|
114
|
+
|
115
|
+
The following can be set in `spec/support/be_valid_asset.rb`:
|
116
|
+
|
117
|
+
### Display Full source for failures:
|
118
|
+
|
119
|
+
BeValidAsset::Configuration.display_invalid_content = false (default)
|
120
|
+
|
121
|
+
### Display surrounding source for failures:
|
122
|
+
|
123
|
+
This will cause it to output the failing line, and n surrounding lines (defaults to 5)
|
124
|
+
|
125
|
+
BeValidAsset::Configuration.display_invalid_lines = false (default)
|
126
|
+
BeValidAsset::Configuration.display_invalid_lines_count = 5 (default)
|
127
|
+
|
128
|
+
### Change validator host/path:
|
129
|
+
|
130
|
+
BeValidAsset::Configuration.markup_validator_host = 'validator.w3.org'
|
131
|
+
BeValidAsset::Configuration.markup_validator_path = '/check'
|
132
|
+
BeValidAsset::Configuration.css_validator_host = 'jigsaw.w3.org'
|
133
|
+
BeValidAsset::Configuration.css_validator_path = '/css-validator/validator'
|
134
|
+
BeValidAsset::Configuration.feed_validator_host = 'validator.w3.org'
|
135
|
+
BeValidAsset::Configuration.feed_validator_path = '/feed/check.cgi'
|
136
|
+
|
137
|
+
If you are doing more than the occasional check, you should probably run your own copy of the validator, and use that.
|
138
|
+
Instructions here: [http://validator.w3.org/docs/install.html](http://validator.w3.org/docs/install.html), [http://jigsaw.w3.org/css-validator/DOWNLOAD.html](http://jigsaw.w3.org/css-validator/DOWNLOAD.html) or [http://validator.w3.org/feed/about.html#where](http://validator.w3.org/feed/about.html#where)
|
139
|
+
|
140
|
+
### Caching
|
141
|
+
|
142
|
+
be\_valid\_asset can cache the responses from the validator to save look-ups for documents that haven't changed.
|
143
|
+
To use this feature, it must be enabled, and a cache path must be set:
|
144
|
+
|
145
|
+
BeValidAsset::Configuration.enable_caching = true
|
146
|
+
BeValidAsset::Configuration.cache_path = File.join(RAILS_ROOT, %w(tmp be_valid_asset_cache))
|
147
|
+
|
148
|
+
Issues / Feature Requests
|
149
|
+
-------------------------
|
150
|
+
|
151
|
+
Please use the [github issue tracker](http://github.com/unboxed/be_valid_asset/issues) to track any bugs/feature requests.
|
152
|
+
|
153
|
+
Licensing etc.
|
154
|
+
--------------
|
155
|
+
|
156
|
+
This was originally based on a blog post here: [http://www.anodyne.ca/2007/09/28/rspec-custom-matchers-and-be\_valid\_xhtml/](http://www.anodyne.ca/2007/09/28/rspec-custom-matchers-and-be_valid_xhtml/)
|
157
|
+
|
158
|
+
This is distributed under the MIT Licence, see `MIT-LICENSE.txt` for the details.
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rake'
|
2
|
+
# require 'rake/rdoctask'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
|
5
|
+
task :default => :spec
|
6
|
+
Spec::Rake::SpecTask.new do |t|
|
7
|
+
t.spec_opts = ['--options', File.join(File.dirname(__FILE__), %w(spec spec.opts))]
|
8
|
+
end
|
9
|
+
|
10
|
+
# desc 'Generate documentation for the be_valid_asset plugin.'
|
11
|
+
# Rake::RDocTask.new(:rdoc) do |rdoc|
|
12
|
+
# rdoc.rdoc_dir = 'rdoc'
|
13
|
+
# rdoc.title = 'BeValidAsset'
|
14
|
+
# rdoc.options << '--line-numbers' << '--inline-source'
|
15
|
+
# rdoc.rdoc_files.include('README')
|
16
|
+
# rdoc.rdoc_files.include('lib/**/*.rb')
|
17
|
+
# end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module BeValidAsset
|
2
|
+
class Configuration
|
3
|
+
@@config = {
|
4
|
+
:display_invalid_content => false,
|
5
|
+
:enable_caching => false,
|
6
|
+
:display_invalid_lines => false,
|
7
|
+
:display_invalid_lines_count => 5
|
8
|
+
}
|
9
|
+
|
10
|
+
def self.method_missing(name, *args)
|
11
|
+
if name.to_s =~ /^(.*)=$/
|
12
|
+
@@config[$1.to_sym] = args[0]
|
13
|
+
elsif @@config.has_key?(name)
|
14
|
+
return @@config[name]
|
15
|
+
else
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.cache_path=(path)
|
21
|
+
@@config[:cache_path] = path
|
22
|
+
unless File.directory? path
|
23
|
+
FileUtils.mkdir_p path
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
require 'be_valid_asset/be_valid_base'
|
31
|
+
require 'be_valid_asset/be_valid_xhtml'
|
32
|
+
require 'be_valid_asset/be_valid_css'
|
33
|
+
require 'be_valid_asset/be_valid_feed'
|
@@ -0,0 +1,98 @@
|
|
1
|
+
|
2
|
+
module BeValidAsset
|
3
|
+
|
4
|
+
# Abstract base class for other matchers
|
5
|
+
class BeValidBase
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def check_net_enabled
|
10
|
+
if ENV["NONET"] == 'true'
|
11
|
+
raise Spec::Example::ExamplePendingError.new('Network tests disabled')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def validate(query_params)
|
16
|
+
query_params.merge!( {:output => 'soap12' } )
|
17
|
+
response = get_validator_response(query_params)
|
18
|
+
|
19
|
+
markup_is_valid = response_indicates_valid?(response)
|
20
|
+
@message = ''
|
21
|
+
unless markup_is_valid
|
22
|
+
process_errors(query_params, response)
|
23
|
+
end
|
24
|
+
return markup_is_valid
|
25
|
+
end
|
26
|
+
|
27
|
+
def response_indicates_valid?(response)
|
28
|
+
response['x-w3c-validator-status'] == 'Valid'
|
29
|
+
end
|
30
|
+
|
31
|
+
def process_errors(query_params, response)
|
32
|
+
fragment = query_params[:fragment] || query_params[:text]
|
33
|
+
if Configuration.display_invalid_content || Configuration.display_invalid_lines
|
34
|
+
lines = fragment.split($/)
|
35
|
+
end
|
36
|
+
lines.each_with_index{|line, index| @message << "#{'%04i' % (index+1)} : #{line}#{$/}"} if Configuration.display_invalid_content
|
37
|
+
REXML::Document.new(response.body).root.each_element('//m:error') do |e|
|
38
|
+
@message << "#{error_line_prefix}: line #{e.elements['m:line'].text}: #{e.elements['m:message'].get_text.value.strip}\n"
|
39
|
+
if Configuration.display_invalid_lines
|
40
|
+
line_no = e.elements['m:line'].text.to_i
|
41
|
+
start_line = [line_no - (Configuration.display_invalid_lines_count / 2), 1].max
|
42
|
+
end_line = [line_no + (Configuration.display_invalid_lines_count / 2), lines.length].min
|
43
|
+
for i in start_line..end_line
|
44
|
+
@message << "#{'%04i' % i}#{ i == line_no ? '>>' : ' ' }: #{ lines[i - 1] }#{ $/ }"
|
45
|
+
end
|
46
|
+
@message << "------\n"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_validator_response(query_params = {})
|
52
|
+
if Configuration.enable_caching
|
53
|
+
digest = Digest::MD5.hexdigest(query_params.to_a.sort {|a,b| a[0].to_s<=>b[0].to_s}.join)
|
54
|
+
cache_filename = File.join(Configuration.cache_path, digest)
|
55
|
+
if File.exist? cache_filename
|
56
|
+
response = File.open(cache_filename) {|f| Marshal.load(f) }
|
57
|
+
else
|
58
|
+
response = call_validator( query_params )
|
59
|
+
File.open(cache_filename, 'w') {|f| Marshal.dump(response, f) } if response.is_a? Net::HTTPSuccess
|
60
|
+
end
|
61
|
+
else
|
62
|
+
response = call_validator( query_params )
|
63
|
+
end
|
64
|
+
raise "HTTP error: #{response.code}" unless response.is_a? Net::HTTPSuccess
|
65
|
+
return response
|
66
|
+
end
|
67
|
+
|
68
|
+
def call_validator(query_params)
|
69
|
+
check_net_enabled
|
70
|
+
boundary = Digest::MD5.hexdigest(Time.now.to_s)
|
71
|
+
data = encode_multipart_params(boundary, query_params)
|
72
|
+
return http_start(validator_host).post2(validator_path, data, "Content-type" => "multipart/form-data; boundary=#{boundary}" )
|
73
|
+
end
|
74
|
+
|
75
|
+
def encode_multipart_params(boundary, params = {})
|
76
|
+
ret = ''
|
77
|
+
params.each do |k,v|
|
78
|
+
unless v.empty?
|
79
|
+
ret << "\r\n--#{boundary}\r\n"
|
80
|
+
ret << "Content-Disposition: form-data; name=\"#{k.to_s}\"\r\n\r\n"
|
81
|
+
ret << v
|
82
|
+
end
|
83
|
+
end
|
84
|
+
ret << "\r\n--#{boundary}--\r\n"
|
85
|
+
ret
|
86
|
+
end
|
87
|
+
|
88
|
+
def http_start(host)
|
89
|
+
if ENV['http_proxy']
|
90
|
+
uri = URI.parse(ENV['http_proxy'])
|
91
|
+
proxy_user, proxy_pass = uri.userinfo.split(/:/) if uri.userinfo
|
92
|
+
Net::HTTP.start(host, nil, uri.host, uri.port, proxy_user, proxy_pass)
|
93
|
+
else
|
94
|
+
Net::HTTP.start(host)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module BeValidAsset
|
4
|
+
|
5
|
+
Configuration.css_validator_host = 'jigsaw.w3.org'
|
6
|
+
Configuration.css_validator_path = '/css-validator/validator'
|
7
|
+
|
8
|
+
class BeValidCss < BeValidBase
|
9
|
+
|
10
|
+
def initialize(profile)
|
11
|
+
@profile = profile
|
12
|
+
end
|
13
|
+
|
14
|
+
def matches?(fragment)
|
15
|
+
|
16
|
+
if fragment.respond_to? :body
|
17
|
+
fragment = fragment.body.to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
# The validator return a 500 Error if it's sent empty string
|
21
|
+
fragment = ' ' if fragment.empty?
|
22
|
+
|
23
|
+
query_params = { :text => fragment, :profile => @profile }
|
24
|
+
return validate(query_params)
|
25
|
+
end
|
26
|
+
|
27
|
+
def description
|
28
|
+
"be valid css"
|
29
|
+
end
|
30
|
+
|
31
|
+
def failure_message
|
32
|
+
" expected css to be valid, but validation produced these errors:\n#{@message}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def negative_failure_message
|
36
|
+
" expected to not be valid, but was (missing validation?)"
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def validator_host
|
42
|
+
Configuration.css_validator_host
|
43
|
+
end
|
44
|
+
|
45
|
+
def validator_path
|
46
|
+
Configuration.css_validator_path
|
47
|
+
end
|
48
|
+
|
49
|
+
def error_line_prefix
|
50
|
+
'Invalid css'
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
def be_valid_css(profile = 'css21')
|
56
|
+
BeValidCss.new(profile)
|
57
|
+
end
|
58
|
+
|
59
|
+
def be_valid_css1
|
60
|
+
be_valid_css 'css1'
|
61
|
+
end
|
62
|
+
|
63
|
+
def be_valid_css2
|
64
|
+
be_valid_css 'css21'
|
65
|
+
end
|
66
|
+
|
67
|
+
def be_valid_css3
|
68
|
+
be_valid_css 'css3'
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module BeValidAsset
|
4
|
+
|
5
|
+
Configuration.feed_validator_host = 'validator.w3.org'
|
6
|
+
Configuration.feed_validator_path = '/feed/check.cgi'
|
7
|
+
|
8
|
+
class BeValidFeed < BeValidBase
|
9
|
+
|
10
|
+
def initialize()
|
11
|
+
end
|
12
|
+
|
13
|
+
def matches?(fragment)
|
14
|
+
|
15
|
+
if fragment.respond_to? :body
|
16
|
+
fragment = fragment.body.to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
query_params = { :rawdata => fragment, :manual => '1' }
|
20
|
+
return validate(query_params)
|
21
|
+
end
|
22
|
+
|
23
|
+
def description
|
24
|
+
"be valid feed (RSS / Atom)"
|
25
|
+
end
|
26
|
+
|
27
|
+
def failure_message
|
28
|
+
" expected feed to be valid, but validation produced these errors:\n#{@message}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def negative_failure_message
|
32
|
+
" expected to not be valid, but was (missing validation?)"
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# The feed service takes params differently.
|
38
|
+
def call_validator(query_params)
|
39
|
+
check_net_enabled
|
40
|
+
params = "rawdata=#{CGI.escape(query_params[:rawdata])}&manual=1&output=soap12"
|
41
|
+
return http_start(validator_host).post(validator_path, params, {} )
|
42
|
+
end
|
43
|
+
|
44
|
+
# The feed validator uses a different response type, so we have to override these here.
|
45
|
+
def response_indicates_valid?(response)
|
46
|
+
REXML::Document.new(response.body).root.get_elements('//m:validity').first.text == 'true'
|
47
|
+
end
|
48
|
+
|
49
|
+
def process_errors(query_params, response)
|
50
|
+
fragment = query_params[:rawdata]
|
51
|
+
fragment.split($/).each_with_index{|line, index| @message << "#{'%04i' % (index+1)} : #{line}#{$/}"} if Configuration.display_invalid_content
|
52
|
+
REXML::Document.new(response.body).root.each_element('//error') do |e|
|
53
|
+
@message << "#{error_line_prefix}: line #{e.elements['line'].text}: #{e.elements['text'].text}\n"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def validator_host
|
58
|
+
Configuration.feed_validator_host
|
59
|
+
end
|
60
|
+
|
61
|
+
def validator_path
|
62
|
+
Configuration.feed_validator_path
|
63
|
+
end
|
64
|
+
|
65
|
+
def error_line_prefix
|
66
|
+
'Invalid feed'
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
def be_valid_feed()
|
72
|
+
BeValidFeed.new()
|
73
|
+
end
|
74
|
+
alias :be_valid_rss :be_valid_feed
|
75
|
+
alias :be_valid_atom :be_valid_feed
|
76
|
+
|
77
|
+
end
|