1

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.

skyboyer
  • 22,209
  • 7
  • 57
  • 64
Kamil Naja
  • 6,267
  • 6
  • 33
  • 47

1 Answers1

5

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

yurzui
  • 205,937
  • 32
  • 433
  • 399
  • Thank you for the very detailed answer – Kamil Naja May 01 '20 at 20:01
  • Excellent answer. Also, I have [fixed spectator's focus patch](https://github.com/ngneat/spectator/blob/991118811d6f98570a97696e6bf9e18a91871797/projects/spectator/src/lib/internals/element-focus.ts#L57) so it always calls the `element.focus()`, and also sends a fake event if a real one isn't detected - some browsers don't send focus events when the browser window isn't in view. So, this workaround shouldn't be required any more (8.0.3 and later). – crimbo Dec 22 '21 at 00:16