initial commit
This commit is contained in:
@ -0,0 +1,7 @@
|
||||
export const ACTION_TYPES = {
|
||||
RECEIVE_COLLECTION: 'RECEIVE_COLLECTION',
|
||||
RESET_COLLECTION: 'RESET_COLLECTION',
|
||||
ERROR: 'ERROR',
|
||||
RECEIVE_LAST_MODIFIED: 'RECEIVE_LAST_MODIFIED',
|
||||
INVALIDATE_RESOLUTION_FOR_STORE: 'INVALIDATE_RESOLUTION_FOR_STORE',
|
||||
};
|
@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ACTION_TYPES as types } from './action-types';
|
||||
|
||||
let Headers = window.Headers || null;
|
||||
Headers = Headers
|
||||
? new Headers()
|
||||
: { get: () => undefined, has: () => undefined };
|
||||
|
||||
/**
|
||||
* Returns an action object used in updating the store with the provided items
|
||||
* retrieved from a request using the given querystring.
|
||||
*
|
||||
* This is a generic response action.
|
||||
*
|
||||
* @param {string} namespace The namespace for the collection route.
|
||||
* @param {string} resourceName The resource name for the collection route.
|
||||
* @param {string} [queryString=''] The query string for the collection
|
||||
* @param {Array} [ids=[]] An array of ids (in correct order) for the
|
||||
* model.
|
||||
* @param {Object} [response={}] An object containing the response from the
|
||||
* collection request.
|
||||
* @param {Array<*>} response.items An array of items for the given collection.
|
||||
* @param {Headers} response.headers A Headers object from the response
|
||||
* link https://developer.mozilla.org/en-US/docs/Web/API/Headers
|
||||
* @param {boolean} [replace=false] If true, signals to replace the current
|
||||
* items in the state with the provided
|
||||
* items.
|
||||
* @return {
|
||||
* {
|
||||
* type: string,
|
||||
* namespace: string,
|
||||
* resourceName: string,
|
||||
* queryString: string,
|
||||
* ids: Array<*>,
|
||||
* items: Array<*>,
|
||||
* }
|
||||
* } Object for action.
|
||||
*/
|
||||
export function receiveCollection(
|
||||
namespace,
|
||||
resourceName,
|
||||
queryString = '',
|
||||
ids = [],
|
||||
response = { items: [], headers: Headers },
|
||||
replace = false
|
||||
) {
|
||||
return {
|
||||
type: replace ? types.RESET_COLLECTION : types.RECEIVE_COLLECTION,
|
||||
namespace,
|
||||
resourceName,
|
||||
queryString,
|
||||
ids,
|
||||
response,
|
||||
};
|
||||
}
|
||||
|
||||
export function receiveCollectionError(
|
||||
namespace,
|
||||
resourceName,
|
||||
queryString,
|
||||
ids,
|
||||
error
|
||||
) {
|
||||
return {
|
||||
type: 'ERROR',
|
||||
namespace,
|
||||
resourceName,
|
||||
queryString,
|
||||
ids,
|
||||
response: {
|
||||
items: [],
|
||||
headers: Headers,
|
||||
error,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function receiveLastModified( timestamp ) {
|
||||
return {
|
||||
type: types.RECEIVE_LAST_MODIFIED,
|
||||
timestamp,
|
||||
};
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
export const STORE_KEY = 'wc/store/collections';
|
||||
export const DEFAULT_EMPTY_ARRAY = [];
|
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { registerStore } from '@wordpress/data';
|
||||
import { controls as dataControls } from '@wordpress/data-controls';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { STORE_KEY } from './constants';
|
||||
import * as selectors from './selectors';
|
||||
import * as actions from './actions';
|
||||
import * as resolvers from './resolvers';
|
||||
import reducer from './reducers';
|
||||
import { controls } from '../shared-controls';
|
||||
|
||||
registerStore( STORE_KEY, {
|
||||
reducer,
|
||||
actions,
|
||||
controls: { ...dataControls, ...controls },
|
||||
selectors,
|
||||
resolvers,
|
||||
} );
|
||||
|
||||
export const COLLECTIONS_STORE_KEY = STORE_KEY;
|
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ACTION_TYPES as types } from './action-types';
|
||||
import { hasInState, updateState } from '../utils';
|
||||
|
||||
/**
|
||||
* Reducer for receiving items to a collection.
|
||||
*
|
||||
* @param {Object} state The current state in the store.
|
||||
* @param {Object} action Action object.
|
||||
*
|
||||
* @return {Object} New or existing state depending on if there are
|
||||
* any changes.
|
||||
*/
|
||||
const receiveCollection = ( state = {}, action ) => {
|
||||
// Update last modified and previous last modified values.
|
||||
if ( action.type === types.RECEIVE_LAST_MODIFIED ) {
|
||||
if ( action.timestamp === state.lastModified ) {
|
||||
return state;
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
lastModified: action.timestamp,
|
||||
};
|
||||
}
|
||||
|
||||
// When invalidating data, remove stored values from state.
|
||||
if ( action.type === types.INVALIDATE_RESOLUTION_FOR_STORE ) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const { type, namespace, resourceName, queryString, response } = action;
|
||||
// ids are stringified so they can be used as an index.
|
||||
const ids = action.ids ? JSON.stringify( action.ids ) : '[]';
|
||||
switch ( type ) {
|
||||
case types.RECEIVE_COLLECTION:
|
||||
if (
|
||||
hasInState( state, [
|
||||
namespace,
|
||||
resourceName,
|
||||
ids,
|
||||
queryString,
|
||||
] )
|
||||
) {
|
||||
return state;
|
||||
}
|
||||
state = updateState(
|
||||
state,
|
||||
[ namespace, resourceName, ids, queryString ],
|
||||
response
|
||||
);
|
||||
break;
|
||||
case types.RESET_COLLECTION:
|
||||
state = updateState(
|
||||
state,
|
||||
[ namespace, resourceName, ids, queryString ],
|
||||
response
|
||||
);
|
||||
break;
|
||||
case types.ERROR:
|
||||
state = updateState(
|
||||
state,
|
||||
[ namespace, resourceName, ids, queryString ],
|
||||
response
|
||||
);
|
||||
break;
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
export default receiveCollection;
|
@ -0,0 +1,109 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { select, dispatch } from '@wordpress/data-controls';
|
||||
import { addQueryArgs } from '@wordpress/url';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { receiveCollection, receiveCollectionError } from './actions';
|
||||
import { STORE_KEY as SCHEMA_STORE_KEY } from '../schema/constants';
|
||||
import { STORE_KEY, DEFAULT_EMPTY_ARRAY } from './constants';
|
||||
import { apiFetchWithHeaders } from '../shared-controls';
|
||||
|
||||
/**
|
||||
* Check if the store needs invalidating due to a change in last modified headers.
|
||||
*
|
||||
* @param {number} timestamp Last update timestamp.
|
||||
*/
|
||||
function* invalidateModifiedCollection( timestamp ) {
|
||||
const lastModified = yield select( STORE_KEY, 'getCollectionLastModified' );
|
||||
|
||||
if ( ! lastModified ) {
|
||||
yield dispatch( STORE_KEY, 'receiveLastModified', timestamp );
|
||||
} else if ( timestamp > lastModified ) {
|
||||
yield dispatch( STORE_KEY, 'invalidateResolutionForStore' );
|
||||
yield dispatch( STORE_KEY, 'receiveLastModified', timestamp );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolver for retrieving a collection via a api route.
|
||||
*
|
||||
* @param {string} namespace
|
||||
* @param {string} resourceName
|
||||
* @param {Object} query
|
||||
* @param {Array} ids
|
||||
*/
|
||||
export function* getCollection( namespace, resourceName, query, ids ) {
|
||||
const route = yield select(
|
||||
SCHEMA_STORE_KEY,
|
||||
'getRoute',
|
||||
namespace,
|
||||
resourceName,
|
||||
ids
|
||||
);
|
||||
const queryString = addQueryArgs( '', query );
|
||||
if ( ! route ) {
|
||||
yield receiveCollection( namespace, resourceName, queryString, ids );
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const {
|
||||
response = DEFAULT_EMPTY_ARRAY,
|
||||
headers,
|
||||
} = yield apiFetchWithHeaders( { path: route + queryString } );
|
||||
|
||||
if ( headers && headers.get && headers.has( 'last-modified' ) ) {
|
||||
// Do any invalidation before the collection is received to prevent
|
||||
// this query running again.
|
||||
yield invalidateModifiedCollection(
|
||||
parseInt( headers.get( 'last-modified' ), 10 )
|
||||
);
|
||||
}
|
||||
|
||||
yield receiveCollection( namespace, resourceName, queryString, ids, {
|
||||
items: response,
|
||||
headers,
|
||||
} );
|
||||
} catch ( error ) {
|
||||
yield receiveCollectionError(
|
||||
namespace,
|
||||
resourceName,
|
||||
queryString,
|
||||
ids,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolver for retrieving a specific collection header for the given arguments
|
||||
*
|
||||
* Note: This triggers the `getCollection` resolver if it hasn't been resolved
|
||||
* yet.
|
||||
*
|
||||
* @param {string} header
|
||||
* @param {string} namespace
|
||||
* @param {string} resourceName
|
||||
* @param {Object} query
|
||||
* @param {Array} ids
|
||||
*/
|
||||
export function* getCollectionHeader(
|
||||
header,
|
||||
namespace,
|
||||
resourceName,
|
||||
query,
|
||||
ids
|
||||
) {
|
||||
// feed the correct number of args in for the select so we don't resolve
|
||||
// unnecessarily. Any undefined args will be excluded. This is important
|
||||
// because resolver resolution is cached by both number and value of args.
|
||||
const args = [ namespace, resourceName, query, ids ].filter(
|
||||
( arg ) => typeof arg !== 'undefined'
|
||||
);
|
||||
//we call this simply to do any resolution of the collection if necessary.
|
||||
yield select( STORE_KEY, 'getCollection', ...args );
|
||||
}
|
@ -0,0 +1,144 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { addQueryArgs } from '@wordpress/url';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { hasInState } from '../utils';
|
||||
import { DEFAULT_EMPTY_ARRAY } from './constants';
|
||||
|
||||
const getFromState = ( {
|
||||
state,
|
||||
namespace,
|
||||
resourceName,
|
||||
query,
|
||||
ids,
|
||||
type = 'items',
|
||||
fallback = DEFAULT_EMPTY_ARRAY,
|
||||
} ) => {
|
||||
// prep ids and query for state retrieval
|
||||
ids = JSON.stringify( ids );
|
||||
query = query !== null ? addQueryArgs( '', query ) : '';
|
||||
if ( hasInState( state, [ namespace, resourceName, ids, query, type ] ) ) {
|
||||
return state[ namespace ][ resourceName ][ ids ][ query ][ type ];
|
||||
}
|
||||
return fallback;
|
||||
};
|
||||
|
||||
const getCollectionHeaders = (
|
||||
state,
|
||||
namespace,
|
||||
resourceName,
|
||||
query = null,
|
||||
ids = DEFAULT_EMPTY_ARRAY
|
||||
) => {
|
||||
return getFromState( {
|
||||
state,
|
||||
namespace,
|
||||
resourceName,
|
||||
query,
|
||||
ids,
|
||||
type: 'headers',
|
||||
fallback: undefined,
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the collection items from the state for the given arguments.
|
||||
*
|
||||
* @param {Object} state The current collections state.
|
||||
* @param {string} namespace The namespace for the collection.
|
||||
* @param {string} resourceName The resource name for the collection.
|
||||
* @param {Object} [query=null] The query for the collection request.
|
||||
* @param {Array} [ids=[]] Any ids for the collection request (these are
|
||||
* values that would be added to the route for a
|
||||
* route with id placeholders)
|
||||
* @return {Array} an array of items stored in the collection.
|
||||
*/
|
||||
export const getCollection = (
|
||||
state,
|
||||
namespace,
|
||||
resourceName,
|
||||
query = null,
|
||||
ids = DEFAULT_EMPTY_ARRAY
|
||||
) => {
|
||||
return getFromState( { state, namespace, resourceName, query, ids } );
|
||||
};
|
||||
|
||||
export const getCollectionError = (
|
||||
state,
|
||||
namespace,
|
||||
resourceName,
|
||||
query = null,
|
||||
ids = DEFAULT_EMPTY_ARRAY
|
||||
) => {
|
||||
return getFromState( {
|
||||
state,
|
||||
namespace,
|
||||
resourceName,
|
||||
query,
|
||||
ids,
|
||||
type: 'error',
|
||||
fallback: null,
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* This selector enables retrieving a specific header value from a given
|
||||
* collection request.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```js
|
||||
* const totalProducts = wp.data.select( COLLECTION_STORE_KEY )
|
||||
* .getCollectionHeader( '/wc/blocks', 'products', 'x-wp-total' )
|
||||
* ```
|
||||
*
|
||||
* @param {string} state The current collection state.
|
||||
* @param {string} header The header to retrieve.
|
||||
* @param {string} namespace The namespace for the collection.
|
||||
* @param {string} resourceName The model name for the collection.
|
||||
* @param {Object} [query=null] The query object on the collection request.
|
||||
* @param {Array} [ids=[]] Any ids for the collection request (these are
|
||||
* values that would be added to the route for a
|
||||
* route with id placeholders)
|
||||
*
|
||||
* @return {*|null} The value for the specified header, null if there are no
|
||||
* headers available and undefined if the header does not exist for the
|
||||
* collection.
|
||||
*/
|
||||
export const getCollectionHeader = (
|
||||
state,
|
||||
header,
|
||||
namespace,
|
||||
resourceName,
|
||||
query = null,
|
||||
ids = DEFAULT_EMPTY_ARRAY
|
||||
) => {
|
||||
const headers = getCollectionHeaders(
|
||||
state,
|
||||
namespace,
|
||||
resourceName,
|
||||
query,
|
||||
ids
|
||||
);
|
||||
// Can't just do a truthy check because `getCollectionHeaders` resolver
|
||||
// invokes the `getCollection` selector to trigger the resolution of the
|
||||
// collection request. Its fallback is an empty array.
|
||||
if ( headers && headers.get ) {
|
||||
return headers.has( header ) ? headers.get( header ) : undefined;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the last modified header for the collection.
|
||||
*
|
||||
* @param {string} state The current collection state.
|
||||
* @return {number} Timestamp.
|
||||
*/
|
||||
export const getCollectionLastModified = ( state ) => {
|
||||
return state.lastModified || 0;
|
||||
};
|
@ -0,0 +1,99 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import deepFreeze from 'deep-freeze';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import receiveCollection from '../reducers';
|
||||
import { ACTION_TYPES as types } from '../action-types';
|
||||
|
||||
describe( 'receiveCollection', () => {
|
||||
const originalState = deepFreeze( {
|
||||
'wc/blocks': {
|
||||
products: {
|
||||
'[]': {
|
||||
'?someQuery=2': {
|
||||
items: [ 'foo' ],
|
||||
headers: { 'x-wp-total': 22 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} );
|
||||
it(
|
||||
'returns original state when there is already an entry in the state ' +
|
||||
'for the given arguments',
|
||||
() => {
|
||||
const testAction = {
|
||||
type: types.RECEIVE_COLLECTION,
|
||||
namespace: 'wc/blocks',
|
||||
resourceName: 'products',
|
||||
queryString: '?someQuery=2',
|
||||
response: {
|
||||
items: [ 'bar' ],
|
||||
headers: { foo: 'bar' },
|
||||
},
|
||||
};
|
||||
expect( receiveCollection( originalState, testAction ) ).toBe(
|
||||
originalState
|
||||
);
|
||||
}
|
||||
);
|
||||
it(
|
||||
'returns new state when items exist in collection but the type is ' +
|
||||
'for a reset',
|
||||
() => {
|
||||
const testAction = {
|
||||
type: types.RESET_COLLECTION,
|
||||
namespace: 'wc/blocks',
|
||||
resourceName: 'products',
|
||||
queryString: '?someQuery=2',
|
||||
response: {
|
||||
items: [ 'cheeseburger' ],
|
||||
headers: { foo: 'bar' },
|
||||
},
|
||||
};
|
||||
const newState = receiveCollection( originalState, testAction );
|
||||
expect( newState ).not.toBe( originalState );
|
||||
expect(
|
||||
newState[ 'wc/blocks' ].products[ '[]' ][ '?someQuery=2' ]
|
||||
).toEqual( {
|
||||
items: [ 'cheeseburger' ],
|
||||
headers: { foo: 'bar' },
|
||||
} );
|
||||
}
|
||||
);
|
||||
it( 'returns new state when items do not exist in collection yet', () => {
|
||||
const testAction = {
|
||||
type: types.RECEIVE_COLLECTION,
|
||||
namespace: 'wc/blocks',
|
||||
resourceName: 'products',
|
||||
queryString: '?someQuery=3',
|
||||
response: { items: [ 'cheeseburger' ], headers: { foo: 'bar' } },
|
||||
};
|
||||
const newState = receiveCollection( originalState, testAction );
|
||||
expect( newState ).not.toBe( originalState );
|
||||
expect(
|
||||
newState[ 'wc/blocks' ].products[ '[]' ][ '?someQuery=3' ]
|
||||
).toEqual( { items: [ 'cheeseburger' ], headers: { foo: 'bar' } } );
|
||||
} );
|
||||
it( 'sets expected state when ids are passed in', () => {
|
||||
const testAction = {
|
||||
type: types.RECEIVE_COLLECTION,
|
||||
namespace: 'wc/blocks',
|
||||
resourceName: 'products/attributes',
|
||||
queryString: '?something',
|
||||
response: { items: [ 10, 20 ], headers: { foo: 'bar' } },
|
||||
ids: [ 30, 42 ],
|
||||
};
|
||||
const newState = receiveCollection( originalState, testAction );
|
||||
expect( newState ).not.toBe( originalState );
|
||||
expect(
|
||||
newState[ 'wc/blocks' ][ 'products/attributes' ][ '[30,42]' ][
|
||||
'?something'
|
||||
]
|
||||
).toEqual( { items: [ 10, 20 ], headers: { foo: 'bar' } } );
|
||||
} );
|
||||
} );
|
@ -0,0 +1,161 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { select } from '@wordpress/data-controls';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getCollection, getCollectionHeader } from '../resolvers';
|
||||
import { receiveCollection } from '../actions';
|
||||
import { STORE_KEY as SCHEMA_STORE_KEY } from '../../schema/constants';
|
||||
import { STORE_KEY } from '../constants';
|
||||
import { apiFetchWithHeaders } from '../../shared-controls';
|
||||
|
||||
jest.mock( '@wordpress/data-controls' );
|
||||
|
||||
describe( 'getCollection', () => {
|
||||
describe( 'yields with expected responses', () => {
|
||||
let fulfillment;
|
||||
const testArgs = [
|
||||
'wc/blocks',
|
||||
'products',
|
||||
{ foo: 'bar' },
|
||||
[ 20, 30 ],
|
||||
];
|
||||
const rewind = () => ( fulfillment = getCollection( ...testArgs ) );
|
||||
test( 'with getRoute call invoked to retrieve route', () => {
|
||||
rewind();
|
||||
fulfillment.next();
|
||||
expect( select ).toHaveBeenCalledWith(
|
||||
SCHEMA_STORE_KEY,
|
||||
'getRoute',
|
||||
testArgs[ 0 ],
|
||||
testArgs[ 1 ],
|
||||
testArgs[ 3 ]
|
||||
);
|
||||
} );
|
||||
test(
|
||||
'when no route is retrieved, yields receiveCollection and ' +
|
||||
'returns',
|
||||
() => {
|
||||
const { value } = fulfillment.next();
|
||||
const expected = receiveCollection(
|
||||
'wc/blocks',
|
||||
'products',
|
||||
'?foo=bar',
|
||||
[ 20, 30 ],
|
||||
{
|
||||
items: [],
|
||||
headers: {
|
||||
get: () => undefined,
|
||||
has: () => undefined,
|
||||
},
|
||||
}
|
||||
);
|
||||
expect( value.type ).toBe( expected.type );
|
||||
expect( value.namespace ).toBe( expected.namespace );
|
||||
expect( value.resourceName ).toBe( expected.resourceName );
|
||||
expect( value.queryString ).toBe( expected.queryString );
|
||||
expect( value.ids ).toEqual( expected.ids );
|
||||
expect( Object.keys( value.response ) ).toEqual(
|
||||
Object.keys( expected.response )
|
||||
);
|
||||
const { done } = fulfillment.next();
|
||||
expect( done ).toBe( true );
|
||||
}
|
||||
);
|
||||
test(
|
||||
'when route is retrieved, yields apiFetchWithHeaders control action with ' +
|
||||
'expected route',
|
||||
() => {
|
||||
rewind();
|
||||
fulfillment.next();
|
||||
const { value } = fulfillment.next( 'https://example.org' );
|
||||
expect( value ).toEqual(
|
||||
apiFetchWithHeaders( {
|
||||
path: 'https://example.org?foo=bar',
|
||||
} )
|
||||
);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'when apiFetchWithHeaders does not return a valid response, ' +
|
||||
'yields expected action',
|
||||
() => {
|
||||
const { value } = fulfillment.next( {} );
|
||||
expect( value ).toEqual(
|
||||
receiveCollection(
|
||||
'wc/blocks',
|
||||
'products',
|
||||
'?foo=bar',
|
||||
[ 20, 30 ],
|
||||
{ items: [], headers: undefined }
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'when apiFetch returns a valid response, yields expected ' +
|
||||
'action',
|
||||
() => {
|
||||
rewind();
|
||||
fulfillment.next();
|
||||
fulfillment.next( 'https://example.org' );
|
||||
const { value } = fulfillment.next( {
|
||||
response: [ '42', 'cheeseburgers' ],
|
||||
headers: { foo: 'bar' },
|
||||
} );
|
||||
expect( value ).toEqual(
|
||||
receiveCollection(
|
||||
'wc/blocks',
|
||||
'products',
|
||||
'?foo=bar',
|
||||
[ 20, 30 ],
|
||||
{
|
||||
items: [ '42', 'cheeseburgers' ],
|
||||
headers: { foo: 'bar' },
|
||||
}
|
||||
)
|
||||
);
|
||||
const { done } = fulfillment.next();
|
||||
expect( done ).toBe( true );
|
||||
}
|
||||
);
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'getCollectionHeader', () => {
|
||||
let fulfillment;
|
||||
const rewind = ( ...testArgs ) =>
|
||||
( fulfillment = getCollectionHeader( ...testArgs ) );
|
||||
it( 'yields expected select control when called with less args', () => {
|
||||
rewind( 'x-wp-total', '/wc/blocks', 'products' );
|
||||
const { value } = fulfillment.next();
|
||||
expect( value ).toEqual(
|
||||
select( STORE_KEY, 'getCollection', '/wc/blocks', 'products' )
|
||||
);
|
||||
} );
|
||||
it( 'yields expected select control when called with all args', () => {
|
||||
const args = [
|
||||
'x-wp-total',
|
||||
'/wc/blocks',
|
||||
'products/attributes',
|
||||
{ sort: 'ASC' },
|
||||
[ 10 ],
|
||||
];
|
||||
rewind( ...args );
|
||||
const { value } = fulfillment.next();
|
||||
expect( value ).toEqual(
|
||||
select(
|
||||
STORE_KEY,
|
||||
'/wc/blocks',
|
||||
'products/attributes',
|
||||
{ sort: 'ASC' },
|
||||
[ 10 ]
|
||||
)
|
||||
);
|
||||
const { done } = fulfillment.next();
|
||||
expect( done ).toBe( true );
|
||||
} );
|
||||
} );
|
@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getCollection, getCollectionHeader } from '../selectors';
|
||||
|
||||
const getHeaderMock = ( total ) => {
|
||||
const headers = { total };
|
||||
return {
|
||||
get: ( key ) => headers[ key ] || null,
|
||||
has: ( key ) => !! headers[ key ],
|
||||
};
|
||||
};
|
||||
|
||||
const state = {
|
||||
'wc/blocks': {
|
||||
products: {
|
||||
'[]': {
|
||||
'?someQuery=2': {
|
||||
items: [ 'foo' ],
|
||||
headers: getHeaderMock( 22 ),
|
||||
},
|
||||
},
|
||||
},
|
||||
'products/attributes': {
|
||||
'[10]': {
|
||||
'?someQuery=2': {
|
||||
items: [ 'bar' ],
|
||||
headers: getHeaderMock( 42 ),
|
||||
},
|
||||
},
|
||||
},
|
||||
'products/attributes/terms': {
|
||||
'[10,20]': {
|
||||
'?someQuery=10': {
|
||||
items: [ 42 ],
|
||||
headers: getHeaderMock( 12 ),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe( 'getCollection', () => {
|
||||
it( 'returns empty array when namespace does not exist in state', () => {
|
||||
expect( getCollection( state, 'invalid', 'products' ) ).toEqual( [] );
|
||||
} );
|
||||
it( 'returns empty array when resourceName does not exist in state', () => {
|
||||
expect( getCollection( state, 'wc/blocks', 'invalid' ) ).toEqual( [] );
|
||||
} );
|
||||
it( 'returns empty array when query does not exist in state', () => {
|
||||
expect( getCollection( state, 'wc/blocks', 'products' ) ).toEqual( [] );
|
||||
} );
|
||||
it( 'returns empty array when ids do not exist in state', () => {
|
||||
expect(
|
||||
getCollection(
|
||||
state,
|
||||
'wc/blocks',
|
||||
'products/attributes',
|
||||
'?someQuery=2',
|
||||
[ 20 ]
|
||||
)
|
||||
).toEqual( [] );
|
||||
} );
|
||||
describe( 'returns expected values for items existing in state', () => {
|
||||
test.each`
|
||||
resourceName | ids | query | expected
|
||||
${ 'products' } | ${ [] } | ${ { someQuery: 2 } } | ${ [ 'foo' ] }
|
||||
${ 'products/attributes' } | ${ [ 10 ] } | ${ { someQuery: 2 } } | ${ [ 'bar' ] }
|
||||
${ 'products/attributes/terms' } | ${ [ 10, 20 ] } | ${ { someQuery: 10 } } | ${ [ 42 ] }
|
||||
`(
|
||||
'for "$resourceName", "$ids", and "$query"',
|
||||
( { resourceName, ids, query, expected } ) => {
|
||||
expect(
|
||||
getCollection(
|
||||
state,
|
||||
'wc/blocks',
|
||||
resourceName,
|
||||
query,
|
||||
ids
|
||||
)
|
||||
).toEqual( expected );
|
||||
}
|
||||
);
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'getCollectionHeader', () => {
|
||||
it(
|
||||
'returns undefined when there are headers but the specific header ' +
|
||||
'does not exist',
|
||||
() => {
|
||||
expect(
|
||||
getCollectionHeader(
|
||||
state,
|
||||
'invalid',
|
||||
'wc/blocks',
|
||||
'products',
|
||||
{
|
||||
someQuery: 2,
|
||||
}
|
||||
)
|
||||
).toBeUndefined();
|
||||
}
|
||||
);
|
||||
it( 'returns null when there are no headers for the given arguments', () => {
|
||||
expect( getCollectionHeader( state, 'wc/blocks', 'invalid' ) ).toBe(
|
||||
null
|
||||
);
|
||||
} );
|
||||
it( 'returns expected header when it exists', () => {
|
||||
expect(
|
||||
getCollectionHeader( state, 'total', 'wc/blocks', 'products', {
|
||||
someQuery: 2,
|
||||
} )
|
||||
).toBe( 22 );
|
||||
} );
|
||||
} );
|
Reference in New Issue
Block a user