]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.spec.ts
import ceph quincy 17.2.1
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / shared / datatable / table / table.component.spec.ts
1 import { ComponentFixture, TestBed } from '@angular/core/testing';
2 import { FormsModule } from '@angular/forms';
3 import { By } from '@angular/platform-browser';
4 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
5 import { RouterTestingModule } from '@angular/router/testing';
6
7 import { NgbDropdownModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
8 import { NgxDatatableModule } from '@swimlane/ngx-datatable';
9 import _ from 'lodash';
10 import { NgxPipeFunctionModule } from 'ngx-pipe-function';
11
12 import { ComponentsModule } from '~/app/shared/components/components.module';
13 import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
14 import { CdTableColumnFilter } from '~/app/shared/models/cd-table-column-filter';
15 import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
16 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
17 import { PipesModule } from '~/app/shared/pipes/pipes.module';
18 import { configureTestBed } from '~/testing/unit-test-helper';
19 import { TableComponent } from './table.component';
20
21 describe('TableComponent', () => {
22 let component: TableComponent;
23 let fixture: ComponentFixture<TableComponent>;
24
25 const createFakeData = (n: number) => {
26 const data = [];
27 for (let i = 0; i < n; i++) {
28 data.push({
29 a: i,
30 b: i * 10,
31 c: !!(i % 2)
32 });
33 }
34 return data;
35 };
36
37 const clearLocalStorage = () => {
38 component.localStorage.clear();
39 };
40
41 configureTestBed({
42 declarations: [TableComponent],
43 imports: [
44 BrowserAnimationsModule,
45 NgxDatatableModule,
46 NgxPipeFunctionModule,
47 FormsModule,
48 ComponentsModule,
49 RouterTestingModule,
50 NgbDropdownModule,
51 PipesModule,
52 NgbTooltipModule
53 ]
54 });
55
56 beforeEach(() => {
57 fixture = TestBed.createComponent(TableComponent);
58 component = fixture.componentInstance;
59
60 component.data = createFakeData(10);
61 component.localColumns = component.columns = [
62 { prop: 'a', name: 'Index', filterable: true },
63 { prop: 'b', name: 'Index times ten' },
64 { prop: 'c', name: 'Odd?', filterable: true }
65 ];
66 });
67
68 it('should create', () => {
69 expect(component).toBeTruthy();
70 });
71
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 });
80
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 });
86
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 });
99
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();
111 });
112 component.ngOnInit();
113 });
114
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
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 );
152 });
153 expect(component.rows).toEqual(results);
154 component.onClearSearch();
155 component.onClearFilters();
156 };
157
158 describe('with visible columns', () => {
159 beforeEach(() => {
160 component.initColumnFilters();
161 component.updateColumnFilterOptions();
162 filterIndex = component.columnFilters[0];
163 filterOdd = component.columnFilters[1];
164 });
165
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']);
174 });
175
176 it('should add filters', () => {
177 // single
178 expectColumnFiltered([{ filter: filterIndex, value: '1' }], [{ a: 1, b: 10, c: true }]);
179
180 // multiple
181 expectColumnFiltered(
182 [
183 { filter: filterOdd, value: 'false' },
184 { filter: filterIndex, value: '2' }
185 ],
186 [{ a: 2, b: 20, c: false }]
187 );
188
189 // Clear should work
190 expect(component.rows).toEqual(component.data);
191 });
192
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 );
236 });
237
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);
247 });
248 });
249
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];
278 });
279
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));
288 });
289
290 it('should remove filters', () => {
291 expectColumnFiltered([{ filter: filterCustom, value: 'no' }], _.slice(component.data, 5));
292 });
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 };
311
312 beforeEach(() => {
313 component.data = [testObject];
314 component.localColumns = [{ prop: 'obj', name: 'Object' }];
315 });
316
317 it('should not search through objects as default case', () => {
318 expect(component.searchableObjects).toBe(false);
319 expectSearch('8', []);
320 });
321
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]);
328 });
329 });
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', () => {
385 component.localColumns = [
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 ];
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
398 component.data = [
399 { a: 1, b: [1, 2] },
400 { a: 2, b: [3, 4] }
401 ];
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 });
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 });
450 });
451
452 describe('after ngInit', () => {
453 const toggleColumn = (prop: string, checked: boolean) => {
454 component.toggleColumn({
455 prop: prop,
456 isHidden: checked
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', () => {
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);
475 });
476
477 it('should have table columns', () => {
478 expect(component.tableColumns.length).toBe(3);
479 expect(component.tableColumns).toEqual(component.localColumns);
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');
493 expect(component.tableColumns.length).toBe(2);
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);
502 expect(component.userConfig.sorts[0].prop).toBe('c');
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');
512 expect(component.tableColumns.length).toBe(3);
513 equalStorageConfig();
514 });
515
516 it('should toggle on off columns', () => {
517 for (const column of component.columns) {
518 component.toggleColumn(column);
519 expect(column.isHidden).toBeTruthy();
520 component.toggleColumn(column);
521 expect(column.isHidden).toBeFalsy();
522 }
523 });
524
525 afterEach(() => {
526 clearLocalStorage();
527 });
528 });
529
530 describe('test cell transformations', () => {
531 interface ExecutingTemplateConfig {
532 valueClass?: string;
533 executingClass?: string;
534 }
535
536 const testExecutingTemplate = (templateConfig?: ExecutingTemplateConfig) => {
537 const state = 'updating';
538 const value = component.data[0].a;
539
540 component.autoReload = -1;
541 component.columns[0].cellTransformation = CellTemplate.executing;
542 if (templateConfig) {
543 component.columns[0].customTemplateConfig = templateConfig;
544 }
545 component.data[0].cdExecuting = state;
546 fixture.detectChanges();
547
548 const elements = fixture.debugElement
549 .query(By.css('datatable-body-row datatable-body-cell'))
550 .queryAll(By.css('span'));
551 expect(elements.length).toBe(2);
552
553 // Value
554 const valueElement = elements[0];
555 if (templateConfig?.valueClass) {
556 templateConfig.valueClass.split(' ').forEach((clz) => {
557 expect(valueElement.classes).toHaveProperty(clz);
558 });
559 }
560 expect(valueElement.nativeElement.textContent.trim()).toBe(`${value}`);
561 // Executing state
562 const executingElement = elements[1];
563 if (templateConfig?.executingClass) {
564 templateConfig.executingClass.split(' ').forEach((clz) => {
565 expect(executingElement.classes).toHaveProperty(clz);
566 });
567 }
568 expect(executingElement.nativeElement.textContent.trim()).toBe(`(${state})`);
569 };
570
571 it('should display executing template', () => {
572 testExecutingTemplate();
573 });
574
575 it('should display executing template with custom classes', () => {
576 testExecutingTemplate({ valueClass: 'a b', executingClass: 'c d' });
577 });
578 });
579
580 describe('test unselect functionality of rows', () => {
581 beforeEach(() => {
582 component.autoReload = -1;
583 component.selectionType = 'single';
584 fixture.detectChanges();
585 });
586
587 it('should unselect row on clicking on it again', () => {
588 const rowCellDebugElement = fixture.debugElement.query(By.css('datatable-body-cell'));
589
590 rowCellDebugElement.triggerEventHandler('click', null);
591 expect(component.selection.selected.length).toEqual(1);
592
593 rowCellDebugElement.triggerEventHandler('click', null);
594 expect(component.selection.selected.length).toEqual(0);
595 });
596 });
597
598 describe('reload data', () => {
599 beforeEach(() => {
600 component.ngOnInit();
601 component.data = [];
602 component['updating'] = false;
603 });
604
605 it('should call fetchData callback function', () => {
606 component.fetchData.subscribe((context: any) => {
607 expect(context instanceof CdTableFetchDataContext).toBeTruthy();
608 });
609 component.reloadData();
610 });
611
612 it('should call error function', () => {
613 component.data = createFakeData(5);
614 component.fetchData.subscribe((context: any) => {
615 context.error();
616 expect(component.status.type).toBe('danger');
617 expect(component.data.length).toBe(0);
618 expect(component.loadingIndicator).toBeFalsy();
619 expect(component['updating']).toBeFalsy();
620 });
621 component.reloadData();
622 });
623
624 it('should call error function with custom config', () => {
625 component.data = createFakeData(10);
626 component.fetchData.subscribe((context: any) => {
627 context.errorConfig.resetData = false;
628 context.errorConfig.displayError = false;
629 context.error();
630 expect(component.status.type).toBe('danger');
631 expect(component.data.length).toBe(10);
632 expect(component.loadingIndicator).toBeFalsy();
633 expect(component['updating']).toBeFalsy();
634 });
635 component.reloadData();
636 });
637
638 it('should update selection on refresh - "onChange"', () => {
639 spyOn(component, 'onSelect').and.callThrough();
640 component.data = createFakeData(10);
641 component.selection.selected = [_.clone(component.data[1])];
642 component.updateSelectionOnRefresh = 'onChange';
643 component.updateSelected();
644 expect(component.onSelect).toHaveBeenCalledTimes(0);
645 component.data[1].d = !component.data[1].d;
646 component.updateSelected();
647 expect(component.onSelect).toHaveBeenCalled();
648 });
649
650 it('should update selection on refresh - "always"', () => {
651 spyOn(component, 'onSelect').and.callThrough();
652 component.data = createFakeData(10);
653 component.selection.selected = [_.clone(component.data[1])];
654 component.updateSelectionOnRefresh = 'always';
655 component.updateSelected();
656 expect(component.onSelect).toHaveBeenCalled();
657 component.data[1].d = !component.data[1].d;
658 component.updateSelected();
659 expect(component.onSelect).toHaveBeenCalled();
660 });
661
662 it('should update selection on refresh - "never"', () => {
663 spyOn(component, 'onSelect').and.callThrough();
664 component.data = createFakeData(10);
665 component.selection.selected = [_.clone(component.data[1])];
666 component.updateSelectionOnRefresh = 'never';
667 component.updateSelected();
668 expect(component.onSelect).toHaveBeenCalledTimes(0);
669 component.data[1].d = !component.data[1].d;
670 component.updateSelected();
671 expect(component.onSelect).toHaveBeenCalledTimes(0);
672 });
673
674 afterEach(() => {
675 clearLocalStorage();
676 });
677 });
678
679 describe('useCustomClass', () => {
680 beforeEach(() => {
681 component.customCss = {
682 'badge badge-danger': 'active',
683 'secret secret-number': 123.456,
684 btn: (v) => _.isString(v) && v.startsWith('http'),
685 secure: (v) => _.isString(v) && v.startsWith('https')
686 };
687 });
688
689 it('should throw an error if custom classes are not set', () => {
690 component.customCss = undefined;
691 expect(() => component.useCustomClass('active')).toThrowError('Custom classes are not set!');
692 });
693
694 it('should not return any class', () => {
695 ['', 'something', 123, { complex: 1 }, [1, 2, 3]].forEach((value) =>
696 expect(component.useCustomClass(value)).toBe(undefined)
697 );
698 });
699
700 it('should match a string and return the corresponding class', () => {
701 expect(component.useCustomClass('active')).toBe('badge badge-danger');
702 });
703
704 it('should match a number and return the corresponding class', () => {
705 expect(component.useCustomClass(123.456)).toBe('secret secret-number');
706 });
707
708 it('should match against a function and return the corresponding class', () => {
709 expect(component.useCustomClass('http://no.ssl')).toBe('btn');
710 });
711
712 it('should match against multiple functions and return the corresponding classes', () => {
713 expect(component.useCustomClass('https://secure.it')).toBe('btn secure');
714 });
715 });
716
717 describe('test expand and collapse feature', () => {
718 beforeEach(() => {
719 spyOn(component.setExpandedRow, 'emit');
720 component.table = {
721 rowDetail: { collapseAllRows: jest.fn(), toggleExpandRow: jest.fn() }
722 } as any;
723
724 // Setup table
725 component.identifier = 'a';
726 component.data = createFakeData(10);
727
728 // Select item
729 component.expanded = _.clone(component.data[1]);
730 });
731
732 describe('update expanded on refresh', () => {
733 const updateExpendedOnState = (state: 'always' | 'never' | 'onChange') => {
734 component.updateExpandedOnRefresh = state;
735 component.updateExpanded();
736 };
737
738 beforeEach(() => {
739 // Mock change
740 component.data[1].b = 'test';
741 });
742
743 it('refreshes "always"', () => {
744 updateExpendedOnState('always');
745 expect(component.expanded.b).toBe('test');
746 expect(component.setExpandedRow.emit).toHaveBeenCalled();
747 });
748
749 it('refreshes "onChange"', () => {
750 updateExpendedOnState('onChange');
751 expect(component.expanded.b).toBe('test');
752 expect(component.setExpandedRow.emit).toHaveBeenCalled();
753 });
754
755 it('does not refresh "onChange" if data is equal', () => {
756 component.data[1].b = 10; // Reverts change
757 updateExpendedOnState('onChange');
758 expect(component.expanded.b).toBe(10);
759 expect(component.setExpandedRow.emit).not.toHaveBeenCalled();
760 });
761
762 it('"never" refreshes', () => {
763 updateExpendedOnState('never');
764 expect(component.expanded.b).toBe(10);
765 expect(component.setExpandedRow.emit).not.toHaveBeenCalled();
766 });
767 });
768
769 it('should open the table details and close other expanded rows', () => {
770 component.toggleExpandRow(component.expanded, false, new Event('click'));
771 expect(component.expanded).toEqual({ a: 1, b: 10, c: true });
772 expect(component.table.rowDetail.collapseAllRows).toHaveBeenCalled();
773 expect(component.setExpandedRow.emit).toHaveBeenCalledWith(component.expanded);
774 expect(component.table.rowDetail.toggleExpandRow).toHaveBeenCalled();
775 });
776
777 it('should close the current table details expansion', () => {
778 component.toggleExpandRow(component.expanded, true, new Event('click'));
779 expect(component.expanded).toBeUndefined();
780 expect(component.setExpandedRow.emit).toHaveBeenCalledWith(undefined);
781 expect(component.table.rowDetail.toggleExpandRow).toHaveBeenCalled();
782 });
783
784 it('should not select the row when the row is expanded', () => {
785 expect(component.selection.selected).toEqual([]);
786 component.toggleExpandRow(component.data[1], false, new Event('click'));
787 expect(component.selection.selected).toEqual([]);
788 });
789
790 it('should not change selection when expanding different row', () => {
791 expect(component.selection.selected).toEqual([]);
792 expect(component.expanded).toEqual(component.data[1]);
793 component.selection.selected = [component.data[2]];
794 component.toggleExpandRow(component.data[3], false, new Event('click'));
795 expect(component.selection.selected).toEqual([component.data[2]]);
796 expect(component.expanded).toEqual(component.data[3]);
797 });
798 });
799 });