shoulda-matchers 2.6.0 → 2.6.1.rc1

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.
Files changed (52) hide show
  1. data/Gemfile.lock +1 -1
  2. data/NEWS.md +34 -0
  3. data/README.md +14 -0
  4. data/features/activemodel_integration.feature +15 -0
  5. data/features/step_definitions/activemodel_steps.rb +21 -0
  6. data/gemfiles/3.0.gemfile.lock +1 -1
  7. data/gemfiles/3.1.gemfile.lock +1 -1
  8. data/gemfiles/3.2.gemfile.lock +1 -1
  9. data/gemfiles/4.0.0.gemfile.lock +1 -1
  10. data/gemfiles/4.0.1.gemfile.lock +1 -1
  11. data/gemfiles/4.1.gemfile.lock +1 -1
  12. data/lib/shoulda/matchers.rb +1 -0
  13. data/lib/shoulda/matchers/action_controller/callback_matcher.rb +11 -6
  14. data/lib/shoulda/matchers/action_controller/strong_parameters_matcher.rb +59 -95
  15. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +10 -18
  16. data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +10 -0
  17. data/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +60 -18
  18. data/lib/shoulda/matchers/active_model/errors.rb +9 -7
  19. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +4 -0
  20. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +24 -5
  21. data/lib/shoulda/matchers/doublespeak.rb +27 -0
  22. data/lib/shoulda/matchers/doublespeak/double.rb +74 -0
  23. data/lib/shoulda/matchers/doublespeak/double_collection.rb +54 -0
  24. data/lib/shoulda/matchers/doublespeak/double_implementation_registry.rb +27 -0
  25. data/lib/shoulda/matchers/doublespeak/object_double.rb +32 -0
  26. data/lib/shoulda/matchers/doublespeak/proxy_implementation.rb +30 -0
  27. data/lib/shoulda/matchers/doublespeak/structs.rb +8 -0
  28. data/lib/shoulda/matchers/doublespeak/stub_implementation.rb +34 -0
  29. data/lib/shoulda/matchers/doublespeak/world.rb +38 -0
  30. data/lib/shoulda/matchers/independent/delegate_matcher.rb +112 -61
  31. data/lib/shoulda/matchers/integrations/test_unit.rb +8 -6
  32. data/lib/shoulda/matchers/rails_shim.rb +16 -0
  33. data/lib/shoulda/matchers/version.rb +1 -1
  34. data/spec/shoulda/matchers/action_controller/callback_matcher_spec.rb +22 -19
  35. data/spec/shoulda/matchers/action_controller/strong_parameters_matcher_spec.rb +174 -65
  36. data/spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb +14 -0
  37. data/spec/shoulda/matchers/active_model/ensure_inclusion_of_matcher_spec.rb +553 -211
  38. data/spec/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +6 -0
  39. data/spec/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +22 -0
  40. data/spec/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +23 -4
  41. data/spec/shoulda/matchers/doublespeak/double_collection_spec.rb +102 -0
  42. data/spec/shoulda/matchers/doublespeak/double_implementation_registry_spec.rb +21 -0
  43. data/spec/shoulda/matchers/doublespeak/double_spec.rb +144 -0
  44. data/spec/shoulda/matchers/doublespeak/object_double_spec.rb +77 -0
  45. data/spec/shoulda/matchers/doublespeak/proxy_implementation_spec.rb +40 -0
  46. data/spec/shoulda/matchers/doublespeak/stub_implementation_spec.rb +88 -0
  47. data/spec/shoulda/matchers/doublespeak/world_spec.rb +88 -0
  48. data/spec/shoulda/matchers/doublespeak_spec.rb +19 -0
  49. data/spec/shoulda/matchers/independent/delegate_matcher_spec.rb +105 -39
  50. data/spec/support/controller_builder.rb +18 -9
  51. data/spec/support/rails_versions.rb +4 -0
  52. metadata +34 -8
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shoulda-matchers (2.6.0)
4
+ shoulda-matchers (2.6.1.rc1)
5
5
  activesupport (>= 3.0.0)
6
6
 
7
7
  GEM
