Skip to content

Commit bc93f01

Browse files
jessebeachbeefancohen
authored andcommitted
Get coveralls to 100% (#141)
* Add test suite for getTabIndex * Return undefined from getTabIndex instead of null * Add coverage for validityCheck * Remove unnecessary additions to aria-proptypes-test * Get anchor-has-content to 100% coverage * Get heading-has-content to 100% coverage * Fix dangling comma * Updated the yarn.lock with ast-types
1 parent 09254b3 commit bc93f01

13 files changed

+298
-47
lines changed

__mocks__/IdentifierMock.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default function IdentifierMock(ident) {
2+
return {
3+
type: 'Identifier',
4+
name: ident,
5+
};
6+
};

__mocks__/JSXAttributeMock.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import JSXExpressionContainerMock from './JSXExpressionContainerMock';
2+
import toAST from 'to-ast';
3+
4+
export default function JSXAttributeMock (
5+
prop,
6+
value,
7+
isExpressionContainer = false
8+
) {
9+
let astValue;
10+
if (value && value.type !== undefined) {
11+
astValue = value;
12+
} else {
13+
astValue = toAST(value);
14+
}
15+
let attributeValue = astValue;
16+
if (
17+
isExpressionContainer
18+
|| astValue.type !== 'Literal'
19+
) {
20+
attributeValue = JSXExpressionContainerMock(
21+
astValue
22+
);
23+
}
24+
25+
return {
26+
type: 'JSXAttribute',
27+
name: {
28+
type: 'JSXIdentifier',
29+
name: prop,
30+
},
31+
value: attributeValue,
32+
};
33+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default function JSXExpressionContainerMock (exp) {
2+
return {
3+
type: 'JSXExpressionContainer',
4+
expression: exp,
5+
};
6+
};

__tests__/src/rules/anchor-has-content-test.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
// -----------------------------------------------------------------------------
99

1010
import { RuleTester } from 'eslint';
11-
import rule from '../../../src/rules/anchor-has-content';
11+
import rule,
12+
{ determineChildType } from '../../../src/rules/anchor-has-content';
1213

1314
const parserOptions = {
1415
ecmaVersion: 6,
@@ -28,6 +29,16 @@ const expectedError = {
2829
type: 'JSXOpeningElement',
2930
};
3031

32+
describe('determineChildType', () => {
33+
describe('default case', () => {
34+
it('should return false', () => {
35+
expect(
36+
determineChildType({})
37+
).toBe(false);
38+
});
39+
});
40+
});
41+
3142
ruleTester.run('anchor-has-content', rule, {
3243
valid: [
3344
{ code: '<div />;', parserOptions },

__tests__/src/rules/aria-proptypes-test.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import { RuleTester } from 'eslint';
1111
import ariaAttributes from '../../../src/util/attributes/ARIA.json';
12-
import rule from '../../../src/rules/aria-proptypes';
12+
import rule, { validityCheck } from '../../../src/rules/aria-proptypes';
1313

1414
const parserOptions = {
1515
ecmaVersion: 6,
@@ -44,6 +44,15 @@ tokens from the following: ${permittedValues}.`;
4444
}
4545
};
4646

47+
describe('validityCheck', () => {
48+
it('should false for an unknown expected type', () => {
49+
expect(validityCheck(
50+
null,
51+
null,
52+
)).toBe(false);
53+
});
54+
});
55+
4756
ruleTester.run('aria-proptypes', rule, {
4857
valid: [
4958
// DON'T TEST INVALID ARIA-* PROPS

__tests__/src/rules/heading-has-content-test.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
// -----------------------------------------------------------------------------
99

1010
import { RuleTester } from 'eslint';
11-
import rule from '../../../src/rules/heading-has-content';
11+
import rule,
12+
{ determineChildType } from '../../../src/rules/heading-has-content';
1213

1314
const parserOptions = {
1415
ecmaVersion: 6,
@@ -28,6 +29,16 @@ const expectedError = {
2829
type: 'JSXOpeningElement',
2930
};
3031

32+
describe('determineChildType', () => {
33+
describe('default case', () => {
34+
it('should return false', () => {
35+
expect(
36+
determineChildType({})
37+
).toBe(false);
38+
});
39+
});
40+
});
41+
3142
ruleTester.run('heading-has-content', rule, {
3243
valid: [
3344
{ code: '<div />;', parserOptions },
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/* eslint-env mocha */
2+
import getTabIndex from '../../../src/util/getTabIndex';
3+
import IdentifierMock from '../../../__mocks__/IdentifierMock';
4+
import JSXAttributeMock from '../../../__mocks__/JSXAttributeMock';
5+
6+
describe('getTabIndex', () => {
7+
describe('tabIndex is defined', () => {
8+
describe('as a number ', () => {
9+
describe('zero', () => {
10+
it('should return zero', () => {
11+
expect(
12+
getTabIndex(
13+
JSXAttributeMock('tabIndex', 0),
14+
),
15+
).toBe(0);
16+
});
17+
});
18+
describe('positive integer', () => {
19+
it('should return the integer', () => {
20+
expect(
21+
getTabIndex(
22+
JSXAttributeMock('tabIndex', 1),
23+
),
24+
).toBe(1);
25+
});
26+
});
27+
describe('negative integer', () => {
28+
it('should return the integer', () => {
29+
expect(
30+
getTabIndex(
31+
JSXAttributeMock('tabIndex', -1),
32+
),
33+
).toBe(-1);
34+
});
35+
});
36+
describe('float', () => {
37+
it('should return undefined', () => {
38+
expect(
39+
getTabIndex(
40+
JSXAttributeMock('tabIndex', 9.1),
41+
),
42+
).toBeUndefined();
43+
});
44+
});
45+
});
46+
describe('as a string', () => {
47+
describe('empty', () => {
48+
it('should return undefined', () => {
49+
expect(
50+
getTabIndex(
51+
JSXAttributeMock('tabIndex', ''),
52+
),
53+
).toBeUndefined();
54+
});
55+
});
56+
describe('which converts to a number', () => {
57+
it('should return an integer', () => {
58+
expect(
59+
getTabIndex(
60+
JSXAttributeMock('tabIndex', '0'),
61+
),
62+
).toBe(0);
63+
});
64+
});
65+
describe('which is NaN', () => {
66+
it('should return undefined', () => {
67+
expect(
68+
getTabIndex(
69+
JSXAttributeMock('tabIndex', '0a'),
70+
),
71+
).toBeUndefined();
72+
});
73+
});
74+
});
75+
describe('as a boolean', () => {
76+
describe('true', () => {
77+
it('should return undefined', () => {
78+
expect(
79+
getTabIndex(
80+
JSXAttributeMock('tabIndex', true),
81+
),
82+
).toBeUndefined();
83+
});
84+
});
85+
describe('false', () => {
86+
it('should return undefined', () => {
87+
expect(
88+
getTabIndex(
89+
JSXAttributeMock('tabIndex', false),
90+
),
91+
).toBeUndefined();
92+
});
93+
});
94+
});
95+
describe('as an expression', () => {
96+
describe('function expression', () => {
97+
it('should return the correct type', () => {
98+
const attr = function mockFn() { return 0; };
99+
expect(
100+
typeof getTabIndex(
101+
JSXAttributeMock('tabIndex', attr),
102+
),
103+
).toEqual('function');
104+
});
105+
});
106+
describe('variable expression', () => {
107+
it('should return the Identifier name', () => {
108+
const name = 'identName';
109+
expect(
110+
getTabIndex(
111+
JSXAttributeMock(
112+
'tabIndex',
113+
IdentifierMock(name),
114+
true
115+
),
116+
),
117+
).toEqual(name);
118+
});
119+
});
120+
});
121+
});
122+
describe('tabIndex is not defined', () => {
123+
it('should return undefined', () => {
124+
expect(
125+
getTabIndex(
126+
JSXAttributeMock('tabIndex', undefined),
127+
),
128+
).toBeUndefined();
129+
});
130+
});
131+
});

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
"eslint-config-airbnb-base": "^11.0.0",
3838
"eslint-plugin-import": "^2.2.0",
3939
"jest": "^17.0.1",
40-
"rimraf": "^2.5.2"
40+
"rimraf": "^2.5.2",
41+
"to-ast": "^1.0.0"
4142
},
4243
"engines": {
4344
"node": ">=4.0"

src/rules/anchor-has-content.js

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,26 @@ const errorMessage =
1616

1717
const schema = generateObjSchema({ components: arraySchema });
1818

19+
const determineChildType = (child) => {
20+
switch (child.type) {
21+
case 'Literal':
22+
return Boolean(child.value);
23+
case 'JSXElement':
24+
return !isHiddenFromScreenReader(
25+
elementType(child.openingElement),
26+
child.openingElement.attributes);
27+
case 'JSXExpressionContainer':
28+
if (child.expression.type === 'Identifier') {
29+
return child.expression.name !== 'undefined';
30+
}
31+
return true;
32+
default:
33+
return false;
34+
}
35+
};
36+
1937
module.exports = {
38+
determineChildType,
2039
meta: {
2140
docs: {},
2241
schema: [schema],
@@ -33,23 +52,9 @@ module.exports = {
3352
if (typeCheck.indexOf(nodeType) === -1) {
3453
return;
3554
}
36-
const isAccessible = node.parent.children.some((child) => {
37-
switch (child.type) {
38-
case 'Literal':
39-
return Boolean(child.value);
40-
case 'JSXElement':
41-
return !isHiddenFromScreenReader(
42-
elementType(child.openingElement),
43-
child.openingElement.attributes);
44-
case 'JSXExpressionContainer':
45-
if (child.expression.type === 'Identifier') {
46-
return child.expression.name !== 'undefined';
47-
}
48-
return true;
49-
default:
50-
return false;
51-
}
52-
}) || hasAnyProp(node.attributes, ['dangerouslySetInnerHTML', 'children']);
55+
const isAccessible = node.parent.children.some(
56+
determineChildType,
57+
) || hasAnyProp(node.attributes, ['dangerouslySetInnerHTML', 'children']);
5358

5459

5560
if (isAccessible) {

src/rules/aria-proptypes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ const validityCheck = (value, expectedType, permittedValues) => {
5454
const schema = generateObjSchema();
5555

5656
module.exports = {
57+
validityCheck,
5758
meta: {
5859
docs: {},
5960
schema: [schema],

0 commit comments

Comments
 (0)