@wordpress/data 6.2.1 → 6.3.1-next.a55ed9455a.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 +8 -0
- package/build/components/use-select/index.js +1 -1
- package/build/components/use-select/index.js.map +1 -1
- package/build/factory.js +1 -1
- package/build/factory.js.map +1 -1
- package/build/plugins/persistence/index.js +114 -12
- package/build/plugins/persistence/index.js.map +1 -1
- package/build/redux-store/index.js +25 -14
- package/build/redux-store/index.js.map +1 -1
- package/build/redux-store/metadata/actions.js +51 -8
- package/build/redux-store/metadata/actions.js.map +1 -1
- package/build/redux-store/metadata/reducer.js +58 -5
- package/build/redux-store/metadata/reducer.js.map +1 -1
- package/build/redux-store/metadata/selectors.js +93 -26
- package/build/redux-store/metadata/selectors.js.map +1 -1
- package/build/redux-store/metadata/utils.js +31 -13
- package/build/redux-store/metadata/utils.js.map +1 -1
- package/build/registry.js +0 -2
- package/build/registry.js.map +1 -1
- package/build/resolvers-cache-middleware.js +3 -3
- package/build/resolvers-cache-middleware.js.map +1 -1
- package/build-module/components/use-select/index.js +1 -1
- package/build-module/components/use-select/index.js.map +1 -1
- package/build-module/factory.js +1 -1
- package/build-module/factory.js.map +1 -1
- package/build-module/plugins/persistence/index.js +111 -12
- package/build-module/plugins/persistence/index.js.map +1 -1
- package/build-module/redux-store/index.js +25 -14
- package/build-module/redux-store/index.js.map +1 -1
- package/build-module/redux-store/metadata/actions.js +47 -8
- package/build-module/redux-store/metadata/actions.js.map +1 -1
- package/build-module/redux-store/metadata/reducer.js +59 -6
- package/build-module/redux-store/metadata/reducer.js.map +1 -1
- package/build-module/redux-store/metadata/selectors.js +86 -26
- package/build-module/redux-store/metadata/selectors.js.map +1 -1
- package/build-module/redux-store/metadata/utils.js +29 -13
- package/build-module/redux-store/metadata/utils.js.map +1 -1
- package/build-module/registry.js +0 -2
- package/build-module/registry.js.map +1 -1
- package/build-module/resolvers-cache-middleware.js +3 -3
- package/build-module/resolvers-cache-middleware.js.map +1 -1
- package/build-types/redux-store/metadata/actions.d.ts +44 -11
- package/build-types/redux-store/metadata/actions.d.ts.map +1 -1
- package/build-types/redux-store/metadata/reducer.d.ts +10 -2
- package/build-types/redux-store/metadata/reducer.d.ts.map +1 -1
- package/build-types/redux-store/metadata/selectors.d.ts +56 -16
- package/build-types/redux-store/metadata/selectors.d.ts.map +1 -1
- package/build-types/redux-store/metadata/utils.d.ts +20 -1
- package/build-types/redux-store/metadata/utils.d.ts.map +1 -1
- package/build-types/types.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/components/use-select/index.js +1 -1
- package/src/components/use-select/test/index.js +7 -7
- package/src/components/with-select/test/index.js +3 -3
- package/src/factory.js +1 -1
- package/src/plugins/persistence/index.js +142 -8
- package/src/plugins/persistence/test/index.js +405 -0
- package/src/redux-store/index.js +44 -19
- package/src/redux-store/metadata/actions.js +47 -8
- package/src/redux-store/metadata/reducer.ts +63 -9
- package/src/redux-store/metadata/selectors.js +81 -23
- package/src/redux-store/metadata/test/reducer.js +98 -18
- package/src/redux-store/metadata/test/selectors.js +266 -45
- package/src/redux-store/metadata/test/utils.js +28 -1
- package/src/redux-store/metadata/utils.ts +59 -0
- package/src/redux-store/test/index.js +42 -0
- package/src/registry.js +0 -2
- package/src/resolvers-cache-middleware.js +4 -3
- package/src/test/controls.js +7 -7
- package/src/test/registry.js +6 -6
- package/src/types.ts +1 -3
- package/tsconfig.tsbuildinfo +1 -1
- package/src/redux-store/metadata/utils.js +0 -37
|
@@ -1,106 +1,327 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* WordPress dependencies
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
4
|
+
import { createRegistry } from '@wordpress/data';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
6
|
+
jest.useRealTimers();
|
|
7
|
+
const testStore = {
|
|
8
|
+
reducer: ( state = null, action ) => {
|
|
9
|
+
if ( action.type === 'RECEIVE' ) {
|
|
10
|
+
return action.items;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return state;
|
|
14
|
+
},
|
|
15
|
+
selectors: {
|
|
16
|
+
getFoo: ( state ) => state,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
async function resolve( registry, selector ) {
|
|
21
|
+
try {
|
|
22
|
+
await registry.resolveSelect( 'store' )[ selector ]();
|
|
23
|
+
} catch ( e ) {}
|
|
24
|
+
}
|
|
15
25
|
|
|
16
26
|
describe( 'getIsResolving', () => {
|
|
27
|
+
let registry;
|
|
28
|
+
beforeEach( () => {
|
|
29
|
+
registry = createRegistry();
|
|
30
|
+
registry.registerStore( 'testStore', testStore );
|
|
31
|
+
} );
|
|
32
|
+
|
|
17
33
|
it( 'should return undefined if no state by reducerKey, selectorName', () => {
|
|
18
|
-
const
|
|
19
|
-
|
|
34
|
+
const result = registry
|
|
35
|
+
.select( 'testStore' )
|
|
36
|
+
.getIsResolving( 'getFoo', [] );
|
|
20
37
|
|
|
21
38
|
expect( result ).toBe( undefined );
|
|
22
39
|
} );
|
|
23
40
|
|
|
24
41
|
it( 'should return undefined if state by reducerKey, selectorName, but not args', () => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
42
|
+
registry.dispatch( 'testStore' ).startResolution( 'getFoo', [] );
|
|
43
|
+
const result = registry
|
|
44
|
+
.select( 'testStore' )
|
|
45
|
+
.getIsResolving( 'getFoo', [ 'bar' ] );
|
|
29
46
|
|
|
30
47
|
expect( result ).toBe( undefined );
|
|
31
48
|
} );
|
|
32
49
|
|
|
33
50
|
it( 'should return value by reducerKey, selectorName', () => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
51
|
+
registry.dispatch( 'testStore' ).startResolution( 'getFoo', [] );
|
|
52
|
+
const result = registry
|
|
53
|
+
.select( 'testStore' )
|
|
54
|
+
.getIsResolving( 'getFoo', [] );
|
|
38
55
|
|
|
39
56
|
expect( result ).toBe( true );
|
|
40
57
|
} );
|
|
58
|
+
|
|
59
|
+
it( 'should normalize args ard return the right value', () => {
|
|
60
|
+
registry.dispatch( 'testStore' ).startResolution( 'getFoo', [] );
|
|
61
|
+
const { getIsResolving } = registry.select( 'testStore' );
|
|
62
|
+
|
|
63
|
+
expect( getIsResolving( 'getFoo' ) ).toBe( true );
|
|
64
|
+
expect( getIsResolving( 'getFoo', [ undefined ] ) ).toBe( true );
|
|
65
|
+
expect( getIsResolving( 'getFoo', [ undefined, undefined ] ) ).toBe(
|
|
66
|
+
true
|
|
67
|
+
);
|
|
68
|
+
} );
|
|
41
69
|
} );
|
|
42
70
|
|
|
43
71
|
describe( 'hasStartedResolution', () => {
|
|
72
|
+
let registry;
|
|
73
|
+
beforeEach( () => {
|
|
74
|
+
registry = createRegistry();
|
|
75
|
+
registry.registerStore( 'testStore', testStore );
|
|
76
|
+
} );
|
|
77
|
+
|
|
44
78
|
it( 'returns false if not has started', () => {
|
|
45
|
-
const
|
|
46
|
-
|
|
79
|
+
const result = registry
|
|
80
|
+
.select( 'testStore' )
|
|
81
|
+
.hasStartedResolution( 'getFoo', [] );
|
|
47
82
|
|
|
48
83
|
expect( result ).toBe( false );
|
|
49
84
|
} );
|
|
50
85
|
|
|
51
86
|
it( 'returns true if has started', () => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const result = hasStartedResolution( state, 'getFoo', [] );
|
|
87
|
+
registry.dispatch( 'testStore' ).startResolution( 'getFoo', [] );
|
|
88
|
+
const { hasStartedResolution } = registry.select( 'testStore' );
|
|
89
|
+
const result = hasStartedResolution( 'getFoo', [] );
|
|
56
90
|
|
|
57
91
|
expect( result ).toBe( true );
|
|
58
92
|
} );
|
|
59
93
|
} );
|
|
60
94
|
|
|
61
95
|
describe( 'hasFinishedResolution', () => {
|
|
96
|
+
let registry;
|
|
97
|
+
beforeEach( () => {
|
|
98
|
+
registry = createRegistry();
|
|
99
|
+
registry.registerStore( 'testStore', testStore );
|
|
100
|
+
} );
|
|
101
|
+
|
|
62
102
|
it( 'returns false if not has finished', () => {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const result = hasFinishedResolution( state, 'getFoo', [] );
|
|
103
|
+
registry.dispatch( 'testStore' ).startResolution( 'getFoo', [] );
|
|
104
|
+
const { hasFinishedResolution } = registry.select( 'testStore' );
|
|
105
|
+
const result = hasFinishedResolution( 'getFoo', [] );
|
|
67
106
|
|
|
68
107
|
expect( result ).toBe( false );
|
|
69
108
|
} );
|
|
70
109
|
|
|
71
110
|
it( 'returns true if has finished', () => {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const result = hasFinishedResolution( state, 'getFoo', [] );
|
|
111
|
+
registry.dispatch( 'testStore' ).finishResolution( 'getFoo', [] );
|
|
112
|
+
const { hasFinishedResolution } = registry.select( 'testStore' );
|
|
113
|
+
const result = hasFinishedResolution( 'getFoo', [] );
|
|
76
114
|
|
|
77
115
|
expect( result ).toBe( true );
|
|
78
116
|
} );
|
|
79
117
|
} );
|
|
80
118
|
|
|
81
119
|
describe( 'isResolving', () => {
|
|
120
|
+
let registry;
|
|
121
|
+
beforeEach( () => {
|
|
122
|
+
registry = createRegistry();
|
|
123
|
+
registry.registerStore( 'testStore', testStore );
|
|
124
|
+
} );
|
|
125
|
+
|
|
82
126
|
it( 'returns false if not has started', () => {
|
|
83
|
-
const
|
|
84
|
-
const result = isResolving(
|
|
127
|
+
const { isResolving } = registry.select( 'testStore' );
|
|
128
|
+
const result = isResolving( 'getFoo', [] );
|
|
85
129
|
|
|
86
130
|
expect( result ).toBe( false );
|
|
87
131
|
} );
|
|
88
132
|
|
|
89
133
|
it( 'returns false if has finished', () => {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
};
|
|
93
|
-
const result = isResolving(
|
|
134
|
+
registry.dispatch( 'testStore' ).startResolution( 'getFoo', [] );
|
|
135
|
+
registry.dispatch( 'testStore' ).finishResolution( 'getFoo', [] );
|
|
136
|
+
const { isResolving } = registry.select( 'testStore' );
|
|
137
|
+
const result = isResolving( 'getFoo', [] );
|
|
94
138
|
|
|
95
139
|
expect( result ).toBe( false );
|
|
96
140
|
} );
|
|
97
141
|
|
|
98
142
|
it( 'returns true if has started but not finished', () => {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const result = isResolving( state, 'getFoo', [] );
|
|
143
|
+
registry.dispatch( 'testStore' ).startResolution( 'getFoo', [] );
|
|
144
|
+
const { isResolving } = registry.select( 'testStore' );
|
|
145
|
+
const result = isResolving( 'getFoo', [] );
|
|
103
146
|
|
|
104
147
|
expect( result ).toBe( true );
|
|
105
148
|
} );
|
|
106
149
|
} );
|
|
150
|
+
|
|
151
|
+
describe( 'hasResolutionFailed', () => {
|
|
152
|
+
let registry;
|
|
153
|
+
|
|
154
|
+
beforeEach( () => {
|
|
155
|
+
registry = createRegistry();
|
|
156
|
+
} );
|
|
157
|
+
|
|
158
|
+
it( 'returns false if the resolution has succeeded', async () => {
|
|
159
|
+
registry.registerStore( 'store', {
|
|
160
|
+
reducer: ( state = null, action ) => {
|
|
161
|
+
if ( action.type === 'RECEIVE' ) {
|
|
162
|
+
return action.items;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return state;
|
|
166
|
+
},
|
|
167
|
+
selectors: {
|
|
168
|
+
getFoo: ( state ) => state,
|
|
169
|
+
},
|
|
170
|
+
resolvers: {
|
|
171
|
+
getFoo: () => {},
|
|
172
|
+
},
|
|
173
|
+
} );
|
|
174
|
+
|
|
175
|
+
expect(
|
|
176
|
+
registry.select( 'store' ).hasResolutionFailed( 'getFoo' )
|
|
177
|
+
).toBeFalsy();
|
|
178
|
+
|
|
179
|
+
registry.select( 'store' ).getFoo();
|
|
180
|
+
|
|
181
|
+
expect(
|
|
182
|
+
registry.select( 'store' ).hasResolutionFailed( 'getFoo' )
|
|
183
|
+
).toBeFalsy();
|
|
184
|
+
} );
|
|
185
|
+
|
|
186
|
+
it( 'returns true if the resolution has failed', async () => {
|
|
187
|
+
registry.registerStore( 'store', {
|
|
188
|
+
reducer: ( state = null, action ) => {
|
|
189
|
+
if ( action.type === 'RECEIVE' ) {
|
|
190
|
+
return action.items;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return state;
|
|
194
|
+
},
|
|
195
|
+
selectors: {
|
|
196
|
+
getFoo: ( state ) => state,
|
|
197
|
+
},
|
|
198
|
+
resolvers: {
|
|
199
|
+
getFoo: () => {
|
|
200
|
+
throw new Error( 'cannot fetch items' );
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
} );
|
|
204
|
+
|
|
205
|
+
expect(
|
|
206
|
+
registry.select( 'store' ).hasResolutionFailed( 'getFoo' )
|
|
207
|
+
).toBeFalsy();
|
|
208
|
+
|
|
209
|
+
await resolve( registry, 'getFoo' );
|
|
210
|
+
|
|
211
|
+
expect(
|
|
212
|
+
registry.select( 'store' ).hasResolutionFailed( 'getFoo' )
|
|
213
|
+
).toBeTruthy();
|
|
214
|
+
} );
|
|
215
|
+
|
|
216
|
+
it( 'returns true if the resolution has failed even if the error is falsy', async () => {
|
|
217
|
+
registry.registerStore( 'store', {
|
|
218
|
+
reducer: ( state = null, action ) => {
|
|
219
|
+
if ( action.type === 'RECEIVE' ) {
|
|
220
|
+
return action.items;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return state;
|
|
224
|
+
},
|
|
225
|
+
selectors: {
|
|
226
|
+
getFoo: ( state ) => state,
|
|
227
|
+
},
|
|
228
|
+
resolvers: {
|
|
229
|
+
getFoo: () => {
|
|
230
|
+
throw null;
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
} );
|
|
234
|
+
|
|
235
|
+
expect(
|
|
236
|
+
registry.select( 'store' ).hasResolutionFailed( 'getFoo' )
|
|
237
|
+
).toBeFalsy();
|
|
238
|
+
|
|
239
|
+
await resolve( registry, 'getFoo' );
|
|
240
|
+
|
|
241
|
+
expect(
|
|
242
|
+
registry.select( 'store' ).hasResolutionFailed( 'getFoo' )
|
|
243
|
+
).toBeTruthy();
|
|
244
|
+
} );
|
|
245
|
+
} );
|
|
246
|
+
|
|
247
|
+
describe( 'getResolutionError', () => {
|
|
248
|
+
let registry;
|
|
249
|
+
let shouldFail;
|
|
250
|
+
|
|
251
|
+
beforeEach( () => {
|
|
252
|
+
shouldFail = false;
|
|
253
|
+
registry = createRegistry();
|
|
254
|
+
|
|
255
|
+
registry.registerStore( 'store', {
|
|
256
|
+
reducer: ( state = null, action ) => {
|
|
257
|
+
if ( action.type === 'RECEIVE' ) {
|
|
258
|
+
return action.items;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return state;
|
|
262
|
+
},
|
|
263
|
+
selectors: {
|
|
264
|
+
getFoo: ( state ) => state,
|
|
265
|
+
},
|
|
266
|
+
resolvers: {
|
|
267
|
+
getFoo: () => {
|
|
268
|
+
if ( shouldFail ) {
|
|
269
|
+
throw new Error( 'cannot fetch items' );
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
} );
|
|
274
|
+
} );
|
|
275
|
+
|
|
276
|
+
it( 'returns undefined if the resolution has succeeded', async () => {
|
|
277
|
+
expect(
|
|
278
|
+
registry.select( 'store' ).getResolutionError( 'getFoo' )
|
|
279
|
+
).toBeFalsy();
|
|
280
|
+
|
|
281
|
+
registry.select( 'store' ).getFoo();
|
|
282
|
+
|
|
283
|
+
expect(
|
|
284
|
+
registry.select( 'store' ).getResolutionError( 'getFoo' )
|
|
285
|
+
).toBeFalsy();
|
|
286
|
+
} );
|
|
287
|
+
|
|
288
|
+
it( 'returns error if the resolution has failed', async () => {
|
|
289
|
+
shouldFail = true;
|
|
290
|
+
|
|
291
|
+
expect(
|
|
292
|
+
registry.select( 'store' ).getResolutionError( 'getFoo' )
|
|
293
|
+
).toBeFalsy();
|
|
294
|
+
|
|
295
|
+
await resolve( registry, 'getFoo' );
|
|
296
|
+
|
|
297
|
+
expect(
|
|
298
|
+
registry.select( 'store' ).getResolutionError( 'getFoo' ).toString()
|
|
299
|
+
).toBe( 'Error: cannot fetch items' );
|
|
300
|
+
} );
|
|
301
|
+
|
|
302
|
+
it( 'returns undefined if the failed resolution succeeded after retry', async () => {
|
|
303
|
+
shouldFail = true;
|
|
304
|
+
expect(
|
|
305
|
+
registry.select( 'store' ).getResolutionError( 'getFoo' )
|
|
306
|
+
).toBeFalsy();
|
|
307
|
+
|
|
308
|
+
await resolve( registry, 'getFoo' );
|
|
309
|
+
|
|
310
|
+
expect(
|
|
311
|
+
registry.select( 'store' ).getResolutionError( 'getFoo' )
|
|
312
|
+
).toBeTruthy();
|
|
313
|
+
|
|
314
|
+
registry.dispatch( 'store' ).invalidateResolution( 'getFoo', [] );
|
|
315
|
+
|
|
316
|
+
expect(
|
|
317
|
+
registry.select( 'store' ).getResolutionError( 'getFoo' )
|
|
318
|
+
).toBeFalsy();
|
|
319
|
+
|
|
320
|
+
shouldFail = false;
|
|
321
|
+
registry.select( 'store' ).getFoo();
|
|
322
|
+
|
|
323
|
+
expect(
|
|
324
|
+
registry.select( 'store' ).getResolutionError( 'getFoo' )
|
|
325
|
+
).toBeFalsy();
|
|
326
|
+
} );
|
|
327
|
+
} );
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Internal dependencies
|
|
3
3
|
*/
|
|
4
|
-
import { onSubKey } from '../utils';
|
|
4
|
+
import { selectorArgsToStateKey, onSubKey } from '../utils';
|
|
5
5
|
|
|
6
6
|
describe( 'onSubKey', () => {
|
|
7
7
|
function createEnhancedReducer( actionProperty ) {
|
|
@@ -39,3 +39,30 @@ describe( 'onSubKey', () => {
|
|
|
39
39
|
} );
|
|
40
40
|
} );
|
|
41
41
|
} );
|
|
42
|
+
|
|
43
|
+
describe( 'selectorArgsToStateKey', () => {
|
|
44
|
+
it( 'should default to an empty array', () => {
|
|
45
|
+
expect( selectorArgsToStateKey( undefined ) ).toEqual( [] );
|
|
46
|
+
} );
|
|
47
|
+
|
|
48
|
+
it( 'should remove trailing undefined values', () => {
|
|
49
|
+
expect( selectorArgsToStateKey( [ 1, 2, undefined ] ) ).toEqual( [
|
|
50
|
+
1,
|
|
51
|
+
2,
|
|
52
|
+
] );
|
|
53
|
+
expect(
|
|
54
|
+
selectorArgsToStateKey( [ 1, 2, undefined, undefined ] )
|
|
55
|
+
).toEqual( [ 1, 2 ] );
|
|
56
|
+
} );
|
|
57
|
+
|
|
58
|
+
it( 'should leave non-trailing undefined values alone', () => {
|
|
59
|
+
expect(
|
|
60
|
+
selectorArgsToStateKey( [ 1, undefined, 2, undefined ] )
|
|
61
|
+
).toEqual( [ 1, undefined, 2 ] );
|
|
62
|
+
} );
|
|
63
|
+
|
|
64
|
+
it( 'should return already normalized array unchanged', () => {
|
|
65
|
+
const args = [ 1, 2, 3 ];
|
|
66
|
+
expect( selectorArgsToStateKey( args ) ).toBe( args );
|
|
67
|
+
} );
|
|
68
|
+
} );
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type { AnyAction, Reducer } from 'redux';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Higher-order reducer creator which creates a combined reducer object, keyed
|
|
8
|
+
* by a property on the action object.
|
|
9
|
+
*
|
|
10
|
+
* @param actionProperty Action property by which to key object.
|
|
11
|
+
* @return Higher-order reducer.
|
|
12
|
+
*/
|
|
13
|
+
export const onSubKey = < TState extends unknown, TAction extends AnyAction >(
|
|
14
|
+
actionProperty: string
|
|
15
|
+
) => (
|
|
16
|
+
reducer: Reducer< TState, TAction >
|
|
17
|
+
): Reducer< Record< string, TState >, TAction > => (
|
|
18
|
+
state: Record< string, TState > = {},
|
|
19
|
+
action
|
|
20
|
+
) => {
|
|
21
|
+
// Retrieve subkey from action. Do not track if undefined; useful for cases
|
|
22
|
+
// where reducer is scoped by action shape.
|
|
23
|
+
const key = action[ actionProperty ];
|
|
24
|
+
if ( key === undefined ) {
|
|
25
|
+
return state;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Avoid updating state if unchanged. Note that this also accounts for a
|
|
29
|
+
// reducer which returns undefined on a key which is not yet tracked.
|
|
30
|
+
const nextKeyState = reducer( state[ key ], action );
|
|
31
|
+
if ( nextKeyState === state[ key ] ) {
|
|
32
|
+
return state;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
...state,
|
|
37
|
+
[ key ]: nextKeyState,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Normalize selector argument array by defaulting `undefined` value to an empty array
|
|
43
|
+
* and removing trailing `undefined` values.
|
|
44
|
+
*
|
|
45
|
+
* @param args Selector argument array
|
|
46
|
+
* @return Normalized state key array
|
|
47
|
+
*/
|
|
48
|
+
export function selectorArgsToStateKey( args: unknown[] | null | undefined ) {
|
|
49
|
+
if ( args === undefined || args === null ) {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const len = args.length;
|
|
54
|
+
let idx = len;
|
|
55
|
+
while ( idx > 0 && args[ idx - 1 ] === undefined ) {
|
|
56
|
+
idx--;
|
|
57
|
+
}
|
|
58
|
+
return idx === len ? args : args.slice( 0, idx );
|
|
59
|
+
}
|
|
@@ -235,3 +235,45 @@ describe( 'controls', () => {
|
|
|
235
235
|
} );
|
|
236
236
|
} );
|
|
237
237
|
} );
|
|
238
|
+
|
|
239
|
+
describe( 'resolveSelect', () => {
|
|
240
|
+
let registry;
|
|
241
|
+
let shouldFail;
|
|
242
|
+
|
|
243
|
+
beforeEach( () => {
|
|
244
|
+
shouldFail = false;
|
|
245
|
+
registry = createRegistry();
|
|
246
|
+
|
|
247
|
+
registry.registerStore( 'store', {
|
|
248
|
+
reducer: ( state = null ) => {
|
|
249
|
+
return state;
|
|
250
|
+
},
|
|
251
|
+
selectors: {
|
|
252
|
+
getItems: () => 'items',
|
|
253
|
+
},
|
|
254
|
+
resolvers: {
|
|
255
|
+
getItems: () => {
|
|
256
|
+
if ( shouldFail ) {
|
|
257
|
+
throw new Error( 'cannot fetch items' );
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
} );
|
|
262
|
+
} );
|
|
263
|
+
|
|
264
|
+
it( 'resolves when the resolution succeeded', async () => {
|
|
265
|
+
shouldFail = false;
|
|
266
|
+
const promise = registry.resolveSelect( 'store' ).getItems();
|
|
267
|
+
jest.runAllTimers();
|
|
268
|
+
await expect( promise ).resolves.toEqual( 'items' );
|
|
269
|
+
} );
|
|
270
|
+
|
|
271
|
+
it( 'rejects when the resolution failed', async () => {
|
|
272
|
+
shouldFail = true;
|
|
273
|
+
const promise = registry.resolveSelect( 'store' ).getItems();
|
|
274
|
+
jest.runAllTimers();
|
|
275
|
+
await expect( promise ).rejects.toEqual(
|
|
276
|
+
new Error( 'cannot fetch items' )
|
|
277
|
+
);
|
|
278
|
+
} );
|
|
279
|
+
} );
|
package/src/registry.js
CHANGED
|
@@ -150,7 +150,6 @@ export function createRegistry( storeConfigs = {}, parent = null ) {
|
|
|
150
150
|
//
|
|
151
151
|
// Deprecated
|
|
152
152
|
// TODO: Remove this after `use()` is removed.
|
|
153
|
-
//
|
|
154
153
|
function withPlugins( attributes ) {
|
|
155
154
|
return mapValues( attributes, ( attribute, key ) => {
|
|
156
155
|
if ( typeof attribute !== 'function' ) {
|
|
@@ -289,7 +288,6 @@ export function createRegistry( storeConfigs = {}, parent = null ) {
|
|
|
289
288
|
//
|
|
290
289
|
// TODO:
|
|
291
290
|
// This function will be deprecated as soon as it is no longer internally referenced.
|
|
292
|
-
//
|
|
293
291
|
function use( plugin, options ) {
|
|
294
292
|
if ( ! plugin ) {
|
|
295
293
|
return;
|
|
@@ -38,10 +38,11 @@ const createResolversCacheMiddleware = ( registry, reducerKey ) => () => (
|
|
|
38
38
|
}
|
|
39
39
|
resolversByArgs.forEach( ( value, args ) => {
|
|
40
40
|
// resolversByArgs is the map Map([ args ] => boolean) storing the cache resolution status for a given selector.
|
|
41
|
-
// If the value is
|
|
42
|
-
// if it's true it means it's inflight and the invalidation is not necessary.
|
|
41
|
+
// If the value is "finished" or "error" it means this resolver has finished its resolution which means we need
|
|
42
|
+
// to invalidate it, if it's true it means it's inflight and the invalidation is not necessary.
|
|
43
43
|
if (
|
|
44
|
-
value !==
|
|
44
|
+
( value?.status !== 'finished' &&
|
|
45
|
+
value?.status !== 'error' ) ||
|
|
45
46
|
! resolver.shouldInvalidate( action, ...args )
|
|
46
47
|
) {
|
|
47
48
|
return;
|
package/src/test/controls.js
CHANGED
|
@@ -6,7 +6,7 @@ import { createRegistry, controls } from '..';
|
|
|
6
6
|
jest.useRealTimers();
|
|
7
7
|
|
|
8
8
|
describe( 'controls', () => {
|
|
9
|
-
//
|
|
9
|
+
// Create a registry with store to test select controls.
|
|
10
10
|
function createSelectTestRegistry() {
|
|
11
11
|
const registry = createRegistry();
|
|
12
12
|
|
|
@@ -21,13 +21,13 @@ describe( 'controls', () => {
|
|
|
21
21
|
}
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
// Select state both without and with a resolver
|
|
24
|
+
// Select state both without and with a resolver.
|
|
25
25
|
const selectors = {
|
|
26
26
|
selectorWithoutResolver: ( state ) => state,
|
|
27
27
|
selectorWithResolver: ( state ) => state,
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
// The resolver receives data after a little delay
|
|
30
|
+
// The resolver receives data after a little delay.
|
|
31
31
|
const resolvers = {
|
|
32
32
|
*selectorWithResolver() {
|
|
33
33
|
yield new Promise( ( r ) => setTimeout( r, 10 ) );
|
|
@@ -35,7 +35,7 @@ describe( 'controls', () => {
|
|
|
35
35
|
},
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
// actions that call the tested controls and return the selected value
|
|
38
|
+
// actions that call the tested controls and return the selected value.
|
|
39
39
|
const actions = {
|
|
40
40
|
*resolveWithoutResolver() {
|
|
41
41
|
const value = yield controls.resolveSelect(
|
|
@@ -130,7 +130,7 @@ describe( 'controls', () => {
|
|
|
130
130
|
function createDispatchTestRegistry() {
|
|
131
131
|
const registry = createRegistry();
|
|
132
132
|
|
|
133
|
-
//
|
|
133
|
+
// Store stores a counter that can be incremented.
|
|
134
134
|
const reducer = ( state = 0, action ) => {
|
|
135
135
|
switch ( action.type ) {
|
|
136
136
|
case 'INC':
|
|
@@ -141,11 +141,11 @@ describe( 'controls', () => {
|
|
|
141
141
|
};
|
|
142
142
|
|
|
143
143
|
const actions = {
|
|
144
|
-
// increment the counter
|
|
144
|
+
// increment the counter.
|
|
145
145
|
inc() {
|
|
146
146
|
return { type: 'INC' };
|
|
147
147
|
},
|
|
148
|
-
//
|
|
148
|
+
// Increment the counter twice in an async routine with controls.
|
|
149
149
|
*doubleInc() {
|
|
150
150
|
yield controls.dispatch( 'test/dispatch', 'inc' );
|
|
151
151
|
yield controls.dispatch( 'test/dispatch', 'inc' );
|
package/src/test/registry.js
CHANGED
|
@@ -467,7 +467,7 @@ describe( 'createRegistry', () => {
|
|
|
467
467
|
let promise = subscribeUntil(
|
|
468
468
|
() => registry.select( 'demo' ).getValue() === 'OK'
|
|
469
469
|
);
|
|
470
|
-
registry.select( 'demo' ).getValue(); // Triggers resolver switches to OK
|
|
470
|
+
registry.select( 'demo' ).getValue(); // Triggers resolver switches to OK.
|
|
471
471
|
jest.runAllTimers();
|
|
472
472
|
await promise;
|
|
473
473
|
|
|
@@ -477,7 +477,7 @@ describe( 'createRegistry', () => {
|
|
|
477
477
|
promise = subscribeUntil(
|
|
478
478
|
() => registry.select( 'demo' ).getValue() === 'NOTOK'
|
|
479
479
|
);
|
|
480
|
-
registry.select( 'demo' ).getValue(); // Triggers the resolver again and switch to NOTOK
|
|
480
|
+
registry.select( 'demo' ).getValue(); // Triggers the resolver again and switch to NOTOK.
|
|
481
481
|
jest.runAllTimers();
|
|
482
482
|
await promise;
|
|
483
483
|
} );
|
|
@@ -609,10 +609,10 @@ describe( 'createRegistry', () => {
|
|
|
609
609
|
} );
|
|
610
610
|
const action = { type: 'dummy' };
|
|
611
611
|
|
|
612
|
-
store.dispatch( action ); //
|
|
612
|
+
store.dispatch( action ); // Increment the data by => data = 2.
|
|
613
613
|
expect( incrementedValue ).toBe( 2 );
|
|
614
614
|
|
|
615
|
-
store.dispatch( action ); //
|
|
615
|
+
store.dispatch( action ); // Increment the data by => data = 3.
|
|
616
616
|
expect( incrementedValue ).toBe( 3 );
|
|
617
617
|
|
|
618
618
|
unsubscribe(); // Store subscribe to changes, the data variable stops upgrading.
|
|
@@ -685,7 +685,7 @@ describe( 'createRegistry', () => {
|
|
|
685
685
|
increment,
|
|
686
686
|
},
|
|
687
687
|
} );
|
|
688
|
-
//
|
|
688
|
+
// State = 1.
|
|
689
689
|
const dispatchResult = await registry
|
|
690
690
|
.dispatch( 'counter' )
|
|
691
691
|
.increment();
|
|
@@ -693,7 +693,7 @@ describe( 'createRegistry', () => {
|
|
|
693
693
|
type: 'increment',
|
|
694
694
|
count: 1,
|
|
695
695
|
} );
|
|
696
|
-
registry.dispatch( 'counter' ).increment( 4 ); //
|
|
696
|
+
registry.dispatch( 'counter' ).increment( 4 ); // State = 5.
|
|
697
697
|
expect( store.getState() ).toBe( 5 );
|
|
698
698
|
} );
|
|
699
699
|
} );
|