remarkable_rails 3.1.8 → 3.1.9
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 +91 -88
- data/LICENSE +20 -20
- data/README +80 -80
- data/lib/remarkable_rails/action_controller/base.rb +29 -29
- data/lib/remarkable_rails/action_controller/macro_stubs.rb +459 -457
- data/lib/remarkable_rails/action_controller/matchers/assign_to_matcher.rb +92 -92
- data/lib/remarkable_rails/action_controller/matchers/filter_params_matcher.rb +41 -41
- data/lib/remarkable_rails/action_controller/matchers/redirect_to_matcher.rb +119 -119
- data/lib/remarkable_rails/action_controller/matchers/render_template_matcher.rb +146 -146
- data/lib/remarkable_rails/action_controller/matchers/respond_with_matcher.rb +126 -126
- data/lib/remarkable_rails/action_controller/matchers/route_matcher.rb +135 -109
- data/lib/remarkable_rails/action_controller/matchers/set_cookies_matcher.rb +49 -49
- data/lib/remarkable_rails/action_controller/matchers/set_session_matcher.rb +106 -106
- data/lib/remarkable_rails/action_controller/matchers/set_the_flash_matcher.rb +54 -54
- data/lib/remarkable_rails/action_controller.rb +22 -22
- data/lib/remarkable_rails/action_view/base.rb +7 -7
- data/lib/remarkable_rails/action_view.rb +18 -18
- data/lib/remarkable_rails/active_orm.rb +19 -19
- data/lib/remarkable_rails.rb +30 -30
- data/locale/en.yml +110 -110
- data/spec/action_controller/assign_to_matcher_spec.rb +142 -142
- data/spec/action_controller/filter_params_matcher_spec.rb +64 -64
- data/spec/action_controller/macro_stubs_spec.rb +234 -208
- data/spec/action_controller/redirect_to_matcher_spec.rb +102 -102
- data/spec/action_controller/render_template_matcher_spec.rb +251 -251
- data/spec/action_controller/respond_with_matcher_spec.rb +223 -223
- data/spec/action_controller/route_matcher_spec.rb +96 -79
- data/spec/action_controller/set_cookies_matcher_spec.rb +149 -149
- data/spec/action_controller/set_session_matcher_spec.rb +141 -141
- data/spec/action_controller/set_the_flash_matcher_spec.rb +93 -93
- data/spec/application/application.rb +15 -15
- data/spec/application/tasks_controller.rb +34 -34
- data/spec/functional_builder.rb +88 -88
- data/spec/rcov.opts +2 -2
- data/spec/remarkable_rails_spec.rb +5 -5
- data/spec/spec.opts +4 -4
- data/spec/spec_helper.rb +42 -42
- metadata +7 -7
@@ -1,109 +1,135 @@
|
|
1
|
-
module Remarkable
|
2
|
-
module ActionController
|
3
|
-
module Matchers
|
4
|
-
# Do not inherit from ActionController::Base since it don't need all macro stubs behavior.
|
5
|
-
class RouteMatcher < Remarkable::Base #:nodoc:
|
6
|
-
arguments :method, :path
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
1
|
+
module Remarkable
|
2
|
+
module ActionController
|
3
|
+
module Matchers
|
4
|
+
# Do not inherit from ActionController::Base since it don't need all macro stubs behavior.
|
5
|
+
class RouteMatcher < Remarkable::Base #:nodoc:
|
6
|
+
arguments :method, :path
|
7
|
+
assertions :map_to_path?, :generate_params?
|
8
|
+
|
9
|
+
# Small hack to allow should route().to/from syntax.
|
10
|
+
#
|
11
|
+
after_initialize do
|
12
|
+
if @path.is_a?(Hash)
|
13
|
+
@options.merge!(@path)
|
14
|
+
@path = nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
before_assert do
|
19
|
+
@options[:controller] ||= controller_name
|
20
|
+
@populated_path = @path.dup
|
21
|
+
|
22
|
+
@options.each do |key, value|
|
23
|
+
@options[key] = value.to_param if value.respond_to?(:to_param)
|
24
|
+
@populated_path.gsub!(key.inspect, value.to_s)
|
25
|
+
end
|
26
|
+
|
27
|
+
::ActionController::Routing::Routes.reload if ::ActionController::Routing::Routes.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
def to(value)
|
31
|
+
@options.merge!(value)
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def from(value)
|
36
|
+
@path = value
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def map_to_path?
|
43
|
+
route_for = ::ActionController::Routing::Routes.generate(@options) rescue nil
|
44
|
+
return route_for == @populated_path, :actual => route_for.inspect
|
45
|
+
end
|
46
|
+
|
47
|
+
def generate_params?
|
48
|
+
env = ::ActionController::Routing::Routes.extract_request_environment(request) if request
|
49
|
+
|
50
|
+
env ||= {}
|
51
|
+
env[:method] = @method.to_sym
|
52
|
+
params_from = ::ActionController::Routing::Routes.recognize_path(@populated_path, env) rescue nil
|
53
|
+
return params_from == @options, :actual => params_from.inspect
|
54
|
+
end
|
55
|
+
|
56
|
+
def controller
|
57
|
+
@controller ||= if @subject.is_a?(::ActionController::Base)
|
58
|
+
@subject
|
59
|
+
elsif @spec.respond_to?(:controller)
|
60
|
+
@spec.controller
|
61
|
+
else
|
62
|
+
raise "Could not find a controller for route specs."
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# First tries to get the controller name from the subject, then from
|
67
|
+
# the spec class using controller class or finally, from the described
|
68
|
+
# class.
|
69
|
+
#
|
70
|
+
# We have to try the described class because we don't have neither the
|
71
|
+
# subject or the controller class in the RoutingExampleGroup.
|
72
|
+
#
|
73
|
+
def controller_name
|
74
|
+
if controller_class
|
75
|
+
controller_class.name.gsub(/Controller$/, '').tableize
|
76
|
+
else
|
77
|
+
raise ArgumentError, "I cannot guess the controller name in route. Please supply :controller as option"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def controller_class
|
82
|
+
@controller_class ||= begin
|
83
|
+
spec_class = @spec.class unless @spec.class == Class
|
84
|
+
|
85
|
+
attempts = []
|
86
|
+
attempts << controller.class if controller
|
87
|
+
attempts << spec_class.controller_class if spec_class.respond_to?(:controller_class)
|
88
|
+
attempts << spec_class.described_class if spec_class.respond_to?(:described_class)
|
89
|
+
|
90
|
+
# Check for not blank names to address an odd rspec/rails behavior.
|
91
|
+
attempts.find { |klass| ::ActionController::Base >= klass && !klass.name.blank? }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def request
|
96
|
+
controller.request
|
97
|
+
end
|
98
|
+
|
99
|
+
def interpolation_options
|
100
|
+
{ :options => @options.inspect, :method => @method.to_s.upcase, :path => @path.inspect }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Assert route generation AND route recognition.
|
105
|
+
#
|
106
|
+
# == Examples
|
107
|
+
#
|
108
|
+
# # autodetects the :controller
|
109
|
+
# should_route :get, '/posts', :action => :index
|
110
|
+
#
|
111
|
+
# # explicitly specify :controller
|
112
|
+
# should_route :post, '/posts', :controller => :posts, :action => :create
|
113
|
+
#
|
114
|
+
# # non-string parameter
|
115
|
+
# should_route :get, '/posts/1', :controller => :posts, :action => :show, :id => 1
|
116
|
+
#
|
117
|
+
# # string-parameter
|
118
|
+
# should_route :put, '/posts/1', :controller => :posts, :action => :update, :id => "1"
|
119
|
+
# should_route :delete, '/posts/1', :controller => :posts, :action => :destroy, :id => 1
|
120
|
+
# should_route :get, '/posts/new', :controller => :posts, :action => :new
|
121
|
+
#
|
122
|
+
# # nested routes
|
123
|
+
# should_route :get, '/users/5/posts', :controller => :posts, :action => :index, :user_id => 5
|
124
|
+
# should_route :post, '/users/5/posts', :controller => :posts, :action => :create, :user_id => 5
|
125
|
+
#
|
126
|
+
# # it example
|
127
|
+
# it { should route(:get, :action => :index).to('/users/5/posts') }
|
128
|
+
#
|
129
|
+
def route(*params, &block)
|
130
|
+
RouteMatcher.new(*params, &block).spec(self)
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -1,9 +1,9 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), 'set_session_matcher')
|
2
|
-
|
3
|
-
module Remarkable
|
4
|
-
module ActionController
|
5
|
-
module Matchers
|
6
|
-
class SetCookiesMatcher < SetSessionMatcher #:nodoc:
|
1
|
+
require File.join(File.dirname(__FILE__), 'set_session_matcher')
|
2
|
+
|
3
|
+
module Remarkable
|
4
|
+
module ActionController
|
5
|
+
module Matchers
|
6
|
+
class SetCookiesMatcher < SetSessionMatcher #:nodoc:
|
7
7
|
|
8
8
|
# For cookies to work properly, we have:
|
9
9
|
#
|
@@ -36,50 +36,50 @@ module Remarkable
|
|
36
36
|
|
37
37
|
@keys.collect!(&:to_s)
|
38
38
|
end
|
39
|
-
|
40
|
-
protected
|
41
|
-
def session
|
42
|
-
@subject ? (@subject.response.cookies || {}) : {}
|
43
|
-
end
|
44
|
-
|
45
|
-
def interpolation_options
|
46
|
-
{ :cookies_inspect => session.symbolize_keys!.inspect }
|
47
|
-
end
|
48
|
-
|
49
|
-
end
|
50
|
-
|
39
|
+
|
40
|
+
protected
|
41
|
+
def session
|
42
|
+
@subject ? (@subject.response.cookies || {}) : {}
|
43
|
+
end
|
44
|
+
|
45
|
+
def interpolation_options
|
46
|
+
{ :cookies_inspect => session.symbolize_keys!.inspect }
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
51
|
# Ensures that the given cookie keys were set. If you want to check that
|
52
|
-
# a cookie is not being set, just do:
|
53
|
-
#
|
54
|
-
# should_not_set_cookies :user
|
55
|
-
#
|
56
|
-
# If you want to assure that a cookie is being set to nil, do instead:
|
57
|
-
#
|
58
|
-
# should_set_cookies :user, :to => nil
|
52
|
+
# a cookie is not being set, just do:
|
53
|
+
#
|
54
|
+
# should_not_set_cookies :user
|
55
|
+
#
|
56
|
+
# If you want to assure that a cookie is being set to nil, do instead:
|
57
|
+
#
|
58
|
+
# should_set_cookies :user, :to => nil
|
59
59
|
#
|
60
60
|
# Note: this method is also aliased as <tt>set_cookie</tt>.
|
61
|
-
#
|
62
|
-
# == Options
|
63
|
-
#
|
64
|
-
# * <tt>:to</tt> - The value to compare the session key.
|
65
|
-
# It accepts procs and be also given as a block (see examples below).
|
66
|
-
#
|
67
|
-
# == Examples
|
68
|
-
#
|
69
|
-
# should_set_cookies :user_id, :user
|
70
|
-
# should_set_cookies :user_id, :to => 2
|
71
|
-
# should_set_cookies :user, :to => proc{ users(:first) }
|
72
|
-
# should_set_cookies(:user){ users(:first) }
|
73
|
-
#
|
74
|
-
# it { should set_cookies(:user_id, :user) }
|
75
|
-
# it { should set_cookies(:user_id, :to => 2) }
|
76
|
-
# it { should set_cookies(:user, :to => users(:first)) }
|
77
|
-
#
|
78
|
-
def set_cookies(*args, &block)
|
79
|
-
SetCookiesMatcher.new(*args, &block).spec(self)
|
61
|
+
#
|
62
|
+
# == Options
|
63
|
+
#
|
64
|
+
# * <tt>:to</tt> - The value to compare the session key.
|
65
|
+
# It accepts procs and be also given as a block (see examples below).
|
66
|
+
#
|
67
|
+
# == Examples
|
68
|
+
#
|
69
|
+
# should_set_cookies :user_id, :user
|
70
|
+
# should_set_cookies :user_id, :to => 2
|
71
|
+
# should_set_cookies :user, :to => proc{ users(:first) }
|
72
|
+
# should_set_cookies(:user){ users(:first) }
|
73
|
+
#
|
74
|
+
# it { should set_cookies(:user_id, :user) }
|
75
|
+
# it { should set_cookies(:user_id, :to => 2) }
|
76
|
+
# it { should set_cookies(:user, :to => users(:first)) }
|
77
|
+
#
|
78
|
+
def set_cookies(*args, &block)
|
79
|
+
SetCookiesMatcher.new(*args, &block).spec(self)
|
80
80
|
end
|
81
|
-
alias :set_cookie :set_cookies
|
82
|
-
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
81
|
+
alias :set_cookie :set_cookies
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -1,108 +1,108 @@
|
|
1
|
-
module Remarkable
|
2
|
-
module ActionController
|
3
|
-
module Matchers
|
4
|
-
class SetSessionMatcher < Remarkable::ActionController::Base #:nodoc:
|
5
|
-
arguments :collection => :keys, :as => :key, :block => true
|
6
|
-
|
7
|
-
optional :to, :block => true
|
8
|
-
|
9
|
-
assertion :is_not_empty?, :contains_value?
|
10
|
-
collection_assertions :assigned_value?, :is_equal_value?
|
11
|
-
|
12
|
-
before_assert :evaluate_expected_value
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
# When no keys are given:
|
17
|
-
#
|
18
|
-
# should set_session
|
19
|
-
#
|
20
|
-
# We check if the session is not empty.
|
21
|
-
#
|
22
|
-
def is_not_empty?
|
23
|
-
!(@keys.empty? && session.empty?)
|
24
|
-
end
|
25
|
-
|
26
|
-
# When no keys are given and a comparision value is given:
|
27
|
-
#
|
28
|
-
# should set_session.to(1)
|
29
|
-
#
|
30
|
-
# We check if any of the session data contains the given value.
|
31
|
-
#
|
32
|
-
def contains_value?
|
33
|
-
return true unless @keys.empty? && value_to_compare?
|
34
|
-
assert_contains(session.values, @options[:to])
|
35
|
-
end
|
36
|
-
|
1
|
+
module Remarkable
|
2
|
+
module ActionController
|
3
|
+
module Matchers
|
4
|
+
class SetSessionMatcher < Remarkable::ActionController::Base #:nodoc:
|
5
|
+
arguments :collection => :keys, :as => :key, :block => true
|
6
|
+
|
7
|
+
optional :to, :block => true
|
8
|
+
|
9
|
+
assertion :is_not_empty?, :contains_value?
|
10
|
+
collection_assertions :assigned_value?, :is_equal_value?
|
11
|
+
|
12
|
+
before_assert :evaluate_expected_value
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
# When no keys are given:
|
17
|
+
#
|
18
|
+
# should set_session
|
19
|
+
#
|
20
|
+
# We check if the session is not empty.
|
21
|
+
#
|
22
|
+
def is_not_empty?
|
23
|
+
!(@keys.empty? && session.empty?)
|
24
|
+
end
|
25
|
+
|
26
|
+
# When no keys are given and a comparision value is given:
|
27
|
+
#
|
28
|
+
# should set_session.to(1)
|
29
|
+
#
|
30
|
+
# We check if any of the session data contains the given value.
|
31
|
+
#
|
32
|
+
def contains_value?
|
33
|
+
return true unless @keys.empty? && value_to_compare?
|
34
|
+
assert_contains(session.values, @options[:to])
|
35
|
+
end
|
36
|
+
|
37
37
|
def assigned_value?
|
38
|
-
session.key?(@key)
|
39
|
-
end
|
40
|
-
|
41
|
-
# Returns true if :to is not given and no block is given.
|
42
|
-
# In case :to is a proc or a block is given, we evaluate it in the
|
43
|
-
# @spec scope.
|
44
|
-
#
|
45
|
-
def is_equal_value?
|
46
|
-
return true unless value_to_compare?
|
47
|
-
assert_contains([session[@key]], @options[:to])
|
48
|
-
end
|
49
|
-
|
50
|
-
def session
|
51
|
-
raw_session.with_indifferent_access.except(:flash)
|
52
|
-
end
|
53
|
-
|
54
|
-
def raw_session
|
55
|
-
@subject ? @subject.response.session.data : {}
|
56
|
-
end
|
57
|
-
|
58
|
-
def value_to_compare?
|
59
|
-
@options.key?(:to) || @block
|
60
|
-
end
|
61
|
-
|
62
|
-
def interpolation_options
|
63
|
-
{ :session_inspect => raw_session.except('flash').symbolize_keys!.inspect }
|
64
|
-
end
|
65
|
-
|
66
|
-
# Evaluate procs before assert to avoid them appearing in descriptions.
|
67
|
-
def evaluate_expected_value
|
68
|
-
if value_to_compare?
|
69
|
-
value = @options.key?(:to) ? @options[:to] : @block
|
70
|
-
value = @spec.instance_eval(&value) if value.is_a?(Proc)
|
71
|
-
@options[:to] = value
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
end
|
76
|
-
|
38
|
+
session.key?(@key)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns true if :to is not given and no block is given.
|
42
|
+
# In case :to is a proc or a block is given, we evaluate it in the
|
43
|
+
# @spec scope.
|
44
|
+
#
|
45
|
+
def is_equal_value?
|
46
|
+
return true unless value_to_compare?
|
47
|
+
assert_contains([session[@key]], @options[:to])
|
48
|
+
end
|
49
|
+
|
50
|
+
def session
|
51
|
+
raw_session.with_indifferent_access.except(:flash)
|
52
|
+
end
|
53
|
+
|
54
|
+
def raw_session
|
55
|
+
@subject ? @subject.response.session.data : {}
|
56
|
+
end
|
57
|
+
|
58
|
+
def value_to_compare?
|
59
|
+
@options.key?(:to) || @block
|
60
|
+
end
|
61
|
+
|
62
|
+
def interpolation_options
|
63
|
+
{ :session_inspect => raw_session.except('flash').symbolize_keys!.inspect }
|
64
|
+
end
|
65
|
+
|
66
|
+
# Evaluate procs before assert to avoid them appearing in descriptions.
|
67
|
+
def evaluate_expected_value
|
68
|
+
if value_to_compare?
|
69
|
+
value = @options.key?(:to) ? @options[:to] : @block
|
70
|
+
value = @spec.instance_eval(&value) if value.is_a?(Proc)
|
71
|
+
@options[:to] = value
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
77
|
# Ensures that the given session keys were set. If you want to check that
|
78
|
-
# a variable is not being set, just do:
|
79
|
-
#
|
80
|
-
# should_not_set_session :user
|
81
|
-
#
|
82
|
-
# If you want to assure that a variable is being set to nil, do instead:
|
83
|
-
#
|
84
|
-
# should_set_session :user, :to => nil
|
85
|
-
#
|
86
|
-
# == Options
|
87
|
-
#
|
88
|
-
# * <tt>:to</tt> - The value to compare the session key.
|
89
|
-
# It accepts procs and be also given as a block (see examples below).
|
90
|
-
#
|
91
|
-
# == Examples
|
92
|
-
#
|
93
|
-
# should_set_session :user_id, :user
|
94
|
-
# should_set_session :user_id, :to => 2
|
95
|
-
# should_set_session :user, :to => proc{ users(:first) }
|
96
|
-
# should_set_session(:user){ users(:first) }
|
97
|
-
#
|
98
|
-
# it { should set_session(:user_id, :user) }
|
99
|
-
# it { should set_session(:user_id, :to => 2) }
|
100
|
-
# it { should set_session(:user, :to => users(:first)) }
|
101
|
-
#
|
102
|
-
def set_session(*args, &block)
|
103
|
-
SetSessionMatcher.new(*args, &block).spec(self)
|
104
|
-
end
|
105
|
-
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
78
|
+
# a variable is not being set, just do:
|
79
|
+
#
|
80
|
+
# should_not_set_session :user
|
81
|
+
#
|
82
|
+
# If you want to assure that a variable is being set to nil, do instead:
|
83
|
+
#
|
84
|
+
# should_set_session :user, :to => nil
|
85
|
+
#
|
86
|
+
# == Options
|
87
|
+
#
|
88
|
+
# * <tt>:to</tt> - The value to compare the session key.
|
89
|
+
# It accepts procs and be also given as a block (see examples below).
|
90
|
+
#
|
91
|
+
# == Examples
|
92
|
+
#
|
93
|
+
# should_set_session :user_id, :user
|
94
|
+
# should_set_session :user_id, :to => 2
|
95
|
+
# should_set_session :user, :to => proc{ users(:first) }
|
96
|
+
# should_set_session(:user){ users(:first) }
|
97
|
+
#
|
98
|
+
# it { should set_session(:user_id, :user) }
|
99
|
+
# it { should set_session(:user_id, :to => 2) }
|
100
|
+
# it { should set_session(:user, :to => users(:first)) }
|
101
|
+
#
|
102
|
+
def set_session(*args, &block)
|
103
|
+
SetSessionMatcher.new(*args, &block).spec(self)
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|