testing-library/svelte-testing-library

How to wrap `render` function and pass along types to underlying `render` function

Closed this issue · 7 comments

Hey there, this is probably user error, but I'm confused about it.

I've created a simple wrapper function around render that sets up a user and returns it, along with all the props that render returns. It looks like this:

import userEvent, { type Options } from '@testing-library/user-event';
import { render } from '@testing-library/svelte';

export function setup(
  component: Parameters<typeof render>[0],
  renderOptions: Parameters<typeof render>[1] = {},
  additionalOptions?: {
    userEventOptions?: Options;
  }
) {
  return {
    user: userEvent.setup(additionalOptions?.userEventOptions),
    ...render(component, renderOptions),
  };
}

And it works, great! Except, VS Code complains about it.

So when calling render, it accurately interprets the prop types:

render(Component, {
  propThatDoesExist, // no TS error, yay!
  propThatDoesNotExist, // TS error, yay!
});

But if I use my helper function, it can't interpret the props correctly:

setup(Component, {
  propThatDoesExist // Object literal may only specify known properties, and 'propThatDoesExist' does not exist in type 'Partial<ComponentConstructorOptions<never>>'.ts(2353)
});

Am I missing something obvious? I thought that if I set the params to be exactly the same, it should be able to infer the types correctly, but maybe there's some TS magic I'm missing?

mcous commented

Oh, interesting! I think the never here is an important hint:

does not exist in type 'Partial<ComponentConstructorOptions<never>>'

render is generic on the component, which is showing up as never in that error. You need to make sure that your setup function is also generic:

export function setup<C>(
  component: Parameters<typeof render<C>>[0],
  renderOptions: Parameters<typeof render<C>>[1] = {},
  additionalOptions?: {
    userEventOptions?: Options;
  }
) {
  // ...
}

Awesome, yep, that works perfectly!

Sorry to bring this up again, but something changed between v5.2.4 and v5.2.6 in the typings, and I'm trying to update my setup function to be correct.

Seeing this now when using it on the component variable:

Argument of type 'Component<Props, {}, "">' is not assignable to parameter of type 'never'.

I'm puzzling through it, but my advanced TypeScript knowledge is quite limited, so I'm not quite grokking what changed that caused this.

I'm sure you're very busy, so if you don't have the bandwidth to help me out, I understand, just thought I'd toss this over the wall in case it's super simple and easy to point out the problem.

Oh, and in the definition of setup, I'm now seeing:

Type 'C' does not satisfy the constraint 'Component<any, any, string> | SvelteComponent<any, any, any>'.
  Type 'C' is not assignable to type 'SvelteComponent<any, any, any>'.ts(2344)
component-test-setup.ts(4, 23): This type parameter might need an `extends SvelteComponent<any, any, any>` constraint.
component-test-setup.ts(4, 23): This type parameter might need an `extends Component<any, any, string> | SvelteComponent<any, any, any>` constraint.
(type parameter) C in setup<C>(component: Parameters<typeof render<C>>[0], renderOptions?: Parameters<typeof render<C>>[1], additionalOptions?: {
    userEventOptions?: Options;
}): {
    container: HTMLElement;
    ... 53 more ...;
    user: UserEvent;

Oh, I figured it out!

function setup<
  C extends Component<any, any, string> | SvelteComponent<any, any, any>,
>(

Helps if you read the TS errors, I suppose. Sorry for the bother, thanks for letting me talk it out here!

mcous commented

Glad you got it working! What changed on our end is that we had a bug in our types, causing our definition of the C constraint to accidentally become any. So things likely got a little more strict, and my advice earlier may have accidentally relied on the buggy looseness

Regardless, I greatly appreciate any and all typing feedback! Typings are a little hard to test and evaluate for usability

From my experience, it works wonderfully and feels magical that it just knows the props definitions. I love it, thanks so much for your work on this!