Reputation: 3203
In our project we can assign user roles to multiple elements throughout the application which disable the elements when the required role is missing. But since for some buttons other conditions are applied, which may also disabled the component even if the role checks, our component also takes it over and disables its child if necessary. The reason behind this design that we can easily display tooltips if the element should be disabled.
// app-role-element
<div matTooltip="{{ tooltipMessage | translate }}" [matTooltipDisabled]="!isDisabled">
<ng-content> </ng-content>
</div>
// .ts
export class AppRoleElement implements AfterConentInit {
@Input('roles') roles: string[];
@ContentChild('roleElement') roleElement: MatButton;
constructor(...){}
ngAfterContentInit(): void {
...
this.setDisabled();
}
setDisabled(): void {
if (this.roleElement) {
if (this.isDisabled) {
this.roleElement.disabled = true; // disable if no role
} else {
this.roleElement.disabled = this.disableCondition; // disable if other condition
}
}
}
}
// usage
<app-role-component
[role]="['required-role']"
[disableCondition]= "isRunning || isUnstable"
[id]="startButtonRoleElem"
>
<button mat-raised-button id="startBtnId" (click)="start()">Start</button>
</app-role-component>
This approach works fine but it's difficult to unit test. In the code above, if I'm to write a unit test where I click on the Start button, selecting it by ID bypasses the role-element and calls the remote service even if it shouldn't. Selecting the role element by ID does not propagate the click on the button.
test('to prevent click on a start btn when form is invalid', () => {
spectator.component.runEnabled$ = of(false);
spectator.detectComponentChanges();
const checkExportFolderSpy = jest.spyOn(spectator.component, 'checkExportFolder');
spectator.inject(PreferencesService).validateCurrentExportPath.andReturn(of(VALIDATION_RESULT_OK));
spectator.detectChanges();
spectator.click('#startBtnId');
spectator.detectChanges();
expect(checkExportFolderSpy).not.toHaveBeenCalled();
expect(dispatchSpy).not.toHaveBeenCalled();
});
We're using JEST together with Spectator and NgMocks and I was hoping to leverage that functionality and mock this component, but I have no idea how. And I'm not really sure to what extend should I try to mock it, should I pass the click event to child, disable child? Any idea or recommendations how to deal with this?
Upvotes: 2
Views: 3316
Reputation: 13574
Your case is a complicated one.
It is complicated because:
MatButton
, therefore it cannot be mockedAppRoleElement
, it also cannot be mockedtriggerEventHandler
doesn't respect disabled
attribute and always triggers clicksTherefore in the test we need:
AppRoleElement
and MatButton
as they arenativeElement
The code below uses only ng-mocks and role detection has been simplified.
import {AfterContentInit, Component, ContentChild, Input, NgModule,} from '@angular/core';
import {MatButton, MatButtonModule} from '@angular/material/button';
import {MockBuilder, MockRender, ngMocks} from 'ng-mocks';
@Component({
selector: 'app-role-component',
template: `
<div>
<ng-content></ng-content>
</div>
`,
})
class AppRoleElement implements AfterContentInit {
@Input() public disable: boolean | null = null;
@ContentChild(MatButton) public roleElement?: MatButton;
public ngAfterContentInit(): void {
this.setDisabled();
}
public setDisabled(): void {
if (this.roleElement) {
this.roleElement.disabled = this.disable;
}
}
}
@NgModule({
declarations: [AppRoleElement],
imports: [MatButtonModule],
})
class AppModule {}
fdescribe('ng-mocks-click', () => {
// Keeping AppRoleElement and MatButton
beforeEach(() => MockBuilder(AppRoleElement, AppModule).keep(MatButton));
it('is not able to click the disabled button', () => {
// specific params for the render
const params = {
disabled: true,
start: jasmine.createSpy('start'),
};
// rendering custom template
MockRender(
`
<app-role-component
[disable]="disabled"
>
<button mat-raised-button id="startBtnId" (click)="start()">Start</button>
</app-role-component>
`,
params,
);
// click on a disable element isn't propagandized
ngMocks.find('button').nativeElement.click();
// asserting
expect(params.start).not.toHaveBeenCalled();
});
// checking the enabled case
it('is able to click the button', () => {
const params = {
disabled: false,
start: jasmine.createSpy('start'),
};
MockRender(
`
<app-role-component
[disable]="disabled"
>
<button mat-raised-button id="startBtnId" (click)="start()">Start</button>
</app-role-component>
`,
params,
);
ngMocks.find('button').nativeElement.click();
expect(params.start).toHaveBeenCalled();
});
});
Upvotes: 1