mcmire-shoulda-matchers 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.travis.yml +32 -0
  4. data/.yardopts +7 -0
  5. data/Appraisals +45 -0
  6. data/CONTRIBUTING.md +41 -0
  7. data/Gemfile +31 -0
  8. data/Gemfile.lock +166 -0
  9. data/MIT-LICENSE +22 -0
  10. data/NEWS.md +299 -0
  11. data/README.md +163 -0
  12. data/Rakefile +116 -0
  13. data/doc_config/gh-pages/index.html.erb +9 -0
  14. data/doc_config/yard/setup.rb +22 -0
  15. data/doc_config/yard/templates/default/fulldoc/html/css/bootstrap.css +5967 -0
  16. data/doc_config/yard/templates/default/fulldoc/html/css/full_list.css +12 -0
  17. data/doc_config/yard/templates/default/fulldoc/html/css/global.css +45 -0
  18. data/doc_config/yard/templates/default/fulldoc/html/css/solarized.css +69 -0
  19. data/doc_config/yard/templates/default/fulldoc/html/css/style.css +283 -0
  20. data/doc_config/yard/templates/default/fulldoc/html/full_list.erb +32 -0
  21. data/doc_config/yard/templates/default/fulldoc/html/full_list_class.erb +1 -0
  22. data/doc_config/yard/templates/default/fulldoc/html/full_list_method.erb +8 -0
  23. data/doc_config/yard/templates/default/fulldoc/html/js/app.js +300 -0
  24. data/doc_config/yard/templates/default/fulldoc/html/js/full_list.js +1 -0
  25. data/doc_config/yard/templates/default/fulldoc/html/js/jquery.stickyheaders.js +289 -0
  26. data/doc_config/yard/templates/default/fulldoc/html/js/underscore.min.js +6 -0
  27. data/doc_config/yard/templates/default/fulldoc/html/setup.rb +8 -0
  28. data/doc_config/yard/templates/default/layout/html/breadcrumb.erb +14 -0
  29. data/doc_config/yard/templates/default/layout/html/fonts.erb +1 -0
  30. data/doc_config/yard/templates/default/layout/html/layout.erb +23 -0
  31. data/doc_config/yard/templates/default/layout/html/search.erb +13 -0
  32. data/doc_config/yard/templates/default/layout/html/setup.rb +8 -0
  33. data/doc_config/yard/templates/default/method_details/html/source.erb +10 -0
  34. data/doc_config/yard/templates/default/module/html/box_info.erb +31 -0
  35. data/features/rails_integration.feature +113 -0
  36. data/features/step_definitions/rails_steps.rb +162 -0
  37. data/features/support/env.rb +5 -0
  38. data/gemfiles/3.0.gemfile +24 -0
  39. data/gemfiles/3.0.gemfile.lock +150 -0
  40. data/gemfiles/3.1.gemfile +27 -0
  41. data/gemfiles/3.1.gemfile.lock +173 -0
  42. data/gemfiles/3.2.gemfile +27 -0
  43. data/gemfiles/3.2.gemfile.lock +171 -0
  44. data/gemfiles/4.0.0.gemfile +28 -0
  45. data/gemfiles/4.0.0.gemfile.lock +172 -0
  46. data/gemfiles/4.0.1.gemfile +28 -0
  47. data/gemfiles/4.0.1.gemfile.lock +172 -0
  48. data/lib/shoulda-matchers.rb +1 -0
  49. data/lib/shoulda/matchers.rb +11 -0
  50. data/lib/shoulda/matchers/action_controller.rb +17 -0
  51. data/lib/shoulda/matchers/action_controller/filter_param_matcher.rb +64 -0
  52. data/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +97 -0
  53. data/lib/shoulda/matchers/action_controller/render_template_matcher.rb +81 -0
  54. data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +117 -0
  55. data/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb +114 -0
  56. data/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +154 -0
  57. data/lib/shoulda/matchers/action_controller/route_matcher.rb +116 -0
  58. data/lib/shoulda/matchers/action_controller/route_params.rb +48 -0
  59. data/lib/shoulda/matchers/action_controller/set_session_matcher.rb +164 -0
  60. data/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb +296 -0
  61. data/lib/shoulda/matchers/active_model.rb +30 -0
  62. data/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb +167 -0
  63. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +314 -0
  64. data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +46 -0
  65. data/lib/shoulda/matchers/active_model/ensure_exclusion_of_matcher.rb +160 -0
  66. data/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +417 -0
  67. data/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb +337 -0
  68. data/lib/shoulda/matchers/active_model/errors.rb +10 -0
  69. data/lib/shoulda/matchers/active_model/exception_message_finder.rb +58 -0
  70. data/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +92 -0
  71. data/lib/shoulda/matchers/active_model/helpers.rb +46 -0
  72. data/lib/shoulda/matchers/active_model/numericality_matchers.rb +9 -0
  73. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +75 -0
  74. data/lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb +27 -0
  75. data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +41 -0
  76. data/lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb +27 -0
  77. data/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb +26 -0
  78. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +112 -0
  79. data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +77 -0
  80. data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +121 -0
  81. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +380 -0
  82. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +89 -0
  83. data/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb +372 -0
  84. data/lib/shoulda/matchers/active_model/validation_matcher.rb +97 -0
  85. data/lib/shoulda/matchers/active_model/validation_message_finder.rb +69 -0
  86. data/lib/shoulda/matchers/active_record.rb +22 -0
  87. data/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb +204 -0
  88. data/lib/shoulda/matchers/active_record/association_matcher.rb +901 -0
  89. data/lib/shoulda/matchers/active_record/association_matchers.rb +9 -0
  90. data/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb +41 -0
  91. data/lib/shoulda/matchers/active_record/association_matchers/dependent_matcher.rb +41 -0
  92. data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +81 -0
  93. data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +65 -0
  94. data/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +94 -0
  95. data/lib/shoulda/matchers/active_record/association_matchers/order_matcher.rb +41 -0
  96. data/lib/shoulda/matchers/active_record/association_matchers/source_matcher.rb +41 -0
  97. data/lib/shoulda/matchers/active_record/association_matchers/through_matcher.rb +63 -0
  98. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +261 -0
  99. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +149 -0
  100. data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +72 -0
  101. data/lib/shoulda/matchers/active_record/serialize_matcher.rb +181 -0
  102. data/lib/shoulda/matchers/assertion_error.rb +19 -0
  103. data/lib/shoulda/matchers/error.rb +6 -0
  104. data/lib/shoulda/matchers/integrations/rspec.rb +20 -0
  105. data/lib/shoulda/matchers/integrations/test_unit.rb +30 -0
  106. data/lib/shoulda/matchers/rails_shim.rb +50 -0
  107. data/lib/shoulda/matchers/version.rb +6 -0
  108. data/lib/shoulda/matchers/warn.rb +8 -0
  109. data/shoulda-matchers.gemspec +23 -0
  110. data/spec/shoulda/matchers/action_controller/filter_param_matcher_spec.rb +22 -0
  111. data/spec/shoulda/matchers/action_controller/redirect_to_matcher_spec.rb +42 -0
  112. data/spec/shoulda/matchers/action_controller/render_template_matcher_spec.rb +78 -0
  113. data/spec/shoulda/matchers/action_controller/render_with_layout_matcher_spec.rb +63 -0
  114. data/spec/shoulda/matchers/action_controller/rescue_from_matcher_spec.rb +63 -0
  115. data/spec/shoulda/matchers/action_controller/respond_with_matcher_spec.rb +31 -0
  116. data/spec/shoulda/matchers/action_controller/route_matcher_spec.rb +70 -0
  117. data/spec/shoulda/matchers/action_controller/route_params_spec.rb +30 -0
  118. data/spec/shoulda/matchers/action_controller/set_session_matcher_spec.rb +51 -0
  119. data/spec/shoulda/matchers/action_controller/set_the_flash_matcher_spec.rb +153 -0
  120. data/spec/shoulda/matchers/active_model/allow_mass_assignment_of_matcher_spec.rb +111 -0
  121. data/spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb +170 -0
  122. data/spec/shoulda/matchers/active_model/disallow_value_matcher_spec.rb +81 -0
  123. data/spec/shoulda/matchers/active_model/ensure_exclusion_of_matcher_spec.rb +95 -0
  124. data/spec/shoulda/matchers/active_model/ensure_inclusion_of_matcher_spec.rb +320 -0
  125. data/spec/shoulda/matchers/active_model/ensure_length_of_matcher_spec.rb +166 -0
  126. data/spec/shoulda/matchers/active_model/exception_message_finder_spec.rb +111 -0
  127. data/spec/shoulda/matchers/active_model/have_secure_password_matcher_spec.rb +20 -0
  128. data/spec/shoulda/matchers/active_model/helpers_spec.rb +158 -0
  129. data/spec/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +169 -0
  130. data/spec/shoulda/matchers/active_model/numericality_matchers/even_number_matcher_spec.rb +59 -0
  131. data/spec/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher_spec.rb +59 -0
  132. data/spec/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher_spec.rb +57 -0
  133. data/spec/shoulda/matchers/active_model/validate_absence_of_matcher_spec.rb +139 -0
  134. data/spec/shoulda/matchers/active_model/validate_acceptance_of_matcher_spec.rb +41 -0
  135. data/spec/shoulda/matchers/active_model/validate_confirmation_of_matcher_spec.rb +47 -0
  136. data/spec/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +331 -0
  137. data/spec/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +180 -0
  138. data/spec/shoulda/matchers/active_model/validate_uniqueness_of_matcher_spec.rb +398 -0
  139. data/spec/shoulda/matchers/active_model/validation_message_finder_spec.rb +127 -0
  140. data/spec/shoulda/matchers/active_record/accept_nested_attributes_for_matcher_spec.rb +107 -0
  141. data/spec/shoulda/matchers/active_record/association_matcher_spec.rb +860 -0
  142. data/spec/shoulda/matchers/active_record/association_matchers/model_reflection_spec.rb +247 -0
  143. data/spec/shoulda/matchers/active_record/have_db_column_matcher_spec.rb +111 -0
  144. data/spec/shoulda/matchers/active_record/have_db_index_matcher_spec.rb +78 -0
  145. data/spec/shoulda/matchers/active_record/have_readonly_attributes_matcher_spec.rb +41 -0
  146. data/spec/shoulda/matchers/active_record/serialize_matcher_spec.rb +86 -0
  147. data/spec/spec_helper.rb +26 -0
  148. data/spec/support/active_model_versions.rb +13 -0
  149. data/spec/support/active_resource_builder.rb +29 -0
  150. data/spec/support/activemodel_helpers.rb +19 -0
  151. data/spec/support/capture_helpers.rb +19 -0
  152. data/spec/support/class_builder.rb +42 -0
  153. data/spec/support/controller_builder.rb +74 -0
  154. data/spec/support/fail_with_message_including_matcher.rb +33 -0
  155. data/spec/support/fail_with_message_matcher.rb +32 -0
  156. data/spec/support/i18n_faker.rb +10 -0
  157. data/spec/support/mailer_builder.rb +10 -0
  158. data/spec/support/model_builder.rb +81 -0
  159. data/spec/support/rails_versions.rb +18 -0
  160. data/spec/support/shared_examples/numerical_submatcher.rb +19 -0
  161. data/spec/support/shared_examples/numerical_type_submatcher.rb +17 -0
  162. data/spec/support/test_application.rb +120 -0
  163. data/yard.watchr +5 -0
  164. metadata +281 -0
