]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.spec.ts
bump version to 18.2.2-pve1
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / shared / datatable / table / table.component.spec.ts
CommitLineData
11fdf7f2
TL
1import { ComponentFixture, TestBed } from '@angular/core/testing';
2import { FormsModule } from '@angular/forms';
f67539c2 3import { By } from '@angular/platform-browser';
e306af50 4import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
11fdf7f2
TL
5import { RouterTestingModule } from '@angular/router/testing';
6
f67539c2 7import { NgbDropdownModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
11fdf7f2 8import { NgxDatatableModule } from '@swimlane/ngx-datatable';
f67539c2
TL
9import _ from 'lodash';
10import { NgxPipeFunctionModule } from 'ngx-pipe-function';
11
12import { ComponentsModule } from '~/app/shared/components/components.module';
13import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
14import { CdTableColumnFilter } from '~/app/shared/models/cd-table-column-filter';
15import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
16import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
17import { PipesModule } from '~/app/shared/pipes/pipes.module';
18import { configureTestBed } from '~/testing/unit-test-helper';
39ae355f 19import { TablePaginationComponent } from '../table-pagination/table-pagination.component';
11fdf7f2
TL
20import { TableComponent } from './table.component';
21
22describe('TableComponent', () => {
23 let component: TableComponent;
24 let fixture: ComponentFixture<TableComponent>;
25
9f95a23c 26 const createFakeData = (n: number) => {
11fdf7f2
TL
27 const data = [];
28 for (let i = 0; i < n; i++) {
29 data.push({
30 a: i,
9f95a23c
TL
31 b: i * 10,
32 c: !!(i % 2)
11fdf7f2
TL
33 });
34 }
35 return data;
36 };
37
38 const clearLocalStorage = () => {
39 component.localStorage.clear();
40 };
41
42 configureTestBed({
39ae355f 43 declarations: [TableComponent, TablePaginationComponent],
9f95a23c 44 imports: [
e306af50 45 BrowserAnimationsModule,
9f95a23c 46 NgxDatatableModule,
f67539c2 47 NgxPipeFunctionModule,
9f95a23c
TL
48 FormsModule,
49 ComponentsModule,
50 RouterTestingModule,
f67539c2
TL
51 NgbDropdownModule,
52 PipesModule,
53 NgbTooltipModule
9f95a23c 54 ]
11fdf7f2
TL
55 });
56
57 beforeEach(() => {
58 fixture = TestBed.createComponent(TableComponent);
59 component = fixture.componentInstance;
11fdf7f2 60
9f95a23c 61 component.data = createFakeData(10);
f67539c2 62 component.localColumns = component.columns = [
9f95a23c
TL
63 { prop: 'a', name: 'Index', filterable: true },
64 { prop: 'b', name: 'Index times ten' },
65 { prop: 'c', name: 'Odd?', filterable: true }
11fdf7f2
TL
66 ];
67 });
68
69 it('should create', () => {
70 expect(component).toBeTruthy();
71 });
72
9f95a23c
TL
73 it('should force an identifier', () => {
74 component.identifier = 'x';
75 component.forceIdentifier = true;
76 component.ngOnInit();
77 expect(component.identifier).toBe('x');
78 expect(component.sorts[0].prop).toBe('a');
79 expect(component.sorts).toEqual(component.createSortingDefinition('a'));
80 });
11fdf7f2 81
9f95a23c
TL
82 it('should have rows', () => {
83 component.useData();
84 expect(component.data.length).toBe(10);
85 expect(component.rows.length).toBe(component.data.length);
86 });
11fdf7f2 87
9f95a23c
TL
88 it('should have an int in setLimit parsing a string', () => {
89 expect(component.limit).toBe(10);
90 expect(component.limit).toEqual(jasmine.any(Number));
91
92 const e = { target: { value: '1' } };
93 component.setLimit(e);
94 expect(component.userConfig.limit).toBe(1);
95 expect(component.userConfig.limit).toEqual(jasmine.any(Number));
96 e.target.value = '-20';
97 component.setLimit(e);
98 expect(component.userConfig.limit).toBe(1);
99 });
11fdf7f2 100
9f95a23c
TL
101 it('should prevent propagation of mouseenter event', (done) => {
102 let wasCalled = false;
103 const mouseEvent = new MouseEvent('mouseenter');
104 mouseEvent.stopPropagation = () => {
105 wasCalled = true;
106 };
107 spyOn(component.table.element, 'addEventListener').and.callFake((eventName, fn) => {
108 fn(mouseEvent);
109 expect(eventName).toBe('mouseenter');
110 expect(wasCalled).toBe(true);
111 done();
11fdf7f2 112 });
9f95a23c
TL
113 component.ngOnInit();
114 });
11fdf7f2 115
f67539c2
TL
116 it('should call updateSelection on init', () => {
117 component.updateSelection.subscribe((selection: CdTableSelection) => {
118 expect(selection.hasSelection).toBeFalsy();
119 expect(selection.hasSingleSelection).toBeFalsy();
120 expect(selection.hasMultiSelection).toBeFalsy();
121 expect(selection.selected.length).toBe(0);
122 });
123 component.ngOnInit();
124 });
125
9f95a23c
TL
126 describe('test column filtering', () => {
127 let filterIndex: CdTableColumnFilter;
128 let filterOdd: CdTableColumnFilter;
129 let filterCustom: CdTableColumnFilter;
130
131 const expectColumnFilterCreated = (
132 filter: CdTableColumnFilter,
133 prop: string,
134 options: string[],
135 value?: { raw: string; formatted: string }
136 ) => {
137 expect(filter.column.prop).toBe(prop);
138 expect(_.map(filter.options, 'raw')).toEqual(options);
139 expect(filter.value).toEqual(value);
140 };
141
142 const expectColumnFiltered = (
143 changes: { filter: CdTableColumnFilter; value?: string }[],
144 results: any[],
145 search: string = ''
146 ) => {
147 component.search = search;
148 _.forEach(changes, (change) => {
149 component.onChangeFilter(
150 change.filter,
151 change.value ? { raw: change.value, formatted: change.value } : undefined
152 );
494da23a 153 });
9f95a23c
TL
154 expect(component.rows).toEqual(results);
155 component.onClearSearch();
156 component.onClearFilters();
157 };
81eedcae 158
9f95a23c
TL
159 describe('with visible columns', () => {
160 beforeEach(() => {
161 component.initColumnFilters();
162 component.updateColumnFilterOptions();
163 filterIndex = component.columnFilters[0];
164 filterOdd = component.columnFilters[1];
165 });
11fdf7f2 166
9f95a23c
TL
167 it('should have filters initialized', () => {
168 expect(component.columnFilters.length).toBe(2);
169 expectColumnFilterCreated(
170 filterIndex,
171 'a',
172 _.map(component.data, (row) => _.toString(row.a))
173 );
174 expectColumnFilterCreated(filterOdd, 'c', ['false', 'true']);
11fdf7f2
TL
175 });
176
9f95a23c
TL
177 it('should add filters', () => {
178 // single
179 expectColumnFiltered([{ filter: filterIndex, value: '1' }], [{ a: 1, b: 10, c: true }]);
180
181 // multiple
182 expectColumnFiltered(
801d1391
TL
183 [
184 { filter: filterOdd, value: 'false' },
185 { filter: filterIndex, value: '2' }
186 ],
9f95a23c
TL
187 [{ a: 2, b: 20, c: false }]
188 );
189
190 // Clear should work
191 expect(component.rows).toEqual(component.data);
11fdf7f2
TL
192 });
193
9f95a23c
TL
194 it('should remove filters', () => {
195 // single
196 expectColumnFiltered(
197 [
198 { filter: filterOdd, value: 'true' },
199 { filter: filterIndex, value: '1' },
200 { filter: filterIndex, value: undefined }
201 ],
202 [
203 { a: 1, b: 10, c: true },
204 { a: 3, b: 30, c: true },
205 { a: 5, b: 50, c: true },
206 { a: 7, b: 70, c: true },
207 { a: 9, b: 90, c: true }
208 ]
209 );
210
211 // multiple
212 expectColumnFiltered(
213 [
214 { filter: filterOdd, value: 'true' },
215 { filter: filterIndex, value: '1' },
216 { filter: filterIndex, value: undefined },
217 { filter: filterOdd, value: undefined }
218 ],
219 component.data
220 );
221
222 // a selected filter should be removed if it's selected again
223 expectColumnFiltered(
224 [
225 { filter: filterOdd, value: 'true' },
226 { filter: filterIndex, value: '1' },
227 { filter: filterIndex, value: '1' }
228 ],
229 [
230 { a: 1, b: 10, c: true },
231 { a: 3, b: 30, c: true },
232 { a: 5, b: 50, c: true },
233 { a: 7, b: 70, c: true },
234 { a: 9, b: 90, c: true }
235 ]
236 );
11fdf7f2
TL
237 });
238
9f95a23c
TL
239 it('should search from filtered rows', () => {
240 expectColumnFiltered(
241 [{ filter: filterOdd, value: 'true' }],
242 [{ a: 9, b: 90, c: true }],
243 '9'
244 );
245
246 // Clear should work
247 expect(component.rows).toEqual(component.data);
11fdf7f2 248 });
9f95a23c 249 });
11fdf7f2 250
9f95a23c
TL
251 describe('with custom columns', () => {
252 beforeEach(() => {
253 // create a new additional column in data
254 for (let i = 0; i < component.data.length; i++) {
255 const row = component.data[i];
256 row['d'] = row.a;
257 }
258 // create a custom column filter
259 component.extraFilterableColumns = [
260 {
261 name: 'd less than 5',
262 prop: 'd',
263 filterOptions: ['yes', 'no'],
264 filterInitValue: 'yes',
265 filterPredicate: (row, value) => {
266 if (value === 'yes') {
267 return row.d < 5;
268 } else {
269 return row.d >= 5;
270 }
271 }
272 }
273 ];
274 component.initColumnFilters();
275 component.updateColumnFilterOptions();
276 filterIndex = component.columnFilters[0];
277 filterOdd = component.columnFilters[1];
278 filterCustom = component.columnFilters[2];
11fdf7f2
TL
279 });
280
9f95a23c
TL
281 it('should have filters initialized', () => {
282 expect(component.columnFilters.length).toBe(3);
283 expectColumnFilterCreated(filterCustom, 'd', ['yes', 'no'], {
284 raw: 'yes',
285 formatted: 'yes'
286 });
287 component.useData();
288 expect(component.rows).toEqual(_.slice(component.data, 0, 5));
11fdf7f2
TL
289 });
290
9f95a23c
TL
291 it('should remove filters', () => {
292 expectColumnFiltered([{ filter: filterCustom, value: 'no' }], _.slice(component.data, 5));
11fdf7f2 293 });
9f95a23c
TL
294 });
295 });
296
297 describe('test search', () => {
298 const expectSearch = (keyword: string, expectedResult: object[]) => {
299 component.search = keyword;
300 component.updateFilter();
301 expect(component.rows).toEqual(expectedResult);
302 component.onClearSearch();
303 };
304
305 describe('searchableObjects', () => {
306 const testObject = {
307 obj: {
308 min: 8,
309 max: 123
310 }
311 };
11fdf7f2 312
9f95a23c
TL
313 beforeEach(() => {
314 component.data = [testObject];
f67539c2 315 component.localColumns = [{ prop: 'obj', name: 'Object' }];
11fdf7f2
TL
316 });
317
9f95a23c
TL
318 it('should not search through objects as default case', () => {
319 expect(component.searchableObjects).toBe(false);
320 expectSearch('8', []);
11fdf7f2
TL
321 });
322
9f95a23c
TL
323 it('should search through objects if searchableObjects is set to true', () => {
324 component.searchableObjects = true;
325 expectSearch('28', []);
326 expectSearch('8', [testObject]);
327 expectSearch('123', [testObject]);
328 expectSearch('max', [testObject]);
11fdf7f2
TL
329 });
330 });
9f95a23c
TL
331
332 it('should find a particular number', () => {
333 expectSearch('5', [{ a: 5, b: 50, c: true }]);
334 expectSearch('9', [{ a: 9, b: 90, c: true }]);
335 });
336
337 it('should find boolean values', () => {
338 expectSearch('true', [
339 { a: 1, b: 10, c: true },
340 { a: 3, b: 30, c: true },
341 { a: 5, b: 50, c: true },
342 { a: 7, b: 70, c: true },
343 { a: 9, b: 90, c: true }
344 ]);
345 expectSearch('false', [
346 { a: 0, b: 0, c: false },
347 { a: 2, b: 20, c: false },
348 { a: 4, b: 40, c: false },
349 { a: 6, b: 60, c: false },
350 { a: 8, b: 80, c: false }
351 ]);
352 });
353
354 it('should test search keyword preparation', () => {
355 const prepare = TableComponent.prepareSearch;
356 const expected = ['a', 'b', 'c'];
357 expect(prepare('a b c')).toEqual(expected);
358 expect(prepare('a,, b,, c')).toEqual(expected);
359 expect(prepare('a,,,, b,,, c')).toEqual(expected);
360 expect(prepare('a+b c')).toEqual(['a+b', 'c']);
361 expect(prepare('a,,,+++b,,, c')).toEqual(['a+++b', 'c']);
362 expect(prepare('"a b c" "d e f", "g, h i"')).toEqual(['a+b+c', 'd+e++f', 'g+h+i']);
363 });
364
365 it('should search for multiple values', () => {
366 expectSearch('2 20 false', [{ a: 2, b: 20, c: false }]);
367 expectSearch('false 2', [{ a: 2, b: 20, c: false }]);
368 });
369
370 it('should filter by column', () => {
371 expectSearch('index:5', [{ a: 5, b: 50, c: true }]);
372 expectSearch('times:50', [{ a: 5, b: 50, c: true }]);
373 expectSearch('times:50 index:5', [{ a: 5, b: 50, c: true }]);
374 expectSearch('Odd?:true', [
375 { a: 1, b: 10, c: true },
376 { a: 3, b: 30, c: true },
377 { a: 5, b: 50, c: true },
378 { a: 7, b: 70, c: true },
379 { a: 9, b: 90, c: true }
380 ]);
381 component.data = createFakeData(100);
382 expectSearch('index:1 odd:true times:110', [{ a: 11, b: 110, c: true }]);
383 });
384
385 it('should search through arrays', () => {
f67539c2 386 component.localColumns = [
801d1391
TL
387 { prop: 'a', name: 'Index' },
388 { prop: 'b', name: 'ArrayColumn' }
389 ];
390
391 component.data = [
392 { a: 1, b: ['foo', 'bar'] },
393 { a: 2, b: ['baz', 'bazinga'] }
394 ];
9f95a23c
TL
395 expectSearch('bar', [{ a: 1, b: ['foo', 'bar'] }]);
396 expectSearch('arraycolumn:bar arraycolumn:foo', [{ a: 1, b: ['foo', 'bar'] }]);
397 expectSearch('arraycolumn:baz arraycolumn:inga', [{ a: 2, b: ['baz', 'bazinga'] }]);
398
801d1391
TL
399 component.data = [
400 { a: 1, b: [1, 2] },
401 { a: 2, b: [3, 4] }
402 ];
9f95a23c
TL
403 expectSearch('arraycolumn:1 arraycolumn:2', [{ a: 1, b: [1, 2] }]);
404 });
405
406 it('should search with spaces', () => {
407 const expectedResult = [{ a: 2, b: 20, c: false }];
408 expectSearch(`'Index times ten':20`, expectedResult);
409 expectSearch('index+times+ten:20', expectedResult);
410 });
411
412 it('should filter results although column name is incomplete', () => {
413 component.data = createFakeData(3);
414 expectSearch(`'Index times ten'`, []);
415 expectSearch(`'Ind'`, []);
416 expectSearch(`'Ind:'`, [
417 { a: 0, b: 0, c: false },
418 { a: 1, b: 10, c: true },
419 { a: 2, b: 20, c: false }
420 ]);
421 });
422
423 it('should search if column name is incomplete', () => {
424 const expectedData = [
425 { a: 0, b: 0, c: false },
426 { a: 1, b: 10, c: true },
427 { a: 2, b: 20, c: false }
428 ];
429 component.data = _.clone(expectedData);
430 expectSearch('inde', []);
431 expectSearch('index:', expectedData);
432 expectSearch('index times te', []);
433 });
434
435 it('should restore full table after search', () => {
436 component.useData();
437 expect(component.rows.length).toBe(10);
438 component.search = '3';
439 component.updateFilter();
440 expect(component.rows.length).toBe(1);
441 component.onClearSearch();
442 expect(component.rows.length).toBe(10);
443 });
e306af50
TL
444
445 it('should work with undefined data', () => {
446 component.data = undefined;
447 component.search = '3';
448 component.updateFilter();
449 expect(component.rows).toBeUndefined();
450 });
11fdf7f2
TL
451 });
452
453 describe('after ngInit', () => {
9f95a23c 454 const toggleColumn = (prop: string, checked: boolean) => {
11fdf7f2 455 component.toggleColumn({
f67539c2
TL
456 prop: prop,
457 isHidden: checked
11fdf7f2
TL
458 });
459 };
460
461 const equalStorageConfig = () => {
462 expect(JSON.stringify(component.userConfig)).toBe(
463 component.localStorage.getItem(component.tableName)
464 );
465 };
466
467 beforeEach(() => {
468 component.ngOnInit();
469 });
470
471 it('should have updated the column definitions', () => {
f67539c2
TL
472 expect(component.localColumns[0].flexGrow).toBe(1);
473 expect(component.localColumns[1].flexGrow).toBe(2);
474 expect(component.localColumns[2].flexGrow).toBe(2);
475 expect(component.localColumns[2].resizeable).toBe(false);
11fdf7f2
TL
476 });
477
478 it('should have table columns', () => {
9f95a23c 479 expect(component.tableColumns.length).toBe(3);
f67539c2 480 expect(component.tableColumns).toEqual(component.localColumns);
11fdf7f2
TL
481 });
482
483 it('should have a unique identifier which it searches for', () => {
484 expect(component.identifier).toBe('a');
485 expect(component.userConfig.sorts[0].prop).toBe('a');
486 expect(component.userConfig.sorts).toEqual(component.createSortingDefinition('a'));
487 equalStorageConfig();
488 });
489
490 it('should remove column "a"', () => {
491 expect(component.userConfig.sorts[0].prop).toBe('a');
492 toggleColumn('a', false);
493 expect(component.userConfig.sorts[0].prop).toBe('b');
9f95a23c 494 expect(component.tableColumns.length).toBe(2);
11fdf7f2
TL
495 equalStorageConfig();
496 });
497
498 it('should not be able to remove all columns', () => {
499 expect(component.userConfig.sorts[0].prop).toBe('a');
500 toggleColumn('a', false);
501 toggleColumn('b', false);
502 toggleColumn('c', false);
9f95a23c 503 expect(component.userConfig.sorts[0].prop).toBe('c');
11fdf7f2
TL
504 expect(component.tableColumns.length).toBe(1);
505 equalStorageConfig();
506 });
507
508 it('should enable column "a" again', () => {
509 expect(component.userConfig.sorts[0].prop).toBe('a');
510 toggleColumn('a', false);
511 toggleColumn('a', true);
512 expect(component.userConfig.sorts[0].prop).toBe('b');
9f95a23c 513 expect(component.tableColumns.length).toBe(3);
11fdf7f2
TL
514 equalStorageConfig();
515 });
516
33c7a0ef
TL
517 it('should toggle on off columns', () => {
518 for (const column of component.columns) {
519 component.toggleColumn(column);
520 expect(column.isHidden).toBeTruthy();
521 component.toggleColumn(column);
522 expect(column.isHidden).toBeFalsy();
523 }
524 });
525
11fdf7f2
TL
526 afterEach(() => {
527 clearLocalStorage();
528 });
529 });
530
f67539c2
TL
531 describe('test cell transformations', () => {
532 interface ExecutingTemplateConfig {
533 valueClass?: string;
534 executingClass?: string;
535 }
536
537 const testExecutingTemplate = (templateConfig?: ExecutingTemplateConfig) => {
538 const state = 'updating';
539 const value = component.data[0].a;
540
541 component.autoReload = -1;
542 component.columns[0].cellTransformation = CellTemplate.executing;
543 if (templateConfig) {
544 component.columns[0].customTemplateConfig = templateConfig;
545 }
546 component.data[0].cdExecuting = state;
547 fixture.detectChanges();
548
549 const elements = fixture.debugElement
550 .query(By.css('datatable-body-row datatable-body-cell'))
551 .queryAll(By.css('span'));
552 expect(elements.length).toBe(2);
553
554 // Value
555 const valueElement = elements[0];
556 if (templateConfig?.valueClass) {
557 templateConfig.valueClass.split(' ').forEach((clz) => {
558 expect(valueElement.classes).toHaveProperty(clz);
559 });
560 }
561 expect(valueElement.nativeElement.textContent.trim()).toBe(`${value}`);
562 // Executing state
563 const executingElement = elements[1];
564 if (templateConfig?.executingClass) {
565 templateConfig.executingClass.split(' ').forEach((clz) => {
566 expect(executingElement.classes).toHaveProperty(clz);
567 });
568 }
569 expect(executingElement.nativeElement.textContent.trim()).toBe(`(${state})`);
570 };
571
39ae355f 572 it.only('should display executing template', () => {
f67539c2
TL
573 testExecutingTemplate();
574 });
575
39ae355f 576 it.only('should display executing template with custom classes', () => {
f67539c2
TL
577 testExecutingTemplate({ valueClass: 'a b', executingClass: 'c d' });
578 });
579 });
580
11fdf7f2
TL
581 describe('reload data', () => {
582 beforeEach(() => {
583 component.ngOnInit();
584 component.data = [];
585 component['updating'] = false;
586 });
587
588 it('should call fetchData callback function', () => {
9f95a23c 589 component.fetchData.subscribe((context: any) => {
11fdf7f2
TL
590 expect(context instanceof CdTableFetchDataContext).toBeTruthy();
591 });
592 component.reloadData();
593 });
594
595 it('should call error function', () => {
596 component.data = createFakeData(5);
9f95a23c 597 component.fetchData.subscribe((context: any) => {
11fdf7f2 598 context.error();
f67539c2 599 expect(component.status.type).toBe('danger');
11fdf7f2
TL
600 expect(component.data.length).toBe(0);
601 expect(component.loadingIndicator).toBeFalsy();
602 expect(component['updating']).toBeFalsy();
603 });
604 component.reloadData();
605 });
606
607 it('should call error function with custom config', () => {
608 component.data = createFakeData(10);
9f95a23c 609 component.fetchData.subscribe((context: any) => {
11fdf7f2
TL
610 context.errorConfig.resetData = false;
611 context.errorConfig.displayError = false;
612 context.error();
f67539c2 613 expect(component.status.type).toBe('danger');
11fdf7f2
TL
614 expect(component.data.length).toBe(10);
615 expect(component.loadingIndicator).toBeFalsy();
616 expect(component['updating']).toBeFalsy();
617 });
618 component.reloadData();
619 });
620
621 it('should update selection on refresh - "onChange"', () => {
622 spyOn(component, 'onSelect').and.callThrough();
623 component.data = createFakeData(10);
624 component.selection.selected = [_.clone(component.data[1])];
625 component.updateSelectionOnRefresh = 'onChange';
626 component.updateSelected();
627 expect(component.onSelect).toHaveBeenCalledTimes(0);
628 component.data[1].d = !component.data[1].d;
629 component.updateSelected();
630 expect(component.onSelect).toHaveBeenCalled();
631 });
632
633 it('should update selection on refresh - "always"', () => {
634 spyOn(component, 'onSelect').and.callThrough();
635 component.data = createFakeData(10);
636 component.selection.selected = [_.clone(component.data[1])];
637 component.updateSelectionOnRefresh = 'always';
638 component.updateSelected();
639 expect(component.onSelect).toHaveBeenCalled();
640 component.data[1].d = !component.data[1].d;
641 component.updateSelected();
642 expect(component.onSelect).toHaveBeenCalled();
643 });
644
645 it('should update selection on refresh - "never"', () => {
646 spyOn(component, 'onSelect').and.callThrough();
647 component.data = createFakeData(10);
648 component.selection.selected = [_.clone(component.data[1])];
649 component.updateSelectionOnRefresh = 'never';
650 component.updateSelected();
651 expect(component.onSelect).toHaveBeenCalledTimes(0);
652 component.data[1].d = !component.data[1].d;
653 component.updateSelected();
654 expect(component.onSelect).toHaveBeenCalledTimes(0);
655 });
656
657 afterEach(() => {
658 clearLocalStorage();
659 });
660 });
661
662 describe('useCustomClass', () => {
663 beforeEach(() => {
664 component.customCss = {
9f95a23c 665 'badge badge-danger': 'active',
11fdf7f2 666 'secret secret-number': 123.456,
9f95a23c 667 btn: (v) => _.isString(v) && v.startsWith('http'),
11fdf7f2
TL
668 secure: (v) => _.isString(v) && v.startsWith('https')
669 };
670 });
671
672 it('should throw an error if custom classes are not set', () => {
673 component.customCss = undefined;
674 expect(() => component.useCustomClass('active')).toThrowError('Custom classes are not set!');
675 });
676
677 it('should not return any class', () => {
678 ['', 'something', 123, { complex: 1 }, [1, 2, 3]].forEach((value) =>
679 expect(component.useCustomClass(value)).toBe(undefined)
680 );
681 });
682
683 it('should match a string and return the corresponding class', () => {
9f95a23c 684 expect(component.useCustomClass('active')).toBe('badge badge-danger');
11fdf7f2
TL
685 });
686
687 it('should match a number and return the corresponding class', () => {
688 expect(component.useCustomClass(123.456)).toBe('secret secret-number');
689 });
690
691 it('should match against a function and return the corresponding class', () => {
9f95a23c 692 expect(component.useCustomClass('http://no.ssl')).toBe('btn');
11fdf7f2
TL
693 });
694
695 it('should match against multiple functions and return the corresponding classes', () => {
9f95a23c 696 expect(component.useCustomClass('https://secure.it')).toBe('btn secure');
11fdf7f2
TL
697 });
698 });
e306af50
TL
699
700 describe('test expand and collapse feature', () => {
701 beforeEach(() => {
702 spyOn(component.setExpandedRow, 'emit');
703 component.table = {
704 rowDetail: { collapseAllRows: jest.fn(), toggleExpandRow: jest.fn() }
705 } as any;
706
707 // Setup table
708 component.identifier = 'a';
709 component.data = createFakeData(10);
710
711 // Select item
712 component.expanded = _.clone(component.data[1]);
713 });
714
715 describe('update expanded on refresh', () => {
716 const updateExpendedOnState = (state: 'always' | 'never' | 'onChange') => {
717 component.updateExpandedOnRefresh = state;
718 component.updateExpanded();
719 };
720
721 beforeEach(() => {
722 // Mock change
723 component.data[1].b = 'test';
724 });
725
726 it('refreshes "always"', () => {
727 updateExpendedOnState('always');
728 expect(component.expanded.b).toBe('test');
729 expect(component.setExpandedRow.emit).toHaveBeenCalled();
730 });
731
732 it('refreshes "onChange"', () => {
733 updateExpendedOnState('onChange');
734 expect(component.expanded.b).toBe('test');
735 expect(component.setExpandedRow.emit).toHaveBeenCalled();
736 });
737
738 it('does not refresh "onChange" if data is equal', () => {
739 component.data[1].b = 10; // Reverts change
740 updateExpendedOnState('onChange');
741 expect(component.expanded.b).toBe(10);
742 expect(component.setExpandedRow.emit).not.toHaveBeenCalled();
743 });
744
745 it('"never" refreshes', () => {
746 updateExpendedOnState('never');
747 expect(component.expanded.b).toBe(10);
748 expect(component.setExpandedRow.emit).not.toHaveBeenCalled();
749 });
750 });
751
752 it('should open the table details and close other expanded rows', () => {
adb31ebb 753 component.toggleExpandRow(component.expanded, false, new Event('click'));
e306af50
TL
754 expect(component.expanded).toEqual({ a: 1, b: 10, c: true });
755 expect(component.table.rowDetail.collapseAllRows).toHaveBeenCalled();
756 expect(component.setExpandedRow.emit).toHaveBeenCalledWith(component.expanded);
757 expect(component.table.rowDetail.toggleExpandRow).toHaveBeenCalled();
758 });
759
760 it('should close the current table details expansion', () => {
adb31ebb 761 component.toggleExpandRow(component.expanded, true, new Event('click'));
e306af50
TL
762 expect(component.expanded).toBeUndefined();
763 expect(component.setExpandedRow.emit).toHaveBeenCalledWith(undefined);
764 expect(component.table.rowDetail.toggleExpandRow).toHaveBeenCalled();
765 });
adb31ebb
TL
766
767 it('should not select the row when the row is expanded', () => {
768 expect(component.selection.selected).toEqual([]);
769 component.toggleExpandRow(component.data[1], false, new Event('click'));
770 expect(component.selection.selected).toEqual([]);
771 });
772
773 it('should not change selection when expanding different row', () => {
774 expect(component.selection.selected).toEqual([]);
775 expect(component.expanded).toEqual(component.data[1]);
776 component.selection.selected = [component.data[2]];
777 component.toggleExpandRow(component.data[3], false, new Event('click'));
778 expect(component.selection.selected).toEqual([component.data[2]]);
779 expect(component.expanded).toEqual(component.data[3]);
780 });
e306af50 781 });
11fdf7f2 782});