marekj-watirloo 0.0.3 → 0.0.5
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/History.txt +12 -0
- data/Manifest.txt +59 -34
- data/README.rdoc +63 -80
- data/Rakefile.rb +32 -39
- data/config/locker.yml +0 -0
- data/lib/watirloo.rb +9 -162
- data/lib/watirloo/browsers.rb +73 -0
- data/lib/watirloo/desktop.rb +44 -0
- data/lib/watirloo/{firewatir_ducktape.rb → extension/firewatir_ducktape.rb} +0 -0
- data/lib/watirloo/extension/object.rb +26 -0
- data/lib/watirloo/{watir_ducktape.rb → extension/watir_ducktape.rb} +181 -16
- data/lib/watirloo/extension/watir_reflector.rb +83 -0
- data/lib/watirloo/locker.rb +84 -0
- data/lib/watirloo/page.rb +105 -0
- data/spec/browser_spec.rb +38 -0
- data/spec/browser_threads_spec.rb +45 -0
- data/spec/checkbox_group_spec.rb +136 -0
- data/spec/checkbox_groups_spec.rb +55 -0
- data/spec/checkboxes_value_spec.rb +35 -0
- data/spec/desktop_spec.rb +54 -0
- data/spec/extra/browser_events_spec.rb +76 -0
- data/spec/extra/page_objects_metrics.rb +139 -0
- data/spec/face_mixing_spec.rb +55 -0
- data/{test → spec}/firewatir/attach_instance_test.rb +0 -0
- data/spec/firewatir/spec_results.html +263 -0
- data/spec/firewatir/spec_results.txt +23 -0
- data/spec/firewatir/spec_results_failed.txt +3 -0
- data/{test → spec}/html/census.html +0 -0
- data/spec/html/checkbox_group1.html +33 -0
- data/spec/html/labels.html +53 -0
- data/spec/html/no_title.html +13 -0
- data/{test → spec}/html/person.html +0 -0
- data/spec/html/radio_group.html +35 -0
- data/{test → spec}/html/select_lists.html +0 -0
- data/spec/input_element_spec.rb +51 -0
- data/spec/label_spec.rb +65 -0
- data/spec/locker_spec.rb +49 -0
- data/spec/page_spec.rb +53 -0
- data/spec/person_def_wrappers_spec.rb +40 -0
- data/spec/radio_group_spec.rb +95 -0
- data/spec/radio_groups_spec.rb +55 -0
- data/spec/reflector_spec.rb +82 -0
- data/spec/select_list_options_spec.rb +40 -0
- data/spec/select_lists_spec.rb +151 -0
- data/{test/test_helper.rb → spec/spec_helper.rb} +6 -4
- data/spec/spec_helper_ff.rb +5 -0
- data/spec/spec_helper_runner.rb +13 -0
- data/spec/spec_results.html +566 -0
- data/spec/spec_results.txt +179 -0
- data/spec/spec_results_failed.txt +1 -0
- data/spec/text_fields_spec.rb +56 -0
- data/watirloo.gemspec +44 -44
- metadata +80 -39
- data/lib/watirloo/reflector.rb +0 -137
- data/test/checkbox_group_test.rb +0 -83
- data/test/checkboxes_value_test.rb +0 -50
- data/test/html/checkbox_group1.html +0 -20
- data/test/html/labels.html +0 -32
- data/test/html/radio_group.html +0 -41
- data/test/interfaces_test.rb +0 -79
- data/test/label_test.rb +0 -64
- data/test/person_def_wrappers_test.rb +0 -55
- data/test/radio_group_test.rb +0 -97
- data/test/select_list_in_class_test.rb +0 -39
- data/test/select_list_options_test.rb +0 -39
- data/test/select_lists_test.rb +0 -145
- data/test/text_fields_test.rb +0 -68
@@ -0,0 +1,73 @@
|
|
1
|
+
module Watirloo
|
2
|
+
|
3
|
+
# This is 'opinionated' method.
|
4
|
+
# The way I work with browsers is this:
|
5
|
+
# I save the current handle of the browser (ie.hwnd) to the storage yaml file so I can reattach to the same
|
6
|
+
# browser later. Basically in exploratory testing I don't want to start and close browsers. I want to maintain
|
7
|
+
# reference to one (or more) browsers and I have nicknames for them.
|
8
|
+
# on restart of tests useing Watirloo I reuse the browser. If the browser is not there I just start a new browser which
|
9
|
+
# will from now on become my new 'default' test session browser.
|
10
|
+
# In Case of Firefox I attach to the 'one' existing firefox or a start a new one.
|
11
|
+
# So this method either attaches to one that's there or it starts a new one and puts it in Browsers::Storage
|
12
|
+
# this way of working with browsers is opinionated I think.
|
13
|
+
# if you want you can just use Watir::IE.start and reuse that browsers for tests. This here is a convenience method
|
14
|
+
def self.browser(key = 'default')
|
15
|
+
case Browsers.target
|
16
|
+
when :ie then Browsers.ie key
|
17
|
+
when :firefox then Browsers.ff
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# manage references to browsers. Currently IE or Firefox.
|
22
|
+
# Safari? Other Browser? not yet
|
23
|
+
module Browsers
|
24
|
+
|
25
|
+
@@target = :ie #by default we talk to IE on Windows.
|
26
|
+
@@targets = [:ie, :firefox]
|
27
|
+
|
28
|
+
class << self
|
29
|
+
|
30
|
+
# set and get the target. by default we talk to :ie.
|
31
|
+
def target
|
32
|
+
return @@target
|
33
|
+
end
|
34
|
+
|
35
|
+
def target=(indicator)
|
36
|
+
if @@targets.include? indicator
|
37
|
+
@@target = indicator
|
38
|
+
else
|
39
|
+
raise Exception, "target indicator #{indicator} is not valid: use :ie or :firefox"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
# provides browser instance to client.
|
45
|
+
# attaches to the existing 'default' test session browser on the desktop
|
46
|
+
# By convention the mental model here is that we are working
|
47
|
+
# with one browser on the desktop. This is how a person would typically work
|
48
|
+
def ie(key='default')
|
49
|
+
begin
|
50
|
+
Locker.browser key
|
51
|
+
rescue => e #XXX it's probably a bad practice to use exception for logic control
|
52
|
+
# TODO logger here
|
53
|
+
ie = Watir::IE.start
|
54
|
+
sleep 3
|
55
|
+
Locker.add(ie, key)
|
56
|
+
ie #return newly created browser for the test session and store it for laterusage
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def ff
|
61
|
+
require 'watirloo/firewatir_ducktape'
|
62
|
+
# this is a cruch for quick work with pages.
|
63
|
+
# in reality you want to create a browser and pass it as argument to initialize Page class
|
64
|
+
begin
|
65
|
+
FireWatir::Firefox.attach #this attach is a crutch
|
66
|
+
rescue
|
67
|
+
puts 'got to start browser ff dude'
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Watirloo
|
2
|
+
|
3
|
+
# The browser desktop manager
|
4
|
+
# checks to see what browsers already exist on the dekstop.
|
5
|
+
# compares what is on the desktop to what was there last time
|
6
|
+
module Desktop
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
# returns browser windows found on the desktop
|
11
|
+
def browsers
|
12
|
+
brs =[]
|
13
|
+
Watir::IE.each {|ie| brs << ie }
|
14
|
+
brs
|
15
|
+
end
|
16
|
+
|
17
|
+
# return handles of Browsers found on desktop
|
18
|
+
def hwnds
|
19
|
+
hs =[]
|
20
|
+
browsers.each {|ie| hs << ie.hwnd}
|
21
|
+
hs
|
22
|
+
end
|
23
|
+
|
24
|
+
# returns handles for browsers that appeared on Desktop since the last scan for browsers
|
25
|
+
def additions(known_hwnds)
|
26
|
+
hwnds.select {|h| !known_hwnds.include?(h)}
|
27
|
+
end
|
28
|
+
|
29
|
+
# returns handles for browsers no longer found on the Desktop since the last scan for browsers
|
30
|
+
def deletions(known_hwnds)
|
31
|
+
known_hwnds.select {|h| !hwnds.include?(h)}
|
32
|
+
end
|
33
|
+
|
34
|
+
# Closes all the browsers on the desktop.
|
35
|
+
# Creats a known clear slate where no browsers exist
|
36
|
+
def clear
|
37
|
+
Watir::IE.each {|ie| ie.close}
|
38
|
+
sleep 3
|
39
|
+
raise Exception, "Failed to clear all the browsers from the desktop" unless browsers.empty?
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
File without changes
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
# see this;http://blog.jayfields.com/2008/02/ruby-dynamically-define-method.html
|
3
|
+
# instance_exec is used for face methods
|
4
|
+
if VERSION <= '1.8.6'
|
5
|
+
class Object
|
6
|
+
module InstanceExecHelper; end
|
7
|
+
include InstanceExecHelper
|
8
|
+
# instance_exec method evaluates a block of code relative to the specified object, with parameters whom come from outside the object.
|
9
|
+
def instance_exec(*args, &block)
|
10
|
+
begin
|
11
|
+
old_critical, Thread.critical = Thread.critical, true
|
12
|
+
n = 0
|
13
|
+
n += 1 while respond_to?(mname="__instance_exec#{n}")
|
14
|
+
InstanceExecHelper.module_eval{ define_method(mname, &block) }
|
15
|
+
ensure
|
16
|
+
Thread.critical = old_critical
|
17
|
+
end
|
18
|
+
begin
|
19
|
+
ret = send(mname, *args)
|
20
|
+
ensure
|
21
|
+
InstanceExecHelper.module_eval{ remove_method(mname) } rescue nil
|
22
|
+
end
|
23
|
+
ret
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -59,13 +59,13 @@ module Watir
|
|
59
59
|
|
60
60
|
def values
|
61
61
|
opts = []
|
62
|
-
@o.each {|
|
62
|
+
@o.each {|rc| opts << rc.ole_object.invoke('value')}
|
63
63
|
return opts
|
64
64
|
end
|
65
65
|
|
66
66
|
def get_by_value value
|
67
67
|
if values.member? value
|
68
|
-
@o.find {|
|
68
|
+
@o.find {|rc| rc.ole_object.invoke('value') == value}
|
69
69
|
else
|
70
70
|
raise ::Watir::Exception::WatirException, "value #{value} not found in hidden_values"
|
71
71
|
end
|
@@ -93,6 +93,14 @@ module Watir
|
|
93
93
|
def selected_radio
|
94
94
|
@o.find {|r| r.isSet?}
|
95
95
|
end
|
96
|
+
|
97
|
+
# if a radio button in a group is set then the group is set
|
98
|
+
# by default it should be set but many HTML implementations provide
|
99
|
+
# the radiogroup to the user with no default one set (Bad practice perhaps)
|
100
|
+
def set?
|
101
|
+
selected_radio ? true : false
|
102
|
+
end
|
103
|
+
|
96
104
|
end
|
97
105
|
|
98
106
|
# radios that share the same :name attribute form a RadioGroup.
|
@@ -118,11 +126,30 @@ module Watir
|
|
118
126
|
include RadioCheckGroup
|
119
127
|
include RadioGroupCommonWatir
|
120
128
|
|
121
|
-
def initialize(container,
|
129
|
+
def initialize(container, how, what)
|
122
130
|
@container = container
|
123
|
-
@
|
124
|
-
@
|
131
|
+
@how = how
|
132
|
+
@what = what
|
133
|
+
@o = locate
|
134
|
+
end
|
135
|
+
|
136
|
+
def name
|
137
|
+
@name
|
138
|
+
end
|
139
|
+
|
140
|
+
def locate
|
141
|
+
@name = case @how
|
142
|
+
when :name then @what
|
143
|
+
when :index then
|
144
|
+
names = []
|
145
|
+
@container.radios.each do |r|
|
146
|
+
names << r.name
|
147
|
+
end
|
148
|
+
names.uniq.at(@what-1) # follow 1-based index addressing for Watir API
|
149
|
+
end
|
150
|
+
@container.radios.find_all {|r| r.name == @name}
|
125
151
|
end
|
152
|
+
private :locate
|
126
153
|
|
127
154
|
# which value is selected?. returns value text as string
|
128
155
|
# So per HTML401 spec I am not sure if we should ever have empyt array returned here
|
@@ -138,17 +165,66 @@ module Watir
|
|
138
165
|
|
139
166
|
end
|
140
167
|
|
168
|
+
class TextFields < ElementCollections
|
169
|
+
|
170
|
+
def reflect
|
171
|
+
ret = []
|
172
|
+
self.each do |item|
|
173
|
+
how, what = get_how_what get_attribs(item)
|
174
|
+
facename = suggest_def_name what
|
175
|
+
value = item.value
|
176
|
+
# this approach relies on doc element
|
177
|
+
ret << "face(:#{facename}) {doc.text_field(:#{how}, #{what.inspect})}"
|
178
|
+
ret << "#{facename}.value.should == #{value.inspect}"
|
179
|
+
end
|
180
|
+
ret
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
class RadioGroups < ElementCollections
|
186
|
+
|
187
|
+
def element_class; RadioGroup; end
|
188
|
+
def length
|
189
|
+
names = []
|
190
|
+
@container.radios.each do |r|
|
191
|
+
names << r.name
|
192
|
+
end
|
193
|
+
names.uniq.size #non repeating names
|
194
|
+
end
|
195
|
+
|
196
|
+
def reflect
|
197
|
+
ret = []
|
198
|
+
self.each do |item|
|
199
|
+
name = item.name
|
200
|
+
facename = suggest_def_name name
|
201
|
+
values = item.values
|
202
|
+
selected = item.selected
|
203
|
+
ret << "face(:#{facename}) {doc.radio_group(#{name.inspect})}"
|
204
|
+
ret << "#{facename}.values.should == #{values.inspect}"
|
205
|
+
ret << "#{facename}.selected.should == #{selected.inspect}"
|
206
|
+
end
|
207
|
+
ret
|
208
|
+
end
|
209
|
+
|
210
|
+
|
211
|
+
private
|
212
|
+
def iterator_object(i)
|
213
|
+
@container.radio_group(:index, i + 1)
|
214
|
+
end
|
215
|
+
end
|
141
216
|
|
142
217
|
module CheckboxGroupCommonWatir
|
143
218
|
|
144
|
-
# returns selected
|
145
|
-
# []
|
146
|
-
# [checkbox, checkbox] = checkboxes that are selected
|
219
|
+
# returns selected checkboxes as array
|
220
|
+
# when empty [] then nothing is selected
|
221
|
+
# when [checkbox, checkbox] = array of checkboxes that are selected
|
222
|
+
# that you can iterate over for tests.
|
147
223
|
def selected_checkboxes
|
148
224
|
@o.select {|cb| cb.isSet?}
|
149
225
|
end
|
150
226
|
|
151
|
-
#
|
227
|
+
# convenience method as a filter for selected_values
|
152
228
|
# returns:
|
153
229
|
# nil => when no checkbox is set
|
154
230
|
# 'value' => if one checkbox is set
|
@@ -168,6 +244,14 @@ module Watir
|
|
168
244
|
# compare to SelectList where selected returns selected_item
|
169
245
|
alias selected selected_value
|
170
246
|
|
247
|
+
|
248
|
+
# if at least one checkbox is selected then the group is considered set
|
249
|
+
def set?
|
250
|
+
(selected_checkboxes != []) ? true : false
|
251
|
+
end
|
252
|
+
|
253
|
+
alias checked? set?
|
254
|
+
|
171
255
|
end
|
172
256
|
|
173
257
|
# Checkbox group semantically behaves like multi select list box.
|
@@ -179,11 +263,30 @@ module Watir
|
|
179
263
|
include RadioCheckGroup
|
180
264
|
include CheckboxGroupCommonWatir
|
181
265
|
|
182
|
-
def initialize(container,
|
266
|
+
def initialize(container, how, what)
|
183
267
|
@container = container
|
184
|
-
@
|
185
|
-
@
|
268
|
+
@how = how
|
269
|
+
@what = what
|
270
|
+
@o = locate
|
271
|
+
end
|
272
|
+
|
273
|
+
def name
|
274
|
+
@name
|
275
|
+
end
|
276
|
+
|
277
|
+
def locate
|
278
|
+
@name = case @how
|
279
|
+
when :name then @what
|
280
|
+
when :index then
|
281
|
+
names = []
|
282
|
+
@container.checkboxes.each do |cb|
|
283
|
+
names << cb.name
|
284
|
+
end
|
285
|
+
names.uniq.at(@what-1) # follow 1-based index addressing for Watir API
|
286
|
+
end
|
287
|
+
@container.checkboxes.find_all {|cb| cb.name == @name}
|
186
288
|
end
|
289
|
+
private :locate
|
187
290
|
|
188
291
|
# returns array of value attributes. Each Checkbox in a group
|
189
292
|
# has a value which is invisible to the user
|
@@ -196,15 +299,57 @@ module Watir
|
|
196
299
|
end
|
197
300
|
end
|
198
301
|
|
302
|
+
class CheckboxGroups < ElementCollections
|
303
|
+
def element_class; CheckboxGroup; end
|
304
|
+
def length
|
305
|
+
names = []
|
306
|
+
@container.checkboxes.each do |cb|
|
307
|
+
names << cb.name
|
308
|
+
end
|
309
|
+
names.uniq.size #non repeating names
|
310
|
+
end
|
311
|
+
|
312
|
+
def reflect
|
313
|
+
ret = []
|
314
|
+
self.each do |item|
|
315
|
+
name = item.name
|
316
|
+
facename = suggest_def_name(name)
|
317
|
+
values = item.values
|
318
|
+
selected = item.selected
|
319
|
+
ret << "face(:#{facename}) {doc.checkbox_group(#{name.inspect})}"
|
320
|
+
ret << "#{facename}.values.should == #{values.inspect}"
|
321
|
+
ret << "#{facename}.selected.should == #{selected.inspect}"
|
322
|
+
end
|
323
|
+
ret
|
324
|
+
end
|
325
|
+
|
326
|
+
private
|
327
|
+
def iterator_object(i)
|
328
|
+
@container.checkbox_group(:index, i + 1)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
199
332
|
|
200
333
|
module Container
|
201
|
-
|
202
|
-
|
334
|
+
|
335
|
+
def radio_group(how, what=nil)
|
336
|
+
how, what = process_default :name, how, what
|
337
|
+
RadioGroup.new(self, how, what)
|
203
338
|
end
|
204
339
|
|
205
|
-
def
|
206
|
-
|
340
|
+
def radio_groups
|
341
|
+
RadioGroups.new(self)
|
342
|
+
end
|
343
|
+
|
344
|
+
def checkbox_group(how, what=nil)
|
345
|
+
how, what = process_default :name, how, what
|
346
|
+
CheckboxGroup.new(self, how, what)
|
347
|
+
end
|
348
|
+
|
349
|
+
def checkbox_groups
|
350
|
+
CheckboxGroups.new(self)
|
207
351
|
end
|
352
|
+
|
208
353
|
end
|
209
354
|
|
210
355
|
class RadioCheckCommon
|
@@ -372,5 +517,25 @@ module Watir
|
|
372
517
|
# alias, items or contents return the same visible text items
|
373
518
|
alias items getAllContents
|
374
519
|
|
520
|
+
|
521
|
+
|
522
|
+
def reflect
|
523
|
+
ret = []
|
524
|
+
self.each do |item|
|
525
|
+
name = item.name
|
526
|
+
facename = suggest_def_name name
|
527
|
+
values = item.values
|
528
|
+
items = item.items
|
529
|
+
selected_item = item.selected_item
|
530
|
+
selected_value = item.selected_value
|
531
|
+
|
532
|
+
ret << "face(:#{facename}) {doc.select_list(:name, #{name.inspect})}"
|
533
|
+
ret << "#{facename}.items.should == #{items.inspect}"
|
534
|
+
ret << "#{facename}.values.should == #{values.inspect}"
|
535
|
+
ret << "#{facename}.selected_item.should == #{selected_item.inspect}"
|
536
|
+
ret << "#{facename}.selected_value.should == #{selected_value.inspect}"
|
537
|
+
end
|
538
|
+
ret
|
539
|
+
end
|
375
540
|
end
|
376
541
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
Look Ma!, I can Has Reflect The Browser
|
3
|
+
|
4
|
+
Watir::Reflector module added to watir.
|
5
|
+
reflect watir element collections. reflections create wrapper methods
|
6
|
+
with suggested semantic naming based on id, name, value or combination.
|
7
|
+
the intention is to create a scaffolding for Watirloo::Page elements.
|
8
|
+
=end
|
9
|
+
|
10
|
+
module Watir
|
11
|
+
|
12
|
+
# Watirloo::Page objects scaffold creation. Talks to the current page and reflects
|
13
|
+
# the watir elements to be used for semantic test objects tests.
|
14
|
+
module Reflector
|
15
|
+
|
16
|
+
#cleanup the def name for some kind of semantic name
|
17
|
+
def suggest_def_name(how)
|
18
|
+
how.gsub!(/_+/,'_') # double underscores to one
|
19
|
+
how.gsub!(/^_/, '') # if it begins with undrscore kill it.
|
20
|
+
how.gsub!(/\s+/, '_') # kill spaces if for some strange reason they exist
|
21
|
+
how.underscore #Any CamelCase will be converted to camel_no_case
|
22
|
+
end
|
23
|
+
|
24
|
+
# glean(:text_fields, [:id, :name, :value]
|
25
|
+
# glean(:radios, [:id, :name, :value])
|
26
|
+
# glean and make a map of types and attributes needed for reflection
|
27
|
+
# this should be private I think
|
28
|
+
def get_attribs(item)
|
29
|
+
attribs = [:id, :name, :value]
|
30
|
+
h = {}
|
31
|
+
attribs.each do |k|
|
32
|
+
v = item.attribute_value k.to_s
|
33
|
+
h[k] = v
|
34
|
+
end
|
35
|
+
h
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_how_what h
|
39
|
+
how, what = '', ''
|
40
|
+
if h[:id] != '' #First Choice: if id is not blank then we'll use it
|
41
|
+
how, what = :id, h[:id]
|
42
|
+
elsif h[:name] != '' #Second Choice: if name is not blank then we'll use it instead of id
|
43
|
+
how, what = :name, h[:name]
|
44
|
+
elsif h[:value] != ''
|
45
|
+
how, what = :value, h[:value]
|
46
|
+
end
|
47
|
+
[how, what]
|
48
|
+
end
|
49
|
+
|
50
|
+
# public interface for Reflector.
|
51
|
+
# ie.reflect # => returns object definitions for entire dom using ie as container
|
52
|
+
# ie.frame('main').select_lists.reflect# => returns definitions for select_lists
|
53
|
+
# only contained by the frame
|
54
|
+
# you can be as granular as needed
|
55
|
+
def reflect
|
56
|
+
puts "I has not exist. Implements me please"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
module Container
|
62
|
+
|
63
|
+
# container asks collections to reflect themselves
|
64
|
+
# each collection knows how to reflect itself and what to reflect
|
65
|
+
def reflect
|
66
|
+
ref = []
|
67
|
+
[:radio_groups, :checkbox_groups, :text_fields, :select_lists].each do |type|
|
68
|
+
ret << self.send(type).reflect
|
69
|
+
end
|
70
|
+
return ref
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
class ElementCollections
|
77
|
+
|
78
|
+
# adds reflect method to element collections
|
79
|
+
include ::Watir::Reflector
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|