From 4dd1bddd41ef358624ba57fb3d31c2de9e6c98b3 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Sat, 28 Sep 2019 00:03:34 +0300 Subject: [PATCH 01/12] tests(theme-shared): add button component tests --- .../src/lib/tests/button.component.spec.ts | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 npm/ng-packs/packages/theme-shared/src/lib/tests/button.component.spec.ts diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/button.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/button.component.spec.ts new file mode 100644 index 0000000000..bfb70f9518 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/button.component.spec.ts @@ -0,0 +1,54 @@ +import { CoreModule } from '@abp/ng.core'; +import { + createComponentFactory, + createHostFactory, + Spectator, + SpectatorHost, + createTestComponentFactory, +} from '@ngneat/spectator'; +import { ButtonComponent } from '../components'; + +describe('ButtonComponent', () => { + let host: SpectatorHost; + + const createHost = createHostFactory(ButtonComponent); + + beforeEach(() => (host = createHost(`Button`))); + + it('should display the button', () => { + expect(host.query('button')).toBeTruthy(); + }); + + it('should equal the default classes to btn btn-primary', () => { + expect(host.query('button')).toHaveClass('btn btn-primary'); + }); + + it('should equal the default type to button', () => { + expect(host.query('button')).toHaveAttribute('type', 'button'); + }); + + it('should enabled', () => { + expect(host.query('[disabled]')).toBeFalsy(); + }); + + it('should have the text content', () => { + expect(host.query('button')).toHaveText('Button'); + }); + + it('should display the icon', () => { + expect(host.query('i.d-none')).toBeFalsy(); + expect(host.query('i')).toHaveClass('fa'); + }); + + it('should display the spinner icon', () => { + host.component.loading = true; + host.detectComponentChanges(); + expect(host.query('i')).toHaveClass('fa-spinner'); + }); + + it('should disabled when the loading input is true', () => { + host.component.loading = true; + host.detectComponentChanges(); + expect(host.query('[disabled]')).toBeDefined(); + }); +}); From 0bcd977d1df796f6fed38ea2f40f87eaa6591237 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Sat, 28 Sep 2019 14:30:40 +0300 Subject: [PATCH 02/12] feature: error handler tests --- .../src/lib/handlers/error.handler.ts | 53 ++++++++------- .../src/lib/tests/error.handler.spec.ts | 64 +++++++++++++++++++ 2 files changed, 95 insertions(+), 22 deletions(-) create mode 100644 npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts diff --git a/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts b/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts index 0baff0da8f..d18cd45bf1 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts @@ -16,26 +16,31 @@ import { Toaster } from '../models/toaster'; import { ConfirmationService } from '../services/confirmation.service'; import snq from 'snq'; -const DEFAULTS = { +export const DEFAULT_ERROR_MESSAGES = { defaultError: { - message: 'An error has occurred!', + title: 'An error has occurred!', details: 'Error detail not sent by server.', }, - defaultError401: { - message: 'You are not authenticated!', + title: 'You are not authenticated!', details: 'You should be authenticated (sign in) in order to perform this operation.', }, - defaultError403: { - message: 'You are not authorized!', + title: 'You are not authorized!', details: 'You are not allowed to perform this operation.', }, - defaultError404: { - message: 'Resource not found!', + title: 'Resource not found!', details: 'The resource requested could not found on the server.', }, + defaultError500: { + title: '500', + details: 'AbpAccount::InternalServerErrorMessage', + }, + defaultErrorUnknown: { + title: 'Unknown Error', + details: 'AbpAccount::InternalServerErrorMessage', + }, }; @Injectable({ providedIn: 'root' }) @@ -51,7 +56,7 @@ export class ErrorHandler { ) { actions.pipe(ofActionSuccessful(RestOccurError)).subscribe(res => { const { payload: err = {} as HttpErrorResponse | any } = res; - const body = snq(() => (err as HttpErrorResponse).error.error, DEFAULTS.defaultError.message); + const body = snq(() => (err as HttpErrorResponse).error.error, DEFAULT_ERROR_MESSAGES.defaultError.title); if (err instanceof HttpErrorResponse && err.headers.get('_AbpErrorFormat')) { const confirmation$ = this.showError(null, null, body); @@ -64,35 +69,39 @@ export class ErrorHandler { } else { switch ((err as HttpErrorResponse).status) { case 401: - this.showError(DEFAULTS.defaultError401.details, DEFAULTS.defaultError401.message).subscribe(() => - this.navigateToLogin(), - ); + this.showError( + DEFAULT_ERROR_MESSAGES.defaultError401.details, + DEFAULT_ERROR_MESSAGES.defaultError401.title, + ).subscribe(() => this.navigateToLogin()); break; case 403: this.createErrorComponent({ - title: DEFAULTS.defaultError403.message, - details: DEFAULTS.defaultError403.details, + title: DEFAULT_ERROR_MESSAGES.defaultError403.title, + details: DEFAULT_ERROR_MESSAGES.defaultError403.details, }); break; case 404: - this.showError(DEFAULTS.defaultError404.details, DEFAULTS.defaultError404.message); + this.showError( + DEFAULT_ERROR_MESSAGES.defaultError404.details, + DEFAULT_ERROR_MESSAGES.defaultError404.title, + ); break; case 500: this.createErrorComponent({ - title: '500', - details: 'AbpAccount::InternalServerErrorMessage', + title: DEFAULT_ERROR_MESSAGES.defaultError500.title, + details: DEFAULT_ERROR_MESSAGES.defaultError500.details, }); break; case 0: if ((err as HttpErrorResponse).statusText === 'Unknown Error') { this.createErrorComponent({ - title: 'Unknown Error', - details: 'AbpAccount::InternalServerErrorMessage', + title: DEFAULT_ERROR_MESSAGES.defaultErrorUnknown.title, + details: DEFAULT_ERROR_MESSAGES.defaultErrorUnknown.details, }); } break; default: - this.showError(DEFAULTS.defaultError.details, DEFAULTS.defaultError.message); + this.showError(DEFAULT_ERROR_MESSAGES.defaultError.details, DEFAULT_ERROR_MESSAGES.defaultError.title); break; } } @@ -105,7 +114,7 @@ export class ErrorHandler { message = body.details; title = body.message; } else { - message = body.message || DEFAULTS.defaultError.message; + message = body.message || DEFAULT_ERROR_MESSAGES.defaultError.title; } } @@ -125,7 +134,7 @@ export class ErrorHandler { createErrorComponent(instance: Partial) { const renderer = this.rendererFactory.createRenderer(null, null); - const host = renderer.selectRootElement('app-root', true); + const host = renderer.selectRootElement(document.body, true); const componentRef = this.cfRes.resolveComponentFactory(ErrorComponent).create(this.injector); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts new file mode 100644 index 0000000000..0f64652cfe --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts @@ -0,0 +1,64 @@ +import { createHostFactory, SpectatorHost } from '@ngneat/spectator'; +import { Component } from '@angular/core'; +import { ErrorHandler, DEFAULT_ERROR_MESSAGES } from '../handlers'; +import { CoreModule, RestOccurError } from '@abp/ng.core'; +import { ThemeSharedModule } from '../theme-shared.module'; +import { NgxsModule, Store } from '@ngxs/store'; +import { RouterModule } from '@angular/router'; +import { HttpErrorResponse } from '@angular/common/http'; + +@Component({ selector: 'dummy', template: 'dummy works! ' }) +class DummyComponent { + constructor(public errorHandler: ErrorHandler, public store: Store) {} +} + +describe('With Custom Host Component', function() { + let host: SpectatorHost; + const createHost = createHostFactory({ + component: DummyComponent, + imports: [CoreModule, ThemeSharedModule.forRoot(), NgxsModule.forRoot([]), RouterModule.forRoot([])], + }); + + beforeEach(() => { + host = createHost(``); + const abpError = document.querySelector('abp-error'); + if (abpError) document.body.removeChild(abpError); + }); + + it('should display the error component when server error occurs', () => { + host.component.store.dispatch(new RestOccurError(new HttpErrorResponse({ status: 500 }))); + host.detectChanges(); + 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', () => { + host.component.store.dispatch(new RestOccurError(new HttpErrorResponse({ status: 403 }))); + host.detectChanges(); + 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', () => { + host.component.store.dispatch( + new RestOccurError(new HttpErrorResponse({ status: 0, statusText: 'Unknown Error' })), + ); + host.detectChanges(); + expect(document.querySelector('.error-template')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultErrorUnknown.title); + expect(document.querySelector('.error-details')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultErrorUnknown.details); + }); + + it('should display the confirmation when not found error occurs', () => { + host.component.store.dispatch(new RestOccurError(new HttpErrorResponse({ status: 404 }))); + host.detectChanges(); + expect(host.query('.abp-confirm-summary')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultError404.title); + expect(host.query('.abp-confirm-body')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultError404.details); + }); + + it('should display the confirmation when default error occurs', () => { + host.component.store.dispatch(new RestOccurError(new HttpErrorResponse({ status: 412 }))); + host.detectChanges(); + expect(host.query('.abp-confirm-summary')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultError.title); + expect(host.query('.abp-confirm-body')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultError.details); + }); +}); From c762edc1498d03f97f8aabc476e166b78e1bd116 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Mon, 30 Sep 2019 09:42:04 +0300 Subject: [PATCH 03/12] feature: add 401 error tests fix: some for directive bugs --- .../core/src/lib/directives/for.directive.ts | 2 + .../theme-shared/src/lib/abstracts/toaster.ts | 2 +- .../confirmation/confirmation.component.ts | 9 ++- .../src/lib/tests/error.handler.spec.ts | 63 ++++++++++++++++--- 4 files changed, 67 insertions(+), 9 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/directives/for.directive.ts b/npm/ng-packs/packages/core/src/lib/directives/for.directive.ts index 604f7a8660..f9c5abeb56 100644 --- a/npm/ng-packs/packages/core/src/lib/directives/for.directive.ts +++ b/npm/ng-packs/packages/core/src/lib/directives/for.directive.ts @@ -113,8 +113,10 @@ export class ForDirective implements OnChanges { private projectItems(items: any[]): void { if (!items.length && this.emptyRef) { + this.vcRef.clear(); this.vcRef.createEmbeddedView(this.emptyRef).rootNodes; this.isShowEmptyRef = true; + this.differ = null; return; } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/abstracts/toaster.ts b/npm/ng-packs/packages/theme-shared/src/lib/abstracts/toaster.ts index 5dbba869f1..9172297039 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/abstracts/toaster.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/abstracts/toaster.ts @@ -2,7 +2,7 @@ import { MessageService } from 'primeng/components/common/messageservice'; import { Observable, Subject } from 'rxjs'; import { Toaster } from '../models/toaster'; -export class AbstractToaster { +export abstract class AbstractToaster { status$: Subject; key: string = 'abpToast'; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.ts index 39cf9b73a2..398f975ff7 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.ts @@ -23,11 +23,18 @@ import { Toaster } from '../../models/toaster'; `, - styleUrls: ['./loader-bar.component.scss'], + styleUrls: ['./loader-bar.component.scss'] }) export class LoaderBarComponent implements OnDestroy { - @Input() - containerClass: string = 'abp-loader-bar'; - - @Input() - color: string = '#77b6ff'; - - @Input() - isLoading: boolean = false; - - @Input() - filter = (action: StartLoader | StopLoader) => action.payload.url.indexOf('openid-configuration') < 0; - - progressLevel: number = 0; - - interval: Subscription; - - timer: Subscription; - get boxShadow(): string { return `0 0 10px rgba(${this.color}, 0.5)`; } @@ -50,7 +32,7 @@ export class LoaderBarComponent implements OnDestroy { .pipe( ofActionSuccessful(StartLoader, StopLoader), filter(this.filter), - takeUntilDestroy(this), + takeUntilDestroy(this) ) .subscribe(action => { if (action instanceof StartLoader) this.startLoading(); @@ -61,15 +43,32 @@ export class LoaderBarComponent implements OnDestroy { .pipe( filter( event => - event instanceof NavigationStart || event instanceof NavigationEnd || event instanceof NavigationError, + event instanceof NavigationStart || event instanceof NavigationEnd || event instanceof NavigationError ), - takeUntilDestroy(this), + takeUntilDestroy(this) ) .subscribe(event => { if (event instanceof NavigationStart) this.startLoading(); else this.stopLoading(); }); } + @Input() + containerClass = 'abp-loader-bar'; + + @Input() + color = '#77b6ff'; + + @Input() + isLoading = false; + + progressLevel = 0; + + interval: Subscription; + + timer: Subscription; + + @Input() + filter = (action: StartLoader | StopLoader) => action.payload.url.indexOf('openid-configuration') < 0; ngOnDestroy() { this.interval.unsubscribe(); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.ts index e38ba270cb..5e6ebff228 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.ts @@ -9,7 +9,7 @@ import { Renderer2, TemplateRef, ViewChild, - ViewChildren, + ViewChildren } from '@angular/core'; import { fromEvent, Subject, timer } from 'rxjs'; import { filter, take, takeUntil, debounceTime } from 'rxjs/operators'; @@ -23,7 +23,7 @@ const ANIMATION_TIMEOUT = 200; @Component({ selector: 'abp-modal', - templateUrl: './modal.component.html', + templateUrl: './modal.component.html' }) export class ModalComponent implements OnDestroy { @Input() @@ -51,7 +51,7 @@ export class ModalComponent implements OnDestroy { this.renderer.addClass(this.modalContent.nativeElement, 'fade-out-top'); setTimeout(() => { this.setVisible(value); - this.ngOnDestroy(); + this.destroy$.next(); }, ANIMATION_TIMEOUT - 10); } } @@ -68,9 +68,9 @@ export class ModalComponent implements OnDestroy { this._busy = value; } - @Input() centered: boolean = false; + @Input() centered = false; - @Input() modalClass: string = ''; + @Input() modalClass = ''; @Input() size: ModalSize = 'lg'; @@ -78,9 +78,9 @@ export class ModalComponent implements OnDestroy { @Input() minHeight: number; - @Output() visibleChange = new EventEmitter(); + @Output() readonly visibleChange = new EventEmitter(); - @Output() init = new EventEmitter(); + @Output() readonly init = new EventEmitter(); @ContentChild('abpHeader', { static: false }) abpHeader: TemplateRef; @@ -88,29 +88,29 @@ export class ModalComponent implements OnDestroy { @ContentChild('abpFooter', { static: false }) abpFooter: TemplateRef; - @ContentChild('abpClose', { static: false, read: ElementRef }) abpClose: ElementRef; + @ContentChild('abpClose', { static: false, read: ElementRef }) + abpClose: ElementRef; - @ContentChild(ButtonComponent, { static: false, read: ButtonComponent }) abpSubmit: ButtonComponent; + @ContentChild(ButtonComponent, { static: false, read: ButtonComponent }) + abpSubmit: ButtonComponent; @ViewChild('abpModalContent', { static: false }) modalContent: ElementRef; @ViewChildren('abp-button') abpButtons; - @Output() - show = new EventEmitter(); + @Output() readonly appear = new EventEmitter(); - @Output() - hide = new EventEmitter(); + @Output() readonly disappear = new EventEmitter(); - _visible: boolean = false; + _visible = false; - _busy: boolean = false; + _busy = false; - showModal: boolean = false; + showModal = false; - isOpenConfirmation: boolean = false; + isOpenConfirmation = false; - closable: boolean = false; + closable = false; destroy$ = new Subject(); @@ -131,11 +131,11 @@ export class ModalComponent implements OnDestroy { .subscribe(_ => (this.closable = true)); this.renderer.addClass(document.body, 'modal-open'); - this.show.emit(); + this.appear.emit(); } else { this.closable = false; this.renderer.removeClass(document.body, 'modal-open'); - this.hide.emit(); + this.disappear.emit(); } } @@ -144,7 +144,7 @@ export class ModalComponent implements OnDestroy { .pipe( takeUntil(this.destroy$), debounceTime(150), - filter((key: KeyboardEvent) => key && key.code === 'Escape' && this.closable), + filter((key: KeyboardEvent) => key && key.code === 'Escape' && this.closable) ) .subscribe(_ => { this.close(); @@ -155,7 +155,7 @@ export class ModalComponent implements OnDestroy { fromEvent(this.abpClose.nativeElement, 'click') .pipe( takeUntil(this.destroy$), - filter(() => !!(this.closable && this.modalContent)), + filter(() => !!(this.closable && this.modalContent)) ) .subscribe(() => this.close()); }, 0); @@ -167,7 +167,7 @@ export class ModalComponent implements OnDestroy { if (!this.closable || this.busy) return; const nodes = getFlatNodes( - (this.modalContent.nativeElement.querySelector('#abp-modal-body') as HTMLElement).childNodes, + (this.modalContent.nativeElement.querySelector('#abp-modal-body') as HTMLElement).childNodes ); if (hasNgDirty(nodes)) { @@ -194,7 +194,7 @@ export class ModalComponent implements OnDestroy { function getFlatNodes(nodes: NodeList): HTMLElement[] { return Array.from(nodes).reduce( (acc, val) => [...acc, ...(val.childNodes && val.childNodes.length ? getFlatNodes(val.childNodes) : [val])], - [], + [] ); } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/profile/profile.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/profile/profile.component.ts index 0dc8ef2768..e7d110365b 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/profile/profile.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/profile/profile.component.ts @@ -9,7 +9,7 @@ const { maxLength, required, email } = Validators; @Component({ selector: 'abp-profile', - templateUrl: './profile.component.html', + templateUrl: './profile.component.html' }) export class ProfileComponent implements OnChanges { protected _visible; @@ -24,15 +24,14 @@ export class ProfileComponent implements OnChanges { this.visibleChange.emit(value); } - @Output() - visibleChange = new EventEmitter(); + @Output() readonly visibleChange = new EventEmitter(); @Select(ProfileState.getProfile) profile$: Observable; form: FormGroup; - modalBusy: boolean = false; + modalBusy = false; constructor(private fb: FormBuilder, private store: Store) {} @@ -41,7 +40,7 @@ export class ProfileComponent implements OnChanges { .dispatch(new GetProfile()) .pipe( withLatestFrom(this.profile$), - take(1), + take(1) ) .subscribe(([, profile]) => { this.form = this.fb.group({ @@ -49,7 +48,7 @@ export class ProfileComponent implements OnChanges { email: [profile.email, [required, email, maxLength(256)]], name: [profile.name || '', [maxLength(64)]], surname: [profile.surname || '', [maxLength(64)]], - phoneNumber: [profile.phoneNumber || '', [maxLength(16)]], + phoneNumber: [profile.phoneNumber || '', [maxLength(16)]] }); }); } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/table-empty-message/table-empty-message.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/table-empty-message/table-empty-message.component.ts index e53b90129e..5924a9f187 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/table-empty-message/table-empty-message.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/table-empty-message/table-empty-message.component.ts @@ -1,12 +1,13 @@ import { Component, OnInit, Input } from '@angular/core'; @Component({ + // tslint:disable-next-line: component-selector selector: '[abp-table-empty-message]', template: ` {{ emptyMessage | abpLocalization }} - `, + ` }) export class TableEmptyMessageComponent { @Input() @@ -16,10 +17,10 @@ export class TableEmptyMessageComponent { message: string; @Input() - localizationResource: string = 'AbpAccount'; + localizationResource = 'AbpAccount'; @Input() - localizationProp: string = 'NoDataAvailableInDatatable'; + localizationProp = 'NoDataAvailableInDatatable'; get emptyMessage(): string { return this.message || `${this.localizationResource}::${this.localizationProp}`; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/toast/toast.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/toast/toast.component.ts index 382b18974b..422c67f3b9 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/toast/toast.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/toast/toast.component.ts @@ -2,6 +2,7 @@ import { Component } from '@angular/core'; @Component({ selector: 'abp-toast', + // tslint:disable-next-line: component-max-inline-declarations template: ` @@ -20,6 +21,6 @@ import { Component } from '@angular/core'; - `, + ` }) export class ToastComponent {} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/contants/scripts.ts b/npm/ng-packs/packages/theme-shared/src/lib/contants/scripts.ts index 1bc3ad0fa9..08d725cd4e 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/contants/scripts.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/contants/scripts.ts @@ -1 +1 @@ -export default ``; +export default ''; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/services/confirmation.service.ts b/npm/ng-packs/packages/theme-shared/src/lib/services/confirmation.service.ts index f8338d95e8..69bb5eaf72 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/services/confirmation.service.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/services/confirmation.service.ts @@ -8,9 +8,9 @@ import { Toaster } from '../models/toaster'; @Injectable({ providedIn: 'root' }) export class ConfirmationService extends AbstractToaster { - key: string = 'abpConfirmation'; + key = 'abpConfirmation'; - sticky: boolean = true; + sticky = true; destroy$ = new Subject(); @@ -22,7 +22,7 @@ export class ConfirmationService extends AbstractToaster { message: string, title: string, severity: Toaster.Severity, - options?: Confirmation.Options, + options?: Confirmation.Options ): Observable { this.listenToEscape(); @@ -40,7 +40,7 @@ export class ConfirmationService extends AbstractToaster { .pipe( takeUntil(this.destroy$), debounceTime(150), - filter((key: KeyboardEvent) => key && key.code === 'Escape'), + filter((key: KeyboardEvent) => key && key.code === 'Escape') ) .subscribe(_ => { this.clear(); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/button.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/button.component.spec.ts index bfb70f9518..b8682ae224 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/button.component.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/button.component.spec.ts @@ -4,7 +4,7 @@ import { createHostFactory, Spectator, SpectatorHost, - createTestComponentFactory, + createTestComponentFactory } from '@ngneat/spectator'; import { ButtonComponent } from '../components'; @@ -13,7 +13,7 @@ describe('ButtonComponent', () => { const createHost = createHostFactory(ButtonComponent); - beforeEach(() => (host = createHost(`Button`))); + beforeEach(() => (host = createHost('Button'))); it('should display the button', () => { expect(host.query('button')).toBeTruthy(); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts index 835f4a33a9..26e3753609 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts @@ -9,18 +9,18 @@ import { NgxsResetPluginModule, StateOverwrite } from 'ngxs-reset-plugin'; import { DEFAULT_ERROR_MESSAGES, ErrorHandler } from '../handlers'; import { ThemeSharedModule } from '../theme-shared.module'; -@Component({ selector: 'dummy', template: 'dummy works! ' }) +@Component({ selector: 'abp-dummy', template: 'dummy works! ' }) class DummyComponent { constructor(public errorHandler: ErrorHandler, public store: Store) {} } -describe('With Custom Host Component', function() { +describe('With Custom Host Component', () => { let component: SpectatorRouting; const createComponent = createRoutingFactory({ component: DummyComponent, imports: [CoreModule, ThemeSharedModule.forRoot(), NgxsModule.forRoot([]), NgxsResetPluginModule.forRoot()], stubsEnabled: false, - routes: [{ path: '', component: DummyComponent }, { path: 'account/login', component: RouterOutletComponent }], + routes: [{ path: '', component: DummyComponent }, { path: 'account/login', component: RouterOutletComponent }] }); beforeEach(() => { @@ -45,7 +45,7 @@ describe('With Custom Host Component', function() { it('should display the error component when unknown error occurs', () => { component.component.store.dispatch( - new RestOccurError(new HttpErrorResponse({ status: 0, statusText: 'Unknown Error' })), + new RestOccurError(new HttpErrorResponse({ status: 0, statusText: 'Unknown Error' })) ); component.detectChanges(); expect(document.querySelector('.error-template')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultErrorUnknown.title); @@ -97,9 +97,9 @@ describe('With Custom Host Component', function() { new HttpErrorResponse({ error: { error: { message: 'test message', details: 'test detail' } }, status: 412, - headers, - }), - ), + headers + }) + ) ); component.detectChanges(); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts b/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts index 58e2ab5bb4..a1839f6217 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts @@ -21,7 +21,7 @@ import { TableEmptyMessageComponent } from './components/table-empty-message/tab import { NgxValidateCoreModule } from '@ngx-validate/core'; export function appendScript(injector: Injector) { - const fn = function() { + const fn = () => { import('chart.js').then(() => chartJsLoaded$.next(true)); const lazyLoadService: LazyLoadService = injector.get(LazyLoadService); @@ -32,8 +32,8 @@ export function appendScript(injector: Injector) { 'style', styles, 'head', - 'afterbegin', - ) /* lazyLoadService.load(null, 'script', scripts) */, + 'afterbegin' + ) /* lazyLoadService.load(null, 'script', scripts) */ ).pipe(take(1)); }; @@ -53,7 +53,7 @@ export function appendScript(injector: Injector) { ModalComponent, ProfileComponent, TableEmptyMessageComponent, - ToastComponent, + ToastComponent ], exports: [ BreadcrumbComponent, @@ -65,9 +65,9 @@ export function appendScript(injector: Injector) { ModalComponent, ProfileComponent, TableEmptyMessageComponent, - ToastComponent, + ToastComponent ], - entryComponents: [ErrorComponent], + entryComponents: [ErrorComponent] }) export class ThemeSharedModule { static forRoot(): ModuleWithProviders { @@ -78,10 +78,10 @@ export class ThemeSharedModule { provide: APP_INITIALIZER, multi: true, deps: [Injector, ErrorHandler], - useFactory: appendScript, + useFactory: appendScript }, - { provide: MessageService, useClass: MessageService }, - ], + { provide: MessageService, useClass: MessageService } + ] }; } } diff --git a/npm/ng-packs/packages/theme-shared/tslint.json b/npm/ng-packs/packages/theme-shared/tslint.json index 124133f849..9d39c7dc74 100644 --- a/npm/ng-packs/packages/theme-shared/tslint.json +++ b/npm/ng-packs/packages/theme-shared/tslint.json @@ -1,17 +1,7 @@ { "extends": "../../tslint.json", "rules": { - "directive-selector": [ - true, - "attribute", - "lib", - "camelCase" - ], - "component-selector": [ - true, - "element", - "lib", - "kebab-case" - ] + "directive-selector": [true, "attribute", "abp", "camelCase"], + "component-selector": [true, "element", "abp", "kebab-case"] } } diff --git a/npm/ng-packs/tslint.json b/npm/ng-packs/tslint.json index 0888c34adb..348320008c 100644 --- a/npm/ng-packs/tslint.json +++ b/npm/ng-packs/tslint.json @@ -1,55 +1,100 @@ { "extends": "tslint:recommended", + "rulesDirectory": ["node_modules/codelyzer"], "rules": { "array-type": false, + "contextual-lifecycle": true, + "component-class-suffix": [true, "Component"], + "directive-class-suffix": [true, "Directive"], + "max-line-length": [true, 140], + "no-consecutive-blank-lines": false, + "no-redundant-jsdoc": true, + "no-var-requires": false, + "object-literal-key-quotes": [true, "as-needed"], + "ordered-imports": false, + "trailing-comma": false, + "component-max-inline-declarations": [true, { "animations": 20, "styles": 10, "template": 10 }], + "no-forward-ref": true, + "no-lifecycle-call": true, + "no-pipe-impure": true, + "no-queries-metadata-property": true, + "no-unused-css": true, + "prefer-output-readonly": true, + "template-conditional-complexity": [true, 4], + "use-component-selector": true, + "max-classes-per-file": false, "arrow-parens": false, + "arrow-return-shorthand": true, + "callable-types": true, + "class-name": true, + "component-selector": [true, "element", "abp", "kebab-case"], + "curly": false, "deprecation": { "severity": "warn" }, - "component-class-suffix": true, - "contextual-lifecycle": true, - "directive-class-suffix": true, "directive-selector": [true, "attribute", "abp", "camelCase"], - "component-selector": [true, "element", "abp", "kebab-case"], + "forin": true, "import-blacklist": [true, "rxjs/Rx"], - "interface-name": false, - "max-classes-per-file": false, - "max-line-length": [true, 140], - "member-access": false, + "interface-over-type-literal": true, + "interface-name": [true, "never-prefix"], + "member-access": [true, "no-public"], "member-ordering": [ true, { "order": ["static-field", "instance-field", "static-method", "instance-method"] } ], - "no-consecutive-blank-lines": false, + "no-arg": true, + "no-bitwise": true, + "no-conflicting-lifecycle": true, "no-console": [true, "debug", "info", "time", "timeEnd", "trace"], + "no-construct": true, + "no-debugger": true, + "no-duplicate-super": true, + "no-empty-interface": true, "no-empty": false, - "no-inferrable-types": [false, "ignore-params"], - "no-non-null-assertion": true, - "no-redundant-jsdoc": true, - "no-switch-case-fall-through": true, - "no-use-before-declare": true, - "no-var-requires": false, - "curly": false, - "object-literal-key-quotes": [true, "as-needed"], - "object-literal-sort-keys": false, - "ordered-imports": false, - "quotemark": [true, "single"], - "trailing-comma": false, - "no-conflicting-lifecycle": true, + "no-eval": true, "no-host-metadata-property": true, - "no-input-rename": true, + "no-inferrable-types": [true, "ignore-params"], + "no-input-rename": false, "no-inputs-metadata-property": true, + "no-misused-new": true, + "no-namespace": false, + "no-non-null-assertion": true, "no-output-native": true, "no-output-on-prefix": true, - "no-output-rename": true, + "no-output-rename": false, "no-outputs-metadata-property": true, - "no-namespace": false, - "template-banana-in-box": true, - "template-no-negated-async": true, + "no-shadowed-variable": true, + "no-string-literal": false, + "no-string-throw": true, + "no-switch-case-fall-through": true, + "no-unnecessary-initializer": true, + "no-unnecessary-semicolons": false, + "no-unused-expression": true, + "no-var-keyword": true, + "object-literal-sort-keys": false, + "prefer-const": true, + "quotemark": [true, "single", "avoid-escape", "avoid-template"], + "radix": true, + "semicolon": [true, "always", "ignore-bound-class-methods"], + // "template-accessibility-alt-text": true, + // "template-accessibility-elements-content": true, + // "template-accessibility-label-for": true, + // "template-accessibility-tabindex-no-positive": true, + // "template-accessibility-table-scope": true, + // "template-accessibility-valid-aria": true, + // "template-banana-in-box": true, + // "template-click-events-have-key-events": true, + // "template-mouse-events-have-key-events": true, + // "template-no-autofocus": true, + // "template-no-distracting-elements": true, + // "template-no-negated-async": true, + "triple-equals": [true, "allow-null-check"], + "unified-signatures": true, "use-lifecycle-interface": true, - "use-pipe-transform-interface": true - }, - "rulesDirectory": ["codelyzer"] + "use-pipe-transform-interface": true, + "variable-name": false, + "prefer-for-of": false + } }