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,153 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
module Roadie
|
5
|
+
describe AssetScanner do
|
6
|
+
let(:provider) { TestProvider.new }
|
7
|
+
let(:dom) { dom_document "<html></html>" }
|
8
|
+
|
9
|
+
def dom_fragment(html); Nokogiri::HTML.fragment html; end
|
10
|
+
def dom_document(html); Nokogiri::HTML.parse html; end
|
11
|
+
|
12
|
+
it "is initialized with a DOM tree and a asset provider set" do
|
13
|
+
scanner = AssetScanner.new dom, provider
|
14
|
+
expect(scanner.dom).to eq(dom)
|
15
|
+
expect(scanner.asset_provider).to eq(provider)
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "finding" do
|
19
|
+
it "returns nothing when no stylesheets are referenced" do
|
20
|
+
scanner = AssetScanner.new dom, provider
|
21
|
+
expect(scanner.find_css).to eq([])
|
22
|
+
end
|
23
|
+
|
24
|
+
it "finds all embedded stylesheets" do
|
25
|
+
dom = dom_document <<-HTML
|
26
|
+
<html>
|
27
|
+
<head>
|
28
|
+
<style>a { color: green; }</style>
|
29
|
+
</head>
|
30
|
+
<body>
|
31
|
+
<style>
|
32
|
+
body { color: red; }
|
33
|
+
</style>
|
34
|
+
</body>
|
35
|
+
</html>
|
36
|
+
HTML
|
37
|
+
scanner = AssetScanner.new dom, provider
|
38
|
+
|
39
|
+
stylesheets = scanner.find_css
|
40
|
+
|
41
|
+
expect(stylesheets).to have(2).stylesheets
|
42
|
+
expect(stylesheets[0].to_s).to include("green")
|
43
|
+
expect(stylesheets[1].to_s).to include("red")
|
44
|
+
|
45
|
+
expect(stylesheets.first.name).to eq("(inline)")
|
46
|
+
end
|
47
|
+
|
48
|
+
it "does not find any embedded stylesheets marked for ignoring" do
|
49
|
+
dom = dom_document <<-HTML
|
50
|
+
<html>
|
51
|
+
<head>
|
52
|
+
<style>a { color: green; }</style>
|
53
|
+
<style data-roadie-ignore>a { color: red; }</style>
|
54
|
+
</head>
|
55
|
+
</html>
|
56
|
+
HTML
|
57
|
+
scanner = AssetScanner.new dom, provider
|
58
|
+
expect(scanner.find_css).to have(1).stylesheet
|
59
|
+
end
|
60
|
+
|
61
|
+
it "finds referenced stylesheets through the provider" do
|
62
|
+
stylesheet = double "A stylesheet"
|
63
|
+
expect(provider).to receive(:find_stylesheet!).with("/some/url.css").and_return stylesheet
|
64
|
+
|
65
|
+
dom = dom_fragment %(<link rel="stylesheet" href="/some/url.css">)
|
66
|
+
scanner = AssetScanner.new dom, provider
|
67
|
+
|
68
|
+
expect(scanner.find_css).to eq([stylesheet])
|
69
|
+
end
|
70
|
+
|
71
|
+
it "ignores referenced print stylesheets" do
|
72
|
+
dom = dom_fragment %(<link rel="stylesheet" href="/error.css" media="print">)
|
73
|
+
expect(provider).not_to receive(:find_stylesheet!)
|
74
|
+
|
75
|
+
scanner = AssetScanner.new dom, provider
|
76
|
+
|
77
|
+
expect(scanner.find_css).to eq([])
|
78
|
+
end
|
79
|
+
|
80
|
+
it "does not look for ignored referenced stylesheets" do
|
81
|
+
dom = dom_fragment %(<link rel="stylesheet" href="/error.css" data-roadie-ignore>)
|
82
|
+
expect(provider).not_to receive(:find_stylesheet!)
|
83
|
+
|
84
|
+
scanner = AssetScanner.new dom, provider
|
85
|
+
|
86
|
+
expect(scanner.find_css).to eq([])
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'ignores HTML comments and CDATA sections' do
|
90
|
+
# TinyMCE posts invalid CSS. We support that just to be pragmatic.
|
91
|
+
dom = dom_fragment %(<style><![CDATA[
|
92
|
+
<!--
|
93
|
+
p { color: green }
|
94
|
+
-->
|
95
|
+
]]></style>)
|
96
|
+
|
97
|
+
scanner = AssetScanner.new dom, provider
|
98
|
+
stylesheet = scanner.find_css.first
|
99
|
+
|
100
|
+
expect(stylesheet.to_s).to include("green")
|
101
|
+
expect(stylesheet.to_s).not_to include("!--")
|
102
|
+
expect(stylesheet.to_s).not_to include("CDATA")
|
103
|
+
end
|
104
|
+
|
105
|
+
it "does not pick up scripts generating styles" do
|
106
|
+
dom = dom_fragment <<-HTML
|
107
|
+
<script>
|
108
|
+
var color = "red";
|
109
|
+
document.write("<style type='text/css'>p { color: " + color + "; }</style>");
|
110
|
+
</script>
|
111
|
+
HTML
|
112
|
+
|
113
|
+
scanner = AssetScanner.new dom, provider
|
114
|
+
expect(scanner.find_css).to eq([])
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "extracting" do
|
119
|
+
it "returns the stylesheets found, and removes them from the DOM" do
|
120
|
+
dom = dom_document <<-HTML
|
121
|
+
<html>
|
122
|
+
<head>
|
123
|
+
<title>Hello world!</title>
|
124
|
+
<style>span { color: green; }</style>
|
125
|
+
<link rel="stylesheet" href="/some/url.css">
|
126
|
+
<link rel="stylesheet" href="/error.css" media="print">
|
127
|
+
<link rel="stylesheet" href="/cool.css" data-roadie-ignore>
|
128
|
+
</head>
|
129
|
+
<body>
|
130
|
+
<style data-roadie-ignore>a { color: red; }</style>
|
131
|
+
</body>
|
132
|
+
</html>
|
133
|
+
HTML
|
134
|
+
provider = TestProvider.new "/some/url.css" => "body { color: green; }"
|
135
|
+
scanner = AssetScanner.new dom, provider
|
136
|
+
|
137
|
+
stylesheets = scanner.extract_css
|
138
|
+
|
139
|
+
expect(stylesheets).to have(2).stylesheets
|
140
|
+
expect(stylesheets[0].to_s).to include("span")
|
141
|
+
expect(stylesheets[1].to_s).to include("body")
|
142
|
+
|
143
|
+
expect(dom).to have_selector("html > head > title")
|
144
|
+
expect(dom).to have_selector("html > body > style[data-roadie-ignore]")
|
145
|
+
expect(dom).to have_selector("link[data-roadie-ignore]")
|
146
|
+
expect(dom).to have_selector("link[media=print]")
|
147
|
+
|
148
|
+
expect(dom).not_to have_selector("html > head > style")
|
149
|
+
expect(dom).not_to have_selector("html > head > link[href='/some/url.css']")
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Roadie
|
4
|
+
describe CssNotFound do
|
5
|
+
it "is initialized with a name" do
|
6
|
+
error = CssNotFound.new('style.css')
|
7
|
+
expect(error.css_name).to eq('style.css')
|
8
|
+
expect(error.message).to eq('Could not find stylesheet "style.css"')
|
9
|
+
end
|
10
|
+
|
11
|
+
it "can be initialized with an extra message" do
|
12
|
+
expect(CssNotFound.new('file.css', "directory is missing").message).to eq(
|
13
|
+
'Could not find stylesheet "file.css": directory is missing'
|
14
|
+
)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
module Roadie
|
5
|
+
describe Document do
|
6
|
+
sample_html = "<html><body><p>Hello world!</p></body></html>"
|
7
|
+
subject(:document) { described_class.new sample_html }
|
8
|
+
|
9
|
+
it "is initialized with HTML" do
|
10
|
+
doc = Document.new "<html></html>"
|
11
|
+
expect(doc.html).to eq("<html></html>")
|
12
|
+
end
|
13
|
+
|
14
|
+
it "has an accessor for URL options" do
|
15
|
+
document.url_options = {host: "foo.bar"}
|
16
|
+
expect(document.url_options).to eq({host: "foo.bar"})
|
17
|
+
end
|
18
|
+
|
19
|
+
it "has a ProviderList" do
|
20
|
+
expect(document.asset_providers).to be_instance_of(ProviderList)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "defaults to having just a FilesystemProvider in the provider list" do
|
24
|
+
expect(document).to have(1).asset_providers
|
25
|
+
provider = document.asset_providers.first
|
26
|
+
expect(provider).to be_instance_of(FilesystemProvider)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "allows changes to the asset providers" do
|
30
|
+
other_provider = double "Other proider"
|
31
|
+
old_list = document.asset_providers
|
32
|
+
|
33
|
+
document.asset_providers = [other_provider]
|
34
|
+
expect(document.asset_providers).to be_instance_of(ProviderList)
|
35
|
+
expect(document.asset_providers.each.to_a).to eq([other_provider])
|
36
|
+
|
37
|
+
document.asset_providers = old_list
|
38
|
+
expect(document.asset_providers).to eq(old_list)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "can store callbacks for inlining" do
|
42
|
+
callable = double "Callable"
|
43
|
+
|
44
|
+
document.before_transformation = callable
|
45
|
+
document.after_transformation = callable
|
46
|
+
|
47
|
+
expect(document.before_transformation).to eq(callable)
|
48
|
+
expect(document.after_transformation).to eq(callable)
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "transforming" do
|
52
|
+
it "runs the before and after callbacks" do
|
53
|
+
document = Document.new "<body></body>"
|
54
|
+
before = double call: nil
|
55
|
+
after = double call: nil
|
56
|
+
document.before_transformation = before
|
57
|
+
document.after_transformation = after
|
58
|
+
|
59
|
+
expect(before).to receive(:call).with(instance_of(Nokogiri::HTML::Document)).ordered
|
60
|
+
expect(Inliner).to receive(:new).ordered.and_return double.as_null_object
|
61
|
+
expect(after).to receive(:call).with(instance_of(Nokogiri::HTML::Document)).ordered
|
62
|
+
|
63
|
+
document.transform
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe Document, "(integration)" do
|
69
|
+
it "can transform the document" do
|
70
|
+
document = Document.new <<-HTML
|
71
|
+
<html>
|
72
|
+
<head>
|
73
|
+
<title>Greetings</title>
|
74
|
+
</head>
|
75
|
+
<body>
|
76
|
+
<p>Hello, world!</p>
|
77
|
+
</body>
|
78
|
+
</html>
|
79
|
+
HTML
|
80
|
+
|
81
|
+
document.add_css "p { color: green; }"
|
82
|
+
|
83
|
+
result = Nokogiri::HTML.parse document.transform
|
84
|
+
|
85
|
+
expect(result).to have_selector('html > head > title')
|
86
|
+
expect(result.at_css('title').text).to eq("Greetings")
|
87
|
+
|
88
|
+
expect(result).to have_selector('html > body > p')
|
89
|
+
paragraph = result.at_css('p')
|
90
|
+
expect(paragraph.text).to eq("Hello, world!")
|
91
|
+
expect(paragraph.to_xml).to eq('<p style="color:green">Hello, world!</p>')
|
92
|
+
end
|
93
|
+
|
94
|
+
it "extracts styles from the HTML" do
|
95
|
+
document = Document.new <<-HTML
|
96
|
+
<html>
|
97
|
+
<head>
|
98
|
+
<title>Greetings</title>
|
99
|
+
<link rel="stylesheet" href="/sample.css" type="text/css">
|
100
|
+
</head>
|
101
|
+
<body>
|
102
|
+
<p>Hello, world!</p>
|
103
|
+
</body>
|
104
|
+
</html>
|
105
|
+
HTML
|
106
|
+
|
107
|
+
document.asset_providers = TestProvider.new({
|
108
|
+
"/sample.css" => "p { color: red; text-align: right; }",
|
109
|
+
})
|
110
|
+
|
111
|
+
document.add_css "p { color: green; text-size: 2em; }"
|
112
|
+
|
113
|
+
result = Nokogiri::HTML.parse document.transform
|
114
|
+
|
115
|
+
expect(result).to have_styling([
|
116
|
+
%w[color red],
|
117
|
+
%w[text-align right],
|
118
|
+
%w[color green],
|
119
|
+
%w[text-size 2em]
|
120
|
+
]).at_selector("p")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -1,93 +1,69 @@
|
|
1
|
+
# encoding: UTF-8
|
1
2
|
require 'spec_helper'
|
2
|
-
require '
|
3
|
-
require '
|
3
|
+
require 'roadie/rspec'
|
4
|
+
require 'shared_examples/asset_provider'
|
4
5
|
|
5
6
|
module Roadie
|
6
7
|
describe FilesystemProvider do
|
7
|
-
let(:
|
8
|
+
let(:fixtures_path) { File.expand_path "../../../fixtures", __FILE__ }
|
9
|
+
subject(:provider) { FilesystemProvider.new(fixtures_path) }
|
8
10
|
|
9
|
-
it_behaves_like
|
11
|
+
it_behaves_like "roadie asset provider", valid_name: "stylesheets/green.css", invalid_name: "foo"
|
10
12
|
|
11
|
-
it "
|
12
|
-
FilesystemProvider.new("/
|
13
|
+
it "takes a path" do
|
14
|
+
expect(FilesystemProvider.new("/tmp").path).to eq("/tmp")
|
13
15
|
end
|
14
16
|
|
15
|
-
it "
|
16
|
-
|
17
|
-
FilesystemProvider.new('', path).path.should == path
|
17
|
+
it "defaults to the current working directory" do
|
18
|
+
expect(FilesystemProvider.new.path).to eq(Dir.pwd)
|
18
19
|
end
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
it 'has a path of "<root>/public/stylesheets" by default' do
|
26
|
-
FilesystemProvider.new.path.should == Roadie.app.root.join('public', 'stylesheets')
|
27
|
-
end
|
28
|
-
|
29
|
-
it 'has a prefix of "/stylesheets" by default' do
|
30
|
-
FilesystemProvider.new.prefix.should == "/stylesheets"
|
31
|
-
end
|
21
|
+
describe "finding stylesheets" do
|
22
|
+
it "finds files in the path" do
|
23
|
+
full_path = File.join(fixtures_path, "stylesheets", "green.css")
|
24
|
+
file_contents = File.read full_path
|
32
25
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
let(:provider) { FilesystemProvider.new('/prefix', Pathname.new('.')) }
|
41
|
-
|
42
|
-
def create_file(name, contents = '')
|
43
|
-
Pathname.new(name).tap do |path|
|
44
|
-
path.dirname.mkpath unless path.dirname.directory?
|
45
|
-
path.open('w') { |file| file << contents }
|
46
|
-
end
|
26
|
+
stylesheet = provider.find_stylesheet("stylesheets/green.css")
|
27
|
+
expect(stylesheet).not_to be_nil
|
28
|
+
expect(stylesheet.name).to eq(full_path)
|
29
|
+
expect(stylesheet.to_s).to eq(Stylesheet.new("", file_contents).to_s)
|
47
30
|
end
|
48
31
|
|
49
|
-
it "
|
50
|
-
|
51
|
-
provider.find('foo.css').should == 'contents of foo.css'
|
32
|
+
it "returns nil on non-existant files" do
|
33
|
+
expect(provider.find_stylesheet("non/existant.css")).to be_nil
|
52
34
|
end
|
53
35
|
|
54
|
-
it "
|
55
|
-
|
56
|
-
provider.
|
57
|
-
provider.find('prefix/foo.css').should == 'contents of foo.css'
|
36
|
+
it "finds files inside the base path when using absolute paths" do
|
37
|
+
full_path = File.join(fixtures_path, "stylesheets", "green.css")
|
38
|
+
expect(provider.find_stylesheet("/stylesheets/green.css").name).to eq(full_path)
|
58
39
|
end
|
59
40
|
|
60
|
-
it
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
provider.find('bar').should == 'in bare bar'
|
66
|
-
provider.find('foo').should == 'in foo.css'
|
67
|
-
end
|
68
|
-
|
69
|
-
it "strips the contents" do
|
70
|
-
create_file('foo.css', " contents \n ")
|
71
|
-
provider.find('foo.css').should == "contents"
|
41
|
+
it "does not read files above the base directory" do
|
42
|
+
expect {
|
43
|
+
provider.find_stylesheet("../#{File.basename(__FILE__)}")
|
44
|
+
}.to raise_error FilesystemProvider::InsecurePathError
|
72
45
|
end
|
46
|
+
end
|
73
47
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
provider.find('path/to/foo.css')
|
79
|
-
provider.find('path/from/bar.css')
|
80
|
-
end
|
48
|
+
describe "finding stylesheets with query strings" do
|
49
|
+
it "ignores the query string" do
|
50
|
+
full_path = File.join(fixtures_path, "stylesheets", "green.css")
|
51
|
+
file_contents = File.read full_path
|
81
52
|
|
82
|
-
|
83
|
-
|
84
|
-
|
53
|
+
stylesheet = provider.find_stylesheet("/stylesheets/green.css?time=111")
|
54
|
+
expect(stylesheet).not_to be_nil
|
55
|
+
expect(stylesheet.name).to eq(full_path)
|
56
|
+
expect(stylesheet.to_s).to eq(Stylesheet.new("", file_contents).to_s)
|
85
57
|
end
|
86
58
|
|
87
|
-
it "
|
88
|
-
|
89
|
-
provider.
|
90
|
-
|
59
|
+
it "shows that the query string is ignored inside raised errors" do
|
60
|
+
begin
|
61
|
+
provider.find_stylesheet!("/foo.css?query-string")
|
62
|
+
fail "No error was raised"
|
63
|
+
rescue CssNotFound => error
|
64
|
+
expect(error.css_name).to eq("foo.css")
|
65
|
+
expect(error.to_s).to include("/foo.css?query-string")
|
66
|
+
end
|
91
67
|
end
|
92
68
|
end
|
93
69
|
end
|
@@ -1,581 +1,149 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
require 'spec_helper'
|
3
3
|
|
4
|
-
|
5
|
-
|
4
|
+
module Roadie
|
5
|
+
describe Inliner do
|
6
|
+
before { @stylesheet = "" }
|
7
|
+
def use_css(css) @stylesheet = Stylesheet.new("example", css) end
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def rendering(html, options = {})
|
12
|
-
url_options = options.fetch(:url_options, {:host => 'example.com'})
|
13
|
-
after_inlining_handler = options[:after_inlining_handler]
|
14
|
-
Nokogiri::HTML.parse Roadie::Inliner.new(provider, ['global.css'], html, url_options, after_inlining_handler).execute
|
15
|
-
end
|
16
|
-
|
17
|
-
describe "initialization" do
|
18
|
-
it "warns about asset_path_prefix being non-functional" do
|
19
|
-
expect {
|
20
|
-
Roadie::Inliner.new(provider, [], '', :asset_path_prefix => 'foo')
|
21
|
-
}.to raise_error(ArgumentError, /asset_path_prefix/)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
describe "inlining styles" do
|
26
|
-
before(:each) do
|
27
|
-
# Make sure to have some css even when we don't specify any
|
28
|
-
# We have specific tests for when this is nil
|
29
|
-
use_css ''
|
30
|
-
end
|
31
|
-
|
32
|
-
it "inlines simple attributes" do
|
33
|
-
use_css 'p { color: green }'
|
34
|
-
rendering('<p></p>').should have_styling('color' => 'green')
|
35
|
-
end
|
36
|
-
|
37
|
-
it "inlines browser-prefixed attributes" do
|
38
|
-
use_css 'p { -vendor-color: green }'
|
39
|
-
rendering('<p></p>').should have_styling('-vendor-color' => 'green')
|
40
|
-
end
|
41
|
-
|
42
|
-
it "inlines CSS3 attributes" do
|
43
|
-
use_css 'p { border-radius: 2px; }'
|
44
|
-
rendering('<p></p>').should have_styling('border-radius' => '2px')
|
45
|
-
end
|
46
|
-
|
47
|
-
it "keeps the order of the styles that are inlined" do
|
48
|
-
use_css 'h1 { padding: 2px; margin: 5px; }'
|
49
|
-
rendering('<h1></h1>').should have_styling([['padding', '2px'], ['margin', '5px']])
|
50
|
-
end
|
51
|
-
|
52
|
-
it "combines multiple selectors into one" do
|
53
|
-
use_css 'p { color: green; }
|
54
|
-
.tip { float: right; }'
|
55
|
-
rendering('<p class="tip"></p>').should have_styling([['color', 'green'], ['float', 'right']])
|
56
|
-
end
|
57
|
-
|
58
|
-
it "uses the attributes with the highest specificity when conflicts arises" do
|
59
|
-
use_css "p { color: red; }
|
60
|
-
.safe { color: green; }"
|
61
|
-
rendering('<p class="safe"></p>').should have_styling('color' => 'green')
|
62
|
-
end
|
63
|
-
|
64
|
-
it "sorts styles by specificity order" do
|
65
|
-
use_css 'p { margin: 2px; }
|
66
|
-
#big { margin: 10px; }
|
67
|
-
.down { margin-bottom: 5px; }'
|
68
|
-
|
69
|
-
rendering('<p class="down"></p>').should have_styling([
|
70
|
-
['margin', '2px'], ['margin-bottom', '5px']
|
71
|
-
])
|
72
|
-
|
73
|
-
rendering('<p class="down" id="big"></p>').should have_styling([
|
74
|
-
['margin-bottom', '5px'], ['margin', '10px']
|
75
|
-
])
|
76
|
-
end
|
77
|
-
|
78
|
-
it "supports multiple selectors for the same rules" do
|
79
|
-
use_css 'p, a { color: green; }'
|
80
|
-
rendering('<p></p><a></a>').tap do |document|
|
81
|
-
document.should have_styling('color' => 'green').at_selector('p')
|
82
|
-
document.should have_styling('color' => 'green').at_selector('a')
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
it "keeps !important properties" do
|
87
|
-
use_css "a { text-decoration: underline !important; }
|
88
|
-
a.hard-to-spot { text-decoration: none; }"
|
89
|
-
rendering('<a class="hard-to-spot"></a>').should have_styling('text-decoration' => 'underline !important')
|
9
|
+
def rendering(html, stylesheet = @stylesheet)
|
10
|
+
dom = Nokogiri::HTML.parse html
|
11
|
+
Inliner.new([stylesheet]).inline(dom)
|
12
|
+
dom
|
90
13
|
end
|
91
14
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
it "does not touch already present inline styles" do
|
98
|
-
use_css "p { color: red }"
|
99
|
-
rendering('<p style="color: green"></p>').should have_styling([['color', 'red'], ['color', 'green']])
|
100
|
-
end
|
101
|
-
|
102
|
-
it "does not apply link and dynamic pseudo selectors" do
|
103
|
-
use_css "
|
104
|
-
p:active { color: red }
|
105
|
-
p:focus { color: red }
|
106
|
-
p:hover { color: red }
|
107
|
-
p:link { color: red }
|
108
|
-
p:target { color: red }
|
109
|
-
p:visited { color: red }
|
110
|
-
|
111
|
-
p.active { width: 100%; }
|
112
|
-
"
|
113
|
-
rendering('<p class="active"></p>').should have_styling('width' => '100%')
|
114
|
-
end
|
115
|
-
|
116
|
-
it "does not crash on any pseudo element selectors" do
|
117
|
-
use_css "
|
118
|
-
p.some-element { width: 100%; }
|
119
|
-
p::some-element { color: red; }
|
120
|
-
"
|
121
|
-
rendering('<p class="some-element"></p>').should have_styling('width' => '100%')
|
122
|
-
end
|
123
|
-
|
124
|
-
it "works with nth-child" do
|
125
|
-
use_css "
|
126
|
-
p { color: red; }
|
127
|
-
p:nth-child(2n) { color: green; }
|
128
|
-
"
|
129
|
-
rendering("
|
130
|
-
<p class='one'></p>
|
131
|
-
<p class='two'></p>
|
132
|
-
").should have_styling('color' => 'green').at_selector('.two')
|
133
|
-
end
|
134
|
-
|
135
|
-
it "ignores selectors with @" do
|
136
|
-
use_css '@keyframes progress-bar-stripes {
|
137
|
-
from {
|
138
|
-
background-position: 40px 0;
|
139
|
-
}
|
140
|
-
to {
|
141
|
-
background-position: 0 0;
|
142
|
-
}
|
143
|
-
}'
|
144
|
-
expect { rendering('<p></p>') }.not_to raise_error
|
145
|
-
end
|
146
|
-
|
147
|
-
it 'ignores HTML comments and CDATA sections' do
|
148
|
-
# TinyMCE posts invalid CSS. We support that just to be pragmatic.
|
149
|
-
use_css %(<![CDATA[
|
150
|
-
<!--
|
151
|
-
p { color: green }
|
152
|
-
-->
|
153
|
-
]]>)
|
154
|
-
expect { rendering '<p></p>' }.not_to raise_error
|
155
|
-
|
156
|
-
use_css %(
|
157
|
-
<!--
|
158
|
-
<![CDATA[
|
159
|
-
<![CDATA[
|
160
|
-
span { color: red }
|
161
|
-
]]>
|
162
|
-
-->
|
163
|
-
)
|
164
|
-
expect { rendering '<p></p>' }.not_to raise_error
|
165
|
-
end
|
166
|
-
|
167
|
-
it "does not pick up scripts generating styles" do
|
168
|
-
expect {
|
169
|
-
rendering <<-HTML
|
170
|
-
<script>
|
171
|
-
var color = "red";
|
172
|
-
document.write("<style type='text/css'>p { color: " + color + "; }</style>");
|
173
|
-
</script>
|
174
|
-
HTML
|
175
|
-
}.not_to raise_error
|
176
|
-
end
|
177
|
-
|
178
|
-
describe "inline <style> element" do
|
179
|
-
it "is used for inlined styles" do
|
180
|
-
rendering(<<-HTML).should have_styling([['color', 'green'], ['font-size', '1.1em']])
|
181
|
-
<html>
|
182
|
-
<head>
|
183
|
-
<style type="text/css">p { color: green; }</style>
|
184
|
-
</head>
|
185
|
-
<body>
|
186
|
-
<p>Hello World</p>
|
187
|
-
<style type="text/css">p { font-size: 1.1em; }</style>
|
188
|
-
</body>
|
189
|
-
</html>
|
190
|
-
HTML
|
15
|
+
describe "inlining styles" do
|
16
|
+
it "inlines simple attributes" do
|
17
|
+
use_css 'p { color: green }'
|
18
|
+
expect(rendering('<p></p>')).to have_styling('color' => 'green')
|
191
19
|
end
|
192
20
|
|
193
|
-
it "
|
194
|
-
|
195
|
-
|
196
|
-
<head>
|
197
|
-
<style type="text/css">p { color: green; }</style>
|
198
|
-
</head>
|
199
|
-
<body>
|
200
|
-
<style type="text/css">p { font-size: 1.1em; }</style>
|
201
|
-
</body>
|
202
|
-
</html>
|
203
|
-
HTML
|
21
|
+
it "inlines browser-prefixed attributes" do
|
22
|
+
use_css 'p { -vendor-color: green }'
|
23
|
+
expect(rendering('<p></p>')).to have_styling('-vendor-color' => 'green')
|
204
24
|
end
|
205
25
|
|
206
|
-
it "
|
207
|
-
|
208
|
-
|
209
|
-
<p></p>
|
210
|
-
HTML
|
211
|
-
document.should have_selector('style[data-immutable]')
|
212
|
-
document.should_not have_styling('color' => 'red')
|
26
|
+
it "inlines CSS3 attributes" do
|
27
|
+
use_css 'p { border-radius: 2px; }'
|
28
|
+
expect(rendering('<p></p>')).to have_styling('border-radius' => '2px')
|
213
29
|
end
|
214
30
|
|
215
|
-
it "
|
216
|
-
|
217
|
-
|
218
|
-
<p></p>
|
219
|
-
HTML
|
220
|
-
document.should have_selector('style[media=print]')
|
221
|
-
document.should_not have_styling('color' => 'red').at_selector('p')
|
31
|
+
it "keeps the order of the styles that are inlined" do
|
32
|
+
use_css 'h1 { padding: 2px; margin: 5px; }'
|
33
|
+
expect(rendering('<h1></h1>')).to have_styling([['padding', '2px'], ['margin', '5px']])
|
222
34
|
end
|
223
35
|
|
224
|
-
it "
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
rendering(<<-HTML).should have_styling('color' => 'green').at_selector('p')
|
229
|
-
<style type="text/css">p { color: green; }</style>
|
230
|
-
<p>Hello World</p>
|
231
|
-
HTML
|
36
|
+
it "combines multiple selectors into one" do
|
37
|
+
use_css 'p { color: green; }
|
38
|
+
.tip { float: right; }'
|
39
|
+
expect(rendering('<p class="tip"></p>')).to have_styling([['color', 'green'], ['float', 'right']])
|
232
40
|
end
|
233
41
|
|
234
|
-
it "
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
<svg>
|
239
|
-
<style>This is not parseable by the CSS parser!</style>
|
240
|
-
</svg>
|
241
|
-
HTML
|
242
|
-
}.to_not raise_error
|
42
|
+
it "uses the attributes with the highest specificity when conflicts arises" do
|
43
|
+
use_css ".safe { color: green; }
|
44
|
+
p { color: red; }"
|
45
|
+
expect(rendering('<p class="safe"></p>')).to have_styling([['color', 'red'], ['color', 'green']])
|
243
46
|
end
|
244
|
-
end
|
245
|
-
end
|
246
|
-
|
247
|
-
describe "linked stylesheets" do
|
248
|
-
def fake_file(name, contents)
|
249
|
-
provider.should_receive(:find).with(name).and_return(contents)
|
250
|
-
end
|
251
|
-
|
252
|
-
it "inlines styles from the linked stylesheet" do
|
253
|
-
fake_file('/assets/green_paragraphs.css', 'p { color: green; }')
|
254
|
-
|
255
|
-
rendering(<<-HTML).should have_styling('color' => 'green').at_selector('p')
|
256
|
-
<html>
|
257
|
-
<head>
|
258
|
-
<link rel="stylesheet" href="/assets/green_paragraphs.css">
|
259
|
-
</head>
|
260
|
-
<body>
|
261
|
-
<p></p>
|
262
|
-
</body>
|
263
|
-
</html>
|
264
|
-
HTML
|
265
|
-
end
|
266
|
-
|
267
|
-
it "inlines styles from the linked stylesheet in subdirectory" do
|
268
|
-
fake_file('/assets/subdirectory/red_paragraphs.css', 'p { color: red; }')
|
269
|
-
|
270
|
-
rendering(<<-HTML).should have_styling('color' => 'red').at_selector('p')
|
271
|
-
<html>
|
272
|
-
<head>
|
273
|
-
<link rel="stylesheet" href="/assets/subdirectory/red_paragraphs.css">
|
274
|
-
</head>
|
275
|
-
<body>
|
276
|
-
<p></p>
|
277
|
-
</body>
|
278
|
-
</html>
|
279
|
-
HTML
|
280
|
-
end
|
281
47
|
|
282
|
-
|
283
|
-
|
284
|
-
|
48
|
+
it "sorts styles by specificity order" do
|
49
|
+
use_css 'p { important: no; }
|
50
|
+
#important { important: very; }
|
51
|
+
.important { important: yes; }'
|
285
52
|
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
<link rel="stylesheet" href="/assets/large_purple_paragraphs.css">
|
290
|
-
<link rel="stylesheet" href="/assets/green_paragraphs.css">
|
291
|
-
</head>
|
292
|
-
<body>
|
293
|
-
<p></p>
|
294
|
-
</body>
|
295
|
-
</html>
|
296
|
-
HTML
|
53
|
+
expect(rendering('<p class="important"></p>')).to have_styling([
|
54
|
+
%w[important no], %w[important yes]
|
55
|
+
])
|
297
56
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
]).at_selector('p')
|
302
|
-
end
|
303
|
-
|
304
|
-
it "removes the stylesheet links from the DOM" do
|
305
|
-
provider.stub(:find => '')
|
306
|
-
rendering(<<-HTML).should_not have_selector('link')
|
307
|
-
<html>
|
308
|
-
<head>
|
309
|
-
<link rel="stylesheet" href="/assets/green_paragraphs.css">
|
310
|
-
<link rel="stylesheet" href="/assets/large_purple_paragraphs.css">
|
311
|
-
</head>
|
312
|
-
<body>
|
313
|
-
</body>
|
314
|
-
</html>
|
315
|
-
HTML
|
316
|
-
end
|
317
|
-
|
318
|
-
context "when stylesheet is for print media" do
|
319
|
-
it "does not inline the stylesheet" do
|
320
|
-
rendering(<<-HTML).should_not have_styling('color' => 'green').at_selector('p')
|
321
|
-
<html>
|
322
|
-
<head>
|
323
|
-
<link rel="stylesheet" href="/assets/green_paragraphs.css" media="print">
|
324
|
-
</head>
|
325
|
-
<body>
|
326
|
-
<p></p>
|
327
|
-
</body>
|
328
|
-
</html>
|
329
|
-
HTML
|
57
|
+
expect(rendering('<p class="important" id="important"></p>')).to have_styling([
|
58
|
+
%w[important no], %w[important yes], %w[important very]
|
59
|
+
])
|
330
60
|
end
|
331
61
|
|
332
|
-
it "
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
<body>
|
339
|
-
</body>
|
340
|
-
</html>
|
341
|
-
HTML
|
342
|
-
end
|
343
|
-
end
|
344
|
-
|
345
|
-
context "when stylesheet is marked as immutable" do
|
346
|
-
it "does not inline the stylesheet" do
|
347
|
-
rendering(<<-HTML).should_not have_styling('color' => 'green').at_selector('p')
|
348
|
-
<html>
|
349
|
-
<head>
|
350
|
-
<link rel="stylesheet" href="/assets/green_paragraphs.css" data-immutable="true">
|
351
|
-
</head>
|
352
|
-
<body>
|
353
|
-
<p></p>
|
354
|
-
</body>
|
355
|
-
</html>
|
356
|
-
HTML
|
62
|
+
it "supports multiple selectors for the same rules" do
|
63
|
+
use_css 'p, a { color: green; }'
|
64
|
+
rendering('<p></p><a></a>').tap do |document|
|
65
|
+
expect(document).to have_styling('color' => 'green').at_selector('p')
|
66
|
+
expect(document).to have_styling('color' => 'green').at_selector('a')
|
67
|
+
end
|
357
68
|
end
|
358
69
|
|
359
|
-
it "
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
<body>
|
366
|
-
</body>
|
367
|
-
</html>
|
368
|
-
HTML
|
70
|
+
it "keeps !important properties" do
|
71
|
+
use_css "a { text-decoration: underline !important; }
|
72
|
+
a.hard-to-spot { text-decoration: none; }"
|
73
|
+
expect(rendering('<a class="hard-to-spot"></a>')).to have_styling([
|
74
|
+
['text-decoration', 'none'], ['text-decoration', 'underline !important']
|
75
|
+
])
|
369
76
|
end
|
370
|
-
end
|
371
77
|
|
372
|
-
|
373
|
-
|
374
|
-
rendering(
|
375
|
-
<html>
|
376
|
-
<head>
|
377
|
-
<link rel="stylesheet" href="http://www.example.com/green_paragraphs.css">
|
378
|
-
</head>
|
379
|
-
<body>
|
380
|
-
<p></p>
|
381
|
-
</body>
|
382
|
-
</html>
|
383
|
-
HTML
|
78
|
+
it "combines with already present inline styles" do
|
79
|
+
use_css "p { color: green }"
|
80
|
+
expect(rendering('<p style="font-size: 1.1em"></p>')).to have_styling([['color', 'green'], ['font-size', '1.1em']])
|
384
81
|
end
|
385
82
|
|
386
|
-
it "does not
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
</html>
|
395
|
-
HTML
|
83
|
+
it "does not override inline styles" do
|
84
|
+
use_css "p { text-transform: uppercase; color: red }"
|
85
|
+
# The two color properties are kept to make css fallbacks work correctly
|
86
|
+
expect(rendering('<p style="color: green"></p>')).to have_styling([
|
87
|
+
['text-transform', 'uppercase'],
|
88
|
+
['color', 'red'],
|
89
|
+
['color', 'green'],
|
90
|
+
])
|
396
91
|
end
|
397
|
-
end
|
398
92
|
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
</body>
|
408
|
-
</html>
|
409
|
-
HTML
|
93
|
+
it "does not apply link and dynamic pseudo selectors" do
|
94
|
+
use_css "
|
95
|
+
p:active { color: red }
|
96
|
+
p:focus { color: red }
|
97
|
+
p:hover { color: red }
|
98
|
+
p:link { color: red }
|
99
|
+
p:target { color: red }
|
100
|
+
p:visited { color: red }
|
410
101
|
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
error.guess.should == '/assets/not_found.css'
|
415
|
-
end
|
102
|
+
p.active { width: 100%; }
|
103
|
+
"
|
104
|
+
expect(rendering('<p class="active"></p>')).to have_styling('width' => '100%')
|
416
105
|
end
|
417
|
-
end
|
418
106
|
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
</head>
|
426
|
-
<body>
|
427
|
-
<p></p>
|
428
|
-
</body>
|
429
|
-
</html>
|
430
|
-
HTML
|
431
|
-
rendering(html).tap do |document|
|
432
|
-
document.should_not have_styling('color' => 'green').at_selector('p')
|
433
|
-
document.should have_selector('link')
|
434
|
-
end
|
107
|
+
it "does not crash on any pseudo element selectors" do
|
108
|
+
use_css "
|
109
|
+
p.some-element { width: 100%; }
|
110
|
+
p::some-element { color: red; }
|
111
|
+
"
|
112
|
+
expect(rendering('<p class="some-element"></p>')).to have_styling('width' => '100%')
|
435
113
|
end
|
436
|
-
end
|
437
|
-
end
|
438
|
-
|
439
|
-
describe "making urls absolute" do
|
440
|
-
it "works on image sources" do
|
441
|
-
rendering('<img src="/images/foo.jpg" />').should have_attribute('src' => 'http://example.com/images/foo.jpg')
|
442
|
-
rendering('<img src="../images/foo.jpg" />').should have_attribute('src' => 'http://example.com/images/foo.jpg')
|
443
|
-
rendering('<img src="foo.jpg" />').should have_attribute('src' => 'http://example.com/foo.jpg')
|
444
|
-
end
|
445
|
-
|
446
|
-
it "does not touch image sources that are already absolute" do
|
447
|
-
rendering('<img src="http://other.example.org/images/foo.jpg" />').should have_attribute('src' => 'http://other.example.org/images/foo.jpg')
|
448
|
-
end
|
449
|
-
|
450
|
-
it "works on inlined style attributes" do
|
451
|
-
rendering('<p style="background: url(/paper.png)"></p>').should have_styling('background' => 'url(http://example.com/paper.png)')
|
452
|
-
rendering('<p style="background: url("/paper.png")"></p>').should have_styling('background' => 'url("http://example.com/paper.png")')
|
453
|
-
end
|
454
114
|
|
455
|
-
|
456
|
-
|
457
|
-
table { background-image: url('/paper.png'); }
|
458
|
-
div { background-image: url(\"/paper.png\"); }"
|
459
|
-
rendering('<p></p>').should have_styling('background-image' => 'url(http://example.com/paper.png)')
|
460
|
-
rendering('<table></table>').should have_styling('background-image' => "url('http://example.com/paper.png')")
|
461
|
-
rendering('<div></div>').should have_styling('background-image' => 'url("http://example.com/paper.png")')
|
462
|
-
end
|
463
|
-
|
464
|
-
it "does not touch style urls that are already absolute" do
|
465
|
-
external_url = 'url(http://other.example.org/paper.png)'
|
466
|
-
use_css "p { background-image: #{external_url}; }"
|
467
|
-
rendering('<p></p>').should have_styling('background-image' => external_url)
|
468
|
-
rendering(%(<div style="background-image: #{external_url}"></div>)).should have_styling('background-image' => external_url)
|
469
|
-
end
|
115
|
+
it "warns on selectors that crash Nokogiri" do
|
116
|
+
dom = Nokogiri::HTML.parse "<p></p>"
|
470
117
|
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
118
|
+
stylesheet = Stylesheet.new "foo.css", "p[%^=foo] { color: red; }"
|
119
|
+
inliner = Inliner.new([stylesheet])
|
120
|
+
expect(inliner).to receive(:warn).with(
|
121
|
+
%{Roadie cannot use "p[%^=foo]" (from "foo.css" stylesheet) when inlining stylesheets}
|
122
|
+
)
|
123
|
+
inliner.inline(dom)
|
476
124
|
end
|
477
|
-
end
|
478
125
|
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
end
|
126
|
+
it "works with nth-child" do
|
127
|
+
use_css "
|
128
|
+
p { color: red; }
|
129
|
+
p:nth-child(2n) { color: green; }
|
130
|
+
"
|
131
|
+
result = rendering("<p></p> <p></p>")
|
486
132
|
|
487
|
-
|
488
|
-
|
489
|
-
# values, but it looks like Rails do accept it so we might as well do it
|
490
|
-
# too.
|
491
|
-
it "supports protocol settings with additional tokens" do
|
492
|
-
use_css "img { background: url(/a.jpg); }"
|
493
|
-
rendering('<img src="/b.jpg" />', :url_options => {:host => 'example.com', :protocol => 'https://'}).tap do |document|
|
494
|
-
document.should have_attribute('src' => 'https://example.com/b.jpg').at_selector('img')
|
495
|
-
document.should have_styling('background' => 'url(https://example.com/a.jpg)')
|
133
|
+
expect(result).to have_styling([['color', 'red']]).at_selector('p:first')
|
134
|
+
expect(result).to have_styling([['color', 'red'], ['color', 'green']]).at_selector('p:last')
|
496
135
|
end
|
497
|
-
end
|
498
|
-
|
499
|
-
it "does not touch data: URIs" do
|
500
|
-
use_css "div { background: url(data:abcdef); }"
|
501
|
-
rendering('<div></div>').should have_styling('background' => 'url(data:abcdef)')
|
502
|
-
end
|
503
|
-
end
|
504
|
-
|
505
|
-
describe "custom converter" do
|
506
|
-
let(:html) { '<div id="foo"></div>' }
|
507
|
-
|
508
|
-
it "is invoked" do
|
509
|
-
after_inlining_handler = double("converter")
|
510
|
-
after_inlining_handler.should_receive(:call).with(anything)
|
511
|
-
rendering(html, :after_inlining_handler => after_inlining_handler)
|
512
|
-
end
|
513
|
-
|
514
|
-
it "modifies the document using lambda" do
|
515
|
-
after_inlining_handler = lambda {|d| d.css("#foo").first["class"] = "bar"}
|
516
|
-
rendering(html, :after_inlining_handler => after_inlining_handler).css("#foo").first["class"].should == "bar"
|
517
|
-
end
|
518
|
-
|
519
|
-
it "modifies the document using object" do
|
520
|
-
klass = Class.new do
|
521
|
-
def call(d)
|
522
|
-
d.css("#foo").first["class"] = "bar"
|
523
|
-
end
|
524
|
-
end
|
525
|
-
after_inlining_handler = klass.new
|
526
|
-
rendering(html, :after_inlining_handler => after_inlining_handler).css("#foo").first["class"].should == "bar"
|
527
|
-
end
|
528
|
-
end
|
529
|
-
|
530
|
-
describe "inserting tags" do
|
531
|
-
it "inserts a doctype if not present" do
|
532
|
-
rendering('<html><body></body></html>').to_xml.should include('<!DOCTYPE ')
|
533
|
-
rendering('<!DOCTYPE html><html><body></body></html>').to_xml.should_not match(/(DOCTYPE.*?){2}/)
|
534
|
-
end
|
535
|
-
|
536
|
-
it "sets xmlns of <html> to that of XHTML" do
|
537
|
-
rendering('<html><body></body></html>').should have_node('html').with_attributes("xmlns" => "http://www.w3.org/1999/xhtml")
|
538
|
-
end
|
539
|
-
|
540
|
-
it "inserts basic html structure if not present" do
|
541
|
-
rendering('<h1>Hey!</h1>').should have_selector('html > head + body > h1')
|
542
|
-
end
|
543
|
-
|
544
|
-
it "inserts <head> if not present" do
|
545
|
-
rendering('<html><body></body></html>').should have_selector('html > head + body')
|
546
|
-
end
|
547
|
-
|
548
|
-
it "inserts meta tag describing content-type" do
|
549
|
-
rendering('<html><head></head><body></body></html>').tap do |document|
|
550
|
-
document.should have_selector('head meta[http-equiv="Content-Type"]')
|
551
|
-
document.css('head meta[http-equiv="Content-Type"]').first['content'].should == 'text/html; charset=UTF-8'
|
552
|
-
end
|
553
|
-
end
|
554
|
-
|
555
|
-
it "does not insert duplicate meta tags describing content-type" do
|
556
|
-
rendering(<<-HTML).to_html.scan('meta').should have(1).item
|
557
|
-
<html>
|
558
|
-
<head>
|
559
|
-
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
|
560
|
-
</head>
|
561
|
-
</html>
|
562
|
-
HTML
|
563
|
-
end
|
564
|
-
end
|
565
136
|
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
}.each do |raw, expected|
|
577
|
-
raw =~ Roadie::Inliner::CSS_URL_REGEXP
|
578
|
-
$2.should == expected
|
137
|
+
it "ignores selectors with @" do
|
138
|
+
use_css '@keyframes progress-bar-stripes {
|
139
|
+
from {
|
140
|
+
background-position: 40px 0;
|
141
|
+
}
|
142
|
+
to {
|
143
|
+
background-position: 0 0;
|
144
|
+
}
|
145
|
+
}'
|
146
|
+
expect { rendering('<p></p>') }.not_to raise_error
|
579
147
|
end
|
580
148
|
end
|
581
149
|
end
|