shoulda-matchers 2.6.0 → 2.6.1.rc1

Sign up to get free protection for your applications and to get access to all the features.
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?