Store: Add product API + state handlers (https://github.com/woocommerce/woocommerce-admin/pull/321)
* Add product state * Add products store tests
This commit is contained in:
parent
c06c3b0cef
commit
4e995590db
|
@ -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,
|
||||||
},
|
},
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -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 || {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
|
@ -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,
|
||||||
|
};
|
|
@ -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;
|
||||||
|
}
|
|
@ -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 );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
|
@ -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 );
|
||||||
|
},
|
||||||
|
};
|
|
@ -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 );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -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 ) );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -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 );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -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() );
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue