Skip to content

Commit c5f68b9

Browse files
committed
[chore]: tests, getAttributes clean-up
1 parent f43e0c7 commit c5f68b9

File tree

2 files changed

+130
-44
lines changed

2 files changed

+130
-44
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import test from 'tape';
2+
3+
import getAttributes from '../../../src/util/getAttributes';
4+
import JSXElementMock from '../../../__mocks__/JSXElementMock';
5+
import JSXAttributeMock from '../../../__mocks__/JSXAttributeMock';
6+
7+
const componentsSettings = {
8+
'jsx-a11y': {
9+
components: {
10+
Button: 'button',
11+
MyInput: {
12+
component: 'input',
13+
attributes: {
14+
type: ['myType'],
15+
},
16+
},
17+
Link: {
18+
component: 'a',
19+
attributes: {
20+
href: ['to', 'href'],
21+
onClick: ['onClick', 'handleClick'],
22+
},
23+
},
24+
},
25+
},
26+
};
27+
28+
test('getAttributes', (t) => {
29+
t.test('no settings in context', (st) => {
30+
st.deepEqual(
31+
getAttributes(
32+
JSXElementMock('input').openingElement,
33+
'input',
34+
{},
35+
),
36+
[],
37+
'returns no attributes',
38+
);
39+
40+
st.deepEqual(
41+
getAttributes(
42+
JSXElementMock('input', [JSXAttributeMock('type', 'text')]).openingElement,
43+
'input',
44+
{},
45+
),
46+
[JSXAttributeMock('type', 'text')],
47+
'returns raw attributes if no settings are provided',
48+
);
49+
50+
st.end();
51+
});
52+
53+
t.test('components settings in context', (st) => {
54+
st.deepEqual(
55+
getAttributes(
56+
JSXElementMock('MyInput', [JSXAttributeMock('type', 'text')]).openingElement,
57+
'input',
58+
{ settings: componentsSettings },
59+
),
60+
[JSXAttributeMock('myType', 'text')],
61+
'should expect a custom attribute name mapping to `myType`',
62+
);
63+
64+
st.deepEqual(
65+
getAttributes(
66+
JSXElementMock('input', [JSXAttributeMock('type', 'text')]).openingElement,
67+
'input',
68+
{ settings: componentsSettings },
69+
),
70+
[JSXAttributeMock('type', 'text')],
71+
'should not expect a custom attribute name when native element is used',
72+
);
73+
74+
st.deepEqual(
75+
getAttributes(
76+
JSXElementMock('Button', [JSXAttributeMock('type', 'text')]).openingElement,
77+
'button',
78+
{ settings: componentsSettings },
79+
),
80+
[JSXAttributeMock('type', 'text')],
81+
'should not expect a custom attribute name when no custom attributes configured',
82+
);
83+
84+
st.deepEqual(
85+
getAttributes(
86+
JSXElementMock('Link', [JSXAttributeMock('href', '/path'), JSXAttributeMock('onClick', 'handler')]).openingElement,
87+
'a',
88+
{ settings: componentsSettings },
89+
),
90+
[JSXAttributeMock('to', '/path'), JSXAttributeMock('href', '/path'), JSXAttributeMock('onClick', 'handler'), JSXAttributeMock('handleClick', 'handler')],
91+
'should map multiple attributes according to configuration',
92+
);
93+
94+
st.end();
95+
});
96+
97+
t.end();
98+
});

src/util/getAttributes.js

Lines changed: 32 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,35 @@
1-
import { getProp } from 'jsx-ast-utils';
2-
3-
const getAttributes = (node, type, context) => {
4-
const { settings } = context;
5-
const { attributes } = node;
6-
const components = settings['jsx-a11y']?.components;
7-
8-
if (!components || !type) {
9-
return attributes;
10-
}
11-
12-
const componentConfig = Object.entries(components).find(([, config]) => (
13-
config && typeof config === 'object' && config.component === type
14-
));
15-
16-
if (!componentConfig) {
17-
return attributes;
18-
}
19-
20-
const [, config] = componentConfig;
21-
const attributeMap = config && typeof config === 'object' ? config.attributes : null;
22-
23-
if (!attributeMap || typeof attributeMap !== 'object') {
24-
return attributes;
25-
}
26-
27-
const mappedAttributes = [...attributes];
28-
29-
Object.entries(attributeMap).forEach(([originalAttr, mappedAttrs]) => {
30-
if (Array.isArray(mappedAttrs)) {
31-
mappedAttrs.forEach((mappedAttr) => {
32-
const mappedProp = getProp(attributes, mappedAttr);
33-
if (mappedProp) {
34-
const newAttribute = {
35-
...mappedProp,
36-
name: {
37-
...mappedProp.name,
38-
name: originalAttr,
39-
},
40-
};
41-
mappedAttributes.push(newAttribute);
42-
}
43-
});
44-
}
1+
// @flow
2+
3+
import type { Node, JSXOpeningElement } from 'ast-types-flow';
4+
import { getProp, elementType } from 'jsx-ast-utils';
5+
import type { ESLintContext } from '../../flow/eslint';
6+
7+
const getAttributes = (node: JSXOpeningElement, type: string, context: ESLintContext): Node[] => {
8+
const { attributes: rawAttributes } = node;
9+
const { components } = context?.settings?.['jsx-a11y'] || {};
10+
11+
if (!type || !components) return rawAttributes;
12+
13+
const jsxElementName = elementType(node);
14+
const componentConfig = components[jsxElementName];
15+
16+
if (!componentConfig || componentConfig.component !== type) return rawAttributes;
17+
18+
const { attributes: settingsAttributes } = typeof componentConfig === 'object' ? componentConfig : {};
19+
20+
if (!settingsAttributes || typeof settingsAttributes !== 'object') return rawAttributes;
21+
22+
const mappedAttributes = Object.entries(settingsAttributes).flatMap(([originalAttr, mappedAttrs]) => {
23+
if (!Array.isArray(mappedAttrs)) return [];
24+
25+
const originalProp = getProp(rawAttributes, originalAttr);
26+
return originalProp ? mappedAttrs.map((mappedAttr) => ({
27+
...originalProp,
28+
name: {
29+
...originalProp.name,
30+
name: mappedAttr,
31+
},
32+
})) : [];
4533
});
4634

4735
return mappedAttributes;

0 commit comments

Comments
 (0)