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

MockLink: update the function types returned by ResultFunction & VariableMatcher to define their own generics #11812

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

sf-twingate
Copy link
Contributor

@sf-twingate sf-twingate commented Apr 26, 2024

Issue

A feature introduced last year (#6701) which updated MockLink's ResultFunction type and introduced a new VariableMatcher type (both referenced by MockedResponse) has caused a type issue our repository that is affecting our team's common practice of setting a generic which extends from MockedResponse[] on our test setup functions (e.g. function setup<M extends MockedResponse[]>).

This issue arises due to both types accepting a variables generic type (V), and then returning a function type which uses this generic as its own argument type:

export type ResultFunction<T, V = Record<string, any>> = (variables: V) => T;
export type VariableMatcher<V = Record<string, any>> = (variables: V) => boolean;

When these types are combined with our usage of M extends MockedResponse[], TypeScript is unable to correctly infer the variables argument type for each of of the values in M.

Example Failure

Here's an isolated example of this which will currently fail type checking:

Failing Example:
import { FetchResult, GraphQLRequest, gql } from "@apollo/client";

// The `ResultFunction`, `VariableMatcher` & `MockedResponse` types below are the current types
// defined in `mockLink.ts`.

type ResultFunction<T, V = Record<string, any>> = (variables: V) => T;
type VariableMatcher<V = Record<string, any>> = (variables: V) => boolean;

interface MockedResponse<
  TData = Record<string, any>,
  TVariables = Record<string, any>,
> {
  request: GraphQLRequest<TVariables>;
  maxUsageCount?: number;
  result?: FetchResult<TData> | ResultFunction<FetchResult<TData>, TVariables>;
  error?: Error;
  delay?: number;
  variableMatcher?: VariableMatcher<TVariables>;
  newData?: ResultFunction<FetchResult<TData>, TVariables>;
}

// ---- test case below

function setup<M extends MockedResponse[]>(props: { mocks: M }) {
  const { mocks } = props;

  return {
    mocks,
  };
}

// Type '[MockedResponse<{ a: string; }, { foo: string; }>]' does not satisfy the constraint 'MockedResponse<Record<string, any>, Record<string, any>>[]'.
//   Type 'MockedResponse<{ a: string; }, { foo: string; }>' is not assignable to type 'MockedResponse<Record<string, any>, Record<string, any>>'.
//     Types of property 'result' are incompatible.
//       Type 'FetchResult<{ a: string; }> | ResultFunction<FetchResult<{ a: string; }>, { foo: string; }> | undefined' is not assignable to type 'FetchResult<Record<string, any>> | ResultFunction<FetchResult<Record<string, any>>, Record<string, any>> | undefined'.
//         Type 'ResultFunction<FetchResult<{ a: string; }>, { foo: string; }>' is not assignable to type 'FetchResult<Record<string, any>> | ResultFunction<FetchResult<Record<string, any>>, Record<string, any>> | undefined'.
//           Type 'ResultFunction<FetchResult<{ a: string; }>, { foo: string; }>' is not assignable to type 'ResultFunction<FetchResult<Record<string, any>>, Record<string, any>>'.
//             Type 'Record<string, any>' is not assignable to type '{ foo: string; }'. ts(2344)
setup<[MockedResponse<{ a: string }, { foo: string }>]>({
  mocks: [
    {
      request: {
        query: gql`
          query A {
            a
          }
        `,
        variables: {
          foo: "bar",
        },
      },
    },
  ],
});

Proposed Fix

I believe this can be fixed, without causing any breaking changes, by making the function types returned by ResultFunction & VariableMatcher declare their own generic type, which extends from and defaults to the V type.

This resolves the issue in the snippet above:

Example Fix:
import { FetchResult, GraphQLRequest, gql } from "@apollo/client";

// The `ResultFunction` & `VariableMatcher` types below have been updated to declare their own generics.

type ResultFunction<T, V = Record<string, any>> = <_V extends V = V>(
  variables: _V
) => T;

type VariableMatcher<V = Record<string, any>> = <_V extends V = V>(
  variables: _V
) => boolean;

// This is still the existing `MockedResponse` type from `mockLink.ts`.
export interface MockedResponse<
  TData = Record<string, any>,
  TVariables = Record<string, any>,
> {
  request: GraphQLRequest<TVariables>;
  maxUsageCount?: number;
  result?: FetchResult<TData> | ResultFunction<FetchResult<TData>, TVariables>;
  error?: Error;
  delay?: number;
  variableMatcher?: VariableMatcher<TVariables>;
  newData?: ResultFunction<FetchResult<TData>, TVariables>;
}

// ---- test case below

function setup<M extends MockedResponse[]>(props: { mocks: M }) {
  const { mocks } = props;

  return {
    mocks,
  };
}

// No type error anymore.
setup<[MockedResponse<{ a: string }, { foo: string }>]>({
  mocks: [
    {
      request: {
        query: gql`
          query A {
            a
          }
        `,
        variables: {
          foo: "bar",
        },
      },
    },
  ],
});

// A type error is still _correctly_ thrown below due to the `variables` type mismatch.
setup<[MockedResponse<{ a: string }, { foo: string }>]>({
  mocks: [
    {
      request: {
        query: gql`
          query A {
            a
          }
        `,
        variables: {
          // Object literal may only specify known properties, and 'not' does not exist in type '{ foo: string; }'. ts(2353)
          // types.d.ts(36, 5): The expected type comes from property 'variables' which is declared here on type 'GraphQLRequest<{ foo: string; }>'
          not: "found",
        },
      },
    },
  ],
});

I've implemented the change as part of this PR and added a test case which currently fails when run against main, but passes on this branch.

Please let me know if you have any questions / concerns about the changes — thanks!

Copy link

netlify bot commented Apr 26, 2024

👷 Deploy request for apollo-client-docs pending review.

Visit the deploys page to approve it

Name Link
🔨 Latest commit 24660b8

Copy link

changeset-bot bot commented Apr 26, 2024

🦋 Changeset detected

Latest commit: 24660b8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@apollo/client Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@sf-twingate sf-twingate marked this pull request as draft April 26, 2024 18:28
@sf-twingate sf-twingate force-pushed the feature/sf/adjust-variable-function-types branch from 0d7ca97 to 1717cb5 Compare April 26, 2024 18:35
@sf-twingate sf-twingate changed the title MockedProvider: update the functions returned by ResultFunction & VariableMatcher to define their own generics MockedResponse: update the functions returned by ResultFunction & VariableMatcher to define their own generics Apr 26, 2024
@sf-twingate sf-twingate changed the title MockedResponse: update the functions returned by ResultFunction & VariableMatcher to define their own generics MockLink: update the function types returned by ResultFunction & VariableMatcher to define their own generics Apr 26, 2024
@sf-twingate sf-twingate force-pushed the feature/sf/adjust-variable-function-types branch 3 times, most recently from 6c67aa9 to 4053fb7 Compare April 26, 2024 18:43
@sf-twingate sf-twingate marked this pull request as ready for review April 26, 2024 18:47
@sf-twingate sf-twingate force-pushed the feature/sf/adjust-variable-function-types branch 2 times, most recently from c18b72a to 442f7f6 Compare April 26, 2024 19:09
@sf-twingate sf-twingate force-pushed the feature/sf/adjust-variable-function-types branch from 442f7f6 to 24660b8 Compare April 26, 2024 20:46
@jerelmiller
Copy link
Member

Hey @sf-twingate 👋

We'll do our best to get to this PR soon. Thanks so much for the contribution!

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 this pull request may close these issues.

None yet

2 participants