data/NEWS.md CHANGED
@@ -1,3 +1,37 @@
1
+ # HEAD
2
+
3
+ * Fix `ComparisonMatcher` so that `validate_numericality_of` comparison matchers
4
+ work with large numbers.
5
+
6
+ * Fix so that ActiveRecord matchers aren't included when ActiveRecord
7
+ isn't defined (i.e. if you are using ActiveModel only).
8
+
9
+ * Revert the behavior of `allow_value` changed in 2.6.0 (it will no longer raise
10
+ CouldNotClearAttribute). This was originally done as a part of a fix for
11
+ `validate_presence_of` when used in conjunction with `has_secure_password`.
12
+ That fix has been updated so that it does not affect `allow_value`.
13
+
14
+ * Fix callback matchers and correct test coverage.
15
+
16
+ * Fix `permit` so that it does not interfere with different usages of `params`
17
+ in your controller action. Specifically, this will not raise an error:
18
+ `params.fetch(:foo, {}).permit(:bar, :baz)` (the `permit` will have no
19
+ problems recognizing that :bar and :baz are permitted params).
20
+
21
+ * Fix `permit` on Rails 4.1 to use PATCH by default for #update instead of PUT.
22
+ Previously you had to specify this manually.
23
+
24
+ * Fix `permit` so that it track multiple calls to #permit in your controller
25
+ action. Previously only the last usage of #permit would be considered in
26
+ determining whether the matcher matched.
27
+
28
+ * Fix `permit` so that if the route for your action requires params (such as id)
29
+ then you can now specify those params:
30
+ `permit(:first_name, :last_name).for(:update, params: { id: 42 })`.
31
+
32
+ * Fix `delegate_method` so that it does not stub the target method forever,
33
+ returning it to its original implementation after the match ends.
34
+
1
35
  # 2.6.0
2
36
 
3
37
  * The boolean argument to `have_db_index`'s `unique` option is now optional, for
data/README.md CHANGED
@@ -18,6 +18,20 @@ group :test do
18
18
  end
19
19
  ```
20
20
 
21
+ Note that if you're using a Rails preloader like Spring, you'll need to manually
22
+ require shoulda-matchers in your spec_helper after you require RSpec:
23
+
24
+ ```ruby
25
+ # Gemfile
26
+ group :test do
27
+ gem 'shoulda-matchers', require: false
28
+ end
29
+
30
+ # spec_helper
31
+ require 'rspec/rails'
32
+ require 'shoulda/matchers'
33
+ ```
34
+
21
35
  ### Test::Unit
22
36
 
23
37
  shoulda-matchers was originally a component of
@@ -0,0 +1,15 @@
1
+ Feature: integration with ActiveModel
2
+
3
+ Scenario: create a new project using matchers
4
+ When I generate a new ActiveModel application
5
+ And I configure the application to use "shoulda-matchers" from this project
6
+ And I write to "load_dependencies.rb" with:
7
+ """
8
+ require 'active_model'
9
+ require 'shoulda-matchers'
10
+
11
+ puts ActiveModel::VERSION::STRING
12
+ puts "Loaded all dependencies without errors"
13
+ """
14
+ When I successfully run `bundle exec ruby load_dependencies.rb`
15
+ Then the output should contain "Loaded all dependencies without errors"
@@ -0,0 +1,21 @@
1
+ When 'I generate a new ActiveModel application' do
2
+ steps %{
3
+ When I run `mkdir #{APP_NAME}`
4
+ And I cd to "#{APP_NAME}"
5
+ And I run `bundle init`
6
+ }
7
+
8
+ # Figure out the ActiveModel version to use by reusing the Rails version from
9
+ # the Appraise gemfile.
10
+ if match = File.read(ENV['BUNDLE_GEMFILE']).match(/^gem "rails", "(.*)"/)
11
+ append_to_gemfile %(gem 'activemodel', '#{ match[1] }')
12
+ else
13
+ puts "Couldn't determine which ActiveModel version to load; using latest"
14
+ append_to_gemfile %(gem 'activemodel')
15
+ end
16
+
17
+ steps %{
18
+ And I set the "BUNDLE_GEMFILE" environment variable to "Gemfile"
19
+ And I install gems
20
+ }
21
+ end
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- shoulda-matchers (2.6.0)
4
+ shoulda-matchers (2.6.1.rc1)
5
5
  activesupport (>= 3.0.0)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- shoulda-matchers (2.6.0)
