facebookarchive / draft-js

A React framework for building text editors.
https://draftjs.org/
MIT License
22.58k stars 2.64k forks source link

Testing with React Testing Library #2833

Open dwjohnston opened 3 years ago

dwjohnston commented 3 years ago

Do you want to request a feature or report a bug?

Feature / Help Required

What is the current behavior?

My use is in actually using Draftail, which uses DraftJS under the hood. (I'm actually working on Draftail fork, as Draftail is not passing all of the aria properties to the DraftJS, my work is linked below).

I want to simulate entering some text, and then make assertions about what effect that has in my application.

What I have tried for example, is something like this:

        const saveHandler = jest.fn();

        render(<DraftailEditor
            ariaDescribedBy='foo'
            ariaLabel="aria label"
            inlineStyles={[]}
            spellCheck={false}
            rawContentState={{
                entityMap: {},
                blocks: [
                  {
                    key: "aaa",
                    text:
                      "abc",
                  },
                ],
              }}
            blockTypes={[]}
            onSave={saveHandler}
            placeholder="placeholder"
            className="classname"
        />);

        const textbox = screen.getByRole("textbox", {
            name: "aria label"
        });

        // Check initial value exists 
        getByText(textbox, "abc");

        // Everything is fine till here. 

        userEvent.type('textbox', '123'); 

        //fails 
        getByText(screen.getByRole("textbox", {
            name: "aria label"
        }), "abc123");

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. You can use this jsfiddle to get started: https://jsfiddle.net/gmertk/e61z7nfa/.

Repro of this on this branch and file here: https://github.com/dwjohnston/draftail/blob/draftjs-issue/tests/rtl.test.js

What is the expected behavior?

The draftjs editor responds userEvent methods, ie. the content of the editor is updated.

Which versions of Draft.js, and which browser / OS are affected by this issue? Did this work in previous versions of Draft.js?

draftjs 10.5 jest-dom

Other notes:

I have investigated other techniques such as:

https://github.com/facebook/draft-js/issues/325#issuecomment-229961851

This one looks platform/test framework agnostic, but that var textEvent = document.createEvent('TextEvent'); gives the error:

NotSupportedError: The provided event type ("TextEvent") is invalid

I've updated that to:


    function addTextToDraftJs(className) {
        const components = document.getElementsByClassName(className);
        if(components && components.length) {
          const textarea = components[0].getElementsByClassName('public-DraftEditor-content')[0];
          const keyboardEvent = new KeyboardEvent("keyDown", {
            key: "a"
          });           
          textarea.dispatchEvent(keyboardEvent);
        }  
      }

      addTextToDraftJs('Draftail-Editor');

      await new Promise((res) => setTimeout(()=> res(null), 1000)); // Just incase it's an async issue 

But the content does not appear to update.

This comment seems to suggest that a keydown/keypress even would not work, and that a textInput event is what would be required. But I'm not sure what that is or how I would emit it.

A lot of the proposed solutions such as:

https://github.com/facebook/draft-js/issues/325#issuecomment-300372428 https://github.com/facebook/draft-js/issues/325#issuecomment-344091458 https://github.com/facebook/draft-js/issues/325#issuecomment-373212478

Seem to rely on using using Enzyme and using the react class instance to simulate an event.

It seems like it is not possible to access a components class instance from RTL.

https://github.com/facebook/draft-js/issues/347

This one looks quite out of date, and my understand is to the the TestUtils methods, I would need access to the component's class instance.

dwjohnston commented 3 years ago

OK, so after doing a bit of investigation it looks like JSDom doesn't support the "TextEvent" type, which that comment suggests you need.

Any suggestions for how we can extend JSDom to allow it?

dwjohnston commented 3 years ago

Ok, following this comment, it looks like we have a solution.

  it.only ("You can update the state of it (no hack)", async () => {

    const saveHandler = jest.fn();

    render(
      <DraftailEditor
        ariaDescribedBy="foo"
        ariaLabel="aria label"
        inlineStyles={[]}
        spellCheck={false}
        rawContentState={{
          entityMap: {},
          blocks: [
            {
              key: "aaa",
              text: "abc",
            },
          ],
        }}
        blockTypes={[]}
        onSave={saveHandler}
        placeholder="placeholder"
        className="classname"
      />,

    );

    const textarea = screen.getByRole("textbox", {
      name: "aria label",
    });

    const event = createEvent.paste(textarea, {
      clipboardData:{
        types: ['text/plain'],
        getData: () => "hello" // The linked comment had stuff around pasting html in, I can't see that it's needed for this test. 
      }
    }); 

    fireEvent(textarea, event);

     getByText(screen.getByRole("textbox", {
            name: "aria label"
        }), "abchello");
  }); 

Basically RTL's createEvent does not have a way to create a TextEvent Event, and JSDom doesn't have a TextEvent, but RTL does have a paste event, which it seems that draft-js listens to.

So this works.

This solution doesn't work for every testing scenario - for example I don't think it'll work for testing custom key bindings, or things like hitting backspace/delete etc, but works fine for just entering some text.

dwjohnston commented 3 years ago

We can close this issue - or do we want to update the documentation to reflect 'How to use with RTL'?

fabb commented 3 years ago

pasting could behave differently than typing depending on implementation, so while this solution is a helpful stop-gap, it's not an ideal solution imho.