@wordpress/compose 5.17.0 → 5.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +4 -1
  3. package/build/hooks/use-dialog/index.js.map +1 -1
  4. package/build/hooks/use-disabled/index.js +10 -98
  5. package/build/hooks/use-disabled/index.js.map +1 -1
  6. package/build/hooks/use-focus-outside/index.js +15 -56
  7. package/build/hooks/use-focus-outside/index.js.map +1 -1
  8. package/build/hooks/use-focusable-iframe/index.js +5 -2
  9. package/build/hooks/use-focusable-iframe/index.js.map +1 -1
  10. package/build/hooks/use-resize-observer/index.native.js +1 -0
  11. package/build/hooks/use-resize-observer/index.native.js.map +1 -1
  12. package/build-module/hooks/use-dialog/index.js.map +1 -1
  13. package/build-module/hooks/use-disabled/index.js +10 -97
  14. package/build-module/hooks/use-disabled/index.js.map +1 -1
  15. package/build-module/hooks/use-focus-outside/index.js +15 -56
  16. package/build-module/hooks/use-focus-outside/index.js.map +1 -1
  17. package/build-module/hooks/use-focusable-iframe/index.js +5 -2
  18. package/build-module/hooks/use-focusable-iframe/index.js.map +1 -1
  19. package/build-module/hooks/use-resize-observer/index.native.js +1 -0
  20. package/build-module/hooks/use-resize-observer/index.native.js.map +1 -1
  21. package/build-types/hooks/use-dialog/index.d.ts +2 -2
  22. package/build-types/hooks/use-dialog/index.d.ts.map +1 -1
  23. package/build-types/hooks/use-disabled/index.d.ts +4 -1
  24. package/build-types/hooks/use-disabled/index.d.ts.map +1 -1
  25. package/build-types/hooks/use-focus-outside/index.d.ts +16 -63
  26. package/build-types/hooks/use-focus-outside/index.d.ts.map +1 -1
  27. package/build-types/hooks/use-focusable-iframe/index.d.ts +6 -2
  28. package/build-types/hooks/use-focusable-iframe/index.d.ts.map +1 -1
  29. package/package.json +8 -8
  30. package/src/higher-order/with-global-events/test/index.js +18 -29
  31. package/src/higher-order/with-state/test/index.js +13 -26
  32. package/src/hooks/use-dialog/index.ts +3 -4
  33. package/src/hooks/use-disabled/index.ts +81 -0
  34. package/src/hooks/use-disabled/test/index.js +4 -36
  35. package/src/hooks/use-focus-outside/{index.js → index.ts} +55 -62
  36. package/src/hooks/use-focus-outside/test/index.js +112 -84
  37. package/src/hooks/use-focusable-iframe/{index.js → index.ts} +8 -3
  38. package/src/hooks/use-instance-id/test/index.js +9 -16
  39. package/src/hooks/use-media-query/test/index.js +33 -53
  40. package/src/hooks/use-resize-observer/index.native.js +5 -1
  41. package/src/hooks/use-resize-observer/test/index.native.js +18 -22
  42. package/src/hooks/use-viewport-match/test/index.js +78 -78
  43. package/tsconfig.tsbuildinfo +1 -1
  44. package/src/hooks/use-disabled/index.js +0 -197
@@ -1,124 +1,152 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import TestUtils from 'react-dom/test-utils';
5
-
6
- /**
7
- * WordPress dependencies
8
- */
9
- import { Component } from '@wordpress/element';
4
+ import { render, screen } from '@testing-library/react';
5
+ import userEvent from '@testing-library/user-event';
10
6
 
11
7
  /**
12
8
  * Internal dependencies
13
9
  */
14
10
  import useFocusOutside from '../';
15
- import ReactDOM from 'react-dom';
16
-
17
- let wrapper, onFocusOutside;
18
11
 
