From a4b9c045cd8dae1418bc274d311e6b938d9ddbf5 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Mon, 19 Dec 2022 12:06:07 +0200 Subject: [PATCH] add deny input to allow directive & update tests --- src/lib/permissions/README.md | 19 +- .../directives/allow/allow.directive.spec.ts | 189 +++++++++++++----- .../directives/allow/allow.directive.ts | 28 +++ .../directives/deny/deny.directive.ts | 2 +- .../services/permissions.service.ts | 33 ++- src/lib/permissions/services/roles.service.ts | 23 +-- 6 files changed, 206 insertions(+), 88 deletions(-) diff --git a/src/lib/permissions/README.md b/src/lib/permissions/README.md index 409e1da..40bd849 100644 --- a/src/lib/permissions/README.md +++ b/src/lib/permissions/README.md @@ -28,8 +28,8 @@ export class AppComponent implements OnInit {
You can see this text congrats
@@ -38,11 +38,24 @@ export class AppComponent implements OnInit {
You can see this text congrats
+---------------------------------------------- +
+
You cant see this text congrats
+
+ + +
You cant see this text congrats
+
---------------------------------------------- @@ -57,7 +70,7 @@ export class AppComponent implements OnInit { ---------------------------------------------- -
main
+
main
elseBlock
diff --git a/src/lib/permissions/directives/allow/allow.directive.spec.ts b/src/lib/permissions/directives/allow/allow.directive.spec.ts index 34bc3d7..ab9ba23 100644 --- a/src/lib/permissions/directives/allow/allow.directive.spec.ts +++ b/src/lib/permissions/directives/allow/allow.directive.spec.ts @@ -41,11 +41,13 @@ function getFixtureContent(): HTMLElement { describe('Permission directive', () => { @Component({ - template: ` -
123
-
`, + template: ` + +
123
+
`, }) - class TestComponent extends BaseTestComponent {} + class TestComponent extends BaseTestComponent { + } beforeEach(() => configureTestBed(TestComponent)); @@ -104,11 +106,13 @@ describe('Permission directive', () => { describe('Permission directive with roles', () => { @Component({ - template: ` -
123
-
`, + template: ` + +
123
+
`, }) - class TestComponent extends BaseTestComponent {} + class TestComponent extends BaseTestComponent { + } const awesomePermissions = 'AWESOME'; beforeEach(() => configureTestBed(TestComponent)); @@ -166,11 +170,13 @@ describe('Permission directive with roles', () => { describe('Permission directive with roles array', () => { @Component({ - template: ` -
123
-
`, + template: ` + +
123
+
`, }) - class TestComponent extends BaseTestComponent {} + class TestComponent extends BaseTestComponent { + } const awesomePermission = 'AWESOME'; beforeEach(() => configureTestBed(TestComponent)); @@ -221,11 +227,13 @@ describe('Permission directive with roles array', () => { describe('Permission directive testing different selectors *allow', () => { @Component({ - template: `
-
123
-
`, + template: ` +
+
123
+
`, }) - class TestComponent extends BaseTestComponent {} + class TestComponent extends BaseTestComponent { + } beforeEach(() => configureTestBed(TestComponent)); @@ -249,15 +257,56 @@ describe('Permission directive testing different selectors *allow', () => { expect(getFixtureContent()).toEqual(null); })); + + it('should hide the component when deny permission is added', fakeAsync(() => { + permissionsService.add(ADMIN); + tick(); + const content = getFixtureContent(); + expect(content).toBeTruthy(); + expect(content.innerHTML).toEqual('
123
'); + + permissionsService.add(GUEST); + tick(); + + expect(getFixtureContent()).toEqual(null); + })); + + it('should hide the component when deny role is added', fakeAsync(() => { + permissionsService.add(ADMIN); + tick(); + const content = getFixtureContent(); + expect(content).toBeTruthy(); + expect(content.innerHTML).toEqual('
123
'); + + rolesService.add(GUEST); + tick(); + + expect(getFixtureContent()).toEqual(null); + })); + + it('should show the component when deny permission is remove', fakeAsync(() => { + permissionsService.add([ADMIN, GUEST]); + tick(); + expect(getFixtureContent()).toEqual(null); + + permissionsService.remove(GUEST); + tick(); + + const content = getFixtureContent(); + expect(content).toBeTruthy(); + expect(content.innerHTML).toEqual('
123
'); + })); }); describe('Permission directive angular testing different async functions in roles', () => { @Component({ - template: `
-
123
-
`, + template: ` +
+
123
+
`, }) - class TestComponent extends BaseTestComponent {} + class TestComponent extends BaseTestComponent { + } beforeEach(() => configureTestBed(TestComponent)); @@ -284,11 +333,13 @@ describe('Permission directive angular testing different async functions in role describe('Permission directive angular testing different async functions in roles via array', () => { @Component({ - template: `
-
123
-
`, + template: ` +
+
123
+
`, }) - class TestComponent extends BaseTestComponent {} + class TestComponent extends BaseTestComponent { + } beforeEach(() => configureTestBed(TestComponent)); @@ -327,11 +378,13 @@ describe('Permission directive angular testing different async functions in role describe('Permission directive with different async functions in permissions via array', () => { @Component({ - template: `
-
123
-
`, + template: ` +
+
123
+
`, }) - class TestComponent extends BaseTestComponent {} + class TestComponent extends BaseTestComponent { + } beforeEach(() => configureTestBed(TestComponent)); @@ -390,11 +443,13 @@ describe('Permission directive with different async functions in permissions via describe('Permission directive testing different async functions in permissions via string', () => { @Component({ - template: `
-
123
-
`, + template: ` +
+
123
+
`, }) - class TestComponent extends BaseTestComponent {} + class TestComponent extends BaseTestComponent { + } beforeEach(() => configureTestBed(TestComponent)); @@ -472,7 +527,8 @@ describe('Permissions directive with else block', () => {
`, }) - class TestComponent extends BaseTestComponent {} + class TestComponent extends BaseTestComponent { + } beforeEach(() => configureTestBed(TestComponent)); @@ -513,7 +569,8 @@ describe('Permissions directive with then block', () => { `, }) - class TestComponent extends BaseTestComponent {} + class TestComponent extends BaseTestComponent { + } beforeEach(() => configureTestBed(TestComponent)); @@ -535,7 +592,8 @@ describe('Permissions directive with empty argument', () => { `, }) - class TestComponent extends BaseTestComponent {} + class TestComponent extends BaseTestComponent { + } beforeEach(() => configureTestBed(TestComponent)); @@ -554,7 +612,8 @@ describe('Permissions directive with an empty array', () => { `, }) - class TestComponent extends BaseTestComponent {} + class TestComponent extends BaseTestComponent { + } beforeEach(() => configureTestBed(TestComponent)); @@ -567,11 +626,13 @@ describe('Permissions directive with an empty array', () => { describe('Permission directive with true if condition', () => { @Component({ - template: ` -
123
-
`, + template: ` + +
123
+
`, }) - class TestComponent extends BaseTestComponent {} + class TestComponent extends BaseTestComponent { + } beforeEach(() => configureTestBed(TestComponent)); @@ -591,11 +652,13 @@ describe('Permission directive with true if condition', () => { describe('Permission directive with true if condition', () => { @Component({ - template: ` -
123
-
`, + template: ` + +
123
+
`, }) - class TestComponent extends BaseTestComponent {} + class TestComponent extends BaseTestComponent { + } beforeEach(() => configureTestBed(TestComponent)); @@ -613,9 +676,10 @@ describe('Permission directive with true if condition', () => { describe('Permission directive with true promise if condition', () => { @Component({ - template: ` -
123
-
`, + template: ` + +
123
+
`, }) class TestComponent extends BaseTestComponent { condition = Promise.resolve(true); @@ -639,9 +703,10 @@ describe('Permission directive with true promise if condition', () => { describe('Permission directive with false promise if condition', () => { @Component({ - template: `
-
123
-
`, + template: ` +
+
123
+
`, }) class TestComponent extends BaseTestComponent { condition = Promise.resolve(false); @@ -718,9 +783,10 @@ describe('Permissions directive with then block and if condition', () => { describe('Permission directive with false promise, if condition and change detection on push', () => { @Component({ - template: `
-
123
-
`, + template: ` +
+
123
+
`, changeDetection: ChangeDetectionStrategy.OnPush, }) class TestComponent extends BaseTestComponent { @@ -740,7 +806,7 @@ describe('Permission directive with false promise, if condition and change detec expect(getFixtureContent()).toEqual(null); })); - it('should show else block when permission is missing and condition is false', fakeAsync(() => { + it('should show component when permission is added and condition is true', fakeAsync(() => { permissionsService.add(ADMIN); (fixture.componentInstance as TestComponent).condition.next(true); tick(); @@ -749,4 +815,19 @@ describe('Permission directive with false promise, if condition and change detec expect(content).toBeTruthy(); expect(content.innerHTML).toEqual(`
123
`); })); + + it('should hide component when permission is present, condition is true and deny permission is present', fakeAsync(() => { + permissionsService.add(ADMIN); + (fixture.componentInstance as TestComponent).condition.next(true); + tick(); + + const content = getFixtureContent(); + expect(content).toBeTruthy(); + expect(content.innerHTML).toEqual(`
123
`); + + permissionsService.add(GUEST); + tick(); + + expect(getFixtureContent()).toEqual(null); + })); }); diff --git a/src/lib/permissions/directives/allow/allow.directive.ts b/src/lib/permissions/directives/allow/allow.directive.ts index 74b69bf..7b5a3d3 100644 --- a/src/lib/permissions/directives/allow/allow.directive.ts +++ b/src/lib/permissions/directives/allow/allow.directive.ts @@ -18,6 +18,7 @@ export class IqserAllowDirective extends IqserPermissionsDirective implements On * narrow its type, which allows the strictNullChecks feature of TypeScript to work with `IqserPermissionsDirective`. */ static ngTemplateGuard_allow: 'binding'; + #deny?: string | List; constructor(templateRef: TemplateRef) { super(templateRef); @@ -40,8 +41,35 @@ export class IqserAllowDirective extends IqserPermissionsDirective implements On this.setElseTemplateRef(template); } + @Input() + set allowDeny(value: string | List) { + this.#deny = value; + this._updateView.next(); + } + @Input() set allowIf(value: boolean | Promise | Observable) { this.setIf(value); } + + get #hasDenyPermission() { + if (!this.#deny) { + return false; + } + + return this._permissionsService.hasSome(this.#deny) || this._rolesService.hasSome(this.#deny); + } + + protected override _validate() { + const hasDenyPermission = this.#hasDenyPermission; + if (!this._permissions) { + return !hasDenyPermission; + } + + if (hasDenyPermission) { + return false; + } + + return this._permissionsService.has(this._permissions) || this._rolesService.has(this._permissions); + } } diff --git a/src/lib/permissions/directives/deny/deny.directive.ts b/src/lib/permissions/directives/deny/deny.directive.ts index cfa85cc..7e55fa6 100644 --- a/src/lib/permissions/directives/deny/deny.directive.ts +++ b/src/lib/permissions/directives/deny/deny.directive.ts @@ -50,6 +50,6 @@ export class IqserDenyDirective extends IqserPermissionsDirective implements OnD return true; } - return !(this._permissionsService.hasAtLeastOne(this._permissions) || this._rolesService.hasAtLeastOne(this._permissions)); + return !(this._permissionsService.hasSome(this._permissions) || this._rolesService.hasSome(this._permissions)); } } diff --git a/src/lib/permissions/services/permissions.service.ts b/src/lib/permissions/services/permissions.service.ts index fa7555c..f2f8529 100644 --- a/src/lib/permissions/services/permissions.service.ts +++ b/src/lib/permissions/services/permissions.service.ts @@ -24,25 +24,11 @@ export class IqserPermissionsService { has(permissions: List): boolean; has(permissions: string | List): boolean; has(permissions: string | List): boolean { - const isEmpty = !permissions || permissions.length === 0; - if (isEmpty) { - return true; - } - - const all = this.#permissions$.value; - const results = toArray(permissions).map(permission => all[permission]?.(permission, all) ?? false); - return results.every(result => result); + return this.#evaluate(permissions).every(result => result); } - hasAtLeastOne(permissions: List | string): boolean { - const isEmpty = !permissions || permissions.length === 0; - if (isEmpty) { - return true; - } - - const all = this.#permissions$.value; - const results = toArray(permissions).map(permission => all[permission]?.(permission, all) ?? false); - return results.some(result => result); + hasSome(permissions: List | string): boolean { + return this.#evaluate(permissions).some(result => result); } load(permissions: IqserPermissions | List) { @@ -69,8 +55,11 @@ export class IqserPermissionsService { } has$(permission: string): Observable; + has$(permissions: List): Observable; + has$(permissions: string | List): Observable; + has$(permissions: string | List) { const isEmpty = !permissions || permissions.length === 0; if (isEmpty) { @@ -83,6 +72,16 @@ export class IqserPermissionsService { ); } + #evaluate(permissions: List | string) { + const isEmpty = !permissions || permissions.length === 0; + if (isEmpty) { + return [true]; + } + + const all = this.#permissions$.value; + return toArray(permissions).map(permission => all[permission]?.(permission, all) ?? false); + } + #reduce(permissions: IqserPermissions | List, initialValue = {} as IqserPermissions) { if (isArray(permissions)) { return this.#permissions$.next(this.#reduceList(permissions, initialValue)); diff --git a/src/lib/permissions/services/roles.service.ts b/src/lib/permissions/services/roles.service.ts index c45863f..ca5f3be 100644 --- a/src/lib/permissions/services/roles.service.ts +++ b/src/lib/permissions/services/roles.service.ts @@ -41,25 +41,22 @@ export class IqserRolesService { } has(roles: string | List): boolean { - const isEmpty = !roles || roles.length === 0; - - if (isEmpty) { - return true; - } - - const validations = toArray(roles).map(role => this.#runValidation(role)); - return this.#checkPermissionsIfNeeded(validations).every(result => result); + const results = this.#evaluate(roles); + return this.#checkPermissionsIfNeeded(results).every(result => result); } - hasAtLeastOne(roles: string | List): boolean { - const isEmpty = !roles || roles.length === 0; + hasSome(roles: string | List): boolean { + const results = this.#evaluate(roles); + return this.#checkPermissionsIfNeeded(results).some(result => result); + } + #evaluate(roles: string | List) { + const isEmpty = !roles || roles.length === 0; if (isEmpty) { - return true; + return [true]; } - const validations = toArray(roles).map(role => this.#runValidation(role)); - return this.#checkPermissionsIfNeeded(validations).some(result => result); + return toArray(roles).map(role => this.#runValidation(role)); } #checkPermissionsIfNeeded(results: List) {