Skip to content

Commit

Permalink
Merge pull request #4376 from preactjs/debug/button-anchor-nesting
Browse files Browse the repository at this point in the history
debug: Provide error for illegal nesting of <button> and <a>
  • Loading branch information
rschristian committed May 9, 2024
2 parents c29caa3 + 06a2df1 commit bfdd189
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 1 deletion.
18 changes: 17 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 @@ -420,6 +426,16 @@ export function initDebug() {
`\n\n${getOwnerStack(vnode)}`
);
}
} else if (type === 'a' || type === 'button') {
if (getDomChildren(vnode).indexOf(type) !== -1) {
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 @@ -667,6 +667,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 bfdd189

Please sign in to comment.