Skip to content

Commit

Permalink
feat(services): add bulk transactions in Grid Service CRUD methods (#256
Browse files Browse the repository at this point in the history
)

* feat(services): add bulk transactions in Grid Service CRUD methods
- ref SlickGrid new bulk transaction [PR](6pac/SlickGrid#572)
  • Loading branch information
ghiscoding authored Feb 2, 2021
1 parent d835468 commit 03385d9
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 151 deletions.
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
*.md
*.zip
*.spec.ts
**/**/*.json
**/**/*.js
**/__tests__/*.*
**/dist/**/*.*
**/packages/**/*.js
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"ignorePatterns": [
"**/dist/**/*.*",
"**/packages/**/*.js",
"**/**/*.json",
"**/__tests__/*.ts"
],
"rules": {
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@
"devDependencies": {
"@types/jest": "^26.0.20",
"@types/node": "^14.14.22",
"@typescript-eslint/eslint-plugin": "^4.14.1",
"@typescript-eslint/parser": "^4.14.1",
"cypress": "^6.3.0",
"@typescript-eslint/eslint-plugin": "^4.14.2",
"@typescript-eslint/parser": "^4.14.2",
"cypress": "^6.4.0",
"eslint": "^7.19.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-prefer-arrow": "^1.2.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
"lodash.isequal": "^4.5.0",
"moment-mini": "^2.24.0",
"multiple-select-modified": "^1.3.10",
"slickgrid": "^2.4.32"
"slickgrid": "^2.4.33"
},
"devDependencies": {
"@types/dompurify": "^2.2.1",
Expand Down
49 changes: 32 additions & 17 deletions packages/common/src/interfaces/slickDataView.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@ export interface SlickDataView {
// --
// Available Methods

/** Add an item to the dataset */
/** Add an item to the DataView */
addItem(item: any): void;

/** Begin Data Update Transaction */
beginUpdate(): void;
/** Add multiple items to the DataView */
addItems(items: any[]): void;

/**
* Begin Data Update Transaction
* @param {Boolean} isBulkUpdate - are we doing a bulk update transactions? Defaults to false
*/
beginUpdate(isBulkUpdate?: boolean): void;

/** Destroy (dispose) of Slick DataView */
destroy(): void;
Expand All @@ -26,9 +32,12 @@ export interface SlickDataView {
*/
collapseGroup(...args: any): void;

/** Delete an item from the dataset */
/** Delete an item from the DataView identified by its id */
deleteItem(id: string | number): void;

/** Delete multiple items from the DataView identified by their given ids */
deleteItems(ids: Array<string | number>): void;

/** End Data Update Transaction */
endUpdate(): void;

Expand Down Expand Up @@ -59,7 +68,7 @@ export interface SlickDataView {
/** Get current Filter used by the DataView */
getFilter(): any;

/** Get only the dataset filtered items */
/** Get only the DataView filtered items */
getFilteredItems: <T = any>() => T[];

/** Get current Grouping info */
Expand All @@ -71,22 +80,22 @@ export interface SlickDataView {
/** Get the DataView Id property name to use (defaults to "Id" but could be customized to something else when instantiating the DataView) */
getIdPropertyName(): string;

/** Get all Dataset Items */
/** Get all DataView Items */
getItems: <T = any>() => T[];

/** Get dataset item at specific index */
/** Get DataView item at specific index */
getItem: <T = any>(index: number) => T;

/** Get an item in the dataset by its Id */
/** Get an item in the DataView by its Id */
getItemById: <T = any>(id: string | number) => T;

/** Get an item in the dataset by its row index */
/** Get an item in the DataView by its row index */
getItemByIdx: <T = any>(idx: number) => T;

/** Get row index in the dataset by its Id */
/** Get row index in the DataView by its Id */
getIdxById(id: string | number): number | undefined;

/** Get item count, full dataset length */
/** Get item count, full dataset length that is defined in the DataView */
getItemCount(): number;

/** Get item metadata at specific index */
Expand All @@ -98,10 +107,10 @@ export interface SlickDataView {
/** Get Paging Options */
getPagingInfo(): PagingInfo;

/** Get row number of an item in the dataset */
/** Get row number of an item in the DataView */
getRowByItem(item: any): number | undefined;

/** Get row number of an item in the dataset by its Id */
/** Get row number of an item in the DataView by its Id */
getRowById(id: string | number): number | undefined;

/**
Expand All @@ -116,9 +125,12 @@ export interface SlickDataView {
*/
getAllSelectedItems<T = any>(): T[];

/** Insert an item to the dataset before a specific index */
/** Insert an item to the DataView before a specific index */
insertItem(insertBefore: number, item: any): void;

/** Insert multiple items to the DataView before a specific index */
insertItems(insertBefore: number, items: any[]): void;

/** From the items array provided, return the mapped rows */
mapItemsToRows(items: any[]): number[];

Expand Down Expand Up @@ -190,9 +202,12 @@ export interface SlickDataView {
/** Update a specific Index */
updateIdxById(startingIndex: number): void;

/** Update an item in the dataset by its Id */
/** Update an item in the DataView identified by its Id */
updateItem<T = any>(id: string | number, item: T): void;

/** Update multiple items in the DataView identified by their Ids */
updateItems<T = any>(id: Array<string | number>, items: T[]): void;

// ---------------------------
// Available DataView Events
// ---------------------------
Expand All @@ -209,13 +224,13 @@ export interface SlickDataView {
/** Event triggered while Paging Info is getting changed */
onPagingInfoChanged: SlickEvent<PagingInfo>;

/** Event triggered when the dataset row count changes */
/** Event triggered when the DataView row count changes */
onRowCountChanged: SlickEvent<OnRowCountChangedEventArgs>;

/** Event triggered when any of the row got changed */
onRowsChanged: SlickEvent<OnRowsChangedEventArgs>;

/** Event triggered when the dataset row count changes OR any of the row got changed */
/** Event triggered when the DataView row count changes OR any of the row got changed */
onRowsOrCountChanged: SlickEvent<OnRowsOrCountChangedEventArgs>;

/** Event triggered when "setItems" function is called */
Expand Down
88 changes: 31 additions & 57 deletions packages/common/src/services/__tests__/grid.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,19 @@ const sortServiceStub = {

const dataviewStub = {
addItem: jest.fn(),
addItems: jest.fn(),
beginUpdate: jest.fn(),
endUpdate: jest.fn(),
deleteItem: jest.fn(),
deleteItems: jest.fn(),
getIdxById: jest.fn(),
getItem: jest.fn(),
getRowById: jest.fn(),
insertItem: jest.fn(),
insertItems: jest.fn(),
reSort: jest.fn(),
updateItem: jest.fn(),
updateItems: jest.fn(),
} as unknown as SlickDataView;

const gridStateServiceStub = {
Expand Down Expand Up @@ -370,29 +374,6 @@ describe('Grid Service', () => {
expect(pubSubSpy).toHaveBeenLastCalledWith(`onItemUpdated`, mockItem);
});

it('should expect the service to call the "updateItemById" multiple times when calling "updateItems" with an array of items', () => {
const mockItems = [{ id: 0, user: { firstName: 'John', lastName: 'Doe' } }, { id: 5, user: { firstName: 'Jane', lastName: 'Doe' } }];
const getRowIdSpy = jest.spyOn(dataviewStub, 'getRowById').mockReturnValue(0).mockReturnValueOnce(0).mockReturnValueOnce(1);
const getRowIndexSpy = jest.spyOn(dataviewStub, 'getIdxById').mockReturnValue(0).mockReturnValueOnce(0).mockReturnValueOnce(1);
const serviceUpdateSpy = jest.spyOn(service, 'updateItemById');
const serviceHighlightSpy = jest.spyOn(service, 'highlightRow');
const beginUpdateSpy = jest.spyOn(dataviewStub, 'beginUpdate');
const endUpdateSpy = jest.spyOn(dataviewStub, 'endUpdate');
const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish');

service.updateItems(mockItems);

expect(beginUpdateSpy).toHaveBeenCalled();
expect(endUpdateSpy).toHaveBeenCalled();
expect(getRowIdSpy).toHaveBeenCalledTimes(2);
expect(getRowIndexSpy).toHaveBeenCalledTimes(2);
expect(serviceUpdateSpy).toHaveBeenCalledTimes(2);
expect(serviceUpdateSpy).toHaveBeenNthCalledWith(1, mockItems[0].id, mockItems[0], { highlightRow: false, selectRow: false, scrollRowIntoView: false, triggerEvent: false });
expect(serviceUpdateSpy).toHaveBeenNthCalledWith(2, mockItems[1].id, mockItems[1], { highlightRow: false, selectRow: false, scrollRowIntoView: false, triggerEvent: false });
expect(serviceHighlightSpy).not.toHaveBeenCalled();
expect(pubSubSpy).toHaveBeenLastCalledWith(`onItemUpdated`, mockItems);
});

it('should expect the service to call the "updateItem" when calling "updateItems" with a single item which is not an array', () => {
const mockItem = { id: 0, user: { firstName: 'John', lastName: 'Doe' } };
const getRowIdSpy = jest.spyOn(dataviewStub, 'getRowById');
Expand All @@ -418,7 +399,7 @@ describe('Grid Service', () => {
it('should expect the row to be selected when calling "updateItems" with an item when setting the "selecRow" flag and the grid option "enableRowSelection" is set', () => {
const mockItem = { id: 0, user: { firstName: 'John', lastName: 'Doe' } };
jest.spyOn(gridStub, 'getOptions').mockReturnValue({ enableAutoResize: true, enableRowSelection: true } as GridOption);
const updateSpy = jest.spyOn(service, 'updateItem');
const updateSpy = jest.spyOn(dataviewStub, 'updateItems');
const selectSpy = jest.spyOn(service, 'setSelectedRows');
const serviceHighlightSpy = jest.spyOn(service, 'highlightRow');
const beginUpdateSpy = jest.spyOn(dataviewStub, 'beginUpdate');
Expand All @@ -430,7 +411,7 @@ describe('Grid Service', () => {
expect(beginUpdateSpy).toHaveBeenCalled();
expect(endUpdateSpy).toHaveBeenCalled();
expect(updateSpy).toHaveBeenCalledTimes(1);
expect(updateSpy).toHaveBeenCalledWith(mockItem, { highlightRow: false, selectRow: false, scrollRowIntoView: false, triggerEvent: false });
expect(updateSpy).toHaveBeenCalledWith([0], [mockItem]);
expect(selectSpy).toHaveBeenCalledWith([0]);
expect(serviceHighlightSpy).toHaveBeenCalledWith([0]);
expect(pubSubSpy).toHaveBeenCalledWith(`onItemUpdated`, [mockItem]);
Expand Down Expand Up @@ -645,10 +626,10 @@ describe('Grid Service', () => {
jest.spyOn(gridStub, 'getOptions').mockReturnValue(mockGridOptions);
});

it('should expect the service to call the "addItem" multiple times when calling "addItems" with an array of items', () => {
it('should expect to call the DataView "insertItems" once when calling the service "addItems" with an array of items and no position is provided (defaults to insert "top")', () => {
const mockItems = [{ id: 0, user: { firstName: 'John', lastName: 'Doe' } }, { id: 5, user: { firstName: 'Jane', lastName: 'Doe' } }];
jest.spyOn(dataviewStub, 'getRowById').mockReturnValue(0).mockReturnValueOnce(0).mockReturnValueOnce(1).mockReturnValueOnce(1);
const serviceAddSpy = jest.spyOn(service, 'addItem');
jest.spyOn(dataviewStub, 'getRowById').mockReturnValueOnce(0).mockReturnValueOnce(1);
const insertItemsSpy = jest.spyOn(dataviewStub, 'insertItems');
const serviceHighlightSpy = jest.spyOn(service, 'highlightRow');
const beginUpdateSpy = jest.spyOn(dataviewStub, 'beginUpdate');
const endUpdateSpy = jest.spyOn(dataviewStub, 'endUpdate');
Expand All @@ -658,22 +639,21 @@ describe('Grid Service', () => {

expect(beginUpdateSpy).toHaveBeenCalled();
expect(endUpdateSpy).toHaveBeenCalled();
expect(serviceAddSpy).toHaveBeenCalledTimes(2);
expect(serviceAddSpy).toHaveBeenNthCalledWith(1, mockItems[0], { highlightRow: false, position: 'top', resortGrid: false, selectRow: false, triggerEvent: false });
expect(serviceAddSpy).toHaveBeenNthCalledWith(2, mockItems[1], { highlightRow: false, position: 'top', resortGrid: false, selectRow: false, triggerEvent: false });
expect(insertItemsSpy).toHaveBeenCalledTimes(1);
expect(insertItemsSpy).toHaveBeenCalledWith(0, [mockItems[0], mockItems[1]]);
expect(serviceHighlightSpy).toHaveBeenCalledTimes(1);
expect(serviceHighlightSpy).toHaveBeenCalledWith([0, 1]);
expect(pubSubSpy).toHaveBeenLastCalledWith(`onItemAdded`, mockItems);
});

it('should expect the service to call the "addItem" multiple times when calling "addItems" with an array of items and the option "position" set to "bottom"', () => {
it('should expect to call the DataView "addItems" once when calling the service "addItems" with an array of items and the option "position" set to "bottom"', () => {
const expectationNewRowPosition1 = 1000;
const expectationNewRowPosition2 = 1001;
const mockItems = [{ id: 0, user: { firstName: 'John', lastName: 'Doe' } }, { id: 5, user: { firstName: 'Jane', lastName: 'Doe' } }];
jest.spyOn(dataviewStub, 'getRowById')
.mockReturnValueOnce(expectationNewRowPosition1).mockReturnValueOnce(expectationNewRowPosition1)
.mockReturnValueOnce(expectationNewRowPosition2).mockReturnValueOnce(expectationNewRowPosition2);
const serviceAddSpy = jest.spyOn(service, 'addItem');
.mockReturnValueOnce(expectationNewRowPosition1)
.mockReturnValueOnce(expectationNewRowPosition2);
const addItemsSpy = jest.spyOn(dataviewStub, 'addItems');
const serviceHighlightSpy = jest.spyOn(service, 'highlightRow');
const beginUpdateSpy = jest.spyOn(dataviewStub, 'beginUpdate');
const endUpdateSpy = jest.spyOn(dataviewStub, 'endUpdate');
Expand All @@ -683,9 +663,8 @@ describe('Grid Service', () => {

expect(beginUpdateSpy).toHaveBeenCalled();
expect(endUpdateSpy).toHaveBeenCalled();
expect(serviceAddSpy).toHaveBeenCalledTimes(2);
expect(serviceAddSpy).toHaveBeenNthCalledWith(1, mockItems[0], { highlightRow: false, position: 'bottom', resortGrid: false, selectRow: false, triggerEvent: false });
expect(serviceAddSpy).toHaveBeenNthCalledWith(2, mockItems[1], { highlightRow: false, position: 'bottom', resortGrid: false, selectRow: false, triggerEvent: false });
expect(addItemsSpy).toHaveBeenCalledTimes(1);
expect(addItemsSpy).toHaveBeenCalledWith([mockItems[0], mockItems[1]]);
expect(serviceHighlightSpy).toHaveBeenCalledTimes(1);
expect(serviceHighlightSpy).toHaveBeenCalledWith([expectationNewRowPosition1, expectationNewRowPosition2]);
expect(pubSubSpy).toHaveBeenLastCalledWith(`onItemAdded`, mockItems);
Expand Down Expand Up @@ -731,7 +710,7 @@ describe('Grid Service', () => {

it('should add a single item by calling "addItems" method and expect to call a grid resort & highlight but without triggering an event', () => {
const mockItems = [{ id: 0, user: { firstName: 'John', lastName: 'Doe' } }, { id: 5, user: { firstName: 'Jane', lastName: 'Doe' } }];
const serviceAddSpy = jest.spyOn(service, 'addItem');
const insertItemsSpy = jest.spyOn(dataviewStub, 'insertItems');
const serviceHighlightSpy = jest.spyOn(service, 'highlightRow');
const beginUpdateSpy = jest.spyOn(dataviewStub, 'beginUpdate');
const endUpdateSpy = jest.spyOn(dataviewStub, 'endUpdate');
Expand All @@ -743,10 +722,9 @@ describe('Grid Service', () => {

expect(beginUpdateSpy).toHaveBeenCalled();
expect(endUpdateSpy).toHaveBeenCalled();
expect(serviceAddSpy).toHaveBeenCalled();
expect(insertItemsSpy).toHaveBeenCalledTimes(1);
expect(resortSpy).toHaveBeenCalled();
expect(serviceAddSpy).toHaveBeenNthCalledWith(1, mockItems[0], { highlightRow: false, position: 'top', resortGrid: false, selectRow: false, triggerEvent: false });
expect(serviceAddSpy).toHaveBeenNthCalledWith(2, mockItems[1], { highlightRow: false, position: 'top', resortGrid: false, selectRow: false, triggerEvent: false });
expect(insertItemsSpy).toHaveBeenCalledWith(0, [mockItems[0], mockItems[1]]);
expect(serviceHighlightSpy).toHaveBeenCalledTimes(1);
expect(getRowByIdSpy).toHaveBeenCalledTimes(2);
expect(pubSubSpy).not.toHaveBeenLastCalledWith(`onItemAdded`);
Expand All @@ -756,7 +734,7 @@ describe('Grid Service', () => {
const mockItem = { id: 0, user: { firstName: 'John', lastName: 'Doe' } };
jest.spyOn(dataviewStub, 'getRowById').mockReturnValue(0);
jest.spyOn(gridStub, 'getOptions').mockReturnValue({ enableAutoResize: true, enableRowSelection: true } as GridOption);
const addSpy = jest.spyOn(dataviewStub, 'insertItem');
const insertSpy = jest.spyOn(dataviewStub, 'insertItems');
const beginUpdateSpy = jest.spyOn(dataviewStub, 'beginUpdate');
const endUpdateSpy = jest.spyOn(dataviewStub, 'endUpdate');
const selectSpy = jest.spyOn(service, 'setSelectedRows');
Expand All @@ -766,8 +744,8 @@ describe('Grid Service', () => {

expect(beginUpdateSpy).toHaveBeenCalled();
expect(endUpdateSpy).toHaveBeenCalled();
expect(addSpy).toHaveBeenCalledTimes(1);
expect(addSpy).toHaveBeenCalledWith(0, mockItem);
expect(insertSpy).toHaveBeenCalledTimes(1);
expect(insertSpy).toHaveBeenCalledWith(0, [mockItem]);
expect(selectSpy).toHaveBeenCalledWith([0]);
expect(pubSubSpy).toHaveBeenLastCalledWith(`onItemAdded`, [mockItem]);
});
Expand Down Expand Up @@ -885,9 +863,9 @@ describe('Grid Service', () => {
expect(selectionSpy).toHaveBeenCalledWith([]);
});

it('should expect the service to call the "deleteItem" multiple times when calling "deleteItems" with an array of items', () => {
it('should expect the service to call the DataView "deleteItems" once with array of item Ids when calling "deleteItems" with an array of items', () => {
const mockItems = [{ id: 0, user: { firstName: 'John', lastName: 'Doe' } }, { id: 5, user: { firstName: 'Jane', lastName: 'Doe' } }];
const serviceDeleteSpy = jest.spyOn(service, 'deleteItem');
const deleteItemsSpy = jest.spyOn(dataviewStub, 'deleteItems');
const beginUpdateSpy = jest.spyOn(dataviewStub, 'beginUpdate');
const endUpdateSpy = jest.spyOn(dataviewStub, 'endUpdate');
const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish');
Expand All @@ -897,9 +875,8 @@ describe('Grid Service', () => {
expect(beginUpdateSpy).toHaveBeenCalled();
expect(endUpdateSpy).toHaveBeenCalled();
expect(output).toEqual([0, 5]);
expect(serviceDeleteSpy).toHaveBeenCalledTimes(2);
expect(serviceDeleteSpy).toHaveBeenNthCalledWith(1, mockItems[0], { triggerEvent: false });
expect(serviceDeleteSpy).toHaveBeenNthCalledWith(2, mockItems[1], { triggerEvent: false });
expect(deleteItemsSpy).toHaveBeenCalledTimes(1);
expect(deleteItemsSpy).toHaveBeenCalledWith([0, 5]);
expect(pubSubSpy).toHaveBeenLastCalledWith(`onItemDeleted`, mockItems);
});

Expand Down Expand Up @@ -937,10 +914,9 @@ describe('Grid Service', () => {
expect(pubSubSpy).not.toHaveBeenLastCalledWith(`onItemDeleted`);
});

it('should delete a single item by calling "deleteItems" method and expect to trigger a single an event', () => {
it('should delete multiple items by calling "deleteItems" method and expect to trigger a single an event', () => {
const mockItems = [{ id: 0, user: { firstName: 'John', lastName: 'Doe' } }, { id: 5, user: { firstName: 'Jane', lastName: 'Doe' } }];
const serviceDeleteSpy = jest.spyOn(service, 'deleteItem');
const dataviewDeleteSpy = jest.spyOn(dataviewStub, 'deleteItem');
const deleteItemsSpy = jest.spyOn(dataviewStub, 'deleteItems');
const beginUpdateSpy = jest.spyOn(dataviewStub, 'beginUpdate');
const endUpdateSpy = jest.spyOn(dataviewStub, 'endUpdate');
const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish');
Expand All @@ -950,10 +926,8 @@ describe('Grid Service', () => {
expect(beginUpdateSpy).toHaveBeenCalled();
expect(endUpdateSpy).toHaveBeenCalled();
expect(output).toEqual([0, 5]);
expect(serviceDeleteSpy).toHaveBeenCalled();
expect(serviceDeleteSpy).toHaveBeenNthCalledWith(1, mockItems[0], { triggerEvent: false });
expect(serviceDeleteSpy).toHaveBeenNthCalledWith(2, mockItems[1], { triggerEvent: false });
expect(dataviewDeleteSpy).toHaveBeenCalledTimes(2);
expect(deleteItemsSpy).toHaveBeenCalledTimes(1);
expect(deleteItemsSpy).toHaveBeenCalledWith([0, 5]);
expect(pubSubSpy).toHaveBeenCalledTimes(1);
});

Expand Down
Loading

0 comments on commit 03385d9

Please sign in to comment.