CRUD: populates `items` and `itemsCount` when a new item is created (#47632)
* minor change in reducer * use organizeItemsById to compute new items * add reducer test with pick optimisticUrlParameters * rename constant name * add optimisticUrlParameters to CRUD actions types * use organizeItemsById to generate store IDs * add more reducer tests * compute nextItemsData based on ids * tessstssssss * add a order_by: name test * fix sorting data when url parameters * add tests * introduce filterDataByKeys helper fn * fix process to sort items optimistically * rollback wrong change in actions * changelog * set action `options` as optional * set default value for options
This commit is contained in:
parent
460d73eee0
commit
f51b93359f
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: patch
|
||||||
|
Type: update
|
||||||
|
|
||||||
|
CRUD: populates items and itemsCount when a new item is created
|
|
@ -36,7 +36,7 @@ export function createItemSuccess(
|
||||||
key: IdType,
|
key: IdType,
|
||||||
item: Item,
|
item: Item,
|
||||||
query: Partial< ItemQuery >,
|
query: Partial< ItemQuery >,
|
||||||
options: CrudActionOptions
|
options?: CrudActionOptions
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
type: TYPES.CREATE_ITEM_SUCCESS as const,
|
type: TYPES.CREATE_ITEM_SUCCESS as const,
|
||||||
|
|
|
@ -8,7 +8,11 @@ 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 { getRequestIdentifier, organizeItemsById } from './utils';
|
import {
|
||||||
|
filterDataByKeys,
|
||||||
|
getRequestIdentifier,
|
||||||
|
organizeItemsById,
|
||||||
|
} from './utils';
|
||||||
import { getTotalCountResourceName } from '../utils';
|
import { getTotalCountResourceName } from '../utils';
|
||||||
import { TYPES } from './action-types';
|
import { TYPES } from './action-types';
|
||||||
import type { IdType, Item, ItemQuery } from './types';
|
import type { IdType, Item, ItemQuery } from './types';
|
||||||
|
@ -85,23 +89,41 @@ export const createReducer = (
|
||||||
};
|
};
|
||||||
|
|
||||||
case TYPES.CREATE_ITEM_SUCCESS: {
|
case TYPES.CREATE_ITEM_SUCCESS: {
|
||||||
|
const { options = {} } = payload;
|
||||||
|
|
||||||
|
const { objItems, ids } = organizeItemsById(
|
||||||
|
[ payload.item ],
|
||||||
|
options.optimisticUrlParameters,
|
||||||
|
itemData
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
...itemData,
|
||||||
|
...objItems,
|
||||||
|
};
|
||||||
|
|
||||||
const createItemSuccessRequestId = getRequestIdentifier(
|
const createItemSuccessRequestId = getRequestIdentifier(
|
||||||
CRUD_ACTIONS.CREATE_ITEM,
|
CRUD_ACTIONS.CREATE_ITEM,
|
||||||
payload.key,
|
ids[ 0 ],
|
||||||
payload.query
|
payload.query
|
||||||
);
|
);
|
||||||
|
|
||||||
const { options } = payload;
|
const getItemQueryId = getRequestIdentifier(
|
||||||
const data = {
|
CRUD_ACTIONS.GET_ITEMS,
|
||||||
...itemData,
|
options.optimisticQueryUpdate
|
||||||
[ payload.key ]: {
|
);
|
||||||
...( itemData[ payload.key ] || {} ),
|
|
||||||
...payload.item,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let items = state.items;
|
const getItemCountQueryId = getTotalCountResourceName(
|
||||||
let queryItems = Object.keys( data ).map( ( key ) => +key );
|
CRUD_ACTIONS.GET_ITEMS,
|
||||||
|
options?.optimisticQueryUpdate || {}
|
||||||
|
);
|
||||||
|
|
||||||
|
let currentItems = state.items;
|
||||||
|
|
||||||
|
const currentItemsByQueryId =
|
||||||
|
currentItems[ getItemQueryId ]?.data || [];
|
||||||
|
|
||||||
|
let nextItemsData = [ ...currentItemsByQueryId, ...ids ];
|
||||||
|
|
||||||
let itemsCount = state.itemsCount;
|
let itemsCount = state.itemsCount;
|
||||||
|
|
||||||
|
@ -126,49 +148,52 @@ export const createReducer = (
|
||||||
const order_by = options.optimisticQueryUpdate
|
const order_by = options.optimisticQueryUpdate
|
||||||
?.order_by as OrderBy;
|
?.order_by as OrderBy;
|
||||||
|
|
||||||
let sortingData = Object.values( data );
|
/*
|
||||||
sortingData = sortingData.sort( ( a, b ) =>
|
* Pick the data to sort by the order_by property,
|
||||||
( a[ order_by ] as string )
|
* from the data store,
|
||||||
.toLowerCase()
|
* based on the nextItemsData ids.
|
||||||
.localeCompare(
|
*/
|
||||||
(
|
let sourceDataToOrderBy = Object.values(
|
||||||
b[ order_by ] as string
|
filterDataByKeys( data, nextItemsData )
|
||||||
).toLowerCase()
|
) as Item[];
|
||||||
)
|
|
||||||
|
sourceDataToOrderBy = sourceDataToOrderBy.sort(
|
||||||
|
( a, b ) =>
|
||||||
|
String( a[ order_by ] as IdType )
|
||||||
|
.toLowerCase()
|
||||||
|
.localeCompare(
|
||||||
|
String(
|
||||||
|
b[ order_by ] as IdType
|
||||||
|
).toLowerCase()
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
queryItems = sortingData.map( ( item ) =>
|
// Pick the ids from the sorted data.
|
||||||
Number( item.id )
|
const { ids: sortedIds } = organizeItemsById(
|
||||||
|
sourceDataToOrderBy,
|
||||||
|
options.optimisticUrlParameters
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Update the items data with the sorted ids.
|
||||||
|
nextItemsData = sortedIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getItemQuery = getRequestIdentifier(
|
currentItems = {
|
||||||
CRUD_ACTIONS.GET_ITEMS,
|
...currentItems,
|
||||||
options.optimisticQueryUpdate
|
[ getItemQueryId ]: {
|
||||||
);
|
data: nextItemsData,
|
||||||
|
|
||||||
const getItemCountQuery = getTotalCountResourceName(
|
|
||||||
CRUD_ACTIONS.GET_ITEMS,
|
|
||||||
options.optimisticQueryUpdate
|
|
||||||
);
|
|
||||||
|
|
||||||
items = {
|
|
||||||
...state.items,
|
|
||||||
[ getItemQuery ]: {
|
|
||||||
...state.items[ getItemQuery ],
|
|
||||||
data: queryItems,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
itemsCount = {
|
itemsCount = {
|
||||||
...state.itemsCount,
|
...state.itemsCount,
|
||||||
[ getItemCountQuery ]: Object.keys( data ).length,
|
[ getItemCountQueryId ]: nextItemsData.length,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
items,
|
items: currentItems,
|
||||||
itemsCount,
|
itemsCount,
|
||||||
data,
|
data,
|
||||||
requesting: {
|
requesting: {
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
import { Actions } from '../actions';
|
import { Actions } from '../actions';
|
||||||
import { createReducer, ResourceState } from '../reducer';
|
import { createReducer, ResourceState } from '../reducer';
|
||||||
import { CRUD_ACTIONS } from '../crud-actions';
|
import { CRUD_ACTIONS } from '../crud-actions';
|
||||||
import { getResourceName } from '../../utils';
|
import { getResourceName, getTotalCountResourceName } from '../../utils';
|
||||||
import { getRequestIdentifier } from '..//utils';
|
import { getRequestIdentifier } from '../utils';
|
||||||
import { Item, ItemQuery } from '../types';
|
import { Item, ItemQuery } from '../types';
|
||||||
import TYPES from '../action-types';
|
import TYPES from '../action-types';
|
||||||
|
|
||||||
|
@ -314,7 +314,7 @@ describe( 'crud reducer', () => {
|
||||||
} );
|
} );
|
||||||
|
|
||||||
describe( 'should handle CREATE_ITEM_SUCCESS', () => {
|
describe( 'should handle CREATE_ITEM_SUCCESS', () => {
|
||||||
it( 'when no options are passed', () => {
|
it( 'with empty previous state', () => {
|
||||||
const item: Item = {
|
const item: Item = {
|
||||||
id: 2,
|
id: 2,
|
||||||
name: 'Off the hook!',
|
name: 'Off the hook!',
|
||||||
|
@ -325,12 +325,6 @@ describe( 'crud reducer', () => {
|
||||||
status: 'draft',
|
status: 'draft',
|
||||||
};
|
};
|
||||||
|
|
||||||
const resourceName = getRequestIdentifier(
|
|
||||||
CRUD_ACTIONS.CREATE_ITEM,
|
|
||||||
item.id,
|
|
||||||
query
|
|
||||||
);
|
|
||||||
|
|
||||||
const state = reducer( defaultState, {
|
const state = reducer( defaultState, {
|
||||||
type: TYPES.CREATE_ITEM_SUCCESS,
|
type: TYPES.CREATE_ITEM_SUCCESS,
|
||||||
key: item.id,
|
key: item.id,
|
||||||
|
@ -339,15 +333,90 @@ describe( 'crud reducer', () => {
|
||||||
options: {},
|
options: {},
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
const resourceName = getRequestIdentifier(
|
||||||
|
CRUD_ACTIONS.CREATE_ITEM,
|
||||||
|
item.id,
|
||||||
|
query
|
||||||
|
);
|
||||||
|
|
||||||
expect( state.data[ 2 ].name ).toEqual( item.name );
|
expect( state.data[ 2 ].name ).toEqual( item.name );
|
||||||
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 );
|
||||||
|
|
||||||
|
// Not optimitic query update
|
||||||
expect( state.items ).toEqual( {} );
|
expect( state.items ).toEqual( {} );
|
||||||
expect( state.itemsCount ).toEqual( {} );
|
expect( state.itemsCount ).toEqual( {} );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
it( 'when optimisticQueryUpdate is defined', () => {
|
it( 'with previous state', () => {
|
||||||
|
const item: Item = {
|
||||||
|
id: 3,
|
||||||
|
name: 'banana',
|
||||||
|
status: 'draft',
|
||||||
|
};
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
name: 'banana',
|
||||||
|
status: 'draft',
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryId = { type: 'fruit' };
|
||||||
|
|
||||||
|
const getItemsQueryId = getRequestIdentifier(
|
||||||
|
CRUD_ACTIONS.GET_ITEMS,
|
||||||
|
queryId
|
||||||
|
);
|
||||||
|
|
||||||
|
const getItemsCountQueryId = getTotalCountResourceName(
|
||||||
|
CRUD_ACTIONS.GET_ITEMS,
|
||||||
|
queryId
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialState: ResourceState = {
|
||||||
|
items: {
|
||||||
|
[ getItemsQueryId ]: {
|
||||||
|
data: [ 1, 2 ],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
itemsCount: {
|
||||||
|
[ getItemsCountQueryId ]: 2,
|
||||||
|
},
|
||||||
|
errors: {},
|
||||||
|
data: {
|
||||||
|
1: { id: 1, name: 'apple', status: 'draft' },
|
||||||
|
2: { id: 2, name: 'pine', status: 'publish' },
|
||||||
|
},
|
||||||
|
requesting: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = reducer( initialState, {
|
||||||
|
type: TYPES.CREATE_ITEM_SUCCESS,
|
||||||
|
key: item.id,
|
||||||
|
item,
|
||||||
|
query,
|
||||||
|
options: {},
|
||||||
|
} );
|
||||||
|
|
||||||
|
expect( state.data ).toEqual( {
|
||||||
|
1: { id: 1, name: 'apple', status: 'draft' },
|
||||||
|
2: { id: 2, name: 'pine', status: 'publish' },
|
||||||
|
3: { id: 3, name: 'banana', status: 'draft' },
|
||||||
|
} );
|
||||||
|
|
||||||
|
const resourceName = getRequestIdentifier(
|
||||||
|
CRUD_ACTIONS.CREATE_ITEM,
|
||||||
|
item.id,
|
||||||
|
query
|
||||||
|
);
|
||||||
|
expect( state.requesting[ resourceName ] ).toEqual( false );
|
||||||
|
|
||||||
|
// Not optimitic query update
|
||||||
|
expect( state.items[ getItemsQueryId ].data ).toHaveLength( 2 );
|
||||||
|
expect( state.items[ getItemsQueryId ].data ).toEqual( [ 1, 2 ] );
|
||||||
|
expect( state.itemsCount[ getItemsCountQueryId ] ).toEqual( 2 );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'with empty previous state, and optimisticQueryUpdate options', () => {
|
||||||
const item: Item = {
|
const item: Item = {
|
||||||
id: 7,
|
id: 7,
|
||||||
name: 'Off the hook!',
|
name: 'Off the hook!',
|
||||||
|
@ -370,8 +439,13 @@ describe( 'crud reducer', () => {
|
||||||
options,
|
options,
|
||||||
} );
|
} );
|
||||||
|
|
||||||
expect( state.data[ 7 ].name ).toEqual( item.name );
|
expect( state.data ).toEqual( {
|
||||||
expect( state.data[ 7 ].status ).toEqual( item.status );
|
7: {
|
||||||
|
id: 7,
|
||||||
|
name: 'Off the hook!',
|
||||||
|
status: 'draft',
|
||||||
|
},
|
||||||
|
} );
|
||||||
|
|
||||||
const resourceName = getRequestIdentifier(
|
const resourceName = getRequestIdentifier(
|
||||||
CRUD_ACTIONS.CREATE_ITEM,
|
CRUD_ACTIONS.CREATE_ITEM,
|
||||||
|
@ -380,30 +454,447 @@ describe( 'crud reducer', () => {
|
||||||
);
|
);
|
||||||
expect( state.requesting[ resourceName ] ).toEqual( false );
|
expect( state.requesting[ resourceName ] ).toEqual( false );
|
||||||
|
|
||||||
const itemQuery = getRequestIdentifier(
|
const getItemsQueryId = getRequestIdentifier(
|
||||||
CRUD_ACTIONS.GET_ITEMS,
|
CRUD_ACTIONS.GET_ITEMS,
|
||||||
options.optimisticQueryUpdate
|
options.optimisticQueryUpdate
|
||||||
);
|
);
|
||||||
|
|
||||||
expect( state.items[ itemQuery ].data ).toHaveLength( 1 );
|
expect( state.items[ getItemsQueryId ].data ).toHaveLength( 1 );
|
||||||
expect( state.items[ itemQuery ].data[ 0 ] ).toEqual( 7 ); // Item id
|
expect( state.items[ getItemsQueryId ].data[ 0 ] ).toEqual( 7 ); // Item id
|
||||||
|
|
||||||
const itemsKey = Object.keys( state.items );
|
const getItemsCountQueryId = getTotalCountResourceName(
|
||||||
expect( itemsKey ).toHaveLength( 1 );
|
|
||||||
expect( itemsKey[ 0 ] ).toEqual( itemQuery );
|
|
||||||
|
|
||||||
const itemsCountQuery = getRequestIdentifier(
|
|
||||||
CRUD_ACTIONS.GET_ITEMS,
|
CRUD_ACTIONS.GET_ITEMS,
|
||||||
options.optimisticQueryUpdate
|
options.optimisticQueryUpdate
|
||||||
);
|
);
|
||||||
|
|
||||||
const itemsCountKey = Object.keys( state.itemsCount );
|
expect( state.itemsCount[ getItemsCountQueryId ] ).toEqual( 1 );
|
||||||
|
} );
|
||||||
|
|
||||||
// ItemsCount should be 1
|
it( 'with previous state, and optimisticQueryUpdate options', () => {
|
||||||
expect( state.itemsCount[ itemsCountQuery ] ).toEqual( 1 );
|
const item: Item = {
|
||||||
|
id: 7,
|
||||||
|
name: 'kiwi',
|
||||||
|
status: 'draft',
|
||||||
|
};
|
||||||
|
|
||||||
expect( itemsCountKey ).toHaveLength( 1 );
|
const query = {
|
||||||
expect( itemsCountKey[ 0 ] ).toEqual( itemsCountQuery );
|
name: 'kiwi',
|
||||||
|
status: 'draft',
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
optimisticQueryUpdate: { random: 'fruit' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const getItemsQueryId = getRequestIdentifier(
|
||||||
|
CRUD_ACTIONS.GET_ITEMS,
|
||||||
|
options.optimisticQueryUpdate
|
||||||
|
);
|
||||||
|
|
||||||
|
const getItemsCountQueryId = getTotalCountResourceName(
|
||||||
|
CRUD_ACTIONS.GET_ITEMS,
|
||||||
|
options.optimisticQueryUpdate
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialState: ResourceState = {
|
||||||
|
items: {
|
||||||
|
[ getItemsQueryId ]: {
|
||||||
|
data: [ 1, 2 ],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
itemsCount: {
|
||||||
|
[ getItemsCountQueryId ]: 2,
|
||||||
|
},
|
||||||
|
errors: {},
|
||||||
|
data: {
|
||||||
|
1: { id: 1, name: 'apple', status: 'draft' },
|
||||||
|
2: { id: 2, name: 'pine', status: 'publish' },
|
||||||
|
},
|
||||||
|
requesting: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = reducer( initialState, {
|
||||||
|
type: TYPES.CREATE_ITEM_SUCCESS,
|
||||||
|
key: item.id,
|
||||||
|
item,
|
||||||
|
query,
|
||||||
|
options,
|
||||||
|
} );
|
||||||
|
|
||||||
|
expect( state.data ).toEqual( {
|
||||||
|
1: { id: 1, name: 'apple', status: 'draft' },
|
||||||
|
2: { id: 2, name: 'pine', status: 'publish' },
|
||||||
|
7: { id: 7, name: 'kiwi', status: 'draft' },
|
||||||
|
} );
|
||||||
|
|
||||||
|
const resourceName = getRequestIdentifier(
|
||||||
|
CRUD_ACTIONS.CREATE_ITEM,
|
||||||
|
item.id,
|
||||||
|
query
|
||||||
|
);
|
||||||
|
expect( state.requesting[ resourceName ] ).toEqual( false );
|
||||||
|
|
||||||
|
expect( state.items[ getItemsQueryId ].data ).toHaveLength( 3 );
|
||||||
|
expect( state.items[ getItemsQueryId ].data ).toEqual( [
|
||||||
|
1, 2, 7,
|
||||||
|
] );
|
||||||
|
|
||||||
|
expect( state.itemsCount[ getItemsCountQueryId ] ).toEqual( 3 );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( `with previous state,
|
||||||
|
and optimisticQueryUpdate options with order_by: name`, () => {
|
||||||
|
const item: Item = {
|
||||||
|
id: 7,
|
||||||
|
name: 'kiwi',
|
||||||
|
status: 'draft',
|
||||||
|
};
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
name: 'kiwi',
|
||||||
|
status: 'draft',
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
optimisticQueryUpdate: { order_by: 'name' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const getItemsQueryId = getRequestIdentifier(
|
||||||
|
CRUD_ACTIONS.GET_ITEMS,
|
||||||
|
options.optimisticQueryUpdate
|
||||||
|
);
|
||||||
|
|
||||||
|
const getItemsCountQueryId = getTotalCountResourceName(
|
||||||
|
CRUD_ACTIONS.GET_ITEMS,
|
||||||
|
options.optimisticQueryUpdate
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialState: ResourceState = {
|
||||||
|
items: {
|
||||||
|
[ getItemsQueryId ]: {
|
||||||
|
data: [ 1, 2 ],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
itemsCount: {
|
||||||
|
[ getItemsCountQueryId ]: 2,
|
||||||
|
},
|
||||||
|
errors: {},
|
||||||
|
data: {
|
||||||
|
1: { id: 1, name: 'apple', status: 'draft' },
|
||||||
|
2: { id: 2, name: 'pine', status: 'publish' },
|
||||||
|
},
|
||||||
|
requesting: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = reducer( initialState, {
|
||||||
|
type: TYPES.CREATE_ITEM_SUCCESS,
|
||||||
|
key: item.id,
|
||||||
|
item,
|
||||||
|
query,
|
||||||
|
options,
|
||||||
|
} );
|
||||||
|
|
||||||
|
expect( state.data ).toEqual( {
|
||||||
|
1: { id: 1, name: 'apple', status: 'draft' },
|
||||||
|
2: { id: 2, name: 'pine', status: 'publish' },
|
||||||
|
7: { id: 7, name: 'kiwi', status: 'draft' },
|
||||||
|
} );
|
||||||
|
|
||||||
|
const resourceName = getRequestIdentifier(
|
||||||
|
CRUD_ACTIONS.CREATE_ITEM,
|
||||||
|
item.id,
|
||||||
|
query
|
||||||
|
);
|
||||||
|
expect( state.requesting[ resourceName ] ).toEqual( false );
|
||||||
|
|
||||||
|
expect( state.items[ getItemsQueryId ].data ).toEqual( [
|
||||||
|
// order by name: apple(1), kiwi(7), pine(2)
|
||||||
|
1, 7, 2,
|
||||||
|
] );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( `with empty previous state,
|
||||||
|
and optimisticQueryUpdate and
|
||||||
|
optimisticUrlParameters options`, () => {
|
||||||
|
const item: Item = {
|
||||||
|
id: 7,
|
||||||
|
name: 'Off the hook!',
|
||||||
|
status: 'draft',
|
||||||
|
};
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
name: 'Off the hook!',
|
||||||
|
status: 'draft',
|
||||||
|
parent_id: 200,
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
optimisticQueryUpdate: { parent_id: 200 },
|
||||||
|
optimisticUrlParameters: [ 200 ],
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = reducer( defaultState, {
|
||||||
|
type: TYPES.CREATE_ITEM_SUCCESS,
|
||||||
|
key: item.id,
|
||||||
|
item,
|
||||||
|
query,
|
||||||
|
options,
|
||||||
|
} );
|
||||||
|
|
||||||
|
expect( state.data ).toEqual( {
|
||||||
|
'200/7': {
|
||||||
|
id: 7,
|
||||||
|
name: 'Off the hook!',
|
||||||
|
status: 'draft',
|
||||||
|
},
|
||||||
|
} );
|
||||||
|
|
||||||
|
const resourceName = getRequestIdentifier(
|
||||||
|
CRUD_ACTIONS.CREATE_ITEM,
|
||||||
|
'200/7',
|
||||||
|
query
|
||||||
|
);
|
||||||
|
|
||||||
|
expect( state.requesting[ resourceName ] ).toEqual( false );
|
||||||
|
|
||||||
|
const getItemsQueryId = getRequestIdentifier(
|
||||||
|
CRUD_ACTIONS.GET_ITEMS,
|
||||||
|
options.optimisticQueryUpdate
|
||||||
|
);
|
||||||
|
|
||||||
|
expect( state.items[ getItemsQueryId ] ).toBeDefined();
|
||||||
|
expect( state.items[ getItemsQueryId ].data ).toEqual( [
|
||||||
|
'200/7',
|
||||||
|
] );
|
||||||
|
|
||||||
|
const getItemsCountQueryId = getTotalCountResourceName(
|
||||||
|
CRUD_ACTIONS.GET_ITEMS,
|
||||||
|
options.optimisticQueryUpdate
|
||||||
|
);
|
||||||
|
|
||||||
|
expect( state.itemsCount[ getItemsCountQueryId ] ).toEqual( 1 );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( `with previous state,
|
||||||
|
and optimisticQueryUpdate with order_by: name,
|
||||||
|
and optimisticUrlParameters options`, () => {
|
||||||
|
const item: Item = {
|
||||||
|
id: 7,
|
||||||
|
name: 'kiwi',
|
||||||
|
status: 'draft',
|
||||||
|
};
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
name: 'kiwi',
|
||||||
|
status: 'draft',
|
||||||
|
parent_id: 200,
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
optimisticQueryUpdate: { parent_id: 200, order_by: 'name' },
|
||||||
|
optimisticUrlParameters: [ 200 ],
|
||||||
|
};
|
||||||
|
|
||||||
|
const getItemsQueryId = getRequestIdentifier(
|
||||||
|
CRUD_ACTIONS.GET_ITEMS,
|
||||||
|
options.optimisticQueryUpdate
|
||||||
|
);
|
||||||
|
|
||||||
|
const getItemsCountQueryId = getTotalCountResourceName(
|
||||||
|
CRUD_ACTIONS.GET_ITEMS,
|
||||||
|
options.optimisticQueryUpdate
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialState: ResourceState = {
|
||||||
|
items: {
|
||||||
|
[ getItemsQueryId ]: {
|
||||||
|
data: [ '200/1', '200/2' ],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
itemsCount: {
|
||||||
|
[ getItemsCountQueryId ]: 2,
|
||||||
|
},
|
||||||
|
errors: {},
|
||||||
|
data: {
|
||||||
|
'200/1': { id: 1, name: 'apple', status: 'draft' },
|
||||||
|
'200/2': { id: 2, name: 'pine', status: 'publish' },
|
||||||
|
},
|
||||||
|
requesting: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = reducer( initialState, {
|
||||||
|
type: TYPES.CREATE_ITEM_SUCCESS,
|
||||||
|
key: item.id,
|
||||||
|
item,
|
||||||
|
query,
|
||||||
|
options,
|
||||||
|
} );
|
||||||
|
|
||||||
|
expect( state.data ).toEqual( {
|
||||||
|
'200/1': {
|
||||||
|
id: 1,
|
||||||
|
name: 'apple',
|
||||||
|
status: 'draft',
|
||||||
|
},
|
||||||
|
'200/2': {
|
||||||
|
id: 2,
|
||||||
|
name: 'pine',
|
||||||
|
status: 'publish',
|
||||||
|
},
|
||||||
|
'200/7': {
|
||||||
|
id: 7,
|
||||||
|
name: 'kiwi',
|
||||||
|
status: 'draft',
|
||||||
|
},
|
||||||
|
} );
|
||||||
|
|
||||||
|
const resourceName = getRequestIdentifier(
|
||||||
|
CRUD_ACTIONS.CREATE_ITEM,
|
||||||
|
'200/7',
|
||||||
|
query
|
||||||
|
);
|
||||||
|
|
||||||
|
expect( state.requesting[ resourceName ] ).toEqual( false );
|
||||||
|
|
||||||
|
expect( state.items[ getItemsQueryId ] ).toBeDefined();
|
||||||
|
expect( state.items[ getItemsQueryId ].data ).toEqual( [
|
||||||
|
'200/1',
|
||||||
|
'200/7',
|
||||||
|
'200/2',
|
||||||
|
] );
|
||||||
|
|
||||||
|
expect( state.itemsCount[ getItemsCountQueryId ] ).toEqual( 3 );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( `with previous state,
|
||||||
|
multiple items,
|
||||||
|
and optimisticQueryUpdate with order_by: name,
|
||||||
|
and optimisticUrlParameters options`, () => {
|
||||||
|
const item: Item = {
|
||||||
|
id: 9,
|
||||||
|
name: 'Mootools',
|
||||||
|
status: 'draft',
|
||||||
|
};
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
name: 'Mootools',
|
||||||
|
status: 'draft',
|
||||||
|
parent_id: 500,
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
optimisticQueryUpdate: { parent_id: 500, order_by: 'name' },
|
||||||
|
optimisticUrlParameters: [ 500 ],
|
||||||
|
};
|
||||||
|
|
||||||
|
const getItemsQueryId_200 = getRequestIdentifier(
|
||||||
|
CRUD_ACTIONS.GET_ITEMS,
|
||||||
|
{
|
||||||
|
parent_id: 200,
|
||||||
|
rocking_by: 'name',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const getItemsQueryId_300 = getRequestIdentifier(
|
||||||
|
CRUD_ACTIONS.GET_ITEMS,
|
||||||
|
{
|
||||||
|
parent_id: 300,
|
||||||
|
rocking_by: 'name',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const getItemsQueryId = getRequestIdentifier(
|
||||||
|
CRUD_ACTIONS.GET_ITEMS,
|
||||||
|
options.optimisticQueryUpdate
|
||||||
|
);
|
||||||
|
|
||||||
|
const getItemsCountQueryId_200 = getTotalCountResourceName(
|
||||||
|
CRUD_ACTIONS.GET_ITEMS,
|
||||||
|
{
|
||||||
|
parent_id: 200,
|
||||||
|
rocking_by: 'name',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const getItemsCountQueryId_300 = getTotalCountResourceName(
|
||||||
|
CRUD_ACTIONS.GET_ITEMS,
|
||||||
|
{
|
||||||
|
parent_id: 300,
|
||||||
|
order_by: 'name',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const getItemsCountQueryId = getTotalCountResourceName(
|
||||||
|
CRUD_ACTIONS.GET_ITEMS,
|
||||||
|
options.optimisticQueryUpdate
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialState: ResourceState = {
|
||||||
|
items: {
|
||||||
|
[ getItemsQueryId_200 ]: {
|
||||||
|
data: [ '500/1', '500/2' ],
|
||||||
|
},
|
||||||
|
[ getItemsQueryId_300 ]: {
|
||||||
|
data: [ '300/1', '300/2' ],
|
||||||
|
},
|
||||||
|
[ getItemsQueryId ]: {
|
||||||
|
data: [ '500/1', '500/2', '500/3' ],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
itemsCount: {
|
||||||
|
[ getItemsCountQueryId_200 ]: 2,
|
||||||
|
[ getItemsCountQueryId_300 ]: 2,
|
||||||
|
[ getItemsCountQueryId ]: 3,
|
||||||
|
},
|
||||||
|
errors: {},
|
||||||
|
data: {
|
||||||
|
'200/1': { id: 1, name: 'apple', status: 'draft' },
|
||||||
|
'200/2': { id: 2, name: 'pine', status: 'publish' },
|
||||||
|
'300/1': { id: 1, name: 'cat', status: 'draft' },
|
||||||
|
'300/2': { id: 2, name: 'dog', status: 'draft' },
|
||||||
|
'500/1': { id: 1, name: 'jQuery', status: 'draft' },
|
||||||
|
'500/2': { id: 2, name: 'AlphaPro', status: 'draft' },
|
||||||
|
'500/3': { id: 3, name: 'Vue', status: 'draft' },
|
||||||
|
},
|
||||||
|
requesting: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = reducer( initialState, {
|
||||||
|
type: TYPES.CREATE_ITEM_SUCCESS,
|
||||||
|
key: item.id,
|
||||||
|
item,
|
||||||
|
query,
|
||||||
|
options,
|
||||||
|
} );
|
||||||
|
|
||||||
|
expect( state.data ).toEqual( {
|
||||||
|
'200/1': { id: 1, name: 'apple', status: 'draft' },
|
||||||
|
'200/2': { id: 2, name: 'pine', status: 'publish' },
|
||||||
|
'300/1': { id: 1, name: 'cat', status: 'draft' },
|
||||||
|
'300/2': { id: 2, name: 'dog', status: 'draft' },
|
||||||
|
'500/1': { id: 1, name: 'jQuery', status: 'draft' },
|
||||||
|
'500/2': { id: 2, name: 'AlphaPro', status: 'draft' },
|
||||||
|
'500/3': { id: 3, name: 'Vue', status: 'draft' },
|
||||||
|
'500/9': { id: 9, name: 'Mootools', status: 'draft' }, // New item
|
||||||
|
} );
|
||||||
|
|
||||||
|
const resourceName = getRequestIdentifier(
|
||||||
|
CRUD_ACTIONS.CREATE_ITEM,
|
||||||
|
'500/9',
|
||||||
|
query
|
||||||
|
);
|
||||||
|
|
||||||
|
expect( state.requesting[ resourceName ] ).toEqual( false );
|
||||||
|
|
||||||
|
expect( state.items[ getItemsQueryId ] ).toBeDefined();
|
||||||
|
expect( state.items[ getItemsQueryId ].data ).toEqual( [
|
||||||
|
'500/2',
|
||||||
|
'500/1',
|
||||||
|
'500/9',
|
||||||
|
'500/3',
|
||||||
|
] );
|
||||||
|
|
||||||
|
expect( state.itemsCount[ getItemsCountQueryId ] ).toEqual( 4 );
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@ type WithRequiredProperty< Type, Key extends keyof Type > = Type & {
|
||||||
|
|
||||||
export type CrudActionOptions = {
|
export type CrudActionOptions = {
|
||||||
optimisticQueryUpdate?: ItemQuery;
|
optimisticQueryUpdate?: ItemQuery;
|
||||||
|
optimisticUrlParameters?: IdType[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CrudActions<
|
export type CrudActions<
|
||||||
|
|
|
@ -96,6 +96,26 @@ export const organizeItemsById = (
|
||||||
return { objItems, ids };
|
return { objItems, ids };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the input data object, returning a new object that contains only the keys
|
||||||
|
* specified in the keys array.
|
||||||
|
*
|
||||||
|
* @param {Record<string, unknown>} data - The original data object to filter.
|
||||||
|
* @param {IdType[]} keys - An array of keys that should be included in the returned object.
|
||||||
|
* @return {Record<string, unknown>} A new object containing only the specified keys.
|
||||||
|
*/
|
||||||
|
export function filterDataByKeys(
|
||||||
|
data: Record< string, unknown >,
|
||||||
|
keys: IdType[]
|
||||||
|
): Record< string, unknown > {
|
||||||
|
return keys.reduce( ( acc: Record< string, unknown >, key ) => {
|
||||||
|
if ( data[ key ] ) {
|
||||||
|
acc[ key ] = data[ key ];
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {} );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse an ID query into a ID string.
|
* Parse an ID query into a ID string.
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in New Issue