typescript - Angular - Cómo simular un componente envoltorio con Jest y NgMocks
En nuestro proyecto podemos asignar roles de usuario a múltiples elementos a lo largo de la aplicación, lo que desactiva los elementos cuando falta el rol requerido. Pero dado que para algunos botones se aplican otras condiciones, que también pueden desactivar el componente incluso si se cumple la verificación de roles, nuestro componente también lo maneja y desactiva su elemento hijo si es necesario. La razón detrás de este diseño es que podemos mostrar fácilmente información sobre herramientas si el elemento debe estar deshabilitado.
// 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>
Este enfoque funciona bien, pero es difícil de probar unitariamente. En el código anterior, si voy a escribir una prueba unitaria donde hago clic en el botón Iniciar, seleccionarlo por ID omite el elemento de rol y llama al servicio remoto incluso si no debería. Seleccionar el elemento de rol por ID no propaga el clic en el botón.
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();
});
Estamos usando JEST junto con Spectator y NgMocks y esperaba aprovechar esa funcionalidad y simular este componente, pero no tengo idea de cómo. Y realmente no estoy seguro hasta qué punto debería intentar simularlo, ¿debería pasar el evento de clic al hijo, deshabilitar al hijo? ¿Alguna idea o recomendación sobre cómo manejar esto?
Respuesta #1
Tu caso es complicado.
Es complicado porque:
- el botón está deshabilitado mediante
MatButton, por lo tanto no puede ser simulado - porque queremos probar
AppRoleElement, tampoco se puede simular triggerEventHandlerno respeta el atributodisabledy siempre dispara clics
Por lo tanto, en la prueba necesitamos:
- mantén
AppRoleElementyMatButtontal como están - crear un entorno especial para los casos discapacitados y habilitados
- hacer clic en el botón a través de
nativeElement
El código a continuación solo usa ng-mocks y la detección de roles se ha simplificado.
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();
});
});
