AXElements 0.9.0 → 1.0.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/.yardopts +0 -4
  2. data/README.markdown +22 -17
  3. data/Rakefile +1 -1
  4. data/ext/accessibility/key_coder/extconf.rb +1 -1
  5. data/ext/accessibility/key_coder/key_coder.c +2 -4
  6. data/lib/accessibility.rb +3 -3
  7. data/lib/accessibility/core.rb +948 -0
  8. data/lib/accessibility/dsl.rb +30 -186
  9. data/lib/accessibility/enumerators.rb +1 -0
  10. data/lib/accessibility/factory.rb +78 -134
  11. data/lib/accessibility/graph.rb +5 -9
  12. data/lib/accessibility/highlighter.rb +86 -0
  13. data/lib/accessibility/{pretty_printer.rb → pp_inspector.rb} +4 -3
  14. data/lib/accessibility/qualifier.rb +3 -5
  15. data/lib/accessibility/screen_recorder.rb +217 -0
  16. data/lib/accessibility/statistics.rb +57 -0
  17. data/lib/accessibility/translator.rb +23 -32
  18. data/lib/accessibility/version.rb +2 -22
  19. data/lib/ax/application.rb +20 -159
  20. data/lib/ax/element.rb +42 -32
  21. data/lib/ax/scroll_area.rb +5 -6
  22. data/lib/ax/systemwide.rb +1 -33
  23. data/lib/ax_elements.rb +1 -9
  24. data/lib/ax_elements/core_graphics_workaround.rb +5 -0
  25. data/lib/ax_elements/nsarray_compat.rb +17 -97
  26. data/lib/ax_elements/vendor/inflection_data.rb +66 -0
  27. data/lib/ax_elements/vendor/inflections.rb +176 -0
  28. data/lib/ax_elements/vendor/inflector.rb +306 -0
  29. data/lib/minitest/ax_elements.rb +180 -0
  30. data/lib/mouse.rb +227 -0
  31. data/lib/rspec/expectations/ax_elements.rb +234 -0
  32. data/rakelib/gem.rake +3 -12
  33. data/rakelib/test.rake +15 -0
  34. data/test/helper.rb +20 -10
  35. data/test/integration/accessibility/test_core.rb +18 -0
  36. data/test/integration/accessibility/test_dsl.rb +40 -38
  37. data/test/integration/accessibility/test_enumerators.rb +1 -0
  38. data/test/integration/accessibility/test_graph.rb +0 -1
  39. data/test/integration/accessibility/test_qualifier.rb +2 -2
  40. data/test/integration/ax/test_application.rb +2 -9
  41. data/test/integration/ax/test_element.rb +0 -40
  42. data/test/integration/minitest/test_ax_elements.rb +89 -0
  43. data/test/integration/rspec/expectations/test_ax_elements.rb +102 -0
  44. data/test/sanity/accessibility/test_factory.rb +2 -2
  45. data/test/sanity/accessibility/test_highlighter.rb +56 -0
  46. data/test/sanity/accessibility/{test_pretty_printer.rb → test_pp_inspector.rb} +9 -9
  47. data/test/sanity/accessibility/test_statistics.rb +57 -0
  48. data/test/sanity/ax/test_application.rb +1 -16
  49. data/test/sanity/ax/test_element.rb +2 -2
  50. data/test/sanity/ax_elements/test_nsobject_inspect.rb +2 -4
  51. data/test/sanity/minitest/test_ax_elements.rb +17 -0
  52. data/test/sanity/rspec/expectations/test_ax_elements.rb +15 -0
  53. data/test/sanity/test_mouse.rb +22 -0
  54. data/test/test_core.rb +454 -0
  55. metadata +44 -69
  56. data/History.markdown +0 -41
  57. data/lib/accessibility/system_info.rb +0 -230
  58. data/lib/ax_elements/active_support_selections.rb +0 -10
  59. data/lib/ax_elements/mri.rb +0 -57
  60. data/test/sanity/accessibility/test_version.rb +0 -15
@@ -22,13 +22,12 @@ class AX::ScrollArea < AX::Element
22
22
  def scroll_to element
23
23
  return if NSContainsRect(self.bounds, element.bounds)
24
24
  Mouse.move_to self.to_point
25
-
26
- # calculate direction and velocity for scrolling
25
+ # calculate direction to scroll
27
26
  direction = element.position.y > self.position.y ? -5 : 5
28
-
29
- Mouse.scroll direction until NSContainsRect(self.bounds, element.bounds)
30
-
31
- spin 0.1
27
+ until NSContainsRect(self.bounds, element.bounds)
28
+ Mouse.scroll direction
29
+ end
30
+ sleep 0.1
32
31
  end
