shoulda-matchers 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.travis.yml +5 -0
  2. data/Appraisals +6 -0
  3. data/Gemfile.lock +15 -15
  4. data/NEWS.md +20 -1
  5. data/README.md +6 -1
  6. data/features/step_definitions/rails_steps.rb +3 -3
  7. data/gemfiles/3.0.gemfile.lock +23 -27
  8. data/gemfiles/3.1.gemfile.lock +26 -30
  9. data/gemfiles/3.2.gemfile +16 -0
  10. data/gemfiles/3.2.gemfile.lock +157 -0
  11. data/lib/shoulda/matchers/action_controller/assign_to_matcher.rb +46 -29
  12. data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +6 -2
  13. data/lib/shoulda/matchers/action_controller/respond_with_content_type_matcher.rb +12 -7
  14. data/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb +28 -15
  15. data/lib/shoulda/matchers/action_mailer/have_sent_email_matcher.rb +1 -1
  16. data/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb +13 -9
  17. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +9 -8
  18. data/lib/shoulda/matchers/active_model/ensure_exclusion_of_matcher.rb +8 -6
  19. data/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +63 -8
  20. data/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb +67 -34
  21. data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +3 -3
  22. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +9 -5
  23. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +6 -3
  24. data/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb +27 -23
  25. data/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb +38 -25
  26. data/lib/shoulda/matchers/active_record/association_matcher.rb +49 -33
  27. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +42 -35
  28. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +15 -13
  29. data/lib/shoulda/matchers/active_record/query_the_database_matcher.rb +24 -23
  30. data/lib/shoulda/matchers/active_record/serialize_matcher.rb +13 -12
  31. data/lib/shoulda/matchers/version.rb +1 -1
  32. data/shoulda-matchers.gemspec +1 -1
  33. data/spec/shoulda/action_controller/assign_to_matcher_spec.rb +5 -3
  34. data/spec/shoulda/action_mailer/have_sent_email_spec.rb +40 -0
  35. data/spec/shoulda/active_model/allow_mass_assignment_of_matcher_spec.rb +12 -10
  36. data/spec/shoulda/active_model/ensure_inclusion_of_matcher_spec.rb +52 -0
  37. data/spec/shoulda/active_model/helpers_spec.rb +35 -6
  38. data/spec/shoulda/active_model/validate_presence_of_matcher_spec.rb +0 -1
  39. data/spec/shoulda/active_model/validate_uniqueness_of_matcher_spec.rb +8 -1
  40. data/spec/shoulda/active_record/serialize_matcher_spec.rb +1 -1
  41. data/spec/support/active_model_versions.rb +9 -0
  42. data/spec/support/model_builder.rb +15 -7
  43. metadata +123 -128
@@ -18,15 +18,16 @@ module Shoulda # :nodoc:
18
18
  class SerializeMatcher # :nodoc:
19
19
  def initialize(name)
20
20
  @name = name.to_s
21
+ @options = {}
21
22
  end
22
23
 
23
24
  def as(type)
24
- @type = type
25
+ @options[:type] = type
25
26
  self
26
27
  end
27
28
 
28
29
  def as_instance_of(type)
29
- @instance_type = type
30
+ @options[:instance_type] = type
30
31
  self
31
32
  end
32
33
 
@@ -45,7 +46,7 @@ module Shoulda # :nodoc:
45
46
 
46
47
  def description
47
48
  description = "serialize :#{@name}"
48
- description += " class_name => #{@type}" if @type
49
+ description += " class_name => #{@options[:type]}" if @options.key?(:type)
49
50
  description
50
51
  end
51
52
 
@@ -61,15 +62,15 @@ module Shoulda # :nodoc:
61
62
  end
62
63
 
63
64
  def class_valid?
64
- if @type
65
+ if @options[:type]
65
66
  klass = model_class.serialized_attributes[@name]
66
- if klass == @type
67
+ if klass == @options[:type]
67
68
  true
68
69
  else
69
- if klass.respond_to?(:object_class) && klass.object_class == @type
70
+ if klass.respond_to?(:object_class) && klass.object_class == @options[:type]
70
71
  true
