diff --git a/packages/common/src/services/__tests__/grid.service.spec.ts b/packages/common/src/services/__tests__/grid.service.spec.ts index b223a303e..28591ec03 100644 --- a/packages/common/src/services/__tests__/grid.service.spec.ts +++ b/packages/common/src/services/__tests__/grid.service.spec.ts @@ -1,3 +1,4 @@ +import 'jest-extended'; import { BasePubSubService } from '@slickgrid-universal/event-pub-sub'; import { FilterService, GridService, GridStateService, PaginationService, SharedService, SortService, TreeDataService } from '../index'; @@ -373,6 +374,7 @@ describe('Grid Service', () => { const mockItem = { id: 0, user: { firstName: 'John', lastName: 'Doe' } }; const getRowIdSpy = jest.spyOn(dataviewStub, 'getRowById').mockReturnValue(mockItem.id); const getRowIndexSpy = jest.spyOn(dataviewStub, 'getIdxById').mockReturnValue(mockItem.id); + const serviceHighlightSpy = jest.spyOn(service, 'highlightRow'); const updateSpy = jest.spyOn(service, 'updateItemById'); const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish'); @@ -381,10 +383,30 @@ describe('Grid Service', () => { expect(updateSpy).toHaveBeenCalledTimes(1); expect(getRowIdSpy).toHaveBeenCalledWith(0); expect(getRowIndexSpy).toHaveBeenCalledWith(0); + expect(serviceHighlightSpy).toHaveBeenCalled(); expect(updateSpy).toHaveBeenCalledWith(mockItem.id, mockItem, { highlightRow: true, selectRow: false, scrollRowIntoView: false, skipError: false, triggerEvent: true }); expect(pubSubSpy).toHaveBeenLastCalledWith(`onItemUpdated`, mockItem); }); + it('should be able to update an item that exist in the dataview even when it is not showing in the grid (filtered from the grid) but will not highlight/selectRow since it is not in showing in the grid', () => { + const mockItem = { id: 0, user: { firstName: 'John', lastName: 'Doe' } }; + const getRowIdSpy = jest.spyOn(dataviewStub, 'getRowById').mockReturnValue(undefined); + const getRowIndexSpy = jest.spyOn(dataviewStub, 'getIdxById').mockReturnValue(mockItem.id); + const serviceHighlightSpy = jest.spyOn(service, 'highlightRow'); + const updateRowSpy = jest.spyOn(gridStub, 'updateRow'); + const selectSpy = jest.spyOn(service, 'setSelectedRows'); + const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish'); + + service.updateItemById(0, mockItem, { highlightRow: true, selectRow: true }); + + expect(getRowIdSpy).toHaveBeenCalledWith(0); + expect(getRowIndexSpy).toHaveBeenCalledWith(0); + expect(serviceHighlightSpy).not.toHaveBeenCalled(); + expect(updateRowSpy).not.toHaveBeenCalled(); + expect(selectSpy).not.toHaveBeenCalled(); + expect(pubSubSpy).toHaveBeenLastCalledWith(`onItemUpdated`, mockItem); + }); + it('should expect the service to call the "updateItemById" when calling "updateItem" and setting the "selecRow" flag and the grid option "enableRowSelection" is set', () => { const mockItem = { id: 0, user: { firstName: 'John', lastName: 'Doe' } }; const getRowIdSpy = jest.spyOn(dataviewStub, 'getRowById').mockReturnValue(mockItem.id); @@ -469,7 +491,7 @@ describe('Grid Service', () => { expect(pubSubSpy).toHaveBeenLastCalledWith(`onItemUpdated`, mockItem); }); - it('should expect the service to call the DataView "insertItem" when calling "addItem" with an item that has an Id defined by the "datasetIdPropertyName" property', () => { + it('should expect the service to call the DataView "updateItem" when calling "addItem" with an item that has an Id defined by the "datasetIdPropertyName" property', () => { jest.spyOn(gridStub, 'getOptions').mockReturnValue({ ...mockGridOptions, datasetIdPropertyName: 'customId' }); const mockItem = { customId: 0, user: { firstName: 'John', lastName: 'Doe' } }; const getRowIdSpy = jest.spyOn(dataviewStub, 'getRowById').mockReturnValue(mockItem.customId); @@ -494,15 +516,14 @@ describe('Grid Service', () => { expect(() => service.updateItemById(undefined as any, mockItem)).toThrowError('Cannot update a row without a valid "id"'); }); - it('should NOT throw an error when "skipError" is enabled even when calling "updateItemById" without a valid "id"', () => { - const mockItem = { id: 0, user: { firstName: 'John', lastName: 'Doe' } }; - expect(() => service.updateItemById(undefined as any, mockItem, { skipError: true })).not.toThrowError('Cannot update a row without a valid "id"'); + it('should throw an error when calling "updateItemById" with an invalid/undefined item', () => { + jest.spyOn(dataviewStub, 'getRowById').mockReturnValue(undefined as any); + expect(() => service.updateItemById(5, undefined)).toThrowError('The item to update in the grid was not found with id: 5'); }); - it('should throw an error when calling "updateItemById" and not finding the item in the grid', () => { + it('should NOT throw an error when "skipError" is enabled even when calling "updateItemById" without a valid "id"', () => { const mockItem = { id: 0, user: { firstName: 'John', lastName: 'Doe' } }; - jest.spyOn(dataviewStub, 'getRowById').mockReturnValue(undefined as any); - expect(() => service.updateItemById(5, mockItem)).toThrowError('The item to update in the grid was not found with id: 5'); + expect(() => service.updateItemById(undefined as any, mockItem, { skipError: true })).not.toThrowError('Cannot update a row without a valid "id"'); }); it('should NOT throw an error when "skipError" is enabled even when calling "updateItemById" and not finding the item in the grid', () => { @@ -1237,7 +1258,7 @@ describe('Grid Service', () => { const clearPinningSpy = jest.spyOn(service, 'clearPinning'); jest.spyOn(SharedService.prototype, 'slickGrid', 'get').mockReturnValue(gridStub); - service.setPinning(mockPinning); + service.setPinning(mockPinning as any); expect(clearPinningSpy).toHaveBeenCalled(); }); @@ -1745,7 +1766,7 @@ describe('Grid Service', () => { service.highlightRow(0, 10, 15); jest.runAllTimers(); // fast-forward timer - expect(updateSpy).toHaveBeenCalledWith(0, mockItem) + expect(updateSpy).toHaveBeenCalledWith(0, mockItem); expect(renderSpy).toHaveBeenCalledTimes(3); }); }); diff --git a/packages/common/src/services/grid.service.ts b/packages/common/src/services/grid.service.ts index cc258c89e..9cbd06d69 100644 --- a/packages/common/src/services/grid.service.ts +++ b/packages/common/src/services/grid.service.ts @@ -647,7 +647,7 @@ export class GridService { * @param options: provide the possibility to do certain actions after or during the upsert (highlightRow, selectRow, triggerEvent) * @return grid row index */ - updateItem(item: T, options?: GridServiceUpdateOption): number { + updateItem(item: T, options?: GridServiceUpdateOption): number | undefined { options = { ...GridServiceUpdateOptionDefaults, ...options }; const idPropName = this._gridOptions.datasetIdPropertyName || 'id'; const itemId = (!item || !(idPropName in item)) ? undefined : (item as any)[idPropName]; @@ -665,7 +665,7 @@ export class GridService { * @param options: provide the possibility to do certain actions after or during the update (highlightRow, selectRow, triggerEvent) * @return grid row indexes */ - updateItems(items: T | T[], options?: GridServiceUpdateOption): number[] { + updateItems(items: T | T[], options?: GridServiceUpdateOption): Array { options = { ...GridServiceUpdateOptionDefaults, ...options }; const idPropName = this._gridOptions.datasetIdPropertyName || 'id'; @@ -731,7 +731,7 @@ export class GridService { * @param options: provide the possibility to do certain actions after or during the upsert (highlightRow, selectRow, triggerEvent) * @return grid row number */ - updateItemById(itemId: number | string, item: T, options?: GridServiceUpdateOption): number { + updateItemById(itemId: number | string, item: T, options?: GridServiceUpdateOption): number | undefined { options = { ...GridServiceUpdateOptionDefaults, ...options }; if (!options?.skipError && itemId === undefined) { throw new Error(`Cannot update a row without a valid "id"`); @@ -739,14 +739,16 @@ export class GridService { const rowNumber = this._dataView.getRowById(itemId) as number; // when using pagination the item to update might not be on current page, so we bypass this condition - if (!options?.skipError && ((!item || rowNumber === undefined) && !this._gridOptions.enablePagination)) { + if (!options?.skipError && (!item && !this._gridOptions.enablePagination)) { throw new Error(`The item to update in the grid was not found with id: ${itemId}`); } if (this._dataView.getIdxById(itemId) !== undefined) { // Update the item itself inside the dataView this._dataView.updateItem(itemId, item); - this._grid.updateRow(rowNumber); + if (rowNumber !== undefined) { + this._grid.updateRow(rowNumber); + } if (this._gridOptions?.enableTreeData) { // if we add/remove item(s) from the dataset, we need to also refresh our tree data filters @@ -754,17 +756,17 @@ export class GridService { } // do we want to scroll to the row so that it shows in the Viewport (UI) - if (options.scrollRowIntoView) { + if (options.scrollRowIntoView && rowNumber !== undefined) { this._grid.scrollRowIntoView(rowNumber); } // highlight the row we just updated, if defined - if (options.highlightRow) { + if (options.highlightRow && rowNumber !== undefined) { this.highlightRow(rowNumber); } // select the row in the grid - if (options.selectRow && this._gridOptions && (this._gridOptions.enableCheckboxSelector || this._gridOptions.enableRowSelection)) { + if (rowNumber !== undefined && options.selectRow && this._gridOptions && (this._gridOptions.enableCheckboxSelector || this._gridOptions.enableRowSelection)) { this.setSelectedRow(rowNumber); } @@ -781,7 +783,7 @@ export class GridService { * @param item object which must contain a unique "id" property and any other suitable properties * @param options: provide the possibility to do certain actions after or during the upsert (highlightRow, resortGrid, selectRow, triggerEvent) */ - upsertItem(item: T, options?: GridServiceInsertOption): { added: number | undefined, updated: number | undefined } { + upsertItem(item: T, options?: GridServiceInsertOption): { added: number | undefined; updated: number | undefined; } { options = { ...GridServiceInsertOptionDefaults, ...options }; const idPropName = this._gridOptions.datasetIdPropertyName || 'id'; const itemId = (!item || !(idPropName in item)) ? undefined : (item as any)[idPropName]; @@ -799,7 +801,7 @@ export class GridService { * @param options: provide the possibility to do certain actions after or during the upsert (highlightRow, resortGrid, selectRow, triggerEvent) * @return row numbers in the grid */ - upsertItems(items: T | T[], options?: GridServiceInsertOption): { added: number | undefined, updated: number | undefined }[] { + upsertItems(items: T | T[], options?: GridServiceInsertOption): { added: number | undefined; updated: number | undefined; }[] { options = { ...GridServiceInsertOptionDefaults, ...options }; // when it's not an array, we can call directly the single item upsert if (!Array.isArray(items)) { @@ -809,7 +811,7 @@ export class GridService { // begin bulk transaction this._dataView.beginUpdate(true); - const upsertedRows: { added: number | undefined, updated: number | undefined }[] = []; + const upsertedRows: { added: number | undefined, updated: number | undefined; }[] = []; items.forEach((item: T) => { upsertedRows.push(this.upsertItem(item, { ...options, highlightRow: false, resortGrid: false, selectRow: false, triggerEvent: false })); }); @@ -852,7 +854,7 @@ export class GridService { * @param options: provide the possibility to do certain actions after or during the upsert (highlightRow, resortGrid, selectRow, triggerEvent) * @return grid row number in the grid */ - upsertItemById(itemId: number | string, item: T, options?: GridServiceInsertOption): { added: number | undefined, updated: number | undefined } { + upsertItemById(itemId: number | string, item: T, options?: GridServiceInsertOption): { added: number | undefined; updated: number | undefined; } { let isItemAdded = false; options = { ...GridServiceInsertOptionDefaults, ...options }; if (!options?.skipError && (itemId === undefined && !this.hasRowSelectionEnabled())) {