]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/frontend/src/testing/unit-test-helper.ts
bump version to 15.2.1-pve1
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / testing / unit-test-helper.ts
CommitLineData
9f95a23c 1import { LOCALE_ID, TRANSLATIONS, TRANSLATIONS_FORMAT, Type } from '@angular/core';
11fdf7f2
TL
2import { async, ComponentFixture, TestBed } from '@angular/core/testing';
3import { AbstractControl } from '@angular/forms';
4import { By } from '@angular/platform-browser';
5
6import { I18n } from '@ngx-translate/i18n-polyfill';
9f95a23c 7import { BsModalRef } from 'ngx-bootstrap/modal';
11fdf7f2
TL
8
9import { TableActionsComponent } from '../app/shared/datatable/table-actions/table-actions.component';
9f95a23c 10import { Icons } from '../app/shared/enum/icons.enum';
11fdf7f2 11import { CdFormGroup } from '../app/shared/forms/cd-form-group';
9f95a23c
TL
12import { CdTableAction } from '../app/shared/models/cd-table-action';
13import { CdTableSelection } from '../app/shared/models/cd-table-selection';
11fdf7f2
TL
14import { Permission } from '../app/shared/models/permissions';
15import {
494da23a
TL
16 AlertmanagerAlert,
17 AlertmanagerNotification,
18 AlertmanagerNotificationAlert,
19 PrometheusRule
11fdf7f2
TL
20} from '../app/shared/models/prometheus-alerts';
21import { _DEV_ } from '../unit-test-configuration';
22
9f95a23c 23export function configureTestBed(configuration: any, useOldMethod?: boolean) {
11fdf7f2
TL
24 if (_DEV_ && !useOldMethod) {
25 const resetTestingModule = TestBed.resetTestingModule;
26 beforeAll((done) =>
27 (async () => {
28 TestBed.resetTestingModule();
29 TestBed.configureTestingModule(configuration);
30 // prevent Angular from resetting testing module
31 TestBed.resetTestingModule = () => TestBed;
32 })()
33 .then(done)
34 .catch(done.fail)
35 );
36 afterAll(() => {
37 TestBed.resetTestingModule = resetTestingModule;
38 });
39 } else {
40 beforeEach(async(() => {
41 TestBed.configureTestingModule(configuration);
42 }));
43 }
44}
45
46export class PermissionHelper {
9f95a23c 47 tac: TableActionsComponent;
11fdf7f2 48 permission: Permission;
11fdf7f2 49
9f95a23c 50 constructor(permission: Permission) {
11fdf7f2 51 this.permission = permission;
11fdf7f2
TL
52 }
53
9f95a23c
TL
54 setPermissionsAndGetActions(tableActions: CdTableAction[]): any {
55 const result = {};
56 [true, false].forEach((create) => {
57 [true, false].forEach((update) => {
58 [true, false].forEach((deleteP) => {
59 this.permission.create = create;
60 this.permission.update = update;
61 this.permission.delete = deleteP;
62
63 this.tac = new TableActionsComponent();
64 this.tac.selection = new CdTableSelection();
65 this.tac.tableActions = [...tableActions];
66 this.tac.permission = this.permission;
67 this.tac.ngOnInit();
68
69 const perms = [];
70 if (create) {
71 perms.push('create');
72 }
73 if (update) {
74 perms.push('update');
75 }
76 if (deleteP) {
77 perms.push('delete');
78 }
79 const permissionText = perms.join(',');
80
81 result[permissionText !== '' ? permissionText : 'no-permissions'] = {
82 actions: this.tac.tableActions.map((action) => action.name),
83 primary: this.testScenarios()
84 };
85 });
86 });
87 });
88
89 return result;
90 }
91
92 testScenarios() {
93 const result: any = {};
94 // 'multiple selections'
95 result.multiple = this.testScenario([{}, {}]);
96 // 'select executing item'
97 result.executing = this.testScenario([{ cdExecuting: 'someAction' }]);
98 // 'select non-executing item'
99 result.single = this.testScenario([{}]);
100 // 'no selection'
101 result.no = this.testScenario([]);
102
103 return result;
104 }
105
106 private testScenario(selection: object[]) {
11fdf7f2 107 this.setSelection(selection);
9f95a23c
TL
108 const btn = this.tac.getCurrentButton();
109 return btn ? btn.name : '';
11fdf7f2
TL
110 }
111
112 setSelection(selection: object[]) {
9f95a23c 113 this.tac.selection.selected = selection;
11fdf7f2
TL
114 }
115}
116
117export class FormHelper {
118 form: CdFormGroup;
119
120 constructor(form: CdFormGroup) {
121 this.form = form;
122 }
123
124 /**
125 * Changes multiple values in multiple controls
126 */
127 setMultipleValues(values: { [controlName: string]: any }, markAsDirty?: boolean) {
128 Object.keys(values).forEach((key) => {
129 this.setValue(key, values[key], markAsDirty);
130 });
131 }
132
133 /**
134 * Changes the value of a control
135 */
136 setValue(control: AbstractControl | string, value: any, markAsDirty?: boolean): AbstractControl {
137 control = this.getControl(control);
138 if (markAsDirty) {
139 control.markAsDirty();
140 }
141 control.setValue(value);
142 return control;
143 }
144
145 private getControl(control: AbstractControl | string): AbstractControl {
146 if (typeof control === 'string') {
147 return this.form.get(control);
148 }
149 return control;
150 }
151
152 /**
153 * Change the value of the control and expect the control to be valid afterwards.
154 */
155 expectValidChange(control: AbstractControl | string, value: any, markAsDirty?: boolean) {
156 this.expectValid(this.setValue(control, value, markAsDirty));
157 }
158
159 /**
160 * Expect that the given control is valid.
161 */
162 expectValid(control: AbstractControl | string) {
163 // 'isValid' would be false for disabled controls
164 expect(this.getControl(control).errors).toBe(null);
165 }
166
167 /**
168 * Change the value of the control and expect a specific error.
169 */
170 expectErrorChange(
171 control: AbstractControl | string,
172 value: any,
173 error: string,
174 markAsDirty?: boolean
175 ) {
176 this.expectError(this.setValue(control, value, markAsDirty), error);
177 }
178
179 /**
180 * Expect a specific error for the given control.
181 */
182 expectError(control: AbstractControl | string, error: string) {
183 expect(this.getControl(control).hasError(error)).toBeTruthy();
184 }
185}
186
9f95a23c
TL
187/**
188 * Use this to mock 'ModalService.show' to make the embedded component with it's fixture usable
189 * in tests. The function gives back all needed parts including the modal reference.
190 *
191 * Please make sure to call this function *inside* your mock and return the reference at the end.
192 */
193export function modalServiceShow(componentClass: Type<any>, modalConfig: any) {
194 const ref = new BsModalRef();
195 const fixture = TestBed.createComponent(componentClass);
196 let component = fixture.componentInstance;
197 if (modalConfig.initialState) {
198 component = Object.assign(component, modalConfig.initialState);
199 }
200 fixture.detectChanges();
201 ref.content = component;
202 return { ref, fixture, component };
203}
204
11fdf7f2
TL
205export class FixtureHelper {
206 fixture: ComponentFixture<any>;
207
9f95a23c
TL
208 constructor(fixture?: ComponentFixture<any>) {
209 if (fixture) {
210 this.updateFixture(fixture);
211 }
212 }
213
214 updateFixture(fixture: ComponentFixture<any>) {
11fdf7f2
TL
215 this.fixture = fixture;
216 }
217
218 /**
219 * Expect a list of id elements to be visible or not.
220 */
221 expectIdElementsVisible(ids: string[], visibility: boolean) {
222 ids.forEach((css) => {
223 this.expectElementVisible(`#${css}`, visibility);
224 });
225 }
226
227 /**
228 * Expect a specific element to be visible or not.
229 */
230 expectElementVisible(css: string, visibility: boolean) {
9f95a23c 231 expect(visibility).toBe(Boolean(this.getElementByCss(css)));
11fdf7f2
TL
232 }
233
234 expectFormFieldToBe(css: string, value: string) {
235 const props = this.getElementByCss(css).properties;
236 expect(props['value'] || props['checked'].toString()).toBe(value);
237 }
238
9f95a23c
TL
239 expectTextToBe(css: string, value: string) {
240 expect(this.getText(css)).toBe(value);
241 }
242
11fdf7f2
TL
243 clickElement(css: string) {
244 this.getElementByCss(css).triggerEventHandler('click', null);
245 this.fixture.detectChanges();
246 }
247
9f95a23c
TL
248 selectElement(css: string, value: string) {
249 const nativeElement = this.getElementByCss(css).nativeElement;
250 nativeElement.value = value;
251 nativeElement.dispatchEvent(new Event('change'));
252 this.fixture.detectChanges();
253 }
254
11fdf7f2
TL
255 getText(css: string) {
256 const e = this.getElementByCss(css);
257 return e ? e.nativeElement.textContent.trim() : null;
258 }
259
9f95a23c
TL
260 getTextAll(css: string) {
261 const elements = this.getElementByCssAll(css);
262 return elements.map((element) => {
263 return element ? element.nativeElement.textContent.trim() : null;
264 });
265 }
266
11fdf7f2
TL
267 getElementByCss(css: string) {
268 this.fixture.detectChanges();
269 return this.fixture.debugElement.query(By.css(css));
270 }
9f95a23c
TL
271
272 getElementByCssAll(css: string) {
273 this.fixture.detectChanges();
274 return this.fixture.debugElement.queryAll(By.css(css));
275 }
11fdf7f2
TL
276}
277
278export class PrometheusHelper {
9f95a23c 279 createSilence(id: string) {
494da23a
TL
280 return {
281 id: id,
282 createdBy: `Creator of ${id}`,
283 comment: `A comment for ${id}`,
284 startsAt: new Date('2022-02-22T22:22:00').toISOString(),
285 endsAt: new Date('2022-02-23T22:22:00').toISOString(),
286 matchers: [
287 {
288 name: 'job',
289 value: 'someJob',
290 isRegex: true
291 }
292 ]
293 };
294 }
295
9f95a23c 296 createRule(name: string, severity: string, alerts: any[]): PrometheusRule {
494da23a
TL
297 return {
298 name: name,
299 labels: {
300 severity: severity
301 },
302 alerts: alerts
303 } as PrometheusRule;
304 }
305
9f95a23c 306 createAlert(name: string, state = 'active', timeMultiplier = 1): AlertmanagerAlert {
11fdf7f2
TL
307 return {
308 fingerprint: name,
309 status: { state },
310 labels: {
494da23a
TL
311 alertname: name,
312 instance: 'someInstance',
313 job: 'someJob',
314 severity: 'someSeverity'
11fdf7f2
TL
315 },
316 annotations: {
317 summary: `${name} is ${state}`
318 },
319 generatorURL: `http://${name}`,
320 startsAt: new Date(new Date('2022-02-22').getTime() * timeMultiplier).toString()
494da23a 321 } as AlertmanagerAlert;
11fdf7f2
TL
322 }
323
9f95a23c 324 createNotificationAlert(name: string, status = 'firing'): AlertmanagerNotificationAlert {
11fdf7f2
TL
325 return {
326 status: status,
327 labels: {
328 alertname: name
329 },
330 annotations: {
331 summary: `${name} is ${status}`
332 },
333 generatorURL: `http://${name}`
494da23a 334 } as AlertmanagerNotificationAlert;
11fdf7f2
TL
335 }
336
494da23a 337 createNotification(alertNumber = 1, status = 'firing'): AlertmanagerNotification {
11fdf7f2
TL
338 const alerts = [];
339 for (let i = 0; i < alertNumber; i++) {
340 alerts.push(this.createNotificationAlert('alert' + i, status));
341 }
494da23a 342 return { alerts, status } as AlertmanagerNotification;
11fdf7f2
TL
343 }
344
9f95a23c
TL
345 createLink(url: string) {
346 return `<a href="${url}" target="_blank"><i class="${Icons.lineChart}"></i></a>`;
11fdf7f2
TL
347 }
348}
349
350const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
351<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
352 <file source-language="en" datatype="plaintext" original="ng2.template">
353 <body>
354 </body>
355 </file>
356</xliff>
357`;
358
359const i18nProviders = [
360 { provide: TRANSLATIONS_FORMAT, useValue: 'xlf' },
361 { provide: TRANSLATIONS, useValue: XLIFF },
362 { provide: LOCALE_ID, useValue: 'en' },
363 I18n
364];
365
366export { i18nProviders };
eafe8130
TL
367
368export function expectItemTasks(item: any, executing: string, percentage?: number) {
369 if (executing) {
370 executing = executing + '...';
371 if (percentage) {
372 executing = `${executing} ${percentage}%`;
373 }
374 }
375 expect(item.cdExecuting).toBe(executing);
376}