premailer-rails 1.8.0 → 1.11.1

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.
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
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'ActionMailer::Base delivery' do
4
+ it 'delivers email with inlined CSS' do
5
+ WelcomeMailer.welcome_email("world").deliver_now
6
+
7
+ mail = ActionMailer::Base.deliveries.last
8
+ expect(mail).to be_present
9
+ body = mail.html_part.body.to_s
10
+ expect(body).to be_present
11
+ expect(body).to include(%{<p style="font-size: 12px;">Hello world</p>})
12
+ end
13
+ end
@@ -5,6 +5,10 @@ describe Premailer::Rails::Hook do
5
5
  Premailer::Rails::Hook.perform(message)
6
6
  end
7
7
 
8
+ def body_content(message)
9
+ Nokogiri::HTML(message.html_string).at('body').content.gsub("\r\n", "\n")
10
+ end
11
+
8
12
  class Mail::Message
9
13
  def html_string
10
14
  (html_part || self).body.to_s
@@ -43,6 +47,54 @@ describe Premailer::Rails::Hook do
43
47
  expect(processed_message.parts).to match_array(expected_parts)
44
48
  end
45
49
 
50
+ it 'does not screw up the text by maintaining the original body encoding' do
51
+ raw_msg = Fixtures::Message.latin_message
52
+ processed_msg = Fixtures::Message.latin_message
53
+ run_hook(processed_msg)
54
+ expect(body_content(processed_msg)).to eq(body_content(raw_msg))
55
+
56
+ raw_msg = Fixtures::Message.non_latin_message
57
+ processed_msg = Fixtures::Message.non_latin_message
58
+ run_hook(processed_msg)
59
+ expect(body_content(processed_msg)).to eq(body_content(raw_msg))
60
+
61
+ raw_msg = Fixtures::Message.greek_message
62
+ processed_msg = Fixtures::Message.greek_message
63
+ run_hook(processed_msg)
64
+ expect(body_content(processed_msg)).to eq(body_content(raw_msg))
65
+
66
+ raw_msg = Fixtures::Message.dash_message
67
+ processed_msg = Fixtures::Message.dash_message
68
+ run_hook(processed_msg)
69
+ expect(body_content(processed_msg)).to eq(body_content(raw_msg))
70
+ end
71
+
72
+ it 'supports US-ASCII output' do
73
+ Premailer::Rails.config.merge!(output_encoding: 'US-ASCII')
74
+
75
+ raw_msg = Fixtures::Message.latin_message
76
+ processed_msg = Fixtures::Message.latin_message
77
+ run_hook(processed_msg)
78
+ expect(body_content(processed_msg)).to eq(body_content(raw_msg))
79
+
80
+ raw_msg = Fixtures::Message.non_latin_message
81
+ processed_msg = Fixtures::Message.non_latin_message
82
+ run_hook(processed_msg)
83
+ expect(body_content(processed_msg)).to eq(body_content(raw_msg))
84
+
85
+ raw_msg = Fixtures::Message.greek_message
86
+ processed_msg = Fixtures::Message.greek_message
87
+ run_hook(processed_msg)
88
+ expect(body_content(processed_msg)).to eq(body_content(raw_msg))
89
+
90
+ raw_msg = Fixtures::Message.dash_message
91
+ processed_msg = Fixtures::Message.dash_message
92
+ run_hook(processed_msg)
93
+ expect(body_content(processed_msg)).to eq(body_content(raw_msg))
94
+ ensure
95
+ Premailer::Rails.config.delete(:output_encoding)
96
+ end
97
+
46
98
  it 'generates a text part from the html' do
47
99
  expect { run_hook(message) }.to change(message, :text_part)
48
100
  end
@@ -64,7 +116,7 @@ describe Premailer::Rails::Hook do
64
116
 
65
117
  it 'does not replace any message part' do
66
118
  expect { run_hook(message) }.to_not \
67
- change { message.all_parts.map(&:content_type) }
119
+ change { message.all_parts.map(&:content_type).sort }
68
120
  end
69
121
  end
70
122
 
