Skip to content

Commit

Permalink
feat: Provide error log for nesting of <button> and <a>
Browse files Browse the repository at this point in the history
  • Loading branch information
rschristian committed May 9, 2024
1 parent 414c870 commit 32a60db
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 1 deletion.
21 changes: 20 additions & 1 deletion debug/src/debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,13 @@ export function initDebug() {
});
}

if (typeof type === 'string' && (isTableElement(type) || type === 'p')) {
if (
typeof type === 'string' &&
(isTableElement(type) ||
type === 'p' ||
type === 'a' ||
type === 'button')
) {
// Avoid false positives when Preact only partially rendered the
// HTML tree. Whilst we attempt to include the outer DOM in our
// validation, this wouldn't work on the server for
Expand Down Expand Up @@ -421,6 +427,19 @@ export function initDebug() {
`\n\n${getOwnerStack(vnode)}`
);
}
} else if (type === 'a' || type === 'button') {
if (getDomChildren(vnode).find(childType => childType === type)) {
console.error(
'Improper nesting of interactive content. Your <' +
type +
'> should not have ' +
'other ' +
(type === 'a' ? 'anchor' : 'button') +
' tags as child-elements.' +
serializeVNode(vnode) +
`\n\n${getOwnerStack(vnode)}`
);
}
}
}

Expand Down
88 changes: 88 additions & 0 deletions debug/test/browser/debug.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,94 @@ describe('debug', () => {
});
});

describe('button nesting', () => {
it('should not warn on a regular button', () => {
const Button = () => <button>Hello world</button>;

render(<Button />, scratch);
expect(console.error).to.not.be.called;
});

it('should warn for nesting illegal dom-nodes under a button', () => {
const Button = () => (
<button>
<button>Hello world</button>
</button>
);

render(<Button />, scratch);
expect(console.error).to.be.calledOnce;
});

it('should warn for nesting illegal dom-nodes under a button as func', () => {
const ButtonChild = ({ children }) => <button>{children}</button>;
const Button = () => (
<button>
<ButtonChild>Hello world</ButtonChild>
</button>
);

render(<Button />, scratch);
expect(console.error).to.be.calledOnce;
});

it('should not warn for nesting non-interactive content under a button', () => {
const Button = () => (
<button>
<span>Hello </span>
<a>World</a>
</button>
);

render(<Button />, scratch);
expect(console.error).to.not.be.called;
});
});

describe('anchor nesting', () => {
it('should not warn a regular anchor', () => {
const Anchor = () => <a>Hello world</a>;

render(<Anchor />, scratch);
expect(console.error).to.not.be.called;
});

it('should warn for nesting illegal dom-nodes under an anchor', () => {
const Anchor = () => (
<a>
<a>Hello world</a>
</a>
);

render(<Anchor />, scratch);
expect(console.error).to.be.calledOnce;
});

it('should warn for nesting illegal dom-nodes under an anchor as func', () => {
const AnchorChild = ({ children }) => <a>{children}</a>;
const Anchor = () => (
<a>
<AnchorChild>Hello world</AnchorChild>
</a>
);

render(<Anchor />, scratch);
expect(console.error).to.be.calledOnce;
});

it('should not warn for nesting non-interactive content under an anchor', () => {
const Anchor = () => (
<a>
<span>Hello </span>
<button>World</button>
</a>
);

render(<Anchor />, scratch);
expect(console.error).to.not.be.called;
});
});

describe('PropTypes', () => {
beforeEach(() => {
resetPropWarnings();
Expand Down

0 comments on commit 32a60db

Please sign in to comment.