* Add product state

* Add products store tests
This commit is contained in:
Kelly Dwan 2018-08-28 14:43:26 -04:00 committed by GitHub
parent c06c3b0cef
commit 4e995590db
10 changed files with 388 additions and 1 deletions

View File

@ -10,26 +10,31 @@ import { combineReducers } from 'redux';
*/ */
import { applyMiddleware, addThunks } from './middleware'; import { applyMiddleware, addThunks } from './middleware';
import orders from 'store/orders'; import orders from 'store/orders';
import products from 'store/products';
import reports from 'store/reports'; import reports from 'store/reports';
const store = registerStore( 'wc-admin', { const store = registerStore( 'wc-admin', {
reducer: combineReducers( { reducer: combineReducers( {
orders: orders.reducer, orders: orders.reducer,
products: products.reducer,
reports: reports.reducer, reports: reports.reducer,
} ), } ),
actions: { actions: {
...orders.actions, ...orders.actions,
...products.actions,
...reports.actions, ...reports.actions,
}, },
selectors: { selectors: {
...orders.selectors, ...orders.selectors,
...products.selectors,
...reports.selectors, ...reports.selectors,
}, },
resolvers: { resolvers: {
...orders.resolvers, ...orders.resolvers,
...products.resolvers,
...reports.resolvers, ...reports.resolvers,
}, },
} ); } );

View File

@ -0,0 +1,18 @@
/** @format */
export default {
setProducts( products, query ) {
return {
type: 'SET_PRODUCTS',
products,
query: query || {},
};
},
setProductsError( query ) {
return {
type: 'SET_PRODUCTS_ERROR',
query: query || {},
};
},
};

View File

@ -0,0 +1,15 @@
/** @format */
/**
* Internal dependencies
*/
import actions from './actions';
import reducer from './reducer';
import resolvers from './resolvers';
import selectors from './selectors';
export default {
actions,
reducer,
resolvers,
selectors,
};

View File

@ -0,0 +1,43 @@
/** @format */
/**
* External dependencies
*/
import { get } from 'lodash';
/**
* Internal dependencies
*/
import { ERROR } from 'store/constants';
import { getJsonString } from 'store/util';
export const DEFAULT_STATE = {
queries: {},
};
export default function productsReducer( state = DEFAULT_STATE, action ) {
if ( 'SET_PRODUCTS' === action.type ) {
const prevQueries = get( state, 'queries', {} );
const queryKey = getJsonString( action.query );
const queries = {
...prevQueries,
[ queryKey ]: [ ...action.products ],
};
return {
...state,
queries,
};
}
if ( 'SET_PRODUCTS_ERROR' === action.type ) {
const prevQueries = get( state, 'queries', {} );
const queryKey = getJsonString( action.query );
const queries = {
...prevQueries,
[ queryKey ]: ERROR,
};
return {
...state,
queries,
};
}
return state;
}

View File

@ -0,0 +1,19 @@
/** @format */
/**
* External dependencies
*/
import apiFetch from '@wordpress/api-fetch';
import { dispatch } from '@wordpress/data';
import { stringify } from 'qs';
export default {
async getProducts( state, query ) {
try {
const params = query ? '?' + stringify( query ) : '';
const products = await apiFetch( { path: '/wc/v3/products' + params } );
dispatch( 'wc-admin' ).setProducts( products, query );
} catch ( error ) {
dispatch( 'wc-admin' ).setProductsError( query );
}
},
};

View File

@ -0,0 +1,50 @@
/** @format */
/**
* External dependencies
*/
import { get } from 'lodash';
import { select } from '@wordpress/data';
/**
* Internal dependencies
*/
import { ERROR } from 'store/constants';
import { getJsonString } from 'store/util';
/**
* Returns revenue report details for a specific report query.
*
* @param {Object} state Current state
* @param {Object} query Report query paremters
* @return {Object} Report details
*/
function getProducts( state, query = {} ) {
const queries = get( state, 'products.queries', {} );
return queries[ getJsonString( query ) ];
}
export default {
getProducts,
/**
* Returns true if a products request is pending.
*
* @param {Object} state Current state
* @return {Object} True if the `getProducts` request is pending, false otherwise
*/
isProductsRequesting( state, ...args ) {
return select( 'core/data' ).isResolving( 'wc-admin', 'getProducts', args );
},
/**
* Returns true if a products request has returned an error.
*
* @param {Object} state Current state
* @param {Object} query Report query paremters
* @return {Object} True if the `getProducts` request has failed, false otherwise
*/
isProductsError( state, query ) {
return ERROR === getProducts( state, query );
},
};

View File