19
- describe( 'useFocusOutside', () => {
20
- let origHasFocus;
21
-
22
- const FocusOutsideComponent = ( { onFocusOutside: callback } ) => (
12
+ const FocusOutsideComponent = ( { onFocusOutside: callback } ) => (
13
+ <div>
14
+ { /* Wrapper */ }
23
15
  <div { ...useFocusOutside( callback ) }>
24
- <input />
25
- <input type="button" />
16
+ <input type="text" />
17
+ <button>Button inside the wrapper</button>
18
+ <iframe title="test-iframe">
19
+ <button>Inside the iframe</button>
20
+ </iframe>
26
21
  </div>
27
- );
28
-
29
- // This is needed because TestUtils does not accept a stateless component.
30
- // anything run through a HOC ends up as a stateless component.
31
- const getTestComponent = ( WrappedComponent, props ) => {
32
- class TestComponent extends Component {
33
- render() {
34
- return <WrappedComponent { ...props } />;
35
- }
36
- }
37
- return <TestComponent />;
38
- };
39
-
40
- const simulateEvent = ( event, index = 0 ) => {
41
- const element = TestUtils.scryRenderedDOMComponentsWithTag(
42
- wrapper,
43
- 'input'
44
- );
45
- TestUtils.Simulate[ event ]( element[ index ] );
46
- };
47
-
48
- beforeEach( () => {
49
- // Mock document.hasFocus() to always be true for testing
50
- // note: we overide this for some tests.
51
- origHasFocus = document.hasFocus;
52
- document.hasFocus = () => true;
53
-
54
- onFocusOutside = jest.fn();
55
- wrapper = TestUtils.renderIntoDocument(
56
- getTestComponent( FocusOutsideComponent, { onFocusOutside } )
57
- );
58
- } );
59
22
 
60
- afterEach( () => {
61
- document.hasFocus = origHasFocus;
62
- } );
23
+ <button>Button outside the wrapper</button>
24
+ </div>
25
+ );
63
26
 
64
- it( 'should not call handler if focus shifts to element within component', () => {
65
- simulateEvent( 'focus' );
66
- simulateEvent( 'blur' );
67
- simulateEvent( 'focus', 1 );
27
+ describe( 'useFocusOutside', () => {
28
+ it( 'should not call handler if focus shifts to element within component', async () => {
29
+ const mockOnFocusOutside = jest.fn();
30
+ const user = userEvent.setup( {
31
+ advanceTimers: jest.advanceTimersByTime,
32
+ } );
33
+
34
+ render(
35
+ <FocusOutsideComponent onFocusOutside={ mockOnFocusOutside } />
36
+ );
68
37
 
69
- jest.runAllTimers();
38
+ // Tab through the interactive elements inside the wrapper,
39
+ // causing multiple focus/blur events.
40
+ await user.tab();
41
+ expect( screen.getByRole( 'textbox' ) ).toHaveFocus();
42
+
43
+ await user.tab();
44
+ expect(
45
+ screen.getByRole( 'button', { name: 'Button inside the wrapper' } )
46
+ ).toHaveFocus();
70
47
 
71
- expect( onFocusOutside ).not.toHaveBeenCalled();
48
+ expect( mockOnFocusOutside ).not.toHaveBeenCalled();
72
49
  } );
73
50
 
74
- it( 'should not call handler if focus transitions via click to button', () => {
75
- simulateEvent( 'focus' );
76
- simulateEvent( 'mouseDown', 1 );
77
- simulateEvent( 'blur' );
51
+ it( 'should not call handler if focus transitions via click to button', async () => {
52
+ const mockOnFocusOutside = jest.fn();
53
+ const user = userEvent.setup( {
54
+ advanceTimers: jest.advanceTimersByTime,
55
+ } );
78
56
 
79
- // In most browsers, the input at index 1 would receive a focus event
80
- // at this point, but this is not guaranteed, which is the intention of
81
- // the normalization behavior tested here.
82
- simulateEvent( 'mouseUp', 1 );
57
+ render(
58
+ <FocusOutsideComponent onFocusOutside={ mockOnFocusOutside } />
59
+ );
83
60
 
84
- jest.runAllTimers();
61
+ // Click the input and the button, causing multiple focus/blur events.
62
+ await user.click( screen.getByRole( 'textbox' ) );
63
+ await user.click(
64
+ screen.getByRole( 'button', { name: 'Button inside the wrapper' } )
65
+ );
85
66
 
86
- expect( onFocusOutside ).not.toHaveBeenCalled();
67
+ expect( mockOnFocusOutside ).not.toHaveBeenCalled();
87
68
  } );
88
69
 
89
- it( 'should call handler if focus doesn’t shift to element within component', () => {
90
- simulateEvent( 'focus' );
91
- simulateEvent( 'blur' );
70
+ it( 'should call handler if focus shifts to element outside component', async () => {
71
+ const mockOnFocusOutside = jest.fn();
72
+ const user = userEvent.setup( {
73
+ advanceTimers: jest.advanceTimersByTime,
74
+ } );
92
75
 
93
- jest.runAllTimers();
76
+ render(
77
+ <FocusOutsideComponent onFocusOutside={ mockOnFocusOutside } />
78
+ );
94
79
 
95
- expect( onFocusOutside ).toHaveBeenCalled();
80
+ // Click and focus button inside the wrapper
81
+ await user.click(
82
+ screen.getByRole( 'button', { name: 'Button inside the wrapper' } )
83
+ );
84
+
85
+ expect( mockOnFocusOutside ).not.toHaveBeenCalled();
86
+
87
+ // Click and focus button outside the wrapper
88
+ await user.click(
89
+ screen.getByRole( 'button', { name: 'Button outside the wrapper' } )
90
+ );
91
+
92
+ expect( mockOnFocusOutside ).toHaveBeenCalled();
96
93
  } );
97
94
 
98
- it( 'should not call handler if focus shifts outside the component when the document does not have focus', () => {
95
+ it( 'should not call handler if focus shifts outside the component when the document does not have focus', async () => {
99
96
  // Force document.hasFocus() to return false to simulate the window/document losing focus
100
97
  // See https://developer.mozilla.org/en-US/docs/Web/API/Document/hasFocus.
101
- document.hasFocus = () => false;
98
+ const mockedDocumentHasFocus = jest
99
+ .spyOn( document, 'hasFocus' )
100
+ .mockImplementation( () => false );
101
+ const mockOnFocusOutside = jest.fn();
102
+ const user = userEvent.setup( {
103
+ advanceTimers: jest.advanceTimersByTime,
104
+ } );
105
+
106
+ render(
107
+ <FocusOutsideComponent onFocusOutside={ mockOnFocusOutside } />
108
+ );
102
109
 
103
- simulateEvent( 'focus' );
104
- simulateEvent( 'blur' );
110
+ // Click and focus button inside the wrapper, then click and focus
111
+ // a button outside the wrapper.
112
+ await user.click(
113
+ screen.getByRole( 'button', { name: 'Button inside the wrapper' } )
114
+ );
115
+ await user.click(
116
+ screen.getByRole( 'button', { name: 'Button outside the wrapper' } )
117
+ );
105
118
 
106
- jest.runAllTimers();
119
+ // The handler is not called thanks to the mocked return value of
120
+ // `document.hasFocus()`
121
+ expect( mockOnFocusOutside ).not.toHaveBeenCalled();
107
122
 
108
- expect( onFocusOutside ).not.toHaveBeenCalled();
123
+ // Restore the `document.hasFocus()` function to its original implementation.
124
+ mockedDocumentHasFocus.mockRestore();
109
125
  } );
110
126
 
111
- it( 'should cancel check when unmounting while queued', () => {
112
- simulateEvent( 'focus' );
113
- simulateEvent( 'input' );
127
+ it( 'should cancel check when unmounting while queued', async () => {
128
+ const mockOnFocusOutside = jest.fn();
129
+ const user = userEvent.setup( {
130
+ advanceTimers: jest.advanceTimersByTime,
131
+ } );
114
132
 
115
- ReactDOM.unmountComponentAtNode(
116
- // eslint-disable-next-line react/no-find-dom-node
117
- ReactDOM.findDOMNode( wrapper ).parentNode
133
+ const { unmount } = render(
134
+ <FocusOutsideComponent onFocusOutside={ mockOnFocusOutside } />
118
135
  );
119
136
 
137
+ // Click and focus button inside the wrapper.
138
+ const button = screen.getByRole( 'button', {
139
+ name: 'Button inside the wrapper',
140
+ } );
141
+ await user.click( button );
142
+
143
+ // Simulate a blur event and the wrapper unmounting while the blur event
144
+ // handler is queued
145
+ button.blur();
146
+ unmount();
147
+
120
148
  jest.runAllTimers();
121
149
 
122
- expect( onFocusOutside ).not.toHaveBeenCalled();
150
+ expect( mockOnFocusOutside ).not.toHaveBeenCalled();
123
151
  } );
124
152
  } );
@@ -1,3 +1,8 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import type { RefCallback } from 'react';
5
+
1
6
  /**
2
7
  * Internal dependencies
3
8
  */
@@ -7,9 +12,9 @@ import useRefEffect from '../use-ref-effect';
7
12
  * Dispatches a bubbling focus event when the iframe receives focus. Use
8
13
  * `onFocus` as usual on the iframe or a parent element.
9
14
  *
10
- * @return {Object} Ref to pass to the iframe.
15
+ * @return Ref to pass to the iframe.
11
16
  */
12
- export default function useFocusableIframe() {
17
+ export default function useFocusableIframe(): RefCallback< HTMLIFrameElement > {
13
18
  return useRefEffect( ( element ) => {
14
19
  const { ownerDocument } = element;
15
20
  if ( ! ownerDocument ) return;
@@ -22,7 +27,7 @@ export default function useFocusableIframe() {
22
27
  */
23
28
  function checkFocus() {
24
29
  if ( ownerDocument && ownerDocument.activeElement === element ) {
25
- /** @type {HTMLElement} */ ( element ).focus();
30
+ ( element as HTMLElement ).focus();
26
31
  }
27
32
  }
28
33
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { create, act } from 'react-test-renderer';
4
+ import { render } from '@testing-library/react';
5
5
 
6
6
  /**
7
7
  * Internal dependencies
@@ -14,23 +14,16 @@ describe( 'useInstanceId', () => {
14
14
  };
15
15
 
16
16
  it( 'should manage ids', async () => {
17
- let test0;
17
+ const { container, rerender } = render( <TestComponent /> );
18
18
 
19
- await act( async () => {
20
- test0 = create( <TestComponent /> );
21
- } );
19
+ expect( container ).toHaveTextContent( '0' );
22
20
 
23
- expect( test0.toJSON() ).toBe( '0' );
21
+ rerender(
22
+ <div>
23
+ <TestComponent />
24
+ </div>
25
+ );
24
26
 
25
- let test1;
26
-
27
- await act( async () => {
28
- test1 = create( <TestComponent /> );
29
- } );
30
-
31
- expect( test1.toJSON() ).toBe( '1' );
32
-
33
- test0.unmount();
34
- test1.unmount();
27
+ expect( container ).toHaveTextContent( '1' );
35
28
  } );
36
29
  } );
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { create, act } from 'react-test-renderer';
4
+ import { render, act } from '@testing-library/react';
5
5
 
6
6
  /**
7
7
  * Internal dependencies
@@ -33,18 +33,6 @@ describe( 'useMediaQuery', () => {
33
33
  return `useMediaQuery: ${ queryResult }`;
34
34
  };
35
35
 
36
- it( 'should return true when query matches on the first render', async () => {
37
- global.matchMedia.mockReturnValue( {
38
- addListener,
39
- removeListener,
40
- matches: true,
41
- } );
42
-
43
- const root = create( <TestComponent query="(min-width: 782px)" /> );
44
-
45
- expect( root.toJSON() ).toBe( 'useMediaQuery: true' );
46
- } );
47
-
48
36
  it( 'should return true when query matches', async () => {
49
37
  global.matchMedia.mockReturnValue( {
50
38
  addListener,
@@ -52,17 +40,14 @@ describe( 'useMediaQuery', () => {
52
40
  matches: true,
53
41
  } );
54
42
 
55
- let root;
43
+ const { container, unmount } = render(
44
+ <TestComponent query="(min-width: 782px)" />
45
+ );
56
46
 
57
- await act( async () => {
58
- root = create( <TestComponent query="(min-width: 782px)" /> );
59
- } );
47
+ expect( container ).toHaveTextContent( 'useMediaQuery: true' );
60
48
 
61
- expect( root.toJSON() ).toBe( 'useMediaQuery: true' );
49
+ unmount();
62
50
 
63
- await act( async () => {
64
- root.unmount();
65
- } );
66
51
  expect( removeListener ).toHaveBeenCalled();
67
52
  } );
68
53
 
@@ -90,24 +75,23 @@ describe( 'useMediaQuery', () => {
90
75
  matches: false,
91
76
  } );
92
77
 
93
- let root, updateMatchFunction;
94
- await act( async () => {
95
- root = create( <TestComponent query="(min-width: 782px)" /> );
96
- } );
97
- expect( root.toJSON() ).toBe( 'useMediaQuery: true' );
78
+ const { container, unmount } = render(
79
+ <TestComponent query="(min-width: 782px)" />
80
+ );
81
+
82
+ expect( container ).toHaveTextContent( 'useMediaQuery: true' );
98
83
 
84
+ let updateMatchFunction;
99
85
  await act( async () => {
100
86
  updateMatchFunction = addListener.mock.calls[ 0 ][ 0 ];
101
87
  updateMatchFunction();
102
88
  } );
103
- expect( root.toJSON() ).toBe( 'useMediaQuery: false' );
104
89
 
105
- await act( async () => {
106
- root.unmount();
107
- } );
108
- expect( removeListener.mock.calls ).toEqual( [
109
- [ updateMatchFunction ],
110
- ] );
90
+ expect( container ).toHaveTextContent( 'useMediaQuery: false' );
91
+
92
+ unmount();
93
+
94
+ expect( removeListener ).toHaveBeenCalledWith( updateMatchFunction );
111
95
  } );
112
96
 
113
97
  it( 'should return false when the query does not matches', async () => {
@@ -116,15 +100,15 @@ describe( 'useMediaQuery', () => {
116
100
  removeListener,
117
101
  matches: false,
118
102
  } );
119
- let root;
120
- await act( async () => {
121
- root = create( <TestComponent query="(min-width: 782px)" /> );
122
- } );
123
- expect( root.toJSON() ).toBe( 'useMediaQuery: false' );
124
103
 
125
- await act( async () => {
126
- root.unmount();
127
- } );
104
+ const { container, unmount } = render(
105
+ <TestComponent query="(min-width: 782px)" />
106
+ );
107
+
108
+ expect( container ).toHaveTextContent( 'useMediaQuery: false' );
109
+
110
+ unmount();
111
+
128
112
  expect( removeListener ).toHaveBeenCalled();
129
113
  } );
130
114
 
@@ -134,21 +118,17 @@ describe( 'useMediaQuery', () => {
134
118
  removeListener,
135
119
  matches: false,
136
120
  } );
137
- let root;
138
- await act( async () => {
139
- root = create( <TestComponent /> );
140
- } );
121
+
122
+ const { container, rerender, unmount } = render( <TestComponent /> );
123
+
141
124
  // Query will be case to a boolean to simplify the return type.
142
- expect( root.toJSON() ).toBe( 'useMediaQuery: false' );
125
+ expect( container ).toHaveTextContent( 'useMediaQuery: false' );
143
126
 
144
- await act( async () => {
145
- root.update( <TestComponent query={ false } /> );
146
- } );
147
- expect( root.toJSON() ).toBe( 'useMediaQuery: false' );
127
+ rerender( <TestComponent query={ false } /> );
148
128
 
149
- await act( async () => {
150
- root.unmount();
151
- } );
129
+ expect( container ).toHaveTextContent( 'useMediaQuery: false' );
130
+
131
+ unmount();
152
132
  expect( global.matchMedia ).not.toHaveBeenCalled();
153
133
  expect( addListener ).not.toHaveBeenCalled();
154
134
  expect( removeListener ).not.toHaveBeenCalled();
@@ -47,7 +47,11 @@ const useResizeObserver = () => {
47
47
  }, [] );
48
48
 
49
49
  const observer = (
50
- <View style={ StyleSheet.absoluteFill } onLayout={ onLayout } />
50
+ <View
51
+ testID="resize-observer"
52
+ style={ StyleSheet.absoluteFill }
53
+ onLayout={ onLayout }
54
+ />
51
55
  );
52
56
 
53
57
  return [ observer, measurements ];
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { create, act } from 'react-test-renderer';
4
+ import { render, fireEvent } from 'test/helpers';
5
5
  import { View } from 'react-native';
6
6
 
7
7
  /**
@@ -13,36 +13,32 @@ const TestComponent = ( { onLayout } ) => {
13
13
  const [ resizeObserver, sizes ] = useResizeObserver();
14
14
 
15
15
  return (
16
- <View sizes={ sizes } onLayout={ onLayout }>
16
+ <View testID="test-component" sizes={ sizes } onLayout={ onLayout }>
17
17
  { resizeObserver }
18
18
  </View>
19
19
  );
20
20
  };
21
21
 
22
- const renderWithOnLayout = ( component ) => {
23
- const testComponent = create( component );
24
-
25
- const mockNativeEvent = {
26
- nativeEvent: {
27
- layout: {
28
- width: 300,
29
- height: 500,
22
+ describe( 'useResizeObserver()', () => {
23
+ it( 'should return "{ width: 300, height: 500 }"', () => {
24
+ const mockNativeEvent = {
25
+ nativeEvent: {
26
+ layout: {
27
+ width: 300,
28
+ height: 500,
29
+ },
30
30
  },
31
- },
32
- };
33
-
34
- act( () => {
35
- testComponent.toJSON().children[ 0 ].props.onLayout( mockNativeEvent );
36
- } );
31
+ };
37
32
 
38
- return testComponent.toJSON();
39
- };
33
+ const { getByTestId } = render(
34
+ <TestComponent onLayout={ mockNativeEvent } />
35
+ );
40
36
 
41
- describe( 'useResizeObserver()', () => {
42
- it( 'should return "{ width: 300, height: 500 }"', () => {
43
- const component = renderWithOnLayout( <TestComponent /> );
37
+ const resizeObserver = getByTestId( 'resize-observer' );
38
+ fireEvent( resizeObserver, 'layout', mockNativeEvent );
44
39
 
45
- expect( component.props.sizes ).toMatchObject( {
40
+ const testComponent = getByTestId( 'test-component' );
41
+ expect( testComponent.props.sizes ).toMatchObject( {
46
42
  width: 300,
47
43
  height: 500,
48
44
  } );