71
72
  else
72
- @missing = ":#{@name} should be a type of #{@type}"
73
+ @missing = ":#{@name} should be a type of #{@options[:type]}"
73
74
  false
74
75
  end
75
76
  end
@@ -83,11 +84,11 @@ module Shoulda # :nodoc:
83
84
  end
84
85
 
85
86
  def instance_class_valid?
86
- if @instance_type
87
- if model_class.serialized_attributes[@name].class == @instance_type
87
+ if @options.key?(:instance_type)
88
+ if model_class.serialized_attributes[@name].class == @options[:instance_type]
88
89
  true
89
90
  else
90
- @missing = ":#{@name} should be an instance of #{@type}"
91
+ @missing = ":#{@name} should be an instance of #{@options[:type]}"
91
92
  false
92
93
  end
93
94
  else
@@ -101,8 +102,8 @@ module Shoulda # :nodoc:
101
102
 
102
103
  def expectation
103
104
  expectation = "#{model_class.name} to serialize the attribute called :#{@name}"
104
- expectation += " with a type of #{@type}" if @type
105
- expectation += " with an instance of #{@instance_type}" if @instance_type
105
+ expectation += " with a type of #{@options[:type]}" if @options[:type]
106
+ expectation += " with an instance of #{@options[:instance_type]}" if @options[:instance_type]
106
107
  expectation
107
108
  end
108
109
  end
@@ -1,5 +1,5 @@
1
1
  module Shoulda
2
2
  module Matchers
3
- VERSION = '1.1.0'.freeze
3
+ VERSION = '1.2.0'.freeze
4
4
  end
5
5
  end
@@ -26,5 +26,5 @@ Gem::Specification.new do |s|
26
26
  s.add_development_dependency('cucumber', '~> 1.1.9')
27
27
  s.add_development_dependency('rails', '~> 3.0')
28
28
  s.add_development_dependency('rake', '~> 0.9.2')
29
- s.add_development_dependency('rspec-rails', '~> 2.6.1')
29
+ s.add_development_dependency('rspec-rails', '~> 2.8.1')
30
30
  end
@@ -2,12 +2,14 @@ require 'spec_helper'
2
2
 
3
3
  describe Shoulda::Matchers::ActionController::AssignToMatcher do
4
4
  it "should include the actual class in the failure message" do
5
- define_constant(:WrongClass) do
6
- def to_s; "wrong class" end
5
+ define_class(:WrongClass) do
6
+ def to_s
7
+ 'wrong class'
8
+ end
7
9
  end
8
10
 
9
11
  controller = build_response { @var = WrongClass.new }
10
- matcher = Shoulda::Matchers::ActionController::AssignToMatcher.new(:var).with_kind_of(Fixnum)
12
+ matcher = assign_to(:var).with_kind_of(Fixnum)
11
13
  matcher.matches?(controller)
12
14
 
13
15
  matcher.failure_message.should =~ /but got wrong class \(WrongClass\)$/
@@ -272,6 +272,46 @@ describe Shoulda::Matchers::ActionMailer::HaveSentEmailMatcher do
272
272
  end
273
273
  end
274
274
 
275
+ context "testing multiple email deliveries at once" do
276
+ let(:info1) do
277
+ {
278
+ :from => "do-not-reply@example.com",
279
+ :to => "one@me.com",
280
+ :subject => "subject",
281
+ :body => "body"
282
+ }
283
+ end
284
+
285
+ let(:info2) do
286
+ {
287
+ :from => "do-not-reply@example.com",
288
+ :to => "two@me.com",
289
+ :subject => "subject",
290
+ :body => "body"
291
+ }
292
+ end
293
+
294
+ before do
295
+ define_mailer(:mailer, [:the_email]) do
296
+ def the_email(params)
297
+ mail params
298
+ end
299
+ end
300
+ add_mail_to_deliveries(info1)
301
+ add_mail_to_deliveries(info2)
302
+ end
303
+
304
+ after { ::ActionMailer::Base.deliveries.clear }
305
+
306
+ it "should send an e-mail based on recipient 1" do
307
+ should have_sent_email.to("one@me.com")
308
+ end
309
+
310
+ it "should send an e-mail based on recipient 2" do
311
+ should have_sent_email.to("two@me.com")
312
+ end
313
+ end
314
+
275
315
  it "provides a detailed description of the e-mail expected to be sent" do
