@wordpress/core-data 4.1.0 → 4.1.2
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 +9 -3
- package/build/actions.js +1 -1
- package/build/actions.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 +67 -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/resolvers.js +2 -17
- package/build/resolvers.js.map +1 -1
- package/build/types/attachment.js +6 -0
- package/build/types/attachment.js.map +1 -0
- package/build/types/base-entity-types.js +47 -0
- package/build/types/base-entity-types.js.map +1 -0
- package/build/types/comment.js +6 -0
- package/build/types/comment.js.map +1 -0
- package/build/types/helpers.js +6 -0
- package/build/types/helpers.js.map +1 -0
- package/build/types/index.js +6 -0
- package/build/types/index.js.map +1 -0
- package/build/types/menu-location.js +6 -0
- package/build/types/menu-location.js.map +1 -0
- package/build/types/nav-menu-item.js +6 -0
- package/build/types/nav-menu-item.js.map +1 -0
- package/build/types/nav-menu.js +6 -0
- package/build/types/nav-menu.js.map +1 -0
- package/build/types/navigation-area.js +6 -0
- package/build/types/navigation-area.js.map +1 -0
- package/build/types/page.js +6 -0
- package/build/types/page.js.map +1 -0
- package/build/types/plugin.js +6 -0
- package/build/types/plugin.js.map +1 -0
- package/build/types/post.js +6 -0
- package/build/types/post.js.map +1 -0
- package/build/types/settings.js +6 -0
- package/build/types/settings.js.map +1 -0
- package/build/types/sidebar.js +6 -0
- package/build/types/sidebar.js.map +1 -0
- package/build/types/taxonomy.js +6 -0
- package/build/types/taxonomy.js.map +1 -0
- package/build/types/theme.js +6 -0
- package/build/types/theme.js.map +1 -0
- package/build/types/type.js +6 -0
- package/build/types/type.js.map +1 -0
- package/build/types/user.js +6 -0
- package/build/types/user.js.map +1 -0
- package/build/types/widget-type.js +6 -0
- package/build/types/widget-type.js.map +1 -0
- package/build/types/widget.js +6 -0
- package/build/types/widget.js.map +1 -0
- package/build/types/wp-template-part.js +6 -0
- package/build/types/wp-template-part.js.map +1 -0
- package/build/types/wp-template.js +6 -0
- package/build/types/wp-template.js.map +1 -0
- package/build-module/actions.js +1 -1
- package/build-module/actions.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 +56 -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/resolvers.js +3 -18
- package/build-module/resolvers.js.map +1 -1
- package/build-module/types/attachment.js +2 -0
- package/build-module/types/attachment.js.map +1 -0
- package/build-module/types/base-entity-types.js +39 -0
- package/build-module/types/base-entity-types.js.map +1 -0
- package/build-module/types/comment.js +2 -0
- package/build-module/types/comment.js.map +1 -0
- package/build-module/types/helpers.js +2 -0
- package/build-module/types/helpers.js.map +1 -0
- package/build-module/types/index.js +2 -0
- package/build-module/types/index.js.map +1 -0
- package/build-module/types/menu-location.js +2 -0
- package/build-module/types/menu-location.js.map +1 -0
- package/build-module/types/nav-menu-item.js +2 -0
- package/build-module/types/nav-menu-item.js.map +1 -0
- package/build-module/types/nav-menu.js +2 -0
- package/build-module/types/nav-menu.js.map +1 -0
- package/build-module/types/navigation-area.js +2 -0
- package/build-module/types/navigation-area.js.map +1 -0
- package/build-module/types/page.js +2 -0
- package/build-module/types/page.js.map +1 -0
- package/build-module/types/plugin.js +2 -0
- package/build-module/types/plugin.js.map +1 -0
- package/build-module/types/post.js +2 -0
- package/build-module/types/post.js.map +1 -0
- package/build-module/types/settings.js +2 -0
- package/build-module/types/settings.js.map +1 -0
- package/build-module/types/sidebar.js +2 -0
- package/build-module/types/sidebar.js.map +1 -0
- package/build-module/types/taxonomy.js +2 -0
- package/build-module/types/taxonomy.js.map +1 -0
- package/build-module/types/theme.js +2 -0
- package/build-module/types/theme.js.map +1 -0
- package/build-module/types/type.js +2 -0
- package/build-module/types/type.js.map +1 -0
- package/build-module/types/user.js +2 -0
- package/build-module/types/user.js.map +1 -0
- package/build-module/types/widget-type.js +2 -0
- package/build-module/types/widget-type.js.map +1 -0
- package/build-module/types/widget.js +2 -0
- package/build-module/types/widget.js.map +1 -0
- package/build-module/types/wp-template-part.js +2 -0
- package/build-module/types/wp-template-part.js.map +1 -0
- package/build-module/types/wp-template.js +2 -0
- package/build-module/types/wp-template.js.map +1 -0
- package/package.json +12 -11
- package/src/actions.js +1 -1
- 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 +79 -0
- package/src/hooks/use-query-select.ts +131 -0
- package/src/index.js +3 -1
- package/src/resolvers.js +3 -17
- package/src/test/resolvers.js +4 -10
- package/src/types/README.md +193 -0
- package/src/types/attachment.ts +146 -0
- package/src/types/base-entity-types.ts +36 -0
- package/src/types/comment.ts +96 -0
- package/src/types/helpers.ts +153 -0
- package/src/types/index.ts +72 -0
- package/src/types/menu-location.ts +29 -0
- package/src/types/nav-menu-item.ts +106 -0
- package/src/types/nav-menu.ts +53 -0
- package/src/types/navigation-area.ts +29 -0
- package/src/types/page.ts +144 -0
- package/src/types/plugin.ts +74 -0
- package/src/types/post.ts +153 -0
- package/src/types/settings.ts +93 -0
- package/src/types/sidebar.ts +60 -0
- package/src/types/taxonomy.ts +92 -0
- package/src/types/theme.ts +222 -0
- package/src/types/type.ts +80 -0
- package/src/types/user.ts +109 -0
- package/src/types/widget-type.ts +37 -0
- package/src/types/widget.ts +64 -0
- package/src/types/wp-template-part.ts +94 -0
- package/src/types/wp-template.ts +94 -0
|
@@ -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,79 @@
|
|
|
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 EntityRecordsResolution< RecordType > {
|
|
9
|
+
/** The requested entity record */
|
|
10
|
+
records: 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 records.
|
|
28
|
+
*
|
|
29
|
+
* @param kind Kind of the requested entities.
|
|
30
|
+
* @param name Name of the requested entities.
|
|
31
|
+
* @param queryArgs HTTP query for the requested entities.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```js
|
|
35
|
+
* import { useEntityRecord } from '@wordpress/core-data';
|
|
36
|
+
*
|
|
37
|
+
* function PageTitlesList() {
|
|
38
|
+
* const { records, isResolving } = useEntityRecords( 'postType', 'page' );
|
|
39
|
+
*
|
|
40
|
+
* if ( isResolving ) {
|
|
41
|
+
* return 'Loading...';
|
|
42
|
+
* }
|
|
43
|
+
*
|
|
44
|
+
* return (
|
|
45
|
+
* <ul>
|
|
46
|
+
* {records.map(( page ) => (
|
|
47
|
+
* <li>{ page.title }</li>
|
|
48
|
+
* ))}
|
|
49
|
+
* </ul>
|
|
50
|
+
* );
|
|
51
|
+
* }
|
|
52
|
+
*
|
|
53
|
+
* // Rendered in the application:
|
|
54
|
+
* // <PageTitlesList />
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* In the above example, when `PageTitlesList` is rendered into an
|
|
58
|
+
* application, the list of records and the resolution details will be retrieved from
|
|
59
|
+
* the store state using `getEntityRecords()`, or resolved if missing.
|
|
60
|
+
*
|
|
61
|
+
* @return {EntityRecordsResolution<RecordType>} Entity records data.
|
|
62
|
+
* @template RecordType
|
|
63
|
+
*/
|
|
64
|
+
export default function __experimentalUseEntityRecords< RecordType >(
|
|
65
|
+
kind: string,
|
|
66
|
+
name: string,
|
|
67
|
+
queryArgs: unknown = {}
|
|
68
|
+
): EntityRecordsResolution< RecordType > {
|
|
69
|
+
const { data: records, ...rest } = useQuerySelect(
|
|
70
|
+
( query ) =>
|
|
71
|
+
query( coreStore ).getEntityRecords( kind, name, queryArgs ),
|
|
72
|
+
[ kind, name, queryArgs ]
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
records,
|
|
77
|
+
...rest,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -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 './types';
|
package/src/resolvers.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
-
import { find, includes, get,
|
|
4
|
+
import { find, includes, get, compact, uniq } from 'lodash';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* WordPress dependencies
|
|
@@ -283,11 +283,7 @@ export const canUser = ( action, resource, id ) => async ( { dispatch } ) => {
|
|
|
283
283
|
try {
|
|
284
284
|
response = await apiFetch( {
|
|
285
285
|
path,
|
|
286
|
-
|
|
287
|
-
// a bug in the REST API which causes the Allow header to not be sent on
|
|
288
|
-
// OPTIONS requests to /posts/:id routes.
|
|
289
|
-
// https://core.trac.wordpress.org/ticket/45753
|
|
290
|
-
method: id ? 'GET' : 'OPTIONS',
|
|
286
|
+
method: 'OPTIONS',
|
|
291
287
|
parse: false,
|
|
292
288
|
} );
|
|
293
289
|
} catch ( error ) {
|
|
@@ -296,17 +292,7 @@ export const canUser = ( action, resource, id ) => async ( { dispatch } ) => {
|
|
|
296
292
|
return;
|
|
297
293
|
}
|
|
298
294
|
|
|
299
|
-
|
|
300
|
-
if ( hasIn( response, [ 'headers', 'get' ] ) ) {
|
|
301
|
-
// If the request is fetched using the fetch api, the header can be
|
|
302
|
-
// retrieved using the 'get' method.
|
|
303
|
-
allowHeader = response.headers.get( 'allow' );
|
|
304
|
-
} else {
|
|
305
|
-
// If the request was preloaded server-side and is returned by the
|
|
306
|
-
// preloading middleware, the header will be a simple property.
|
|
307
|
-
allowHeader = get( response, [ 'headers', 'Allow' ], '' );
|
|
308
|
-
}
|
|
309
|
-
|
|
295
|
+
const allowHeader = response.headers.get( 'allow' );
|
|
310
296
|
const key = compact( [ action, resource, id ] ).join( '/' );
|
|
311
297
|
const isAllowed = includes( allowHeader, method );
|
|
312
298
|
dispatch.receiveUserPermission( key, isAllowed );
|
package/src/test/resolvers.js
CHANGED
|
@@ -312,9 +312,7 @@ describe( 'canUser', () => {
|
|
|
312
312
|
} );
|
|
313
313
|
|
|
314
314
|
triggerFetch.mockImplementation( () => ( {
|
|
315
|
-
headers:
|
|
316
|
-
Allow: 'GET',
|
|
317
|
-
},
|
|
315
|
+
headers: new Map( [ [ 'allow', 'GET' ] ] ),
|
|
318
316
|
} ) );
|
|
319
317
|
|
|
320
318
|
await canUser( 'create', 'media' )( { dispatch } );
|
|
@@ -337,9 +335,7 @@ describe( 'canUser', () => {
|
|
|
337
335
|
} );
|
|
338
336
|
|
|
339
337
|
triggerFetch.mockImplementation( () => ( {
|
|
340
|
-
headers:
|
|
341
|
-
Allow: 'POST, GET, PUT, DELETE',
|
|
342
|
-
},
|
|
338
|
+
headers: new Map( [ [ 'allow', 'POST, GET, PUT, DELETE' ] ] ),
|
|
343
339
|
} ) );
|
|
344
340
|
|
|
345
341
|
await canUser( 'create', 'media' )( { dispatch } );
|
|
@@ -362,16 +358,14 @@ describe( 'canUser', () => {
|
|
|
362
358
|
} );
|
|
363
359
|
|
|
364
360
|
triggerFetch.mockImplementation( () => ( {
|
|
365
|
-
headers:
|
|
366
|
-
Allow: 'POST, GET, PUT, DELETE',
|
|
367
|
-
},
|
|
361
|
+
headers: new Map( [ [ 'allow', 'POST, GET, PUT, DELETE' ] ] ),
|
|
368
362
|
} ) );
|
|
369
363
|
|
|
370
364
|
await canUser( 'create', 'blocks', 123 )( { dispatch } );
|
|
371
365
|
|
|
372
366
|
expect( triggerFetch ).toHaveBeenCalledWith( {
|
|
373
367
|
path: '/wp/v2/blocks/123',
|
|
374
|
-
method: '
|
|
368
|
+
method: 'OPTIONS',
|
|
375
369
|
parse: false,
|
|
376
370
|
} );
|
|
377
371
|
|