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 CRUD_ACTIONS from './crud-actions';
|
||||
import { getKey, getRequestIdentifier } from './utils';
|
||||
import { getRequestIdentifier, organizeItemsById } from './utils';
|
||||
import { getTotalCountResourceName } from '../utils';
|
||||
import { IdType, Item, ItemQuery } from './types';
|
||||
import { TYPES } from './action-types';
|
||||
import type { IdType, Item, ItemQuery } from './types';
|
||||
|
||||
export type Data = Record< IdType, Item >;
|
||||
export type ResourceState = {
|
||||
|
@ -291,19 +291,11 @@ export const createReducer = (
|
|||
};
|
||||
|
||||
case TYPES.GET_ITEMS_SUCCESS:
|
||||
const ids: IdType[] = [];
|
||||
|
||||
const nextResources = payload.items.reduce<
|
||||
Record< string, Item >
|
||||
>( ( result, item ) => {
|
||||
const key = getKey( item.id, payload.urlParameters );
|
||||
ids.push( key );
|
||||
result[ key ] = {
|
||||
...( state.data[ key ] || {} ),
|
||||
...item,
|
||||
};
|
||||
return result;
|
||||
}, {} );
|
||||
const { objItems, ids } = organizeItemsById(
|
||||
payload.items,
|
||||
payload.urlParameters,
|
||||
itemData
|
||||
);
|
||||
|
||||
const itemQuery = getRequestIdentifier(
|
||||
CRUD_ACTIONS.GET_ITEMS,
|
||||
|
@ -318,7 +310,7 @@ export const createReducer = (
|
|||
},
|
||||
data: {
|
||||
...state.data,
|
||||
...nextResources,
|
||||
...objItems,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -111,7 +111,7 @@ describe( 'crud reducer', () => {
|
|||
type: TYPES.GET_ITEMS_SUCCESS,
|
||||
items,
|
||||
query,
|
||||
urlParameters: [ 5 ],
|
||||
urlParameters: [ 100, 5 ],
|
||||
} );
|
||||
|
||||
const resourceName = getRequestIdentifier(
|
||||
|
@ -120,8 +120,8 @@ describe( 'crud reducer', () => {
|
|||
);
|
||||
|
||||
expect( state.items[ resourceName ].data ).toHaveLength( 2 );
|
||||
expect( state.data[ '5/1' ] ).toEqual( items[ 0 ] );
|
||||
expect( state.data[ '5/2' ] ).toEqual( items[ 1 ] );
|
||||
expect( state.data[ '100/5/1' ] ).toEqual( items[ 0 ] );
|
||||
expect( state.data[ '100/5/2' ] ).toEqual( items[ 1 ] );
|
||||
} );
|
||||
|
||||
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.requesting[ resourceName ] ).toEqual( false );
|
||||
|
||||
// Test the items object is an empty object
|
||||
expect( state.items ).toEqual( {} );
|
||||
|
||||
// Test the itemsCount object is an empty object
|
||||
expect( state.itemsCount ).toEqual( {} );
|
||||
} );
|
||||
|
||||
|
@ -383,32 +380,27 @@ describe( 'crud reducer', () => {
|
|||
);
|
||||
expect( state.requesting[ resourceName ] ).toEqual( false );
|
||||
|
||||
// Items
|
||||
const itemQuery = getRequestIdentifier(
|
||||
CRUD_ACTIONS.GET_ITEMS,
|
||||
options.optimisticQueryUpdate
|
||||
);
|
||||
|
||||
// Items should have the item id
|
||||
expect( state.items[ itemQuery ].data ).toHaveLength( 1 );
|
||||
expect( state.items[ itemQuery ].data[ 0 ] ).toEqual( 7 ); // Item id
|
||||
|
||||
// Items Key
|
||||
const itemsKey = Object.keys( state.items );
|
||||
expect( itemsKey ).toHaveLength( 1 );
|
||||
expect( itemsKey[ 0 ] ).toEqual( itemQuery );
|
||||
|
||||
// ItemsCount
|
||||
const itemsCountQuery = getRequestIdentifier(
|
||||
CRUD_ACTIONS.GET_ITEMS,
|
||||
options.optimisticQueryUpdate
|
||||
);
|
||||
|
||||
// ItemsCount Key
|
||||
const itemsCountKey = Object.keys( state.itemsCount );
|
||||
|
||||
// ItemsCount should be 1
|
||||
expect( state.itemsCount[ itemsCountQuery ] ).toEqual( 1 ); // Items count
|
||||
expect( state.itemsCount[ itemsCountQuery ] ).toEqual( 1 );
|
||||
|
||||
expect( itemsCountKey ).toHaveLength( 1 );
|
||||
expect( itemsCountKey[ 0 ] ).toEqual( itemsCountQuery );
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
cleanQuery,
|
||||
getGenericActionName,
|
||||
getKey,
|
||||
organizeItemsById,
|
||||
getNamespaceKeys,
|
||||
getRequestIdentifier,
|
||||
getRestPath,
|
||||
|
@ -15,6 +16,7 @@ import {
|
|||
isValidIdQuery,
|
||||
parseId,
|
||||
} from '../utils';
|
||||
import type { Item } from '../types';
|
||||
|
||||
describe( 'utils', () => {
|
||||
it( 'should get the rest path when no parameters are given', () => {
|
||||
|
@ -247,4 +249,89 @@ describe( 'utils', () => {
|
|||
const sanitizedArgs = maybeReplaceIdQuery( args, '/my/namespace/' );
|
||||
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
|
||||
*/
|
||||
import CRUD_ACTIONS from './crud-actions';
|
||||
import { IdQuery, IdType, ItemQuery } from './types';
|
||||
import { getResourceName } from '../utils';
|
||||
import type { IdQuery, IdType, Item, ItemQuery } from './types';
|
||||
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
|
||||
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.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue