Skip to content

Commit

Permalink
Components: Refactor useFocusOutside to @testing-library/react (#44698)
Browse files Browse the repository at this point in the history
  • Loading branch information
tyxla authored Oct 5, 2022
1 parent 2c45ac1 commit 30d67e8
Showing 1 changed file with 44 additions and 45 deletions.
89 changes: 44 additions & 45 deletions packages/compose/src/hooks/use-focus-outside/test/index.js
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,38 +13,24 @@ import { Component } from '@wordpress/element';
* Internal dependencies
*/
import useFocusOutside from '../';
import ReactDOM from 'react-dom';

let wrapper, onFocusOutside;
let onFocusOutside;

describe( 'useFocusOutside', () => {
let origHasFocus;

const FocusOutsideComponent = ( { onFocusOutside: callback } ) => (
<div { ...useFocusOutside( callback ) }>
<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 <FocusOutsideComponent { ...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 @@ -52,71 +39,83 @@ describe( 'useFocusOutside', () => {
document.hasFocus = () => true;

onFocusOutside = jest.fn();
wrapper = TestUtils.renderIntoDocument(
getTestComponent( FocusOutsideComponent, { 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 } /> );

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

// 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 );
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

0 comments on commit 30d67e8

Please sign in to comment.