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

Fix issue where a merge function that returns an incomplete result would not allow the client to refetch from network #11839

Merged
merged 14 commits into from
May 14, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add test to show that partial cache write does not trigger errored query
  • Loading branch information
jerelmiller committed May 14, 2024
commit 87d1017ec1505fd90c7ffdbb279d274694be916b
190 changes: 190 additions & 0 deletions src/react/hooks/__tests__/useQuery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4664,6 +4664,196 @@ describe("useQuery Hook", () => {
await expect(Profiler).not.toRerender();
});

it("does not rerender or refetch queries with errors for partial cache writes with returnPartialData: true", async () => {
interface Query1 {
person: {
__typename: "Person";
id: number;
firstName: string;
alwaysFails: boolean;
} | null;
}

interface Query2 {
person: {
__typename: "Person";
id: number;
lastName: string;
} | null;
}

interface Variables {
id: number;
}

const user = userEvent.setup();

const query1: TypedDocumentNode<Query1, Variables> = gql`
query PersonQuery1($id: ID!) {
person(id: $id) {
id
firstName
alwaysFails
}
}
`;

const query2: TypedDocumentNode<Query2, Variables> = gql`
query PersonQuery2($id: ID!) {
person(id: $id) {
id
lastName
}
}
`;

const Profiler = createProfiler({
initialSnapshot: {
useQueryResult: null as QueryResult<Query1, Variables> | null,
useLazyQueryResult: null as QueryResult<Query2, Variables> | null,
},
});

const client = new ApolloClient({
link: new MockLink([
{
request: { query: query1, variables: { id: 1 } },
result: {
data: { person: null },
errors: [new GraphQLError("Intentional error")],
},
delay: 20,
maxUsageCount: Number.POSITIVE_INFINITY,
},
{
request: { query: query2, variables: { id: 1 } },
result: {
data: {
person: {
__typename: "Person",
id: 1,
lastName: "Doe",
},
},
},
delay: 20,
},
]),
cache: new InMemoryCache(),
});

function App() {
const useQueryResult = useQuery(query1, {
variables: { id: 1 },
notifyOnNetworkStatusChange: true,
returnPartialData: true,
});

const [execute, useLazyQueryResult] = useLazyQuery(query2, {
variables: { id: 1 },
});

Profiler.replaceSnapshot({ useQueryResult, useLazyQueryResult });

return <button onClick={() => execute()}>Run 2nd query</button>;
}

render(<App />, {
wrapper: ({ children }) => (
<ApolloProvider client={client}>
<Profiler>{children}</Profiler>
</ApolloProvider>
),
});

{
const { snapshot } = await Profiler.takeRender();

expect(snapshot.useQueryResult).toMatchObject({
data: undefined,
loading: true,
networkStatus: NetworkStatus.loading,
});

expect(snapshot.useLazyQueryResult).toMatchObject({
called: false,
data: undefined,
loading: false,
networkStatus: NetworkStatus.ready,
});
}

{
const { snapshot } = await Profiler.takeRender();

expect(snapshot.useQueryResult).toMatchObject({
data: undefined,
error: new ApolloError({
graphQLErrors: [new GraphQLError("Intentional error")],
}),
loading: false,
networkStatus: NetworkStatus.error,
});

expect(snapshot.useLazyQueryResult).toMatchObject({
called: false,
data: undefined,
loading: false,
networkStatus: NetworkStatus.ready,
});
}

await act(() => user.click(screen.getByText("Run 2nd query")));

{
const { snapshot } = await Profiler.takeRender();

expect(snapshot.useQueryResult).toMatchObject({
data: undefined,
error: new ApolloError({
graphQLErrors: [new GraphQLError("Intentional error")],
}),
loading: false,
networkStatus: NetworkStatus.error,
});

expect(snapshot.useLazyQueryResult).toMatchObject({
called: true,
data: undefined,
loading: true,
networkStatus: NetworkStatus.loading,
});
}

{
const { snapshot } = await Profiler.takeRender();

expect(snapshot.useQueryResult).toMatchObject({
data: undefined,
error: new ApolloError({
graphQLErrors: [new GraphQLError("Intentional error")],
}),
loading: false,
networkStatus: NetworkStatus.error,
});

expect(snapshot.useLazyQueryResult).toMatchObject({
called: true,
data: {
person: {
__typename: "Person",
id: 1,
lastName: "Doe",
},
},
loading: false,
networkStatus: NetworkStatus.ready,
});
}

await expect(Profiler).not.toRerender();
});

it("delivers the full network response when a merge function returns an incomplete result", async () => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also a documenting test. I wanted to make sure we captured this as behavior in the client since I did not expect to see this behavior work.

const query = gql`
query {
Expand Down
Loading