4
+ shoulda-matchers (2.6.1.rc1)
5
5
  activesupport (>= 3.0.0)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- shoulda-matchers (2.6.0)
4
+ shoulda-matchers (2.6.1.rc1)
5
5
  activesupport (>= 3.0.0)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- shoulda-matchers (2.6.0)
4
+ shoulda-matchers (2.6.1.rc1)
5
5
  activesupport (>= 3.0.0)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- shoulda-matchers (2.6.0)
4
+ shoulda-matchers (2.6.1.rc1)
5
5
  activesupport (>= 3.0.0)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .././
3
3
  specs:
4
- shoulda-matchers (2.6.0)
4
+ shoulda-matchers (2.6.1.rc1)
5
5
  activesupport (>= 3.0.0)
6
6
 
7
7
  GEM
@@ -1,4 +1,5 @@
1
1
  require 'shoulda/matchers/assertion_error'
2
+ require 'shoulda/matchers/doublespeak'
2
3
  require 'shoulda/matchers/error'
3
4
  require 'shoulda/matchers/rails_shim'
4
5
  require 'shoulda/matchers/warn'
@@ -68,18 +68,20 @@ module Shoulda # :nodoc:
68
68
  @callback_type = callback_type
69
69
  end
70
70
 
71
- def matches?(subject)
72
- @subject = subject
71
+ def matches?(controller)
72
+ @controller = controller
73
+ @controller_class = controller.class
74
+
73
75
  callbacks.map(&:filter).include?(method_name)
74
76
  end
75
77
 
76
78
  def failure_message
77
- "Expected that #{subject.name} would have :#{method_name} as a #{kind}_#{callback_type}"
79
+ "Expected that #{controller_class.name} would have :#{method_name} as a #{kind}_#{callback_type}"
78
80
  end
79
81
  alias failure_message_for_should failure_message
80
82
 
81
83
  def failure_message_when_negated
82
- "Expected that #{subject.name} would not have :#{method_name} as a #{kind}_#{callback_type}"
84
+ "Expected that #{controller_class.name} would not have :#{method_name} as a #{kind}_#{callback_type}"
83
85
  end
84
86
  alias failure_message_for_should_not failure_message_when_negated
85
87
 
@@ -90,10 +92,13 @@ module Shoulda # :nodoc:
90
92
  private
91
93
 
92
94
  def callbacks
93
- subject._process_action_callbacks.select { |callback| callback.kind == kind }
95
+ controller_class._process_action_callbacks.select do |callback|
96
+ callback.kind == kind
97
+ end
94
98
  end
95
99
 
96
- attr_reader :method_name, :subject, :kind, :callback_type
100
+ attr_reader :method_name, :controller, :controller_class, :kind,
101
+ :callback_type
97
102
  end
98
103
  end
99
104
  end
@@ -1,34 +1,34 @@
1
+ require 'delegate'
2
+
1
3
  begin
2
4
  require 'strong_parameters'
3
5
  rescue LoadError
4
6
  end
5
7
 
8
+ require 'active_support/hash_with_indifferent_access'
9
+
6
10
  module Shoulda
7
11
  module Matchers
8
12
  module ActionController
9
- def permit(*attributes)
10
- StrongParametersMatcher.new(self, attributes)
13
+ def permit(*params)
14
+ StrongParametersMatcher.new(params).in_context(self)
11
15
  end
12
16
 
13
17
  class StrongParametersMatcher
14
- def self.stubbed_parameters_class
15
- @stubbed_parameters_class ||= build_stubbed_parameters_class
16
- end
18
+ attr_writer :stubbed_params
17
19
 
18
- def self.build_stubbed_parameters_class
19
- Class.new(::ActionController::Parameters) do
20
- include StubbedParameters
21
- end
22
- end
23
-
24
- def initialize(context = nil, attributes)
25
- @attributes = attributes
26
- @context = context
20
+ def initialize(expected_permitted_params)
21
+ @action = nil
22
+ @verb = nil
23
+ @request_params = {}
24
+ @expected_permitted_params = expected_permitted_params
25
+ set_double_collection
27
26
  end
