diff --git a/config/jest.config.ts b/config/jest.config.ts
index 5d2a19d8090..e7bf53b2fa6 100644
--- a/config/jest.config.ts
+++ b/config/jest.config.ts
@@ -42,6 +42,7 @@ const reactSharedTestFileIgnoreList = [
ignoreDTSFiles,
ignoreTSFiles,
"src/react/hooks/__tests__/useBackgroundQuery/testUtils.tsx",
+ "src/react/hooks/__tests__/useLoadableQuery/testUtils.tsx",
"src/react/hooks/__tests__/useSuspenseQuery/testUtils.tsx",
"src/react/query-preloader/__tests__/createQueryPreloader/testUtils.tsx",
];
diff --git a/src/core/__tests__/client.mutate/customScalars.test.ts b/src/core/__tests__/client.mutate/customScalars.test.ts
index c2444645784..8f9210fc56b 100644
--- a/src/core/__tests__/client.mutate/customScalars.test.ts
+++ b/src/core/__tests__/client.mutate/customScalars.test.ts
@@ -1,7 +1,13 @@
import { delay, of } from "rxjs";
import type { OperationVariables, TypedDocumentNode } from "@apollo/client";
-import { ApolloClient, ApolloLink, gql, NetworkStatus } from "@apollo/client";
+import {
+ ApolloClient,
+ ApolloLink,
+ CombinedGraphQLErrors,
+ gql,
+ NetworkStatus,
+} from "@apollo/client";
import { InMemoryCache } from "@apollo/client/cache";
import { MockLink } from "@apollo/client/testing";
import { dateScalar, ObservableStream } from "@apollo/client/testing/internal";
@@ -658,3 +664,196 @@ test("parses custom scalar fields in queries triggered by refetchQueries", async
},
});
});
+
+test("serializes scalar fields in the error with a `none` error policy", async () => {
+ const mutation = gql`
+ mutation CreateEvent {
+ createEvent {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ createEvent: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["createEvent", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ await expect(
+ client.mutate({ mutation, errorPolicy: "none" })
+ ).rejects.toEqual(
+ new CombinedGraphQLErrors({
+ data: {
+ createEvent: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["createEvent", "endDate"],
+ },
+ ],
+ })
+ );
+});
+
+test("parses scalar fields in the result and serializes them in the error with an `all` error policy", async () => {
+ const mutation = gql`
+ mutation CreateEvent {
+ createEvent {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ createEvent: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["createEvent", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ await expect(
+ client.mutate({ mutation, errorPolicy: "all" })
+ ).resolves.toStrictEqualTyped({
+ data: {
+ createEvent: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ error: new CombinedGraphQLErrors({
+ data: {
+ createEvent: {
+ __typename: "Event",
+ id: "1",
+ // TODO: Determine if this is correct
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["createEvent", "endDate"],
+ },
+ ],
+ }),
+ });
+});
+
+test("parses custom scalar fields with an `ignore` error policy", async () => {
+ const mutation = gql`
+ mutation CreateEvent {
+ createEvent {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ createEvent: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["createEvent", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ await expect(
+ client.mutate({ mutation, errorPolicy: "ignore" })
+ ).resolves.toStrictEqualTyped({
+ data: {
+ createEvent: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ });
+});
diff --git a/src/core/__tests__/client.query/customScalars.test.ts b/src/core/__tests__/client.query/customScalars.test.ts
index b4071d63b4b..07141c18341 100644
--- a/src/core/__tests__/client.query/customScalars.test.ts
+++ b/src/core/__tests__/client.query/customScalars.test.ts
@@ -1,7 +1,12 @@
import { delay, of } from "rxjs";
import type { OperationVariables } from "@apollo/client";
-import { ApolloClient, ApolloLink, gql } from "@apollo/client";
+import {
+ ApolloClient,
+ ApolloLink,
+ CombinedGraphQLErrors,
+ gql,
+} from "@apollo/client";
import { InMemoryCache } from "@apollo/client/cache";
import { dateScalar } from "@apollo/client/testing/internal";
@@ -443,3 +448,248 @@ test.failing(
});
}
);
+
+test("preserves referential identity when fetching identical serialized scalar values", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ }).pipe(delay(20))
+ ),
+ });
+
+ const { data: first } = await client.query({
+ query,
+ fetchPolicy: "network-only",
+ });
+
+ expect(first).toStrictEqualTyped({
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ });
+
+ const { data: second } = await client.query({
+ query,
+ fetchPolicy: "network-only",
+ });
+
+ expect(second).toBe(first);
+});
+
+test("serializes scalar fields in the error with a `none` error policy", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ await expect(client.query({ query, errorPolicy: "none" })).rejects.toEqual(
+ new CombinedGraphQLErrors({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ })
+ );
+});
+
+test("parses scalar fields in the result and serializes them in the error with an `all` error policy", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ await expect(
+ client.query({ query, errorPolicy: "all" })
+ ).resolves.toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ error: new CombinedGraphQLErrors({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ // TODO: Determine if this is correct
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }),
+ });
+});
+
+test("parses custom scalar fields with an `ignore` error policy", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ await expect(
+ client.query({ query, errorPolicy: "ignore" })
+ ).resolves.toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ });
+});
diff --git a/src/core/__tests__/client.readQuery/customScalars.test.ts b/src/core/__tests__/client.readQuery/customScalars.test.ts
index 16890c39044..b0898f18d9c 100644
--- a/src/core/__tests__/client.readQuery/customScalars.test.ts
+++ b/src/core/__tests__/client.readQuery/customScalars.test.ts
@@ -162,3 +162,61 @@ test("returns parsed custom scalar fields", () => {
},
});
});
+
+test("preserves referential identity when writing identical serialized scalar values", () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: ApolloLink.empty(),
+ });
+
+ client.writeQuery({
+ query,
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ });
+
+ const previousResult = client.readQuery({ query });
+
+ expect(previousResult).toStrictEqualTyped({
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ });
+
+ client.writeQuery({
+ query,
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ });
+
+ expect(client.readQuery({ query })).toBe(previousResult);
+});
diff --git a/src/core/__tests__/client.subscribe/customScalars.test.ts b/src/core/__tests__/client.subscribe/customScalars.test.ts
index 0e77a52a7a6..be12a659adf 100644
--- a/src/core/__tests__/client.subscribe/customScalars.test.ts
+++ b/src/core/__tests__/client.subscribe/customScalars.test.ts
@@ -1,7 +1,12 @@
import { delay, of } from "rxjs";
-import type { OperationVariables } from "@apollo/client";
-import { ApolloClient, ApolloLink, gql } from "@apollo/client";
+import type { OperationVariables, TypedDocumentNode } from "@apollo/client";
+import {
+ ApolloClient,
+ ApolloLink,
+ CombinedGraphQLErrors,
+ gql,
+} from "@apollo/client";
import { InMemoryCache } from "@apollo/client/cache";
import { dateScalar, ObservableStream } from "@apollo/client/testing/internal";
@@ -401,3 +406,278 @@ test.failing(
await expect(stream).toComplete();
}
);
+
+test("preserves referential identity when a subscription emits identical serialized scalar values", async () => {
+ const subscription: TypedDocumentNode<{
+ eventCreated: { __typename: "Event"; id: string; startDate: Date };
+ }> = gql`
+ subscription EventCreated {
+ eventCreated {
+ id
+ startDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of(
+ {
+ data: {
+ eventCreated: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ },
+ {
+ data: {
+ eventCreated: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ }
+ ).pipe(delay(20))
+ ),
+ });
+
+ using stream = new ObservableStream(
+ client.subscribe({ query: subscription })
+ );
+
+ const first = await stream.takeNext();
+
+ expect(first).toStrictEqualTyped({
+ data: {
+ eventCreated: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ });
+
+ const second = await stream.takeNext();
+
+ expect(second.data!.eventCreated).toBe(first.data!.eventCreated);
+
+ await expect(stream).toComplete();
+});
+
+test("serializes scalar fields in the error with a `none` error policy", async () => {
+ const subscription = gql`
+ subscription EventCreated {
+ eventCreated {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ eventCreated: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["eventCreated", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ using stream = new ObservableStream(
+ client.subscribe({ query: subscription, errorPolicy: "none" })
+ );
+
+ await expect(stream).toEmitTypedValue({
+ data: undefined,
+ error: new CombinedGraphQLErrors({
+ data: {
+ eventCreated: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["eventCreated", "endDate"],
+ },
+ ],
+ }),
+ });
+
+ await expect(stream).toComplete();
+});
+
+test("parses scalar fields in the result and serializes them in the error with an `all` error policy", async () => {
+ const subscription = gql`
+ subscription EventCreated {
+ eventCreated {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ eventCreated: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["eventCreated", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ using stream = new ObservableStream(
+ client.subscribe({ query: subscription, errorPolicy: "all" })
+ );
+
+ await expect(stream).toEmitTypedValue({
+ data: {
+ eventCreated: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ error: new CombinedGraphQLErrors({
+ data: {
+ eventCreated: {
+ __typename: "Event",
+ id: "1",
+ // TODO: Determine if this is correct
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["eventCreated", "endDate"],
+ },
+ ],
+ }),
+ });
+
+ await expect(stream).toComplete();
+});
+
+test("parses custom scalar fields with an `ignore` error policy", async () => {
+ const subscription = gql`
+ subscription EventCreated {
+ eventCreated {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ eventCreated: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["eventCreated", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ using stream = new ObservableStream(
+ client.subscribe({ query: subscription, errorPolicy: "ignore" })
+ );
+
+ await expect(stream).toEmitTypedValue({
+ data: {
+ eventCreated: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ });
+
+ await expect(stream).toComplete();
+});
diff --git a/src/core/__tests__/client.watchQuery/customScalars.test.ts b/src/core/__tests__/client.watchQuery/customScalars.test.ts
index c5a3c3cfafa..73eb0831416 100644
--- a/src/core/__tests__/client.watchQuery/customScalars.test.ts
+++ b/src/core/__tests__/client.watchQuery/customScalars.test.ts
@@ -1,9 +1,25 @@
import { delay, of } from "rxjs";
import type { OperationVariables } from "@apollo/client";
-import { ApolloClient, ApolloLink, gql, NetworkStatus } from "@apollo/client";
+import {
+ ApolloClient,
+ ApolloLink,
+ CombinedGraphQLErrors,
+ gql,
+ NetworkStatus,
+} from "@apollo/client";
import { InMemoryCache } from "@apollo/client/cache";
-import { dateScalar, ObservableStream } from "@apollo/client/testing/internal";
+import {
+ Defer20220824Handler,
+ GraphQL17Alpha9Handler,
+} from "@apollo/client/incremental";
+import {
+ dateScalar,
+ markAsStreaming,
+ mockDefer20220824,
+ mockDeferStreamGraphQL17Alpha9,
+ ObservableStream,
+} from "@apollo/client/testing/internal";
test("serializes scalar variables used in field arguments", async () => {
let requestVariables!: OperationVariables;
@@ -920,3 +936,1043 @@ test.failing(
});
}
);
+
+test("preserves referential identity when refetching identical serialized scalar values", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ }).pipe(delay(20))
+ ),
+ });
+
+ const observable = client.watchQuery({
+ query,
+ notifyOnNetworkStatusChange: false,
+ });
+ using stream = new ObservableStream(observable);
+
+ await expect(stream).toEmitTypedValue({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ dataState: "complete",
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ partial: false,
+ });
+
+ const { data: previousData } = observable.getCurrentResult();
+
+ await expect(observable.refetch()).resolves.toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ });
+
+ await expect(stream).toEmitTypedValue({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ dataState: "complete",
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ partial: false,
+ });
+
+ expect(observable.getCurrentResult().data).toBe(previousData);
+
+ await expect(stream).not.toEmitAnything();
+});
+
+test("serializes scalar fields in the error with a `none` error policy", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ using stream = new ObservableStream(
+ client.watchQuery({ query, errorPolicy: "none" })
+ );
+
+ await expect(stream).toEmitTypedValue({
+ data: undefined,
+ dataState: "empty",
+ loading: true,
+ networkStatus: NetworkStatus.loading,
+ partial: true,
+ });
+
+ await expect(stream).toEmitTypedValue({
+ data: undefined,
+ dataState: "empty",
+ error: new CombinedGraphQLErrors({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }),
+ loading: false,
+ networkStatus: NetworkStatus.error,
+ partial: true,
+ });
+
+ await expect(stream).not.toEmitAnything();
+});
+
+test("parses scalar fields in the result and serializes them in the error with an `all` error policy", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ using stream = new ObservableStream(
+ client.watchQuery({ query, errorPolicy: "all" })
+ );
+
+ await expect(stream).toEmitTypedValue({
+ data: undefined,
+ dataState: "empty",
+ loading: true,
+ networkStatus: NetworkStatus.loading,
+ partial: true,
+ });
+
+ await expect(stream).toEmitTypedValue({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ error: new CombinedGraphQLErrors({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ // TODO: Determine if this is correct
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }),
+ dataState: "complete",
+ loading: false,
+ networkStatus: NetworkStatus.error,
+ partial: false,
+ });
+
+ await expect(stream).not.toEmitAnything();
+});
+
+test("parses custom scalar fields with an `ignore` error policy", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ using stream = new ObservableStream(
+ client.watchQuery({ query, errorPolicy: "ignore" })
+ );
+
+ await expect(stream).toEmitTypedValue({
+ data: undefined,
+ dataState: "empty",
+ loading: true,
+ networkStatus: NetworkStatus.loading,
+ partial: true,
+ });
+
+ await expect(stream).toEmitTypedValue({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ dataState: "complete",
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ partial: false,
+ });
+
+ await expect(stream).not.toEmitAnything();
+});
+
+test("parses custom scalar fields across `@defer` payloads (defer20220824)", async () => {
+ const link = mockDefer20220824();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new Defer20220824Handler(),
+ });
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ ... @defer {
+ endDate
+ }
+ }
+ }
+ `;
+
+ using stream = new ObservableStream(client.watchQuery({ query }));
+
+ await expect(stream).toEmitTypedValue({
+ data: undefined,
+ dataState: "empty",
+ loading: true,
+ networkStatus: NetworkStatus.loading,
+ partial: true,
+ });
+
+ link.enqueueInitialChunk({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ hasNext: true,
+ });
+
+ await expect(stream).toEmitTypedValue({
+ data: markAsStreaming({
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ }),
+ dataState: "streaming",
+ loading: true,
+ networkStatus: NetworkStatus.streaming,
+ partial: true,
+ });
+
+ link.enqueueSubsequentChunk({
+ incremental: [{ data: { endDate: "2026-02-02" }, path: ["event"] }],
+ hasNext: false,
+ });
+
+ await expect(stream).toEmitTypedValue({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: new Date(2026, 1, 2),
+ },
+ },
+ dataState: "complete",
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ partial: false,
+ });
+
+ await expect(stream).not.toEmitAnything();
+});
+
+test("parses custom scalar fields across `@defer` payloads (graphql17Alpha9)", async () => {
+ const link = mockDeferStreamGraphQL17Alpha9();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new GraphQL17Alpha9Handler(),
+ });
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ ... @defer {
+ endDate
+ }
+ }
+ }
+ `;
+
+ using stream = new ObservableStream(client.watchQuery({ query }));
+
+ await expect(stream).toEmitTypedValue({
+ data: undefined,
+ dataState: "empty",
+ loading: true,
+ networkStatus: NetworkStatus.loading,
+ partial: true,
+ });
+
+ link.enqueueInitialChunk({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ pending: [{ id: "0", path: ["event"] }],
+ hasNext: true,
+ });
+
+ await expect(stream).toEmitTypedValue({
+ data: markAsStreaming({
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ }),
+ dataState: "streaming",
+ loading: true,
+ networkStatus: NetworkStatus.streaming,
+ partial: true,
+ });
+
+ link.enqueueSubsequentChunk({
+ incremental: [{ data: { endDate: "2026-02-02" }, id: "0" }],
+ completed: [{ id: "0" }],
+ hasNext: false,
+ });
+
+ await expect(stream).toEmitTypedValue({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: new Date(2026, 1, 2),
+ },
+ },
+ dataState: "complete",
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ partial: false,
+ });
+
+ await expect(stream).not.toEmitAnything();
+});
+
+test("parses custom scalar fields across `@defer` payloads when refetching (defer20220824)", async () => {
+ const link = mockDefer20220824();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new Defer20220824Handler(),
+ });
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ ... @defer {
+ endDate
+ }
+ }
+ }
+ `;
+
+ const observable = client.watchQuery({ query });
+ using stream = new ObservableStream(observable);
+
+ await expect(stream).toEmitTypedValue({
+ data: undefined,
+ dataState: "empty",
+ loading: true,
+ networkStatus: NetworkStatus.loading,
+ partial: true,
+ });
+
+ link.enqueueInitialChunk({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ hasNext: true,
+ });
+
+ await expect(stream).toEmitTypedValue({
+ data: markAsStreaming({
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ }),
+ dataState: "streaming",
+ loading: true,
+ networkStatus: NetworkStatus.streaming,
+ partial: true,
+ });
+
+ link.enqueueSubsequentChunk({
+ incremental: [{ data: { endDate: "2026-02-02" }, path: ["event"] }],
+ hasNext: false,
+ });
+
+ await expect(stream).toEmitTypedValue({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: new Date(2026, 1, 2),
+ },
+ },
+ dataState: "complete",
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ partial: false,
+ });
+
+ const refetchPromise = observable.refetch();
+
+ await expect(stream).toEmitTypedValue({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: new Date(2026, 1, 2),
+ },
+ },
+ dataState: "complete",
+ loading: true,
+ networkStatus: NetworkStatus.refetch,
+ partial: false,
+ });
+
+ link.enqueueInitialChunk({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2027-01-01",
+ },
+ },
+ hasNext: true,
+ });
+
+ await expect(stream).toEmitTypedValue({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2027, 0, 1),
+ endDate: new Date(2026, 1, 2),
+ },
+ },
+ dataState: "complete",
+ loading: true,
+ networkStatus: NetworkStatus.streaming,
+ partial: false,
+ });
+
+ link.enqueueSubsequentChunk({
+ incremental: [{ data: { endDate: "2027-02-02" }, path: ["event"] }],
+ hasNext: false,
+ });
+
+ await expect(stream).toEmitTypedValue({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2027, 0, 1),
+ endDate: new Date(2027, 1, 2),
+ },
+ },
+ dataState: "complete",
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ partial: false,
+ });
+
+ await expect(refetchPromise).resolves.toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2027, 0, 1),
+ endDate: new Date(2027, 1, 2),
+ },
+ },
+ });
+
+ await expect(stream).not.toEmitAnything();
+});
+
+test("parses custom scalar fields across `@defer` payloads when refetching (graphql17Alpha9)", async () => {
+ const link = mockDeferStreamGraphQL17Alpha9();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new GraphQL17Alpha9Handler(),
+ });
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ ... @defer {
+ endDate
+ }
+ }
+ }
+ `;
+
+ const observable = client.watchQuery({ query });
+ using stream = new ObservableStream(observable);
+
+ await expect(stream).toEmitTypedValue({
+ data: undefined,
+ dataState: "empty",
+ loading: true,
+ networkStatus: NetworkStatus.loading,
+ partial: true,
+ });
+
+ link.enqueueInitialChunk({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ pending: [{ id: "0", path: ["event"] }],
+ hasNext: true,
+ });
+
+ await expect(stream).toEmitTypedValue({
+ data: markAsStreaming({
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ }),
+ dataState: "streaming",
+ loading: true,
+ networkStatus: NetworkStatus.streaming,
+ partial: true,
+ });
+
+ link.enqueueSubsequentChunk({
+ incremental: [{ data: { endDate: "2026-02-02" }, id: "0" }],
+ completed: [{ id: "0" }],
+ hasNext: false,
+ });
+
+ await expect(stream).toEmitTypedValue({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: new Date(2026, 1, 2),
+ },
+ },
+ dataState: "complete",
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ partial: false,
+ });
+
+ const refetchPromise = observable.refetch();
+
+ await expect(stream).toEmitTypedValue({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: new Date(2026, 1, 2),
+ },
+ },
+ dataState: "complete",
+ loading: true,
+ networkStatus: NetworkStatus.refetch,
+ partial: false,
+ });
+
+ link.enqueueInitialChunk({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2027-01-01",
+ },
+ },
+ pending: [{ id: "0", path: ["event"] }],
+ hasNext: true,
+ });
+
+ await expect(stream).toEmitTypedValue({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2027, 0, 1),
+ endDate: new Date(2026, 1, 2),
+ },
+ },
+ dataState: "complete",
+ loading: true,
+ networkStatus: NetworkStatus.streaming,
+ partial: false,
+ });
+
+ link.enqueueSubsequentChunk({
+ incremental: [{ data: { endDate: "2027-02-02" }, id: "0" }],
+ completed: [{ id: "0" }],
+ hasNext: false,
+ });
+
+ await expect(stream).toEmitTypedValue({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2027, 0, 1),
+ endDate: new Date(2027, 1, 2),
+ },
+ },
+ dataState: "complete",
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ partial: false,
+ });
+
+ await expect(refetchPromise).resolves.toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2027, 0, 1),
+ endDate: new Date(2027, 1, 2),
+ },
+ },
+ });
+
+ await expect(stream).not.toEmitAnything();
+});
+
+test("parses custom scalar fields across `@stream` payloads (defer20220824)", async () => {
+ const link = mockDefer20220824();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new Defer20220824Handler(),
+ });
+ const query = gql`
+ query Events {
+ events @stream(initialCount: 1) {
+ id
+ startDate
+ }
+ }
+ `;
+
+ using stream = new ObservableStream(client.watchQuery({ query }));
+
+ await expect(stream).toEmitTypedValue({
+ data: undefined,
+ dataState: "empty",
+ loading: true,
+ networkStatus: NetworkStatus.loading,
+ partial: true,
+ });
+
+ link.enqueueInitialChunk({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ ],
+ },
+ hasNext: true,
+ });
+
+ await expect(stream).toEmitTypedValue({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ ],
+ },
+ dataState: "complete",
+ loading: true,
+ networkStatus: NetworkStatus.streaming,
+ partial: false,
+ });
+
+ link.enqueueSubsequentChunk({
+ incremental: [
+ {
+ items: [
+ {
+ __typename: "Event",
+ id: "2",
+ startDate: "2026-02-02",
+ },
+ ] as any,
+ path: ["events", 1],
+ },
+ ],
+ hasNext: false,
+ });
+
+ await expect(stream).toEmitTypedValue({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ {
+ __typename: "Event",
+ id: "2",
+ startDate: new Date(2026, 1, 2),
+ },
+ ],
+ },
+ dataState: "complete",
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ partial: false,
+ });
+
+ await expect(stream).not.toEmitAnything();
+});
+
+test("parses custom scalar fields across `@stream` payloads (graphql17Alpha9)", async () => {
+ const link = mockDeferStreamGraphQL17Alpha9();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new GraphQL17Alpha9Handler(),
+ });
+ const query = gql`
+ query Events {
+ events @stream(initialCount: 1) {
+ id
+ startDate
+ }
+ }
+ `;
+
+ using stream = new ObservableStream(client.watchQuery({ query }));
+
+ await expect(stream).toEmitTypedValue({
+ data: undefined,
+ dataState: "empty",
+ loading: true,
+ networkStatus: NetworkStatus.loading,
+ partial: true,
+ });
+
+ link.enqueueInitialChunk({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ ],
+ },
+ pending: [{ id: "0", path: ["events"] }],
+ hasNext: true,
+ });
+
+ await expect(stream).toEmitTypedValue({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ ],
+ },
+ dataState: "complete",
+ loading: true,
+ networkStatus: NetworkStatus.streaming,
+ partial: false,
+ });
+
+ link.enqueueSubsequentChunk({
+ incremental: [
+ {
+ items: [
+ {
+ __typename: "Event",
+ id: "2",
+ startDate: "2026-02-02",
+ },
+ ] as any,
+ id: "0",
+ },
+ ],
+ completed: [{ id: "0" }],
+ hasNext: false,
+ });
+
+ await expect(stream).toEmitTypedValue({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ {
+ __typename: "Event",
+ id: "2",
+ startDate: new Date(2026, 1, 2),
+ },
+ ],
+ },
+ dataState: "complete",
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ partial: false,
+ });
+
+ await expect(stream).not.toEmitAnything();
+});
diff --git a/src/react/hooks/__tests__/useBackgroundQuery/customScalars.test.tsx b/src/react/hooks/__tests__/useBackgroundQuery/customScalars.test.tsx
index d5f024bfdae..9d6bcf7d21e 100644
--- a/src/react/hooks/__tests__/useBackgroundQuery/customScalars.test.tsx
+++ b/src/react/hooks/__tests__/useBackgroundQuery/customScalars.test.tsx
@@ -1,13 +1,29 @@
+import assert from "node:assert";
+
import { disableActEnvironment } from "@testing-library/react-render-stream";
-import { of } from "rxjs";
+import { delay, of } from "rxjs";
import type { OperationVariables } from "@apollo/client";
-import { ApolloClient, ApolloLink, gql, NetworkStatus } from "@apollo/client";
+import {
+ ApolloClient,
+ ApolloLink,
+ CombinedGraphQLErrors,
+ gql,
+ NetworkStatus,
+} from "@apollo/client";
import { InMemoryCache } from "@apollo/client/cache";
+import {
+ Defer20220824Handler,
+ GraphQL17Alpha9Handler,
+} from "@apollo/client/incremental";
import { useBackgroundQuery } from "@apollo/client/react";
import {
createClientWrapper,
dateScalar,
+ markAsStreaming,
+ mockDefer20220824,
+ mockDeferStreamGraphQL17Alpha9,
+ spyOnConsole,
} from "@apollo/client/testing/internal";
import { renderUseBackgroundQuery } from "./testUtils.js";
@@ -43,9 +59,14 @@ test("serializes scalar variables used in field arguments", async () => {
{ wrapper: createClientWrapper(client) }
);
- await expect(takeRender()).resolves.toMatchObject({
- renderedComponents: ["useBackgroundQuery", ""],
- });
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useBackgroundQuery",
+ "",
+ ]);
+ }
{
const { snapshot, renderedComponents } = await takeRender();
@@ -99,9 +120,14 @@ test("serializes scalar variables used in directive arguments", async () => {
{ wrapper: createClientWrapper(client) }
);
- await expect(takeRender()).resolves.toMatchObject({
- renderedComponents: ["useBackgroundQuery", ""],
- });
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useBackgroundQuery",
+ "",
+ ]);
+ }
{
const { snapshot, renderedComponents } = await takeRender();
@@ -164,9 +190,14 @@ test("serializes scalar fields in input object variables", async () => {
{ wrapper: createClientWrapper(client) }
);
- await expect(takeRender()).resolves.toMatchObject({
- renderedComponents: ["useBackgroundQuery", ""],
- });
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useBackgroundQuery",
+ "",
+ ]);
+ }
{
const { snapshot, renderedComponents } = await takeRender();
@@ -190,3 +221,816 @@ test("serializes scalar fields in input object variables", async () => {
filter: { date: "2026-01-01" },
});
});
+
+test("preserves referential identity when refetching identical serialized scalar values", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ }).pipe(delay(20))
+ ),
+ });
+
+ using _disabledAct = disableActEnvironment();
+ const { takeRender, refetch } = await renderUseBackgroundQuery(
+ () => useBackgroundQuery(query),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useBackgroundQuery",
+ "",
+ ]);
+ }
+
+ let previousData: unknown;
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+
+ assert("data" in snapshot);
+
+ previousData = snapshot.data;
+ }
+
+ await expect(refetch()).resolves.toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ });
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useBackgroundQuery",
+ "",
+ ]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+
+ assert("data" in snapshot);
+
+ expect(snapshot.data).toBe(previousData);
+ }
+
+ await expect(takeRender).not.toRerender();
+});
+
+test("serializes scalar fields in the error with a `none` error policy", async () => {
+ using _consoleSpy = spyOnConsole("error");
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ using _disabledAct = disableActEnvironment();
+ const { takeRender } = await renderUseBackgroundQuery(
+ () => useBackgroundQuery(query, { errorPolicy: "none" }),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useBackgroundQuery",
+ "",
+ ]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ expect(snapshot).toStrictEqualTyped({
+ error: new CombinedGraphQLErrors({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }),
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+});
+
+test("parses scalar fields in the result and serializes them in the error with an `all` error policy", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ using _disabledAct = disableActEnvironment();
+ const { takeRender } = await renderUseBackgroundQuery(
+ () => useBackgroundQuery(query, { errorPolicy: "all" }),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useBackgroundQuery",
+ "",
+ ]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.error,
+ error: new CombinedGraphQLErrors({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ // TODO: Determine if this is correct
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }),
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+});
+
+test("parses custom scalar fields with an `ignore` error policy", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ using _disabledAct = disableActEnvironment();
+ const { takeRender } = await renderUseBackgroundQuery(
+ () => useBackgroundQuery(query, { errorPolicy: "ignore" }),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useBackgroundQuery",
+ "",
+ ]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+});
+
+test("parses custom scalar fields across `@defer` payloads (defer20220824)", async () => {
+ const link = mockDefer20220824();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new Defer20220824Handler(),
+ });
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ ... @defer {
+ endDate
+ }
+ }
+ }
+ `;
+
+ using _disabledAct = disableActEnvironment();
+ const { takeRender } = await renderUseBackgroundQuery(
+ () => useBackgroundQuery(query),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useBackgroundQuery",
+ "",
+ ]);
+ }
+
+ link.enqueueInitialChunk({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ hasNext: true,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: markAsStreaming({
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ }),
+ dataState: "streaming",
+ networkStatus: NetworkStatus.streaming,
+ error: undefined,
+ });
+ }
+
+ link.enqueueSubsequentChunk({
+ incremental: [{ data: { endDate: "2026-02-02" }, path: ["event"] }],
+ hasNext: false,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: new Date(2026, 1, 2),
+ },
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+});
+
+test("parses custom scalar fields across `@defer` payloads (graphql17Alpha9)", async () => {
+ const link = mockDeferStreamGraphQL17Alpha9();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new GraphQL17Alpha9Handler(),
+ });
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ ... @defer {
+ endDate
+ }
+ }
+ }
+ `;
+
+ using _disabledAct = disableActEnvironment();
+ const { takeRender } = await renderUseBackgroundQuery(
+ () => useBackgroundQuery(query),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useBackgroundQuery",
+ "",
+ ]);
+ }
+
+ link.enqueueInitialChunk({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ pending: [{ id: "0", path: ["event"] }],
+ hasNext: true,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: markAsStreaming({
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ }),
+ dataState: "streaming",
+ networkStatus: NetworkStatus.streaming,
+ error: undefined,
+ });
+ }
+
+ link.enqueueSubsequentChunk({
+ incremental: [{ data: { endDate: "2026-02-02" }, id: "0" }],
+ completed: [{ id: "0" }],
+ hasNext: false,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: new Date(2026, 1, 2),
+ },
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+});
+
+test("parses custom scalar fields across `@stream` payloads (defer20220824)", async () => {
+ const link = mockDefer20220824();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new Defer20220824Handler(),
+ });
+ const query = gql`
+ query Events {
+ events @stream(initialCount: 1) {
+ id
+ startDate
+ }
+ }
+ `;
+
+ using _disabledAct = disableActEnvironment();
+ const { takeRender } = await renderUseBackgroundQuery(
+ () => useBackgroundQuery(query),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useBackgroundQuery",
+ "",
+ ]);
+ }
+
+ link.enqueueInitialChunk({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ ],
+ },
+ hasNext: true,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ ],
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.streaming,
+ error: undefined,
+ });
+ }
+
+ link.enqueueSubsequentChunk({
+ incremental: [
+ {
+ items: [
+ {
+ __typename: "Event",
+ id: "2",
+ startDate: "2026-02-02",
+ },
+ ] as any,
+ path: ["events", 1],
+ },
+ ],
+ hasNext: false,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ {
+ __typename: "Event",
+ id: "2",
+ startDate: new Date(2026, 1, 2),
+ },
+ ],
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+});
+
+test("parses custom scalar fields across `@stream` payloads (graphql17Alpha9)", async () => {
+ const link = mockDeferStreamGraphQL17Alpha9();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new GraphQL17Alpha9Handler(),
+ });
+ const query = gql`
+ query Events {
+ events @stream(initialCount: 1) {
+ id
+ startDate
+ }
+ }
+ `;
+
+ using _disabledAct = disableActEnvironment();
+ const { takeRender } = await renderUseBackgroundQuery(
+ () => useBackgroundQuery(query),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useBackgroundQuery",
+ "",
+ ]);
+ }
+
+ link.enqueueInitialChunk({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ ],
+ },
+ pending: [{ id: "0", path: ["events"] }],
+ hasNext: true,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ ],
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.streaming,
+ error: undefined,
+ });
+ }
+
+ link.enqueueSubsequentChunk({
+ incremental: [
+ {
+ items: [
+ {
+ __typename: "Event",
+ id: "2",
+ startDate: "2026-02-02",
+ },
+ ] as any,
+ id: "0",
+ },
+ ],
+ completed: [{ id: "0" }],
+ hasNext: false,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ {
+ __typename: "Event",
+ id: "2",
+ startDate: new Date(2026, 1, 2),
+ },
+ ],
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+});
diff --git a/src/react/hooks/__tests__/useBackgroundQuery/testUtils.tsx b/src/react/hooks/__tests__/useBackgroundQuery/testUtils.tsx
index e1d34750a5b..cdeec1ac208 100644
--- a/src/react/hooks/__tests__/useBackgroundQuery/testUtils.tsx
+++ b/src/react/hooks/__tests__/useBackgroundQuery/testUtils.tsx
@@ -4,6 +4,7 @@ import {
useTrackRenders,
} from "@testing-library/react-render-stream";
import React, { Suspense } from "react";
+import { flushSync } from "react-dom";
import { ErrorBoundary } from "react-error-boundary";
import type { DataState, ErrorLike, OperationVariables } from "@apollo/client";
@@ -45,9 +46,13 @@ export async function renderUseBackgroundQuery<
return null;
}
+ let currentRefetch!: useBackgroundQuery.Result["refetch"];
+
function App({ props }: { props: Props | undefined }) {
useTrackRenders({ name: "useBackgroundQuery" });
- const [queryRef] = renderHook(props as any);
+ const [queryRef, { refetch }] = renderHook(props as any);
+
+ currentRefetch = refetch;
return (
}>
@@ -71,5 +76,13 @@ export async function renderUseBackgroundQuery<
return utils.rerender();
}
- return { takeRender, rerender };
+ // React 18 skips committing the suspense fallback when refetch is triggered
+ // as a default-priority update, so the fallback render is missed and tests
+ // may fail. flushSync forces a synchronous commit so the fallback renders in
+ // both React 18 and 19, while returning the refetch promise.
+ const refetch: typeof currentRefetch = (...args) => {
+ return flushSync(() => currentRefetch(...args));
+ };
+
+ return { takeRender, rerender, refetch };
}
diff --git a/src/react/hooks/__tests__/useLazyQuery/customScalars.test.tsx b/src/react/hooks/__tests__/useLazyQuery/customScalars.test.tsx
index 10118829de4..2a30cfdf851 100644
--- a/src/react/hooks/__tests__/useLazyQuery/customScalars.test.tsx
+++ b/src/react/hooks/__tests__/useLazyQuery/customScalars.test.tsx
@@ -5,7 +5,13 @@ import {
import { delay, of } from "rxjs";
import type { OperationVariables } from "@apollo/client";
-import { ApolloClient, ApolloLink, gql, NetworkStatus } from "@apollo/client";
+import {
+ ApolloClient,
+ ApolloLink,
+ CombinedGraphQLErrors,
+ gql,
+ NetworkStatus,
+} from "@apollo/client";
import { InMemoryCache } from "@apollo/client/cache";
import { useLazyQuery } from "@apollo/client/react";
import {
@@ -1056,3 +1062,550 @@ test.failing(
}
}
);
+
+test("preserves referential identity when re-executing with identical serialized scalar values", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ }).pipe(delay(20))
+ ),
+ });
+
+ using _disabledAct = disableActEnvironment();
+ const { takeSnapshot, getCurrentSnapshot } = await renderHookToSnapshotStream(
+ () => useLazyQuery(query, { fetchPolicy: "network-only" }),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: undefined,
+ dataState: "empty",
+ called: false,
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ previousData: undefined,
+ variables: {},
+ });
+ }
+
+ const [execute] = getCurrentSnapshot();
+
+ await expect(execute()).resolves.toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ });
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: undefined,
+ dataState: "empty",
+ called: true,
+ loading: true,
+ networkStatus: NetworkStatus.loading,
+ previousData: undefined,
+ variables: {},
+ });
+ }
+
+ let previousData: unknown;
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ dataState: "complete",
+ called: true,
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ previousData: undefined,
+ variables: {},
+ });
+
+ previousData = result.data;
+ }
+
+ await expect(execute()).resolves.toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ });
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ dataState: "complete",
+ called: true,
+ loading: true,
+ networkStatus: NetworkStatus.loading,
+ previousData: undefined,
+ variables: {},
+ });
+ }
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ dataState: "complete",
+ called: true,
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ previousData: undefined,
+ variables: {},
+ });
+
+ expect(result.data).toBe(previousData);
+ }
+
+ await expect(takeSnapshot).not.toRerender();
+});
+
+test("serializes scalar fields in the error with a `none` error policy", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ using _disabledAct = disableActEnvironment();
+ const { takeSnapshot, getCurrentSnapshot } = await renderHookToSnapshotStream(
+ () => useLazyQuery(query, { errorPolicy: "none" }),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: undefined,
+ dataState: "empty",
+ called: false,
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ previousData: undefined,
+ variables: {},
+ });
+ }
+
+ const [execute] = getCurrentSnapshot();
+
+ await expect(execute()).rejects.toEqual(
+ new CombinedGraphQLErrors({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ })
+ );
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: undefined,
+ dataState: "empty",
+ called: true,
+ loading: true,
+ networkStatus: NetworkStatus.loading,
+ previousData: undefined,
+ variables: {},
+ });
+ }
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: undefined,
+ dataState: "empty",
+ error: new CombinedGraphQLErrors({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }),
+ called: true,
+ loading: false,
+ networkStatus: NetworkStatus.error,
+ previousData: undefined,
+ variables: {},
+ });
+ }
+
+ await expect(takeSnapshot).not.toRerender();
+});
+
+test("parses scalar fields in the result and serializes them in the error with an `all` error policy", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ using _disabledAct = disableActEnvironment();
+ const { takeSnapshot, getCurrentSnapshot } = await renderHookToSnapshotStream(
+ () => useLazyQuery(query, { errorPolicy: "all" }),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: undefined,
+ dataState: "empty",
+ called: false,
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ previousData: undefined,
+ variables: {},
+ });
+ }
+
+ const [execute] = getCurrentSnapshot();
+
+ await expect(execute()).resolves.toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ error: new CombinedGraphQLErrors({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ // TODO: Determine if this is correct
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }),
+ });
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: undefined,
+ dataState: "empty",
+ called: true,
+ loading: true,
+ networkStatus: NetworkStatus.loading,
+ previousData: undefined,
+ variables: {},
+ });
+ }
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ dataState: "complete",
+ error: new CombinedGraphQLErrors({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ // TODO: Determine if this is correct
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }),
+ called: true,
+ loading: false,
+ networkStatus: NetworkStatus.error,
+ previousData: undefined,
+ variables: {},
+ });
+ }
+
+ await expect(takeSnapshot).not.toRerender();
+});
+
+test("parses custom scalar fields with an `ignore` error policy", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ using _disabledAct = disableActEnvironment();
+ const { takeSnapshot, getCurrentSnapshot } = await renderHookToSnapshotStream(
+ () => useLazyQuery(query, { errorPolicy: "ignore" }),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: undefined,
+ dataState: "empty",
+ called: false,
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ previousData: undefined,
+ variables: {},
+ });
+ }
+
+ const [execute] = getCurrentSnapshot();
+
+ await expect(execute()).resolves.toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ });
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: undefined,
+ dataState: "empty",
+ called: true,
+ loading: true,
+ networkStatus: NetworkStatus.loading,
+ previousData: undefined,
+ variables: {},
+ });
+ }
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ dataState: "complete",
+ called: true,
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ previousData: undefined,
+ variables: {},
+ });
+ }
+
+ await expect(takeSnapshot).not.toRerender();
+});
diff --git a/src/react/hooks/__tests__/useLoadableQuery/customScalars.test.tsx b/src/react/hooks/__tests__/useLoadableQuery/customScalars.test.tsx
index 9271b6e631f..f06d1a9df92 100644
--- a/src/react/hooks/__tests__/useLoadableQuery/customScalars.test.tsx
+++ b/src/react/hooks/__tests__/useLoadableQuery/customScalars.test.tsx
@@ -1,22 +1,31 @@
-import { screen } from "@testing-library/react";
-import {
- createRenderStream,
- disableActEnvironment,
- useTrackRenders,
-} from "@testing-library/react-render-stream";
-import { userEvent } from "@testing-library/user-event";
-import React, { Suspense } from "react";
+import { disableActEnvironment } from "@testing-library/react-render-stream";
import { delay, of } from "rxjs";
import type { OperationVariables } from "@apollo/client";
-import { ApolloClient, ApolloLink, gql, NetworkStatus } from "@apollo/client";
+import {
+ ApolloClient,
+ ApolloLink,
+ CombinedGraphQLErrors,
+ gql,
+ NetworkStatus,
+} from "@apollo/client";
import { InMemoryCache } from "@apollo/client/cache";
-import type { QueryRef } from "@apollo/client/react";
-import { useLoadableQuery, useReadQuery } from "@apollo/client/react";
+import {
+ Defer20220824Handler,
+ GraphQL17Alpha9Handler,
+} from "@apollo/client/incremental";
+import { useLoadableQuery } from "@apollo/client/react";
import {
createClientWrapper,
dateScalar,
+ markAsStreaming,
+ mockDefer20220824,
+ mockDeferStreamGraphQL17Alpha9,
+ spyOnConsole,
} from "@apollo/client/testing/internal";
+import { invariant } from "@apollo/client/utilities/invariant";
+
+import { renderUseLoadableQueryHook } from "./testUtils.js";
test("serializes scalar variables used in field arguments", async () => {
let requestVariables!: OperationVariables;
@@ -34,13 +43,6 @@ test("serializes scalar variables used in field arguments", async () => {
}),
});
- const renderStream = createRenderStream({
- initialSnapshot: {
- result: null as useReadQuery.Result | null,
- },
- skipNonTrackingRenders: true,
- });
-
const query = gql`
query Event($date: Date!) {
event(date: $date) {
@@ -49,53 +51,33 @@ test("serializes scalar variables used in field arguments", async () => {
}
`;
- function ReadQuery({ queryRef }: { queryRef: QueryRef }) {
- useTrackRenders({ name: "useReadQuery" });
- renderStream.mergeSnapshot({ result: useReadQuery(queryRef) });
-
- return null;
- }
-
- function Fallback() {
- useTrackRenders({ name: "" });
- return null;
- }
+ using _disabledAct = disableActEnvironment();
+ const { takeRender, loadQuery } = await renderUseLoadableQueryHook(
+ () => useLoadableQuery(query),
+ { wrapper: createClientWrapper(client) }
+ );
- function App() {
- useTrackRenders({ name: "useLoadableQuery" });
- const [loadQuery, queryRef] = useLoadableQuery(query);
+ {
+ const { renderedComponents } = await takeRender();
- return (
- <>
-
- }>
- {queryRef && }
-
- >
- );
+ expect(renderedComponents).toStrictEqual(["useLoadableQuery"]);
}
- using _disabledAct = disableActEnvironment();
- const user = userEvent.setup();
- await renderStream.render(, {
- wrapper: createClientWrapper(client),
- });
-
- await expect(renderStream.takeRender()).resolves.toMatchObject({
- renderedComponents: ["useLoadableQuery"],
- });
+ loadQuery({ date: new Date(2026, 0, 1) });
- await user.click(screen.getByText("Load query"));
+ {
+ const { renderedComponents } = await takeRender();
- await expect(renderStream.takeRender()).resolves.toMatchObject({
- renderedComponents: ["useLoadableQuery", ""],
- });
+ expect(renderedComponents).toStrictEqual([
+ "useLoadableQuery",
+ "",
+ ]);
+ }
{
- const { snapshot, renderedComponents } = await renderStream.takeRender();
+ const { snapshot, renderedComponents } = await takeRender();
+ invariant("result" in snapshot);
expect(renderedComponents).toStrictEqual(["useReadQuery"]);
expect(snapshot.result).toStrictEqualTyped({
data: {
@@ -110,7 +92,7 @@ test("serializes scalar variables used in field arguments", async () => {
});
}
- await expect(renderStream).not.toRerender();
+ await expect(takeRender).not.toRerender();
expect(requestVariables).toStrictEqualTyped({ date: "2026-01-01" });
});
@@ -130,13 +112,6 @@ test("serializes scalar variables used in directive arguments", async () => {
}),
});
- const renderStream = createRenderStream({
- initialSnapshot: {
- result: null as useReadQuery.Result | null,
- },
- skipNonTrackingRenders: true,
- });
-
const query = gql`
query Event($date: Date!) {
event @on(date: $date) {
@@ -145,53 +120,33 @@ test("serializes scalar variables used in directive arguments", async () => {
}
`;
- function ReadQuery({ queryRef }: { queryRef: QueryRef }) {
- useTrackRenders({ name: "useReadQuery" });
- renderStream.mergeSnapshot({ result: useReadQuery(queryRef) });
-
- return null;
- }
-
- function Fallback() {
- useTrackRenders({ name: "" });
- return null;
- }
+ using _disabledAct = disableActEnvironment();
+ const { takeRender, loadQuery } = await renderUseLoadableQueryHook(
+ () => useLoadableQuery(query),
+ { wrapper: createClientWrapper(client) }
+ );
- function App() {
- useTrackRenders({ name: "useLoadableQuery" });
- const [loadQuery, queryRef] = useLoadableQuery(query);
+ {
+ const { renderedComponents } = await takeRender();
- return (
- <>
-
- }>
- {queryRef && }
-
- >
- );
+ expect(renderedComponents).toStrictEqual(["useLoadableQuery"]);
}
- using _disabledAct = disableActEnvironment();
- const user = userEvent.setup();
- await renderStream.render(, {
- wrapper: createClientWrapper(client),
- });
-
- await expect(renderStream.takeRender()).resolves.toMatchObject({
- renderedComponents: ["useLoadableQuery"],
- });
+ loadQuery({ date: new Date(2026, 0, 1) });
- await user.click(screen.getByText("Load query"));
+ {
+ const { renderedComponents } = await takeRender();
- await expect(renderStream.takeRender()).resolves.toMatchObject({
- renderedComponents: ["useLoadableQuery", ""],
- });
+ expect(renderedComponents).toStrictEqual([
+ "useLoadableQuery",
+ "",
+ ]);
+ }
{
- const { snapshot, renderedComponents } = await renderStream.takeRender();
+ const { snapshot, renderedComponents } = await takeRender();
+ invariant("result" in snapshot);
expect(renderedComponents).toStrictEqual(["useReadQuery"]);
expect(snapshot.result).toStrictEqualTyped({
data: {
@@ -206,7 +161,7 @@ test("serializes scalar variables used in directive arguments", async () => {
});
}
- await expect(renderStream).not.toRerender();
+ await expect(takeRender).not.toRerender();
expect(requestVariables).toStrictEqualTyped({ date: "2026-01-01" });
});
@@ -233,13 +188,6 @@ test("serializes scalar fields in input object variables", async () => {
}),
});
- const renderStream = createRenderStream({
- initialSnapshot: {
- result: null as useReadQuery.Result | null,
- },
- skipNonTrackingRenders: true,
- });
-
const query = gql`
query Event($filter: EventFilter!) {
event(filter: $filter) {
@@ -248,61 +196,448 @@ test("serializes scalar fields in input object variables", async () => {
}
`;
- function ReadQuery({ queryRef }: { queryRef: QueryRef }) {
- useTrackRenders({ name: "useReadQuery" });
- renderStream.mergeSnapshot({ result: useReadQuery(queryRef) });
+ using _disabledAct = disableActEnvironment();
+ const { takeRender, loadQuery } = await renderUseLoadableQueryHook(
+ () => useLoadableQuery(query),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
- return null;
+ expect(renderedComponents).toStrictEqual(["useLoadableQuery"]);
}
- function Fallback() {
- useTrackRenders({ name: "" });
- return null;
+ loadQuery({ filter: { date: new Date(2026, 0, 1) } });
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useLoadableQuery",
+ "",
+ ]);
}
- function App() {
- useTrackRenders({ name: "useLoadableQuery" });
- const [loadQuery, queryRef] = useLoadableQuery(query);
+ {
+ const { snapshot, renderedComponents } = await takeRender();
- return (
- <>
-
- }>
- {queryRef && }
-
- >
- );
+ invariant("result" in snapshot);
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot.result).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ name: "GraphQL Summit",
+ },
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
}
+ await expect(takeRender).not.toRerender();
+ expect(requestVariables).toStrictEqualTyped({
+ filter: { date: "2026-01-01" },
+ });
+});
+
+test("preserves referential identity when refetching identical serialized scalar values", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ }).pipe(delay(20))
+ ),
+ });
+
using _disabledAct = disableActEnvironment();
- const user = userEvent.setup();
- await renderStream.render(, {
- wrapper: createClientWrapper(client),
+ const { takeRender, refetch, loadQuery } = await renderUseLoadableQueryHook(
+ () => useLoadableQuery(query),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useLoadableQuery"]);
+ }
+
+ loadQuery();
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useLoadableQuery",
+ "",
+ ]);
+ }
+
+ let previousData: unknown;
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ invariant("result" in snapshot);
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot.result).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+
+ previousData = snapshot.result!.data;
+ }
+
+ await expect(refetch()).resolves.toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ });
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useLoadableQuery",
+ "",
+ ]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ invariant("result" in snapshot);
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot.result).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+
+ expect(snapshot.result!.data).toBe(previousData);
+ }
+
+ await expect(takeRender).not.toRerender();
+});
+
+test("serializes scalar fields in the error with a `none` error policy", async () => {
+ using _consoleSpy = spyOnConsole("error");
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
});
- await expect(renderStream.takeRender()).resolves.toMatchObject({
- renderedComponents: ["useLoadableQuery"],
+ using _disabledAct = disableActEnvironment();
+ const { takeRender, loadQuery } = await renderUseLoadableQueryHook(
+ () => useLoadableQuery(query, { errorPolicy: "none" }),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useLoadableQuery"]);
+ }
+
+ loadQuery();
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useLoadableQuery",
+ "",
+ ]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ expect(snapshot).toStrictEqualTyped({
+ error: new CombinedGraphQLErrors({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }),
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+});
+
+test("parses scalar fields in the result and serializes them in the error with an `all` error policy", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
});
- await user.click(screen.getByText("Load query"));
+ using _disabledAct = disableActEnvironment();
+ const { takeRender, loadQuery } = await renderUseLoadableQueryHook(
+ () => useLoadableQuery(query, { errorPolicy: "all" }),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useLoadableQuery"]);
+ }
+
+ loadQuery();
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useLoadableQuery",
+ "",
+ ]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ invariant("result" in snapshot);
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot.result).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.error,
+ error: new CombinedGraphQLErrors({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ // TODO: Determine if this is correct
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }),
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+});
- await expect(renderStream.takeRender()).resolves.toMatchObject({
- renderedComponents: ["useLoadableQuery", ""],
+test("parses custom scalar fields with an `ignore` error policy", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
});
+ using _disabledAct = disableActEnvironment();
+ const { takeRender, loadQuery } = await renderUseLoadableQueryHook(
+ () => useLoadableQuery(query, { errorPolicy: "ignore" }),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useLoadableQuery"]);
+ }
+
+ loadQuery();
+
{
- const { snapshot, renderedComponents } = await renderStream.takeRender();
+ const { renderedComponents } = await takeRender();
+ expect(renderedComponents).toStrictEqual([
+ "useLoadableQuery",
+ "",
+ ]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ invariant("result" in snapshot);
expect(renderedComponents).toStrictEqual(["useReadQuery"]);
expect(snapshot.result).toStrictEqualTyped({
data: {
event: {
__typename: "Event",
- name: "GraphQL Summit",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
},
},
dataState: "complete",
@@ -311,8 +646,483 @@ test("serializes scalar fields in input object variables", async () => {
});
}
- await expect(renderStream).not.toRerender();
- expect(requestVariables).toStrictEqualTyped({
- filter: { date: "2026-01-01" },
+ await expect(takeRender).not.toRerender();
+});
+
+test("parses custom scalar fields across `@defer` payloads (defer20220824)", async () => {
+ const link = mockDefer20220824();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new Defer20220824Handler(),
+ });
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ ... @defer {
+ endDate
+ }
+ }
+ }
+ `;
+
+ using _disabledAct = disableActEnvironment();
+ const { takeRender, loadQuery } = await renderUseLoadableQueryHook(
+ () => useLoadableQuery(query),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useLoadableQuery"]);
+ }
+
+ loadQuery();
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useLoadableQuery",
+ "",
+ ]);
+ }
+
+ link.enqueueInitialChunk({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ hasNext: true,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ invariant("result" in snapshot);
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot.result).toStrictEqualTyped({
+ data: markAsStreaming({
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ }),
+ dataState: "streaming",
+ networkStatus: NetworkStatus.streaming,
+ error: undefined,
+ });
+ }
+
+ link.enqueueSubsequentChunk({
+ incremental: [{ data: { endDate: "2026-02-02" }, path: ["event"] }],
+ hasNext: false,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ invariant("result" in snapshot);
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot.result).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: new Date(2026, 1, 2),
+ },
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+});
+
+test("parses custom scalar fields across `@defer` payloads (graphql17Alpha9)", async () => {
+ const link = mockDeferStreamGraphQL17Alpha9();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new GraphQL17Alpha9Handler(),
+ });
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ ... @defer {
+ endDate
+ }
+ }
+ }
+ `;
+
+ using _disabledAct = disableActEnvironment();
+ const { takeRender, loadQuery } = await renderUseLoadableQueryHook(
+ () => useLoadableQuery(query),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useLoadableQuery"]);
+ }
+
+ loadQuery();
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useLoadableQuery",
+ "",
+ ]);
+ }
+
+ link.enqueueInitialChunk({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ pending: [{ id: "0", path: ["event"] }],
+ hasNext: true,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ invariant("result" in snapshot);
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot.result).toStrictEqualTyped({
+ data: markAsStreaming({
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ }),
+ dataState: "streaming",
+ networkStatus: NetworkStatus.streaming,
+ error: undefined,
+ });
+ }
+
+ link.enqueueSubsequentChunk({
+ incremental: [{ data: { endDate: "2026-02-02" }, id: "0" }],
+ completed: [{ id: "0" }],
+ hasNext: false,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ invariant("result" in snapshot);
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot.result).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: new Date(2026, 1, 2),
+ },
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+});
+
+test("parses custom scalar fields across `@stream` payloads (defer20220824)", async () => {
+ const link = mockDefer20220824();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new Defer20220824Handler(),
+ });
+ const query = gql`
+ query Events {
+ events @stream(initialCount: 1) {
+ id
+ startDate
+ }
+ }
+ `;
+
+ using _disabledAct = disableActEnvironment();
+ const { takeRender, loadQuery } = await renderUseLoadableQueryHook(
+ () => useLoadableQuery(query),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useLoadableQuery"]);
+ }
+
+ loadQuery();
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useLoadableQuery",
+ "",
+ ]);
+ }
+
+ link.enqueueInitialChunk({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ ],
+ },
+ hasNext: true,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ invariant("result" in snapshot);
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot.result).toStrictEqualTyped({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ ],
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.streaming,
+ error: undefined,
+ });
+ }
+
+ link.enqueueSubsequentChunk({
+ incremental: [
+ {
+ items: [
+ {
+ __typename: "Event",
+ id: "2",
+ startDate: "2026-02-02",
+ },
+ ] as any,
+ path: ["events", 1],
+ },
+ ],
+ hasNext: false,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ invariant("result" in snapshot);
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot.result).toStrictEqualTyped({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ {
+ __typename: "Event",
+ id: "2",
+ startDate: new Date(2026, 1, 2),
+ },
+ ],
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+});
+
+test("parses custom scalar fields across `@stream` payloads (graphql17Alpha9)", async () => {
+ const link = mockDeferStreamGraphQL17Alpha9();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new GraphQL17Alpha9Handler(),
+ });
+ const query = gql`
+ query Events {
+ events @stream(initialCount: 1) {
+ id
+ startDate
+ }
+ }
+ `;
+
+ using _disabledAct = disableActEnvironment();
+ const { takeRender, loadQuery } = await renderUseLoadableQueryHook(
+ () => useLoadableQuery(query),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useLoadableQuery"]);
+ }
+
+ loadQuery();
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useLoadableQuery",
+ "",
+ ]);
+ }
+
+ link.enqueueInitialChunk({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ ],
+ },
+ pending: [{ id: "0", path: ["events"] }],
+ hasNext: true,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ invariant("result" in snapshot);
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot.result).toStrictEqualTyped({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ ],
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.streaming,
+ error: undefined,
+ });
+ }
+
+ link.enqueueSubsequentChunk({
+ incremental: [
+ {
+ items: [
+ {
+ __typename: "Event",
+ id: "2",
+ startDate: "2026-02-02",
+ },
+ ] as any,
+ id: "0",
+ },
+ ],
+ completed: [{ id: "0" }],
+ hasNext: false,
});
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ invariant("result" in snapshot);
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot.result).toStrictEqualTyped({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ {
+ __typename: "Event",
+ id: "2",
+ startDate: new Date(2026, 1, 2),
+ },
+ ],
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
});
diff --git a/src/react/hooks/__tests__/useLoadableQuery/testUtils.tsx b/src/react/hooks/__tests__/useLoadableQuery/testUtils.tsx
new file mode 100644
index 00000000000..1bbc6536b94
--- /dev/null
+++ b/src/react/hooks/__tests__/useLoadableQuery/testUtils.tsx
@@ -0,0 +1,125 @@
+import type { RenderOptions } from "@testing-library/react";
+import {
+ createRenderStream,
+ useTrackRenders,
+} from "@testing-library/react-render-stream";
+import React, { Suspense } from "react";
+import { flushSync } from "react-dom";
+import { ErrorBoundary } from "react-error-boundary";
+
+import type { DataState, ErrorLike, OperationVariables } from "@apollo/client";
+import {
+ type QueryRef,
+ type useLoadableQuery,
+ useReadQuery,
+} from "@apollo/client/react";
+import { invariant } from "@apollo/client/utilities/invariant";
+
+export async function renderUseLoadableQueryHook<
+ TData,
+ TVariables extends OperationVariables,
+ TStates extends DataState["dataState"] = DataState["dataState"],
+ Props = never,
+>(
+ renderHook: (
+ props: Props extends never ? undefined : Props
+ ) => useLoadableQuery.Result,
+ options: Pick & { initialProps?: Props }
+) {
+ function UseReadQuery({
+ queryRef,
+ }: {
+ queryRef: QueryRef;
+ }) {
+ useTrackRenders({ name: "useReadQuery" });
+ mergeSnapshot({ result: useReadQuery(queryRef) });
+
+ return null;
+ }
+
+ function SuspenseFallback() {
+ useTrackRenders({ name: "" });
+
+ return null;
+ }
+
+ function ErrorFallback() {
+ useTrackRenders({ name: "" });
+
+ return null;
+ }
+
+ type RefetchFunction = useLoadableQuery.Handlers<
+ TData,
+ TVariables
+ >["refetch"];
+
+ function App({ props }: { props: Props | undefined }) {
+ useTrackRenders({ name: "useLoadableQuery" });
+ const [loadQuery, queryRef, { refetch }] = renderHook(props as any);
+
+ mergeSnapshot({ loadQuery, refetch });
+
+ return (
+ }>
+ replaceSnapshot({ error })}
+ >
+ {queryRef && }
+
+
+ );
+ }
+
+ const {
+ render,
+ getCurrentRender,
+ takeRender,
+ mergeSnapshot,
+ replaceSnapshot,
+ } = createRenderStream<
+ | {
+ loadQuery: useLoadableQuery.LoadQueryFunction;
+ refetch: RefetchFunction;
+ result?: useReadQuery.Result;
+ }
+ | { error: ErrorLike }
+ >({
+ // These values should always be available, but createRenderStream needs an
+ // initial snapshot when using mergeSnapshot so we provide it with something
+ initialSnapshot: { loadQuery: null as any, refetch: null as any },
+ });
+
+ const utils = await render(, options);
+
+ function rerender(props: Props) {
+ return utils.rerender();
+ }
+
+ function getCurrentSnapshot() {
+ const { snapshot } = getCurrentRender();
+ invariant(
+ "loadQuery" in snapshot,
+ "Expected rendered hook instead of error boundary"
+ );
+
+ return snapshot;
+ }
+
+ // React 18 skips committing the suspense fallback when loadQuery/refetch is
+ // triggered as a default-priority update, so the fallback render is missed
+ // and tests may fail. flushSync forces a synchronous commit so the fallback
+ // renders in both React 18 and 19, while returning the underlying result.
+ const loadQuery: useLoadableQuery.LoadQueryFunction = (
+ ...args
+ ) => {
+ return flushSync(() => getCurrentSnapshot().loadQuery(...args));
+ };
+
+ const refetch: RefetchFunction = (...args) => {
+ return flushSync(() => getCurrentSnapshot().refetch(...args));
+ };
+
+ return { takeRender, rerender, getCurrentSnapshot, refetch, loadQuery };
+}
diff --git a/src/react/hooks/__tests__/useMutation/customScalars.test.tsx b/src/react/hooks/__tests__/useMutation/customScalars.test.tsx
index a7f5fd20736..8a3612f66ee 100644
--- a/src/react/hooks/__tests__/useMutation/customScalars.test.tsx
+++ b/src/react/hooks/__tests__/useMutation/customScalars.test.tsx
@@ -5,13 +5,25 @@ import {
import { delay, of } from "rxjs";
import type { OperationVariables, TypedDocumentNode } from "@apollo/client";
-import { ApolloClient, ApolloLink, gql, NetworkStatus } from "@apollo/client";
+import {
+ ApolloClient,
+ ApolloLink,
+ CombinedGraphQLErrors,
+ gql,
+ NetworkStatus,
+} from "@apollo/client";
import { InMemoryCache } from "@apollo/client/cache";
+import {
+ Defer20220824Handler,
+ GraphQL17Alpha9Handler,
+} from "@apollo/client/incremental";
import { useMutation, useQuery } from "@apollo/client/react";
import { MockLink } from "@apollo/client/testing";
import {
createClientWrapper,
dateScalar,
+ mockDefer20220824,
+ mockDeferStreamGraphQL17Alpha9,
} from "@apollo/client/testing/internal";
test("serializes scalar variables used in field arguments", async () => {
@@ -1107,3 +1119,588 @@ test("parses custom scalar fields in queries triggered by refetchQueries", async
},
});
});
+
+test("serializes scalar fields in the error with a `none` error policy", async () => {
+ const mutation = gql`
+ mutation CreateEvent {
+ createEvent {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ createEvent: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["createEvent", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ using _disabledAct = disableActEnvironment();
+ const { takeSnapshot, getCurrentSnapshot } = await renderHookToSnapshotStream(
+ () => useMutation(mutation, { errorPolicy: "none" }),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: undefined,
+ error: undefined,
+ called: false,
+ loading: false,
+ });
+ }
+
+ const [mutate] = getCurrentSnapshot();
+
+ await expect(mutate()).rejects.toEqual(
+ new CombinedGraphQLErrors({
+ data: {
+ createEvent: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["createEvent", "endDate"],
+ },
+ ],
+ })
+ );
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: undefined,
+ error: undefined,
+ called: true,
+ loading: true,
+ });
+ }
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: undefined,
+ error: new CombinedGraphQLErrors({
+ data: {
+ createEvent: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["createEvent", "endDate"],
+ },
+ ],
+ }),
+ called: true,
+ loading: false,
+ });
+ }
+
+ await expect(takeSnapshot).not.toRerender();
+});
+
+test("parses scalar fields in the result and serializes them in the error with an `all` error policy", async () => {
+ const mutation = gql`
+ mutation CreateEvent {
+ createEvent {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ createEvent: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["createEvent", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ using _disabledAct = disableActEnvironment();
+ const { takeSnapshot, getCurrentSnapshot } = await renderHookToSnapshotStream(
+ () => useMutation(mutation, { errorPolicy: "all" }),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: undefined,
+ error: undefined,
+ called: false,
+ loading: false,
+ });
+ }
+
+ const [mutate] = getCurrentSnapshot();
+
+ await expect(mutate()).resolves.toStrictEqualTyped({
+ data: {
+ createEvent: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ error: new CombinedGraphQLErrors({
+ data: {
+ createEvent: {
+ __typename: "Event",
+ id: "1",
+ // TODO: Determine if this is correct
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["createEvent", "endDate"],
+ },
+ ],
+ }),
+ });
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: undefined,
+ error: undefined,
+ called: true,
+ loading: true,
+ });
+ }
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: {
+ createEvent: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ error: new CombinedGraphQLErrors({
+ data: {
+ createEvent: {
+ __typename: "Event",
+ id: "1",
+ // TODO: Determine if this is correct
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["createEvent", "endDate"],
+ },
+ ],
+ }),
+ called: true,
+ loading: false,
+ });
+ }
+
+ await expect(takeSnapshot).not.toRerender();
+});
+
+test("parses custom scalar fields with an `ignore` error policy", async () => {
+ const mutation = gql`
+ mutation CreateEvent {
+ createEvent {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ createEvent: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["createEvent", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ using _disabledAct = disableActEnvironment();
+ const { takeSnapshot, getCurrentSnapshot } = await renderHookToSnapshotStream(
+ () => useMutation(mutation, { errorPolicy: "ignore" }),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: undefined,
+ error: undefined,
+ called: false,
+ loading: false,
+ });
+ }
+
+ const [mutate] = getCurrentSnapshot();
+
+ await expect(mutate()).resolves.toStrictEqualTyped({
+ data: {
+ createEvent: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ });
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: undefined,
+ error: undefined,
+ called: true,
+ loading: true,
+ });
+ }
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: {
+ createEvent: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ error: undefined,
+ called: true,
+ loading: false,
+ });
+ }
+
+ await expect(takeSnapshot).not.toRerender();
+});
+
+test("parses custom scalar fields across `@defer` payloads (defer20220824)", async () => {
+ const link = mockDefer20220824();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new Defer20220824Handler(),
+ });
+ const mutation = gql`
+ mutation CreateEvent {
+ createEvent {
+ id
+ startDate
+ ... @defer {
+ endDate
+ }
+ }
+ }
+ `;
+
+ using _disabledAct = disableActEnvironment();
+ const { takeSnapshot, getCurrentSnapshot } = await renderHookToSnapshotStream(
+ () => useMutation(mutation),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: undefined,
+ error: undefined,
+ called: false,
+ loading: false,
+ });
+ }
+
+ const [mutate] = getCurrentSnapshot();
+
+ const promise = mutate();
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: undefined,
+ error: undefined,
+ called: true,
+ loading: true,
+ });
+ }
+
+ link.enqueueInitialChunk({
+ data: {
+ createEvent: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ hasNext: true,
+ });
+
+ await expect(takeSnapshot).not.toRerender();
+
+ link.enqueueSubsequentChunk({
+ incremental: [{ data: { endDate: "2026-02-02" }, path: ["createEvent"] }],
+ hasNext: false,
+ });
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: {
+ createEvent: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: new Date(2026, 1, 2),
+ },
+ },
+ error: undefined,
+ called: true,
+ loading: false,
+ });
+ }
+
+ await expect(promise).resolves.toStrictEqualTyped({
+ data: {
+ createEvent: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: new Date(2026, 1, 2),
+ },
+ },
+ });
+
+ await expect(takeSnapshot).not.toRerender();
+});
+
+test("parses custom scalar fields across `@defer` payloads (graphql17Alpha9)", async () => {
+ const link = mockDeferStreamGraphQL17Alpha9();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new GraphQL17Alpha9Handler(),
+ });
+ const mutation = gql`
+ mutation CreateEvent {
+ createEvent {
+ id
+ startDate
+ ... @defer {
+ endDate
+ }
+ }
+ }
+ `;
+
+ using _disabledAct = disableActEnvironment();
+ const { takeSnapshot, getCurrentSnapshot } = await renderHookToSnapshotStream(
+ () => useMutation(mutation),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: undefined,
+ error: undefined,
+ called: false,
+ loading: false,
+ });
+ }
+
+ const [mutate] = getCurrentSnapshot();
+
+ const promise = mutate();
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: undefined,
+ error: undefined,
+ called: true,
+ loading: true,
+ });
+ }
+
+ link.enqueueInitialChunk({
+ data: {
+ createEvent: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ pending: [{ id: "0", path: ["createEvent"] }],
+ hasNext: true,
+ });
+
+ await expect(takeSnapshot).not.toRerender();
+
+ link.enqueueSubsequentChunk({
+ incremental: [{ data: { endDate: "2026-02-02" }, id: "0" }],
+ completed: [{ id: "0" }],
+ hasNext: false,
+ });
+
+ {
+ const [, result] = await takeSnapshot();
+
+ expect(result).toStrictEqualTyped({
+ data: {
+ createEvent: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: new Date(2026, 1, 2),
+ },
+ },
+ error: undefined,
+ called: true,
+ loading: false,
+ });
+ }
+
+ await expect(promise).resolves.toStrictEqualTyped({
+ data: {
+ createEvent: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: new Date(2026, 1, 2),
+ },
+ },
+ });
+
+ await expect(takeSnapshot).not.toRerender();
+});
diff --git a/src/react/hooks/__tests__/useQuery/customScalars.test.tsx b/src/react/hooks/__tests__/useQuery/customScalars.test.tsx
index a92bbd12abc..afa599abec3 100644
--- a/src/react/hooks/__tests__/useQuery/customScalars.test.tsx
+++ b/src/react/hooks/__tests__/useQuery/customScalars.test.tsx
@@ -5,12 +5,25 @@ import {
import { delay, of } from "rxjs";
import type { OperationVariables } from "@apollo/client";
-import { ApolloClient, ApolloLink, gql, NetworkStatus } from "@apollo/client";
+import {
+ ApolloClient,
+ ApolloLink,
+ CombinedGraphQLErrors,
+ gql,
+ NetworkStatus,
+} from "@apollo/client";
import { InMemoryCache } from "@apollo/client/cache";
+import {
+ Defer20220824Handler,
+ GraphQL17Alpha9Handler,
+} from "@apollo/client/incremental";
import { useQuery } from "@apollo/client/react";
import {
createClientWrapper,
dateScalar,
+ markAsStreaming,
+ mockDefer20220824,
+ mockDeferStreamGraphQL17Alpha9,
} from "@apollo/client/testing/internal";
test("serializes scalar variables used in field arguments", async () => {
@@ -696,3 +709,811 @@ test.failing(
});
}
);
+
+test("preserves referential identity when refetching identical serialized scalar values", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ }).pipe(delay(20))
+ ),
+ });
+
+ using _disabledAct = disableActEnvironment();
+ const { takeSnapshot, getCurrentSnapshot } = await renderHookToSnapshotStream(
+ () => useQuery(query),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ await expect(takeSnapshot()).resolves.toStrictEqualTyped({
+ data: undefined,
+ dataState: "empty",
+ loading: true,
+ networkStatus: NetworkStatus.loading,
+ previousData: undefined,
+ variables: {},
+ });
+
+ await expect(takeSnapshot()).resolves.toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ dataState: "complete",
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ previousData: undefined,
+ variables: {},
+ });
+
+ const { data: previousData, refetch } = getCurrentSnapshot();
+
+ await expect(refetch()).resolves.toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ });
+
+ await expect(takeSnapshot()).resolves.toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ dataState: "complete",
+ loading: true,
+ networkStatus: NetworkStatus.refetch,
+ previousData: undefined,
+ variables: {},
+ });
+
+ await expect(takeSnapshot()).resolves.toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ dataState: "complete",
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ previousData: undefined,
+ variables: {},
+ });
+
+ const { data } = getCurrentSnapshot();
+
+ expect(data).toBe(previousData);
+
+ await expect(takeSnapshot).not.toRerender();
+});
+
+test("serializes scalar fields in the error with a `none` error policy", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ using _disabledAct = disableActEnvironment();
+ const { takeSnapshot } = await renderHookToSnapshotStream(
+ () => useQuery(query, { errorPolicy: "none" }),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ await expect(takeSnapshot()).resolves.toStrictEqualTyped({
+ data: undefined,
+ dataState: "empty",
+ loading: true,
+ networkStatus: NetworkStatus.loading,
+ previousData: undefined,
+ variables: {},
+ });
+
+ await expect(takeSnapshot()).resolves.toStrictEqualTyped({
+ data: undefined,
+ dataState: "empty",
+ error: new CombinedGraphQLErrors({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }),
+ loading: false,
+ networkStatus: NetworkStatus.error,
+ previousData: undefined,
+ variables: {},
+ });
+
+ await expect(takeSnapshot).not.toRerender();
+});
+
+test("parses scalar fields in the result and serializes them in the error with an `all` error policy", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ using _disabledAct = disableActEnvironment();
+ const { takeSnapshot } = await renderHookToSnapshotStream(
+ () => useQuery(query, { errorPolicy: "all" }),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ await expect(takeSnapshot()).resolves.toStrictEqualTyped({
+ data: undefined,
+ dataState: "empty",
+ loading: true,
+ networkStatus: NetworkStatus.loading,
+ previousData: undefined,
+ variables: {},
+ });
+
+ await expect(takeSnapshot()).resolves.toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ dataState: "complete",
+ error: new CombinedGraphQLErrors({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ // TODO: Determine if this is correct
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }),
+ loading: false,
+ networkStatus: NetworkStatus.error,
+ previousData: undefined,
+ variables: {},
+ });
+
+ await expect(takeSnapshot).not.toRerender();
+});
+
+test("parses custom scalar fields with an `ignore` error policy", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ using _disabledAct = disableActEnvironment();
+ const { takeSnapshot } = await renderHookToSnapshotStream(
+ () => useQuery(query, { errorPolicy: "ignore" }),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ await expect(takeSnapshot()).resolves.toStrictEqualTyped({
+ data: undefined,
+ dataState: "empty",
+ loading: true,
+ networkStatus: NetworkStatus.loading,
+ previousData: undefined,
+ variables: {},
+ });
+
+ await expect(takeSnapshot()).resolves.toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ dataState: "complete",
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ previousData: undefined,
+ variables: {},
+ });
+
+ await expect(takeSnapshot).not.toRerender();
+});
+
+test("parses custom scalar fields across `@defer` payloads (defer20220824)", async () => {
+ const link = mockDefer20220824();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new Defer20220824Handler(),
+ });
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ ... @defer {
+ endDate
+ }
+ }
+ }
+ `;
+
+ using _disabledAct = disableActEnvironment();
+ const { takeSnapshot } = await renderHookToSnapshotStream(
+ () => useQuery(query),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ await expect(takeSnapshot()).resolves.toStrictEqualTyped({
+ data: undefined,
+ dataState: "empty",
+ loading: true,
+ networkStatus: NetworkStatus.loading,
+ previousData: undefined,
+ variables: {},
+ });
+
+ link.enqueueInitialChunk({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ hasNext: true,
+ });
+
+ await expect(takeSnapshot()).resolves.toStrictEqualTyped({
+ data: markAsStreaming({
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ }),
+ dataState: "streaming",
+ loading: true,
+ networkStatus: NetworkStatus.streaming,
+ previousData: undefined,
+ variables: {},
+ });
+
+ link.enqueueSubsequentChunk({
+ incremental: [{ data: { endDate: "2026-02-02" }, path: ["event"] }],
+ hasNext: false,
+ });
+
+ await expect(takeSnapshot()).resolves.toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: new Date(2026, 1, 2),
+ },
+ },
+ dataState: "complete",
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ previousData: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ variables: {},
+ });
+
+ await expect(takeSnapshot).not.toRerender();
+});
+
+test("parses custom scalar fields across `@defer` payloads (graphql17Alpha9)", async () => {
+ const link = mockDeferStreamGraphQL17Alpha9();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new GraphQL17Alpha9Handler(),
+ });
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ ... @defer {
+ endDate
+ }
+ }
+ }
+ `;
+
+ using _disabledAct = disableActEnvironment();
+ const { takeSnapshot } = await renderHookToSnapshotStream(
+ () => useQuery(query),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ await expect(takeSnapshot()).resolves.toStrictEqualTyped({
+ data: undefined,
+ dataState: "empty",
+ loading: true,
+ networkStatus: NetworkStatus.loading,
+ previousData: undefined,
+ variables: {},
+ });
+
+ link.enqueueInitialChunk({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ pending: [{ id: "0", path: ["event"] }],
+ hasNext: true,
+ });
+
+ await expect(takeSnapshot()).resolves.toStrictEqualTyped({
+ data: markAsStreaming({
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ }),
+ dataState: "streaming",
+ loading: true,
+ networkStatus: NetworkStatus.streaming,
+ previousData: undefined,
+ variables: {},
+ });
+
+ link.enqueueSubsequentChunk({
+ incremental: [{ data: { endDate: "2026-02-02" }, id: "0" }],
+ completed: [{ id: "0" }],
+ hasNext: false,
+ });
+
+ await expect(takeSnapshot()).resolves.toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: new Date(2026, 1, 2),
+ },
+ },
+ dataState: "complete",
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ previousData: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ variables: {},
+ });
+
+ await expect(takeSnapshot).not.toRerender();
+});
+
+test("parses custom scalar fields across `@stream` payloads (defer20220824)", async () => {
+ const link = mockDefer20220824();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new Defer20220824Handler(),
+ });
+ const query = gql`
+ query Events {
+ events @stream(initialCount: 1) {
+ id
+ startDate
+ }
+ }
+ `;
+
+ using _disabledAct = disableActEnvironment();
+ const { takeSnapshot } = await renderHookToSnapshotStream(
+ () => useQuery(query),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ await expect(takeSnapshot()).resolves.toStrictEqualTyped({
+ data: undefined,
+ dataState: "empty",
+ loading: true,
+ networkStatus: NetworkStatus.loading,
+ previousData: undefined,
+ variables: {},
+ });
+
+ link.enqueueInitialChunk({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ ],
+ },
+ hasNext: true,
+ });
+
+ await expect(takeSnapshot()).resolves.toStrictEqualTyped({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ ],
+ },
+ dataState: "complete",
+ loading: true,
+ networkStatus: NetworkStatus.streaming,
+ previousData: undefined,
+ variables: {},
+ });
+
+ link.enqueueSubsequentChunk({
+ incremental: [
+ {
+ items: [
+ {
+ __typename: "Event",
+ id: "2",
+ startDate: "2026-02-02",
+ },
+ ] as any,
+ path: ["events", 1],
+ },
+ ],
+ hasNext: false,
+ });
+
+ await expect(takeSnapshot()).resolves.toStrictEqualTyped({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ {
+ __typename: "Event",
+ id: "2",
+ startDate: new Date(2026, 1, 2),
+ },
+ ],
+ },
+ dataState: "complete",
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ previousData: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ ],
+ },
+ variables: {},
+ });
+
+ await expect(takeSnapshot).not.toRerender();
+});
+
+test("parses custom scalar fields across `@stream` payloads (graphql17Alpha9)", async () => {
+ const link = mockDeferStreamGraphQL17Alpha9();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new GraphQL17Alpha9Handler(),
+ });
+ const query = gql`
+ query Events {
+ events @stream(initialCount: 1) {
+ id
+ startDate
+ }
+ }
+ `;
+
+ using _disabledAct = disableActEnvironment();
+ const { takeSnapshot } = await renderHookToSnapshotStream(
+ () => useQuery(query),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ await expect(takeSnapshot()).resolves.toStrictEqualTyped({
+ data: undefined,
+ dataState: "empty",
+ loading: true,
+ networkStatus: NetworkStatus.loading,
+ previousData: undefined,
+ variables: {},
+ });
+
+ link.enqueueInitialChunk({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ ],
+ },
+ pending: [{ id: "0", path: ["events"] }],
+ hasNext: true,
+ });
+
+ await expect(takeSnapshot()).resolves.toStrictEqualTyped({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ ],
+ },
+ dataState: "complete",
+ loading: true,
+ networkStatus: NetworkStatus.streaming,
+ previousData: undefined,
+ variables: {},
+ });
+
+ link.enqueueSubsequentChunk({
+ incremental: [
+ {
+ items: [
+ {
+ __typename: "Event",
+ id: "2",
+ startDate: "2026-02-02",
+ },
+ ] as any,
+ id: "0",
+ },
+ ],
+ completed: [{ id: "0" }],
+ hasNext: false,
+ });
+
+ await expect(takeSnapshot()).resolves.toStrictEqualTyped({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ {
+ __typename: "Event",
+ id: "2",
+ startDate: new Date(2026, 1, 2),
+ },
+ ],
+ },
+ dataState: "complete",
+ loading: false,
+ networkStatus: NetworkStatus.ready,
+ previousData: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ ],
+ },
+ variables: {},
+ });
+
+ await expect(takeSnapshot).not.toRerender();
+});
diff --git a/src/react/hooks/__tests__/useQueryRefHandlers/customScalars.test.tsx b/src/react/hooks/__tests__/useQueryRefHandlers/customScalars.test.tsx
index 3f5f3beffbe..9d4f6078f1f 100644
--- a/src/react/hooks/__tests__/useQueryRefHandlers/customScalars.test.tsx
+++ b/src/react/hooks/__tests__/useQueryRefHandlers/customScalars.test.tsx
@@ -103,9 +103,14 @@ test("serializes scalar variables passed to refetch", async () => {
wrapper: createClientWrapper(client),
});
- await expect(renderStream.takeRender()).resolves.toMatchObject({
- renderedComponents: ["useQueryRefHandlers", ""],
- });
+ {
+ const { renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useQueryRefHandlers",
+ "",
+ ]);
+ }
{
const { snapshot, renderedComponents } = await renderStream.takeRender();
@@ -135,9 +140,14 @@ test("serializes scalar variables passed to refetch", async () => {
},
});
- await expect(renderStream.takeRender()).resolves.toMatchObject({
- renderedComponents: ["useQueryRefHandlers", ""],
- });
+ {
+ const { renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useQueryRefHandlers",
+ "",
+ ]);
+ }
{
const { snapshot, renderedComponents } = await renderStream.takeRender();
@@ -242,9 +252,14 @@ test("serializes scalar variables passed to fetchMore", async () => {
wrapper: createClientWrapper(client),
});
- await expect(renderStream.takeRender()).resolves.toMatchObject({
- renderedComponents: ["useQueryRefHandlers", ""],
- });
+ {
+ const { renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useQueryRefHandlers",
+ "",
+ ]);
+ }
{
const { snapshot, renderedComponents } = await renderStream.takeRender();
@@ -274,9 +289,14 @@ test("serializes scalar variables passed to fetchMore", async () => {
},
});
- await expect(renderStream.takeRender()).resolves.toMatchObject({
- renderedComponents: ["useQueryRefHandlers", ""],
- });
+ {
+ const { renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useQueryRefHandlers",
+ "",
+ ]);
+ }
{
const { snapshot, renderedComponents } = await renderStream.takeRender();
@@ -388,9 +408,14 @@ test("serializes scalar variables passed to subscribeToMore", async () => {
wrapper: createClientWrapper(client),
});
- await expect(renderStream.takeRender()).resolves.toMatchObject({
- renderedComponents: ["useQueryRefHandlers", ""],
- });
+ {
+ const { renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useQueryRefHandlers",
+ "",
+ ]);
+ }
{
const { snapshot, renderedComponents } = await renderStream.takeRender();
@@ -445,3 +470,160 @@ test("serializes scalar variables passed to subscribeToMore", async () => {
date: "2026-01-01",
});
});
+
+test("preserves referential identity when refetching identical serialized scalar values", async () => {
+ let refetchPromise!: ReturnType<
+ ReturnType["refetch"]
+ >;
+
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ }).pipe(delay(20))
+ ),
+ });
+
+ const preloadQuery = createQueryPreloader(client);
+ const queryRef = preloadQuery(query);
+ const renderStream = createRenderStream({
+ initialSnapshot: {
+ result: null as useReadQuery.Result | null,
+ },
+ });
+
+ function ReadQuery() {
+ useTrackRenders({ name: "useReadQuery" });
+ renderStream.mergeSnapshot({ result: useReadQuery(queryRef) });
+
+ return null;
+ }
+
+ function Fallback() {
+ useTrackRenders({ name: "" });
+ return null;
+ }
+
+ function App() {
+ useTrackRenders({ name: "useQueryRefHandlers" });
+ const { refetch } = useQueryRefHandlers(queryRef);
+
+ return (
+ <>
+
+ }>
+
+
+ >
+ );
+ }
+
+ const user = userEvent.setup();
+ using _disabledAct = disableActEnvironment();
+ await renderStream.render(, {
+ wrapper: createClientWrapper(client),
+ });
+
+ {
+ const { renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useQueryRefHandlers",
+ "",
+ ]);
+ }
+
+ let previousData: unknown;
+ {
+ const { snapshot, renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot.result).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+
+ previousData = snapshot.result!.data;
+ }
+
+ await user.click(screen.getByText("Refetch"));
+
+ await expect(refetchPromise).resolves.toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ });
+
+ {
+ const { renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useQueryRefHandlers",
+ "",
+ ]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot.result).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+
+ expect(snapshot.result!.data).toBe(previousData);
+ }
+
+ await expect(renderStream).not.toRerender();
+});
diff --git a/src/react/hooks/__tests__/useSuspenseQuery/customScalars.test.tsx b/src/react/hooks/__tests__/useSuspenseQuery/customScalars.test.tsx
index 504ed0dc071..17154023002 100644
--- a/src/react/hooks/__tests__/useSuspenseQuery/customScalars.test.tsx
+++ b/src/react/hooks/__tests__/useSuspenseQuery/customScalars.test.tsx
@@ -2,12 +2,26 @@ import { disableActEnvironment } from "@testing-library/react-render-stream";
import { delay, of } from "rxjs";
import type { OperationVariables } from "@apollo/client";
-import { ApolloClient, ApolloLink, gql, NetworkStatus } from "@apollo/client";
+import {
+ ApolloClient,
+ ApolloLink,
+ CombinedGraphQLErrors,
+ gql,
+ NetworkStatus,
+} from "@apollo/client";
import { InMemoryCache } from "@apollo/client/cache";
+import {
+ Defer20220824Handler,
+ GraphQL17Alpha9Handler,
+} from "@apollo/client/incremental";
import { useSuspenseQuery } from "@apollo/client/react";
import {
createClientWrapper,
dateScalar,
+ markAsStreaming,
+ mockDefer20220824,
+ mockDeferStreamGraphQL17Alpha9,
+ spyOnConsole,
} from "@apollo/client/testing/internal";
import { renderUseSuspenseQuery } from "./testUtils.js";
@@ -629,3 +643,783 @@ test.failing(
}
}
);
+
+test("preserves referential identity when refetching identical serialized scalar values", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ }).pipe(delay(20))
+ ),
+ });
+
+ using _disabledAct = disableActEnvironment();
+ const { takeRender, getCurrentSnapshot, refetch } =
+ await renderUseSuspenseQuery(() => useSuspenseQuery(query), {
+ wrapper: createClientWrapper(client),
+ });
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+ }
+
+ const { data: previousData } = getCurrentSnapshot();
+
+ await expect(refetch()).resolves.toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ });
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+ }
+
+ expect(getCurrentSnapshot().data).toBe(previousData);
+
+ await expect(takeRender).not.toRerender();
+});
+
+test("serializes scalar fields in the error with a `none` error policy", async () => {
+ using _consoleSpy = spyOnConsole("error");
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ using _disabledAct = disableActEnvironment();
+ const { takeRender } = await renderUseSuspenseQuery(
+ () => useSuspenseQuery(query, { errorPolicy: "none" }),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ expect(snapshot).toStrictEqualTyped({
+ error: new CombinedGraphQLErrors({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }),
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+});
+
+test("parses scalar fields in the result and serializes them in the error with an `all` error policy", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ using _disabledAct = disableActEnvironment();
+ const { takeRender } = await renderUseSuspenseQuery(
+ () => useSuspenseQuery(query, { errorPolicy: "all" }),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.error,
+ error: new CombinedGraphQLErrors({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ // TODO: Determine if this is correct
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }),
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+});
+
+test("parses custom scalar fields with an `ignore` error policy", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ using _disabledAct = disableActEnvironment();
+ const { takeRender } = await renderUseSuspenseQuery(
+ () => useSuspenseQuery(query, { errorPolicy: "ignore" }),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+});
+
+test("parses custom scalar fields across `@defer` payloads (defer20220824)", async () => {
+ const link = mockDefer20220824();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new Defer20220824Handler(),
+ });
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ ... @defer {
+ endDate
+ }
+ }
+ }
+ `;
+
+ using _disabledAct = disableActEnvironment();
+ const { takeRender } = await renderUseSuspenseQuery(
+ () => useSuspenseQuery(query),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ link.enqueueInitialChunk({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ hasNext: true,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: markAsStreaming({
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ }),
+ dataState: "streaming",
+ networkStatus: NetworkStatus.streaming,
+ error: undefined,
+ });
+ }
+
+ link.enqueueSubsequentChunk({
+ incremental: [{ data: { endDate: "2026-02-02" }, path: ["event"] }],
+ hasNext: false,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: new Date(2026, 1, 2),
+ },
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+});
+
+test("parses custom scalar fields across `@defer` payloads (graphql17Alpha9)", async () => {
+ const link = mockDeferStreamGraphQL17Alpha9();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new GraphQL17Alpha9Handler(),
+ });
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ ... @defer {
+ endDate
+ }
+ }
+ }
+ `;
+
+ using _disabledAct = disableActEnvironment();
+ const { takeRender } = await renderUseSuspenseQuery(
+ () => useSuspenseQuery(query),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ link.enqueueInitialChunk({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ pending: [{ id: "0", path: ["event"] }],
+ hasNext: true,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: markAsStreaming({
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ }),
+ dataState: "streaming",
+ networkStatus: NetworkStatus.streaming,
+ error: undefined,
+ });
+ }
+
+ link.enqueueSubsequentChunk({
+ incremental: [{ data: { endDate: "2026-02-02" }, id: "0" }],
+ completed: [{ id: "0" }],
+ hasNext: false,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: new Date(2026, 1, 2),
+ },
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+});
+
+test("parses custom scalar fields across `@stream` payloads (defer20220824)", async () => {
+ const link = mockDefer20220824();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new Defer20220824Handler(),
+ });
+ const query = gql`
+ query Events {
+ events @stream(initialCount: 1) {
+ id
+ startDate
+ }
+ }
+ `;
+
+ using _disabledAct = disableActEnvironment();
+ const { takeRender } = await renderUseSuspenseQuery(
+ () => useSuspenseQuery(query),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ link.enqueueInitialChunk({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ ],
+ },
+ hasNext: true,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ ],
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.streaming,
+ error: undefined,
+ });
+ }
+
+ link.enqueueSubsequentChunk({
+ incremental: [
+ {
+ items: [
+ {
+ __typename: "Event",
+ id: "2",
+ startDate: "2026-02-02",
+ },
+ ] as any,
+ path: ["events", 1],
+ },
+ ],
+ hasNext: false,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ {
+ __typename: "Event",
+ id: "2",
+ startDate: new Date(2026, 1, 2),
+ },
+ ],
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+});
+
+test("parses custom scalar fields across `@stream` payloads (graphql17Alpha9)", async () => {
+ const link = mockDeferStreamGraphQL17Alpha9();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new GraphQL17Alpha9Handler(),
+ });
+ const query = gql`
+ query Events {
+ events @stream(initialCount: 1) {
+ id
+ startDate
+ }
+ }
+ `;
+
+ using _disabledAct = disableActEnvironment();
+ const { takeRender } = await renderUseSuspenseQuery(
+ () => useSuspenseQuery(query),
+ { wrapper: createClientWrapper(client) }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ link.enqueueInitialChunk({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ ],
+ },
+ pending: [{ id: "0", path: ["events"] }],
+ hasNext: true,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ ],
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.streaming,
+ error: undefined,
+ });
+ }
+
+ link.enqueueSubsequentChunk({
+ incremental: [
+ {
+ items: [
+ {
+ __typename: "Event",
+ id: "2",
+ startDate: "2026-02-02",
+ },
+ ] as any,
+ id: "0",
+ },
+ ],
+ completed: [{ id: "0" }],
+ hasNext: false,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ {
+ __typename: "Event",
+ id: "2",
+ startDate: new Date(2026, 1, 2),
+ },
+ ],
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+});
diff --git a/src/react/hooks/__tests__/useSuspenseQuery/testUtils.tsx b/src/react/hooks/__tests__/useSuspenseQuery/testUtils.tsx
index 8554c0f566e..9bf1e9d17a1 100644
--- a/src/react/hooks/__tests__/useSuspenseQuery/testUtils.tsx
+++ b/src/react/hooks/__tests__/useSuspenseQuery/testUtils.tsx
@@ -4,6 +4,7 @@ import {
useTrackRenders,
} from "@testing-library/react-render-stream";
import React, { Suspense } from "react";
+import { flushSync } from "react-dom";
import { ErrorBoundary } from "react-error-boundary";
import type { ErrorLike, OperationVariables } from "@apollo/client";
@@ -69,5 +70,15 @@ export async function renderUseSuspenseQuery<
return snapshot;
}
- return { getCurrentSnapshot, rerender, takeRender };
+ // React 18 skips committing the suspense fallback when refetch is triggered
+ // as a default-priority update, so the fallback render is missed and tests
+ // may fail. flushSync forces a synchronous commit so the fallback renders in
+ // both React 18 and 19, while returning the refetch promise.
+ const refetch: useSuspenseQuery.Result["refetch"] = (
+ ...args
+ ) => {
+ return flushSync(() => getCurrentSnapshot().refetch(...args));
+ };
+
+ return { getCurrentSnapshot, rerender, takeRender, refetch };
}
diff --git a/src/react/query-preloader/__tests__/createQueryPreloader/customScalars.test.tsx b/src/react/query-preloader/__tests__/createQueryPreloader/customScalars.test.tsx
index 8ea858fed3d..a89e9bb7d51 100644
--- a/src/react/query-preloader/__tests__/createQueryPreloader/customScalars.test.tsx
+++ b/src/react/query-preloader/__tests__/createQueryPreloader/customScalars.test.tsx
@@ -1,11 +1,27 @@
import { disableActEnvironment } from "@testing-library/react-render-stream";
-import { of } from "rxjs";
+import { delay, of } from "rxjs";
import type { OperationVariables } from "@apollo/client";
-import { ApolloClient, ApolloLink, gql, NetworkStatus } from "@apollo/client";
+import {
+ ApolloClient,
+ ApolloLink,
+ CombinedGraphQLErrors,
+ gql,
+ NetworkStatus,
+} from "@apollo/client";
import { InMemoryCache } from "@apollo/client/cache";
+import {
+ Defer20220824Handler,
+ GraphQL17Alpha9Handler,
+} from "@apollo/client/incremental";
import { createQueryPreloader } from "@apollo/client/react";
-import { dateScalar } from "@apollo/client/testing/internal";
+import {
+ dateScalar,
+ markAsStreaming,
+ mockDefer20220824,
+ mockDeferStreamGraphQL17Alpha9,
+ spyOnConsole,
+} from "@apollo/client/testing/internal";
import { renderDefaultTestApp } from "./testUtils.js";
@@ -158,3 +174,679 @@ test("serializes scalar fields in input object variables", async () => {
filter: { date: "2026-01-01" },
});
});
+
+test("serializes scalar fields in the error with a `none` error policy", async () => {
+ using _consoleSpy = spyOnConsole("error");
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ const preloadQuery = createQueryPreloader(client);
+ const queryRef = preloadQuery(query, { errorPolicy: "none" });
+
+ using _disabledAct = disableActEnvironment();
+ const { renderStream } = await renderDefaultTestApp({ client, queryRef });
+
+ {
+ const { renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual(["App", ""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual(["Error"]);
+ expect(snapshot.error).toEqual(
+ new CombinedGraphQLErrors({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ })
+ );
+ }
+
+ await expect(renderStream).not.toRerender();
+});
+
+test("parses scalar fields in the result and serializes them in the error with an `all` error policy", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ const preloadQuery = createQueryPreloader(client);
+ const queryRef = preloadQuery(query, { errorPolicy: "all" });
+
+ using _disabledAct = disableActEnvironment();
+ const { renderStream } = await renderDefaultTestApp({ client, queryRef });
+
+ {
+ const { renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual(["App", ""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot.result).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.error,
+ error: new CombinedGraphQLErrors({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ // TODO: Determine if this is correct
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }),
+ });
+ }
+
+ await expect(renderStream).not.toRerender();
+});
+
+test("parses custom scalar fields with an `ignore` error policy", async () => {
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ endDate
+ }
+ }
+ `;
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: new ApolloLink(() =>
+ of({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ endDate: null,
+ },
+ },
+ errors: [
+ {
+ message: "Could not resolve endDate",
+ path: ["event", "endDate"],
+ },
+ ],
+ }).pipe(delay(20))
+ ),
+ });
+
+ const preloadQuery = createQueryPreloader(client);
+ const queryRef = preloadQuery(query, { errorPolicy: "ignore" });
+
+ using _disabledAct = disableActEnvironment();
+ const { renderStream } = await renderDefaultTestApp({ client, queryRef });
+
+ {
+ const { renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual(["App", ""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot.result).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: null,
+ },
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+ }
+
+ await expect(renderStream).not.toRerender();
+});
+
+test("parses custom scalar fields across `@defer` payloads (defer20220824)", async () => {
+ const link = mockDefer20220824();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new Defer20220824Handler(),
+ });
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ ... @defer {
+ endDate
+ }
+ }
+ }
+ `;
+
+ const preloadQuery = createQueryPreloader(client);
+ const queryRef = preloadQuery(query);
+
+ using _disabledAct = disableActEnvironment();
+ const { renderStream } = await renderDefaultTestApp({ client, queryRef });
+
+ {
+ const { renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual(["App", ""]);
+ }
+
+ link.enqueueInitialChunk({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ hasNext: true,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot.result).toStrictEqualTyped({
+ data: markAsStreaming({
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ }),
+ dataState: "streaming",
+ networkStatus: NetworkStatus.streaming,
+ error: undefined,
+ });
+ }
+
+ link.enqueueSubsequentChunk({
+ incremental: [{ data: { endDate: "2026-02-02" }, path: ["event"] }],
+ hasNext: false,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot.result).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: new Date(2026, 1, 2),
+ },
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+ }
+
+ await expect(renderStream).not.toRerender();
+});
+
+test("parses custom scalar fields across `@defer` payloads (graphql17Alpha9)", async () => {
+ const link = mockDeferStreamGraphQL17Alpha9();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ endDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new GraphQL17Alpha9Handler(),
+ });
+ const query = gql`
+ query Event {
+ event {
+ id
+ startDate
+ ... @defer {
+ endDate
+ }
+ }
+ }
+ `;
+
+ const preloadQuery = createQueryPreloader(client);
+ const queryRef = preloadQuery(query);
+
+ using _disabledAct = disableActEnvironment();
+ const { renderStream } = await renderDefaultTestApp({ client, queryRef });
+
+ {
+ const { renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual(["App", ""]);
+ }
+
+ link.enqueueInitialChunk({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ },
+ pending: [{ id: "0", path: ["event"] }],
+ hasNext: true,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot.result).toStrictEqualTyped({
+ data: markAsStreaming({
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ }),
+ dataState: "streaming",
+ networkStatus: NetworkStatus.streaming,
+ error: undefined,
+ });
+ }
+
+ link.enqueueSubsequentChunk({
+ incremental: [{ data: { endDate: "2026-02-02" }, id: "0" }],
+ completed: [{ id: "0" }],
+ hasNext: false,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot.result).toStrictEqualTyped({
+ data: {
+ event: {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ endDate: new Date(2026, 1, 2),
+ },
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+ }
+
+ await expect(renderStream).not.toRerender();
+});
+
+test("parses custom scalar fields across `@stream` payloads (defer20220824)", async () => {
+ const link = mockDefer20220824();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new Defer20220824Handler(),
+ });
+ const query = gql`
+ query Events {
+ events @stream(initialCount: 1) {
+ id
+ startDate
+ }
+ }
+ `;
+
+ const preloadQuery = createQueryPreloader(client);
+ const queryRef = preloadQuery(query);
+
+ using _disabledAct = disableActEnvironment();
+ const { renderStream } = await renderDefaultTestApp({ client, queryRef });
+
+ {
+ const { renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual(["App", ""]);
+ }
+
+ link.enqueueInitialChunk({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ ],
+ },
+ hasNext: true,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot.result).toStrictEqualTyped({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ ],
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.streaming,
+ error: undefined,
+ });
+ }
+
+ link.enqueueSubsequentChunk({
+ incremental: [
+ {
+ items: [
+ {
+ __typename: "Event",
+ id: "2",
+ startDate: "2026-02-02",
+ },
+ ] as any,
+ path: ["events", 1],
+ },
+ ],
+ hasNext: false,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot.result).toStrictEqualTyped({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ {
+ __typename: "Event",
+ id: "2",
+ startDate: new Date(2026, 1, 2),
+ },
+ ],
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+ }
+
+ await expect(renderStream).not.toRerender();
+});
+
+test("parses custom scalar fields across `@stream` payloads (graphql17Alpha9)", async () => {
+ const link = mockDeferStreamGraphQL17Alpha9();
+ const client = new ApolloClient({
+ cache: new InMemoryCache({
+ scalars: { Date: dateScalar },
+ typePolicies: {
+ Event: {
+ fields: {
+ startDate: { scalar: "Date" },
+ },
+ },
+ },
+ }),
+ link: link.httpLink,
+ incrementalHandler: new GraphQL17Alpha9Handler(),
+ });
+ const query = gql`
+ query Events {
+ events @stream(initialCount: 1) {
+ id
+ startDate
+ }
+ }
+ `;
+
+ const preloadQuery = createQueryPreloader(client);
+ const queryRef = preloadQuery(query);
+
+ using _disabledAct = disableActEnvironment();
+ const { renderStream } = await renderDefaultTestApp({ client, queryRef });
+
+ {
+ const { renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual(["App", ""]);
+ }
+
+ link.enqueueInitialChunk({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: "2026-01-01",
+ },
+ ],
+ },
+ pending: [{ id: "0", path: ["events"] }],
+ hasNext: true,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot.result).toStrictEqualTyped({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ ],
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.streaming,
+ error: undefined,
+ });
+ }
+
+ link.enqueueSubsequentChunk({
+ incremental: [
+ {
+ items: [
+ {
+ __typename: "Event",
+ id: "2",
+ startDate: "2026-02-02",
+ },
+ ] as any,
+ id: "0",
+ },
+ ],
+ completed: [{ id: "0" }],
+ hasNext: false,
+ });
+
+ {
+ const { snapshot, renderedComponents } = await renderStream.takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot.result).toStrictEqualTyped({
+ data: {
+ events: [
+ {
+ __typename: "Event",
+ id: "1",
+ startDate: new Date(2026, 0, 1),
+ },
+ {
+ __typename: "Event",
+ id: "2",
+ startDate: new Date(2026, 1, 2),
+ },
+ ],
+ },
+ dataState: "complete",
+ networkStatus: NetworkStatus.ready,
+ error: undefined,
+ });
+ }
+
+ await expect(renderStream).not.toRerender();
+});