276
316
  matcher = have_sent_email
277
317
  matcher.description.should == 'send an email'
@@ -49,17 +49,19 @@ describe Shoulda::Matchers::ActiveModel::AllowMassAssignmentOfMatcher do
49
49
  end
50
50
  end
51
51
 
52
- context "an attribute on a class with no protected attributes" do
53
- let(:model) { define_model(:example, :attr => :string).new }
52
+ unless active_model_3_2?
53
+ context "an attribute on a class with no protected attributes" do
54
+ let(:model) { define_model(:example, :attr => :string).new }
54
55
 
55
- it "should accept being mass-assignable" do
56
- model.should allow_mass_assignment_of(:attr)
57
- end
56
+ it "should accept being mass-assignable" do
57
+ model.should allow_mass_assignment_of(:attr)
58
+ end
58
59
 
59
- it "should assign a negative failure message" do
60
- matcher = allow_mass_assignment_of(:attr)
61
- matcher.matches?(model).should == true
62
- matcher.negative_failure_message.should_not be_nil
60
+ it "should assign a negative failure message" do
61
+ matcher = allow_mass_assignment_of(:attr)
62
+ matcher.matches?(model).should == true
63
+ matcher.negative_failure_message.should_not be_nil
64
+ end
63
65
  end
64
66
  end
65
67
 
@@ -75,7 +77,7 @@ describe Shoulda::Matchers::ActiveModel::AllowMassAssignmentOfMatcher do
75
77
  end
76
78
  end
77
79
 
78
- if ::ActiveModel::VERSION::MAJOR == 3 && ::ActiveModel::VERSION::MINOR >= 1
80
+ if active_model_3_1?
79
81
  context "an attribute included in the mass-assignment whitelist for admin role only" do
80
82
  let(:model) do
81
83
  define_model(:example, :attr => :string) do
@@ -65,7 +65,59 @@ describe Shoulda::Matchers::ActiveModel::EnsureInclusionOfMatcher do
65
65
  it "should accept ensuring the correct range and messages" do
66
66
  @model.should ensure_inclusion_of(:attr).in_range(2..5).with_low_message(/low/).with_high_message(/high/)
67
67
  end
68
+ end
69
+
70
+ context "an attribute which must be included in an array" do
71
+ before do
72
+ @model = define_model(:example, :attr => :string) do
73
+ validates_inclusion_of :attr, :in => %w(one two)
74
+ end.new
75
+ end
76
+
77
+ it "accepts with correct array" do
78
+ @model.should ensure_inclusion_of(:attr).in_array(%w(one two))
79
+ end
80
+
81
+ it "rejects when only part of array matches" do
82
+ @model.should_not ensure_inclusion_of(:attr).in_array(%w(one wrong_value))
83
+ end
84
+
85
+ it "rejects when array doesn't match at all" do
86
+ @model.should_not ensure_inclusion_of(:attr).in_array(%w(cat dog))
87
+ end
88
+
89
+ it "has correct description" do
90
+ ensure_inclusion_of(:attr).in_array([true, 'dog']).description.should == 'ensure inclusion of attr in [true, "dog"]'
91
+ end
92
+
93
+ it "rejects allow_blank" do
94
+ @model.should_not ensure_inclusion_of(:attr).in_array(['one', 'two']).allow_blank(true)
95
+ @model.should ensure_inclusion_of(:attr).in_array(['one', 'two']).allow_blank(false)
96
+ end
68
97
 
98
+ it "rejects allow_nil" do
99
+ @model.should_not ensure_inclusion_of(:attr).in_array(['one', 'two']).allow_nil(true)
100
+ @model.should ensure_inclusion_of(:attr).in_array(['one', 'two']).allow_nil(false)
101
+ end
69
102
  end
