Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save jonasgrilleres/5109a1119b252771c81bc3a20f1f597d to your computer and use it in GitHub Desktop.
Save jonasgrilleres/5109a1119b252771c81bc3a20f1f597d to your computer and use it in GitHub Desktop.

I sent Chat GPT a unit test describing the behavior of a React app:

Can you write a react component based on the following test:

import { screen, within } from "@testing-library/dom";
import { render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

import { Chat } from "./Chat-after-refactoring";
import { MessagerieFake } from "../model/MessagerieFake";

const zoneSaisie = () => screen.getByLabelText("Votre message");
const boutonEnvoyer = () => screen.getByRole("button", { name: "Envoyer" });
const laConversation = () =>
  screen.getByRole("list", { name: "Messages de la conversation" });
const leMessageALaPosition = (position: number) =>
  within(laConversation()).getAllByRole("listitem")[position - 1];

describe("<Chat />", () => {
  it("affiche les messages envoyés", () => {
    // Given
    render(<Chat messagerie={new MessagerieFake()} />);

    // When
    userEvent.type(zoneSaisie(), "Bonjour");
    userEvent.click(boutonEnvoyer());

    userEvent.type(zoneSaisie(), "  ");
    userEvent.click(boutonEnvoyer());

    userEvent.type(zoneSaisie(), "Comment allez-vous ?");
    userEvent.click(boutonEnvoyer());

    userEvent.type(zoneSaisie(), "");
    userEvent.click(boutonEnvoyer());

    // Then
    expect(laConversation().children).toHaveLength(2);
    expect(leMessageALaPosition(1)).toHaveTextContent("Bonjour").toBeVisible();
    expect(leMessageALaPosition(2))
      .toHaveTextContent("Comment allez-vous ?")
      .toBeVisible();
  });
});

He suggest this first implementation:

import React, { useState } from 'react';
import { Messagerie } from '../model/Messagerie';

interface Props {
  messagerie: Messagerie;
}

const Chat = (props: Props): ReactElement => {
  const [message, setMessage] = useState('');

  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (message.trim().length > 0) {
      props.messagerie.envoyer(message);
      setMessage('');
    }
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <label htmlFor="message-input">Votre message</label>
        <input
          id="message-input"
          type="text"
          value={message}
          onChange={e => setMessage(e.target.value)}
        />
        <button type="submit">Envoyer</button>
      </form>
      <ul aria-label="Messages de la conversation">
        {props.messagerie.messages.map((message, index) => (
          <li key={index}>{message}</li>
        ))}
      </ul>
    </div>
  );
};

export default Chat;

In this component, I've used the useState hook to keep track of the current message being typed in the input field. The component has a form with a text input and a submit button.

The handleSubmit function is called when the form is submitted, it prevents the default form behavior and checks if the message is not empty, then calls the envoyer function of the messagerie prop, and it clears the input field.

The component also renders an unordered list of messages, which is populated using props.messagerie.messages and is labeled "Messages de la conversation" for accessibility

Please note that this implementation assume that the Messagerie and MessagerieFake classes are correctly implemented and exported from the model file.

Can you refactor this component in smaller components?

import React, { useState } from 'react';
import { Messagerie } from '../model/Messagerie';

interface Props {
  messagerie: Messagerie;
}

const Chat = (props: Props): ReactElement => {
  return (
    <div>
      <MessageInput onSubmit={props.messagerie.envoyer} />
      <MessageList messages={props.messagerie.messages} />
    </div>
  );
};

const MessageInput = (props: { onSubmit: (message: string) => void }): ReactElement => {
  const [message, setMessage] = useState('');

  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (message.trim().length > 0) {
      props.onSubmit(message);
      setMessage('');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="message-input">Votre message</label>
      <input
        id="message-input"
        type="text"
        value={message}
        onChange={e => setMessage(e.target.value)}
      />
      <button type="submit">Envoyer</button>
    </form>
  );
};

const MessageList = (props: { messages: string[] }): ReactElement => {
  return (
    <ul aria-label="Messages de la conversation">
      {props.messages.map((message, index) => (
        <li key={index}>{message}</li>
      ))}
    </ul>
  );
};

export default Chat;

In this refactored version, I've extracted the form and its event handlers into a new component called MessageInput. This component takes a single prop onSubmit which is a function that will be called when the form is submitted, this function is passed by the parent component Chat.

The MessageList component takes a single prop messages which is an array of strings to be rendered as list items.

The Chat component now only renders the MessageInput and the MessageList, and it passes the necessary props to each component.

By splitting the Chat component into smaller components, it's easier to understand and test each part of the code.

Please note that this refactored version assumes that the Messagerie class is correctly implemented and exported from the model file.

@jonasgrilleres
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment