email_validator 1.5.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- 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