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.
Files changed (54) hide show
  1. data/.yardopts +20 -0
  2. data/LICENSE.txt +25 -0
  3. data/README.markdown +150 -0
  4. data/Rakefile +109 -0
  5. data/docs/AccessibilityTips.markdown +119 -0
  6. data/docs/Acting.markdown +340 -0
  7. data/docs/Debugging.markdown +326 -0
  8. data/docs/Inspecting.markdown +255 -0
  9. data/docs/KeyboardEvents.markdown +57 -0
  10. data/docs/NewBehaviour.markdown +151 -0
  11. data/docs/Notifications.markdown +271 -0
  12. data/docs/Searching.markdown +250 -0
  13. data/docs/TestingExtensions.markdown +52 -0
  14. data/docs/images/AX.png +0 -0
  15. data/docs/images/all_the_buttons.jpg +0 -0
  16. data/docs/images/ui_hierarchy.dot +34 -0
  17. data/docs/images/ui_hierarchy.png +0 -0
  18. data/ext/key_coder/extconf.rb +6 -0
  19. data/ext/key_coder/key_coder.m +77 -0
  20. data/lib/ax_elements/accessibility/enumerators.rb +104 -0
  21. data/lib/ax_elements/accessibility/language.rb +347 -0
  22. data/lib/ax_elements/accessibility/qualifier.rb +73 -0
  23. data/lib/ax_elements/accessibility.rb +164 -0
  24. data/lib/ax_elements/core.rb +541 -0
  25. data/lib/ax_elements/element.rb +593 -0
  26. data/lib/ax_elements/elements/application.rb +88 -0
  27. data/lib/ax_elements/elements/button.rb +18 -0
  28. data/lib/ax_elements/elements/radio_button.rb +18 -0
  29. data/lib/ax_elements/elements/row.rb +30 -0
  30. data/lib/ax_elements/elements/static_text.rb +17 -0
  31. data/lib/ax_elements/elements/systemwide.rb +46 -0
  32. data/lib/ax_elements/inspector.rb +116 -0
  33. data/lib/ax_elements/macruby_extensions.rb +255 -0
  34. data/lib/ax_elements/notification.rb +37 -0
  35. data/lib/ax_elements/version.rb +9 -0
  36. data/lib/ax_elements.rb +30 -0
  37. data/lib/minitest/ax_elements.rb +19 -0
  38. data/lib/mouse.rb +185 -0
  39. data/lib/rspec/expectations/ax_elements.rb +15 -0
  40. data/test/elements/test_application.rb +72 -0
  41. data/test/elements/test_row.rb +27 -0
  42. data/test/elements/test_systemwide.rb +38 -0
  43. data/test/helper.rb +119 -0
  44. data/test/test_accessibility.rb +127 -0
  45. data/test/test_blankness.rb +26 -0
  46. data/test/test_core.rb +448 -0
  47. data/test/test_element.rb +939 -0
  48. data/test/test_enumerators.rb +81 -0
  49. data/test/test_inspector.rb +121 -0
  50. data/test/test_language.rb +157 -0
  51. data/test/test_macruby_extensions.rb +303 -0
  52. data/test/test_mouse.rb +5 -0
  53. data/test/test_search_semantics.rb +143 -0
  54. 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