Skip to content

Commit 8f2a22a

Browse files
author
Todd Kloots
committed
[fixed] bug where label assertion didn't account for the label being an image created by a custom component (resolves #52)
[fixed] bug where label assertion wouldn't fail when none of the child Components have label text (resolves #53)
1 parent e6b3df5 commit 8f2a22a

3 files changed

Lines changed: 77 additions & 34 deletions

File tree

lib/__tests__/index-test.js

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ describe('labels', () => {
291291
});
292292
});
293293

294-
it('does not warn when the label text is inside a child component', (done) => {
294+
it('does not warn when the label text is inside a child component', () => {
295295
var Foo = React.createClass({
296296
render: function() {
297297
return (
@@ -303,11 +303,39 @@ describe('labels', () => {
303303
});
304304

305305
doNotExpectWarning(assertions.render.NO_LABEL.msg, () => {
306-
React.render(<div role="button"><span><Foo/></span></div>, fixture, done);
306+
React.render(<div role="button"><span><Foo/></span></div>, fixture);
307307
});
308308
});
309309

310-
it('does not warn when the label is an image with alt text inside a child component', (done) => {
310+
it('does not warn when the label is an image with alt text', () => {
311+
var Foo = React.createClass({
312+
render: function() {
313+
return (
314+
<img alt="foo"/>
315+
);
316+
}
317+
});
318+
319+
doNotExpectWarning(assertions.render.NO_LABEL.msg, () => {
320+
React.render(<div role="button"><Foo/></div>, fixture);
321+
});
322+
});
323+
324+
it('warns when the label is an image without alt text', () => {
325+
var Foo = React.createClass({
326+
render: function() {
327+
return (
328+
<img alt=""/>
329+
);
330+
}
331+
});
332+
333+
expectWarning(assertions.render.NO_LABEL.msg, () => {
334+
React.render(<div role="button"><Foo/></div>, fixture);
335+
});
336+
});
337+
338+
it('does not warn when the label is an image with alt text nested inside a child component', () => {
311339
var Foo = React.createClass({
312340
render: function() {
313341
return (
@@ -319,11 +347,11 @@ describe('labels', () => {
319347
});
320348

321349
doNotExpectWarning(assertions.render.NO_LABEL.msg, () => {
322-
React.render(<div role="button"><span><Foo/></span></div>, fixture, done);
350+
React.render(<div role="button"><span><Foo/></span></div>, fixture);
323351
});
324352
});
325353

326-
it('warns when a child is a component with image content without alt', (done) => {
354+
it('warns when an image without alt text is nested inside a child component', () => {
327355
var Foo = React.createClass({
328356
render: function() {
329357
return (
@@ -335,11 +363,11 @@ describe('labels', () => {
335363
});
336364

337365
expectWarning(assertions.render.NO_LABEL.msg, () => {
338-
React.render(<div role="button"><span><Foo/></span></div>, fixture, done);
366+
React.render(<div role="button"><span><Foo/></span></div>, fixture);
339367
});
340368
});
341369

342-
it('does not warn when there is an image without alt text with a sibling text node', (done) => {
370+
it('does not warn when there is an image without alt text with a sibling text node', () => {
343371
var Foo = React.createClass({
344372
render: function() {
345373
return (
@@ -351,11 +379,11 @@ describe('labels', () => {
351379
});
352380

353381
doNotExpectWarning(assertions.render.NO_LABEL.msg, () => {
354-
React.render(<div role="button"><span><Foo/></span></div>, fixture, done);
382+
React.render(<div role="button"><span><Foo/></span></div>, fixture);
355383
});
356384
});
357385

358-
it('warns when a child is a component without text content', (done) => {
386+
it('warns when a child is a component without text content', () => {
359387
var Bar = React.createClass({
360388
render: () => {
361389
return (
@@ -365,11 +393,11 @@ describe('labels', () => {
365393
});
366394

367395
expectWarning(assertions.render.NO_LABEL.msg, () => {
368-
React.render(<div role="button"><Bar/></div>, fixture, done);
396+
React.render(<div role="button"><Bar/></div>, fixture);
369397
});
370398
});
371399

372-
it('does not warn as long as one child component has label text', (done) => {
400+
it('does not warn as long as one child component has label text', () => {
373401
var Bar = React.createClass({
374402
render: () => {
375403
return (
@@ -389,11 +417,11 @@ describe('labels', () => {
389417
});
390418

391419
doNotExpectWarning(assertions.render.NO_LABEL.msg, () => {
392-
React.render(<div role="button"><Bar/><Foo/></div>, fixture, done);
420+
React.render(<div role="button"><Bar/><Foo/></div>, fixture);
393421
});
394422
});
395423

396-
it('warns if no child components have label text', (done) => {
424+
it('warns if no child components have label text', () => {
397425
var Bar = React.createClass({
398426
render: () => {
399427
return (
@@ -411,12 +439,12 @@ describe('labels', () => {
411439
});
412440

413441
expectWarning(assertions.render.NO_LABEL.msg, () => {
414-
React.render(<div role="button"><Bar/><Foo/></div>, fixture, done);
442+
React.render(<div role="button"><Bar/><div /><Foo/></div>, fixture);
415443
});
416444
});
417445

418446

419-
it('does not error when the component has a componentDidMount callback', (done) => {
447+
it('does not error when the component has a componentDidMount callback', () => {
420448
var Bar = React.createClass({
421449
_privateProp: 'bar',
422450

@@ -431,7 +459,7 @@ describe('labels', () => {
431459
});
432460

433461
expectWarning(assertions.render.NO_LABEL.msg, () => {
434-
React.render(<div role="button"><Bar/></div>, fixture, done);
462+
React.render(<div role="button"><Bar/></div>, fixture);
435463
});
436464
});
437465

lib/assertions.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ var getComponents = (children) => {
4242
};
4343

4444
var hasLabel = (node) => {
45-
var hasTextContent = node.textContent.trim().length > 0;
45+
var text = node.tagName.toLowerCase() == 'img' ? node.alt : node.textContent;
46+
var hasTextContent = text.trim().length > 0;
47+
4648
var images = node.querySelectorAll('img[alt]');
4749
images = Array.prototype.slice.call(images);
4850

@@ -66,7 +68,9 @@ var assertLabel = function(node, context, failureCB) {
6668
var hasChildTextNode = (props, children, failureCB) => {
6769
var hasText = false;
6870
var childComponents = getComponents(children);
69-
var hasChildComponents = childComponents.length > 0;
71+
var nChildComponents = childComponents.length;
72+
var hasChildComponents = nChildComponents > 0;
73+
var nCurrentComponent = 0;
7074
var context;
7175

7276
if (hasChildComponents)
@@ -97,7 +101,7 @@ var hasChildTextNode = (props, children, failureCB) => {
97101
// Return true because the label check is now going to be async
98102
// (due to the componentDidMount listener) and we want to avoid
99103
// pre-maturely calling the failure callback.
100-
hasText = true;
104+
hasText = (nChildComponents == ++nCurrentComponent);
101105
}
102106
});
103107
return hasText;

lib/index.js

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ var runTagTests = (tagName, props, children, deviceFilter, onFailure) => {
1818
!tagTests[key].test(tagName, props, children);
1919

2020
if (tagTests[key] && testFailed)
21-
onFailure(tagName, props, children, tagTests[key].msg);
21+
onFailure(tagName, props, tagTests[key].msg);
2222
}
2323
};
2424

@@ -37,7 +37,7 @@ var runPropTests = (tagName, props, children, deviceFilter, onFailure) => {
3737
!propTests[key].test(tagName, props, children);
3838

3939
if (propTests[key] && testTailed)
40-
onFailure(tagName, props, children, propTests[key].msg);
40+
onFailure(tagName, props, propTests[key].msg);
4141
}
4242
}
4343
};
@@ -49,7 +49,7 @@ var runLabelTests = (tagName, props, children, deviceFilter, onFailure) => {
4949
for (key in renderTests) {
5050
if (renderTests[key]) {
5151
let failureCB = onFailure.bind(
52-
undefined, tagName, props, children, renderTests[key].msg);
52+
undefined, tagName, props, renderTests[key].msg);
5353

5454
renderTests[key].test(tagName, props, children, failureCB);
5555
}
@@ -66,7 +66,7 @@ var runTests = (tagName, props, children, deviceFilter, onFailure) => {
6666
var shouldShowError = (failureInfo, options) => {
6767
var filterFn = options.filterFn;
6868
if (filterFn)
69-
return filterFn(failureInfo.name, failureInfo.id);
69+
return filterFn(failureInfo.tagName, failureInfo.id);
7070

7171
return true;
7272
};
@@ -75,7 +75,7 @@ var throwError = (failureInfo, options) => {
7575
if (!shouldShowError(failureInfo, options))
7676
return;
7777

78-
var error = [failureInfo.name, failureInfo.failure];
78+
var error = [failureInfo.tagName, failureInfo.msg];
7979

8080
if (options.includeSrcNode)
8181
error.push(failureInfo.id);
@@ -95,25 +95,36 @@ var logWarning = (component, failureInfo, options) => {
9595
if (!shouldShowError(failureInfo, options))
9696
return;
9797

98-
var warning = [failureInfo.name, failureInfo.failure];
99-
100-
if (includeSrcNode)
101-
warning.push(document.getElementById(failureInfo.id));
98+
var warning = [failureInfo.tagName, failureInfo.msg];
99+
100+
if (includeSrcNode && component) {
101+
// TODO:
102+
// 1) Consider using React.findDOMNode() over document.getElementById
103+
// https://github.com/rackt/react-a11y/issues/54
104+
// 2) Consider using ref to expand element element reference logging
105+
// to all element (https://github.com/rackt/react-a11y/issues/55)
106+
let srcNode = document.getElementById(failureInfo.id);
107+
108+
// Guard against logging null element references should render()
109+
// return null or false.
110+
// https://facebook.github.io/react/docs/component-api.html#getdomnode
111+
if (srcNode)
112+
warning.push(srcNode);
113+
}
102114

103115
console.warn.apply(console, warning);
104116
};
105117

106-
if (component && includeSrcNode) {
118+
if (includeSrcNode && component)
107119
// Cannot log a node reference until the component is in the DOM,
108120
// so defer the document.getElementById call until componentDidMount
109121
// or componentDidUpdate.
110122
logAfterRender(component._instance, warn);
111-
} else {
123+
else
112124
warn();
113-
}
114125
};
115126

116-
var handleFailure = (options, reactEl, type, props, children, failure) => {
127+
var handleFailure = (options, reactEl, type, props, failureMsg) => {
117128
var includeSrcNode = options && !!options.includeSrcNode;
118129
var reactComponent = reactEl._owner;
119130

@@ -123,9 +134,9 @@ var handleFailure = (options, reactEl, type, props, children, failure) => {
123134
type + '#' + props.id;
124135

125136
var failureInfo = {
126-
'name': name ,
137+
'tagName': name ,
127138
'id': props.id,
128-
'failure': failure
139+
'msg': failureMsg
129140
};
130141

131142
var notifyOpts = {

0 commit comments

Comments
 (0)