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,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
|