33
32
 
34
33
  end
data/lib/ax/systemwide.rb CHANGED
@@ -11,37 +11,10 @@ require 'accessibility/string'
11
11
  class AX::SystemWide < AX::Element
12
12
  include Accessibility::String
13
13
 
14
- class << self
15
- ##
16
- # Find and return the group that represents the desktop
17
- #
18
- # @return [AX::Group]
19
- def desktop
20
- AX::Application.finder.scroll_areas.first.groups.first
21
- end
22
-
23
- ##
24
- # @note This currently does not include spotlight or the
25
- # notification center as they interact oddly with
26
- # accessibility APIs and how AXElements handle errors
27
- #
28
- # Find and return menu bar items for the system
29
- #
30
- # That is, menu bar items that do not belong to the current
31
- # app, but that belong to the system, such as the clock or
32
- # wi-fi menu.
33
- #
34
- # @return [AX::MenuBarItem]
35
- def status_items
36
- AX::Application.new('SystemUIServer').menu_bar.children
37
- end
38
- end
39
-
40
-
41
14
  ##
42
15
  # Overridden since there is only one way to get the element ref.
43
16
  def initialize
44
- super Accessibility::Element.system_wide
17
+ super AXUIElementCreateSystemWide()
45
18
  end
46
19
 
47
20
  ##
@@ -123,9 +96,4 @@ class AX::SystemWide < AX::Element
123
96
  @ref.set_timeout_to seconds
124
97
  end
125
98
 
126
- # (see AX::Application.frontmost_application)
127
- def focused_application
128
- AX::Application.frontmost_app
129
- end
130
-
131
99
  end
data/lib/ax_elements.rb CHANGED
@@ -1,20 +1,12 @@
1
- require 'ax_elements/active_support_selections'
2
- require 'accessibility/bridge'
3
- require 'ax_elements/mri' unless on_macruby?
4
-
5
1
  # Mix the language methods into the TopLevel
6
2
  require 'accessibility/dsl'
7
3
  include Accessibility::DSL
8
4
 
9
- require 'accessibility/system_info'
10
-
11
5
  ##
12
- # @deprecated Please use {AX::Application.dock} instead
13
- #
14
6
  # The Mac OS X dock application.
15
7
  #
16
8
  # @return [AX::Application]
17
- AX::DOCK = AX::Application.dock
9
+ AX::DOCK = AX::Application.new('com.apple.dock')
18
10
 
19
11
  # Load explicitly defined elements that are optional
20
12
  require 'ax/button'
@@ -0,0 +1,5 @@
1
+ MOUNTAIN_LION_APPKIT_VERSION = 1187
2
+ if NSAppKitVersionNumber >= MOUNTAIN_LION_APPKIT_VERSION
3
+ framework '/System/Library/Frameworks/CoreGraphics.framework'
4
+ end
5
+
@@ -1,6 +1,5 @@
1
1
  require 'ax/element'
2
2
  require 'accessibility/translator'
3
- require 'active_support/core_ext/array/access'
4
3
 
5
4
  ##
6
5
  # An old hack on arrays that allows you to map a single method across
@@ -12,6 +11,18 @@ require 'active_support/core_ext/array/access'
12
11
  # on this and so I will just keep it around for backwards compatability.
13
12
  module Accessibility::NSArrayCompat
14
13
 
14
+ ##
15
+ # Equivalent to `#at(1)`
16
+ def second
17
+ at(1)
18
+ end
19
+
20
+ ##
21
+ # Equivalent to `#at(2)`
22
+ def third
23
+ at(2)
24
+ end
25
+
15
26
  ##
16
27
  # @note Debatably bad idea. Maintained for backwards compatibility.
17
28
  #
@@ -29,11 +40,11 @@ module Accessibility::NSArrayCompat
29
40
  # outline.rows.text_fields.values # all at once
30
41
  #
31
42
  def method_missing method, *args
32
- smethod = TRANSLATOR.singularize(method.to_s.chomp('?'))
43
+ smethod = TRANSLATOR.singularize(method.chomp('?'))
33
44
  map do |x|
34
- if !x.kind_of?(AX::Element) then super
35
- elsif x.respond_to? method then x.send method, *args
36
- else x.send smethod, *args
45
+ if !x.kind_of? AX::Element then super
46
+ elsif x.respond_to? method then x.send method, *args
47
+ else x.send smethod, *args
37
48
  end
38
49
  end
39
50
  end
