facebook / relay

Relay is a JavaScript framework for building data-driven React applications.
https://relay.dev
MIT License
18.39k stars 1.82k forks source link

MockPayloadGenerator: __typename mocking depends on generated request ordering #3744

Open tomgasson opened 2 years ago

tomgasson commented 2 years ago

We have a regression between v12 and v13 in example queries using MockPayloadGenerator.

This is caused by a bug in MockPayloadGenerator, but appears as a regression when upgrading.

It has to do with the feature where the __typename is correctly provided in the mock payload if you don't provide a __typename value in the mock data.

Assume we have these 2 selections generated by the compiler(s):

const generalSelection = {
  kind: 'InlineFragment',
  selections: [
    {
      kind: 'ScalarField',
      name: 'id',
    },
  ],
  type: 'TypeB',
};

const typenameSelection = {
  kind: 'ScalarField',
  name: '__typename',
};

For a query that looks like this

query testQuery {
    a {
        b {
            ... on TypeB {
                id
            }
            __typename
        }
    }
}

These 2 selections are going to be in a request under a linked field

const dummyOperationDescriptor = (items) => ({
    request: {
      node: { // <-- the generated ConcreteRequest
        operation: {
          name: 'testQuery',
          selections: [
            {
              concreteType: 'TypeA',
              kind: 'LinkedField',
              name: 'a',
              selections: [
                {
                  concreteType: null,
                  kind: 'LinkedField',
                  name: 'b',
                  plural: false,
                  selections: items, // <-- here's the order-dependent list
                },
              ],
            },
          ],
        },
        params: {
          metadata: {},
        },
      },
      variables: {},
      cacheConfig: {},
    },
  });

The JS compiler always output the __typename field first in this list of selections

const working = RelayMockPayloadGenerator.generate(dummyOperationDescriptor([
  typenameSelection,
  generalSelection
 ]));

Which leads to this (correct) output

{
    "data": {
        "a": {
            "b": {
                "__typename": "TypeB",
                "id": "<TypeB-mock-id-1>"
            }
        }
    }
}

The rust compiler changed the output ordering of fields, and __typename is no longer first.

const failing = RelayMockPayloadGenerator.generate(dummyOperationDescriptor([
  generalSelection,
  typenameSelection
]));

Now this gives us incorrect output

{
    "data": {
        "a": {
            "b": {
                "__typename": "__MockObject",
                "id": "<TypeB-mock-id-1>"
            }
        }
    }
}

This means that queries that we're previously mocked fine no longer work

alunyov commented 2 years ago

Oh, interesting. I wonder how we didn't got this internally.

I'm not sure what is the right fix could be here... Can you try adding a __typename field explicitly to your queries?

tomgasson commented 2 years ago

Yeah there's a few workarounds here, that all work:

Mostly just reporting for visibility in case someone else hits this too. A simple fix to MockPayloadGenerator might just sort the selections to shift a __typename up to the top before generating?