70
103
 
104
+ context "allowed blank and allowed nil" do
105
+ before do
106
+ @model = define_model(:example, :attr => :string) do
107
+ validates_inclusion_of :attr, :in => %w(one two), :allow_blank => true, :allow_nil => true
108
+ end.new
109
+ end
110
+
111
+ it "allows allow_blank" do
112
+ @model.should ensure_inclusion_of(:attr).in_array(['one', 'two']).allow_blank(true)
113
+ @model.should ensure_inclusion_of(:attr).in_array(['one', 'two']).allow_blank()
114
+ @model.should_not ensure_inclusion_of(:attr).in_array(['one', 'two']).allow_blank(false)
115
+ end
116
+
117
+ it "allows allow_nil" do
118
+ @model.should ensure_inclusion_of(:attr).in_array(['one', 'two']).allow_nil(true)
119
+ @model.should ensure_inclusion_of(:attr).in_array(['one', 'two']).allow_nil()
120
+ @model.should_not ensure_inclusion_of(:attr).in_array(['one', 'two']).allow_nil(false)
121
+ end
122
+ end
71
123
  end
@@ -26,19 +26,23 @@ def store_translations(options = {:without => []})
26
26
  }
27
27
 
28
28
  unless options[:without].include?(:model_attribute)
29
- translations[:activerecord][:errors][:models][:example][:attributes][:attr][:blank] = 'Don’t you do that to me!'
29
+ translations[:activerecord][:errors][:models][:example][:attributes][:attr][:blank] = "Don't you do that to me!"
30
+ translations[:activerecord][:errors][:models][:example][:attributes][:attr][:wrong_length] = "Don't you do that to me!"
30
31
  end
31
32
 
32
33
  unless options[:without].include?(:model)
33
34
  translations[:activerecord][:errors][:models][:example][:blank] = 'Give it one more try!'
35
+ translations[:activerecord][:errors][:models][:example][:wrong_length] = 'Give it one more try!'
34
36
  end
35
37
 
36
38
  unless options[:without].include?(:message)
37
39
  translations[:activerecord][:errors][:messages][:blank] = 'Oh no!'
40
+ translations[:activerecord][:errors][:messages][:wrong_length] = 'Oh no!'
38
41
  end
39
42
 
40
43
  unless options[:without].include?(:attribute)
41
44
  translations[:errors][:attributes][:attr][:blank] = 'Seriously?'
45
+ translations[:errors][:attributes][:attr][:wrong_length] = 'Seriously?'
42
46
  end
43
47
 
44
48
  I18n.backend.store_translations(:en, translations)
@@ -51,6 +55,7 @@ describe Shoulda::Matchers::ActiveModel::Helpers do
51
55
  before do
52
56
  define_model :example, :attr => :string do
53
57
  validates_presence_of :attr
58
+ validates_length_of :attr, :is => 40, :allow_blank => true
54
59
  end
55
60
  @model = Example.new
56
61
  end
@@ -58,40 +63,64 @@ describe Shoulda::Matchers::ActiveModel::Helpers do
58
63
  after { I18n.backend.reload! }
59
64
 
60
65
  context "if the translation for the model attribute’s error exists" do
61
- it "provides the right error message" do
66
+ it "provides the right error message for validate_presence_of" do
62
67
  store_translations
63
68
  @model.should validate_presence_of(:attr)
64
69
  end
70
+
71
+ it "provides the right error message for validates_length_of" do
72
+ store_translations
73
+ @model.should ensure_length_of(:attr).is_equal_to(40)
74
+ end
65
75
  end
66
76
 
67
77
  context "if no translation for the model attribute’s error exists" do
68
78
  context "and the translation for the model’s error exists" do
69
- it "provides the right error message" do
79
+ it "provides the right error message for validate_presence_of" do
70
80
  store_translations(:without => :model_attribute)
71
81
  @model.should validate_presence_of(:attr)
72
82
  end
