Stencil: Dispatching events manually in tests result in TypeError

Created on 24 Feb 2018  ยท  5Comments  ยท  Source: ionic-team/stencil

Stencil version:

 @stencil/core@<0.6.1>

I'm submitting a:
[ x] bug report
[ ] feature request
[ ] support request => Please do not submit support requests here, use one of these channels: https://forum.ionicframework.com/ or https://stencil-worldwide.slack.com

Current behavior:
I have implemented a special button component based on https://github.com/ionic-team/stencil-component-starter. The web component is intrested in events of its child button html component (mouseleave and keydown.tab) to be exact. Testing it manually in a browser succeeds.

When I try to do automated tests I have to dispatch the specified events. No matter how I try to create the specified Event I get the following error on dispatching:
```
TypeError: Failed to execute 'dispatchEvent' on 'EventTarget': parameter 1 is not of type 'Event'.

I logged the event to the console before dispatching and it is indeed an `Event`. I have tried several event types (Event, MouseEvent, CustomEvent) and several ways to construct the event but the result is always the same.

**Expected behavior:**
Tests should allow to fire events manually.

**Steps to reproduce:**

1. Create a clean projecet based on component-starter.
2. Create a custom component.
3. In the spec dispatch an event via `element.dispatch(Event)` 

**Other information:**
Stack tarce:

TypeError: Failed to execute 'dispatchEvent' on 'EventTarget': parameter 1 is not of type 'Event'.

  at convert (node_modules/jsdom/lib/jsdom/living/generated/Event.js:307:11)
  at HTMLUnknownElement.dispatchEvent (node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:141:13)
  at Object.<anonymous> (src/components/slds-button/tests/slds-button-stateful.spec.ts:95:41)
  at step (src/components/slds-button/tests/slds-button-stateful.spec.ts:32:23)
  at Object.next (src/components/slds-button/tests/slds-button-stateful.spec.ts:13:53)
  at fulfilled (src/components/slds-button/tests/slds-button-stateful.spec.ts:4:58)
      at <anonymous>
  at process._tickCallback (internal/process/next_tick.js:188:7)

ยดยดยด

node version: v8.9.4
npm version: 5.6.0
OS: Linux subsystem on Win10

bug

Most helpful comment

for the record, what my example above missed was to use window.Event instead of MouseEvent in the .dispatchEvent call. So it should be as below instead:

cancelButton.dispatchEvent(new window.Event('click', {
    bubbles: true,
    cancelable: true
}));

and I learned also that after this fix it's also possible this nicer alternative:

cancelButton.click();

Finally, I also want to record that, for other event classes, this also works fine:

const body = window.document.body;
body.dispatchEvent(new (window.window as any).KeyboardEvent('keyup', {
    keyCode: 27,
    bubbles: true,
    cancelable: true
}));

All 5 comments

just for the record, as I'm also experiencing this issue, I tried the following approach (in spec file):

const JsdomMouseEvent = require('jsdom/lib/jsdom/living/generated/MouseEvent').interface;
...
button.dispatchEvent(new JsdomMouseEvent('click', {
  bubbles: true,
  cancelable: true
}));

That error is no longer showing, but @Listen still doesn't listen. So, it seems to me that stencil, jest and jsdom are just not using the same source reference here.

I'm looking for a work around until this issue is solved.

I am experiencing this as well using @stencil/[email protected]. Full example for posterity:

my-component.tsx

import { Component, Listen } from '@stencil/core';

@Component({
  tag: 'my-component'
})
export class MyComponent {
  opened: boolean = false;

  @Listen('click')
  handleClick() {
    this.opened = !this.opened;
  }

  render() {
    return (
      <div>my component</div>
    );
  }
}

my-component.spec.tsx

import { render } from '@stencil/core/testing';
import { MyComponent } from './my-component';

describe('my-component', () => {
  let element;

  beforeEach(async () => {
    const components = [MyComponent];
    const html = '<my-component></my-component>';
    element = await render({ components, html });
  });

  describe('when clicked', () => {
    it('toggles `opened`', () => {
      element.dispatchEvent(new Event('click'));
      expect(cmp.opened).toEqual(true);
    });
  });
});

Result:

 FAIL  src/my-component/my-component.spec.tsx
  โ— my-component โ€บ when clicked โ€บ toggles `opened`

    TypeError: Failed to execute 'dispatchEvent' on 'EventTarget': parameter 1 is not of type 'Event'.

@adamdbradley , @3279676 doesn't seem to have solved this issue.

I've just updated my app to version 0.7.24 (and Jest to 22.4.3 too), and I still get the same error:

TypeError: Failed to execute 'dispatchEvent' on 'EventTarget': parameter 1 is not of type 'Event'.

This is how my test is defined:

import { TestWindow } from '@stencil/core/testing';
import { MyDialog } from './my-dialog';

describe('MyDialog', () => {
    let window: TestWindow;

    it('should build', () => {
        expect(new MyDialog()).toBeTruthy();
    });

    describe('rendering', () => {
        let element;

        beforeEach(async () => {
            window = new TestWindow();
            element = await window.load({
                components: [
                    MyDialog
                ],
                html: `
                <my-dialog></my-dialog>
                `
            });
        });

        it('should blabla when clicked on cancel button', async () => {
            await window.flush();

            const cancelButton = element.querySelector('button') as HTMLButtonElement;
            cancelButton.dispatchEvent(new MouseEvent('click', {
                bubbles: true,
                cancelable: true
            }));

            // ...
        });

    });
});

Did I miss anything?

for the record, what my example above missed was to use window.Event instead of MouseEvent in the .dispatchEvent call. So it should be as below instead:

cancelButton.dispatchEvent(new window.Event('click', {
    bubbles: true,
    cancelable: true
}));

and I learned also that after this fix it's also possible this nicer alternative:

cancelButton.click();

Finally, I also want to record that, for other event classes, this also works fine:

const body = window.document.body;
body.dispatchEvent(new (window.window as any).KeyboardEvent('keyup', {
    keyCode: 27,
    bubbles: true,
    cancelable: true
}));

These solutions did not work for me for KeyboardEvents. Looking through the Ionic tests I found that you can do these with much less code!

Assuming you are writing an E2E test and starting with const page = await newE2EPage();:

await page.keyboard.down("ArrowDown");

There is also a keyboard.up and a keyboard.press (press does both up and down).

I use @marinho's solution for clicks - e.g.

const button = await page.find("mds-dropdown-menu #menuButton button");
await button.click();

There is also a page.mouse, and this is what one of the Ionic tests does with it for example:

    // Start in the center of the item
    await page.mouse.move(centerX, centerY);
    await page.mouse.down();

    // Move halfway first
    await page.mouse.move(halfX, centerY);

    // Move all of the way to the end
    await page.mouse.move(endX, centerY);
    await page.mouse.up();

From: https://github.com/ionic-team/ionic/blob/master/core/src/components/item-sliding/test/test.utils.ts

Was this page helpful?
0 / 5 - 0 ratings