@wordpress/data 9.4.0 → 9.5.1

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 (74) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/components/use-dispatch/use-dispatch-with-map.js +2 -6
  3. package/build/components/use-dispatch/use-dispatch-with-map.js.map +1 -1
  4. package/build/components/use-select/index.js +1 -1
  5. package/build/components/use-select/index.js.map +1 -1
  6. package/build/components/with-dispatch/index.js +3 -5
  7. package/build/components/with-dispatch/index.js.map +1 -1
  8. package/build/components/with-registry/index.js +2 -6
  9. package/build/components/with-registry/index.js.map +1 -1
  10. package/build/components/with-select/index.js +3 -3
  11. package/build/components/with-select/index.js.map +1 -1
  12. package/build/controls.js +18 -37
  13. package/build/controls.js.map +1 -1
  14. package/build/factory.js +2 -4
  15. package/build/factory.js.map +1 -1
  16. package/build/{private-apis.js → lock-unlock.js} +1 -1
  17. package/build/lock-unlock.js.map +1 -0
  18. package/build/redux-store/index.js +140 -204
  19. package/build/redux-store/index.js.map +1 -1
  20. package/build/redux-store/metadata/reducer.js +2 -8
  21. package/build/redux-store/metadata/reducer.js.map +1 -1
  22. package/build/redux-store/metadata/selectors.js +5 -15
  23. package/build/redux-store/metadata/selectors.js.map +1 -1
  24. package/build/redux-store/metadata/utils.js +1 -3
  25. package/build/redux-store/metadata/utils.js.map +1 -1
  26. package/build/registry.js +14 -24
  27. package/build/registry.js.map +1 -1
  28. package/build/resolvers-cache-middleware.js +3 -6
  29. package/build/resolvers-cache-middleware.js.map +1 -1
  30. package/build/store/index.js +2 -10
  31. package/build/store/index.js.map +1 -1
  32. package/build-module/components/use-dispatch/use-dispatch-with-map.js +2 -6
  33. package/build-module/components/use-dispatch/use-dispatch-with-map.js.map +1 -1
  34. package/build-module/components/use-select/index.js +1 -1
  35. package/build-module/components/use-select/index.js.map +1 -1
  36. package/build-module/components/with-dispatch/index.js +3 -2
  37. package/build-module/components/with-dispatch/index.js.map +1 -1
  38. package/build-module/components/with-registry/index.js +2 -3
  39. package/build-module/components/with-registry/index.js.map +1 -1
  40. package/build-module/components/with-select/index.js +3 -2
  41. package/build-module/components/with-select/index.js.map +1 -1
  42. package/build-module/controls.js +18 -37
  43. package/build-module/controls.js.map +1 -1
  44. package/build-module/factory.js +2 -4
  45. package/build-module/factory.js.map +1 -1
  46. package/build-module/{private-apis.js → lock-unlock.js} +1 -1
  47. package/build-module/lock-unlock.js.map +1 -0
  48. package/build-module/redux-store/index.js +138 -202
  49. package/build-module/redux-store/index.js.map +1 -1
  50. package/build-module/redux-store/metadata/reducer.js +2 -8
  51. package/build-module/redux-store/metadata/reducer.js.map +1 -1
  52. package/build-module/redux-store/metadata/selectors.js +5 -15
  53. package/build-module/redux-store/metadata/selectors.js.map +1 -1
  54. package/build-module/redux-store/metadata/utils.js +1 -3
  55. package/build-module/redux-store/metadata/utils.js.map +1 -1
  56. package/build-module/registry.js +9 -19
  57. package/build-module/registry.js.map +1 -1
  58. package/build-module/resolvers-cache-middleware.js +3 -6
  59. package/build-module/resolvers-cache-middleware.js.map +1 -1
  60. package/build-module/store/index.js +2 -10
  61. package/build-module/store/index.js.map +1 -1
  62. package/build-types/{private-apis.d.ts → lock-unlock.d.ts} +1 -1
  63. package/build-types/lock-unlock.d.ts.map +1 -0
  64. package/build-types/redux-store/index.d.ts.map +1 -1
  65. package/package.json +9 -9
  66. package/src/factory.js +1 -1
  67. package/src/redux-store/index.js +170 -224
  68. package/src/registry.js +1 -1
  69. package/src/test/privateAPIs.js +118 -11
  70. package/tsconfig.tsbuildinfo +1 -1
  71. package/build/private-apis.js.map +0 -1
  72. package/build-module/private-apis.js.map +0 -1
  73. package/build-types/private-apis.d.ts.map +0 -1
  74. /package/src/{private-apis.js → lock-unlock.js} +0 -0