83
+
84
+ it "provides the right error message for validates_length_of" do
85
+ store_translations(:without => :model_attribute)
86
+ @model.should ensure_length_of(:attr).is_equal_to(40)
87
+ end
73
88
  end
74
89
 
75
90
  context "and no translation for the model’s error exists" do
76
91
  context "and the translation for the message exists" do
77
- it "provides the right error message" do
92
+ it "provides the right error message for validate_presence_of" do
78
93
  store_translations(:without => [:model_attribute, :model])
79
94
  @model.should validate_presence_of(:attr)
80
95
  end
96
+
97
+ it "provides the right error message for validates_length_of" do
98
+ store_translations(:without => [:model_attribute, :model])
99
+ @model.should ensure_length_of(:attr).is_equal_to(40)
100
+ end
81
101
  end
82
102
 
83
103
  context "and no translation for the message exists" do
84
104
  context "and the translation for the attribute exists" do
85
- it "provides the right error message" do
105
+ it "provides the right error message for validate_presence_of" do
86
106
  store_translations(:without => [:model_attribute, :model, :message])
87
107
  @model.should validate_presence_of(:attr)
88
108
  end
109
+
110
+ it "provides the right error message for validates_length_of" do
111
+ store_translations(:without => [:model_attribute, :model, :message])
112
+ @model.should ensure_length_of(:attr).is_equal_to(40)
113
+ end
89
114
  end
90
115
 
91
116
  context "and no translation for the attribute exists" do
92
- it "provides the general error message" do
117
+ it "provides the general error message for validate_presence_of" do
93
118
  @model.should validate_presence_of(:attr)
94
119
  end
120
+
121
+ it "provides the general error message for validates_length_of" do
122
+ @model.should ensure_length_of(:attr).is_equal_to(40)
123
+ end
95
124
  end
96
125
  end
97
126
  end
@@ -1,7 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Shoulda::Matchers::ActiveModel::ValidatePresenceOfMatcher do
4
-
5
4
  context "a required attribute" do
6
5
  before do
7
6
  define_model :example, :attr => :string do
@@ -5,6 +5,7 @@ describe Shoulda::Matchers::ActiveModel::ValidateUniquenessOfMatcher do
5
5
  before do
6
6
  @model = define_model(:example, :attr => :string,
7
7
  :other => :integer) do
8
+ attr_accessible :attr, :other
8
9
  validates_uniqueness_of :attr
9
10
  end.new
10
11
  end
@@ -47,6 +48,7 @@ describe Shoulda::Matchers::ActiveModel::ValidateUniquenessOfMatcher do
47
48
  context "a unique attribute with a custom error and an existing value" do
48
49
  before do
49
50
  @model = define_model(:example, :attr => :string) do
51
+ attr_accessible :attr
50
52
  validates_uniqueness_of :attr, :message => 'Bad value'
51
53
  end.new
52
54
  Example.create!(:attr => 'value')
@@ -70,6 +72,7 @@ describe Shoulda::Matchers::ActiveModel::ValidateUniquenessOfMatcher do
70
72
  @model = define_model(:example, :attr => :string,
71
73
  :scope1 => :integer,
72
74
  :scope2 => :integer) do
75
+ attr_accessible :attr, :scope1, :scope2
73
76
  validates_uniqueness_of :attr, :scope => [:scope1, :scope2]
74
77
  end.new
75
78
  @existing = Example.create!(:attr => 'value', :scope1 => 1, :scope2 => 2)
@@ -98,7 +101,9 @@ describe Shoulda::Matchers::ActiveModel::ValidateUniquenessOfMatcher do
98
101
 
99
102
  context "a non-unique attribute with an existing value" do
100
103
  before do
101
- @model = define_model(:example, :attr => :string).new
104
+ @model = define_model(:example, :attr => :string) do
105
+ attr_accessible :attr
106
+ end.new
102
107
  Example.create!(:attr => 'value')
103
108
  end
104
109
 
@@ -110,6 +115,7 @@ describe Shoulda::Matchers::ActiveModel::ValidateUniquenessOfMatcher do
110
115
  context "a case sensitive unique attribute with an existing value" do
