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,36 @@
|
|
1
|
+
#
|
2
|
+
# Let the user know that they need to act to get Roadie compatible with their
|
3
|
+
# Rails apps if they might have upgraded from Roadie 2.
|
4
|
+
# TODO: Remove this file by the time of version 3.1.
|
5
|
+
#
|
6
|
+
|
7
|
+
if defined?(Rails) && !defined?(ROADIE_I_KNOW_ABOUT_VERSION_3)
|
8
|
+
begin
|
9
|
+
require 'roadie/rails'
|
10
|
+
rescue LoadError
|
11
|
+
warn <<-WARNING
|
12
|
+
Hey there! It looks like you might have tried to upgrade to Roadie 3 from Roadie 2.
|
13
|
+
|
14
|
+
Roadie 3 is a completely new version that is no longer interfacing with Rails
|
15
|
+
out-of-the-box. In order to use it you need to add the gem roadie-rails too.
|
16
|
+
|
17
|
+
You should really read the upgrade guide since the API have changed:
|
18
|
+
|
19
|
+
https://github.com/Mange/roadie-rails/blob/master/Upgrading.md#upgrading-from-roadie-2
|
20
|
+
|
21
|
+
I hope this new version will work better for you, but if you are not ready to
|
22
|
+
upgrade right now add a version specifier to your Gemfile:
|
23
|
+
|
24
|
+
gem 'roadie', '~> 2.4' # Support any minor version in the Roadie 2 series.
|
25
|
+
|
26
|
+
In case you have a need for Roadie without the default Rails integration you
|
27
|
+
can remove this warning by setting a constant:
|
28
|
+
|
29
|
+
# config/application.rb
|
30
|
+
ROADIE_I_KNOW_ABOUT_VERSION_3 = true # Remove after Roadie 3.1
|
31
|
+
|
32
|
+
Thank you for your attention.
|
33
|
+
WARNING
|
34
|
+
raise
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Roadie
|
4
|
+
# @api private
|
5
|
+
# Class that handles URL generation
|
6
|
+
#
|
7
|
+
# URL generation is all about converting relative URLs into absolute URLS
|
8
|
+
# according to the given options. It is written such as absolute URLs will
|
9
|
+
# get passed right through, so all URLs could be passed through here.
|
10
|
+
class UrlGenerator
|
11
|
+
attr_reader :url_options
|
12
|
+
|
13
|
+
# Create a new instance with the given URL options.
|
14
|
+
#
|
15
|
+
# Initializing without a host setting raises an error, as do unknown keys.
|
16
|
+
#
|
17
|
+
# @param [Hash] url_options
|
18
|
+
# @option url_options [String] :host (required)
|
19
|
+
# @option url_options [String, Integer] :port
|
20
|
+
# @option url_options [String] :path root path
|
21
|
+
# @option url_options [String] :scheme URL scheme ("http" is default)
|
22
|
+
# @option url_options [String] :protocol alias for :scheme
|
23
|
+
def initialize(url_options)
|
24
|
+
raise ArgumentError, "No URL options were specified" unless url_options
|
25
|
+
raise ArgumentError, "No :host was specified; options are: #{url_options.inspect}" unless url_options[:host]
|
26
|
+
validate_options url_options
|
27
|
+
|
28
|
+
@url_options = url_options
|
29
|
+
@root_uri = build_root_uri
|
30
|
+
end
|
31
|
+
|
32
|
+
# Generate an absolute URL from a relative URL.
|
33
|
+
#
|
34
|
+
# If the passed path is already an absolute URL, it will be returned as-is.
|
35
|
+
# If passed a blank path, the "root URL" will be returned. The root URL is
|
36
|
+
# the URL that the {#url_options} would generate by themselves.
|
37
|
+
#
|
38
|
+
# An optional base can be specified. The base is another relative path from
|
39
|
+
# the root that specifies an "offset" from which the path was found in. A
|
40
|
+
# common use-case is to convert a relative path found in a stylesheet which
|
41
|
+
# resides in a subdirectory.
|
42
|
+
#
|
43
|
+
# @example Normal conversions
|
44
|
+
# generator = Roadie::UrlGenerator.new host: "foo.com", scheme: "https"
|
45
|
+
# generator.generate_url("bar.html") # => "https://foo.com/bar.html"
|
46
|
+
# generator.generate_url("/bar.html") # => "https://foo.com/bar.html"
|
47
|
+
# generator.generate_url("") # => "https://foo.com"
|
48
|
+
#
|
49
|
+
# @example Conversions with a base
|
50
|
+
# generator = Roadie::UrlGenerator.new host: "foo.com", scheme: "https"
|
51
|
+
# generator.generate_url("../images/logo.png", "/css") # => "https://foo.com/images/logo.png"
|
52
|
+
# generator.generate_url("../images/logo.png", "/assets/css") # => "https://foo.com/assets/images/logo.png"
|
53
|
+
#
|
54
|
+
# @param [String] base The base which the relative path comes from
|
55
|
+
# @return [String] an absolute URL
|
56
|
+
def generate_url(path, base = "/")
|
57
|
+
return root_uri.to_s if path.nil? or path.empty?
|
58
|
+
return path if path_is_absolute?(path)
|
59
|
+
|
60
|
+
combine_segments(root_uri, base, path).to_s
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
attr_reader :root_uri
|
65
|
+
|
66
|
+
def build_root_uri
|
67
|
+
path = make_absolute url_options[:path]
|
68
|
+
port = parse_port url_options[:port]
|
69
|
+
scheme = normalize_scheme(url_options[:scheme] || url_options[:protocol])
|
70
|
+
URI::Generic.build(scheme: scheme, host: url_options[:host], port: port, path: path)
|
71
|
+
end
|
72
|
+
|
73
|
+
def combine_segments(root, base, path)
|
74
|
+
new_path = apply_base(base, path)
|
75
|
+
if root.path
|
76
|
+
new_path = File.join(root.path, new_path)
|
77
|
+
end
|
78
|
+
root.merge(new_path)
|
79
|
+
end
|
80
|
+
|
81
|
+
def apply_base(base, path)
|
82
|
+
if path[0] == "/"
|
83
|
+
path
|
84
|
+
else
|
85
|
+
File.join(base, path)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Strip :// from any scheme, if present
|
90
|
+
def normalize_scheme(scheme)
|
91
|
+
return 'http' unless scheme
|
92
|
+
scheme.to_s[/^\w+/]
|
93
|
+
end
|
94
|
+
|
95
|
+
def parse_port(port)
|
96
|
+
(port ? port.to_i : port)
|
97
|
+
end
|
98
|
+
|
99
|
+
def make_absolute(path)
|
100
|
+
if path.nil? || path[0] == "/"
|
101
|
+
path
|
102
|
+
else
|
103
|
+
"/#{path}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def path_is_absolute?(path)
|
108
|
+
not parse_path(path).relative?
|
109
|
+
end
|
110
|
+
|
111
|
+
def parse_path(path)
|
112
|
+
URI.parse(path)
|
113
|
+
rescue URI::InvalidURIError => error
|
114
|
+
raise InvalidUrlPath.new(path, error)
|
115
|
+
end
|
116
|
+
|
117
|
+
VALID_OPTIONS = Set[:host, :port, :path, :protocol, :scheme].freeze
|
118
|
+
|
119
|
+
def validate_options(options)
|
120
|
+
keys = Set.new(options.keys)
|
121
|
+
unless keys.subset? VALID_OPTIONS
|
122
|
+
raise ArgumentError, "Passed invalid options: #{(keys - VALID_OPTIONS).to_a}, valid options are: #{VALID_OPTIONS.to_a}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Roadie
|
2
|
+
# @api private
|
3
|
+
#
|
4
|
+
# Class that rewrites URLs in the DOM.
|
5
|
+
class UrlRewriter
|
6
|
+
# @param [UrlGenerator] generator
|
7
|
+
def initialize(generator)
|
8
|
+
@generator = generator
|
9
|
+
end
|
10
|
+
|
11
|
+
# Mutates the passed DOM tree, rewriting certain element's attributes.
|
12
|
+
#
|
13
|
+
# This will make all a[href] and img[src] into absolute URLs, as well as
|
14
|
+
# all "url()" directives inside style-attributes.
|
15
|
+
#
|
16
|
+
# [nil] is returned so no one can misunderstand that this method mutates
|
17
|
+
# the passed instance.
|
18
|
+
#
|
19
|
+
# @param [Nokogiri::HTML::Document] dom
|
20
|
+
# @return [nil] DOM tree is mutated
|
21
|
+
def transform_dom(dom)
|
22
|
+
# Use only a single loop to do this
|
23
|
+
dom.css("a[href], img[src], *[style]").each do |element|
|
24
|
+
transform_element_style element if element.has_attribute?('style')
|
25
|
+
transform_element element
|
26
|
+
end
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
# Mutates passed CSS, rewriting url() directives.
|
31
|
+
#
|
32
|
+
# This will make all URLs inside url() absolute.
|
33
|
+
#
|
34
|
+
# [nil] is returned so no one can misunderstand that this method mutates
|
35
|
+
# the passed string.
|
36
|
+
#
|
37
|
+
# @param [String] css the css to mutate
|
38
|
+
# @return [nil] css is mutated
|
39
|
+
def transform_css(css)
|
40
|
+
css.gsub!(CSS_URL_REGEXP) do
|
41
|
+
matches = Regexp.last_match
|
42
|
+
"url(#{matches[:quote]}#{generate_url(matches[:url])}#{matches[:quote]})"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def generate_url(*args) @generator.generate_url(*args) end
|
48
|
+
|
49
|
+
# Regexp matching all the url() declarations in CSS
|
50
|
+
#
|
51
|
+
# It matches without any quotes and with both single and double quotes
|
52
|
+
# inside the parenthesis. There's much room for improvement, of course.
|
53
|
+
CSS_URL_REGEXP = %r{
|
54
|
+
url\(
|
55
|
+
(?<quote>
|
56
|
+
(?:["']|%22)? # Optional opening quote
|
57
|
+
)
|
58
|
+
(?<url> # The URL.
|
59
|
+
# We match URLs with parenthesis inside it here,
|
60
|
+
# so url(foo(bar)baz) will match correctly.
|
61
|
+
[^(]* # Text leading up to before opening parens
|
62
|
+
(?:\([^)]*\))* # Texts containing parens pairs
|
63
|
+
[^(]+ # Texts without parens - required
|
64
|
+
)
|
65
|
+
\k'quote' # Closing quote
|
66
|
+
\)
|
67
|
+
}x
|
68
|
+
|
69
|
+
def transform_element(element)
|
70
|
+
case element.name
|
71
|
+
when "a" then element["href"] = generate_url element["href"]
|
72
|
+
when "img" then element["src"] = generate_url element["src"]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def transform_element_style(element)
|
77
|
+
# We need to use a setter for Nokogiri to detect the string mutation.
|
78
|
+
# If nokogiri used "dumber" data structures, this would all be redundant.
|
79
|
+
css = element["style"]
|
80
|
+
transform_css css
|
81
|
+
element["style"] = css
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/lib/roadie/version.rb
CHANGED
data/roadie.gemspec
CHANGED
@@ -11,20 +11,17 @@ Gem::Specification.new do |s|
|
|
11
11
|
s.authors = ['Magnus Bergmark']
|
12
12
|
s.email = ['magnus.bergmark@gmail.com']
|
13
13
|
s.homepage = 'http://github.com/Mange/roadie'
|
14
|
-
s.summary = %q{Making HTML emails comfortable for the
|
15
|
-
s.description = %q{Roadie tries to make sending HTML emails a little less painful
|
14
|
+
s.summary = %q{Making HTML emails comfortable for the Ruby rockstars}
|
15
|
+
s.description = %q{Roadie tries to make sending HTML emails a little less painful by inlining stylesheets and rewriting relative URLs for you.}
|
16
|
+
s.license = "MIT"
|
16
17
|
|
17
|
-
s.
|
18
|
-
s.add_dependency 'css_parser', '~> 1.3.4'
|
19
|
-
s.add_dependency 'actionmailer', '> 3.0.0', '< 5.0.0'
|
20
|
-
s.add_dependency 'sprockets'
|
18
|
+
s.required_ruby_version = ">= 1.9"
|
21
19
|
|
22
|
-
s.
|
23
|
-
s.
|
24
|
-
s.add_development_dependency 'rspec'
|
25
|
-
s.add_development_dependency 'rspec-rails'
|
20
|
+
s.add_dependency 'nokogiri', '~> 1.6.0'
|
21
|
+
s.add_dependency 'css_parser', '~> 1.3.4'
|
26
22
|
|
27
|
-
s.add_development_dependency '
|
23
|
+
s.add_development_dependency 'rspec', '~> 3.0'
|
24
|
+
s.add_development_dependency 'rspec-collection_matchers', '~> 1.0'
|
28
25
|
|
29
26
|
s.extra_rdoc_files = %w[README.md Changelog.md]
|
30
27
|
s.require_paths = %w[lib]
|
@@ -0,0 +1 @@
|
|
1
|
+
em { font-size: 200%; }
|
@@ -0,0 +1 @@
|
|
1
|
+
body { color: green; }
|
data/spec/integration_spec.rb
CHANGED
@@ -1,110 +1,140 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
append_view_path FIXTURES_PATH.join('views')
|
8
|
-
|
9
|
-
# Needed for correct path lookup
|
10
|
-
self.mailer_name = "integration_mailer"
|
11
|
-
|
12
|
-
def notification(to, reason)
|
13
|
-
@reason = reason
|
14
|
-
mail(:subject => 'Notification for you', :to => to) { |format| format.html; format.text }
|
15
|
-
end
|
16
|
-
|
17
|
-
def marketing(to)
|
18
|
-
headers('X-Spam' => 'No way! Trust us!')
|
19
|
-
mail(:subject => 'Buy cheap v1agra', :to => to)
|
20
|
-
end
|
21
|
-
|
22
|
-
def url_options
|
23
|
-
# This allows apps to calculate any options on a per-email basis
|
24
|
-
super.merge(:protocol => 'https')
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def parse_html_in_email(mail)
|
29
|
-
Nokogiri::HTML.parse mail.html_part.body.decoded
|
30
|
-
end
|
31
|
-
|
32
|
-
before(:each) do
|
33
|
-
change_default_url_options(:host => 'example.app.org')
|
34
|
-
mailer.delivery_method = :test
|
35
|
-
end
|
36
|
-
|
37
|
-
it "inlines styles for an email" do
|
38
|
-
email = mailer.notification('doe@example.com', 'your quota limit has been reached')
|
39
|
-
|
40
|
-
email.to.should == ['doe@example.com']
|
41
|
-
email.from.should == ['john@example.com']
|
42
|
-
email.should have(2).parts
|
43
|
-
|
44
|
-
email.text_part.body.decoded.should_not match(/<.*>/)
|
45
|
-
|
46
|
-
html = email.html_part.body.decoded
|
47
|
-
html.should include '<!DOCTYPE'
|
48
|
-
html.should include '<head'
|
49
|
-
|
50
|
-
document = parse_html_in_email(email)
|
51
|
-
document.should have_selector('body #message h1')
|
52
|
-
document.should have_styling('background' => 'url(https://example.app.org/images/dots.png) repeat-x').at_selector('body')
|
53
|
-
document.should have_selector('strong[contains("quota")]')
|
54
|
-
|
55
|
-
# If we deliver mails we can catch weird problems with headers being invalid
|
56
|
-
email.deliver
|
57
|
-
end
|
58
|
-
|
59
|
-
it "does not add headers for the roadie options" do
|
60
|
-
email = mailer.notification('doe@example.com', 'no berries left in chest')
|
61
|
-
email.header.fields.map(&:name).should_not include('css')
|
62
|
-
end
|
63
|
-
|
64
|
-
it "keeps custom headers in place" do
|
65
|
-
email = mailer.marketing('everyone@inter.net')
|
66
|
-
email.header['X-Spam'].should be_present
|
67
|
-
end
|
3
|
+
describe "Roadie functionality" do
|
4
|
+
def parse_html(html)
|
5
|
+
Nokogiri::HTML.parse(html)
|
6
|
+
end
|
68
7
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
8
|
+
it "adds missing structure" do
|
9
|
+
html = "<h1>Hello world!</h1>".encode("Shift_JIS")
|
10
|
+
document = Roadie::Document.new(html)
|
11
|
+
result = document.transform
|
12
|
+
|
13
|
+
unless defined?(JRuby)
|
14
|
+
# JRuby has a bug that makes DTD manipulation impossible
|
15
|
+
# See Nokogiri bugs #984 and #985
|
16
|
+
# https://github.com/sparklemotion/nokogiri/issues/984
|
17
|
+
# https://github.com/sparklemotion/nokogiri/issues/985
|
18
|
+
expect(result).to include("<!DOCTYPE html>")
|
76
19
|
end
|
77
20
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
email = mailer.notification('doe@example.com', 'your quota limit has been reached')
|
21
|
+
expect(result).to include("<html>")
|
22
|
+
expect(result).to include("<head>")
|
23
|
+
expect(result).to include("<body>")
|
82
24
|
|
83
|
-
|
25
|
+
expect(result).to include("<meta")
|
26
|
+
expect(result).to include("text/html; charset=Shift_JIS")
|
27
|
+
end
|
84
28
|
|
85
|
-
|
86
|
-
|
87
|
-
|
29
|
+
it "inlines given css" do
|
30
|
+
document = Roadie::Document.new <<-HTML
|
31
|
+
<html>
|
32
|
+
<head>
|
33
|
+
<title>Hello world!</title>
|
34
|
+
</head>
|
35
|
+
<body>
|
36
|
+
<h1>Hello world!</h1>
|
37
|
+
<p>Check out these <em>awesome</em> prices!</p>
|
38
|
+
</body>
|
39
|
+
</html>
|
40
|
+
HTML
|
41
|
+
document.add_css <<-CSS
|
42
|
+
em { color: red; }
|
43
|
+
h1 { text-align: center; }
|
44
|
+
CSS
|
45
|
+
|
46
|
+
result = parse_html document.transform
|
47
|
+
expect(result).to have_styling('text-align' => 'center').at_selector('h1')
|
48
|
+
expect(result).to have_styling('color' => 'red').at_selector('p > em')
|
49
|
+
end
|
88
50
|
|
89
|
-
|
90
|
-
|
91
|
-
html
|
51
|
+
it "inlines css from disk" do
|
52
|
+
document = Roadie::Document.new <<-HTML
|
53
|
+
<!DOCTYPE html>
|
54
|
+
<html>
|
55
|
+
<head>
|
56
|
+
<title>Hello world!</title>
|
57
|
+
<link rel="stylesheet" href="/spec/fixtures/big_em.css">
|
58
|
+
</head>
|
59
|
+
<body>
|
60
|
+
<h1>Hello world!</h1>
|
61
|
+
<p>Check out these <em>awesome</em> prices!</p>
|
62
|
+
</body>
|
63
|
+
</html>
|
64
|
+
HTML
|
65
|
+
|
66
|
+
result = parse_html document.transform
|
67
|
+
expect(result).to have_styling('font-size' => '200%').at_selector('p > em')
|
68
|
+
end
|
92
69
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
70
|
+
it "crashes when stylesheets cannot be found, unless using NullProvider" do
|
71
|
+
document = Roadie::Document.new <<-HTML
|
72
|
+
<!DOCTYPE html>
|
73
|
+
<html>
|
74
|
+
<head>
|
75
|
+
<link rel="stylesheet" href="/spec/fixtures/does_not_exist.css">
|
76
|
+
</head>
|
77
|
+
<body>
|
78
|
+
</body>
|
79
|
+
</html>
|
80
|
+
HTML
|
81
|
+
|
82
|
+
expect { document.transform }.to raise_error(Roadie::CssNotFound, /does_not_exist\.css/)
|
83
|
+
|
84
|
+
document.asset_providers << Roadie::NullProvider.new
|
85
|
+
expect { document.transform }.to_not raise_error
|
97
86
|
end
|
98
87
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
88
|
+
it "makes URLs absolute" do
|
89
|
+
document = Roadie::Document.new <<-HTML
|
90
|
+
<!DOCTYPE html>
|
91
|
+
<html>
|
92
|
+
<head>
|
93
|
+
<style>
|
94
|
+
body { background: url("/assets/bg-abcdef1234567890.png"); }
|
95
|
+
</style>
|
96
|
+
<link rel="stylesheet" href="/style.css">
|
97
|
+
</head>
|
98
|
+
<body>
|
99
|
+
<a href="/about_us"><img src="/assets/about_us-abcdef1234567890.png" alt="About us"></a>
|
100
|
+
</body>
|
101
|
+
</html>
|
102
|
+
HTML
|
103
|
+
|
104
|
+
document.asset_providers = TestProvider.new(
|
105
|
+
"/style.css" => "a { background: url(/assets/link-abcdef1234567890.png); }"
|
106
|
+
)
|
107
|
+
document.url_options = {host: "myapp.com", scheme: "https", path: "rails/app/"}
|
108
|
+
result = parse_html document.transform
|
109
|
+
|
110
|
+
expect(result.at_css("a")["href"]).to eq("https://myapp.com/rails/app/about_us")
|
111
|
+
|
112
|
+
expect(result.at_css("img")["src"]).to eq("https://myapp.com/rails/app/assets/about_us-abcdef1234567890.png")
|
113
|
+
|
114
|
+
expect(result).to have_styling(
|
115
|
+
"background" => 'url("https://myapp.com/rails/app/assets/bg-abcdef1234567890.png")'
|
116
|
+
).at_selector("body")
|
117
|
+
|
118
|
+
expect(result).to have_styling(
|
119
|
+
"background" => 'url(https://myapp.com/rails/app/assets/link-abcdef1234567890.png)'
|
120
|
+
).at_selector("a")
|
103
121
|
end
|
104
122
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
123
|
+
it "allows custom callbacks during inlining" do
|
124
|
+
document = Roadie::Document.new <<-HTML
|
125
|
+
<!DOCTYPE html>
|
126
|
+
<html>
|
127
|
+
<body>
|
128
|
+
<span>Hello world</span>
|
129
|
+
</body>
|
130
|
+
</html>
|
131
|
+
HTML
|
132
|
+
|
133
|
+
document.before_transformation = proc { |dom| dom.at_css("body")["class"] = "roadie" }
|
134
|
+
document.after_transformation = proc { |dom| dom.at_css("span").remove }
|
135
|
+
|
136
|
+
result = parse_html document.transform
|
137
|
+
expect(result.at_css("body")["class"]).to eq("roadie")
|
138
|
+
expect(result.at_css("span")).to be_nil
|
109
139
|
end
|
110
140
|
end
|