Skip to content

SpectatorHost.focus() is broken in a few ways when using Jest #373

@johncrim

Description

@johncrim

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

When using jest, calls to SpectatorHost.focus() do dispatch a (fake) focus event, but the current implementation prevents automatic blur events from occurring, and also causes the toBeFocused() matcher to not work. This is due to the call to patchElementFocus() in DomSpectator.focus(), which overwrites valid and more useful jsdom focus() methods with versions that don't work as well.

The following test shows the issue, and a workaround:

import { ChangeDetectionStrategy, Component } from '@angular/core';
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest';

@Component({
  selector: 'ui-test-focus',
  template: `<button id="button1" (focus)="countFocus('button1')" (blur)="countBlur('button1')">Button1</button>
             <button id="button2" (focus)="countFocus('button2')" (blur)="countBlur('button2')">Button2</button>`,
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: {
    '[attr.tabindex]': '0',
    '(focus)': 'countFocus("ui-test-focus")',
    '(blur)': 'countBlur("ui-test-focus")'
  }
})
export class TestFocusComponent {

  private readonly focusCounts = new Map<string, number>();
  private readonly blurCounts = new Map<string, number>();

  public countFocus(id: string) {
    this.focusCounts.set(id, this.focusCount(id) + 1);
  }

  public countBlur(id: string) {
    this.blurCounts.set(id, this.blurCount(id) + 1);
  }

  public focusCount(id: string): number {
    return this.focusCounts.get(id) ?? 0;
  }

  public blurCount(id: string): number {
    return this.blurCounts.get(id) ?? 0;
  }

}

describe('Spectator focus() in jest', () => {

  const createHost = createHostFactory(TestFocusComponent);
  let host: SpectatorHost<TestFocusComponent>;

  beforeEach(() => {
    host = createHost('<ui-test-focus></ui-test-focus>');
  })

  it('SpectatorHost.focus() in jest does not track active element', () => {
    host.focus('#button1');

    // FAILS
    expect(host.query('#button1')).toBeFocused();
  });

  it('HTMLElement.focus() in jest tracks the active element', () => {
    (host.query('#button1') as HTMLElement).focus();

    // passes
    expect(host.query('#button1')).toBeFocused();
  });

  it('SpectatorHost.focus() in jest does not cause blur events', () => {
    host.focus();
    host.focus('#button1');
    host.focus('#button2');

    // FAILS: blur counts are not present
    expect(host.component.focusCount('ui-test-focus')).toBe(1);
    expect(host.component.blurCount('ui-test-focus')).toBe(1);
    expect(host.component.focusCount('button1')).toBe(1);
    expect(host.component.blurCount('button1')).toBe(1);
    expect(host.component.focusCount('button2')).toBe(1);
    expect(host.component.blurCount('button2')).toBe(0);
  });


  it('HTMLElement.focus() in jest does cause blur events', () => {
    host.element.focus();
    (host.query('#button1') as HTMLElement).focus();
    (host.query('#button2') as HTMLElement).focus();

    // passes
    expect(host.component.focusCount('ui-test-focus')).toBe(1);
    expect(host.component.blurCount('ui-test-focus')).toBe(1);
    expect(host.component.focusCount('button1')).toBe(1);
    expect(host.component.blurCount('button1')).toBe(1);
    expect(host.component.focusCount('button2')).toBe(1);
    expect(host.component.blurCount('button2')).toBe(0);
  });

});

Expected behavior

I think the best fix would be to remove patchElementFocus() from all platforms, but I'm not 100% sure why it's there (the comment says something about IE 11). My thinking in recommending this is that we want to test browser or platform capabilities as much as possible (eg if running karma on IE11, I would want the browser implementation to be used, b/c I'm testing browser compatibility).

Alternatively, patchElementFocus() could be left as is for non-jest, and overridden for jest only, and only patch the focus() and blur() methods when they're not present.

Minimal reproduction of the problem with instructions

git clone [email protected]:johncrim/jest-spectator-bugs.git
yarn
yarn test

What is the motivation / use case for changing the behavior?

Make host.focus() work as expected.

Environment


jest: 26.6.3
spectator: 6.1.2

$ ng version

Angular CLI: 11.0.3
Node: 15.3.0
OS: win32 x64

Angular: 11.0.3
... animations, cli, common, compiler, compiler-cli, core, forms
... platform-browser, platform-browser-dynamic, router
Ivy Workspace: 

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1100.3
@angular-devkit/build-angular   0.1100.3
@angular-devkit/core            11.0.3
@angular-devkit/schematics      11.0.3
@schematics/angular             11.0.3
@schematics/update              0.1100.3
ng-packagr                      11.0.3
rxjs                            6.6.3
typescript                      4.0.5

Browser:
- [ ] Chrome (desktop) version XX
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
For Tooling issues:
- Node version: XX  
- Platform: Windows

Others:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions