]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.spec.ts
buildsys: use download.ceph.com to download source tar ball
[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
90 it('should force an identifier', () => {
91 clearLocalStorage();
92 component.identifier = 'x';
93 component.forceIdentifier = true;
94 component.ngOnInit();
95 expect(component.identifier).toBe('x');
96 expect(component.sorts[0].prop).toBe('a');
97 expect(component.sorts).toEqual(component.createSortingDefinition('a'));
98 });
99
100 describe('test search', () => {
101 const doSearch = (search: string, expectedLength: number, firstObject: object) => {
102 component.search = search;
103 component.updateFilter();
104 expect(component.rows.length).toBe(expectedLength);
105 expect(component.rows[0]).toEqual(firstObject);
106 };
107
108 it('should search for 13', () => {
109 doSearch('13', 9, { a: 7, b: 49, c: [-7, 'score13'], d: false });
110 expect(component.rows[1].a).toBe(13);
111 expect(component.rows[8].a).toBe(87);
112 });
113
114 it('should search for true', () => {
115 doSearch('true', 50, { a: 0, b: 0, c: [-0, 'score6'], d: true });
116 expect(component.rows[0].d).toBe(true);
117 expect(component.rows[1].d).toBe(true);
118 });
119
120 it('should search for false', () => {
121 doSearch('false', 50, { a: 1, b: 1, c: [-1, 'score7'], d: false });
122 expect(component.rows[0].d).toBe(false);
123 expect(component.rows[1].d).toBe(false);
124 });
125
126 it('should test search manipulation', () => {
127 let searchTerms = [];
128 spyOn(component, 'subSearch').and.callFake((_d, search) => {
129 expect(search).toEqual(searchTerms);
130 });
131 const searchTest = (s: string, st: string[]) => {
132 component.search = s;
133 searchTerms = st;
134 component.updateFilter();
135 };
136 searchTest('a b c', ['a', 'b', 'c']);
137 searchTest('a+b c', ['a+b', 'c']);
138 searchTest('a,,,, b,,, c', ['a', 'b', 'c']);
139 searchTest('a,,,+++b,,, c', ['a+++b', 'c']);
140 searchTest('"a b c" "d e f", "g, h i"', ['a+b+c', 'd+e++f', 'g+h+i']);
141 });
142
143 it('should search for multiple values', () => {
144 doSearch('7 5 3', 5, { a: 57, b: 3249, c: [-7, 'score15'], d: false });
145 });
146
147 it('should search with column filter', () => {
148 doSearch('power:1369', 1, { a: 37, b: 1369, c: [-7, 'score11'], d: false });
149 doSearch('ndex:7 ofa:5 poker:3', 3, { a: 71, b: 5041, c: [-1, 'score13'], d: false });
150 });
151
152 it('should search with through array', () => {
153 doSearch('array:score21', 6, { a: 15, b: 225, c: [-5, 'score21'], d: false });
154 });
155
156 it('should search with spaces', () => {
157 doSearch(`'poker array':score21`, 6, { a: 15, b: 225, c: [-5, 'score21'], d: false });
158 doSearch('"poker array":score21', 6, { a: 15, b: 225, c: [-5, 'score21'], d: false });
159 doSearch('poker+array:score21', 6, { a: 15, b: 225, c: [-5, 'score21'], d: false });
160 });
161
162 it('should not search if column name is incomplete', () => {
163 doSearch(`'poker array'`, 100, { a: 0, b: 0, c: [-0, 'score6'], d: true });
164 doSearch('pok', 100, { a: 0, b: 0, c: [-0, 'score6'], d: true });
165 doSearch('pok:', 100, { a: 0, b: 0, c: [-0, 'score6'], d: true });
166 });
167
168 it('should restore full table after search', () => {
169 expect(component.rows.length).toBe(100);
170 component.search = '13';
171 component.updateFilter();
172 expect(component.rows.length).toBe(9);
173 component.updateFilter(true);
174 expect(component.rows.length).toBe(100);
175 });
176 });
177 });
178
179 describe('after ngInit', () => {
180 const toggleColumn = (prop, checked) => {
181 component.toggleColumn({
182 target: {
183 name: prop,
184 checked: checked
185 }
186 });
187 };
188
189 const equalStorageConfig = () => {
190 expect(JSON.stringify(component.userConfig)).toBe(
191 component.localStorage.getItem(component.tableName)
192 );
193 };
194
195 beforeEach(() => {
196 component.ngOnInit();
197 });
198
199 it('should have updated the column definitions', () => {
200 expect(component.columns[0].flexGrow).toBe(1);
201 expect(component.columns[1].flexGrow).toBe(2);
202 expect(component.columns[2].flexGrow).toBe(2);
203 expect(component.columns[2].resizeable).toBe(false);
204 });
205
206 it('should have table columns', () => {
207 expect(component.tableColumns.length).toBe(4);
208 expect(component.tableColumns).toEqual(component.columns);
209 });
210
211 it('should have a unique identifier which it searches for', () => {
212 expect(component.identifier).toBe('a');
213 expect(component.userConfig.sorts[0].prop).toBe('a');
214 expect(component.userConfig.sorts).toEqual(component.createSortingDefinition('a'));
215 equalStorageConfig();
216 });
217
218 it('should remove column "a"', () => {
219 expect(component.userConfig.sorts[0].prop).toBe('a');
220 toggleColumn('a', false);
221 expect(component.userConfig.sorts[0].prop).toBe('b');
222 expect(component.tableColumns.length).toBe(3);
223 equalStorageConfig();
224 });
225
226 it('should not be able to remove all columns', () => {
227 expect(component.userConfig.sorts[0].prop).toBe('a');
228 toggleColumn('a', false);
229 toggleColumn('b', false);
230 toggleColumn('c', false);
231 toggleColumn('d', false);
232 expect(component.userConfig.sorts[0].prop).toBe('d');
233 expect(component.tableColumns.length).toBe(1);
234 equalStorageConfig();
235 });
236
237 it('should enable column "a" again', () => {
238 expect(component.userConfig.sorts[0].prop).toBe('a');
239 toggleColumn('a', false);
240 toggleColumn('a', true);
241 expect(component.userConfig.sorts[0].prop).toBe('b');
242 expect(component.tableColumns.length).toBe(4);
243 equalStorageConfig();
244 });
245
246 afterEach(() => {
247 clearLocalStorage();
248 });
249 });
250
251 describe('reload data', () => {
252 beforeEach(() => {
253 component.ngOnInit();
254 component.data = [];
255 component['updating'] = false;
256 });
257
258 it('should call fetchData callback function', () => {
259 component.fetchData.subscribe((context) => {
260 expect(context instanceof CdTableFetchDataContext).toBeTruthy();
261 });
262 component.reloadData();
263 });
264
265 it('should call error function', () => {
266 component.data = createFakeData(5);
267 component.fetchData.subscribe((context) => {
268 context.error();
269 expect(component.loadingError).toBeTruthy();
270 expect(component.data.length).toBe(0);
271 expect(component.loadingIndicator).toBeFalsy();
272 expect(component['updating']).toBeFalsy();
273 });
274 component.reloadData();
275 });
276
277 it('should call error function with custom config', () => {
278 component.data = createFakeData(10);
279 component.fetchData.subscribe((context) => {
280 context.errorConfig.resetData = false;
281 context.errorConfig.displayError = false;
282 context.error();
283 expect(component.loadingError).toBeFalsy();
284 expect(component.data.length).toBe(10);
285 expect(component.loadingIndicator).toBeFalsy();
286 expect(component['updating']).toBeFalsy();
287 });
288 component.reloadData();
289 });
290
291 it('should update selection on refresh - "onChange"', () => {
292 spyOn(component, 'onSelect').and.callThrough();
293 component.data = createFakeData(10);
294 component.selection.selected = [_.clone(component.data[1])];
295 component.updateSelectionOnRefresh = 'onChange';
296 component.updateSelected();
297 expect(component.onSelect).toHaveBeenCalledTimes(0);
298 component.data[1].d = !component.data[1].d;
299 component.updateSelected();
300 expect(component.onSelect).toHaveBeenCalled();
301 });
302
303 it('should update selection on refresh - "always"', () => {
304 spyOn(component, 'onSelect').and.callThrough();
305 component.data = createFakeData(10);
306 component.selection.selected = [_.clone(component.data[1])];
307 component.updateSelectionOnRefresh = 'always';
308 component.updateSelected();
309 expect(component.onSelect).toHaveBeenCalled();
310 component.data[1].d = !component.data[1].d;
311 component.updateSelected();
312 expect(component.onSelect).toHaveBeenCalled();
313 });
314
315 it('should update selection on refresh - "never"', () => {
316 spyOn(component, 'onSelect').and.callThrough();
317 component.data = createFakeData(10);
318 component.selection.selected = [_.clone(component.data[1])];
319 component.updateSelectionOnRefresh = 'never';
320 component.updateSelected();
321 expect(component.onSelect).toHaveBeenCalledTimes(0);
322 component.data[1].d = !component.data[1].d;
323 component.updateSelected();
324 expect(component.onSelect).toHaveBeenCalledTimes(0);
325 });
326
327 afterEach(() => {
328 clearLocalStorage();
329 });
330 });
331
332 describe('useCustomClass', () => {
333 beforeEach(() => {
334 component.customCss = {
335 'label label-danger': 'active',
336 'secret secret-number': 123.456,
337 'btn btn-sm': (v) => _.isString(v) && v.startsWith('http'),
338 secure: (v) => _.isString(v) && v.startsWith('https')
339 };
340 });
341
342 it('should throw an error if custom classes are not set', () => {
343 component.customCss = undefined;
344 expect(() => component.useCustomClass('active')).toThrowError('Custom classes are not set!');
345 });
346
347 it('should not return any class', () => {
348 ['', 'something', 123, { complex: 1 }, [1, 2, 3]].forEach((value) =>
349 expect(component.useCustomClass(value)).toBe(undefined)
350 );
351 });
352
353 it('should match a string and return the corresponding class', () => {
354 expect(component.useCustomClass('active')).toBe('label label-danger');
355 });
356
357 it('should match a number and return the corresponding class', () => {
358 expect(component.useCustomClass(123.456)).toBe('secret secret-number');
359 });
360
361 it('should match against a function and return the corresponding class', () => {
362 expect(component.useCustomClass('http://no.ssl')).toBe('btn btn-sm');
363 });
364
365 it('should match against multiple functions and return the corresponding classes', () => {
366 expect(component.useCustomClass('https://secure.it')).toBe('btn btn-sm secure');
367 });
368 });
369});