email_validator 1.5.0 → 2.2.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
- SHA1:
3
- metadata.gz: 46498bfe9a730667a04d6defd887df5d2db1e2d9
4
- data.tar.gz: 8d82a1910be79a0301fb8bea538e48ce39e1783e
2
+ SHA256:
3
+ metadata.gz: 0bd676f012ed7b109082842ed227bc1881aec6ac24f0e1ed4be24772b206b14f
4
+ data.tar.gz: 6c2f92e0a494f751b690743b15d4d97fd8dd1f9213b8cb2640d6f8f729fca51f
5
5
  SHA512:
6
- metadata.gz: 5021fee86809281a8931171f82cde476dc5aff110c904c0a6d1c1aa57b8c06757dcd77215d28c5c66cd43e27da643b58e241bfb1a930da0d5110f86a976a28a5
7
- data.tar.gz: 90b1b4a2b6b6cc9a7a80edbbf5adabd15ce0bb0218a289da766c40fe3d4ab53de192641cbaf946e769ce3dcf606ff0b7df638ac16b1e7181daf28c8e74d60847
6
+ metadata.gz: 8dd2ab72b087727acec6c7509d3ffa2a51e35074b99113bc8b105c16fc62e9c3288712516e3fc0f15be125d83e0d7db50d368da269672d7ecdddec9ba21f925e
7
+ data.tar.gz: e35ab67f7e7337b1bb1cd04d27d0f47dee128b093cdd6015fdade7967b404441d7abc522dcec18bfda8e682e7ae5418a24ba3a400afa9292b6585e796b644c27
@@ -0,0 +1,85 @@
1
+ # CHANGELOG
2
+
3
+ This file is used to list changes made in `email_validator`.
4
+
5
+ All notable changes to this project will be documented in this file.
6
+ This project adheres to [Semantic Versioning](http://semver.org/).
7
+
8
+ ## 2.2.0 (2020-12-09)
9
+
10
+ * [karlwilbur] - Rename `:strict` -> `:rfc`; `:moderate` -> `:strict`
11
+
12
+ ## 2.1.0 (2020-12-09)
13
+
14
+ * [karlwilbur] - Add linters and commit hooks to validate code prior to commits
15
+ * [karlwilbur] - Add `:mode` config option; values `:loose`, `:moderate`, `:strict`; default to `:loose`
16
+ * [karlwilbur] - Merge in changes from <https://github.com/karlwilbur/email_validator> fork
17
+
18
+ ## 1.9.0.pre (2020-10-14)
19
+
20
+ * [karlwilbur] - Add `require_fqdn` option, require FQDN by default
21
+ * [karlwilbur] - Add support for IPv4 and IPv6 address hosts
22
+ * [karlwilbur] - Add Rubocop, `.editorconfig`; code cleanup/linting
23
+
24
+ ## 1.8.0 (2019-06-14)
25
+
26
+ * [karlwilbur] - Refactor class methods for readability
27
+ * [karlwilbur] - `gemspec` meta updates
28
+ * [karlwilbur] - Use POSIX classes for better performance
29
+ * [karlwilbur] - Refactored tests to check specical characters one at a time
30
+ * [karlwilbur] - Refactored validation regex to be more techincally correct
31
+ * [karlwilbur] - Add this `CHANGELOG`
32
+
33
+ ## 1.7.0 (2019-04-20)
34
+
35
+ * [karlwilbur] - Added test coverage badge to README
36
+ * [karlwilbur] - Added I18n directive to remove warning message in testing
37
+ * [karlwilbur] - Added RFC-2822 reference
38
+ * [karlwilbur] - Ignore local rspec config file
39
+ * [karlwilbur] - Check for invalid double dots in strict mode
40
+ * [karlwilbur] - Updated spec_helper to remove Code Climate Test Reporter; it is to be run separately now
41
+ * [karlwilbur] - Allow leading/trailing whitespace in normal, not strict
42
+ * [karlwilbur] - Added `invalid?` as inverse of `valid?`
43
+ * [karlwilbur] - Add the ability to limit to a domain
44
+ * [karlwilbur] - Removed CodeShip badge
45
+ * [karlwilbur] - Make the dot in the domain part non-conditional
46
+ * [karlwilbur] - Fix domain label pattern to allow numbers per rfc5321
47
+
48
+ ## 1.6.0 (2015-06-14)
49
+
50
+ * [karlwilbur] - Fixed validation to be closer to RFC-5321
51
+ * [karlwilbur] - Updated specs to use Rspec 3 syntax
52
+ * [karlwilbur] - Added unicode suport to validation regexp
53
+ * [karlwilbur] - Added class access to regexp, and `valid?` calss method
54
+ * [karlwilbur] - Simplified code using new methods
55
+ * [karlwilbur] - Added CodeClimate and SimpleCov
56
+ * [karlwilbur] - Updated version and contact info
57
+
58
+ *** Forked from <https://github.com/balexand/email_validator>
59
+
60
+ ## 2.0.1 (2019-03-09)
61
+
62
+ * Add email value to error details [f1sherman #50]
63
+ * CI doesn't test Ruby versions that no longer receive updates [f1sherman #51]
64
+
65
+ ## 2.0.0 (2019-03-02)
66
+
67
+ * Looser validation [#49]
68
+
69
+ ## 1.6.0 (2015-05-12)
70
+
71
+ * Unicode characters support [i7an #24]
72
+
73
+ ## 1.5.0 (2014-12-08)
74
+
75
+ * Add a class method for simpler validation [TiteiKo and cluesque #19]
76
+ * RSpec 3.0 syntax [strivedi183 #17]
77
+ * Create Changes.md
78
+
79
+ ---
80
+
81
+ Check the [Markdown Syntax Guide](http://daringfireball.net/projects/markdown/syntax)
82
+ for help with Markdown.
83
+
84
+ The [Github Flavored Markdown page](http://github.github.com/github-flavored-markdown/)
85
+ describes the differences between markdown on github and standard markdown.
data/README.md CHANGED
@@ -1,6 +1,29 @@
1
- [![Build Status](https://secure.travis-ci.org/balexand/email_validator.png)](http://travis-ci.org/balexand/email_validator)
1
+ # EmailValidator
2
2
 
3
- ## Usage
3
+ [![Build Status](https://travis-ci.com/K-and-R/email_validator.svg?branch=master)](http://travis-ci.com/K-and-R/email_validator)
4
+ [![Code Climate](https://codeclimate.com/github/K-and-R/email_validator/badges/gpa.svg)](https://codeclimate.com/github/K-and-R/email_validator)
5
+ [![Test Coverage](https://codeclimate.com/github/K-and-R/email_validator/badges/coverage.svg)](https://codeclimate.com/github/K-and-R/email_validator/coverage)
6
+
7
+ An email validator for Rails 3+.
8
+
9
+ Supports RFC-2822-compliant and RFC-5321-compliant email validation using RFC-3696 validation.
10
+
11
+ Formerly found at: <https://github.com/balexand/email_validator>
12
+
13
+ ## Validation philosophy
14
+
15
+ The default validation provided by this gem (the `:loose` configuration option)
16
+ is extremely loose. It just checks that there's an `@` with something before and
17
+ after it without any whitespace. See [this article by David Gilbertson](https://hackernoon.com/the-100-correct-way-to-validate-email-addresses-7c4818f24643)
18
+ for an explanation of why.
19
+
20
+ We understand that many use cases require an increased level of validation. This
21
+ is supported by using the `:strict` validation mode. Additionally, the `:rfc`
22
+ RFC-compliant mode will consider technically valid emails address as valid which
23
+ may not be wanted, such as the valid `user` or `user@somehost` addresses. These
24
+ would be valid in `:rfc` mode but not valid in `:loose` or `:strict`.
25
+
26
+ ## Installation
4
27
 
5
28
  Add to your Gemfile:
6
29
 
@@ -10,54 +33,181 @@ gem 'email_validator'
10
33
 
11
34
  Run:
12
35
 
13
- ```
36
+ ```bash
14
37
  bundle install
15
38
  ```
16
39
 
17
- Then add the following to your model:
40
+ ## Usage
41
+
42
+ Add the following to your model:
18
43
 
19
44
  ```ruby
20
- validates :my_email_attribute, :email => true
45
+ validates :my_email_attribute, email: true
21
46
  ```
22
47
 
23
- ## Strict mode
48
+ You may wish to allow domains without a FDQN, like `user@somehost`. While this
49
+ is technically a valid address, it is uncommon to consider such address valid.
50
+ We will consider them valid by default with the `:loose` checking. Disallowed
51
+ by setting `require_fqdn: true` or by enabling `:strict` checking:
52
+
53
+ ```ruby
54
+ validates :my_email_attribute, email: {mode: :strict, require_fqdn: true}
55
+ ```
24
56
 
25
- In order to have stricter validation (according to http://www.remote.org/jochen/mail/info/chars.html) enable strict mode. You can do this globally by adding the following to your Gemfile:
57
+ You can also limit to a single domain (e.g: this might help if, for example, you
58
+ have separate `User` and `AdminUser` models and want to ensure that `AdminUser`
59
+ emails are on a specific domain):
26
60
 
27
61
  ```ruby
28
- gem 'email_validator', :require => 'email_validator/strict'
62
+ validates :my_email_attribute, email: {domain: 'example.com'}
29
63
  ```
30
64
 
31
- Or you can do this in a specific `validates` call:
65
+ ## Configuration
66
+
67
+ Default configuration can be overridden by setting options in `config/initializers/email_validator.rb`:
32
68
 
33
69
  ```ruby
34
- validates :my_email_attribute, :email => {:strict_mode => true}
70
+ if defined?(EmailValidator)
71
+ # To completly override the defaults
72
+ EmailValidator.default_options = {
73
+ allow_nil: false,
74
+ domain: nil,
75
+ require_fqdn: nil,
76
+ mode: :loose
77
+ }
78
+
79
+ # or just a few options
80
+ EmailValidator.default_options.merge!({ domain: 'mydomain.com' })
81
+ end
35
82
  ```
36
83
 
84
+ ### Loose mode
85
+
86
+ This it the default validation mode of this gem. It is intentionally extremely
87
+ loose (see the [Validation Philosophy section](#validation_philosophy) above. It
88
+ just checks that there's an `@` with something before and after it without any
89
+ whitespace.
90
+
91
+ ### Strict mode
92
+
93
+ Enabling `:strict` checking will check for a "normal" email format that would
94
+ be expected in most common everyday usage. Strict mode basically checks for a
95
+ properly sized and formatted mailbox label, a single "@" symbol, and a properly
96
+ sized and formatted FQDN. Enabling `:strict` mode will also enable `:require_fqdn`
97
+ configuration option.
98
+
99
+ Strict mode can be enabled globally by requiring `email_validator/strict` in
100
+ your `Gemfile`, by setting the option in `config/initializers/email_validator.rb`,
101
+ or by specifying the option in a specific `validates` call.
102
+
103
+ * `Gemfile`:
104
+
105
+ ```ruby
106
+ gem 'email_validator', require: 'email_validator/strict'
107
+ ```
108
+
109
+ * `config/initializers/email_validator.rb`:
110
+
111
+ ```ruby
112
+ if defined?(EmailValidator)
113
+ EmailValidator.default_options[:mode] = :strict
114
+ end
115
+ ```
116
+
117
+ * `validates` call:
118
+
119
+ ```ruby
120
+ validates :my_email_attribute, email: {mode: :strict}
121
+ ```
122
+
123
+ ### RFC mode
124
+
125
+ In order to have RFC-compliant validation (according to [http://www.remote.org/jochen/mail/info/chars.html](https://web.archive.org/web/20150508102948/http://www.remote.org/jochen/mail/info/chars.html)),
126
+ enable `:rfc` mode.
127
+
128
+ You can do this globally by requiring `email_validator/rfc` in your `Gemfile`,
129
+ by setting the options in `config/initializers/email_validator.rb`, or you can do
130
+ this in a specific `validates` call.
131
+
132
+ * `Gemfile`:
133
+
134
+ ```ruby
135
+ gem 'email_validator', require: 'email_validator/rfc'
136
+ ```
137
+
138
+ * `config/initializers/email_validator.rb`:
139
+
140
+ ```ruby
141
+ if defined?(EmailValidator)
142
+ EmailValidator.default_options[:mode] = :rfc
143
+ end
144
+ ```
145
+
146
+ * `validates` call:
147
+
148
+ ```ruby
149
+ validates :my_email_attribute, email: {mode: :rfc}
150
+ ```
151
+
37
152
  ## Validation outside a model
38
153
 
39
- If you need to validate an email outside a model, you can get the regexp :
154
+ If you need to validate an email outside a model, you can get the regexp:
40
155
 
41
- ### Normal mode
156
+ ### Loose/default mode
42
157
 
43
158
  ```ruby
44
- EmailValidator.regexp # returns the regex
45
159
  EmailValidator.valid?('narf@example.com') # boolean
46
160
  ```
47
161
 
162
+ ### Requiring a FQDN
163
+
164
+ ```ruby
165
+ EmailValidator.valid?('narf@somehost') # boolean false
166
+ EmailValidator.invalid?('narf@somehost', require_fqdn: false) # boolean true
167
+ ```
168
+
169
+ ### Requiring a specific domain
170
+
171
+ ```ruby
172
+ EmailValidator.valid?('narf@example.com', domain: 'foo.com') # boolean false
173
+ EmailValidator.invalid?('narf@example.com', domain: 'foo.com') # boolean true
174
+ ```
175
+
48
176
  ### Strict mode
49
177
 
50
178
  ```ruby
51
- EmailValidator.regexp(:strict_mode => true)
179
+ EmailValidator.regexp(mode: :strict) # returns the regex
180
+ EmailValidator.valid?('narf@example.com', mode: :strict) # boolean
181
+ ```
182
+
183
+ ### RFC mode
184
+
185
+ ```ruby
186
+ EmailValidator.regexp(mode: :rfc) # returns the regex
187
+ EmailValidator.valid?('narf@example.com', mode: :rfc) # boolean
52
188
  ```
53
189
 
54
190
  ## Thread safety
55
191
 
56
- This gem is thread safe, with one caveat: `EmailValidator.default_options` must be configured before use in a multi-threaded environment. If you configure `default_options` in a Rails initializer file, then you're good to go since initializers are run before worker threads are spawned.
192
+ This gem is thread safe, with one caveat: `EmailValidator.default_options` must
193
+ be configured before use in a multi-threaded environment. If you configure
194
+ `default_options` in a Rails initializer file, then you're good to go since
195
+ initializers are run before worker threads are spawned.
196
+
197
+ ## Alternative gems
198
+
199
+ Do you prefer a different email validation gem? If so, open an issue with a brief
200
+ explanation of how it differs from this gem. I'll add a link to it in this README.
201
+
202
+ * [`email_address`](https://github.com/afair/email_address) (<https://github.com/K-and-R/email_validator/issues/58>)
203
+ * [`email_verifier`](https://github.com/kamilc/email_verifier) (<https://github.com/K-and-R/email_validator/issues/65>)
57
204
 
58
- ## Credit
205
+ ## Maintainers
59
206
 
60
- Based on http://thelucid.com/2010/01/08/sexy-validation-in-edge-rails-rails-3
207
+ All thanks is given to [Brian Alexander (balexand)](https://github.com/balexand)
208
+ for is initial work on this gem.
61
209
 
62
- Regular Expression based on http://fightingforalostcause.net/misc/2006/compare-email-regex.php tests.
210
+ Currently maintained by:
63
211
 
212
+ * Karl Wilbur (<https://github.com/karlwilbur>)
213
+ * K&R Software (<https://github.com/K-and-R>)
@@ -1,28 +1,101 @@
1
1
  # Based on work from http://thelucid.com/2010/01/08/sexy-validation-in-edge-rails-rails-3/
2
2
  class EmailValidator < ActiveModel::EachValidator
3
- @@default_options = {}
3
+ # rubocop:disable Style/ClassVars
4
+ @@default_options = {
5
+ :allow_nil => false,
6
+ :domain => nil,
7
+ :require_fqdn => nil,
8
+ :mode => :loose
9
+ }
10
+ # rubocop:enable Style/ClassVars
4
11
 
5
- def self.regexp(options = {})
6
- options = default_options.merge(options)
12
+ class << self
13
+ def default_options
14
+ @@default_options
15
+ end
7
16
 
8
- name_validation = options[:strict_mode] ? "-a-z0-9+._" : "^@\\s"
17
+ def valid?(value, options = {})
18
+ options = parse_options(options)
19
+ return true if value.nil? && options[:allow_nil] == true
20
+ return false if value.nil?
21
+ !!(value =~ regexp(options))
22
+ end
9
23
 
10
- /\A\s*([#{name_validation}]{1,64})@((?:[-a-z0-9]+\.)+[a-z]{2,})\s*\z/i
11
- end
24
+ def invalid?(value, options = {})
25
+ !valid?(value, options)
26
+ end
12
27
 
13
- def self.valid?(value, options = {})
14
- !!(value =~ regexp(options))
15
- end
28
+ # Refs:
29
+ # https://tools.ietf.org/html/rfc2822 : 3.2. Lexical Tokens, 3.4.1. Addr-spec specification
30
+ # https://tools.ietf.org/html/rfc5321 : 4.1.2. Command Argument Syntax
31
+ def regexp(options = {})
32
+ options = parse_options(options)
33
+ if options[:mode] == :loose
34
+ return /\A[^\s]+@[^\s]+\z/ if options[:domain].nil?
35
+ return /\A[^\s]+@#{domain_pattern(options)}\z/
36
+ end
37
+ /\A(?>#{local_part_pattern})@#{domain_pattern(options)}\z/i
38
+ end
39
+
40
+ protected
41
+
42
+ def alpha
43
+ '[[:alpha:]]'
44
+ end
45
+
46
+ def alnum
47
+ '[[:alnum:]]'
48
+ end
49
+
50
+ def alnumhy
51
+ "(?:#{alnum}|-)"
52
+ end
53
+
54
+ def ipv4
55
+ '\d{1,3}(?:\.\d{1,3}){3}'
56
+ end
16
57
 
17
- def self.default_options
18
- @@default_options
58
+ def ipv6
59
+ # only supporting full IPv6 addresses right now
60
+ 'IPv6:[[:xdigit:]]{1,4}(?::[[:xdigit:]]{1,4}){7}'
61
+ end
62
+
63
+ def address_literal
64
+ "\\[(?:#{ipv4}|#{ipv6})\\]"
65
+ end
66
+
67
+ def label_pattern
68
+ "#{alpha}(?:#{alnumhy}{,62}#{alnum})?"
69
+ end
70
+
71
+ def atom_char
72
+ # The `atext` spec
73
+ # We are looking at this without whitespace; no whitespace support here
74
+ "[-#{alpha}#{alnum}+_!\"'#$%^&*{}/=?`|~]"
75
+ end
76
+
77
+ def local_part_pattern
78
+ # the `dot-atom-text` spec, but with a 64 character limit
79
+ "#{atom_char}(?:\\.?#{atom_char}){,63}"
80
+ end
81
+
82
+ def domain_pattern(options)
83
+ return options[:domain].sub(/\./, '\.') if options[:domain].present?
84
+ return "(?:#{label_pattern}\\.)+#{label_pattern}" if options[:require_fqdn]
85
+ "(?:#{address_literal}|(?:#{label_pattern}\\.)*#{label_pattern})"
86
+ end
87
+
88
+ private
89
+
90
+ def parse_options(options)
91
+ # `:strict` mode enables `:require_fqdn`, unless it is already explicitly disabled
92
+ options[:require_fqdn] = true if options[:require_fqdn].nil? && options[:mode] == :strict
93
+ default_options.merge(options)
94
+ end
19
95
  end
20
96
 
21
97
  def validate_each(record, attribute, value)
22
98
  options = @@default_options.merge(self.options)
23
-
24
- unless self.class.valid?(value, self.options)
25
- record.errors.add(attribute, options[:message] || :invalid)
26
- end
99
+ record.errors.add(attribute, options[:message] || :invalid) unless self.class.valid?(value, options)
27
100
  end
28
101
  end
@@ -1,198 +1,884 @@
1
1
  require 'spec_helper'
2
2
 
3
- class TestUser < TestModel
3
+ class DefaultUser < TestModel
4
4
  validates :email, :email => true
5
5
  end
6
6
 
7
7
  class StrictUser < TestModel
8
- validates :email, :email => {:strict_mode => true}
8
+ validates :email, :email => { :mode => :strict }
9
9
  end
10
10
 
11
- class TestUserAllowsNil < TestModel
12
- validates :email, :email => {:allow_nil => true}
11
+ class RfcUser < TestModel
12
+ validates :email, :email => { :mode => :rfc }
13
13
  end
14
14
 
15
- class TestUserAllowsNilFalse < TestModel
16
- validates :email, :email => {:allow_nil => false}
15
+ class AllowNilDefaultUser < TestModel
16
+ validates :email, :email => { :allow_nil => true }
17
17
  end
18
18
 
19
- class TestUserWithMessage < TestModel
20
- validates :email_address, :email => {:message => 'is not looking very good!'}
19
+ class AllowNilStrictUser < TestModel
20
+ validates :email, :email => { :allow_nil => true, :mode => :strict }
21
21
  end
22
22
 
23
- describe EmailValidator do
23
+ class AllowNilRfcUser < TestModel
24
+ validates :email, :email => { :allow_nil => true, :mode => :rfc }
25
+ end
24
26
 
25
- describe "validation" do
26
- context "given the valid emails" do
27
- [
28
- "a+b@plus-in-local.com",
29
- "a_b@underscore-in-local.com",
30
- "user@example.com",
31
- " user@example.com ",
32
- "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@letters-in-local.org",
33
- "01234567890@numbers-in-local.net",
34
- "a@single-character-in-local.org",
35
- "one-character-third-level@a.example.com",
36
- "single-character-in-sld@x.org",
37
- "local@dash-in-sld.com",
38
- "letters-in-sld@123.com",
39
- "one-letter-sld@x.org",
40
- "uncommon-tld@sld.museum",
41
- "uncommon-tld@sld.travel",
42
- "uncommon-tld@sld.mobi",
43
- "country-code-tld@sld.uk",
44
- "country-code-tld@sld.rw",
45
- "local@sld.newTLD",
46
- "local@sub.domains.com",
47
- "aaa@bbb.co.jp",
48
- "nigel.worthington@big.co.uk",
49
- "f@c.com",
50
- "areallylongnameaasdfasdfasdfasdf@asdfasdfasdfasdfasdf.ab.cd.ef.gh.co.ca"
51
- ].each do |email|
27
+ class DisallowNilDefaultUser < TestModel
28
+ validates :email, :email => { :allow_nil => false }
29
+ end
30
+
31
+ class DisallowNilStrictUser < TestModel
32
+ validates :email, :email => { :allow_nil => false, :mode => :strict }
33
+ end
34
+
35
+ class DisallowNilRfcUser < TestModel
36
+ validates :email, :email => { :allow_nil => false, :mode => :rfc }
37
+ end
38
+
39
+ class DomainStrictUser < TestModel
40
+ validates :email, :email => { :domain => 'example.com', :mode => :strict }
41
+ end
52
42
 
53
- it "#{email.inspect} should be valid" do
54
- expect(TestUser.new(:email => email)).to be_valid
43
+ class DomainRfcUser < TestModel
44
+ validates :email, :email => { :domain => 'example.com', :mode => :rfc }
45
+ end
46
+
47
+ class NonFqdnStrictUser < TestModel
48
+ validates :email, :email => { :require_fqdn => false, :mode => :strict }
49
+ end
50
+
51
+ class NonFqdnRfcUser < TestModel
52
+ validates :email, :email => { :require_fqdn => false, :mode => :rfc }
53
+ end
54
+
55
+ class DefaultUserWithMessage < TestModel
56
+ validates :email_address, :email => { :message => 'is not looking very good!' }
57
+ end
58
+
59
+ RSpec.describe EmailValidator do
60
+ describe 'validation' do
61
+ valid_special_chars = {
62
+ :ampersand => '&',
63
+ :asterisk => '*',
64
+ :backtick => '`',
65
+ :braceleft => '{',
66
+ :braceright => '}',
67
+ :caret => '^',
68
+ :dollar => '$',
69
+ :equals => '=',
70
+ :exclaim => '!',
71
+ :hash => '#',
72
+ :hyphen => '-',
73
+ :percent => '%',
74
+ :plus => '+',
75
+ :pipe => '|',
76
+ :question => '?',
77
+ :quotedouble => '"',
78
+ :quotesingle => "'",
79
+ :slash => '/',
80
+ :tilde => '~',
81
+ :underscore => '_'
82
+ }
83
+
84
+ invalid_special_chars = {
85
+ :backslash => '\\',
86
+ :braketleft => '[',
87
+ :braketright => ']',
88
+ :colon => ':',
89
+ :comma => ',',
90
+ :greater => '>',
91
+ :lesser => '<',
92
+ :parenleft => '(',
93
+ :parenright => ')',
94
+ :semicolon => ';'
95
+ }
96
+
97
+ valid_includable = valid_special_chars.merge({ :dot => '.' })
98
+ valid_beginable = valid_special_chars
99
+ valid_endable = valid_special_chars
100
+ invalid_includable = { :at => '@' }
101
+ whitespace = { :newline => "\n", :tab => "\t", :carriage_return => "\r", :space => ' ' }
102
+ strictly_invalid_includable = invalid_special_chars
103
+ strictly_invalid_beginable = strictly_invalid_includable.merge({ :dot => '.' })
104
+ strictly_invalid_endable = strictly_invalid_beginable
105
+ domain_invalid_beginable = invalid_special_chars.merge(valid_special_chars)
106
+ domain_invalid_endable = domain_invalid_beginable
107
+ domain_invalid_includable = domain_invalid_beginable.reject { |k, _v| k == :hyphen }
108
+
109
+ # rubocop:disable Layout/BlockEndNewline, Layout/MultilineBlockLayout, Layout/MultilineMethodCallBraceLayout, Style/BlockDelimiters, Style/MultilineBlockChain
110
+ context 'when given the valid email' do
111
+ valid_includable.map { |k, v| [
112
+ "include-#{v}-#{k}@valid-characters-in-local.dev"
113
+ ]}.concat(valid_beginable.map { |k, v| [
114
+ "#{v}start-with-#{k}@valid-characters-in-local.dev"
115
+ ]}).concat(valid_endable.map { |k, v| [
116
+ "end-with-#{k}-#{v}@valid-characters-in-local.dev"
117
+ ]}).concat([
118
+ 'a+b@plus-in-local.com',
119
+ 'a_b@underscore-in-local.com',
120
+ 'user@example.com',
121
+ 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@letters-in-local.dev',
122
+ '01234567890@numbers-in-local.dev',
123
+ 'a@single-character-in-local.dev',
124
+ 'one-character-third-level@a.example.com',
125
+ 'single-character-in-sld@x.dev',
126
+ 'local@dash-in-sld.com',
127
+ 'numbers-in-sld@s123.com',
128
+ 'one-letter-sld@x.dev',
129
+ 'uncommon-tld@sld.museum',
130
+ 'uncommon-tld@sld.travel',
131
+ 'uncommon-tld@sld.mobi',
132
+ 'country-code-tld@sld.uk',
133
+ 'country-code-tld@sld.rw',
134
+ 'local@sld.newTLD',
135
+ 'local@sub.domains.com',
136
+ 'aaa@bbb.co.jp',
137
+ 'nigel.worthington@big.co.uk',
138
+ 'f@c.com',
139
+ 'f@s.c',
140
+ 'someuser@somehost.somedomain',
141
+ 'mixed-1234-in-{+^}-local@sld.dev',
142
+ 'partially."quoted"@sld.com',
143
+ 'areallylongnameaasdfasdfasdfasdf@asdfasdfasdfasdfasdf.ab.cd.ef.gh.co.ca'
144
+ ]).flatten.each do |email|
145
+ context 'when using defaults' do
146
+ it "'#{email}' should be valid" do
147
+ expect(DefaultUser.new(:email => email)).to be_valid
148
+ end
149
+
150
+ it "'#{email}' should be valid using EmailValidator.valid?" do
151
+ expect(described_class).to be_valid(email)
152
+ end
153
+
154
+ it "'#{email}' should not be invalid using EmailValidator.invalid?" do
155
+ expect(described_class).not_to be_invalid(email)
156
+ end
157
+
158
+ it "'#{email}' should match the regexp" do
159
+ expect(!!(email.strip =~ described_class.regexp)).to be(true)
160
+ end
55
161
  end
56
162
 
57
- it "#{email.inspect} should be valid in strict_mode" do
58
- expect(StrictUser.new(:email => email)).to be_valid
163
+ context 'when in `:strict` mode' do
164
+ it "'#{email}' should be valid" do
165
+ expect(StrictUser.new(:email => email)).to be_valid
166
+ end
167
+
168
+ it "'#{email}' should be valid using EmailValidator.valid?" do
169
+ expect(described_class).to be_valid(email, :mode => :strict)
170
+ end
171
+
172
+ it "'#{email}' should not be invalid using EmailValidator.valid?" do
173
+ expect(described_class).not_to be_invalid(email, :mode => :strict)
174
+ end
175
+
176
+ it "'#{email}' should match the regexp" do
177
+ expect(!!(email.strip =~ described_class.regexp(:mode => :strict))).to be(true)
178
+ end
59
179
  end
60
180
 
61
- it "#{email.inspect} should match the regexp" do
62
- expect(email =~ EmailValidator.regexp).to be_truthy
181
+ context 'when in `:rfc` mode' do
182
+ it "'#{email}' should be valid" do
183
+ expect(RfcUser.new(:email => email)).to be_valid
184
+ end
185
+
186
+ it "'#{email}' should be valid using EmailValidator.valid?" do
187
+ expect(described_class).to be_valid(email, :mode => :rfc)
188
+ end
189
+
190
+ it "'#{email}' should not be invalid using EmailValidator.invalid?" do
191
+ expect(described_class).not_to be_invalid(email, :mode => :rfc)
192
+ end
193
+
194
+ it "'#{email}' should match the regexp" do
195
+ expect(!!(email.strip =~ described_class.regexp(:mode => :rfc))).to be(true)
196
+ end
63
197
  end
198
+ end
199
+ end
64
200
 
65
- it "#{email.inspect} should match the strict regexp" do
66
- expect(email =~ EmailValidator.regexp(:strict_mode => true)).to be_truthy
201
+ context 'when given the valid host-only email' do
202
+ [
203
+ 'f@s',
204
+ 'user@localhost',
205
+ 'someuser@somehost'
206
+ ].each do |email|
207
+ context 'when using defaults' do
208
+ it "'#{email}' should be valid" do
209
+ expect(DefaultUser.new(:email => email)).to be_valid
210
+ end
211
+
212
+ it "'#{email}' should be valid using EmailValidator.valid?" do
213
+ expect(described_class).to be_valid(email)
214
+ end
215
+
216
+ it "'#{email}' should not be invalid using EmailValidator.invalid?" do
217
+ expect(described_class).not_to be_invalid(email)
218
+ end
219
+
220
+ it "'#{email}' should match the regexp" do
221
+ expect(!!(email.strip =~ described_class.regexp)).to be(true)
222
+ end
67
223
  end
68
224
 
69
- it "#{email.inspect} should pass the class tester" do
70
- expect(EmailValidator.valid?(email)).to be_truthy
225
+ context 'when in `:strict` mode' do
226
+ it "'#{email}' should not be valid" do
227
+ expect(StrictUser.new(:email => email)).not_to be_valid
228
+ end
229
+
230
+ it "'#{email}' should not be valid using EmailValidator.valid?" do
231
+ expect(described_class).not_to be_valid(email, :mode => :strict)
232
+ end
233
+
234
+ it "'#{email}' should be invalid using EmailValidator.invalid?" do
235
+ expect(described_class).to be_invalid(email, :mode => :strict)
236
+ end
237
+
238
+ it "'#{email}' should not match the regexp" do
239
+ expect(!!(email.strip =~ described_class.regexp(:mode => :strict))).to be(false)
240
+ end
71
241
  end
72
242
 
73
- end
243
+ context 'when in `:rfc` mode' do
244
+ it "'#{email}' should be valid" do
245
+ expect(RfcUser.new(:email => email)).to be_valid
246
+ end
247
+
248
+ it "'#{email}' should be valid using EmailValidator.valid?" do
249
+ expect(described_class).to be_valid(email, :mode => :rfc)
250
+ end
74
251
 
252
+ it "'#{email}' should not be invalid using EmailValidator.invalid?" do
253
+ expect(described_class).not_to be_invalid(email, :mode => :rfc)
254
+ end
255
+
256
+ it "'#{email}' should match the regexp" do
257
+ expect(!!(email.strip =~ described_class.regexp(:mode => :rfc))).to be(true)
258
+ end
259
+ end
260
+ end
75
261
  end
76
262
 
77
- context "given the invalid emails" do
263
+ context 'when given the valid IP address email' do
78
264
  [
79
- "",
80
- "f@s",
81
- "f@s.c",
82
- "@bar.com",
83
- "test@example.com@example.com",
84
- "test@",
85
- "@missing-local.org",
86
- "a b@space-in-local.com",
87
- "! \#$%\`|@invalid-characters-in-local.org",
88
- "<>@[]\`|@even-more-invalid-characters-in-local.org",
89
- "missing-sld@.com",
90
- "invalid-characters-in-sld@! \"\#$%(),/;<>_[]\`|.org",
91
- "missing-dot-before-tld@com",
92
- "missing-tld@sld.",
93
- " ",
94
- "missing-at-sign.net",
95
- "unbracketed-IP@127.0.0.1",
96
- "invalid-ip@127.0.0.1.26",
97
- "another-invalid-ip@127.0.0.256",
98
- "IP-and-port@127.0.0.1:25",
99
- "the-local-part-is-invalid-if-it-is-longer-than-sixty-four-characters@sld.net",
100
- "user@example.com\n<script>alert('hello')</script>"
265
+ 'bracketed-IP@[127.0.0.1]',
266
+ 'bracketed-and-labeled-IPv6@[IPv6:abcd:ef01:1234:5678:9abc:def0:1234:5678]'
101
267
  ].each do |email|
268
+ context 'when using defaults' do
269
+ it "'#{email}' should be valid" do
270
+ expect(DefaultUser.new(:email => email)).to be_valid
271
+ end
272
+
273
+ it "'#{email}' should be valid using EmailValidator.valid?" do
274
+ expect(described_class).to be_valid(email)
275
+ end
276
+
277
+ it "'#{email}' should not be invalid using EmailValidator.invalid?" do
278
+ expect(described_class).not_to be_invalid(email)
279
+ end
280
+
281
+ it "'#{email}' should match the regexp" do
282
+ expect(!!(email.strip =~ described_class.regexp)).to be(true)
283
+ end
284
+ end
285
+
286
+ context 'when in `:strict` mode' do
287
+ it "'#{email}' should not be valid" do
288
+ expect(StrictUser.new(:email => email)).not_to be_valid
289
+ end
290
+
291
+ it "'#{email}' should not be valid using EmailValidator.valid?" do
292
+ expect(described_class).not_to be_valid(email, :mode => :strict)
293
+ end
294
+
295
+ it "'#{email}' should be invalid using EmailValidator.invalid?" do
296
+ expect(described_class).to be_invalid(email, :mode => :strict)
297
+ end
102
298
 
103
- it "#{email.inspect} should not be valid" do
104
- expect(TestUser.new(:email => email)).not_to be_valid
299
+ it "'#{email}' should not match the regexp" do
300
+ expect(!!(email.strip =~ described_class.regexp(:mode => :strict))).to be(false)
301
+ end
105
302
  end
106
303
 
107
- it "#{email.inspect} should not be valid in strict_mode" do
108
- expect(StrictUser.new(:email => email)).not_to be_valid
304
+ context 'when in `:rfc` mode' do
305
+ it "'#{email}' should be valid" do
306
+ expect(RfcUser.new(:email => email)).to be_valid
307
+ end
308
+
309
+ it "'#{email}' should be valid using EmailValidator.valid?" do
310
+ expect(described_class).to be_valid(email, :mode => :rfc)
311
+ end
312
+
313
+ it "'#{email}' should not be invalid using EmailValidator.invalid?" do
314
+ expect(described_class).not_to be_invalid(email, :mode => :rfc)
315
+ end
316
+
317
+ it "'#{email}' should match the regexp" do
318
+ expect(!!(email.strip =~ described_class.regexp(:mode => :rfc))).to be(true)
319
+ end
320
+ end
321
+ end
322
+ end
323
+
324
+ context 'when given the invalid email' do
325
+ invalid_includable.map { |k, v| [
326
+ "include-#{v}-#{k}@invalid-characters-in-local.dev"
327
+ ]}.concat(domain_invalid_beginable.map { |k, v| [
328
+ "start-with-#{k}@#{v}invalid-characters-in-domain.dev"
329
+ ]}).concat(domain_invalid_endable.map { |k, v| [
330
+ "end-with-#{k}@invalid-characters-in-domain#{v}.dev"
331
+ ]}).concat(domain_invalid_includable.map { |k, v| [
332
+ "include-#{k}@invalid-characters-#{v}-in-domain.dev"
333
+ ]}).concat([
334
+ 'test@example.com@example.com',
335
+ 'missing-sld@.com',
336
+ 'missing-tld@sld.',
337
+ 'only-numbers-in-domain-label@sub.123.com',
338
+ 'only-numbers-in-domain-label@123.example.com',
339
+ 'unbracketed-IPv6@abcd:ef01:1234:5678:9abc:def0:1234:5678',
340
+ 'unbracketed-and-labled-IPv6@IPv6:abcd:ef01:1234:5678:9abc:def0:1234:5678',
341
+ 'bracketed-and-unlabeled-IPv6@[abcd:ef01:1234:5678:9abc:def0:1234:5678]',
342
+ 'unbracketed-IPv4@127.0.0.1',
343
+ 'invalid-IPv4@127.0.0.1.26',
344
+ 'another-invalid-IPv4@127.0.0.256',
345
+ 'IPv4-and-port@127.0.0.1:25',
346
+ 'host-beginning-with-dot@.example.com',
347
+ 'domain-beginning-with-dash@-example.com',
348
+ 'domain-ending-with-dash@example-.com',
349
+ 'the-local-part-is-invalid-if-it-is-longer-than-sixty-four-characters@sld.dev',
350
+ "user@example.com<script>alert('hello')</script>"
351
+ ]).flatten.each do |email|
352
+ context 'when using defaults' do
353
+ it "'#{email}' should be valid" do
354
+ expect(DefaultUser.new(:email => email)).to be_valid
355
+ end
356
+
357
+ it "'#{email}' should be valid using EmailValidator.valid?" do
358
+ expect(described_class).to be_valid(email)
359
+ end
360
+
361
+ it "'#{email}' should not be invalid using EmailValidator.invalid?" do
362
+ expect(described_class).not_to be_invalid(email)
363
+ end
364
+
365
+ it "'#{email}' should match the regexp" do
366
+ expect(!!(email.strip =~ described_class.regexp)).to be(true)
367
+ end
368
+ end
369
+
370
+ context 'when in `:strict` mode' do
371
+ it "'#{email}' should not be valid" do
372
+ expect(StrictUser.new(:email => email)).not_to be_valid
373
+ end
374
+
375
+ it "'#{email}' should not be valid using EmailValidator.valid?" do
376
+ expect(described_class).not_to be_valid(email, :mode => :strict)
377
+ end
378
+
379
+ it "'#{email}' should be invalid using EmailValidator.invalid?" do
380
+ expect(described_class).to be_invalid(email, :mode => :strict)
381
+ end
382
+
383
+ it "'#{email}' should not match the regexp" do
384
+ expect(!!(email.strip =~ described_class.regexp(:mode => :strict))).to be(false)
385
+ end
109
386
  end
110
387
 
111
- it "#{email.inspect} should not match the regexp" do
112
- expect(email =~ EmailValidator.regexp).to be_falsy
388
+ context 'when in `:rfc` mode' do
389
+ it "'#{email}' should not be valid" do
390
+ expect(RfcUser.new(:email => email)).not_to be_valid
391
+ end
392
+
393
+ it "'#{email}' should not be valid using EmailValidator.valid?" do
394
+ expect(described_class).not_to be_valid(email, :mode => :rfc)
395
+ end
396
+
397
+ it "'#{email}' should be invalid using EmailValidator.invalid?" do
398
+ expect(described_class).to be_invalid(email, :mode => :rfc)
399
+ end
400
+
401
+ it "'#{email}' should not match the regexp" do
402
+ expect(!!(email.strip =~ described_class.regexp(:mode => :rfc))).to be(false)
403
+ end
113
404
  end
405
+ end
406
+ end
114
407
 
115
- it "#{email.inspect} should not match the strict regexp" do
116
- expect(email =~ EmailValidator.regexp(:strict_mode => true)).to be_falsy
408
+ context 'when given the invalid email with whitespace in parts' do
409
+ whitespace.map { |k, v| [
410
+ "include-#{v}-#{k}@invalid-characters-in-local.dev"
411
+ ]}.concat([
412
+ 'foo @bar.com',
413
+ "foo\t@bar.com",
414
+ "foo\n@bar.com",
415
+ "foo\r@bar.com",
416
+ 'test@ example.com',
417
+ 'user@example .com',
418
+ "user@example\t.com",
419
+ "user@example\n.com",
420
+ "user@example\r.com",
421
+ 'user@exam ple.com',
422
+ "user@exam\tple.com",
423
+ "user@exam\nple.com",
424
+ "user@exam\rple.com",
425
+ 'us er@example.com',
426
+ "us\ter@example.com",
427
+ "us\ner@example.com",
428
+ "us\rer@example.com",
429
+ "user@example.com\n<script>alert('hello')</script>",
430
+ "user@example.com\t<script>alert('hello')</script>",
431
+ "user@example.com\r<script>alert('hello')</script>",
432
+ "user@example.com <script>alert('hello')</script>",
433
+ ' leading-whitespace@example.com',
434
+ 'trailing-whitespace@example.com ',
435
+ ' leading-and-trailing-whitespace@example.com ',
436
+ ' user-with-leading-whitespace-space@example.com',
437
+ "\tuser-with-leading-whitespace-tab@example.com",
438
+ "
439
+ user-with-leading-whitespace-newline@example.com",
440
+ 'domain-with-trailing-whitespace-space@example.com ',
441
+ "domain-with-trailing-whitespace-tab@example.com\t",
442
+ "domain-with-trailing-whitespace-newline@example.com
443
+ "
444
+ ]).flatten.each do |email|
445
+ context 'when using defaults' do
446
+ it "'#{email}' should not be valid" do
447
+ expect(DefaultUser.new(:email => email)).not_to be_valid
448
+ end
449
+
450
+ it "'#{email}' should not be valid using EmailValidator.valid?" do
451
+ expect(described_class).not_to be_valid(email)
452
+ end
453
+
454
+ it "'#{email}' should be invalid using EmailValidator.invalid?" do
455
+ expect(described_class).to be_invalid(email)
456
+ end
457
+
458
+ it "'#{email}' should not match the regexp" do
459
+ expect(!!(email =~ described_class.regexp)).to be(false)
460
+ end
117
461
  end
118
462
 
119
- it "#{email.inspect} should fail the class tester" do
120
- expect(EmailValidator.valid?(email)).to be_falsy
463
+ context 'when in `:strict` mode' do
464
+ it "'#{email}' should not be valid" do
465
+ expect(StrictUser.new(:email => email)).not_to be_valid
466
+ end
467
+
468
+ it "'#{email}' should not be valid using EmailValidator.valid?" do
469
+ expect(described_class).not_to be_valid(email, :mode => :strict)
470
+ end
471
+
472
+ it "'#{email}' should be invalid using EmailValidator.invalid?" do
473
+ expect(described_class).to be_invalid(email, :mode => :strict)
474
+ end
475
+
476
+ it "'#{email}' should not match the regexp" do
477
+ expect(!!(email =~ described_class.regexp(:mode => :strict))).to be(false)
478
+ end
121
479
  end
122
480
 
481
+ context 'when in `:rfc` mode' do
482
+ it "'#{email}' should not be valid" do
483
+ expect(RfcUser.new(:email => email)).not_to be_valid
484
+ end
485
+
486
+ it "'#{email}' should not be valid using EmailValidator.valid?" do
487
+ expect(described_class).not_to be_valid(email, :mode => :rfc)
488
+ end
489
+
490
+ it "'#{email}' should be invalid using EmailValidator.invalid?" do
491
+ expect(described_class).to be_invalid(email, :mode => :rfc)
492
+ end
493
+
494
+ it "'#{email}' should not match the regexp" do
495
+ expect(!!(email =~ described_class.regexp(:mode => :rfc))).to be(false)
496
+ end
497
+ end
123
498
  end
124
499
  end
125
500
 
126
- context "given the emails that should be invalid in strict_mode but valid in normal mode" do
501
+ context 'when given the invalid email with missing parts' do
127
502
  [
128
- "hans,peter@example.com",
129
- "hans(peter@example.com",
130
- "hans)peter@example.com",
131
- "partially.\"quoted\"@sld.com",
132
- "&'*+-./=?^_{}~@other-valid-characters-in-local.net",
133
- "mixed-1234-in-{+^}-local@sld.net"
503
+ '',
504
+ '@bar.com',
505
+ 'test@',
506
+ '@missing-local.dev',
507
+ ' ',
508
+ 'missing-at-sign.dev'
134
509
  ].each do |email|
510
+ context 'when using defaults' do
511
+ it "'#{email}' should not be valid" do
512
+ expect(DefaultUser.new(:email => email)).not_to be_valid
513
+ end
514
+
515
+ it "'#{email}' should not be valid using EmailValidator.valid?" do
516
+ expect(described_class).not_to be_valid(email)
517
+ end
518
+
519
+ it "'#{email}' should be invalid using EmailValidator.invalid?" do
520
+ expect(described_class).to be_invalid(email)
521
+ end
522
+
523
+ it "'#{email}' should not match the regexp" do
524
+ expect(!!(email.strip =~ described_class.regexp)).to be(false)
525
+ end
526
+ end
135
527
 
136
- it "#{email.inspect} should be valid" do
137
- expect(TestUser.new(:email => email)).to be_valid
528
+ context 'when in `:strict` mode' do
529
+ it "'#{email}' should not be valid" do
530
+ expect(StrictUser.new(:email => email)).not_to be_valid
531
+ end
532
+
533
+ it "'#{email}' should not be valid using EmailValidator.valid?" do
534
+ expect(described_class).not_to be_valid(email, :mode => :strict)
535
+ end
536
+
537
+ it "'#{email}' should be invalid using EmailValidator.invalid?" do
538
+ expect(described_class).to be_invalid(email, :mode => :strict)
539
+ end
540
+
541
+ it "'#{email}' should not match the regexp" do
542
+ expect(!!(email.strip =~ described_class.regexp(:mode => :strict))).to be(false)
543
+ end
138
544
  end
139
545
 
140
- it "#{email.inspect} should not be valid in strict_mode" do
141
- expect(StrictUser.new(:email => email)).not_to be_valid
546
+ context 'when in `:rfc` mode' do
547
+ it "'#{email}' should not be valid" do
548
+ expect(RfcUser.new(:email => email)).not_to be_valid
549
+ end
550
+
551
+ it "'#{email}' should not be valid using EmailValidator.valid?" do
552
+ expect(described_class).not_to be_valid(email, :mode => :rfc)
553
+ end
554
+
555
+ it "'#{email}' should be invalid using EmailValidator.invalid?" do
556
+ expect(described_class).to be_invalid(email, :mode => :rfc)
557
+ end
558
+
559
+ it "'#{email}' should not match the regexp" do
560
+ expect(!!(email.strip =~ described_class.regexp(:mode => :rfc))).to be(false)
561
+ end
142
562
  end
563
+ end
564
+ end
143
565
 
144
- it "#{email.inspect} should match the regexp" do
145
- expect(email =~ EmailValidator.regexp).to be_truthy
566
+ context 'when given the strictly invalid email' do
567
+ strictly_invalid_includable.map { |k, v| [
568
+ "include-#{v}-#{k}@invalid-characters-in-local.dev"
569
+ ]}.concat(strictly_invalid_beginable.map { |k, v| [
570
+ "#{v}start-with-#{k}@invalid-characters-in-local.dev"
571
+ ]}).concat(strictly_invalid_endable.map { |k, v| [
572
+ "end-with-#{k}#{v}@invalid-characters-in-local.dev"
573
+ ]}).concat([
574
+ 'user..-with-double-dots@example.com',
575
+ '.user-beginning-with-dot@example.com',
576
+ 'user-ending-with-dot.@example.com'
577
+ ]).flatten.each do |email|
578
+ context 'when using defaults' do
579
+ it "#{email.strip} in a model should be valid" do
580
+ expect(DefaultUser.new(:email => email)).to be_valid
581
+ end
582
+
583
+ it "#{email.strip} should be valid using EmailValidator.valid?" do
584
+ expect(described_class).to be_valid(email)
585
+ end
586
+
587
+ it "#{email.strip} should not be invalid using EmailValidator.invalid?" do
588
+ expect(described_class).not_to be_invalid(email)
589
+ end
590
+
591
+ it "#{email.strip} should match the regexp" do
592
+ expect(!!(email =~ described_class.regexp)).to be(true)
593
+ end
146
594
  end
147
595
 
148
- it "#{email.inspect} should not match the strict regexp" do
149
- expect(email =~ EmailValidator.regexp(:strict_mode => true)).to be_falsy
596
+ context 'when in `:strict` mode' do
597
+ it "#{email.strip} in a model should be valid" do
598
+ expect(StrictUser.new(:email => email)).not_to be_valid
599
+ end
600
+
601
+ it "#{email.strip} should not be valid using EmailValidator.valid?" do
602
+ expect(described_class).not_to be_valid(email, :mode => :strict)
603
+ end
604
+
605
+ it "#{email.strip} should be invalid using EmailValidator.invalid?" do
606
+ expect(described_class).to be_invalid(email, :mode => :strict)
607
+ end
608
+
609
+ it "#{email.strip} should not match the regexp" do
610
+ expect(!!(email =~ described_class.regexp(:mode => :strict))).to be(false)
611
+ end
150
612
  end
151
613
 
614
+ context 'when in `:rfc` mode' do
615
+ it "#{email.strip} in a model should not be valid" do
616
+ expect(RfcUser.new(:email => email)).not_to be_valid
617
+ end
618
+
619
+ it "#{email.strip} should not be valid using EmailValidator.valid?" do
620
+ expect(described_class).not_to be_valid(email, :mode => :rfc)
621
+ end
622
+
623
+ it "#{email.strip} should be invalid using EmailValidator.invalid?" do
624
+ expect(described_class).to be_invalid(email, :mode => :rfc)
625
+ end
626
+
627
+ it "#{email.strip} should not match the regexp" do
628
+ expect(!!(email =~ described_class.regexp(:mode => :rfc))).to be(false)
629
+ end
630
+ end
631
+ end
632
+ end
633
+
634
+ context 'when `require_fqdn` is explicitly disabled' do
635
+ let(:opts) { { :require_fqdn => false } }
636
+
637
+ context 'when given a valid hostname-only email' do
638
+ let(:email) { 'someuser@somehost' }
639
+
640
+ context 'when using defaults' do
641
+ it 'is valid using EmailValidator.valid?' do
642
+ expect(described_class).to be_valid(email, opts)
643
+ end
644
+
645
+ it 'is not invalid using EmailValidator.invalid?' do
646
+ expect(described_class).not_to be_invalid(email, opts)
647
+ end
648
+
649
+ it 'matches the regexp' do
650
+ expect(!!(email =~ described_class.regexp(opts))).to be(true)
651
+ end
652
+ end
653
+
654
+ context 'when in `:strict` mode' do
655
+ let(:opts) { { :require_fqdn => false, :mode => :strict } }
656
+
657
+ it 'is valid' do
658
+ expect(NonFqdnStrictUser.new(:email => email)).to be_valid
659
+ end
660
+
661
+ it 'is valid using EmailValidator.valid?' do
662
+ expect(described_class).to be_valid(email, opts)
663
+ end
664
+
665
+ it 'is not invalid using EmailValidator.invalid?' do
666
+ expect(described_class).not_to be_invalid(email, opts)
667
+ end
668
+
669
+ it 'matches the regexp' do
670
+ expect(!!(email =~ described_class.regexp(opts))).to be(true)
671
+ end
672
+ end
673
+
674
+ context 'when in `:rfc` mode' do
675
+ let(:opts) { { :require_fqdn => false, :mode => :rfc } }
676
+
677
+ it 'is valid' do
678
+ expect(NonFqdnRfcUser.new(:email => email)).to be_valid
679
+ end
680
+
681
+ it 'is valid using EmailValidator.valid?' do
682
+ expect(described_class).to be_valid(email, opts)
683
+ end
684
+
685
+ it 'is not invalid using EmailValidator.invalid?' do
686
+ expect(described_class).not_to be_invalid(email, opts)
687
+ end
688
+
689
+ it 'matches the regexp' do
690
+ expect(!!(email =~ described_class.regexp(opts))).to be(true)
691
+ end
692
+ end
693
+ end
694
+
695
+ context 'when given a valid email using an FQDN' do
696
+ let(:email) { 'someuser@somehost.somedomain' }
697
+
698
+ it 'is valid' do
699
+ expect(NonFqdnStrictUser.new(:email => email)).to be_valid
700
+ end
701
+
702
+ # rubocop:disable RSpec/PredicateMatcher
703
+ it 'is valid using EmailValidator.valid?' do
704
+ expect(described_class.valid?(email, opts)).to be(true)
705
+ end
706
+
707
+ it 'is not invalid using EmailValidator.invalid?' do
708
+ expect(described_class.invalid?(email, opts)).to be(false)
709
+ end
710
+ # rubocop:enable RSpec/PredicateMatcher
711
+
712
+ it 'matches the regexp' do
713
+ expect(!!(email =~ described_class.regexp(opts))).to be(true)
714
+ end
715
+
716
+ context 'when in `:rfc` mode' do
717
+ let(:opts) { { :require_fqdn => false, :mode => :rfc } }
718
+
719
+ # rubocop:disable RSpec/PredicateMatcher
720
+ it 'is valid using EmailValidator.valid?' do
721
+ expect(described_class.valid?(email, opts)).to be(true)
722
+ end
723
+
724
+ it 'is not invalid using EmailValidator.invalid?' do
725
+ expect(described_class.invalid?(email, opts)).to be(false)
726
+ end
727
+ # rubocop:enable RSpec/PredicateMatcher
728
+
729
+ it 'is valid' do
730
+ expect(NonFqdnRfcUser.new(:email => email)).to be_valid
731
+ end
732
+
733
+ it 'matches the regexp' do
734
+ expect(!!(email =~ described_class.regexp(opts))).to be(true)
735
+ end
736
+ end
737
+
738
+ context 'when requiring a non-matching domain' do
739
+ let(:domain) { 'example.com' }
740
+ let(:opts) { { :domain => domain } }
741
+
742
+ it 'is not valid' do
743
+ expect(DomainStrictUser.new(:email => email)).not_to be_valid
744
+ end
745
+
746
+ it 'is not valid using EmailValidator.valid?' do
747
+ expect(described_class).not_to be_valid(email, opts)
748
+ end
749
+
750
+ it 'is invalid using EmailValidator.invalid?' do
751
+ expect(described_class).to be_invalid(email, opts)
752
+ end
753
+
754
+ it 'does not matches the regexp' do
755
+ expect(!!(email =~ described_class.regexp(opts))).to be(false)
756
+ end
757
+
758
+ context 'when in `:rfc` mode' do
759
+ let(:opts) { { :domain => domain, :require_fqdn => false, :mode => :rfc } }
760
+
761
+ it 'is not valid using EmailValidator.valid?' do
762
+ expect(described_class).not_to be_valid(email, opts)
763
+ end
764
+
765
+ it 'is invalid using EmailValidator.invalid?' do
766
+ expect(described_class).to be_invalid(email, opts)
767
+ end
768
+
769
+ it 'is not valid' do
770
+ expect(DomainRfcUser.new(:email => email)).not_to be_valid
771
+ end
772
+
773
+ it 'does not match the regexp' do
774
+ expect(!!(email =~ described_class.regexp(opts))).to be(false)
775
+ end
776
+ end
777
+ end
152
778
  end
153
779
  end
154
780
  end
781
+ # rubocop:enable Layout/BlockEndNewline, Layout/MultilineBlockLayout, Layout/MultilineMethodCallBraceLayout, Style/BlockDelimiters, Style/MultilineBlockChain
782
+
783
+ describe 'error messages' do
784
+ context 'when the message is not defined' do
785
+ let(:model) { DefaultUser.new :email => 'invalidemail@' }
155
786
 
156
- describe "error messages" do
157
- context "when the message is not defined" do
158
- subject { TestUser.new :email => 'invalidemail@' }
159
- before { subject.valid? }
787
+ before { model.valid? }
160
788
 
161
- it "should add the default message" do
162
- expect(subject.errors[:email]).to include "is invalid"
789
+ it 'adds the default message' do
790
+ expect(model.errors[:email]).to include 'is invalid'
163
791
  end
164
792
  end
165
793
 
166
- context "when the message is defined" do
167
- subject { TestUserWithMessage.new :email_address => 'invalidemail@' }
168
- before { subject.valid? }
794
+ context 'when the message is defined' do
795
+ let(:model) { DefaultUserWithMessage.new :email_address => 'invalidemail@' }
169
796
 
170
- it "should add the customized message" do
171
- expect(subject.errors[:email_address]).to include "is not looking very good!"
797
+ before { model.valid? }
798
+
799
+ it 'adds the customized message' do
800
+ expect(model.errors[:email_address]).to include 'is not looking very good!'
172
801
  end
173
802
  end
174
803
  end
175
804
 
176
- describe "nil email" do
177
- it "should not be valid when :allow_nil option is missing" do
178
- expect(TestUser.new(:email => nil)).not_to be_valid
805
+ describe 'nil email' do
806
+ it 'is not valid when :allow_nil option is missing' do
807
+ expect(DefaultUser.new(:email => nil)).not_to be_valid
179
808
  end
180
809
 
181
- it "should be valid when :allow_nil options is set to true" do
182
- expect(TestUserAllowsNil.new(:email => nil)).to be_valid
810
+ it 'is valid when :allow_nil options is set to true' do
811
+ expect(AllowNilDefaultUser.new(:email => nil)).to be_valid
183
812
  end
184
813
 
185
- it "should not be valid when :allow_nil option is set to false" do
186
- expect(TestUserAllowsNilFalse.new(:email => nil)).not_to be_valid
814
+ it 'is not valid when :allow_nil option is set to false' do
815
+ expect(DisallowNilDefaultUser.new(:email => nil)).not_to be_valid
187
816
  end
188
817
  end
189
818
 
190
- describe "default_options" do
191
- context "when 'email_validator/strict' has been required" do
819
+ describe 'limited to a domain' do
820
+ context 'when in `:strict` mode' do
821
+ it 'is not valid with mismatched domain' do
822
+ expect(DomainStrictUser.new(:email => 'user@not-matching.io')).not_to be_valid
823
+ end
824
+
825
+ it 'is valid with matching domain' do
826
+ expect(DomainStrictUser.new(:email => 'user@example.com')).to be_valid
827
+ end
828
+
829
+ it 'does not interpret the dot as any character' do
830
+ expect(DomainStrictUser.new(:email => 'user@example-com')).not_to be_valid
831
+ end
832
+ end
833
+
834
+ context 'when in `:rfc` mode' do
835
+ it 'does not interpret the dot as any character' do
836
+ expect(DomainRfcUser.new(:email => 'user@example-com')).not_to be_valid
837
+ end
838
+
839
+ it 'is valid with matching domain' do
840
+ expect(DomainRfcUser.new(:email => 'user@example.com')).to be_valid
841
+ end
842
+
843
+ it 'is not valid with mismatched domain' do
844
+ expect(DomainRfcUser.new(:email => 'user@not-matching.io')).not_to be_valid
845
+ end
846
+ end
847
+ end
848
+
849
+ describe 'default_options' do
850
+ let(:valid_email) { 'valid-email@localhost.localdomain' }
851
+ let(:invalid_email) { 'invalid email@localhost.localdomain' }
852
+
853
+ it 'validates valid using `:loose` mode' do
854
+ expect(DefaultUser.new(:email => valid_email)).to be_valid
855
+ end
856
+
857
+ it 'invalidates invalid using `:loose` mode' do
858
+ expect(DefaultUser.new(:email => invalid_email)).to be_invalid
859
+ end
860
+
861
+ context 'when `email_validator/strict` has been required' do
192
862
  before { require 'email_validator/strict' }
193
863
 
194
- it "should validate using strict mode" do
195
- expect(TestUser.new(:email => "&'*+-./=?^_{}~@other-valid-characters-in-local.net")).not_to be_valid
864
+ it 'validates valid using `:strict` mode' do
865
+ expect(DefaultUser.new(:email => valid_email)).to be_valid
866
+ end
867
+
868
+ it 'invalidates invalid using `:strict` mode' do
869
+ expect(DefaultUser.new(:email => invalid_email)).to be_invalid
870
+ end
871
+ end
872
+
873
+ context 'when `email_validator/rfc` has been required' do
874
+ before { require 'email_validator/rfc' }
875
+
876
+ it 'validates valid using `:rfc` mode' do
877
+ expect(DefaultUser.new(:email => valid_email)).to be_valid
878
+ end
879
+
880
+ it 'invalidates invalid using `:rfc` mode' do
881
+ expect(DefaultUser.new(:email => invalid_email)).to be_invalid
196
882
  end
197
883
  end
198
884
  end