preactjs/enzyme-adapter-preact-pure

simulate(): TypeError: Cannot set property target of [object Event] which has only a getter

philipmw opened this issue ยท 4 comments

I have the following Enzyme code that worked when my project used React, but stopped working when I switched to Preact:

wrapper.find("input#number-input")
  .simulate("change", { target: { valueAsNumber: 16 }});

The error now:

TypeError: Cannot set property target of [object Event] which has only a getter

I found and looked through issue #46, and tried several approaches suggested there (adding a /// <reference, and casting the wrapper to any), but none helped.

FYI: Casting to any or adding a /// <reference tag won't help because this isn't a TypeScript error. This error is thrown by the JavaScript runtime.

I spent some time on this issue tonight. So far I wrote a unit test for this package that reproduces the problem and fails. I also tried to find and fix the problem. I believe the problem lies here:

// To be more faithful to a real browser, this should use the appropriate

It's even nicely commented, explaining the limitation.

We are creating a generic Event, which does not allow setting the target property. Instead, I think we should be creating a change event, but I do not see a way to accomplish this with TypeScript's lib.dom.d.ts. I don't see a class that extends Event that allows changing target. So for now I am stumped.

Do you have any ideas? My next step is to look at how React's Enzyme adapter does this.

Hi @philipmw,

The target of an event is normally set by the DOM when calling domElement.dispatchEvent(<event object>). The reason you can't set it is because the browser sets this for you.

As noted in the docs, the Preact adapter currently dispatches real events in simulate() whereas the React adapter just calls the on<EventName> prop with argument that you pass. Because the Preact adapter uses real event dispatch (via domElement.dispatchEvent(...), target is set automatically to point at the note that you simulated an event on. Therefore to do what your original test did, you would need to set the actual <input> element's value.

const input = wrapper.find('input#number-input');
input.getDOMNode().value = '123';
input.simulate('change');

The advantage of dispatching real events is that it better aligns what happens in the test with behavior in the real application. The downside is that this is a behavior difference if you are porting tests from React.

If porting existing tests that use React, you could do what simulate() does in the React adapter with something like:

import { act } from 'preact/test-utils';

act(() => {
  const event = /* create event here. It can be a real event or just an event-like object */
  wrapper.find("input#number-input").prop('onChange')(event);
});

See also: https://preactjs.com/guide/v10/unit-testing-with-enzyme#triggering-state-updates-and-effects-with-act

I would be open to the idea of a compatibility flag for the Preact adapter which made its behavior for simulate() work the same way as React.

Thank you, your comment was super helpful. I got my unit test to work the Preact way.

Two hurdles I ran into that I want to document for posterity:

  1. I can only simulate("input"), not simulate("change"). The former triggers both onInput and onChange, while the latter does not trigger anything.
  2. getDOMNode() did not exist for me, because I was using shallow render. Once I switched to mount render, it worked just as you suggested.

Resolving.