javascriptreactjsreact-testing-libraryjsdomslate.js

onChange is not called when used with userEvent.type from React testing library


I am using RTL with Jest to test a Slate editor. I use .type to emulate a user typing into the editor and expect that Slate's onChange will be called. However, this does not happen.

Here's my editor code

import React, { useState } from "react";
import { createEditor } from "slate";
import { Slate, Editable, withReact } from "slate-react";

const initialValue = [
  {
    type: "paragraph",
    children: [{ text: "Foo" }]
  }
];

export default function Editor({ onChange }) {
  const [editor] = useState(() => withReact(createEditor()));
  return (
    <Slate onChange={onChange} editor={editor} value={initialValue}>
      <Editable data-testid="my_editor" />
    </Slate>
  );
}

Here's my test code:

import React from "react";
import "@testing-library/jest-dom/extend-expect";
import userEvent from "@testing-library/user-event";
import { render, screen } from "@testing-library/react";
import Editor from "../editor";

test("test", async () => {
  const user = userEvent.setup();
  const onChange = jest.fn();
  render(<Editor onChange={onChange} />);

  expect(screen.getByTestId("my_editor")).toHaveTextContent("Foo");

  user.type(screen.getByTestId("my_editor"), " some more text");

  // I also tried this but did not work...
  // user.click(screen.getByTestId("my_editor"));
  // user.keyboard("bar");

  // also this did not work...
  // fireEvent.change(screen.getByTestId("my_editor"), {
  //   target: { innerHTML: "<p>hello world</p>" }
  // });

  // or this...
  // fireEvent.blur(screen.getByTestId("my_editor"), {
  //   target: { innerHTML: "<p>hello world</p>" }
  // });

  expect(onChange).toHaveBeenCalled();
});

If it's easier I have also created a minimal reproduction here.

To reproduce the behavior:

I expected that the onChange will be called. This behavior only happens when tests are ran. In a normal browser onChange is indeed called.


Solution

  • This is a known issue with contenteditable which is what slate uses.

    Essentially the issue lies with js-dom itself and testing libraries that use js-dom will all run into this issue.

    Look at the discussion and links here Testing react-contenteditable with react testing library

    Even the fix that works for testing-library that uses UserEvents is able to have the change reflected but doesn't trigger the onChange events.

    On a fork of your code I have added the UserEvent to modify the editor and also added a test that checks for the updated output, which passes. However, the onChange test still fails.

    Also, UserEvents v14.x.x is giving some trouble, so I have used v13.5 in my example CodeSandbox

    So due to the limitations of the js-dom, it will be impossible to have a workable test for this. Instead, it is recommended to use other libraries such as puppeteer.