@@ -0,0 +1,3 @@
1
+ //= link_tree ../images
2
+ //= link_directory ../javascripts .js
3
+ //= link_directory ../stylesheets .css
@@ -0,0 +1,3 @@
1
+ p {
2
+ font-size: 12px;
3
+ }
@@ -0,0 +1,4 @@
1
+ class ApplicationMailer < ActionMailer::Base
2
+ default from: 'from@example.com'
3
+ layout 'mailer'
4
+ end
@@ -0,0 +1,6 @@
1
+ class WelcomeMailer < ApplicationMailer
2
+ def welcome_email(greeting)
3
+ @greeting = greeting
4
+ mail to: "example@example.com"
5
+ end
6
+ end
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5
+ <link rel="stylesheet" href="/assets/application.css"/>
6
+ </head>
7
+
8
+ <body>
9
+ <%= yield %>
10
+ </body>
11
+ </html>
@@ -0,0 +1 @@
1
+ <p>Hello <%= @greeting %></p>
@@ -0,0 +1,5 @@
1
+ # This file is used by Rack-based servers to start the application.
2
+
3
+ require_relative 'config/environment'
4
+
5
+ run Rails.application
@@ -0,0 +1,13 @@
1
+ require_relative 'boot'
2
+
3
+ require "action_mailer/railtie"
4
+ require "action_view/railtie"
5
+ require "sprockets/railtie"
6
+ require "rails/test_unit/railtie"
7
+
8
+ Bundler.require(*Rails.groups)
9
+
10
+ module Dummy
11
+ class Application < Rails::Application
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ # Set up gems listed in the Gemfile.
2
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__)
3
+
4
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
5
+ $LOAD_PATH.unshift File.expand_path('../../../lib', __dir__)
@@ -0,0 +1,2 @@
1
+ require_relative 'application'
2
+ Rails.application.initialize!
@@ -0,0 +1,10 @@
1
+ Rails.application.configure do
2
+ config.cache_classes = true
3
+ config.eager_load = false
4
+ config.consider_all_requests_local = true
5
+ config.action_controller.perform_caching = false
6
+ config.action_dispatch.show_exceptions = false
7
+ config.action_controller.allow_forgery_protection = false
8
+ config.action_mailer.delivery_method = :test
9
+ config.active_support.deprecation = :stderr
10
+ end
@@ -0,0 +1 @@
1
+ Rails.application.config.assets.version = '1.0'
@@ -0,0 +1,3 @@
1
+ Rails.application.routes.draw do
2
+ root 'application#main'
3
+ end
data/spec/spec_helper.rb CHANGED
@@ -11,12 +11,11 @@ if RUBY_ENGINE == 'ruby'
11
11
  end
12
12
  end
13
13
 
14
- require 'premailer/rails'
14
+ # Configure Rails Environment
15
+ ENV["RAILS_ENV"] = "test"
16
+ require File.expand_path("../../spec/rails_app/config/environment.rb", __FILE__)
15
17
 
16
- require 'support/stubs/action_mailer'
17
- require 'support/stubs/rails'
18
18
  require 'support/fixtures/message'
19
19
  require 'support/fixtures/html'
20
20
 
21
- require 'hpricot' unless RUBY_PLATFORM == 'java'
22
21
  require 'nokogiri'
@@ -18,17 +18,21 @@ module Fixtures
18
18
  </html>
19
19
  HTML
20
20
 
21
- LINK = <<-LINK
22
- <link rel='stylesheet' href='%s' />
23
- LINK
21
+ LINK = "<link rel='stylesheet' %s />\n"
24
22
 
25
23
  def with_css_links(*files)
24
+ opts = files.last.is_a?(Hash) ? files.pop : {}
26
25
  links = []
27
26
  files.each do |file|
28
- links << LINK % "http://example.com/#{file}"
27
+ attrs = { href: "http://example.com/#{file}" }.merge(opts)
28
+ links << LINK % hash_to_attributes(attrs)
29
29
  end
30
30
 
31
31
  TEMPLATE % links.join
32
32
  end
