motion-accessibility 2.2 → 3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +131 -48
- data/lib/project/constants.rb +6 -9
- data/lib/project/element.rb +18 -17
- data/lib/project/inspector.rb +29 -13
- data/lib/project/motion-accessibility-console/browser.rb +1 -1
- data/lib/project/motion-accessibility-console/tree.rb +17 -2
- data/lib/project/object.rb +0 -12
- data/lib/project/test.rb +456 -0
- data/lib/project/test_log.rb +25 -0
- metadata +9 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3c821b39f3467d6525a96b0a695bed9b4450ee98
|
4
|
+
data.tar.gz: e6abe481288ab71e3b52d18dd9ff8ffc0856dc11
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 92fda9f928f534f81fc2f45bf1d7b834c60927a7ec51880b79f15fcdbcb7775c8ec27d1ad9e5eb37435b196d78dbe5f8858ca4add557ceb2b97777611efbfe14
|
7
|
+
data.tar.gz: 9c61a401a1ccb1fcef1b0d02140cc3be343981e9f7f265609e9cd12de1883bc5d93ecc0d7c8cc04aeca2901c19713e68a3c9875700b732f40fc94277e8f59119
|
data/README.md
CHANGED
@@ -4,9 +4,7 @@
|
|
4
4
|
|
5
5
|
https://github.com/austinseraphin/motion-accessibility
|
6
6
|
|
7
|
-
Motion-
|
8
|
-
ruby. I hope that making them easier will encourage developers to use it
|
9
|
-
more and make their apps accessible. The gem also includes an accessibility inspector and a text console to aide blind iOS developers.
|
7
|
+
Motion-accessibility provides the tools needed for sighted and blind iOS RubyMotion developers to make their apps more accessible. It wraps Apple’s UIAccessibility protocols in Ruby, and provides an accessibility inspector. It has a console for blind developers, since the iOS simulator doesn’t work well with VoiceOver. It also has automated accessibility testing for your views, and the accessibility doctor will help diagnose your problems and tell you how to fix them. You can build accessibility testing into your specs, so you will never break VoiceOver compatibility!
|
10
8
|
|
11
9
|
## Installation
|
12
10
|
|
@@ -38,24 +36,23 @@ The `browse` or `b` command lets you examine the view hierarchy in a speech-frie
|
|
38
36
|
The following examples come from the sample app included with motion-accessibility.
|
39
37
|
|
40
38
|
```
|
41
|
-
(main)>
|
42
|
-
Browsing UIWindow
|
43
|
-
1
|
44
|
-
2
|
45
|
-
3
|
46
|
-
4 UINavigationBar
|
47
|
-
5 UITabBar with 2 subviews
|
39
|
+
(main)> browse
|
40
|
+
Browsing UIWindow
|
41
|
+
1 UIView with 3 subviews
|
42
|
+
2 UINavigationBar with 2 subviews
|
43
|
+
3 UITabBar with 3 subviews
|
48
44
|
=> nil
|
49
45
|
```
|
50
46
|
|
51
47
|
If a view has subviews, you can browse that view.
|
52
48
|
|
53
49
|
```
|
54
|
-
(main)>
|
55
|
-
Browsing
|
56
|
-
0 Superview UIWindow
|
57
|
-
1
|
58
|
-
2 Touchable
|
50
|
+
((main)> b 1
|
51
|
+
Browsing UIView
|
52
|
+
0 Superview UIWindow
|
53
|
+
1 UILabel Hello!
|
54
|
+
2 Touchable UITextField
|
55
|
+
3 Touchable UIButton Update
|
59
56
|
=> nil
|
60
57
|
```
|
61
58
|
|
@@ -63,71 +60,152 @@ You can refresh the browser by passing the `:refresh` or `:top` keyword.
|
|
63
60
|
|
64
61
|
You may pass the `:scroll` keyword to scroll a UIScrollView or descendants, such as a UITableView. This still has some minor issues .
|
65
62
|
|
66
|
-
You may pass any view as an argument to browse it. Use the `:refresh` or `:top` keywords to switch back to the running application.
|
67
|
-
|
68
63
|
#### `view` or `v`
|
69
64
|
|
70
65
|
The `view` or `v` command simply returns the current view. If you have just browsed a view, it will return that. Otherwise, you may specify the view you wish to browse. Note that for all the commands, you may either use its number or accessibility label.
|
71
66
|
|
72
67
|
```
|
73
|
-
(main)>
|
74
|
-
=> #<
|
68
|
+
(main)> v 1
|
69
|
+
=> #<UILabel:0x8feda00>
|
75
70
|
```
|
76
71
|
|
77
72
|
#### `touch`
|
78
73
|
The `touch` command lets you interact with the various controls. It works on all standard UIControls. `touch` can accept an argument depending on the type of control. For example, you can pass a UITextField a string to set its value.
|
79
74
|
|
80
75
|
```
|
81
|
-
(main)>
|
82
|
-
Browsing
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
5 UITabBar with 2 subviews
|
76
|
+
(main)> touch 2,"Motion-accessibility rocks!"
|
77
|
+
Browsing UIView
|
78
|
+
0 Superview UIWindow
|
79
|
+
1 UILabel Hello!
|
80
|
+
2 Touchable UITextField Motion-accessibility rocks!
|
81
|
+
3 Touchable UIButton Update
|
88
82
|
=> nil
|
89
83
|
```
|
90
84
|
|
91
85
|
UIButtons can take a UIControlEvent, but default to `UIControlEventTouchUpInside`. Note here the use of an accessibility label to reference the view.
|
92
86
|
|
93
87
|
```
|
94
|
-
(main)>
|
95
|
-
Browsing
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
5 UITabBar with 2 subviews
|
88
|
+
(main)> touch "update"
|
89
|
+
Browsing UIView
|
90
|
+
0 Superview UIWindow
|
91
|
+
1 UILabel Motion-accessibility rocks!
|
92
|
+
2 Touchable UITextField Motion-accessibility rocks!
|
93
|
+
3 Touchable UIButton Update
|
101
94
|
=> nil
|
102
95
|
```
|
103
96
|
|
104
97
|
### The Accessibility Inspector
|
105
98
|
|
106
|
-
You can easily see the state of any of the following attributes and methods by using the accessibility inspector. Just call
|
99
|
+
You can easily see the state of any of the following attributes and methods by using the accessibility inspector. Just call `Accessibility.inspect` and pass any object as an argument.
|
107
100
|
|
108
101
|
```
|
109
|
-
main)>
|
110
|
-
=> #<UILabel:
|
111
|
-
(main)> label.text="Hello!"
|
112
|
-
=> "Hello!"
|
113
|
-
(main)> label
|
102
|
+
(main)> label=UILabel.alloc.initWithFrame(CGRect.new([0,0], [100,100]))
|
103
|
+
=> #<UILabel:0x103b8c40>
|
104
|
+
(main)> label.text="Hello!"
|
105
|
+
=> "Hello!"
|
106
|
+
(main)> Accessibility.inspect label
|
107
|
+
#<UILabel:0x103b8c40>
|
114
108
|
Accessibility label: Hello!
|
115
109
|
Accessibility hint: nil
|
116
110
|
Accessibility traits: Static text
|
117
111
|
Accessibility value: nil
|
118
112
|
Accessibility language: nil
|
119
|
-
Accessibility frame: x=0.0 y=
|
120
|
-
Accessibility activation point: x=
|
113
|
+
Accessibility frame: x=0.0 y=0.0 width=100.0 height=100.0
|
114
|
+
Accessibility activation point: x=50.0 y=50.0
|
121
115
|
Accessibility path: nil
|
122
116
|
Accessibility view is modal: false
|
123
117
|
Should group accessibility children: false
|
124
118
|
Accessibility elements hidden: false
|
125
119
|
Is accessibility element: true
|
126
120
|
Accessibility identifier: nil
|
121
|
+
Accessible: true
|
127
122
|
=> nil
|
128
123
|
```
|
129
124
|
|
130
|
-
By the way, `a11y` stands for `accessibility`, because it has a, then 11 letters, then y. Hence, you can use `
|
125
|
+
By the way, `a11y` stands for `accessibility`, because it has a, then 11 letters, then y. Hence, you can use `A11y.inspect` as a shortcut.
|
126
|
+
|
127
|
+
### Automated Accessibility Testing
|
128
|
+
|
129
|
+
Below you will find detailed documentation about all of the accessibility protocols. Don’t feel overwhelmed. The accessibility inspector will tell you exactly what you have to do. Let’s start by creating an unlabeled button, the bane of all VOiceOver users.
|
130
|
+
|
131
|
+
```
|
132
|
+
(main)> button=UIButton.new
|
133
|
+
=> #<UIButton:0xd7831a0>
|
134
|
+
(main)> A11y.inspect button
|
135
|
+
#<UIButton:0xd7831a0>
|
136
|
+
Accessibility label: nil
|
137
|
+
Accessibility hint: nil
|
138
|
+
Accessibility traits: Button
|
139
|
+
Accessibility value: nil
|
140
|
+
Accessibility language: nil
|
141
|
+
Accessibility frame: x=0.0 y=0.0 width=0.0 height=0.0
|
142
|
+
Accessibility activation point: x=0.0 y=0.0
|
143
|
+
Accessibility path: nil
|
144
|
+
Accessibility view is modal: false
|
145
|
+
Should group accessibility children: false
|
146
|
+
Accessibility elements hidden: false
|
147
|
+
Is accessibility element: false
|
148
|
+
Accessibility identifier: nil
|
149
|
+
Accessible: false
|
150
|
+
2014-05-27 19:02:50.209 motion-accessibility[8851:70b] #<UIButton:0xd7831a0>: You must set the accessibility_label. You can use the setTitle:forState method to do this on a button.
|
151
|
+
2014-05-27 19:02:50.223 motion-accessibility[8851:70b] #<UIButton:0xd7831a0>: You must set is_accessibility_element=true to make VoiceOver aware of it. This will often happen automatically when a view becomes visible by giving it a frame and adding it to a subview.
|
152
|
+
=> #<UIButton:0xd7831a0>
|
153
|
+
```
|
154
|
+
|
155
|
+
This incorporates two features discussed below.
|
156
|
+
|
157
|
+
#### `accessible?`
|
158
|
+
Call the `accessible?` predicate on any object to determine its accessibility. Like all predicates it returns true or false. You can include this in your specs. If you build in accessibility testing you will never break accessibility, something even worse than no accessibility at all. For example, if you have a variable `@label` which contains a label, you could write:
|
159
|
+
```
|
160
|
+
@label.should.be.accessible
|
161
|
+
```
|
162
|
+
So simple! This works recursively. Say you run some functional tests on a view controller.
|
163
|
+
|
164
|
+
```
|
165
|
+
tests Test_Controller
|
166
|
+
it “#accessible?`
|
167
|
+
controller.should.be.accessible
|
168
|
+
end
|
169
|
+
```
|
170
|
+
|
171
|
+
Or for the ultimate in laziness:
|
172
|
+
|
173
|
+
```
|
174
|
+
before do
|
175
|
+
@app=UIApplication.sharedApplication
|
176
|
+
end
|
177
|
+
|
178
|
+
it “accessible?” do
|
179
|
+
@app.should.be.accessible
|
180
|
+
end
|
181
|
+
```
|
182
|
+
You may not want to do this however, because it can get confusing navigating down subview hierarchies, though it will report the path taken. Still, better to do that then nothing at all.
|
183
|
+
|
184
|
+
#### `Accessibility.doctor`
|
185
|
+
The accessibility doctor will report on what you have to do. It writes this to the NSLog. If given no arguments it will report on the last object called with the `accessible?` predicate. It returns the object with the problem, or nil if it finds nothing wrong. The accessibility inspector returns this as well. If a spec fails remember to put this before the test.
|
186
|
+
|
187
|
+
#### `accessibility_test`
|
188
|
+
Finally, you can specify which accessibility test applies to an object by setting this value. You may do this in the same way you set other attributes. You can use a setter:
|
189
|
+
|
190
|
+
```
|
191
|
+
view=UIView.new
|
192
|
+
view.accessibility_test=:UILabel
|
193
|
+
```
|
194
|
+
|
195
|
+
Or you may define it in a class. If you do this make sure that it returns a symbol of the class you want to test against, since it has no error checking unlike the setter.
|
196
|
+
```
|
197
|
+
class Custom_View < UIView
|
198
|
+
|
199
|
+
def accessibility_test
|
200
|
+
:UILabel
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
```
|
205
|
+
|
206
|
+
### Accessibility vs. Usability
|
207
|
+
|
208
|
+
A difference exists between accessibility and usability, though often the two get lumped together under the umbrella of the former. Accessibility refers to whether or not a user can view and interact with something in a meaningful way. In this case, this means making VoiceOver aware of the different elements of your app. Usability gets into more intangible realms, and has to do with whether or not it actually makes sense to a user. In this case this means a blind human using VoiceOver, and only a blind human using VoiceOver can tell you this. A computer can test for accessibility, but only a human can test for usability.
|
131
209
|
|
132
210
|
### UIAccessibility Informal Protocol
|
133
211
|
|
@@ -171,15 +249,15 @@ Hints describe the results of performing an action. Only provide one when not ob
|
|
171
249
|
#### `accessibility_traits`
|
172
250
|
|
173
251
|
Traits describe an element's state, behavior, or usage. They tell
|
174
|
-
VoiceOver how to respond to a view. To combine them, use the single vertical bar `|` binary or operator.
|
252
|
+
VoiceOver how to respond to a view. To combine them, use the single vertical bar `|` binary or operator. Remember to call `super.accessibility_traits` if defining them in a method.
|
175
253
|
|
176
|
-
The `
|
254
|
+
The `accessibility_traits=` method also accepts a symbol or array of symbols, and applies the accessibility_traits method to them. For example, if a view displays an image that opens a link, you can do this.
|
177
255
|
|
178
256
|
```
|
179
257
|
class ImageLinkView < UIView
|
180
258
|
# ....
|
181
259
|
def accessibility_traits
|
182
|
-
|
260
|
+
super.accessibility_traits|:image.accessibility_trait|:link.accessibility_trait
|
183
261
|
end
|
184
262
|
end
|
185
263
|
```
|
@@ -252,7 +330,7 @@ Ignores elements within views which are siblings of the receiver. If you present
|
|
252
330
|
|
253
331
|
#### `group_accessibility_children?` or `should_group_accessibility_children`
|
254
332
|
|
255
|
-
VoiceOver gives two ways to browse the screen. The user can drag their finger around the screen and hear the contents. They can also swipe right or left with one finger to hear the next or previous element.
|
333
|
+
VoiceOver gives two ways to browse the screen. The user can drag their finger around the screen and hear the contents. They can also swipe right or left with one finger to hear the next or previous element. When swiping to the next element, VoiceOver reads the elements from left to right, and from top to bottom. Sometimes this can get confusing, depending on the layout of the screen. Setting this to true tells VoiceOver to read the views in the order defined in the subviews array.
|
256
334
|
|
257
335
|
#### `accessibility_elements_hidden?` or `accessibility_elements_hidden`
|
258
336
|
|
@@ -313,9 +391,9 @@ Increments the value of the accessibility element. Make sure to have the :adjust
|
|
313
391
|
|
314
392
|
Decrements the value of the accessibility element. Make sure to have the :adjustable accessibility trait set for this to work.
|
315
393
|
|
316
|
-
###
|
394
|
+
### Accessibility::Element
|
317
395
|
|
318
|
-
If you have something in your view that does not inherit from UIView or UIControl and you want to make it accessible, you need to define it as an accessibility element. Accessibility elements belong to an accessibility container, in other words the view which contains them. To create one, just call `Accessibility::Element.
|
396
|
+
If you have something in your view that does not inherit from UIView or UIControl and you want to make it accessible, you need to define it as an accessibility element. Accessibility elements belong to an accessibility container, in other words the view which contains them. To create one, just call `Accessibility::Element.init_with_accessibility_container` with the container, usually self. Like a UIView, an accessibility element has attributes, and you get and set them in exactly the same way.
|
319
397
|
|
320
398
|
```
|
321
399
|
class CustomView < UIView
|
@@ -325,6 +403,7 @@ super
|
|
325
403
|
# …
|
326
404
|
accessibility=Accessibility::Element.new(self)
|
327
405
|
accessibility.label="Hello."
|
406
|
+
accessibility.hint=“Presses the magic button”
|
328
407
|
accessibility.frame=view.frame
|
329
408
|
accessibility.traits=:button
|
330
409
|
end
|
@@ -458,3 +537,7 @@ iOS 7 adds some speech attributes to use in attributed strings. To get them, jus
|
|
458
537
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
459
538
|
4. Push to the branch (`git push origin my-new-feature`)
|
460
539
|
5. Create new Pull Request
|
540
|
+
|
541
|
+
## A Special Offer for You
|
542
|
+
|
543
|
+
I do [freelance accessibility consulting.](austinseraphin.com) If you use my gem I will give you a discount. Contact me for more information.
|
data/lib/project/constants.rb
CHANGED
@@ -42,22 +42,19 @@ Attributes = {
|
|
42
42
|
Element_Attributes = {
|
43
43
|
:container => :accessibilityContainer,
|
44
44
|
:container= => :setAccessibilityContainer,
|
45
|
-
:frame => :accessibilityFrame,
|
46
|
-
:frame= => :setAccessibilityFrame,
|
47
|
-
:hint => :accessibilityHint,
|
48
|
-
:hint= => :setAccessibilityHint,
|
49
45
|
:label => :accessibilityLabel,
|
50
46
|
:label= => :setAccessibilityLabel,
|
47
|
+
:hint => :accessibilityHint,
|
48
|
+
:hint= => :setAccessibilityHint,
|
51
49
|
:traits => :accessibilityTraits,
|
52
50
|
:value => :accessibilityValue,
|
53
51
|
:value= => :setAccessibilityValue,
|
52
|
+
:frame => :accessibilityFrame,
|
53
|
+
:frame= => :setAccessibilityFrame,
|
54
54
|
:did_become_focused => :accessibilityElementDidBecomeFocused,
|
55
55
|
:did_lose_focus => :accessibilityElementDidLoseFocus,
|
56
56
|
:is_focused => :accessibilityElementIsFocused,
|
57
|
-
:focused? => :accessibilityElementIsFocused
|
58
|
-
:is_accessibility_element => :isAccessibilityElement,
|
59
|
-
:is_accessibility_element= => :setIsAccessibilityElement,
|
60
|
-
:accessibility_element? => :isAccessibilityElement
|
57
|
+
:focused? => :accessibilityElementIsFocused
|
61
58
|
}
|
62
59
|
|
63
60
|
Traits = {
|
@@ -204,7 +201,7 @@ Touchable_Types = ["UITextField", "UIButton", "UIPickerView", "UIDatePicker",
|
|
204
201
|
"UISegmentedControl", "UISlider", "UIStepper", "UISwitch",
|
205
202
|
"UITableViewCell", "UITabBarButton","UINavigationItemButtonView"]
|
206
203
|
|
207
|
-
Ignored_Views = ["
|
204
|
+
Ignored_Views = ["UILayoutContainerView", "UITransitionView", "UINavigationTransitionView", "UIViewControllerWrapperView", "UITableViewCellContentView", "UINavigationItemView", "UITableViewWrapperView"]
|
208
205
|
|
209
206
|
Ignored_ImageViews = ["UINavigationBar", "UITabBar", "UITableView"]
|
210
207
|
|
data/lib/project/element.rb
CHANGED
@@ -1,12 +1,9 @@
|
|
1
1
|
class UIAccessibilityElement
|
2
2
|
|
3
|
-
def
|
4
|
-
|
5
|
-
UIAccessibilityElement.alloc.initWithAccessibilityContainer(self)
|
3
|
+
def self.init_with_accessibility_container(container)
|
4
|
+
UIAccessibilityElement.alloc.initWithAccessibilityContainer(container)
|
6
5
|
end
|
7
6
|
|
8
|
-
alias :init_with_accessibility_container :initialize
|
9
|
-
|
10
7
|
Accessibility::Element_Attributes.each do |ruby,ios|
|
11
8
|
if ruby=~/=$/
|
12
9
|
define_method(ruby) {|value| self.send(ios,value)}
|
@@ -22,18 +19,8 @@ define_method(ruby) {self.send(ios)}
|
|
22
19
|
end
|
23
20
|
end
|
24
21
|
|
25
|
-
def
|
26
|
-
|
27
|
-
if traits.kind_of?(Fixnum)
|
28
|
-
bits=traits
|
29
|
-
elsif traits.kind_of?(Symbol)
|
30
|
-
bits=traits.accessibility_trait
|
31
|
-
elsif traits.kind_of?(Array)
|
32
|
-
traits.each {|trait| bits|=trait.accessibility_trait}
|
33
|
-
else
|
34
|
-
raise "Pass a bitmask, a symbol, or an array to accessibility_traits="
|
35
|
-
end
|
36
|
-
OAself.accessibilityTraits=bits
|
22
|
+
def traits=(traits)
|
23
|
+
self.accessibility_traits=traits
|
37
24
|
end
|
38
25
|
|
39
26
|
if self.respond_to?(:method_added)
|
@@ -61,6 +48,20 @@ define_method(ruby) { self.send(ios)}
|
|
61
48
|
end
|
62
49
|
end
|
63
50
|
|
51
|
+
def self.container?(obj)
|
52
|
+
!obj.accessibility_element_at_index(0).nil?
|
53
|
+
end
|
54
|
+
|
64
55
|
end
|
65
56
|
|
66
57
|
Accessibility::Element=UIAccessibilityElement
|
58
|
+
|
59
|
+
class NSObject
|
60
|
+
|
61
|
+
def each_accessibility_element
|
62
|
+
return unless A11y::Element.container?(self)
|
63
|
+
self.accessibility_element_count.times {|n| yield(self.accessibility_element_at_index(n))}
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
data/lib/project/inspector.rb
CHANGED
@@ -1,33 +1,36 @@
|
|
1
1
|
# Accessibility Inspector
|
2
2
|
|
3
|
-
|
3
|
+
module Accessibility
|
4
4
|
|
5
|
-
def
|
5
|
+
def self.inspect(obj)
|
6
6
|
displayed=[]
|
7
|
+
if obj.class==Accessibility::Element
|
8
|
+
attributes=A11y::Element_Attributes.dup
|
9
|
+
else
|
7
10
|
attributes=Accessibility::All_Attributes.dup
|
8
|
-
attributes.merge(Accessibility::PickerView_Attributes) if
|
9
|
-
|
11
|
+
attributes.merge(Accessibility::PickerView_Attributes) if obj.class==UIPickerView
|
12
|
+
end
|
13
|
+
puts obj.inspect
|
10
14
|
attributes.each do |ruby,ios|
|
11
15
|
next if ios=~/^set/
|
12
16
|
next if displayed.member?(ios)
|
13
|
-
self.inspect_accessibility_attribute(ios)
|
17
|
+
self.inspect_accessibility_attribute(obj,ios)
|
14
18
|
displayed<<ios
|
15
19
|
end
|
16
|
-
|
20
|
+
puts "Accessibility test: #{obj.accessibility_test}" if obj.accessibility_test
|
21
|
+
puts "Accessibility container: #{obj.accessibility_element_count} elements" if A11y::Element.container?(obj)
|
22
|
+
puts "Accessible: #{obj.accessible?}"
|
23
|
+
A11y.doctor
|
17
24
|
end
|
18
25
|
|
19
|
-
|
20
|
-
|
21
|
-
protected
|
22
|
-
|
23
|
-
def inspect_accessibility_attribute(attribute)
|
26
|
+
def self.inspect_accessibility_attribute(obj,attribute)
|
24
27
|
name=attribute.gsub(/(.)([A-Z])/,'\1 \2').capitalize
|
25
28
|
if Accessibility::Attribute_Types.member?(attribute)
|
26
29
|
begin
|
27
30
|
case attribute
|
28
|
-
when :accessibilityTraits then value=inspect_accessibility_traits
|
31
|
+
when :accessibilityTraits then value=self.inspect_accessibility_traits(obj)
|
29
32
|
else
|
30
|
-
value=
|
33
|
+
value=obj.send(attribute)
|
31
34
|
end
|
32
35
|
if value
|
33
36
|
case Accessibility.attribute_type(attribute)
|
@@ -50,4 +53,17 @@ puts "#{name}: Defined"
|
|
50
53
|
end
|
51
54
|
end
|
52
55
|
|
56
|
+
def self.inspect_accessibility_traits(obj)
|
57
|
+
return obj.accessibility_traits if obj.accessibility_traits>Accessibility::Traits.values.max
|
58
|
+
traits=[]
|
59
|
+
Accessibility::Traits.each do |trait, bitmask|
|
60
|
+
if obj.accessibility_traits&bitmask>0
|
61
|
+
name=trait.gsub(/_/,' ').capitalize
|
62
|
+
traits<<name
|
63
|
+
end
|
64
|
+
end
|
65
|
+
traits=["None"] if traits.empty?
|
66
|
+
traits.join(', ')
|
67
|
+
end
|
68
|
+
|
53
69
|
end
|
@@ -27,6 +27,12 @@ self.subviews.each_index {|index| return false unless self.subviews[index]==othe
|
|
27
27
|
return true
|
28
28
|
end
|
29
29
|
|
30
|
+
def [](n)
|
31
|
+
a=[@view]
|
32
|
+
a+=@subviews if @subviews
|
33
|
+
a[n]
|
34
|
+
end
|
35
|
+
|
30
36
|
def browsable_nodes
|
31
37
|
nodes=[@superview]
|
32
38
|
if @subviews
|
@@ -80,11 +86,12 @@ display.join(" ")
|
|
80
86
|
end
|
81
87
|
|
82
88
|
def self.accessible_view?(view)
|
83
|
-
|
89
|
+
result= view.accessibility_element?||view.accessibility_label||view.accessibility_value||view.accessibility_traits
|
90
|
+
(result)?true:false
|
84
91
|
end
|
85
92
|
|
86
93
|
def self.ignore_view?(view)
|
87
|
-
return true if view.subviews.empty?&&!
|
94
|
+
return true if view.subviews.empty?&&!view.accessible?
|
88
95
|
if view.superview
|
89
96
|
sv=view.superview
|
90
97
|
while sv&&self.ignore_view?(sv)
|
@@ -102,6 +109,13 @@ def self.build(view=nil, superview=nil)
|
|
102
109
|
tree=self.new
|
103
110
|
view=UIApplication.sharedApplication.keyWindow if view.nil?
|
104
111
|
subviews=[]
|
112
|
+
if A11y::Element.container?(view)
|
113
|
+
view.each_accessibility_element do |element|
|
114
|
+
subview_tree=self.build(element, tree)
|
115
|
+
subviews<<subview_tree
|
116
|
+
end
|
117
|
+
end
|
118
|
+
if view.respond_to?(:subviews)
|
105
119
|
view.subviews.each do |subview|
|
106
120
|
subview_tree=self.build(subview, tree)
|
107
121
|
if self.ignore_view?(subview)
|
@@ -111,6 +125,7 @@ else
|
|
111
125
|
subviews<<subview_tree
|
112
126
|
end
|
113
127
|
end
|
128
|
+
end
|
114
129
|
tree.view=view
|
115
130
|
tree.subviews=subviews
|
116
131
|
tree.superview=superview
|
data/lib/project/object.rb
CHANGED
@@ -50,18 +50,6 @@ self.accessibilityTraits=bits
|
|
50
50
|
self
|
51
51
|
end
|
52
52
|
|
53
|
-
def inspect_accessibility_traits
|
54
|
-
traits=[]
|
55
|
-
Accessibility::Traits.each do |trait, bitmask|
|
56
|
-
if self.accessibility_traits&bitmask>0
|
57
|
-
name=trait.gsub(/_/,' ').capitalize
|
58
|
-
traits<<name
|
59
|
-
end
|
60
|
-
end
|
61
|
-
traits=["None"] if traits.empty?
|
62
|
-
traits.join(', ')
|
63
|
-
end
|
64
|
-
|
65
53
|
if self.respond_to?(:method_added)
|
66
54
|
class << self
|
67
55
|
alias :method_added_motion_accessibility :method_added
|
data/lib/project/test.rb
ADDED
@@ -0,0 +1,456 @@
|
|
1
|
+
module Accessibility
|
2
|
+
module Test
|
3
|
+
|
4
|
+
Path=Array.new
|
5
|
+
Data= {
|
6
|
+
depth: 0,
|
7
|
+
debug: false
|
8
|
+
}
|
9
|
+
|
10
|
+
Options = {
|
11
|
+
recurse: true
|
12
|
+
}
|
13
|
+
|
14
|
+
Tests = {
|
15
|
+
NSObject: {
|
16
|
+
accessibility_label: [String, "You must set an accessibility label to tell VoiceOver what to read."],
|
17
|
+
accessibility_traits: [UIAccessibilityTraitNone, "You must set accessibility_trait to :none.accessibility_trait"],
|
18
|
+
accessibility_value: nil,
|
19
|
+
accessibility_frame: [CGRect, "You must set an accessibility_frame to tell VoiceOver the bounds of the view." ],
|
20
|
+
accessibility_activation_point: [CGPoint, "You must set an accessibility_activation_point so VoiceOver knows where to touch."],
|
21
|
+
accessibility_path: nil,
|
22
|
+
accessibility_view_is_modal: false,
|
23
|
+
should_group_accessibility_children: false,
|
24
|
+
accessibility_elements_hidden: false,
|
25
|
+
is_accessibility_element: [true, "You must set is_accessibility_element=true to make VoiceOver aware of it. This will often happen automatically when a view becomes visible by giving it a frame and adding it to a subview."]
|
26
|
+
},
|
27
|
+
UIActionSheet: {
|
28
|
+
accessibility_label: nil,
|
29
|
+
is_accessibility_element: false,
|
30
|
+
accessibility_view_is_modal: true
|
31
|
+
},
|
32
|
+
UIActivityIndicatorView: {
|
33
|
+
accessibility_label: [String, "You must set the accessibility_label to the title of the activity indicator."],
|
34
|
+
accessibility_value: [String, "You must set the accessibility_value to the value of the indicator."],
|
35
|
+
accessibility_elements_hidden: true,
|
36
|
+
is_accessibility_element: false
|
37
|
+
},
|
38
|
+
UIApplication: {
|
39
|
+
accessibility_label: [String,"You must set the accessibility_label. Setting the app's title will do this."],
|
40
|
+
is_accessibility_element: false,
|
41
|
+
options: {
|
42
|
+
test: :application
|
43
|
+
}
|
44
|
+
},
|
45
|
+
UIBarItem: {
|
46
|
+
title: [String, "Set the title to tell VoiceOver what to say."],
|
47
|
+
accessibility_label: nil,
|
48
|
+
is_accessibility_element: false,
|
49
|
+
},
|
50
|
+
UIButton: {
|
51
|
+
accessibility_label: [String,"You must set the accessibility_label. You can use the setTitle:forState method to do this on a button."],
|
52
|
+
accessibility_traits: UIAccessibilityTraitButton,
|
53
|
+
},
|
54
|
+
UICollectionReusableView: {
|
55
|
+
accessibility_label: nil,
|
56
|
+
is_accessibility_element: false},
|
57
|
+
UIDatePicker: {
|
58
|
+
accessibility_label: nil,
|
59
|
+
is_accessibility_element: false,
|
60
|
+
options: {
|
61
|
+
recurse: false
|
62
|
+
}
|
63
|
+
},
|
64
|
+
UIImageView: {
|
65
|
+
accessibility_label: nil,
|
66
|
+
accessibility_traits: [UIAccessibilityTraitImage, "You must set accessibility_trait to :image"],
|
67
|
+
is_accessibility_element: false
|
68
|
+
},
|
69
|
+
UILabel: {
|
70
|
+
accessibility_label: [String, "You must set the accessibility_label. You can use the text method to do this."],
|
71
|
+
accessibility_traits: [UIAccessibilityTraitStaticText, "You must set accessibility_traits to :static_text"]
|
72
|
+
},
|
73
|
+
UINavigationBar: {
|
74
|
+
accessibility_label: nil,
|
75
|
+
accessibility_traits: ->(t){A11y::Test.nonstandard(t)},
|
76
|
+
accessibility_elements_hidden: false,
|
77
|
+
should_group_accessibility_children: true,
|
78
|
+
accessibility_identifier: [String, "You must set the accessibility_identifier to the title of the view. You can set the title of the view controller or of the navigation item."],
|
79
|
+
is_accessibility_element: false,
|
80
|
+
options: {
|
81
|
+
recurse: false,
|
82
|
+
test: :bar
|
83
|
+
}
|
84
|
+
},
|
85
|
+
UINavigationController: {
|
86
|
+
accessibility_label: nil,
|
87
|
+
is_accessibility_element: false,
|
88
|
+
options: {
|
89
|
+
recurse: false,
|
90
|
+
test: :navigationViewController
|
91
|
+
}
|
92
|
+
},
|
93
|
+
UINavigationTransitionView: {
|
94
|
+
accessibility_label: nil,
|
95
|
+
should_group_accessibility_children: true,
|
96
|
+
is_accessibility_element: false
|
97
|
+
},
|
98
|
+
UIPageControl: {
|
99
|
+
accessibility_label: nil,
|
100
|
+
is_accessibility_element: false,
|
101
|
+
accessibility_value: [String, "You must set the accessibility_value to something meaningful, for example 'Page 1 of 1'"],
|
102
|
+
accessibility_traits: UIAccessibilityTraitUpdatesFrequently
|
103
|
+
},
|
104
|
+
UIPickerView: {
|
105
|
+
accessibility_label: nil,
|
106
|
+
is_accessibility_element: false,
|
107
|
+
options: {
|
108
|
+
recurse: false,
|
109
|
+
test: :pickerView
|
110
|
+
}
|
111
|
+
},
|
112
|
+
UIAccessibilityPickerComponent: {
|
113
|
+
accessibility_label: nil,
|
114
|
+
accessibility_traits: Fixnum,
|
115
|
+
accessibility_value: String
|
116
|
+
},
|
117
|
+
UIProgressView: {
|
118
|
+
accessibility_label: String,
|
119
|
+
accessibility_traits: UIAccessibilityTraitUpdatesFrequently,
|
120
|
+
accessibility_value: [String, "The accessibility_value should contain a textual representation of the progress, for instance \"50%\""],
|
121
|
+
is_accessibility_element: false
|
122
|
+
},
|
123
|
+
UISegment: {
|
124
|
+
accessibility_label: String,
|
125
|
+
accessibility_traits: [UIAccessibilityTraitButton, "You must make this a button by setting accessibility_trait to :button"],
|
126
|
+
accessibility_value: String
|
127
|
+
},
|
128
|
+
UISegmentedControl: {
|
129
|
+
accessibility_label: nil,
|
130
|
+
is_accessibility_element: false,
|
131
|
+
should_group_accessibility_children: true
|
132
|
+
},
|
133
|
+
UISlider: {
|
134
|
+
accessibility_label: nil,
|
135
|
+
accessibility_value: String,
|
136
|
+
accessibility_traits: UIAccessibilityTraitAdjustable,
|
137
|
+
options: {recurse: false}
|
138
|
+
},
|
139
|
+
UIStepper: {
|
140
|
+
accessibility_label: nil,
|
141
|
+
is_accessibility_element: false,
|
142
|
+
options: {recurse: false}
|
143
|
+
},
|
144
|
+
UISwitch: {
|
145
|
+
accessibility_label: nil,
|
146
|
+
accessibility_traits: ->(t){A11y::Test.nonstandard(t, custom: UIAccessibilityTraitButton, message: "You can use :button.")},
|
147
|
+
accessibility_value: [String, "You must set the accessibility_value to \"1\" or \"0\""]
|
148
|
+
},
|
149
|
+
UITabBar: {
|
150
|
+
accessibility_label: nil,
|
151
|
+
accessibility_traits: ->(t){A11y::Test.nonstandard(t)},
|
152
|
+
should_group_accessibility_children: true,
|
153
|
+
is_accessibility_element: false,
|
154
|
+
options: {
|
155
|
+
recurse: false,
|
156
|
+
test: :bar
|
157
|
+
}
|
158
|
+
},
|
159
|
+
UITabBarButton: {
|
160
|
+
accessibility_label: [String, "You must set the title of this button. You can se tthe title of the UITabBarItem."],
|
161
|
+
accessibility_traits: ->(t){A11y::Test.nonstandard(t, apple: Bignum, custom: UIAccessibilityTraitButton, message: "You can use :button.")},
|
162
|
+
},
|
163
|
+
UITabBarController: {
|
164
|
+
accessibility_label: nil,
|
165
|
+
is_accessibility_element: false,
|
166
|
+
options: {
|
167
|
+
recurse: false,
|
168
|
+
test: :tabBarViewController
|
169
|
+
}
|
170
|
+
},
|
171
|
+
UITableView: {
|
172
|
+
accessibility_label: [String, "You must set the accessibility_label to the default contents of the table view, for example \"Empty List\""],
|
173
|
+
accessibility_traits: ->(t){A11y::Test.nonstandard(t)},
|
174
|
+
should_group_accessibility_children: true,
|
175
|
+
is_accessibility_element: false
|
176
|
+
},
|
177
|
+
UITableViewCell: {
|
178
|
+
accessibility_label: :ignore,
|
179
|
+
accessibility_value: :ignore,
|
180
|
+
is_accessibility_element: false,
|
181
|
+
options: {
|
182
|
+
recurse: false,
|
183
|
+
test: :tableViewCell
|
184
|
+
}
|
185
|
+
},
|
186
|
+
UITableViewCellAccessibilityElement: {
|
187
|
+
accessibility_label: :ignore,
|
188
|
+
accessibility_traits: :ignore,
|
189
|
+
accessibility_value: String,
|
190
|
+
should_group_accessibility_children: :ignore,
|
191
|
+
is_accessibility_element: :ignore
|
192
|
+
},
|
193
|
+
UITableTextAccessibilityElement: {
|
194
|
+
accessibility_label: [String, "Set the accessibility_label to the text of the view."],
|
195
|
+
accessibility_traits: UIAccessibilityTraitStaticText,
|
196
|
+
accessibility_value: String,
|
197
|
+
},
|
198
|
+
UITableViewHeaderFooterView: {
|
199
|
+
accessibility_label: [String, "Set the accessibility_label to tell VoiceOver what to read. You can do this with the textLabel.text property."]
|
200
|
+
},
|
201
|
+
UITextField: {
|
202
|
+
accessibility_label: nil,
|
203
|
+
accessibility_traits: ->(t){A11y::Test.nonstandard(t, apple: Fixnum)},
|
204
|
+
accessibility_value: [:something, "You must set the text of the textfield."],
|
205
|
+
is_accessibility_element: false
|
206
|
+
},
|
207
|
+
UIAccessibilityTextFieldElement: {
|
208
|
+
accessibility_label: nil,
|
209
|
+
accessibility_traits: ->(t){A11y::Test.nonstandard(t, apple: Fixnum)},
|
210
|
+
accessibility_value: [:something, "You must set the text of the textfield."],
|
211
|
+
is_accessibility_element: true
|
212
|
+
},
|
213
|
+
UIToolbar: {
|
214
|
+
accessibility_label: nil,
|
215
|
+
accessibility_traits: ->(t){A11y::Test.nonstandard(t)},
|
216
|
+
should_group_accessibility_children: true,
|
217
|
+
is_accessibility_element:false,
|
218
|
+
options: {
|
219
|
+
test: :bar
|
220
|
+
}
|
221
|
+
},
|
222
|
+
UIToolbarButton: {
|
223
|
+
accessibility_label: [String, "You must set the accessibility_label. You can do this by setting the UIBarButtonItem's title with the setTItle:style:target:action: method."],
|
224
|
+
accessibility_traits: UIAccessibilityTraitButton
|
225
|
+
},
|
226
|
+
UIView: {
|
227
|
+
accessibility_label: nil,
|
228
|
+
is_accessibility_element: false
|
229
|
+
},
|
230
|
+
UIViewController: {
|
231
|
+
accessibility_label: nil,
|
232
|
+
is_accessibility_element: false,
|
233
|
+
options: {
|
234
|
+
test: :viewController
|
235
|
+
}
|
236
|
+
},
|
237
|
+
UIViewControllerWrapperView: {
|
238
|
+
accessibility_label: nil,
|
239
|
+
should_group_accessibility_children: true,
|
240
|
+
is_accessibility_element: false
|
241
|
+
},
|
242
|
+
UIWebView: {
|
243
|
+
accessibility_label: nil,
|
244
|
+
is_accessibility_element: false,
|
245
|
+
options: {
|
246
|
+
recurse: false
|
247
|
+
}
|
248
|
+
},
|
249
|
+
UIWindow: {
|
250
|
+
accessibility_label: nil,
|
251
|
+
is_accessibility_element: false,
|
252
|
+
options: {
|
253
|
+
test: :window
|
254
|
+
}
|
255
|
+
}
|
256
|
+
}
|
257
|
+
|
258
|
+
def self.debug
|
259
|
+
Data[:debug]
|
260
|
+
end
|
261
|
+
def self.debug=(d)
|
262
|
+
Data[:debug]=d
|
263
|
+
end
|
264
|
+
|
265
|
+
def self.nonstandard(value, options={})
|
266
|
+
options[:apple]||=Bignum
|
267
|
+
options[:custom]||=:none.accessibility_trait
|
268
|
+
return true if value.class==options[:apple]||value==options[:custom]
|
269
|
+
options[:attribute]||=:accessibility_traits
|
270
|
+
message="Apple has this set to a non-standard value."
|
271
|
+
options[:message]||="Hopefully you can get away with using :none."
|
272
|
+
options[:message]="#{options[:attribute]}: #{message} #{options[:message]}"
|
273
|
+
A11y::Test::Log.add(Path, options[:message])
|
274
|
+
false
|
275
|
+
end
|
276
|
+
|
277
|
+
def self.application(app)
|
278
|
+
self.run_tests(app.keyWindow)
|
279
|
+
end
|
280
|
+
|
281
|
+
def self.bar(obj)
|
282
|
+
result=true
|
283
|
+
obj.items {|item| result=result&&self.run_tests(item)}
|
284
|
+
result
|
285
|
+
end
|
286
|
+
|
287
|
+
def self.container(container)
|
288
|
+
container.accessibility_element_count.times {|n| return false unless container.accessibility_element_at_index(n).accessible?}
|
289
|
+
true
|
290
|
+
end
|
291
|
+
|
292
|
+
def self.navigationViewController(controller)
|
293
|
+
result=true
|
294
|
+
result&&self.run_tests(controller.navigationBar)
|
295
|
+
controller.viewControllers.each {|c| result=result&&self.run_tests(c)}
|
296
|
+
result
|
297
|
+
end
|
298
|
+
|
299
|
+
def self.pickerView(picker)
|
300
|
+
result=true
|
301
|
+
picker.numberOfComponents.times do |component|
|
302
|
+
picker.numberOfRowsInComponent(component).times do |row|
|
303
|
+
title=picker.delegate.pickerView(picker, titleForRow: row, forComponent: component)
|
304
|
+
view=picker. viewForRow(row, forComponent: component)
|
305
|
+
if !title&&!self.run_tests(view)
|
306
|
+
A11y::Test::Log(Path, picker.inspect+": component #{component} row #{row} not accessible. You can use the pickerView:titleForRow:forComponent or pickerView:accessibility_label_for_component methods to do this.")
|
307
|
+
result=false
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
result
|
312
|
+
end
|
313
|
+
|
314
|
+
def self.tabBarViewController(controller)
|
315
|
+
result=true
|
316
|
+
result&&self.run_tests(controller.tabBar)
|
317
|
+
controller.viewControllers.each {|c| result=result&&self.run_tests(c)}
|
318
|
+
result
|
319
|
+
end
|
320
|
+
|
321
|
+
def self.tableViewCell(cell)
|
322
|
+
return true if cell.accessibility_label||cell.textLabel.text
|
323
|
+
A11y::Test::Log.add(Path, "Please set the accessibility_label of the UITableViewCell. You can do this by setting the textLabel.text property.")
|
324
|
+
false
|
325
|
+
end
|
326
|
+
|
327
|
+
def self.viewController(controller)
|
328
|
+
self.run_tests(controller.view)
|
329
|
+
end
|
330
|
+
|
331
|
+
def self.window(window)
|
332
|
+
self.run_tests(window.rootViewController)
|
333
|
+
end
|
334
|
+
|
335
|
+
def self.find_tests(obj)
|
336
|
+
return Tests[obj] if obj.kind_of?(Symbol)
|
337
|
+
obj_tests=A11y::Test::Tests[:NSObject].clone
|
338
|
+
cl=obj.class
|
339
|
+
class_name=cl.to_s.to_sym
|
340
|
+
if obj.accessibility_test
|
341
|
+
tests=Tests[obj.accessibility_test]
|
342
|
+
else
|
343
|
+
tests=Tests[class_name]
|
344
|
+
until tests do
|
345
|
+
cl=cl.superclass
|
346
|
+
class_name=cl.to_s.to_sym
|
347
|
+
tests=Tests[class_name]
|
348
|
+
end
|
349
|
+
end
|
350
|
+
return self.find_tests(tests) if tests.kind_of?(Symbol)
|
351
|
+
tests.each do |attribute, test|
|
352
|
+
obj_tests[attribute]=test
|
353
|
+
end
|
354
|
+
obj_tests
|
355
|
+
end
|
356
|
+
|
357
|
+
def self.run_test(obj, attribute, expected, message=nil)
|
358
|
+
return true if expected==:ignore
|
359
|
+
value=obj.send(attribute) if obj.respond_to?(attribute)
|
360
|
+
return value if expected==:something
|
361
|
+
result=true
|
362
|
+
if expected.class==Class
|
363
|
+
if value.class!=expected
|
364
|
+
result=false
|
365
|
+
message||="#{attribute} must have an object of type #{expected} instead of #{value}"
|
366
|
+
end
|
367
|
+
elsif expected.kind_of?(Proc)
|
368
|
+
r=expected.call(value)
|
369
|
+
unless r
|
370
|
+
result=false
|
371
|
+
message||="The test function for #{attribute} failed."
|
372
|
+
end
|
373
|
+
else
|
374
|
+
unless expected==value
|
375
|
+
result=false
|
376
|
+
message||="#{attribute} must have the value \"#{expected}\" instead of \"#{value}\""
|
377
|
+
end
|
378
|
+
end
|
379
|
+
A11y::Test::Log.add(Path, message) unless result
|
380
|
+
puts "Testing #{attribute}... #{result}" if Data[:debug]
|
381
|
+
result
|
382
|
+
end
|
383
|
+
|
384
|
+
def self.run_tests(obj)
|
385
|
+
puts "Entering run_tests: #{obj.inspect}" if Data[:debug]
|
386
|
+
if Data[:depth]==0
|
387
|
+
A11y::Test::Log::Events.clear
|
388
|
+
Path.clear
|
389
|
+
end
|
390
|
+
Path<<obj
|
391
|
+
tests=self.find_tests(obj)
|
392
|
+
tests[:options]||={}
|
393
|
+
tests[:options]=self::Options.merge(tests[:options])
|
394
|
+
result=true
|
395
|
+
tests.each do |attribute, test|
|
396
|
+
next if attribute==:options
|
397
|
+
if test.kind_of?(Array)
|
398
|
+
(expected, message)=test
|
399
|
+
this_result=self.run_test(obj, attribute, expected, message)
|
400
|
+
else
|
401
|
+
this_result=self.run_test(obj, attribute, test)
|
402
|
+
end
|
403
|
+
result=result&&this_result
|
404
|
+
end
|
405
|
+
after=tests[:options][:test]
|
406
|
+
if after&&self.respond_to?(after)
|
407
|
+
puts "Running the after test: #{after}" if Data[:debug]
|
408
|
+
Data[:depth]=Data[:depth]+1
|
409
|
+
this_result=self.send(after, obj)
|
410
|
+
result=result&&this_result
|
411
|
+
Data[:depth]=Data[:depth]-1
|
412
|
+
end
|
413
|
+
if result&&A11y::Element.container?(obj)
|
414
|
+
result=result&&self.container(obj)
|
415
|
+
end
|
416
|
+
if result&&tests[:options][:recurse]&&obj.respond_to?(:subviews)&&obj.subviews
|
417
|
+
puts "Recursing..." if Data[:debug]
|
418
|
+
Data[:depth]=Data[:depth]+1
|
419
|
+
obj.subviews.each {|view| result=result&&A11y::Test.run_tests(view)}
|
420
|
+
Data[:depth]=Data[:depth]-1
|
421
|
+
end
|
422
|
+
Path.pop
|
423
|
+
puts "Returning #{result}" if Data[:debug]
|
424
|
+
result
|
425
|
+
end
|
426
|
+
|
427
|
+
end
|
428
|
+
|
429
|
+
def self.doctor(view=nil)
|
430
|
+
view.accessible? if view
|
431
|
+
return if A11y::Test::Log::Events.empty?
|
432
|
+
A11y::Test::Log::Events.each do |event|
|
433
|
+
NSLog(event.to_s)
|
434
|
+
end
|
435
|
+
A11y::Test::Log::Events.last.path.last
|
436
|
+
end
|
437
|
+
|
438
|
+
end
|
439
|
+
|
440
|
+
class NSObject
|
441
|
+
|
442
|
+
attr_reader :accessibility_test
|
443
|
+
|
444
|
+
def accessibility_test=(t)
|
445
|
+
t=t.to_s.to_sym if t.kind_of?(Class)
|
446
|
+
@accessibility_test=t if A11y::Test::Tests[t]
|
447
|
+
@accessibility_test
|
448
|
+
end
|
449
|
+
|
450
|
+
def accessible?
|
451
|
+
A11y::Test::Data[:depth]=0
|
452
|
+
A11y::Test.run_tests(self)
|
453
|
+
end
|
454
|
+
|
455
|
+
end
|
456
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Accessibility
|
2
|
+
class Test
|
3
|
+
class Log
|
4
|
+
|
5
|
+
Events=Array.new
|
6
|
+
attr_reader :path, :message
|
7
|
+
|
8
|
+
def initialize(path, message)
|
9
|
+
@path=path||Array.new
|
10
|
+
@message=message
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
@path.join(" -> ")+": "+@message
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.add(path, name)
|
18
|
+
event=self.new(path.clone, name)
|
19
|
+
Events<<event
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: motion-accessibility
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '
|
4
|
+
version: '3.0'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Austin Seraphin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-05-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -24,7 +24,8 @@ dependencies:
|
|
24
24
|
- - '>='
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
-
description:
|
27
|
+
description: A RubyMotion gem to make iOS accessibility easier for the sighted and
|
28
|
+
blind developer
|
28
29
|
email:
|
29
30
|
- austin@austinseraphin.com
|
30
31
|
executables: []
|
@@ -44,6 +45,8 @@ files:
|
|
44
45
|
- lib/project/object.rb
|
45
46
|
- lib/project/picker.rb
|
46
47
|
- lib/project/status.rb
|
48
|
+
- lib/project/test.rb
|
49
|
+
- lib/project/test_log.rb
|
47
50
|
homepage: https://github.com/austinseraphin/motion-accessibility
|
48
51
|
licenses:
|
49
52
|
- GPL
|
@@ -64,9 +67,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
64
67
|
version: '0'
|
65
68
|
requirements: []
|
66
69
|
rubyforge_project:
|
67
|
-
rubygems_version: 2.0.
|
70
|
+
rubygems_version: 2.0.6
|
68
71
|
signing_key:
|
69
72
|
specification_version: 4
|
70
73
|
summary: This gem provides easy ruby-like wrappers around the protocols which interact
|
71
|
-
with VoiceOver and other assistive technologies.
|
74
|
+
with VoiceOver and other assistive technologies. It has an accessibiity inspector,
|
75
|
+
a text console, and automated testing. Making accessibility accessible!
|
72
76
|
test_files: []
|