Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 0 additions & 15 deletions packages/@react-aria/combobox/src/useComboBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {getChildNodes, getItemCount} from '@react-stately/collections';
import intlMessages from '../intl/*.json';
import {ListKeyboardDelegate, useSelectableCollection} from '@react-aria/selection';
import {privateValidationStateProp} from '@react-stately/form';
import {useInteractOutside} from '@react-aria/interactions';
import {useLocalizedStringFormatter} from '@react-aria/i18n';
import {useMenuTrigger} from '@react-aria/menu';
import {useTextField} from '@react-aria/textfield';
Expand Down Expand Up @@ -366,20 +365,6 @@ export function useComboBox<T, M extends SelectionMode = 'single'>(props: AriaCo
state.close();
} : undefined);

// usePopover -> useOverlay calls useInteractOutside, but ComboBox is non-modal, so `isDismissable` is false
// Because of this, onInteractOutside is not passed to useInteractOutside, so we need to call it here.
useInteractOutside({
ref: popoverRef,
onInteractOutside: (e) => {
let target = getEventTarget(e) as Element;
if (nodeContains(buttonRef?.current, target) || nodeContains(inputRef.current, target)) {
return;
}
state.close();
},
isDisabled: !state.isOpen
});

return {
labelProps,
buttonProps: {
Expand Down
2 changes: 1 addition & 1 deletion packages/@react-aria/dialog/docs/useDialog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ import {useViewportSize} from '@react-aria/utils';

function Modal({state, children, ...props}) {
let ref = React.useRef(null);
let {modalProps, underlayProps} = useModalOverlay(props, state, ref);
let {modalProps, underlayProps} = useModalOverlay({...props, isDismissable: true}, state, ref);

return (
<Overlay>
Expand Down
13 changes: 1 addition & 12 deletions packages/@react-aria/overlays/src/useOverlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ export function useOverlay(props: AriaOverlayProps, ref: RefObject<Element | nul
if (!shouldCloseOnInteractOutside || shouldCloseOnInteractOutside(getEventTarget(e) as Element)) {
if (topMostOverlay === ref) {
e.stopPropagation();
e.preventDefault();
}
}
};
Expand All @@ -108,7 +107,6 @@ export function useOverlay(props: AriaOverlayProps, ref: RefObject<Element | nul
if (!shouldCloseOnInteractOutside || shouldCloseOnInteractOutside(getEventTarget(e) as Element)) {
if (visibleOverlays[visibleOverlays.length - 1] === ref) {
e.stopPropagation();
e.preventDefault();
}
if (lastVisibleOverlay.current === ref) {
onHide();
Expand Down Expand Up @@ -151,20 +149,11 @@ export function useOverlay(props: AriaOverlayProps, ref: RefObject<Element | nul
}
});

let onPointerDownUnderlay = e => {
// fixes a firefox issue that starts text selection https://bugzilla.mozilla.org/show_bug.cgi?id=1675846
if (getEventTarget(e) === e.currentTarget) {
e.preventDefault();
}
};

return {
overlayProps: {
onKeyDown,
...focusWithinProps
},
underlayProps: {
onPointerDown: onPointerDownUnderlay
}
underlayProps: {}
};
}
11 changes: 0 additions & 11 deletions packages/@react-aria/overlays/test/useOverlay.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,4 @@ describe('useOverlay', function () {
fireEvent.keyDown(el, {key: 'Escape'});
expect(onClose).toHaveBeenCalledTimes(1);
});

describe('firefox bug', () => {
installPointerEvent();
it('should prevent default on pointer down on the underlay', function () {
let underlayRef = React.createRef();
render(<Example isOpen isDismissable underlayProps={{ref: underlayRef}} />);
let isPrevented = fireEvent.pointerDown(underlayRef.current, {button: 0, pointerId: 1});
fireEvent.pointerUp(document.body);
expect(isPrevented).toBeFalsy(); // meaning the event had preventDefault called
});
});
});
17 changes: 9 additions & 8 deletions packages/@react-spectrum/combobox/test/ComboBox.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4362,7 +4362,8 @@ describe('ComboBox', function () {
let dismissButtons = within(tray).getAllByRole('button');
switch (Method) {
case 'clicking outside tray':
await user.click(document.body);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not leave as user.click?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to break the react 16/17 tests

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but... why?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alright I did some more digging, best I can tell in React 17/16

displayValue !== lastValueRef.current &&
(props.inputValue === undefined || value === undefined)
) {
resetInputValue();
} else if (lastValue !== inputValue) {
setLastValue(inputValue);
}
is running in different fashion based on if we use userEvent.click or fireEvent. With userEvent, the tracked displayValue and lastValueRef.current become out of sync versus fireEvent keeping these two in sync (aka both become null due to a custom value commit). If I had to hazard a guess, there is probably an extra commit or something happening which the current useEffect in useComboBoxState is fragile against. We should probably revisit that hook in general (and have talked about that for ages now haha) but its out of scope of this PR IMO.

As an aside, adding the extra click event doesn't cause the test to fail so it seems to be something specific to how userEvent.click works

fireEvent.mouseDown(document.body, {button: 0});
fireEvent.mouseUp(document.body, {button: 0});
break;
case 'dismiss button':
// TODO: not entirely sure why using user.click here breaks the test... It seems to cause the selectedKey
Expand Down Expand Up @@ -5268,9 +5269,9 @@ describe('ComboBox', function () {

if (parseInt(React.version, 10) >= 19) {
it('resets to defaultSelectedKey when submitting form action', async () => {
function Test(props) {
function Test(props) {
const [value, formAction] = React.useActionState(() => '2', '1');

return (
<Provider theme={theme}>
<form action={formAction}>
Expand All @@ -5280,11 +5281,11 @@ describe('ComboBox', function () {
</Provider>
);
}

let {getByTestId, getByRole, rerender} = render(<Test />);
let input = getByRole('combobox');
expect(input).toHaveValue('One');

let button = getByTestId('submit');
// For some reason, user.click() causes act warnings related to suspense...
await act(() => button.click());
Expand Down Expand Up @@ -5604,7 +5605,7 @@ describe('ComboBox', function () {
it('resets to defaultSelectedKey when submitting form action', async () => {
function Test(props) {
const [value, formAction] = React.useActionState(() => '2', '1');

return (
<Provider theme={theme}>
<form action={formAction}>
Expand All @@ -5614,11 +5615,11 @@ describe('ComboBox', function () {
</Provider>
);
}

let {getByTestId, rerender} = render(<Test />);
let input = document.querySelector('input[name=combobox]');
expect(input).toHaveValue('One');

let button = getByTestId('submit');
await act(async () => await user.click(button));
expect(input).toHaveValue('Two');
Expand Down
5 changes: 3 additions & 2 deletions packages/@react-spectrum/menu/test/MenuTrigger.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ describe('MenuTrigger', function () {
it.each`
Name | Component | props
${'MenuTrigger'} | ${MenuTrigger} | ${{onOpenChange, isOpen: true}}
`('$Name supports a controlled open state ', async function ({Component, props}) {
`('$Name supports a controlled open state ', function ({Component, props}) {
let tree = renderComponent(Component, props);
act(() => {jest.runAllTimers();});
expect(onOpenChange).toBeCalledTimes(0);
Expand All @@ -163,7 +163,8 @@ describe('MenuTrigger', function () {
expect(menu).toBeInTheDocument();

let triggerButton = tree.getByText('Menu Button');
await user.click(triggerButton);
fireEvent.mouseDown(triggerButton, {button: 0});
fireEvent.mouseUp(triggerButton, {button: 0});
act(() => {jest.runAllTimers();});

expect(menu).toBeInTheDocument();
Expand Down
19 changes: 5 additions & 14 deletions packages/@react-spectrum/s2/test/Combobox.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
*/

jest.mock('@react-aria/live-announcer');
import {act, fireEvent, pointerMap, render, setupIntersectionObserverMock, within} from '@react-spectrum/test-utils-internal';
import {act, pointerMap, render, setupIntersectionObserverMock, waitFor, within} from '@react-spectrum/test-utils-internal';
import {announce} from '@react-aria/live-announcer';
import {Button, ComboBox, ComboBoxItem, Content, ContextualHelp, Dialog, DialogTrigger, Heading, Text} from '../src';
import React from 'react';
Expand Down Expand Up @@ -214,6 +214,7 @@ describe('Combobox', () => {
});

it('should close the combobox when clicking outside the combobox on a dialog backdrop', async () => {
let user = userEvent.setup({delay: null, pointerMap});
let tree = render(
<DialogTrigger>
<Button>Open</Button>
Expand Down Expand Up @@ -247,20 +248,10 @@ describe('Combobox', () => {
jest.runAllTimers();
});
let backdrop = document.querySelector('[style*="--visual-viewport-height"]');
// can't use userEvent here for some reason
fireEvent.mouseDown(backdrop!, {button: 0});
fireEvent.mouseUp(backdrop!, {button: 0});
act(() => {
jest.runAllTimers();
});
expect(comboboxTester.listbox).toBeNull();
await user.click(backdrop!);


fireEvent.mouseDown(backdrop!, {button: 0});
fireEvent.mouseUp(backdrop!, {button: 0});
act(() => {
jest.runAllTimers();
});
await waitFor(() => expect(comboboxTester.listbox).toBeNull());
await user.click(backdrop!);
expect(dialogTester.dialog).toBeNull();
});
});
14 changes: 9 additions & 5 deletions packages/react-aria-components/test/Popover.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* governing permissions and limitations under the License.
*/

import {act, pointerMap, render} from '@react-spectrum/test-utils-internal';
import {act, fireEvent, pointerMap, render} from '@react-spectrum/test-utils-internal';
import {Button, Dialog, DialogTrigger, OverlayArrow, Popover, Pressable} from '../';
import React, {useRef} from 'react';
import {UNSAFE_PortalProvider} from '@react-aria/overlays';
Expand Down Expand Up @@ -102,7 +102,7 @@ describe('Popover', () => {
expect(dialog).toHaveAttribute('data-custom', 'true');
});

it('should support being used standalone', async () => {
it('should support being used standalone', () => {
let triggerRef = React.createRef();
let onOpenChange = jest.fn();
let {getByRole} = render(<>
Expand All @@ -115,12 +115,14 @@ describe('Popover', () => {
let dialog = getByRole('dialog');
expect(dialog).toHaveTextContent('A popover');

await user.click(document.body);
// userEvent seems to trigger a double close event
fireEvent.mouseDown(document.body, {button: 0});
fireEvent.mouseUp(document.body, {button: 0});
expect(onOpenChange).toHaveBeenCalledTimes(1);
expect(onOpenChange).toHaveBeenCalledWith(false);
});

it('isOpen and defaultOpen should override state from context', async () => {
it('isOpen and defaultOpen should override state from context', () => {
let onOpenChange = jest.fn();
let {getByRole} = render(<>
<DialogTrigger>
Expand All @@ -134,7 +136,9 @@ describe('Popover', () => {
let dialog = getByRole('dialog');
expect(dialog).toHaveTextContent('A popover');

await user.click(document.body);
// userEvent seems to trigger a double close event
fireEvent.mouseDown(document.body, {button: 0});
fireEvent.mouseUp(document.body, {button: 0});
expect(onOpenChange).toHaveBeenCalledTimes(1);
expect(onOpenChange).toHaveBeenCalledWith(false);
});
Expand Down