@@ -47,99 +58,8 @@ module Accessibility::NSArrayCompat
47
58
 
48
59
  end
49
60
 
50
- unless defined? NSArray
51
- NSArray = Array
52
- end
53
61
 
54
- ##
55
- # AXElements extensions for `NSArray`
62
+ # AXElements extensions for `NSArray`.
56
63
  class NSArray
57
64
  include Accessibility::NSArrayCompat
58
-
59
- if on_macruby?
60
-
61
- ##
62
- # Returns the tail of the array from `position`
63
- #
64
- # @example
65
- #
66
- # [1, 2, 3, 4].from(0) # => [1, 2, 3, 4]
67
- # [1, 2, 3, 4].from(2) # => [3, 4]
68
- # [1, 2, 3, 4].from(10) # => []
69
- # [].from(0) # => []
70
- #
71
- # @param position [Fixnum]
72
- # @return [Array]
73
- def from position
74
- self[position, length] || []
75
- end
76
-
77
- ##
78
- # Returns the beginning of the array up to `position`
79
- #
80
- # [1, 2, 3, 4].to(0) # => [1]
81
- # [1, 2, 3, 4].to(2) # => [1, 2, 3]
82
- # [1, 2, 3, 4].to(10) # => [1, 2, 3, 4]
83
- # [].to(0) # => []
84
- #
85
- # @param count [Fixnum]
86
- # @return [Array]
87
- def to count
88
- take count + 1
89
- end
90
-
91
- ##
92
- # Equal to `self[1]`
93
- def second
94
- self[1]
95
- end
96
-
97
- ##
98
- # Equal to `self[2]`
99
- def third
100
- self[2]
101
- end
102
-
103
- ##
104
- # Equal to `self[3]`
105
- def fourth
106
- self[3]
107
- end
108
-
109
- ##
110
- # Equal to `self[4]`
111
- def fifth
112
- self[4]
113
- end
114
-
115
- ##
116
- # Equal to `self[41]`
117
- #
118
- # Also known as accessing "the reddit".
119
- def forty_two
120
- self[41]
121
- end
122
-
123
- else
124
-
125
- ##
126
- # Create a new array with the same contents as the given array
127
- #
128
- # @param ary [Array]
129
- def self.arrayWithArray ary
130
- ary.dup
131
- end
132
-
133
- ##
134
- # Create and return a new empty array
135
- #
136
- # @return [Array]
137
- def self.array
138
- []
139
- end
140
-
141
- end
142
-
143
- alias_method :the_reddit, :forty_two
144
-
145
65
  end
