Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Types are breaking #1275

Closed
Akallabet opened this issue Feb 2, 2024 · 12 comments
Closed

Types are breaking #1275

Akallabet opened this issue Feb 2, 2024 · 12 comments

Comments

@Akallabet
Copy link

Akallabet commented Feb 2, 2024

  • @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

@eps1lon
Copy link
Member

eps1lon commented Feb 2, 2024

Please provide a reproduction first.

@Akallabet
Copy link
Author

I'll add one asap

@jgarplind
Copy link

jgarplind commented Feb 5, 2024

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.

@Akallabet
Copy link
Author

Akallabet commented Feb 5, 2024

@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.

@eps1lon
Copy link
Member

eps1lon commented Feb 5, 2024

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.

@eps1lon eps1lon closed this as not planned Won't fix, can't repro, duplicate, stale Feb 5, 2024
@Akallabet
Copy link
Author

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.

@eps1lon
Copy link
Member

eps1lon commented Feb 6, 2024

This could've been handled much better.

How should we handle bug fixes instead?

@Akallabet
Copy link
Author

Akallabet commented Feb 7, 2024

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.

@eps1lon
Copy link
Member

eps1lon commented Feb 7, 2024

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.

@Akallabet
Copy link
Author

Akallabet commented Feb 7, 2024

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

@eps1lon
Copy link
Member

eps1lon commented Feb 7, 2024

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.

@Akallabet
Copy link
Author

Akallabet commented Feb 7, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants