-import { LOCALE_ID, TRANSLATIONS, TRANSLATIONS_FORMAT, Type } from '@angular/core';
+import { DebugElement, Type } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AbstractControl } from '@angular/forms';
import { By } from '@angular/platform-browser';
+import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
-import { I18n } from '@ngx-translate/i18n-polyfill';
+import { NgbModal, NgbNav, NgbNavItem } from '@ng-bootstrap/ng-bootstrap';
+import _ from 'lodash';
import { configureTestSuite } from 'ng-bullet';
-import { BsModalRef } from 'ngx-bootstrap/modal';
-
-import { TableActionsComponent } from '../app/shared/datatable/table-actions/table-actions.component';
-import { Icons } from '../app/shared/enum/icons.enum';
-import { CdFormGroup } from '../app/shared/forms/cd-form-group';
-import { CdTableAction } from '../app/shared/models/cd-table-action';
-import { CdTableSelection } from '../app/shared/models/cd-table-selection';
-import { CrushNode } from '../app/shared/models/crush-node';
-import { CrushRule, CrushRuleConfig } from '../app/shared/models/crush-rule';
-import { Permission } from '../app/shared/models/permissions';
+import { of } from 'rxjs';
+
+import { InventoryDevice } from '~/app/ceph/cluster/inventory/inventory-devices/inventory-device.model';
+import { Pool } from '~/app/ceph/pool/pool';
+import { RgwDaemon } from '~/app/ceph/rgw/models/rgw-daemon';
+import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
+import { RgwDaemonService } from '~/app/shared/api/rgw-daemon.service';
+import { TableActionsComponent } from '~/app/shared/datatable/table-actions/table-actions.component';
+import { Icons } from '~/app/shared/enum/icons.enum';
+import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+import { CrushNode } from '~/app/shared/models/crush-node';
+import { CrushRule, CrushRuleConfig } from '~/app/shared/models/crush-rule';
+import { OrchestratorFeature } from '~/app/shared/models/orchestrator.enum';
+import { Permission } from '~/app/shared/models/permissions';
import {
AlertmanagerAlert,
AlertmanagerNotification,
AlertmanagerNotificationAlert,
PrometheusRule
-} from '../app/shared/models/prometheus-alerts';
-
-export function configureTestBed(configuration: any) {
- configureTestSuite(() => TestBed.configureTestingModule(configuration));
+} from '~/app/shared/models/prometheus-alerts';
+
+export function configureTestBed(configuration: any, entryComponents?: any) {
+ configureTestSuite(() => {
+ if (entryComponents) {
+ // Declare entryComponents without having to add them to a module
+ // This is needed since Jest doesn't yet support not declaring entryComponents
+ TestBed.configureTestingModule(configuration).overrideModule(BrowserDynamicTestingModule, {
+ set: { entryComponents: entryComponents }
+ });
+ } else {
+ TestBed.configureTestingModule(configuration);
+ }
+ });
}
export class PermissionHelper {
tac: TableActionsComponent;
permission: Permission;
+ selection: { single: object; multiple: object[] };
- constructor(permission: Permission) {
+ /**
+ * @param permission The permissions used by this test.
+ * @param selection The selection used by this test. Configure this if
+ * the table actions require a more complex selection object to perform
+ * a correct test run.
+ * Defaults to `{ single: {}, multiple: [{}, {}] }`.
+ */
+ constructor(permission: Permission, selection?: { single: object; multiple: object[] }) {
this.permission = permission;
+ this.selection = _.defaultTo(selection, { single: {}, multiple: [{}, {}] });
}
setPermissionsAndGetActions(tableActions: CdTableAction[]): any {
testScenarios() {
const result: any = {};
// 'multiple selections'
- result.multiple = this.testScenario([{}, {}]);
+ result.multiple = this.testScenario(this.selection.multiple);
// 'select executing item'
- result.executing = this.testScenario([{ cdExecuting: 'someAction' }]);
+ result.executing = this.testScenario([
+ _.merge({ cdExecuting: 'someAction' }, this.selection.single)
+ ]);
// 'select non-executing item'
- result.single = this.testScenario([{}]);
+ result.single = this.testScenario([this.selection.single]);
// 'no selection'
result.no = this.testScenario([]);
private testScenario(selection: object[]) {
this.setSelection(selection);
- const btn = this.tac.getCurrentButton();
- return btn ? btn.name : '';
+ const action: CdTableAction = this.tac.currentAction;
+ return action ? action.name : '';
}
setSelection(selection: object[]) {
this.tac.selection.selected = selection;
+ this.tac.onSelectionChange();
}
}
}
/**
- * Use this to mock 'ModalService.show' to make the embedded component with it's fixture usable
+ * Use this to mock 'modalService.open' to make the embedded component with it's fixture usable
* in tests. The function gives back all needed parts including the modal reference.
*
* Please make sure to call this function *inside* your mock and return the reference at the end.
*/
export function modalServiceShow(componentClass: Type<any>, modalConfig: any) {
- const ref = new BsModalRef();
- const fixture = TestBed.createComponent(componentClass);
- let component = fixture.componentInstance;
- if (modalConfig.initialState) {
- component = Object.assign(component, modalConfig.initialState);
- }
- fixture.detectChanges();
- ref.content = component;
- return { ref, fixture, component };
+ const modal: NgbModal = TestBed.inject(NgbModal);
+ const modalRef = modal.open(componentClass);
+ if (modalConfig) {
+ Object.assign(modalRef.componentInstance, modalConfig);
+ }
+ return modalRef;
}
export class FixtureHelper {
severity: 'someSeverity'
},
annotations: {
- summary: `${name} is ${state}`
+ description: `${name} is ${state}`
},
generatorURL: `http://${name}`,
startsAt: new Date(new Date('2022-02-22').getTime() * timeMultiplier).toString()
alertname: name
},
annotations: {
- summary: `${name} is ${status}`
+ description: `${name} is ${status}`
},
generatorURL: `http://${name}`
} as AlertmanagerNotificationAlert;
}
}
-const XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
-<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
- <file source-language="en" datatype="plaintext" original="ng2.template">
- <body>
- </body>
- </file>
-</xliff>
-`;
-
-const i18nProviders = [
- { provide: TRANSLATIONS_FORMAT, useValue: 'xlf' },
- { provide: TRANSLATIONS, useValue: XLIFF },
- { provide: LOCALE_ID, useValue: 'en' },
- I18n
-];
-
-export { i18nProviders };
-
export function expectItemTasks(item: any, executing: string, percentage?: number) {
if (executing) {
executing = executing + '...';
}
}
+export class RgwHelper {
+ static readonly daemons = RgwHelper.getDaemonList();
+ static readonly DAEMON_QUERY_PARAM = `daemon_name=${RgwHelper.daemons[0].id}`;
+
+ static getDaemonList() {
+ const daemonList: RgwDaemon[] = [];
+ for (let daemonIndex = 1; daemonIndex <= 3; daemonIndex++) {
+ const rgwDaemon = new RgwDaemon();
+ rgwDaemon.id = `daemon${daemonIndex}`;
+ rgwDaemon.default = daemonIndex === 2;
+ rgwDaemon.zonegroup_name = `zonegroup${daemonIndex}`;
+ daemonList.push(rgwDaemon);
+ }
+ return daemonList;
+ }
+
+ static selectDaemon() {
+ const service = TestBed.inject(RgwDaemonService);
+ service.selectDaemon(this.daemons[0]);
+ }
+}
+
export class Mocks {
static getCrushNode(
name: string,
return { name, type, type_id, id, children, device_class };
}
+ static getPool = (name: string, id: number): Pool => {
+ return _.merge(new Pool(name), {
+ pool: id,
+ type: 'replicated',
+ pg_num: 256,
+ pg_placement_num: 256,
+ pg_num_target: 256,
+ pg_placement_num_target: 256,
+ size: 3
+ });
+ };
+
/**
* Create the following test crush map:
* > default
];
return rule;
}
+
+ static getInventoryDevice(
+ hostname: string,
+ uid: string,
+ path = 'sda',
+ available = false
+ ): InventoryDevice {
+ return {
+ hostname,
+ uid,
+ path,
+ available,
+ sys_api: {
+ vendor: 'AAA',
+ model: 'aaa',
+ size: 1024,
+ rotational: 'false',
+ human_readable_size: '1 KB'
+ },
+ rejected_reasons: [''],
+ device_id: 'AAA-aaa-id0',
+ human_readable_type: 'nvme/ssd',
+ osd_ids: []
+ };
+ }
+}
+
+export class TabHelper {
+ static getNgbNav(fixture: ComponentFixture<any>) {
+ const debugElem: DebugElement = fixture.debugElement;
+ return debugElem.query(By.directive(NgbNav)).injector.get(NgbNav);
+ }
+
+ static getNgbNavItems(fixture: ComponentFixture<any>) {
+ const debugElems = this.getNgbNavItemsDebugElems(fixture);
+ return debugElems.map((de) => de.injector.get(NgbNavItem));
+ }
+
+ static getTextContents(fixture: ComponentFixture<any>) {
+ const debugElems = this.getNgbNavItemsDebugElems(fixture);
+ return debugElems.map((de) => de.nativeElement.textContent);
+ }
+
+ private static getNgbNavItemsDebugElems(fixture: ComponentFixture<any>) {
+ const debugElem: DebugElement = fixture.debugElement;
+ return debugElem.queryAll(By.directive(NgbNavItem));
+ }
+}
+
+export class OrchestratorHelper {
+ /**
+ * Mock Orchestrator status.
+ * @param available is the Orchestrator enabled?
+ * @param features A list of enabled Orchestrator features.
+ */
+ static mockStatus(available: boolean, features?: OrchestratorFeature[]) {
+ const orchStatus = { available: available, description: '', features: {} };
+ if (features) {
+ features.forEach((feature: OrchestratorFeature) => {
+ orchStatus.features[feature] = { available: true };
+ });
+ }
+ spyOn(TestBed.inject(OrchestratorService), 'status').and.callFake(() => of(orchStatus));
+ }
+}
+
+export class TableActionHelper {
+ /**
+ * Verify table action buttons, including the button disabled state and disable description.
+ *
+ * @param fixture test fixture
+ * @param tableActions table actions
+ * @param expectResult expected values. e.g. {Create: { disabled: true, disableDesc: 'not supported'}}.
+ * Expect the Create button to be disabled with 'not supported' tooltip.
+ */
+ static verifyTableActions = async (
+ fixture: ComponentFixture<any>,
+ tableActions: CdTableAction[],
+ expectResult: {
+ [action: string]: { disabled: boolean; disableDesc: string };
+ }
+ ) => {
+ // click dropdown to update all actions buttons
+ const dropDownToggle = fixture.debugElement.query(By.css('.dropdown-toggle'));
+ dropDownToggle.triggerEventHandler('click', null);
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ const tableActionElement = fixture.debugElement.query(By.directive(TableActionsComponent));
+ const toClassName = TestBed.inject(TableActionsComponent).toClassName;
+ const getActionElement = (action: CdTableAction) =>
+ tableActionElement.query(By.css(`[ngbDropdownItem].${toClassName(action)}`));
+
+ const actions = {};
+ tableActions.forEach((action) => {
+ const actionElement = getActionElement(action);
+ if (expectResult[action.name]) {
+ actions[action.name] = {
+ disabled: actionElement.classes.disabled,
+ disableDesc: actionElement.properties.title
+ };
+ }
+ });
+ expect(actions).toEqual(expectResult);
+ };
}