skip to Main Content

I have the following Chat box that sends the chat when someone clicks on Enter. This is working as expected in normal life but RTL is disagreeing.

import React, { useState } from "react";

export default function Test() {
  const [chatText, setChatText] = useState("");

  function sendChat() {
    setChatText("");
  }

  return (
    <div>
      <textarea
        rows={2}
        value={chatText}
        onChange={(e) => {
          setChatText(e.target.value);
        }}
        onKeyDown={(e) => {
          if (e.key === "Enter" && !e.shiftKey) {
            e.stopPropagation();
            e.preventDefault();
            sendChat();
          }
        }}
        aria-label="chat-input"
      />
      <button onClick={sendChat}>Send</button>
    </div>
  );
}

This is working fine. You send a text and the chat box clears when you press Enter.

The following is the test I have written in RTL, but it is not working.

import React from "react";
import { render, screen, waitFor, act } from "@testing-library/react";
import "@testing-library/jest-dom";
import user from "@testing-library/user-event";

import Test from "./index";

describe("Chat", () => {
  test("clear text on Enter", async () => {
    render(<Test />);
    let textBox = screen.getByLabelText("chat-input");
    await act(async () => {
      textBox.focus();
      expect(textBox).toHaveFocus();
      await user.keyboard("hello");
      await user.keyboard("{Enter}");
      await user.keyboard("hello");
      await waitFor(() => {
        expect(textBox).toHaveValue("hello");
      });
    });
  });
});

Is the test I’ve written wrong or is something wrong with the code?
RTL is not triggering the clear on Enter part, instead is showing hellohello

2

Answers


  1. Just remove the act wrapper

    describe('Chat', () => {
      test('clear text on Enter', async () => {
        render(<Test />);
        let textBox = screen.getByLabelText('chat-input');
    
        textBox.focus();
        expect(textBox).toHaveFocus();
        await user.keyboard('hello');
        await user.keyboard('{Enter}');
        await user.keyboard('hello');
        await waitFor(() => {
          expect(textBox).toHaveValue('hello');
        });
      });
    });
    
    Login or Signup to reply.
  2. Done some digging around and it may have to do something with user.keyboard in general. This might work better instead for you:

    // import userEvent from "@testing-library/user-event"
    
    await userEvent.type(textBox, 'hello{enter}')
    // little safety check that the input reset after enter key
    await waitFor(() => {
      expect(textBox).toHaveValue('');
    });
    
    await userEvent.type(textBox, 'hello')
    // check for new hello value
    await waitFor(() => {
      expect(textBox).toHaveValue('hello');
    });
    

    Sidenote, I highly recommend explicitly defining your onChange and onKeyDown functions rather than making them anonymous arrow functions. ie:

    const handleOnChange = (e) => {
      setChatText(e.target.value)
    }
    const handleKeyDown = (e) => {
      e.stopPropagation()
      e.preventDefault()
    
      if (e.key === 'Enter') sendChat()
    }
    

    The reason is that as your app scales, anonymous arrow functions are less performant than declarative ones because they have to be re-initialized every render. This alone has its use cases, but generally because your input is controlled it’s being re-rendered every onChange which means it’s reinitializing those anonymous arrow functions over and over again.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search