Skip to content

Commit

Permalink
Add support for deferred fragments in MockPayloadGenerator
Browse files Browse the repository at this point in the history
Summary: Add support for defer in tests using Relay MockPayloadGenerator

Reviewed By: alunyov

Differential Revision: D48653997

fbshipit-source-id: 207258207368fc7e29061c0ad40f69a253fe3763
  • Loading branch information
Fernando Gorodscy authored and facebook-github-bot committed Aug 29, 2023
1 parent b50b78b commit 2108aed
Show file tree
Hide file tree
Showing 10 changed files with 722 additions and 23 deletions.
70 changes: 62 additions & 8 deletions packages/relay-test-utils/RelayMockPayloadGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import type {
OperationDescriptor,
Variables,
} from 'relay-runtime';
import type {GraphQLResponseWithData} from 'relay-runtime/network/RelayNetworkTypes';
import type {GraphQLResponse} from 'relay-runtime/network/RelayNetworkTypes';

const invariant = require('invariant');
const {
Expand Down Expand Up @@ -181,12 +183,15 @@ class RelayMockPayloadGenerator {
_mockResolvers: MockResolvers;
_selectionMetadata: SelectionMetadata;
_mockClientData: boolean;
_generateDeferredPayload: boolean;
_deferredPayloads: Array<GraphQLResponseWithData>;

constructor(options: {
+variables: Variables,
+mockResolvers: MockResolvers | null,
+selectionMetadata: SelectionMetadata | null,
+mockClientData: ?boolean,
+generateDeferredPayload: ?boolean,
}) {
this._variables = options.variables;
this._mockResolvers = {
Expand All @@ -196,20 +201,22 @@ class RelayMockPayloadGenerator {
this._selectionMetadata = options.selectionMetadata ?? {};
this._resolveValue = createValueResolver(this._mockResolvers);
this._mockClientData = options.mockClientData ?? false;
this._generateDeferredPayload = options.generateDeferredPayload ?? false;
this._deferredPayloads = [];
}

generate(
selections: $ReadOnlyArray<NormalizationSelection>,
operationType: string,
): MockData {
): GraphQLResponse {
const defaultValues = this._getDefaultValuesForObject(
operationType,
null,
null,
[], // path
{},
);
return this._traverse(
const data = this._traverse(
{
selections,
typeName: operationType,
Expand All @@ -222,6 +229,8 @@ class RelayMockPayloadGenerator {
null, // prevData
defaultValues,
);

return [{data}, ...this._deferredPayloads];
}

_traverse(
Expand Down Expand Up @@ -304,6 +313,24 @@ class RelayMockPayloadGenerator {
// falls through
case DEFER:
case STREAM: {
if (this._generateDeferredPayload) {
const deferredData = this._traverseSelections(
selection.selections,
typeName,
isAbstractType,
path,
{},
defaultValues,
);

this._deferredPayloads.push({
path: [...path],
label: selection.label,
data: deferredData,
});

break;
}
mockData = this._traverseSelections(
selection.selections,
typeName,
Expand Down Expand Up @@ -892,13 +919,14 @@ function generateData(
variables: Variables,
mockResolvers: MockResolvers | null,
selectionMetadata: SelectionMetadata | null,
options: ?{mockClientData?: boolean},
): MockData {
options: ?{mockClientData?: boolean, generateDeferredPayload?: boolean},
): GraphQLResponse {
const mockGenerator = new RelayMockPayloadGenerator({
variables,
mockResolvers,
selectionMetadata,
mockClientData: options?.mockClientData,
generateDeferredPayload: options?.generateDeferredPayload,
});
let operationType;
if (node.name.endsWith('Mutation')) {
Expand All @@ -908,6 +936,7 @@ function generateData(
} else {
operationType = 'Query';
}

return mockGenerator.generate(node.selections, operationType);
}

Expand Down Expand Up @@ -955,16 +984,41 @@ function generateDataForOperation(
mockResolvers: ?MockResolvers,
options: ?{mockClientData?: boolean},
): GraphQLSingularResponse {
const data = generateData(
operation.request.node.operation,
const concreteOperation = operation.request.node.operation;
const [initialPayload] = generateData(
concreteOperation,
operation.request.variables,
mockResolvers ?? null,
getSelectionMetadataFromOperation(operation),
options,
{...options, generateDeferredPayload: false},
);
return {data};

return initialPayload;
}

function generateWithDefer(
operation: OperationDescriptor,
mockResolvers: ?MockResolvers,
options: ?{mockClientData?: boolean, generateDeferredPayload?: boolean},
): GraphQLResponse {
const {generateDeferredPayload = false, ...otherOptions} = options ?? {};
const concreteOperation = operation.request.node.operation;
const payloads = generateData(
concreteOperation,
operation.request.variables,
mockResolvers ?? null,
getSelectionMetadataFromOperation(operation),
{...otherOptions, generateDeferredPayload: generateDeferredPayload},
);

if (!generateDeferredPayload) {
return payloads[0];
}

return payloads;
}

module.exports = {
generate: generateDataForOperation,
generateWithDefer,
};
9 changes: 6 additions & 3 deletions packages/relay-test-utils/RelayModernMockEnvironment.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ type MockFunctions = {
+complete: (request: ConcreteRequest | OperationDescriptor) => void,
+resolve: (
request: ConcreteRequest | OperationDescriptor,
payload: GraphQLSingularResponse,
payload: $ReadOnlyArray<GraphQLSingularResponse> | GraphQLSingularResponse,
) => void,
+getAllOperations: () => $ReadOnlyArray<OperationDescriptor>,
+findOperation: (
Expand Down Expand Up @@ -400,12 +400,15 @@ function createMockEnvironment(

const resolve = (
request: ConcreteRequest | OperationDescriptor,
payload: GraphQLSingularResponse,
response: $ReadOnlyArray<GraphQLSingularResponse> | GraphQLSingularResponse,
): void => {
getRequests(request).forEach(foundRequest => {
const {sink} = foundRequest;
invariant(sink !== null, 'Sink should be defined.');
sink.next(ensureValidPayload(payload));
const payloads = Array.isArray(response) ? response : [response];
payloads.forEach(payload => {
sink.next(ensureValidPayload(payload));
});
sink.complete();
});
};
Expand Down
110 changes: 100 additions & 10 deletions packages/relay-test-utils/__tests__/RelayMockEnvironment-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,34 @@

'use strict';

import type {MockResolvers} from '../RelayMockPayloadGenerator';
import type {RelayMockEnvironmentTestWithDeferFragment_user$key} from './__generated__/RelayMockEnvironmentTestWithDeferFragment_user.graphql';

const preloadQuery = require('../../react-relay/relay-hooks/preloadQuery_DEPRECATED');
const RelayEnvironmentProvider = require('../../react-relay/relay-hooks/RelayEnvironmentProvider');
const useFragment = require('../../react-relay/relay-hooks/useFragment');
const useLazyLoadQuery = require('../../react-relay/relay-hooks/useLazyLoadQuery');
const usePreloadedQuery = require('../../react-relay/relay-hooks/usePreloadedQuery');
const React = require('react');
const TestRenderer = require('react-test-renderer');
const {act} = require('react-test-renderer');
const {graphql} = require('relay-runtime');
const {
MockPayloadGenerator,
createMockEnvironment,
} = require('relay-test-utils');

const query = graphql`
query RelayMockEnvironmentTestQuery($id: ID!) {
node(id: $id) {
id
... on User {
name
describe('when using queuePendingOperation, queueOperationResolver and preloadQuery in tests', () => {
const query = graphql`
query RelayMockEnvironmentTestQuery($id: ID!) {
node(id: $id) {
id
... on User {
name
}
}
}
}
`;

describe('when using queuePendingOperation, queueOperationResolver and preloadQuery in tests', () => {
`;
let prefetched;
let mockEnvironment;

Expand Down Expand Up @@ -130,3 +135,88 @@ describe('when using queuePendingOperation, queueOperationResolver and preloadQu
});
});
});

describe('when generating multiple payloads for deferred data', () => {
const query = graphql`
query RelayMockEnvironmentTestWithDeferQuery($id: ID!) {
node(id: $id) {
id
... on User {
...RelayMockEnvironmentTestWithDeferFragment_user @defer
}
}
}
`;

const fragment = graphql`
fragment RelayMockEnvironmentTestWithDeferFragment_user on User {
name
}
`;

const render = () => {
const mockEnvironment = createMockEnvironment();
const variables = {id: '4'};

function Component(props: {}) {
const data = useLazyLoadQuery(query, variables);
return (
<>
{data.node?.id}
{data.node && <DeferredComponent user={data.node} />}
</>
);
}
function DeferredComponent(props: {
user: RelayMockEnvironmentTestWithDeferFragment_user$key,
}) {
const data = useFragment(fragment, props.user);
return data?.name;
}
const renderer = TestRenderer.create(
<RelayEnvironmentProvider environment={mockEnvironment}>
<React.Suspense fallback="Fallback">
<Component />
</React.Suspense>
</RelayEnvironmentProvider>,
);

const isSuspended = () => renderer.toJSON() === 'Fallback';

const generateData = (resolvers: MockResolvers) => {
const operation = mockEnvironment.mock.getMostRecentOperation();
const mockData = MockPayloadGenerator.generateWithDefer(
operation,
resolvers,
{generateDeferredPayload: true},
);
mockEnvironment.mock.resolve(operation, mockData);

act(() => jest.runAllTimers());
};

return {
generateData,
renderer,
isSuspended,
};
};

it('renders the initial and deferred payloads', () => {
const {renderer, isSuspended, generateData} = render();

expect(isSuspended()).toEqual(true);

generateData({
ID() {
return '4';
},
String() {
return 'Zuck';
},
});

expect(isSuspended()).toEqual(false);
expect(renderer.toJSON()).toEqual(['4', 'Zuck']);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ const {FIXTURE_TAG} = require('relay-test-utils-internal');
function testGeneratedData<TVariables: Variables, TData, TRawResponse>(
query: Query<TVariables, TData, TRawResponse>,
mockResolvers: ?MockResolvers,
options: ?{mockClientData?: boolean},
options: ?{mockClientData?: boolean, generateDeferredPayload?: boolean},
variables: Variables = {},
): void {
const operation = createOperationDescriptor(query, variables);
const payload = RelayMockPayloadGenerator.generate(
const payload = RelayMockPayloadGenerator.generateWithDefer(
operation,
mockResolvers,
options,
Expand Down Expand Up @@ -1739,3 +1739,23 @@ test('Query with @no_inline fragment spread with variable argument', () => {
},
);
});

test('generate mock for deferred fragments', () => {
graphql`
fragment RelayMockPayloadGeneratorTest61Fragment on User {
name
}
`;
testGeneratedData(
graphql`
query RelayMockPayloadGeneratorTest61Query {
node(id: "my-id") {
id
...RelayMockPayloadGeneratorTest61Fragment @defer
}
}
`,
null,
{generateDeferredPayload: true},
);
});
Loading

0 comments on commit 2108aed

Please sign in to comment.