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