page_magic 2.0.0.alpha1 → 2.0.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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +5 -2
  3. data/.zsh_config +5 -5
  4. data/Dockerfile +2 -1
  5. data/Gemfile +2 -1
  6. data/Gemfile.lock +9 -5
  7. data/Makefile +7 -3
  8. data/README.md +16 -4
  9. data/VERSION +1 -1
  10. data/lib/active_support/core_ext/object/to_query.rb +6 -6
  11. data/lib/page_magic.rb +15 -16
  12. data/lib/page_magic/class_methods.rb +1 -1
  13. data/lib/page_magic/comparator.rb +37 -0
  14. data/lib/page_magic/comparator/fuzzy.rb +23 -0
  15. data/lib/page_magic/comparator/literal.rb +22 -0
  16. data/lib/page_magic/comparator/null.rb +26 -0
  17. data/lib/page_magic/comparator/parameter_map.rb +52 -0
  18. data/lib/page_magic/drivers.rb +2 -2
  19. data/lib/page_magic/element.rb +19 -8
  20. data/lib/page_magic/element/locators.rb +4 -4
  21. data/lib/page_magic/element/not_found.rb +38 -0
  22. data/lib/page_magic/element/query.rb +19 -27
  23. data/lib/page_magic/element/query/multiple_results.rb +21 -0
  24. data/lib/page_magic/element/query/prefetched_result.rb +26 -0
  25. data/lib/page_magic/element/query/single_result.rb +20 -0
  26. data/lib/page_magic/element/selector.rb +38 -16
  27. data/lib/page_magic/element/selector/methods.rb +18 -0
  28. data/lib/page_magic/element/selector/model.rb +21 -0
  29. data/lib/page_magic/element_context.rb +5 -21
  30. data/lib/page_magic/element_definition_builder.rb +17 -24
  31. data/lib/page_magic/elements.rb +62 -102
  32. data/lib/page_magic/elements/config.rb +103 -0
  33. data/lib/page_magic/elements/inheritance_hooks.rb +15 -0
  34. data/lib/page_magic/elements/types.rb +25 -0
  35. data/lib/page_magic/exceptions.rb +3 -0
  36. data/lib/page_magic/instance_methods.rb +2 -2
  37. data/lib/page_magic/mapping.rb +79 -0
  38. data/lib/page_magic/session.rb +10 -32
  39. data/lib/page_magic/session_methods.rb +1 -1
  40. data/lib/page_magic/transitions.rb +49 -0
  41. data/lib/page_magic/utils/string.rb +4 -0
  42. data/lib/page_magic/utils/url.rb +20 -0
  43. data/lib/page_magic/watcher.rb +10 -17
  44. data/lib/page_magic/watchers.rb +28 -15
  45. data/spec/page_magic/class_methods_spec.rb +64 -37
  46. data/spec/page_magic/comparator/fuzzy_spec.rb +44 -0
  47. data/spec/page_magic/comparator/literal_spec.rb +41 -0
  48. data/spec/page_magic/comparator/null_spec.rb +35 -0
  49. data/spec/page_magic/comparator/parameter_map_spec.rb +75 -0
  50. data/spec/page_magic/driver_spec.rb +25 -29
  51. data/spec/page_magic/drivers/poltergeist_spec.rb +4 -7
  52. data/spec/page_magic/drivers/rack_test_spec.rb +4 -9
  53. data/spec/page_magic/drivers/selenium_spec.rb +9 -12
  54. data/spec/page_magic/drivers_spec.rb +36 -29
  55. data/spec/page_magic/element/locators_spec.rb +26 -25
  56. data/spec/page_magic/element/not_found_spec.rb +24 -0
  57. data/spec/page_magic/element/query/multiple_results_spec.rb +14 -0
  58. data/spec/page_magic/element/query/single_result_spec.rb +21 -0
  59. data/spec/page_magic/element/query_spec.rb +26 -47
  60. data/spec/page_magic/element/selector_spec.rb +118 -110
  61. data/spec/page_magic/element_context_spec.rb +46 -88
  62. data/spec/page_magic/element_definition_builder_spec.rb +12 -71
  63. data/spec/page_magic/element_spec.rb +256 -0
  64. data/spec/page_magic/elements/config_spec.rb +200 -0
  65. data/spec/page_magic/elements_spec.rb +87 -138
  66. data/spec/page_magic/instance_methods_spec.rb +63 -63
  67. data/spec/page_magic/mapping_spec.rb +181 -0
  68. data/spec/page_magic/session_methods_spec.rb +27 -25
  69. data/spec/page_magic/session_spec.rb +109 -198
  70. data/spec/page_magic/transitions_spec.rb +43 -0
  71. data/spec/page_magic/utils/string_spec.rb +20 -27
  72. data/spec/page_magic/utils/url_spec.rb +9 -0
  73. data/spec/page_magic/wait_methods_spec.rb +14 -22
  74. data/spec/page_magic/watcher_spec.rb +22 -0
  75. data/spec/page_magic/watchers_spec.rb +56 -62
  76. data/spec/page_magic_spec.rb +27 -24
  77. data/spec/spec_helper.rb +7 -3
  78. data/spec/support/shared_examples.rb +15 -17
  79. metadata +48 -15
  80. data/lib/page_magic/element/query_builder.rb +0 -61
  81. data/lib/page_magic/element/selector_methods.rb +0 -16
  82. data/lib/page_magic/matcher.rb +0 -130
  83. data/spec/element_spec.rb +0 -251
  84. data/spec/page_magic/element/query_builder_spec.rb +0 -110
  85. data/spec/page_magic/matcher_spec.rb +0 -338
  86. data/spec/support/shared_contexts/files_context.rb +0 -9
  87. data/spec/support/shared_contexts/nested_elements_html_context.rb +0 -18
  88. data/spec/support/shared_contexts/rack_application_context.rb +0 -11
  89. data/spec/support/shared_contexts/webapp_fixture_context.rb +0 -41
  90. data/spec/watcher_spec.rb +0 -64
