roadie 2.4.3 → 3.0.0.pre1
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 +9 -14
- data/.yardopts +1 -1
- data/Changelog.md +22 -10
- data/Gemfile +3 -0
- data/Guardfile +11 -1
- data/README.md +165 -163
- data/Rakefile +2 -19
- data/lib/roadie.rb +14 -69
- 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 +21 -62
- data/lib/roadie/inliner.rb +71 -218
- 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 +67 -0
- data/lib/roadie/rspec.rb +1 -0
- data/lib/roadie/rspec/asset_provider.rb +49 -0
- data/lib/roadie/selector.rb +42 -18
- data/lib/roadie/style_block.rb +33 -0
- data/lib/roadie/style_properties.rb +29 -0
- data/lib/roadie/style_property.rb +93 -0
- data/lib/roadie/stylesheet.rb +65 -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 +6 -10
- 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 +16 -0
- data/spec/lib/roadie/document_spec.rb +123 -0
- data/spec/lib/roadie/filesystem_provider_spec.rb +25 -72
- 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 +81 -0
- data/spec/lib/roadie/selector_spec.rb +7 -5
- data/spec/lib/roadie/style_block_spec.rb +35 -0
- data/spec/lib/roadie/style_properties_spec.rb +61 -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 +120 -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 +5 -60
- data/spec/support/have_node_matcher.rb +2 -2
- data/spec/support/have_selector_matcher.rb +1 -1
- data/spec/support/have_styling_matcher.rb +48 -14
- data/spec/support/test_provider.rb +13 -0
- metadata +73 -177
- 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,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,16 @@ 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.required_ruby_version = ">= 1.9"
|
19
|
+
|
20
|
+
s.add_dependency 'nokogiri', '~> 1.6.0'
|
18
21
|
s.add_dependency 'css_parser', '~> 1.3.4'
|
19
|
-
s.add_dependency 'actionmailer', '> 3.0.0', '< 5.0.0'
|
20
|
-
s.add_dependency 'sprockets'
|
21
22
|
|
22
|
-
s.add_development_dependency 'rake'
|
23
|
-
s.add_development_dependency 'rails'
|
24
23
|
s.add_development_dependency 'rspec'
|
25
|
-
s.add_development_dependency 'rspec-rails'
|
26
|
-
|
27
|
-
s.add_development_dependency 'appraisal'
|
28
24
|
|
29
25
|
s.extra_rdoc_files = %w[README.md Changelog.md]
|
30
26
|
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
|
+
result.should 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
|
+
result.should include("<html>")
|
22
|
+
result.should include("<head>")
|
23
|
+
result.should include("<body>")
|
82
24
|
|
83
|
-
|
25
|
+
result.should include("<meta")
|
26
|
+
result.should 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
|
+
result.should have_styling('text-align' => 'center').at_selector('h1')
|
48
|
+
result.should 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
|
+
result.should 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
|
+
result.at_css("a")["href"].should == "https://myapp.com/rails/app/about_us"
|
111
|
+
|
112
|
+
result.at_css("img")["src"].should == "https://myapp.com/rails/app/assets/about_us-abcdef1234567890.png"
|
113
|
+
|
114
|
+
result.should have_styling(
|
115
|
+
"background" => 'url("https://myapp.com/rails/app/assets/bg-abcdef1234567890.png")'
|
116
|
+
).at_selector("body")
|
117
|
+
|
118
|
+
result.should 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
|
+
result.at_css("body")["class"].should == "roadie"
|
138
|
+
result.at_css("span").should be_nil
|
109
139
|
end
|
110
140
|
end
|