rspec-expectations 2.6.0 → 2.7.0.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.
- data/README.md +2 -2
- data/features/built_in_matchers/README.md +7 -5
- data/features/built_in_matchers/be.feature +1 -1
- data/features/built_in_matchers/cover.feature +1 -1
- data/features/built_in_matchers/expect_error.feature +59 -26
- data/features/built_in_matchers/have.feature +2 -2
- data/features/built_in_matchers/predicates.feature +1 -1
- data/features/step_definitions/additional_cli_steps.rb +1 -1
- data/features/support/env.rb +1 -1
- data/lib/rspec/expectations.rb +0 -2
- data/lib/rspec/expectations/deprecation.rb +6 -4
- data/lib/rspec/expectations/errors.rb +4 -7
- data/lib/rspec/expectations/extensions.rb +1 -0
- data/lib/rspec/expectations/extensions/array.rb +2 -0
- data/lib/rspec/expectations/extensions/kernel.rb +18 -44
- data/lib/rspec/expectations/{backward_compatibility.rb → extensions/object.rb} +5 -3
- data/lib/rspec/expectations/fail_with.rb +8 -5
- data/lib/rspec/expectations/version.rb +5 -4
- data/lib/rspec/matchers.rb +77 -73
- data/lib/rspec/matchers/be.rb +42 -51
- data/lib/rspec/matchers/be_within.rb +1 -1
- data/lib/rspec/matchers/change.rb +5 -13
- data/lib/rspec/matchers/dsl.rb +2 -1
- data/lib/rspec/matchers/eq.rb +3 -3
- data/lib/rspec/matchers/extensions/{instance_exec.rb → instance_eval_with_args.rb} +15 -7
- data/lib/rspec/matchers/has.rb +11 -6
- data/lib/rspec/matchers/have.rb +36 -19
- data/lib/rspec/matchers/match_array.rb +1 -1
- data/lib/rspec/matchers/matcher.rb +5 -5
- data/spec/rspec/matchers/change_spec.rb +38 -0
- data/spec/rspec/matchers/description_generation_spec.rb +32 -32
- data/spec/rspec/matchers/eq_spec.rb +2 -2
- data/spec/rspec/matchers/has_spec.rb +33 -1
- data/spec/rspec/matchers/have_spec.rb +64 -7
- data/spec/rspec/matchers/match_array_spec.rb +0 -3
- data/spec/rspec/matchers/operator_matcher_spec.rb +10 -4
- data/spec/rspec/matchers/raise_error_spec.rb +6 -6
- data/spec/support/classes.rb +21 -10
- metadata +51 -62
- data/.document +0 -5
- data/.gitignore +0 -10
- data/.travis.yml +0 -7
- data/Gemfile +0 -40
- data/Guardfile +0 -5
- data/License.txt +0 -22
- data/Rakefile +0 -81
- data/cucumber.yml +0 -10
- data/features/.nav +0 -29
- data/features/Changelog.md +0 -101
- data/rspec-expectations.gemspec +0 -27
- data/specs.watchr +0 -57
data/lib/rspec/matchers/has.rb
CHANGED
@@ -10,15 +10,15 @@ module RSpec
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def failure_message_for_should
|
13
|
-
"expected ##{predicate(@expected)}
|
13
|
+
"expected ##{predicate(@expected)}#{failure_message_args_description} to return true, got false"
|
14
14
|
end
|
15
15
|
|
16
16
|
def failure_message_for_should_not
|
17
|
-
"expected ##{predicate(@expected)}
|
17
|
+
"expected ##{predicate(@expected)}#{failure_message_args_description} to return false, got true"
|
18
18
|
end
|
19
19
|
|
20
20
|
def description
|
21
|
-
[method_description(@expected), args_description
|
21
|
+
[method_description(@expected), args_description].compact.join(' ')
|
22
22
|
end
|
23
23
|
|
24
24
|
private
|
@@ -30,9 +30,14 @@ module RSpec
|
|
30
30
|
method.to_s.gsub('_', ' ')
|
31
31
|
end
|
32
32
|
|
33
|
-
def args_description
|
34
|
-
return nil if args.empty?
|
35
|
-
args.map { |arg| arg.inspect }.join(', ')
|
33
|
+
def args_description
|
34
|
+
return nil if @args.empty?
|
35
|
+
@args.map { |arg| arg.inspect }.join(', ')
|
36
|
+
end
|
37
|
+
|
38
|
+
def failure_message_args_description
|
39
|
+
desc = args_description
|
40
|
+
"(#{desc})" if desc
|
36
41
|
end
|
37
42
|
end
|
38
43
|
end
|
data/lib/rspec/matchers/have.rb
CHANGED
@@ -2,7 +2,11 @@ module RSpec
|
|
2
2
|
module Matchers
|
3
3
|
class Have #:nodoc:
|
4
4
|
def initialize(expected, relativity=:exactly)
|
5
|
-
@expected =
|
5
|
+
@expected = case expected
|
6
|
+
when :no then 0
|
7
|
+
when String then expected.to_i
|
8
|
+
else expected
|
9
|
+
end
|
6
10
|
@relativity = relativity
|
7
11
|
@actual = @collection_name = @plural_collection_name = nil
|
8
12
|
end
|
@@ -15,26 +19,36 @@ module RSpec
|
|
15
19
|
}
|
16
20
|
end
|
17
21
|
|
18
|
-
def matches?(
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
def matches?(collection_or_owner)
|
23
|
+
collection = determine_collection(collection_or_owner)
|
24
|
+
query_method = determine_query_method(collection)
|
25
|
+
raise not_a_collection unless query_method
|
26
|
+
@actual = collection.send(query_method)
|
27
|
+
case @relativity
|
28
|
+
when :at_least then @actual >= @expected
|
29
|
+
when :at_most then @actual <= @expected
|
30
|
+
else @actual == @expected
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def determine_collection(collection_or_owner)
|
35
|
+
if collection_or_owner.respond_to?(@collection_name)
|
36
|
+
collection_or_owner.send(@collection_name, *@args, &@block)
|
37
|
+
elsif (@plural_collection_name && collection_or_owner.respond_to?(@plural_collection_name))
|
38
|
+
collection_or_owner.send(@plural_collection_name, *@args, &@block)
|
39
|
+
elsif determine_query_method(collection_or_owner)
|
40
|
+
collection_or_owner
|
25
41
|
else
|
26
|
-
|
42
|
+
collection_or_owner.send(@collection_name, *@args, &@block)
|
27
43
|
end
|
28
|
-
@actual = collection.size if collection.respond_to?(:size)
|
29
|
-
@actual = collection.length if collection.respond_to?(:length)
|
30
|
-
raise not_a_collection if @actual.nil?
|
31
|
-
return @actual >= @expected if @relativity == :at_least
|
32
|
-
return @actual <= @expected if @relativity == :at_most
|
33
|
-
return @actual == @expected
|
34
44
|
end
|
35
|
-
|
45
|
+
|
46
|
+
def determine_query_method(collection)
|
47
|
+
[:size, :length, :count].detect {|m| collection.respond_to?(m)}
|
48
|
+
end
|
49
|
+
|
36
50
|
def not_a_collection
|
37
|
-
"expected #{@collection_name} to be a collection but it does not respond to #length or #
|
51
|
+
"expected #{@collection_name} to be a collection but it does not respond to #length, #size or #count"
|
38
52
|
end
|
39
53
|
|
40
54
|
def failure_message_for_should
|
@@ -67,8 +81,8 @@ EOF
|
|
67
81
|
"have #{relative_expectation} #{@collection_name}"
|
68
82
|
end
|
69
83
|
|
70
|
-
def respond_to?(
|
71
|
-
@expected.respond_to?(
|
84
|
+
def respond_to?(m)
|
85
|
+
@expected.respond_to?(m) || super
|
72
86
|
end
|
73
87
|
|
74
88
|
private
|
@@ -117,6 +131,9 @@ EOF
|
|
117
131
|
# # Passes if [1,2,3].length == 3
|
118
132
|
# [1,2,3].should have(3).items #"items" is pure sugar
|
119
133
|
#
|
134
|
+
# # Passes if ['a', 'b', 'c'].count == 3
|
135
|
+
# [1,2,3].should have(3).items #"items" is pure sugar
|
136
|
+
#
|
120
137
|
# # Passes if "this string".length == 11
|
121
138
|
# "this string".should have(11).characters #"characters" is pure sugar
|
122
139
|
def have(n)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module RSpec
|
2
2
|
module Matchers
|
3
3
|
class Matcher
|
4
|
-
include RSpec::Matchers::
|
4
|
+
include RSpec::Matchers::Extensions::InstanceEvalWithArgs
|
5
5
|
include RSpec::Matchers::Pretty
|
6
6
|
include RSpec::Matchers
|
7
7
|
|
@@ -20,7 +20,7 @@ module RSpec
|
|
20
20
|
:failure_message_for_should_not => lambda {|actual| "expected #{actual.inspect} not to #{name_to_sentence}#{expected_to_sentence}"}
|
21
21
|
}
|
22
22
|
making_declared_methods_public do
|
23
|
-
|
23
|
+
instance_eval_with_args(*@expected, &declarations)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
@@ -29,14 +29,14 @@ module RSpec
|
|
29
29
|
@actual = actual
|
30
30
|
if @expected_exception
|
31
31
|
begin
|
32
|
-
|
32
|
+
instance_eval_with_args(actual, &@match_block)
|
33
33
|
true
|
34
34
|
rescue @expected_exception => @rescued_exception
|
35
35
|
false
|
36
36
|
end
|
37
37
|
else
|
38
38
|
begin
|
39
|
-
|
39
|
+
instance_eval_with_args(actual, &@match_block)
|
40
40
|
rescue RSpec::Expectations::ExpectationNotMetError
|
41
41
|
false
|
42
42
|
end
|
@@ -47,7 +47,7 @@ module RSpec
|
|
47
47
|
def does_not_match?(actual)
|
48
48
|
@actual = actual
|
49
49
|
@match_for_should_not_block ?
|
50
|
-
|
50
|
+
instance_eval_with_args(actual, &@match_for_should_not_block) :
|
51
51
|
!matches?(actual)
|
52
52
|
end
|
53
53
|
|
@@ -95,6 +95,44 @@ describe "should change(actual, message)" do
|
|
95
95
|
end.to fail
|
96
96
|
end
|
97
97
|
end
|
98
|
+
|
99
|
+
context "with an arbitrary enumerable" do
|
100
|
+
before(:each) do
|
101
|
+
@instance = SomethingExpected.new
|
102
|
+
@instance.some_value = Class.new do
|
103
|
+
include Enumerable
|
104
|
+
|
105
|
+
attr_reader :elements
|
106
|
+
|
107
|
+
def initialize(*elements)
|
108
|
+
@elements = elements.dup
|
109
|
+
end
|
110
|
+
|
111
|
+
def <<(element)
|
112
|
+
elements << element
|
113
|
+
end
|
114
|
+
|
115
|
+
def dup
|
116
|
+
self.class.new *elements
|
117
|
+
end
|
118
|
+
|
119
|
+
def ==(other)
|
120
|
+
elements == other.elements
|
121
|
+
end
|
122
|
+
end.new
|
123
|
+
end
|
124
|
+
|
125
|
+
it "passes when actual is modified by the block" do
|
126
|
+
expect {@instance.some_value << 1}.to change(@instance, :some_value)
|
127
|
+
end
|
128
|
+
|
129
|
+
it "fails when actual is not modified by the block" do
|
130
|
+
expect do
|
131
|
+
expect {}.to change(@instance, :some_value)
|
132
|
+
end.to fail_with(/^some_value should have changed, but is still/)
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
98
136
|
end
|
99
137
|
|
100
138
|
describe "should_not change(actual, message)" do
|
@@ -5,75 +5,75 @@ describe "Matchers should be able to generate their own descriptions" do
|
|
5
5
|
RSpec::Matchers.clear_generated_description
|
6
6
|
end
|
7
7
|
|
8
|
-
it "should
|
9
|
-
"this".should
|
10
|
-
RSpec::Matchers.generated_description.should
|
8
|
+
it "should eq expected" do
|
9
|
+
"this".should eq "this"
|
10
|
+
RSpec::Matchers.generated_description.should eq "should eq this"
|
11
11
|
end
|
12
12
|
|
13
|
-
it "should not
|
14
|
-
"this".should_not
|
15
|
-
RSpec::Matchers.generated_description.should
|
13
|
+
it "should not eq expected" do
|
14
|
+
"this".should_not eq "that"
|
15
|
+
RSpec::Matchers.generated_description.should eq "should not eq that"
|
16
16
|
end
|
17
17
|
|
18
18
|
it "should be empty (arbitrary predicate)" do
|
19
19
|
[].should be_empty
|
20
|
-
RSpec::Matchers.generated_description.should
|
20
|
+
RSpec::Matchers.generated_description.should eq "should be empty"
|
21
21
|
end
|
22
22
|
|
23
23
|
it "should not be empty (arbitrary predicate)" do
|
24
24
|
[1].should_not be_empty
|
25
|
-
RSpec::Matchers.generated_description.should
|
25
|
+
RSpec::Matchers.generated_description.should eq "should not be empty"
|
26
26
|
end
|
27
27
|
|
28
28
|
it "should be true" do
|
29
29
|
true.should be_true
|
30
|
-
RSpec::Matchers.generated_description.should
|
30
|
+
RSpec::Matchers.generated_description.should eq "should be true"
|
31
31
|
end
|
32
32
|
|
33
33
|
it "should be false" do
|
34
34
|
false.should be_false
|
35
|
-
RSpec::Matchers.generated_description.should
|
35
|
+
RSpec::Matchers.generated_description.should eq "should be false"
|
36
36
|
end
|
37
37
|
|
38
38
|
it "should be nil" do
|
39
39
|
nil.should be_nil
|
40
|
-
RSpec::Matchers.generated_description.should
|
40
|
+
RSpec::Matchers.generated_description.should eq "should be nil"
|
41
41
|
end
|
42
42
|
|
43
43
|
it "should be > n" do
|
44
44
|
5.should be > 3
|
45
|
-
RSpec::Matchers.generated_description.should
|
45
|
+
RSpec::Matchers.generated_description.should eq "should be > 3"
|
46
46
|
end
|
47
47
|
|
48
48
|
it "should be predicate arg1, arg2 and arg3" do
|
49
49
|
5.0.should be_between(0,10)
|
50
|
-
RSpec::Matchers.generated_description.should
|
50
|
+
RSpec::Matchers.generated_description.should eq "should be between 0 and 10"
|
51
51
|
end
|
52
52
|
|
53
53
|
it "should equal" do
|
54
54
|
expected = "expected"
|
55
55
|
expected.should equal(expected)
|
56
|
-
RSpec::Matchers.generated_description.should
|
56
|
+
RSpec::Matchers.generated_description.should eq "should equal \"expected\""
|
57
57
|
end
|
58
58
|
|
59
59
|
it "should_not equal" do
|
60
60
|
5.should_not equal(37)
|
61
|
-
RSpec::Matchers.generated_description.should
|
61
|
+
RSpec::Matchers.generated_description.should eq "should not equal 37"
|
62
62
|
end
|
63
63
|
|
64
64
|
it "should eql" do
|
65
65
|
"string".should eql("string")
|
66
|
-
RSpec::Matchers.generated_description.should
|
66
|
+
RSpec::Matchers.generated_description.should eq "should eql \"string\""
|
67
67
|
end
|
68
68
|
|
69
69
|
it "should not eql" do
|
70
70
|
"a".should_not eql(:a)
|
71
|
-
RSpec::Matchers.generated_description.should
|
71
|
+
RSpec::Matchers.generated_description.should eq "should not eql :a"
|
72
72
|
end
|
73
73
|
|
74
74
|
it "should have_key" do
|
75
75
|
{:a => "a"}.should have_key(:a)
|
76
|
-
RSpec::Matchers.generated_description.should
|
76
|
+
RSpec::Matchers.generated_description.should eq "should have key :a"
|
77
77
|
end
|
78
78
|
|
79
79
|
it "should have_some_method" do
|
@@ -81,7 +81,7 @@ describe "Matchers should be able to generate their own descriptions" do
|
|
81
81
|
def object.has_eyes_closed?; true; end
|
82
82
|
|
83
83
|
object.should have_eyes_closed
|
84
|
-
RSpec::Matchers.generated_description.should
|
84
|
+
RSpec::Matchers.generated_description.should eq 'should have eyes closed'
|
85
85
|
end
|
86
86
|
|
87
87
|
it "should have_some_method(args*)" do
|
@@ -89,67 +89,67 @@ describe "Matchers should be able to generate their own descriptions" do
|
|
89
89
|
def object.has_taste_for?(*args); true; end
|
90
90
|
|
91
91
|
object.should have_taste_for("wine", "cheese")
|
92
|
-
RSpec::Matchers.generated_description.should
|
92
|
+
RSpec::Matchers.generated_description.should eq 'should have taste for "wine", "cheese"'
|
93
93
|
end
|
94
94
|
|
95
95
|
it "should have n items" do
|
96
96
|
team.should have(3).players
|
97
|
-
RSpec::Matchers.generated_description.should
|
97
|
+
RSpec::Matchers.generated_description.should eq "should have 3 players"
|
98
98
|
end
|
99
99
|
|
100
100
|
it "should have at least n items" do
|
101
101
|
team.should have_at_least(2).players
|
102
|
-
RSpec::Matchers.generated_description.should
|
102
|
+
RSpec::Matchers.generated_description.should eq "should have at least 2 players"
|
103
103
|
end
|
104
104
|
|
105
105
|
it "should have at most n items" do
|
106
106
|
team.should have_at_most(4).players
|
107
|
-
RSpec::Matchers.generated_description.should
|
107
|
+
RSpec::Matchers.generated_description.should eq "should have at most 4 players"
|
108
108
|
end
|
109
109
|
|
110
110
|
it "should include" do
|
111
111
|
[1,2,3].should include(3)
|
112
|
-
RSpec::Matchers.generated_description.should
|
112
|
+
RSpec::Matchers.generated_description.should eq "should include 3"
|
113
113
|
end
|
114
114
|
|
115
115
|
it "array.should =~ [1,2,3]" do
|
116
116
|
[1,2,3].should =~ [1,2,3]
|
117
|
-
RSpec::Matchers.generated_description.should
|
117
|
+
RSpec::Matchers.generated_description.should eq "should contain exactly 1, 2 and 3"
|
118
118
|
end
|
119
119
|
|
120
120
|
it "should match" do
|
121
121
|
"this string".should match(/this string/)
|
122
|
-
RSpec::Matchers.generated_description.should
|
122
|
+
RSpec::Matchers.generated_description.should eq "should match /this string/"
|
123
123
|
end
|
124
124
|
|
125
125
|
it "should raise_error" do
|
126
126
|
lambda { raise }.should raise_error
|
127
|
-
RSpec::Matchers.generated_description.should
|
127
|
+
RSpec::Matchers.generated_description.should eq "should raise Exception"
|
128
128
|
end
|
129
129
|
|
130
130
|
it "should raise_error with type" do
|
131
131
|
lambda { raise }.should raise_error(RuntimeError)
|
132
|
-
RSpec::Matchers.generated_description.should
|
132
|
+
RSpec::Matchers.generated_description.should eq "should raise RuntimeError"
|
133
133
|
end
|
134
134
|
|
135
135
|
it "should raise_error with type and message" do
|
136
136
|
lambda { raise "there was an error" }.should raise_error(RuntimeError, "there was an error")
|
137
|
-
RSpec::Matchers.generated_description.should
|
137
|
+
RSpec::Matchers.generated_description.should eq "should raise RuntimeError with \"there was an error\""
|
138
138
|
end
|
139
139
|
|
140
140
|
it "should respond_to" do
|
141
141
|
[].should respond_to(:insert)
|
142
|
-
RSpec::Matchers.generated_description.should
|
142
|
+
RSpec::Matchers.generated_description.should eq "should respond to #insert"
|
143
143
|
end
|
144
144
|
|
145
145
|
it "should throw symbol" do
|
146
146
|
lambda { throw :what_a_mess }.should throw_symbol
|
147
|
-
RSpec::Matchers.generated_description.should
|
147
|
+
RSpec::Matchers.generated_description.should eq "should throw a Symbol"
|
148
148
|
end
|
149
149
|
|
150
150
|
it "should throw symbol (with named symbol)" do
|
151
151
|
lambda { throw :what_a_mess }.should throw_symbol(:what_a_mess)
|
152
|
-
RSpec::Matchers.generated_description.should
|
152
|
+
RSpec::Matchers.generated_description.should eq "should throw :what_a_mess"
|
153
153
|
end
|
154
154
|
|
155
155
|
def team
|
@@ -18,13 +18,13 @@ module RSpec
|
|
18
18
|
it "describes itself" do
|
19
19
|
matcher = eq(1)
|
20
20
|
matcher.matches?(1)
|
21
|
-
matcher.description.should == "
|
21
|
+
matcher.description.should == "eq 1"
|
22
22
|
end
|
23
23
|
|
24
24
|
it "provides message, expected and actual on #failure_message" do
|
25
25
|
matcher = eq("1")
|
26
26
|
matcher.matches?(1)
|
27
|
-
matcher.failure_message_for_should.should == "\nexpected \"1\"\n got 1\n\n(compared using ==)\n"
|
27
|
+
matcher.failure_message_for_should.should == "\nexpected: \"1\"\n got: 1\n\n(compared using ==)\n"
|
28
28
|
end
|
29
29
|
|
30
30
|
it "provides message, expected and actual on #negative_failure_message" do
|
@@ -11,6 +11,22 @@ describe "should have_sym(*args)" do
|
|
11
11
|
}.should fail_with("expected #has_key?(:a) to return true, got false")
|
12
12
|
end
|
13
13
|
|
14
|
+
it 'does not include any args in the failure message if no args were given to the matcher' do
|
15
|
+
o = Object.new
|
16
|
+
def o.has_some_stuff?; false; end
|
17
|
+
expect {
|
18
|
+
o.should have_some_stuff
|
19
|
+
}.to fail_with("expected #has_some_stuff? to return true, got false")
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'includes multiple args in the failure message if multiple args were given to the matcher' do
|
23
|
+
o = Object.new
|
24
|
+
def o.has_some_stuff?(*_); false; end
|
25
|
+
expect {
|
26
|
+
o.should have_some_stuff(:a, 7, "foo")
|
27
|
+
}.to fail_with('expected #has_some_stuff?(:a, 7, "foo") to return true, got false')
|
28
|
+
end
|
29
|
+
|
14
30
|
it "fails if #has_sym?(*args) returns nil" do
|
15
31
|
klass = Class.new do
|
16
32
|
def has_foo?
|
@@ -18,7 +34,7 @@ describe "should have_sym(*args)" do
|
|
18
34
|
end
|
19
35
|
lambda {
|
20
36
|
klass.new.should have_foo
|
21
|
-
}.should fail_with(
|
37
|
+
}.should fail_with(/expected #has_foo.* to return true, got false/)
|
22
38
|
end
|
23
39
|
|
24
40
|
it "fails if target does not respond to #has_sym?" do
|
@@ -68,6 +84,22 @@ describe "should_not have_sym(*args)" do
|
|
68
84
|
end
|
69
85
|
lambda { o.should_not have_sym(:foo) }.should raise_error("Funky exception")
|
70
86
|
end
|
87
|
+
|
88
|
+
it 'does not include any args in the failure message if no args were given to the matcher' do
|
89
|
+
o = Object.new
|
90
|
+
def o.has_some_stuff?; true; end
|
91
|
+
expect {
|
92
|
+
o.should_not have_some_stuff
|
93
|
+
}.to fail_with("expected #has_some_stuff? to return false, got true")
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'includes multiple args in the failure message if multiple args were given to the matcher' do
|
97
|
+
o = Object.new
|
98
|
+
def o.has_some_stuff?(*_); true; end
|
99
|
+
expect {
|
100
|
+
o.should_not have_some_stuff(:a, 7, "foo")
|
101
|
+
}.to fail_with('expected #has_some_stuff?(:a, 7, "foo") to return false, got true')
|
102
|
+
end
|
71
103
|
end
|
72
104
|
|
73
105
|
describe "has" do
|