Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

user management - CRUD #310

Merged
merged 5 commits into from
Jul 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { fillFormField } from '../../framework';

declare interface B2BUserCreateForm {
title: string;
firstName: string;
lastName: string;
email: string;
phone: string;
}

export class UserCreatePage {
readonly tag = 'ish-user-create-page';

private submitButton = () => cy.get('[data-testing-id="create-user-submit"]');

fillForm(content: B2BUserCreateForm) {
Object.keys(content)
.filter(key => content[key] !== undefined)
.forEach((key: keyof B2BUserCreateForm) => {
fillFormField(this.tag, key, content[key]);
});

return this;
}

submit() {
this.submitButton().click();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { fillFormField } from '../../framework';

export class UserEditPage {
readonly tag = 'ish-user-edit-profile-page';

private submitButton = () => cy.get('[data-testing-id="edit-user-submit"]');

editTitle(val: string) {
fillFormField(this.tag, 'title', val);
}

editFirstName(val: string) {
fillFormField(this.tag, 'firstName', val);
}

submit() {
this.submitButton().click();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { HeaderModule } from '../header.module';

export class UsersDetailPage {
readonly tag = 'ish-users-detail-page';
readonly tag = 'ish-user-detail-page';

readonly header = new HeaderModule();

Expand All @@ -12,4 +12,12 @@ export class UsersDetailPage {
get email() {
return cy.get(this.tag).find('[data-testing-id="email-field"]');
}

editUser() {
return cy.get('[data-testing-id="edit-user"]').click();
}

goToUserManagement() {
cy.get('[data-testing-id="back-to-user-management"]').click();
}
}
15 changes: 15 additions & 0 deletions e2e/cypress/integration/pages/organizationmanagement/users.page.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { waitLoadingEnd } from '../../framework';
import { HeaderModule } from '../header.module';

export class UsersPage {
Expand All @@ -13,7 +14,21 @@ export class UsersPage {
return cy.get('div[data-testing-id="user-list"]');
}

goToUser(name: string) {
this.usersList.find('a').contains(name).click();
}

goToUserDetailLink(id: string) {
cy.get(`a[href="/account/organization/users/${id}"]`).first().click();
}

addUser() {
cy.get('a[data-testing-id="add-user-link"]').click();
}

deleteUser(name: string) {
cy.get(this.tag).contains('div.list-item-row', name).find('[data-testing-id="remove-user"]').click();
cy.get('[data-testing-id="confirm"]', { timeout: 2000 }).click();
waitLoadingEnd(2000);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('User Management', () => {
page.goToUserDetailLink(_.selectedUser.email);
});
at(UsersDetailPage, page => {
page.name.should('have.text', `${_.selectedUser.name}`);
page.name.should('contain', `${_.selectedUser.name}`);
page.email.should('have.text', `${_.selectedUser.email}`);
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { at } from '../../framework';
import { createB2BUserViaREST } from '../../framework/b2b-user';
import { LoginPage } from '../../pages/account/login.page';
import { sensibleDefaults } from '../../pages/account/registration.page';
import { UserCreatePage } from '../../pages/organizationmanagement/user-create.page';
import { UserEditPage } from '../../pages/organizationmanagement/user-edit.page';
import { UsersDetailPage } from '../../pages/organizationmanagement/users-detail.page';
import { UsersPage } from '../../pages/organizationmanagement/users.page';

// test for viewing functionality only

const _ = {
user: {
login: `test${new Date().getTime()}@testcity.de`,
...sensibleDefaults,
},
newUser: {
title: 'Mr.',
firstName: 'John',
lastName: 'Doe',
phone: '5551234',
email: `j.joe${new Date().getTime()}@testcity.de`,
},
editUser: {
title: 'Ms.',
firstName: 'Jane',
},
};

describe('User Management - CRUD', () => {
before(() => {
createB2BUserViaREST(_.user);
});

it('should start user management by logging in', () => {
LoginPage.navigateTo('/account/organization/users');
at(LoginPage, page => {
page.fillForm(_.user.login, _.user.password);
page.submit().its('status').should('equal', 200);
});
at(UsersPage, page => {
page.usersList.should('contain', `${_.user.firstName}`);
});
});

it('should be able to create a new user', () => {
at(UsersPage, page => page.addUser());
at(UserCreatePage, page => {
page.fillForm(_.newUser);
page.submit();
});
at(UsersPage, page => {
page.usersList.should('contain', `${_.newUser.firstName}`);
});
});

it('should be able to edit user', () => {
at(UsersPage, page => page.goToUser(`${_.newUser.firstName} ${_.newUser.lastName}`));
at(UsersDetailPage, page => {
page.name.should('contain', `${_.newUser.firstName} ${_.newUser.lastName}`);
page.editUser();
});
at(UserEditPage, page => {
page.editTitle(_.editUser.title);
page.editFirstName(_.editUser.firstName);
page.submit();
});
at(UsersDetailPage, page => {
page.name.should('contain', `${_.editUser.firstName} ${_.newUser.lastName}`);
page.goToUserManagement();
});
});

it('should be able to delete user', () => {
at(UsersPage, page => {
page.usersList.should('contain', `${_.editUser.firstName}`);
page.deleteUser(`${_.editUser.firstName} ${_.newUser.lastName}`);
page.usersList.should('not.contain', `${_.editUser.firstName}`);
page.usersList.should('contain', `${_.user.firstName}`);
});
});
});
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ module.exports = {
transformIgnorePatterns: [`node_modules/(?!${esModules.join('|')})`],
moduleNameMapper: {
'^ish-(.*)$': '<rootDir>/src/app/$1',
'^organization-management$': '<rootDir>/projects/organization-management/src/app/exports',
},
snapshotSerializers: [
'./src/jest-serializer/AngularHTMLSerializer.js',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<div>
<div *ngIf="error" role="alert" class="alert alert-danger">
<span>{{ error.headers['error-key'] | translate }}</span>
</div>
<p class="indicates-required"><span class="required">*</span>{{ 'account.required_field.message' | translate }}</p>
<fieldset>
<ish-select-title [form]="form" controlName="title" [titles]="titles"></ish-select-title>
<ish-input
[form]="form"
controlName="firstName"
label="account.address.firstname.label"
[errorMessages]="{
required: 'account.user.new.firstname.error.required'
}"
></ish-input>
<ish-input
[form]="form"
controlName="lastName"
label="account.address.lastname.label"
[errorMessages]="{
required: 'account.user.new.lastname.error.required'
}"
></ish-input>
</fieldset>
<fieldset *ngIf="form.value.email !== undefined">
<ish-input
[form]="form"
controlName="email"
label="account.user.email.label"
[errorMessages]="{
required: 'account.update_email.email.error.notempty',
email: 'account.update_email.email.error.email'
}"
></ish-input>
</fieldset>
<fieldset>
<ish-input [form]="form" controlName="phone" label="account.profile.phone.label"></ish-input>
</fieldset>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import { TranslateModule } from '@ngx-translate/core';
import { MockComponent } from 'ng-mocks';
import { CustomValidators } from 'ngx-custom-validators';
import { of } from 'rxjs';
import { instance, mock, when } from 'ts-mockito';

import { AppFacade } from 'ish-core/facades/app.facade';
import { Locale } from 'ish-core/models/locale/locale.model';
import { ErrorMessageComponent } from 'ish-shared/components/common/error-message/error-message.component';
import { InputComponent } from 'ish-shared/forms/components/input/input.component';
import { SelectTitleComponent } from 'ish-shared/forms/components/select-title/select-title.component';

import { UserProfileFormComponent } from './user-profile-form.component';

describe('User Profile Form Component', () => {
let component: UserProfileFormComponent;
let fixture: ComponentFixture<UserProfileFormComponent>;
let element: HTMLElement;
let fb: FormBuilder;
let appFacade: AppFacade;

beforeEach(async(() => {
appFacade = mock(AppFacade);

TestBed.configureTestingModule({
imports: [ReactiveFormsModule, RouterTestingModule, TranslateModule.forRoot()],
declarations: [
MockComponent(ErrorMessageComponent),
MockComponent(InputComponent),
MockComponent(SelectTitleComponent),
UserProfileFormComponent,
],
providers: [{ provide: AppFacade, useFactory: () => instance(appFacade) }],
}).compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(UserProfileFormComponent);
component = fixture.componentInstance;
element = fixture.nativeElement;
fb = TestBed.inject(FormBuilder);
when(appFacade.currentLocale$).thenReturn(of({ lang: 'en_US' } as Locale));

component.form = fb.group({
email: ['', [Validators.required, CustomValidators.email]],
});
});

it('should be created', () => {
expect(component).toBeTruthy();
expect(element).toBeTruthy();
expect(() => fixture.detectChanges()).not.toThrow();
});

it('should display form input fields on creation', () => {
fixture.detectChanges();

expect(element.querySelector('[controlname=firstName]')).toBeTruthy();
expect(element.querySelector('[controlname=lastName]')).toBeTruthy();
expect(element.querySelector('[controlname=phone]')).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

import { AppFacade } from 'ish-core/facades/app.facade';
import { HttpError } from 'ish-core/models/http-error/http-error.model';
import { Locale } from 'ish-core/models/locale/locale.model';
import { whenTruthy } from 'ish-core/utils/operators';
import { determineSalutations } from 'ish-shared/forms/utils/form-utils';

@Component({
selector: 'ish-user-profile-form',
templateUrl: './user-profile-form.component.html',
changeDetection: ChangeDetectionStrategy.Default,
})
export class UserProfileFormComponent implements OnInit, OnDestroy {
@Input() form: FormGroup;
@Input() error: HttpError;

currentLocale$: Observable<Locale>;
private destroy$ = new Subject();

titles = [];

constructor(private appFacade: AppFacade) {}

ngOnInit() {
this.currentLocale$ = this.appFacade.currentLocale$;

// determine default language from session and available locales
this.currentLocale$.pipe(whenTruthy(), takeUntil(this.destroy$)).subscribe(locale => {
this.titles = locale?.lang ? determineSalutations(locale.lang.slice(3)) : undefined;
});
}

ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
5 changes: 5 additions & 0 deletions projects/organization-management/src/app/exports/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// tslint:disable: no-barrel-files

export { OrganizationManagementModule } from '../organization-management.module';

export { OrganizationManagementBreadcrumbService } from '../services/organization-management-breadcrumb/organization-management-breadcrumb.service';
Loading