premailer-rails 1.8.0 → 1.11.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +4 -0
  3. data/.travis.yml +10 -15
  4. data/CHANGELOG.md +74 -0
  5. data/Gemfile +10 -4
  6. data/README.md +49 -39
  7. data/VERSION +1 -1
  8. data/example/Gemfile +1 -3
  9. data/example/bin/rails +4 -0
  10. data/example/config.ru +1 -1
  11. data/example/config/application.rb +1 -0
  12. data/example/config/boot.rb +1 -2
  13. data/example/config/environments/development.rb +4 -0
  14. data/example/config/environments/production.rb +9 -0
  15. data/example/config/initializers/assets.rb +2 -0
  16. data/lib/premailer/rails.rb +4 -2
  17. data/lib/premailer/rails/css_helper.rb +45 -16
  18. data/lib/premailer/rails/css_loaders.rb +0 -1
  19. data/lib/premailer/rails/css_loaders/asset_pipeline_loader.rb +19 -10
  20. data/lib/premailer/rails/css_loaders/file_system_loader.rb +24 -2
  21. data/lib/premailer/rails/css_loaders/network_loader.rb +15 -8
  22. data/lib/premailer/rails/customized_premailer.rb +6 -6
  23. data/lib/premailer/rails/hook.rb +16 -8
  24. data/premailer-rails.gemspec +4 -4
  25. data/spec/integration/css_helper_spec.rb +92 -42
  26. data/spec/integration/delivery_spec.rb +13 -0
  27. data/spec/integration/hook_spec.rb +53 -1
  28. data/spec/rails_app/app/assets/config/manifest.js +3 -0
  29. data/spec/rails_app/app/assets/stylesheets/application.css +3 -0
  30. data/spec/rails_app/app/mailers/application_mailer.rb +4 -0
  31. data/spec/rails_app/app/mailers/welcome_mailer.rb +6 -0
  32. data/spec/rails_app/app/views/layouts/mailer.html.erb +11 -0
  33. data/spec/rails_app/app/views/welcome_mailer/welcome_email.html.erb +1 -0
  34. data/spec/rails_app/config.ru +5 -0
  35. data/spec/rails_app/config/application.rb +13 -0
  36. data/spec/rails_app/config/boot.rb +5 -0
  37. data/spec/rails_app/config/environment.rb +2 -0
  38. data/spec/rails_app/config/environments/test.rb +10 -0
  39. data/spec/rails_app/config/initializers/assets.rb +1 -0
  40. data/spec/rails_app/config/routes.rb +3 -0
  41. data/spec/spec_helper.rb +3 -4
  42. data/spec/support/fixtures/html.rb +8 -4
  43. data/spec/support/fixtures/message.rb +56 -0
  44. data/spec/unit/css_loaders/asset_pipeline_loader_spec.rb +19 -4
  45. data/spec/unit/css_loaders/file_system_loader_spec.rb +37 -0
  46. data/spec/unit/css_loaders/network_loader_spec.rb +58 -0
  47. data/spec/unit/customized_premailer_spec.rb +32 -40
  48. metadata +63 -34
  49. data/lib/premailer/rails/css_loaders/cache_loader.rb +0 -19
  50. data/spec/integration/hook_registration_spec.rb +0 -11
  51. data/spec/support/stubs/action_mailer.rb +0 -5
  52. data/spec/support/stubs/rails.rb +0 -51
@@ -1,6 +1,5 @@
1
1
  require 'uri'
2
2
 
3
- require 'premailer/rails/css_loaders/cache_loader'
4
3
  require 'premailer/rails/css_loaders/file_system_loader'
5
4
  require 'premailer/rails/css_loaders/asset_pipeline_loader'
6
5
  require 'premailer/rails/css_loaders/network_loader'
@@ -5,21 +5,30 @@ class Premailer
5
5
  extend self
6
6
 
7
7
  def load(url)
8
- if asset_pipeline_present?
9
- file = file_name(url)
10
- asset = ::Rails.application.assets.find_asset(file)
11
- asset.to_s if asset
12
- end
13
- end
8
+ return unless asset_pipeline_present?
14
9
 
15
- def asset_pipeline_present?
16
- defined?(::Rails) and ::Rails.application.respond_to?(:assets)
10
+ file = file_name(url)
11
+ ::Rails.application.assets_manifest.find_sources(file).first
12
+ rescue Errno::ENOENT, TypeError => _error
17
13
  end
18
14
 
19
15
  def file_name(url)
