Search code examples
angularjestjsspectator

Angular Jest Spectator - focus function does not work


So, I have Angular component that looks like this

<div class="hello" (keydown.enter)="doSomething()"></div>

and I'm trying to write test for case - when user is focused in div, pressing enter should call doSomething(). Unfortunately, I cannot mock this situation with Spectator. I've already tried:

spectator.focus(spectator.query('.hello'));
expect(spectator.query('.hello')).toBeFocused(); // test failed
spectator.keyboard.pressEnter();

also

spectator.query('.hello').dispatchEvent(new Event('focus'));

and both also with

spectator.detectChanges(); // without success

I suppose, that problem is in my HTML template, but those functions does not work also with:

<div class="hello" tabindex="0">

and even with

<input class="hello" type="text">

Please gimmie some support, how to focus on div element and then press enter on this.


Solution

  • First, you need to understand what spectator.focus() method does.

    Let's take a look at this method in Spectator source code:

    public focus(selector: SpectatorElement = this.element): void {
        const element = this.getNativeElement(selector);
    
        if (!(element instanceof HTMLElement)) {
            throw new Error(`Cannot focus: ${selector} is not a HTMLElement`);
        }
    
        patchElementFocus(element);
        element.focus();
        this.detectChanges();
    }
    

    We can notice that before triggering native element.focus() method it also calls patchElementFocus(element); Here's the code of this method:

    export function patchElementFocus(element: HTMLElement): void {
      element.focus = () => dispatchFakeEvent(element, 'focus');
      element.blur = () => dispatchFakeEvent(element, 'blur');
    }
    

    where dispatchFakeEvent calls node.dispatchEvent(event); native method under the hood.

    So, spectator.focus(element) triggers node.dispatchEvent(...).

    Now, you need to understand difference between trusted and untrusted events.

    Events fired by using node.dispatchEvent are called untrusted events and they do not trigger default browser actions (w3.org reference)

    That means that manually firing an event does not generate the default action associated with that event. For example, manually firing a focus event does not cause the element to receive focus, manually firing a submit event does not submit a form.

    You can only listen to that manually created events through events handlers. This is what Spectator demonstrates us. (Test https://github.com/ngneat/spectator/blob/fcdb6a809571706fac3d7b5d8da5bf2f7ba0e305/projects/spectator/test/events/events.component.spec.ts#L13) (Listener https://github.com/ngneat/spectator/blob/fcdb6a809571706fac3d7b5d8da5bf2f7ba0e305/projects/spectator/test/events/events.component.html#L2)

    Finally, the solution here is to use native element.focus() method to be able to set focus on your div. Also, tabindex attribute is required here.

    spectator.query<HTMLDivElement>('.hello').focus();
    expect(spectator.query('.hello')).toBeFocused();
    

    Stackblitz Example