Stencil: EventEmitter is undefined

Created on 5 Mar 2018  ·  11Comments  ·  Source: ionic-team/stencil

Stencil version:

 @stencil/[email protected]

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:
Event emitters are not initialised during tests if the class is just instantiated and not rendered.

Expected behavior:
Should be able to test components that emit events even without rendering.

Steps to reproduce:
Emit an event in the code component. In testing, initialize the component and trigger the action that will emit the event.
The following error will appear:
TypeError: Cannot read property 'emit' of undefined

Related code:
Add the following to stencil-component-starter project and run tests

// my-component.ts
  @Event() myEvent: EventEmitter<boolean>

  doSomething() {
    this.myEvent.emit(true)
  }

// my-component.spec.ts
it('should emit on method call', () => {
    let comp = new MyComponent();
    comp.doSomething();
  });



Other information:

bug

Most helpful comment

@Tallyb The event emitter is actually really easy to mock. This will at least get you around the issue:

it('should test something', () => {
  // mock the event emitter
  component.someEventEmitter = {
    emit: () => {}
  };

  const spy = jest.spyOn(component.someEventEmitter, 'emit');
  component.methodThatCallsEventEmitter();
  expect(spy).toHaveBeenCalled();
});

Hope this helps!

All 11 comments

Thanks for opening an issue with us! Would you mind sharing what your whole test looks like?

I am experiencing this as well using @stencil/[email protected]. Here's a trimmed down version of the component and spec I'm working with:

my-component.tsx:

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

@Component({
  tag: 'my-component'
})
export class MyComponent {
  @Event() toggle: EventEmitter;

  opened: boolean = false;

  handleToggle() {
    this.opened = !this.opened;
    this.toggle.emit();
  }

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

my-component.spec.tsx:

import { MyComponent } from './my-component';

describe('my-component', () => {
  describe('handleToggle()', () => {
    it('toggles `opened`', () => {
      const cmp = new MyComponent();
      cmp.handleToggle();
      expect(cmp.opened).toEqual(true);
    });

    it('emits toggle event', () => {
      const cmp = new MyComponent();
      const spy = jest.spyOn(cmp.toggle, 'emit');
      cmp.handleToggle();
      expect(spy).toHaveBeenCalled();
    });
  });
});

Results:

FAIL  src/my-component/my-component.spec.tsx
 ● my-component › handleToggle() › toggles `opened`

   TypeError: Cannot read property 'emit' of undefined

 ● my-component › handleToggle() › emits toggle event

   Cannot spyOn on a primitive value; undefined given

@jgw96 the test above is the whole code and test. You can just add my code to stencil-component-starter. (just need to fix the relevant imports). There is not even a need to expect, as the test will fail because of the error.

Related to #618 ?

@bitflower I don't think so. This is 0.6.18 thing.

I'm experiencing this issue as well. Is there a way to mock or provide the EventEmitter to the component when instantiated (vs rendered)?

@Tallyb The event emitter is actually really easy to mock. This will at least get you around the issue:

it('should test something', () => {
  // mock the event emitter
  component.someEventEmitter = {
    emit: () => {}
  };

  const spy = jest.spyOn(component.someEventEmitter, 'emit');
  component.methodThatCallsEventEmitter();
  expect(spy).toHaveBeenCalled();
});

Hope this helps!

@hartjus - helps indeed. thanks for this solution.

I'm experiencing this issue after cloning the stencil-app-starter and copy pasting some event code from the documentation (updated to 0.17.1)

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

@Component({
  tag: 'app-root',
  styleUrl: 'app-root.css',
  shadow: true
})
export class AppRoot {

  @Event() todoCompleted: EventEmitter;

  todoCompletedHandler() {
    this.todoCompleted.emit();
  }

  render() {
    return (
      <div>
        <header>
          <h1>Stencil App Starter</h1>
        </header>
        <main>
          <button onClick={this.todoCompletedHandler}></button>         
        </main>
      </div>
    );
  }
}

edit works when binding the method like this:

  render() {
    return (
      <div>
        <header>
          <h1>Stencil App Starter</h1>
        </header>
        <main>
          <div>
            <button onClick={this.playTriggerHandler.bind(this)}></button>    
            <video-player></video-player>
          </div>     
        </main>
      </div>
    );
  }
}

You could also do it like using an arrow function.
todoCompleteHandler = () => { this.todoCompleted.emit(); }

@Tallyb The event emitter is actually really easy to mock. This will at least get you around the issue:

it('should test something', () => {
  // mock the event emitter
  component.someEventEmitter = {
    emit: () => {}
  };

  const spy = jest.spyOn(component.someEventEmitter, 'emit');
  component.methodThatCallsEventEmitter();
  expect(spy).toHaveBeenCalled();
});

Hope this helps!

This was very helpful, thank you!!

I did find that typescript was complaining if I just mocked it with emit: () => {} but I solved that by changing it to this: emit: (data?: any) => new CustomEvent(data), in case that helps anyone else...

Was this page helpful?
0 / 5 - 0 ratings

Related issues

glemiere picture glemiere  ·  3Comments

elmariofredo picture elmariofredo  ·  3Comments

romulocintra picture romulocintra  ·  3Comments

mitchellsimoens picture mitchellsimoens  ·  3Comments

ryanmunger picture ryanmunger  ·  3Comments