33
+
34
+ def hash_to_attributes(attrs)
35
+ attrs.map { |attr, value| "#{attr}='#{value}'" }.join(' ')
36
+ end
33
37
  end
34
38
  end
@@ -35,6 +35,30 @@ module Fixtures
35
35
  </html>
36
36
  HTML
37
37
 
38
+ HTML_PART_IN_GREEK = <<-HTML.encode(Encoding::ISO_8859_7)
39
+ <html>
40
+ <head>
41
+ </head>
42
+ <body>
43
+ <p>
44
+ Αα Ββ Γγ Δδ Εε Ζζ Ηη Θθ Ιι Κκ Λλ Μμ Νν Ξξ Οο Ππ Ρρ Σσ Ττ Υυ Φφ Χχ Ψψ Ωω
45
+ </p>
46
+ </body>
47
+ </html>
48
+ HTML
49
+
50
+ HTML_PART_WITH_DASHES = <<-HTML
51
+ <html>
52
+ <head>
53
+ </head>
54
+ <body>
55
+ <p>
56
+ Hello there—yes you! What's up with – pardon the interrupion – dashes? I can also do &ndash; and &mdash;.
57
+ </p>
58
+ </body>
59
+ </html>
60
+ HTML
61
+
38
62
  HTML_PART_WITH_CSS = <<-HTML
39
63
  <html>
40
64
  <head>
@@ -114,6 +138,38 @@ nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
114
138
  message
115
139
  end
116
140
 
141
+ def latin_message
142
+ base_message.tap do |message|
143
+ message.body = HTML_PART
144
+ message.content_type 'text/html; charset=UTF-8'
145
+ message.ready_to_send!
146
+ end
147
+ end
148
+
149
+ def non_latin_message
150
+ base_message.tap do |message|
151
+ message.body = HTML_PART_WITH_UNICODE
152
+ message.content_type 'text/html; charset=UTF-8'
153
+ message.ready_to_send!
154
+ end
155
+ end
156
+
157
+ def greek_message
158
+ base_message.tap do |message|
159
+ message.body = HTML_PART_IN_GREEK
160
+ message.content_type 'text/html; charset=ISO-8859-7'
161
+ message.ready_to_send!
162
+ end
163
+ end
164
+
165
+ def dash_message
166
+ base_message.tap do |message|
167
+ message.body = HTML_PART_WITH_DASHES
168
+ message.content_type 'text/html; charset=UTF-8'
169
+ message.ready_to_send!
170
+ end
171
+ end
172
+
117
173
  private
118
174
 
119
175
  def base_message
@@ -2,9 +2,9 @@ require 'spec_helper'
2
2
 
3
3
  describe Premailer::Rails::CSSLoaders::AssetPipelineLoader do
4
4
  before do
5
- assets = double(prefix: '/assets')
6
- config = double(assets: assets)
7
- allow(Rails).to receive(:configuration).and_return(config)
5
+ allow(Rails.configuration)
6
+ .to receive(:assets).and_return(double(prefix: '/assets'))
7
+ allow(Rails.configuration).to receive(:relative_url_root).and_return(nil)
8
8
  end
9
9
 
10
10
  describe ".file_name" do
@@ -17,11 +17,26 @@ describe Premailer::Rails::CSSLoaders::AssetPipelineLoader do
17
17
  it { is_expected.to eq('application.css') }
18
18
  end
19
19
 
20
- context "when asset file path contains fingerprint" do
20
+ context "when asset file path contains prefix and relative_url_root is set" do
21
+ before do
22
+ allow(Rails.configuration)
23
+ .to receive(:relative_url_root).and_return('/foo')
24
+ end
25
+
26
+ let(:asset) { '/foo/assets/application.css' }
27
+ it { is_expected.to eq('application.css') }
28
+ end
29
+
30
+ context "when asset file path contains 32 chars fingerprint" do
21
31
  let(:asset) { 'application-6776f581a4329e299531e1d52aa59832.css' }
22
32
  it { is_expected.to eq('application.css') }
23
33
  end
24
34
 
