AXElements 0.9.0 → 1.0.0.alpha

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 (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