primitives icon indicating copy to clipboard operation
primitives copied to clipboard

Tabs: Snapshot tests for Components with Tab.Content are often flaky

Open hansjoachim opened this issue 1 year ago • 1 comments

Bug report

I like to use snapshot tests to verify my React components. Given a certain input or state I can verify the expected result and catch regressions if anything changes. I have a minimal example below to demonstrate the issue. Vitest/jest/other testing framework doesn't affect the issue.

Current Behavior

As we see in the snapshot, when it renders the div for Tab.Content it inserts the line style="animation-duration: 0s;". The problem is that sometimes when running these tests, especially on a slower or faster system it flips between the aforementioned style and style="".

I believe this is because it adds some extra style when mounting (https://github.com/radix-ui/primitives/blob/main/packages/react/tabs/src/Tabs.tsx#L257).

This means that occasionally any component containing Tab.Content renders slightly different which breaks the test.

Expected behavior

Ideally, the component should render consistently so that it would match the snapshot every time.

Reproducible example

Some component:

import React from "react";
import * as Tabs from "@radix-ui/react-tabs";

export const Example = () => (
  <Tabs.Root>
    <Tabs.List>
      <Tabs.Trigger>First</Tabs.Trigger>
      <Tabs.Trigger>Second</Tabs.Trigger>
    </Tabs.List>
    <Tabs.Content>
      <p>This is the first</p>
    </Tabs.Content>
    <Tabs.Content>
      <p>This is the second</p>
    </Tabs.Content>
  </Tabs.Root>
);

Some test:

import { expect, describe, it } from "vitest";
import { render } from "@testing-library/react";
import { Example } from "./Example";

describe("<Example>", () => {
  it("should render consistently", () => {
    const { container } = render(<Example />);

    expect(container).toMatchSnapshot();
  });
});

resulting in the following snapshot:

// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`<Example> > should render consistently 1`] = `
<div>
  <div
    data-orientation="horizontal"
    dir="ltr"
  >
    <div
      aria-orientation="horizontal"
      data-orientation="horizontal"
      role="tablist"
      style="outline: none;"
      tabindex="0"
    >
      <button
        aria-controls="radix-:r0:-content-undefined"
        aria-selected="true"
        data-orientation="horizontal"
        data-radix-collection-item=""
        data-state="active"
        id="radix-:r0:-trigger-undefined"
        role="tab"
        tabindex="-1"
        type="button"
      >
        First
      </button>
      <button
        aria-controls="radix-:r0:-content-undefined"
        aria-selected="true"
        data-orientation="horizontal"
        data-radix-collection-item=""
        data-state="active"
        id="radix-:r0:-trigger-undefined"
        role="tab"
        tabindex="-1"
        type="button"
      >
        Second
      </button>
    </div>
    <div
      aria-labelledby="radix-:r0:-trigger-undefined"
      data-orientation="horizontal"
      data-state="active"
      id="radix-:r0:-content-undefined"
      role="tabpanel"
      style="animation-duration: 0s;"
      tabindex="0"
    >
      <p>
        This is the first
      </p>
    </div>
    <div
      aria-labelledby="radix-:r0:-trigger-undefined"
      data-orientation="horizontal"
      data-state="active"
      id="radix-:r0:-content-undefined"
      role="tabpanel"
      style="animation-duration: 0s;"
      tabindex="0"
    >
      <p>
        This is the second
      </p>
    </div>
  </div>
</div>
`;

Suggested solution

I'm not sure how to solve this in practice. Part of the reason I'm filing this is to see if there are any suggestions or workarounds. I've tried searching both the bug tracker and online in general, but to my surprise haven't found any others with this problem.

Normally in snapshot tests, if the component has interactive or async elements, one can use waitFor to see if some specific element or text is present in the document. This is slightly trickier to do when it comes to style properties, especially when once it's loaded the property is set to undefined so I don't have anything to check for.

Some workarounds we have tried:

  • Mocking the Tab.Content import and replace it with a div. The snapshot doesn't break anymore, but we lose a couple of other things :(
  • In some testsuites we have added an extra line to sleep for an additional 100ms before checking the snapshot. This seems to give more consistent results but is of course rather hacky.

Additional context

Your environment

Software Name(s) Version
Radix Package(s) react-tabs 1.0.4
React n/a 18.2.0
Browser
Assistive tech
Node n/a 16.x / 18.x
npm/yarn
Operating System

hansjoachim avatar Oct 10 '23 18:10 hansjoachim

@hansjoachim did you find any workaround for this?

julioxavierr avatar Feb 19 '24 16:02 julioxavierr

Not really found any workarounds beyond those mentioned in the original report.

hansjoachim avatar Feb 25 '24 13:02 hansjoachim