test_assistant 0.1.2 → 1.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9311d43bef33250abf4b13c6f10cfa03df96f8ed
4
- data.tar.gz: 1fb3475a4bed626f4a5744724c340ac8702888b4
3
+ metadata.gz: 8304109fa9e5a8dcd0e7664b4034699179311eb5
4
+ data.tar.gz: 847f3f376f40a1a4665e4a5866131488a2f04278
5
5
  SHA512:
6
- metadata.gz: '00185e2575b08c763500c145db50601143a73da0fa669c40eb70f7cc4e9156905c9b49ab13b3d4c6db8c7fe27fb82b60d9bd1d7a87df21c9472034e83c893609'
7
- data.tar.gz: e76955c99b919c852f83de86ebb6ee173ac41acc8dbff90b8100d5784f0847fc35f09c4f03a0b3d61ee672a479aa51ba2f1f37f5cf52d8ee05e4a3a723289eb9
6
+ metadata.gz: a1293ac55e9244756384126f45f2c8b54fca5cf6f9f55f9a001e8838661aa3fe224f479a5dee95fb838379950e43d28c32d3a121049eb5d8187babdd92d028d4
7
+ data.tar.gz: c3732fbe8aae17fa613a69d80328fe06063373f9917c02d83610c6cb90dbeaecea1df64ef390f28d31c40c1971ee014b49921659ebc4d174cbbb12cac5c84a94
data/README.md CHANGED
@@ -1,12 +1,10 @@
1
1
  # TestAssistant
2
2
 
3
- RSpec toolbox for writing and diagnosing Ruby on Rails tests, faster - especially emails and JSON APIs.
3
+ RSpec toolbox for writing and diagnosing Ruby on Rails tests, faster.
4
4
 
5
5
  ## Features
6
6
 
7
7
  * Light-weight, scoped, lazily executed and composable tool-box, so you only include the features you want to use, when you want to use them with no unnecessary overhead
8
- * JSON assertion that gives noise-free reports on complex nested structures, so you can find out exactly what has changed with your JSON API without having to manually diff large objects
9
- * Expressive email assertions that let you succinctly describe when emails should and should not sent
10
8
  * Automatic reporting of the context around failing tests, so you don't have to re-run them with additional logging or a debugger
11
9
 
12
10
  ## Installation
@@ -35,155 +33,8 @@ RSpec.configure do |config|
35
33
  end