16
+ prefix = [
17
+ ::Rails.configuration.relative_url_root,
18
+ ::Rails.configuration.assets.prefix,
19
+ '/'
20
+ ].join
20
21
  URI(url).path
21
- .sub("#{::Rails.configuration.assets.prefix}/", '')
22
- .sub(/-\h{32}\.css$/, '.css')
22
+ .sub(/\A#{prefix}/, '')
23
+ .sub(/-(\h{32}|\h{64})\.css\z/, '.css')
24
+ end
25
+
26
+ def asset_pipeline_present?
27
+ defined?(::Rails) &&
28
+ ::Rails.respond_to?(:application) &&
29
+ ::Rails.application &&
30
+ ::Rails.application.respond_to?(:assets_manifest) &&
31
+ ::Rails.application.assets_manifest
23
32
  end
24
33
  end
25
34
  end
@@ -5,9 +5,31 @@ class Premailer
5
5
  extend self
6
6
 
7
7
  def load(url)
8
+ file = file_name(url)
9
+ File.read(file) if File.file?(file)
10
+ end
11
+
12
+ def file_name(url)
8
13
  path = URI(url).path
9
- file_path = "public#{path}"
10
- File.read(file_path) if File.exist?(file_path)
14
+ if relative_url_root
15
+ path = path.sub(/\A#{relative_url_root.chomp('/')}/, '')
16
+ end
17
+ asset_filename(path)
18
+ end
19
+
20
+ def asset_filename(filename)
21
+ if defined?(::Rails) && ::Rails.respond_to?(:root)
22
+ File.join(::Rails.root, 'public', filename)
23
+ else
24
+ File.join('public', filename)
25
+ end
26
+ end
27
+
28
+ def relative_url_root
29
+ defined?(::Rails) &&
30
+ ::Rails.respond_to?(:configuration) &&
31
+ ::Rails.configuration.respond_to?(:relative_url_root) &&
32
+ ::Rails.configuration.relative_url_root
11
33
  end
12
34
  end
13
35
  end
@@ -12,19 +12,26 @@ class Premailer
12
12
  def uri_for_url(url)
13
13
  uri = URI(url)
14
14
 
15
- if not valid_uri?(uri) and defined?(::Rails)
16
- scheme, host =
17
- ::Rails.configuration.action_controller.asset_host.split(%r{:?//})
15
+ if uri.host.present?
16
+ return uri if uri.scheme.present?
17
+ URI("http:#{uri}")
18
+ elsif asset_host_present?
19
+ scheme, host = asset_host(url).split(%r{:?//})
20
+ scheme, host = host, scheme if host.nil?
18
21
  scheme = 'http' if scheme.blank?
19
- uri.scheme ||= scheme
20
- uri.host ||= host
22
+ path = url
23
+ URI(File.join("#{scheme}://#{host}", path))
21
24
  end
25
+ end
22
26
 
23
- uri if valid_uri?(uri)
27
+ def asset_host_present?
28
+ ::Rails.respond_to?(:configuration) &&
29
+ ::Rails.configuration.action_controller.asset_host.present?
24
30
  end
25
31
 
26
- def valid_uri?(uri)
27
- uri.host.present? && uri.scheme.present?
32
+ def asset_host(url)
33
+ config = ::Rails.configuration.action_controller.asset_host
34
+ config.respond_to?(:call) ? config.call(url) : config
28
35
  end
29
36
  end
30
37
  end
@@ -4,16 +4,16 @@ class Premailer
4
4
  def initialize(html)
5
5
  # In order to pass the CSS as string to super it is necessary to access
6
6
  # the parsed HTML beforehand. To do so, the adapter needs to be
7
- # initialized. The ::Premailer::Adaptor handles the discovery of a
8
- # suitable adaptor (Nokogiri or Hpricot). To make load_html work, an
9
- # adaptor needs to be included and @options[:with_html_string] needs to
10
- # be set. For further information, refer to ::Premailer#initialize.
7
+ # initialized. The ::Premailer::Adapter handles the discovery of
8
+ # a suitable adapter. To make load_html work, an adapter needs to be
9
+ # included and @options[:with_html_string] needs to be set. For further
10
+ # information, refer to ::Premailer#initialize.
11
11
  @options = Rails.config.merge(with_html_string: true)
12
12
  Premailer.send(:include, Adapter.find(Adapter.use))
13
13
  doc = load_html(html)
14
-
15
14
  options = @options.merge(css_string: CSSHelper.css_for_doc(doc))
16
- super(html, options)
15
+
16
+ super(doc.to_s, options)
17
17
  end
18
18
  end
19
19
  end
@@ -42,7 +42,7 @@ class Premailer
42
42
  # Returns true if the message itself has a content type of text/html, thus
43
43
  # it does not contain other parts such as alternatives and attachments.
44
44
  def pure_html_message?
45
- message.content_type.include?('text/html')
45
+ message.content_type && message.content_type.include?('text/html')
46
46
  end
47
47
 
48
48
  def generate_html_part_replacement
@@ -68,17 +68,25 @@ class Premailer
68
68
  def generate_html_part
69
69
  # Make sure that the text part is generated first. Otherwise the text
70
70
  # can end up containing CSS rules.
71
- generate_text_part if generate_text_part?
71
+ generate_text_part if generate_text_part?
72
72
 
73
- Mail::Part.new(
74
- content_type: "text/html; charset=#{html_part.charset}",
75
- body: premailer.to_inline_css)
73
+ part = html_part
74
+ html = premailer.to_inline_css
75
+ Mail::Part.new do
76
+ content_type "text/html; charset=#{html.encoding}"
77
+ body html
78
+ end
76
79
  end
77
80
 
78
81
  def generate_text_part
79
- @text_part ||= Mail::Part.new(
80
- content_type: "text/plain; charset=#{html_part.charset}",
81
- body: premailer.to_plain_text)
82
+ @text_part ||= begin
83
+ part = html_part
84
+ text = premailer.to_plain_text
85
+ Mail::Part.new do
86
+ content_type "text/plain; charset=#{text.encoding}"
87
+ body text
88
+ end
89
+ end
82
90
  end
83
91
 
84
92
  def premailer
@@ -6,6 +6,7 @@ Gem::Specification.new do |s|
6
6
  s.name = "premailer-rails"
7
7
  s.version = Premailer::Rails::VERSION
8
8
  s.platform = Gem::Platform::RUBY
9
+ s.license = 'MIT'
9
10
  s.authors = ["Philipe Fatio"]
10
11
  s.email = ["philipe.fatio@gmail.com"]
11
12
  s.homepage = "https://github.com/fphilipe/premailer-rails"
@@ -16,15 +17,14 @@ Gem::Specification.new do |s|
16
17
  premailer will inline the included CSS.}
17
18
 
18
19
  s.files = `git ls-files`.split("\n")
19
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ s.test_files = `git ls-files -- {example,spec}/*`.split("\n")
20
21
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
22
  s.require_paths = ["lib"]
22
23
 
23
24
  s.add_dependency 'premailer', '~> 1.7', '>= 1.7.9'
24
- s.add_dependency 'actionmailer', '>= 3', '< 5'
25
+ s.add_dependency 'actionmailer', '>= 3'
25
26
 
26
- s.add_development_dependency 'rspec', '~> 3.0.0'
27
+ s.add_development_dependency 'rspec', '~> 3.3'
27
28
  s.add_development_dependency 'nokogiri'
28
- s.add_development_dependency 'hpricot' unless RUBY_PLATFORM == 'java'
29
29
  s.add_development_dependency 'coveralls' if RUBY_ENGINE == 'ruby'
30
30
  end
@@ -3,11 +3,11 @@ require 'spec_helper'
3
3
  describe Premailer::Rails::CSSHelper do
4
4
  # Reset the CSS cache:
5
5
  after do
6
- Premailer::Rails::CSSHelper.send(:instance_variable_set, '@cache', {})
6
+ Premailer::Rails::CSSHelper.cache = {}
7
7
  end
8
8
 
9
- def load_css(path)
10
- Premailer::Rails::CSSHelper.send(:load_css, path)
9
+ def css_for_url(path)
10
+ Premailer::Rails::CSSHelper.css_for_url(path)
11
11
  end
12
12
 
13
13
  def css_for_doc(doc)
@@ -15,7 +15,8 @@ describe Premailer::Rails::CSSHelper do
15
15
  end
16
16
 
17
17
  def expect_file(path, content='file content')
18
- allow(File).to receive(:exist?).with(path).and_return(true)
18
+ path = "#{Rails.root}/#{path}"
19
+ allow(File).to receive(:file?).with(path).and_return(true)
19
20
  expect(File).to receive(:read).with(path).and_return(content)
20
21
  end
21
22
 
@@ -28,63 +29,79 @@ describe Premailer::Rails::CSSHelper do
28
29
 
29
30
  it 'returns the content of both files concatenated' do
30
31
  allow(Premailer::Rails::CSSHelper).to \
31
- receive(:load_css)
32
+ receive(:css_for_url)
32
33
  .with('http://example.com/stylesheets/base.css')
33
34
  .and_return('content of base.css')
34
35
  allow(Premailer::Rails::CSSHelper).to \
35
- receive(:load_css)
36
+ receive(:css_for_url)
36
37
  .with('http://example.com/stylesheets/font.css')
37
38
  .and_return('content of font.css')
38
39
 
39
40
  expect(css_for_doc(doc)).to eq("content of base.css\ncontent of font.css")
40
41
  end
41
42
  end
43
+
44
+ context 'when HTML contains ignored links' do
45
+ let(:files) { ['ignore.css', 'data-premailer' => 'ignore'] }
46
+
47
+ it 'ignores links' do
48
+ expect(Premailer::Rails::CSSHelper).to_not receive(:css_for_url)
49
+ css_for_doc(doc)
50
+ end
51
+ end
42
52
  end
43
53
 
44
- describe '#load_css' do
54
+ describe '#css_for_url' do
45
55
  context 'when path is a url' do
46
56
  it 'loads the CSS at the local path' do
47
57
  expect_file('public/stylesheets/base.css')
48
58
 
49
- load_css('http://example.com/stylesheets/base.css?test')
59
+ css_for_url('http://example.com/stylesheets/base.css?test')
50
60
  end
51
61
  end
52
62
 
53
63
  context 'when path is a relative url' do
54
64
  it 'loads the CSS at the local path' do
55
65
  expect_file('public/stylesheets/base.css')
56
- load_css('/stylesheets/base.css?test')
66
+ css_for_url('/stylesheets/base.css?test')
57
67
  end
58
68
  end
59
69
 
60
- context 'when file is cached' do
61
- it 'returns the cached value' do
62
- cache =
63
- Premailer::Rails::CSSHelper.send(:instance_variable_get, '@cache')
64
- cache['http://example.com/stylesheets/base.css'] = 'content of base.css'
70
+ context 'when cache is enabled' do
71
+ before do
72
+ allow(Premailer::Rails::CSSHelper).to receive(:cache_enabled?).and_return(true)
73
+ end
65
74
 
66
- expect(load_css('http://example.com/stylesheets/base.css')).to \
67
- eq('content of base.css')
75
+ context 'when file is cached' do
76
+ it 'returns the cached value' do
77
+ Premailer::Rails::CSSHelper.cache['http://example.com/stylesheets/base.css'] = 'content of base.css'
78
+
79
+ expect(css_for_url('http://example.com/stylesheets/base.css')).to \
80
+ eq('content of base.css')
81
+ end
68
82
  end
69
83
  end
70
84
 
71
- context 'when in development mode' do
85
+ context 'when cache is disabled' do
86
+ before do
87
+ allow(Premailer::Rails::CSSHelper).to receive(:cache_enabled?).and_return(false)
88
+ end
89
+
72
90
  it 'does not return cached values' do
73
- cache =
74
- Premailer::Rails::CSSHelper.send(:instance_variable_get, '@cache')
75
- cache['http://example.com/stylesheets/base.css'] =
76
- 'cached content of base.css'
91
+ Premailer::Rails::CSSHelper.cache['http://example.com/stylesheets/base.css'] = 'cached content'
77
92
  content = 'new content of base.css'
78
93
  expect_file('public/stylesheets/base.css', content)
79
- allow(Rails.env).to receive(:development?).and_return(true)
80
94
 
81
- expect(load_css('http://example.com/stylesheets/base.css')).to eq(content)
95
+ expect(css_for_url('http://example.com/stylesheets/base.css')).to eq(content)
82
96
  end
83
97
  end
84
98
 
85
99
  context 'when Rails asset pipeline is used' do
86
100
  before do
87
- allow(Rails.configuration).to receive(:assets).and_return(double(prefix: '/assets'))
101
+ allow(Rails.configuration)
102
+ .to receive(:assets).and_return(double(prefix: '/assets'))
103
+ allow(Rails.configuration)
104
+ .to receive(:relative_url_root).and_return(nil)
88
105
  end
89
106
 
90
107
  context 'and a precompiled file exists' do
@@ -92,27 +109,61 @@ describe Premailer::Rails::CSSHelper do
92
109
  path = '/assets/email-digest.css'
93
110
  content = 'read from file'
94
111
  expect_file("public#{path}", content)
95
- expect(load_css(path)).to eq(content)
112
+ expect(css_for_url(path)).to eq(content)
113
+ end
114
+ end
115
+
116
+ context "when find_sources raises TypeError" do
117
+ let(:response) { 'content of base.css' }
118
+ let(:uri) { URI('http://example.com/assets/base.css') }
119
+
120
+ it "falls back to Net::HTTP" do
121
+ expect(Rails.application.assets_manifest).to \
122
+ receive(:find_sources)
123
+ .with('base.css')
124
+ .and_raise(TypeError)
125
+
126
+ allow(Net::HTTP).to \
127
+ receive(:get).with(uri).and_return(response)
128
+ expect(css_for_url('http://example.com/assets/base.css')).to \
129
+ eq(response)
130
+ end
131
+ end
132
+
133
+ context "when find_sources raises Errno::ENOENT" do
134
+ let(:response) { 'content of base.css' }
135
+ let(:uri) { URI('http://example.com/assets/base.css') }
136
+
137
+ it "falls back to Net::HTTP" do
138
+ expect(Rails.application.assets_manifest).to \
139
+ receive(:find_sources)
140
+ .with('base.css')
141
+ .and_raise(Errno::ENOENT)
142
+
143
+ allow(Net::HTTP).to \
144
+ receive(:get).with(uri).and_return(response)
145
+ expect(css_for_url('http://example.com/assets/base.css')).to \
146
+ eq(response)
96
147
  end
97
148
  end
98
149
 
99
150
  it 'returns the content of the file compiled by Rails' do
100
- expect(Rails.application.assets).to \
101
- receive(:find_asset)
151
+ expect(Rails.application.assets_manifest).to \
152
+ receive(:find_sources)
102
153
  .with('base.css')
103
- .and_return(double(to_s: 'content of base.css'))
154
+ .and_return(['content of base.css'])
104
155
 
105
- expect(load_css('http://example.com/assets/base.css')).to \
156
+ expect(css_for_url('http://example.com/assets/base.css')).to \
106
157
  eq('content of base.css')
107
158
  end
108
159
 
109
160
  it 'returns same file when path contains file fingerprint' do
110
- expect(Rails.application.assets).to \
111
- receive(:find_asset)
161
+ expect(Rails.application.assets_manifest).to \
162
+ receive(:find_sources)
112
163
  .with('base.css')
113
- .and_return(double(to_s: 'content of base.css'))
164
+ .and_return(['content of base.css'])
114
165
 
115
- expect(load_css(
166
+ expect(css_for_url(
116
167
  'http://example.com/assets/base-089e35bd5d84297b8d31ad552e433275.css'
117
168
  )).to eq('content of base.css')
118
169
  end
@@ -124,43 +175,42 @@ describe Premailer::Rails::CSSHelper do
124
175
  let(:asset_host) { 'http://assets.example.com' }
125
176
 
126
177
  before do
127
- allow(Rails.application.assets).to \
128
- receive(:find_asset).and_return(nil)
178
+ allow(Rails.application.assets_manifest).to \
179
+ receive(:find_sources).and_return([])
129
180
 
130
181
  config = double(asset_host: asset_host)
131
182
  allow(Rails.configuration).to \
132
183
  receive(:action_controller).and_return(config)
133
184
 
134
- uri_satisfaction = satisfy { |uri| uri.to_s == url }
135
185
  allow(Net::HTTP).to \
136
- receive(:get).with(uri_satisfaction).and_return(response)
186
+ receive(:get).with(URI(url)).and_return(response)
137
187
  end
138
188
 
139
189
  it 'requests the file' do
140
- expect(load_css(url)).to eq('content of base.css')
190
+ expect(css_for_url(url)).to eq('content of base.css')
141
191
  end
142
192
 
143
193
  context 'when file url does not include the host' do
144
194
  it 'requests the file using the asset host as host' do
145
- expect(load_css(path)).to eq('content of base.css')
195
+ expect(css_for_url(path)).to eq('content of base.css')
146
196
  end
147
197
 
148
198
  context 'and the asset host uses protocol relative scheme' do
149
199
  let(:asset_host) { '//assets.example.com' }
150
200
 
151
201
  it 'requests the file using http as the scheme' do
152
- expect(load_css(path)).to eq('content of base.css')
202
+ expect(css_for_url(path)).to eq('content of base.css')
153
203
  end
154
204
  end
155
205
  end
156
- end
206
+ end
157
207
  end
158
208
 
159
209
  context 'when static stylesheets are used' do
160
210
  it 'returns the content of the static file' do
161
211
  content = 'content of base.css'
162
212
  expect_file('public/stylesheets/base.css', content)
163
- loaded_content = load_css('http://example.com/stylesheets/base.css')
213
+ loaded_content = css_for_url('http://example.com/stylesheets/base.css')
164
214
  expect(loaded_content).to eq(content)
165
215
  end
166
216
  end