CRUD: introduce organizeItemsById helper function (#47624)
* introduce processItems() util function * add processItems fn tests * use processItems in the reducer process * udpate GET_ITEMS reducer test * minor CREATE_ITEM_SUCCESS test update * changelog
This commit is contained in:
parent
64b8c680fc
commit
f5713b3f94
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: patch
|
||||||
|
Type: update
|
||||||
|
|
||||||
|
CRUD: introduce organizeItemsById helper function
|
|
@ -8,10 +8,10 @@ import { Reducer } from 'redux';
|
||||||
*/
|
*/
|
||||||
import { Actions } from './actions';
|
import { Actions } from './actions';
|
||||||
import CRUD_ACTIONS from './crud-actions';
|
import CRUD_ACTIONS from './crud-actions';
|
||||||
import { getKey, getRequestIdentifier } from './utils';
|
import { getRequestIdentifier, organizeItemsById } from './utils';
|
||||||
import { getTotalCountResourceName } from '../utils';
|
import { getTotalCountResourceName } from '../utils';
|
||||||
import { IdType, Item, ItemQuery } from './types';
|
|
||||||
import { TYPES } from './action-types';
|
import { TYPES } from './action-types';
|
||||||
|
import type { IdType, Item, ItemQuery } from './types';
|
||||||
|
|
||||||
export type Data = Record< IdType, Item >;
|
export type Data = Record< IdType, Item >;
|
||||||
export type ResourceState = {
|
export type ResourceState = {
|
||||||
|
@ -291,19 +291,11 @@ export const createReducer = (
|
||||||
};
|
};
|
||||||
|
|
||||||
case TYPES.GET_ITEMS_SUCCESS:
|
case TYPES.GET_ITEMS_SUCCESS:
|
||||||
const ids: IdType[] = [];
|
const { objItems, ids } = organizeItemsById(
|
||||||
|
payload.items,
|
||||||
const nextResources = payload.items.reduce<
|
payload.urlParameters,
|
||||||
Record< string, Item >
|
itemData
|
||||||
>( ( result, item ) => {
|
);
|
||||||
const key = getKey( item.id, payload.urlParameters );
|
|
||||||
ids.push( key );
|
|
||||||
result[ key ] = {
|
|
||||||
...( state.data[ key ] || {} ),
|
|
||||||
...item,
|
|
||||||
};
|
|
||||||
return result;
|
|
||||||
}, {} );
|
|
||||||
|
|
||||||
const itemQuery = getRequestIdentifier(
|
const itemQuery = getRequestIdentifier(
|
||||||
CRUD_ACTIONS.GET_ITEMS,
|
CRUD_ACTIONS.GET_ITEMS,
|
||||||
|
@ -318,7 +310,7 @@ export const createReducer = (
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
...state.data,
|
...state.data,
|
||||||
...nextResources,
|
...objItems,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -111,7 +111,7 @@ describe( 'crud reducer', () => {
|
||||||
type: TYPES.GET_ITEMS_SUCCESS,
|
type: TYPES.GET_ITEMS_SUCCESS,
|
||||||
items,
|
items,
|
||||||
query,
|
query,
|
||||||
urlParameters: [ 5 ],
|
urlParameters: [ 100, 5 ],
|
||||||
} );
|
} );
|
||||||
|
|
||||||
const resourceName = getRequestIdentifier(
|
const resourceName = getRequestIdentifier(
|
||||||
|
@ -120,8 +120,8 @@ describe( 'crud reducer', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
expect( state.items[ resourceName ].data ).toHaveLength( 2 );
|
expect( state.items[ resourceName ].data ).toHaveLength( 2 );
|
||||||
expect( state.data[ '5/1' ] ).toEqual( items[ 0 ] );
|
expect( state.data[ '100/5/1' ] ).toEqual( items[ 0 ] );
|
||||||
expect( state.data[ '5/2' ] ).toEqual( items[ 1 ] );
|
expect( state.data[ '100/5/2' ] ).toEqual( items[ 1 ] );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
it( 'GET_ITEMS_SUCCESS should not remove previously added fields, only update new ones', () => {
|
it( 'GET_ITEMS_SUCCESS should not remove previously added fields, only update new ones', () => {
|
||||||
|
@ -343,10 +343,7 @@ describe( 'crud reducer', () => {
|
||||||
expect( state.data[ 2 ].status ).toEqual( item.status );
|
expect( state.data[ 2 ].status ).toEqual( item.status );
|
||||||
expect( state.requesting[ resourceName ] ).toEqual( false );
|
expect( state.requesting[ resourceName ] ).toEqual( false );
|
||||||
|
|
||||||
// Test the items object is an empty object
|
|
||||||
expect( state.items ).toEqual( {} );
|
expect( state.items ).toEqual( {} );
|
||||||
|
|
||||||
// Test the itemsCount object is an empty object
|
|
||||||
expect( state.itemsCount ).toEqual( {} );
|
expect( state.itemsCount ).toEqual( {} );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
@ -383,32 +380,27 @@ describe( 'crud reducer', () => {
|
||||||
);
|
);
|
||||||
expect( state.requesting[ resourceName ] ).toEqual( false );
|
expect( state.requesting[ resourceName ] ).toEqual( false );
|
||||||
|
|
||||||
// Items
|
|
||||||
const itemQuery = getRequestIdentifier(
|
const itemQuery = getRequestIdentifier(
|
||||||
CRUD_ACTIONS.GET_ITEMS,
|
CRUD_ACTIONS.GET_ITEMS,
|
||||||
options.optimisticQueryUpdate
|
options.optimisticQueryUpdate
|
||||||
);
|
);
|
||||||
|
|
||||||
// Items should have the item id
|
|
||||||
expect( state.items[ itemQuery ].data ).toHaveLength( 1 );
|
expect( state.items[ itemQuery ].data ).toHaveLength( 1 );
|
||||||
expect( state.items[ itemQuery ].data[ 0 ] ).toEqual( 7 ); // Item id
|
expect( state.items[ itemQuery ].data[ 0 ] ).toEqual( 7 ); // Item id
|
||||||
|
|
||||||
// Items Key
|
|
||||||
const itemsKey = Object.keys( state.items );
|
const itemsKey = Object.keys( state.items );
|
||||||
expect( itemsKey ).toHaveLength( 1 );
|
expect( itemsKey ).toHaveLength( 1 );
|
||||||
expect( itemsKey[ 0 ] ).toEqual( itemQuery );
|
expect( itemsKey[ 0 ] ).toEqual( itemQuery );
|
||||||
|
|
||||||
// ItemsCount
|
|
||||||
const itemsCountQuery = getRequestIdentifier(
|
const itemsCountQuery = getRequestIdentifier(
|
||||||
CRUD_ACTIONS.GET_ITEMS,
|
CRUD_ACTIONS.GET_ITEMS,
|
||||||
options.optimisticQueryUpdate
|
options.optimisticQueryUpdate
|
||||||
);
|
);
|
||||||
|
|
||||||
// ItemsCount Key
|
|
||||||
const itemsCountKey = Object.keys( state.itemsCount );
|
const itemsCountKey = Object.keys( state.itemsCount );
|
||||||
|
|
||||||
// ItemsCount should be 1
|
// ItemsCount should be 1
|
||||||
expect( state.itemsCount[ itemsCountQuery ] ).toEqual( 1 ); // Items count
|
expect( state.itemsCount[ itemsCountQuery ] ).toEqual( 1 );
|
||||||
|
|
||||||
expect( itemsCountKey ).toHaveLength( 1 );
|
expect( itemsCountKey ).toHaveLength( 1 );
|
||||||
expect( itemsCountKey[ 0 ] ).toEqual( itemsCountQuery );
|
expect( itemsCountKey[ 0 ] ).toEqual( itemsCountQuery );
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
cleanQuery,
|
cleanQuery,
|
||||||
getGenericActionName,
|
getGenericActionName,
|
||||||
getKey,
|
getKey,
|
||||||
|
organizeItemsById,
|
||||||
getNamespaceKeys,
|
getNamespaceKeys,
|
||||||
getRequestIdentifier,
|
getRequestIdentifier,
|
||||||
getRestPath,
|
getRestPath,
|
||||||
|
@ -15,6 +16,7 @@ import {
|
||||||
isValidIdQuery,
|
isValidIdQuery,
|
||||||
parseId,
|
parseId,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
|
import type { Item } from '../types';
|
||||||
|
|
||||||
describe( 'utils', () => {
|
describe( 'utils', () => {
|
||||||
it( 'should get the rest path when no parameters are given', () => {
|
it( 'should get the rest path when no parameters are given', () => {
|
||||||
|
@ -247,4 +249,89 @@ describe( 'utils', () => {
|
||||||
const sanitizedArgs = maybeReplaceIdQuery( args, '/my/namespace/' );
|
const sanitizedArgs = maybeReplaceIdQuery( args, '/my/namespace/' );
|
||||||
expect( sanitizedArgs ).toEqual( args );
|
expect( sanitizedArgs ).toEqual( args );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
describe( 'organizeItemsById', () => {
|
||||||
|
it( 'should return the object Items and IDs when no urlParameters and existing data is provided', () => {
|
||||||
|
const items: Item[] = [
|
||||||
|
{ id: 1, name: 'Yum!' },
|
||||||
|
{ id: 2, name: 'Dynamite!' },
|
||||||
|
];
|
||||||
|
const { objItems, ids } = organizeItemsById( items );
|
||||||
|
|
||||||
|
expect( objItems ).toEqual( {
|
||||||
|
1: { id: 1, name: 'Yum!' },
|
||||||
|
2: { id: 2, name: 'Dynamite!' },
|
||||||
|
} );
|
||||||
|
|
||||||
|
expect( ids ).toEqual( [ 1, 2 ] );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should return the object Items and IDs when urlParameters but no existing data is provided', () => {
|
||||||
|
const items: Item[] = [
|
||||||
|
{ id: 1, name: 'Yum!' },
|
||||||
|
{ id: 2, name: 'Dynamite!' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const urlParameters = [ 200, 10 ];
|
||||||
|
const { objItems, ids } = organizeItemsById( items, urlParameters );
|
||||||
|
|
||||||
|
expect( objItems ).toEqual( {
|
||||||
|
'200/10/1': { id: 1, name: 'Yum!' },
|
||||||
|
'200/10/2': { id: 2, name: 'Dynamite!' },
|
||||||
|
} );
|
||||||
|
|
||||||
|
expect( ids ).toEqual( [ '200/10/1', '200/10/2' ] );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should return the object Items and IDs when existing data but no urlParameters is provided', () => {
|
||||||
|
const items: Item[] = [
|
||||||
|
{ id: 1, name: 'Yum! Yum!!' },
|
||||||
|
{ id: 2, name: 'Dynamite!' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const existingData = {
|
||||||
|
1: { id: 1, name: 'Yum!', price: 8.5 },
|
||||||
|
2: { id: 2, name: 'Dynamite!', price: 2.5 },
|
||||||
|
};
|
||||||
|
|
||||||
|
const { objItems, ids } = organizeItemsById(
|
||||||
|
items,
|
||||||
|
[],
|
||||||
|
existingData
|
||||||
|
);
|
||||||
|
|
||||||
|
expect( objItems ).toEqual( {
|
||||||
|
1: { id: 1, name: 'Yum! Yum!!', price: 8.5 },
|
||||||
|
2: { id: 2, name: 'Dynamite!', price: 2.5 },
|
||||||
|
} );
|
||||||
|
|
||||||
|
expect( ids ).toEqual( [ 1, 2 ] );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should return the object Items and IDs when urlParameters and existing data is provided', () => {
|
||||||
|
const items: Item[] = [
|
||||||
|
{ id: 1, name: 'Yum! Yum!!' },
|
||||||
|
{ id: 2, name: 'Dynamite!' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const urlParameters = [ 200, 10 ];
|
||||||
|
const existingData = {
|
||||||
|
'200/10/1': { id: 1, name: 'Yum!', price: 8.5 },
|
||||||
|
'200/10/2': { id: 2, name: 'Dynamite!', price: 2.5 },
|
||||||
|
};
|
||||||
|
|
||||||
|
const { objItems, ids } = organizeItemsById(
|
||||||
|
items,
|
||||||
|
urlParameters,
|
||||||
|
existingData
|
||||||
|
);
|
||||||
|
|
||||||
|
expect( objItems ).toEqual( {
|
||||||
|
'200/10/1': { id: 1, name: 'Yum! Yum!!', price: 8.5 },
|
||||||
|
'200/10/2': { id: 2, name: 'Dynamite!', price: 2.5 },
|
||||||
|
} );
|
||||||
|
|
||||||
|
expect( ids ).toEqual( [ '200/10/1', '200/10/2' ] );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -7,8 +7,8 @@ import { addQueryArgs } from '@wordpress/url';
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import CRUD_ACTIONS from './crud-actions';
|
import CRUD_ACTIONS from './crud-actions';
|
||||||
import { IdQuery, IdType, ItemQuery } from './types';
|
|
||||||
import { getResourceName } from '../utils';
|
import { getResourceName } from '../utils';
|
||||||
|
import type { IdQuery, IdType, Item, ItemQuery } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a REST path given a template path and URL params.
|
* Get a REST path given a template path and URL params.
|
||||||
|
@ -57,6 +57,45 @@ export const getKey = ( query: IdQuery, urlParameters: IdType[] = [] ) => {
|
||||||
return urlParameters.join( '/' ) + '/' + id;
|
return urlParameters.join( '/' ) + '/' + id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type organizeItemsByIdReturn = {
|
||||||
|
objItems: Record< string, Item >;
|
||||||
|
ids: IdType[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function takes an array of items and reduces it into a single object,
|
||||||
|
* where each key is a unique identifier generated by
|
||||||
|
* combining the item ID and optional URL parameters.
|
||||||
|
* It also returns an array of these keys (`ids`).
|
||||||
|
*
|
||||||
|
* @param {Array<Item>} items - The items to process.
|
||||||
|
* @param {Array<IdType>} urlParameters - The URL parameters used to generate keys.
|
||||||
|
* @param {Record<string, Item>} currentState - The current state data to merge with.
|
||||||
|
* @return {organizeItemsByIdReturn} An object with two properties: `objItems` and `ids`.
|
||||||
|
*/
|
||||||
|
export const organizeItemsById = (
|
||||||
|
items: Item[],
|
||||||
|
urlParameters: IdType[] = [],
|
||||||
|
currentState: Record< string, Item > = {}
|
||||||
|
): organizeItemsByIdReturn => {
|
||||||
|
const ids: IdType[] = [];
|
||||||
|
const objItems: Record< string, Item > = {};
|
||||||
|
|
||||||
|
const hasUrlParams = urlParameters.length > 0;
|
||||||
|
|
||||||
|
items.forEach( ( item ) => {
|
||||||
|
const key = hasUrlParams ? getKey( item.id, urlParameters ) : item.id;
|
||||||
|
ids.push( key );
|
||||||
|
|
||||||
|
objItems[ key ] = {
|
||||||
|
...( currentState[ key ] || {} ),
|
||||||
|
...item,
|
||||||
|
};
|
||||||
|
} );
|
||||||
|
|
||||||
|
return { objItems, ids };
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse an ID query into a ID string.
|
* Parse an ID query into a ID string.
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in New Issue