111
116
  before do
112
117
  @model = define_model(:example, :attr => :string) do
118
+ attr_accessible :attr
113
119
  validates_uniqueness_of :attr, :case_sensitive => true
114
120
  end.new
115
121
  Example.create!(:attr => 'value')
@@ -127,6 +133,7 @@ describe Shoulda::Matchers::ActiveModel::ValidateUniquenessOfMatcher do
127
133
  context "a case sensitive unique integer attribute with an existing value" do
128
134
  before do
129
135
  @model = define_model(:example, :attr => :integer) do
136
+ attr_accessible :attr
130
137
  validates_uniqueness_of :attr, :case_sensitive => true
131
138
  end.new
132
139
  Example.create!(:attr => 'value')
@@ -59,7 +59,7 @@ describe Shoulda::Matchers::ActiveRecord::SerializeMatcher do
59
59
 
60
60
  context "a serializer that is an instance of a class" do
61
61
  before do
62
- define_constant(:ExampleSerializer, Object) do
62
+ define_class(:ExampleSerializer, Object) do
63
63
  def load(*); end
64
64
  def dump(*); end
65
65
  end
@@ -0,0 +1,9 @@
1
+ RSpec.configure do |c|
2
+ def active_model_3_1?
3
+ ::ActiveModel::VERSION::MAJOR == 3 && ::ActiveModel::VERSION::MINOR >= 1
4
+ end
5
+
6
+ def active_model_3_2?
7
+ ::ActiveModel::VERSION::MAJOR == 3 && ::ActiveModel::VERSION::MINOR >= 2
8
+ end
9
+ end
@@ -28,11 +28,19 @@ module ModelBuilder
28
28
  end
29
29
  end
30
30
 
31
- def define_constant(class_name, base = Object, &block)
31
+ def define_class(class_name, base = Object, &block)
32
32
  class_name = class_name.to_s.camelize
33
33
 
34
- Class.new(base).tap do |constant_class|
35
- Object.const_set(class_name, constant_class)
34
+ # FIXME: ActionMailer 3.2 calls `name.underscore` immediately upon
35
+ # subclassing. Class.new.name == nil. So, Class.new(ActionMailer::Base)
36
+ # errors out since it's trying to do `nil.underscore`. This is very ugly but
37
+ # allows us to test against ActionMailer 3.2.x.
38
+ eval <<-A_REAL_CLASS_FOR_ACTION_MAILER_3_2
39
+ class ::#{class_name} < #{base}
40
+ end
41
+ A_REAL_CLASS_FOR_ACTION_MAILER_3_2
42
+
43
+ Object.const_get(class_name).tap do |constant_class|
36
44
  constant_class.unloadable
37
45
 
38
46
  if block_given?
@@ -46,11 +54,11 @@ module ModelBuilder
46
54
  end
47
55
 
48
56
  def define_model_class(class_name, &block)
49
- define_constant(class_name, ActiveRecord::Base, &block)
57
+ define_class(class_name, ActiveRecord::Base, &block)
50
58
  end
51
59
 
52
60
  def define_active_model_class(class_name, options = {}, &block)
53
- define_constant(class_name, Object) do
61
+ define_class(class_name, Object) do
54
62
  include ActiveModel::Validations
55
63
 
56
64
  options[:accessors].each do |column|
@@ -78,13 +86,13 @@ module ModelBuilder
78
86
 
79
87
  def define_mailer(name, paths, &block)
80
88
  class_name = name.to_s.pluralize.classify
81
- define_constant(class_name, ActionMailer::Base, &block)
89
+ define_class(class_name, ActionMailer::Base, &block)
82
90
  end
83
91
 
84
92
  def define_controller(class_name, &block)
85
93
  class_name = class_name.to_s
86
94
  class_name << 'Controller' unless class_name =~ /Controller$/
87
- define_constant(class_name, ActionController::Base, &block)
95
+ define_class(class_name, ActionController::Base, &block)
88
96
  end
89
97
 
90
98
  def define_routes(&block)