35
+ context "when asset file path contains 64 chars fingerprint" do
36
+ let(:asset) { 'application-02275ccb3fd0c11615bbfb11c99ea123ca2287e75045fe7b72cefafb880dad2b.css' }
37
+ it { is_expected.to eq('application.css') }
38
+ end
39
+
25
40
  context "when asset file page contains numbers, but not a fingerprint" do
26
41
  let(:asset) { 'test/20130708152545-foo-bar.css' }
27
42
  it { is_expected.to eq("test/20130708152545-foo-bar.css") }
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Premailer::Rails::CSSLoaders::FileSystemLoader do
4
+ before do
5
+ allow(Rails.configuration)
6
+ .to receive(:assets).and_return(double(prefix: '/assets'))
7
+ allow(Rails)
8
+ .to receive(:root).and_return(Pathname.new('/rails_root'))
9
+ end
10
+
11
+ describe '#file_name' do
12
+ subject { described_class.file_name(asset) }
13
+ let(:relative_url_root) { nil }
14
+
15
+ before do
16
+ config = double(relative_url_root: relative_url_root)
17
+ allow(Rails).to receive(:configuration).and_return(config)
18
+ end
19
+
20
+ context 'when relative_url_root is not set' do
21
+ let(:asset) { '/assets/application.css' }
22
+ it { is_expected.to eq(File.join(Rails.root, 'public/assets/application.css')) }
23
+ end
24
+
25
+ context 'when relative_url_root is set' do
26
+ let(:relative_url_root) { '/foo' }
27
+ let(:asset) { '/foo/assets/application.css' }
28
+ it { is_expected.to eq(File.join(Rails.root, 'public/assets/application.css')) }
29
+ end
30
+
31
+ context 'when relative_url_root has a trailing slash' do
32
+ let(:relative_url_root) { '/foo/' }
33
+ let(:asset) { '/foo/assets/application.css' }
34
+ it { is_expected.to eq(File.join(Rails.root, 'public/assets/application.css')) }
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ describe Premailer::Rails::CSSLoaders::NetworkLoader do
4
+ describe '#uri_for_url' do
5
+ subject { described_class.uri_for_url(url) }
6
+ let(:asset_host) { nil }
7
+
8
+ before do
9
+ action_controller = double(asset_host: asset_host)
10
+ config = double(action_controller: action_controller)
11
+ allow(Rails).to receive(:configuration).and_return(config)
12
+ end
13
+
14
+ context 'with a valid URL' do
15
+ let(:url) { 'http://example.com/test.css' }
16
+ it { is_expected.to eq(URI(url)) }
17
+ end
18
+
19
+ context 'with a protocol relative URL' do
20
+ let(:url) { '//example.com/test.css' }
21
+ it { is_expected.to eq(URI("http:#{url}")) }
22
+ end
23
+
24
+ context 'with a file path' do
25
+ let(:url) { '/assets/foo.css' }
26
+
27
+ context 'and a domain as asset host' do
28
+ let(:asset_host) { 'example.com' }
29
+ it { is_expected.to eq(URI("http://example.com#{url}")) }
30
+ end
31
+
32
+ context 'and a URL as asset host' do
33
+ let(:asset_host) { 'https://example.com' }
34
+ it { is_expected.to eq(URI("https://example.com/assets/foo.css")) }
35
+ end
36
+
37
+ context 'and a protocol relative URL as asset host' do
38
+ let(:asset_host) { '//example.com' }
39
+ it { is_expected.to eq(URI("http://example.com/assets/foo.css")) }
40
+ end
41
+
42
+ context 'and a callable object as asset host' do
43
+ let(:asset_host) { double }
44
+
45
+ it 'calls #call with the asset path as argument' do
46
+ expect(asset_host).to receive(:call).with(url).and_return(
47
+ 'http://example.com')
48
+ expect(subject).to eq(URI('http://example.com/assets/foo.css'))
49
+ end
50
+ end
51
+
52
+ context 'without an asset host' do
53
+ let(:asset_host) { nil }
54
+ it { is_expected.not_to be }
55
+ end
56
+ end
57
+ end
58
+ end
@@ -1,51 +1,43 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Premailer::Rails::CustomizedPremailer do
4
- [ :nokogiri, :hpricot ].each do |adapter|
5
- next if adapter == :hpricot and RUBY_PLATFORM == 'java'
4
+ describe '#to_plain_text' do
5
+ it 'includes the text from the HTML part' do
6
+ premailer =
7
+ Premailer::Rails::CustomizedPremailer
8
+ .new(Fixtures::Message::HTML_PART)
9
+ expect(premailer.to_plain_text.gsub(/\s/, ' ').strip).to \
10
+ eq(Fixtures::Message::TEXT_PART.gsub(/\s/, ' ').strip)
11
+ end
12
+ end
6
13
 
