testing-library / react-testing-library

🐐 Simple and complete React DOM testing utilities that encourage good testing practices.

Home Page:https://testing-library.com/react

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Types are breaking

Akallabet opened this issue · comments

  • @testing-library/react version: 14.2.1
  • Testing Framework and version: jest@29.0.0
  • DOM Environment: jsdom@29.0.0

What you did:

No changes

What happened:

Typescript started to throw errors

Reproduction:

I do not have a reproduction as the code is proprietary

Problem description:

This is the stacktrace

Type 'JSXElementConstructor<{ children: ReactElement<any, string | JSXElementConstructor<any>>; }>' is not  assignable to type 'JSXElementConstructor<{ children: ReactNode; }> | undefined'.

Suggested solution:

Revert this change

Please provide a reproduction first.

I'll add one asap

commented

Happened to us too.

Offending code:

const wrapper = ({ children }: { children: JSX.Element }) => (
  <QueryClientProvider client={testQueryClient}>{children}</QueryClientProvider>
);

Replacing JSX.Element with React.ReactNode made typechecking pass again for us.

@eps1lon

Please provide a reproduction first.

Hope this is enough.

import React from 'react';
import { render, screen } from '@testing-library/react';

it('This test generates a typescript error', async () => {
  const wrapper: React.JSXElementConstructor<{ children: React.ReactElement }> = ({ children }) => (
    <div>{children}</div>
  );
  render(<div>Test</div>, { wrapper });

  expect(await screen.findByText('Test')).toBeTruthy();
});

This is the TS error:

No overload matches this call.
     Overload 1 of 2, '(ui: ReactNode, options: RenderOptions<typeof import("[local path removed]/node_modules/@testing-library/dom/types/queries"), HTMLElement, HTMLElement>): RenderResult<...>', gave the following error.
       Type '(props: { children: ReactElement<any, string | JSXElementConstructor<any>>; }, deprecatedLegacyContext?: any) => ReactNode' is not assignable to type 'JSXElementConstructor<{ children: ReactNode; }> | undefined'.
         Type '(props: { children: ReactElement<any, string | JSXElementConstructor<any>>; }, deprecatedLegacyContext?: any) => ReactNode' is not assignable to type '(props: { children: ReactNode; }, deprecatedLegacyContext?: any) => ReactNode'.
           Types of parameters 'props' and 'props' are incompatible.
             Type '{ children: ReactNode; }' is not assignable to type '{ children: ReactElement<any, string | JSXElementConstructor<any>>; }'.
               Types of property 'children' are incompatible.
                 Type 'ReactNode' is not assignable to type 'ReactElement<any, string | JSXElementConstructor<any>>'.
     Overload 2 of 2, '(ui: ReactNode, options?: Omit<RenderOptions<typeof import("[local path removed]/node_modules/@testing-library/dom/types/queries"), HTMLElement, HTMLElement>, "queries"> | undefined): RenderResult<...>', gave the following error.
       Type '(props: { children: ReactElement<any, string | JSXElementConstructor<any>>; }, deprecatedLegacyContext?: any) => ReactNode' is not assignable to type 'JSXElementConstructor<{ children: ReactNode; }> | undefined'.

I would like to highlight the fact that the error did not appear on version 14.2.0 and since I have to change what was previously working, this is supposed to be a breaking change.

this is supposed to be a breaking change.

It was a bug fix. Bug fixes are by definition backwards incompatible and thus a breaking change for everybody relying on the bug. The change is compliant with Semantic Versioning.

The fix is described in #1275 (comment)

Closing as working as intended.

Thanks for your hard work, this library is awesome.
I'm really disappointed by this answer: even if this is a bug, other softwares use this feature and now have to migrate, the fact that you are following semantic versioning doesn't change that.
This could've been handled much better.

This could've been handled much better.

How should we handle bug fixes instead?

As a reiteration, since this was already used by others, changing this specific behaviour essentially creates a bug for all of them. This is true even if the original intended behaviour was different, because people where already relying on it.
Fixing a bug by creating another one is not a great way to handle it IMHO.
This is a breaking change and therefore it should have been logged and changed in the next major version.

If this is not possible, an alternative could be to extend the existing contract instead of changing it e.g.:

// Existing functionality not being affected
rerender: (ui: React.ReactNode) => void
// Extended functionality
rerenderWithElement: (ui: React.ReactElement) => void

This way you do not create issues for people relying on this behaviour and offer a way for whomever want's to take advantage of the new behaviour.
Then in the next major version you could've make the new functionality deprecated or just remove it and update the previous one accordingly.

This way you do not create issues for people relying on this behaviour

But I'm also not fixing it for the people that want to rely on the intended behavior. Every bug fix would have to be a major release. That's not how semantic versioning works for very practical reasons.

If you want to rely on existing behavior and don't want any bug fixes, you should pin your dependencies to an exact version. That way we cannot break your code.

Just to be clear I have nothing against this library in particular or against anyone maintaining it, I understand you have a tough job, I'm just trying to get a point across and eventually find some common ground.

Every bug fix would have to be a major release.

Not necessarily, it depends on the bug.

This specific bug fix requires a change in an API, which makes it backwards incompatible.
Also as it is written in the semver official doc:

1. MAJOR version when you make incompatible API changes
2. MINOR version when you add functionality in a backward compatible
   manner
3. PATCH version when you make backward compatible bug fixes

From here: https://github.com/semver/semver/blob/master/semver.md?plain=1#L12

Oh wow that's not how I remember it. Bug fixes cannot be backwards compatible by definition since they change behavior. This change didn't change the API. The types are not equivalent with the API. They are a description of that but we can always make mistakes describing the API with types. This change is an instant where the description of the API via types was buggy.

Bug fixes cannot be backwards compatible by definition since they change behaviour.

I honestly don't know where this is coming from. Why is this a rule and who established it?
Even in semver says PATCH version when you make backward compatible bug fixes, so I guess it's possible?

This change didn't change the API. The types are not equivalent with the API.

I strongly disagree here. These types where exported for everyone to use therefore they are an interface or API.

This change is an instant where the description of the API via types was buggy.

Mistakes happen, we all make mistakes. I just gave you an alternative to creating a breaking change in order to fix them though.