email_spectacular 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0f37914ebc9f8c1fba4ef39afba24d808dce2557
4
- data.tar.gz: 59dd333158d579bd98ba60c68eb795e9a88d380f
3
+ metadata.gz: 9271c7cddf1f63951c54a4685af8f6f8069ee9a8
4
+ data.tar.gz: aa500292e90e1d059b5434348fa6405cabafc02a
5
5
  SHA512:
6
- metadata.gz: 17185632819ec97f045453a508ad419d4ea9bbb8f669415d070ccb249a118fcdaa7656ef7eea29d26062f5895396fba2cc91e65a8a3af396b2721f90cb96cd25
7
- data.tar.gz: 1168413e77f916258af5a793dda207dfa0d98c4cda0e055c7e95e944393807faf175618eca0511e0d4083ec459b9ec54a110e886355479d62beaedd7c5f06b97
6
+ metadata.gz: 23a2af089ff52cce59e7c0922b933e34b1e055a0e7b87f2e14d0f06aebbc63e9cc76e8bb5a6c01ff5e1d306b71860f0a720c22e3e5e286db606703c7ae950070
7
+ data.tar.gz: 2d5f76c70907cf4d980c55e4712034e9c1d8e4368c3d3b730fc00c783ac54159669cbb9e51c10bfe70f3eeb1327383b01d30e326eea7289c50b541c6f06f0f26
data/README.md CHANGED
@@ -1,4 +1,7 @@
1
- # EmailSpectacular
1
+ <p align="center">
2
+ <img src="https://svgshare.com/i/CSb.svg" width="200px"><br/>
3
+ <h2 align="center">EmailSpectacular</h2>
4
+ </p>
2
5
 
