AXElements 0.7.6 → 0.7.7
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/lib/accessibility/core.rb +2 -2
- data/lib/accessibility/dsl.rb +9 -10
- data/lib/accessibility/enumerators.rb +6 -12
- data/lib/accessibility/factory.rb +14 -9
- data/lib/accessibility/graph.rb +2 -2
- data/lib/accessibility/qualifier.rb +10 -13
- data/lib/accessibility/translator.rb +29 -33
- data/lib/accessibility/version.rb +1 -1
- data/lib/ax/application.rb +13 -5
- data/lib/ax/element.rb +24 -32
- data/lib/ax/systemwide.rb +2 -2
- data/rakelib/ext.rake +1 -1
- data/test/integration/accessibility/test_dsl.rb +11 -25
- data/test/integration/accessibility/test_enumerators.rb +1 -2
- data/test/integration/accessibility/test_qualifier.rb +2 -1
- data/test/integration/ax/test_element.rb +4 -0
- data/test/sanity/accessibility/test_core.rb +1 -1
- data/test/sanity/accessibility/test_translator.rb +61 -85
- data/test/sanity/ax/test_application.rb +28 -27
- data/test/sanity/ax/test_systemwide.rb +9 -18
- metadata +5 -8
- data/docs/images/AX.png +0 -0
data/lib/accessibility/core.rb
CHANGED
@@ -378,8 +378,8 @@ module Accessibility::Core
|
|
378
378
|
# events).
|
379
379
|
#
|
380
380
|
# This is just a magic number from trial and error. Both the repeat
|
381
|
-
# interval (NXKeyRepeatInterval) and threshold (NXKeyRepeatThreshold)
|
382
|
-
# but
|
381
|
+
# interval (NXKeyRepeatInterval) and threshold (NXKeyRepeatThreshold)
|
382
|
+
# were tried, but were way too big.
|
383
383
|
#
|
384
384
|
# @return [Number]
|
385
385
|
KEY_RATE = case ENV['KEY_RATE']
|
data/lib/accessibility/dsl.rb
CHANGED
@@ -210,7 +210,7 @@ module Accessibility::DSL
|
|
210
210
|
#
|
211
211
|
# @param [AX::Element]
|
212
212
|
def set_focus_to element
|
213
|
-
element.set(:focused, true) if element.
|
213
|
+
element.set(:focused, true) if element.writable? :focused
|
214
214
|
end
|
215
215
|
alias_method :set_focus, :set_focus_to
|
216
216
|
|
@@ -242,15 +242,13 @@ module Accessibility::DSL
|
|
242
242
|
#
|
243
243
|
# @return [nil] do not rely on a return value
|
244
244
|
def set element, change
|
245
|
-
|
246
|
-
if element.writable? :focused
|
247
|
-
element.set :focused, true
|
248
|
-
end
|
249
|
-
end
|
245
|
+
set_focus_to element
|
250
246
|
|
251
|
-
|
252
|
-
|
253
|
-
|
247
|
+
if change.kind_of? Hash
|
248
|
+
element.set *change.first
|
249
|
+
else
|
250
|
+
element.set :value, change
|
251
|
+
end
|
254
252
|
end
|
255
253
|
|
256
254
|
##
|
@@ -267,9 +265,10 @@ module Accessibility::DSL
|
|
267
265
|
# Send input to a specific application
|
268
266
|
# @param [#to_s]
|
269
267
|
# @param [AX::Application]
|
268
|
+
#
|
270
269
|
def type string, app = system_wide
|
271
270
|
sleep 0.1
|
272
|
-
app.
|
271
|
+
app.type string.to_s
|
273
272
|
end
|
274
273
|
|
275
274
|
##
|
@@ -18,13 +18,11 @@ module Accessibility::Enumerators
|
|
18
18
|
#
|
19
19
|
# @yieldparam [AX::Element]
|
20
20
|
def each
|
21
|
-
# @todo
|
22
|
-
# for fat trees; what is impact on thin trees?
|
23
|
-
# @todo See if we can implement the method in a single loop
|
21
|
+
# @todo Mutate the queue less
|
24
22
|
queue = [@root]
|
25
23
|
until queue.empty?
|
26
24
|
queue.shift.attribute(:children).each do |x|
|
27
|
-
queue << x
|
25
|
+
queue << x
|
28
26
|
yield x
|
29
27
|
end
|
30
28
|
end
|
@@ -61,10 +59,8 @@ module Accessibility::Enumerators
|
|
61
59
|
until stack.empty?
|
62
60
|
current = stack.shift
|
63
61
|
yield current
|
64
|
-
|
65
|
-
|
66
|
-
stack.unshift *current.attribute(:children)
|
67
|
-
end
|
62
|
+
# needed to reverse it since child ordering seems to matter in practice
|
63
|
+
stack.unshift *current.attribute(:children)
|
68
64
|
end
|
69
65
|
end
|
70
66
|
|
@@ -92,10 +88,8 @@ module Accessibility::Enumerators
|
|
92
88
|
# @param [#call]
|
93
89
|
def recursive_each_with_level element, depth, block
|
94
90
|
block.call element, depth
|
95
|
-
|
96
|
-
|
97
|
-
recursive_each_with_level x, depth + 1, block
|
98
|
-
end
|
91
|
+
element.attribute(:children).each do |x|
|
92
|
+
recursive_each_with_level x, depth + 1, block
|
99
93
|
end
|
100
94
|
end
|
101
95
|
|
@@ -66,11 +66,16 @@ module Accessibility::Factory
|
|
66
66
|
# @param [AXUIElementRef]
|
67
67
|
# @return [AX::Element]
|
68
68
|
def process_element ref
|
69
|
-
role
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
69
|
+
role = TRANSLATOR.unprefix ref.role
|
70
|
+
attrs = ref.attributes
|
71
|
+
klass = if attrs.include? KAXSubroleAttribute
|
72
|
+
subrole = ref.subrole
|
73
|
+
# Some objects claim to have a subrole but return nil
|
74
|
+
if subrole
|
75
|
+
class_for TRANSLATOR.unprefix(subrole), and: role
|
76
|
+
else
|
77
|
+
class_for role
|
78
|
+
end
|
74
79
|
else
|
75
80
|
class_for role
|
76
81
|
end
|
@@ -82,9 +87,9 @@ module Accessibility::Factory
|
|
82
87
|
#
|
83
88
|
# @return [Array]
|
84
89
|
def process_array vals
|
85
|
-
|
86
|
-
|
87
|
-
vals.map { |val| process_element val }
|
90
|
+
return vals if vals.empty?
|
91
|
+
return vals if CFGetTypeID(vals.first) != REF_TYPE
|
92
|
+
return vals.map { |val| process_element val }
|
88
93
|
end
|
89
94
|
|
90
95
|
##
|
@@ -132,7 +137,7 @@ module Accessibility::Factory
|
|
132
137
|
|
133
138
|
##
|
134
139
|
# Create a new class in the {AX} namesapce that has the given
|
135
|
-
# `superklass` as the superclass
|
140
|
+
# `superklass` as the superclass..
|
136
141
|
#
|
137
142
|
# @param [#to_s] name
|
138
143
|
# @param [#to_s] superklass
|
data/lib/accessibility/graph.rb
CHANGED
@@ -56,11 +56,11 @@ class Accessibility::Graph
|
|
56
56
|
end
|
57
57
|
|
58
58
|
def enabled
|
59
|
-
FILL if @element.
|
59
|
+
FILL if @element.enabled?
|
60
60
|
end
|
61
61
|
|
62
62
|
def focus
|
63
|
-
BOLD if @element.
|
63
|
+
BOLD if @element.focused?
|
64
64
|
end
|
65
65
|
|
66
66
|
OVAL = '[shape = oval]'
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
1
2
|
require 'accessibility/translator'
|
2
3
|
|
3
4
|
##
|
@@ -37,9 +38,7 @@ class Accessibility::Qualifier
|
|
37
38
|
#
|
38
39
|
# @param [AX::Element]
|
39
40
|
def qualifies? element
|
40
|
-
|
41
|
-
return false unless meets_criteria? element
|
42
|
-
return true
|
41
|
+
the_right_type?(element) && meets_criteria?(element)
|
43
42
|
end
|
44
43
|
|
45
44
|
# @return [String]
|
@@ -65,18 +64,18 @@ class Accessibility::Qualifier
|
|
65
64
|
def compile criteria
|
66
65
|
@filters = criteria.map do |key, value|
|
67
66
|
if value.kind_of? Hash
|
68
|
-
[:
|
67
|
+
[:subsearch, key, value]
|
69
68
|
elsif key.kind_of? Array
|
70
69
|
filter = value.kind_of?(Regexp) ?
|
71
70
|
:parameterized_match : :parameterized_equality
|
72
|
-
[
|
71
|
+
[filter, *key, value]
|
73
72
|
else
|
74
73
|
filter = value.kind_of?(Regexp) ?
|
75
74
|
:match : :equality
|
76
|
-
[
|
75
|
+
[filter, key, value]
|
77
76
|
end
|
78
77
|
end
|
79
|
-
@filters << [:
|
78
|
+
@filters << [:block_check] if @block
|
80
79
|
end
|
81
80
|
|
82
81
|
##
|
@@ -92,7 +91,7 @@ class Accessibility::Qualifier
|
|
92
91
|
return false
|
93
92
|
end
|
94
93
|
end
|
95
|
-
|
94
|
+
element.kind_of? @const
|
96
95
|
end
|
97
96
|
|
98
97
|
##
|
@@ -102,9 +101,7 @@ class Accessibility::Qualifier
|
|
102
101
|
# @param [AX::Element]
|
103
102
|
def meets_criteria? element
|
104
103
|
@filters.all? do |filter|
|
105
|
-
|
106
|
-
self.send *filter.last, element
|
107
|
-
end
|
104
|
+
self.send *filter, element
|
108
105
|
end
|
109
106
|
end
|
110
107
|
|
@@ -113,7 +110,7 @@ class Accessibility::Qualifier
|
|
113
110
|
end
|
114
111
|
|
115
112
|
def match attr, regexp, element
|
116
|
-
element.attribute(attr).match regexp
|
113
|
+
element.attribute(attr).to_s.match regexp
|
117
114
|
end
|
118
115
|
|
119
116
|
def equality attr, value, element
|
@@ -121,7 +118,7 @@ class Accessibility::Qualifier
|
|
121
118
|
end
|
122
119
|
|
123
120
|
def parameterized_match attr, param, regexp, element
|
124
|
-
element.attribute(attr, for_parameter: param).match regexp
|
121
|
+
element.attribute(attr, for_parameter: param).to_s.match regexp
|
125
122
|
end
|
126
123
|
|
127
124
|
def parameterized_equality attr, param, value, element
|
@@ -27,8 +27,8 @@ class Accessibility::Translator
|
|
27
27
|
# Initialize the caches.
|
28
28
|
def initialize
|
29
29
|
init_unprefixes
|
30
|
-
init_normalizations
|
31
30
|
init_rubyisms
|
31
|
+
init_cocoaifications
|
32
32
|
init_classifications
|
33
33
|
init_singularizations
|
34
34
|
end
|
@@ -54,32 +54,18 @@ class Accessibility::Translator
|
|
54
54
|
@unprefixes[key]
|
55
55
|
end
|
56
56
|
|
57
|
-
##
|
58
|
-
# Given a symbol, return the equivalent accessibility constant.
|
59
|
-
#
|
60
|
-
# @param [#to_sym]
|
61
|
-
# @param [Array<String>]
|
62
|
-
# @return [String]
|
63
|
-
def lookup key, values
|
64
|
-
@values = values
|
65
|
-
@rubyisms[key.to_sym]
|
66
|
-
end
|
67
|
-
|
68
57
|
# @return [Array<Symbol>]
|
69
58
|
def rubyize keys
|
70
|
-
keys.map { |x| @
|
59
|
+
keys.map { |x| @rubyisms[x] }
|
71
60
|
end
|
72
61
|
|
73
62
|
##
|
74
|
-
#
|
75
|
-
# then get the value of the constant.
|
63
|
+
# Given a symbol, return the equivalent accessibility constant.
|
76
64
|
#
|
77
|
-
# @param [#
|
65
|
+
# @param [#to_sym]
|
78
66
|
# @return [String]
|
79
|
-
def
|
80
|
-
|
81
|
-
const = "KAX#{name}Notification"
|
82
|
-
Object.const_defined?(const) ? Object.const_get(const) : name
|
67
|
+
def cocoaify key
|
68
|
+
@cocoaifications[key.to_sym]
|
83
69
|
end
|
84
70
|
|
85
71
|
##
|
@@ -114,36 +100,46 @@ class Accessibility::Translator
|
|
114
100
|
@singularizations[klass]
|
115
101
|
end
|
116
102
|
|
103
|
+
##
|
104
|
+
# Try to turn an arbitrary symbol into a notification constant, and
|
105
|
+
# then get the value of the constant.
|
106
|
+
#
|
107
|
+
# @param [#to_s]
|
108
|
+
# @return [String]
|
109
|
+
def guess_notification name
|
110
|
+
name = name.to_s.gsub /(?:^|_)(.)/ do $1.upcase! || $1 end
|
111
|
+
const = "KAX#{name}Notification"
|
112
|
+
Object.const_defined?(const) ? Object.const_get(const) : name
|
113
|
+
end
|
114
|
+
|
117
115
|
|
118
116
|
private
|
119
117
|
|
120
118
|
# @return [Hash{String=>String}]
|
121
119
|
def init_unprefixes
|
122
120
|
@unprefixes = Hash.new do |hash, key|
|
123
|
-
hash[key] = key.sub /^[A-Z]*?AX
|
121
|
+
hash[key] = key.sub /^[A-Z]*?AX|\s+/, EMPTY_STRING
|
124
122
|
end
|
125
123
|
end
|
126
124
|
|
127
125
|
# @return [Hash{String=>Symbol}]
|
128
|
-
def
|
129
|
-
@
|
126
|
+
def init_rubyisms
|
127
|
+
@rubyisms = Hash.new do |hash, key|
|
130
128
|
hash[key] = Accessibility::Inflector.underscore(@unprefixes[key]).to_sym
|
131
129
|
end
|
132
130
|
end
|
133
131
|
|
134
132
|
# @return [Hash{Symbol=>String}]
|
135
|
-
def
|
136
|
-
@
|
137
|
-
|
138
|
-
hash.fetch(key) do |k|
|
139
|
-
chomped_key = k.chomp(QUESTION_MARK).to_sym
|
140
|
-
chomped_val = hash.fetch(chomped_key, nil)
|
141
|
-
hash[key] = chomped_val if chomped_val
|
142
|
-
end
|
133
|
+
def init_cocoaifications
|
134
|
+
@cocoaifications = Hash.new do |hash, key|
|
135
|
+
hash[key] = "AX#{Accessibility::Inflector.camelize(key.chomp QUESTION_MARK)}"
|
143
136
|
end
|
144
137
|
# preload the table
|
145
|
-
@
|
146
|
-
@
|
138
|
+
@cocoaifications[:id] = KAXIdentifierAttribute
|
139
|
+
@cocoaifications[:placeholder] = KAXPlaceholderValueAttribute
|
140
|
+
# workaround the one known case where AX uses "Is" for a boolean attribute
|
141
|
+
@cocoaifications[:application_running] = # let the value all fall through
|
142
|
+
@cocoaifications[:application_running?] = KAXIsApplicationRunningAttribute
|
147
143
|
end
|
148
144
|
|
149
145
|
# @return [Hash{String=>String}]
|
data/lib/ax/application.rb
CHANGED
@@ -47,15 +47,23 @@ class AX::Application < AX::Element
|
|
47
47
|
# @group Attributes
|
48
48
|
|
49
49
|
##
|
50
|
-
# Overridden to handle the {Accessibility::
|
50
|
+
# Overridden to handle the {Accessibility::DSL#set_focus} case.
|
51
51
|
#
|
52
52
|
# (see AX::Element#attribute)
|
53
53
|
def attribute attr
|
54
54
|
case attr
|
55
55
|
when :focused?, :focused then active?
|
56
56
|
when :hidden?, :hidden then hidden?
|
57
|
-
else
|
58
|
-
|
57
|
+
else super
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Overridden to handle the {Accessibility::DSL#set_focus_to} case.
|
63
|
+
def writable? attr
|
64
|
+
case attr
|
65
|
+
when :focused?, :focused, :hidden?, :hidden then true
|
66
|
+
else super
|
59
67
|
end
|
60
68
|
end
|
61
69
|
|
@@ -136,7 +144,7 @@ class AX::Application < AX::Element
|
|
136
144
|
# {file:docs/KeyboardEvents.markdown Keyboard} documentation.
|
137
145
|
#
|
138
146
|
# @return [Boolean]
|
139
|
-
def
|
147
|
+
def type string
|
140
148
|
@ref.post keyboard_events_for string
|
141
149
|
true
|
142
150
|
end
|
@@ -199,7 +207,7 @@ class AX::Application < AX::Element
|
|
199
207
|
# @return [AX::Window]
|
200
208
|
def show_preferences_window
|
201
209
|
windows = self.children.select { |x| x.kind_of? AX::Window }
|
202
|
-
|
210
|
+
type "\\COMMAND+,"
|
203
211
|
wait_for(:window, parent: self) { |window| !windows.include?(window) }
|
204
212
|
end
|
205
213
|
|
data/lib/ax/element.rb
CHANGED
@@ -49,10 +49,8 @@ class AX::Element
|
|
49
49
|
# element.attribute :position # => #<CGPoint x=123.0 y=456.0>
|
50
50
|
#
|
51
51
|
# @param [#to_sym]
|
52
|
-
def attribute
|
53
|
-
|
54
|
-
process @ref.attribute(rattr)
|
55
|
-
end
|
52
|
+
def attribute attr
|
53
|
+
process @ref.attribute TRANSLATOR.cocoaify attr
|
56
54
|
end
|
57
55
|
|
58
56
|
##
|
@@ -77,11 +75,7 @@ class AX::Element
|
|
77
75
|
# @param [#to_sym]
|
78
76
|
# @return [Number]
|
79
77
|
def size_of attr
|
80
|
-
|
81
|
-
@ref.size_of rattr
|
82
|
-
else
|
83
|
-
0
|
84
|
-
end
|
78
|
+
@ref.size_of TRANSLATOR.cocoaify attr
|
85
79
|
end
|
86
80
|
|
87
81
|
##
|
@@ -107,11 +101,7 @@ class AX::Element
|
|
107
101
|
#
|
108
102
|
# @param [#to_sym]
|
109
103
|
def writable? attr
|
110
|
-
|
111
|
-
@ref.writable? rattr
|
112
|
-
else
|
113
|
-
false
|
114
|
-
end
|
104
|
+
@ref.writable? TRANSLATOR.cocoaify attr
|
115
105
|
end
|
116
106
|
|
117
107
|
##
|
@@ -129,7 +119,7 @@ class AX::Element
|
|
129
119
|
raise NoMethodError, "#{attr} is read-only for #{inspect}"
|
130
120
|
end
|
131
121
|
value = value.relative_to(@ref.value.size) if value.kind_of? Range
|
132
|
-
@ref.set TRANSLATOR.
|
122
|
+
@ref.set TRANSLATOR.cocoaify(attr), value
|
133
123
|
end
|
134
124
|
|
135
125
|
|
@@ -158,7 +148,7 @@ class AX::Element
|
|
158
148
|
#
|
159
149
|
# @param [#to_sym]
|
160
150
|
def attribute attr, for_parameter: param
|
161
|
-
if rattr = TRANSLATOR.
|
151
|
+
if rattr = TRANSLATOR.cocoaify(attr)
|
162
152
|
param = param.relative_to(@ref.value.size) if value.kind_of? Range
|
163
153
|
process @ref.attribute(rattr, for_parameter: param)
|
164
154
|
end
|
@@ -197,7 +187,7 @@ class AX::Element
|
|
197
187
|
# @param [#to_sym]
|
198
188
|
# @return [Boolean] true if successful
|
199
189
|
def perform action
|
200
|
-
if raction = TRANSLATOR.
|
190
|
+
if raction = TRANSLATOR.cocoaify(action)
|
201
191
|
@ref.perform raction
|
202
192
|
else
|
203
193
|
false
|
@@ -298,17 +288,17 @@ class AX::Element
|
|
298
288
|
# window.application # => SearchFailure is raised
|
299
289
|
#
|
300
290
|
def method_missing method, *args, &block
|
301
|
-
if method[-1] == EQUALS
|
302
|
-
return set(method.chomp(EQUALS), args.first)
|
291
|
+
return set(method.chomp(EQUALS), args.first) if method[-1] == EQUALS
|
303
292
|
|
304
|
-
|
293
|
+
key = TRANSLATOR.cocoaify method
|
294
|
+
if @ref.attributes.include? key
|
305
295
|
return attribute method
|
306
296
|
|
307
|
-
elsif parameterized_attributes.include?
|
308
|
-
return attribute
|
297
|
+
elsif @ref.parameterized_attributes.include? key
|
298
|
+
return attribute(method, for_parameter: args.first)
|
309
299
|
|
310
|
-
elsif attributes.include?
|
311
|
-
if (result = search
|
300
|
+
elsif @ref.attributes.include? KAXChildrenAttribute
|
301
|
+
if (result = search(method, *args, &block)).blank?
|
312
302
|
raise Accessibility::SearchFailure.new(self, method, args.first)
|
313
303
|
else
|
314
304
|
return result
|
@@ -359,7 +349,7 @@ class AX::Element
|
|
359
349
|
# @yieldreturn [Boolean]
|
360
350
|
# @return [Array(Observer, String, CFRunLoopSource)]
|
361
351
|
def on_notification name, &block
|
362
|
-
notif = TRANSLATOR.
|
352
|
+
notif = TRANSLATOR.guess_notification name
|
363
353
|
observer = @ref.observer ¬if_callback_for(&block)
|
364
354
|
source = @ref.run_loop_source_for observer
|
365
355
|
@ref.register observer, to_receive: notif
|
@@ -396,11 +386,10 @@ class AX::Element
|
|
396
386
|
# @return [String]
|
397
387
|
def inspect
|
398
388
|
msg = "#<#{self.class}" << pp_identifier
|
399
|
-
|
400
|
-
msg <<
|
401
|
-
msg <<
|
402
|
-
msg << pp_checkbox(:
|
403
|
-
msg << pp_checkbox(:focused) if attrs.include? KAXFocusedAttribute
|
389
|
+
msg << pp_position if attributes.include? :position
|
390
|
+
msg << pp_children if attributes.include? :children
|
391
|
+
msg << pp_checkbox(:enabled) if attributes.include? :enabled
|
392
|
+
msg << pp_checkbox(:focused) if attributes.include? :focused
|
404
393
|
msg << '>'
|
405
394
|
end
|
406
395
|
|
@@ -418,9 +407,12 @@ class AX::Element
|
|
418
407
|
##
|
419
408
|
# Overriden to respond properly with regards to dynamic attribute
|
420
409
|
# lookups, but will return false for potential implicit searches.
|
410
|
+
#
|
411
|
+
# This does not work for predicate methods at the moment.
|
421
412
|
def respond_to? name
|
422
|
-
|
423
|
-
|
413
|
+
key = TRANSLATOR.cocoaify name.chomp(EQUALS)
|
414
|
+
@ref.attributes.include?(key) ||
|
415
|
+
@ref.parameterized_attributes.include?(key) ||
|
424
416
|
super
|
425
417
|
end
|
426
418
|
|
data/lib/ax/systemwide.rb
CHANGED
@@ -18,7 +18,7 @@ class AX::SystemWide < AX::Element
|
|
18
18
|
end
|
19
19
|
|
20
20
|
##
|
21
|
-
# @note With the `SystemWide` class, using {#
|
21
|
+
# @note With the `SystemWide` class, using {#type} will send the
|
22
22
|
# events to which ever app has focus.
|
23
23
|
#
|
24
24
|
# Generate keyboard events by simulating keyboard input.
|
@@ -27,7 +27,7 @@ class AX::SystemWide < AX::Element
|
|
27
27
|
# more information on how to format strings.
|
28
28
|
#
|
29
29
|
# @return [Boolean]
|
30
|
-
def
|
30
|
+
def type string
|
31
31
|
@ref.post keyboard_events_for string
|
32
32
|
true
|
33
33
|
end
|
data/rakelib/ext.rake
CHANGED
@@ -12,29 +12,15 @@ class TestAccessibilityDSL < MiniTest::Unit::TestCase
|
|
12
12
|
@dsl ||= DSL.new
|
13
13
|
end
|
14
14
|
|
15
|
-
def app
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
|
-
def text_area
|
20
|
-
@@text_area ||= app.main_window.text_area
|
21
|
-
end
|
22
|
-
|
23
|
-
def pref_window
|
24
|
-
app.children.find { |x|
|
25
|
-
x.attributes.include?(:title) && x.title == 'Preferences'
|
26
|
-
}
|
27
|
-
end
|
28
|
-
|
29
|
-
def spelling_window
|
30
|
-
app.children.find { |x|
|
31
|
-
x.attributes.include?(:title) && x.title.match(/^Spelling/)
|
32
|
-
}
|
33
|
-
end
|
15
|
+
def app; @@app ||= AX::Application.new REF end
|
16
|
+
def text_area; @@text_area ||= app.main_window.text_area end
|
17
|
+
def pref_window; app.children.find { |x| x.attribute(:title) == 'Preferences' } end
|
18
|
+
def spelling_window; app.children.find { |x| x.attribute(:title).to_s.match(/^Spelling/) } end
|
34
19
|
|
35
20
|
def try_typing string, expected = nil
|
36
21
|
expected = string unless expected
|
37
22
|
text_area.set :focused, true
|
23
|
+
assert text_area.focused?
|
38
24
|
dsl.type string
|
39
25
|
assert_equal expected, text_area.value
|
40
26
|
ensure # reset for next test
|
@@ -219,16 +205,16 @@ class TestAccessibilityDSL < MiniTest::Unit::TestCase
|
|
219
205
|
pop_up = app.main_window.pop_up_button
|
220
206
|
|
221
207
|
pop_up.perform :press
|
222
|
-
|
223
|
-
dsl.scroll_menu_to
|
224
|
-
assert_equal
|
208
|
+
item = wait_for :menu_item, ancestor: pop_up, title: '49'
|
209
|
+
dsl.scroll_menu_to item
|
210
|
+
assert_equal item, element_under_mouse
|
225
211
|
dsl.click
|
226
212
|
assert_equal '49', pop_up.value
|
227
213
|
|
228
214
|
pop_up.perform :press
|
229
|
-
|
230
|
-
dsl.scroll_menu_to
|
231
|
-
assert_equal
|
215
|
+
item = wait_for :menu_item, ancestor: pop_up, title: 'Togusa'
|
216
|
+
dsl.scroll_menu_to item
|
217
|
+
assert_equal item, element_under_mouse
|
232
218
|
dsl.click
|
233
219
|
assert_equal 'Togusa', pop_up.value
|
234
220
|
|
@@ -26,7 +26,7 @@ class TestAccessibilityEnumeratorsBreadthFirst < MiniTest::Unit::TestCase
|
|
26
26
|
def enum.first
|
27
27
|
each { |x| return x }
|
28
28
|
end
|
29
|
-
|
29
|
+
refute_instance_of AX::Application, enum.first
|
30
30
|
end
|
31
31
|
|
32
32
|
# this was a big performance issue, so we should make sure it
|
@@ -119,4 +119,3 @@ class TestAccessibilityEnumeratorsDepthFirst < MiniTest::Unit::TestCase
|
|
119
119
|
end
|
120
120
|
|
121
121
|
end
|
122
|
-
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
1
2
|
require 'test/integration/helper'
|
2
3
|
|
3
4
|
class TestAccessibilityQualifier < MiniTest::Unit::TestCase
|
@@ -99,7 +100,7 @@ class TestAccessibilityQualifier < MiniTest::Unit::TestCase
|
|
99
100
|
|
100
101
|
def test_qualifiers_can_use_aliased_attributes
|
101
102
|
id = "I'm a little teapot"
|
102
|
-
q
|
103
|
+
q = qualifier(:Button, id: id)
|
103
104
|
app.main_window.children.each do |kid|
|
104
105
|
if kid.attributes.include?(:identifier) && kid.id == id
|
105
106
|
assert q.qualifies? kid
|
@@ -220,7 +220,7 @@ class TestAccessibilityCore < MiniTest::Unit::TestCase
|
|
220
220
|
##
|
221
221
|
# The keyboard simulation stuff is a bit weird...
|
222
222
|
|
223
|
-
def
|
223
|
+
def test_post
|
224
224
|
events = [[0x56,true], [0x56,false], [0x54,true], [0x54,false]]
|
225
225
|
string = '42'
|
226
226
|
|
@@ -12,49 +12,35 @@ class TestAccessibilityTranslator < MiniTest::Unit::TestCase
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def test_unprefixing
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
prefix_test 'MCAXButton', 'Button'
|
21
|
-
prefix_test 'AXURL', 'URL'
|
22
|
-
prefix_test 'AXTitleUIElement', 'TitleUIElement'
|
23
|
-
prefix_test 'AXIsApplicationRunning', 'ApplicationRunning'
|
24
|
-
prefix_test 'AXAX', 'AX'
|
25
|
-
prefix_test 'Quick Look', 'QuickLook'
|
15
|
+
assert_equal 'Button', TRANSLATOR.unprefix('AXButton')
|
16
|
+
assert_equal 'URL', TRANSLATOR.unprefix('AXURL')
|
17
|
+
assert_equal 'TitleUIElement', TRANSLATOR.unprefix('AXTitleUIElement')
|
18
|
+
assert_equal 'AX', TRANSLATOR.unprefix('AXAX')
|
19
|
+
assert_equal 'QuickLook', TRANSLATOR.unprefix('Quick Look')
|
26
20
|
end
|
27
21
|
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
44
|
-
|
45
|
-
lookup_test :id, [], KAXIdentifierAttribute
|
46
|
-
lookup_test :placeholder, [], KAXPlaceholderValueAttribute
|
22
|
+
def test_cocoaification
|
23
|
+
assert_equal KAXChildrenAttribute, TRANSLATOR.cocoaify(:children)
|
24
|
+
assert_equal KAXTitleUIElementAttribute, TRANSLATOR.cocoaify(:title_ui_element)
|
25
|
+
assert_equal KAXFocusedAttribute, TRANSLATOR.cocoaify(:focused?)
|
26
|
+
assert_equal 'AXTotallyFake', TRANSLATOR.cocoaify(:totally_fake)
|
27
|
+
|
28
|
+
# also check the aliases we have added
|
29
|
+
assert_equal KAXIdentifierAttribute, TRANSLATOR.cocoaify(:id)
|
30
|
+
assert_equal KAXPlaceholderValueAttribute, TRANSLATOR.cocoaify(:placeholder)
|
31
|
+
|
32
|
+
# cover this edge case :/
|
33
|
+
assert_equal KAXIsApplicationRunningAttribute, TRANSLATOR.cocoaify(:is_application_running?)
|
34
|
+
assert_equal KAXIsApplicationRunningAttribute, TRANSLATOR.cocoaify(:is_application_running)
|
35
|
+
assert_equal KAXIsApplicationRunningAttribute, TRANSLATOR.cocoaify(:application_running?)
|
36
|
+
assert_equal KAXIsApplicationRunningAttribute, TRANSLATOR.cocoaify(:application_running)
|
47
37
|
end
|
48
38
|
|
49
39
|
def test_rubyize
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
rubyize_test KAXMainWindowAttribute, :main_window
|
55
|
-
rubyize_test KAXRoleAttribute, :role
|
56
|
-
rubyize_test KAXStringForRangeParameterizedAttribute, :string_for_range
|
57
|
-
rubyize_test [KAXSubroleAttribute, KAXChildrenAttribute], [:subrole, :children]
|
40
|
+
assert_equal [:role], TRANSLATOR.rubyize([KAXRoleAttribute])
|
41
|
+
assert_equal [:main_window], TRANSLATOR.rubyize([KAXMainWindowAttribute])
|
42
|
+
assert_equal [:string_for_range], TRANSLATOR.rubyize([KAXStringForRangeParameterizedAttribute])
|
43
|
+
assert_equal [:subrole, :children], TRANSLATOR.rubyize([KAXSubroleAttribute, KAXChildrenAttribute])
|
58
44
|
end
|
59
45
|
|
60
46
|
def test_rubyize_doesnt_eat_original_data
|
@@ -63,73 +49,63 @@ class TestAccessibilityTranslator < MiniTest::Unit::TestCase
|
|
63
49
|
assert_equal [KAXTitleAttribute, KAXMainWindowAttribute], array
|
64
50
|
end
|
65
51
|
|
66
|
-
def test_guess_notification_for
|
67
|
-
def notif_test actual, expected
|
68
|
-
assert_equal expected, TRANSLATOR.guess_notification_for(actual)
|
69
|
-
end
|
70
|
-
|
71
|
-
notif_test 'window_created', KAXWindowCreatedNotification
|
72
|
-
notif_test :window_created, KAXWindowCreatedNotification
|
73
|
-
notif_test KAXValueChangedNotification, KAXValueChangedNotification
|
74
|
-
notif_test 'Cheezburger', 'Cheezburger'
|
75
|
-
end
|
76
|
-
|
77
52
|
def test_classify
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
classify_test :button, 'Button'
|
84
|
-
classify_test :menu_item, 'MenuItem'
|
85
|
-
classify_test 'floating_window', 'FloatingWindow'
|
86
|
-
classify_test 'outline_rows', 'OutlineRow'
|
53
|
+
assert_equal 'Button', TRANSLATOR.classify(:buttons)
|
54
|
+
assert_equal 'Button', TRANSLATOR.classify(:button)
|
55
|
+
assert_equal 'MenuItem', TRANSLATOR.classify(:menu_item)
|
56
|
+
assert_equal 'FloatingWindow', TRANSLATOR.classify('floating_window')
|
57
|
+
assert_equal 'OutlineRow', TRANSLATOR.classify('outline_rows')
|
87
58
|
end
|
88
59
|
|
89
60
|
def test_singularize
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
singularize_test :button, 'button'
|
96
|
-
singularize_test :windows, 'window'
|
97
|
-
singularize_test :check_boxes, 'check_box'
|
98
|
-
singularize_test 'classes', 'class'
|
61
|
+
assert_equal 'button', TRANSLATOR.singularize(:buttons)
|
62
|
+
assert_equal 'button', TRANSLATOR.singularize('button')
|
63
|
+
assert_equal 'window', TRANSLATOR.singularize(:windows)
|
64
|
+
assert_equal 'check_box', TRANSLATOR.singularize(:check_boxes)
|
65
|
+
assert_equal 'class', TRANSLATOR.singularize('classes')
|
99
66
|
end
|
100
67
|
|
68
|
+
def test_guess_notification
|
69
|
+
assert_equal KAXWindowCreatedNotification, TRANSLATOR.guess_notification('window_created')
|
70
|
+
assert_equal KAXWindowCreatedNotification, TRANSLATOR.guess_notification(:window_created)
|
71
|
+
assert_equal KAXValueChangedNotification, TRANSLATOR.guess_notification(KAXValueChangedNotification)
|
72
|
+
assert_equal 'Cheezburger', TRANSLATOR.guess_notification('Cheezburger')
|
73
|
+
end
|
101
74
|
|
102
75
|
def test_unprefixes_are_cached
|
103
76
|
unprefixes = TRANSLATOR.instance_variable_get :@unprefixes
|
77
|
+
|
104
78
|
unprefixed = unprefixes['AXPieIsTheTruth']
|
105
79
|
assert_includes unprefixes.keys, 'AXPieIsTheTruth'
|
106
80
|
assert_equal unprefixed, unprefixes['AXPieIsTheTruth']
|
107
81
|
end
|
108
82
|
|
109
|
-
def test_normalizations_are_cached
|
110
|
-
normalizations = TRANSLATOR.instance_variable_get :@normalizations
|
111
|
-
normalized = normalizations['AXTheAnswer']
|
112
|
-
assert_includes normalizations.keys, 'AXTheAnswer'
|
113
|
-
assert_equal normalized, normalizations['AXTheAnswer']
|
114
|
-
end
|
115
|
-
|
116
83
|
def test_rubyisms_are_cached
|
117
84
|
rubyisms = TRANSLATOR.instance_variable_get :@rubyisms
|
118
|
-
|
119
|
-
rubyized = rubyisms[
|
120
|
-
assert_includes rubyisms.keys,
|
121
|
-
assert_equal rubyized,
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
85
|
+
|
86
|
+
rubyized = rubyisms['AXTheAnswer']
|
87
|
+
assert_includes rubyisms.keys, 'AXTheAnswer'
|
88
|
+
assert_equal rubyized, rubyisms['AXTheAnswer']
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_cocoaifications_are_cached
|
92
|
+
cocoas = TRANSLATOR.instance_variable_get :@cocoaifications
|
93
|
+
|
94
|
+
cocoaed = cocoas[:chocolate_pancake]
|
95
|
+
assert_includes cocoas.keys, :chocolate_pancake
|
96
|
+
assert_equal cocoaed, cocoas[:chocolate_pancake]
|
97
|
+
|
98
|
+
cocoaed = cocoas[:chocolate_pancake?]
|
99
|
+
assert_includes cocoas.keys, :chocolate_pancake?
|
100
|
+
assert_equal cocoaed, cocoas[:chocolate_pancake?]
|
101
|
+
# make sure we didn't kill the other guy
|
102
|
+
assert_equal cocoaed, cocoas[:chocolate_pancake]
|
127
103
|
end
|
128
104
|
|
129
105
|
def test_classifications_are_cached
|
130
106
|
classifications = TRANSLATOR.instance_variable_get :@classifications
|
131
107
|
|
132
|
-
classified
|
108
|
+
classified = TRANSLATOR.classify 'made_up_class_name'
|
133
109
|
assert_includes classifications.keys, 'made_up_class_name'
|
134
110
|
assert_equal classified, classifications['made_up_class_name']
|
135
111
|
end
|
@@ -137,7 +113,7 @@ class TestAccessibilityTranslator < MiniTest::Unit::TestCase
|
|
137
113
|
def test_singularizations_are_cached
|
138
114
|
singulars = TRANSLATOR.instance_variable_get :@singularizations
|
139
115
|
|
140
|
-
singular
|
116
|
+
singular = TRANSLATOR.singularize 'buttons'
|
141
117
|
assert_includes singulars.keys, 'buttons'
|
142
118
|
assert_equal singular, singulars['buttons']
|
143
119
|
end
|
@@ -2,9 +2,6 @@
|
|
2
2
|
require 'test/helper'
|
3
3
|
require 'ax/application'
|
4
4
|
|
5
|
-
class AX::Element
|
6
|
-
attr_reader :ref
|
7
|
-
end
|
8
5
|
|
9
6
|
class TestAXApplication < MiniTest::Unit::TestCase
|
10
7
|
|
@@ -17,15 +14,13 @@ class TestAXApplication < MiniTest::Unit::TestCase
|
|
17
14
|
NSRunningApplication.runningApplicationWithProcessIdentifier app.pid
|
18
15
|
end
|
19
16
|
|
20
|
-
def
|
17
|
+
def test_subclass_of_element
|
21
18
|
assert_equal AX::Element, AX::Application.superclass
|
22
19
|
end
|
23
20
|
|
24
|
-
def
|
21
|
+
def test_inspect
|
22
|
+
assert_match app.inspect, /children/
|
25
23
|
assert_match /\spid=\d+/, app.inspect
|
26
|
-
end
|
27
|
-
|
28
|
-
def test_inspect_includes_focused
|
29
24
|
assert_match /\sfocused\[(?:✔|✘)\]/, app.inspect
|
30
25
|
end
|
31
26
|
|
@@ -35,10 +30,9 @@ class TestAXApplication < MiniTest::Unit::TestCase
|
|
35
30
|
mock.define_singleton_method(:terminate) { got_called = true }
|
36
31
|
mock.define_singleton_method(:terminated?) { false }
|
37
32
|
app.instance_variable_set :@app, mock
|
33
|
+
|
38
34
|
app.perform :terminate
|
39
35
|
assert got_called
|
40
|
-
ensure
|
41
|
-
app.instance_variable_set :@app, running_app
|
42
36
|
end
|
43
37
|
|
44
38
|
def test_force_terminate
|
@@ -50,37 +44,44 @@ class TestAXApplication < MiniTest::Unit::TestCase
|
|
50
44
|
|
51
45
|
app.perform :force_terminate
|
52
46
|
assert got_called
|
53
|
-
ensure
|
54
|
-
app.instance_variable_set :@app, running_app
|
55
47
|
end
|
56
48
|
|
57
|
-
def
|
58
|
-
|
49
|
+
def test_writable_handles_focused_and_hidden
|
50
|
+
assert app.writable? :focused?
|
51
|
+
assert app.writable? :focused
|
52
|
+
assert app.writable? :hidden
|
53
|
+
assert app.writable? :hidden?
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_attribute_calls_super
|
59
57
|
assert_equal 'AXElementsTester', app.title
|
58
|
+
end
|
60
59
|
|
60
|
+
def test_set_calls_super
|
61
61
|
called_super = false
|
62
|
-
app.define_singleton_method :
|
63
|
-
called_super = true if
|
62
|
+
app.define_singleton_method :set do |attr, value|
|
63
|
+
called_super = true if attr == :thingy && value == 'pie'
|
64
64
|
end
|
65
|
-
app.
|
65
|
+
app.set :thingy, 'pie'
|
66
66
|
assert called_super
|
67
|
+
end
|
67
68
|
|
69
|
+
def test_writable_calls_super
|
68
70
|
called_super = false
|
69
|
-
app.define_singleton_method :
|
70
|
-
called_super = true if attr == :
|
71
|
+
app.define_singleton_method :writable? do |attr|
|
72
|
+
called_super = true if attr == :brain
|
71
73
|
end
|
72
|
-
app.
|
74
|
+
app.writable? :brain
|
73
75
|
assert called_super
|
74
76
|
end
|
75
77
|
|
76
|
-
def
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
got_callback = true if events.kind_of?(Array)
|
78
|
+
def test_perform_calls_super
|
79
|
+
called_super = false
|
80
|
+
app.define_singleton_method :perform do |name|
|
81
|
+
called_super = true if name == :some_action
|
81
82
|
end
|
82
|
-
app.
|
83
|
-
assert
|
83
|
+
app.perform :some_action
|
84
|
+
assert called_super
|
84
85
|
end
|
85
86
|
|
86
87
|
def test_bundle_identifier
|
@@ -11,21 +11,17 @@ class TestAXSystemWide < MiniTest::Unit::TestCase
|
|
11
11
|
@system_wide ||= AX::SystemWide.new
|
12
12
|
end
|
13
13
|
|
14
|
+
# this is more so I know if Apple ever changes how CFEqual() works on AX stuff
|
14
15
|
def test_is_effectively_a_singleton
|
15
16
|
assert_equal AX::SystemWide.new, AX::SystemWide.new
|
16
17
|
end
|
17
18
|
|
18
|
-
def
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
25
|
-
system_wide.type_string 'test'
|
26
|
-
assert got_callback
|
27
|
-
ensure
|
28
|
-
system.define_singleton_method :post, meth if meth
|
19
|
+
def test_type_forwards_events
|
20
|
+
ref = system_wide.ref
|
21
|
+
called_back = false
|
22
|
+
ref.define_singleton_method(:post) { |x| called_back = true if x.kind_of? Array }
|
23
|
+
assert system_wide.type 'test'
|
24
|
+
assert called_back
|
29
25
|
end
|
30
26
|
|
31
27
|
def test_search_not_allowed
|
@@ -36,13 +32,8 @@ class TestAXSystemWide < MiniTest::Unit::TestCase
|
|
36
32
|
assert_raises(NoMethodError) { system_wide.on_notification }
|
37
33
|
end
|
38
34
|
|
39
|
-
def test_element_at
|
40
|
-
|
41
|
-
[[10,10],[500,500]].each do |point|
|
42
|
-
expected = system.element_at(point)
|
43
|
-
actual = system_wide.element_at(point).ref
|
44
|
-
assert_equal expected, actual
|
45
|
-
end
|
35
|
+
def test_element_at # returns a wrapped dude
|
36
|
+
assert_kind_of AX::Element, system_wide.element_at([10,10])
|
46
37
|
end
|
47
38
|
|
48
39
|
def test_parameterized_attributes_is_empty
|
metadata
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
name: AXElements
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.7.
|
5
|
+
version: 0.7.7
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Mark Rada
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-04-
|
12
|
+
date: 2012-04-07 00:00:00 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: minitest
|
@@ -59,12 +59,11 @@ dependencies:
|
|
59
59
|
- - ~>
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: "1.17"
|
62
|
-
description: 'AXElements is a DSL
|
63
|
-
Framework
|
62
|
+
description: 'AXElements is a UI automation DSL built on top of the Mac OS X Accessibility
|
64
63
|
|
65
|
-
that allows code to be written in a very natural and declarative
|
64
|
+
Framework that allows code to be written in a very natural and declarative
|
66
65
|
|
67
|
-
describes user interactions.
|
66
|
+
style that describes user interactions.
|
68
67
|
|
69
68
|
'
|
70
69
|
email: mrada@marketcircle.com
|
@@ -76,7 +75,6 @@ extra_rdoc_files:
|
|
76
75
|
- docs/Acting.markdown
|
77
76
|
- docs/Debugging.markdown
|
78
77
|
- docs/images/all_the_buttons.jpg
|
79
|
-
- docs/images/AX.png
|
80
78
|
- docs/images/next_version.png
|
81
79
|
- docs/images/ui_hierarchy.dot
|
82
80
|
- docs/images/ui_hierarchy.png
|
@@ -165,7 +163,6 @@ files:
|
|
165
163
|
- docs/Acting.markdown
|
166
164
|
- docs/Debugging.markdown
|
167
165
|
- docs/images/all_the_buttons.jpg
|
168
|
-
- docs/images/AX.png
|
169
166
|
- docs/images/next_version.png
|
170
167
|
- docs/images/ui_hierarchy.dot
|
171
168
|
- docs/images/ui_hierarchy.png
|
data/docs/images/AX.png
DELETED
Binary file
|