1 import { ComponentFixture, TestBed } from '@angular/core/testing';
2 import { FormsModule } from '@angular/forms';
3 import { RouterTestingModule } from '@angular/router/testing';
5 import { NgxDatatableModule } from '@swimlane/ngx-datatable';
6 import * as _ from 'lodash';
8 import { configureTestBed } from '../../../../testing/unit-test-helper';
9 import { ComponentsModule } from '../../components/components.module';
10 import { CdTableFetchDataContext } from '../../models/cd-table-fetch-data-context';
11 import { TableComponent } from './table.component';
13 describe('TableComponent', () => {
14 let component: TableComponent;
15 let fixture: ComponentFixture<TableComponent>;
17 const createFakeData = (n) => {
19 for (let i = 0; i < n; i++) {
23 c: [-(i % 10), 'score' + ((i % 16) + 6)],
30 const clearLocalStorage = () => {
31 component.localStorage.clear();
35 declarations: [TableComponent],
36 imports: [NgxDatatableModule, FormsModule, ComponentsModule, RouterTestingModule]
40 fixture = TestBed.createComponent(TableComponent);
41 component = fixture.componentInstance;
45 component.data = createFakeData(100);
47 { prop: 'a', name: 'Index' },
48 { prop: 'b', name: 'Power ofA' },
49 { prop: 'c', name: 'Poker array' },
50 { prop: 'd', name: 'Boolean value' }
54 it('should create', () => {
55 expect(component).toBeTruthy();
58 describe('after useData', () => {
63 it('should force an identifier', () => {
64 component.identifier = 'x';
65 component.forceIdentifier = true;
67 expect(component.identifier).toBe('x');
68 expect(component.sorts[0].prop).toBe('a');
69 expect(component.sorts).toEqual(component.createSortingDefinition('a'));
72 it('should have rows', () => {
73 expect(component.data.length).toBe(100);
74 expect(component.rows.length).toBe(component.data.length);
77 it('should have an int in setLimit parsing a string', () => {
78 expect(component.limit).toBe(10);
79 expect(component.limit).toEqual(jasmine.any(Number));
81 const e = { target: { value: '1' } };
82 component.setLimit(e);
83 expect(component.userConfig.limit).toBe(1);
84 expect(component.userConfig.limit).toEqual(jasmine.any(Number));
85 e.target.value = '-20';
86 component.setLimit(e);
87 expect(component.userConfig.limit).toBe(1);
90 it('should prevent propagation of mouseenter event', (done) => {
91 let wasCalled = false;
92 const mouseEvent = new MouseEvent('mouseenter');
93 mouseEvent.stopPropagation = () => {
96 spyOn(window, 'addEventListener').and.callFake((eventName, fn) => {
98 expect(eventName).toBe('mouseenter');
99 expect(wasCalled).toBe(true);
102 component.ngOnInit();
105 it('should force an identifier', () => {
107 component.identifier = 'x';
108 component.forceIdentifier = true;
109 component.ngOnInit();
110 expect(component.identifier).toBe('x');
111 expect(component.sorts[0].prop).toBe('a');
112 expect(component.sorts).toEqual(component.createSortingDefinition('a'));
115 describe('test search', () => {
116 const doSearch = (search: string, expectedLength: number, firstObject?: object) => {
117 component.search = search;
118 component.updateFilter();
119 expect(component.rows.length).toBe(expectedLength);
121 expect(component.rows[0]).toEqual(firstObject);
125 it('should search for 13', () => {
126 doSearch('13', 9, { a: 7, b: 49, c: [-7, 'score13'], d: false });
127 expect(component.rows[1].a).toBe(13);
128 expect(component.rows[8].a).toBe(87);
131 it('should search for true', () => {
132 doSearch('true', 50, { a: 0, b: 0, c: [-0, 'score6'], d: true });
133 expect(component.rows[0].d).toBe(true);
134 expect(component.rows[1].d).toBe(true);
137 it('should search for false', () => {
138 doSearch('false', 50, { a: 1, b: 1, c: [-1, 'score7'], d: false });
139 expect(component.rows[0].d).toBe(false);
140 expect(component.rows[1].d).toBe(false);
143 it('should test search manipulation', () => {
144 let searchTerms = [];
145 spyOn(component, 'subSearch').and.callFake((_d, search) => {
146 expect(search).toEqual(searchTerms);
148 const searchTest = (s: string, st: string[]) => {
149 component.search = s;
151 component.updateFilter();
153 searchTest('a b c', ['a', 'b', 'c']);
154 searchTest('a+b c', ['a+b', 'c']);
155 searchTest('a,,,, b,,, c', ['a', 'b', 'c']);
156 searchTest('a,,,+++b,,, c', ['a+++b', 'c']);
157 searchTest('"a b c" "d e f", "g, h i"', ['a+b+c', 'd+e++f', 'g+h+i']);
160 it('should search for multiple values', () => {
161 doSearch('7 5 3', 5, { a: 57, b: 3249, c: [-7, 'score15'], d: false });
164 it('should search with column filter', () => {
165 doSearch('power:1369', 1, { a: 37, b: 1369, c: [-7, 'score11'], d: false });
166 doSearch('ndex:7 ofa:5 poker:3', 3, { a: 71, b: 5041, c: [-1, 'score13'], d: false });
169 it('should search with through array', () => {
170 doSearch('array:score21', 6, { a: 15, b: 225, c: [-5, 'score21'], d: false });
173 it('should search with spaces', () => {
174 doSearch(`'poker array':score21`, 6, { a: 15, b: 225, c: [-5, 'score21'], d: false });
175 doSearch('"poker array":score21', 6, { a: 15, b: 225, c: [-5, 'score21'], d: false });
176 doSearch('poker+array:score21', 6, { a: 15, b: 225, c: [-5, 'score21'], d: false });
179 it('should search if column name is incomplete', () => {
180 doSearch(`'poker array'`, 0);
182 doSearch('pok:', 100);
185 it('should restore full table after search', () => {
186 expect(component.rows.length).toBe(100);
187 component.search = '13';
188 component.updateFilter();
189 expect(component.rows.length).toBe(9);
190 component.updateFilter(true);
191 expect(component.rows.length).toBe(100);
196 describe('after ngInit', () => {
197 const toggleColumn = (prop, checked) => {
198 component.toggleColumn({
206 const equalStorageConfig = () => {
207 expect(JSON.stringify(component.userConfig)).toBe(
208 component.localStorage.getItem(component.tableName)
213 component.ngOnInit();
216 it('should have updated the column definitions', () => {
217 expect(component.columns[0].flexGrow).toBe(1);
218 expect(component.columns[1].flexGrow).toBe(2);
219 expect(component.columns[2].flexGrow).toBe(2);
220 expect(component.columns[2].resizeable).toBe(false);
223 it('should have table columns', () => {
224 expect(component.tableColumns.length).toBe(4);
225 expect(component.tableColumns).toEqual(component.columns);
228 it('should have a unique identifier which it searches for', () => {
229 expect(component.identifier).toBe('a');
230 expect(component.userConfig.sorts[0].prop).toBe('a');
231 expect(component.userConfig.sorts).toEqual(component.createSortingDefinition('a'));
232 equalStorageConfig();
235 it('should remove column "a"', () => {
236 expect(component.userConfig.sorts[0].prop).toBe('a');
237 toggleColumn('a', false);
238 expect(component.userConfig.sorts[0].prop).toBe('b');
239 expect(component.tableColumns.length).toBe(3);
240 equalStorageConfig();
243 it('should not be able to remove all columns', () => {
244 expect(component.userConfig.sorts[0].prop).toBe('a');
245 toggleColumn('a', false);
246 toggleColumn('b', false);
247 toggleColumn('c', false);
248 toggleColumn('d', false);
249 expect(component.userConfig.sorts[0].prop).toBe('d');
250 expect(component.tableColumns.length).toBe(1);
251 equalStorageConfig();
254 it('should enable column "a" again', () => {
255 expect(component.userConfig.sorts[0].prop).toBe('a');
256 toggleColumn('a', false);
257 toggleColumn('a', true);
258 expect(component.userConfig.sorts[0].prop).toBe('b');
259 expect(component.tableColumns.length).toBe(4);
260 equalStorageConfig();
268 describe('reload data', () => {
270 component.ngOnInit();
272 component['updating'] = false;
275 it('should call fetchData callback function', () => {
276 component.fetchData.subscribe((context) => {
277 expect(context instanceof CdTableFetchDataContext).toBeTruthy();
279 component.reloadData();
282 it('should call error function', () => {
283 component.data = createFakeData(5);
284 component.fetchData.subscribe((context) => {
286 expect(component.loadingError).toBeTruthy();
287 expect(component.data.length).toBe(0);
288 expect(component.loadingIndicator).toBeFalsy();
289 expect(component['updating']).toBeFalsy();
291 component.reloadData();
294 it('should call error function with custom config', () => {
295 component.data = createFakeData(10);
296 component.fetchData.subscribe((context) => {
297 context.errorConfig.resetData = false;
298 context.errorConfig.displayError = false;
300 expect(component.loadingError).toBeFalsy();
301 expect(component.data.length).toBe(10);
302 expect(component.loadingIndicator).toBeFalsy();
303 expect(component['updating']).toBeFalsy();
305 component.reloadData();
308 it('should update selection on refresh - "onChange"', () => {
309 spyOn(component, 'onSelect').and.callThrough();
310 component.data = createFakeData(10);
311 component.selection.selected = [_.clone(component.data[1])];
312 component.updateSelectionOnRefresh = 'onChange';
313 component.updateSelected();
314 expect(component.onSelect).toHaveBeenCalledTimes(0);
315 component.data[1].d = !component.data[1].d;
316 component.updateSelected();
317 expect(component.onSelect).toHaveBeenCalled();
320 it('should update selection on refresh - "always"', () => {
321 spyOn(component, 'onSelect').and.callThrough();
322 component.data = createFakeData(10);
323 component.selection.selected = [_.clone(component.data[1])];
324 component.updateSelectionOnRefresh = 'always';
325 component.updateSelected();
326 expect(component.onSelect).toHaveBeenCalled();
327 component.data[1].d = !component.data[1].d;
328 component.updateSelected();
329 expect(component.onSelect).toHaveBeenCalled();
332 it('should update selection on refresh - "never"', () => {
333 spyOn(component, 'onSelect').and.callThrough();
334 component.data = createFakeData(10);
335 component.selection.selected = [_.clone(component.data[1])];
336 component.updateSelectionOnRefresh = 'never';
337 component.updateSelected();
338 expect(component.onSelect).toHaveBeenCalledTimes(0);
339 component.data[1].d = !component.data[1].d;
340 component.updateSelected();
341 expect(component.onSelect).toHaveBeenCalledTimes(0);
349 describe('useCustomClass', () => {
351 component.customCss = {
352 'label label-danger': 'active',
353 'secret secret-number': 123.456,
354 'btn btn-sm': (v) => _.isString(v) && v.startsWith('http'),
355 secure: (v) => _.isString(v) && v.startsWith('https')
359 it('should throw an error if custom classes are not set', () => {
360 component.customCss = undefined;
361 expect(() => component.useCustomClass('active')).toThrowError('Custom classes are not set!');
364 it('should not return any class', () => {
365 ['', 'something', 123, { complex: 1 }, [1, 2, 3]].forEach((value) =>
366 expect(component.useCustomClass(value)).toBe(undefined)
370 it('should match a string and return the corresponding class', () => {
371 expect(component.useCustomClass('active')).toBe('label label-danger');
374 it('should match a number and return the corresponding class', () => {
375 expect(component.useCustomClass(123.456)).toBe('secret secret-number');
378 it('should match against a function and return the corresponding class', () => {
379 expect(component.useCustomClass('http://no.ssl')).toBe('btn btn-sm');
382 it('should match against multiple functions and return the corresponding classes', () => {
383 expect(component.useCustomClass('https://secure.it')).toBe('btn btn-sm secure');