With a @ViewChild
decorator I grab the ion-input
element to set focus with the setFocus()
method. And this works as expected. I don't know why, but I can't figure out why all the Jasmine unit tests of the component fails. I've tried mocking the setFocus()
method, but without success. Can anyone help me with this and know what I'm doing wrong?
Template with the @ViewChild
reference #input_username
<ion-input id="inputusername"
#input_username
color="dark"
role="textbox"
aria-label=""
placeholder=""
type="text"
formControlName="name"
ngDefaultControl>
</ion-input>
Component method that sets the focus with setFocus()
private _setFocusDefaultInput(input: ElementRef): void {
const ionInput = input['el']; // get the DOM element from Ionic
ionInput.setFocus();
}
The _setFocusDefaultInput()
method is called inside the ngAfterViewInit()
method
ngAfterViewInit(): void {
setTimeout(() => {
this._setFocusDefaultInput(this.inputUsername);
}, 800);
}
Error message I get when I run ALL the Jasmine unit tests that belongs to this component:
Failed: input.setFocus is not a function
error properties: Object({ longStack: 'TypeError: input.setFocus is not a function
at LoginPage._setFocusDefaultInput (src/app/login/login/login.page.ts:123:11)
at src/app/login/login/login.page.ts:54:12
at ZoneDelegate.invokeTask (node_modules/zone.js/dist/zone.js:429:1)
at AsyncTestZoneSpec.onInvokeTask (node_modules/zone.js/dist/zone-testing.js:1231:33)
at ProxyZoneSpec.onInvokeTask (node_modules/zone.js/dist/zone-testing.js:328:43)
at ZoneDelegate.invokeTask (node_modules/zone.js/dist/zone.js:428:1)
at Object.onInvokeTask (node_modules/zone.js/dist/zone.js:307:88)
at ZoneDelegate.invokeTask (nod ...
TypeError: input.setFocus is not a function
I'v tried to mock the setFocus()
method with the following code, but without success
TestBed configuration
{
provide: ElementRef,
useClass: ElementRefMock
},
{
provide: IonInput,
useClass: IonInputMock
}
Mocking classes
class ElementRefMock {
setFocus(){}
el: {
setFocus: () => {}
};
}
class IonInputMock {
setFocus(){}
el: {
setFocus: () => {}
};
CodePudding user response:
I am thinking that each individual test runs faster than the timeout of 800ms and so the view of the input is destroyed but you still want to refer to it about 800ms later. Let's say a test took 200ms, 600ms the focus logic runs but within the 200ms the view has been destroyed and we are referring to something that does not exist.
Try this to debug:
private _setFocusDefaultInput(input: ElementRef): void {
const ionInput = input['el']; // get the DOM element from Ionic
// See what you get for the ionInput
console.log(ionInput);
// If ionInput is undefined, put a question mark between the . and setFocus
// This way, it won't run if ionInput is undefined or null
// You can of course put an if check as well.
ionInput?.setFocus();
}
CodePudding user response:
The previous answer solved my problem, but I have found a new solution that no longer needs the setTimeout method to set the focus correctly on the Ionic input field. The Jasmine unit test issue I ran into have also been resolved with this implementation
I replaced the
ElementRef
interface with theIonInput
interface to directly callsetFocus()
:@ViewChild('input_username') inputUsername: IonInput;
I implemented the
ionViewDidEnter()
Ionic lifecycle method. This method is not called until all Ionic elements have been successfully rendered into the DOM. That's when I want to set the focus on the Ionic input field:ionViewDidEnter(): void { this.inputUsername.setFocus(); }