Products table (https://github.com/woocommerce/woocommerce-admin/pull/619)
* Add Products table * Update tests * Cleanup * Make placeholder table have the correct sorted column selected * Change default sort to items_sold & desc * Fix wrong CSV filename * Remove unnecessary constructor in ProductsReportTable * Add @TODO comment to Products table summary * Set ProductsReportTable link to wc-admin type * Make sure categories is an array before using map
This commit is contained in:
parent
7be34b3f37
commit
29f68a9ce8
|
@ -198,7 +198,7 @@ export default class OrdersReportTable extends Component {
|
|||
|
||||
renderLinks( items = [] ) {
|
||||
return items.map( ( item, i ) => (
|
||||
<Link href={ item.href } key={ i } type={ 'wp-admin' }>
|
||||
<Link href={ item.href } key={ i } type="wp-admin">
|
||||
{ item.label }
|
||||
</Link>
|
||||
) );
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
/** @format */
|
||||
|
||||
export default [
|
||||
{
|
||||
product_id: 658,
|
||||
items_sold: 8,
|
||||
gross_revenue: 6625.76,
|
||||
orders_count: 1,
|
||||
name: 'Mug',
|
||||
stock_quantity: 184,
|
||||
categories: [],
|
||||
variations: [],
|
||||
},
|
||||
{
|
||||
product_id: 412,
|
||||
items_sold: 4,
|
||||
gross_revenue: 60,
|
||||
orders_count: 1,
|
||||
name: 'Beanie',
|
||||
stock_quantity: 98,
|
||||
categories: [],
|
||||
variations: [],
|
||||
},
|
||||
{
|
||||
product_id: 724,
|
||||
items_sold: 11,
|
||||
gross_revenue: 6552.37,
|
||||
orders_count: 2,
|
||||
name: 'Sweater',
|
||||
stock_quantity: null,
|
||||
categories: [],
|
||||
variations: [],
|
||||
},
|
||||
{
|
||||
product_id: 653,
|
||||
items_sold: 14,
|
||||
gross_revenue: 2566.76,
|
||||
orders_count: 2,
|
||||
name: 'Octopus Tee',
|
||||
stock_quantity: 114,
|
||||
categories: [],
|
||||
variations: [],
|
||||
},
|
||||
{
|
||||
product_id: 254,
|
||||
items_sold: 9,
|
||||
gross_revenue: 135,
|
||||
orders_count: 1,
|
||||
name: 'Bubble Tee',
|
||||
stock_quantity: 79,
|
||||
categories: [],
|
||||
variations: [ 255, 256, 257 ],
|
||||
},
|
||||
];
|
|
@ -2,170 +2,77 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Component, Fragment } from '@wordpress/element';
|
||||
import { map, noop } from 'lodash';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import { withSelect } from '@wordpress/data';
|
||||
import { get } from 'lodash';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { filters } from './config';
|
||||
import { formatCurrency, getCurrencyFormatDecimal } from 'lib/currency';
|
||||
import { numberFormat } from 'lib/number';
|
||||
import { getAdminLink, onQueryChange } from 'lib/nav-utils';
|
||||
import { ReportFilters, TableCard } from '@woocommerce/components';
|
||||
import { ReportFilters } from '@woocommerce/components';
|
||||
import { appendTimestamp, getCurrentDates } from 'lib/date';
|
||||
import { getReportChartData } from 'store/reports/utils';
|
||||
import ProductsReportChart from './chart';
|
||||
|
||||
import products from './__mocks__/data';
|
||||
import ProductsReportTable from './table';
|
||||
|
||||
class ProductsReport extends Component {
|
||||
getHeadersContent() {
|
||||
return [
|
||||
{
|
||||
label: __( 'Product Title', 'wc-admin' ),
|
||||
key: 'name',
|
||||
required: true,
|
||||
isLeftAligned: true,
|
||||
isSortable: true,
|
||||
},
|
||||
{
|
||||
label: __( 'Items Sold', 'wc-admin' ),
|
||||
key: 'items_sold',
|
||||
required: true,
|
||||
defaultSort: true,
|
||||
isSortable: true,
|
||||
isNumeric: true,
|
||||
},
|
||||
{
|
||||
label: __( 'Gross Revenue', 'wc-admin' ),
|
||||
key: 'gross_revenue',
|
||||
required: true,
|
||||
isSortable: true,
|
||||
isNumeric: true,
|
||||
},
|
||||
{
|
||||
label: __( 'Orders', 'wc-admin' ),
|
||||
key: 'orders_count',
|
||||
required: false,
|
||||
isSortable: true,
|
||||
isNumeric: true,
|
||||
},
|
||||
{
|
||||
label: __( 'Category', 'wc-admin' ),
|
||||
key: 'product_cat',
|
||||
},
|
||||
{
|
||||
label: __( 'Variations', 'wc-admin' ),
|
||||
key: 'variation',
|
||||
},
|
||||
{
|
||||
label: __( 'Status', 'wc-admin' ),
|
||||
key: 'stock_status',
|
||||
},
|
||||
{
|
||||
label: __( 'Stock', 'wc-admin' ),
|
||||
key: 'stock',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getRowsContent( data = [] ) {
|
||||
return map( data, row => {
|
||||
const {
|
||||
product_id,
|
||||
items_sold,
|
||||
gross_revenue,
|
||||
orders_count,
|
||||
name,
|
||||
stock_quantity,
|
||||
variations,
|
||||
} = row;
|
||||
|
||||
return [
|
||||
{
|
||||
display: (
|
||||
<a href={ getAdminLink( 'post.php?action=edit&post=' + product_id ) }>{ name }</a>
|
||||
),
|
||||
value: name,
|
||||
},
|
||||
{
|
||||
display: items_sold,
|
||||
value: Number( items_sold ),
|
||||
},
|
||||
{
|
||||
display: formatCurrency( gross_revenue ),
|
||||
value: getCurrencyFormatDecimal( gross_revenue ),
|
||||
},
|
||||
{
|
||||
display: orders_count,
|
||||
value: Number( orders_count ),
|
||||
},
|
||||
{
|
||||
display: 'Categories',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
display: numberFormat( variations.length ),
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
display: 'Status',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
display: numberFormat( stock_quantity ),
|
||||
value: Number( stock_quantity ),
|
||||
},
|
||||
];
|
||||
} );
|
||||
}
|
||||
|
||||
renderTable() {
|
||||
const { query } = this.props;
|
||||
|
||||
const rowsPerPage = parseInt( query.per_page ) || 25;
|
||||
|
||||
const rows = this.getRowsContent( products );
|
||||
const headers = this.getHeadersContent();
|
||||
const labels = {
|
||||
helpText: __( 'Select at least two products to compare', 'wc-admin' ),
|
||||
placeholder: __( 'Search by product name or SKU', 'wc-admin' ),
|
||||
};
|
||||
|
||||
const tableQuery = {
|
||||
...query,
|
||||
orderby: query.orderby || 'date_start',
|
||||
order: query.order || 'asc',
|
||||
};
|
||||
return (
|
||||
<TableCard
|
||||
title={ __( 'Products', 'wc-admin' ) }
|
||||
rows={ rows }
|
||||
totalRows={ 500 }
|
||||
rowsPerPage={ rowsPerPage }
|
||||
headers={ headers }
|
||||
compareBy={ 'product' }
|
||||
labels={ labels }
|
||||
ids={ products.map( p => p.product_id ) }
|
||||
onClickDownload={ noop }
|
||||
onQueryChange={ onQueryChange }
|
||||
query={ tableQuery }
|
||||
summary={ null }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { query, path } = this.props;
|
||||
const {
|
||||
isProductsError,
|
||||
isProductsRequesting,
|
||||
path,
|
||||
primaryData,
|
||||
products,
|
||||
query,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<ReportFilters query={ query } path={ path } filters={ filters } />
|
||||
<ProductsReportChart query={ query } />
|
||||
{ this.renderTable() }
|
||||
<ProductsReportTable
|
||||
isError={ isProductsError || primaryData.isError }
|
||||
isRequesting={ isProductsRequesting || primaryData.isRequesting }
|
||||
products={ products }
|
||||
query={ query }
|
||||
totalRows={ get(
|
||||
primaryData,
|
||||
[ 'data', 'totals', 'products_count' ],
|
||||
Object.keys( products ).length
|
||||
) }
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ProductsReport;
|
||||
export default compose(
|
||||
withSelect( ( select, props ) => {
|
||||
const { query } = props;
|
||||
const datesFromQuery = getCurrentDates( query );
|
||||
const primaryData = getReportChartData( 'products', 'primary', query, select );
|
||||
|
||||
const { getProducts, isGetProductsError, isGetProductsRequesting } = select( 'wc-admin' );
|
||||
const tableQuery = {
|
||||
orderby: query.orderby || 'items_sold',
|
||||
order: query.order || 'desc',
|
||||
page: query.page || 1,
|
||||
per_page: query.per_page || 25,
|
||||
after: appendTimestamp( datesFromQuery.primary.after, 'start' ),
|
||||
before: appendTimestamp( datesFromQuery.primary.before, 'end' ),
|
||||
extended_product_info: true,
|
||||
};
|
||||
const products = getProducts( tableQuery );
|
||||
const isProductsError = isGetProductsError( tableQuery );
|
||||
const isProductsRequesting = isGetProductsRequesting( tableQuery );
|
||||
|
||||
return {
|
||||
isProductsError,
|
||||
isProductsRequesting,
|
||||
primaryData,
|
||||
products,
|
||||
};
|
||||
} )
|
||||
)( ProductsReport );
|
||||
|
|
|
@ -0,0 +1,219 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Component } from '@wordpress/element';
|
||||
import { map, orderBy } from 'lodash';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Card, Link, TableCard, TablePlaceholder } from '@woocommerce/components';
|
||||
import { downloadCSVFile, generateCSVDataFromTable, generateCSVFileName } from 'lib/csv';
|
||||
import { formatCurrency, getCurrencyFormatDecimal } from 'lib/currency';
|
||||
import { onQueryChange } from 'lib/nav-utils';
|
||||
import ReportError from 'analytics/components/report-error';
|
||||
|
||||
export default class ProductsReportTable extends Component {
|
||||
onDownload( headers, rows, query ) {
|
||||
// @TODO The current implementation only downloads the contents displayed in the table.
|
||||
// Another solution is required when the data set is larger (see #311).
|
||||
return () => {
|
||||
downloadCSVFile(
|
||||
generateCSVFileName( 'products', query ),
|
||||
generateCSVDataFromTable( headers, rows )
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
getHeadersContent() {
|
||||
return [
|
||||
{
|
||||
label: __( 'Product Title', 'wc-admin' ),
|
||||
key: 'name',
|
||||
required: true,
|
||||
isLeftAligned: true,
|
||||
},
|
||||
{
|
||||
label: __( 'SKU', 'wc-admin' ),
|
||||
key: 'sku',
|
||||
hiddenByDefault: true,
|
||||
},
|
||||
{
|
||||
label: __( 'Items Sold', 'wc-admin' ),
|
||||
key: 'items_sold',
|
||||
required: true,
|
||||
defaultSort: true,
|
||||
isSortable: true,
|
||||
isNumeric: true,
|
||||
},
|
||||
{
|
||||
label: __( 'G. Revenue', 'wc-admin' ),
|
||||
key: 'gross_revenue',
|
||||
required: true,
|
||||
isSortable: true,
|
||||
isNumeric: true,
|
||||
},
|
||||
{
|
||||
label: __( 'Orders', 'wc-admin' ),
|
||||
key: 'orders_count',
|
||||
isSortable: true,
|
||||
isNumeric: true,
|
||||
},
|
||||
{
|
||||
label: __( 'Category', 'wc-admin' ),
|
||||
key: 'product_cat',
|
||||
},
|
||||
{
|
||||
label: __( 'Variations', 'wc-admin' ),
|
||||
key: 'variation',
|
||||
},
|
||||
{
|
||||
label: __( 'Status', 'wc-admin' ),
|
||||
key: 'stock_status',
|
||||
},
|
||||
{
|
||||
label: __( 'Stock', 'wc-admin' ),
|
||||
key: 'stock',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getRowsContent( data = [] ) {
|
||||
const { stockStatuses } = wcSettings;
|
||||
|
||||
return map( data, row => {
|
||||
const {
|
||||
product_id,
|
||||
sku = '', // @TODO
|
||||
name,
|
||||
items_sold,
|
||||
gross_revenue,
|
||||
orders_count,
|
||||
categories = [], // @TODO
|
||||
variations = [], // @TODO
|
||||
stock_status = 'outofstock', // @TODO
|
||||
stock_quantity = '0', // @TODO
|
||||
} = row;
|
||||
|
||||
return [
|
||||
{
|
||||
// @TODO Must link to the product detail report.
|
||||
display: name,
|
||||
value: name,
|
||||
},
|
||||
{
|
||||
display: sku,
|
||||
value: sku,
|
||||
},
|
||||
{
|
||||
display: items_sold,
|
||||
value: items_sold,
|
||||
},
|
||||
{
|
||||
display: formatCurrency( gross_revenue ),
|
||||
value: getCurrencyFormatDecimal( gross_revenue ),
|
||||
},
|
||||
{
|
||||
display: (
|
||||
<Link href={ 'orders?filter=advanced&product_includes=' + product_id } type="wc-admin">
|
||||
{ orders_count }
|
||||
</Link>
|
||||
),
|
||||
value: orders_count,
|
||||
},
|
||||
{
|
||||
display: Array.isArray( categories )
|
||||
? categories.map( cat => cat.name ).join( ', ' )
|
||||
: '',
|
||||
value: Array.isArray( categories ) ? categories.map( cat => cat.name ).join( ', ' ) : '',
|
||||
},
|
||||
{
|
||||
display: variations.length,
|
||||
value: variations.length,
|
||||
},
|
||||
{
|
||||
display: (
|
||||
<Link href={ 'post.php?action=edit&post=' + product_id } type="wp-admin">
|
||||
{ stockStatuses[ stock_status ] }
|
||||
</Link>
|
||||
),
|
||||
value: stockStatuses[ stock_status ],
|
||||
},
|
||||
{
|
||||
display: stock_quantity,
|
||||
value: stock_quantity,
|
||||
},
|
||||
];
|
||||
} );
|
||||
}
|
||||
|
||||
renderPlaceholderTable( tableQuery ) {
|
||||
const headers = this.getHeadersContent();
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={ __( 'Products', 'wc-admin' ) }
|
||||
className="woocommerce-analytics__table-placeholder"
|
||||
>
|
||||
<TablePlaceholder
|
||||
caption={ __( 'Products', 'wc-admin' ) }
|
||||
headers={ headers }
|
||||
query={ tableQuery }
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
renderTable( tableQuery ) {
|
||||
const { products, query, totalRows } = this.props;
|
||||
|
||||
const rowsPerPage = parseInt( tableQuery.per_page ) || 25;
|
||||
const orderedProducts = orderBy( products, tableQuery.orderby, tableQuery.order );
|
||||
const rows = this.getRowsContent( orderedProducts );
|
||||
|
||||
const headers = this.getHeadersContent();
|
||||
const labels = {
|
||||
helpText: __( 'Select at least two products to compare', 'wc-admin' ),
|
||||
placeholder: __( 'Search by product name or SKU', 'wc-admin' ),
|
||||
};
|
||||
|
||||
return (
|
||||
<TableCard
|
||||
title={ __( 'Products', 'wc-admin' ) }
|
||||
rows={ rows }
|
||||
totalRows={ totalRows }
|
||||
rowsPerPage={ rowsPerPage }
|
||||
headers={ headers }
|
||||
labels={ labels }
|
||||
ids={ orderedProducts.map( p => p.product_id ) }
|
||||
compareBy={ 'product' }
|
||||
onClickDownload={ this.onDownload( headers, rows, query ) }
|
||||
onQueryChange={ onQueryChange }
|
||||
query={ tableQuery }
|
||||
summary={ null } // @TODO
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isError, isRequesting, query } = this.props;
|
||||
|
||||
if ( isError ) {
|
||||
return <ReportError isError />;
|
||||
}
|
||||
|
||||
const tableQuery = {
|
||||
...query,
|
||||
orderby: query.orderby || 'items_sold',
|
||||
order: query.order || 'desc',
|
||||
};
|
||||
|
||||
if ( isRequesting ) {
|
||||
return this.renderPlaceholderTable( tableQuery );
|
||||
}
|
||||
|
||||
return this.renderTable( tableQuery );
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import classnames from 'classnames';
|
||||
import { Component } from '@wordpress/element';
|
||||
import { fill, find, findIndex, first, isEqual, noop, partial, uniq } from 'lodash';
|
||||
import { find, findIndex, first, isEqual, noop, partial, uniq } from 'lodash';
|
||||
import { IconButton, ToggleControl } from '@wordpress/components';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
|
@ -39,7 +39,7 @@ class TableCard extends Component {
|
|||
super( props );
|
||||
const { compareBy, query } = props;
|
||||
this.state = {
|
||||
showCols: fill( Array( props.headers.length ), true ),
|
||||
showCols: props.headers.map( ( { hiddenByDefault } ) => ! hiddenByDefault ),
|
||||
selectedRows: getIdsFromQuery( query[ compareBy ] ),
|
||||
};
|
||||
this.toggleCols = this.toggleCols.bind( this );
|
||||
|
@ -302,6 +302,7 @@ TableCard.propTypes = {
|
|||
*/
|
||||
headers: PropTypes.arrayOf(
|
||||
PropTypes.shape( {
|
||||
hiddenByDefault: PropTypes.bool,
|
||||
defaultSort: PropTypes.bool,
|
||||
isSortable: PropTypes.bool,
|
||||
key: PropTypes.string,
|
||||
|
|
|
@ -17,8 +17,9 @@ import Table from './table';
|
|||
class TablePlaceholder extends Component {
|
||||
render() {
|
||||
const { caption, headers, numberOfRows, query } = this.props;
|
||||
const filteredHeaders = headers.filter( header => ! header.hiddenByDefault );
|
||||
const rows = range( numberOfRows ).map( () =>
|
||||
headers.map( () => ( { display: <span className="is-placeholder" /> } ) )
|
||||
filteredHeaders.map( () => ( { display: <span className="is-placeholder" /> } ) )
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -26,7 +27,7 @@ class TablePlaceholder extends Component {
|
|||
ariaHidden={ true }
|
||||
caption={ caption }
|
||||
classNames="is-loading"
|
||||
headers={ headers }
|
||||
headers={ filteredHeaders }
|
||||
rowHeader={ false }
|
||||
rows={ rows }
|
||||
query={ query }
|
||||
|
@ -49,6 +50,7 @@ TablePlaceholder.propTypes = {
|
|||
*/
|
||||
headers: PropTypes.arrayOf(
|
||||
PropTypes.shape( {
|
||||
hiddenByDefault: PropTypes.bool,
|
||||
defaultSort: PropTypes.bool,
|
||||
isSortable: PropTypes.bool,
|
||||
key: PropTypes.string,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { get } from 'lodash';
|
||||
import { merge } from 'lodash';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -10,34 +10,22 @@ import { get } from 'lodash';
|
|||
import { ERROR } from 'store/constants';
|
||||
import { getJsonString } from 'store/utils';
|
||||
|
||||
export const DEFAULT_STATE = {
|
||||
queries: {},
|
||||
};
|
||||
export const DEFAULT_STATE = {};
|
||||
|
||||
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,
|
||||
};
|
||||
const queryKey = getJsonString( action.query );
|
||||
|
||||
switch ( action.type ) {
|
||||
case 'SET_PRODUCTS':
|
||||
return merge( {}, state, {
|
||||
[ queryKey ]: action.products,
|
||||
} );
|
||||
|
||||
case 'SET_PRODUCTS_ERROR':
|
||||
return merge( {}, state, {
|
||||
[ queryKey ]: ERROR,
|
||||
} );
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ export default {
|
|||
async getProducts( state, query ) {
|
||||
try {
|
||||
const params = query ? '?' + stringify( query ) : '';
|
||||
const products = await apiFetch( { path: '/wc/v3/products' + params } );
|
||||
const products = await apiFetch( { path: '/wc/v3/reports/products' + params } );
|
||||
dispatch( 'wc-admin' ).setProducts( products, query );
|
||||
} catch ( error ) {
|
||||
dispatch( 'wc-admin' ).setProductsError( query );
|
||||
|
|
|
@ -20,8 +20,7 @@ import { getJsonString } from 'store/utils';
|
|||
* @return {Object} Report details
|
||||
*/
|
||||
function getProducts( state, query = {} ) {
|
||||
const queries = get( state, 'products.queries', {} );
|
||||
return queries[ getJsonString( query ) ];
|
||||
return get( state, [ 'products', getJsonString( query ) ], [] );
|
||||
}
|
||||
|
||||
export default {
|
||||
|
@ -33,7 +32,7 @@ export default {
|
|||
* @param {Object} state Current state
|
||||
* @return {Object} True if the `getProducts` request is pending, false otherwise
|
||||
*/
|
||||
isProductsRequesting( state, ...args ) {
|
||||
isGetProductsRequesting( state, ...args ) {
|
||||
return select( 'core/data' ).isResolving( 'wc-admin', 'getProducts', args );
|
||||
},
|
||||
|
||||
|
@ -44,7 +43,7 @@ export default {
|
|||
* @param {Object} query Report query paremters
|
||||
* @return {Object} True if the `getProducts` request has failed, false otherwise
|
||||
*/
|
||||
isProductsError( state, query ) {
|
||||
isGetProductsError( state, query ) {
|
||||
return ERROR === getProducts( state, query );
|
||||
},
|
||||
};
|
||||
|
|
|
@ -38,7 +38,7 @@ describe( 'productsReducer', () => {
|
|||
products,
|
||||
} );
|
||||
const queryKey = getJsonString( query );
|
||||
expect( state.queries[ queryKey ] ).toEqual( products );
|
||||
expect( state[ queryKey ] ).toEqual( products );
|
||||
} );
|
||||
|
||||
it( 'returns received product data for multiple queries', () => {
|
||||
|
@ -76,8 +76,8 @@ describe( 'productsReducer', () => {
|
|||
|
||||
const queryKey1 = getJsonString( query1 );
|
||||
const queryKey2 = getJsonString( query2 );
|
||||
expect( finalState.queries[ queryKey1 ] ).toEqual( products1 );
|
||||
expect( finalState.queries[ queryKey2 ] ).toEqual( products2 );
|
||||
expect( finalState[ queryKey1 ] ).toEqual( products1 );
|
||||
expect( finalState[ queryKey2 ] ).toEqual( products2 );
|
||||
} );
|
||||
|
||||
it( 'returns error appropriately', () => {
|
||||
|
@ -91,6 +91,6 @@ describe( 'productsReducer', () => {
|
|||
query,
|
||||
} );
|
||||
const queryKey = getJsonString( query );
|
||||
expect( state.queries[ queryKey ] ).toEqual( ERROR );
|
||||
expect( state[ queryKey ] ).toEqual( ERROR );
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -17,22 +17,43 @@ const { getProducts } = resolvers;
|
|||
jest.mock( '@wordpress/api-fetch', () => jest.fn() );
|
||||
|
||||
describe( 'getProducts', () => {
|
||||
const products = [
|
||||
const PRODUCTS_1 = [
|
||||
{
|
||||
id: 3,
|
||||
name: 'my-product-3',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'my-product-2-4',
|
||||
},
|
||||
];
|
||||
const PRODUCTS_2 = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'my-product',
|
||||
name: 'my-product-1',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'my-product-2',
|
||||
},
|
||||
];
|
||||
|
||||
beforeAll( () => {
|
||||
apiFetch.mockImplementation( options => {
|
||||
if ( options.path === '/wc/v3/products?search=abc' ) {
|
||||
return Promise.resolve( products );
|
||||
if ( options.path === '/wc/v3/products' ) {
|
||||
return Promise.resolve( PRODUCTS_1 );
|
||||
}
|
||||
if ( options.path === '/wc/v3/products?orderby=date' ) {
|
||||
return Promise.resolve( PRODUCTS_2 );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'returns requested products', async () => {
|
||||
getProducts( {}, { search: 'abc' } ).then( data => expect( data ).toEqual( products ) );
|
||||
getProducts().then( data => expect( data ).toEqual( PRODUCTS_1 ) );
|
||||
} );
|
||||
|
||||
it( 'returns requested products for a specific query', async () => {
|
||||
getProducts( { orderby: 'date' } ).then( data => expect( data ).toEqual( PRODUCTS_2 ) );
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -15,16 +15,19 @@ import selectors from '../selectors';
|
|||
import { select } from '@wordpress/data';
|
||||
import { getJsonString } from 'store/utils';
|
||||
|
||||
const { getProducts, isProductsRequesting, isProductsError } = selectors;
|
||||
const { getProducts, isGetProductsRequesting, isGetProductsError } = selectors;
|
||||
jest.mock( '@wordpress/data', () => ( {
|
||||
...require.requireActual( '@wordpress/data' ),
|
||||
select: jest.fn().mockReturnValue( {} ),
|
||||
} ) );
|
||||
|
||||
const query = { orderby: 'date' };
|
||||
const queryKey = getJsonString( query );
|
||||
|
||||
describe( 'getProducts', () => {
|
||||
it( 'returns undefined when no query matches values in state', () => {
|
||||
it( 'returns an empty array when no query matches values in state', () => {
|
||||
const state = deepFreeze( {} );
|
||||
expect( getProducts( state, { search: 'abc' } ) ).toEqual( undefined );
|
||||
expect( getProducts( state, query ) ).toEqual( [] );
|
||||
} );
|
||||
|
||||
it( 'returns products for a given query', () => {
|
||||
|
@ -34,20 +37,16 @@ describe( 'getProducts', () => {
|
|||
name: 'my-product',
|
||||
},
|
||||
];
|
||||
const query = { search: 'abc' };
|
||||
const queryKey = getJsonString( query );
|
||||
const state = deepFreeze( {
|
||||
products: {
|
||||
queries: {
|
||||
[ queryKey ]: products,
|
||||
},
|
||||
[ queryKey ]: products,
|
||||
},
|
||||
} );
|
||||
expect( getProducts( state, query ) ).toEqual( products );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'isProductsRequesting', () => {
|
||||
describe( 'isGetProductsRequesting', () => {
|
||||
beforeAll( () => {
|
||||
select( 'core/data' ).isResolving = jest.fn().mockReturnValue( false );
|
||||
} );
|
||||
|
@ -56,8 +55,6 @@ describe( 'isProductsRequesting', () => {
|
|||
select( 'core/data' ).isResolving.mockRestore();
|
||||
} );
|
||||
|
||||
const query = { search: 'abc' };
|
||||
|
||||
function setIsResolving( isResolving ) {
|
||||
select( 'core/data' ).isResolving.mockImplementation(
|
||||
( reducerKey, selectorName ) =>
|
||||
|
@ -66,39 +63,34 @@ describe( 'isProductsRequesting', () => {
|
|||
}
|
||||
|
||||
it( 'returns false if never requested', () => {
|
||||
const result = isProductsRequesting( query );
|
||||
const result = isGetProductsRequesting( query );
|
||||
expect( result ).toBe( false );
|
||||
} );
|
||||
|
||||
it( 'returns false if request finished', () => {
|
||||
setIsResolving( false );
|
||||
const result = isProductsRequesting( query );
|
||||
const result = isGetProductsRequesting( query );
|
||||
expect( result ).toBe( false );
|
||||
} );
|
||||
|
||||
it( 'returns true if requesting', () => {
|
||||
setIsResolving( true );
|
||||
const result = isProductsRequesting( query );
|
||||
const result = isGetProductsRequesting( query );
|
||||
expect( result ).toBe( true );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'isProductsError', () => {
|
||||
const query = { search: 'abc' };
|
||||
|
||||
describe( 'isGetProductsError', () => {
|
||||
it( 'returns false by default', () => {
|
||||
const state = deepFreeze( {} );
|
||||
expect( isProductsError( state, query ) ).toEqual( false );
|
||||
expect( isGetProductsError( state, query ) ).toEqual( false );
|
||||
} );
|
||||
it( 'returns true if ERROR constant is found', () => {
|
||||
const queryKey = getJsonString( query );
|
||||
const state = deepFreeze( {
|
||||
products: {
|
||||
queries: {
|
||||
[ queryKey ]: ERROR,
|
||||
},
|
||||
[ queryKey ]: ERROR,
|
||||
},
|
||||
} );
|
||||
expect( isProductsError( state, query ) ).toEqual( true );
|
||||
expect( isGetProductsError( state, query ) ).toEqual( true );
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -85,6 +85,7 @@ function wc_admin_register_script() {
|
|||
'dow' => get_option( 'start_of_week', 0 ),
|
||||
),
|
||||
'orderStatuses' => wc_get_order_statuses(),
|
||||
'stockStatuses' => wc_get_product_stock_status_options(),
|
||||
'siteTitle' => get_bloginfo( 'name' ),
|
||||
'trackingEnabled' => $tracking_enabled,
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue