@@ -10,108 +10,188 @@ module.exports = function(grunt) {
10
10
'aria-supported' ,
11
11
'Task for generating a diff of supported aria roles and properties.' ,
12
12
function ( ) {
13
- const entry = this . data . entry ;
14
- const destFile = this . data . destFile ;
15
- const listType = this . data . listType . toLowerCase ( ) ;
16
-
17
13
/**
18
- * `axe` has to be dynamically required at this stage, as `axe` does not exist until grunt task `build:uglify` is complete, and hence cannot be required at the top of the file.
14
+ * NOTE:
15
+ * `axe` has to be dynamically required at this stage,
16
+ * as `axe` does not exist until grunt task `build:uglify` is complete,
17
+ * hence cannot be required at the top of the file.
19
18
*/
20
19
const axe = require ( '../../axe' ) ;
20
+ const listType = this . data . listType . toLowerCase ( ) ;
21
+ const headings = {
22
+ main :
23
+ `# ARIA Roles and Attributes ${
24
+ listType === 'all' ? 'available' : listType
25
+ } in axe-core.\n\n` +
26
+ 'It can be difficult to know which features of web technologies are accessible across ' +
27
+ 'different platforms, and with different screen readers and other assistive technologies. ' +
28
+ 'Axe-core does some of this work for you, by raising issues when accessibility features are ' +
29
+ 'used that are known to cause problems.\n\n' +
30
+ 'This page contains a list of ARIA 1.1 features that axe-core raises as unsupported. ' +
31
+ 'For more information, read [We’ve got your back with “Accessibility Supported” in axe]' +
32
+ '(https://www.deque.com/blog/weve-got-your-back-with-accessibility-supported-in-axe/).\n\n' +
33
+ 'For a detailed description about how accessibility support is decided, see [How we make ' +
34
+ 'decisions on rules](accessibility-supported.md).' ,
35
+ rolesMdTableHeader : [ 'aria-role' , 'axe-core support' ] ,
36
+ attributesMdTableHeader : [ 'aria-attribute' , 'axe-core support' ]
37
+ } ;
38
+
39
+ const { diff : rolesTable , notes : rolesFootnotes } = getDiff (
40
+ roles ,
41
+ axe . commons . aria . lookupTable . role ,
42
+ listType
43
+ ) ;
44
+ const rolesTableMarkdown = mdTable ( [
45
+ headings . rolesMdTableHeader ,
46
+ ...rolesTable
47
+ ] ) ;
48
+
49
+ const ariaQueryAriaAttributes = getAriaQueryAttributes ( ) ;
50
+ const { diff : attributesTable , notes : attributesFootnotes } = getDiff (
51
+ ariaQueryAriaAttributes ,
52
+ axe . commons . aria . lookupTable . attributes ,
53
+ listType
54
+ ) ;
55
+ const attributesTableMarkdown = mdTable ( [
56
+ headings . attributesMdTableHeader ,
57
+ ...attributesTable
58
+ ] ) ;
59
+
60
+ const footnotes = [ ...rolesFootnotes , ...attributesFootnotes ] . map (
61
+ ( footnote , index ) => `[^${ index + 1 } ]: ${ footnote } `
62
+ ) ;
63
+
64
+ const content = `${
65
+ headings . main
66
+ } \n\n## Roles\n\n${ rolesTableMarkdown } \n\n## Attributes\n\n${ attributesTableMarkdown } \n\n${ footnotes } `;
67
+
68
+ const destFile = this . data . destFile ;
69
+ // Format the content so Prettier doesn't create a diff after running.
70
+ // See https://github.com/dequelabs/axe-core/issues/1310.
71
+ const formattedContent = format ( content , destFile ) ;
72
+
73
+ // write `aria supported` file contents
74
+ grunt . file . write ( destFile , formattedContent ) ;
21
75
22
76
/**
23
- * As `aria-query` roles map, does not list all aria attributes in its props,
24
- * the below reduce function aims to concatanate and unique the below two,
25
- * - list from props with in roles map
26
- * - list from aria map
27
- *
28
- * @return {Map } `aQaria` - This gives a composite list of aria attributes, which is later used to diff against axe-core supported attributes.
77
+ * Get list of aria attributes, from `aria-query`
78
+ * @returns {Set|Object } collection of aria attributes from `aria-query` module
29
79
*/
30
- const ariaKeys = Array . from ( props ) . map ( ( [ key ] ) => key ) ;
31
- const roleAriaKeys = Array . from ( roles ) . reduce ( ( out , [ name , rule ] ) => {
32
- return [ ...out , ...Object . keys ( rule . props ) ] ;
33
- } , [ ] ) ;
34
- const aQaria = new Set ( axe . utils . uniqueArray ( roleAriaKeys , ariaKeys ) ) ;
80
+ function getAriaQueryAttributes ( ) {
81
+ const ariaKeys = Array . from ( props ) . map ( ( [ key ] ) => key ) ;
82
+ const roleAriaKeys = Array . from ( roles ) . reduce ( ( out , [ name , rule ] ) => {
83
+ return [ ...out , ...Object . keys ( rule . props ) ] ;
84
+ } , [ ] ) ;
85
+ return new Set ( axe . utils . uniqueArray ( roleAriaKeys , ariaKeys ) ) ;
86
+ }
35
87
36
88
/**
37
89
* Given a `base` Map and `subject` Map object,
38
90
* The function converts the `base` Map entries to an array which is sorted then enumerated to compare each entry against the `subject` Map
39
- * The function constructs a `string` to represent a `markdown table` to
91
+ * The function constructs a `string` to represent a `markdown table`, as well as returns notes to append to footnote
40
92
* @param {Map } base Base Map Object
41
93
* @param {Map } subject Subject Map Object
42
- * @return {Array[] } Example Output: [ [ 'alert', 'No' ], [ 'figure', 'Yes' ] ]
94
+ * @param {String } type type to compare
95
+ * @returns {Array<Object>[] }
96
+ * @example Example Output: [ [ 'alert', 'No' ], [ 'figure', 'Yes' ] ]
43
97
*/
44
- const getDiff = ( base , subject ) => {
45
- return Array . from ( base . entries ( ) )
46
- . sort ( )
47
- . reduce ( ( out , [ key ] = item ) => {
48
- switch ( listType ) {
49
- case 'supported' :
50
- if (
51
- subject . hasOwnProperty ( key ) &&
52
- subject [ key ] . unsupported === false
53
- ) {
54
- out . push ( [ `${ key } ` , 'Yes' ] ) ;
55
- }
56
- break ;
57
- case 'unsupported' :
58
- if (
59
- ( subject [ key ] && subject [ key ] . unsupported === true ) ||
60
- ! subject . hasOwnProperty ( key )
61
- ) {
62
- out . push ( [ `${ key } ` , 'No' ] ) ;
98
+ function getDiff ( base , subject , type ) {
99
+ const diff = [ ] ;
100
+ const notes = [ ] ;
101
+
102
+ const sortedBase = Array . from ( base . entries ( ) ) . sort ( ) ;
103
+
104
+ sortedBase . forEach ( ( [ key ] = item ) => {
105
+ switch ( type ) {
106
+ case 'supported' :
107
+ if (
108
+ subject . hasOwnProperty ( key ) &&
109
+ subject [ key ] . unsupported === false
110
+ ) {
111
+ diff . push ( [ `${ key } ` , 'Yes' ] ) ;
112
+ }
113
+ break ;
114
+ case 'unsupported' :
115
+ if (
116
+ ( subject [ key ] && subject [ key ] . unsupported === true ) ||
117
+ ! subject . hasOwnProperty ( key )
118
+ ) {
119
+ diff . push ( [ `${ key } ` , 'No' ] ) ;
120
+ } else if (
121
+ subject [ key ] &&
122
+ subject [ key ] . unsupported &&
123
+ subject [ key ] . unsupported . exceptions
124
+ ) {
125
+ diff . push ( [ `${ key } ` , `Mixed[^${ notes . length + 1 } ]` ] ) ;
126
+ notes . push (
127
+ getSupportedElementsAsFootnote (
128
+ subject [ key ] . unsupported . exceptions
129
+ )
130
+ ) ;
131
+ }
132
+ break ;
133
+ case 'all' :
134
+ default :
135
+ diff . push ( [
136
+ `${ key } ` ,
137
+ subject . hasOwnProperty ( key ) &&
138
+ subject [ key ] . unsupported === false
139
+ ? 'Yes'
140
+ : 'No'
141
+ ] ) ;
142
+ break ;
143
+ }
144
+ } ) ;
145
+
146
+ return {
147
+ diff,
148
+ notes
149
+ } ;
150
+ }
151
+
152
+ /**
153
+ * Parse a list of unsupported exception elements and add a footnote
154
+ * detailing which HTML elements are supported.
155
+ *
156
+ * @param {Array<String|Object> } elements List of supported elements
157
+ * @returns {Array<String|Object> } notes
158
+ */
159
+ function getSupportedElementsAsFootnote ( elements ) {
160
+ const notes = [ ] ;
161
+
162
+ const supportedElements = elements . map ( element => {
163
+ if ( typeof element === 'string' ) {
164
+ return `\`<${ element } >\`` ;
165
+ }
166
+
167
+ /**
168
+ * if element is not a string it will be an object with structure:
169
+ {
170
+ nodeName: string,
171
+ properties: {
172
+ type: {string|string[]}
63
173
}
64
- break ;
65
- case 'all' :
66
- default :
67
- out . push ( [
68
- `${ key } ` ,
69
- subject . hasOwnProperty ( key ) &&
70
- subject [ key ] . unsupported === false
71
- ? 'Yes'
72
- : 'No'
73
- ] ) ;
74
- break ;
174
+ }
175
+ */
176
+ return Object . keys ( element . properties ) . map ( prop => {
177
+ const value = element . properties [ prop ] ;
178
+
179
+ // the 'type' property can be a string or an array
180
+ if ( typeof value === 'string' ) {
181
+ return `\`<${ element . nodeName } ${ prop } ="${ value } ">\`` ;
75
182
}
76
- return out ;
77
- } , [ ] ) ;
78
- } ;
79
183
80
- const getMdContent = ( heading , rolesTable , attributesTable ) => {
81
- return `${ heading } \n\n## Roles\n\n${ rolesTable } \n\n## Attributes\n\n${ attributesTable } ` ;
82
- } ;
184
+ // output format for an array of types:
185
+ // <input type="button" | "checkbox">
186
+ const values = value . map ( v => `"${ v } "` ) . join ( ' | ' ) ;
187
+ return `\`<${ element . nodeName } ${ prop } =${ values } >\`` ;
188
+ } ) ;
189
+ } ) ;
83
190
84
- const generateDoc = ( ) => {
85
- const content = getMdContent (
86
- `# ARIA Roles and Attributes ${
87
- listType === 'all' ? 'available' : listType
88
- } in axe-core.\n\n` +
89
- 'It can be difficult to know which features of web technologies are accessible across ' +
90
- 'different platforms, and with different screen readers and other assistive technologies. ' +
91
- 'Axe-core does some of this work for you, by raising issues when accessibility features are ' +
92
- 'used that are known to cause problems.\n\n' +
93
- 'This page contains a list of ARIA 1.1 features that axe-core raises as unsupported. ' +
94
- 'For more information, read [We’ve got your back with “Accessibility Supported” in axe]' +
95
- '(https://www.deque.com/blog/weve-got-your-back-with-accessibility-supported-in-axe/).\n\n' +
96
- 'For a detailed description about how accessibility support is decided, see [How we make ' +
97
- 'decisions on rules](accessibility-supported.md).' ,
98
- mdTable ( [
99
- [ 'aria-role' , 'axe-core support' ] ,
100
- ...getDiff ( roles , axe . commons . aria . lookupTable . role )
101
- ] ) ,
102
- mdTable ( [
103
- [ 'aria-attribute' , 'axe-core support' ] ,
104
- ...getDiff ( aQaria , axe . commons . aria . lookupTable . attributes )
105
- ] )
106
- ) ;
107
-
108
- // Format the content so Prettier doesn't create a diff after running.
109
- // See https://github.com/dequelabs/axe-core/issues/1310.
110
- const formattedContent = format ( content , destFile ) ;
111
- grunt . file . write ( destFile , formattedContent ) ;
112
- } ;
191
+ notes . push ( 'Supported on elements: ' + supportedElements . join ( ', ' ) ) ;
113
192
114
- generateDoc ( ) ;
193
+ return notes ;
194
+ }
115
195
}
116
196
) ;
117
197
} ;
0 commit comments