28
27
 
29
28
  def for(action, options = {})
30
29
  @action = action
31
- @verb = options[:verb] || verb_for_action
30
+ @verb = options.fetch(:verb, default_verb)
31
+ @request_params = options.fetch(:params, {})
32
32
  self
33
33
  end
34
34
 
@@ -38,128 +38,92 @@ module Shoulda
38
38
  end
39
39
 
40
40
  def description
41
- "permit #{verb.upcase} ##{action} to receive parameters #{attributes_as_sentence}"
41
+ "permit #{verb.upcase} ##{action} to receive parameters #{param_names_as_sentence}"
42
42
  end
43
43
 
44
- def matches?(controller = nil)
45
- simulate_controller_action && parameters_difference.empty?
46
- end
44
+ def matches?(controller)
45
+ @controller = controller
46
+ ensure_action_and_verb_present!
47
47
 
48
- def does_not_match?(controller = nil)
49
- simulate_controller_action && parameters_intersection.empty?
48
+ Doublespeak.with_doubles_activated do
49
+ context.__send__(verb, action, request_params)
50
+ end
51
+
52
+ unpermitted_params.empty?
50
53
  end
51
54
 
52
55
  def failure_message
53
- "Expected controller to permit #{parameters_difference.to_sentence}, but it did not."
56
+ "Expected controller to permit #{unpermitted_params.to_sentence}, but it did not."
54
57
  end
55
58
  alias failure_message_for_should failure_message
56
59
 
57
60
  def failure_message_when_negated
58
- "Expected controller not to permit #{parameters_intersection.to_sentence}, but it did."
61
+ "Expected controller not to permit #{verified_permitted_params.to_sentence}, but it did."
59
62
  end
60
63
  alias failure_message_for_should_not failure_message_when_negated
61
64
 
62
65
  private
63
66
 
64
- attr_reader :verb, :action, :attributes, :context
67
+ attr_reader :controller, :double_collection, :action, :verb,
68
+ :request_params, :expected_permitted_params, :context
65
69
 
66
- def simulate_controller_action
67
- ensure_action_and_verb_present!
68
- stub_model_attributes
69
-
70
- begin
71
- context.send(verb, action)
72
- ensure
73
- unstub_model_attributes
74
- end
70
+ def set_double_collection
71
+ @double_collection =
72
+ Doublespeak.register_double_collection(::ActionController::Parameters)
75
73
 
76
- verify_permit_call
74
+ @double_collection.register_stub(:require).to_return { |params| params }
75
+ @double_collection.register_proxy(:permit)
77
76
  end
78
77
 
79
- def verify_permit_call
80
- @model_attrs.permit_was_called
78
+ def actual_permitted_params
79
+ double_collection.calls_to(:permit).inject([]) do |all_param_names, call|
80
+ all_param_names + call.args
81
+ end.flatten
81
82
  end
82
83
 
83
- def parameters_difference
84
- attributes - @model_attrs.shoulda_permitted_params
84
+ def permit_called?
85
+ actual_permitted_params.any?
85
86
  end
86
87
 
87
- def parameters_intersection
88
- attributes & @model_attrs.shoulda_permitted_params
89
- end
90
-
91
- def stub_model_attributes
92
- @model_attrs = self.class.stubbed_parameters_class.new(arbitrary_attributes)
93
-
94
- local_model_attrs = @model_attrs
95
- ::ActionController::Parameters.class_eval do
96
- alias_method :'shoulda_original_[]', :[]
97
-
98
- define_method :[] do |*args|
99
- local_model_attrs
100
- end
101
- end
88
+ def unpermitted_params
89
+ expected_permitted_params - actual_permitted_params
102
90
  end
103
91
 
104
- def unstub_model_attributes
105
- ::ActionController::Parameters.class_eval do
106
- alias_method :[], :'shoulda_original_[]'
107
- undef_method :'shoulda_original_[]'
108
- end
92
+ def verified_permitted_params
93
+ expected_permitted_params & actual_permitted_params
109
94
  end
110
95
 
111
96
  def ensure_action_and_verb_present!
112
97
  if action.blank?