36
34
  ```
37
35
 
38
- ## JSON expectations
39
-
40
- Test Assistant lets you include helpers in your controller and request specs to get succinct declarative methods for defining the expected results for JSON responses.
41
-
42
- ### Setup
43
-
44
- ```ruby
45
- TestAssistant.configure(config) do |ta_config|
46
- ta_config.include_json_helpers type: :request
47
- end
48
- ```
49
-
50
- ### Asserting JSON responses
51
-
52
- Among the helpers provided are `json_response`, which automatically parses the last response object as json, and a custom assertion `eql_json` that reports failures in a format that is much clearer than anything provided by RSpec.
53
-
54
- The full `expected` and `actual` values are still reported, but below is a separate report that only includes the paths to the failed nested values and their differences, removing the need to manually compare the two complete objects to find the difference.
55
-
56
- ```ruby
57
- RSpec.describe 'making some valid request', type: :request do
58
- context 'some important context' do
59
- it 'should return some complicated JSON' do
60
-
61
- perform_request
62
-
63
- expect(json_response).to eql_json([
64
- {
65
- "a" => [
66
- 1, 2, 3
67
- ],
68
- "c" => { "d" => "d'"}
69
- },
70
- {
71
- "b" => [
72
- 1, 2, 3
73
- ],
74
- "c" => { "d" => "d'"}
75
- }
76
- ])
77
- end
78
- end
79
- end
80
- ```
81
-
82
- ## Email expectations
83
-
84
- Test Assistant provides a declarative API for describing when emails should be sent and their characteristics.
85
-
86
- ### Setup
87
-
88
- ```ruby
89
- TestAssistant.configure(config) do |ta_config|
90
- ta_config.include_email_helpers type: :request
91
- end
92
- ```
93
-
94
- ### Clearing emails
95
-
96
- Emails can be cleared at any point by calling `clear_emails` in your tests. This is helpful when you are testing a user workflow that may trigger multiple emails.
97
-
98
- Emails are automatically cleared between each request spec.
99
-
100
- ### Email receiver address
101
-
102
- It's possible to assert an email was sent to one or more or more addresses using the following format:
103
-
104
- ```ruby
105
- expect(email).to have_been_sent.to('user@email.com')
106
- ```
107
-
108
- ### Email sender address
109
-
110
- Similarly, you can assert an email was sent from an address:
111
-
112
- ```ruby
113
- expect(email).to have_been_sent.from('user@email.com')
114
- ```
115
-
116
- ### Email subject
117
-
118
- You can assert an email's subject:
119
-
120
- ```ruby
121
- expect(email).to have_been_sent.with_subject('Welcome!')
122
- ```
123
-
124
-
125
- ### Email body
126
-
127
- You can assert the body of an email by text:
128
-
129
- ```ruby
130
- expect(email).to have_been_sent.with_text('Welcome, user@email.com')
131
- ```
132
-
133
- Or using a selector on the email's HTML:
134
-
135
- ```ruby
136
- expect(email).to have_been_sent.with_selector('#password')
137
- ```
138
-
139
- Or look for links:
140
-
141
- ```ruby
142
- expect(email).to have_been_sent.with_link('www.site.com/onboarding/1')
143
- ```
144
-
145
- Or images:
146
-
147
- ```ruby
148
- expect(email).to have_been_sent.with_image('www.site.com/assets/images/welcome.png')
149
- ```
150
-
151
- ### Chaining assertions
152
-
153
- You can chain any combination of the above that you want for ultra specific assertions:
154
-
155
-
156
- ```ruby
157
- expect(email).to have_been_sent
158
- .to('user@email.com')
159
- .from('admin@site.com')
160
- .with_subject('Welcome!')
161
- .with_text('Welcome, user@email.com')
162
- .with_selector('#password').and('#username')
163
- .with_link('www.site.com/onboarding/1')
164
- .with_image('www.site.com/assets/images/welcome.png')
165
-
166
- ```
167
-
168
- You can also chain multiple assertions of the the same type with the `and` method:
169
-
170
- ```ruby
171
- expect(email).to have_been_sent
172
- .with_text('Welcome, user@email.com').and('Thanks for signing up')
173
- ```
174
-
175
- ### Asserting emails are NOT sent
176
-
177
- The `have_sent_email` assertion works with the negative case as well:
178
-
179
- ```ruby
180
- expect(email).to_not have_been_sent.with_text('Secret token')
181
- ```
182
-
183
36
  ## Failure Reporting
184
37
 
185
-
186
-
187
38
  ### Rendering a response context when a test fails
188
39
 
189
40
  Test Assistant can automatically render the server response in your browser when a test fails and you have applied a nominated tag.
@@ -215,8 +66,6 @@ The `debug_failed_responses` accepts a the following options:
215
66
 
216
67
  ```ruby
217
68
  TestAssistant.configure(config) do |ta_config|
218
- ta_config.include_json_helpers type: :request
219
-
220
69
  ta_config.debug_failed_responses tag: :debugger
221
70
  end
222
71
  ```
@@ -231,14 +80,6 @@ RSpec.describe 'making some valid request', type: :request do
231
80
  end
232
81
  ```
233
82
 
234
- ## Test suite
235
-
236
- TestAssistant comes with close to complete test coverage. You can run the test suite as follows:
237
-
238
- ```bash
239
- rspec
240
- ```
241
-
242
83
  ## Contributing
243
84
 
