Merge pull request #2610 from abpframework/fix/remember-me

fix: remember me functionality
pull/2612/head
Mehmet Erim 6 years ago committed by GitHub
commit df2b147adb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<title>DevApp</title>
<title>ABP Dev</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />

@ -6,7 +6,7 @@
<div class="abp-account-container">
<div
*ngIf="(enableLocalLogin$ | async) !== 'False'; else disableLocalLoginTemplate"
*ngIf="enableLocalLogin; else disableLocalLoginTemplate"
class="card mt-3 shadow-sm rounded"
>
<div class="card-body p-5">

@ -1,8 +1,7 @@
import { Component, Input, TemplateRef } from '@angular/core';
import { ConfigState, takeUntilDestroy } from '@abp/ng.core';
import { Component, Input, OnDestroy, OnInit, TemplateRef } from '@angular/core';
import { Store } from '@ngxs/store';
import { Account } from '../../models/account';
import { Select } from '@ngxs/store';
import { ConfigState } from '@abp/ng.core';
import { Observable } from 'rxjs';
@Component({
selector: 'abp-auth-wrapper',
@ -10,13 +9,31 @@ import { Observable } from 'rxjs';
exportAs: 'abpAuthWrapper',
})
export class AuthWrapperComponent
implements Account.AuthWrapperComponentInputs, Account.AuthWrapperComponentOutputs {
@Select(ConfigState.getSetting('Abp.Account.EnableLocalLogin'))
enableLocalLogin$: Observable<'True' | 'False'>;
implements
Account.AuthWrapperComponentInputs,
Account.AuthWrapperComponentOutputs,
OnInit,
OnDestroy {
@Input()
readonly mainContentRef: TemplateRef<any>;
@Input()
readonly cancelContentRef: TemplateRef<any>;
enableLocalLogin = true;
constructor(private store: Store) {}
ngOnInit() {
this.store
.select(ConfigState.getSetting('Abp.Account.EnableLocalLogin'))
.pipe(takeUntilDestroy(this))
.subscribe(value => {
if (value) {
this.enableLocalLogin = value.toLowerCase() !== 'false';
}
});
}
ngOnDestroy() {}
}

@ -72,7 +72,7 @@ export class ChangePasswordComponent
required,
validatePassword(passwordRulesArr),
minLength(requiredLength),
maxLength(32),
maxLength(128),
],
},
],
@ -83,7 +83,7 @@ export class ChangePasswordComponent
required,
validatePassword(passwordRulesArr),
minLength(requiredLength),
maxLength(32),
maxLength(128),
],
},
],

