diff --git a/CHANGELOG.md b/CHANGELOG.md index 22703722a64..d1c7a799ffd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -209,6 +209,9 @@ - Support passing a `context` object through the link execution chain when using subscriptions.
[@sgtpepper43](https://github.com/sgtpepper43) in [#4925](https://github.com/apollographql/apollo-client/pull/4925) +- `MockSubscriptionLink` now supports multiple subscriptions.
+ [@dfrankland](https://github.com/dfrankland) in [#6081](https://github.com/apollographql/apollo-client/pull/6081) + ### Bug Fixes - `useMutation` adjustments to help avoid an infinite loop / too many renders issue, caused by unintentionally modifying the `useState` based mutation result directly.
diff --git a/src/utilities/testing/mocking/__tests__/mockSubscriptionLink.test.tsx b/src/utilities/testing/mocking/__tests__/mockSubscriptionLink.test.tsx new file mode 100644 index 00000000000..eeaa8d1e2f9 --- /dev/null +++ b/src/utilities/testing/mocking/__tests__/mockSubscriptionLink.test.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { render, wait } from '@testing-library/react'; +import gql from 'graphql-tag'; + +import { MockSubscriptionLink } from '../mockSubscriptionLink'; +import { ApolloClient } from '../../../../ApolloClient'; +import { InMemoryCache as Cache } from '../../../../cache/inmemory/inMemoryCache'; +import { ApolloProvider } from '../../../../react/context/ApolloProvider'; +import { useSubscription } from '../../../../react/hooks/useSubscription'; + +describe('mockSubscriptionLink', () => { + it('should work with multiple subscribers to the same mock websocket', () => { + const subscription = gql` + subscription { + car { + make + } + } + `; + + const link = new MockSubscriptionLink(); + const client = new ApolloClient({ + link, + cache: new Cache({ addTypename: false }) + }); + + let renderCountA = 0; + const ComponentA = () => { + useSubscription(subscription); + renderCountA += 1; + return null; + }; + + let renderCountB = 0; + const ComponentB = () => { + useSubscription(subscription); + renderCountB += 1; + return null; + }; + + const results = ['Audi', 'BMW', 'Mercedes', 'Hyundai'].map(make => ({ + result: { data: { car: { make } } } + })); + + const Component = () => { + const [index, setIndex] = React.useState(0); + React.useEffect(() => { + if (index >= results.length) return; + link.simulateResult(results[index]); + setIndex(index + 1); + }, [index]); + return null; + }; + + render( + +
+ + + +
+
+ ); + + return wait(() => { + expect(renderCountA).toBe(results.length + 1); + expect(renderCountB).toBe(results.length + 1); + }, { timeout: 1000 }); + }); +}); diff --git a/src/utilities/testing/mocking/mockSubscriptionLink.ts b/src/utilities/testing/mocking/mockSubscriptionLink.ts index 78956933501..642499db788 100644 --- a/src/utilities/testing/mocking/mockSubscriptionLink.ts +++ b/src/utilities/testing/mocking/mockSubscriptionLink.ts @@ -17,7 +17,7 @@ export class MockSubscriptionLink extends ApolloLink { public setups: any[] = []; public operation: Operation; - private observer: any; + private observers: any[] = []; constructor() { super(); @@ -27,7 +27,7 @@ export class MockSubscriptionLink extends ApolloLink { this.operation = operation; return new Observable(observer => { this.setups.forEach(x => x()); - this.observer = observer; + this.observers.push(observer); return () => { this.unsubscribers.forEach(x => x()); }; @@ -36,18 +36,22 @@ export class MockSubscriptionLink extends ApolloLink { public simulateResult(result: MockedSubscriptionResult, complete = false) { setTimeout(() => { - const { observer } = this; - if (!observer) throw new Error('subscription torn down'); - if (complete && observer.complete) observer.complete(); - if (result.result && observer.next) observer.next(result.result); - if (result.error && observer.error) observer.error(result.error); + const { observers } = this; + if (!observers.length) throw new Error('subscription torn down'); + observers.forEach(observer => { + if (complete && observer.complete) observer.complete(); + if (result.result && observer.next) observer.next(result.result); + if (result.error && observer.error) observer.error(result.error); + }); }, result.delay || 0); } public simulateComplete() { - const { observer } = this; - if (!observer) throw new Error('subscription torn down'); - if (observer.complete) observer.complete(); + const { observers } = this; + if (!observers.length) throw new Error('subscription torn down'); + observers.forEach(observer => { + if (observer.complete) observer.complete(); + }) } public onSetup(listener: any): void {