7
- context "when adapter is #{adapter}" do
8
- before { allow(Premailer::Adapter).to receive(:use).and_return(adapter) }
14
+ describe '#to_inline_css' do
15
+ let(:regex) { %r{<p style=("|')color: ?red;?\1>} }
9
16
 
10
- describe '#to_plain_text' do
11
- it 'includes the text from the HTML part' do
12
- premailer =
13
- Premailer::Rails::CustomizedPremailer
14
- .new(Fixtures::Message::HTML_PART)
15
- expect(premailer.to_plain_text.gsub(/\s/, ' ').strip).to \
16
- eq(Fixtures::Message::TEXT_PART.gsub(/\s/, ' ').strip)
17
- end
17
+ context 'when inline CSS block present' do
18
+ it 'returns the HTML with the CSS inlined' do
19
+ allow(Premailer::Rails::CSSHelper).to \
20
+ receive(:css_for_doc).and_return('p { color: red; }')
21
+ html = Fixtures::Message::HTML_PART
22
+ premailer = Premailer::Rails::CustomizedPremailer.new(html)
23
+ expect(premailer.to_inline_css).to match(regex)
18
24
  end
25
+ end
19
26
 
20
- describe '#to_inline_css' do
21
- let(:regex) { %r{<p style=("|')color: ?red;?\1>} }
22
-
23
- context 'when inline CSS block present' do
24
- it 'returns the HTML with the CSS inlined' do
25
- allow(Premailer::Rails::CSSHelper).to \
26
- receive(:css_for_doc).and_return('p { color: red; }')
27
- html = Fixtures::Message::HTML_PART
28
- premailer = Premailer::Rails::CustomizedPremailer.new(html)
29
- expect(premailer.to_inline_css).to match(regex)
30
- end
31
- end
32
-
33
- context 'when CSS is loaded externally' do
34
- it 'returns the HTML with the CSS inlined' do
35
- html = Fixtures::Message::HTML_PART_WITH_CSS
36
- premailer = Premailer::Rails::CustomizedPremailer.new(html)
37
- expect(premailer.to_inline_css).to match(regex)
38
- end
39
- end
27
+ context 'when CSS is loaded externally' do
28
+ it 'returns the HTML with the CSS inlined' do
29
+ html = Fixtures::Message::HTML_PART_WITH_CSS
30
+ premailer = Premailer::Rails::CustomizedPremailer.new(html)
31
+ expect(premailer.to_inline_css).to match(regex)
32
+ end
33
+ end
40
34
 
41
- context 'when HTML contains unicode' do
42
- it 'does not mess those up' do
43
- html = Fixtures::Message::HTML_PART_WITH_UNICODE
44
- premailer = Premailer::Rails::CustomizedPremailer.new(html)
45
- expect(premailer.to_inline_css).to \
46
- include(Fixtures::Message::UNICODE_STRING)
47
- end
48
- end
35
+ context 'when HTML contains unicode' do
36
+ it 'does not mess those up' do
37
+ html = Fixtures::Message::HTML_PART_WITH_UNICODE
38
+ premailer = Premailer::Rails::CustomizedPremailer.new(html)
39
+ expect(premailer.to_inline_css).to \
40
+ include(Fixtures::Message::UNICODE_STRING)
49
41
  end
50
42
  end
51
43
  end