123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- /**
- * @flow
- */
- import { dom, roles } from 'aria-query';
- import includes from 'array-includes';
- import JSXAttributeMock from './JSXAttributeMock';
- import JSXElementMock from './JSXElementMock';
- import type { TJSXElementMock } from './JSXElementMock';
- const domElements = [...dom.keys()];
- const roleNames = [...roles.keys()];
- const interactiveElementsMap = {
- a: [{ prop: 'href', value: '#' }],
- area: [{ prop: 'href', value: '#' }],
- audio: [],
- button: [],
- canvas: [],
- datalist: [],
- embed: [],
- input: [],
- 'input[type="button"]': [{ prop: 'type', value: 'button' }],
- 'input[type="checkbox"]': [{ prop: 'type', value: 'checkbox' }],
- 'input[type="color"]': [{ prop: 'type', value: 'color' }],
- 'input[type="date"]': [{ prop: 'type', value: 'date' }],
- 'input[type="datetime"]': [{ prop: 'type', value: 'datetime' }],
- 'input[type="email"]': [{ prop: 'type', value: 'email' }],
- 'input[type="file"]': [{ prop: 'type', value: 'file' }],
- 'input[type="image"]': [{ prop: 'type', value: 'image' }],
- 'input[type="month"]': [{ prop: 'type', value: 'month' }],
- 'input[type="number"]': [{ prop: 'type', value: 'number' }],
- 'input[type="password"]': [{ prop: 'type', value: 'password' }],
- 'input[type="radio"]': [{ prop: 'type', value: 'radio' }],
- 'input[type="range"]': [{ prop: 'type', value: 'range' }],
- 'input[type="reset"]': [{ prop: 'type', value: 'reset' }],
- 'input[type="search"]': [{ prop: 'type', value: 'search' }],
- 'input[type="submit"]': [{ prop: 'type', value: 'submit' }],
- 'input[type="tel"]': [{ prop: 'type', value: 'tel' }],
- 'input[type="text"]': [{ prop: 'type', value: 'text' }],
- 'input[type="time"]': [{ prop: 'type', value: 'time' }],
- 'input[type="url"]': [{ prop: 'type', value: 'url' }],
- 'input[type="week"]': [{ prop: 'type', value: 'week' }],
- link: [{ prop: 'href', value: '#' }],
- menuitem: [],
- option: [],
- select: [],
- summary: [],
- // Whereas ARIA makes a distinction between cell and gridcell, the AXObject
- // treats them both as CellRole and since gridcell is interactive, we consider
- // cell interactive as well.
- // td: [],
- th: [],
- tr: [],
- textarea: [],
- video: [],
- };
- const nonInteractiveElementsMap: {[string]: Array<{[string]: string}>} = {
- abbr: [],
- aside: [],
- article: [],
- blockquote: [],
- body: [],
- br: [],
- caption: [],
- dd: [],
- details: [],
- dfn: [],
- dialog: [],
- dir: [],
- dl: [],
- dt: [],
- fieldset: [],
- figcaption: [],
- figure: [],
- footer: [],
- form: [],
- frame: [],
- h1: [],
- h2: [],
- h3: [],
- h4: [],
- h5: [],
- h6: [],
- hr: [],
- iframe: [],
- img: [],
- label: [],
- legend: [],
- li: [],
- main: [],
- mark: [],
- marquee: [],
- menu: [],
- meter: [],
- nav: [],
- ol: [],
- optgroup: [],
- output: [],
- p: [],
- pre: [],
- progress: [],
- ruby: [],
- 'section[aria-label]': [{ prop: 'aria-label' }],
- 'section[aria-labelledby]': [{ prop: 'aria-labelledby' }],
- table: [],
- tbody: [],
- td: [],
- tfoot: [],
- thead: [],
- time: [],
- ul: [],
- };
- const indeterminantInteractiveElementsMap = domElements.reduce(
- (accumulator: { [key: string]: Array<any> }, name: string): { [key: string]: Array<any> } => ({
- ...accumulator,
- [name]: [],
- }),
- {},
- );
- Object.keys(interactiveElementsMap)
- .concat(Object.keys(nonInteractiveElementsMap))
- .forEach((name: string) => delete indeterminantInteractiveElementsMap[name]);
- const abstractRoles = roleNames.filter((role) => roles.get(role).abstract);
- const nonAbstractRoles = roleNames.filter((role) => !roles.get(role).abstract);
- const interactiveRoles = []
- .concat(
- roleNames,
- // 'toolbar' does not descend from widget, but it does support
- // aria-activedescendant, thus in practice we treat it as a widget.
- 'toolbar',
- )
- .filter((role) => !roles.get(role).abstract)
- .filter((role) => roles.get(role).superClass.some((klasses) => includes(klasses, 'widget')));
- const nonInteractiveRoles = roleNames
- .filter((role) => !roles.get(role).abstract)
- .filter((role) => !roles.get(role).superClass.some((klasses) => includes(klasses, 'widget')))
- // 'toolbar' does not descend from widget, but it does support
- // aria-activedescendant, thus in practice we treat it as a widget.
- .filter((role) => !includes(['toolbar'], role));
- export function genElementSymbol(openingElement: Object) {
- return (
- openingElement.name.name + (openingElement.attributes.length > 0
- ? `${openingElement.attributes
- .map((attr) => `[${attr.name.name}="${attr.value.value}"]`)
- .join('')}`
- : ''
- )
- );
- }
- export function genInteractiveElements(): Array<TJSXElementMock> {
- return Object.keys(interactiveElementsMap).map((elementSymbol: string): TJSXElementMock => {
- const bracketIndex = elementSymbol.indexOf('[');
- let name = elementSymbol;
- if (bracketIndex > -1) {
- name = elementSymbol.slice(0, bracketIndex);
- }
- const attributes = interactiveElementsMap[elementSymbol].map(({ prop, value }) => JSXAttributeMock(prop, value));
- return JSXElementMock(name, attributes);
- });
- }
- export function genInteractiveRoleElements(): Array<TJSXElementMock> {
- return [...interactiveRoles, 'button article', 'fakerole button article'].map((value): TJSXElementMock => JSXElementMock(
- 'div',
- [JSXAttributeMock('role', value)],
- ));
- }
- export function genNonInteractiveElements(): Array<TJSXElementMock> {
- return Object.keys(nonInteractiveElementsMap).map((elementSymbol): TJSXElementMock => {
- const bracketIndex = elementSymbol.indexOf('[');
- let name = elementSymbol;
- if (bracketIndex > -1) {
- name = elementSymbol.slice(0, bracketIndex);
- }
- const attributes = nonInteractiveElementsMap[elementSymbol].map(({ prop, value }) => JSXAttributeMock(prop, value));
- return JSXElementMock(name, attributes);
- });
- }
- export function genNonInteractiveRoleElements() {
- return [
- ...nonInteractiveRoles,
- 'article button',
- 'fakerole article button',
- ].map((value) => JSXElementMock('div', [JSXAttributeMock('role', value)]));
- }
- export function genAbstractRoleElements() {
- return abstractRoles.map((value) => JSXElementMock('div', [JSXAttributeMock('role', value)]));
- }
- export function genNonAbstractRoleElements() {
- return nonAbstractRoles.map((value) => JSXElementMock('div', [JSXAttributeMock('role', value)]));
- }
- export function genIndeterminantInteractiveElements(): Array<TJSXElementMock> {
- return Object.keys(indeterminantInteractiveElementsMap).map((name) => {
- const attributes = indeterminantInteractiveElementsMap[name].map(({ prop, value }): TJSXElementMock => JSXAttributeMock(prop, value));
- return JSXElementMock(name, attributes);
- });
- }
|