Skip to content

Commit

Permalink
fix(gridService): upsertItem(s) should trigger onItemAdded/Updated event
Browse files Browse the repository at this point in the history
  • Loading branch information
ghiscoding-SE committed Oct 5, 2019
1 parent a150807 commit df9af21
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ describe('Grid Service', () => {
});

describe('upsertItem methods', () => {
jest.clearAllMocks();
afterEach(() => {
jest.clearAllMocks();
});

it('should throw an error when 1st argument for the item object is missing', () => {
expect(() => service.upsertItem(null)).toThrowError('Calling Upsert of an item requires the item to include an "id" property');
Expand All @@ -101,8 +103,9 @@ describe('Grid Service', () => {
const serviceSpy = jest.spyOn(service, 'addItem');
const rxSpy = jest.spyOn(service.onItemUpserted, 'next');

service.upsertItem(mockItem);
const upsertRow = service.upsertItem(mockItem);

expect(upsertRow).toEqual({ added: 0, updated: undefined });
expect(serviceSpy).toHaveBeenCalledTimes(1);
expect(dataviewSpy).toHaveBeenCalledWith(0);
expect(serviceSpy).toHaveBeenCalledWith(mockItem, { highlightRow: true, position: 'top', resortGrid: false, selectRow: false, triggerEvent: true });
Expand All @@ -117,45 +120,81 @@ describe('Grid Service', () => {
const scrollSpy = jest.spyOn(gridStub, 'scrollRowIntoView');
const rxSpy = jest.spyOn(service.onItemAdded, 'next');

service.upsertItem(mockItem, { position: 'bottom' });
const upsertRow = service.upsertItem(mockItem, { position: 'bottom' });

expect(upsertRow).toEqual({ added: 1000, updated: undefined });
expect(addSpy).toHaveBeenCalledTimes(1);
expect(addSpy).toHaveBeenCalledWith(mockItem);
expect(scrollSpy).toHaveBeenCalledWith(expectationNewRowPosition);
expect(rxSpy).toHaveBeenCalledWith(mockItem);
});

it('should expect the service to call the "updateItem" multiple times when calling "upsertItems" with the items not being found in the grid', () => {
it('should expect the service to call the "updateItem" multiple times when calling "upsertItems" with the items found in the grid', () => {
const mockItems = [{ id: 0, user: { firstName: 'John', lastName: 'Doe' } }, { id: 5, user: { firstName: 'Jane', lastName: 'Doe' } }];
const dataviewSpy = jest.spyOn(dataviewStub, 'getRowById').mockReturnValue(0).mockReturnValueOnce(0).mockReturnValueOnce(0).mockReturnValueOnce(1).mockReturnValueOnce(1);
const serviceUpsertSpy = jest.spyOn(service, 'upsertItem');
const serviceHighlightSpy = jest.spyOn(service, 'highlightRow');
const rxSpy = jest.spyOn(service.onItemUpserted, 'next');
const rxUpsertSpy = jest.spyOn(service.onItemUpserted, 'next');
const rxAddedSpy = jest.spyOn(service.onItemAdded, 'next');
const rxUpdatedSpy = jest.spyOn(service.onItemUpdated, 'next');

service.upsertItems(mockItems);
const upsertRows = service.upsertItems(mockItems);

expect(dataviewSpy).toHaveBeenCalledTimes(4); // called 4x times, 2x by the upsert itself and 2x by the addItem
expect(upsertRows).toEqual([{ added: undefined, updated: 0 }, { added: undefined, updated: 1 }]);
expect(dataviewSpy).toHaveBeenCalledTimes(4); // called 4x times, 2x by the upsert itself and 2x by the updateItem
expect(serviceUpsertSpy).toHaveBeenCalledTimes(2);
expect(serviceUpsertSpy).toHaveBeenNthCalledWith(1, mockItems[0], { highlightRow: false, position: 'top', resortGrid: false, selectRow: false, triggerEvent: false });
expect(serviceUpsertSpy).toHaveBeenNthCalledWith(2, mockItems[1], { highlightRow: false, position: 'top', resortGrid: false, selectRow: false, triggerEvent: false });
expect(serviceHighlightSpy).toHaveBeenCalledWith([0, 1]);
expect(rxSpy).toHaveBeenCalledWith(mockItems);
expect(rxUpsertSpy).toHaveBeenCalledWith(mockItems);
expect(rxAddedSpy).toHaveBeenCalledTimes(0);
expect(rxUpdatedSpy).toHaveBeenCalledTimes(1);
expect(rxUpdatedSpy).toHaveBeenCalledWith([{ added: undefined, updated: 0 }, { added: undefined, updated: 1 }]);
});

it('should expect the service to call both "addItem" and "updateItem" when calling "upsertItems" with first item found but second not found', () => {
const mockItems = [{ id: 0, user: { firstName: 'John', lastName: 'Doe' } }, { id: 5, user: { firstName: 'Jane', lastName: 'Doe' } }];
const dataviewSpy = jest.spyOn(dataviewStub, 'getRowById').mockReturnValue(undefined).mockReturnValueOnce(undefined).mockReturnValueOnce(15).mockReturnValueOnce(15);
const serviceUpsertSpy = jest.spyOn(service, 'upsertItem');
const serviceHighlightSpy = jest.spyOn(service, 'highlightRow');
const rxUpsertSpy = jest.spyOn(service.onItemUpserted, 'next');
const rxAddedSpy = jest.spyOn(service.onItemAdded, 'next');
const rxUpdatedSpy = jest.spyOn(service.onItemUpdated, 'next');

const upsertRows = service.upsertItems(mockItems);

expect(upsertRows).toEqual([{ added: 0, updated: undefined }, { added: undefined, updated: 15 }]);
expect(dataviewSpy).toHaveBeenCalledTimes(3); // called 4x times, 2x by the upsert itself and 2x by the updateItem
expect(serviceUpsertSpy).toHaveBeenCalledTimes(2);
expect(serviceUpsertSpy).toHaveBeenNthCalledWith(1, mockItems[0], { highlightRow: false, position: 'top', resortGrid: false, selectRow: false, triggerEvent: false });
expect(serviceUpsertSpy).toHaveBeenNthCalledWith(2, mockItems[1], { highlightRow: false, position: 'top', resortGrid: false, selectRow: false, triggerEvent: false });
expect(serviceHighlightSpy).toHaveBeenCalledWith([0, 15]);
expect(rxUpsertSpy).toHaveBeenCalledWith(mockItems);
expect(rxAddedSpy).toHaveBeenCalledTimes(1);
expect(rxUpdatedSpy).toHaveBeenCalledTimes(1);
expect(rxAddedSpy).toHaveBeenCalledWith([{ added: 0, updated: undefined }]);
expect(rxUpdatedSpy).toHaveBeenCalledWith([{ added: undefined, updated: 15 }]);
});

it('should expect the service to call the "upsertItem" when calling "upsertItems" with a single item which is not an array', () => {
it('should expect the service to call the "upsertItem" when calling "upsertItems" with a single item object and without triggering an event', () => {
const mockItem = { id: 0, user: { firstName: 'John', lastName: 'Doe' } };
const dataviewSpy = jest.spyOn(dataviewStub, 'getRowById');
const dataviewSpy = jest.spyOn(dataviewStub, 'getRowById').mockReturnValue(0).mockReturnValueOnce(0).mockReturnValueOnce(0).mockReturnValueOnce(1).mockReturnValueOnce(1);;
const serviceUpsertSpy = jest.spyOn(service, 'upsertItem');
const serviceHighlightSpy = jest.spyOn(service, 'highlightRow');
const rxSpy = jest.spyOn(service.onItemUpserted, 'next');
const rxUpsertSpy = jest.spyOn(service.onItemUpserted, 'next');
const rxAddedSpy = jest.spyOn(service.onItemAdded, 'next');
const rxUpdatedSpy = jest.spyOn(service.onItemUpdated, 'next');

service.upsertItems(mockItem, { highlightRow: false, resortGrid: true, selectRow: false, triggerEvent: false });
const upsertRows = service.upsertItems(mockItem, { highlightRow: false, resortGrid: true, selectRow: false, triggerEvent: false });

expect(upsertRows).toEqual([{ added: undefined, updated: 0 }]);
expect(dataviewSpy).toHaveBeenCalledTimes(2);
expect(serviceUpsertSpy).toHaveBeenCalledTimes(1);
expect(serviceUpsertSpy).toHaveBeenCalledWith(mockItem, { highlightRow: false, position: 'top', resortGrid: true, selectRow: false, triggerEvent: false });
expect(serviceHighlightSpy).not.toHaveBeenCalled();
expect(rxSpy).not.toHaveBeenCalled();
expect(rxUpsertSpy).not.toHaveBeenCalled();
expect(rxAddedSpy).toHaveBeenCalledTimes(0);
expect(rxUpdatedSpy).toHaveBeenCalledTimes(0);
});

it('should expect the row to be selected when calling "upsertItems" with an item when setting the "selecRow" flag and the grid option "enableRowSelection" is set', () => {
Expand Down
38 changes: 26 additions & 12 deletions src/app/modules/angular-slickgrid/services/grid.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,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: any, options?: GridServiceInsertOption): number {
upsertItem(item: any, options?: GridServiceInsertOption): { added: number, updated: number } {
options = { ...GridServiceInsertOptionDefaults, ...options };
const itemId = (!item || !item.hasOwnProperty('id')) ? undefined : item.id;

Expand All @@ -628,34 +628,43 @@ 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: any | any[], options?: GridServiceInsertOption): number[] {
upsertItems(items: any | any[], options?: GridServiceInsertOption): { added: number, updated: number }[] {
options = { ...GridServiceInsertOptionDefaults, ...options };
// when it's not an array, we can call directly the single item update
if (!Array.isArray(items)) {
return [this.upsertItem(items, options)];
}

const gridRowNumbers: number[] = [];
const upsertedRows: { added: number, updated: number }[] = [];
items.forEach((item: any) => {
gridRowNumbers.push(this.upsertItem(item, { ...options, highlightRow: false, resortGrid: false, selectRow: false, triggerEvent: false }));
upsertedRows.push(this.upsertItem(item, { ...options, highlightRow: false, resortGrid: false, selectRow: false, triggerEvent: false }));
});

// only highlight at the end, all at once
// we have to do this because doing highlight 1 by 1 would only re-select the last highlighted row which is wrong behavior
if (options.highlightRow) {
this.highlightRow(gridRowNumbers);
const rowNumbers = upsertedRows.map((upsertRow) => upsertRow.added !== undefined ? upsertRow.added : upsertRow.updated);
this.highlightRow(rowNumbers);
}

// select the row in the grid
if (options.selectRow && this._gridOptions && (this._gridOptions.enableCheckboxSelector || this._gridOptions.enableRowSelection)) {
this._grid.setSelectedRows(gridRowNumbers);
this._grid.setSelectedRows(upsertedRows);
}

// do we want to trigger an event after updating the item
if (options.triggerEvent) {
this.onItemUpserted.next(items);
const addedItems = upsertedRows.filter((upsertRow) => upsertRow.added !== undefined);
if (Array.isArray(addedItems) && addedItems.length > 0) {
this.onItemAdded.next(addedItems);
}
const updatedItems = upsertedRows.filter((upsertRow) => upsertRow.updated !== undefined);
if (Array.isArray(updatedItems) && updatedItems.length > 0) {
this.onItemUpdated.next(updatedItems);
}
}
return gridRowNumbers;
return upsertedRows;
}

/**
Expand All @@ -665,23 +674,28 @@ 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: any, options?: GridServiceInsertOption): number {
upsertItemById(itemId: number | string, item: any, options?: GridServiceInsertOption): { added: number, updated: number } {
let isItemAdded = false;
options = { ...GridServiceInsertOptionDefaults, ...options };
if (itemId === undefined) {
throw new Error(`Calling Upsert of an item requires the item to include a valid and unique "id" property`);
}

let rowNumber: number;
let rowNumberAdded: number;
let rowNumberUpdated: number;
if (this._dataView.getRowById(itemId) === undefined) {
rowNumber = this.addItem(item, options);
rowNumberAdded = this.addItem(item, options);
isItemAdded = true;
} else {
rowNumber = this.updateItem(item, { highlightRow: options.highlightRow, selectRow: options.selectRow, triggerEvent: options.triggerEvent });
rowNumberUpdated = this.updateItem(item, { highlightRow: options.highlightRow, selectRow: options.selectRow, triggerEvent: options.triggerEvent });
isItemAdded = false;
}

// do we want to trigger an event after updating the item
if (options.triggerEvent) {
this.onItemUpserted.next(item);
isItemAdded ? this.onItemAdded.next(item) : this.onItemUpdated.next(item);
}
return rowNumber;
return { added: rowNumberAdded, updated: rowNumberUpdated };
}
}

0 comments on commit df9af21

Please sign in to comment.