@wordpress/core-data 4.1.1 → 4.2.0-next.e230fbab09.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.
- package/CHANGELOG.md +6 -0
- package/README.md +2 -10
- package/build/actions.js +9 -13
- package/build/actions.js.map +1 -1
- package/build/batch/create-batch.js +17 -12
- package/build/batch/create-batch.js.map +1 -1
- package/build/entities.js +1 -1
- package/build/entities.js.map +1 -1
- package/build/entity-types/attachment.js +6 -0
- package/build/entity-types/attachment.js.map +1 -0
- package/build/entity-types/base-entity-types.js +47 -0
- package/build/entity-types/base-entity-types.js.map +1 -0
- package/build/entity-types/comment.js +6 -0
- package/build/entity-types/comment.js.map +1 -0
- package/build/entity-types/helpers.js +6 -0
- package/build/entity-types/helpers.js.map +1 -0
- package/build/entity-types/index.js +6 -0
- package/build/entity-types/index.js.map +1 -0
- package/build/entity-types/menu-location.js +6 -0
- package/build/entity-types/menu-location.js.map +1 -0
- package/build/entity-types/nav-menu-item.js +6 -0
- package/build/entity-types/nav-menu-item.js.map +1 -0
- package/build/entity-types/nav-menu.js +6 -0
- package/build/entity-types/nav-menu.js.map +1 -0
- package/build/entity-types/navigation-area.js +6 -0
- package/build/entity-types/navigation-area.js.map +1 -0
- package/build/entity-types/page.js +6 -0
- package/build/entity-types/page.js.map +1 -0
- package/build/entity-types/plugin.js +6 -0
- package/build/entity-types/plugin.js.map +1 -0
- package/build/entity-types/post.js +6 -0
- package/build/entity-types/post.js.map +1 -0
- package/build/entity-types/settings.js +6 -0
- package/build/entity-types/settings.js.map +1 -0
- package/build/entity-types/sidebar.js +6 -0
- package/build/entity-types/sidebar.js.map +1 -0
- package/build/entity-types/taxonomy.js +6 -0
- package/build/entity-types/taxonomy.js.map +1 -0
- package/build/entity-types/theme.js +6 -0
- package/build/entity-types/theme.js.map +1 -0
- package/build/entity-types/type.js +6 -0
- package/build/entity-types/type.js.map +1 -0
- package/build/entity-types/user.js +6 -0
- package/build/entity-types/user.js.map +1 -0
- package/build/entity-types/widget-type.js +6 -0
- package/build/entity-types/widget-type.js.map +1 -0
- package/build/entity-types/widget.js +6 -0
- package/build/entity-types/widget.js.map +1 -0
- package/build/entity-types/wp-template-part.js +6 -0
- package/build/entity-types/wp-template-part.js.map +1 -0
- package/build/entity-types/wp-template.js +6 -0
- package/build/entity-types/wp-template.js.map +1 -0
- package/build/fetch/__experimental-fetch-link-suggestions.js +2 -2
- package/build/fetch/__experimental-fetch-link-suggestions.js.map +1 -1
- package/build/hooks/constants.js +18 -0
- package/build/hooks/constants.js.map +1 -0
- package/build/hooks/memoize.js +18 -0
- package/build/hooks/memoize.js.map +1 -0
- package/build/hooks/use-entity-record.js +60 -0
- package/build/hooks/use-entity-record.js.map +1 -0
- package/build/hooks/use-entity-records.js +77 -0
- package/build/hooks/use-entity-records.js.map +1 -0
- package/build/hooks/use-query-select.js +130 -0
- package/build/hooks/use-query-select.js.map +1 -0
- package/build/index.js +34 -3
- package/build/index.js.map +1 -1
- package/build/reducer.js +1 -1
- package/build/reducer.js.map +1 -1
- package/build/resolvers.js +7 -17
- package/build/resolvers.js.map +1 -1
- package/build-module/actions.js +9 -13
- package/build-module/actions.js.map +1 -1
- package/build-module/batch/create-batch.js +17 -12
- package/build-module/batch/create-batch.js.map +1 -1
- package/build-module/entities.js +1 -1
- package/build-module/entities.js.map +1 -1
- package/build-module/entity-types/attachment.js +2 -0
- package/build-module/entity-types/attachment.js.map +1 -0
- package/build-module/entity-types/base-entity-types.js +39 -0
- package/build-module/entity-types/base-entity-types.js.map +1 -0
- package/build-module/entity-types/comment.js +2 -0
- package/build-module/entity-types/comment.js.map +1 -0
- package/build-module/entity-types/helpers.js +2 -0
- package/build-module/entity-types/helpers.js.map +1 -0
- package/build-module/entity-types/index.js +2 -0
- package/build-module/entity-types/index.js.map +1 -0
- package/build-module/entity-types/menu-location.js +2 -0
- package/build-module/entity-types/menu-location.js.map +1 -0
- package/build-module/entity-types/nav-menu-item.js +2 -0
- package/build-module/entity-types/nav-menu-item.js.map +1 -0
- package/build-module/entity-types/nav-menu.js +2 -0
- package/build-module/entity-types/nav-menu.js.map +1 -0
- package/build-module/entity-types/navigation-area.js +2 -0
- package/build-module/entity-types/navigation-area.js.map +1 -0
- package/build-module/entity-types/page.js +2 -0
- package/build-module/entity-types/page.js.map +1 -0
- package/build-module/entity-types/plugin.js +2 -0
- package/build-module/entity-types/plugin.js.map +1 -0
- package/build-module/entity-types/post.js +2 -0
- package/build-module/entity-types/post.js.map +1 -0
- package/build-module/entity-types/settings.js +2 -0
- package/build-module/entity-types/settings.js.map +1 -0
- package/build-module/entity-types/sidebar.js +2 -0
- package/build-module/entity-types/sidebar.js.map +1 -0
- package/build-module/entity-types/taxonomy.js +2 -0
- package/build-module/entity-types/taxonomy.js.map +1 -0
- package/build-module/entity-types/theme.js +2 -0
- package/build-module/entity-types/theme.js.map +1 -0
- package/build-module/entity-types/type.js +2 -0
- package/build-module/entity-types/type.js.map +1 -0
- package/build-module/entity-types/user.js +2 -0
- package/build-module/entity-types/user.js.map +1 -0
- package/build-module/entity-types/widget-type.js +2 -0
- package/build-module/entity-types/widget-type.js.map +1 -0
- package/build-module/entity-types/widget.js +2 -0
- package/build-module/entity-types/widget.js.map +1 -0
- package/build-module/entity-types/wp-template-part.js +2 -0
- package/build-module/entity-types/wp-template-part.js.map +1 -0
- package/build-module/entity-types/wp-template.js +2 -0
- package/build-module/entity-types/wp-template.js.map +1 -0
- package/build-module/fetch/__experimental-fetch-link-suggestions.js +2 -2
- package/build-module/fetch/__experimental-fetch-link-suggestions.js.map +1 -1
- package/build-module/hooks/constants.js +10 -0
- package/build-module/hooks/constants.js.map +1 -0
- package/build-module/hooks/memoize.js +7 -0
- package/build-module/hooks/memoize.js.map +1 -0
- package/build-module/hooks/use-entity-record.js +49 -0
- package/build-module/hooks/use-entity-record.js.map +1 -0
- package/build-module/hooks/use-entity-records.js +65 -0
- package/build-module/hooks/use-entity-records.js.map +1 -0
- package/build-module/hooks/use-query-select.js +116 -0
- package/build-module/hooks/use-query-select.js.map +1 -0
- package/build-module/index.js +4 -2
- package/build-module/index.js.map +1 -1
- package/build-module/reducer.js +1 -1
- package/build-module/reducer.js.map +1 -1
- package/build-module/resolvers.js +8 -18
- package/build-module/resolvers.js.map +1 -1
- package/package.json +15 -11
- package/src/actions.js +9 -13
- package/src/batch/create-batch.js +16 -12
- package/src/entities.js +1 -1
- package/src/entity-types/README.md +193 -0
- package/src/entity-types/attachment.ts +146 -0
- package/src/entity-types/base-entity-types.ts +36 -0
- package/src/entity-types/comment.ts +96 -0
- package/src/entity-types/helpers.ts +153 -0
- package/src/entity-types/index.ts +72 -0
- package/src/entity-types/menu-location.ts +29 -0
- package/src/entity-types/nav-menu-item.ts +106 -0
- package/src/entity-types/nav-menu.ts +53 -0
- package/src/entity-types/navigation-area.ts +29 -0
- package/src/entity-types/page.ts +144 -0
- package/src/entity-types/plugin.ts +74 -0
- package/src/entity-types/post.ts +153 -0
- package/src/entity-types/settings.ts +93 -0
- package/src/entity-types/sidebar.ts +60 -0
- package/src/entity-types/taxonomy.ts +92 -0
- package/src/entity-types/theme.ts +222 -0
- package/src/entity-types/type.ts +80 -0
- package/src/entity-types/user.ts +109 -0
- package/src/entity-types/widget-type.ts +37 -0
- package/src/entity-types/widget.ts +64 -0
- package/src/entity-types/wp-template-part.ts +94 -0
- package/src/entity-types/wp-template.ts +94 -0
- package/src/fetch/__experimental-fetch-link-suggestions.js +2 -2
- package/src/hooks/constants.ts +7 -0
- package/src/hooks/memoize.js +7 -0
- package/src/hooks/test/use-entity-record.js +74 -0
- package/src/hooks/test/use-entity-records.js +78 -0
- package/src/hooks/test/use-query-select.js +194 -0
- package/src/hooks/use-entity-record.ts +72 -0
- package/src/hooks/use-entity-records.ts +89 -0
- package/src/hooks/use-query-select.ts +131 -0
- package/src/index.js +3 -1
- package/src/locks/test/engine.js +13 -13
- package/src/locks/test/reducer.js +3 -3
- package/src/locks/test/utils.js +2 -2
- package/src/reducer.js +1 -1
- package/src/resolvers.js +6 -17
- package/src/test/resolvers.js +17 -23
- package/src/test/selectors.js +2 -3
|
@@ -103,7 +103,7 @@ const fetchLinkSuggestions = async (
|
|
|
103
103
|
};
|
|
104
104
|
} );
|
|
105
105
|
} )
|
|
106
|
-
.catch( () => [] ) //
|
|
106
|
+
.catch( () => [] ) // Fail by returning no results.
|
|
107
107
|
);
|
|
108
108
|
}
|
|
109
109
|
|
|
@@ -156,7 +156,7 @@ const fetchLinkSuggestions = async (
|
|
|
156
156
|
return Promise.all( queries ).then( ( results ) => {
|
|
157
157
|
return results
|
|
158
158
|
.reduce(
|
|
159
|
-
( accumulator, current ) => accumulator.concat( current ), //
|
|
159
|
+
( accumulator, current ) => accumulator.concat( current ), // Flatten list.
|
|
160
160
|
[]
|
|
161
161
|
)
|
|
162
162
|
.filter(
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import triggerFetch from '@wordpress/api-fetch';
|
|
5
|
+
import { createRegistry, RegistryProvider } from '@wordpress/data';
|
|
6
|
+
|
|
7
|
+
jest.mock( '@wordpress/api-fetch' );
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* External dependencies
|
|
11
|
+
*/
|
|
12
|
+
import { act, render } from '@testing-library/react';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Internal dependencies
|
|
16
|
+
*/
|
|
17
|
+
import { store as coreDataStore } from '../../index';
|
|
18
|
+
import useEntityRecord from '../use-entity-record';
|
|
19
|
+
|
|
20
|
+
describe( 'useEntityRecord', () => {
|
|
21
|
+
let registry;
|
|
22
|
+
beforeEach( () => {
|
|
23
|
+
jest.useFakeTimers();
|
|
24
|
+
|
|
25
|
+
registry = createRegistry();
|
|
26
|
+
registry.register( coreDataStore );
|
|
27
|
+
} );
|
|
28
|
+
|
|
29
|
+
afterEach( () => {
|
|
30
|
+
jest.runOnlyPendingTimers();
|
|
31
|
+
jest.useRealTimers();
|
|
32
|
+
} );
|
|
33
|
+
|
|
34
|
+
const TEST_RECORD = { id: 1, hello: 'world' };
|
|
35
|
+
|
|
36
|
+
it( 'resolves the entity record when missing from the state', async () => {
|
|
37
|
+
// Provide response
|
|
38
|
+
triggerFetch.mockImplementation( () => TEST_RECORD );
|
|
39
|
+
|
|
40
|
+
let data;
|
|
41
|
+
const TestComponent = () => {
|
|
42
|
+
data = useEntityRecord( 'root', 'widget', 1 );
|
|
43
|
+
return <div />;
|
|
44
|
+
};
|
|
45
|
+
render(
|
|
46
|
+
<RegistryProvider value={ registry }>
|
|
47
|
+
<TestComponent />
|
|
48
|
+
</RegistryProvider>
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect( data ).toEqual( {
|
|
52
|
+
records: undefined,
|
|
53
|
+
hasResolved: false,
|
|
54
|
+
isResolving: false,
|
|
55
|
+
status: 'IDLE',
|
|
56
|
+
} );
|
|
57
|
+
|
|
58
|
+
await act( async () => {
|
|
59
|
+
jest.advanceTimersByTime( 1 );
|
|
60
|
+
} );
|
|
61
|
+
|
|
62
|
+
// Fetch request should have been issued
|
|
63
|
+
expect( triggerFetch ).toHaveBeenCalledWith( {
|
|
64
|
+
path: '/wp/v2/widgets/1?context=edit',
|
|
65
|
+
} );
|
|
66
|
+
|
|
67
|
+
expect( data ).toEqual( {
|
|
68
|
+
record: { hello: 'world', id: 1 },
|
|
69
|
+
hasResolved: true,
|
|
70
|
+
isResolving: false,
|
|
71
|
+
status: 'SUCCESS',
|
|
72
|
+
} );
|
|
73
|
+
} );
|
|
74
|
+
} );
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import triggerFetch from '@wordpress/api-fetch';
|
|
5
|
+
import { createRegistry, RegistryProvider } from '@wordpress/data';
|
|
6
|
+
|
|
7
|
+
jest.mock( '@wordpress/api-fetch' );
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* External dependencies
|
|
11
|
+
*/
|
|
12
|
+
import { act, render } from '@testing-library/react';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Internal dependencies
|
|
16
|
+
*/
|
|
17
|
+
import { store as coreDataStore } from '../../index';
|
|
18
|
+
import useEntityRecords from '../use-entity-records';
|
|
19
|
+
|
|
20
|
+
describe( 'useEntityRecords', () => {
|
|
21
|
+
let registry;
|
|
22
|
+
beforeEach( () => {
|
|
23
|
+
jest.useFakeTimers();
|
|
24
|
+
|
|
25
|
+
registry = createRegistry();
|
|
26
|
+
registry.register( coreDataStore );
|
|
27
|
+
} );
|
|
28
|
+
|
|
29
|
+
afterEach( () => {
|
|
30
|
+
jest.runOnlyPendingTimers();
|
|
31
|
+
jest.useRealTimers();
|
|
32
|
+
} );
|
|
33
|
+
|
|
34
|
+
const TEST_RECORDS = [
|
|
35
|
+
{ id: 1, hello: 'world1' },
|
|
36
|
+
{ id: 2, hello: 'world2' },
|
|
37
|
+
{ id: 3, hello: 'world3' },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
it( 'resolves the entity records when missing from the state', async () => {
|
|
41
|
+
// Provide response
|
|
42
|
+
triggerFetch.mockImplementation( () => TEST_RECORDS );
|
|
43
|
+
|
|
44
|
+
let data;
|
|
45
|
+
const TestComponent = () => {
|
|
46
|
+
data = useEntityRecords( 'root', 'widget', { status: 'draft' } );
|
|
47
|
+
return <div />;
|
|
48
|
+
};
|
|
49
|
+
render(
|
|
50
|
+
<RegistryProvider value={ registry }>
|
|
51
|
+
<TestComponent />
|
|
52
|
+
</RegistryProvider>
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
expect( data ).toEqual( {
|
|
56
|
+
records: null,
|
|
57
|
+
hasResolved: false,
|
|
58
|
+
isResolving: false,
|
|
59
|
+
status: 'IDLE',
|
|
60
|
+
} );
|
|
61
|
+
|
|
62
|
+
await act( async () => {
|
|
63
|
+
jest.advanceTimersByTime( 1 );
|
|
64
|
+
} );
|
|
65
|
+
|
|
66
|
+
// Fetch request should have been issued
|
|
67
|
+
expect( triggerFetch ).toHaveBeenCalledWith( {
|
|
68
|
+
path: '/wp/v2/widgets?context=edit&status=draft',
|
|
69
|
+
} );
|
|
70
|
+
|
|
71
|
+
expect( data ).toEqual( {
|
|
72
|
+
records: TEST_RECORDS,
|
|
73
|
+
hasResolved: true,
|
|
74
|
+
isResolving: false,
|
|
75
|
+
status: 'SUCCESS',
|
|
76
|
+
} );
|
|
77
|
+
} );
|
|
78
|
+
} );
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
createReduxStore,
|
|
6
|
+
createRegistry,
|
|
7
|
+
RegistryProvider,
|
|
8
|
+
} from '@wordpress/data';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* External dependencies
|
|
12
|
+
*/
|
|
13
|
+
import { act, render } from '@testing-library/react';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Internal dependencies
|
|
17
|
+
*/
|
|
18
|
+
import useQuerySelect from '../use-query-select';
|
|
19
|
+
|
|
20
|
+
describe( 'useQuerySelect', () => {
|
|
21
|
+
let registry;
|
|
22
|
+
beforeEach( () => {
|
|
23
|
+
jest.useFakeTimers();
|
|
24
|
+
|
|
25
|
+
registry = createRegistry();
|
|
26
|
+
registry.registerStore( 'testStore', {
|
|
27
|
+
reducer: () => ( { foo: 'bar' } ),
|
|
28
|
+
selectors: {
|
|
29
|
+
getFoo: ( state ) => state.foo,
|
|
30
|
+
testSelector: ( state, key ) => state[ key ],
|
|
31
|
+
},
|
|
32
|
+
} );
|
|
33
|
+
} );
|
|
34
|
+
|
|
35
|
+
afterEach( () => {
|
|
36
|
+
jest.runOnlyPendingTimers();
|
|
37
|
+
jest.useRealTimers();
|
|
38
|
+
} );
|
|
39
|
+
|
|
40
|
+
const getTestComponent = ( mapSelectSpy, dependencyKey ) => ( props ) => {
|
|
41
|
+
const dependencies = props[ dependencyKey ];
|
|
42
|
+
mapSelectSpy.mockImplementation( ( select ) => ( {
|
|
43
|
+
results: select( 'testStore' ).testSelector( props.keyName ),
|
|
44
|
+
} ) );
|
|
45
|
+
const data = useQuerySelect( mapSelectSpy, [ dependencies ] );
|
|
46
|
+
return <div>{ data.results.data }</div>;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
it( 'passes the relevant data to the component', () => {
|
|
50
|
+
const selectSpy = jest.fn();
|
|
51
|
+
const TestComponent = jest
|
|
52
|
+
.fn()
|
|
53
|
+
.mockImplementation( getTestComponent( selectSpy, 'keyName' ) );
|
|
54
|
+
const testInstance = render(
|
|
55
|
+
<RegistryProvider value={ registry }>
|
|
56
|
+
<TestComponent keyName="foo" />
|
|
57
|
+
</RegistryProvider>
|
|
58
|
+
);
|
|
59
|
+
// 2 times expected
|
|
60
|
+
// - 1 for initial mount
|
|
61
|
+
// - 1 for after mount before subscription set.
|
|
62
|
+
expect( selectSpy ).toHaveBeenCalledTimes( 2 );
|
|
63
|
+
expect( TestComponent ).toHaveBeenCalledTimes( 2 );
|
|
64
|
+
|
|
65
|
+
// ensure expected state was rendered
|
|
66
|
+
expect( testInstance.findByText( 'bar' ) ).toBeTruthy();
|
|
67
|
+
} );
|
|
68
|
+
|
|
69
|
+
it( 'uses memoized selectors', () => {
|
|
70
|
+
const selectors = [];
|
|
71
|
+
const TestComponent = jest.fn().mockImplementation( ( props ) => {
|
|
72
|
+
useQuerySelect(
|
|
73
|
+
function ( query ) {
|
|
74
|
+
selectors.push( query( 'testStore' ) );
|
|
75
|
+
selectors.push( query( 'testStore' ) );
|
|
76
|
+
return null;
|
|
77
|
+
},
|
|
78
|
+
[ props.keyName ]
|
|
79
|
+
);
|
|
80
|
+
return <div />;
|
|
81
|
+
} );
|
|
82
|
+
|
|
83
|
+
render(
|
|
84
|
+
<RegistryProvider value={ registry }>
|
|
85
|
+
<TestComponent keyName="foo" />
|
|
86
|
+
</RegistryProvider>
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// ensure the selectors were properly memoized
|
|
90
|
+
expect( selectors ).toHaveLength( 4 );
|
|
91
|
+
expect( selectors[ 0 ] ).toHaveProperty( 'testSelector' );
|
|
92
|
+
expect( selectors[ 0 ] ).toBe( selectors[ 1 ] );
|
|
93
|
+
expect( selectors[ 1 ] ).toBe( selectors[ 2 ] );
|
|
94
|
+
|
|
95
|
+
// Re-render
|
|
96
|
+
render(
|
|
97
|
+
<RegistryProvider value={ registry }>
|
|
98
|
+
<TestComponent keyName="bar" />
|
|
99
|
+
</RegistryProvider>
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// ensure we still got the memoized results after re-rendering
|
|
103
|
+
expect( selectors ).toHaveLength( 8 );
|
|
104
|
+
expect( selectors[ 3 ] ).toHaveProperty( 'testSelector' );
|
|
105
|
+
expect( selectors[ 5 ] ).toBe( selectors[ 6 ] );
|
|
106
|
+
} );
|
|
107
|
+
|
|
108
|
+
it( 'returns the expected "response" details – no resolvers and arguments', () => {
|
|
109
|
+
let querySelectData;
|
|
110
|
+
const TestComponent = jest.fn().mockImplementation( () => {
|
|
111
|
+
querySelectData = useQuerySelect( function ( query ) {
|
|
112
|
+
return query( 'testStore' ).getFoo();
|
|
113
|
+
}, [] );
|
|
114
|
+
return <div />;
|
|
115
|
+
} );
|
|
116
|
+
|
|
117
|
+
render(
|
|
118
|
+
<RegistryProvider value={ registry }>
|
|
119
|
+
<TestComponent />
|
|
120
|
+
</RegistryProvider>
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
expect( querySelectData ).toEqual( {
|
|
124
|
+
data: 'bar',
|
|
125
|
+
isResolving: false,
|
|
126
|
+
hasResolved: false,
|
|
127
|
+
status: 'IDLE',
|
|
128
|
+
} );
|
|
129
|
+
} );
|
|
130
|
+
|
|
131
|
+
it( 'returns the expected "response" details – resolvers and arguments', async () => {
|
|
132
|
+
registry.register(
|
|
133
|
+
createReduxStore( 'resolverStore', {
|
|
134
|
+
reducer: ( state = { resolvedFoo: 0 }, action ) => {
|
|
135
|
+
if ( action?.type === 'RECEIVE_FOO' ) {
|
|
136
|
+
return { ...state, resolvedFoo: action.value };
|
|
137
|
+
}
|
|
138
|
+
return state;
|
|
139
|
+
},
|
|
140
|
+
actions: {
|
|
141
|
+
receiveFoo: ( value ) => ( {
|
|
142
|
+
type: 'RECEIVE_FOO',
|
|
143
|
+
value,
|
|
144
|
+
} ),
|
|
145
|
+
},
|
|
146
|
+
resolvers: {
|
|
147
|
+
getResolvedFoo: () => ( { dispatch } ) =>
|
|
148
|
+
dispatch.receiveFoo( 5 ),
|
|
149
|
+
},
|
|
150
|
+
selectors: {
|
|
151
|
+
getResolvedFoo: ( state, arg ) => state.resolvedFoo + arg,
|
|
152
|
+
},
|
|
153
|
+
} )
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
let querySelectData;
|
|
157
|
+
const TestComponent = jest.fn().mockImplementation( () => {
|
|
158
|
+
querySelectData = useQuerySelect( function ( query ) {
|
|
159
|
+
return query( 'resolverStore' ).getResolvedFoo( 10 );
|
|
160
|
+
}, [] );
|
|
161
|
+
return <div />;
|
|
162
|
+
} );
|
|
163
|
+
|
|
164
|
+
// Initial render, expect default values
|
|
165
|
+
render(
|
|
166
|
+
<RegistryProvider value={ registry }>
|
|
167
|
+
<TestComponent />
|
|
168
|
+
</RegistryProvider>
|
|
169
|
+
);
|
|
170
|
+
expect( querySelectData ).toEqual( {
|
|
171
|
+
data: 10,
|
|
172
|
+
isResolving: false,
|
|
173
|
+
hasResolved: false,
|
|
174
|
+
status: 'IDLE',
|
|
175
|
+
} );
|
|
176
|
+
|
|
177
|
+
await act( async () => {
|
|
178
|
+
jest.advanceTimersToNextTimer();
|
|
179
|
+
} );
|
|
180
|
+
|
|
181
|
+
// Re-render, expect resolved data
|
|
182
|
+
render(
|
|
183
|
+
<RegistryProvider value={ registry }>
|
|
184
|
+
<TestComponent />
|
|
185
|
+
</RegistryProvider>
|
|
186
|
+
);
|
|
187
|
+
expect( querySelectData ).toEqual( {
|
|
188
|
+
data: 15,
|
|
189
|
+
isResolving: false,
|
|
190
|
+
hasResolved: true,
|
|
191
|
+
status: 'SUCCESS',
|
|
192
|
+
} );
|
|
193
|
+
} );
|
|
194
|
+
} );
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import useQuerySelect from './use-query-select';
|
|
5
|
+
import { store as coreStore } from '../';
|
|
6
|
+
import { Status } from './constants';
|
|
7
|
+
|
|
8
|
+
interface EntityRecordResolution< RecordType > {
|
|
9
|
+
/** The requested entity record */
|
|
10
|
+
record: RecordType | null;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Is the record still being resolved?
|
|
14
|
+
*/
|
|
15
|
+
isResolving: boolean;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Is the record resolved by now?
|
|
19
|
+
*/
|
|
20
|
+
hasResolved: boolean;
|
|
21
|
+
|
|
22
|
+
/** Resolution status */
|
|
23
|
+
status: Status;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Resolves the specified entity record.
|
|
28
|
+
*
|
|
29
|
+
* @param kind Kind of the requested entity.
|
|
30
|
+
* @param name Name of the requested entity.
|
|
31
|
+
* @param recordId Record ID of the requested entity.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```js
|
|
35
|
+
* import { useEntityRecord } from '@wordpress/core-data';
|
|
36
|
+
*
|
|
37
|
+
* function PageTitleDisplay( { id } ) {
|
|
38
|
+
* const { record, isResolving } = useEntityRecord( 'postType', 'page', id );
|
|
39
|
+
*
|
|
40
|
+
* if ( isResolving ) {
|
|
41
|
+
* return 'Loading...';
|
|
42
|
+
* }
|
|
43
|
+
*
|
|
44
|
+
* return record.title;
|
|
45
|
+
* }
|
|
46
|
+
*
|
|
47
|
+
* // Rendered in the application:
|
|
48
|
+
* // <PageTitleDisplay id={ 1 } />
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* In the above example, when `PageTitleDisplay` is rendered into an
|
|
52
|
+
* application, the page and the resolution details will be retrieved from
|
|
53
|
+
* the store state using `getEntityRecord()`, or resolved if missing.
|
|
54
|
+
*
|
|
55
|
+
* @return {EntityRecordResolution<RecordType>} Entity record data.
|
|
56
|
+
* @template RecordType
|
|
57
|
+
*/
|
|
58
|
+
export default function __experimentalUseEntityRecord< RecordType >(
|
|
59
|
+
kind: string,
|
|
60
|
+
name: string,
|
|
61
|
+
recordId: string | number
|
|
62
|
+
): EntityRecordResolution< RecordType > {
|
|
63
|
+
const { data: record, ...rest } = useQuerySelect(
|
|
64
|
+
( query ) => query( coreStore ).getEntityRecord( kind, name, recordId ),
|
|
65
|
+
[ kind, name, recordId ]
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
record,
|
|
70
|
+
...rest,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { addQueryArgs } from '@wordpress/url';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Internal dependencies
|
|
8
|
+
*/
|
|
9
|
+
import useQuerySelect from './use-query-select';
|
|
10
|
+
import { store as coreStore } from '../';
|
|
11
|
+
import { Status } from './constants';
|
|
12
|
+
|
|
13
|
+
interface EntityRecordsResolution< RecordType > {
|
|
14
|
+
/** The requested entity record */
|
|
15
|
+
records: RecordType[] | null;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Is the record still being resolved?
|
|
19
|
+
*/
|
|
20
|
+
isResolving: boolean;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Is the record resolved by now?
|
|
24
|
+
*/
|
|
25
|
+
hasResolved: boolean;
|
|
26
|
+
|
|
27
|
+
/** Resolution status */
|
|
28
|
+
status: Status;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Resolves the specified entity records.
|
|
33
|
+
*
|
|
34
|
+
* @param kind Kind of the requested entities.
|
|
35
|
+
* @param name Name of the requested entities.
|
|
36
|
+
* @param queryArgs HTTP query for the requested entities.
|
|
37
|
+
* @example
|
|
38
|
+
* ```js
|
|
39
|
+
* import { useEntityRecord } from '@wordpress/core-data';
|
|
40
|
+
*
|
|
41
|
+
* function PageTitlesList() {
|
|
42
|
+
* const { records, isResolving } = useEntityRecords( 'postType', 'page' );
|
|
43
|
+
*
|
|
44
|
+
* if ( isResolving ) {
|
|
45
|
+
* return 'Loading...';
|
|
46
|
+
* }
|
|
47
|
+
*
|
|
48
|
+
* return (
|
|
49
|
+
* <ul>
|
|
50
|
+
* {records.map(( page ) => (
|
|
51
|
+
* <li>{ page.title }</li>
|
|
52
|
+
* ))}
|
|
53
|
+
* </ul>
|
|
54
|
+
* );
|
|
55
|
+
* }
|
|
56
|
+
*
|
|
57
|
+
* // Rendered in the application:
|
|
58
|
+
* // <PageTitlesList />
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* In the above example, when `PageTitlesList` is rendered into an
|
|
62
|
+
* application, the list of records and the resolution details will be retrieved from
|
|
63
|
+
* the store state using `getEntityRecords()`, or resolved if missing.
|
|
64
|
+
*
|
|
65
|
+
* @return {EntityRecordsResolution<RecordType>} Entity records data.
|
|
66
|
+
* @template RecordType
|
|
67
|
+
*/
|
|
68
|
+
export default function __experimentalUseEntityRecords< RecordType >(
|
|
69
|
+
kind: string,
|
|
70
|
+
name: string,
|
|
71
|
+
queryArgs: unknown = {}
|
|
72
|
+
): EntityRecordsResolution< RecordType > {
|
|
73
|
+
// Serialize queryArgs to a string that can be safely used as a React dep.
|
|
74
|
+
// We can't just pass queryArgs as one of the deps, because if it is passed
|
|
75
|
+
// as an object literal, then it will be a different object on each call even
|
|
76
|
+
// if the values remain the same.
|
|
77
|
+
const queryAsString = addQueryArgs( '', queryArgs );
|
|
78
|
+
|
|
79
|
+
const { data: records, ...rest } = useQuerySelect(
|
|
80
|
+
( query ) =>
|
|
81
|
+
query( coreStore ).getEntityRecords( kind, name, queryArgs ),
|
|
82
|
+
[ kind, name, queryAsString ]
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
records,
|
|
87
|
+
...rest,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { useSelect } from '@wordpress/data';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Internal dependencies
|
|
8
|
+
*/
|
|
9
|
+
import memoize from './memoize';
|
|
10
|
+
import { Status } from './constants';
|
|
11
|
+
|
|
12
|
+
export const META_SELECTORS = [
|
|
13
|
+
'getIsResolving',
|
|
14
|
+
'hasStartedResolution',
|
|
15
|
+
'hasFinishedResolution',
|
|
16
|
+
'isResolving',
|
|
17
|
+
'getCachedResolvers',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
interface QuerySelectResponse {
|
|
21
|
+
/** the requested selector return value */
|
|
22
|
+
data: Object;
|
|
23
|
+
|
|
24
|
+
/** is the record still being resolved? Via the `getIsResolving` meta-selector */
|
|
25
|
+
isResolving: boolean;
|
|
26
|
+
|
|
27
|
+
/** was the resolution started? Via the `hasStartedResolution` meta-selector */
|
|
28
|
+
hasStarted: boolean;
|
|
29
|
+
|
|
30
|
+
/** has the resolution finished? Via the `hasFinishedResolution` meta-selector. */
|
|
31
|
+
hasResolved: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Like useSelect, but the selectors return objects containing
|
|
36
|
+
* both the original data AND the resolution info.
|
|
37
|
+
*
|
|
38
|
+
* @param {Function} mapQuerySelect see useSelect
|
|
39
|
+
* @param {Array} deps see useSelect
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```js
|
|
43
|
+
* import { useQuerySelect } from '@wordpress/data';
|
|
44
|
+
* import { store as coreDataStore } from '@wordpress/core-data';
|
|
45
|
+
*
|
|
46
|
+
* function PageTitleDisplay( { id } ) {
|
|
47
|
+
* const { data: page, isResolving } = useQuerySelect( ( query ) => {
|
|
48
|
+
* return query( coreDataStore ).getEntityRecord( 'postType', 'page', id )
|
|
49
|
+
* }, [ id ] );
|
|
50
|
+
*
|
|
51
|
+
* if ( isResolving ) {
|
|
52
|
+
* return 'Loading...';
|
|
53
|
+
* }
|
|
54
|
+
*
|
|
55
|
+
* return page.title;
|
|
56
|
+
* }
|
|
57
|
+
*
|
|
58
|
+
* // Rendered in the application:
|
|
59
|
+
* // <PageTitleDisplay id={ 10 } />
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* In the above example, when `PageTitleDisplay` is rendered into an
|
|
63
|
+
* application, the page and the resolution details will be retrieved from
|
|
64
|
+
* the store state using the `mapSelect` callback on `useQuerySelect`.
|
|
65
|
+
*
|
|
66
|
+
* If the id prop changes then any page in the state for that id is
|
|
67
|
+
* retrieved. If the id prop doesn't change and other props are passed in
|
|
68
|
+
* that do change, the title will not change because the dependency is just
|
|
69
|
+
* the id.
|
|
70
|
+
* @see useSelect
|
|
71
|
+
*
|
|
72
|
+
* @return {QuerySelectResponse} Queried data.
|
|
73
|
+
*/
|
|
74
|
+
export default function __experimentalUseQuerySelect( mapQuerySelect, deps ) {
|
|
75
|
+
return useSelect( ( select, registry ) => {
|
|
76
|
+
const resolve = ( store ) => enrichSelectors( select( store ) );
|
|
77
|
+
return mapQuerySelect( resolve, registry );
|
|
78
|
+
}, deps );
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
type QuerySelector = ( ...args ) => QuerySelectResponse;
|
|
82
|
+
interface EnrichedSelectors {
|
|
83
|
+
[ key: string ]: QuerySelector;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Transform simple selectors into ones that return an object with the
|
|
88
|
+
* original return value AND the resolution info.
|
|
89
|
+
*
|
|
90
|
+
* @param {Object} selectors Selectors to enrich
|
|
91
|
+
* @return {EnrichedSelectors} Enriched selectors
|
|
92
|
+
*/
|
|
93
|
+
const enrichSelectors = memoize( ( selectors ) => {
|
|
94
|
+
const resolvers = {};
|
|
95
|
+
for ( const selectorName in selectors ) {
|
|
96
|
+
if ( META_SELECTORS.includes( selectorName ) ) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
Object.defineProperty( resolvers, selectorName, {
|
|
100
|
+
get: () => ( ...args ) => {
|
|
101
|
+
const { getIsResolving, hasFinishedResolution } = selectors;
|
|
102
|
+
const isResolving = !! getIsResolving( selectorName, args );
|
|
103
|
+
const hasResolved =
|
|
104
|
+
! isResolving &&
|
|
105
|
+
hasFinishedResolution( selectorName, args );
|
|
106
|
+
const data = selectors[ selectorName ]( ...args );
|
|
107
|
+
|
|
108
|
+
let status;
|
|
109
|
+
if ( isResolving ) {
|
|
110
|
+
status = Status.Resolving;
|
|
111
|
+
} else if ( hasResolved ) {
|
|
112
|
+
if ( data ) {
|
|
113
|
+
status = Status.Success;
|
|
114
|
+
} else {
|
|
115
|
+
status = Status.Error;
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
status = Status.Idle;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
data,
|
|
123
|
+
status,
|
|
124
|
+
isResolving,
|
|
125
|
+
hasResolved,
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
} );
|
|
129
|
+
}
|
|
130
|
+
return resolvers;
|
|
131
|
+
} );
|
package/src/index.js
CHANGED
|
@@ -59,7 +59,6 @@ const storeConfig = () => ( {
|
|
|
59
59
|
actions: { ...actions, ...entityActions, ...createLocksActions() },
|
|
60
60
|
selectors: { ...selectors, ...entitySelectors },
|
|
61
61
|
resolvers: { ...resolvers, ...entityResolvers },
|
|
62
|
-
__experimentalUseThunks: true,
|
|
63
62
|
} );
|
|
64
63
|
|
|
65
64
|
/**
|
|
@@ -74,5 +73,8 @@ export const store = createReduxStore( STORE_NAME, storeConfig() );
|
|
|
74
73
|
register( store );
|
|
75
74
|
|
|
76
75
|
export { default as EntityProvider } from './entity-provider';
|
|
76
|
+
export { default as __experimentalUseEntityRecord } from './hooks/use-entity-record';
|
|
77
|
+
export { default as __experimentalUseEntityRecords } from './hooks/use-entity-records';
|
|
77
78
|
export * from './entity-provider';
|
|
78
79
|
export * from './fetch';
|
|
80
|
+
export * from './entity-types';
|