roadie 2.4.3 → 3.0.0
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/.gitignore +3 -0
- data/.travis.yml +10 -14
- data/.yardopts +1 -1
- data/Changelog.md +38 -5
- data/Gemfile +3 -4
- data/Guardfile +12 -1
- data/README.md +168 -164
- data/Rakefile +2 -19
- data/lib/roadie.rb +15 -68
- data/lib/roadie/asset_provider.rb +7 -58
- data/lib/roadie/asset_scanner.rb +92 -0
- data/lib/roadie/document.rb +103 -0
- data/lib/roadie/errors.rb +57 -0
- data/lib/roadie/filesystem_provider.rb +30 -60
- data/lib/roadie/inliner.rb +72 -217
- data/lib/roadie/markup_improver.rb +88 -0
- data/lib/roadie/null_provider.rb +13 -0
- data/lib/roadie/null_url_rewriter.rb +12 -0
- data/lib/roadie/provider_list.rb +71 -0
- data/lib/roadie/rspec.rb +1 -0
- data/lib/roadie/rspec/asset_provider.rb +49 -0
- data/lib/roadie/selector.rb +43 -18
- data/lib/roadie/style_attribute_builder.rb +25 -0
- data/lib/roadie/style_block.rb +32 -0
- data/lib/roadie/style_property.rb +93 -0
- data/lib/roadie/stylesheet.rb +65 -0
- data/lib/roadie/upgrade_guide.rb +36 -0
- data/lib/roadie/url_generator.rb +126 -0
- data/lib/roadie/url_rewriter.rb +84 -0
- data/lib/roadie/version.rb +1 -1
- data/roadie.gemspec +8 -11
- data/spec/fixtures/big_em.css +1 -0
- data/spec/fixtures/stylesheets/green.css +1 -0
- data/spec/integration_spec.rb +125 -95
- data/spec/lib/roadie/asset_scanner_spec.rb +153 -0
- data/spec/lib/roadie/css_not_found_spec.rb +17 -0
- data/spec/lib/roadie/document_spec.rb +123 -0
- data/spec/lib/roadie/filesystem_provider_spec.rb +44 -68
- data/spec/lib/roadie/inliner_spec.rb +105 -537
- data/spec/lib/roadie/markup_improver_spec.rb +78 -0
- data/spec/lib/roadie/null_provider_spec.rb +21 -0
- data/spec/lib/roadie/null_url_rewriter_spec.rb +19 -0
- data/spec/lib/roadie/provider_list_spec.rb +89 -0
- data/spec/lib/roadie/selector_spec.rb +15 -10
- data/spec/lib/roadie/style_attribute_builder_spec.rb +29 -0
- data/spec/lib/roadie/style_block_spec.rb +35 -0
- data/spec/lib/roadie/style_property_spec.rb +82 -0
- data/spec/lib/roadie/stylesheet_spec.rb +41 -0
- data/spec/lib/roadie/test_provider_spec.rb +29 -0
- data/spec/lib/roadie/url_generator_spec.rb +121 -0
- data/spec/lib/roadie/url_rewriter_spec.rb +79 -0
- data/spec/shared_examples/asset_provider.rb +11 -0
- data/spec/shared_examples/url_rewriter.rb +23 -0
- data/spec/spec_helper.rb +6 -60
- data/spec/support/have_attribute_matcher.rb +2 -2
- data/spec/support/have_node_matcher.rb +4 -4
- data/spec/support/have_selector_matcher.rb +3 -3
- data/spec/support/have_styling_matcher.rb +51 -15
- data/spec/support/test_provider.rb +13 -0
- metadata +86 -175
- data/Appraisals +0 -15
- data/gemfiles/rails_3.0.gemfile +0 -7
- data/gemfiles/rails_3.0.gemfile.lock +0 -123
- data/gemfiles/rails_3.1.gemfile +0 -7
- data/gemfiles/rails_3.1.gemfile.lock +0 -126
- data/gemfiles/rails_3.2.gemfile +0 -7
- data/gemfiles/rails_3.2.gemfile.lock +0 -124
- data/gemfiles/rails_4.0.gemfile +0 -7
- data/gemfiles/rails_4.0.gemfile.lock +0 -119
- data/lib/roadie/action_mailer_extensions.rb +0 -95
- data/lib/roadie/asset_pipeline_provider.rb +0 -28
- data/lib/roadie/css_file_not_found.rb +0 -22
- data/lib/roadie/railtie.rb +0 -39
- data/lib/roadie/style_declaration.rb +0 -42
- data/spec/fixtures/app/assets/stylesheets/integration.css +0 -10
- data/spec/fixtures/public/stylesheets/integration.css +0 -10
- data/spec/fixtures/views/integration_mailer/marketing.html.erb +0 -2
- data/spec/fixtures/views/integration_mailer/notification.html.erb +0 -8
- data/spec/fixtures/views/integration_mailer/notification.text.erb +0 -6
- data/spec/lib/roadie/action_mailer_extensions_spec.rb +0 -227
- data/spec/lib/roadie/asset_pipeline_provider_spec.rb +0 -65
- data/spec/lib/roadie/css_file_not_found_spec.rb +0 -29
- data/spec/lib/roadie/style_declaration_spec.rb +0 -49
- data/spec/lib/roadie_spec.rb +0 -101
- data/spec/shared_examples/asset_provider_examples.rb +0 -11
- data/spec/support/anonymous_mailer.rb +0 -21
- data/spec/support/change_url_options.rb +0 -5
- data/spec/support/parse_styling.rb +0 -25
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'roadie/rspec'
|
3
|
+
|
4
|
+
describe TestProvider do
|
5
|
+
subject(:provider) { TestProvider.new }
|
6
|
+
|
7
|
+
it_behaves_like "roadie asset provider", valid_name: "existing.css", invalid_name: "invalid.css" do
|
8
|
+
subject { TestProvider.new "existing.css" => "" }
|
9
|
+
end
|
10
|
+
|
11
|
+
it "finds styles from a predefined hash" do
|
12
|
+
provider = TestProvider.new({
|
13
|
+
"foo.css" => "a { color: red; }",
|
14
|
+
"bar.css" => "body { color: green; }",
|
15
|
+
})
|
16
|
+
expect(provider.find_stylesheet("foo.css").to_s).not_to include("body")
|
17
|
+
expect(provider.find_stylesheet("bar.css").to_s).to include("body")
|
18
|
+
expect(provider.find_stylesheet("baz.css")).to be_nil
|
19
|
+
end
|
20
|
+
|
21
|
+
it "can have a default for missing entries" do
|
22
|
+
provider = TestProvider.new({
|
23
|
+
"foo.css" => "a { color: red; }",
|
24
|
+
:default => "body { color: green; }",
|
25
|
+
})
|
26
|
+
expect(provider.find_stylesheet("foo.css").to_s).not_to include("body")
|
27
|
+
expect(provider.find_stylesheet("bar.css").to_s).to include("body")
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
module Roadie
|
5
|
+
describe UrlGenerator do
|
6
|
+
it "is initialized with URL options" do
|
7
|
+
expect(UrlGenerator.new(host: "foo.com").url_options).to eq({host: "foo.com"})
|
8
|
+
end
|
9
|
+
|
10
|
+
it "raises an argument error if no URL options are specified" do
|
11
|
+
expect {
|
12
|
+
UrlGenerator.new(nil)
|
13
|
+
}.to raise_error ArgumentError, /url options/i
|
14
|
+
end
|
15
|
+
|
16
|
+
it "raises an argument error if no host is specified" do
|
17
|
+
expect {
|
18
|
+
UrlGenerator.new(port: 3000)
|
19
|
+
}.to raise_error ArgumentError, /host/i
|
20
|
+
end
|
21
|
+
|
22
|
+
it "raises an argument error if unknown option is passed" do
|
23
|
+
expect {
|
24
|
+
UrlGenerator.new(host: "localhost", secret: true)
|
25
|
+
}.to raise_error ArgumentError, /secret/
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "generating URLs" do
|
29
|
+
def url(path, options = {})
|
30
|
+
UrlGenerator.new(options).generate_url(path)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "uses the given host" do
|
34
|
+
expect(url("/hello.jpg", host: "goats.com")).to eq("http://goats.com/hello.jpg")
|
35
|
+
end
|
36
|
+
|
37
|
+
it "uses the given port" do
|
38
|
+
expect(url("/", host: "example.com", port: 1337)).to eq("http://example.com:1337/")
|
39
|
+
end
|
40
|
+
|
41
|
+
it "uses the given scheme" do
|
42
|
+
expect(url("/", host: "example.com", scheme: "https")).to eq("https://example.com/")
|
43
|
+
end
|
44
|
+
|
45
|
+
it "regards :protocol as an alias for scheme" do
|
46
|
+
expect(url("/", host: "example.com", protocol: "https")).to eq("https://example.com/")
|
47
|
+
end
|
48
|
+
|
49
|
+
it "strips extra characters from the scheme" do
|
50
|
+
expect(url("/", host: "example.com", scheme: "https://")).to eq("https://example.com/")
|
51
|
+
end
|
52
|
+
|
53
|
+
it "uses the given path as a prefix" do
|
54
|
+
expect(url("/my_file", host: "example.com", path: "/my_app")).to eq(
|
55
|
+
"http://example.com/my_app/my_file"
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "returns the original URL if it is absolute" do
|
60
|
+
expect(url("http://foo.com/", host: "bar.com")).to eq("http://foo.com/")
|
61
|
+
end
|
62
|
+
|
63
|
+
it "returns the base URL for blank paths" do
|
64
|
+
expect(url("", host: "foo.com")).to eq("http://foo.com")
|
65
|
+
expect(url(nil, host: "foo.com")).to eq("http://foo.com")
|
66
|
+
end
|
67
|
+
|
68
|
+
it "raises an error on invalid path" do
|
69
|
+
expect {
|
70
|
+
url("://", host: "example.com")
|
71
|
+
}.to raise_error InvalidUrlPath, %r{://}
|
72
|
+
end
|
73
|
+
|
74
|
+
it "accepts base paths without a slash in the beginning" do
|
75
|
+
expect(url("/bar", host: "example.com", path: "foo")).to eq("http://example.com/foo/bar")
|
76
|
+
expect(url("/bar/", host: "example.com", path: "foo/")).to eq("http://example.com/foo/bar/")
|
77
|
+
end
|
78
|
+
|
79
|
+
it "accepts input paths without a slash in the beginning" do
|
80
|
+
expect(url("bar", host: "example.com", path: "/foo")).to eq("http://example.com/foo/bar")
|
81
|
+
expect(url("bar", host: "example.com", path: "/foo/")).to eq("http://example.com/foo/bar")
|
82
|
+
end
|
83
|
+
|
84
|
+
it "does not touch data: URIs" do
|
85
|
+
expect(url("data:deadbeef", host: "example.com")).to eq("data:deadbeef")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# URLs in resources that are not based inside the root requires that we may
|
90
|
+
# specify a "custom base" to properly handle relative paths. Here's an
|
91
|
+
# example:
|
92
|
+
#
|
93
|
+
# # /
|
94
|
+
# # document.html
|
95
|
+
# # images/
|
96
|
+
# # bg.png
|
97
|
+
# # stylesheets/
|
98
|
+
# # my_style.css
|
99
|
+
#
|
100
|
+
# # stylesheets/my_style.css
|
101
|
+
# body { background-image: url(../images/bg.png); }
|
102
|
+
#
|
103
|
+
# In this example, the stylesheet refers to /images/bg.png by using a
|
104
|
+
# relative path from /stylesheets/. In order to understand these cases, we
|
105
|
+
# need to specify where the "base" is in relation to the root.
|
106
|
+
describe "generating URLs with custom base" do
|
107
|
+
it "resolves relative paths" do
|
108
|
+
generator = UrlGenerator.new(host: "foo.com")
|
109
|
+
expect(generator.generate_url("../images/bg.png", "/stylesheets")).to eq("http://foo.com/images/bg.png")
|
110
|
+
expect(generator.generate_url("../images/bg.png", "email/stylesheets")).to eq("http://foo.com/email/images/bg.png")
|
111
|
+
expect(generator.generate_url("images/bg.png", "email/")).to eq("http://foo.com/email/images/bg.png")
|
112
|
+
end
|
113
|
+
|
114
|
+
it "does not use the base when presented with a root-based path" do
|
115
|
+
generator = UrlGenerator.new(host: "foo.com")
|
116
|
+
expect(generator.generate_url("/images/bg.png", "/stylesheets")).to eq("http://foo.com/images/bg.png")
|
117
|
+
expect(generator.generate_url("/images/bg.png", "email/stylesheets")).to eq("http://foo.com/images/bg.png")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'shared_examples/url_rewriter'
|
3
|
+
|
4
|
+
module Roadie
|
5
|
+
describe UrlRewriter do
|
6
|
+
let(:generator) { double("URL generator") }
|
7
|
+
subject(:rewriter) { UrlRewriter.new(generator) }
|
8
|
+
|
9
|
+
it_behaves_like "url rewriter"
|
10
|
+
|
11
|
+
describe "transforming DOM trees" do
|
12
|
+
def dom_document(html); Nokogiri::HTML.parse html; end
|
13
|
+
|
14
|
+
it "rewrites all a[href]" do
|
15
|
+
expect(generator).to receive(:generate_url).with("some/path").and_return "http://foo.com/"
|
16
|
+
dom = dom_document <<-HTML
|
17
|
+
<body>
|
18
|
+
<a href="some/path">Some path</a>
|
19
|
+
</body>
|
20
|
+
HTML
|
21
|
+
|
22
|
+
expect {
|
23
|
+
rewriter.transform_dom dom
|
24
|
+
}.to change { dom.at_css("a")["href"] }.to "http://foo.com/"
|
25
|
+
end
|
26
|
+
|
27
|
+
it "rewrites relative img[src]" do
|
28
|
+
expect(generator).to receive(:generate_url).with("some/path.jpg").and_return "http://foo.com/image.jpg"
|
29
|
+
dom = dom_document <<-HTML
|
30
|
+
<body>
|
31
|
+
<img src="some/path.jpg">
|
32
|
+
</body>
|
33
|
+
HTML
|
34
|
+
|
35
|
+
expect {
|
36
|
+
rewriter.transform_dom dom
|
37
|
+
}.to change { dom.at_css("img")["src"] }.to "http://foo.com/image.jpg"
|
38
|
+
end
|
39
|
+
|
40
|
+
it "rewrites url() directives inside style attributes" do
|
41
|
+
expect(generator).to receive(:generate_url).with("some/path.jpg").and_return "http://foo.com/image.jpg"
|
42
|
+
dom = dom_document <<-HTML
|
43
|
+
<body>
|
44
|
+
<div style="background-image: url("some/path.jpg");">
|
45
|
+
</body>
|
46
|
+
HTML
|
47
|
+
|
48
|
+
expect {
|
49
|
+
rewriter.transform_dom dom
|
50
|
+
}.to change { dom.at_css("div")["style"] }.to 'background-image: url("http://foo.com/image.jpg");'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "transforming css" do
|
55
|
+
it "rewrites all url() directives" do
|
56
|
+
expect(generator).to receive(:generate_url).with("some/path.jpg").and_return "http://foo.com/image.jpg"
|
57
|
+
css = "body { background: top url(some/path.jpg) #eee; }"
|
58
|
+
expect {
|
59
|
+
rewriter.transform_css css
|
60
|
+
}.to change { css }.to "body { background: top url(http://foo.com/image.jpg) #eee; }"
|
61
|
+
end
|
62
|
+
|
63
|
+
it "correctly identifies URLs with single quotes" do
|
64
|
+
expect(generator).to receive(:generate_url).with("images/foo.png").and_return "x"
|
65
|
+
rewriter.transform_css "url('images/foo.png')"
|
66
|
+
end
|
67
|
+
|
68
|
+
it "correctly identifies URLs with double quotes" do
|
69
|
+
expect(generator).to receive(:generate_url).with("images/foo.png").and_return "x"
|
70
|
+
rewriter.transform_css 'url("images/foo.png")'
|
71
|
+
end
|
72
|
+
|
73
|
+
it "correctly identifies URLs with parenthesis inside them" do
|
74
|
+
expect(generator).to receive(:generate_url).with("images/map_(large_(extra)).png").and_return "x"
|
75
|
+
rewriter.transform_css 'url(images/map_(large_(extra)).png)'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
shared_examples_for "asset provider role" do
|
2
|
+
it "responds to #find_stylesheet" do
|
3
|
+
expect(subject).to respond_to(:find_stylesheet)
|
4
|
+
expect(subject.method(:find_stylesheet).arity).to eq(1)
|
5
|
+
end
|
6
|
+
|
7
|
+
it "responds to #find_stylesheet!" do
|
8
|
+
expect(subject).to respond_to(:find_stylesheet!)
|
9
|
+
expect(subject.method(:find_stylesheet!).arity).to eq(1)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
shared_examples_for "url rewriter" do
|
2
|
+
it "is constructed with a generator" do
|
3
|
+
generator = double "URL generator"
|
4
|
+
expect {
|
5
|
+
described_class.new(generator)
|
6
|
+
}.to_not raise_error
|
7
|
+
end
|
8
|
+
|
9
|
+
it "has a #transform_dom(dom) method that returns nil" do
|
10
|
+
expect(subject).to respond_to(:transform_dom)
|
11
|
+
expect(subject.method(:transform_dom).arity).to eq(1)
|
12
|
+
|
13
|
+
dom = Nokogiri::HTML.parse "<body></body>"
|
14
|
+
expect(subject.transform_dom(dom)).to be_nil
|
15
|
+
end
|
16
|
+
|
17
|
+
it "has a #transform_css(css) method that returns nil" do
|
18
|
+
expect(subject).to respond_to(:transform_css)
|
19
|
+
expect(subject.method(:transform_css).arity).to eq(1)
|
20
|
+
|
21
|
+
expect(subject.transform_css("")).to be_nil
|
22
|
+
end
|
23
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,68 +1,14 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'ostruct'
|
4
|
-
require 'rubygems'
|
5
|
-
require 'bundler'
|
1
|
+
require 'rspec/collection_matchers'
|
6
2
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
$stderr.puts e.message
|
11
|
-
$stderr.puts "Run `bundle install` to install missing gems"
|
12
|
-
exit e.status_code
|
3
|
+
if ENV['CI']
|
4
|
+
require 'coveralls'
|
5
|
+
Coveralls.wear!
|
13
6
|
end
|
14
7
|
|
15
|
-
|
16
|
-
require '
|
17
|
-
require 'sprockets'
|
18
|
-
|
19
|
-
require 'roadie/railtie'
|
20
|
-
require 'action_mailer/railtie'
|
21
|
-
|
22
|
-
FIXTURES_PATH = Pathname.new(File.dirname(__FILE__)).join('fixtures')
|
23
|
-
|
24
|
-
class TestApplication < Rails::Application
|
25
|
-
def config
|
26
|
-
@config
|
27
|
-
end
|
28
|
-
|
29
|
-
def assets
|
30
|
-
env = Sprockets::Environment.new
|
31
|
-
env.append_path root.join('app','assets','stylesheets')
|
32
|
-
env
|
33
|
-
end
|
34
|
-
|
35
|
-
def root
|
36
|
-
FIXTURES_PATH
|
37
|
-
end
|
38
|
-
|
39
|
-
def reset_test_config
|
40
|
-
@config = OpenStruct.new({
|
41
|
-
:action_mailer => OpenStruct.new(:default_url_options => {}),
|
42
|
-
:assets => OpenStruct.new(:enabled => false),
|
43
|
-
:roadie => OpenStruct.new(:provider => nil, :enabled => true),
|
44
|
-
})
|
45
|
-
change_default_url_options(:host => "example.com")
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
if Roadie::Railtie.respond_to?(:run_initializers)
|
50
|
-
# Rails >= 3.1
|
51
|
-
ActionMailer::Railtie.run_initializers(:default, Rails.application)
|
52
|
-
Roadie::Railtie.run_initializers(:default, Rails.application)
|
53
|
-
else
|
54
|
-
# Rails 3.0
|
55
|
-
Rails.application.config.active_support.deprecation = :log
|
56
|
-
Rails.logger = Logger.new('/dev/null')
|
57
|
-
Rails.application.initialize!
|
58
|
-
end
|
8
|
+
$: << File.dirname(__FILE__) + '/../lib'
|
9
|
+
require 'roadie'
|
59
10
|
|
60
11
|
RSpec.configure do |config|
|
61
|
-
config.before(:each) do
|
62
|
-
Rails.application.reset_test_config
|
63
|
-
end
|
64
|
-
|
65
|
-
config.treat_symbols_as_metadata_keys_with_true_values = true
|
66
12
|
config.run_all_when_everything_filtered = true
|
67
13
|
end
|
68
14
|
|
@@ -11,11 +11,11 @@ RSpec::Matchers.define :have_attribute do |attribute|
|
|
11
11
|
end
|
12
12
|
|
13
13
|
description { "have attribute #{attribute.inspect} at selector #{@selector.inspect}" }
|
14
|
-
|
14
|
+
failure_message do |document|
|
15
15
|
name, expected = attribute.first
|
16
16
|
"expected #{name} attribute at #{@selector.inspect} to be #{expected.inspect} but was #{attribute(document, name).inspect}"
|
17
17
|
end
|
18
|
-
|
18
|
+
failure_message_when_negated do |document|
|
19
19
|
name, expected = attribute.first
|
20
20
|
"expected #{name} attribute at #{@selector.inspect} to not be #{expected.inspect}"
|
21
21
|
end
|
@@ -3,14 +3,14 @@ RSpec::Matchers.define :have_node do |selector|
|
|
3
3
|
match do |document|
|
4
4
|
node = document.at_css(selector)
|
5
5
|
if @attributes
|
6
|
-
node
|
6
|
+
node && match_attributes(node.attributes)
|
7
7
|
else
|
8
|
-
node
|
8
|
+
node
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
failure_message { "expected document to #{name_to_sentence}#{expected_to_sentence}"}
|
13
|
+
failure_message_when_negated { "expected document to not #{name_to_sentence}#{expected_to_sentence}"}
|
14
14
|
|
15
15
|
def match_attributes(node_attributes)
|
16
16
|
attributes = Hash[node_attributes.map { |name, attribute| [name, attribute.value] }]
|
@@ -1,6 +1,6 @@
|
|
1
1
|
RSpec::Matchers.define :have_selector do |selector|
|
2
|
-
match { |document| document.css(selector).
|
3
|
-
|
4
|
-
|
2
|
+
match { |document| !document.css(selector).empty? }
|
3
|
+
failure_message { "expected document to #{name_to_sentence}#{to_sentence selector}"}
|
4
|
+
failure_message_when_negated { "expected document to not #{name_to_sentence}#{to_sentence selector}"}
|
5
5
|
end
|
6
6
|
|
@@ -1,25 +1,61 @@
|
|
1
1
|
RSpec::Matchers.define :have_styling do |rules|
|
2
|
-
|
2
|
+
normalized_rules = StylingExpectation.new(rules)
|
3
3
|
|
4
|
-
chain
|
5
|
-
|
4
|
+
chain(:at_selector) { |selector| @selector = selector }
|
5
|
+
match { |document|
|
6
|
+
@selector ||= 'body > *:first'
|
7
|
+
normalized_rules == styles_at_selector(document)
|
8
|
+
}
|
9
|
+
|
10
|
+
description {
|
11
|
+
"have styles #{normalized_rules.inspect} at selector #{@selector.inspect}"
|
12
|
+
}
|
13
|
+
|
14
|
+
failure_message { |document|
|
15
|
+
"expected styles at #{@selector.inspect} to be:\n#{normalized_rules}\nbut was:\n#{styles_at_selector(document)}"
|
16
|
+
}
|
17
|
+
|
18
|
+
failure_message_when_negated {
|
19
|
+
"expected styles at #{@selector.inspect} to not be:\n#{normalized_rules}"
|
20
|
+
}
|
21
|
+
|
22
|
+
def styles_at_selector(document)
|
23
|
+
expect(document).to have_selector(@selector)
|
24
|
+
StylingExpectation.new document.at_css(@selector)['style']
|
6
25
|
end
|
26
|
+
end
|
7
27
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
28
|
+
class StylingExpectation
|
29
|
+
def initialize(styling)
|
30
|
+
case styling
|
31
|
+
when String then @rules = parse_rules(styling)
|
32
|
+
when Array then @rules = styling
|
33
|
+
when Hash then @rules = styling.to_a
|
34
|
+
else fail "I don't understand #{styling.inspect}!"
|
13
35
|
end
|
14
36
|
end
|
15
37
|
|
16
|
-
|
17
|
-
|
18
|
-
|
38
|
+
def ==(other)
|
39
|
+
rules == other.rules
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s() rules.to_s end
|
19
43
|
|
20
|
-
|
21
|
-
|
22
|
-
|
44
|
+
protected
|
45
|
+
attr_reader :rules
|
46
|
+
|
47
|
+
private
|
48
|
+
def parse_rules(css)
|
49
|
+
css.split(';').map { |property| parse_property(property) }
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse_property(property)
|
53
|
+
rule, value = property.split(':', 2).map(&:strip)
|
54
|
+
[rule, normalize_quotes(value)]
|
23
55
|
end
|
24
|
-
end
|
25
56
|
|
57
|
+
# JRuby's Nokogiri encodes quotes
|
58
|
+
def normalize_quotes(string)
|
59
|
+
string.gsub '%22', '"'
|
60
|
+
end
|
61
|
+
end
|