mirror of https://github.com/abpframework/abp.git
48 changed files with 399 additions and 331 deletions
@ -1 +1 @@ |
|||
export * from './config/config.plugin'; |
|||
export * from './config.plugin'; |
|||
|
|||
@ -0,0 +1,20 @@ |
|||
import { createHttpFactory, HttpMethod, SpectatorHttp } from '@ngneat/spectator/jest'; |
|||
import { ApplicationConfigurationService, RestService } from '../services'; |
|||
import { Store } from '@ngxs/store'; |
|||
|
|||
describe('ApplicationConfigurationService', () => { |
|||
let spectator: SpectatorHttp<ApplicationConfigurationService>; |
|||
const createHttp = createHttpFactory({ |
|||
dataService: ApplicationConfigurationService, |
|||
providers: [RestService], |
|||
mocks: [Store], |
|||
}); |
|||
|
|||
beforeEach(() => (spectator = createHttp())); |
|||
|
|||
it('should send a GET to application-configuration API', () => { |
|||
spectator.get(Store).selectSnapshot.andReturn('https://abp.io'); |
|||
spectator.service.getConfiguration().subscribe(); |
|||
spectator.expectOne('https://abp.io/api/abp/application-configuration', HttpMethod.GET); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,42 @@ |
|||
import { createHttpFactory, HttpMethod, SpectatorHttp } from '@ngneat/spectator/jest'; |
|||
import { ProfileService, RestService } from '../services'; |
|||
import { Store } from '@ngxs/store'; |
|||
|
|||
describe('ProfileService', () => { |
|||
let spectator: SpectatorHttp<ProfileService>; |
|||
const createHttp = createHttpFactory({ |
|||
dataService: ProfileService, |
|||
providers: [RestService], |
|||
mocks: [Store], |
|||
}); |
|||
|
|||
beforeEach(() => (spectator = createHttp())); |
|||
|
|||
it('should send a GET to my-profile API', () => { |
|||
spectator.get(Store).selectSnapshot.andReturn('https://abp.io'); |
|||
spectator.service.get().subscribe(); |
|||
spectator.expectOne('https://abp.io/api/identity/my-profile', HttpMethod.GET); |
|||
}); |
|||
|
|||
it('should send a POST to change-password API', () => { |
|||
const mock = { currentPassword: 'test', newPassword: 'test' }; |
|||
spectator.get(Store).selectSnapshot.andReturn('https://abp.io'); |
|||
spectator.service.changePassword(mock).subscribe(); |
|||
const req = spectator.expectOne('https://abp.io/api/identity/my-profile/change-password', HttpMethod.POST); |
|||
expect(req.request.body).toEqual(mock); |
|||
}); |
|||
|
|||
it('should send a PUT to my-profile API', () => { |
|||
const mock = { |
|||
email: 'info@volosoft.com', |
|||
userName: 'admin', |
|||
name: 'John', |
|||
surname: 'Doe', |
|||
phoneNumber: '+123456', |
|||
}; |
|||
spectator.get(Store).selectSnapshot.andReturn('https://abp.io'); |
|||
spectator.service.update(mock).subscribe(); |
|||
const req = spectator.expectOne('https://abp.io/api/identity/my-profile', HttpMethod.PUT); |
|||
expect(req.request.body).toEqual(mock); |
|||
}); |
|||
}); |
|||
@ -1,21 +1,39 @@ |
|||
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; |
|||
import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; |
|||
import { IdentityStateService } from '../services/identity-state.service'; |
|||
import { IdentityState } from '../states/identity.state'; |
|||
import { Store } from '@ngxs/store'; |
|||
describe('IdentityStateService', () => { |
|||
let service: IdentityStateService; |
|||
let spectator: SpectatorService<IdentityStateService>; |
|||
let store: SpyObject<Store>; |
|||
|
|||
const createService = createServiceFactory({ service: IdentityStateService, mocks: [Store] }); |
|||
beforeEach(() => { |
|||
spectator = createService(); |
|||
service = spectator.service; |
|||
store = spectator.get(Store); |
|||
}); |
|||
|
|||
test('should have the all IdentityState static methods', () => { |
|||
const reg = /(?<=static )(.*)(?=\()/gm; |
|||
IdentityState.toString() |
|||
.match(reg) |
|||
.forEach(fnName => { |
|||
expect(service[fnName]).toBeTruthy(); |
|||
|
|||
const spy = jest.spyOn(store, 'selectSnapshot'); |
|||
spy.mockClear(); |
|||
|
|||
const isDynamicSelector = IdentityState[fnName].name !== 'memoized'; |
|||
|
|||
if (isDynamicSelector) { |
|||
IdentityState[fnName] = jest.fn((...args) => args); |
|||
service[fnName]('test', 0, {}); |
|||
expect(IdentityState[fnName]).toHaveBeenCalledWith('test', 0, {}); |
|||
} else { |
|||
service[fnName](); |
|||
expect(spy).toHaveBeenCalledWith(IdentityState[fnName]); |
|||
} |
|||
}); |
|||
}); |
|||
}); |
|||
|
|||
@ -1,21 +1,39 @@ |
|||
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; |
|||
import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; |
|||
import { TenantManagementStateService } from '../services/tenant-management-state.service'; |
|||
import { TenantManagementState } from '../states/tenant-management.state'; |
|||
import { Store } from '@ngxs/store'; |
|||
describe('TenantManagementStateService', () => { |
|||
let service: TenantManagementStateService; |
|||
let spectator: SpectatorService<TenantManagementStateService>; |
|||
let store: SpyObject<Store>; |
|||
|
|||
const createService = createServiceFactory({ service: TenantManagementStateService, mocks: [Store] }); |
|||
beforeEach(() => { |
|||
spectator = createService(); |
|||
service = spectator.service; |
|||
store = spectator.get(Store); |
|||
}); |
|||
|
|||
test('should have the all TenantManagementState static methods', () => { |
|||
const reg = /(?<=static )(.*)(?=\()/gm; |
|||
TenantManagementState.toString() |
|||
.match(reg) |
|||
.forEach(fnName => { |
|||
expect(service[fnName]).toBeTruthy(); |
|||
|
|||
const spy = jest.spyOn(store, 'selectSnapshot'); |
|||
spy.mockClear(); |
|||
|
|||
const isDynamicSelector = TenantManagementState[fnName].name !== 'memoized'; |
|||
|
|||
if (isDynamicSelector) { |
|||
TenantManagementState[fnName] = jest.fn((...args) => args); |
|||
service[fnName]('test', 0, {}); |
|||
expect(TenantManagementState[fnName]).toHaveBeenCalledWith('test', 0, {}); |
|||
} else { |
|||
service[fnName](); |
|||
expect(spy).toHaveBeenCalledWith(TenantManagementState[fnName]); |
|||
} |
|||
}); |
|||
}); |
|||
}); |
|||
|
|||
@ -1,21 +1,39 @@ |
|||
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; |
|||
import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; |
|||
import { LayoutStateService } from '../services/layout-state.service'; |
|||
import { LayoutState } from '../states/layout.state'; |
|||
import { Store } from '@ngxs/store'; |
|||
describe('LayoutStateService', () => { |
|||
let service: LayoutStateService; |
|||
let spectator: SpectatorService<LayoutStateService>; |
|||
let store: SpyObject<Store>; |
|||
|
|||
const createService = createServiceFactory({ service: LayoutStateService, mocks: [Store] }); |
|||
beforeEach(() => { |
|||
spectator = createService(); |
|||
service = spectator.service; |
|||
store = spectator.get(Store); |
|||
}); |
|||
|
|||
test('should have the all LayoutState static methods', () => { |
|||
const reg = /(?<=static )(.*)(?=\()/gm; |
|||
LayoutState.toString() |
|||
.match(reg) |
|||
.forEach(fnName => { |
|||
expect(service[fnName]).toBeTruthy(); |
|||
|
|||
const spy = jest.spyOn(store, 'selectSnapshot'); |
|||
spy.mockClear(); |
|||
|
|||
const isDynamicSelector = LayoutState[fnName].name !== 'memoized'; |
|||
|
|||
if (isDynamicSelector) { |
|||
LayoutState[fnName] = jest.fn((...args) => args); |
|||
service[fnName]('test', 0, {}); |
|||
expect(LayoutState[fnName]).toHaveBeenCalledWith('test', 0, {}); |
|||
} else { |
|||
service[fnName](); |
|||
expect(spy).toHaveBeenCalledWith(LayoutState[fnName]); |
|||
} |
|||
}); |
|||
}); |
|||
}); |
|||
|
|||
@ -1,31 +0,0 @@ |
|||
<abp-modal [(visible)]="visible" [busy]="modalBusy"> |
|||
<ng-template #abpHeader> |
|||
<h4>{{ 'AbpIdentity::ChangePassword' | abpLocalization }}</h4> |
|||
</ng-template> |
|||
<ng-template #abpBody> |
|||
<form [formGroup]="form" (ngSubmit)="onSubmit()" [mapErrorsFn]="mapErrorsFn"> |
|||
<div class="form-group"> |
|||
<label for="current-password">{{ 'AbpIdentity::DisplayName:CurrentPassword' | abpLocalization }}</label |
|||
><span> * </span |
|||
><input type="password" id="current-password" class="form-control" formControlName="password" autofocus /> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label for="new-password">{{ 'AbpIdentity::DisplayName:NewPassword' | abpLocalization }}</label |
|||
><span> * </span><input type="password" id="new-password" class="form-control" formControlName="newPassword" /> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label for="confirm-new-password">{{ 'AbpIdentity::DisplayName:NewPasswordConfirm' | abpLocalization }}</label |
|||
><span> * </span |
|||
><input type="password" id="confirm-new-password" class="form-control" formControlName="repeatNewPassword" /> |
|||
</div> |
|||
</form> |
|||
</ng-template> |
|||
<ng-template #abpFooter> |
|||
<button type="button" class="btn btn-secondary color-white" #abpClose> |
|||
{{ 'AbpIdentity::Cancel' | abpLocalization }} |
|||
</button> |
|||
<abp-button iconClass="fa fa-check" buttonClass="btn btn-primary color-white" (click)="onSubmit()">{{ |
|||
'AbpIdentity::Save' | abpLocalization |
|||
}}</abp-button> |
|||
</ng-template> |
|||
</abp-modal> |
|||
@ -1,113 +0,0 @@ |
|||
import { ChangePassword } from '@abp/ng.core'; |
|||
import { |
|||
Component, |
|||
EventEmitter, |
|||
Input, |
|||
OnChanges, |
|||
OnInit, |
|||
Output, |
|||
SimpleChanges, |
|||
TemplateRef, |
|||
ViewChild, |
|||
} from '@angular/core'; |
|||
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; |
|||
import { comparePasswords, Validation } from '@ngx-validate/core'; |
|||
import { Store } from '@ngxs/store'; |
|||
import snq from 'snq'; |
|||
import { finalize } from 'rxjs/operators'; |
|||
import { ToasterService } from '../../services/toaster.service'; |
|||
|
|||
const { minLength, required } = Validators; |
|||
|
|||
const PASSWORD_FIELDS = ['newPassword', 'repeatNewPassword']; |
|||
|
|||
@Component({ |
|||
selector: 'abp-change-password', |
|||
templateUrl: './change-password.component.html', |
|||
}) |
|||
export class ChangePasswordComponent implements OnInit, OnChanges { |
|||
protected _visible; |
|||
|
|||
@Input() |
|||
get visible(): boolean { |
|||
return this._visible; |
|||
} |
|||
|
|||
set visible(value: boolean) { |
|||
this._visible = value; |
|||
this.visibleChange.emit(value); |
|||
} |
|||
|
|||
@Output() readonly visibleChange = new EventEmitter<boolean>(); |
|||
|
|||
@ViewChild('modalContent', { static: false }) |
|||
modalContent: TemplateRef<any>; |
|||
|
|||
form: FormGroup; |
|||
|
|||
modalBusy = false; |
|||
|
|||
mapErrorsFn: Validation.MapErrorsFn = (errors, groupErrors, control) => { |
|||
if (PASSWORD_FIELDS.indexOf(control.name) < 0) return errors; |
|||
|
|||
return errors.concat(groupErrors.filter(({ key }) => key === 'passwordMismatch')); |
|||
}; |
|||
|
|||
constructor(private fb: FormBuilder, private store: Store, private toasterService: ToasterService) {} |
|||
|
|||
ngOnInit(): void { |
|||
this.form = this.fb.group( |
|||
{ |
|||
password: ['', required], |
|||
newPassword: ['', required], |
|||
repeatNewPassword: ['', required], |
|||
}, |
|||
{ |
|||
validators: [comparePasswords(PASSWORD_FIELDS)], |
|||
}, |
|||
); |
|||
} |
|||
|
|||
onSubmit() { |
|||
if (this.form.invalid) return; |
|||
this.modalBusy = true; |
|||
|
|||
this.store |
|||
.dispatch( |
|||
new ChangePassword({ |
|||
currentPassword: this.form.get('password').value, |
|||
newPassword: this.form.get('newPassword').value, |
|||
}), |
|||
) |
|||
.pipe( |
|||
finalize(() => { |
|||
this.modalBusy = false; |
|||
}), |
|||
) |
|||
.subscribe({ |
|||
next: () => { |
|||
this.visible = false; |
|||
this.form.reset(); |
|||
}, |
|||
error: err => { |
|||
this.toasterService.error(snq(() => err.error.error.message, 'AbpAccount::DefaultErrorMessage'), 'Error', { |
|||
life: 7000, |
|||
}); |
|||
}, |
|||
}); |
|||
} |
|||
|
|||
openModal() { |
|||
this.visible = true; |
|||
} |
|||
|
|||
ngOnChanges({ visible }: SimpleChanges): void { |
|||
if (!visible) return; |
|||
|
|||
if (visible.currentValue) { |
|||
this.openModal(); |
|||
} else if (visible.currentValue === false && this.visible) { |
|||
this.visible = false; |
|||
} |
|||
} |
|||
} |
|||
@ -1,11 +1,9 @@ |
|||
export * from './breadcrumb/breadcrumb.component'; |
|||
export * from './button/button.component'; |
|||
export * from './change-password/change-password.component'; |
|||
export * from './chart/chart.component'; |
|||
export * from './confirmation/confirmation.component'; |
|||
export * from './loader-bar/loader-bar.component'; |
|||
export * from './modal/modal.component'; |
|||
export * from './profile/profile.component'; |
|||
export * from './table-empty-message/table-empty-message.component'; |
|||
export * from './toast/toast.component'; |
|||
export * from './sort-order-icon/sort-order-icon.component'; |
|||
|
|||
@ -1,41 +0,0 @@ |
|||
<abp-modal [(visible)]="visible" [busy]="modalBusy"> |
|||
<ng-template #abpHeader> |
|||
<h4>{{ 'AbpIdentity::PersonalInfo' | abpLocalization }}</h4> |
|||
</ng-template> |
|||
<ng-template #abpBody> |
|||
<form novalidate *ngIf="form" [formGroup]="form" (ngSubmit)="submit()"> |
|||
<div class="form-group"> |
|||
<label for="username">{{ 'AbpIdentity::DisplayName:UserName' | abpLocalization }}</label |
|||
><span> * </span><input type="text" id="username" class="form-control" formControlName="userName" autofocus /> |
|||
</div> |
|||
<div class="row"> |
|||
<div class="col col-md-6"> |
|||
<div class="form-group"> |
|||
<label for="name">{{ 'AbpIdentity::DisplayName:Name' | abpLocalization }}</label |
|||
><input type="text" id="name" class="form-control" formControlName="name" /> |
|||
</div> |
|||
</div> |
|||
<div class="col col-md-6"> |
|||
<div class="form-group"> |
|||
<label for="surname">{{ 'AbpIdentity::DisplayName:Surname' | abpLocalization }}</label |
|||
><input type="text" id="surname" class="form-control" formControlName="surname" /> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label for="email-address">{{ 'AbpIdentity::DisplayName:Email' | abpLocalization }}</label |
|||
><span> * </span><input type="text" id="email-address" class="form-control" formControlName="email" /> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label for="phone-number">{{ 'AbpIdentity::DisplayName:PhoneNumber' | abpLocalization }}</label |
|||
><input type="text" id="phone-number" class="form-control" formControlName="phoneNumber" /> |
|||
</div> |
|||
</form> |
|||
</ng-template> |
|||
<ng-template #abpFooter> |
|||
<button #abpClose type="button" class="btn btn-secondary color-white"> |
|||
{{ 'AbpIdentity::Cancel' | abpLocalization }} |
|||
</button> |
|||
<abp-button iconClass="fa fa-check" buttonClass="btn btn-primary color-white" (click)="submit()">{{ 'AbpIdentity::Save' | abpLocalization }}</abp-button> |
|||
</ng-template> |
|||
</abp-modal> |
|||
@ -1,81 +0,0 @@ |
|||
import { Profile, GetProfile, ProfileState, UpdateProfile } from '@abp/ng.core'; |
|||
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; |
|||
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; |
|||
import { Select, Store } from '@ngxs/store'; |
|||
import { Observable } from 'rxjs'; |
|||
import { take, withLatestFrom } from 'rxjs/operators'; |
|||
|
|||
const { maxLength, required, email } = Validators; |
|||
|
|||
@Component({ |
|||
selector: 'abp-profile', |
|||
templateUrl: './profile.component.html' |
|||
}) |
|||
export class ProfileComponent implements OnChanges { |
|||
protected _visible; |
|||
|
|||
@Input() |
|||
get visible(): boolean { |
|||
return this._visible; |
|||
} |
|||
|
|||
set visible(value: boolean) { |
|||
this._visible = value; |
|||
this.visibleChange.emit(value); |
|||
} |
|||
|
|||
@Output() readonly visibleChange = new EventEmitter<boolean>(); |
|||
|
|||
@Select(ProfileState.getProfile) |
|||
profile$: Observable<Profile.Response>; |
|||
|
|||
form: FormGroup; |
|||
|
|||
modalBusy = false; |
|||
|
|||
constructor(private fb: FormBuilder, private store: Store) {} |
|||
|
|||
buildForm() { |
|||
this.store |
|||
.dispatch(new GetProfile()) |
|||
.pipe( |
|||
withLatestFrom(this.profile$), |
|||
take(1) |
|||
) |
|||
.subscribe(([, profile]) => { |
|||
this.form = this.fb.group({ |
|||
userName: [profile.userName, [required, maxLength(256)]], |
|||
email: [profile.email, [required, email, maxLength(256)]], |
|||
name: [profile.name || '', [maxLength(64)]], |
|||
surname: [profile.surname || '', [maxLength(64)]], |
|||
phoneNumber: [profile.phoneNumber || '', [maxLength(16)]] |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
submit() { |
|||
if (this.form.invalid) return; |
|||
this.modalBusy = true; |
|||
|
|||
this.store.dispatch(new UpdateProfile(this.form.value)).subscribe(() => { |
|||
this.modalBusy = false; |
|||
this.visible = false; |
|||
this.form.reset(); |
|||
}); |
|||
} |
|||
|
|||
openModal() { |
|||
this.buildForm(); |
|||
this.visible = true; |
|||
} |
|||
|
|||
ngOnChanges({ visible }: SimpleChanges): void { |
|||
if (!visible) return; |
|||
|
|||
if (visible.currentValue) { |
|||
this.openModal(); |
|||
} else if (visible.currentValue === false && this.visible) { |
|||
this.visible = false; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue