@wordpress/data
Version:
Data module for WordPress.
467 lines (420 loc) • 14.2 kB
JavaScript
/**
* Internal dependencies
*/
import { createRegistry } from '../registry';
import createReduxStore from '../redux-store';
import { unlock } from '../lock-unlock';
import { createRegistrySelector } from '../factory';
describe( 'Private data APIs', () => {
let registry;
beforeEach( () => {
registry = createRegistry();
} );
function getPublicPrice( state ) {
return state.price;
}
function getSecretDiscount( state ) {
return state.secretDiscount;
}
function setSecretDiscount( price ) {
return { type: 'SET_PRIVATE_PRICE', price };
}
function setPublicPrice( price ) {
return { type: 'SET_PUBLIC_PRICE', price };
}
const storeName = 'grocer';
const storeDescriptor = {
selectors: {
getPublicPrice,
getState: ( state ) => state,
},
actions: { setPublicPrice },
reducer: ( state = { price: 1000, secretDiscount: 800 }, action ) => {
if ( action.type === 'SET_PRIVATE_PRICE' ) {
return {
...state,
secretDiscount: action.price,
};
} else if ( action.type === 'SET_PUBLIC_PRICE' ) {
return {
...state,
price: action.price,
};
}
return state;
},
};
function createStore() {
const groceryStore = createReduxStore( storeName, storeDescriptor );
registry.register( groceryStore );
return groceryStore;
}
describe( 'private selectors', () => {
it( 'should expose public selectors by default', () => {
const groceryStore = createStore();
unlock( groceryStore ).registerPrivateSelectors( groceryStore, {
getSecretDiscount,
} );
const publicSelectors = registry.select( groceryStore );
expect( publicSelectors.getPublicPrice ).toEqual(
expect.any( Function )
);
} );
it( 'should not expose private selectors by default', () => {
const groceryStore = createStore();
unlock( groceryStore ).registerPrivateSelectors( {
getSecretDiscount,
} );
const publicSelectors = registry.select( groceryStore );
expect( publicSelectors.getSecretDiscount ).toEqual( undefined );
} );
it( 'should make private selectors available via unlock()', () => {
const groceryStore = createStore();
unlock( groceryStore ).registerPrivateSelectors( {
getSecretDiscount,
} );
const privateSelectors = unlock( registry.select( groceryStore ) );
expect( privateSelectors.getSecretDiscount ).toEqual(
expect.any( Function )
);
// The public selector is still accessible:
expect( privateSelectors.getPublicPrice ).toEqual(
expect.any( Function )
);
} );
it( 'should support combination of private selectors and resolvers', async () => {
const testStore = createReduxStore( 'test', {
reducer: ( state = {}, action ) => {
if ( action.type === 'RECEIVE' ) {
return { ...state, [ action.key ]: action.value };
}
return state;
},
selectors: {
get: ( state, key ) => state[ key ],
},
resolvers: {
get:
( key ) =>
async ( { dispatch } ) => {
const value = await ( 'resolved-' + key );
dispatch( { type: 'RECEIVE', key, value } );
},
},
} );
unlock( testStore ).registerPrivateSelectors( {
peek: ( state, key ) => state[ key ],
} );
registry.register( testStore );
await registry.resolveSelect( testStore ).get( 'x' );
expect( unlock( registry.select( testStore ) ).peek( 'x' ) ).toBe(
'resolved-x'
);
} );
it( 'should support private selectors with resolvers', async () => {
const testStore = createReduxStore( 'test', {
reducer: ( state = {}, action ) => {
if ( action.type === 'RECEIVE' ) {
return { ...state, [ action.key ]: action.value };
}
return state;
},
selectors: {},
resolvers: {
get:
( key ) =>
async ( { dispatch } ) => {
const value = await ( 'resolved-' + key );
dispatch( { type: 'RECEIVE', key, value } );
},
},
} );
registry.register( testStore );
unlock( testStore ).registerPrivateSelectors( {
get: ( state, key ) => state[ key ],
} );
// Verify that the private selector is available in resolveSelect.
const resolved = await unlock(
registry.resolveSelect( testStore )
).get( 'x' );
expect( resolved ).toBe( 'resolved-x' );
// Verify that the private selector is available in suspendSelect.
let suspended;
try {
unlock( registry.suspendSelect( testStore ) ).get( 'y' );
} catch ( error ) {
suspended = error;
}
expect( suspended ).toBeInstanceOf( Promise );
await suspended;
// After the suspense promise resolves, the result should be ready.
expect(
unlock( registry.suspendSelect( testStore ) ).get( 'y' )
).toBe( 'resolved-y' );
} );
it( 'should give private selectors access to the state', () => {
const groceryStore = createStore();
unlock( groceryStore ).registerPrivateSelectors( {
getSecretDiscount,
} );
const privateSelectors = unlock( registry.select( groceryStore ) );
expect( privateSelectors.getSecretDiscount() ).toEqual( 800 );
} );
it( 'should support public selectors accessed via unlock()', () => {
const groceryStore = createStore();
unlock( groceryStore ).registerPrivateSelectors( {
getSecretDiscount,
} );
const unlockedSelectors = unlock( registry.select( groceryStore ) );
expect( unlockedSelectors.getPublicPrice() ).toEqual( 1000 );
} );
it( 'should return stable references to selectors', () => {
const groceryStore = createStore();
unlock( groceryStore ).registerPrivateSelectors( {
getSecretDiscount,
} );
const select = unlock( registry.select( groceryStore ) );
expect( select.getPublicPrice ).toBe( select.getPublicPrice );
expect( select.getSecretDiscount ).toBe( select.getSecretDiscount );
} );
it( 'should support registerStore', () => {
const groceryStore = registry.registerStore(
storeName,
storeDescriptor
);
unlock( groceryStore ).registerPrivateSelectors( {
getSecretDiscount,
} );
const privateSelectors = unlock( registry.select( storeName ) );
expect( privateSelectors.getSecretDiscount() ).toEqual( 800 );
} );
it( 'should support mixing createReduxStore and registerStore', () => {
createReduxStore( storeName, storeDescriptor );
const groceryStore2 = registry.registerStore(
storeName,
storeDescriptor
);
unlock( groceryStore2 ).registerPrivateSelectors( {
getSecretDiscount,
} );
const privateSelectors = unlock( registry.select( storeName ) );
expect( privateSelectors.getSecretDiscount() ).toEqual( 800 );
} );
it( 'should support sub registries', () => {
const groceryStore = registry.registerStore(
storeName,
storeDescriptor
);
unlock( groceryStore ).registerPrivateSelectors( {
getSecretDiscount,
} );
const subRegistry = createRegistry( {}, registry );
subRegistry.registerStore( storeName, storeDescriptor );
const parentPrivateSelectors = unlock(
registry.select( storeName )
);
expect( parentPrivateSelectors.getSecretDiscount() ).toEqual( 800 );
const subPrivateSelectors = unlock(
subRegistry.select( storeName )
);
expect( subPrivateSelectors.getSecretDiscount() ).toEqual( 800 );
} );
it( 'should support private registry selectors', () => {
const groceryStore = createStore();
const otherStore = createReduxStore( 'other', {
reducer: ( state = {} ) => state,
} );
unlock( otherStore ).registerPrivateSelectors( {
getPrice: createRegistrySelector(
( select ) => () => select( groceryStore ).getPublicPrice()
),
} );
registry.register( otherStore );
const privateSelectors = unlock( registry.select( otherStore ) );
expect( privateSelectors.getPrice() ).toEqual( 1000 );
} );
it( 'should support calling a private registry selector from a public selector', () => {
const groceryStore = createStore();
const getPriceWithShipping = createRegistrySelector(
( select ) => () => select( groceryStore ).getPublicPrice() + 5
);
const store = createReduxStore( 'a', {
reducer: ( state = {} ) => state,
selectors: {
getPriceWithShippingAndTax: ( state ) =>
getPriceWithShipping( state ) * 1.1,
},
} );
unlock( store ).registerPrivateSelectors( {
getPriceWithShipping,
} );
registry.register( store );
expect(
registry.select( store ).getPriceWithShippingAndTax()
).toEqual( 1105.5 );
} );
} );
describe( 'private actions', () => {
it( 'should expose public actions by default', () => {
const groceryStore = createStore();
unlock( groceryStore ).registerPrivateActions( groceryStore, {
setSecretDiscount,
} );
const publicActions = registry.dispatch( groceryStore );
expect( publicActions.setPublicPrice ).toEqual(
expect.any( Function )
);
} );
it( 'should not expose private actions by default', () => {
const groceryStore = createStore();
unlock( groceryStore ).registerPrivateActions( {
setSecretDiscount,
} );
const publicActions = registry.dispatch( groceryStore );
expect( publicActions.setSecretDiscount ).toEqual( undefined );
} );
it( 'should make private actions available via unlock)', () => {
const groceryStore = createStore();
unlock( groceryStore ).registerPrivateActions( {
setSecretDiscount,
} );
const privateActions = unlock( registry.dispatch( groceryStore ) );
expect( privateActions.setSecretDiscount ).toEqual(
expect.any( Function )
);
// The public action is still accessible:
expect( privateActions.setPublicPrice ).toEqual(
expect.any( Function )
);
} );
it( 'should work with both private actions and private selectors at the same time', () => {
const groceryStore = createStore();
unlock( groceryStore ).registerPrivateActions( {
setSecretDiscount,
} );
unlock( groceryStore ).registerPrivateSelectors( {
getSecretDiscount,
} );
const privateActions = unlock( registry.dispatch( groceryStore ) );
const privateSelectors = unlock( registry.select( groceryStore ) );
expect( privateActions.setSecretDiscount ).toEqual(
expect.any( Function )
);
expect( privateSelectors.getSecretDiscount ).toEqual(
expect.any( Function )
);
} );
it( 'should dispatch private actions like regular actions', () => {
const groceryStore = createStore();
unlock( groceryStore ).registerPrivateActions( {
setSecretDiscount,
} );
const privateActions = unlock( registry.dispatch( groceryStore ) );
privateActions.setSecretDiscount( 400 );
expect(
registry.select( groceryStore ).getState().secretDiscount
).toEqual( 400 );
} );
it( 'should return stable references to actions', () => {
const groceryStore = createStore();
unlock( groceryStore ).registerPrivateActions( {
setSecretDiscount,
} );
const disp = unlock( registry.dispatch( groceryStore ) );
expect( disp.setPublicPrice ).toBe( disp.setPublicPrice );
expect( disp.setSecretDiscount ).toBe( disp.setSecretDiscount );
} );
it( 'should dispatch public actions on the unlocked store', () => {
const groceryStore = createStore();
unlock( groceryStore ).registerPrivateActions( {
setSecretDiscount,
} );
const privateActions = unlock( registry.dispatch( groceryStore ) );
privateActions.setPublicPrice( 400 );
expect( registry.select( groceryStore ).getState().price ).toEqual(
400
);
} );
it( 'should dispatch private action thunks like regular actions', () => {
const groceryStore = createStore();
unlock( groceryStore ).registerPrivateActions( {
setSecretDiscountThunk:
( price ) =>
( { dispatch } ) => {
dispatch( { type: 'SET_PRIVATE_PRICE', price } );
},
} );
unlock( groceryStore ).registerPrivateSelectors( {
getSecretDiscount,
} );
const privateActions = unlock( registry.dispatch( groceryStore ) );
privateActions.setSecretDiscountThunk( 100 );
expect(
unlock( registry.select( groceryStore ) ).getSecretDiscount()
).toEqual( 100 );
} );
it( 'should expose unlocked private selectors and actions to thunks', () => {
const groceryStore = createStore();
unlock( groceryStore ).registerPrivateSelectors( {
getSecretDiscount,
} );
unlock( groceryStore ).registerPrivateActions( {
setSecretDiscount,
doubleSecretDiscount() {
return ( { dispatch, select } ) => {
dispatch.setSecretDiscount(
select.getSecretDiscount() * 2
);
};
},
} );
const privateActions = unlock( registry.dispatch( groceryStore ) );
privateActions.setSecretDiscount( 100 );
privateActions.doubleSecretDiscount();
expect(
unlock( registry.select( groceryStore ) ).getSecretDiscount()
).toEqual( 200 );
} );
it( 'should support registerStore', () => {
const groceryStore = registry.registerStore(
storeName,
storeDescriptor
);
unlock( groceryStore ).registerPrivateActions( {
setSecretDiscount,
} );
const privateActions = unlock( registry.dispatch( storeName ) );
privateActions.setSecretDiscount( 400 );
expect(
registry.select( storeName ).getState().secretDiscount
).toEqual( 400 );
} );
it( 'should support sub registries', () => {
const groceryStore = createStore();
unlock( groceryStore ).registerPrivateSelectors( {
getSecretDiscount,
} );
unlock( groceryStore ).registerPrivateActions( {
setSecretDiscount,
} );
const subRegistry = createRegistry( {}, registry );
subRegistry.registerStore( storeName, storeDescriptor );
const parentPrivateActions = unlock(
registry.dispatch( storeName )
);
const parentPrivateSelectors = unlock(
registry.select( storeName )
);
const subPrivateActions = unlock(
subRegistry.dispatch( storeName )
);
const subPrivateSelectors = unlock(
subRegistry.select( storeName )
);
parentPrivateActions.setSecretDiscount( 400 );
subPrivateActions.setSecretDiscount( 478 );
expect( parentPrivateSelectors.getSecretDiscount() ).toEqual( 400 );
expect( subPrivateSelectors.getSecretDiscount() ).toEqual( 478 );
} );
} );
} );