Skip to content

Commit ad517c7

Browse files
authored
[useButton] Capture component stack for nativeButton error message (#3861)
1 parent 39b7fe8 commit ad517c7

File tree

3 files changed

+20
-11
lines changed

3 files changed

+20
-11
lines changed

packages/react/src/toolbar/button/ToolbarButton.test.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@ describe('<Toolbar.Button />', () => {
107107

108108
expect(console.error).toHaveBeenCalledTimes(1);
109109
expect(console.error).toHaveBeenCalledWith(
110-
'Base UI: A component that acts as a button was not rendered as a native <button>, which does not match the default. Ensure that the element passed to the `render` prop of the component is a real <button>, or set the `nativeButton` prop on the component to `false`.',
110+
expect.stringContaining(
111+
'Base UI: A component that acts as a button was not rendered as a native <button>, which does not match the default. Ensure that the element passed to the `render` prop of the component is a real <button>, or set the `nativeButton` prop on the component to `false`.',
112+
),
111113
);
112114

113115
expect(screen.getByTestId('button')).to.equal(screen.getByRole('switch'));
@@ -131,7 +133,9 @@ describe('<Toolbar.Button />', () => {
131133

132134
expect(console.error).toHaveBeenCalledTimes(1);
133135
expect(console.error).toHaveBeenCalledWith(
134-
'Base UI: A component that acts as a button was not rendered as a native <button>, which does not match the default. Ensure that the element passed to the `render` prop of the component is a real <button>, or set the `nativeButton` prop on the component to `false`.',
136+
expect.stringContaining(
137+
'Base UI: A component that acts as a button was not rendered as a native <button>, which does not match the default. Ensure that the element passed to the `render` prop of the component is a real <button>, or set the `nativeButton` prop on the component to `false`.',
138+
),
135139
);
136140

137141
const switchElement = screen.getByRole('switch');
@@ -175,7 +179,9 @@ describe('<Toolbar.Button />', () => {
175179

176180
expect(console.error).toHaveBeenCalledTimes(1);
177181
expect(console.error).toHaveBeenCalledWith(
178-
'Base UI: A component that acts as a button was not rendered as a native <button>, which does not match the default. Ensure that the element passed to the `render` prop of the component is a real <button>, or set the `nativeButton` prop on the component to `false`.',
182+
expect.stringContaining(
183+
'Base UI: A component that acts as a button was not rendered as a native <button>, which does not match the default. Ensure that the element passed to the `render` prop of the component is a real <button>, or set the `nativeButton` prop on the component to `false`.',
184+
),
179185
);
180186

181187
const switchElement = screen.getByRole('switch');

packages/react/src/use-button/useButton.test.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -283,31 +283,31 @@ describe('useButton', () => {
283283
.spyOn(console, 'error')
284284
.mockName('console.error')
285285
.mockImplementation(() => {});
286+
const message =
287+
'Base UI: A component that acts as a button was not rendered as a native <button>, which does not match the default. Ensure that the element passed to the `render` prop of the component is a real <button>, or set the `nativeButton` prop on the component to `false`.';
286288
function TestButton() {
287289
const { getButtonProps, buttonRef } = useButton({ native: true });
288290
return <span {...getButtonProps()} ref={buttonRef} />;
289291
}
290292
await render(<TestButton />);
291293
expect(errorSpy).toHaveBeenCalledTimes(1);
292-
expect(errorSpy).toHaveBeenCalledWith(
293-
'Base UI: A component that acts as a button was not rendered as a native <button>, which does not match the default. Ensure that the element passed to the `render` prop of the component is a real <button>, or set the `nativeButton` prop on the component to `false`.',
294-
);
294+
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining(message));
295295
});
296296

297297
it('errors if nativeButton=false but ref is a button', async () => {
298298
const errorSpy = vi
299299
.spyOn(console, 'error')
300300
.mockName('console.error')
301301
.mockImplementation(() => {});
302+
const message =
303+
'Base UI: A component that acts as a button was rendered as a native <button>, which does not match the default. Ensure that the element passed to the `render` prop of the component is not a real <button>, or set the `nativeButton` prop on the component to `true`.';
302304
function TestButton() {
303305
const { getButtonProps, buttonRef } = useButton({ native: false });
304306
return <button {...getButtonProps()} ref={buttonRef} />;
305307
}
306308
await render(<TestButton />);
307309
expect(errorSpy).toHaveBeenCalledTimes(1);
308-
expect(errorSpy).toHaveBeenCalledWith(
309-
'Base UI: A component that acts as a button was rendered as a native <button>, which does not match the default. Ensure that the element passed to the `render` prop of the component is not a real <button>, or set the `nativeButton` prop on the component to `true`.',
310-
);
310+
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining(message));
311311
});
312312
});
313313
});

packages/react/src/use-button/useButton.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { isHTMLElement } from '@floating-ui/utils/dom';
44
import { useStableCallback } from '@base-ui/utils/useStableCallback';
55
import { error } from '@base-ui/utils/error';
66
import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';
7+
import { SafeReact } from '@base-ui/utils/safeReact';
78
import { makeEventPreventable, mergeProps } from '../merge-props';
89
import { useCompositeRootContext } from '../composite/root/CompositeRootContext';
910
import { BaseUIEvent, HTMLProps } from '../utils/types';
@@ -45,13 +46,15 @@ export function useButton(parameters: useButton.Parameters = {}): useButton.Retu
4546

4647
if (isNativeButton) {
4748
if (!isButtonTag) {
49+
const ownerStackMessage = SafeReact.captureOwnerStack?.() || '';
4850
error(
49-
'A component that acts as a button was not rendered as a native <button>, which does not match the default. Ensure that the element passed to the `render` prop of the component is a real <button>, or set the `nativeButton` prop on the component to `false`.',
51+
`A component that acts as a button was not rendered as a native <button>, which does not match the default. Ensure that the element passed to the \`render\` prop of the component is a real <button>, or set the \`nativeButton\` prop on the component to \`false\`.${ownerStackMessage}`,
5052
);
5153
}
5254
} else if (isButtonTag) {
55+
const ownerStackMessage = SafeReact.captureOwnerStack?.() || '';
5356
error(
54-
'A component that acts as a button was rendered as a native <button>, which does not match the default. Ensure that the element passed to the `render` prop of the component is not a real <button>, or set the `nativeButton` prop on the component to `true`.',
57+
`A component that acts as a button was rendered as a native <button>, which does not match the default. Ensure that the element passed to the \`render\` prop of the component is not a real <button>, or set the \`nativeButton\` prop on the component to \`true\`.${ownerStackMessage}`,
5558
);
5659
}
5760
}, [isNativeButton]);

0 commit comments

Comments
 (0)