Skip to content
This repository was archived by the owner on Jun 1, 2025. It is now read-only.

Commit eb2ec1f

Browse files
Ghislain BeaulacGhislain Beaulac
authored andcommitted
(feat): add UpsertItem tests
1 parent 5b8b404 commit eb2ec1f

File tree

2 files changed

+214
-20
lines changed

2 files changed

+214
-20
lines changed
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import { TestBed } from '@angular/core/testing';
2+
import { TranslateService, TranslateModule } from '@ngx-translate/core';
3+
import { GridService, ExtensionService, FilterService, GridStateService, SortService } from '..';
4+
import { GridOption } from '../..';
5+
6+
declare var Slick: any;
7+
const HIGHLIGHT_TIMEOUT = 1500;
8+
9+
const mockSelectionModel = jest.fn().mockImplementation(() => ({
10+
init: jest.fn(),
11+
destroy: jest.fn()
12+
}));
13+
14+
jest.mock('slickgrid/plugins/slick.rowselectionmodel', () => mockSelectionModel);
15+
Slick.RowSelectionModel = mockSelectionModel;
16+
17+
let extensionServiceStub = {
18+
} as ExtensionService;
19+
20+
let filterServiceStub = {
21+
} as FilterService;
22+
23+
let gridStateServiceStub = {
24+
} as GridStateService;
25+
26+
let sortServiceStub = {
27+
} as SortService;
28+
29+
const dataviewStub = {
30+
getIdxById: jest.fn(),
31+
getItem: jest.fn(),
32+
getRowById: jest.fn(),
33+
insertItem: jest.fn(),
34+
reSort: jest.fn(),
35+
};
36+
37+
const gridStub = {
38+
getOptions: jest.fn(),
39+
getColumns: jest.fn(),
40+
getSelectionModel: jest.fn(),
41+
setSelectionModel: jest.fn(),
42+
setSelectedRows: jest.fn(),
43+
scrollRowIntoView: jest.fn(),
44+
};
45+
46+
describe('Grid Service', () => {
47+
let service: GridService;
48+
let translate: TranslateService;
49+
const gridSpy = jest.spyOn(gridStub, 'getOptions').mockReturnValue({ enableAutoResize: true } as GridOption);
50+
51+
beforeEach(() => {
52+
TestBed.configureTestingModule({
53+
providers: [
54+
{ provide: ExtensionService, useValue: extensionServiceStub },
55+
{ provide: FilterService, useValue: filterServiceStub },
56+
{ provide: GridStateService, useValue: gridStateServiceStub },
57+
{ provide: SortService, useValue: sortServiceStub },
58+
GridService,
59+
],
60+
imports: [TranslateModule.forRoot()]
61+
});
62+
translate = TestBed.get(TranslateService);
63+
service = TestBed.get(GridService);
64+
service.init(gridStub, dataviewStub);
65+
});
66+
67+
afterEach(() => {
68+
jest.clearAllMocks();
69+
});
70+
71+
it('should create the service', () => {
72+
expect(service).toBeTruthy();
73+
});
74+
75+
describe('upsertItem method', () => {
76+
it('should throw an error when 1st argument for the item object is missing', () => {
77+
expect(() => service.upsertItem(null)).toThrowError('Calling Upsert of an item requires the item to include an "id" property');
78+
});
79+
80+
it('should expect the service to call the "addItem" when calling "upsertItem" with the item not being found in the grid', () => {
81+
const mockItem = { id: 0, user: { firstName: 'John', lastName: 'Doe' } };
82+
const dataviewSpy = jest.spyOn(dataviewStub, 'getRowById').mockReturnValue(undefined);
83+
const serviceSpy = jest.spyOn(service, 'addItem');
84+
const rxOnUpsertSpy = jest.spyOn(service.onItemUpserted, 'next');
85+
86+
service.upsertItem(mockItem);
87+
88+
expect(serviceSpy).toHaveBeenCalledTimes(1);
89+
expect(dataviewSpy).toHaveBeenCalledWith(0);
90+
expect(serviceSpy).toHaveBeenCalledWith(mockItem, true, false, true);
91+
expect(rxOnUpsertSpy).toHaveBeenCalledWith(mockItem);
92+
});
93+
94+
it('should expect the service to call the "addItem" when calling "upsertItems" with the items not being found in the grid', () => {
95+
const mockItems = [{ id: 0, user: { firstName: 'John', lastName: 'Doe' } }, { id: 5, user: { firstName: 'Jane', lastName: 'Doe' } }];
96+
const dataviewSpy = jest.spyOn(dataviewStub, 'getRowById').mockReturnValue(0).mockReturnValueOnce(0).mockReturnValueOnce(0).mockReturnValueOnce(1).mockReturnValueOnce(1);
97+
const serviceUpsertSpy = jest.spyOn(service, 'upsertItem');
98+
const serviceHighlightSpy = jest.spyOn(service, 'highlightRow');
99+
const rxOnUpsertSpy = jest.spyOn(service.onItemUpserted, 'next');
100+
101+
service.upsertItems(mockItems);
102+
103+
expect(dataviewSpy).toHaveBeenCalledTimes(4); // called 4x times, 2x by the upsert itself and 2x by the addItem
104+
expect(serviceUpsertSpy).toHaveBeenCalledTimes(2);
105+
expect(serviceUpsertSpy).toHaveBeenNthCalledWith(1, mockItems[0], false, false, false);
106+
expect(serviceUpsertSpy).toHaveBeenNthCalledWith(2, mockItems[1], false, false, false);
107+
expect(serviceHighlightSpy).toHaveBeenCalledWith([0, 1]);
108+
expect(rxOnUpsertSpy).toHaveBeenCalledWith(mockItems);
109+
});
110+
111+
it('should expect the service to call the "upsertItem" when calling "upsertItems" with a single item which is not an array', () => {
112+
const mockItem = { id: 0, user: { firstName: 'John', lastName: 'Doe' } };
113+
const dataviewSpy = jest.spyOn(dataviewStub, 'getRowById');
114+
const serviceUpsertSpy = jest.spyOn(service, 'upsertItem');
115+
const serviceHighlightSpy = jest.spyOn(service, 'highlightRow');
116+
const rxOnUpsertSpy = jest.spyOn(service.onItemUpserted, 'next');
117+
118+
service.upsertItems(mockItem, false, true, false);
119+
120+
expect(dataviewSpy).toHaveBeenCalledTimes(2);
121+
expect(serviceUpsertSpy).toHaveBeenCalledTimes(1);
122+
expect(serviceUpsertSpy).toHaveBeenCalledWith(mockItem, false, true, false);
123+
expect(serviceHighlightSpy).not.toHaveBeenCalled();
124+
expect(rxOnUpsertSpy).not.toHaveBeenCalled();
125+
});
126+
127+
it('should call the "upsertItemById" method and expect it to call the "addItem"', () => {
128+
const mockItem = { id: 0, user: { firstName: 'John', lastName: 'Doe' } };
129+
expect(() => service.upsertItemById(undefined, mockItem)).toThrowError('Calling Upsert of an item requires the item to include a valid and unique "id" property');
130+
});
131+
132+
it('should call the "upsertItemById" method and expect it to call the "addItem" with default boolean flags', () => {
133+
const mockItem = { id: 0, user: { firstName: 'John', lastName: 'Doe' } };
134+
const dataviewSpy = jest.spyOn(dataviewStub, 'getRowById').mockReturnValue(undefined);
135+
const serviceAddItemSpy = jest.spyOn(service, 'addItem');
136+
const serviceHighlightSpy = jest.spyOn(service, 'highlightRow');
137+
const rxOnUpsertSpy = jest.spyOn(service.onItemUpserted, 'next');
138+
139+
service.upsertItemById(0, mockItem);
140+
141+
expect(dataviewSpy).toHaveBeenCalledWith(0);
142+
expect(serviceAddItemSpy).toHaveBeenCalled();
143+
expect(serviceAddItemSpy).toHaveBeenCalledWith(mockItem, true, false, true);
144+
expect(serviceHighlightSpy).toHaveBeenCalledWith(0, HIGHLIGHT_TIMEOUT);
145+
expect(rxOnUpsertSpy).toHaveBeenCalledWith(mockItem);
146+
});
147+
148+
it('should call the "upsertItemById" method and expect it to call the "addItem" with different boolean flag provided as arguments', () => {
149+
const mockItem = { id: 0, user: { firstName: 'John', lastName: 'Doe' } };
150+
const dataviewSpy = jest.spyOn(dataviewStub, 'getRowById').mockReturnValue(undefined);
151+
const serviceAddItemSpy = jest.spyOn(service, 'addItem');
152+
const serviceHighlightSpy = jest.spyOn(service, 'highlightRow');
153+
const rxOnUpsertSpy = jest.spyOn(service.onItemUpserted, 'next');
154+
155+
service.upsertItemById(0, mockItem, false, true, false);
156+
157+
expect(dataviewSpy).toHaveBeenCalledWith(0);
158+
expect(serviceAddItemSpy).toHaveBeenCalled();
159+
expect(serviceAddItemSpy).toHaveBeenCalledWith(mockItem, false, true, false);
160+
expect(serviceHighlightSpy).not.toHaveBeenCalled();
161+
expect(rxOnUpsertSpy).not.toHaveBeenCalled();
162+
});
163+
});
164+
});

