Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Components: Refactor withFocusOutside to @testing-library/react #44695

Merged
merged 1 commit into from
Oct 5, 2022
Merged
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
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/**
* External dependencies
*/
import TestUtils from 'react-dom/test-utils';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

/**
* WordPress dependencies
Expand All @@ -12,9 +13,8 @@ import { Component } from '@wordpress/element';
* Internal dependencies
*/
import withFocusOutside from '../';
import ReactDOM from 'react-dom';

let wrapper, onFocusOutside;
let onFocusOutside;

describe( 'withFocusOutside', () => {
let origHasFocus;
Expand All @@ -28,32 +28,19 @@ describe( 'withFocusOutside', () => {
render() {
return (
<div>
<input />
<input type="text" />
<input type="button" />
</div>
);
}
}
);

// This is needed because TestUtils does not accept a stateless component.
// anything run through a HOC ends up as a stateless component.
const getTestComponent = ( WrappedComponent, props ) => {
class TestComponent extends Component {
render() {
return <WrappedComponent { ...props } />;
}
class TestComponent extends Component {
render() {
return <EnhancedComponent { ...this.props } />;
}
return <TestComponent />;
};

const simulateEvent = ( event, index = 0 ) => {
const element = TestUtils.scryRenderedDOMComponentsWithTag(
wrapper,
'input'
);
TestUtils.Simulate[ event ]( element[ index ] );
};
}

beforeEach( () => {
// Mock document.hasFocus() to always be true for testing
Expand All @@ -62,71 +49,83 @@ describe( 'withFocusOutside', () => {
document.hasFocus = () => true;

onFocusOutside = jest.fn();
wrapper = TestUtils.renderIntoDocument(
getTestComponent( EnhancedComponent, { onFocusOutside } )
);
} );

afterEach( () => {
document.hasFocus = origHasFocus;
} );

it( 'should not call handler if focus shifts to element within component', () => {
simulateEvent( 'focus' );
simulateEvent( 'blur' );
simulateEvent( 'focus', 1 );
render( <TestComponent onFocusOutside={ onFocusOutside } /> );

const input = screen.getByRole( 'textbox' );
const button = screen.getByRole( 'button' );

input.focus();
input.blur();
button.focus();

jest.runAllTimers();

expect( onFocusOutside ).not.toHaveBeenCalled();
} );

it( 'should not call handler if focus transitions via click to button', () => {
simulateEvent( 'focus' );
simulateEvent( 'mouseDown', 1 );
simulateEvent( 'blur' );
it( 'should not call handler if focus transitions via click to button', async () => {
const user = userEvent.setup( {
advanceTimers: jest.advanceTimersByTime,
} );
render( <TestComponent onFocusOutside={ onFocusOutside } /> );

// In most browsers, the input at index 1 would receive a focus event
// at this point, but this is not guaranteed, which is the intention of
// the normalization behavior tested here.
simulateEvent( 'mouseUp', 1 );
const input = screen.getByRole( 'textbox' );
const button = screen.getByRole( 'button' );

input.focus();
await user.click( button );

jest.runAllTimers();

expect( onFocusOutside ).not.toHaveBeenCalled();
} );

it( 'should call handler if focus doesn’t shift to element within component', () => {
simulateEvent( 'focus' );
simulateEvent( 'blur' );
render( <TestComponent onFocusOutside={ onFocusOutside } /> );

const input = screen.getByRole( 'textbox' );
input.focus();
input.blur();

jest.runAllTimers();

expect( onFocusOutside ).toHaveBeenCalled();
} );

it( 'should not call handler if focus shifts outside the component when the document does not have focus', () => {
render( <TestComponent onFocusOutside={ onFocusOutside } /> );

// Force document.hasFocus() to return false to simulate the window/document losing focus
// See https://developer.mozilla.org/en-US/docs/Web/API/Document/hasFocus.
document.hasFocus = () => false;

simulateEvent( 'focus' );
simulateEvent( 'blur' );
const input = screen.getByRole( 'textbox' );
input.focus();
input.blur();

jest.runAllTimers();

expect( onFocusOutside ).not.toHaveBeenCalled();
} );

it( 'should cancel check when unmounting while queued', () => {
simulateEvent( 'focus' );
simulateEvent( 'input' );

ReactDOM.unmountComponentAtNode(
// eslint-disable-next-line react/no-find-dom-node
ReactDOM.findDOMNode( wrapper ).parentNode
const { rerender } = render(
<TestComponent onFocusOutside={ onFocusOutside } />
);

const input = screen.getByRole( 'textbox' );
input.focus();
input.blur();

rerender( <div /> );

jest.runAllTimers();

expect( onFocusOutside ).not.toHaveBeenCalled();
Expand Down