email_spectacular 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0f37914ebc9f8c1fba4ef39afba24d808dce2557
4
+ data.tar.gz: 59dd333158d579bd98ba60c68eb795e9a88d380f
5
+ SHA512:
6
+ metadata.gz: 17185632819ec97f045453a508ad419d4ea9bbb8f669415d070ccb249a118fcdaa7656ef7eea29d26062f5895396fba2cc91e65a8a3af396b2721f90cb96cd25
7
+ data.tar.gz: 1168413e77f916258af5a793dda207dfa0d98c4cda0e055c7e95e944393807faf175618eca0511e0d4083ec459b9ec54a110e886355479d62beaedd7c5f06b97
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,40 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.5
3
+
4
+ Metrics/LineLength:
5
+ Max: 120
6
+
7
+ Metrics/AbcSize:
8
+ Enabled: false
9
+
10
+ Style/Documentation:
11
+ Enabled: false
12
+
13
+ Style/ClassAndModuleChildren:
14
+ Enabled: false
15
+
16
+ Style/RescueModifier:
17
+ Enabled: false
18
+
19
+ Layout/MultilineOperationIndentation:
20
+ Enabled: false
21
+
22
+ Layout/TrailingWhitespace:
23
+ Enabled: false
24
+
25
+ Metrics/PerceivedComplexity:
26
+ Max: 20
27
+
28
+ Metrics/CyclomaticComplexity:
29
+ Max: 20
30
+
31
+ Metrics/MethodLength:
32
+ CountComments: false
33
+ Max: 20
34
+
35
+ Metrics/BlockLength:
36
+ ExcludedMethods:
37
+ - describe
38
+ - context
39
+ - class_eval
40
+
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
4
+ script:
5
+ - bundle exec rspec
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ - README LICENSE
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ guard :rspec, cmd: 'bundle exec rspec' do
4
+ require 'guard/rspec/dsl'
5
+
6
+ dsl = Guard::RSpec::Dsl.new(self)
7
+
8
+ last_run_spec = nil
9
+
10
+ watch(%r{^lib/(.+)\.rb$}) do |match|
11
+ file_path =
12
+ if match[1] == 'lib'
13
+ "spec/lib/#{match[2]}_spec.rb"
14
+ else
15
+ "spec/#{match[2]}_spec.rb"
16
+ end
17
+
18
+ if File.exist?(file_path)
19
+ file_path
20
+ else
21
+ last_run_spec
22
+ end
23
+ end
24
+
25
+ # RSpec files
26
+ rspec = dsl.rspec
27
+
28
+ # noinspection RubyResolve
29
+ watch(rspec.spec_helper) { rspec.spec_dir }
30
+ # noinspection RubyResolve
31
+ watch(rspec.spec_support) { rspec.spec_dir }
32
+ # noinspection RubyResolve
33
+ watch(rspec.spec_files) do |spec|
34
+ # noinspection RubyUnusedLocalVariable
35
+ last_run_spec = spec[0]
36
+ end
37
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2019 Aleck Greenham
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,166 @@
1
+ # EmailSpectacular
2
+
3
+ [![Gem](https://img.shields.io/gem/dt/email_spectacular.svg)]()
4
+ [![Build Status](https://travis-ci.org/greena13/email_spectacular.svg)](https://travis-ci.org/greena13/email_spectacular)
5
+ [![GitHub license](https://img.shields.io/github/license/greena13/email_spectacular.svg)](https://github.com/greena13/email_spectacular/blob/master/LICENSE)
6
+
7
+ High-level email spec helpers for acceptance, feature and request tests.
8
+
9
+ ## What EmailSpectacular is
10
+
11
+ Expressive email assertions that let you succinctly describe when emails should and should not be sent.
12
+
13
+ ### What EmailSpectacular is NOT
14
+
15
+ A library for low-level or unit-testing of ActionMailers.
16
+
17
+ ## Installation
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ ```ruby
22
+ group :test do
23
+ gem 'email_spectacular', require: false
24
+ end
25
+ ```
26
+
27
+ Add `email_spectacular` to your `spec/rails_helper.rb`
28
+
29
+ ```ruby
30
+
31
+ require 'email_spectacular'
32
+
33
+ # ...
34
+
35
+ RSpec.configure do |config|
36
+ # ...
37
+
38
+ 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
+
45
+ email_spectacular_spec_types.each do |spec_type|
46
+ # Include email spectacular syntax in rspec tests
47
+ config.include EmailSpectacular::RSpec, type: spec_type
48
+ end
49
+ end
50
+ ```
51
+
52
+ And then execute:
53
+
54
+ ```bash
55
+ bundle install
56
+ ```
57
+
58
+ ## Usage
59
+
60
+ ### Email receiver address
61
+
62
+ It's possible to assert an email was sent to one or more or more addresses using the following format:
63
+
64
+ ```ruby
65
+ expect(email).to have_been_sent.to('user@email.com')
66
+ ```
67
+
68
+ ### Email sender address
69
+
70
+ Similarly, you can assert an email was sent from an address:
71
+
72
+ ```ruby
73
+ expect(email).to have_been_sent.from('user@email.com')
74
+ ```
75
+
76
+ ### Email subject
77
+
78
+ You can assert an email's subject:
79
+
80
+ ```ruby
81
+ expect(email).to have_been_sent.with_subject('Welcome!')
82
+ ```
83
+
84
+ ### Email body
85
+
86
+ You can assert the body of an email by text:
87
+
88
+ ```ruby
89
+ expect(email).to have_been_sent.with_text('Welcome, user@email.com')
90
+ ```
91
+
92
+ Or using a selector on the email's HTML:
93
+
94
+ ```ruby
95
+ expect(email).to have_been_sent.with_selector('#password')
96
+ ```
97
+
98
+ Or look for links:
99
+
100
+ ```ruby
101
+ expect(email).to have_been_sent.with_link('www.site.com/onboarding/1')
102
+ ```
103
+
104
+ Or images:
105
+
106
+ ```ruby
107
+ expect(email).to have_been_sent.with_image('www.site.com/assets/images/welcome.png')
108
+ ```
109
+
110
+ ### Chaining assertions
111
+
112
+ You can chain any combination of the above that you want for ultra specific assertions:
113
+
114
+
115
+ ```ruby
116
+ expect(email).to have_been_sent
117
+ .to('user@email.com')
118
+ .from('admin@site.com')
119
+ .with_subject('Welcome!')
120
+ .with_text('Welcome, user@email.com')
121
+ .with_selector('#password').and('#username')
122
+ .with_link('www.site.com/onboarding/1')
123
+ .with_image('www.site.com/assets/images/welcome.png')
124
+
125
+ ```
126
+
127
+ You can also chain multiple assertions of the the same type with the `and` method:
128
+
129
+ ```ruby
130
+ expect(email).to have_been_sent
131
+ .with_text('Welcome, user@email.com').and('Thanks for signing up')
132
+ ```
133
+
134
+ ### Asserting emails are NOT sent
135
+
136
+ The `have_sent_email` assertion works with the negative case as well:
137
+
138
+ ```ruby
139
+ expect(email).to_not have_been_sent.with_text('Secret token')
140
+ ```
141
+
142
+ ### Clearing emails
143
+
144
+ 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.
145
+
146
+ If you followed in installation steps above, emails will automatically be cleared between each spec.
147
+
148
+ ## Test suite
149
+
150
+ `email_spectacular` comes with close-to-complete test coverage. You can run the test suite as follows:
151
+
152
+ ```bash
153
+ rspec
154
+ ```
155
+
156
+ ## Contributing
157
+
158
+ 1. Fork it ( https://github.com/greena13/email_spectacular/fork )
159
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
160
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
161
+ 4. Push to the branch (`git push origin my-new-feature`)
162
+ 5. Create a new Pull Request
163
+
164
+ ## Inspirations
165
+
166
+ * [CapybaraEmail](https://github.com/DockYard/capybara-email)
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'email_spectacular/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'email_spectacular'
9
+ spec.version = EmailSpectacular::VERSION
10
+ spec.authors = ['Aleck Greenham']
11
+ spec.email = ['greenhama13@gmail.com']
12
+ spec.summary = 'High-level email spec helpers for acceptance, feature and request ' \
13
+ 'tests'
14
+ spec.description = 'Expressive email assertions that let you succinctly describe when ' \
15
+ 'emails should and should not be sent'
16
+ spec.homepage = 'https://github.com/greena13/email_spectacular'
17
+ spec.license = 'MIT'
18
+
19
+ spec.files = `git ls-files -z`.split("\x0")
20
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
+ spec.test_files = spec.files.grep(%r{^(spec)/})
22
+ spec.require_paths = ['lib']
23
+
24
+ spec.add_dependency 'actionmailer', '>= 0'
25
+ spec.add_dependency 'capybara', '~> 2.5', '>= 2.5.0'
26
+
27
+ spec.add_development_dependency 'bundler', '~> 1.6'
28
+ spec.add_development_dependency 'guard', '~> 2.1'
29
+ spec.add_development_dependency 'guard-rspec', '~> 4.7'
30
+ spec.add_development_dependency 'rake', '~> 0'
31
+ spec.add_development_dependency 'rspec', '>= 3.5.0'
32
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'email_spectacular/version'
4
+
5
+ # High-level email spec helpers for acceptance, feature and request tests.
6
+ #
7
+ # @author Aleck Greenham
8
+ #
9
+ # @see https://github.com/greena13/email_spectacular EmailSpectacular Github page
10
+ module EmailSpectacular
11
+ end
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EmailSpectacular
4
+ # Module containing the domain-specific language for expressing expectations of emails
5
+ #
6
+ # @author Aleck Greenham
7
+ module DSL
8
+ def self.included(base) # rubocop:disable Metrics/MethodLength
9
+ base.class_eval do
10
+ def initialize
11
+ @scopes = {}
12
+ @and_scope = nil
13
+ end
14
+
15
+ # Allows chaining two assertions on the same email attribute together without
16
+ # having to repeat the same method. Intended as syntactical sugar only and is
17
+ # functionally equivalent to repeating the method.
18
+ #
19
+ # @example Asserting an email was sent to two email addresses
20
+ # expect(email).to have_been_sent.to('user1@email.com').and('user2@email.com')
21
+ #
22
+ # @param [Array<String>] arguments parameters to pass to whatever assertion is
23
+ # being extended.
24
+ # @return [self] reference to self, to allow for further method chaining
25
+ def and(*arguments)
26
+ if @and_scope
27
+ send(@and_scope, *arguments)
28
+ else
29
+ ArgumentError.new('Cannot use an and modifier without a proceeding assertion.')
30
+ end
31
+ end
32
+
33
+ # For constructing an assertion that at least one email was sent to a
34
+ # <tt>email_address</tt>
35
+ #
36
+ # @example Asserting an email was sent to user@email.com
37
+ # expect(email).to have_been_sent.to('user@email.com')
38
+ #
39
+ # @param [String, Array<String>] email_address address email is expected to be
40
+ # sent to. If an array of email addresses, the email is expected to have been
41
+ # sent to all of them.
42
+ # @return [self] reference to self, to allow for further method chaining
43
+ def to(email_address)
44
+ @scopes[:to] ||= []
45
+
46
+ if email_address.is_a?(Array)
47
+ @scopes[:to] = @scopes[:to].concat(email_address)
48
+ else
49
+ @scopes[:to] ||= []
50
+ @scopes[:to] << email_address
51
+ end
52
+
53
+ @and_scope = :to
54
+
55
+ self
56
+ end
57
+
58
+ # For constructing an assertion that at least one email was sent from
59
+ # <tt>email_address</tt>.
60
+ #
61
+ # @example Asserting an email was sent from admin@site.com
62
+ # expect(email).to have_been_sent.from('admin@site.com')
63
+ #
64
+ # @param [String] email_address Address email is expected to be sent from.
65
+ # @raise ArgumentError when {#from} is called more than once on the same
66
+ # expectation, as an email can only ben sent from a single sender.
67
+ # @return [self] reference to self, to allow for further method chaining
68
+ def from(email_address)
69
+ if @scopes[:from]
70
+ raise ArgumentError(
71
+ 'An email can only have one from address, but you tried to assert the ' \
72
+ 'presence of 2 or more values.'
73
+ )
74
+ end
75
+
76
+ @scopes[:from] = email_address
77
+ @and_scope = :from
78
+
79
+ self
80
+ end
81
+
82
+ # For constructing an assertion that at least one email was sent with a
83
+ # particular subject line
84
+ #
85
+ # @example Asserting an email was sent with subject line 'Hello'
86
+ # expect(email).to have_been_sent.with_subject('Hello')
87
+ #
88
+ # @param [String] subject Subject line an email is expected to have been sent
89
+ # with
90
+ # @raise ArgumentError when {#with_subject} is called more than once on the
91
+ # same expectation, as an email can only have one subject line.
92
+ # @return [self] reference to self, to allow for further method chaining
93
+ def with_subject(subject)
94
+ if @scopes[:with_subject]
95
+ raise ArgumentError(
96
+ 'An email can only have one subject, but you tried to assert the presence ' \
97
+ 'of 2 or more values.'
98
+ )
99
+ end
100
+
101
+ @scopes[:with_subject] = subject
102
+
103
+ @and_scope = :with_subject
104
+
105
+ self
106
+ end
107
+
108
+ # For constructing an assertion that at least one email was sent with a particular
109
+ # string in the body of the email.
110
+ #
111
+ # @example Asserting an email was sent with the text 'User 1'
112
+ # expect(email).to have_been_sent.with_text('User 1')
113
+ #
114
+ # @param [String] text Text an email is expected to have been sent with in the
115
+ # body
116
+ # @return [self] reference to self, to allow for further method chaining
117
+ def with_text(text)
118
+ @scopes[:with_text] ||= []
119
+ @scopes[:with_text].push(text)
120
+
121
+ @and_scope = :with_text
122
+ self
123
+ end
124
+
125
+ # For constructing an assertion that at least one email was sent with a body that
126
+ # matches a particular CSS selector.
127
+ #
128
+ # @example Asserting an email was sent with a body matching selector '#imporant-div'
129
+ # expect(email).to have_been_sent.matching_selector('#imporant-div')
130
+ #
131
+ # @param [String] selector CSS selector that should match at least one sent
132
+ # email's body
133
+ # @return [EmailSpectacular::Expectation] reference to self, to allow for
134
+ # further method chaining
135
+ def matching_selector(selector)
136
+ @scopes[:matching_selector] ||= []
137
+ @scopes[:matching_selector].push(selector)
138
+
139
+ @and_scope = :matching_selector
140
+ self
141
+ end
142
+
143
+ # For constructing an assertion that at least one email was sent with a link to
144
+ # a particular URL in the body.
145
+ #
146
+ # @example Asserting an email was sent with a link to http://www.example.com
147
+ # expect(email).to have_been_sent.with_link('http://www.example.com')
148
+ #
149
+ # @param [String] href URL that should appear in at least one sent email's body
150
+ # @return [self] reference to self, to allow for further method chaining
151
+ def with_link(href)
152
+ @scopes[:with_link] ||= []
153
+ @scopes[:with_link].push(href)
154
+
155
+ @and_scope = :with_link
156
+ self
157
+ end
158
+
159
+ # For constructing an assertion that at least one email was sent with an image
160
+ # hosted at a particular URL
161
+ #
162
+ # @example Asserting an email was sent with the image http://www.example.com/image.png
163
+ # expect(email).to have_been_sent.with_link('http://www.example.com/image.png')
164
+ #
165
+ # @param [String] src URL of the image that should appear in at least one sent
166
+ # email's body
167
+ # @return [self] reference to self, to allow for further method chaining
168
+ def with_image(src)
169
+ @scopes[:with_image] ||= []
170
+ @scopes[:with_image].push(src)
171
+
172
+ @and_scope = :with_image
173
+ self
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end