@ -12,7 +12,7 @@
</abp-auth-wrapper>
<ng-template #mainContentRef>
<h4>{{ 'AbpAccount::Login' | abpLocalization }}</h4>
<strong>
<strong *ngIf="isSelfRegistrationEnabled">
{{ 'AbpAccount::AreYouANewUser' | abpLocalization }}
<a class="text-decoration-none" routerLink="/account/register">{{
'AbpAccount::Register' | abpLocalization

@ -1,15 +1,12 @@
import { GetAppConfiguration, ConfigState, SessionState } from '@abp/ng.core';
import { Component, Inject, Optional } from '@angular/core';
import { AuthService, SetRemember, ConfigState } from '@abp/ng.core';
import { ToasterService } from '@abp/ng.theme.shared';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Navigate } from '@ngxs/router-plugin';
import { Store } from '@ngxs/store';
import { OAuthService } from 'angular-oauth2-oidc';
import { from, throwError } from 'rxjs';
import { Options } from '../../models/options';
import { ToasterService } from '@abp/ng.theme.shared';
import { catchError, finalize, switchMap, tap } from 'rxjs/operators';
import { throwError } from 'rxjs';
import { catchError, finalize } from 'rxjs/operators';
import snq from 'snq';
import { HttpHeaders } from '@angular/common/http';
const { maxLength, minLength, required } = Validators;
@ -17,47 +14,43 @@ const { maxLength, minLength, required } = Validators;
selector: 'abp-login',
templateUrl: './login.component.html',
})
export class LoginComponent {
export class LoginComponent implements OnInit {
form: FormGroup;
inProgress: boolean;
isSelfRegistrationEnabled = true;
constructor(
private fb: FormBuilder,
private oauthService: OAuthService,
private store: Store,
private toasterService: ToasterService,
@Optional() @Inject('ACCOUNT_OPTIONS') private options: Options,
) {
this.oauthService.configure(this.store.selectSnapshot(ConfigState.getOne('environment')).oAuthConfig);
this.oauthService.loadDiscoveryDocument();
private authService: AuthService,
) {}
ngOnInit() {
this.isSelfRegistrationEnabled =
(
(this.store.selectSnapshot(
ConfigState.getSetting('Abp.Account.IsSelfRegistrationEnabled'),
) as string) || ''
).toLowerCase() !== 'false';
this.form = this.fb.group({
username: ['', [required, maxLength(255)]],
password: ['', [required, maxLength(32)]],
password: ['', [required, maxLength(128)]],
remember: [false],
});
}
onSubmit() {
if (this.form.invalid) return;
// this.oauthService.setStorage(this.form.value.remember ? localStorage : sessionStorage);
this.inProgress = true;
const tenant = this.store.selectSnapshot(SessionState.getTenant);
from(
this.oauthService.fetchTokenUsingPasswordFlow(
this.form.get('username').value,
this.form.get('password').value,
new HttpHeaders({ ...(tenant && tenant.id && { __tenant: tenant.id }) }),
),
)
this.authService
.login(this.form.get('username').value, this.form.get('password').value)
.pipe(
switchMap(() => this.store.dispatch(new GetAppConfiguration())),
tap(() => {
const redirectUrl = snq(() => window.history.state).redirectUrl || (this.options || {}).redirectUrl || '/';
this.store.dispatch(new Navigate([redirectUrl]));
}),
catchError(err => {
this.toasterService.error(
snq(() => err.error.error_description) ||
@ -69,6 +62,8 @@ export class LoginComponent {
}),
finalize(() => (this.inProgress = false)),
)
.subscribe();
.subscribe(() => {
this.store.dispatch(new SetRemember(this.form.get('remember').value));
});
}
}

@ -16,7 +16,13 @@
'AbpAccount::Login' | abpLocalization
}}</a>
</strong>
<form [formGroup]="form" (ngSubmit)="onSubmit()" validateOnSubmit class="mt-4">
<form
*ngIf="isSelfRegistrationEnabled"
[formGroup]="form"
(ngSubmit)="onSubmit()"
validateOnSubmit
class="mt-4"
>
<div class="form-group">
<label for="input-user-name">{{ 'AbpAccount::UserName' | abpLocalization }}</label
><span> * </span

@ -1,4 +1,4 @@
import { ConfigState, GetAppConfiguration, ABP, SessionState } from '@abp/ng.core';
import { ConfigState, GetAppConfiguration, ABP, SessionState, AuthService } from '@abp/ng.core';
import { ToasterService } from '@abp/ng.theme.shared';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@ -23,20 +23,36 @@ export class RegisterComponent implements OnInit {
inProgress: boolean;
isSelfRegistrationEnabled = true;
constructor(
private fb: FormBuilder,
private accountService: AccountService,
private oauthService: OAuthService,
private store: Store,
private toasterService: ToasterService,
) {
this.oauthService.configure(
this.store.selectSnapshot(ConfigState.getOne('environment')).oAuthConfig,
);
this.oauthService.loadDiscoveryDocument();
}
private authService: AuthService,
) {}
ngOnInit() {
this.isSelfRegistrationEnabled =
(
this.store.selectSnapshot(
ConfigState.getSetting('Abp.Account.IsSelfRegistrationEnabled'),
) || ''
).toLowerCase() !== 'false';
if (!this.isSelfRegistrationEnabled) {
this.toasterService.warn(
{
key: 'AbpAccount::SelfRegistrationDisabledMessage',
defaultValue: 'Self registration is disabled.',
},
null,
{ life: 10000 },
);
return;
}
const passwordRules: ABP.Dictionary<string> = this.store.selectSnapshot(
ConfigState.getSettings('Identity.Password'),
);
@ -67,7 +83,7 @@ export class RegisterComponent implements OnInit {
username: ['', [required, maxLength(255)]],
password: [
'',
[required, validatePassword(passwordRulesArr), minLength(requiredLength), maxLength(32)],
[required, validatePassword(passwordRulesArr), minLength(requiredLength), maxLength(128)],
],
email: ['', [required, email]],
});
@ -85,25 +101,10 @@ export class RegisterComponent implements OnInit {
appName: 'Angular',
} as RegisterRequest;
const tenant = this.store.selectSnapshot(SessionState.getTenant);
this.accountService
.register(newUser)
.pipe(
switchMap(() =>
from(
this.oauthService.fetchTokenUsingPasswordFlow(
newUser.userName,
newUser.password,
new HttpHeaders({
...(tenant && tenant.id && { __tenant: tenant.id }),
}),
),
),
),
switchMap(() => this.store.dispatch(new GetAppConfiguration())),
tap(() => this.store.dispatch(new Navigate(['/']))),
take(1),
switchMap(() => this.authService.login(newUser.userName, newUser.password)),
catchError(err => {
this.toasterService.error(
snq(() => err.error.error_description) ||

@ -8,3 +8,11 @@ export class SetTenant {
static readonly type = '[Session] Set Tenant';
constructor(public payload: ABP.BasicItem) {}
}
export class ModifyOpenedTabCount {
static readonly type = '[Session] Modify Opened Tab Count';
constructor(public operation: 'increase' | 'decrease') {}
}
export class SetRemember {
static readonly type = '[Session] Set Remember';
constructor(public payload: boolean) {}
}

@ -6,7 +6,7 @@ import { RouterModule } from '@angular/router';
import { NgxsRouterPluginModule } from '@ngxs/router-plugin';
import { NgxsStoragePluginModule } from '@ngxs/storage-plugin';
import { NgxsModule, NGXS_PLUGINS } from '@ngxs/store';
import { OAuthModule } from 'angular-oauth2-oidc';
import { OAuthModule, OAuthStorage } from 'angular-oauth2-oidc';
import { AbstractNgModelComponent } from './abstracts/ng-model.component';
import { DynamicLayoutComponent } from './components/dynamic-layout.component';
import { RouterOutletComponent } from './components/router-outlet.component';
@ -34,12 +34,15 @@ import { ReplaceableComponentsState } from './states/replaceable-components.stat
import { InitDirective } from './directives/init.directive';
import { ReplaceableTemplateDirective } from './directives/replaceable-template.directive';
export function storageFactory(): OAuthStorage {
return localStorage;
}
@NgModule({
imports: [
NgxsModule.forFeature([ReplaceableComponentsState, ProfileState, SessionState, ConfigState]),
NgxsRouterPluginModule.forRoot(),
NgxsStoragePluginModule.forRoot({ key: ['SessionState'] }),
OAuthModule.forRoot(),
OAuthModule,
CommonModule,
HttpClientModule,
FormsModule,
@ -127,6 +130,8 @@ export class CoreModule {
deps: [Injector],
useFactory: localeInitializer,
},
...OAuthModule.forRoot().providers,
{ provide: OAuthStorage, useFactory: storageFactory },
],
};
}

@ -4,5 +4,12 @@ export namespace Session {
export interface State {
language: string;
tenant: ABP.BasicItem;
sessionDetail: SessionDetail;
}
export interface SessionDetail {
openedTabCount: number;
lastExitTime: number;
remember: boolean;
}
}

@ -0,0 +1,51 @@
import { HttpHeaders } from '@angular/common/http';
import { Inject, Injectable, Optional } from '@angular/core';
import { Navigate } from '@ngxs/router-plugin';
import { Store } from '@ngxs/store';
import { OAuthService } from 'angular-oauth2-oidc';
import { from, Observable } from 'rxjs';
import { switchMap, tap, take } from 'rxjs/operators';
import snq from 'snq';
import { GetAppConfiguration } from '../actions/config.actions';
import { SessionState } from '../states/session.state';
import { RestService } from './rest.service';
import { ConfigState } from '../states/config.state';
@Injectable({
providedIn: 'root',
})
export class AuthService {
constructor(
private rest: RestService,
private oAuthService: OAuthService,
private store: Store,
@Optional() @Inject('ACCOUNT_OPTIONS') private options: any,
) {}
login(username: string, password: string): Observable<any> {
const tenant = this.store.selectSnapshot(SessionState.getTenant);
this.oAuthService.configure(
this.store.selectSnapshot(ConfigState.getOne('environment')).oAuthConfig,
);
return from(this.oAuthService.loadDiscoveryDocument()).pipe(
switchMap(() =>
from(
this.oAuthService.fetchTokenUsingPasswordFlow(
username,
password,
new HttpHeaders({ ...(tenant && tenant.id && { __tenant: tenant.id }) }),
),
),
),
switchMap(() => this.store.dispatch(new GetAppConfiguration())),
tap(() => {
const redirectUrl =
snq(() => window.history.state.redirectUrl) || (this.options || {}).redirectUrl || '/';
this.store.dispatch(new Navigate([redirectUrl]));
}),
take(1),
);
}
}

@ -1,4 +1,5 @@
export * from './application-configuration.service';
export * from './auth.service';
export * from './config-state.service';
export * from './lazy-load.service';
export * from './localization.service';

@ -1,8 +1,12 @@
import { Injectable } from '@angular/core';
import { Store } from '@ngxs/store';
import {
SetLanguage,
SetRemember,
SetTenant,
ModifyOpenedTabCount,
} from '../actions/session.actions';
import { SessionState } from '../states';
import { ABP } from '../models';
import { SetLanguage, SetTenant } from '../actions';
@Injectable({
providedIn: 'root',
@ -18,6 +22,10 @@ export class SessionStateService {
return this.store.selectSnapshot(SessionState.getTenant);
}
getSessionDetail() {
return this.store.selectSnapshot(SessionState.getSessionDetail);
}
dispatchSetLanguage(...args: ConstructorParameters<typeof SetLanguage>) {
return this.store.dispatch(new SetLanguage(...args));
}
@ -25,4 +33,12 @@ export class SessionStateService {
dispatchSetTenant(...args: ConstructorParameters<typeof SetTenant>) {
return this.store.dispatch(new SetTenant(...args));
}
dispatchSetRemember(...args: ConstructorParameters<typeof SetRemember>) {
return this.store.dispatch(new SetRemember(...args));
}
dispatchModifyOpenedTabCount(...args: ConstructorParameters<typeof ModifyOpenedTabCount>) {
return this.store.dispatch(new ModifyOpenedTabCount(...args));
}
}

@ -1,14 +1,29 @@
import { Action, Selector, State, StateContext } from '@ngxs/store';
import { from } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import {
Action,
Selector,
State,
StateContext,
Store,
NgxsOnInit,
Actions,
ofActionSuccessful,
} from '@ngxs/store';
import { from, fromEvent } from 'rxjs';
import { switchMap, take } from 'rxjs/operators';
import { GetAppConfiguration } from '../actions/config.actions';
import { SetLanguage, SetTenant } from '../actions/session.actions';
import {
SetLanguage,
SetTenant,
ModifyOpenedTabCount,
SetRemember,
} from '../actions/session.actions';
import { ABP, Session } from '../models';
import { LocalizationService } from '../services/localization.service';
import { OAuthService } from 'angular-oauth2-oidc';
@State<Session.State>({
name: 'SessionState',
defaults: {} as Session.State,
defaults: { sessionDetail: { openedTabCount: 0 } } as Session.State,
})
export class SessionState {
@Selector()
@ -21,7 +36,42 @@ export class SessionState {
return tenant;
}
constructor(private localizationService: LocalizationService) {}
@Selector()
static getSessionDetail({ sessionDetail }: Session.State): Session.SessionDetail {
return sessionDetail;
}
constructor(
private localizationService: LocalizationService,
private oAuthService: OAuthService,
private store: Store,
private actions: Actions,
) {
actions
.pipe(ofActionSuccessful(GetAppConfiguration))
.pipe(take(1))
.subscribe(() => {
const { sessionDetail } = this.store.selectSnapshot(SessionState) || { sessionDetail: {} };
const fiveMinutesBefore = new Date().valueOf() - 5 * 60 * 1000;
if (
sessionDetail.lastExitTime &&
sessionDetail.openedTabCount === 0 &&
this.oAuthService.hasValidAccessToken() &&
sessionDetail.remember === false &&
sessionDetail.lastExitTime < fiveMinutesBefore
) {
this.oAuthService.logOut();
}
this.store.dispatch(new ModifyOpenedTabCount('increase'));
fromEvent(window, 'beforeunload').subscribe(() => {
this.store.dispatch(new ModifyOpenedTabCount('decrease'));
});
});
}
@Action(SetLanguage)
setLanguage({ patchState, dispatch }: StateContext<Session.State>, { payload }: SetLanguage) {
@ -40,4 +90,48 @@ export class SessionState {
tenant: payload,
});
}
@Action(SetRemember)
setRemember(
{ getState, patchState }: StateContext<Session.State>,
{ payload: remember }: SetRemember,
) {
const { sessionDetail } = getState();
patchState({
sessionDetail: {
...sessionDetail,
remember,
},
});
}
@Action(ModifyOpenedTabCount)
modifyOpenedTabCount(
{ getState, patchState }: StateContext<Session.State>,
{ operation }: ModifyOpenedTabCount,
) {
// tslint:disable-next-line: prefer-const
let { openedTabCount, lastExitTime, ...detail } =
getState().sessionDetail || ({ openedTabCount: 0 } as Session.SessionDetail);
if (operation === 'increase') {
openedTabCount++;
} else if (operation === 'decrease') {
openedTabCount--;
lastExitTime = new Date().valueOf();
}
if (!openedTabCount || openedTabCount < 0) {
openedTabCount = 0;
}
patchState({
sessionDetail: {
openedTabCount,
lastExitTime,
...detail,
},
});
}
}

@ -10,6 +10,7 @@ import { ABP } from '../models';
import { ConfigPlugin, NGXS_CONFIG_PLUGIN_OPTIONS } from '../plugins';
import { ConfigState } from '../states';
import { addAbpRoutes } from '../utils';
import { OAuthModule } from 'angular-oauth2-oidc';
addAbpRoutes([
{
@ -323,6 +324,7 @@ describe('ConfigPlugin', () => {
service: ConfigPlugin,
imports: [
CoreModule,
OAuthModule.forRoot(),
NgxsModule.forRoot([]),
RouterTestingModule.withRoutes([
{

@ -3,13 +3,17 @@ import { SessionStateService } from '../services/session-state.service';
import { SessionState } from '../states/session.state';
import { Store } from '@ngxs/store';
import * as SessionActions from '../actions';
import { OAuthService } from 'angular-oauth2-oidc';
describe('SessionStateService', () => {
let service: SessionStateService;
let spectator: SpectatorService<SessionStateService>;
let store: SpyObject<Store>;
const createService = createServiceFactory({ service: SessionStateService, mocks: [Store] });
const createService = createServiceFactory({
service: SessionStateService,
mocks: [Store, OAuthService],
});
beforeEach(() => {
spectator = createService();
service = spectator.service;

@ -1,9 +1,11 @@
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { Session } from '../models/session';
import { LocalizationService } from '../services';
import { LocalizationService, AuthService } from '../services';
import { SessionState } from '../states';
import { GetAppConfiguration } from '../actions/config.actions';
import { of } from 'rxjs';
import { of, Subject } from 'rxjs';
import { Store, Actions } from '@ngxs/store';
import { OAuthService } from 'angular-oauth2-oidc';
export class DummyClass {}
@ -18,12 +20,12 @@ describe('SessionState', () => {
const createService = createServiceFactory({
service: DummyClass,
mocks: [LocalizationService],
mocks: [LocalizationService, Store, Actions, OAuthService],
});
beforeEach(() => {
spectator = createService();
state = new SessionState(spectator.get(LocalizationService));
state = new SessionState(spectator.get(LocalizationService), null, null, new Subject());
});
describe('#getLanguage', () => {

@ -144,7 +144,7 @@ export class UsersComponent implements OnInit {
const passwordValidators = [
validatePassword(this.passwordRulesArr),
Validators.minLength(this.requiredPasswordLength),
Validators.maxLength(32),
Validators.maxLength(128),
];
this.form.addControl('password', new FormControl('', [...passwordValidators]));

@ -6,6 +6,7 @@ import { NgxsModule } from '@ngxs/store';
import { MessageService } from 'primeng/components/common/messageservice';
import { ConfirmationService } from '../services/confirmation.service';
import { ThemeSharedModule } from '../theme-shared.module';
import { OAuthModule, OAuthService } from 'angular-oauth2-oidc';
@Component({
selector: 'abp-dummy',
@ -24,6 +25,7 @@ describe('ConfirmationService', () => {
component: DummyComponent,
imports: [CoreModule, ThemeSharedModule.forRoot(), NgxsModule.forRoot(), RouterTestingModule],
providers: [MessageService],
mocks: [OAuthService],
});
beforeEach(() => {

@ -9,8 +9,12 @@ import { ThemeSharedModule } from '../theme-shared.module';
import { MessageService } from 'primeng/components/common/messageservice';
import { RouterError, RouterDataResolved } from '@ngxs/router-plugin';
import { NavigationError, ResolveEnd } from '@angular/router';
import { OAuthModule, OAuthService } from 'angular-oauth2-oidc';
@Component({ selector: 'abp-dummy', template: 'dummy works! <abp-confirmation></abp-confirmation>' })
@Component({
selector: 'abp-dummy',
template: 'dummy works! <abp-confirmation></abp-confirmation>',
})
class DummyComponent {
constructor(public errorHandler: ErrorHandler) {}
}
@ -21,6 +25,7 @@ describe('ErrorHandler', () => {
const createComponent = createRoutingFactory({
component: DummyComponent,
imports: [CoreModule, ThemeSharedModule.forRoot(), NgxsModule.forRoot([])],
mocks: [OAuthService],
stubsEnabled: false,
routes: [
{ path: '', component: DummyComponent },
@ -38,33 +43,53 @@ describe('ErrorHandler', () => {
it('should display the error component when server error occurs', () => {
store.dispatch(new RestOccurError(new HttpErrorResponse({ status: 500 })));
expect(document.querySelector('.error-template')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultError500.title);
expect(document.querySelector('.error-details')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultError500.details);
expect(document.querySelector('.error-template')).toHaveText(
DEFAULT_ERROR_MESSAGES.defaultError500.title,
);
expect(document.querySelector('.error-details')).toHaveText(
DEFAULT_ERROR_MESSAGES.defaultError500.details,
);
});
it('should display the error component when authorize error occurs', () => {
store.dispatch(new RestOccurError(new HttpErrorResponse({ status: 403 })));
expect(document.querySelector('.error-template')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultError403.title);
expect(document.querySelector('.error-details')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultError403.details);
expect(document.querySelector('.error-template')).toHaveText(
DEFAULT_ERROR_MESSAGES.defaultError403.title,
);
expect(document.querySelector('.error-details')).toHaveText(
DEFAULT_ERROR_MESSAGES.defaultError403.details,
);
});
it('should display the error component when unknown error occurs', () => {
store.dispatch(new RestOccurError(new HttpErrorResponse({ status: 0, statusText: 'Unknown Error' })));
expect(document.querySelector('.error-template')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultError.title);
store.dispatch(
new RestOccurError(new HttpErrorResponse({ status: 0, statusText: 'Unknown Error' })),
);
expect(document.querySelector('.error-template')).toHaveText(
DEFAULT_ERROR_MESSAGES.defaultError.title,
);
});
it('should display the confirmation when not found error occurs', () => {
store.dispatch(new RestOccurError(new HttpErrorResponse({ status: 404 })));
spectator.detectChanges();
expect(spectator.query('.abp-confirm-summary')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultError404.title);
expect(spectator.query('.abp-confirm-body')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultError404.details);
expect(spectator.query('.abp-confirm-summary')).toHaveText(
DEFAULT_ERROR_MESSAGES.defaultError404.title,
);
expect(spectator.query('.abp-confirm-body')).toHaveText(
DEFAULT_ERROR_MESSAGES.defaultError404.details,
);
});
it('should display the confirmation when default error occurs', () => {
store.dispatch(new RestOccurError(new HttpErrorResponse({ status: 412 })));
spectator.detectChanges();
expect(spectator.query('.abp-confirm-summary')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultError.title);
expect(spectator.query('.abp-confirm-body')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultError.details);
expect(spectator.query('.abp-confirm-summary')).toHaveText(
DEFAULT_ERROR_MESSAGES.defaultError.title,
);
expect(spectator.query('.abp-confirm-body')).toHaveText(
DEFAULT_ERROR_MESSAGES.defaultError.details,
);
});
it('should display the confirmation when authenticated error occurs', async () => {
@ -110,7 +135,8 @@ describe('ErrorHandler', () => {
@Component({
selector: 'abp-dummy-error',
template: '<p>{{errorStatus}}</p><button id="close-dummy" (click)="destroy$.next()">Close</button>',
template:
'<p>{{errorStatus}}</p><button id="close-dummy" (click)="destroy$.next()">Close</button>',
})
class DummyErrorComponent {
errorStatus;
@ -130,12 +156,16 @@ describe('ErrorHandler with custom error component', () => {
imports: [
CoreModule,
ThemeSharedModule.forRoot({
httpErrorConfig: { errorScreen: { component: DummyErrorComponent, forWhichErrors: [401, 403, 404, 500] } },
httpErrorConfig: {
errorScreen: { component: DummyErrorComponent, forWhichErrors: [401, 403, 404, 500] },
},
}),
NgxsModule.forRoot([]),
ErrorModule,
],
mocks: [OAuthService],
stubsEnabled: false,
routes: [
{ path: '', component: DummyComponent },
{ path: 'account/login', component: RouterOutletComponent },

@ -6,6 +6,7 @@ import { NgxsModule } from '@ngxs/store';
import { MessageService } from 'primeng/components/common/messageservice';
import { ToasterService } from '../services/toaster.service';
import { ThemeSharedModule } from '../theme-shared.module';
import { OAuthService } from 'angular-oauth2-oidc';
@Component({
selector: 'abp-dummy',
@ -24,6 +25,7 @@ describe('ToasterService', () => {
component: DummyComponent,
imports: [CoreModule, ThemeSharedModule.forRoot(), NgxsModule.forRoot(), RouterTestingModule],
providers: [MessageService],
mocks: [OAuthService],
});
beforeEach(() => {
@ -67,10 +69,9 @@ describe('ToasterService', () => {
{ summary: 'summary2', detail: 'detail2' },
]);
spectator.detectChanges();
expect(spectator.queryAll('div.ui-toast-summary').map(node => node.textContent.trim())).toEqual([
'summary1',
'summary2',
]);
expect(
spectator.queryAll('div.ui-toast-summary').map(node => node.textContent.trim()),
).toEqual(['summary1', 'summary2']);
expect(spectator.queryAll('div.ui-toast-detail').map(node => node.textContent.trim())).toEqual([
'detail1',
'detail2',

Loading…
Cancel
Save