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