AXElements 0.6.0beta1

Sign up to get free protection for your applications and to get access to all the features.
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
data/test/test_core.rb ADDED
@@ -0,0 +1,448 @@
1
+ class TestCore < TestAX
2
+
3
+ WINDOW = attribute_for REF, KAXMainWindowAttribute
4
+
5
+ def child name
6
+ children_for(WINDOW).find do |item|
7
+ attribute_for(item, KAXRoleAttribute) == name
8
+ end
9
+ end
10
+
11
+ def slider
12
+ @@slider ||= child KAXSliderRole
13
+ end
14
+
15
+ def check_box
16
+ @@check_box ||= child KAXCheckBoxRole
17
+ end
18
+
19
+ def search_box
20
+ @@search_box ||= child KAXTextFieldRole
21
+ end
22
+
23
+ def button
24
+ @@button ||= child KAXButtonRole
25
+ end
26
+
27
+ def static_text
28
+ @@static_text ||= child KAXStaticTextRole
29
+ end
30
+
31
+ def yes_button
32
+ @@yes_button ||= children_for(WINDOW).find do |item|
33
+ if attribute_for(item, KAXRoleAttribute) == KAXButtonRole
34
+ attribute_for(item, KAXTitleAttribute) == 'Yes'
35
+ end
36
+ end
37
+ end
38
+
39
+ end
40
+
41
+
42
+ ##
43
+ # AFAICT every accessibility object **MUST** have attributes, so
44
+ # there are no tests to check what happens when they do not exist;
45
+ # though I am quite sure that AXElements will explode.
46
+ class TestAttrsOfElement < TestCore
47
+
48
+ def attrs
49
+ @@attrs ||= AX.attrs_of_element REF
50
+ end
51
+
52
+ def test_returns_array_of_strings
53
+ assert_instance_of String, attrs.first
54
+ end
55
+
56
+ def test_make_sure_certain_attributes_are_provided
57
+ assert_includes attrs, KAXRoleAttribute
58
+ assert_includes attrs, KAXRoleDescriptionAttribute
59
+ end
60
+
61
+ def test_other_attributes_that_the_app_should_have
62
+ assert_includes attrs, KAXChildrenAttribute
63
+ assert_includes attrs, KAXTitleAttribute
64
+ assert_includes attrs, KAXMenuBarAttribute
65
+ end
66
+
67
+ end
68
+
69
+
70
+ class TestAttrCountOfElement < TestCore
71
+
72
+ def test_returns_number_of_children
73
+ assert_equal children_for(REF).size, AX.attr_count_of_element(REF, KAXChildrenAttribute)
74
+ assert_equal 0, AX.attr_count_of_element(button, KAXChildrenAttribute)
75
+ end
76
+
77
+ # @todo there are things we care about?
78
+
79
+ end
80
+
81
+
82
+ class TestAttrOfElementGetsCorrectAttribute < TestCore
83
+
84
+ def test_title_is_title
85
+ assert_equal 'AXElementsTester', AX.attr_of_element(REF, KAXTitleAttribute)
86
+ end
87
+
88
+ # @note the app gives CGRectZero in screen coordinates, and then they are
89
+ # flipped for us to, so we need to flip them again
90
+ def test_custom_lol_is_rect
91
+ point = CGPointZero.dup
92
+ point.y = NSScreen.mainScreen.frame.size.height
93
+ expected_rect = CGRect.new point, CGSizeZero
94
+ ret = AX.attr_of_element WINDOW, 'AXLol'
95
+ ptr = Pointer.new CGRect.type
96
+ AXValueGetValue(ret, 3, ptr)
97
+ assert_equal expected_rect, ptr[0]
98
+ end
99
+
100
+ def test_hidden_is_hidden_value
101
+ assert_equal false, AX.attr_of_element(REF, KAXHiddenAttribute)
102
+ end
103
+
104
+ end
105
+
106
+
107
+ class TestAttrOfElementErrors < TestCore
108
+ include LoggingCapture
109
+
110
+ def test_logs_message_for_non_existant_attributes
111
+ with_logging do AX.attr_of_element REF, 'MADEUPATTRIBUTE' end
112
+ assert_match /#{KAXErrorAttributeUnsupported}/, @log_output.string
113
+ end
114
+
115
+ end
116
+
117
+
118
+ class TestAttrOfElementWritable < TestCore
119
+
120
+ def test_true_for_writable_attribute
121
+ assert AX.attr_of_element_writable?(WINDOW, KAXMainAttribute)
122
+ end
123
+
124
+ def test_false_for_non_writable_attribute
125
+ refute AX.attr_of_element_writable?(REF, KAXTitleAttribute)
126
+ end
127
+
128
+ def test_false_for_non_existante_attribute
129
+ refute AX.attr_of_element_writable?(REF, 'FAKE')
130
+ end
131
+
132
+ end
133
+
134
+
135
+ class TestSetAttrOfElement < TestCore
136
+
137
+ def test_set_a_slider
138
+ [25, 75, 50].each do |value|
139
+ AX.set_attr_of_element slider, KAXValueAttribute, value
140
+ assert_equal value, value_for(slider)
141
+ end
142
+ end
143
+
144
+ def test_set_a_text_field
145
+ [Time.now.to_s, ''].each do |value|
146
+ AX.set_attr_of_element(search_box, KAXValueAttribute, value)
147
+ assert_equal value, value_for(search_box)
148
+ end
149
+ end
150
+
151
+ end
152
+
153
+
154
+ class TestActionsOfElement < TestCore
155
+
156
+ def test_works_when_there_are_no_actions
157
+ assert_empty AX.actions_of_element REF
158
+ end
159
+
160
+ def test_returns_array_of_strings
161
+ assert_instance_of String, AX.actions_of_element(yes_button).first
162
+ end
163
+
164
+ def test_make_sure_certain_actions_are_present
165
+ actions = AX.actions_of_element slider
166
+ assert_includes actions, KAXIncrementAction
167
+ assert_includes actions, KAXDecrementAction
168
+ end
169
+
170
+ end
171
+
172
+
173
+ class TestActionOfElement < TestCore
174
+
175
+ def test_check_a_check_box
176
+ 2.times do # twice so it should be back where it started
177
+ value = value_for check_box
178
+ AX.action_of_element check_box, KAXPressAction
179
+ refute_equal value, value_for(check_box)
180
+ end
181
+ end
182
+
183
+ def test_sliding_the_slider
184
+ value = attribute_for slider, KAXValueAttribute
185
+ AX.action_of_element slider, KAXIncrementAction
186
+ assert attribute_for(slider, KAXValueAttribute) > value
187
+
188
+ value = attribute_for slider, KAXValueAttribute
189
+ AX.action_of_element slider, KAXDecrementAction
190
+ assert attribute_for(slider, KAXValueAttribute) < value
191
+ end
192
+
193
+ end
194
+
195
+
196
+ class TestKeyboardAction < TestCore
197
+
198
+ SYSTEM = AXUIElementCreateSystemWide()
199
+
200
+ def post string
201
+ set_attribute_for search_box, KAXFocusedAttribute, true
202
+ AX.keyboard_action REF, string
203
+ # sleep 0.01
204
+ assert_equal string, attribute_for(search_box, KAXValueAttribute)
205
+ ensure
206
+ button = children_for(search_box).find { |x| x.class == AX::Button }
207
+ action_for button, KAXPressAction
208
+ end
209
+
210
+ def test_uppercase_letters
211
+ post 'HELLO, WORLD'
212
+ end
213
+
214
+ def test_numbers
215
+ post '42'
216
+ end
217
+
218
+ def test_letters
219
+ post 'the cake is a lie'
220
+ end
221
+
222
+ def test_escape_sequences
223
+ post "\s"
224
+ end
225
+
226
+ end
227
+
228
+
229
+ class TestAXParamAttrsOfElement < TestCore
230
+
231
+ def test_empty_for_dock
232
+ assert_empty AX.param_attrs_of_element REF
233
+ end
234
+
235
+ def test_not_empty_for_search_field
236
+ assert_includes AX.param_attrs_of_element(static_text), KAXStringForRangeParameterizedAttribute
237
+ assert_includes AX.param_attrs_of_element(static_text), KAXLineForIndexParameterizedAttribute
238
+ assert_includes AX.param_attrs_of_element(static_text), KAXBoundsForRangeParameterizedAttribute
239
+ end
240
+
241
+ end
242
+
243
+
244
+ class TestAXParamAttrOfElement < TestCore
245
+
246
+ def test_contains_proper_info
247
+ attr = AX.param_attr_of_element(static_text,
248
+ KAXStringForRangeParameterizedAttribute,
249
+ CFRange.new(0, 5).to_axvalue)
250
+ assert_equal 'AXEle', attr
251
+ end
252
+
253
+ def test_get_attributed_string
254
+ attr = AX.param_attr_of_element(static_text,
255
+ # this is why we need name tranformers
256
+ KAXAttributedStringForRangeParameterizedAttribute,
257
+ CFRange.new(0, 5).to_axvalue)
258
+ assert_kind_of NSAttributedString, attr
259
+ assert_equal 'AXEle', attr.string
260
+ end
261
+
262
+ end
263
+
264
+
265
+ # be very careful about cleaning up state after notification tests
266
+ class TestAXNotifications < TestCore
267
+
268
+ # custom notification name
269
+ CHEEZBURGER = 'Cheezburger'
270
+
271
+ SHORT_TIMEOUT = 0.1
272
+ TIMEOUT = 1.0
273
+
274
+ def radio_group
275
+ @radio_group ||= child KAXRadioGroupRole
276
+ end
277
+
278
+ def radio_gaga
279
+ @@gaga ||= children_for(radio_group).find do |item|
280
+ attribute_for(item, KAXTitleAttribute) == 'Gaga'
281
+ end
282
+ end
283
+
284
+ def radio_flyer
285
+ @@flyer ||= children_for(radio_group).find do |item|
286
+ attribute_for(item, KAXTitleAttribute) == 'Flyer'
287
+ end
288
+ end
289
+
290
+ def teardown
291
+ if attribute_for(radio_gaga, KAXValueAttribute) == 1
292
+ action_for radio_flyer, KAXPressAction
293
+ end
294
+ AX.unregister_notifs
295
+ end
296
+
297
+ def test_break_if_ignoring
298
+ AX.register_for_notif(radio_gaga, KAXValueChangedNotification) { |_,_| true }
299
+ AX.instance_variable_set :@ignore_notifs, true
300
+
301
+ action_for radio_gaga, KAXPressAction
302
+
303
+ start = Time.now
304
+ refute AX.wait_for_notif(SHORT_TIMEOUT)
305
+ done = Time.now
306
+
307
+ refute_in_delta done, start, SHORT_TIMEOUT
308
+ end
309
+
310
+ def test_break_if_block_returns_falsey
311
+ AX.register_for_notif(radio_gaga, KAXValueChangedNotification) { |_,_| false }
312
+ action_for radio_gaga, KAXPressAction
313
+
314
+ start = Time.now
315
+ refute AX.wait_for_notif(SHORT_TIMEOUT)
316
+ done = Time.now
317
+
318
+ refute_in_delta done, start, SHORT_TIMEOUT
319
+ end
320
+
321
+ def test_stops_if_block_returns_truthy
322
+ AX.register_for_notif(radio_gaga, KAXValueChangedNotification) { |_,_| true }
323
+ action_for radio_gaga, KAXPressAction
324
+ assert AX.wait_for_notif(SHORT_TIMEOUT)
325
+ end
326
+
327
+ def test_returns_triple
328
+ ret = AX.register_for_notif(radio_gaga, KAXValueChangedNotification) { |_,_| true }
329
+ assert_equal AXObserverGetTypeID(), CFGetTypeID(ret[0])
330
+ assert_equal radio_gaga, ret[1]
331
+ assert_equal KAXValueChangedNotification, ret[2]
332
+ end
333
+
334
+ def test_wait_stops_waiting_when_notif_received
335
+ AX.register_for_notif(radio_gaga, KAXValueChangedNotification) { |_,_| true }
336
+ action_for radio_gaga, KAXPressAction
337
+
338
+ start = Time.now
339
+ AX.wait_for_notif(SHORT_TIMEOUT)
340
+ done = Time.now
341
+
342
+ msg = 'Might fail if your machine is under heavy load'
343
+ assert_in_delta done, start, 0.02, msg
344
+ end
345
+
346
+ def test_works_with_custom_notifs
347
+ got_callback = false
348
+ AX.register_for_notif(yes_button, CHEEZBURGER) do |_,_|
349
+ got_callback = true
350
+ end
351
+ action_for yes_button, KAXPressAction
352
+ AX.wait_for_notif(SHORT_TIMEOUT)
353
+ assert got_callback, 'did not get callback'
354
+ end
355
+
356
+ def test_unregistering_clears_notif
357
+ AX.register_for_notif(yes_button, CHEEZBURGER) { |_,_| true }
358
+ action_for yes_button, KAXPressAction
359
+ end
360
+
361
+ def test_unregistering_noops_if_not_registered
362
+ assert_block do
363
+ AX.unregister_notifs
364
+ AX.unregister_notifs
365
+ AX.unregister_notifs
366
+ end
367
+ end
368
+
369
+ def test_unregistering_sets_ignore_to_true
370
+ AX.register_for_notif(yes_button, CHEEZBURGER) { |_,_| true }
371
+ refute AX.instance_variable_get(:@ignore_notifs)
372
+ AX.unregister_notifs
373
+ assert AX.instance_variable_get(:@ignore_notifs)
374
+ end
375
+
376
+ def test_listening_to_app_catches_everything
377
+ got_callback = false
378
+ AX.register_for_notif(REF, KAXValueChangedNotification) do |_,_|
379
+ got_callback = true
380
+ end
381
+ action_for radio_gaga, KAXPressAction
382
+ AX.wait_for_notif(TIMEOUT)
383
+ assert got_callback
384
+ end
385
+
386
+ end
387
+
388
+
389
+ class TestElementAtPosition < TestCore
390
+
391
+ def test_returns_a_button_when_i_give_the_coordinates_of_a_button
392
+ point = attribute_for button, KAXPositionAttribute
393
+ ptr = Pointer.new CGPoint.type
394
+ AXValueGetValue(point, KAXValueCGPointType, ptr)
395
+ point = ptr[0]
396
+ element = AX.element_at_point(*point.to_a)
397
+ assert_equal button, element
398
+ end
399
+
400
+ end
401
+
402
+
403
+ class TestAXPIDThings < TestCore
404
+
405
+ def test_app_for_pid_returns_raw_element
406
+ ret = AX.application_for_pid PID
407
+ role = attribute_for ret, KAXRoleAttribute
408
+ assert_equal KAXApplicationRole, role
409
+ end
410
+
411
+ def test_app_for_pid_raises_if_pid_is_zero
412
+ assert_raises ArgumentError do
413
+ AX.application_for_pid 0
414
+ end
415
+ assert_raises ArgumentError do
416
+ AX.application_for_pid -1
417
+ end
418
+ end
419
+
420
+ def test_pid_for_app
421
+ assert_equal PID, AX.pid_of_element(REF)
422
+ end
423
+
424
+ def test_pid_for_dock_app_is_docks_pid
425
+ assert_equal PID, AX.pid_of_element(WINDOW)
426
+ end
427
+
428
+ end
429
+
430
+
431
+ # I'd prefer to not to be directly calling the log method bypassing
432
+ # the fact that it is a private method
433
+ class TestLogAXCall < TestCore
434
+ include LoggingCapture
435
+
436
+ def test_looks_up_code_properly
437
+ with_logging { AX.send(:log_error, REF, KAXErrorAPIDisabled) }
438
+ assert_match /API Disabled/, @log_output.string
439
+ with_logging { AX.send(:log_error, REF, KAXErrorNotImplemented) }
440
+ assert_match /Not Implemented/, @log_output.string
441
+ end
442
+
443
+ def test_handles_unknown_error_codes
444
+ with_logging { AX.send(:log_error, REF, 1234567) }
445
+ assert_match /UNKNOWN ERROR CODE/, @log_output.string
446
+ end
447
+
448
+ end