@@ -15,7 +15,7 @@ import { compose } from '@wordpress/compose';
15
15
  * Internal dependencies
16
16
  */
17
17
  import { builtinControls } from '../controls';
18
- import { lock } from '../private-apis';
18
+ import { lock } from '../lock-unlock';
19
19
  import promise from '../promise-middleware';
20
20
  import createResolversCacheMiddleware from '../resolvers-cache-middleware';
21
21
  import createThunkMiddleware from './thunk-middleware';
@@ -54,12 +54,11 @@ const trimUndefinedValues = ( array ) => {
54
54
  * @return {Array} Transformed object.
55
55
  */
56
56
  const mapValues = ( obj, callback ) =>
57
- Object.entries( obj ?? {} ).reduce(
58
- ( acc, [ key, value ] ) => ( {
59
- ...acc,
60
- [ key ]: callback( value, key ),
61
- } ),
62
- {}
57
+ Object.fromEntries(
58
+ Object.entries( obj ?? {} ).map( ( [ key, value ] ) => [
59
+ key,
60
+ callback( value, key ),
61
+ ] )
63
62
  );
64
63
 
65
64
  // Convert Map objects to plain objects
@@ -102,6 +101,21 @@ function createResolversCache() {
102
101
  };
103
102
  }
104
103
 
104
+ function createBindingCache( bind ) {
105
+ const cache = new WeakMap();
106
+
107
+ return {
108
+ get( item ) {
109
+ let boundItem = cache.get( item );
110
+ if ( ! boundItem ) {
111
+ boundItem = bind( item );
112
+ cache.set( item, boundItem );
113
+ }
114
+ return boundItem;
115
+ },
116
+ };
117
+ }
118
+
105
119
  /**
106
120
  * Creates a data store descriptor for the provided Redux store configuration containing
107
121
  * properties describing reducer, actions, selectors, controls and resolvers.
@@ -148,17 +162,10 @@ export default function createReduxStore( key, options ) {
148
162
  const thunkArgs = {
149
163
  registry,
150
164
  get dispatch() {
151
- return Object.assign(
152
- ( action ) => store.dispatch( action ),
153
- getActions()
154
- );
165
+ return thunkActions;
155
166
  },
156
167
  get select() {
157
- return Object.assign(
158
- ( selector ) =>
159
- selector( store.__unstableOriginalGetState() ),
160
- getSelectors()
161
- );
168
+ return thunkSelectors;
162
169
  },
163
170
  get resolveSelect() {
164
171
  return getResolveSelectors();
@@ -176,77 +183,94 @@ export default function createReduxStore( key, options ) {
176
183
  lock( store, privateRegistrationFunctions );
177
184
  const resolversCache = createResolversCache();
178
185
 
179
- let resolvers;
180
- const actions = mapActions(
181
- {
182
- ...metadataActions,
183
- ...options.actions,
184
- },
185
- store
186
- );
187
- lock(
188
- actions,
189
- new Proxy( privateActions, {
190
- get: ( target, prop ) => {
191
- return (
192
- mapActions( privateActions, store )[ prop ] ||
193
- actions[ prop ]
194
- );
195
- },
196
- } )
197
- );
186
+ function bindAction( action ) {
187
+ return ( ...args ) =>
188
+ Promise.resolve( store.dispatch( action( ...args ) ) );
189
+ }
198
190
 
199
- let selectors = mapSelectors(
200
- {
201
- ...mapValues(
202
- metadataSelectors,
203
- ( selector ) =>
204
- ( state, ...args ) =>
205
- selector( state.metadata, ...args )
206
- ),
207
- ...mapValues( options.selectors, ( selector ) => {
208
- if ( selector.isRegistrySelector ) {
209
- selector.registry = registry;
210
- }
191
+ const actions = {
192
+ ...mapValues( metadataActions, bindAction ),
193
+ ...mapValues( options.actions, bindAction ),
194
+ };
211
195
 
212
- return ( state, ...args ) =>
213
- selector( state.root, ...args );
214
- } ),
196
+ const boundPrivateActions = createBindingCache( bindAction );
197
+ const allActions = new Proxy( () => {}, {
198
+ get: ( target, prop ) => {
199
+ const privateAction = privateActions[ prop ];
200
+ return privateAction
201
+ ? boundPrivateActions.get( privateAction )
202
+ : actions[ prop ];
215
203
  },
216
- store
217
- );
218
- lock(
219
- selectors,
220
- new Proxy( privateSelectors, {
221
- get: ( target, prop ) => {
222
- return (
223
- mapSelectors(
224
- mapValues( privateSelectors, ( selector ) => {
225
- if ( selector.isRegistrySelector ) {
226
- selector.registry = registry;
227
- }
228
-
229
- return ( state, ...args ) =>
230
- selector( state.root, ...args );
231
- } ),
232
- store
233
- )[ prop ] || selectors[ prop ]
234
- );
235
- },
236
- } )
237
- );
204
+ } );
205
+
206
+ const thunkActions = new Proxy( allActions, {
207
+ apply: ( target, thisArg, [ action ] ) =>
208
+ store.dispatch( action ),
209
+ } );
210
+
211
+ lock( actions, allActions );
212
+
213
+ function bindSelector( selector ) {
214
+ if ( selector.isRegistrySelector ) {
215
+ selector.registry = registry;
216
+ }
217
+ const boundSelector = ( ...args ) => {
218
+ const state = store.__unstableOriginalGetState();
219
+ return selector( state.root, ...args );
220
+ };
221
+ boundSelector.hasResolver = false;
222
+ return boundSelector;
223
+ }
224
+
225
+ function bindMetadataSelector( selector ) {
226
+ const boundSelector = ( ...args ) => {
227
+ const state = store.__unstableOriginalGetState();
228
+ return selector( state.metadata, ...args );
229
+ };
230
+ boundSelector.hasResolver = false;
231
+ return boundSelector;
232
+ }
233
+
234
+ let selectors = {
235
+ ...mapValues( metadataSelectors, bindMetadataSelector ),
236
+ ...mapValues( options.selectors, bindSelector ),
237
+ };
238
238
 
239
+ let resolvers;
239
240
  if ( options.resolvers ) {
240
- const result = mapResolvers(
241
- options.resolvers,
241
+ resolvers = mapResolvers( options.resolvers );
242
+ selectors = mapSelectorsWithResolvers(
242
243
  selectors,
244
+ resolvers,
243
245
  store,
244
246
  resolversCache
245
247
  );
246
- resolvers = result.resolvers;
247
- selectors = result.selectors;
248
248
  }
249
249
 
250
+ const boundPrivateSelectors = createBindingCache( bindSelector );
251
+
252
+ // Pre-bind the private selectors that have been registered by the time of
253
+ // instantiation, so that registry selectors are bound to the registry.
254
+ for ( const privateSelector of Object.values( privateSelectors ) ) {
255
+ boundPrivateSelectors.get( privateSelector );
256
+ }
257
+
258
+ const allSelectors = new Proxy( () => {}, {
259
+ get: ( target, prop ) => {
260
+ const privateSelector = privateSelectors[ prop ];
261
+ return privateSelector
262
+ ? boundPrivateSelectors.get( privateSelector )
263
+ : selectors[ prop ];
264
+ },
265
+ } );
266
+
267
+ const thunkSelectors = new Proxy( allSelectors, {
268
+ apply: ( target, thisArg, [ selector ] ) =>
269
+ selector( store.__unstableOriginalGetState() ),
270
+ } );
271
+
272
+ lock( selectors, allSelectors );
273
+
250
274
  const resolveSelectors = mapResolveSelectors( selectors, store );
251
275
  const suspendSelectors = mapSuspendSelectors( selectors, store );
252
276
 
@@ -360,59 +384,6 @@ function instantiateReduxStore( key, options, registry, thunkArgs ) {
360
384
  );
361
385
  }
362
386
 
363
- /**
364
- * Maps selectors to a store.
365
- *
366
- * @param {Object} selectors Selectors to register. Keys will be used as the
367
- * public facing API. Selectors will get passed the
368
- * state as first argument.
369
- * @param {Object} store The store to which the selectors should be mapped.
370
- * @return {Object} Selectors mapped to the provided store.
371
- */
372
- function mapSelectors( selectors, store ) {
373
- const createStateSelector = ( registrySelector ) => {
374
- const selector = function runSelector() {
375
- // This function is an optimized implementation of:
376
- //
377
- // selector( store.getState(), ...arguments )
378
- //
379
- // Where the above would incur an `Array#concat` in its application,
380
- // the logic here instead efficiently constructs an arguments array via
381
- // direct assignment.
382
- const argsLength = arguments.length;
383
- const args = new Array( argsLength + 1 );
384
- args[ 0 ] = store.__unstableOriginalGetState();
385
- for ( let i = 0; i < argsLength; i++ ) {
386
- args[ i + 1 ] = arguments[ i ];
387
- }
388
-
389
- return registrySelector( ...args );
390
- };
391
- selector.hasResolver = false;
392
- return selector;
393
- };
394
-
395
- return mapValues( selectors, createStateSelector );
396
- }
397
-
398
- /**
399
- * Maps actions to dispatch from a given store.
400
- *
401
- * @param {Object} actions Actions to register.
402
- * @param {Object} store The redux store to which the actions should be mapped.
403
- *
404
- * @return {Object} Actions mapped to the redux store provided.
405
- */
406
- function mapActions( actions, store ) {
407
- const createBoundAction =
408
- ( action ) =>
409
- ( ...args ) => {
410
- return Promise.resolve( store.dispatch( action( ...args ) ) );
411
- };
412
-
413
- return mapValues( actions, createBoundAction );
414
- }
415
-
416
387
  /**
417
388
  * Maps selectors to functions that return a resolution promise for them
418
389
  *
@@ -519,20 +490,13 @@ function mapSuspendSelectors( selectors, store ) {
519
490
  }
520
491
 
521
492
  /**
522
- * Returns resolvers with matched selectors for a given namespace.
523
- * Resolvers are side effects invoked once per argument set of a given selector call,
524
- * used in ensuring that the data needs for the selector are satisfied.
493
+ * Convert resolvers to a normalized form, an object with `fulfill` method and
494
+ * optional methods like `isFulfilled`.
525
495
  *
526
- * @param {Object} resolvers Resolvers to register.
527
- * @param {Object} selectors The current selectors to be modified.
528
- * @param {Object} store The redux store to which the resolvers should be mapped.
529
- * @param {Object} resolversCache Resolvers Cache.
496
+ * @param {Object} resolvers Resolver to convert
530
497
  */
531
- function mapResolvers( resolvers, selectors, store, resolversCache ) {
532
- // The `resolver` can be either a function that does the resolution, or, in more advanced
533
- // cases, an object with a `fullfill` method and other optional methods like `isFulfilled`.
534
- // Here we normalize the `resolver` function to an object with `fulfill` method.
535
- const mappedResolvers = mapValues( resolvers, ( resolver ) => {
498
+ function mapResolvers( resolvers ) {
499
+ return mapValues( resolvers, ( resolver ) => {
536
500
  if ( resolver.fulfill ) {
537
501
  return resolver;
538
502
  }
@@ -542,99 +506,81 @@ function mapResolvers( resolvers, selectors, store, resolversCache ) {
542
506
  fulfill: resolver, // Add the fulfill method.
543
507
  };
544
508
  } );
509
+ }
545
510
 
546
- const mapSelector = ( selector, selectorName ) => {
547
- const resolver = resolvers[ selectorName ];
548
- if ( ! resolver ) {
549
- selector.hasResolver = false;
550
- return selector;
511
+ /**
512
+ * Returns resolvers with matched selectors for a given namespace.
513
+ * Resolvers are side effects invoked once per argument set of a given selector call,
514
+ * used in ensuring that the data needs for the selector are satisfied.
515
+ *
516
+ * @param {Object} selectors The current selectors to be modified.
517
+ * @param {Object} resolvers Resolvers to register.
518
+ * @param {Object} store The redux store to which the resolvers should be mapped.
519
+ * @param {Object} resolversCache Resolvers Cache.
520
+ */
521
+ function mapSelectorsWithResolvers(
522
+ selectors,
523
+ resolvers,
524
+ store,
525
+ resolversCache
526
+ ) {
527
+ function fulfillSelector( resolver, selectorName, args ) {
528
+ const state = store.getState();
529
+
530
+ if (
531
+ resolversCache.isRunning( selectorName, args ) ||
532
+ ( typeof resolver.isFulfilled === 'function' &&
533
+ resolver.isFulfilled( state, ...args ) )
534
+ ) {
535
+ return;
551
536
  }
552
537
 
553
- const selectorResolver = ( ...args ) => {
554
- async function fulfillSelector() {
555
- const state = store.getState();
556
-
557
- if (
558
- resolversCache.isRunning( selectorName, args ) ||
559
- ( typeof resolver.isFulfilled === 'function' &&
560
- resolver.isFulfilled( state, ...args ) )
561
- ) {
562
- return;
563
- }
538
+ const { metadata } = store.__unstableOriginalGetState();
564
539
 
565
- const { metadata } = store.__unstableOriginalGetState();
566
-
567
- if (
568
- metadataSelectors.hasStartedResolution(
569
- metadata,
570
- selectorName,
571
- args
572
- )
573
- ) {
574
- return;
575
- }
540
+ if (
541
+ metadataSelectors.hasStartedResolution(
542
+ metadata,
543
+ selectorName,
544
+ args
545
+ )
546
+ ) {
547
+ return;
548
+ }
576
549
 
577
- resolversCache.markAsRunning( selectorName, args );
550
+ resolversCache.markAsRunning( selectorName, args );
578
551
 
579
- setTimeout( async () => {
580
- resolversCache.clear( selectorName, args );
581
- store.dispatch(
582
- metadataActions.startResolution( selectorName, args )
583
- );
584
- try {
585
- await fulfillResolver(
586
- store,
587
- mappedResolvers,
588
- selectorName,
589
- ...args
590
- );
591
- store.dispatch(
592
- metadataActions.finishResolution(
593
- selectorName,
594
- args
595
- )
596
- );
597
- } catch ( error ) {
598
- store.dispatch(
599
- metadataActions.failResolution(
600
- selectorName,
601
- args,
602
- error
603
- )
604
- );
605
- }
606
- } );
552
+ setTimeout( async () => {
553
+ resolversCache.clear( selectorName, args );
554
+ store.dispatch(
555
+ metadataActions.startResolution( selectorName, args )
556
+ );
557
+ try {
558
+ const action = resolver.fulfill( ...args );
559
+ if ( action ) {
560
+ await store.dispatch( action );
561
+ }
562
+ store.dispatch(
563
+ metadataActions.finishResolution( selectorName, args )
564
+ );
565
+ } catch ( error ) {
566
+ store.dispatch(
567
+ metadataActions.failResolution( selectorName, args, error )
568
+ );
607
569
  }
570
+ }, 0 );
571
+ }
572
+
573
+ return mapValues( selectors, ( selector, selectorName ) => {
574
+ const resolver = resolvers[ selectorName ];
575
+ if ( ! resolver ) {
576
+ return selector;
577
+ }
608
578
 
609
- fulfillSelector( ...args );
579
+ const selectorResolver = ( ...args ) => {
580
+ fulfillSelector( resolver, selectorName, args );
610
581
  return selector( ...args );
611
582
  };
612
583
  selectorResolver.hasResolver = true;
613
584
  return selectorResolver;
614
- };
615
-
616
- return {
617
- resolvers: mappedResolvers,
618
- selectors: mapValues( selectors, mapSelector ),
619
- };
620
- }
621
-
622
- /**
623
- * Calls a resolver given arguments
624
- *
625
- * @param {Object} store Store reference, for fulfilling via resolvers
626
- * @param {Object} resolvers Store Resolvers
627
- * @param {string} selectorName Selector name to fulfill.
628
- * @param {Array} args Selector Arguments.
629
- */
630
- async function fulfillResolver( store, resolvers, selectorName, ...args ) {
631
- const resolver = resolvers[ selectorName ];
632
- if ( ! resolver ) {
633
- return;
634
- }
635
-
636
- const action = resolver.fulfill( ...args );
637
- if ( action ) {
638
- await store.dispatch( action );
639
- }
585
+ } );
640
586
  }
package/src/registry.js CHANGED
@@ -9,7 +9,7 @@ import deprecated from '@wordpress/deprecated';
9
9
  import createReduxStore from './redux-store';
10
10
  import coreDataStore from './store';
11
11
  import { createEmitter } from './utils/emitter';
12
- import { lock, unlock } from './private-apis';
12
+ import { lock, unlock } from './lock-unlock';
13
13
 
14
14
  /** @typedef {import('./types').StoreDescriptor} StoreDescriptor */
15
15
 
@@ -3,7 +3,8 @@
3
3
  */
4
4
  import { createRegistry } from '../registry';
5
5
  import createReduxStore from '../redux-store';
6
- import { unlock } from '../private-apis';
6
+ import { unlock } from '../lock-unlock';
7
+ import { createRegistrySelector } from '../factory';
7
8
 
8
9
  describe( 'Private data APIs', () => {
9
10
  let registry;
@@ -32,23 +33,19 @@ describe( 'Private data APIs', () => {
32
33
  getState: ( state ) => state,
33
34
  },
34
35
  actions: { setPublicPrice },
35
- reducer: ( state, action ) => {
36
- if ( action?.type === 'SET_PRIVATE_PRICE' ) {
36
+ reducer: ( state = { price: 1000, secretDiscount: 800 }, action ) => {
37
+ if ( action.type === 'SET_PRIVATE_PRICE' ) {
37
38
  return {
38
39
  ...state,
39
- secretDiscount: action?.price,
40
+ secretDiscount: action.price,
40
41
  };
41
- } else if ( action?.type === 'SET_PUBLIC_PRICE' ) {
42
+ } else if ( action.type === 'SET_PUBLIC_PRICE' ) {
42
43
  return {
43
44
  ...state,
44
- price: action?.price,
45
+ price: action.price,
45
46
  };
46
47
  }
47
- return {
48
- price: 1000,
49
- secretDiscount: 800,
50
- ...( state || {} ),
51
- };
48
+ return state;
52
49
  },
53
50
  };
54
51
  function createStore() {
@@ -96,6 +93,37 @@ describe( 'Private data APIs', () => {
96
93
  );
97
94
  } );
98
95
 
96
+ it( 'should support combination of private selectors and resolvers', async () => {
97
+ const testStore = createReduxStore( 'test', {
98
+ reducer: ( state = {}, action ) => {
99
+ if ( action.type === 'RECEIVE' ) {
100
+ return { ...state, [ action.key ]: action.value };
101
+ }
102
+ return state;
103
+ },
104
+ selectors: {
105
+ get: ( state, key ) => state[ key ],
106
+ },
107
+ resolvers: {
108
+ get:
109
+ ( key ) =>
110
+ async ( { dispatch } ) => {
111
+ const value = await ( 'resolved-' + key );
112
+ dispatch( { type: 'RECEIVE', key, value } );
113
+ },
114
+ },
115
+ } );
116
+ unlock( testStore ).registerPrivateSelectors( {
117
+ peek: ( state, key ) => state[ key ],
118
+ } );
119
+ registry.register( testStore );
120
+
121
+ await registry.resolveSelect( testStore ).get( 'x' );
122
+ expect( unlock( registry.select( testStore ) ).peek( 'x' ) ).toBe(
123
+ 'resolved-x'
124
+ );
125
+ } );
126
+
99
127
  it( 'should give private selectors access to the state', () => {
100
128
  const groceryStore = createStore();
101
129
  unlock( groceryStore ).registerPrivateSelectors( {
@@ -116,6 +144,16 @@ describe( 'Private data APIs', () => {
116
144
  expect( unlockedSelectors.getPublicPrice() ).toEqual( 1000 );
117
145
  } );
118
146
 
147
+ it( 'should return stable references to selectors', () => {
148
+ const groceryStore = createStore();
149
+ unlock( groceryStore ).registerPrivateSelectors( {
150
+ getSecretDiscount,
151
+ } );
152
+ const select = unlock( registry.select( groceryStore ) );
153
+ expect( select.getPublicPrice ).toBe( select.getPublicPrice );
154
+ expect( select.getSecretDiscount ).toBe( select.getSecretDiscount );
155
+ } );
156
+
119
157
  it( 'should support registerStore', () => {
120
158
  const groceryStore = registry.registerStore(
121
159
  storeName,
@@ -164,6 +202,42 @@ describe( 'Private data APIs', () => {
164
202
  );
165
203
  expect( subPrivateSelectors.getSecretDiscount() ).toEqual( 800 );
166
204
  } );
205
+
206
+ it( 'should support private registry selectors', () => {
207
+ const groceryStore = createStore();
208
+ const otherStore = createReduxStore( 'other', {
209
+ reducer: ( state = {} ) => state,
210
+ } );
211
+ unlock( otherStore ).registerPrivateSelectors( {
212
+ getPrice: createRegistrySelector(
213
+ ( select ) => () => select( groceryStore ).getPublicPrice()
214
+ ),
215
+ } );
216
+ registry.register( otherStore );
217
+ const privateSelectors = unlock( registry.select( otherStore ) );
218
+ expect( privateSelectors.getPrice() ).toEqual( 1000 );
219
+ } );
220
+
221
+ it( 'should support calling a private registry selector from a public selector', () => {
222
+ const groceryStore = createStore();
223
+ const getPriceWithShipping = createRegistrySelector(
224
+ ( select ) => () => select( groceryStore ).getPublicPrice() + 5
225
+ );
226
+ const store = createReduxStore( 'a', {
227
+ reducer: ( state = {} ) => state,
228
+ selectors: {
229
+ getPriceWithShippingAndTax: ( state ) =>
230
+ getPriceWithShipping( state ) * 1.1,
231
+ },
232
+ } );
233
+ unlock( store ).registerPrivateSelectors( {
234
+ getPriceWithShipping,
235
+ } );
236
+ registry.register( store );
237
+ expect(
238
+ registry.select( store ).getPriceWithShippingAndTax()
239
+ ).toEqual( 1105.5 );
240
+ } );
167
241
  } );
168
242
 
169
243
  describe( 'private actions', () => {
@@ -232,6 +306,16 @@ describe( 'Private data APIs', () => {
232
306
  ).toEqual( 400 );
233
307
  } );
234
308
 
309
+ it( 'should return stable references to actions', () => {
310
+ const groceryStore = createStore();
311
+ unlock( groceryStore ).registerPrivateActions( {
312
+ setSecretDiscount,
313
+ } );
314
+ const disp = unlock( registry.dispatch( groceryStore ) );
315
+ expect( disp.setPublicPrice ).toBe( disp.setPublicPrice );
316
+ expect( disp.setSecretDiscount ).toBe( disp.setSecretDiscount );
317
+ } );
318
+
235
319
  it( 'should dispatch public actions on the unlocked store', () => {
236
320
  const groceryStore = createStore();
237
321
  unlock( groceryStore ).registerPrivateActions( {
@@ -263,6 +347,29 @@ describe( 'Private data APIs', () => {
263
347
  ).toEqual( 100 );
264
348
  } );
265
349
 
350
+ it( 'should expose unlocked private selectors and actions to thunks', () => {
351
+ const groceryStore = createStore();
352
+ unlock( groceryStore ).registerPrivateSelectors( {
353
+ getSecretDiscount,
354
+ } );
355
+ unlock( groceryStore ).registerPrivateActions( {
356
+ setSecretDiscount,
357
+ doubleSecretDiscount() {
358
+ return ( { dispatch, select } ) => {
359
+ dispatch.setSecretDiscount(
360
+ select.getSecretDiscount() * 2
361
+ );
362
+ };
363
+ },
364
+ } );
365
+ const privateActions = unlock( registry.dispatch( groceryStore ) );
366
+ privateActions.setSecretDiscount( 100 );
367
+ privateActions.doubleSecretDiscount();
368
+ expect(
369
+ unlock( registry.select( groceryStore ) ).getSecretDiscount()
370
+ ).toEqual( 200 );
371
+ } );
372
+
266
373
  it( 'should support registerStore', () => {
267
374
  const groceryStore = registry.registerStore(
268
375
  storeName,