@@ -1,78 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module PageMagic
4
- describe ElementDefinitionBuilder do
5
- describe '#initialize' do
6
- context 'selector missing' do
7
- context 'object prefetched' do
8
- it 'does not raise an error' do
9
- execution = proc do
10
- described_class.new(definition_class: Element,
11
- type: :element,
12
- selector: nil,
13
- element: Object.new)
14
- end
15
- expect(&execution).to_not raise_exception
16
- end
17
- end
18
-
19
- context 'selector defined on the definition_class' do
20
- it 'uses the selector on the class' do
21
- definition_class = Class.new(Element) do
22
- selector css: 'selector'
23
- end
24
-
25
- builder = described_class.new(definition_class: definition_class,
26
- type: :element,
27
- selector: nil)
28
-
29
- expect(builder.selector).to eq(css: 'selector')
30
- end
31
- end
32
-
33
- context 'selector nil' do
34
- it 'raises an error' do
35
- execution = proc { described_class.new(definition_class: Element, type: :element, selector: nil) }
36
- expect(&execution).to raise_exception UndefinedSelectorException, described_class::INVALID_SELECTOR_MSG
37
- end
38
- end
39
-
40
- context 'selector empty' do
41
- it 'raises an error' do
42
- execution = proc { described_class.new(definition_class: Element, type: :element, selector: {}) }
43
- expect(&execution).to raise_exception UndefinedSelectorException, described_class::INVALID_SELECTOR_MSG
44
- end
45
- end
46
- end
47
-
48
- context 'selector defined on definition_class' do
49
- it 'uses the supplied selector' do
50
- definition_class = Class.new(Element) do
51
- selector css: 'selector'
52
- end
53
-
54
- expected_selector = { id: 'id' }
55
- builder = described_class.new(definition_class: definition_class,
56
- type: :element,
57
- selector: expected_selector)
58
-
59
- expect(builder.selector).to eq(expected_selector)
60
- end
3
+ RSpec.describe PageMagic::ElementDefinitionBuilder do
4
+ describe '#build' do
5
+ it 'returns an instance of `definition_class`' do
6
+ options = { count: 1 }
7
+ builder = described_class.new(
8
+ definition_class: PageMagic::Element,
9
+ selector: PageMagic::Element::Selector.find(:xpath).build(:text_field, '//xpath', options: options)
10
+ )
11
+
12
+ allow_any_instance_of(PageMagic::Element::Query::SingleResult).to receive(:execute) do |_query, element, &block|
13
+ block.call(element)
61
14
  end