3
6
  [![Gem](https://img.shields.io/gem/dt/email_spectacular.svg)]()
4
7
  [![Build Status](https://travis-ci.org/greena13/email_spectacular.svg)](https://travis-ci.org/greena13/email_spectacular)
@@ -6,6 +9,16 @@
6
9
 
7
10
  High-level email spec helpers for acceptance, feature and request tests.
8
11
 
12
+ ## Basic Usage
13
+
14
+ ```ruby
15
+ it 'does many things, including sending an email' do
16
+ # ...
17
+
18
+ expect(email).to have_been_sent.to('user@email.com')
19
+ end
20
+ ```
21
+
9
22
  ## What EmailSpectacular is
10
23
 
11
24
  Expressive email assertions that let you succinctly describe when emails should and should not be sent.
@@ -27,22 +40,19 @@ end
27
40
  Add `email_spectacular` to your `spec/rails_helper.rb`
28
41
 
29
42
  ```ruby
30
-
31
- require 'email_spectacular'
32
-
33
- # ...
43
+ require 'email_spectacular/rspec'
34
44
 
35
45
  RSpec.configure do |config|
36
46
  # ...
37
47
 
38
48
  email_spectacular_spec_types = %i[acceptance feature request]
39
-
40
- config.after(:each, type: email_spectacular_spec_types) do
41
- # Clear emails between specs
42
- clear_emails
43
- end
44
-
49
+
45
50
  email_spectacular_spec_types.each do |spec_type|
51
+ config.after(:each, type: spec_type) do
52
+ # Clear emails between specs
53
+ clear_emails
54
+ end
55
+
46
56
  # Include email spectacular syntax in rspec tests
47
57
  config.include EmailSpectacular::RSpec, type: spec_type
48
58
  end
@@ -53,6 +63,46 @@ And then execute:
53
63
 
54
64
  ```bash
55
65
  bundle install
66
+ ```
67
+
68
+ ### Configuration
69
+
70
+ Email Spectacular is configured using the `configure` method. It's suggested you place this in your `spec/rails_helper.rb` file, after you require `email_specatular`:
71
+
72
+
73
+ ```ruby
74
+ require 'email_spectacular/rspec'
75
+
76
+ EmailSpectacular.configure do |config|
77
+ # Configuration here
78
+ end
79
+ ```
80
+
81
+ #### Setting the name of the email helper
82
+
83
+ By default, Email Spectacular makes a `email` helper available for your expectation syntax (all examples below assume this default helper), however if this conflicts with anything in your test suite or is not preferred, you can specify a different helper name:
84
+
85
+ ```ruby
86
+ EmailSpectacular.configure do |config|
87
+ config.helper_name = :an_email # Default is 'email'
88
+ end
89
+ ```
90
+
91
+ #### Working with enqueued emails
92
+
93
+ If your emails are not sent immediately in your application - using `deliver_later` - you must mock this method in test mode so they appear to have sent to Email Spectacular, which is enabled using the `mock_sending_enqueued_emails` option:
94
+
95
+ ```ruby
96
+ EmailSpectacular.configure do |config|
97
+ # Mocks the enqueueing of emails so they appear in the list of sent email
98
+ config.mock_sending_enqueued_emails = true
99
+ end
100
+ ```
101
+
102
+ This then enables the assertion `have_been_enqueued`, which has the same arguments and behaviour as `have_been_sent`, but will verify the email has been enqueued rather than sent immediately:
103
+
104
+ ```ruby
105
+ expect(email).to have_been_enqueued.to('user@email.com')
56
106
  ```
57
107
 
58
108
  ## Usage
@@ -145,6 +195,16 @@ Emails can be cleared at any point by calling `clear_emails` in your tests. This
145
195
 
146
196
  If you followed in installation steps above, emails will automatically be cleared between each spec.
147
197
 
198
+ ## Gotchas and Troubleshooting
199
+
200
+ EmailSpectacular expects your application to configure `ActionMailer` to store emails in the `ActionMailer::Base.deliveries` array.
201
+
202
+ In a Rails app, this is done (automatically, by default) in your environment file: `config/environment/test.rb`
203
+
204
+ ```ruby
205
+ config.action_mailer.delivery_method = :test
206
+ ```
207
+
148
208
  ## Test suite
149
209
 
150
210
  `email_spectacular` comes with close-to-complete test coverage. You can run the test suite as follows:
@@ -21,12 +21,12 @@ Gem::Specification.new do |spec|
21
21
  spec.test_files = spec.files.grep(%r{^(spec)/})
22
22
  spec.require_paths = ['lib']
23
23
 
24
- spec.add_dependency 'actionmailer', '>= 0'
24
+ spec.add_dependency 'actionmailer'
25
25
  spec.add_dependency 'capybara', '~> 2.5', '>= 2.5.0'
26
26
 
27
- spec.add_development_dependency 'bundler', '~> 1.6'
27
+ spec.add_development_dependency 'bundler', '~> 2'
28
28
  spec.add_development_dependency 'guard', '~> 2.1'
29
29
  spec.add_development_dependency 'guard-rspec', '~> 4.7'
30
- spec.add_development_dependency 'rake', '~> 0'
30
+ spec.add_development_dependency 'rake', '>= 12.3.3'
31
31
  spec.add_development_dependency 'rspec', '>= 3.5.0'
32
32
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'email_spectacular/version'
4
+ require 'email_spectacular/adaptors/action_mailer_adaptor'
5
+ require 'email_spectacular/extensions/action_mailer_extension'
4
6
 
5
7
  # High-level email spec helpers for acceptance, feature and request tests.
6
8
  #
@@ -8,4 +10,25 @@ require 'email_spectacular/version'
8
10
  #
9
11
  # @see https://github.com/greena13/email_spectacular EmailSpectacular Github page
10
12
  module EmailSpectacular
13
+ class << self
14
+ def helper_name=(method_name)
15
+ EmailSpectacular::ActionMailerAdaptor.alias_method method_name, :email
16
+ EmailSpectacular::ActionMailerAdaptor.remove_method :email
17
+ end
18
+
19
+ def mock_sending_enqueued_emails=(enabled)
20
+ return unless enabled
21
+
22
+ @_mocking_sending_enqueued_emails = true
23
+ ActionMailer::MessageDelivery.include(EmailSpectacular::ActionMailerExtension)
24
+ end
25
+
26
+ attr_reader :_mocking_sending_enqueued_emails
27
+
28
+ def configure
29
+ if block_given?
30
+ yield(EmailSpectacular)
31
+ end
32
+ end
33
+ end
11
34
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EmailSpectacular
4
+ module ActionMailerAdaptor
5
+ # Syntactic sugar for referencing the list of emails sent since the start of the
6
+ # test
7
+ #
8
+ # @example Asserting email has been sent
9
+ # expect(email).to have_been_sent.to('test@email.com')
10
+ #
11
+ # @return [Array<Mail::Message>] List of sent emails
12
+ def email
13
+ ActionMailer::Base.deliveries
14
+ end
15
+
16
+ # Clears the list of sent emails.
17
+ #
18
+ # @return void
19
+ def clear_emails
20
+ ActionMailer::Base.deliveries = []
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capybara'
4
+
5
+ module EmailSpectacular
6
+ # Module for parsing email bodies
7
+ #
8
+ # @author Aleck Greenham
9
+ module CapybaraAdaptor
10
+ def parsed_email_parts(email)
11
+ email_parts_as_hash(email) do |email_part|
12
+ parse(email_part)
13
+ end
14
+ end
15
+
16
+ def raw_email_parts(email)
17
+ email_parts_as_hash(email)
18
+ end
19
+
20
+ private
21
+
22
+ def email_parts_as_hash(email)
23
+ if email.parts.any?
24
+ email.parts.each_with_object({}) do |email_part, memo|
25
+ decoded = email_part.body.decoded
26
+ memo[content_type_key(email_part)] = block_given? ? yield(decoded) : decoded
27
+ end
28
+ else
29
+ encoded = email.body.encoded
30
+ { content_type_key(email) => block_given? ? yield(encoded) : encoded }
31
+ end
32
+ end
33
+
34
+ def parse(target)
35
+ Capybara::Node::Simple.new(target)
36
+ end
37
+
38
+ def content_type_key(target)
39
+ target.content_type.split(';').first
40
+ end
41
+ end
42
+ end
@@ -7,9 +7,10 @@ module EmailSpectacular
7
7
  module DSL
8
8
  def self.included(base) # rubocop:disable Metrics/MethodLength
9
9
  base.class_eval do
10
- def initialize
10
+ def initialize(options = {})
11
11
  @scopes = {}
12
12
  @and_scope = nil
13
+ @enqueued = options[:enqueued]
13
14
  end
14
15
 
15
16
  # Allows chaining two assertions on the same email attribute together without
@@ -130,7 +131,7 @@ module EmailSpectacular
130
131
  #
131
132
  # @param [String] selector CSS selector that should match at least one sent
132
133
  # email's body
133
- # @return [EmailSpectacular::Expectation] reference to self, to allow for
134
+ # @return [EmailSpectacular::RSpecMatcher] reference to self, to allow for
134
135
  # further method chaining
135
136
  def matching_selector(selector)
136
137
  @scopes[:matching_selector] ||= []
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'email_spectacular/parser'
4
- require 'email_spectacular/matchers'
3
+ require 'email_spectacular/adaptors/capybara_adaptor'
4
+ require 'email_spectacular/concerns/matchers'
5
5
 
6
6
  module EmailSpectacular
7
7
  # Module containing the helper methods to describe the difference between the expected
@@ -9,7 +9,7 @@ module EmailSpectacular
9
9
  #
10
10
  # @author Aleck Greenham
11
11
  module FailureDescriptions # rubocop:disable Metrics/ModuleLength
12
- include Parser
12
+ include CapybaraAdaptor
13
13
 
14
14
  def self.included(base) # rubocop:disable Metrics/MethodLength
15
15
  base.class_eval do
@@ -19,7 +19,9 @@ module EmailSpectacular
19
19
  scopes.each do |attribute, expected|
20
20
  matching_emails =
21
21
  emails.select do |email|
22
- email_matches?(email, EmailSpectacular::Matchers::MATCHERS[attribute], expected)
22
+ email_matches?(email, EmailSpectacular::Matchers::MATCHERS[attribute], expected) &&
23
+ (!EmailSpectacular._mocking_sending_enqueued_emails ||
24
+ email.instance_variable_get(:@enqueued) == @enqueued)
23
25
  end
24
26
 
25
27
  return [attribute, expected] if matching_emails.empty?
@@ -28,23 +30,30 @@ module EmailSpectacular
28
30
  [nil, nil]
29
31
  end
30
32
 
31
- def describe_failed_assertion(emails, attribute_name, attribute_value)
33
+ def describe_failed_assertion(attribute_name, attribute_value)
34
+ action = mail_action_description
35
+
32
36
  field_descriptions = attribute_descriptions([attribute_name])
33
37
  value_descriptions = value_descriptions([attribute_value])
34
38
 
35
39
  base_clause = expectation_description(
36
- 'Expected an email to be sent',
40
+ "Expected an email to be #{action}",
37
41
  field_descriptions,
38
42
  value_descriptions
39
43
  )
40
44
 
41
- if emails.empty?
42
- "#{base_clause} However, no emails were sent."
45
+ if @emails.empty?
46
+ "#{base_clause} However, no emails were #{action}."
47
+ elsif @matching_emails[:sent].any? || @matching_emails[:enqueued].any?
48
+ opposite_action = @enqueued ? 'sent' : 'enqueued'
49
+ "#{base_clause} However, it was #{opposite_action} instead."
43
50
  else
44
- email_values = sent_email_values(emails, attribute_name)
51
+ field_descriptions = attribute_descriptions([attribute_name])
52
+
53
+ email_values = sent_email_values(@emails, attribute_name)
45
54
 
46
55
  if email_values.any?
47
- base_clause + " However, #{email_pluralisation(emails)} sent " \
56
+ base_clause + " However, #{email_pluralisation(@emails)} #{action} " \
48
57
  "#{result_description(field_descriptions, [to_sentence(email_values)])}."
49
58
  else
50
59
  base_clause
@@ -92,6 +101,14 @@ module EmailSpectacular
92
101
 
93
102
  private
94
103
 
104
+ def mail_action_description
105
+ if EmailSpectacular._mocking_sending_enqueued_emails
106
+ @enqueued ? 'enqueued' : 'sent'
107
+ else
108
+ 'sent'
109
+ end
110
+ end
111
+
95
112
  def result_description(field_descriptions, values)
96
113
  to_sentence(
97
114
  field_descriptions.map.with_index do |field_description, index|
@@ -109,7 +126,11 @@ module EmailSpectacular
109
126
  def sent_email_values(emails, attribute)
110
127
  emails.each_with_object([]) do |email, memo|
111
128
  if %i[matching_selector with_link with_image].include?(attribute)
112
- memo << email_body(email)
129
+ memo << raw_email_parts(email).inject([]) do |description, (content_type, raw_email_part)|
130
+ description.push(
131
+ "\n\n(Content Type #{content_type}):\n\n#{raw_email_part}"
132
+ )
133
+ end.join('')
113
134
  else
114
135
  matcher = EmailSpectacular::Matchers::MATCHERS[attribute]
115
136
 
@@ -118,12 +139,19 @@ module EmailSpectacular
118
139
  when String, Symbol
119
140
  email.send(matcher)
120
141
  when Hash
121
- matcher[:actual].call(email, parsed_emails(email))
142
+ parsed_email_parts(email).inject([]) do |description, (content_type, parsed_email_part)|
143
+ description.push(
144
+ "\n\n(Content Type #{content_type}):\n\n#{matcher[:actual].call(email, parsed_email_part)}"
145
+ )
146
+ end.join('')
122
147
  else
123
148
  raise ArgumentError, "Failure related to an unknown or unsupported email attribute #{attribute}"
124
149
  end
125
150
 
126
- value = value.is_a?(String) ? "'#{value}'" : value.map { |element| "'#{element}'" }
151
+ unless attribute == :with_text
152
+ value = value.is_a?(String) ? "'#{value}'" : value.map { |element| "'#{element}'" }
153
+ end
154
+
127
155
  memo << value
128
156
  end
129
157
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'email_spectacular/adaptors/capybara_adaptor'
4
+
5
+ module EmailSpectacular
6
+ # Module containing helper methods for matching expectations against emails
7
+ #
8
+ # @author Aleck Greenham
9
+ module Matchers
10
+ include CapybaraAdaptor
11
+
12
+ MATCHERS = {
13
+ to: :to,
14
+ from: :from,
15
+ with_subject: :subject,
16
+ with_text: {
17
+ match: lambda { |_, email_parts, value|
18
+ value.all? { |text| email_parts.values.any? { |email_part| email_part.has_text?(text) } }
19
+ },
20
+ actual: ->(_, parsed_email_part) { parsed_email_part.text }
21
+ },
22
+ matching_selector: {
23
+ match: lambda { |_, email_parts, value|
24
+ email_parts.values.any? { |email_part| value.all? { |selector| email_part.has_selector?(selector) } }
25
+ },
26
+ actual: ->(_, parsed_email_part) { parsed_email_part.native },
27
+ actual_name: :with_body
28
+ },
29
+ with_link: {
30
+ match: lambda { |_, email_parts, value|
31
+ email_parts.values.any? { |email_part| value.all? { |url| email_part.has_selector?("a[href='#{url}']") } }
32
+ },
33
+ actual: ->(_, parsed_email_part) { parsed_email_part.native },
34
+ actual_name: :with_body
35
+ },
36
+ with_image: {
37
+ match: lambda { |_, email_parts, value|
38
+ email_parts.values.any? { |email_part| value.all? { |url| email_part.has_selector?("img[src='#{url}']") } }
39
+ },
40
+ actual: ->(_, parsed_email_part) { parsed_email_part.native },
41
+ actual_name: :with_body
42
+ }
43
+ }.freeze
44
+
45
+ def self.included(base) # rubocop:disable Metrics/MethodLength
46
+ base.class_eval do
47
+ def matching_emails(emails, scopes)
48
+ emails.each_with_object(sent: [], enqueued: []) do |email, memo|
49
+ matches_scopes = scopes.all? do |attribute, expected|
50
+ email_matches?(email, MATCHERS[attribute], expected)
51
+ end
52
+
53
+ if matches_scopes
54
+ if email.instance_variable_get(:@enqueued)
55
+ memo[:enqueued] << email
56
+ else
57
+ memo[:sent] << email
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ def email_matches?(email, assertion, expected)
64
+ case assertion
65
+ when :to
66
+ !(expected & email.send(assertion)).empty?
67
+ when String, Symbol
68
+ email.send(assertion).include?(expected)
69
+ when Hash
70
+ assertion[:match].call(email, parsed_email_parts(email), expected)
71
+ else
72
+ raise "Unsupported assertion mapping '#{assertion}' of type #{assertion.class.name}"
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'email_spectacular/dsl'
4
- require 'email_spectacular/failure_descriptions'
5
- require 'email_spectacular/matchers'
3
+ require 'email_spectacular/concerns/dsl'
4
+ require 'email_spectacular/concerns/failure_descriptions'
5
+ require 'email_spectacular/concerns/matchers'
6
6
 
7
7
  module EmailSpectacular
8
8
  class EmailFilter
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EmailSpectacular
4
+ # Extensions to ActionMailer::MessageDelivery to mock the enqueuing of emails
5
+ module ActionMailerExtension
6
+ def self.included(base)
7
+ base.class_eval do
8
+ def deliver_later(options = {})
9
+ message.instance_variable_set(:@enqueued, true)
10
+ deliver_now
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,39 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'email_spectacular/expectation'
3
+ require 'email_spectacular.rb'
4
+ require 'email_spectacular/rspec_matcher'
5
+ require 'email_spectacular/adaptors/action_mailer_adaptor'
4
6
 
5
7
  module EmailSpectacular
6
8
  # Module containing email helper methods that can be mixed into the RSpec test scope
7
9
  #
8
10
  # @author Aleck Greenham
9
11
  module RSpec
10
- # Syntactic sugar for referencing the list of emails sent since the start of the
11
- # test
12
+ include ActionMailerAdaptor
13
+
14
+ # Creates a new email expectation that allows asserting emails should have specific
15
+ # attributes, applied only to send emails.
16
+ #
17
+ # @see EmailSpectacular::Expectation
12
18
  #
13
19
  # @example Asserting email has been sent
14
20
  # expect(email).to have_been_sent.to('test@email.com')
15
- #
16
- # @return [Array<Mail::Message>] List of sent emails
17
- def email
18
- ActionMailer::Base.deliveries
19
- end
20
-
21
- # Clears the list of sent emails.
22
- #
23
- # @return void
24
- def clear_emails
25
- ActionMailer::Base.deliveries = []
21
+ def have_been_sent # rubocop:disable Naming/PredicateName
22
+ EmailSpectacular::RSpecMatcher.new(enqueued: false)
26
23
  end
27
24
 
28
25
  # Creates a new email expectation that allows asserting emails should have specific
29
- # attributes.
26
+ # attributes, applied only to emails that have been enqueued to be sent.
30
27
  #
31
28
  # @see EmailSpectacular::Expectation
32
29
  #
33
- # @example Asserting email has been sent
34
- # expect(email).to have_been_sent.to('test@email.com')
35
- def have_been_sent # rubocop:disable Naming/PredicateName
36
- EmailSpectacular::Expectation.new
30
+ # @example Asserting email has been enqueued
31
+ # expect(email).to have_been_enqueued.to('test@email.com')
32
+ def have_been_enqueued # rubocop:disable Naming/PredicateName
33
+ unless EmailSpectacular._mocking_sending_enqueued_emails
34
+ raise 'EmailSpectacular: Cannot use the have_been_enqueued assertion without setting the ' \
35
+ 'mock_sending_enqueued_emails configuration option.'
36
+ end
37
+
38
+ EmailSpectacular::RSpecMatcher.new(enqueued: true)
37
39
  end
38
40
  end
39
41
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'email_spectacular/email_filter'
3
+ require 'email_spectacular/concerns/dsl'
4
+ require 'email_spectacular/concerns/failure_descriptions'
5
+ require 'email_spectacular/concerns/matchers'
4
6
 
5
7
  module EmailSpectacular
6
8
  # Backing class for {#have_been_sent} declarative syntax for specifying email
@@ -13,14 +15,10 @@ module EmailSpectacular
13
15
  #
14
16
  # @see EmailSpectacular::RSpec#email
15
17
  # @see EmailSpectacular::RSpec#have_been_sent
16
- class Expectation < EmailFilter
17
- # Creates a new EmailSpectacular::Expectation object
18
- #
19
- # @return [EmailSpectacular::Expectation] new expectation object
20
- def initialize
21
- @failure_message = 'Expected email to be sent'
22
- super
23
- end
18
+ class RSpecMatcher
19
+ include DSL
20
+ include Matchers
21
+ include FailureDescriptions
24
22
 
25
23
  # Declares that RSpec should not attempt to diff the actual and expected values
26
24
  # to put in the failure message. This class takes care of diffing and presenting
@@ -37,7 +35,8 @@ module EmailSpectacular
37
35
  # @return [Boolean] True when a matching email was sent
38
36
  def matches?(emails)
39
37
  @emails = emails
40
- matching_emails(emails, @scopes).any?
38
+ @matching_emails = matching_emails(emails, @scopes)
39
+ (@enqueued ? @matching_emails[:enqueued] : @matching_emails[:sent]).any?
41
40
  end
42
41
 
43
42
  # Message to display to StdOut by RSpec if the equality check fails. Includes a
@@ -58,11 +57,7 @@ module EmailSpectacular
58
57
  attribute, expected_value =
59
58
  attribute_and_expected_value(@scopes, @emails)
60
59
 
61
- describe_failed_assertion(
62
- @emails,
63
- attribute,
64
- expected_value
65
- )
60
+ describe_failed_assertion(attribute, expected_value)
66
61
  end
67
62
 
68
63
  # Failure message to display for negative RSpec assertions, i.e.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EmailSpectacular
4
- VERSION = '1.0.0'
4
+ VERSION = '1.1.0'
5
5
  end
@@ -249,7 +249,13 @@ RSpec.describe 'have_sent_email' do
249
249
  expect do
250
250
  expect(subject).to have_been_sent.with_text('Other text')
251
251
  end.to raise_error.with_message(
252
- "Expected an email to be sent with text 'Other text'. However, 1 was sent with text '#{subject[0].text}'."
252
+ <<~MSG.chomp
253
+ Expected an email to be sent with text 'Other text'. However, 1 was sent with text
254
+
255
+ (Content Type text/html):
256
+
257
+ #{subject[0].text}.
258
+ MSG
253
259
  )
254
260
  end
255
261
 
@@ -263,7 +269,13 @@ RSpec.describe 'have_sent_email' do
263
269
  expect do
264
270
  expect(subject).to have_been_sent.matching_selector('.other')
265
271
  end.to raise_error.with_message(
266
- "Expected an email to be sent matching selector '.other'. However, 1 was sent with body #{subject[0].body}."
272
+ <<~MSG.chomp
273
+ Expected an email to be sent matching selector '.other'. However, 1 was sent with body
274
+
275
+ (Content Type text/html):
276
+
277
+ #{subject[0].body}.
278
+ MSG
267
279
  )
268
280
  end
269
281
 
@@ -277,7 +289,13 @@ RSpec.describe 'have_sent_email' do
277
289
  expect do
278
290
  expect(subject).to have_been_sent.with_link('www.other.com')
279
291
  end.to raise_error.with_message(
280
- "Expected an email to be sent with link 'www.other.com'. However, 1 was sent with body #{subject[0].body}."
292
+ <<~MSG.chomp
293
+ Expected an email to be sent with link 'www.other.com'. However, 1 was sent with body
294
+
295
+ (Content Type text/html):
296
+
297
+ #{subject[0].body}.
298
+ MSG
281
299
  )
282
300
  end
283
301
 
@@ -291,7 +309,13 @@ RSpec.describe 'have_sent_email' do
291
309
  expect do
292
310
  expect(subject).to have_been_sent.with_image('www.other.com')
293
311
  end.to raise_error.with_message(
294
- "Expected an email to be sent with image 'www.other.com'. However, 1 was sent with body #{subject[0].body}."
312
+ <<~MSG.chomp
313
+ Expected an email to be sent with image 'www.other.com'. However, 1 was sent with body
314
+
315
+ (Content Type text/html):
316
+
317
+ #{subject[0].body}.
318
+ MSG
295
319
  )
296
320
  end
297
321
 
@@ -387,8 +411,13 @@ RSpec.describe 'have_sent_email' do
387
411
  expect do
388
412
  expect(subject).to have_been_sent.with_text('Other').and('Email')
389
413
  end.to raise_error.with_message(
390
- "Expected an email to be sent with text 'Other' and 'Email'. However, 1 was " \
391
- "sent with text '#{subject[0].text}'."
414
+ <<~MSG.chomp
415
+ Expected an email to be sent with text 'Other' and 'Email'. However, 1 was sent with text
416
+
417
+ (Content Type text/html):
418
+
419
+ #{subject[0].text}.
420
+ MSG
392
421
  )
393
422
  end
394
423
 
@@ -396,7 +425,13 @@ RSpec.describe 'have_sent_email' do
396
425
  expect do
397
426
  expect(subject).to have_been_sent.with_text('Test').and('Other')
398
427
  end.to raise_error.with_message(
399
- "Expected an email to be sent with text 'Test' and 'Other'. However, 1 was sent with text '#{subject[0].text}'."
428
+ <<~MSG.chomp
429
+ Expected an email to be sent with text 'Test' and 'Other'. However, 1 was sent with text
430
+
431
+ (Content Type text/html):
432
+
433
+ #{subject[0].text}.
434
+ MSG
400
435
  )
401
436
  end
402
437
 
@@ -42,4 +42,8 @@ class EmailBodyMock
42
42
  def decoded
43
43
  @text
44
44
  end
45
+
46
+ def content_type
47
+ 'text/html; charset=UTF-8'
48
+ end
45
49
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: email_spectacular
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aleck Greenham
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-04-11 00:00:00.000000000 Z
11
+ date: 2020-04-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionmailer
@@ -50,14 +50,14 @@ dependencies:
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '1.6'
53
+ version: '2'
54
54
  type: :development
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: '1.6'
60
+ version: '2'
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: guard
63
63
  requirement: !ruby/object:Gem::Requirement
@@ -90,16 +90,16 @@ dependencies:
90
90
  name: rake
91
91
  requirement: !ruby/object:Gem::Requirement
92
92
  requirements:
93
- - - "~>"
93
+ - - ">="
94
94
  - !ruby/object:Gem::Version
95
- version: '0'
95
+ version: 12.3.3
96
96
  type: :development
97
97
  prerelease: false
98
98
  version_requirements: !ruby/object:Gem::Requirement
99
99
  requirements:
100
- - - "~>"
100
+ - - ">="
101
101
  - !ruby/object:Gem::Version
102
- version: '0'
102
+ version: 12.3.3
103
103
  - !ruby/object:Gem::Dependency
104
104
  name: rspec
105
105
  requirement: !ruby/object:Gem::Requirement
@@ -134,13 +134,15 @@ files:
134
134
  - Rakefile
135
135
  - email_spectacular.gemspec
136
136
  - lib/email_spectacular.rb
137
- - lib/email_spectacular/dsl.rb
137
+ - lib/email_spectacular/adaptors/action_mailer_adaptor.rb
138
+ - lib/email_spectacular/adaptors/capybara_adaptor.rb
139
+ - lib/email_spectacular/concerns/dsl.rb
140
+ - lib/email_spectacular/concerns/failure_descriptions.rb
141
+ - lib/email_spectacular/concerns/matchers.rb
138
142
  - lib/email_spectacular/email_filter.rb
139
- - lib/email_spectacular/expectation.rb
140
- - lib/email_spectacular/failure_descriptions.rb
141
- - lib/email_spectacular/matchers.rb
142
- - lib/email_spectacular/parser.rb
143
+ - lib/email_spectacular/extensions/action_mailer_extension.rb
143
144
  - lib/email_spectacular/rspec.rb
145
+ - lib/email_spectacular/rspec_matcher.rb
144
146
  - lib/email_spectacular/version.rb
145
147
  - spec/email_expectation_spec.rb
146
148
  - spec/spec_helper.rb
@@ -1,66 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'email_spectacular/parser'
4
-
5
- module EmailSpectacular
6
- # Module containing helper methods for matching expectations against emails
7
- #
8
- # @author Aleck Greenham
9
- module Matchers
10
- include Parser
11
-
12
- MATCHERS = {
13
- to: :to,
14
- from: :from,
15
- with_subject: :subject,
16
- with_text: {
17
- match: ->(_, email, value) { value.all? { |text| email.has_text?(text) } },
18
- actual: ->(_, email) { email.text }
19
- },
20
- matching_selector: {
21
- match: ->(_, email, value) { value.all? { |selector| email.has_selector?(selector) } },
22
- actual: ->(_, email) { email.native },
23
- actual_name: :with_body
24
- },
25
- with_link: {
26
- match: ->(_, email, value) { value.all? { |url| email.has_selector?("a[href='#{url}']") } },
27
- actual: ->(_, email) { email.native },
28
- actual_name: :with_body
29
- },
30
- with_image: {
31
- match: ->(_, email, value) { value.all? { |url| email.has_selector?("img[src='#{url}']") } },
32
- actual: ->(_, email) { email.native },
33
- actual_name: :with_body
34
- }
35
- }.freeze
36
-
37
- def self.included(base) # rubocop:disable Metrics/MethodLength
38
- base.class_eval do
39
- def matching_emails(emails, scopes)
40
- if scopes.any?
41
- emails.select do |email|
42
- scopes.all? do |attribute, expected|
43
- email_matches?(email, MATCHERS[attribute], expected)
44
- end
45
- end
46
- else
47
- emails
48
- end
49
- end
50
-
51
- def email_matches?(email, assertion, expected)
52
- case assertion
53
- when :to
54
- !(expected & email.send(assertion)).empty?
55
- when String, Symbol
56
- email.send(assertion).include?(expected)
57
- when Hash
58
- assertion[:match].call(email, parsed_emails(email), expected)
59
- else
60
- raise "Unsupported assertion mapping '#{assertion}' of type #{assertion.class.name}"
61
- end
62
- end
63
- end
64
- end
65
- end
66
- end
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'capybara'
4
-
5
- module EmailSpectacular
6
- # Module for parsing email bodies
7
- #
8
- # @author Aleck Greenham
9
- module Parser
10
- def parsed_emails(email)
11
- parser(email)
12
- end
13
-
14
- def parser(email)
15
- Capybara::Node::Simple.new(email_body(email))
16
- end
17
-
18
- def email_body(email)
19
- if email.parts.first
20
- email.parts.first.body.decoded
21
- else
22
- email.body.encoded
23
- end
24
- end
25
- end
26
- end