@ -0,0 +1,96 @@
/**
* @format
*/
/**
* External dependencies
*/
import deepFreeze from 'deep-freeze';
/**
* Internal dependencies
*/
import { ERROR } from 'store/constants';
import productsReducer, { DEFAULT_STATE } from '../reducer';
import { getJsonString } from 'store/util';
describe( 'productsReducer', () => {
it( 'returns default state by default', () => {
const state = productsReducer( undefined, {} );
expect( state ).toEqual( DEFAULT_STATE );
} );
it( 'returns received product data', () => {
const originalState = deepFreeze( { ...DEFAULT_STATE } );
const query = {
page: 3,
per_page: 5,
};
const products = [
{
id: 1,
name: 'my-product',
},
];
const state = productsReducer( originalState, {
type: 'SET_PRODUCTS',
query,
products,
} );
const queryKey = getJsonString( query );
expect( state.queries[ queryKey ] ).toEqual( products );
} );
it( 'returns received product data for multiple queries', () => {
const originalState = deepFreeze( { ...DEFAULT_STATE } );
const query1 = {
page: 3,
per_page: 5,
};
const products1 = [
{
id: 1,
name: 'my-product',
},
];
const intermediateState = productsReducer( originalState, {
type: 'SET_PRODUCTS',
query: query1,
products: products1,
} );
const query2 = {
page: 6232,
per_page: 978,
};
const products2 = [
{
id: 2,
name: 'my-other-product',
},
];
const finalState = productsReducer( intermediateState, {
type: 'SET_PRODUCTS',
query: query2,
products: products2,
} );
const queryKey1 = getJsonString( query1 );
const queryKey2 = getJsonString( query2 );
expect( finalState.queries[ queryKey1 ] ).toEqual( products1 );
expect( finalState.queries[ queryKey2 ] ).toEqual( products2 );
} );
it( 'returns error appropriately', () => {
const originalState = deepFreeze( { ...DEFAULT_STATE } );
const query = {
page: 4,
per_page: 5,
};
const state = productsReducer( originalState, {
type: 'SET_PRODUCTS_ERROR',
query,
} );
const queryKey = getJsonString( query );
expect( state.queries[ queryKey ] ).toEqual( ERROR );
} );
} );

View File

@ -0,0 +1,38 @@
/*
* @format
*/
/**
* External dependencies
*/
import apiFetch from '@wordpress/api-fetch';
/**
* Internal dependencies
*/
import resolvers from '../resolvers';
const { getProducts } = resolvers;
jest.mock( '@wordpress/api-fetch', () => jest.fn() );
describe( 'getProducts', () => {
const products = [
{
id: 1,
name: 'my-product',
},
];
beforeAll( () => {
apiFetch.mockImplementation( options => {
if ( options.path === '/wc/v3/products?search=abc' ) {
return Promise.resolve( products );
}
} );
} );
it( 'returns requested products', async () => {
getProducts( {}, { search: 'abc' } ).then( data => expect( data ).toEqual( products ) );
} );
} );

View File

@ -0,0 +1,104 @@
/*
* @format
*/
/**
* External dependencies
*/
import deepFreeze from 'deep-freeze';
/**
* Internal dependencies
*/
import { ERROR } from 'store/constants';
import selectors from '../selectors';
import { select } from '@wordpress/data';
import { getJsonString } from 'store/util';
const { getProducts, isProductsRequesting, isProductsError } = selectors;
jest.mock( '@wordpress/data', () => ( {
...require.requireActual( '@wordpress/data' ),
select: jest.fn().mockReturnValue( {} ),
} ) );
describe( 'getProducts', () => {
it( 'returns undefined when no query matches values in state', () => {
const state = deepFreeze( {} );
expect( getProducts( state, { search: 'abc' } ) ).toEqual( undefined );
} );
it( 'returns products for a given query', () => {
const products = [
{
id: 1,
name: 'my-product',
},
];
const query = { search: 'abc' };
const queryKey = getJsonString( query );
const state = deepFreeze( {
products: {
queries: {
[ queryKey ]: products,
},
},
} );
expect( getProducts( state, query ) ).toEqual( products );
} );
} );
describe( 'isProductsRequesting', () => {
beforeAll( () => {
select( 'core/data' ).isResolving = jest.fn().mockReturnValue( false );
} );
afterAll( () => {
select( 'core/data' ).isResolving.mockRestore();
} );
const query = { search: 'abc' };
function setIsResolving( isResolving ) {
select( 'core/data' ).isResolving.mockImplementation(
( reducerKey, selectorName ) =>
isResolving && reducerKey === 'wc-admin' && selectorName === 'getProducts'
);
}
it( 'returns false if never requested', () => {
const result = isProductsRequesting( query );
expect( result ).toBe( false );
} );
it( 'returns false if request finished', () => {
setIsResolving( false );
const result = isProductsRequesting( query );
expect( result ).toBe( false );
} );
it( 'returns true if requesting', () => {
setIsResolving( true );
const result = isProductsRequesting( query );
expect( result ).toBe( true );
} );
} );
describe( 'isProductsError', () => {
const query = { search: 'abc' };
it( 'returns false by default', () => {
const state = deepFreeze( {} );
expect( isProductsError( state, query ) ).toEqual( false );
} );
it( 'returns true if ERROR constant is found', () => {
const queryKey = getJsonString( query );
const state = deepFreeze( {
products: {
queries: {
[ queryKey ]: ERROR,
},
},
} );
expect( isProductsError( state, query ) ).toEqual( true );
} );
} );

View File

@ -6,7 +6,6 @@
* @param {Object} query Current state * @param {Object} query Current state
* @return {String} Query Key * @return {String} Query Key
*/ */
export function getJsonString( query = {} ) { export function getJsonString( query = {} ) {
return JSON.stringify( query, Object.keys( query ).sort() ); return JSON.stringify( query, Object.keys( query ).sort() );
} }