diff --git a/.api-reports/api-report-core.api.md b/.api-reports/api-report-core.api.md index c1f15c36f34..b1eb50951ef 100644 --- a/.api-reports/api-report-core.api.md +++ b/.api-reports/api-report-core.api.md @@ -1133,7 +1133,7 @@ export type WatchQueryOptions({ + return this.cache.diff({ query: this.query, variables: this.variables, returnPartialData: true, @@ -691,7 +698,7 @@ export class ObservableQuery< } }, }; - const cancelWatch = this.queryManager.cache.watch(watch); + const cancelWatch = this.cache.watch(watch); this.unsubscribeFromCache = Object.assign( () => { @@ -806,6 +813,7 @@ Did you mean to call refetch(variables) instead of refetch({ variables })?`, TFetchVars > ): Promise>; + public fetchMore< TFetchData = TData, TFetchVars extends OperationVariables = TVariables, @@ -864,7 +872,6 @@ Did you mean to call refetch(variables) instead of refetch({ variables })?`, : combinedOptions.query; let wasUpdated = false; - const isCached = this.options.fetchPolicy !== "no-cache"; if (!isCached) { @@ -877,6 +884,7 @@ Did you mean to call refetch(variables) instead of refetch({ variables })?`, const { finalize, pushNotification } = this.pushOperation( NetworkStatus.fetchMore ); + pushNotification( { source: "newNetworkStatus", @@ -885,115 +893,190 @@ Did you mean to call refetch(variables) instead of refetch({ variables })?`, }, { shouldEmit: EmitBehavior.networkStatusChange } ); - return this.queryManager - .fetchQuery(combinedOptions, NetworkStatus.fetchMore) - .then((fetchMoreResult) => { - // disable the `fetchMore` override that is currently active - // the next updates caused by this should not be `fetchMore` anymore, - // but `ready` or whatever other calculated loading state is currently - // appropriate - finalize(); - if (isCached) { - // Performing this cache update inside a cache.batch transaction ensures - // any affected cache.watch watchers are notified at most once about any - // updates. Most watchers will be using the QueryInfo class, which - // responds to notifications by calling reobserveCacheFirst to deliver - // fetchMore cache results back to this ObservableQuery. - this.queryManager.cache.batch({ - update: (cache) => { - if (updateQuery) { - cache.updateQuery( - { - query: this.query, - variables: this.variables, - returnPartialData: true, - optimistic: false, - }, - (previous) => - updateQuery(previous! as any, { - fetchMoreResult: fetchMoreResult.data as any, - variables: combinedOptions.variables as TFetchVars, - }) - ); - } else { - // If we're using a field policy instead of updateQuery, the only - // thing we need to do is write the new data to the cache using - // combinedOptions.variables (instead of this.variables, which is - // what this.updateQuery uses, because it works by abusing the - // original field value, keyed by the original variables). - cache.writeQuery({ - query: combinedOptions.query, - variables: combinedOptions.variables, - data: fetchMoreResult.data as Unmasked, - }); - } - }, - - onWatchUpdated: (watch) => { - if (watch.watcher === this) { - wasUpdated = true; - } - }, - }); - } else { - // There is a possibility `lastResult` may not be set when - // `fetchMore` is called which would cause this to crash. This should - // only happen if we haven't previously reported a result. We don't - // quite know what the right behavior should be here since this block - // of code runs after the fetch result has executed on the network. - // We plan to let it crash in the meantime. - // - // If we get bug reports due to the `data` property access on - // undefined, this should give us a real-world scenario that we can - // use to test against and determine the right behavior. If we do end - // up changing this behavior, this may require, for example, an - // adjustment to the types on `updateQuery` since that function - // expects that the first argument always contains previous result - // data, but not `undefined`. - const lastResult = this.getCurrentResult(); - const data = updateQuery!(lastResult.data as Unmasked, { - fetchMoreResult: fetchMoreResult.data as Unmasked, - variables: combinedOptions.variables as TFetchVars, - }); - // was reportResult - pushNotification({ - kind: "N", - value: { - ...lastResult, - networkStatus: NetworkStatus.ready, - // will be overwritten anyways, just here for types sake - loading: false, - data: data as any, - dataState: - lastResult.dataState === "streaming" ? "streaming" : "complete", - }, - source: "network", - }); + const { promise, operator } = getTrackingOperatorPromise( + (value: QueryNotification.Value) => { + switch (value.kind) { + case "E": { + throw value.error; + } + case "N": { + if (value.source !== "newNetworkStatus" && !value.value.loading) { + return value.value; + } + } } + } + ); - return this.maskResult(fetchMoreResult); - }) - .finally(() => { - // call `finalize` a second time in case the `.then` case above was not reached - finalize(); + const { observable } = this.queryManager.fetchObservableWithInfo( + combinedOptions, + { networkStatus: NetworkStatus.fetchMore } + ); - // In case the cache writes above did not generate a broadcast - // notification (which would have been intercepted by onWatchUpdated), - // likely because the written data were the same as what was already in - // the cache, we still want fetchMore to deliver its final loading:false - // result with the unchanged data. - if (isCached && !wasUpdated) { - pushNotification( - { + const subscription = observable + .pipe( + operator, + filter( + ( + notification + ): notification is Extract< + QueryNotification.FromNetwork, + { kind: "N" } + > => notification.kind === "N" && notification.source === "network" + ) + ) + .subscribe({ + next: (notification) => { + wasUpdated = false; + const fetchMoreResult = notification.value; + + if (isNetworkRequestSettled(notification.value.networkStatus)) { + finalize(); + } + + if (isCached) { + // Performing this cache update inside a cache.batch transaction ensures + // any affected cache.watch watchers are notified at most once about any + // updates. Most watchers will be using the QueryInfo class, which + // responds to notifications by calling reobserveCacheFirst to deliver + // fetchMore cache results back to this ObservableQuery. + this.cache.batch({ + update: (cache) => { + if (updateQuery) { + cache.updateQuery( + { + query: this.query, + variables: this.variables, + returnPartialData: true, + optimistic: false, + }, + (previous) => + updateQuery(previous! as any, { + fetchMoreResult: fetchMoreResult.data as any, + variables: combinedOptions.variables as TFetchVars, + }) + ); + } else { + // If we're using a field policy instead of updateQuery, the only + // thing we need to do is write the new data to the cache using + // combinedOptions.variables (instead of this.variables, which is + // what this.updateQuery uses, because it works by abusing the + // original field value, keyed by the original variables). + cache.writeQuery({ + query: combinedOptions.query, + variables: combinedOptions.variables, + data: fetchMoreResult.data as Unmasked, + }); + } + }, + + onWatchUpdated: (watch, diff) => { + if (watch.watcher === this) { + wasUpdated = true; + const lastResult = this.getCurrentResult(); + + // Let the cache watch from resubscribeCache handle the final + // result + if (isNetworkRequestInFlight(fetchMoreResult.networkStatus)) { + pushNotification({ + kind: "N", + source: "network", + value: { + ...lastResult, + networkStatus: + ( + fetchMoreResult.networkStatus === + NetworkStatus.error + ) ? + NetworkStatus.ready + : fetchMoreResult.networkStatus, + // will be overwritten anyways, just here for types sake + loading: false, + data: diff.result, + dataState: + fetchMoreResult.dataState === "streaming" ? + "streaming" + : "complete", + }, + }); + } + } + }, + }); + } else { + // There is a possibility `lastResult` may not be set when + // `fetchMore` is called which would cause this to crash. This should + // only happen if we haven't previously reported a result. We don't + // quite know what the right behavior should be here since this block + // of code runs after the fetch result has executed on the network. + // We plan to let it crash in the meantime. + // + // If we get bug reports due to the `data` property access on + // undefined, this should give us a real-world scenario that we can + // use to test against and determine the right behavior. If we do end + // up changing this behavior, this may require, for example, an + // adjustment to the types on `updateQuery` since that function + // expects that the first argument always contains previous result + // data, but not `undefined`. + const lastResult = this.getCurrentResult(); + const data = updateQuery!(lastResult.data as Unmasked, { + fetchMoreResult: fetchMoreResult.data as Unmasked, + variables: combinedOptions.variables as TFetchVars, + }); + + pushNotification({ kind: "N", - source: "newNetworkStatus", - value: {}, - }, - { shouldEmit: EmitBehavior.force } - ); - } + value: { + ...lastResult, + networkStatus: NetworkStatus.ready, + // will be overwritten anyways, just here for types sake + loading: false, + data: data as any, + dataState: + lastResult.dataState === "streaming" ? + "streaming" + : "complete", + }, + source: "network", + }); + } + }, }); + + return preventUnhandledRejection( + promise + .then((result) => toQueryResult(this.maskResult(result))) + .finally(() => { + subscription.unsubscribe(); + if (isCached && !wasUpdated) { + finalize(); + + const lastResult = this.getCurrentResult(); + + if (lastResult.networkStatus === NetworkStatus.streaming) { + pushNotification({ + kind: "N", + source: "network", + value: { + ...lastResult, + dataState: "complete", + networkStatus: NetworkStatus.ready, + } as any, + }); + } else { + pushNotification( + { + kind: "N", + source: "newNetworkStatus", + value: {}, + }, + { shouldEmit: EmitBehavior.force } + ); + } + } + }) + ); } // XXX the subscription variables are separate from the query variables. @@ -1131,7 +1214,7 @@ Did you mean to call refetch(variables) instead of refetch({ variables })?`, ); if (newResult) { - queryManager.cache.writeQuery({ + this.cache.writeQuery({ query: this.options.query, data: newResult, variables: this.variables, @@ -1667,8 +1750,8 @@ Did you mean to call refetch(variables) instead of refetch({ variables })?`, if ( dirty && - (this.options.fetchPolicy == "cache-only" || - this.options.fetchPolicy == "cache-and-network" || + (this.options.fetchPolicy === "cache-only" || + this.options.fetchPolicy === "cache-and-network" || !this.activeOperations.size) ) { const diff = this.getCacheDiff(); diff --git a/src/react/hooks/__tests__/useSuspenseQuery/defer20220824.test.tsx b/src/react/hooks/__tests__/useSuspenseQuery/defer20220824.test.tsx index dcf8e4a32cb..4f5aac41f24 100644 --- a/src/react/hooks/__tests__/useSuspenseQuery/defer20220824.test.tsx +++ b/src/react/hooks/__tests__/useSuspenseQuery/defer20220824.test.tsx @@ -24,7 +24,6 @@ import { markAsStreaming, mockDefer20220824, spyOnConsole, - wait, } from "@apollo/client/testing/internal"; import { offsetLimitPagination } from "@apollo/client/utilities"; import { invariant } from "@apollo/client/utilities/invariant"; @@ -1171,18 +1170,8 @@ test("incrementally renders data returned after skipping a deferred query", asyn await expect(takeRender).not.toRerender(); }); -// TODO: This test is a bit of a lie. `fetchMore` should incrementally -// rerender when using `@defer` but there is currently a bug in the core -// implementation that prevents updates until the final result is returned. -// This test reflects the behavior as it exists today, but will need -// to be updated once the core bug is fixed. -// -// NOTE: A duplicate it.failng test has been added right below this one with -// the expected behavior added in (i.e. the commented code in this test). Once -// the core bug is fixed, this test can be removed in favor of the other test. -// // https://github.com/apollographql/apollo-client/issues/11034 -test("rerenders data returned by `fetchMore` for a deferred query", async () => { +test("incrementally rerenders data returned by a `fetchMore` for a deferred query", async () => { const query = gql` query ($offset: Int) { greetings(offset: $offset) { @@ -1307,35 +1296,33 @@ test("rerenders data returned by `fetchMore` for a deferred query", async () => hasNext: true, }); - // TODO: Re-enable once the core bug is fixed - // { - // const { snapshot, renderedComponents } = await takeRender(); - // - // expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - // expect(snapshot).toStrictEqualTyped({ - // data: markAsStreaming({ - // greetings: [ - // { - // __typename: "Greeting", - // message: "Hello world", - // recipient: { - // __typename: "Person", - // name: "Alice", - // }, - // }, - // { - // __typename: "Greeting", - // message: "Goodbye", - // }, - // ], - // }), - // dataState: "streaming", - // networkStatus: NetworkStatus.streaming, - // error: undefined, - // }); - // } - - await wait(0); + { + const { snapshot, renderedComponents } = await takeRender(); + + expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); + expect(snapshot).toStrictEqualTyped({ + data: markAsStreaming({ + greetings: [ + { + __typename: "Greeting", + message: "Hello world", + recipient: { + __typename: "Person", + name: "Alice", + }, + }, + { + __typename: "Greeting", + message: "Goodbye", + }, + ], + }), + dataState: "streaming", + networkStatus: NetworkStatus.streaming, + error: undefined, + }); + } + enqueueSubsequentChunk({ incremental: [ { @@ -1397,227 +1384,6 @@ test("rerenders data returned by `fetchMore` for a deferred query", async () => await expect(takeRender).not.toRerender(); }); -// TODO: This is a duplicate of the test above, but with the expected behavior -// added (hence the `it.failing`). Remove the previous test once issue #11034 -// is fixed. -// -// https://github.com/apollographql/apollo-client/issues/11034 -it.failing( - "incrementally rerenders data returned by a `fetchMore` for a deferred query", - async () => { - const query = gql` - query ($offset: Int) { - greetings(offset: $offset) { - message - ... @defer { - recipient { - name - } - } - } - } - `; - - const { httpLink, enqueueInitialChunk, enqueueSubsequentChunk } = - mockDefer20220824(); - - const cache = new InMemoryCache({ - typePolicies: { - Query: { - fields: { - greetings: offsetLimitPagination(), - }, - }, - }, - }); - - const client = new ApolloClient({ - link: httpLink, - cache, - incrementalHandler: new Defer20220824Handler(), - }); - - using _disabledAct = disableActEnvironment(); - const { takeRender, getCurrentSnapshot } = await renderSuspenseHook( - () => useSuspenseQuery(query, { variables: { offset: 0 } }), - { - wrapper: createClientWrapper(client), - } - ); - - { - const { renderedComponents } = await takeRender(); - - expect(renderedComponents).toStrictEqual(["SuspenseFallback"]); - } - - enqueueInitialChunk({ - data: { - greetings: [{ __typename: "Greeting", message: "Hello world" }], - }, - hasNext: true, - }); - - { - const { snapshot, renderedComponents } = await takeRender(); - - expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - expect(snapshot).toStrictEqualTyped({ - data: markAsStreaming({ - greetings: [{ __typename: "Greeting", message: "Hello world" }], - }), - dataState: "streaming", - networkStatus: NetworkStatus.streaming, - error: undefined, - }); - } - - enqueueSubsequentChunk({ - incremental: [ - { - data: { - recipient: { name: "Alice", __typename: "Person" }, - }, - path: ["greetings", 0], - }, - ], - hasNext: false, - }); - - { - const { snapshot, renderedComponents } = await takeRender(); - - expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - expect(snapshot).toStrictEqualTyped({ - data: { - greetings: [ - { - __typename: "Greeting", - message: "Hello world", - recipient: { - __typename: "Person", - name: "Alice", - }, - }, - ], - }, - dataState: "complete", - networkStatus: NetworkStatus.ready, - error: undefined, - }); - } - - const fetchMorePromise = getCurrentSnapshot().fetchMore({ - variables: { offset: 1 }, - }); - - { - const { renderedComponents } = await takeRender(); - - expect(renderedComponents).toStrictEqual(["SuspenseFallback"]); - } - - enqueueInitialChunk({ - data: { - greetings: [ - { - __typename: "Greeting", - message: "Goodbye", - }, - ], - }, - hasNext: true, - }); - - { - const { snapshot, renderedComponents } = await takeRender(); - - expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - expect(snapshot).toStrictEqualTyped({ - data: markAsStreaming({ - greetings: [ - { - __typename: "Greeting", - message: "Hello world", - recipient: { - __typename: "Person", - name: "Alice", - }, - }, - { - __typename: "Greeting", - message: "Goodbye", - }, - ], - }), - dataState: "streaming", - networkStatus: NetworkStatus.streaming, - error: undefined, - }); - } - - enqueueSubsequentChunk({ - incremental: [ - { - data: { - recipient: { name: "Bob", __typename: "Person" }, - }, - path: ["greetings", 0], - }, - ], - hasNext: false, - }); - - { - const { snapshot, renderedComponents } = await takeRender(); - - expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - expect(snapshot).toStrictEqualTyped({ - data: { - greetings: [ - { - __typename: "Greeting", - message: "Hello world", - recipient: { - __typename: "Person", - name: "Alice", - }, - }, - { - __typename: "Greeting", - message: "Goodbye", - recipient: { - __typename: "Person", - name: "Bob", - }, - }, - ], - }, - dataState: "complete", - networkStatus: NetworkStatus.ready, - error: undefined, - }); - } - - await expect(fetchMorePromise!).resolves.toStrictEqualTyped({ - data: { - greetings: [ - { - __typename: "Greeting", - message: "Goodbye", - recipient: { - __typename: "Person", - name: "Bob", - }, - }, - ], - }, - }); - - await expect(takeRender).not.toRerender(); - } -); - test("throws network errors returned by deferred queries", async () => { using _consoleSpy = spyOnConsole("error"); diff --git a/src/react/hooks/__tests__/useSuspenseQuery/deferGraphQL17Alpha9.test.tsx b/src/react/hooks/__tests__/useSuspenseQuery/deferGraphQL17Alpha9.test.tsx index 51770ed06e9..f928f171c9f 100644 --- a/src/react/hooks/__tests__/useSuspenseQuery/deferGraphQL17Alpha9.test.tsx +++ b/src/react/hooks/__tests__/useSuspenseQuery/deferGraphQL17Alpha9.test.tsx @@ -24,7 +24,6 @@ import { markAsStreaming, mockDeferStreamGraphQL17Alpha9, spyOnConsole, - wait, } from "@apollo/client/testing/internal"; import { offsetLimitPagination } from "@apollo/client/utilities"; import { invariant } from "@apollo/client/utilities/invariant"; @@ -74,7 +73,7 @@ async function renderSuspenseHook< const { render, takeRender, replaceSnapshot, getCurrentRender } = createRenderStream< useSuspenseQuery.Result | { error: ErrorLike } - >(); + >({ skipNonTrackingRenders: true }); const utils = await render(, options); @@ -1196,18 +1195,8 @@ test("incrementally renders data returned after skipping a deferred query", asyn await expect(takeRender).not.toRerender(); }); -// TODO: This test is a bit of a lie. `fetchMore` should incrementally -// rerender when using `@defer` but there is currently a bug in the core -// implementation that prevents updates until the final result is returned. -// This test reflects the behavior as it exists today, but will need -// to be updated once the core bug is fixed. -// -// NOTE: A duplicate it.failng test has been added right below this one with -// the expected behavior added in (i.e. the commented code in this test). Once -// the core bug is fixed, this test can be removed in favor of the other test. -// // https://github.com/apollographql/apollo-client/issues/11034 -test("rerenders data returned by `fetchMore` for a deferred query", async () => { +test("incrementally rerenders data returned by a `fetchMore` for a deferred query", async () => { const query = gql` query ($offset: Int) { greetings(offset: $offset) { @@ -1243,9 +1232,7 @@ test("rerenders data returned by `fetchMore` for a deferred query", async () => using _disabledAct = disableActEnvironment(); const { takeRender, getCurrentSnapshot } = await renderSuspenseHook( () => useSuspenseQuery(query, { variables: { offset: 0 } }), - { - wrapper: createClientWrapper(client), - } + { wrapper: createClientWrapper(client) } ); { @@ -1335,35 +1322,33 @@ test("rerenders data returned by `fetchMore` for a deferred query", async () => hasNext: true, }); - // TODO: Re-enable once the core bug is fixed - // { - // const { snapshot, renderedComponents } = await takeRender(); - // - // expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - // expect(snapshot).toStrictEqualTyped({ - // data: markAsStreaming({ - // greetings: [ - // { - // __typename: "Greeting", - // message: "Hello world", - // recipient: { - // __typename: "Person", - // name: "Alice", - // }, - // }, - // { - // __typename: "Greeting", - // message: "Goodbye", - // }, - // ], - // }), - // dataState: "streaming", - // networkStatus: NetworkStatus.streaming, - // error: undefined, - // }); - // } - - await wait(0); + { + const { snapshot, renderedComponents } = await takeRender(); + + expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); + expect(snapshot).toStrictEqualTyped({ + data: markAsStreaming({ + greetings: [ + { + __typename: "Greeting", + message: "Hello world", + recipient: { + __typename: "Person", + name: "Alice", + }, + }, + { + __typename: "Greeting", + message: "Goodbye", + }, + ], + }), + dataState: "streaming", + networkStatus: NetworkStatus.streaming, + error: undefined, + }); + } + enqueueSubsequentChunk({ incremental: [ { @@ -1426,231 +1411,6 @@ test("rerenders data returned by `fetchMore` for a deferred query", async () => await expect(takeRender).not.toRerender(); }); -// TODO: This is a duplicate of the test above, but with the expected behavior -// added (hence the `it.failing`). Remove the previous test once issue #11034 -// is fixed. -// -// https://github.com/apollographql/apollo-client/issues/11034 -it.failing( - "incrementally rerenders data returned by a `fetchMore` for a deferred query", - async () => { - const query = gql` - query ($offset: Int) { - greetings(offset: $offset) { - message - ... @defer { - recipient { - name - } - } - } - } - `; - - const { httpLink, enqueueInitialChunk, enqueueSubsequentChunk } = - mockDeferStreamGraphQL17Alpha9(); - - const cache = new InMemoryCache({ - typePolicies: { - Query: { - fields: { - greetings: offsetLimitPagination(), - }, - }, - }, - }); - - const client = new ApolloClient({ - link: httpLink, - cache, - incrementalHandler: new GraphQL17Alpha9Handler(), - }); - - using _disabledAct = disableActEnvironment(); - const { takeRender, getCurrentSnapshot } = await renderSuspenseHook( - () => useSuspenseQuery(query, { variables: { offset: 0 } }), - { - wrapper: createClientWrapper(client), - } - ); - - { - const { renderedComponents } = await takeRender(); - - expect(renderedComponents).toStrictEqual(["SuspenseFallback"]); - } - - enqueueInitialChunk({ - data: { - greetings: [{ __typename: "Greeting", message: "Hello world" }], - }, - pending: [{ id: "0", path: ["greetings", 0] }], - hasNext: true, - }); - - { - const { snapshot, renderedComponents } = await takeRender(); - - expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - expect(snapshot).toStrictEqualTyped({ - data: markAsStreaming({ - greetings: [{ __typename: "Greeting", message: "Hello world" }], - }), - dataState: "streaming", - networkStatus: NetworkStatus.streaming, - error: undefined, - }); - } - - enqueueSubsequentChunk({ - incremental: [ - { - data: { - recipient: { name: "Alice", __typename: "Person" }, - }, - id: "0", - }, - ], - completed: [{ id: "0" }], - hasNext: false, - }); - - { - const { snapshot, renderedComponents } = await takeRender(); - - expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - expect(snapshot).toStrictEqualTyped({ - data: { - greetings: [ - { - __typename: "Greeting", - message: "Hello world", - recipient: { - __typename: "Person", - name: "Alice", - }, - }, - ], - }, - dataState: "complete", - networkStatus: NetworkStatus.ready, - error: undefined, - }); - } - - const fetchMorePromise = getCurrentSnapshot().fetchMore({ - variables: { offset: 1 }, - }); - - { - const { renderedComponents } = await takeRender(); - - expect(renderedComponents).toStrictEqual(["SuspenseFallback"]); - } - - enqueueInitialChunk({ - data: { - greetings: [ - { - __typename: "Greeting", - message: "Goodbye", - }, - ], - }, - pending: [{ id: "0", path: ["greetings", 0] }], - hasNext: true, - }); - - { - const { snapshot, renderedComponents } = await takeRender(); - - expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - expect(snapshot).toStrictEqualTyped({ - data: markAsStreaming({ - greetings: [ - { - __typename: "Greeting", - message: "Hello world", - recipient: { - __typename: "Person", - name: "Alice", - }, - }, - { - __typename: "Greeting", - message: "Goodbye", - }, - ], - }), - dataState: "streaming", - networkStatus: NetworkStatus.streaming, - error: undefined, - }); - } - - enqueueSubsequentChunk({ - incremental: [ - { - data: { - recipient: { name: "Bob", __typename: "Person" }, - }, - id: "0", - }, - ], - completed: [{ id: "0" }], - hasNext: false, - }); - - { - const { snapshot, renderedComponents } = await takeRender(); - - expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - expect(snapshot).toStrictEqualTyped({ - data: { - greetings: [ - { - __typename: "Greeting", - message: "Hello world", - recipient: { - __typename: "Person", - name: "Alice", - }, - }, - { - __typename: "Greeting", - message: "Goodbye", - recipient: { - __typename: "Person", - name: "Bob", - }, - }, - ], - }, - dataState: "complete", - networkStatus: NetworkStatus.ready, - error: undefined, - }); - } - - await expect(fetchMorePromise).resolves.toStrictEqualTyped({ - data: { - greetings: [ - { - __typename: "Greeting", - message: "Goodbye", - recipient: { - __typename: "Person", - name: "Bob", - }, - }, - ], - }, - }); - - await expect(takeRender).not.toRerender(); - } -); - test("throws network errors returned by deferred queries", async () => { using _consoleSpy = spyOnConsole("error"); diff --git a/src/react/hooks/__tests__/useSuspenseQuery/streamDefer20220824.test.tsx b/src/react/hooks/__tests__/useSuspenseQuery/streamDefer20220824.test.tsx index 4468bb2a2d2..a14e4145350 100644 --- a/src/react/hooks/__tests__/useSuspenseQuery/streamDefer20220824.test.tsx +++ b/src/react/hooks/__tests__/useSuspenseQuery/streamDefer20220824.test.tsx @@ -822,322 +822,152 @@ test("incrementally renders data returned after skipping a streamed query", asyn await expect(takeRender).not.toRerender(); }); -// TODO: This test is a bit of a lie. `fetchMore` should incrementally -// rerender when using `@stream` but there is currently a bug in the core -// implementation that prevents updates until the final result is returned. -// This test reflects the behavior as it exists today, but will need -// to be updated once the core bug is fixed. -// -// NOTE: A duplicate it.failng test has been added right below this one with -// the expected behavior added in (i.e. the commented code in this test). Once -// the core bug is fixed, this test can be removed in favor of the other test. -// // https://github.com/apollographql/apollo-client/issues/11034 -test.failing( - "rerenders data returned by `fetchMore` for a streamed query", - async () => { - let subject!: Subject; - const query = gql` - query ($offset: Int) { - friendList(offset: $offset) @stream(initialCount: 1) { - id - name - } +test("incrementally rerenders data returned by a `fetchMore` for a streamed query", async () => { + let subject!: Subject; + const query = gql` + query ($offset: Int) { + friendList(offset: $offset) @stream(initialCount: 1) { + id + name } - `; + } + `; - const cache = new InMemoryCache({ - typePolicies: { - Query: { - fields: { - greetings: offsetLimitPagination(), - }, + const cache = new InMemoryCache({ + typePolicies: { + Query: { + fields: { + friendList: offsetLimitPagination(), }, }, - }); - - const client = new ApolloClient({ - link: createLink({ - friendList: () => { - const iterator = asyncIterableSubject(); - subject = iterator.subject; - - return iterator.stream; - }, - }), - cache, - incrementalHandler: new Defer20220824Handler(), - }); - - using _disabledAct = disableActEnvironment(); - const { takeRender, getCurrentSnapshot } = await renderSuspenseHook( - () => useSuspenseQuery(query, { variables: { offset: 0 } }), - { wrapper: createClientWrapper(client) } - ); - - { - const { renderedComponents } = await takeRender(); + }, + }); - expect(renderedComponents).toStrictEqual(["SuspenseFallback"]); - } + const client = new ApolloClient({ + link: createLink({ + friendList: () => { + const iterator = asyncIterableSubject(); + subject = iterator.subject; - subject.next(friends[0]); + return iterator.stream; + }, + }), + cache, + incrementalHandler: new Defer20220824Handler(), + }); - { - const { snapshot, renderedComponents } = await takeRender(); + using _disabledAct = disableActEnvironment(); + const { takeRender, getCurrentSnapshot } = await renderSuspenseHook( + () => useSuspenseQuery(query, { variables: { offset: 0 } }), + { wrapper: createClientWrapper(client) } + ); - expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - expect(snapshot).toStrictEqualTyped({ - data: markAsStreaming({ - friendList: [{ __typename: "Friend", id: "1", name: "Luke" }], - }), - dataState: "streaming", - networkStatus: NetworkStatus.streaming, - error: undefined, - }); - } + { + const { renderedComponents } = await takeRender(); - subject.next(friends[1]); - subject.complete(); + expect(renderedComponents).toStrictEqual(["SuspenseFallback"]); + } - { - const { snapshot, renderedComponents } = await takeRender(); + subject.next(friends[0]); - expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - expect(snapshot).toStrictEqualTyped({ - data: { - friendList: [ - { __typename: "Friend", id: "1", name: "Luke" }, - { __typename: "Friend", id: "2", name: "Han" }, - ], - }, - dataState: "complete", - networkStatus: NetworkStatus.ready, - error: undefined, - }); - } + { + const { snapshot, renderedComponents } = await takeRender(); - const fetchMorePromise = getCurrentSnapshot().fetchMore({ - variables: { offset: 2 }, + expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); + expect(snapshot).toStrictEqualTyped({ + data: markAsStreaming({ + friendList: [{ __typename: "Friend", id: "1", name: "Luke" }], + }), + dataState: "streaming", + networkStatus: NetworkStatus.streaming, + error: undefined, }); + } - { - const { renderedComponents } = await takeRender(); - - expect(renderedComponents).toStrictEqual(["SuspenseFallback"]); - } - - subject.next(friends[2]); - - // TODO: Re-enable once the core bug is fixed - // { - // const { snapshot, renderedComponents } = await takeRender(); - // - // expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - // expect(snapshot).toStrictEqualTyped({ - // data: markAsStreaming({ - // friendList: [ - // { __typename: "Friend", id: "1", name: "Luke" }, - // { __typename: "Friend", id: "2", name: "Han" }, - // { __typename: "Friend", id: "3", name: "Leia" }, - // ], - // }), - // dataState: "streaming", - // networkStatus: NetworkStatus.streaming, - // error: undefined, - // }); - // } - - await wait(0); - subject.next({ id: 4, name: "Chewbacca" }); - subject.complete(); - - { - const { snapshot, renderedComponents } = await takeRender(); + subject.next(friends[1]); + subject.complete(); - expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - expect(snapshot).toStrictEqualTyped({ - data: { - friendList: [ - { __typename: "Friend", id: "1", name: "Luke" }, - { __typename: "Friend", id: "2", name: "Han" }, - { __typename: "Friend", id: "3", name: "Leia" }, - { __typename: "Friend", id: "4", name: "Chewbacca" }, - ], - }, - dataState: "complete", - networkStatus: NetworkStatus.ready, - error: undefined, - }); - } + { + const { snapshot, renderedComponents } = await takeRender(); - await expect(fetchMorePromise).resolves.toStrictEqualTyped({ + expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); + expect(snapshot).toStrictEqualTyped({ data: { friendList: [ - { __typename: "Friend", id: "3", name: "Leia" }, - { __typename: "Friend", id: "4", name: "Chewbacca" }, + { __typename: "Friend", id: "1", name: "Luke" }, + { __typename: "Friend", id: "2", name: "Han" }, ], }, + dataState: "complete", + networkStatus: NetworkStatus.ready, + error: undefined, }); - - await expect(takeRender).not.toRerender(); } -); - -// TODO: This is a duplicate of the test above, but with the expected behavior -// added (hence the `it.failing`). Remove the previous test once issue #11034 -// is fixed. -// -// https://github.com/apollographql/apollo-client/issues/11034 -test.failing( - "incrementally rerenders data returned by a `fetchMore` for a streamed query", - async () => { - let subject!: Subject; - const query = gql` - query ($offset: Int) { - friendList(offset: $offset) @stream(initialCount: 1) { - id - name - } - } - `; - - const cache = new InMemoryCache({ - typePolicies: { - Query: { - fields: { - greetings: offsetLimitPagination(), - }, - }, - }, - }); - const client = new ApolloClient({ - link: createLink({ - friendList: () => { - const iterator = asyncIterableSubject(); - subject = iterator.subject; - - return iterator.stream; - }, - }), - cache, - incrementalHandler: new Defer20220824Handler(), - }); - - using _disabledAct = disableActEnvironment(); - const { takeRender, getCurrentSnapshot } = await renderSuspenseHook( - () => useSuspenseQuery(query, { variables: { offset: 0 } }), - { wrapper: createClientWrapper(client) } - ); - - { - const { renderedComponents } = await takeRender(); - - expect(renderedComponents).toStrictEqual(["SuspenseFallback"]); - } - - subject.next(friends[0]); - - { - const { snapshot, renderedComponents } = await takeRender(); + const fetchMorePromise = getCurrentSnapshot().fetchMore({ + variables: { offset: 2 }, + }); - expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - expect(snapshot).toStrictEqualTyped({ - data: markAsStreaming({ - friendList: [{ __typename: "Friend", id: "1", name: "Luke" }], - }), - dataState: "streaming", - networkStatus: NetworkStatus.streaming, - error: undefined, - }); - } + { + const { renderedComponents } = await takeRender(); - subject.next(friends[1]); - subject.complete(); + expect(renderedComponents).toStrictEqual(["SuspenseFallback"]); + } - { - const { snapshot, renderedComponents } = await takeRender(); + subject.next(friends[2]); - expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - expect(snapshot).toStrictEqualTyped({ - data: { - friendList: [ - { __typename: "Friend", id: "1", name: "Luke" }, - { __typename: "Friend", id: "2", name: "Han" }, - ], - }, - dataState: "complete", - networkStatus: NetworkStatus.ready, - error: undefined, - }); - } + { + const { snapshot, renderedComponents } = await takeRender(); - const fetchMorePromise = getCurrentSnapshot().fetchMore({ - variables: { offset: 2 }, + expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); + expect(snapshot).toStrictEqualTyped({ + data: markAsStreaming({ + friendList: [ + { __typename: "Friend", id: "1", name: "Luke" }, + { __typename: "Friend", id: "2", name: "Han" }, + { __typename: "Friend", id: "3", name: "Leia" }, + ], + }), + dataState: "streaming", + networkStatus: NetworkStatus.streaming, + error: undefined, }); + } - { - const { renderedComponents } = await takeRender(); - - expect(renderedComponents).toStrictEqual(["SuspenseFallback"]); - } - - subject.next(friends[2]); - - { - const { snapshot, renderedComponents } = await takeRender(); - - expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - expect(snapshot).toStrictEqualTyped({ - data: markAsStreaming({ - friendList: [ - { __typename: "Friend", id: "1", name: "Luke" }, - { __typename: "Friend", id: "2", name: "Han" }, - { __typename: "Friend", id: "3", name: "Leia" }, - ], - }), - dataState: "streaming", - networkStatus: NetworkStatus.streaming, - error: undefined, - }); - } - - await wait(0); - subject.next({ id: 4, name: "Chewbacca" }); - subject.complete(); - - { - const { snapshot, renderedComponents } = await takeRender(); + subject.next({ id: 4, name: "Chewbacca" }); + subject.complete(); - expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - expect(snapshot).toStrictEqualTyped({ - data: { - friendList: [ - { __typename: "Friend", id: "1", name: "Luke" }, - { __typename: "Friend", id: "2", name: "Han" }, - { __typename: "Friend", id: "3", name: "Leia" }, - { __typename: "Friend", id: "4", name: "Chewbacca" }, - ], - }, - dataState: "complete", - networkStatus: NetworkStatus.ready, - error: undefined, - }); - } + { + const { snapshot, renderedComponents } = await takeRender(); - await expect(fetchMorePromise).resolves.toStrictEqualTyped({ + expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); + expect(snapshot).toStrictEqualTyped({ data: { friendList: [ + { __typename: "Friend", id: "1", name: "Luke" }, + { __typename: "Friend", id: "2", name: "Han" }, { __typename: "Friend", id: "3", name: "Leia" }, { __typename: "Friend", id: "4", name: "Chewbacca" }, ], }, + dataState: "complete", + networkStatus: NetworkStatus.ready, + error: undefined, }); - - await expect(takeRender).not.toRerender(); } -); + + await expect(fetchMorePromise).resolves.toStrictEqualTyped({ + data: { + friendList: [ + { __typename: "Friend", id: "3", name: "Leia" }, + { __typename: "Friend", id: "4", name: "Chewbacca" }, + ], + }, + }); + + await expect(takeRender).not.toRerender(); +}); test("throws network errors returned by streamed queries", async () => { using _consoleSpy = spyOnConsole("error"); diff --git a/src/react/hooks/__tests__/useSuspenseQuery/streamGraphQL17Alpha9.test.tsx b/src/react/hooks/__tests__/useSuspenseQuery/streamGraphQL17Alpha9.test.tsx index 791d712bd25..7a638011913 100644 --- a/src/react/hooks/__tests__/useSuspenseQuery/streamGraphQL17Alpha9.test.tsx +++ b/src/react/hooks/__tests__/useSuspenseQuery/streamGraphQL17Alpha9.test.tsx @@ -822,322 +822,152 @@ test("incrementally renders data returned after skipping a streamed query", asyn await expect(takeRender).not.toRerender(); }); -// TODO: This test is a bit of a lie. `fetchMore` should incrementally -// rerender when using `@stream` but there is currently a bug in the core -// implementation that prevents updates until the final result is returned. -// This test reflects the behavior as it exists today, but will need -// to be updated once the core bug is fixed. -// -// NOTE: A duplicate it.failng test has been added right below this one with -// the expected behavior added in (i.e. the commented code in this test). Once -// the core bug is fixed, this test can be removed in favor of the other test. -// // https://github.com/apollographql/apollo-client/issues/11034 -test.failing( - "rerenders data returned by `fetchMore` for a streamed query", - async () => { - let subject!: Subject; - const query = gql` - query ($offset: Int) { - friendList(offset: $offset) @stream(initialCount: 1) { - id - name - } +test("incrementally rerenders data returned by a `fetchMore` for a streamed query", async () => { + let subject!: Subject; + const query = gql` + query ($offset: Int) { + friendList(offset: $offset) @stream(initialCount: 1) { + id + name } - `; + } + `; - const cache = new InMemoryCache({ - typePolicies: { - Query: { - fields: { - greetings: offsetLimitPagination(), - }, + const cache = new InMemoryCache({ + typePolicies: { + Query: { + fields: { + friendList: offsetLimitPagination(), }, }, - }); - - const client = new ApolloClient({ - link: createLink({ - friendList: () => { - const iterator = asyncIterableSubject(); - subject = iterator.subject; - - return iterator.stream; - }, - }), - cache, - incrementalHandler: new GraphQL17Alpha9Handler(), - }); - - using _disabledAct = disableActEnvironment(); - const { takeRender, getCurrentSnapshot } = await renderSuspenseHook( - () => useSuspenseQuery(query, { variables: { offset: 0 } }), - { wrapper: createClientWrapper(client) } - ); - - { - const { renderedComponents } = await takeRender(); + }, + }); - expect(renderedComponents).toStrictEqual(["SuspenseFallback"]); - } + const client = new ApolloClient({ + link: createLink({ + friendList: () => { + const iterator = asyncIterableSubject(); + subject = iterator.subject; - subject.next(friends[0]); + return iterator.stream; + }, + }), + cache, + incrementalHandler: new GraphQL17Alpha9Handler(), + }); - { - const { snapshot, renderedComponents } = await takeRender(); + using _disabledAct = disableActEnvironment(); + const { takeRender, getCurrentSnapshot } = await renderSuspenseHook( + () => useSuspenseQuery(query, { variables: { offset: 0 } }), + { wrapper: createClientWrapper(client) } + ); - expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - expect(snapshot).toStrictEqualTyped({ - data: markAsStreaming({ - friendList: [{ __typename: "Friend", id: "1", name: "Luke" }], - }), - dataState: "streaming", - networkStatus: NetworkStatus.streaming, - error: undefined, - }); - } + { + const { renderedComponents } = await takeRender(); - subject.next(friends[1]); - subject.complete(); + expect(renderedComponents).toStrictEqual(["SuspenseFallback"]); + } - { - const { snapshot, renderedComponents } = await takeRender(); + subject.next(friends[0]); - expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - expect(snapshot).toStrictEqualTyped({ - data: { - friendList: [ - { __typename: "Friend", id: "1", name: "Luke" }, - { __typename: "Friend", id: "2", name: "Han" }, - ], - }, - dataState: "complete", - networkStatus: NetworkStatus.ready, - error: undefined, - }); - } + { + const { snapshot, renderedComponents } = await takeRender(); - const fetchMorePromise = getCurrentSnapshot().fetchMore({ - variables: { offset: 2 }, + expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); + expect(snapshot).toStrictEqualTyped({ + data: markAsStreaming({ + friendList: [{ __typename: "Friend", id: "1", name: "Luke" }], + }), + dataState: "streaming", + networkStatus: NetworkStatus.streaming, + error: undefined, }); + } - { - const { renderedComponents } = await takeRender(); - - expect(renderedComponents).toStrictEqual(["SuspenseFallback"]); - } - - subject.next(friends[2]); - - // TODO: Re-enable once the core bug is fixed - // { - // const { snapshot, renderedComponents } = await takeRender(); - // - // expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - // expect(snapshot).toStrictEqualTyped({ - // data: markAsStreaming({ - // friendList: [ - // { __typename: "Friend", id: "1", name: "Luke" }, - // { __typename: "Friend", id: "2", name: "Han" }, - // { __typename: "Friend", id: "3", name: "Leia" }, - // ], - // }), - // dataState: "streaming", - // networkStatus: NetworkStatus.streaming, - // error: undefined, - // }); - // } - - await wait(0); - subject.next({ id: 4, name: "Chewbacca" }); - subject.complete(); - - { - const { snapshot, renderedComponents } = await takeRender(); + subject.next(friends[1]); + subject.complete(); - expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - expect(snapshot).toStrictEqualTyped({ - data: { - friendList: [ - { __typename: "Friend", id: "1", name: "Luke" }, - { __typename: "Friend", id: "2", name: "Han" }, - { __typename: "Friend", id: "3", name: "Leia" }, - { __typename: "Friend", id: "4", name: "Chewbacca" }, - ], - }, - dataState: "complete", - networkStatus: NetworkStatus.ready, - error: undefined, - }); - } + { + const { snapshot, renderedComponents } = await takeRender(); - await expect(fetchMorePromise).resolves.toStrictEqualTyped({ + expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); + expect(snapshot).toStrictEqualTyped({ data: { friendList: [ - { __typename: "Friend", id: "3", name: "Leia" }, - { __typename: "Friend", id: "4", name: "Chewbacca" }, + { __typename: "Friend", id: "1", name: "Luke" }, + { __typename: "Friend", id: "2", name: "Han" }, ], }, + dataState: "complete", + networkStatus: NetworkStatus.ready, + error: undefined, }); - - await expect(takeRender).not.toRerender(); } -); - -// TODO: This is a duplicate of the test above, but with the expected behavior -// added (hence the `it.failing`). Remove the previous test once issue #11034 -// is fixed. -// -// https://github.com/apollographql/apollo-client/issues/11034 -test.failing( - "incrementally rerenders data returned by a `fetchMore` for a streamed query", - async () => { - let subject!: Subject; - const query = gql` - query ($offset: Int) { - friendList(offset: $offset) @stream(initialCount: 1) { - id - name - } - } - `; - - const cache = new InMemoryCache({ - typePolicies: { - Query: { - fields: { - greetings: offsetLimitPagination(), - }, - }, - }, - }); - const client = new ApolloClient({ - link: createLink({ - friendList: () => { - const iterator = asyncIterableSubject(); - subject = iterator.subject; - - return iterator.stream; - }, - }), - cache, - incrementalHandler: new GraphQL17Alpha9Handler(), - }); - - using _disabledAct = disableActEnvironment(); - const { takeRender, getCurrentSnapshot } = await renderSuspenseHook( - () => useSuspenseQuery(query, { variables: { offset: 0 } }), - { wrapper: createClientWrapper(client) } - ); - - { - const { renderedComponents } = await takeRender(); - - expect(renderedComponents).toStrictEqual(["SuspenseFallback"]); - } - - subject.next(friends[0]); - - { - const { snapshot, renderedComponents } = await takeRender(); + const fetchMorePromise = getCurrentSnapshot().fetchMore({ + variables: { offset: 2 }, + }); - expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - expect(snapshot).toStrictEqualTyped({ - data: markAsStreaming({ - friendList: [{ __typename: "Friend", id: "1", name: "Luke" }], - }), - dataState: "streaming", - networkStatus: NetworkStatus.streaming, - error: undefined, - }); - } + { + const { renderedComponents } = await takeRender(); - subject.next(friends[1]); - subject.complete(); + expect(renderedComponents).toStrictEqual(["SuspenseFallback"]); + } - { - const { snapshot, renderedComponents } = await takeRender(); + subject.next(friends[2]); - expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - expect(snapshot).toStrictEqualTyped({ - data: { - friendList: [ - { __typename: "Friend", id: "1", name: "Luke" }, - { __typename: "Friend", id: "2", name: "Han" }, - ], - }, - dataState: "complete", - networkStatus: NetworkStatus.ready, - error: undefined, - }); - } + { + const { snapshot, renderedComponents } = await takeRender(); - const fetchMorePromise = getCurrentSnapshot().fetchMore({ - variables: { offset: 2 }, + expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); + expect(snapshot).toStrictEqualTyped({ + data: markAsStreaming({ + friendList: [ + { __typename: "Friend", id: "1", name: "Luke" }, + { __typename: "Friend", id: "2", name: "Han" }, + { __typename: "Friend", id: "3", name: "Leia" }, + ], + }), + dataState: "streaming", + networkStatus: NetworkStatus.streaming, + error: undefined, }); + } - { - const { renderedComponents } = await takeRender(); - - expect(renderedComponents).toStrictEqual(["SuspenseFallback"]); - } - - subject.next(friends[2]); - - { - const { snapshot, renderedComponents } = await takeRender(); - - expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - expect(snapshot).toStrictEqualTyped({ - data: markAsStreaming({ - friendList: [ - { __typename: "Friend", id: "1", name: "Luke" }, - { __typename: "Friend", id: "2", name: "Han" }, - { __typename: "Friend", id: "3", name: "Leia" }, - ], - }), - dataState: "streaming", - networkStatus: NetworkStatus.streaming, - error: undefined, - }); - } - - await wait(0); - subject.next({ id: 4, name: "Chewbacca" }); - subject.complete(); - - { - const { snapshot, renderedComponents } = await takeRender(); + subject.next({ id: 4, name: "Chewbacca" }); + subject.complete(); - expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); - expect(snapshot).toStrictEqualTyped({ - data: { - friendList: [ - { __typename: "Friend", id: "1", name: "Luke" }, - { __typename: "Friend", id: "2", name: "Han" }, - { __typename: "Friend", id: "3", name: "Leia" }, - { __typename: "Friend", id: "4", name: "Chewbacca" }, - ], - }, - dataState: "complete", - networkStatus: NetworkStatus.ready, - error: undefined, - }); - } + { + const { snapshot, renderedComponents } = await takeRender(); - await expect(fetchMorePromise).resolves.toStrictEqualTyped({ + expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]); + expect(snapshot).toStrictEqualTyped({ data: { friendList: [ + { __typename: "Friend", id: "1", name: "Luke" }, + { __typename: "Friend", id: "2", name: "Han" }, { __typename: "Friend", id: "3", name: "Leia" }, { __typename: "Friend", id: "4", name: "Chewbacca" }, ], }, + dataState: "complete", + networkStatus: NetworkStatus.ready, + error: undefined, }); - - await expect(takeRender).not.toRerender(); } -); + + await expect(fetchMorePromise).resolves.toStrictEqualTyped({ + data: { + friendList: [ + { __typename: "Friend", id: "3", name: "Leia" }, + { __typename: "Friend", id: "4", name: "Chewbacca" }, + ], + }, + }); + + await expect(takeRender).not.toRerender(); +}); test("throws network errors returned by streamed queries", async () => { using _consoleSpy = spyOnConsole("error");