244
85
  1. Fork it ( https://github.com/greena13/test_assistant/fork )
@@ -246,7 +87,3 @@ rspec
246
87
  3. Commit your changes (`git commit -am 'Add some feature'`)
247
88
  4. Push to the branch (`git push origin my-new-feature`)
248
89
  5. Create a new Pull Request
249
-
250
- ## Inspirations
251
-
252
- * [CapybaraEmail](https://github.com/DockYard/capybara-email)
@@ -4,8 +4,6 @@ require 'test_assistant/configuration'
4
4
  # Utility module for working with RSpec test suites for Rails or similar applications
5
5
  #
6
6
  # Contains:
7
- # - Expressive syntax for asserting testing emails
8
- # - Sophisticated JSON matchers and diff failure reports
9
7
  # - Rendering and debugging tools for viewing test failures
10
8
  #
11
9
  # @see https://github.com/greena13/test_assistant Test Assistant Github page
@@ -1,6 +1,4 @@
1
1
  module TestAssistant
2
- autoload :Json, 'test_assistant/json/helpers'
3
- autoload :Email, 'test_assistant/email/helpers'
4
2
  autoload :FailureReporter, 'test_assistant/failure_reporter'
5
3
 
6
4
  # Class that provides configuration methods to control what parts of Test Assistant
@@ -19,49 +17,6 @@ module TestAssistant
19
17
  @rspec_config = rspec_config
20
18
  end
21
19
 
22
- # Configures RSpec to include the JSON helpers provided by Test Assistant in the
23
- # the test suite's scope
24
- #
25
- # @see TestAssistant::Json::Helpers
26
- # @see RSpec::Core::Configuration#include
27
- #
28
- # @param [Hash] options RSpec::Core::Configuration#include options
29
- # @return void
30
- #
31
- # @example Include JSON helpers in your RSpec test suite
32
- # RSpec.configure do |config|
33
- # TestAssistant.configure(config) do |ta_config|
34
- # ta_config.include_json_helpers
35
- # end
36
- # end
37
- def include_json_helpers(options = {})
38
- @rspec_config.include Json::Helpers, options
39
- end
40
-
41
- # Configures RSpec to include the email helpers provided by Test Assistant in the
42
- # the test suite's scope and to clear the list of emails sent after each test
43
- #
44
- # @see TestAssistant::Email::Helpers
45
- # @see RSpec::Core::Configuration#include
46
- #
47
- # @param [Hash] options RSpec::Core::Configuration#include options
48
- # @return void
49
- #
50
- # @example Include Email helpers in your RSpec test suite
51
- # RSpec.configure do |config|
52
- # TestAssistant.configure(config) do |ta_config|
53
- # ta_config.include_email_helpers
54
- # end
55
- # end
56
- def include_email_helpers(options = {})
57
- @rspec_config.include Email::Helpers, options
58
-
59
- @rspec_config.after :each, type: :request do
60
- # clear emails after every request spec
61
- ActionMailer::Base.deliveries = []
62
- end
63
- end
64
-
65
20
  # Configures under what circumstances a failing test should open a failure report
66
21
  # detailing the last HTTP request and response in a browser
67
22
  #
@@ -105,12 +60,17 @@ module TestAssistant
105
60
  no_type_filter = !type_filter
106
61
 
107
62
  @rspec_config.after(:each) do |example|
108
- if example.exception
109
- if (example.metadata[tag_filter] || no_tag_filter) && (example.metadata[:type] == type_filter || no_type_filter)
110
- reporter = FailureReporter.report(request, response)
111
- reporter.write
112
- reporter.open
113
- end
63
+ next unless example.exception
64
+
65
+ metadata = example.metadata
66
+ next unless (metadata[tag_filter] || no_tag_filter) && (metadata[:type] == type_filter || no_type_filter)
67
+
68
+ if metadata[:type] == :feature
69
+ save_and_open_page
70
+ else
71
+ reporter = FailureReporter.report(request, response)
72
+ reporter.write
73
+ reporter.open
114
74
  end
115
75
  end
116
76
  end
@@ -153,16 +113,18 @@ module TestAssistant
153
113
  no_type_filter = !type_filter
154
114
 
155
115
  @rspec_config.after(:each) do |example|
156
- if example.exception
157
- if (example.metadata[tag_filter]) && (example.metadata[:type] == type_filter || no_type_filter)
158
- if defined? binding
159
- binding.pry
160
- elsif defined? byebug
161
- byebug
162
- else
163
- debugger
164
- end
165
- end
116
+ next unless example.exception
117
+
118
+ metadata = example.metadata
119
+ next unless (metadata.key?(tag_filter)) && (metadata[:type] == type_filter || no_type_filter)
120
+
121
+ # noinspection RubyResolve
122
+ if defined? binding
123
+ binding.pry
124
+ elsif defined? byebug
125
+ byebug
126
+ else
127
+ debugger
166
128
  end
167
129
  end
168
130
  end
@@ -1,3 +1,3 @@
1
1
  module TestAssistant
2
- VERSION = "0.1.2"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -18,9 +18,6 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency 'capybara', '~> 2.5', '>= 2.5.0'
22
- spec.add_dependency 'hashdiff', '~> 0'
23
-
24
21
  spec.add_development_dependency "bundler", "~> 1.6"
25
22
  spec.add_development_dependency "rake", "~> 0"
26
23
  spec.add_development_dependency "guard", "~> 2.1"
metadata CHANGED
@@ -1,49 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: test_assistant
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 1.0.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: 2018-05-01 00:00:00.000000000 Z
11
+ date: 2019-04-13 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: capybara
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '2.5'
20
- - - ">="
21
- - !ruby/object:Gem::Version
22
- version: 2.5.0
23
- type: :runtime
24
- prerelease: false
25
- version_requirements: !ruby/object:Gem::Requirement
26
- requirements:
27
- - - "~>"
28
- - !ruby/object:Gem::Version
29
- version: '2.5'
30
- - - ">="
31
- - !ruby/object:Gem::Version
32
- version: 2.5.0
33
- - !ruby/object:Gem::Dependency
34
- name: hashdiff
35
- requirement: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '0'
40
- type: :runtime
41
- prerelease: false
42
- version_requirements: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - "~>"
45
- - !ruby/object:Gem::Version
46
- version: '0'
47
13
  - !ruby/object:Gem::Dependency
48
14
  name: bundler
49
15
  requirement: !ruby/object:Gem::Requirement
@@ -132,16 +98,9 @@ files:
132
98
  - Rakefile
133
99
  - lib/test_assistant.rb
134
100
  - lib/test_assistant/configuration.rb
135
- - lib/test_assistant/email/expectation.rb
136
- - lib/test_assistant/email/helpers.rb
137
101
  - lib/test_assistant/failure_reporter.rb
138
- - lib/test_assistant/json/expectation.rb
139
- - lib/test_assistant/json/helpers.rb
140
102
  - lib/test_assistant/version.rb
141
- - spec/email_expectation_spec.rb
142
- - spec/eql_json_spec.rb
143
103
  - spec/spec_helper.rb
144
- - spec/support/email_mock.rb
145
104
  - test_assistant.gemspec
146
105
  homepage: https://github.com/greena13/test_assistant
147
106
  licenses:
@@ -168,7 +127,4 @@ signing_key:
168
127
  specification_version: 4
169
128
  summary: A toolbox for increased testing efficiency with RSpec
170
129
  test_files:
171
- - spec/email_expectation_spec.rb
172
- - spec/eql_json_spec.rb
173
130
  - spec/spec_helper.rb
174
- - spec/support/email_mock.rb
@@ -1,456 +0,0 @@
1
- require 'capybara/rspec'
2
-
3
- module TestAssistant::Email
4
- # Backing class for have_been_sent declarative syntax for specifying email
5
- # expectations. Provides ability to assert emails have been sent in a given test
6
- # that match particular attribute values, such as sender, receiver or contents.
7
- #
8
- # Expected to be used as part of a RSpec test suite and with
9
- # TestAssistant::Email::Helpers#email.
10
- #
11
- # Has two major components:
12
- # - A Builder or chainable methods syntax for constructing arbitrarily specific
13
- # expectations
14
- # - An implementation of the same interface as RSpec custom matcher classes to
15
- # allow evaluating those expectations those expectations
16
- #
17
- # @see TestAssistant::Email::Helpers#email
18
- # @see TestAssistant::Email::Helpers#have_been_sent
19
- class Expectation
20
- MATCHERS = {
21
- to: :to,
22
- from: :from,
23
- with_subject: :subject,
24
- with_text: {
25
- match: ->(_, email, value){ value.all?{|text| email.has_content?(text) }},
26
- actual: ->(_, email){ email.text}
27
- },
28
- matching_selector: {
29
- match: ->(_, email, value){ value.all?{|selector| email.has_selector?(selector) }},
30
- actual: ->(_, email){ email.native },
31
- actual_name: :with_body
32
- },
33
- with_link: {
34
- match: ->(_, email, value){ value.all?{|url| email.has_selector?("a[href='#{url}']") }},
35
- actual: ->(_, email){ email.native },
36
- actual_name: :with_body
37
- },
38
- with_image: {
39
- match: ->(_, email, value){ value.all?{|url| email.has_selector?("img[src='#{url}']") }},
40
- actual: ->(_, email){ email.native },
41
- actual_name: :with_body
42
- }
43
- }
44
-
45
- # Creates a new TestAssistant::Email::Expectation object
46
- #
47
- # @return [TestAssistant::Email::Expectation] new expectation object
48
- def initialize
49
- @expectations = {}
50
- @failure_message = 'Expected email to be sent'
51
- @and_scope = nil
52
- end
53
-
54
- #
55
- # Expectation creation methods
56
- #
57
-
58
- # Allows chaining two assertions on the same email attribute together without having
59
- # to repeat the same method. Intended as syntactical sugar only and is functionally
60
- # equivalent to repeating the method.
61
- #
62
- # @example Asserting an email was sent to two email addresses
63
- # expect(email).to have_been_sent.to('user1@email.com').and('user2@email.com')
64
- #
65
- # @param [Array] arguments parameters to pass to whatever assertion is being
66
- # extended.
67
- # @return [TestAssistant::Email::Expectation] reference to self, to allow for
68
- # further method chaining
69
- def and(*arguments)
70
- if @and_scope
71
- self.send(@and_scope, *arguments)
72
- else
73
- ArugmentError.new("Cannot use and without a proceeding assertion.")
74
- end
75
- end
76
-
77
- # For constructing an assertion that at least one email was sent to a particular
78
- # email address
79
- #
80
- # @example Asserting an email was sent to user@email.com
81
- # expect(email).to have_been_sent.to('user@email.com')
82
- #
83
- # @param [String, Array<String>] email_address address email is expected to be
84
- # sent to. If an array of email addresses, the email is expected to have been
85
- # sent to all of them.
86
- # @return [TestAssistant::Email::Expectation] reference to self, to allow for
87
- # further method chaining
88
- def to(email_address)
89
- @expectations[:to] ||= []
90
-
91
- if email_address.kind_of?(Array)
92
- @expectations[:to] = @expectations[:to].concat(email_address)
93
- else
94
- @expectations[:to] ||= []
95
- @expectations[:to] << email_address
96
- end
97
-
98
- @and_scope = :to
99
-
100
- self
101
- end
102
-
103
- # For constructing an assertion that at least one email was sent from a particular
104
- # email address
105
- #
106
- # @example Asserting an email was sent from admin@site.com
107
- # expect(email).to have_been_sent.from('admin@site.com')
108
- #
109
- # @param [String] email_address address email is expected to be sent from.
110
- # @raise ArgumentError when from is called more than once on the same expectation,
111
- # as an email can only ben sent from a single sender.
112
- # @return [TestAssistant::Email::Expectation] reference to self, to allow for
113
- # further method chaining
114
- def from(email_address)
115
- if @expectations[:from]
116
- raise ArgumentError('An email can only have one from address, but you tried to assert the presence of 2 or more values')
117
- else
118
- @expectations[:from] = email_address
119
- end
120
-
121
- @and_scope = :from
122
-
123
- self
124
- end
125
-
126
- # For constructing an assertion that at least one email was sent with a particular
127
- # subject line
128
- #
129
- # @example Asserting an email was sent with subject line 'Hello'
130
- # expect(email).to have_been_sent.with_subject('Hello')
131
- #
132
- # @param [String] subject Subject line an email is expected to have been sent with
133
- # @raise ArgumentError when with_subject is called more than once on the same
134
- # expectation, as an email can only have one subject line.
135
- # @return [TestAssistant::Email::Expectation] reference to self, to allow for
136
- # further method chaining
137
- def with_subject(subject)
138
- if @expectations[:with_subject]
139
- raise ArgumentError('An email can only have one subject, but you tried to assert the presence of 2 or more values')
140
- else
141
- @expectations[:with_subject] = subject
142
- end
143
-
144
- @and_scope = :with_subject
145
-
146
- self
147
- end
148
-
149
- # For constructing an assertion that at least one email was sent with a particular
150
- # string in the body of the email
151
- #
152
- # @example Asserting an email was sent with the text 'User 1'
153
- # expect(email).to have_been_sent.with_text('User 1')
154
- #
155
- # @param [String] text Text an email is expected to have been sent with in the body
156
- # @return [TestAssistant::Email::Expectation] reference to self, to allow for
157
- # further method chaining
158
- def with_text(text)
159
- @expectations[:with_text] ||= []
160
- @expectations[:with_text].push(text)
161
-
162
- @and_scope = :with_text
163
- self
164
- end
165
-
166
- # For constructing an assertion that at least one email was sent with a body that
167
- # matches a particular CSS selector
168
- #
169
- # @example Asserting an email was sent with a body matching selector '#imporant-div'
170
- # expect(email).to have_been_sent.matching_selector('#imporant-div')
171
- #
172
- # @param [String] selector CSS selector that should match at least one sent
173
- # email's body
174
- # @return [TestAssistant::Email::Expectation] reference to self, to allow for
175
- # further method chaining
176
- def matching_selector(selector)
177
- @expectations[:matching_selector] ||= []
178
- @expectations[:matching_selector].push(selector)
179
-
180
- @and_scope = :matching_selector
181
- self
182
- end
183
-
184
- # For constructing an assertion that at least one email was sent with a link to
185
- # a particular url in the body
186
- #
187
- # @example Asserting an email was sent with a link to http://www.example.com
188
- # expect(email).to have_been_sent.with_link('http://www.example.com')
189
- #
190
- # @param [String] href URL that should appear in at least one sent email's body
191
- # @return [TestAssistant::Email::Expectation] reference to self, to allow for
192
- # further method chaining
193
- def with_link(href)
194
- @expectations[:with_link] ||= []
195
- @expectations[:with_link].push(href)
196
-
197
- @and_scope = :with_link
198
- self
199
- end
200
-
201
- # For constructing an assertion that at least one email was sent with an image
202
- # hosted at a particular URL
203
- #
204
- # @example Asserting an email was sent with the image http://www.example.com/image.png
205
- # expect(email).to have_been_sent.with_link('http://www.example.com/image.png')
206
- #
207
- # @param [String] src URL of the image that should appear in at least one sent
208
- # email's body
209
- # @return [TestAssistant::Email::Expectation] reference to self, to allow for
210
- # further method chaining
211
- def with_image(src)
212
- @expectations[:with_image] ||= []
213
- @expectations[:with_image].push(src)
214
-
215
- @and_scope = :with_image
216
- self
217
- end
218
-
219
- #
220
- # RSpec Matcher methods
221
- #
222
-
223
- # Declares that RSpec should not attempt to diff the actual and expected values
224
- # to put in the failure message. This class takes care of diffing and presenting
225
- # the differences, itself.
226
- # @return [false] always returns false
227
- def diffable?
228
- false
229
- end
230
-
231
- # Whether at least one email was sent during the current test that matches the
232
- # constructed expectation
233
- # @return [Boolean] whether a matching email was sent
234
- def matches?(emails)
235
- @emails = emails
236
-
237
- matching_emails = @emails
238
-
239
- if @expectations.any?
240
- @expectations.each do |attribute, expected|
241
- @failed_attribute = attribute
242
- @failed_expected = expected
243
-
244
- matching_emails =
245
- matching_emails.select do |email|
246
- email_matches?(email, MATCHERS[attribute], expected)
247
- end
248
-
249
- if matching_emails.empty?
250
- return false
251
- end
252
- end
253
-
254
- true
255
- else
256
- @emails.any?
257
- end
258
- end
259
-
260
- # Message to display to StdOut by RSpec if the equality check fails. Includes a
261
- # complete a human-readable summary of the differences between what emails were
262
- # expected to be sent, and what were actually sent (if any). Only used when the
263
- # positive assertion is used, i.e. expect(email).to have_been_sent. For the
264
- # failure message used for negative assertions, i.e.
265
- # expect(email).to_not have_been_sent, see #failure_message_when_negated
266
- #
267
- # @see #failure_message_when_negated
268
- #
269
- # @return [String] message full failure message with explanation of the differences
270
- # between what emails were expected and what was actually sent
271
- def failure_message
272
- field_descs = attribute_descriptions
273
- value_descs = value_descriptions
274
-
275
- base_clause = expectation_description(
276
- 'Expected an email to be sent',
277
- field_descs,
278
- value_descs
279
- )
280
-
281
- if @emails.length == 0
282
- "#{base_clause} However, no emails were sent."
283
- else
284
- email_values = sent_email_values
285
-
286
- if email_values.any?
287
- base_clause + " However, #{email_pluralisation(@emails)} sent #{result_description(field_descs, [to_sentence(email_values)])}."
288
- else
289
- base_clause
290
- end
291
- end
292
- end
293
-
294
- # Failure message to display for negative RSpec assertions, i.e.
295
- # expect(email).to_not have_been_sent. For the failure message displayed for positive
296
- # assertions, see #failure_message.
297
- #
298
- # @see #failure_message
299
- #
300
- # @return [String] message full failure message with explanation of the differences
301
- # between what emails were expected and what was actually sent
302
- def failure_message_when_negated
303
- field_descs = attribute_descriptions(negated: true)
304
- value_descs = value_descriptions(negated: true)
305
-
306
- expectation_description(
307
- 'Expected no emails to be sent',
308
- field_descs,
309
- value_descs
310
- )
311
- end
312
-
313
- private
314
-
315
- def result_description(field_descriptions, values)
316
- to_sentence(
317
- field_descriptions.map.with_index do |field_description, index|
318
- value = values[index]
319
-
320
- if [ 'matching selector', 'with link', 'with image' ].include?(field_description)
321
- "with body #{value}"
322
- else
323
- "#{field_description} #{value}"
324
- end
325
- end
326
- )
327
- end
328
-
329
- def sent_email_values
330
- @emails.inject([]) do |memo, email|
331
-
332
- if [ :matching_selector, :with_link, :with_image ].include?(@failed_attribute)
333
- memo << email_body(email)
334
- else
335
- matcher = MATCHERS[@failed_attribute]
336
-
337
- value =
338
- case matcher
339
- when String, Symbol
340
- email.send(matcher)
341
- when Hash
342
- field_description = matcher[:actual_name] if matcher[:actual_name]
343
- matcher[:actual].(email, parsed_emails(email))
344
- end
345
-
346
- value = value.kind_of?(String) ? "'#{value}'" : value.map{|element| "'#{element}'"}
347
- memo << value
348
- end
349
-
350
- memo
351
- end
352
- end
353
-
354
- def expectation_description(base_clause, field_descriptions, value_descriptions)
355
- description = base_clause
356
-
357
- additional_clauses = []
358
-
359
- field_descriptions.each.with_index do |field_description, index|
360
- clause = ''
361
- clause += " #{field_description}" if field_description.length > 0
362
-
363
- if (value_description = value_descriptions[index])
364
- clause += " #{value_description}"
365
- end
366
-
367
- additional_clauses.push(clause) if clause.length > 0
368
- end
369
-
370
- description + additional_clauses.join('') + '.'
371
- end
372
-
373
- def attribute_descriptions(negated: false)
374
- attributes_to_describe =
375
- if negated
376
- @expectations.keys
377
- else
378
- [ @failed_attribute ]
379
- end
380
-
381
- attributes_to_describe.map do |attribute|
382
- attribute.to_s.gsub('_', ' ')
383
- end
384
- end
385
-
386
- def value_descriptions(negated: false)
387
- values_to_describe =
388
- if negated
389
- @expectations.values
390
- else
391
- [ @failed_expected ]
392
- end
393
-
394
- values_to_describe.map do |value|
395
- case value
396
- when String
397
- "'#{value}'"
398
- when Array
399
- to_sentence(value.map{|val| "'#{val}'"})
400
- else
401
- value
402
- end
403
- end
404
-
405
- end
406
-
407
- def email_pluralisation(emails)
408
- emails.length > 2 ? "#{emails.length} were": "1 was"
409
- end
410
-
411
- def to_sentence(items)
412
- case items.length
413
- when 0, 1
414
- items.join('')
415
- when 2
416
- items.join(' and ')
417
- else
418
- items[0..(items.length-3)].join(', ') + items[(items.length-3)..items.length-1].join(' and ')
419
- end
420
- end
421
-
422
- def parsed_emails(email)
423
- @parsed_emails ||= {}
424
- @parsed_emails[email] ||= parser(email)
425
- @parsed_emails[email]
426
- end
427
-
428
- def parser(email)
429
- Capybara::Node::Simple.new(email_body(email))
430
- end
431
-
432
- def email_body(email)
433
- if email.parts.first
434
- email.parts.first.body.decoded
435
- else
436
- email.body.encoded
437
- end
438
- end
439
-
440
- def email_matches?(email, assertion, expected)
441
-
442
- case assertion
443
- when :to
444
- (expected & email.send(assertion)).length > 0
445
- when String, Symbol
446
- email.send(assertion).include?(expected)
447
- when Hash
448
- assertion[:match].(email, parsed_emails(email), expected)
449
- else
450
- raise RuntimeError.new(
451
- "Unsupported assertion mapping '#{assertion_match}' of type #{assertion_match.class.name}"
452
- )
453
- end
454
- end
455
- end
456
- end