Skip to content

Commit

Permalink
Add actions, reducers & selectors to get available shipping methods f…
Browse files Browse the repository at this point in the history
…rom merchant host endpoint (Automattic#14610)
  • Loading branch information
DanReyLop authored Jun 7, 2017
1 parent 825223f commit c64287a
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 1 deletion.
2 changes: 2 additions & 0 deletions client/extensions/woocommerce/state/action-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export const WOOCOMMERCE_PRODUCT_EDIT = 'WOOCOMMERCE_PRODUCT_EDIT';
export const WOOCOMMERCE_PRODUCT_VARIATION_EDIT = 'WOOCOMMERCE_PRODUCT_VARIATION_EDIT';
export const WOOCOMMERCE_SETTINGS_GENERAL_REQUEST = 'WOOCOMMERCE_SETTINGS_GENERAL_REQUEST';
export const WOOCOMMERCE_SETTINGS_GENERAL_REQUEST_SUCCESS = 'WOOCOMMERCE_SETTINGS_GENERAL_REQUEST_SUCCESS';
export const WOOCOMMERCE_SHIPPING_METHODS_REQUEST = 'WOOCOMMERCE_SHIPPING_METHODS_REQUEST';
export const WOOCOMMERCE_SHIPPING_METHODS_REQUEST_SUCCESS = 'WOOCOMMERCE_SHIPPING_METHODS_REQUEST_SUCCESS';
export const WOOCOMMERCE_SHIPPING_ZONE_ADD = 'WOOCOMMERCE_SHIPPING_ZONE_ADD';
export const WOOCOMMERCE_SHIPPING_ZONE_CANCEL = 'WOOCOMMERCE_SHIPPING_ZONE_CANCEL';
export const WOOCOMMERCE_SHIPPING_ZONE_CLOSE = 'WOOCOMMERCE_SHIPPING_ZONE_CLOSE';
Expand Down
2 changes: 2 additions & 0 deletions client/extensions/woocommerce/state/sites/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { combineReducers, keyedReducer } from 'state/utils';
import paymentMethods from './payment-methods/reducer';
import productCategories from './product-categories/reducer';
import products from './products/reducer';
import shippingMethods from './shipping-methods/reducer';
import shippingZones from './shipping-zones/reducer';
import settings from './settings/reducer';
import status from './status/reducer';
Expand All @@ -14,6 +15,7 @@ const reducer = combineReducers( {
productCategories,
products,
settings,
shippingMethods,
shippingZones,
status,
} );
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Internal dependencies
*/
import request from '../request';
import { setError } from 'woocommerce/state/sites/status/wc-api/actions';
import {
WOOCOMMERCE_SHIPPING_METHODS_REQUEST,
WOOCOMMERCE_SHIPPING_METHODS_REQUEST_SUCCESS,
} from 'woocommerce/state/action-types';
import {
areShippingMethodsLoaded,
areShippingMethodsLoading,
} from './selectors';

export const fetchShippingMethodsSuccess = ( siteId, data ) => {
return {
type: WOOCOMMERCE_SHIPPING_METHODS_REQUEST_SUCCESS,
siteId,
data,
};
};

export const fetchShippingMethods = ( siteId ) => ( dispatch, getState ) => {
if ( areShippingMethodsLoaded( getState(), siteId ) || areShippingMethodsLoading( getState(), siteId ) ) {
return;
}

const getAction = {
type: WOOCOMMERCE_SHIPPING_METHODS_REQUEST,
siteId,
};

dispatch( getAction );

return request( siteId ).get( 'shipping_methods' )
.then( ( data ) => {
dispatch( fetchShippingMethodsSuccess( siteId, data ) );
} )
.catch( err => {
dispatch( setError( siteId, getAction, err ) );
} );
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Internal dependencies
*/
import { createReducer } from 'state/utils';
import {
WOOCOMMERCE_SHIPPING_METHODS_REQUEST,
WOOCOMMERCE_SHIPPING_METHODS_REQUEST_SUCCESS,
} from 'woocommerce/state/action-types';
import { LOADING } from 'woocommerce/state/constants';

// TODO: Handle error

export default createReducer( null, {
[ WOOCOMMERCE_SHIPPING_METHODS_REQUEST ]: () => {
return LOADING;
},

[ WOOCOMMERCE_SHIPPING_METHODS_REQUEST_SUCCESS ]: ( state, { data } ) => {
return data;
},
} );
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* External dependencies
*/
import { get, isArray } from 'lodash';

/**
* Internal dependencies
*/
import { getSelectedSiteId } from 'state/ui/selectors';
import { LOADING } from 'woocommerce/state/constants';

/**
* @param {Object} state Whole Redux state tree
* @param {Number} [siteId] Site ID to check. If not provided, the Site ID selected in the UI will be used
* @return {Object} The list of shipping methods, as retrieved from the server. It can also be the string "LOADING"
* if the methods are currently being fetched, or a "falsy" value if that haven't been fetched at all.
*/
export const getShippingMethods = ( state, siteId = getSelectedSiteId( state ) ) => {
return get( state, [ 'extensions', 'woocommerce', 'sites', siteId, 'shippingMethods' ] );
};

/**
* @param {Object} state Whole Redux state tree
* @param {Number} [siteId] Site ID to check. If not provided, the Site ID selected in the UI will be used
* @return {boolean} Whether the shipping methods list has been successfully loaded from the server
*/
export const areShippingMethodsLoaded = ( state, siteId = getSelectedSiteId( state ) ) => {
return isArray( getShippingMethods( state, siteId ) );
};

/**
* @param {Object} state Whole Redux state tree
* @param {Number} [siteId] Site ID to check. If not provided, the Site ID selected in the UI will be used
* @return {boolean} Whether the shipping methods list is currently being retrieved from the server
*/
export const areShippingMethodsLoading = ( state, siteId = getSelectedSiteId( state ) ) => {
return LOADING === getShippingMethods( state, siteId );
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* External dependencies
*/
import { expect } from 'chai';

/**
* Internal dependencies
*/
import reducer from '../../reducer';
import { LOADING } from 'woocommerce/state/constants';
import {
WOOCOMMERCE_SHIPPING_METHODS_REQUEST,
} from 'woocommerce/state/action-types';
import { fetchShippingMethodsSuccess } from '../actions';

describe( 'fetch shipping methods', () => {
it( 'should mark the shipping methods tree as "loading"', () => {
const siteId = 123;
const state = {};

const newSiteData = reducer( state, { type: WOOCOMMERCE_SHIPPING_METHODS_REQUEST, siteId } );
expect( newSiteData[ siteId ].shippingMethods ).to.eql( LOADING );
} );
} );

describe( 'fetch shipping methods - success', () => {
it( 'should store data from the action', () => {
const siteId = 123;
const state = {};

const methods = [
{ id: 'free_shipping', title: 'Free Shipping' },
{ id: 'local_pickup', title: 'Local Pickup' },
];
const newState = reducer( state, fetchShippingMethodsSuccess( siteId, methods ) );
expect( newState[ siteId ] ).to.exist;
expect( newState[ siteId ].shippingMethods ).to.deep.equal( methods );
} );
} );

Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* External dependencies
*/
import { expect } from 'chai';

/**
* Internal dependencies
*/
import { getShippingMethods, areShippingMethodsLoaded, areShippingMethodsLoading } from '../selectors';
import { LOADING } from 'woocommerce/state/constants';

describe( 'selectors', () => {
describe( 'shipping methods loading state', () => {
it( 'when woocommerce state is not available.', () => {
const state = {
extensions: {
woocommerce: {},
},
};

expect( getShippingMethods( state, 123 ) ).to.be.falsey;
expect( areShippingMethodsLoaded( state, 123 ) ).to.be.false;
expect( areShippingMethodsLoading( state, 123 ) ).to.be.false;
} );

it( 'when methods are loaded.', () => {
const state = {
extensions: {
woocommerce: {
sites: {
123: {
shippingMethods: [],
},
},
},
},
};

expect( getShippingMethods( state, 123 ) ).to.deep.equal( [] );
expect( areShippingMethodsLoaded( state, 123 ) ).to.be.true;
expect( areShippingMethodsLoading( state, 123 ) ).to.be.false;
} );

it( 'when methods are currently being fetched.', () => {
const state = {
extensions: {
woocommerce: {
sites: {
123: {
shippingMethods: LOADING,
},
},
},
},
};

expect( getShippingMethods( state, 123 ) ).to.equal( LOADING );
expect( areShippingMethodsLoaded( state, 123 ) ).to.be.false;
expect( areShippingMethodsLoading( state, 123 ) ).to.be.true;
} );

it( 'when methods are loaded only for a different site.', () => {
const state = {
extensions: {
woocommerce: {
sites: {
123: {
shippingMethods: [],
},
},
},
},
};

expect( getShippingMethods( state, 456 ) ).to.be.falsey;
expect( areShippingMethodsLoaded( state, 456 ) ).to.be.false;
expect( areShippingMethodsLoading( state, 456 ) ).to.be.false;
} );

it( 'should get the siteId from the UI tree if not provided.', () => {
const stateLoaded = {
extensions: {
woocommerce: {
sites: {
123: {
shippingMethods: [],
},
},
},
},
ui: {
selectedSiteId: 123,
},
};
const stateLoading = {
extensions: {
woocommerce: {
sites: {
123: {
shippingMethods: LOADING,
},
},
},
},
ui: {
selectedSiteId: 123,
},
};

expect( getShippingMethods( stateLoaded ) ).to.deep.equal( [] );
expect( areShippingMethodsLoaded( stateLoaded ) ).to.be.true;
expect( areShippingMethodsLoading( stateLoaded ) ).to.be.false;

expect( getShippingMethods( stateLoading ) ).to.equal( LOADING );
expect( areShippingMethodsLoaded( stateLoading ) ).to.be.false;
expect( areShippingMethodsLoading( stateLoading ) ).to.be.true;
} );
} );
} );
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { LOADING } from 'woocommerce/state/constants';
/**
* @param {Object} state Whole Redux state tree
* @param {Number} [siteId] Site ID to check. If not provided, the Site ID selected in the UI will be used
* @return {Object} The list of shipping zones, as retrieved fro the server. It can also be the string "LOADING"
* @return {Object} The list of shipping zones, as retrieved from the server. It can also be the string "LOADING"
* if the zones are currently being fetched, or a "falsy" value if that haven't been fetched at all.
*/
export const getAPIShippingZones = ( state, siteId = getSelectedSiteId( state ) ) => {
Expand Down

0 comments on commit c64287a

Please sign in to comment.