add deny input to allow directive & update tests

This commit is contained in:
Dan Percic 2022-12-19 12:06:07 +02:00
parent bba66ea0d6
commit a4b9c045cd
6 changed files with 206 additions and 88 deletions

View File

@ -28,8 +28,8 @@ export class AppComponent implements OnInit {
<div
*allow="['ADMIN', 'GUEST']"
(permissionsAuthorized)="yourCustomAuthorizedFunction()"
(permissionsUnauthorized)="yourCustomAuthorizedFunction()"
(authorized)="yourCustomAuthorizedFunction()"
(unauthorized)="yourCustomAuthorizedFunction()"
>
<div>You can see this text congrats</div>
</div>
@ -38,11 +38,24 @@ export class AppComponent implements OnInit {
<div>You can see this text congrats</div>
</ng-template>
----------------------------------------------
<div
*deny="['ADMIN', 'GUEST']"
(authorized)="yourCustomAuthorizedFunction()"
(unauthorized)="yourCustomAuthorizedFunction()"
>
<div>You cant see this text congrats</div>
</div>
<ng-template deny="ADMIN">
<div>You cant see this text congrats</div>
</ng-template>
----------------------------------------------
<ng-template
[allow]="['MANAGER']"
[allowIf]="someObject.isEmpty"
[allowDeny]="['GUEST']"
[allowThen]="thenBlock"
[allowElse]="elseBlock">
</ng-template>
@ -57,7 +70,7 @@ export class AppComponent implements OnInit {
----------------------------------------------
<div *allow="['can-read']; if: !someObject.isEmpty; else: elseBlock; then: thenBlock">main</div>
<div *allow="['can-read']; deny: ['GUEST']; if: !someObject.isEmpty; else: elseBlock; then: thenBlock">main</div>
<ng-template #elseBlock>
<div>elseBlock</div>

View File

@ -41,11 +41,13 @@ function getFixtureContent(): HTMLElement {
describe('Permission directive', () => {
@Component({
template: ` <ng-template [allow]="'ADMIN'" (authorized)="isAuthorized()" (unauthorized)="isUnauthorized()">
<div>123</div>
</ng-template>`,
template: `
<ng-template [allow]="'ADMIN'" (authorized)="isAuthorized()" (unauthorized)="isUnauthorized()">
<div>123</div>
</ng-template>`,
})
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: ` <ng-template [allow]="'ADMIN'">
<div>123</div>
</ng-template>`,
template: `
<ng-template [allow]="'ADMIN'">
<div>123</div>
</ng-template>`,
})
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: ` <ng-template [allow]="['ADMIN', 'GUEST']">
<div>123</div>
</ng-template>`,
template: `
<ng-template [allow]="['ADMIN', 'GUEST']">
<div>123</div>
</ng-template>`,
})
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: ` <div *allow="['ADMIN']">
<div>123</div>
</div>`,
template: `
<div *allow="['ADMIN']; deny: 'GUEST'">
<div>123</div>
</div>`,
})
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('<div>123</div>');
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('<div>123</div>');
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('<div>123</div>');
}));
});
describe('Permission directive angular testing different async functions in roles', () => {
@Component({
template: ` <div *allow="'ADMIN'">
<div>123</div>
</div>`,
template: `
<div *allow="'ADMIN'">
<div>123</div>
</div>`,
})
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: ` <div *allow="['ADMIN', 'GUEST']">
<div>123</div>
</div>`,
template: `
<div *allow="['ADMIN', 'GUEST']">
<div>123</div>
</div>`,
})
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: ` <div *allow="['ADMIN', 'GUEST']">
<div>123</div>
</div>`,
template: `
<div *allow="['ADMIN', 'GUEST']">
<div>123</div>
</div>`,
})
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: ` <div *allow="'ADMIN'">
<div>123</div>
</div>`,
template: `
<div *allow="'ADMIN'">
<div>123</div>
</div>`,
})
class TestComponent extends BaseTestComponent {}
class TestComponent extends BaseTestComponent {
}
beforeEach(() => configureTestBed(TestComponent));
@ -472,7 +527,8 @@ describe('Permissions directive with else block', () => {
</ng-template>
`,
})
class TestComponent extends BaseTestComponent {}
class TestComponent extends BaseTestComponent {
}
beforeEach(() => configureTestBed(TestComponent));
@ -513,7 +569,8 @@ describe('Permissions directive with then block', () => {
</ng-template>
`,
})
class TestComponent extends BaseTestComponent {}
class TestComponent extends BaseTestComponent {
}
beforeEach(() => configureTestBed(TestComponent));
@ -535,7 +592,8 @@ describe('Permissions directive with empty argument', () => {
</ng-template>
`,
})
class TestComponent extends BaseTestComponent {}
class TestComponent extends BaseTestComponent {
}
beforeEach(() => configureTestBed(TestComponent));
@ -554,7 +612,8 @@ describe('Permissions directive with an empty array', () => {
</ng-template>
`,
})
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: ` <ng-template [allow]="'ADMIN'" [allowIf]="true">
<div>123</div>
</ng-template>`,
template: `
<ng-template [allow]="'ADMIN'" [allowIf]="true">
<div>123</div>
</ng-template>`,
})
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: ` <ng-template [allow]="'ADMIN'" [allowIf]="false">
<div>123</div>
</ng-template>`,
template: `
<ng-template [allow]="'ADMIN'" [allowIf]="false">
<div>123</div>
</ng-template>`,
})
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: ` <ng-template [allow]="'ADMIN'" [allowIf]="condition">
<div>123</div>
</ng-template>`,
template: `
<ng-template [allow]="'ADMIN'" [allowIf]="condition">
<div>123</div>
</ng-template>`,
})
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: ` <div *allow="'ADMIN'; if: condition">
<div>123</div>
</div>`,
template: `
<div *allow="'ADMIN'; if: condition">
<div>123</div>
</div>`,
})
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: ` <div *allow="'ADMIN'; if: condition">
<div>123</div>
</div>`,
template: `
<div *allow="'ADMIN'; if: condition; deny: 'GUEST'">
<div>123</div>
</div>`,
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(`<div>123</div>`);
}));
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(`<div>123</div>`);
permissionsService.add(GUEST);
tick();
expect(getFixtureContent()).toEqual(null);
}));
});

View File

@ -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<unknown>) {
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<boolean> | Observable<boolean>) {
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);
}
}

View File

@ -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));
}
}

View File

@ -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<boolean>;
has$(permissions: List): Observable<boolean>;
has$(permissions: string | List): Observable<boolean>;
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));

View File

@ -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<string | boolean | List>) {