roadie 2.4.3 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|