AXElements 0.6.0beta1
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/.yardopts +20 -0
- data/LICENSE.txt +25 -0
- data/README.markdown +150 -0
- data/Rakefile +109 -0
- data/docs/AccessibilityTips.markdown +119 -0
- data/docs/Acting.markdown +340 -0
- data/docs/Debugging.markdown +326 -0
- data/docs/Inspecting.markdown +255 -0
- data/docs/KeyboardEvents.markdown +57 -0
- data/docs/NewBehaviour.markdown +151 -0
- data/docs/Notifications.markdown +271 -0
- data/docs/Searching.markdown +250 -0
- data/docs/TestingExtensions.markdown +52 -0
- data/docs/images/AX.png +0 -0
- data/docs/images/all_the_buttons.jpg +0 -0
- data/docs/images/ui_hierarchy.dot +34 -0
- data/docs/images/ui_hierarchy.png +0 -0
- data/ext/key_coder/extconf.rb +6 -0
- data/ext/key_coder/key_coder.m +77 -0
- data/lib/ax_elements/accessibility/enumerators.rb +104 -0
- data/lib/ax_elements/accessibility/language.rb +347 -0
- data/lib/ax_elements/accessibility/qualifier.rb +73 -0
- data/lib/ax_elements/accessibility.rb +164 -0
- data/lib/ax_elements/core.rb +541 -0
- data/lib/ax_elements/element.rb +593 -0
- data/lib/ax_elements/elements/application.rb +88 -0
- data/lib/ax_elements/elements/button.rb +18 -0
- data/lib/ax_elements/elements/radio_button.rb +18 -0
- data/lib/ax_elements/elements/row.rb +30 -0
- data/lib/ax_elements/elements/static_text.rb +17 -0
- data/lib/ax_elements/elements/systemwide.rb +46 -0
- data/lib/ax_elements/inspector.rb +116 -0
- data/lib/ax_elements/macruby_extensions.rb +255 -0
- data/lib/ax_elements/notification.rb +37 -0
- data/lib/ax_elements/version.rb +9 -0
- data/lib/ax_elements.rb +30 -0
- data/lib/minitest/ax_elements.rb +19 -0
- data/lib/mouse.rb +185 -0
- data/lib/rspec/expectations/ax_elements.rb +15 -0
- data/test/elements/test_application.rb +72 -0
- data/test/elements/test_row.rb +27 -0
- data/test/elements/test_systemwide.rb +38 -0
- data/test/helper.rb +119 -0
- data/test/test_accessibility.rb +127 -0
- data/test/test_blankness.rb +26 -0
- data/test/test_core.rb +448 -0
- data/test/test_element.rb +939 -0
- data/test/test_enumerators.rb +81 -0
- data/test/test_inspector.rb +121 -0
- data/test/test_language.rb +157 -0
- data/test/test_macruby_extensions.rb +303 -0
- data/test/test_mouse.rb +5 -0
- data/test/test_search_semantics.rb +143 -0
- metadata +219 -0
@@ -0,0 +1,939 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
class TestElements < TestAX
|
4
|
+
|
5
|
+
APP = AX::Element.new REF, AX.attrs_of_element(REF)
|
6
|
+
WINDOW = AX::Element.attribute_for REF, KAXMainWindowAttribute
|
7
|
+
|
8
|
+
def window_children
|
9
|
+
@@window_children ||= WINDOW.attribute :children
|
10
|
+
end
|
11
|
+
|
12
|
+
def no_button
|
13
|
+
@@no_button ||= window_children.find do |item|
|
14
|
+
item.is_a?(AX::Button) && attribute_for(item.ref, KAXTitleAttribute) == 'No'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def maybe_button
|
19
|
+
@@maybe_button ||= window_children.find do |item|
|
20
|
+
item.is_a?(AX::Button) && attribute_for(item.ref, KAXTitleAttribute) == 'Maybe So'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def slider
|
25
|
+
@@slider ||= window_children.find { |item| item.class == AX::Slider }
|
26
|
+
end
|
27
|
+
|
28
|
+
def check_box
|
29
|
+
@@check_box ||= window_children.find { |item| item.class == AX::CheckBox }
|
30
|
+
end
|
31
|
+
|
32
|
+
def static_text
|
33
|
+
@@static_text ||= window_children.find { |item| item.class == AX::StaticText }
|
34
|
+
end
|
35
|
+
|
36
|
+
def table
|
37
|
+
@@table ||= WINDOW.attribute(:children).find do |element|
|
38
|
+
element.respond_to?(:identifier) &&
|
39
|
+
element.attribute(:identifier) == 'table'
|
40
|
+
end.attribute(:children).find do |element|
|
41
|
+
element.class == AX::Table
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def value_for element
|
46
|
+
AX.attr_of_element(element.ref, KAXValueAttribute)
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
class TestElementLookupFailure < TestElements
|
53
|
+
|
54
|
+
def failure
|
55
|
+
@@failure ||= AX::Element::LookupFailure.new(APP, :blah)
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_kind_of_argument_error
|
59
|
+
assert_kind_of ArgumentError, failure
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_correct_message
|
63
|
+
assert_match /blah was not found for/, failure.message
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_inspects_sender
|
67
|
+
assert_match /#{APP.inspect}/, failure.message
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
class TestElementReadOnlyAttribute < TestElements
|
74
|
+
|
75
|
+
def read_only
|
76
|
+
@@read_only ||= AX::Element::ReadOnlyAttribute.new(APP, :blah)
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_kind_of_method_missing_error
|
80
|
+
assert_kind_of NoMethodError, read_only
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_correct_message
|
84
|
+
assert_match /read only attribute for/, read_only.message
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_inspects_sender
|
88
|
+
assert_match /#{APP.inspect}/, read_only.message
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
class TestElementSearchFailure < TestElements
|
95
|
+
|
96
|
+
def minimal_exception
|
97
|
+
@@minimal_exception ||= AX::Element::SearchFailure.new(WINDOW, :test, nil)
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_correct_message
|
101
|
+
pattern = /Could not find `test` as a child of AX::StandardWindow/
|
102
|
+
assert_match pattern, minimal_exception.message
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_includes_trace
|
106
|
+
trace = minimal_exception.message.split('Element Path:').last
|
107
|
+
assert_match /AX::StandardWindow "AXElementsTester"/, trace
|
108
|
+
assert_match /AX::Application "AXElementsTester" 2 children/, trace
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_includes_filters_if_filters_given
|
112
|
+
exception = AX::Element::SearchFailure.new(WINDOW, :test, attr: 'value', other: 1)
|
113
|
+
assert_match /`test\(attr: "value", other: 1\)`/, exception.message
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
class TestElementAttributes < TestElements
|
120
|
+
|
121
|
+
def test_not_empty
|
122
|
+
refute_empty APP.attributes
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_contains_proper_info
|
126
|
+
assert_includes APP.attributes, KAXRoleAttribute
|
127
|
+
assert_includes APP.attributes, KAXTitleAttribute
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
class TestElementAttribute < TestElements
|
134
|
+
|
135
|
+
def test_raises_for_non_existent_attributes
|
136
|
+
assert_raises AX::Element::LookupFailure do
|
137
|
+
APP.attribute :fakeattribute
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_raises_if_cache_hit_but_object_does_not_have_the_attribute
|
142
|
+
WINDOW.attribute :nyan? # make sure attr resolves to a constant first
|
143
|
+
assert_raises AX::Element::LookupFailure do
|
144
|
+
APP.attribute :nyan?
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def bench_string_attribute
|
149
|
+
assert_performance_linear do |n|
|
150
|
+
n.times { APP.attribute(:title) }
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def bench_boolean_attribute
|
155
|
+
assert_performance_linear do |n|
|
156
|
+
n.times { WINDOW.attribute(:focused) }
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def bench_children_array
|
161
|
+
assert_performance_linear do |n|
|
162
|
+
n.times { table.attribute(:columns) }
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def bench_simlpe_element_attribute
|
167
|
+
assert_performance_linear do |n|
|
168
|
+
n.times { WINDOW.attribute(:parent) }
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def bench_complex_element_attribute
|
173
|
+
assert_performance_linear do |n|
|
174
|
+
n.times { WINDOW.attribute(:close_button) }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def bench_boxed_attribute
|
179
|
+
assert_performance_linear do |n|
|
180
|
+
n.times { WINDOW.attribute(:position) }
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
class TestElementGetAttributeTransformsSymbolToConstant < TestElements
|
188
|
+
|
189
|
+
def test_matches_single_word_attribute
|
190
|
+
assert_equal KAXApplicationRole, APP.attribute(:role)
|
191
|
+
end
|
192
|
+
|
193
|
+
def test_matches_mutlti_word_attribute
|
194
|
+
assert_equal 'application', APP.attribute(:role_description)
|
195
|
+
end
|
196
|
+
|
197
|
+
def test_matches_acronym_attributes
|
198
|
+
assert_instance_of NSURL, WINDOW.attribute(:url)
|
199
|
+
end
|
200
|
+
|
201
|
+
def test_predicate_that_starts_with_is
|
202
|
+
assert_instance_of_boolean WINDOW.attribute(:nyan?)
|
203
|
+
end
|
204
|
+
|
205
|
+
def test_predicate_that_does_not_start_with_is
|
206
|
+
assert_instance_of_boolean WINDOW.attribute(:focused?)
|
207
|
+
end
|
208
|
+
|
209
|
+
def test_matches_exactly_with_one_word
|
210
|
+
# finds role when role and subrole exist
|
211
|
+
assert_equal KAXStandardWindowSubrole, WINDOW.attribute(:subrole)
|
212
|
+
assert_equal KAXWindowRole, WINDOW.attribute(:role)
|
213
|
+
end
|
214
|
+
|
215
|
+
def test_matches_exactly_with_multiple_words
|
216
|
+
assert_equal 'AXElementsTester', WINDOW.attribute(:title)
|
217
|
+
assert_kind_of AX::Element, WINDOW.attribute(:title_ui_element)
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
|
222
|
+
|
223
|
+
class TestElementAttributeParsesData < TestElements
|
224
|
+
|
225
|
+
def test_does_not_return_raw_values
|
226
|
+
assert_kind_of AX::Element, APP.attribute(:menu_bar)
|
227
|
+
end
|
228
|
+
|
229
|
+
def test_does_not_return_raw_values_in_array
|
230
|
+
assert_kind_of AX::Element, APP.attribute(:children).first
|
231
|
+
end
|
232
|
+
|
233
|
+
def test_returns_nil_for_nil_attributes
|
234
|
+
assert_nil WINDOW.attribute(:proxy)
|
235
|
+
end
|
236
|
+
|
237
|
+
def test_returns_boolean_false_for_false_attributes
|
238
|
+
assert_equal false, APP.attribute(:enhanced_user_interface)
|
239
|
+
end
|
240
|
+
|
241
|
+
def test_returns_boolean_true_for_true_attributes
|
242
|
+
assert_equal true, WINDOW.attribute(:main)
|
243
|
+
end
|
244
|
+
|
245
|
+
def test_wraps_axuielementref_objects
|
246
|
+
# need intermediate step to make sure AX::MenuBar exists
|
247
|
+
ret = APP.attribute(:menu_bar)
|
248
|
+
assert_instance_of AX::MenuBar, ret
|
249
|
+
end
|
250
|
+
|
251
|
+
def test_returns_array_for_array_attributes
|
252
|
+
assert_kind_of Array, APP.attribute(:children)
|
253
|
+
end
|
254
|
+
|
255
|
+
def test_returned_arrays_are_not_empty_when_they_should_have_stuff
|
256
|
+
refute_empty APP.attribute(:children)
|
257
|
+
end
|
258
|
+
|
259
|
+
def test_returned_element_arrays_do_not_have_raw_elements
|
260
|
+
assert_kind_of AX::Element, APP.attribute(:children).first
|
261
|
+
end
|
262
|
+
|
263
|
+
def test_returns_number_for_number_attribute
|
264
|
+
assert_instance_of Fixnum, check_box.attribute(:value)
|
265
|
+
end
|
266
|
+
|
267
|
+
def test_returns_array_of_numbers_when_attribute_has_an_array_of_numbers
|
268
|
+
# could be a float or a fixnum, be more lenient
|
269
|
+
assert_kind_of NSNumber, slider.attribute(:allowed_values).first
|
270
|
+
end
|
271
|
+
|
272
|
+
def test_returns_a_cgsize_for_size_attributes
|
273
|
+
assert_instance_of CGSize, WINDOW.attribute(:size)
|
274
|
+
end
|
275
|
+
|
276
|
+
def test_returns_a_cgpoint_for_point_attributes
|
277
|
+
assert_instance_of CGPoint, WINDOW.attribute(:position)
|
278
|
+
end
|
279
|
+
|
280
|
+
def test_returns_a_cfrange_for_range_attributes
|
281
|
+
assert_instance_of CFRange, static_text.attribute(:visible_character_range)
|
282
|
+
end
|
283
|
+
|
284
|
+
def test_returns_a_cgrect_for_rect_attributes
|
285
|
+
assert_kind_of CGRect, WINDOW.attribute(:lol)
|
286
|
+
end
|
287
|
+
|
288
|
+
def test_works_with_strings
|
289
|
+
assert_instance_of String, APP.attribute(:title)
|
290
|
+
end
|
291
|
+
|
292
|
+
def test_works_with_urls
|
293
|
+
assert_instance_of NSURL, WINDOW.attribute(:url)
|
294
|
+
end
|
295
|
+
|
296
|
+
end
|
297
|
+
|
298
|
+
|
299
|
+
class TestElementAttributeChoosesCorrectClasseForElements < TestElements
|
300
|
+
|
301
|
+
def scroll_area
|
302
|
+
window_children.find { |item| item.class == AX::ScrollArea }
|
303
|
+
end
|
304
|
+
|
305
|
+
def test_chooses_role_if_no_subrole
|
306
|
+
assert_instance_of AX::Application, WINDOW.attribute(:parent)
|
307
|
+
end
|
308
|
+
|
309
|
+
def test_chooses_subrole_if_it_exists
|
310
|
+
classes = window_children.map &:class
|
311
|
+
assert_includes classes, AX::CloseButton
|
312
|
+
assert_includes classes, AX::SearchField
|
313
|
+
end
|
314
|
+
|
315
|
+
def test_chooses_role_if_subrole_is_nil
|
316
|
+
web_area = scroll_area.attribute(:children).first
|
317
|
+
assert_instance_of AX::WebArea, web_area
|
318
|
+
end
|
319
|
+
|
320
|
+
# we use dock items here, because this is an easy case of
|
321
|
+
# the role class being recursively created when trying to
|
322
|
+
# create the subrole class
|
323
|
+
def test_creates_role_for_subrole_if_it_does_not_exist_yet
|
324
|
+
ref = AXUIElementCreateApplication(pid_for 'com.apple.dock')
|
325
|
+
dock = AX::Element.new ref, AX.attrs_of_element(ref)
|
326
|
+
list = dock.attribute(:children).first
|
327
|
+
children = list.attribute(:children).map &:class
|
328
|
+
assert_includes children, AX::ApplicationDockItem
|
329
|
+
end
|
330
|
+
|
331
|
+
# @todo this happens when accessibility is not implemented correctly,
|
332
|
+
# and the problem with fixing it is the performance cost
|
333
|
+
def test_chooses_role_if_subrole_is_unknown_type
|
334
|
+
skip 'This case is not handled right now'
|
335
|
+
end
|
336
|
+
|
337
|
+
# @todo Get another case to test, something not used elsewhere
|
338
|
+
def test_creates_inheritance_chain
|
339
|
+
WINDOW.attribute :children
|
340
|
+
assert_equal AX::Button, AX::CloseButton.superclass
|
341
|
+
assert_equal AX::Element, AX::Button.superclass
|
342
|
+
end
|
343
|
+
|
344
|
+
end
|
345
|
+
|
346
|
+
|
347
|
+
class TestElementDescription < TestElements
|
348
|
+
|
349
|
+
def test_raise_error_if_object_has_no_description
|
350
|
+
assert_raises AX::Element::LookupFailure do
|
351
|
+
APP.description
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
def test_gets_description
|
356
|
+
assert_equal 'The cake is a lie!', no_button.description
|
357
|
+
end
|
358
|
+
|
359
|
+
def test_responds_to # true when it exist, false otherwise
|
360
|
+
assert_respond_to WINDOW, :description
|
361
|
+
refute_respond_to APP, :description
|
362
|
+
end
|
363
|
+
|
364
|
+
end
|
365
|
+
|
366
|
+
|
367
|
+
class TestElementPID < TestElements
|
368
|
+
|
369
|
+
def test_works_for_apps
|
370
|
+
assert_kind_of NSNumber, APP.pid
|
371
|
+
refute_equal 0, APP.pid
|
372
|
+
end
|
373
|
+
|
374
|
+
def test_works_for_aribtrary_elements
|
375
|
+
assert_kind_of NSNumber, WINDOW.pid
|
376
|
+
refute_equal 0, WINDOW.pid
|
377
|
+
assert_equal APP.pid, WINDOW.pid
|
378
|
+
end
|
379
|
+
|
380
|
+
end
|
381
|
+
|
382
|
+
|
383
|
+
class TestElementAttributeWritable < TestElements
|
384
|
+
|
385
|
+
def test_raises_error_for_non_existant_attributes
|
386
|
+
assert_raises AX::Element::LookupFailure do
|
387
|
+
APP.attribute_writable? :fake_attribute
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
def test_true_for_writable_attributes
|
392
|
+
assert WINDOW.attribute_writable? :position
|
393
|
+
end
|
394
|
+
|
395
|
+
def test_false_for_non_writable_attributes
|
396
|
+
refute APP.attribute_writable? :title
|
397
|
+
end
|
398
|
+
|
399
|
+
end
|
400
|
+
|
401
|
+
|
402
|
+
class TestElementSetAttribute < TestElements
|
403
|
+
|
404
|
+
def test_raises_error_if_attribute_is_not_writable
|
405
|
+
assert_raises AX::Element::ReadOnlyAttribute do
|
406
|
+
APP.set_attribute :title, 'pantaloons'
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
def test_passes_values_down_to_core_correctly
|
411
|
+
[25, 75, 50].each do |value|
|
412
|
+
slider.set_attribute :value, value
|
413
|
+
assert_equal value, value_for(slider).to_i
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
# important test since it checks if we wrap boxes
|
418
|
+
def test_set_window_size
|
419
|
+
original_size = WINDOW.attribute :size
|
420
|
+
new_size = original_size.dup
|
421
|
+
new_size.height /= 2
|
422
|
+
WINDOW.set_attribute :size, original_size
|
423
|
+
assert_equal original_size, WINDOW.attribute(:size)
|
424
|
+
ensure
|
425
|
+
WINDOW.set_attribute :size, original_size
|
426
|
+
end
|
427
|
+
|
428
|
+
end
|
429
|
+
|
430
|
+
|
431
|
+
class TestElementParamAttributes < TestElements
|
432
|
+
|
433
|
+
def test_empty_for_dock
|
434
|
+
assert_empty APP.param_attributes
|
435
|
+
end
|
436
|
+
|
437
|
+
def test_not_empty_for_search_field
|
438
|
+
assert_includes static_text.param_attributes, KAXStringForRangeParameterizedAttribute
|
439
|
+
assert_includes static_text.param_attributes, KAXLineForIndexParameterizedAttribute
|
440
|
+
assert_includes static_text.param_attributes, KAXBoundsForRangeParameterizedAttribute
|
441
|
+
end
|
442
|
+
|
443
|
+
end
|
444
|
+
|
445
|
+
|
446
|
+
class TestElementGetParamAttribute < TestElements
|
447
|
+
|
448
|
+
def test_raises_exception_for_non_existent_attribute
|
449
|
+
assert_raises AX::Element::LookupFailure do
|
450
|
+
static_text.param_attribute :bob_loblaw, nil
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
def test_contains_proper_info
|
455
|
+
attr = static_text.param_attribute(:string_for_range, CFRange.new(0, 5))
|
456
|
+
assert_equal 'AXEle', attr
|
457
|
+
end
|
458
|
+
|
459
|
+
def test_get_attributed_string
|
460
|
+
attr = static_text.param_attribute(:attributed_string_for_range, CFRange.new(0, 5))
|
461
|
+
assert_kind_of NSAttributedString, attr
|
462
|
+
assert_equal 'AXEle', attr.string
|
463
|
+
end
|
464
|
+
|
465
|
+
def bench_string_for_range_attribute
|
466
|
+
range = CFRange.new(0, 1)
|
467
|
+
assert_performance_linear do |n|
|
468
|
+
n.times { static_text.param_attribute(:string_for_range, range) }
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
end
|
473
|
+
|
474
|
+
|
475
|
+
class TestElementActions < TestElements
|
476
|
+
|
477
|
+
def test_empty_an_app
|
478
|
+
assert_empty APP.actions
|
479
|
+
end
|
480
|
+
|
481
|
+
def test_not_empty_for_dock_item
|
482
|
+
refute_empty WINDOW.actions
|
483
|
+
end
|
484
|
+
|
485
|
+
def test_contains_proper_info
|
486
|
+
assert_includes WINDOW.actions, KAXRaiseAction
|
487
|
+
end
|
488
|
+
|
489
|
+
end
|
490
|
+
|
491
|
+
|
492
|
+
class TestElementPerformAction < TestElements
|
493
|
+
|
494
|
+
def test_raise_error_for_non_existant_action
|
495
|
+
assert_raises AX::Element::LookupFailure do
|
496
|
+
APP.perform_action :fake_action
|
497
|
+
end
|
498
|
+
end
|
499
|
+
|
500
|
+
def test_returns_boolean
|
501
|
+
[:increment, :decrement].each do |action|
|
502
|
+
assert_equal true, slider.perform_action(action)
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
# this is meant to test the cache hit case
|
507
|
+
def test_only_performs_action_if_object_has_the_action
|
508
|
+
no_button.perform_action(:press) # make sure :press is cached
|
509
|
+
assert_raises AX::Element::LookupFailure do
|
510
|
+
WINDOW.perform_action(:press)
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
def test_actually_performs_action
|
515
|
+
before = value_for slider
|
516
|
+
slider.perform_action :increment
|
517
|
+
after = value_for slider
|
518
|
+
assert after > before
|
519
|
+
slider.perform_action :decrement
|
520
|
+
assert_equal before, value_for(slider)
|
521
|
+
end
|
522
|
+
|
523
|
+
end
|
524
|
+
|
525
|
+
|
526
|
+
class TestElementSearch < TestElements
|
527
|
+
|
528
|
+
def test_boxes_becomes_find_all_box
|
529
|
+
assert_instance_of AX::CheckBox, WINDOW.search(:check_boxes).first
|
530
|
+
end
|
531
|
+
|
532
|
+
def test_sliders_becomes_find_all_slider
|
533
|
+
assert_equal slider.ref, WINDOW.search(:sliders).first.ref
|
534
|
+
end
|
535
|
+
|
536
|
+
def test_value_indicators_becomes_find_all_value_indicator
|
537
|
+
assert_instance_of AX::ValueIndicator, slider.search(:value_indicators).first
|
538
|
+
end
|
539
|
+
|
540
|
+
def test_window_becomes_find_window
|
541
|
+
assert_kind_of AX::Window, APP.search(:window)
|
542
|
+
end
|
543
|
+
|
544
|
+
def test_value_indicator_becomes_find_value_indicator
|
545
|
+
assert_instance_of AX::ValueIndicator, slider.search(:value_indicator)
|
546
|
+
end
|
547
|
+
|
548
|
+
def test_works_with_no_filters
|
549
|
+
assert_equal WINDOW, APP.search(:window)
|
550
|
+
end
|
551
|
+
|
552
|
+
def test_forwards_all_filters
|
553
|
+
assert_equal WINDOW, APP.search(:window, title: 'AXElementsTester')
|
554
|
+
assert_equal nil, slider.search(:value_indicator, help: 'Cookie')
|
555
|
+
end
|
556
|
+
|
557
|
+
def bench_search_table
|
558
|
+
skip
|
559
|
+
# searching a table with a lot of rows
|
560
|
+
# in this test, our variable is the number of rows
|
561
|
+
end
|
562
|
+
|
563
|
+
def bench_search_tall_tree
|
564
|
+
skip
|
565
|
+
# search a tall tree
|
566
|
+
# in this test, our variable is how tall the tree is
|
567
|
+
end
|
568
|
+
|
569
|
+
def bench_search_simple
|
570
|
+
skip
|
571
|
+
# search with no filters
|
572
|
+
# this is a simple case that should be thrown in as a
|
573
|
+
# control, it will help gauge how much search performance
|
574
|
+
# depends on the core implementation of the AX module
|
575
|
+
end
|
576
|
+
|
577
|
+
def bench_search_many_filters
|
578
|
+
skip
|
579
|
+
# search with a lot of filters
|
580
|
+
# this test will become more important as the filtering
|
581
|
+
# logic becomes more complex due to supporting different
|
582
|
+
# ideas (e.g. the :title_ui_element hack that exists in v0.4)
|
583
|
+
end
|
584
|
+
|
585
|
+
end
|
586
|
+
|
587
|
+
|
588
|
+
class TestElementMethodMissing < TestElements
|
589
|
+
|
590
|
+
def test_gets_attribute_if_attribute_found
|
591
|
+
assert_equal 'AXElementsTester', APP.title
|
592
|
+
end
|
593
|
+
|
594
|
+
def test_gets_param_attribute_if_param_attribute_found_and_not_attribute
|
595
|
+
assert_equal 'AXEle', static_text.string_for_range(CFRange.new 0, 5)
|
596
|
+
end
|
597
|
+
|
598
|
+
def test_does_search_if_not_attribute_and_not_param_attribute_but_has_children
|
599
|
+
indicator = slider.value_indicator
|
600
|
+
assert_instance_of AX::ValueIndicator, indicator
|
601
|
+
end
|
602
|
+
|
603
|
+
def test_raises_if_search_returns_blank
|
604
|
+
assert_raises AX::Element::SearchFailure do
|
605
|
+
APP.element_that_does_not_exist
|
606
|
+
end
|
607
|
+
end
|
608
|
+
|
609
|
+
def test_calls_super_if_not_attribute_and_no_children
|
610
|
+
assert_raises NoMethodError do no_button.list end
|
611
|
+
end
|
612
|
+
|
613
|
+
def test_processes_attribute_return_values
|
614
|
+
assert_instance_of AX::StandardWindow, no_button.parent
|
615
|
+
assert_instance_of CGPoint, no_button.position
|
616
|
+
end
|
617
|
+
|
618
|
+
def bench_attribute_string
|
619
|
+
assert_performance_linear do |n|
|
620
|
+
n.times { WINDOW.attribute(:title) }
|
621
|
+
end
|
622
|
+
end
|
623
|
+
|
624
|
+
def bench_attribute_boolean
|
625
|
+
assert_performance_linear do |n|
|
626
|
+
n.times { WINDOW.focused? }
|
627
|
+
end
|
628
|
+
end
|
629
|
+
|
630
|
+
def bench_attribute_child_array
|
631
|
+
assert_performance_linear do |n|
|
632
|
+
n.times { table.columns }
|
633
|
+
end
|
634
|
+
end
|
635
|
+
|
636
|
+
def bench_attribute_boxed
|
637
|
+
assert_performance_linear do |n|
|
638
|
+
n.times { WINDOW.position }
|
639
|
+
end
|
640
|
+
end
|
641
|
+
|
642
|
+
def bench_attribute_element
|
643
|
+
assert_performance_linear do |n|
|
644
|
+
n.times { WINDOW.parent }
|
645
|
+
end
|
646
|
+
end
|
647
|
+
|
648
|
+
def bench_param_attribute
|
649
|
+
range = CFRange.new(0, 1)
|
650
|
+
assert_performance_linear do |n|
|
651
|
+
n.times { static_text.string_for_range(range) }
|
652
|
+
end
|
653
|
+
end
|
654
|
+
|
655
|
+
end
|
656
|
+
|
657
|
+
|
658
|
+
class TestElementNotifications < TestElements
|
659
|
+
|
660
|
+
def teardown
|
661
|
+
AX.unregister_notifs
|
662
|
+
end
|
663
|
+
|
664
|
+
def test_does_no_translation_for_custom_notifications
|
665
|
+
class << AX; alias_method :old_register_for_notif, :register_for_notif; end
|
666
|
+
def AX.register_for_notif ref, notif, &block
|
667
|
+
notif == 'Cheezburger'
|
668
|
+
end
|
669
|
+
assert APP.on_notification('Cheezburger')
|
670
|
+
ensure
|
671
|
+
class << AX; alias_method :register_for_notif, :old_register_for_notif; end
|
672
|
+
end
|
673
|
+
|
674
|
+
def radio_group
|
675
|
+
@@radio_group ||= window_children.find do |item|
|
676
|
+
item.class == AX::RadioGroup
|
677
|
+
end
|
678
|
+
end
|
679
|
+
|
680
|
+
def radio_gaga
|
681
|
+
@@gaga ||= radio_group.attribute(:children).find do |item|
|
682
|
+
item.attribute(:title) == 'Gaga'
|
683
|
+
end
|
684
|
+
end
|
685
|
+
|
686
|
+
# this test is weird, sometimes the radio group sends the notification
|
687
|
+
# first and other times the button sends it, but for the sake of the
|
688
|
+
# test we only care that one of them sent the notif
|
689
|
+
def test_yielded_proper_objects
|
690
|
+
element = notification = nil
|
691
|
+
radio_gaga.on_notification :value_changed do |el,notif|
|
692
|
+
element, notification = el, notif
|
693
|
+
end
|
694
|
+
|
695
|
+
action_for radio_gaga.ref, KAXPressAction
|
696
|
+
|
697
|
+
assert AX.wait_for_notif 1.0
|
698
|
+
assert_kind_of NSString, notification
|
699
|
+
assert_kind_of AX::Element, element
|
700
|
+
end
|
701
|
+
|
702
|
+
def test_returns_wrapped_pair
|
703
|
+
expected = [radio_gaga, KAXValueChangedNotification]
|
704
|
+
assert_equal expected, radio_gaga.on_notification(:value_changed)
|
705
|
+
end
|
706
|
+
|
707
|
+
end
|
708
|
+
|
709
|
+
|
710
|
+
class TestElementSizeOf < TestElements
|
711
|
+
|
712
|
+
def test_raises_for_bad
|
713
|
+
assert_raises AX::Element::LookupFailure do
|
714
|
+
APP.size_of :roflcopter
|
715
|
+
end
|
716
|
+
end
|
717
|
+
|
718
|
+
def test_returns_number
|
719
|
+
assert_equal 2, APP.size_of(:children)
|
720
|
+
end
|
721
|
+
|
722
|
+
end
|
723
|
+
|
724
|
+
|
725
|
+
class TestElementInspect < TestElements
|
726
|
+
|
727
|
+
def app
|
728
|
+
@@app ||= APP.inspect
|
729
|
+
end
|
730
|
+
|
731
|
+
def window
|
732
|
+
@@window ||= WINDOW.inspect
|
733
|
+
end
|
734
|
+
|
735
|
+
def text
|
736
|
+
@@text ||= static_text.inspect
|
737
|
+
end
|
738
|
+
|
739
|
+
def button
|
740
|
+
@@button ||= no_button.inspect
|
741
|
+
end
|
742
|
+
|
743
|
+
def slidr
|
744
|
+
@@slidr ||= slider.inspect
|
745
|
+
end
|
746
|
+
|
747
|
+
def test_inspect_includes_header_and_tail
|
748
|
+
assert_match /^\#<AX::Element/, app
|
749
|
+
assert_match />$/, app
|
750
|
+
end
|
751
|
+
|
752
|
+
def test_inspect_includes_position_if_possible
|
753
|
+
position_regexp = /\(\d+\.\d+, \d+\.\d+\)/
|
754
|
+
assert_match position_regexp, window
|
755
|
+
refute_match position_regexp, app
|
756
|
+
end
|
757
|
+
|
758
|
+
def test_inspect_includes_children_if_possible
|
759
|
+
child_regexp = /\d+ child/
|
760
|
+
assert_match child_regexp, app
|
761
|
+
refute_match child_regexp, text
|
762
|
+
end
|
763
|
+
|
764
|
+
def test_inspect_includes_enabled_if_possible
|
765
|
+
enabled_regexp = /enabled\[.\]/
|
766
|
+
assert_match enabled_regexp, button
|
767
|
+
refute_match enabled_regexp, app
|
768
|
+
end
|
769
|
+
|
770
|
+
def test_inspect_includes_focused_if_possible
|
771
|
+
focused_regexp = /focused\[.\]/
|
772
|
+
assert_match focused_regexp, slider.inspect
|
773
|
+
refute_match focused_regexp, app
|
774
|
+
end
|
775
|
+
|
776
|
+
def test_inspect_always_has_identifier
|
777
|
+
title_regexp = /"AXElementsTester"/
|
778
|
+
assert_match title_regexp, app
|
779
|
+
assert_match title_regexp, window
|
780
|
+
assert_match title_regexp, text
|
781
|
+
|
782
|
+
assert_match /"No"/, button
|
783
|
+
assert_match /value=50/, slidr
|
784
|
+
end
|
785
|
+
|
786
|
+
end
|
787
|
+
|
788
|
+
|
789
|
+
class TestElementRespondTo < TestElements
|
790
|
+
|
791
|
+
def test_true_for_attributes_object_has
|
792
|
+
assert_respond_to APP, :title
|
793
|
+
end
|
794
|
+
|
795
|
+
def test_false_for_attributes_object_does_not_have
|
796
|
+
refute_respond_to APP, :title_ui_element
|
797
|
+
end
|
798
|
+
|
799
|
+
def test_true_for_parameterized_attributes
|
800
|
+
assert_respond_to static_text, :string_for_range
|
801
|
+
end
|
802
|
+
|
803
|
+
def test_false_for_search_names
|
804
|
+
refute_respond_to APP, :window
|
805
|
+
end
|
806
|
+
|
807
|
+
def test_still_works_for_regular_methods
|
808
|
+
assert_respond_to APP, :attributes
|
809
|
+
refute_respond_to APP, :crazy_thing_that_cant_work
|
810
|
+
end
|
811
|
+
|
812
|
+
end
|
813
|
+
|
814
|
+
|
815
|
+
class TestElementToPoint < TestElements
|
816
|
+
|
817
|
+
def test_makes_point_in_center_of_element
|
818
|
+
obj = WINDOW.dup
|
819
|
+
def obj.attribute arg
|
820
|
+
arg == :position ? CGPointMake(400, 350) : CGSizeMake(200, 500)
|
821
|
+
end
|
822
|
+
assert_equal CGPointMake(500, 600), obj.to_point
|
823
|
+
end
|
824
|
+
|
825
|
+
end
|
826
|
+
|
827
|
+
|
828
|
+
class TestElementMethods < TestElements
|
829
|
+
|
830
|
+
def test_includes_objects_attributes
|
831
|
+
assert_includes APP.methods, :title
|
832
|
+
assert_includes APP.methods, :children
|
833
|
+
assert_includes WINDOW.methods, :lol
|
834
|
+
assert_includes WINDOW.methods, :nyan
|
835
|
+
end
|
836
|
+
|
837
|
+
def test_does_not_break_interface_and_calls_super
|
838
|
+
list = APP.methods
|
839
|
+
[:send, :freeze, :nil?].each do |name|
|
840
|
+
assert_includes list, name
|
841
|
+
end
|
842
|
+
list = APP.methods(false)
|
843
|
+
[:send, :freeze, :nil?].each do |name|
|
844
|
+
refute_includes list, name
|
845
|
+
end
|
846
|
+
list = APP.methods(true, true)
|
847
|
+
[:isEqual, :infoForBinding, :valueForKeyPath].each do |name|
|
848
|
+
assert_includes list, name
|
849
|
+
end
|
850
|
+
end
|
851
|
+
|
852
|
+
end
|
853
|
+
|
854
|
+
|
855
|
+
class TestElementEquivalence < TestElements
|
856
|
+
|
857
|
+
def app
|
858
|
+
ref = AXUIElementCreateApplication(PID)
|
859
|
+
AX::Element.new ref, AX.attrs_of_element(ref)
|
860
|
+
end
|
861
|
+
|
862
|
+
def dock
|
863
|
+
ref = AXUIElementCreateApplication(pid_for 'com.apple.dock')
|
864
|
+
AX::Element.new ref, AX.attrs_of_element(ref)
|
865
|
+
end
|
866
|
+
|
867
|
+
def window
|
868
|
+
ref = AX.attr_of_element(REF, KAXMainWindowAttribute)
|
869
|
+
AX::Element.new ref, AX.attrs_of_element(ref)
|
870
|
+
end
|
871
|
+
|
872
|
+
def list
|
873
|
+
dock.attribute(KAXChildrenAttribute).first
|
874
|
+
end
|
875
|
+
|
876
|
+
def test_equal_to_self_at_app_level
|
877
|
+
assert APP == app
|
878
|
+
assert APP.eql? app
|
879
|
+
assert APP.equal? app
|
880
|
+
refute APP != app
|
881
|
+
end
|
882
|
+
|
883
|
+
def test_equal_to_self_for_arbitrary_object
|
884
|
+
assert WINDOW == window
|
885
|
+
assert WINDOW.eql? window
|
886
|
+
assert WINDOW.equal? window
|
887
|
+
refute WINDOW != window
|
888
|
+
end
|
889
|
+
|
890
|
+
# not equal (inter-app)
|
891
|
+
def test_not_equal_between_different_apps
|
892
|
+
refute app == dock
|
893
|
+
refute app.eql? dock
|
894
|
+
refute app.equal? dock
|
895
|
+
assert app != dock
|
896
|
+
end
|
897
|
+
|
898
|
+
# not equal (intra-app)
|
899
|
+
def test_not_equal_inside_app_with_differnt_objects
|
900
|
+
refute app == window
|
901
|
+
refute app.eql? window
|
902
|
+
refute app.equal? window
|
903
|
+
assert app != window
|
904
|
+
end
|
905
|
+
|
906
|
+
end
|
907
|
+
|
908
|
+
|
909
|
+
class TestStripPrefix < MiniTest::Unit::TestCase
|
910
|
+
|
911
|
+
def prefix_test before, after
|
912
|
+
assert_equal after, AX::Element.strip_prefix(before)
|
913
|
+
end
|
914
|
+
|
915
|
+
def test_removes_ax_prefix
|
916
|
+
prefix_test 'AXButton', 'Button'
|
917
|
+
end
|
918
|
+
|
919
|
+
def test_removes_combination_prefixes
|
920
|
+
prefix_test 'MCAXButton', 'Button'
|
921
|
+
end
|
922
|
+
|
923
|
+
def test_works_with_all_caps
|
924
|
+
prefix_test 'AXURL', 'URL'
|
925
|
+
end
|
926
|
+
|
927
|
+
def test_works_with_long_name
|
928
|
+
prefix_test 'AXTitleUIElement', 'TitleUIElement'
|
929
|
+
end
|
930
|
+
|
931
|
+
def test_strips_predicate_too
|
932
|
+
prefix_test 'AXIsApplicationRunning', 'ApplicationRunning'
|
933
|
+
end
|
934
|
+
|
935
|
+
def test_is_not_greedy
|
936
|
+
prefix_test 'AXAX', 'AX'
|
937
|
+
end
|
938
|
+
|
939
|
+
end
|