-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add ignoreResults
option to useSubscription
#11921
Changes from all commits
b9d8137
8d07bbc
513c1e7
45483fc
482117a
d29b33c
2a7f228
d7afc64
cc41b89
6d651c5
03c9739
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@apollo/client": patch | ||
--- | ||
|
||
add `ignoreResults` option to `useSubscription` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
{ | ||
"dist/apollo-client.min.cjs": 39971, | ||
"dist/apollo-client.min.cjs": 40015, | ||
"import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32903 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1455,6 +1455,297 @@ describe("`restart` callback", () => { | |
}); | ||
}); | ||
|
||
describe("ignoreResults", () => { | ||
const subscription = gql` | ||
subscription { | ||
car { | ||
make | ||
} | ||
} | ||
`; | ||
|
||
const results = ["Audi", "BMW"].map((make) => ({ | ||
result: { data: { car: { make } } }, | ||
})); | ||
|
||
it("should not rerender when ignoreResults is true, but will call `onData` and `onComplete`", async () => { | ||
const link = new MockSubscriptionLink(); | ||
const client = new ApolloClient({ | ||
link, | ||
cache: new Cache({ addTypename: false }), | ||
}); | ||
|
||
const onData = jest.fn((() => {}) as SubscriptionHookOptions["onData"]); | ||
const onError = jest.fn((() => {}) as SubscriptionHookOptions["onError"]); | ||
const onComplete = jest.fn( | ||
(() => {}) as SubscriptionHookOptions["onComplete"] | ||
); | ||
const ProfiledHook = profileHook(() => | ||
useSubscription(subscription, { | ||
ignoreResults: true, | ||
onData, | ||
onError, | ||
onComplete, | ||
}) | ||
); | ||
render(<ProfiledHook />, { | ||
wrapper: ({ children }) => ( | ||
<ApolloProvider client={client}>{children}</ApolloProvider> | ||
), | ||
}); | ||
|
||
const snapshot = await ProfiledHook.takeSnapshot(); | ||
expect(snapshot).toStrictEqual({ | ||
loading: false, | ||
error: undefined, | ||
data: undefined, | ||
variables: undefined, | ||
restart: expect.any(Function), | ||
}); | ||
link.simulateResult(results[0]); | ||
|
||
await waitFor(() => { | ||
expect(onData).toHaveBeenCalledTimes(1); | ||
expect(onData).toHaveBeenLastCalledWith( | ||
expect.objectContaining({ | ||
data: { | ||
data: results[0].result.data, | ||
error: undefined, | ||
loading: false, | ||
variables: undefined, | ||
}, | ||
}) | ||
); | ||
expect(onError).toHaveBeenCalledTimes(0); | ||
expect(onComplete).toHaveBeenCalledTimes(0); | ||
}); | ||
|
||
link.simulateResult(results[1], true); | ||
await waitFor(() => { | ||
expect(onData).toHaveBeenCalledTimes(2); | ||
expect(onData).toHaveBeenLastCalledWith( | ||
expect.objectContaining({ | ||
data: { | ||
data: results[1].result.data, | ||
error: undefined, | ||
loading: false, | ||
variables: undefined, | ||
}, | ||
}) | ||
); | ||
expect(onError).toHaveBeenCalledTimes(0); | ||
expect(onComplete).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
await expect(ProfiledHook).not.toRerender(); | ||
}); | ||
|
||
it("should not rerender when ignoreResults is true and an error occurs", async () => { | ||
const link = new MockSubscriptionLink(); | ||
const client = new ApolloClient({ | ||
link, | ||
cache: new Cache({ addTypename: false }), | ||
}); | ||
|
||
const onData = jest.fn((() => {}) as SubscriptionHookOptions["onData"]); | ||
const onError = jest.fn((() => {}) as SubscriptionHookOptions["onError"]); | ||
const onComplete = jest.fn( | ||
(() => {}) as SubscriptionHookOptions["onComplete"] | ||
); | ||
const ProfiledHook = profileHook(() => | ||
useSubscription(subscription, { | ||
ignoreResults: true, | ||
onData, | ||
onError, | ||
onComplete, | ||
}) | ||
); | ||
render(<ProfiledHook />, { | ||
wrapper: ({ children }) => ( | ||
<ApolloProvider client={client}>{children}</ApolloProvider> | ||
), | ||
}); | ||
|
||
const snapshot = await ProfiledHook.takeSnapshot(); | ||
expect(snapshot).toStrictEqual({ | ||
loading: false, | ||
error: undefined, | ||
data: undefined, | ||
variables: undefined, | ||
restart: expect.any(Function), | ||
}); | ||
link.simulateResult(results[0]); | ||
|
||
await waitFor(() => { | ||
expect(onData).toHaveBeenCalledTimes(1); | ||
expect(onData).toHaveBeenLastCalledWith( | ||
expect.objectContaining({ | ||
data: { | ||
data: results[0].result.data, | ||
error: undefined, | ||
loading: false, | ||
variables: undefined, | ||
}, | ||
}) | ||
); | ||
expect(onError).toHaveBeenCalledTimes(0); | ||
expect(onComplete).toHaveBeenCalledTimes(0); | ||
}); | ||
|
||
const error = new Error("test"); | ||
link.simulateResult({ error }); | ||
await waitFor(() => { | ||
expect(onData).toHaveBeenCalledTimes(1); | ||
expect(onError).toHaveBeenCalledTimes(1); | ||
expect(onError).toHaveBeenLastCalledWith(error); | ||
expect(onComplete).toHaveBeenCalledTimes(0); | ||
}); | ||
|
||
await expect(ProfiledHook).not.toRerender(); | ||
}); | ||
|
||
it("can switch from `ignoreResults: true` to `ignoreResults: false` and will start rerendering, without creating a new subscription", async () => { | ||
const subscriptionCreated = jest.fn(); | ||
const link = new MockSubscriptionLink(); | ||
link.onSetup(subscriptionCreated); | ||
const client = new ApolloClient({ | ||
link, | ||
cache: new Cache({ addTypename: false }), | ||
}); | ||
|
||
const onData = jest.fn((() => {}) as SubscriptionHookOptions["onData"]); | ||
const ProfiledHook = profileHook( | ||
({ ignoreResults }: { ignoreResults: boolean }) => | ||
useSubscription(subscription, { | ||
ignoreResults, | ||
onData, | ||
}) | ||
); | ||
const { rerender } = render(<ProfiledHook ignoreResults={true} />, { | ||
wrapper: ({ children }) => ( | ||
<ApolloProvider client={client}>{children}</ApolloProvider> | ||
), | ||
}); | ||
expect(subscriptionCreated).toHaveBeenCalledTimes(1); | ||
|
||
{ | ||
const snapshot = await ProfiledHook.takeSnapshot(); | ||
expect(snapshot).toStrictEqual({ | ||
loading: false, | ||
error: undefined, | ||
data: undefined, | ||
variables: undefined, | ||
restart: expect.any(Function), | ||
}); | ||
expect(onData).toHaveBeenCalledTimes(0); | ||
} | ||
link.simulateResult(results[0]); | ||
await expect(ProfiledHook).not.toRerender({ timeout: 20 }); | ||
expect(onData).toHaveBeenCalledTimes(1); | ||
|
||
rerender(<ProfiledHook ignoreResults={false} />); | ||
{ | ||
const snapshot = await ProfiledHook.takeSnapshot(); | ||
expect(snapshot).toStrictEqual({ | ||
loading: false, | ||
error: undefined, | ||
// `data` appears immediately after changing to `ignoreResults: false` | ||
data: results[0].result.data, | ||
variables: undefined, | ||
restart: expect.any(Function), | ||
}); | ||
// `onData` should not be called again for the same result | ||
expect(onData).toHaveBeenCalledTimes(1); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you perhaps add one this assertion before the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} | ||
|
||
link.simulateResult(results[1]); | ||
{ | ||
const snapshot = await ProfiledHook.takeSnapshot(); | ||
expect(snapshot).toStrictEqual({ | ||
loading: false, | ||
error: undefined, | ||
data: results[1].result.data, | ||
variables: undefined, | ||
restart: expect.any(Function), | ||
}); | ||
expect(onData).toHaveBeenCalledTimes(2); | ||
} | ||
// a second subscription should not have been started | ||
expect(subscriptionCreated).toHaveBeenCalledTimes(1); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👨🍳 💋 |
||
}); | ||
it("can switch from `ignoreResults: false` to `ignoreResults: true` and will stop rerendering, without creating a new subscription", async () => { | ||
const subscriptionCreated = jest.fn(); | ||
const link = new MockSubscriptionLink(); | ||
link.onSetup(subscriptionCreated); | ||
const client = new ApolloClient({ | ||
link, | ||
cache: new Cache({ addTypename: false }), | ||
}); | ||
|
||
const onData = jest.fn((() => {}) as SubscriptionHookOptions["onData"]); | ||
const ProfiledHook = profileHook( | ||
({ ignoreResults }: { ignoreResults: boolean }) => | ||
useSubscription(subscription, { | ||
ignoreResults, | ||
onData, | ||
}) | ||
); | ||
const { rerender } = render(<ProfiledHook ignoreResults={false} />, { | ||
wrapper: ({ children }) => ( | ||
<ApolloProvider client={client}>{children}</ApolloProvider> | ||
), | ||
}); | ||
expect(subscriptionCreated).toHaveBeenCalledTimes(1); | ||
|
||
{ | ||
const snapshot = await ProfiledHook.takeSnapshot(); | ||
expect(snapshot).toStrictEqual({ | ||
loading: true, | ||
error: undefined, | ||
data: undefined, | ||
variables: undefined, | ||
restart: expect.any(Function), | ||
}); | ||
expect(onData).toHaveBeenCalledTimes(0); | ||
} | ||
link.simulateResult(results[0]); | ||
{ | ||
const snapshot = await ProfiledHook.takeSnapshot(); | ||
expect(snapshot).toStrictEqual({ | ||
loading: false, | ||
error: undefined, | ||
data: results[0].result.data, | ||
variables: undefined, | ||
restart: expect.any(Function), | ||
}); | ||
expect(onData).toHaveBeenCalledTimes(1); | ||
} | ||
await expect(ProfiledHook).not.toRerender({ timeout: 20 }); | ||
|
||
rerender(<ProfiledHook ignoreResults={true} />); | ||
{ | ||
const snapshot = await ProfiledHook.takeSnapshot(); | ||
expect(snapshot).toStrictEqual({ | ||
loading: false, | ||
error: undefined, | ||
// switching back to the default `ignoreResults: true` return value | ||
data: undefined, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we return FWIW I think this is reasonable behavior since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
variables: undefined, | ||
restart: expect.any(Function), | ||
}); | ||
// `onData` should not be called again | ||
expect(onData).toHaveBeenCalledTimes(1); | ||
} | ||
|
||
link.simulateResult(results[1]); | ||
await expect(ProfiledHook).not.toRerender({ timeout: 20 }); | ||
expect(onData).toHaveBeenCalledTimes(2); | ||
|
||
// a second subscription should not have been started | ||
expect(subscriptionCreated).toHaveBeenCalledTimes(1); | ||
}); | ||
}); | ||
|
||
describe.skip("Type Tests", () => { | ||
test("NoInfer prevents adding arbitrary additional variables", () => { | ||
const typedNode = {} as TypedDocumentNode<{ foo: string }, { bar: number }>; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity, are these casts needed? I'm able to remove this entirely and see no type errors. If you're using this to convey intent, feel free to ignore this comment.
(this suggestion applies to any place with these mocks, but I'll avoid adding this comment to the other locations)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's for test-writing DX. If you hover
onData
further down in the test to get a feeling what you should assert on, you either getany
, or with this the right type.