@@ -0,0 +1,46 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveModel
4
+ # @private
5
+ class DisallowValueMatcher
6
+ def initialize(value)
7
+ @allow_matcher = AllowValueMatcher.new(value)
8
+ end
9
+
10
+ def matches?(subject)
11
+ !@allow_matcher.matches?(subject)
12
+ end
13
+
14
+ def for(attribute)
15
+ @allow_matcher.for(attribute)
16
+ self
17
+ end
18
+
19
+ def on(context)
20
+ @allow_matcher.on(context)
21
+ self
22
+ end
23
+
24
+ def with_message(message, options={})
25
+ @allow_matcher.with_message(message, options)
26
+ self
27
+ end
28
+
29
+ def failure_message
30
+ @allow_matcher.failure_message_when_negated
31
+ end
32
+ alias failure_message_for_should failure_message
33
+
34
+ def failure_message_when_negated
35
+ @allow_matcher.failure_message
36
+ end
37
+ alias failure_message_for_should_not failure_message_when_negated
38
+
39
+ def strict
40
+ @allow_matcher.strict
41
+ self
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,160 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveModel
4
+ # The `ensure_exclusion_of` matcher tests usage of the
5
+ # `validates_exclusion_of` validation, asserting that an attribute cannot
6
+ # take a blacklist of values, and inversely, can take values outside of
7
+ # this list.
8
+ #
9
+ # #### Qualifiers
10
+ #
11
+ # `in_array` or `in_range` are used to test usage of the `:in` option,
12
+ # and so one must be used.
13
+ #
14
+ # ##### in_array
15
+ #
16
+ # Use `in_array` if your blacklist is an array of values.
17
+ #
18
+ # class Game
19
+ # include ActiveModel::Model
20
+ #
21
+ # validates_exclusion_of :supported_os, in: ['Mac', 'Linux']
22
+ # end
23
+ #
24
+ # # RSpec
25
+ # describe Game do
26
+ # it { should ensure_exclusion_of(:supported_os).in_array(['Mac', 'Linux']) }
27
+ # end
28
+ #
29
+ # # Test::Unit
30
+ # class GameTest < ActiveSupport::TestCase
31
+ # should ensure_exclusion_of(:supported_os).in_array(['Mac', 'Linux'])
32
+ # end
33
+ #
34
+ # ##### in_range
35
+ #
36
+ # Use `in_range` if your blacklist is a range of values.
37
+ #
38
+ # class Game
39
+ # include ActiveModel::Model
40
+ #
41
+ # validates_exclusion_of :supported_os, in: ['Mac', 'Linux']
42
+ # end
43
+ #
44
+ # # RSpec
45
+ # describe Game do
46
+ # it { should ensure_exclusion_of(:floors_with_enemies).in_range(5..8) }
47
+ # end
48
+ #
49
+ # # Test::Unit
50
+ # class GameTest < ActiveSupport::TestCase
51
+ # should ensure_exclusion_of(:floors_with_enemies).in_range(5..8)
52
+ # end
53
+ #
54
+ # ##### with_message
55
+ #
56
+ # Use `with_message` if you are using a custom validation message.
57
+ #
58
+ # class Game
59
+ # validates_exclusion_of :weapon,
60
+ # in: ['pistol', 'paintball gun', 'stick'],
61
+ # message: 'You chose a puny weapon'
62
+ # end
63
+ #
64
+ # # RSpec
65
+ # describe Game do
66
+ # it do
67
+ # should ensure_exclusion_of(:weapon).
68
+ # in_array(['pistol', 'paintball gun', 'stick']).
69
+ # with_message('You chose a puny weapon')
70
+ # end
71
+ # end
72
+ #
73
+ # # Test::Unit
74
+ # class GameTest < ActiveSupport::TestCase
75
+ # should ensure_exclusion_of(:weapon).
76
+ # in_array(['pistol', 'paintball gun', 'stick']).
77
+ # with_message('You chose a puny weapon')
78
+ # end
79
+ #
80
+ # @return [EnsureExclusionOfMatcher]
81
+ #
82
+ def ensure_exclusion_of(attr)
83
+ EnsureExclusionOfMatcher.new(attr)
84
+ end
85
+
86
+ # @private
87
+ class EnsureExclusionOfMatcher < ValidationMatcher
88
+ def in_array(array)
89
+ @array = array
90
+ self
91
+ end
92
+
93
+ def in_range(range)
94
+ @range = range
95
+ @minimum = range.first
96
+ @maximum = range.max
97
+ self
98
+ end
99
+
100
+ def with_message(message)
101
+ @expected_message = message if message
102
+ self
103
+ end
104
+
105
+ def description
106
+ "ensure exclusion of #{@attribute} in #{inspect_message}"
107
+ end
108
+
109
+ def matches?(subject)
110
+ super(subject)
111
+
112
+ if @range
113
+ allows_lower_value &&
114
+ disallows_minimum_value &&
115
+ allows_higher_value &&
116
+ disallows_maximum_value
117
+ elsif @array
118
+ disallows_all_values_in_array?
119
+ end
120
+ end
121
+
122
+ private
123
+
124
+ def disallows_all_values_in_array?
125
+ @array.all? do |value|
126
+ disallows_value_of(value, expected_message)
127
+ end
128
+ end
129
+
130
+ def allows_lower_value
131
+ @minimum == 0 || allows_value_of(@minimum - 1, expected_message)
132
+ end
133
+
134
+ def allows_higher_value
135
+ allows_value_of(@maximum + 1, expected_message)
136
+ end
137
+
138
+ def disallows_minimum_value
139
+ disallows_value_of(@minimum, expected_message)
140
+ end
141
+
142
+ def disallows_maximum_value
143
+ disallows_value_of(@maximum, expected_message)
144
+ end
145
+
146
+ def expected_message
147
+ @expected_message || :exclusion
148
+ end
149
+
150
+ def inspect_message
151
+ if @range
152
+ @range.inspect
153
+ else
154
+ @array.inspect
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,417 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveModel
4
+ # The `ensure_inclusion_of` matcher tests usage of the
5
+ # `validates_inclusion_of` validation, asserting that an attribute can
6
+ # take a whitelist of values and cannot take values outside of this list.
7
+ #
8
+ # #### Qualifiers
9
+ #
10
+ # `in_array` or `in_range` are used to test usage of the `:in` option,
11
+ # and so one must be used.
12
+ #
13
+ # ##### in_array
14
+ #
15
+ # Use `in_array` if your whitelist is an array of values.
16
+ #
17
+ # class Issue
18
+ # include ActiveModel::Model
19
+ #
20
+ # validates_inclusion_of :state, in: %w(open resolved unresolved)
21
+ # end
22
+ #
23
+ # # RSpec
24
+ # describe Issue do
25
+ # it { should ensure_inclusion_of(:state).in_array(%w(open resolved unresolved)) }
26
+ # end
27
+ #
28
+ # # Test::Unit
29
+ # class IssueTest < ActiveSupport::TestCase
30
+ # should ensure_inclusion_of(:state).in_array(%w(open resolved unresolved))
31
+ # end
32
+ #
33
+ # ##### in_range
34
+ #
35
+ # Use `in_range` if your whitelist is a range of values.
36
+ #
37
+ # class Issue
38
+ # include ActiveModel::Model
39
+ #
40
+ # validates_inclusion_of :priority, in: 1..5
41
+ # end
42
+ #
43
+ # # RSpec
44
+ # describe Issue do
45
+ # it { should ensure_inclusion_of(:state).in_range(1..5) }
46
+ # end
47
+ #
48
+ # # Test::Unit
49
+ # class IssueTest < ActiveSupport::TestCase
50
+ # should ensure_inclusion_of(:state).in_range(1..5)
51
+ # end
52
+ #
53
+ # ##### with_message
54
+ #
55
+ # Use `with_message` if you are using a custom validation message.
56
+ #
57
+ # class Issue
58
+ # include ActiveModel::Model
59
+ #
60
+ # validates_inclusion_of :severity,
61
+ # in: %w(low medium high),
62
+ # message: 'Severity must be low, medium, or high'
63
+ # end
64
+ #
65
+ # # RSpec
66
+ # describe Issue do
67
+ # it do
68
+ # should ensure_inclusion_of(:severity).
69
+ # in_array(%w(low medium high)).
70
+ # with_message('Severity must be low, medium, or high')
71
+ # end
72
+ # end
73
+ #
74
+ # # Test::Unit
75
+ # class IssueTest < ActiveSupport::TestCase
76
+ # should ensure_inclusion_of(:severity).
77
+ # in_array(%w(low medium high)).
78
+ # with_message('Severity must be low, medium, or high')
79
+ # end
80
+ #
81
+ # ##### with_low_message
82
+ #
83
+ # Use `with_low_message` if you have a custom validation message for when
84
+ # a given value is too low.
85
+ #
86
+ # class Person
87
+ # include ActiveModel::Model
88
+ #
89
+ # validate :age_must_be_valid
90
+ #
91
+ # private
92
+ #
93
+ # def age_must_be_valid
94
+ # if age < 65
95
+ # self.errors.add :age, 'You do not receive any benefits'
96
+ # end
97
+ # end
98
+ # end
99
+ #
100
+ # # RSpec
101
+ # describe Person do
102
+ # it do
103
+ # should ensure_inclusion_of(:age).
104
+ # in_range(0..65).
105
+ # with_low_message('You do not receive any benefits')
106
+ # end
107
+ # end
108
+ #
109
+ # # Test::Unit
110
+ # class PersonTest < ActiveSupport::TestCase
111
+ # should ensure_inclusion_of(:age).
112
+ # in_range(0..65).
113
+ # with_low_message('You do not receive any benefits')
114
+ # end
115
+ #
116
+ # ##### with_high_message
117
+ #
118
+ # Use `with_high_message` if you have a custom validation message for
119
+ # when a given value is too high.
120
+ #
121
+ # class Person
122
+ # include ActiveModel::Model
123
+ #
124
+ # validate :age_must_be_valid
125
+ #
126
+ # private
127
+ #
128
+ # def age_must_be_valid
129
+ # if age > 21
130
+ # self.errors.add :age, "You're too old for this stuff"
131
+ # end
132
+ # end
133
+ # end
134
+ #
135
+ # # RSpec
136
+ # describe Person do
137
+ # it do
138
+ # should ensure_inclusion_of(:age).
139
+ # in_range(0..21).
140
+ # with_high_message("You're too old for this stuff")
141
+ # end
142
+ # end
143
+ #
144
+ # # Test::Unit
145
+ # class PersonTest < ActiveSupport::TestCase
146
+ # should ensure_inclusion_of(:age).
147
+ # in_range(0..21).
148
+ # with_high_message("You're too old for this stuff")
149
+ # end
150
+ #
151
+ # ##### allow_nil
152
+ #
153
+ # Use `allow_nil` to assert that the attribute allows nil.
154
+ #
155
+ # class Issue
156
+ # include ActiveModel::Model
157
+ #
158
+ # validates_presence_of :state
159
+ # validates_inclusion_of :state,
160
+ # in: %w(open resolved unresolved),
161
+ # allow_nil: true
162
+ # end
163
+ #
164
+ # # RSpec
165
+ # describe Issue do
166
+ # it do
167
+ # should ensure_inclusion_of(:state).
168
+ # in_array(%w(open resolved unresolved)).
169
+ # allow_nil
170
+ # end
171
+ # end
172
+ #
173
+ # # Test::Unit
174
+ # class IssueTest < ActiveSupport::TestCase
175
+ # should ensure_inclusion_of(:state).
176
+ # in_array(%w(open resolved unresolved)).
177
+ # allow_nil
178
+ # end
179
+ #
180
+ # ##### allow_blank
181
+ #
182
+ # Use `allow_blank` to assert that the attribute allows blank.
183
+ #
184
+ # class Issue
185
+ # include ActiveModel::Model
186
+ #
187
+ # validates_presence_of :state
188
+ # validates_inclusion_of :state,
189
+ # in: %w(open resolved unresolved),
190
+ # allow_blank: true
191
+ # end
192
+ #
193
+ # # RSpec
194
+ # describe Issue do
195
+ # it do
196
+ # should ensure_inclusion_of(:state).
197
+ # in_array(%w(open resolved unresolved)).
198
+ # allow_blank
199
+ # end
200
+ # end
201
+ #
202
+ # # Test::Unit
203
+ # class IssueTest < ActiveSupport::TestCase
204
+ # should ensure_inclusion_of(:state).
205
+ # in_array(%w(open resolved unresolved)).
206
+ # allow_blank
207
+ # end
208
+ #
209
+ # @return [EnsureInclusionOfMatcher]
210
+ #
211
+ def ensure_inclusion_of(attr)
212
+ EnsureInclusionOfMatcher.new(attr)
213
+ end
214
+
215
+ # @private
216
+ class EnsureInclusionOfMatcher < ValidationMatcher
217
+ ARBITRARY_OUTSIDE_STRING = 'shouldamatchersteststring'
218
+ ARBITRARY_OUTSIDE_FIXNUM = 123456789
219
+ ARBITRARY_OUTSIDE_DECIMAL = 0.123456789
220
+ BOOLEAN_ALLOWS_BOOLEAN_MESSAGE = <<EOT
221
+ You are using `ensure_inclusion_of` to assert that a boolean column allows
222
+ boolean values and disallows non-boolean ones. Assuming you are using
223
+ `validates_format_of` in your model, be aware that it is not possible to fully
224
+ test this, and in fact the validation is superfluous, as boolean columns will
225
+ automatically convert non-boolean values to boolean ones. Hence, you should
226
+ consider removing this test and the corresponding validation.
227
+ EOT
228
+ BOOLEAN_ALLOWS_NIL_MESSAGE = <<EOT
229
+ You are using `ensure_inclusion_of` to assert that a boolean column allows nil.
230
+ Be aware that it is not possible to fully test this, as anything other than
231
+ true, false or nil will be converted to false. Hence, you should consider
232
+ removing this test and the corresponding validation.
233
+ EOT
234
+ BOOLEAN_ALLOWS_NIL_WITH_NOT_NULL_MESSAGE = <<EOT
235
+ You have specified that your model's #{@attribute} should ensure inclusion of nil.
236
+ However, #{@attribute} is a boolean column which does not allow null values.
237
+ Hence, this test will fail and there is no way to make it pass.
238
+ EOT
239
+
240
+ def initialize(attribute)
241
+ super(attribute)
242
+ @options = {}
243
+ end
244
+
245
+ def in_array(array)
246
+ @array = array
247
+ self
248
+ end
249
+
250
+ def in_range(range)
251
+ @range = range
252
+ @minimum = range.first
253
+ @maximum = range.max
254
+ self
255
+ end
256
+
257
+ def allow_blank(allow_blank = true)
258
+ @options[:allow_blank] = allow_blank
259
+ self
260
+ end
261
+
262
+ def allow_nil(allow_nil = true)
263
+ @options[:allow_nil] = allow_nil
264
+ self
265
+ end
266
+
267
+ def with_message(message)
268
+ if message
269
+ @low_message = message
270
+ @high_message = message
271
+ end
272
+ self
273
+ end
274
+
275
+ def with_low_message(message)
276
+ @low_message = message if message
277
+ self
278
+ end
279
+
280
+ def with_high_message(message)
281
+ @high_message = message if message
282
+ self
283
+ end
284
+
285
+ def description
286
+ "ensure inclusion of #{@attribute} in #{inspect_message}"
287
+ end
288
+
289
+ def matches?(subject)
290
+ super(subject)
291
+
292
+ if @range
293
+ @low_message ||= :inclusion
294
+ @high_message ||= :inclusion
295
+
296
+ disallows_lower_value &&
297
+ allows_minimum_value &&
298
+ disallows_higher_value &&
299
+ allows_maximum_value
300
+ elsif @array
301
+ if allows_all_values_in_array? && allows_blank_value? && allows_nil_value? && disallows_value_outside_of_array?
302
+ true
303
+ else
304
+ @failure_message = "#{@array} doesn't match array in validation"
305
+ false
306
+ end
307
+ end
308
+ end
309
+
310
+ private
311
+
312
+ def allows_blank_value?
313
+ if @options.key?(:allow_blank)
314
+ blank_values = ['', ' ', "\n", "\r", "\t", "\f"]
315
+ @options[:allow_blank] == blank_values.all? { |value| allows_value_of(value) }
316
+ else
317
+ true
318
+ end
319
+ end
320
+
321
+ def allows_nil_value?
322
+ if @options.key?(:allow_nil)
323
+ @options[:allow_nil] == allows_value_of(nil)
324
+ else
325
+ true
326
+ end
327
+ end
328
+
329
+ def inspect_message
330
+ @range.nil? ? @array.inspect : @range.inspect
331
+ end
332
+
333
+ def allows_all_values_in_array?
334
+ @array.all? do |value|
335
+ allows_value_of(value, @low_message)
336
+ end
337
+ end
338
+
339
+ def disallows_lower_value
340
+ @minimum == 0 || disallows_value_of(@minimum - 1, @low_message)
341
+ end
342
+
343
+ def disallows_higher_value
344
+ disallows_value_of(@maximum + 1, @high_message)
345
+ end
346
+
347
+ def allows_minimum_value
348
+ allows_value_of(@minimum, @low_message)
349
+ end
350
+
351
+ def allows_maximum_value
352
+ allows_value_of(@maximum, @high_message)
353
+ end
354
+
355
+ def disallows_value_outside_of_array?
356
+ if attribute_column.type == :boolean
357
+ case @array
358
+ when [true, false]
359
+ Shoulda::Matchers.warn BOOLEAN_ALLOWS_BOOLEAN_MESSAGE
360
+ return true
361
+ when [nil]
362
+ if attribute_column.null
363
+ Shoulda::Matchers.warn BOOLEAN_ALLOWS_NIL_MESSAGE
364
+ return true
365
+ else
366
+ raise NonNullableBooleanError, BOOLEAN_ALLOWS_NIL_WITH_NOT_NULL_MESSAGE
367
+ end
368
+ end
369
+ end
370
+
371
+ !allows_value_of(*values_outside_of_array)
372
+ end
373
+
374
+ def values_outside_of_array
375
+ if !(@array & outside_values).empty?
376
+ raise CouldNotDetermineValueOutsideOfArray
377
+ else
378
+ outside_values
379
+ end
380
+ end
381
+
382
+ def outside_values
383
+ case attribute_column.type
384
+ when :boolean
385
+ boolean_outside_values
386
+ when :integer, :float
387
+ [ARBITRARY_OUTSIDE_FIXNUM]
388
+ when :decimal
389
+ [ARBITRARY_OUTSIDE_DECIMAL]
390
+ else
391
+ [ARBITRARY_OUTSIDE_STRING]
392
+ end
393
+ end
394
+
395
+ def boolean_outside_values
396
+ values = []
397
+
398
+ values << case @array
399
+ when [true] then false
400
+ when [false] then true
401
+ else raise CouldNotDetermineValueOutsideOfArray
402
+ end
403
+
404
+ if attribute_column.null
405
+ values << nil
406
+ end
407
+
408
+ values
409
+ end
410
+
411
+ def attribute_column
412
+ @subject.class.columns_hash[@attribute.to_s]
413
+ end
414
+ end
415
+ end
416
+ end
417
+ end