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.
- checksums.yaml +5 -5
- data/.gitignore +4 -0
- data/.travis.yml +10 -15
- data/CHANGELOG.md +74 -0
- data/Gemfile +10 -4
- data/README.md +49 -39
- data/VERSION +1 -1
- data/example/Gemfile +1 -3
- data/example/bin/rails +4 -0
- data/example/config.ru +1 -1
- data/example/config/application.rb +1 -0
- data/example/config/boot.rb +1 -2
- data/example/config/environments/development.rb +4 -0
- data/example/config/environments/production.rb +9 -0
- data/example/config/initializers/assets.rb +2 -0
- data/lib/premailer/rails.rb +4 -2
- data/lib/premailer/rails/css_helper.rb +45 -16
- data/lib/premailer/rails/css_loaders.rb +0 -1
- data/lib/premailer/rails/css_loaders/asset_pipeline_loader.rb +19 -10
- data/lib/premailer/rails/css_loaders/file_system_loader.rb +24 -2
- data/lib/premailer/rails/css_loaders/network_loader.rb +15 -8
- data/lib/premailer/rails/customized_premailer.rb +6 -6
- data/lib/premailer/rails/hook.rb +16 -8
- data/premailer-rails.gemspec +4 -4
- data/spec/integration/css_helper_spec.rb +92 -42
- data/spec/integration/delivery_spec.rb +13 -0
- data/spec/integration/hook_spec.rb +53 -1
- data/spec/rails_app/app/assets/config/manifest.js +3 -0
- data/spec/rails_app/app/assets/stylesheets/application.css +3 -0
- data/spec/rails_app/app/mailers/application_mailer.rb +4 -0
- data/spec/rails_app/app/mailers/welcome_mailer.rb +6 -0
- data/spec/rails_app/app/views/layouts/mailer.html.erb +11 -0
- data/spec/rails_app/app/views/welcome_mailer/welcome_email.html.erb +1 -0
- data/spec/rails_app/config.ru +5 -0
- data/spec/rails_app/config/application.rb +13 -0
- data/spec/rails_app/config/boot.rb +5 -0
- data/spec/rails_app/config/environment.rb +2 -0
- data/spec/rails_app/config/environments/test.rb +10 -0
- data/spec/rails_app/config/initializers/assets.rb +1 -0
- data/spec/rails_app/config/routes.rb +3 -0
- data/spec/spec_helper.rb +3 -4
- data/spec/support/fixtures/html.rb +8 -4
- data/spec/support/fixtures/message.rb +56 -0
- data/spec/unit/css_loaders/asset_pipeline_loader_spec.rb +19 -4
- data/spec/unit/css_loaders/file_system_loader_spec.rb +37 -0
- data/spec/unit/css_loaders/network_loader_spec.rb +58 -0
- data/spec/unit/customized_premailer_spec.rb +32 -40
- metadata +63 -34
- data/lib/premailer/rails/css_loaders/cache_loader.rb +0 -19
- data/spec/integration/hook_registration_spec.rb +0 -11
- data/spec/support/stubs/action_mailer.rb +0 -5
- 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 @@
|
|
1
|
+
<p>Hello <%= @greeting %></p>
|
@@ -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,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'
|
data/spec/spec_helper.rb
CHANGED
@@ -11,12 +11,11 @@ if RUBY_ENGINE == 'ruby'
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
|
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 =
|
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
|
-
|
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 – and —.
|
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
|
-
|
6
|
-
|
7
|
-
allow(Rails).to receive(:
|
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
|
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
|
-
|
5
|
-
|
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
|
-
|
8
|
-
|
14
|
+
describe '#to_inline_css' do
|
15
|
+
let(:regex) { %r{<p style=("|')color: ?red;?\1>} }
|
9
16
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|