Home > Software design >  React testing Library | .toHaveAttribute()
React testing Library | .toHaveAttribute()

Time:10-15

I'm trying to test a component but having issue with unit testing.

The component have tabs provided via props. It's an array of object. When the component first render, the first tab is supposed to be selected :

  • aria-selected set to true
  • inline style setting a different background color property.
  • The selected tab has a state of isActive = true

When another tab then the selected one is clicked, all the property listed above is supposed to be apply to the clicked tab.

When I test manually, everything is working fine. But, when I make a unit test for testing this behavior, it's not working at all.

Here's the code :

import React, { useState } from "react";
import { Box, Typography, Button } from "@mui/material";
import style from "./header.module.scss";

interface IProps {
  tabs: Array<{ src: string; name: string }>;
}

const Header = ({ tabs }: IProps) => {
  const [activeTab, setActiveTab] = useState<string>(
    tabs[0].name.toUpperCase(),
  );

  const handleClick = (event: React.MouseEvent<HTMLButtonElement>): void => {
    const tabName: string = event.currentTarget.innerText;
    setActiveTab(tabName);
  };

  const buttonBackgroundColor = (
    buttonName: string,
    activeTabName: string,
  ): string => {
    let className: string;
    if (
      buttonName &&
      activeTabName &&
      buttonName.toUpperCase() === activeTabName.toUpperCase()
    ) {
      className = "rgb(94, 70, 136)";
    } else {
      className = "inherit";
    }

    return className;
  };

  const setAriaSelected = (currentTab: string): boolean => {
    return (
      currentTab.localeCompare(activeTab, undefined, {
        sensitivity: "accent",
      }) === 0
    );
  };

  return (
    <Box component="header" className={style.header}>
      {tabs.map((tab) => (
        <Button
          key={tab.name}
          className={style.button}
          onClick={handleClick}
          role="tab"
          aria-selected={setAriaSelected(tab.name)}
          style={{
            backgroundColor: buttonBackgroundColor(tab.name, activeTab),
          }}
        >
          <div className={style.imgWrapper}>
            <img src={tab.src} alt={tab.name} />
          </div>
          <Typography fontWeight={300} letterSpacing="0.1rem" fontSize="0.9rem">
            {tab.name}
          </Typography>
        </Button>
      ))}
    </Box>
  );
};

export default Header;

Testing file:

 it("makes the second tab to have aria-selected attribute to true when it's clicked", async () => {
    render(
      <MemoryRouter>
        <Header tabs={tabs} />
      </MemoryRouter>,
    );
    const user = userEvent.setup();
    const secondTab = screen.getByRole("tab", { name: /tab2/i });
    await user.click(secondTab);

    expect(secondTab).toHaveAttribute("aria-selected", "true");
  });

Console message:

● Header component › makes the second tab to have aria-selected attribute to true when it's clicked

expect(element).toHaveAttribute("aria-selected", "true") // element.getAttribute("aria-selected") === "true"

Expected the element to have attribute:
  aria-selected="true"
Received:
  aria-selected="false"

  57 |     await user.click(secondTab);
  58 |
> 59 |     expect(secondTab).toHaveAttribute("aria-selected", "true");
     |                       ^
  60 |   });
  61 | });
  62 |

  at Object.<anonymous> (src/shared/header/__tests__/Header.tsx:59:23)

CodePudding user response:

The problem is that innerText is not supported by jsdom, so your component is never setting the second tab as active.

Does it work with textContent instead, as that is the standard ? (you will have to call .trim() on it though, as it keeps whitespace as well)

const handleClick = (event: React.MouseEvent<HTMLButtonElement>): void => {
  const tabName: string = event.currentTarget.textContent.trim();
  setActiveTab(tabName);
};

If not, then try passing the tab.name directly to handleClick

const handleClick = (tabName: string): void => {
  setActiveTab(tabName);
};

and

onClick={() => handleClick(tab.name)}
  • Related