@@ -0,0 +1,66 @@
1
+ module Accessibility
2
+ Inflector.inflections do |inflect|
3
+ inflect.plural(/$/, 's')
4
+ inflect.plural(/s$/i, 's')
5
+ inflect.plural(/(ax|test)is$/i, '\1es')
6
+ inflect.plural(/(octop|vir)us$/i, '\1i')
7
+ inflect.plural(/(octop|vir)i$/i, '\1i')
8
+ inflect.plural(/(alias|status)$/i, '\1es')
9
+ inflect.plural(/(bu)s$/i, '\1ses')
10
+ inflect.plural(/(buffal|tomat)o$/i, '\1oes')
11
+ inflect.plural(/([ti])um$/i, '\1a')
12
+ inflect.plural(/([ti])a$/i, '\1a')
13
+ inflect.plural(/sis$/i, 'ses')
14
+ inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
15
+ inflect.plural(/(hive)$/i, '\1s')
16
+ inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
17
+ inflect.plural(/(x|ch|ss|sh)$/i, '\1es')
18
+ inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices')
19
+ inflect.plural(/(m|l)ouse$/i, '\1ice')
20
+ inflect.plural(/(m|l)ice$/i, '\1ice')
21
+ inflect.plural(/^(ox)$/i, '\1en')
22
+ inflect.plural(/^(oxen)$/i, '\1')
23
+ inflect.plural(/(quiz)$/i, '\1zes')
24
+
25
+ inflect.singular(/s$/i, '')
26
+ inflect.singular(/(n)ews$/i, '\1ews')
27
+ inflect.singular(/([ti])a$/i, '\1um')
28
+ inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '\1\2sis')
29
+ inflect.singular(/(^analy)ses$/i, '\1sis')
30
+ inflect.singular(/([^f])ves$/i, '\1fe')
31
+ inflect.singular(/(hive)s$/i, '\1')
32
+ inflect.singular(/(tive)s$/i, '\1')
33
+ inflect.singular(/([lr])ves$/i, '\1f')
34
+ inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y')
35
+ inflect.singular(/(s)eries$/i, '\1eries')
36
+ inflect.singular(/(m)ovies$/i, '\1ovie')
37
+ inflect.singular(/(x|ch|ss|sh)es$/i, '\1')
38
+ inflect.singular(/(m|l)ice$/i, '\1ouse')
39
+ inflect.singular(/(bus)es$/i, '\1')
40
+ inflect.singular(/(o)es$/i, '\1')
41
+ inflect.singular(/(shoe)s$/i, '\1')
42
+ inflect.singular(/(cris|ax|test)es$/i, '\1is')
43
+ inflect.singular(/(octop|vir)i$/i, '\1us')
44
+ inflect.singular(/(alias|status)es$/i, '\1')
45
+ inflect.singular(/^(ox)en/i, '\1')
46
+ inflect.singular(/(vert|ind)ices$/i, '\1ex')
47
+ inflect.singular(/(matr)ices$/i, '\1ix')
48
+ inflect.singular(/(quiz)zes$/i, '\1')
49
+ inflect.singular(/(database)s$/i, '\1')
50
+
51
+ inflect.irregular('person', 'people')
52
+ inflect.irregular('man', 'men')
53
+ inflect.irregular('child', 'children')
54
+ inflect.irregular('sex', 'sexes')
55
+ inflect.irregular('move', 'moves')
56
+ inflect.irregular('cow', 'kine')
57
+ inflect.irregular('zombie', 'zombies')
58
+
59
+ inflect.uncountable(%w(equipment information rice money species series fish sheep jeans))
60
+
61
+ # Related to accessibility
62
+ inflect.acronym('UI')
63
+ inflect.acronym('RTF')
64
+ inflect.acronym('URL')
65
+ end
66
+ end
@@ -0,0 +1,176 @@
1
+ module Accessibility
2
+ module Inflector
3
+ # A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional
4
+ # inflection rules. Examples:
5
+ #
6
+ # ActiveSupport::Inflector.inflections do |inflect|
7
+ # inflect.plural /^(ox)$/i, '\1\2en'
8
+ # inflect.singular /^(ox)en/i, '\1'
9
+ #
10
+ # inflect.irregular 'octopus', 'octopi'
11
+ #
12
+ # inflect.uncountable "equipment"
13
+ # end
14
+ #
15
+ # New rules are added at the top. So in the example above, the irregular rule for octopus will now be the first of the
16
+ # pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may
17
+ # already have been loaded.
18
+ class Inflections
19
+ ##
20
+ # Singleton instance of the inflections database.
21
+ #
22
+ # @return [Accessibility::Inflector::Inflections]
23
+ def self.instance
24
+ @__instance__ ||= new
25
+ end
26
+
27
+ attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
28
+
29
+ def initialize
30
+ @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/
31
+ end
32
+
33
+ # Specifies a new acronym. An acronym must be specified as it will appear in a camelized string. An underscore
34
+ # string that contains the acronym will retain the acronym when passed to `camelize`, `humanize`, or `titleize`.
35
+ # A camelized string that contains the acronym will maintain the acronym when titleized or humanized, and will
36
+ # convert the acronym into a non-delimited single lowercase word when passed to +underscore+.
37
+ #
38
+ # Examples:
39
+ # acronym 'HTML'
40
+ # titleize 'html' #=> 'HTML'
41
+ # camelize 'html' #=> 'HTML'
42
+ # underscore 'MyHTML' #=> 'my_html'
43
+ #
44
+ # The acronym, however, must occur as a delimited unit and not be part of another word for conversions to recognize it:
45
+ #
46
+ # acronym 'HTTP'
47
+ # camelize 'my_http_delimited' #=> 'MyHTTPDelimited'
48
+ # camelize 'https' #=> 'Https', not 'HTTPs'
49
+ # underscore 'HTTPS' #=> 'http_s', not 'https'
50
+ #
51
+ # acronym 'HTTPS'
52
+ # camelize 'https' #=> 'HTTPS'
53
+ # underscore 'HTTPS' #=> 'https'
54
+ #
55
+ # Note: Acronyms that are passed to `pluralize` will no longer be recognized, since the acronym will not occur as
56
+ # a delimited unit in the pluralized result. To work around this, you must specify the pluralized form as an
57
+ # acronym as well:
58
+ #
59
+ # acronym 'API'
60
+ # camelize(pluralize('api')) #=> 'Apis'
61
+ #
62
+ # acronym 'APIs'
63
+ # camelize(pluralize('api')) #=> 'APIs'
64
+ #
65
+ # `acronym` may be used to specify any word that contains an acronym or otherwise needs to maintain a non-standard
66
+ # capitalization. The only restriction is that the word must begin with a capital letter.
67
+ #
68
+ # Examples:
69
+ # acronym 'RESTful'
70
+ # underscore 'RESTful' #=> 'restful'
71
+ # underscore 'RESTfulController' #=> 'restful_controller'
72
+ # titleize 'RESTfulController' #=> 'RESTful Controller'
73
+ # camelize 'restful' #=> 'RESTful'
74
+ # camelize 'restful_controller' #=> 'RESTfulController'
75
+ #
76
+ # acronym 'McDonald'
77
+ # underscore 'McDonald' #=> 'mcdonald'
78
+ # camelize 'mcdonald' #=> 'McDonald'
79
+ def acronym(word)
80
+ @acronyms[word.downcase] = word
81
+ @acronym_regex = /#{@acronyms.values.join("|")}/
82
+ end
83
+
84
+ # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression.
85
+ # The replacement should always be a string that may include references to the matched data from the rule.
86
+ def plural(rule, replacement)
87
+ @uncountables.delete(rule) if rule.is_a?(String)
88
+ @uncountables.delete(replacement)
89
+ @plurals.insert(0, [rule, replacement])
90
+ end
91
+
92
+ # Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression.
93
+ # The replacement should always be a string that may include references to the matched data from the rule.
94
+ def singular(rule, replacement)
95
+ @uncountables.delete(rule) if rule.is_a?(String)
96
+ @uncountables.delete(replacement)
97
+ @singulars.insert(0, [rule, replacement])
98
+ end
99
+
100
+ # Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
101
+ # for strings, not regular expressions. You simply pass the irregular in singular and plural form.
102
+ #
103
+ # Examples:
104
+ # irregular 'octopus', 'octopi'
105
+ # irregular 'person', 'people'
106
+ def irregular(singular, plural)
107
+ @uncountables.delete(singular)
108
+ @uncountables.delete(plural)
109
+ if singular[0,1].upcase == plural[0,1].upcase
110
+ plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1])
111
+ plural(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + plural[1..-1])
112
+ singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1])
113
+ else
114
+ plural(Regexp.new("#{singular[0,1].upcase}(?i)#{singular[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
115
+ plural(Regexp.new("#{singular[0,1].downcase}(?i)#{singular[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
116
+ plural(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
117
+ plural(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
118
+ singular(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), singular[0,1].upcase + singular[1..-1])
119
+ singular(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), singular[0,1].downcase + singular[1..-1])
120
+ end
121
+ end
122
+
123
+ # Add uncountable words that shouldn't be attempted inflected.
124
+ #
125
+ # Examples:
126
+ # uncountable "money"
127
+ # uncountable "money", "information"
128
+ # uncountable %w( money information rice )
129
+ def uncountable(*words)
130
+ (@uncountables << words).flatten!
131
+ end
132
+
133
+ # Specifies a humanized form of a string by a regular expression rule or by a string mapping.
134
+ # When using a regular expression based replacement, the normal humanize formatting is called after the replacement.
135
+ # When a string is used, the human form should be specified as desired (example: 'The name', not 'the_name')
136
+ #
137
+ # Examples:
138
+ # human /_cnt$/i, '\1_count'
139
+ # human "legacy_col_person_name", "Name"
140
+ def human(rule, replacement)
141
+ @humans.insert(0, [rule, replacement])
142
+ end
143
+
144
+ # Clears the loaded inflections within a given scope (default is <tt>:all</tt>).
145
+ # Give the scope as a symbol of the inflection type, the options are: <tt>:plurals</tt>,
146
+ # <tt>:singulars</tt>, <tt>:uncountables</tt>, <tt>:humans</tt>.
147
+ #
148
+ # Examples:
149
+ # clear :all
150
+ # clear :plurals
151
+ def clear(scope = :all)
152
+ case scope
153
+ when :all
154
+ @plurals, @singulars, @uncountables, @humans = [], [], [], []
155
+ else
156
+ instance_variable_set "@#{scope}", []
157
+ end
158
+ end
159
+ end
160
+
161
+ # Yields a singleton instance of Inflector::Inflections so you can specify additional
162
+ # inflector rules.
163
+ #
164
+ # Example:
165
+ # ActiveSupport::Inflector.inflections do |inflect|
166
+ # inflect.uncountable "rails"
167
+ # end
168
+ def inflections
169
+ if block_given?
170
+ yield Inflections.instance
171
+ else
172
+ Inflections.instance
173
+ end
174
+ end
175
+ end
176
+ end