src/app/modules/angular-slickgrid/services/grid.service.ts

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { TranslateService } from '@ngx-translate/core';
21
import { Injectable } from '@angular/core';
32
import { CellArgs, Column, GridOption, OnEventArgs } from './../models/index';
43
import { ExtensionService } from './extension.service';
@@ -18,8 +17,14 @@ export class GridService {
1817
onItemAdded = new Subject<any | any[]>();
1918
onItemDeleted = new Subject<any | any[]>();
2019
onItemUpdated = new Subject<any | any[]>();
20+
onItemUpserted = new Subject<any | any[]>();
2121

22-
constructor(private extensionService: ExtensionService, private filterService: FilterService, private gridStateService: GridStateService, private sortService: SortService, private translate: TranslateService) { }
22+
constructor(
23+
private extensionService: ExtensionService,
24+
private filterService: FilterService,
25+
private gridStateService: GridStateService,
26+
private sortService: SortService
27+
) { }
2328

2429
/** Getter for the Grid Options pulled through the Grid Object */
2530
private get _gridOptions(): GridOption {
@@ -360,7 +365,7 @@ export class GridService {
360365
*/
361366
deleteItem(item: any, shouldTriggerEvent = true) {
362367
if (!item || !item.hasOwnProperty('id')) {
363-
throw new Error(`deleteItem() requires an item object which includes the "id" property`);
368+
throw new Error(`Deleting an item requires the item to include an "id" property`);
364369
}
365370
const itemId = (!item || !item.hasOwnProperty('id')) ? undefined : item.id;
366371
this.deleteItemById(itemId, shouldTriggerEvent);
@@ -374,7 +379,7 @@ export class GridService {
374379
deleteItems(items: any[], shouldTriggerEvent = true) {
375380
// when it's not an array, we can call directly the single item delete
376381
if (!Array.isArray(items)) {
377-
this.deleteItem(items);
382+
this.deleteItem(items, shouldTriggerEvent);
378383
}
379384
items.forEach((item: any) => this.deleteItem(item, false));
380385

@@ -456,7 +461,7 @@ export class GridService {
456461
const itemId = (!item || !item.hasOwnProperty('id')) ? undefined : item.id;
457462

458463
if (itemId === undefined) {
459-
throw new Error(`Could not find the item in the grid or it's associated "id"`);
464+
throw new Error(`Calling Update of an item requires the item to include an "id" property`);
460465
}
461466

462467
return this.updateItemById(itemId, item, shouldHighlightRow, shouldTriggerEvent);
@@ -472,7 +477,7 @@ export class GridService {
472477
updateItems(items: any | any[], shouldHighlightRow = true, shouldTriggerEvent = true): number[] {
473478
// when it's not an array, we can call directly the single item update
474479
if (!Array.isArray(items)) {
475-
this.updateItem(items, shouldHighlightRow);
480+
this.updateItem(items, shouldHighlightRow, shouldTriggerEvent);
476481
}
477482

478483
const gridIndexes: number[] = [];
@@ -509,7 +514,7 @@ export class GridService {
509514
const rowNumber = this._dataView.getRowById(itemId);
510515

511516
if (!item || rowNumber === undefined) {
512-
throw new Error(`Could not find the item in the grid or it's associated "id"`);
517+
throw new Error(`Deleting an item requires the item to include an "id" property`);
513518
}
514519

515520
const gridIdx = this._dataView.getIdxById(itemId);
@@ -537,38 +542,35 @@ export class GridService {
537542
* Insert a row into the grid if it doesn't already exist or update if it does.
538543
* @param item object which must contain a unique "id" property and any other suitable properties
539544
* @param shouldHighlightRow do we want to highlight the row after update
545+
* @param shouldResortGrid defaults to false, do we want the item to be sorted after insert? When set to False, it will add item on first row (default)
540546
* @param shouldTriggerEvent defaults to true, which will trigger an event (used by at least the pagination component)
541547
*/
542-
upsertItem(item: any, shouldHighlightRow = true, shouldTriggerEvent = true): number {
548+
upsertItem(item: any, shouldHighlightRow = true, shouldResortGrid = false, shouldTriggerEvent = true): number {
543549
const itemId = (!item || !item.hasOwnProperty('id')) ? undefined : item.id;
550+
544551
if (itemId === undefined) {
545-
throw new Error(`The item to be Upsert in the grid must have an associated "id" for it to be valid`);
552+
throw new Error(`Calling Upsert of an item requires the item to include an "id" property`);
546553
}
547554

548-
const rowNumber = this._dataView.getRowById(itemId);
549-
550-
if (rowNumber === undefined) {
551-
return this.addItem(item, shouldHighlightRow, shouldTriggerEvent);
552-
} else {
553-
return this.updateItem(item, shouldHighlightRow, shouldTriggerEvent);
554-
}
555+
return this.upsertItemById(itemId, item, shouldHighlightRow, shouldResortGrid, shouldTriggerEvent);
555556
}
556557

557558
/**
558559
* Update an array of existing items with new properties inside the datagrid
559560
* @param item object arrays, which must contain unique "id" property and any other suitable properties
560561
* @param shouldHighlightRow do we want to highlight the row after update
562+
* @param shouldResortGrid defaults to false, do we want the item to be sorted after insert? When set to False, it will add item on first row (default)
561563
* @param shouldTriggerEvent defaults to true, which will trigger an event (used by at least the pagination component)
562564
*/
563-
upsertItems(items: any | any[], shouldHighlightRow = true, shouldTriggerEvent = true): number[] {
565+
upsertItems(items: any | any[], shouldHighlightRow = true, shouldResortGrid = false, shouldTriggerEvent = true): number[] {
564566
// when it's not an array, we can call directly the single item update
565567
if (!Array.isArray(items)) {
566-
return [this.upsertItem(items, shouldHighlightRow)];
568+
return [this.upsertItem(items, shouldHighlightRow, shouldResortGrid, shouldTriggerEvent)];
567569
}
568570

569571
const gridIndexes: number[] = [];
570572
items.forEach((item: any) => {
571-
gridIndexes.push(this.upsertItem(item, false, false));
573+
gridIndexes.push(this.upsertItem(item, false, false, false));
572574
});
573575

574576
// only highlight at the end, all at once
@@ -579,8 +581,36 @@ export class GridService {
579581

580582
// do we want to trigger an event after updating the item
581583
if (shouldTriggerEvent) {
582-
this.onItemUpdated.next(items);
584+
this.onItemUpserted.next(items);
583585
}
584586
return gridIndexes;
585587
}
588+
589+
/**
590+
* Update an existing item in the datagrid by it's id and new properties
591+
* @param itemId: item unique id
592+
* @param item object which must contain a unique "id" property and any other suitable properties
593+
* @param shouldHighlightRow do we want to highlight the row after update
594+
* @param shouldResortGrid defaults to false, do we want the item to be sorted after insert? When set to False, it will add item on first row (default)
595+
* @param shouldTriggerEvent defaults to true, which will trigger an event (used by at least the pagination component)
596+
* @return grid row index
597+
*/
598+
upsertItemById(itemId: number | string, item: any, shouldHighlightRow = true, shouldResortGrid = false, shouldTriggerEvent = true): number {
599+
if (itemId === undefined) {
600+
throw new Error(`Calling Upsert of an item requires the item to include a valid and unique "id" property`);
601+
}
602+
603+
let rowNumber: number;
604+
if (this._dataView.getRowById(itemId) === undefined) {
605+
rowNumber = this.addItem(item, shouldHighlightRow, shouldResortGrid, shouldTriggerEvent);
606+
} else {
607+
rowNumber = this.updateItem(item, shouldHighlightRow, shouldTriggerEvent);
608+
}
609+
610+
// do we want to trigger an event after updating the item
611+
if (shouldTriggerEvent) {
612+
this.onItemUpserted.next(item);
613+
}
614+
return rowNumber;
615+
}
586616
}

0 commit comments

Comments
 (0)