From 8b07e0d31bee258d903b8d8580afcd83dc1544c8 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Mon, 17 Oct 2022 19:27:40 +0300 Subject: [PATCH] update tests & add if condition to permissions directive --- .../permissions/permissions.directive.spec.ts | 207 ++++++++++++++---- src/lib/permissions/permissions.directive.ts | 38 +++- 2 files changed, 198 insertions(+), 47 deletions(-) diff --git a/src/lib/permissions/permissions.directive.spec.ts b/src/lib/permissions/permissions.directive.spec.ts index 62b18e4..c538647 100644 --- a/src/lib/permissions/permissions.directive.spec.ts +++ b/src/lib/permissions/permissions.directive.spec.ts @@ -3,6 +3,7 @@ import { IqserPermissionsModule } from '.'; import { ComponentFixture, ComponentFixtureAutoDetect, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { IqserPermissionsService } from './services/permissions.service'; import { IqserRolesService } from './services/roles.service'; +import { BehaviorSubject } from 'rxjs'; class BaseTestComponent { isAuthorized() {} @@ -184,16 +185,7 @@ describe('Permission directive with roles array', () => { it('should show the component when key of role is the same', fakeAsync(() => { permissionsService.add(awesomePermission); rolesService.add({ ADMIN: [awesomePermission] }); - tick(); - - const content = getFixtureContent(); - expect(content).toBeTruthy(); - expect(content.innerHTML).toEqual('123'); - })); - - it('should show the component when there is permission', fakeAsync(() => { - permissionsService.add(awesomePermission); - rolesService.add({ ADMIN: ['AWESOME'] }); + rolesService.add({ GUEST: [awesomePermission] }); tick(); const content = getFixtureContent(); @@ -204,6 +196,7 @@ describe('Permission directive with roles array', () => { it('should hide the component when user deletes all roles', fakeAsync(() => { permissionsService.add(awesomePermission); rolesService.add({ ADMIN: [awesomePermission] }); + rolesService.add({ GUEST: [awesomePermission] }); tick(); const content = getFixtureContent(); @@ -216,9 +209,10 @@ describe('Permission directive with roles array', () => { expect(getFixtureContent()).toEqual(null); })); - it('should hide the component when user deletes one roles', fakeAsync(() => { + it('should hide the component when user deletes one role', fakeAsync(() => { permissionsService.add(awesomePermission); rolesService.add({ ADMIN: [awesomePermission] }); + rolesService.add({ GUEST: [awesomePermission] }); tick(); const content = getFixtureContent(); @@ -329,6 +323,7 @@ describe('Permission directive angular testing different async functions in role expect(getFixtureContent()).toEqual(null); rolesService.add({ ADMIN: () => Promise.resolve(true) }); + rolesService.add({ GUEST: () => Promise.resolve(true) }); tick(); const content = getFixtureContent(); @@ -340,6 +335,7 @@ describe('Permission directive angular testing different async functions in role expect(getFixtureContent()).toEqual(null); rolesService.add({ ADMIN: () => Promise.resolve(false) }); + rolesService.add({ GUEST: () => Promise.resolve(true) }); tick(); expect(getFixtureContent()).toEqual(null); @@ -348,32 +344,29 @@ describe('Permission directive angular testing different async functions in role it('should not show the component when promise rejects', fakeAsync(() => { expect(getFixtureContent()).toEqual(null); - rolesService.add({ ADMIN: () => Promise.reject() }); + rolesService.add({ ADMIN: () => Promise.resolve(true) }); + rolesService.add({ GUEST: () => Promise.reject() }); tick(); expect(getFixtureContent()).toEqual(null); })); - it('should show the component when one of the promises fulfills', fakeAsync(() => { + it('should hide the component when one of the promises fulfills', fakeAsync(() => { expect(getFixtureContent()).toEqual(null); rolesService.add({ ADMIN: () => Promise.reject(), GUEST: () => Promise.resolve(true) }); tick(); - const content = getFixtureContent(); - expect(content).toBeTruthy(); - expect(content.innerHTML).toEqual('
123
'); + expect(getFixtureContent()).toEqual(null); })); - it('should show the component when one of the promises fulfills with true value', fakeAsync(() => { + it('should hide the component when one of the promises fulfills with true value', fakeAsync(() => { expect(getFixtureContent()).toEqual(null); rolesService.add({ ADMIN: () => Promise.reject(), GUEST: () => Promise.resolve(true) }); tick(); - const content = getFixtureContent(); - expect(content).toBeTruthy(); - expect(content.innerHTML).toEqual('
123
'); + expect(getFixtureContent()).toEqual(null); })); it('should not show the component when all promises fails', fakeAsync(() => { @@ -385,38 +378,23 @@ describe('Permission directive angular testing different async functions in role expect(getFixtureContent()).toEqual(null); })); - it('should show the component when one of promises returns true', fakeAsync(() => { + it('should hide the component when one of promises returns true', fakeAsync(() => { expect(getFixtureContent()).toEqual(null); rolesService.add({ GUEST: () => true, ADMIN: () => Promise.reject() }); tick(); - const content = getFixtureContent(); - expect(content).toBeTruthy(); - expect(content.innerHTML).toEqual('
123
'); + expect(getFixtureContent()).toEqual(null); })); - it('should show the component when 1 passes second fails', fakeAsync(() => { + it('should hide the component when 1 passes second fails', fakeAsync(() => { expect(getFixtureContent()).toEqual(null); rolesService.add({ ADMIN: () => Promise.reject() }); - rolesService.add({ ADMIN: ['AWESOME'] }); + rolesService.add({ GUEST: ['AWESOME'] }); tick(); - const content = getFixtureContent(); - expect(content).toBeTruthy(); - expect(content.innerHTML).toEqual('
123
'); - })); - - it('should show the component when one rejects but another one fulfils', fakeAsync(() => { expect(getFixtureContent()).toEqual(null); - - rolesService.add({ ADMIN: () => Promise.reject(), GUEST: ['AWESOME'] }); - tick(); - - const content = getFixtureContent(); - expect(content).toBeTruthy(); - expect(content.innerHTML).toEqual('
123
'); })); }); @@ -806,3 +784,154 @@ describe('Permissions directive with an empty array', () => { expect(content.innerHTML).toEqual(`123`); }); }); + +describe('Permission directive with true if condition', () => { + @Component({ + template: ` +
123
+
`, + }) + class TestComponent extends BaseTestComponent {} + + beforeEach(() => configureTestBed(TestComponent)); + + it('should show the component when permission is present', fakeAsync(() => { + permissionsService.add(ADMIN); + tick(); + + const content = getFixtureContent(); + expect(content).toBeTruthy(); + expect(content.innerHTML).toEqual('123'); + })); + + it('should hide the component when permission is absent', fakeAsync(() => { + expect(getFixtureContent()).toEqual(null); + })); +}); + +describe('Permission directive with true if condition', () => { + @Component({ + template: ` +
123
+
`, + }) + class TestComponent extends BaseTestComponent {} + + beforeEach(() => configureTestBed(TestComponent)); + + it('should hide the component when permission is present', fakeAsync(() => { + permissionsService.add(ADMIN); + tick(); + + expect(getFixtureContent()).toEqual(null); + })); + + it('should hide the component when permission is absent', fakeAsync(() => { + expect(getFixtureContent()).toEqual(null); + })); +}); + +describe('Permission directive with true promise if condition', () => { + @Component({ + template: ` +
123
+
`, + }) + class TestComponent extends BaseTestComponent { + condition = Promise.resolve(true); + } + + beforeEach(() => configureTestBed(TestComponent)); + + it('should show the component when permission is present', fakeAsync(() => { + permissionsService.add(ADMIN); + tick(); + + const content = getFixtureContent(); + expect(content).toBeTruthy(); + expect(content.innerHTML).toEqual('123'); + })); + + it('should hide the component when permission is absent', fakeAsync(() => { + expect(getFixtureContent()).toEqual(null); + })); +}); + +describe('Permission directive with false promise if condition', () => { + @Component({ + template: `
+
123
+
`, + }) + class TestComponent extends BaseTestComponent { + condition = Promise.resolve(false); + } + + beforeEach(() => configureTestBed(TestComponent)); + + it('should hide the component when permission is present', fakeAsync(() => { + permissionsService.add(ADMIN); + tick(); + + expect(getFixtureContent()).toEqual(null); + })); + + it('should hide the component when permission is absent', fakeAsync(() => { + expect(getFixtureContent()).toEqual(null); + })); +}); + +describe('Permissions directive with then block and if condition', () => { + @Component({ + template: ` +
main
+ + +
elseBlockContent
+
+ + +
thenBlockContent
+
+ `, + }) + class TestComponent extends BaseTestComponent { + condition = new BehaviorSubject(true); + } + + beforeEach(() => configureTestBed(TestComponent)); + + it('should show else block when permission missing', fakeAsync(() => { + const content = getFixtureContent(); + expect(content).toBeTruthy(); + expect(content.innerHTML).toEqual(`elseBlockContent`); + })); + + it('should show then block when permission added', fakeAsync(() => { + permissionsService.add('THEN_BLOCK'); + tick(); + + const content = getFixtureContent(); + expect(content).toBeTruthy(); + expect(content.innerHTML).toEqual(`thenBlockContent`); + })); + + it('should show else block when permission added but condition is false', fakeAsync(() => { + permissionsService.add('THEN_BLOCK'); + (fixture.componentInstance as TestComponent).condition.next(false); + tick(); + + const content = getFixtureContent(); + expect(content).toBeTruthy(); + expect(content.innerHTML).toEqual(`elseBlockContent`); + })); + + it('should show else block when permission is missing and condition is false', fakeAsync(() => { + (fixture.componentInstance as TestComponent).condition.next(false); + tick(); + + const content = getFixtureContent(); + expect(content).toBeTruthy(); + expect(content.innerHTML).toEqual(`elseBlockContent`); + })); +}); diff --git a/src/lib/permissions/permissions.directive.ts b/src/lib/permissions/permissions.directive.ts index f67aba3..e1273a3 100644 --- a/src/lib/permissions/permissions.directive.ts +++ b/src/lib/permissions/permissions.directive.ts @@ -12,12 +12,13 @@ import { ɵstringify as stringify, } from '@angular/core'; -import { merge, Subject, Subscription, switchMap } from 'rxjs'; -import { tap } from 'rxjs/operators'; +import { BehaviorSubject, combineLatest, merge, Observable, of, Subject, Subscription, switchMap } from 'rxjs'; +import { map, tap } from 'rxjs/operators'; import { IqserRolesService } from './services/roles.service'; import { IqserPermissionsService } from './services/permissions.service'; import { List } from '../utils'; +import { isBoolean } from './utils'; @Directive({ selector: '[allow]', @@ -45,6 +46,7 @@ export class IqserPermissionsDirective implements OnDestroy, OnInit { readonly #updateView = new Subject(); readonly #subscription = new Subscription(); + readonly #if = new BehaviorSubject | Observable>(of(true)); constructor( private readonly _permissionsService: IqserPermissionsService, @@ -54,7 +56,17 @@ export class IqserPermissionsDirective implements OnDestroy, OnInit { templateRef: TemplateRef, ) { this.#thenTemplateRef = templateRef; - this.#subscription = this.#updateView.pipe(switchMap(() => this.#waitForRolesAndPermissions())).subscribe(); + + const ifCondition$ = this.#if.pipe( + switchMap(condition => condition), + tap(console.log), + ); + this.#subscription = combineLatest([ifCondition$, this.#updateView]) + .pipe( + switchMap(([ifCondition]) => this.#waitForRolesAndPermissions().pipe(map(hasPermission => ifCondition && hasPermission))), + tap(isAllowed => (isAllowed ? this.#showThenBlock() : this.#showElseBlock())), + ) + .subscribe(); } @Input() @@ -79,6 +91,15 @@ export class IqserPermissionsDirective implements OnDestroy, OnInit { this.#updateView.next(); } + @Input() + set allowIf(value: boolean | Promise | Observable) { + if (isBoolean(value)) { + this.#if.next(of(value)); + } else { + this.#if.next(value); + } + } + /** This assures that when the directive has an empty input (such as [allow]="") the view is updated */ @@ -91,17 +112,18 @@ export class IqserPermissionsDirective implements OnDestroy, OnInit { } #waitForRolesAndPermissions() { - return merge(this._permissionsService.permissions$, this._rolesService.roles$).pipe(tap(() => this.#validate())); + return merge(this._permissionsService.permissions$, this._rolesService.roles$).pipe(switchMap(() => this.#validate())); } - #validate(): void { + #validate() { if (!this.#permissions) { - return this.#showThenBlock(); + return Promise.resolve(true); } const promises = [this._permissionsService.has(this.#permissions), this._rolesService.has(this.#permissions)]; - const result = Promise.all(promises).then(([hasPermission, hasRole]) => hasPermission || hasRole); - result.then(isAllowed => (isAllowed ? this.#showThenBlock() : this.#showElseBlock())).catch(() => this.#showElseBlock()); + return Promise.all(promises) + .then(([hasPermission, hasRole]) => hasPermission || hasRole) + .catch(() => false); } #showElseBlock() {