62
- end
63
-
64
- describe 'build_query' do
65
- it 'returns a capybara query' do
66
- options = { count: 1 }
67
- selector = { xpath: '//xpath' }
68
- builder = described_class.new(definition_class: Element,
69
- type: :text_field,
70
- selector: selector,
71
- element: Object.new,
72
- options: options)
73
15
 
74
- expect(builder.build_query).to eq(Element::Query.new([:xpath, '//xpath', options]))
75
- end
16
+ expect(builder.build(:capybara_object)).to have_attributes(browser_element: :capybara_object)
76
17
  end
77
18
  end
78
19
  end
@@ -0,0 +1,256 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe PageMagic::Element do
4
+ let(:described_class) do
5
+ Class.new(PageMagic::Element) # rubocop:disable RSpec/DescribedClass
6
+ end
7
+
8
+ it_behaves_like 'session accessor'
9
+ it_behaves_like 'element watcher'
10
+ it_behaves_like 'waiter'
11
+ it_behaves_like 'element locator'
12
+
13
+ describe '.after_events' do
14
+ context 'when a hook is registered' do
15
+ it 'returns that hook' do
16
+ hook = proc {}
17
+ described_class.after_events(&hook)
18
+ expect(described_class.after_events).to eq([described_class::DEFAULT_HOOK, hook])
19
+ end
20
+ end
21
+
22
+ context 'when a hook is not registered' do
23
+ it 'returns the default hook' do
24
+ expect(described_class.after_events).to eq([described_class::DEFAULT_HOOK])
25
+ end
26
+ end
27
+ end
28
+
29
+ describe '.before_events' do
30
+ context 'when a is hook registered' do
31
+ it 'returns that hook' do
32
+ hook = proc {}
33
+ described_class.before_events(&hook)
34
+ expect(described_class.before_events).to eq([described_class::DEFAULT_HOOK, hook])
35
+ end
36
+ end
37
+
38
+ context 'when a hook is not registered' do
39
+ it 'returns the default hook' do
40
+ expect(described_class.before_events).to eq([described_class::DEFAULT_HOOK])
41
+ end
42
+ end
43
+ end
44
+
45
+ describe '.inherited' do
46
+ it 'copies before hooks on to the inheritor' do
47
+ before_hook = proc {}
48
+ described_class.before_events(&before_hook)
49
+ sub_class = Class.new(described_class)
50
+ expect(sub_class.before_events).to include(before_hook)
51
+ end
52
+
53
+ it 'copies after hooks on to the inheritor' do
54
+ after_hook = proc {}
55
+ described_class.after_events(&after_hook)
56
+ sub_class = Class.new(described_class)
57
+ expect(sub_class.after_events).to include(after_hook)
58
+ end
59
+
60
+ context 'when subclasses define their own elements' do
61
+ it 'puts the element definition on the sub class' do
62
+ custom_element = Class.new(described_class) do
63
+ text_field :form_field, id: 'field_id'
64
+ end
65
+ expect(custom_element.new(:page_element).element_definitions).to include(:form_field)
66
+ end
67
+
68
+ it 'does not put the definition on the parent class' do
69
+ Class.new(described_class) do
70
+ text_field :form_field, id: 'field_id'
71
+ end
72
+ expect(described_class.new(:page_element).element_definitions).not_to include(:form_field)
73
+ end
74
+ end
75
+ end
76
+
77
+ describe '.load' do
78
+ let(:page_source) do
79
+ <<-HTML
80
+ <div id='links'>
81
+ <a class='cta'>link text</a>
82
+ </div>
83
+ HTML
84
+ end
85
+
86
+ it 'returns an instance that works against the supplied string' do
87
+ subject = Class.new(described_class) do
88
+ element(:links, id: 'links') { link(:cta, css: '.cta') }
89
+ end
90
+ expect(subject.load(page_source).links.cta.text).to eq('link text')
91
+ end
92
+ end
93
+
94
+ describe '.watch' do
95
+ it 'adds a before hook' do
96
+ watch_block = described_class.watch(:object_id).last
97
+ expect(described_class.before_events).to include(watch_block)
98
+ end
99
+
100
+ describe 'the before hook that is added' do
101
+ it 'contains a watcher' do
102
+ watch_block = described_class.watch(:object_id).last
103
+ instance = described_class.new(:element)
104
+ instance.instance_exec(&watch_block)
105
+
106
+ watcher = instance.watchers.first
107
+ expect(watcher.observed_value).to eq(instance.object_id)
108
+ end
109
+ end
110
+ end
111
+
112
+ describe 'EVENT_TYPES' do
113
+ it 'creates methods for each of the event types' do
114
+ instance = described_class.new(:capybara_element)
115
+ missing = described_class::EVENT_TYPES.find_all { |event| !instance.respond_to?(event) }
116
+ expect(missing).to be_empty
117
+ end
118
+
119
+ context 'when one of the methods are called' do
120
+ it 'calls the browser_element passing on all args' do
121
+ browser_element = instance_double(Capybara::Node::Actions)
122
+ allow(browser_element).to receive(:select)
123
+ described_class.new(browser_element).select :args
124
+ expect(browser_element).to have_received(:select).with(:args)
125
+ end
126
+ end
127
+
128
+ context 'when the underlying capybara element does not respond to the method' do
129
+ it 'raises an error' do
130
+ expected_message = (described_class::EVENT_NOT_SUPPORTED_MSG % 'click')
131
+ browser_element = instance_double(Capybara::Node::Element)
132
+ page_element = described_class.new(browser_element)
133
+ expect { page_element.click }.to raise_error(PageMagic::NotSupportedException, expected_message)
134
+ end
135
+ end
136
+ end
137
+
138
+ describe 'hooks' do
139
+ context 'when a method called from within a before_events hook' do
140
+ let(:page_element_class) do
141
+ Class.new(described_class) do
142
+ before_events do
143
+ call_in_before_events
144
+ end
145
+ end
146
+ end
147
+
148
+ it 'delegates to the `PageMagic::Element`' do
149
+ capybara_button = instance_double(Capybara::Node::Element, click: true)
150
+ page_element = page_element_class.new(capybara_button)
151
+ allow(page_element).to receive(:call_in_before_events)
152
+ page_element.click
153
+ expect(page_element).to have_received(:call_in_before_events)
154
+ end
155
+ end
156
+
157
+ context 'when a method called from within a after_events hook' do
158
+ let(:page_element_class) do
159
+ Class.new(described_class) do
160
+ after_events do
161
+ call_in_after_events
162
+ end
163
+ end
164
+ end
165
+
166
+ it 'delegates to the `PageMagic::Element`' do
167
+ capybara_button = instance_double(Capybara::Node::Element, click: true)
168
+ page_element = page_element_class.new(capybara_button)
169
+ allow(page_element).to receive(:call_in_after_events)
170
+ page_element.click
171
+ expect(page_element).to have_received(:call_in_after_events)
172
+ end
173
+ end
174
+ end
175
+
176
+ describe '#initialize' do
177
+ it 'sets the parent element' do
178
+ described_class.parent_element(:page)
179
+ instance = described_class.new(:element)
180
+ expect(instance.parent_element).to eq(:page)
181
+ end
182
+
183
+ describe 'inherited items' do
184
+ it 'copies the before hooks' do
185
+ before_hook = proc {}
186
+ described_class.before_events(&before_hook)
187
+
188
+ instance = described_class.new(:element)
189
+ expect(instance.before_events).to include(before_hook)
190
+ end
191
+
192
+ it 'copies the after hooks' do
193
+ after_hook = proc {}
194
+ described_class.after_events(&after_hook)
195
+
196
+ instance = described_class.new(:element)
197
+ expect(instance.after_events).to include(after_hook)
198
+ end
199
+ end
200
+ end
201
+
202
+ describe '#method_missing' do
203
+ context 'when no sub element definition found' do
204
+ it 'delegates to the capybara element' do
205
+ instance = described_class.new(instance_double(Capybara::Node::Element, visible?: true))
206
+ expect(instance).to be_visible
207
+ end
208
+ end
209
+
210
+ context 'when method not found on the capybara element' do
211
+ it 'calls method on parent element' do
212
+ element = Struct.new(:parent_method).new(:called)
213
+ described_class.parent_element(element)
214
+ instance = described_class.new(:capybara_element)
215
+ expect(instance.parent_method).to eq(:called)
216
+ end
217
+ end
218
+
219
+ context 'when the method is not found on parent' do
220
+ it 'throws and exception' do
221
+ described_class.parent_element(:parent_element)
222
+ instance = described_class.new(:capybara_element)
223
+ expect { instance.bobbins }.to raise_exception NoMethodError
224
+ end
225
+ end
226
+ end
227
+
228
+ describe '#respond_to?' do
229
+ subject(:instance) do
230
+ capybara_element = Struct.new(:element_method).new(:called)
231
+ Class.new(described_class) do
232
+ element :sub_element, css: '.sub-element'
233
+ end.new(capybara_element)
234
+ end
235
+
236
+ it 'checks for methods on self' do
237
+ expect(instance).to respond_to(:session)
238
+ end
239
+
240
+ it 'checks against registered elements' do
241
+ expect(instance).to respond_to(:sub_element)
242
+ end
243
+
244
+ it 'checks for the method of the browser_element' do
245
+ expect(instance).to respond_to(:element_method)
246
+ end
247
+ end
248
+
249
+ describe '#session' do
250
+ it 'has a handle to the session' do
251
+ described_class.parent_element(instance_double(PageMagic::InstanceMethods, session: :session))
252
+ instance = described_class.new(:capybara_element)
253
+ expect(instance.session).to eq(:session)
254
+ end
255
+ end
256
+ end
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe PageMagic::Elements::Config do
4
+ describe '.build' do
5
+ it 'sets of the type' do
6
+ options = described_class.build([], :field)
7
+ expect(options.type).to eq(:field)
8
+ end
9
+
10
+ it 'sets the options' do
11
+ user_options = { option: 1 }
12
+ options = described_class.build([{ id: 'child' }, user_options], :field)
13
+ expect(options.options).to eq(user_options)
14
+ end
15
+
16
+ describe 'name' do
17
+ context 'when supplied' do
18
+ it 'sets it' do
19
+ options = described_class.build([:name], :field)
20
+ expect(options.name).to eq(:name)
21
+ end
22
+
23
+ context 'when an element class supplied' do
24
+ it 'uses the supplied name' do
25
+ element_class = Class.new(PageMagic::Element) do
26
+ selector({ css: 'class' })
27
+ def self.name
28
+ 'PageSection'
29
+ end
30
+ end
31
+
32
+ options = described_class.build([:supplied_name, element_class], :field)
33
+ expect(options.name).to eq(:supplied_name)
34
+ end
35
+ end
36
+ end
37
+
38
+ context 'when not supplied' do
39
+ context 'when an element class supplied' do
40
+ it 'uses the name of the class' do
41
+ element_class = Class.new(PageMagic::Element) do
42
+ selector({ css: 'class' })
43
+ def self.name
44
+ 'PageSection'
45
+ end
46
+ end
47
+
48
+ options = described_class.build([element_class], :field)
49
+ expect(options.name).to eq(:page_section)
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ describe 'selector' do
56
+ context 'when selector supplied' do
57
+ it 'sets the selector' do
58
+ options = described_class.build([{ id: 'child' }], :field)
59
+ expect(options.selector).to eq(PageMagic::Element::Selector.find(:id).build(:field, 'child'))
60
+ end
61
+
62
+ context 'when page_element class supplied' do
63
+ it 'uses the selector on the class' do
64
+ expected_selector = { css: 'class' }
65
+ element_class = Class.new(PageMagic::Element) do
66
+ selector({ css: 'class' })
67
+ def self.name
68
+ 'PageSection'
69
+ end
70
+ end
71
+ options = described_class.build([element_class, expected_selector], :field)
72
+
73
+ expect(options.selector).to eq(PageMagic::Element::Selector.find(:css).build(:field, 'class'))
74
+ end
75
+ end
76
+ end
77
+
78
+ context 'when no selector supplied' do
79
+ context 'when page_element class supplied' do
80
+ it 'uses the selector on the class' do
81
+ element_class = Class.new(PageMagic::Element) do
82
+ selector({ css: 'class' })
83
+
84
+ def self.name
85
+ 'PageSection'
86
+ end
87
+ end
88
+ options = described_class.build([element_class], :field)
89
+
90
+ expect(options.selector).to eq(PageMagic::Element::Selector.find(:css).build(:field, 'class'))
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ it 'sets prefetched options' do
97
+ options = described_class.build(%i[page_section prefetched_element], :field)
98
+ expect(options.element).to eq(:prefetched_element)
99
+ end
100
+
101
+ context 'complex elements' do
102
+ let!(:element_class) do
103
+ Class.new(PageMagic::Element) do
104
+ def self.name
105
+ 'PageSection'
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ describe '.validate!' do
113
+ let(:options) do
114
+ {
115
+ type: :type,
116
+ selector: { css: 'css' },
117
+ element: :object,
118
+ element_class: Class.new(PageMagic::Element)
119
+ }
120
+ end
121
+
122
+ describe 'selector' do
123
+ context 'when nil' do
124
+ context 'and prefetched `element` is nil' do
125
+ it 'raise an error' do
126
+ subject = described_class.new(options.except(:selector, :element))
127
+ expect { subject.validate! }.to raise_exception(PageMagic::InvalidConfigurationException,
128
+ described_class::INVALID_SELECTOR_MSG)
129
+ end
130
+ end
131
+
132
+ context 'when `element` is not nil' do
133
+ it 'does not raise an error' do
134
+ subject = described_class.new(options.except(:selector))
135
+ expect { subject.validate! }.not_to raise_exception
136
+ end
137
+ end
138
+ end
139
+
140
+ context 'when is empty hash' do
141
+ it 'raises an error' do
142
+ subject = described_class.new(options.update(selector: {}).except(:element))
143
+ expect { subject.validate! }.to raise_exception(PageMagic::InvalidConfigurationException,
144
+ described_class::INVALID_SELECTOR_MSG)
145
+ end
146
+ end
147
+
148
+ context 'when defined on both class and as parameter' do
149
+ it 'uses the supplied selector' do
150
+ element_class = Class.new(PageMagic::Element) do
151
+ selector css: 'selector'
152
+ end
153
+
154
+ subject = described_class.new(options.update(element_class: element_class))
155
+ expect { subject.validate! }.not_to raise_exception
156
+ end
157
+ end
158
+ end
159
+
160
+ context 'when type nil' do
161
+ it 'raise an error' do
162
+ subject = described_class.new(options.except(:type))
163
+ expect { subject.validate! }.to raise_exception(PageMagic::InvalidConfigurationException,
164
+ described_class::TYPE_REQUIRED_MESSAGE)
165
+ end
166
+ end
167
+
168
+ describe '`element_class`' do
169
+ context 'when nil' do
170
+ it 'raise and error' do
171
+ subject = described_class.new(options.except(:element_class))
172
+ expect { subject.validate! }.to raise_exception(PageMagic::InvalidConfigurationException,
173
+ described_class::INVALID_ELEMENT_CLASS_MSG)
174
+ end
175
+ end
176
+
177
+ context 'not a type of `PageMagic::Element`' do
178
+ it 'raise and error' do
179
+ subject = described_class.new(options.update(element_class: Object))
180
+ expect { subject.validate! }.to raise_exception(PageMagic::InvalidConfigurationException,
181
+ described_class::INVALID_ELEMENT_CLASS_MSG)
182
+ end
183
+ end
184
+ end
185
+ end
186
+
187
+ describe '#selector' do
188
+ it 'returns a selector' do
189
+ input_options = {
190
+ type: :type,
191
+ selector: { css: 'css' },
192
+ options: { a: :b },
193
+ element: :object,
194
+ element_class: Class.new(PageMagic::Element)
195
+ }
196
+ options = described_class.new(input_options)
197
+ expect(options.selector).to eq(PageMagic::Element::Selector.find(:css).build(:type, 'css', options: { a: :b }))
198
+ end
199
+ end
200
+ end