113
98
  raise ActionNotDefinedError
114
99
  end
100
+
115
101
  if verb.blank?
116
102
  raise VerbNotDefinedError
117
103
  end
118
104
  end
119
105
 
120
- def arbitrary_attributes
121
- {any_key: 'any_value'}
122
- end
123
-
124
- def verb_for_action
125
- verb_lookup = { create: :post, update: :put }
126
- verb_lookup[action]
127
- end
128
-
129
- def attributes_as_sentence
130
- attributes.map(&:inspect).to_sentence
131
- end
132
- end
133
-
134
- module StrongParametersMatcher::StubbedParameters
135
- extend ActiveSupport::Concern
136
-
137
- included do
138
- attr_accessor :permit_was_called, :shoulda_permitted_params
106
+ def default_verb
107
+ case action
108
+ when :create then :post
109
+ when :update then RailsShim.verb_for_update
110
+ end
139
111
  end
140
112
 
141
- def initialize(*)
142
- @permit_was_called = false
143
- super
113
+ def param_names_as_sentence
114
+ expected_permitted_params.map(&:inspect).to_sentence
144
115
  end
145
116
 
146
- def permit(*args)
147
- self.shoulda_permitted_params = args
148
- self.permit_was_called = true
149
- nil
150
- end
151
- end
152
-
153
- class StrongParametersMatcher::ActionNotDefinedError < StandardError
154
- def message
155
- 'You must specify the controller action using the #for method.'
117
+ class ActionNotDefinedError < StandardError
118
+ def message
119
+ 'You must specify the controller action using the #for method.'
120
+ end
156
121
  end
157
- end
158
122
 
159
- class StrongParametersMatcher::VerbNotDefinedError < StandardError
160
- def message
161
- 'You must specify an HTTP verb when using a non-RESTful action.' +
162
- ' e.g. for(:authorize, verb: :post)'
123
+ class VerbNotDefinedError < StandardError
124
+ def message
125
+ 'You must specify an HTTP verb when using a non-RESTful action. For example: for(:authorize, verb: :post)'
126
+ end
163
127
  end
164
128
  end
165
129
  end
@@ -37,6 +37,7 @@ module Shoulda # :nodoc:
37
37
  self.values_to_match = values
38
38
  self.message_finder_factory = ValidationMessageFinder
39
39
  self.options = {}
40
+ self.after_setting_value_callback = -> {}
40
41
  end
41
42
 
42
43
  def for(attribute)
@@ -63,12 +64,16 @@ module Shoulda # :nodoc:
63
64
  self
64
65
  end
65
66
 
67
+ def _after_setting_value(&callback) # :nodoc:
68
+ self.after_setting_value_callback = callback
69
+ end
70
+
66
71
  def matches?(instance)
67
72
  self.instance = instance
68
73
 
69
74
  values_to_match.none? do |value|
70
75
  self.value = value
71
- set_and_double_check_attribute!(attribute_to_set, value)
76
+ set_value(value)
72
77
  errors_match?
73
78
  end
74
79
  end
@@ -91,24 +96,11 @@ module Shoulda # :nodoc:
91
96
 
92
97
  attr_accessor :values_to_match, :message_finder_factory,
93
98
  :instance, :attribute_to_set, :attribute_to_check_message_against,
94
- :context, :value, :matched_error
95
-
96
- def set_and_double_check_attribute!(attribute_name, value)
97
- instance.__send__("#{attribute_name}=", value)
99
+ :context, :value, :matched_error, :after_setting_value_callback
98
100
 
99
- if value.nil?
100
- ensure_attribute_was_cleared!(attribute_name)
101
- end
102
- end
103
-
104
- def ensure_attribute_was_cleared!(attribute_name)
105
- if instance.respond_to?(attribute_name)
106
- actual_value = instance.__send__(attribute_name)
107
-
108
- if !actual_value.nil?
109
- raise Shoulda::Matchers::ActiveModel::CouldNotClearAttribute.create(actual_value)
110
- end
111
- end
101
+ def set_value(value)
102
+ instance.__send__("#{attribute_to_set}=", value)
103
+ after_setting_value_callback.call
112
104
  end
113
105
 
114
106
  def errors_match?