]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.spec.ts
build: use dgit for download target
[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';
3import { RouterTestingModule } from '@angular/router/testing';
4
5import { NgxDatatableModule } from '@swimlane/ngx-datatable';
6import * as _ from 'lodash';
7
8import { configureTestBed } from '../../../../testing/unit-test-helper';
9import { ComponentsModule } from '../../components/components.module';
10import { CdTableFetchDataContext } from '../../models/cd-table-fetch-data-context';
11import { TableComponent } from './table.component';
12
13describe('TableComponent', () => {
14 let component: TableComponent;
15 let fixture: ComponentFixture<TableComponent>;
16
17 const createFakeData = (n) => {
18 const data = [];
19 for (let i = 0; i < n; i++) {
20 data.push({
21 a: i,
22 b: i * i,
23 c: [-(i % 10), 'score' + ((i % 16) + 6)],
24 d: !(i % 2)
25 });
26 }
27 return data;
28 };
29
30 const clearLocalStorage = () => {
31 component.localStorage.clear();
32 };
33
34 configureTestBed({
35 declarations: [TableComponent],
36 imports: [NgxDatatableModule, FormsModule, ComponentsModule, RouterTestingModule]
37 });
38
39 beforeEach(() => {
40 fixture = TestBed.createComponent(TableComponent);
41 component = fixture.componentInstance;
42 });
43
44 beforeEach(() => {
45 component.data = createFakeData(100);
46 component.columns = [
47 { prop: 'a', name: 'Index' },
48 { prop: 'b', name: 'Power ofA' },
49 { prop: 'c', name: 'Poker array' },
50 { prop: 'd', name: 'Boolean value' }
51 ];
52 });
53
54 it('should create', () => {
55 expect(component).toBeTruthy();
56 });
57
58 describe('after useData', () => {
59 beforeEach(() => {
60 component.useData();
61 });
62
63 it('should force an identifier', () => {
64 component.identifier = 'x';
65 component.forceIdentifier = true;
66 component.ngOnInit();
67 expect(component.identifier).toBe('x');
68 expect(component.sorts[0].prop).toBe('a');
69 expect(component.sorts).toEqual(component.createSortingDefinition('a'));
70 });
71
72 it('should have rows', () => {
73 expect(component.data.length).toBe(100);
74 expect(component.rows.length).toBe(component.data.length);
75 });
76
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));
80
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);
88 });
89
81eedcae
TL
90 it('should prevent propagation of mouseenter event', (done) => {
91 fixture.detectChanges();
92 const mouseEvent = new MouseEvent('mouseenter');
93 mouseEvent.stopPropagation = () => done();
94 fixture.debugElement.nativeElement.dispatchEvent(mouseEvent);
95 });
96
11fdf7f2
TL
97 it('should force an identifier', () => {
98 clearLocalStorage();
99 component.identifier = 'x';
100 component.forceIdentifier = true;
101 component.ngOnInit();
102 expect(component.identifier).toBe('x');
103 expect(component.sorts[0].prop).toBe('a');
104 expect(component.sorts).toEqual(component.createSortingDefinition('a'));
105 });
106
107 describe('test search', () => {
81eedcae 108 const doSearch = (search: string, expectedLength: number, firstObject?: object) => {
11fdf7f2
TL
109 component.search = search;
110 component.updateFilter();
111 expect(component.rows.length).toBe(expectedLength);
81eedcae
TL
112 if (firstObject) {
113 expect(component.rows[0]).toEqual(firstObject);
114 }
11fdf7f2
TL
115 };
116
117 it('should search for 13', () => {
118 doSearch('13', 9, { a: 7, b: 49, c: [-7, 'score13'], d: false });
119 expect(component.rows[1].a).toBe(13);
120 expect(component.rows[8].a).toBe(87);
121 });
122
123 it('should search for true', () => {
124 doSearch('true', 50, { a: 0, b: 0, c: [-0, 'score6'], d: true });
125 expect(component.rows[0].d).toBe(true);
126 expect(component.rows[1].d).toBe(true);
127 });
128
129 it('should search for false', () => {
130 doSearch('false', 50, { a: 1, b: 1, c: [-1, 'score7'], d: false });
131 expect(component.rows[0].d).toBe(false);
132 expect(component.rows[1].d).toBe(false);
133 });
134
135 it('should test search manipulation', () => {
136 let searchTerms = [];
137 spyOn(component, 'subSearch').and.callFake((_d, search) => {
138 expect(search).toEqual(searchTerms);
139 });
140 const searchTest = (s: string, st: string[]) => {
141 component.search = s;
142 searchTerms = st;
143 component.updateFilter();
144 };
145 searchTest('a b c', ['a', 'b', 'c']);
146 searchTest('a+b c', ['a+b', 'c']);
147 searchTest('a,,,, b,,, c', ['a', 'b', 'c']);
148 searchTest('a,,,+++b,,, c', ['a+++b', 'c']);
149 searchTest('"a b c" "d e f", "g, h i"', ['a+b+c', 'd+e++f', 'g+h+i']);
150 });
151
152 it('should search for multiple values', () => {
153 doSearch('7 5 3', 5, { a: 57, b: 3249, c: [-7, 'score15'], d: false });
154 });
155
156 it('should search with column filter', () => {
157 doSearch('power:1369', 1, { a: 37, b: 1369, c: [-7, 'score11'], d: false });
158 doSearch('ndex:7 ofa:5 poker:3', 3, { a: 71, b: 5041, c: [-1, 'score13'], d: false });
159 });
160
161 it('should search with through array', () => {
162 doSearch('array:score21', 6, { a: 15, b: 225, c: [-5, 'score21'], d: false });
163 });
164
165 it('should search with spaces', () => {
166 doSearch(`'poker array':score21`, 6, { a: 15, b: 225, c: [-5, 'score21'], d: false });
167 doSearch('"poker array":score21', 6, { a: 15, b: 225, c: [-5, 'score21'], d: false });
168 doSearch('poker+array:score21', 6, { a: 15, b: 225, c: [-5, 'score21'], d: false });
169 });
170
81eedcae
TL
171 it('should search if column name is incomplete', () => {
172 doSearch(`'poker array'`, 0);
173 doSearch('pok', 0);
174 doSearch('pok:', 100);
11fdf7f2
TL
175 });
176
177 it('should restore full table after search', () => {
178 expect(component.rows.length).toBe(100);
179 component.search = '13';
180 component.updateFilter();
181 expect(component.rows.length).toBe(9);
182 component.updateFilter(true);
183 expect(component.rows.length).toBe(100);
184 });
185 });
186 });
187
188 describe('after ngInit', () => {
189 const toggleColumn = (prop, checked) => {
190 component.toggleColumn({
191 target: {
192 name: prop,
193 checked: checked
194 }
195 });
196 };
197
198 const equalStorageConfig = () => {
199 expect(JSON.stringify(component.userConfig)).toBe(
200 component.localStorage.getItem(component.tableName)
201 );
202 };
203
204 beforeEach(() => {
205 component.ngOnInit();
206 });
207
208 it('should have updated the column definitions', () => {
209 expect(component.columns[0].flexGrow).toBe(1);
210 expect(component.columns[1].flexGrow).toBe(2);
211 expect(component.columns[2].flexGrow).toBe(2);
212 expect(component.columns[2].resizeable).toBe(false);
213 });
214
215 it('should have table columns', () => {
216 expect(component.tableColumns.length).toBe(4);
217 expect(component.tableColumns).toEqual(component.columns);
218 });
219
220 it('should have a unique identifier which it searches for', () => {
221 expect(component.identifier).toBe('a');
222 expect(component.userConfig.sorts[0].prop).toBe('a');
223 expect(component.userConfig.sorts).toEqual(component.createSortingDefinition('a'));
224 equalStorageConfig();
225 });
226
227 it('should remove column "a"', () => {
228 expect(component.userConfig.sorts[0].prop).toBe('a');
229 toggleColumn('a', false);
230 expect(component.userConfig.sorts[0].prop).toBe('b');
231 expect(component.tableColumns.length).toBe(3);
232 equalStorageConfig();
233 });
234
235 it('should not be able to remove all columns', () => {
236 expect(component.userConfig.sorts[0].prop).toBe('a');
237 toggleColumn('a', false);
238 toggleColumn('b', false);
239 toggleColumn('c', false);
240 toggleColumn('d', false);
241 expect(component.userConfig.sorts[0].prop).toBe('d');
242 expect(component.tableColumns.length).toBe(1);
243 equalStorageConfig();
244 });
245
246 it('should enable column "a" again', () => {
247 expect(component.userConfig.sorts[0].prop).toBe('a');
248 toggleColumn('a', false);
249 toggleColumn('a', true);
250 expect(component.userConfig.sorts[0].prop).toBe('b');
251 expect(component.tableColumns.length).toBe(4);
252 equalStorageConfig();
253 });
254
255 afterEach(() => {
256 clearLocalStorage();
257 });
258 });
259
260 describe('reload data', () => {
261 beforeEach(() => {
262 component.ngOnInit();
263 component.data = [];
264 component['updating'] = false;
265 });
266
267 it('should call fetchData callback function', () => {
268 component.fetchData.subscribe((context) => {
269 expect(context instanceof CdTableFetchDataContext).toBeTruthy();
270 });
271 component.reloadData();
272 });
273
274 it('should call error function', () => {
275 component.data = createFakeData(5);
276 component.fetchData.subscribe((context) => {
277 context.error();
278 expect(component.loadingError).toBeTruthy();
279 expect(component.data.length).toBe(0);
280 expect(component.loadingIndicator).toBeFalsy();
281 expect(component['updating']).toBeFalsy();
282 });
283 component.reloadData();
284 });
285
286 it('should call error function with custom config', () => {
287 component.data = createFakeData(10);
288 component.fetchData.subscribe((context) => {
289 context.errorConfig.resetData = false;
290 context.errorConfig.displayError = false;
291 context.error();
292 expect(component.loadingError).toBeFalsy();
293 expect(component.data.length).toBe(10);
294 expect(component.loadingIndicator).toBeFalsy();
295 expect(component['updating']).toBeFalsy();
296 });
297 component.reloadData();
298 });
299
300 it('should update selection on refresh - "onChange"', () => {
301 spyOn(component, 'onSelect').and.callThrough();
302 component.data = createFakeData(10);
303 component.selection.selected = [_.clone(component.data[1])];
304 component.updateSelectionOnRefresh = 'onChange';
305 component.updateSelected();
306 expect(component.onSelect).toHaveBeenCalledTimes(0);
307 component.data[1].d = !component.data[1].d;
308 component.updateSelected();
309 expect(component.onSelect).toHaveBeenCalled();
310 });
311
312 it('should update selection on refresh - "always"', () => {
313 spyOn(component, 'onSelect').and.callThrough();
314 component.data = createFakeData(10);
315 component.selection.selected = [_.clone(component.data[1])];
316 component.updateSelectionOnRefresh = 'always';
317 component.updateSelected();
318 expect(component.onSelect).toHaveBeenCalled();
319 component.data[1].d = !component.data[1].d;
320 component.updateSelected();
321 expect(component.onSelect).toHaveBeenCalled();
322 });
323
324 it('should update selection on refresh - "never"', () => {
325 spyOn(component, 'onSelect').and.callThrough();
326 component.data = createFakeData(10);
327 component.selection.selected = [_.clone(component.data[1])];
328 component.updateSelectionOnRefresh = 'never';
329 component.updateSelected();
330 expect(component.onSelect).toHaveBeenCalledTimes(0);
331 component.data[1].d = !component.data[1].d;
332 component.updateSelected();
333 expect(component.onSelect).toHaveBeenCalledTimes(0);
334 });
335
336 afterEach(() => {
337 clearLocalStorage();
338 });
339 });
340
341 describe('useCustomClass', () => {
342 beforeEach(() => {
343 component.customCss = {
344 'label label-danger': 'active',
345 'secret secret-number': 123.456,
346 'btn btn-sm': (v) => _.isString(v) && v.startsWith('http'),
347 secure: (v) => _.isString(v) && v.startsWith('https')
348 };
349 });
350
351 it('should throw an error if custom classes are not set', () => {
352 component.customCss = undefined;
353 expect(() => component.useCustomClass('active')).toThrowError('Custom classes are not set!');
354 });
355
356 it('should not return any class', () => {
357 ['', 'something', 123, { complex: 1 }, [1, 2, 3]].forEach((value) =>
358 expect(component.useCustomClass(value)).toBe(undefined)
359 );
360 });
361
362 it('should match a string and return the corresponding class', () => {
363 expect(component.useCustomClass('active')).toBe('label label-danger');
364 });
365
366 it('should match a number and return the corresponding class', () => {
367 expect(component.useCustomClass(123.456)).toBe('secret secret-number');
368 });
369
370 it('should match against a function and return the corresponding class', () => {
371 expect(component.useCustomClass('http://no.ssl')).toBe('btn btn-sm');
372 });
373
374 it('should match against multiple functions and return the corresponding classes', () => {
375 expect(component.useCustomClass('https://secure.it')).toBe('btn btn-sm secure');
376 });
377 });
378});