rspec-expectations 2.12.1 → 2.13.0
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/Changelog.md +31 -0
- data/README.md +1 -1
- data/features/built_in_matchers/be.feature +6 -4
- data/features/built_in_matchers/be_within.feature +3 -1
- data/features/built_in_matchers/cover.feature +2 -0
- data/features/built_in_matchers/end_with.feature +2 -0
- data/features/built_in_matchers/equality.feature +9 -15
- data/features/built_in_matchers/exist.feature +2 -0
- data/features/built_in_matchers/expect_error.feature +14 -8
- data/features/built_in_matchers/have.feature +11 -5
- data/features/built_in_matchers/include.feature +53 -0
- data/features/built_in_matchers/match.feature +2 -0
- data/features/built_in_matchers/operators.feature +17 -11
- data/features/built_in_matchers/predicates.feature +21 -13
- data/features/built_in_matchers/respond_to.feature +7 -1
- data/features/built_in_matchers/satisfy.feature +2 -0
- data/features/built_in_matchers/start_with.feature +2 -0
- data/features/built_in_matchers/throw_symbol.feature +6 -0
- data/features/built_in_matchers/types.feature +8 -6
- data/lib/rspec/expectations/deprecation.rb +1 -1
- data/lib/rspec/expectations/differ.rb +8 -8
- data/lib/rspec/expectations/fail_with.rb +17 -3
- data/lib/rspec/expectations/syntax.rb +46 -0
- data/lib/rspec/expectations/version.rb +1 -1
- data/lib/rspec/matchers/built_in/be.rb +7 -3
- data/lib/rspec/matchers/built_in/be_within.rb +13 -4
- data/lib/rspec/matchers/built_in/change.rb +2 -2
- data/lib/rspec/matchers/built_in/equal.rb +5 -1
- data/lib/rspec/matchers/built_in/exist.rb +1 -1
- data/lib/rspec/matchers/built_in/have.rb +8 -8
- data/lib/rspec/matchers/built_in/include.rb +19 -3
- data/lib/rspec/matchers/built_in/respond_to.rb +1 -1
- data/lib/rspec/matchers/extensions/instance_eval_with_args.rb +1 -1
- data/lib/rspec/matchers/matcher.rb +4 -3
- data/lib/rspec/matchers/operator_matcher.rb +1 -1
- data/lib/rspec/matchers/pretty.rb +5 -1
- data/spec/rspec/expectations/differ_spec.rb +8 -15
- data/spec/rspec/expectations/expectation_target_spec.rb +18 -8
- data/spec/rspec/expectations/extensions/kernel_spec.rb +15 -15
- data/spec/rspec/expectations/fail_with_spec.rb +41 -16
- data/spec/rspec/expectations/handler_spec.rb +13 -13
- data/spec/rspec/expectations/syntax_spec.rb +70 -8
- data/spec/rspec/matchers/base_matcher_spec.rb +14 -12
- data/spec/rspec/matchers/be_close_spec.rb +1 -1
- data/spec/rspec/matchers/be_instance_of_spec.rb +14 -8
- data/spec/rspec/matchers/be_kind_of_spec.rb +12 -8
- data/spec/rspec/matchers/be_spec.rb +212 -148
- data/spec/rspec/matchers/be_within_spec.rb +91 -42
- data/spec/rspec/matchers/change_spec.rb +52 -38
- data/spec/rspec/matchers/configuration_spec.rb +19 -15
- data/spec/rspec/matchers/cover_spec.rb +19 -19
- data/spec/rspec/matchers/description_generation_spec.rb +86 -86
- data/spec/rspec/matchers/dsl_spec.rb +7 -7
- data/spec/rspec/matchers/eq_spec.rb +17 -11
- data/spec/rspec/matchers/eql_spec.rb +10 -10
- data/spec/rspec/matchers/equal_spec.rb +27 -9
- data/spec/rspec/matchers/exist_spec.rb +35 -21
- data/spec/rspec/matchers/has_spec.rb +33 -29
- data/spec/rspec/matchers/have_spec.rb +165 -151
- data/spec/rspec/matchers/include_matcher_integration_spec.rb +30 -0
- data/spec/rspec/matchers/include_spec.rb +282 -124
- data/spec/rspec/matchers/match_array_spec.rb +90 -49
- data/spec/rspec/matchers/match_spec.rb +21 -21
- data/spec/rspec/matchers/matcher_spec.rb +85 -48
- data/spec/rspec/matchers/matchers_spec.rb +12 -6
- data/spec/rspec/matchers/method_missing_spec.rb +5 -1
- data/spec/rspec/matchers/operator_matcher_spec.rb +216 -237
- data/spec/rspec/matchers/raise_error_spec.rb +132 -132
- data/spec/rspec/matchers/respond_to_spec.rb +109 -112
- data/spec/rspec/matchers/satisfy_spec.rb +16 -16
- data/spec/rspec/matchers/start_with_end_with_spec.rb +36 -32
- data/spec/rspec/matchers/throw_symbol_spec.rb +24 -24
- data/spec/rspec/matchers/yield_spec.rb +7 -7
- data/spec/spec_helper.rb +46 -19
- data/spec/support/in_sub_process.rb +27 -20
- metadata +81 -83
@@ -3,8 +3,10 @@ Feature: satisfy matcher
|
|
3
3
|
The satisfy matcher is extremely flexible and can handle almost anything
|
4
4
|
you want to specify. It passes if the block you provide returns true:
|
5
5
|
|
6
|
+
```ruby
|
6
7
|
10.should satisfy { |v| v % 5 == 0 }
|
7
8
|
7.should_not satisfy { |v| v % 5 == 0 }
|
9
|
+
```
|
8
10
|
|
9
11
|
This flexibility comes at a cost, however: the failure message
|
10
12
|
("expected [actual] to satisfy block") is not very descriptive
|
@@ -3,9 +3,11 @@ Feature: start_with matcher
|
|
3
3
|
Use the `start_with` matcher to specify that a string or array starts with
|
4
4
|
the expected characters or elements.
|
5
5
|
|
6
|
+
```ruby
|
6
7
|
"this string".should start_with("this")
|
7
8
|
"this string".should_not start_with("that")
|
8
9
|
[0,1,2].should start_with(0, 1)
|
10
|
+
```
|
9
11
|
|
10
12
|
Scenario: with a string
|
11
13
|
Given a file named "example_spec.rb" with:
|
@@ -3,16 +3,22 @@ Feature: throw_symbol matcher
|
|
3
3
|
The throw_symbol matcher is used to specify that a block of code
|
4
4
|
throws a symbol. The most basic form passes if any symbol is thrown:
|
5
5
|
|
6
|
+
```ruby
|
6
7
|
expect { throw :foo }.to throw_symbol
|
8
|
+
```
|
7
9
|
|
8
10
|
You'll often want to specify that a particular symbol is thrown:
|
9
11
|
|
12
|
+
```ruby
|
10
13
|
expect { throw :foo }.to throw_symbol(:foo)
|
14
|
+
```
|
11
15
|
|
12
16
|
If you care about the additional argument given to throw, you can
|
13
17
|
specify that as well:
|
14
18
|
|
19
|
+
```ruby
|
15
20
|
expect { throw :foo, 7 }.to throw_symbol(:foo, 7)
|
21
|
+
```
|
16
22
|
|
17
23
|
Scenario: basic usage
|
18
24
|
Given a file named "throw_symbol_matcher_spec.rb" with:
|
@@ -2,18 +2,20 @@ Feature: specify types of objects
|
|
2
2
|
|
3
3
|
rspec-expectations includes two matchers to specify types of objects:
|
4
4
|
|
5
|
-
* obj.should be_kind_of(type)
|
5
|
+
* `obj.should be_kind_of(type)`: calls `obj.kind_of?(type)`, which returns
|
6
6
|
true if type is in obj's class hierarchy or is a module and is
|
7
7
|
included in a class in obj's class hierarchy.
|
8
|
-
* obj.should be_instance_of(type)
|
8
|
+
* `obj.should be_instance_of(type)`: calls `obj.instance_of?(type)`, which
|
9
9
|
returns true if and only if type if obj's class.
|
10
10
|
|
11
11
|
Both of these matchers have aliases:
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
```ruby
|
14
|
+
obj.should be_a_kind_of(type) # same as obj.should be_kind_of(type)
|
15
|
+
obj.should be_a(type) # same as obj.should be_kind_of(type)
|
16
|
+
obj.should be_an(type) # same as obj.should be_kind_of(type)
|
17
|
+
obj.should be_an_instance_of(type) # same as obj.should be_instance_of(type)
|
18
|
+
```
|
17
19
|
|
18
20
|
Scenario: be_(a_)kind_of matcher
|
19
21
|
Given a file named "be_kind_of_matcher_spec.rb" with:
|
@@ -25,7 +25,13 @@ module RSpec
|
|
25
25
|
# diff includes lines of context. Otherwise, we might print
|
26
26
|
# redundant lines.
|
27
27
|
if (context_lines > 0) and hunk.overlaps?(oldhunk)
|
28
|
-
hunk.
|
28
|
+
if hunk.respond_to?(:merge)
|
29
|
+
# diff-lcs 1.2.x
|
30
|
+
hunk.merge(oldhunk)
|
31
|
+
else
|
32
|
+
# diff-lcs 1.1.3
|
33
|
+
hunk.unshift(oldhunk)
|
34
|
+
end
|
29
35
|
else
|
30
36
|
output << oldhunk.diff(format)
|
31
37
|
end
|
@@ -42,13 +48,7 @@ module RSpec
|
|
42
48
|
def diff_as_object(actual, expected)
|
43
49
|
actual_as_string = object_to_string(actual)
|
44
50
|
expected_as_string = object_to_string(expected)
|
45
|
-
diff = diff_as_string(actual_as_string, expected_as_string)
|
46
|
-
|
47
|
-
if diff.empty?
|
48
|
-
"#{actual}.==(#{expected}) returned false even though the diff " \
|
49
|
-
"between #{actual} and #{expected} is empty. Check the " \
|
50
|
-
"implementation of #{actual}.==."
|
51
|
-
else
|
51
|
+
if diff = diff_as_string(actual_as_string, expected_as_string)
|
52
52
|
color_diff diff
|
53
53
|
end
|
54
54
|
end
|
@@ -22,8 +22,7 @@ module RSpec
|
|
22
22
|
if actual && expected
|
23
23
|
if all_strings?(actual, expected)
|
24
24
|
if any_multiline_strings?(actual, expected)
|
25
|
-
|
26
|
-
message << "\nDiff:" << differ.diff_as_string(actual, expected)
|
25
|
+
message << "\nDiff:" << differ.diff_as_string(coerce_to_string(actual), coerce_to_string(expected))
|
27
26
|
end
|
28
27
|
elsif no_procs?(actual, expected) && no_numbers?(actual, expected)
|
29
28
|
message << "\nDiff:" << differ.diff_as_object(actual, expected)
|
@@ -44,12 +43,27 @@ module RSpec
|
|
44
43
|
end
|
45
44
|
|
46
45
|
def any_multiline_strings?(*args)
|
47
|
-
all_strings?(*args) && args.any? {|a| a
|
46
|
+
all_strings?(*args) && args.flatten.any? { |a| multiline?(a) }
|
48
47
|
end
|
49
48
|
|
50
49
|
def no_numbers?(*args)
|
51
50
|
args.flatten.none? {|a| Numeric === a}
|
52
51
|
end
|
52
|
+
|
53
|
+
def coerce_to_string(string_or_array)
|
54
|
+
return string_or_array unless Array === string_or_array
|
55
|
+
string_or_array.join(',')
|
56
|
+
end
|
57
|
+
|
58
|
+
if String.method_defined?(:encoding)
|
59
|
+
def multiline?(string)
|
60
|
+
string.include?("\n".encode(string.encoding))
|
61
|
+
end
|
62
|
+
else
|
63
|
+
def multiline?(string)
|
64
|
+
string.include?("\n")
|
65
|
+
end
|
66
|
+
end
|
53
67
|
end
|
54
68
|
end
|
55
69
|
end
|
@@ -113,6 +113,52 @@ module RSpec
|
|
113
113
|
def expect_enabled?(syntax_host = ::RSpec::Matchers)
|
114
114
|
syntax_host.method_defined?(:expect)
|
115
115
|
end
|
116
|
+
|
117
|
+
# @api private
|
118
|
+
# Generates a positive expectation expression.
|
119
|
+
def positive_expression(target_expression, matcher_expression)
|
120
|
+
expression_generator.positive_expression(target_expression, matcher_expression)
|
121
|
+
end
|
122
|
+
|
123
|
+
# @api private
|
124
|
+
# Generates a negative expectation expression.
|
125
|
+
def negative_expression(target_expression, matcher_expression)
|
126
|
+
expression_generator.negative_expression(target_expression, matcher_expression)
|
127
|
+
end
|
128
|
+
|
129
|
+
# @api private
|
130
|
+
# Selects which expression generator to use based on the configured syntax.
|
131
|
+
def expression_generator
|
132
|
+
if expect_enabled?
|
133
|
+
ExpectExpressionGenerator
|
134
|
+
else
|
135
|
+
ShouldExpressionGenerator
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# @api private
|
140
|
+
# Generates expectation expressions for the `should` syntax.
|
141
|
+
module ShouldExpressionGenerator
|
142
|
+
def self.positive_expression(target_expression, matcher_expression)
|
143
|
+
"#{target_expression}.should #{matcher_expression}"
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.negative_expression(target_expression, matcher_expression)
|
147
|
+
"#{target_expression}.should_not #{matcher_expression}"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# @api private
|
152
|
+
# Generates expectation expressions for the `expect` syntax.
|
153
|
+
module ExpectExpressionGenerator
|
154
|
+
def self.positive_expression(target_expression, matcher_expression)
|
155
|
+
"expect(#{target_expression}).to #{matcher_expression}"
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.negative_expression(target_expression, matcher_expression)
|
159
|
+
"expect(#{target_expression}).not_to #{matcher_expression}"
|
160
|
+
end
|
161
|
+
end
|
116
162
|
end
|
117
163
|
end
|
118
164
|
end
|
@@ -62,7 +62,7 @@ module RSpec
|
|
62
62
|
"expected #{@actual.inspect} to evaluate to false"
|
63
63
|
end
|
64
64
|
|
65
|
-
[:==, :<, :<=, :>=, :>,
|
65
|
+
[:==, :<, :<=, :>=, :>, :===, :=~].each do |operator|
|
66
66
|
define_method operator do |operand|
|
67
67
|
BeComparedTo.new(operand, operator)
|
68
68
|
end
|
@@ -99,7 +99,7 @@ module RSpec
|
|
99
99
|
|
100
100
|
def matches?(actual)
|
101
101
|
@actual = actual
|
102
|
-
@actual.
|
102
|
+
@actual.__send__ @operator, @expected
|
103
103
|
end
|
104
104
|
|
105
105
|
def failure_message_for_should
|
@@ -108,7 +108,7 @@ module RSpec
|
|
108
108
|
|
109
109
|
def failure_message_for_should_not
|
110
110
|
message = <<-MESSAGE
|
111
|
-
|
111
|
+
`#{negative_expectation_expression}` not only FAILED,
|
112
112
|
it is a bit confusing.
|
113
113
|
MESSAGE
|
114
114
|
|
@@ -120,6 +120,10 @@ it is a bit confusing.
|
|
120
120
|
def description
|
121
121
|
"be #{@operator} #{expected_to_sentence}#{args_to_sentence}"
|
122
122
|
end
|
123
|
+
|
124
|
+
def negative_expectation_expression
|
125
|
+
Expectations::Syntax.negative_expression("actual", "be #{@operator} #{@expected}")
|
126
|
+
end
|
123
127
|
end
|
124
128
|
|
125
129
|
class BePredicate < Be
|
@@ -8,14 +8,23 @@ module RSpec
|
|
8
8
|
|
9
9
|
def matches?(actual)
|
10
10
|
@actual = actual
|
11
|
-
raise needs_expected unless defined? @expected
|
11
|
+
raise needs_expected unless defined? @expected
|
12
12
|
raise needs_subtractable unless @actual.respond_to? :-
|
13
|
-
(@actual - @expected).abs <= @
|
13
|
+
(@actual - @expected).abs <= @tolerance
|
14
14
|
end
|
15
15
|
alias == matches?
|
16
16
|
|
17
17
|
def of(expected)
|
18
|
-
@expected
|
18
|
+
@expected = expected
|
19
|
+
@tolerance = @delta
|
20
|
+
@unit = ''
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def percent_of(expected)
|
25
|
+
@expected = expected
|
26
|
+
@tolerance = @delta * @expected / 100
|
27
|
+
@unit = '%'
|
19
28
|
self
|
20
29
|
end
|
21
30
|
|
@@ -28,7 +37,7 @@ module RSpec
|
|
28
37
|
end
|
29
38
|
|
30
39
|
def description
|
31
|
-
"be within #{@delta} of #{@expected}"
|
40
|
+
"be within #{@delta}#{@unit} of #{@expected}"
|
32
41
|
end
|
33
42
|
|
34
43
|
private
|
@@ -28,7 +28,7 @@ MESSAGE
|
|
28
28
|
|
29
29
|
def evaluate_value_proc
|
30
30
|
case val = @value_proc.call
|
31
|
-
when Enumerable
|
31
|
+
when Enumerable, String
|
32
32
|
val.dup
|
33
33
|
else
|
34
34
|
val
|
@@ -72,7 +72,7 @@ MESSAGE
|
|
72
72
|
def by_at_most(maximum)
|
73
73
|
@maximum = maximum
|
74
74
|
self
|
75
|
-
end
|
75
|
+
end
|
76
76
|
|
77
77
|
def to(to)
|
78
78
|
@eval_after = true
|
@@ -14,7 +14,7 @@ expected #{inspect_object(expected)}
|
|
14
14
|
|
15
15
|
Compared using equal?, which compares object identity,
|
16
16
|
but expected and actual are not the same object. Use
|
17
|
-
|
17
|
+
`#{eq_expression}` if you don't care about
|
18
18
|
object identity in this example.
|
19
19
|
|
20
20
|
MESSAGE
|
@@ -38,6 +38,10 @@ MESSAGE
|
|
38
38
|
def inspect_object(o)
|
39
39
|
"#<#{o.class}:#{o.object_id}> => #{o.inspect}"
|
40
40
|
end
|
41
|
+
|
42
|
+
def eq_expression
|
43
|
+
Expectations::Syntax.positive_expression("actual", "eq(expected)")
|
44
|
+
end
|
41
45
|
end
|
42
46
|
end
|
43
47
|
end
|
@@ -9,7 +9,7 @@ module RSpec
|
|
9
9
|
def matches?(actual)
|
10
10
|
@actual = actual
|
11
11
|
predicates = [:exist?, :exists?].select { |p| @actual.respond_to?(p) }
|
12
|
-
existence_values = predicates.map { |p| @actual.
|
12
|
+
existence_values = predicates.map { |p| @actual.__send__(p, *@expected) }
|
13
13
|
uniq_truthy_values = existence_values.map { |v| !!v }.uniq
|
14
14
|
|
15
15
|
case uniq_truthy_values.size
|
@@ -24,7 +24,7 @@ module RSpec
|
|
24
24
|
collection = determine_collection(collection_or_owner)
|
25
25
|
query_method = determine_query_method(collection)
|
26
26
|
raise not_a_collection unless query_method
|
27
|
-
@actual = collection.
|
27
|
+
@actual = collection.__send__(query_method)
|
28
28
|
case @relativity
|
29
29
|
when :at_least then @actual >= @expected
|
30
30
|
when :at_most then @actual <= @expected
|
@@ -35,13 +35,13 @@ module RSpec
|
|
35
35
|
|
36
36
|
def determine_collection(collection_or_owner)
|
37
37
|
if collection_or_owner.respond_to?(@collection_name)
|
38
|
-
collection_or_owner.
|
38
|
+
collection_or_owner.__send__(@collection_name, *@args, &@block)
|
39
39
|
elsif (@plural_collection_name && collection_or_owner.respond_to?(@plural_collection_name))
|
40
|
-
collection_or_owner.
|
40
|
+
collection_or_owner.__send__(@plural_collection_name, *@args, &@block)
|
41
41
|
elsif determine_query_method(collection_or_owner)
|
42
42
|
collection_or_owner
|
43
43
|
else
|
44
|
-
collection_or_owner.
|
44
|
+
collection_or_owner.__send__(@collection_name, *@args, &@block)
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
@@ -64,17 +64,17 @@ module RSpec
|
|
64
64
|
return <<-EOF
|
65
65
|
Isn't life confusing enough?
|
66
66
|
Instead of having to figure out the meaning of this:
|
67
|
-
|
67
|
+
#{Expectations::Syntax.negative_expression("actual", "have_at_most(#{@expected}).#{@collection_name}")}
|
68
68
|
We recommend that you use this instead:
|
69
|
-
|
69
|
+
#{Expectations::Syntax.positive_expression("actual", "have_at_least(#{@expected + 1}).#{@collection_name}")}
|
70
70
|
EOF
|
71
71
|
elsif @relativity == :at_least
|
72
72
|
return <<-EOF
|
73
73
|
Isn't life confusing enough?
|
74
74
|
Instead of having to figure out the meaning of this:
|
75
|
-
|
75
|
+
#{Expectations::Syntax.negative_expression("actual", "have_at_least(#{@expected}).#{@collection_name}")}
|
76
76
|
We recommend that you use this instead:
|
77
|
-
|
77
|
+
#{Expectations::Syntax.positive_expression("actual", "have_at_most(#{@expected - 1}).#{@collection_name}")}
|
78
78
|
EOF
|
79
79
|
end
|
80
80
|
end
|
@@ -21,19 +21,23 @@ module RSpec
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def diffable?
|
24
|
-
|
24
|
+
# Matchers do not diff well, since diff uses their inspect
|
25
|
+
# output, which includes their instance variables and such.
|
26
|
+
@expected.none? { |e| is_a_matcher?(e) }
|
25
27
|
end
|
26
28
|
|
27
29
|
private
|
28
30
|
|
29
31
|
def perform_match(predicate, hash_predicate, actuals, expecteds)
|
30
|
-
expecteds.
|
32
|
+
expecteds.__send__(predicate) do |expected|
|
31
33
|
if comparing_hash_values?(actuals, expected)
|
32
|
-
expected.
|
34
|
+
expected.__send__(hash_predicate) { |k,v|
|
33
35
|
actuals.has_key?(k) && actuals[k] == v
|
34
36
|
}
|
35
37
|
elsif comparing_hash_keys?(actuals, expected)
|
36
38
|
actuals.has_key?(expected)
|
39
|
+
elsif comparing_with_matcher?(actual, expected)
|
40
|
+
actual.any? { |value| expected.matches?(value) }
|
37
41
|
else
|
38
42
|
actuals.include?(expected)
|
39
43
|
end
|
@@ -47,6 +51,18 @@ module RSpec
|
|
47
51
|
def comparing_hash_values?(actual, expected)
|
48
52
|
actual.is_a?(Hash) && expected.is_a?(Hash)
|
49
53
|
end
|
54
|
+
|
55
|
+
def comparing_with_matcher?(actual, expected)
|
56
|
+
actual.is_a?(Array) && is_a_matcher?(expected)
|
57
|
+
end
|
58
|
+
|
59
|
+
def is_a_matcher?(object)
|
60
|
+
return false if object.respond_to?(:i_respond_to_everything_so_im_not_really_a_matcher)
|
61
|
+
|
62
|
+
[:failure_message_for_should, :failure_message].any? do |msg|
|
63
|
+
object.respond_to?(msg)
|
64
|
+
end && object.respond_to?(:matches?)
|
65
|
+
end
|
50
66
|
end
|
51
67
|
end
|
52
68
|
end
|
@@ -42,7 +42,7 @@ module RSpec
|
|
42
42
|
|
43
43
|
def find_failing_method_names(actual, filter_method)
|
44
44
|
@actual = actual
|
45
|
-
@failing_method_names = @names.
|
45
|
+
@failing_method_names = @names.__send__(filter_method) do |name|
|
46
46
|
@actual.respond_to?(name) && matches_arity?(actual, name)
|
47
47
|
end
|
48
48
|
end
|