From 56731e34ccd9fb247a2f781d182675b0bcb6bbbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20G=C5=82ownia?= Date: Wed, 8 Feb 2023 18:35:10 +0100 Subject: [PATCH] chore(react-helpers): add generic data provider unit test (#2776) --- .../src/lib/generic-data-provider.spec.ts | 89 ++++++++++++++----- .../src/lib/generic-data-provider.ts | 18 ++-- 2 files changed, 78 insertions(+), 29 deletions(-) diff --git a/libs/react-helpers/src/lib/generic-data-provider.spec.ts b/libs/react-helpers/src/lib/generic-data-provider.spec.ts index 820da6129..96c6f9e58 100644 --- a/libs/react-helpers/src/lib/generic-data-provider.spec.ts +++ b/libs/react-helpers/src/lib/generic-data-provider.spec.ts @@ -19,7 +19,10 @@ import type { OperationVariables, ApolloQueryResult, QueryOptions, + ApolloError, } from '@apollo/client'; +import { GraphQLError } from 'graphql'; + import type { Subscription, Observable } from 'zen-observable-ts'; type Item = { @@ -58,12 +61,16 @@ const query: Query = { }; const subscriptionQuery: Query = query; +const getData = jest.fn((r: QueryData | null) => r?.data || null); + +const getDelta = jest.fn((r: SubscriptionData) => r.data); + const subscribe = makeDataProvider({ query, subscriptionQuery, update, - getData: (r) => r?.data || null, - getDelta: (r) => r.data, + getData, + getDelta, }); const combineData = jest.fn< @@ -91,8 +98,8 @@ const paginatedSubscribe = makeDataProvider< query, subscriptionQuery, update, - getData: (r) => r?.data || null, - getDelta: (r) => r.data, + getData, + getDelta, pagination: { first, append: defaultAppend, @@ -186,9 +193,20 @@ const clearPendingQueries = () => { }; describe('data provider', () => { + beforeEach(() => { + clearPendingQueries(); + callback.mockClear(); + getData.mockClear(); + clientQuery.mockClear(); + clientSubscribeUnsubscribe.mockClear(); + clientSubscribeSubscribe.mockClear(); + }); it('memoize instance and unsubscribe if no subscribers', () => { - const subscription1 = subscribe(jest.fn(), client); - const subscription2 = subscribe(jest.fn(), client); + const variables = { var: 'val' }; + const subscription1 = subscribe(jest.fn(), client, variables); + const subscription2 = subscribe(jest.fn(), client, { ...variables }); + // const subscription1 = subscribe(jest.fn(), client); + // const subscription2 = subscribe(jest.fn(), client); expect(clientSubscribeSubscribe.mock.calls.length).toEqual(1); subscription1.unsubscribe(); expect(clientSubscribeUnsubscribe.mock.calls.length).toEqual(0); @@ -197,7 +215,6 @@ describe('data provider', () => { }); it('calls callback before and after initial fetch', async () => { - callback.mockClear(); const data: Item[] = []; const subscription = subscribe(callback, client); expect(callback.mock.calls.length).toBe(1); @@ -210,6 +227,49 @@ describe('data provider', () => { subscription.unsubscribe(); }); + it('calls callback on error', async () => { + const subscription = subscribe(callback, client); + expect(callback.mock.calls.length).toBe(1); + expect(callback.mock.calls[0][0].data).toBe(null); + expect(callback.mock.calls[0][0].loading).toBe(true); + const error = new Error('Rejected by unit test'); + await rejectQuery(error); + expect(getData).not.toBeCalled(); + expect(callback.mock.calls.length).toBe(2); + expect(callback.mock.calls[1][0].error).toEqual(error); + expect(callback.mock.calls[1][0].loading).toBe(false); + subscription.unsubscribe(); + }); + + it('calls successful callback on NotFound error on party path', async () => { + const subscription = subscribe(callback, client); + expect(callback.mock.calls.length).toBe(1); + expect(callback.mock.calls[0][0].data).toBe(null); + expect(callback.mock.calls[0][0].loading).toBe(true); + const error = new Error() as ApolloError; + const graphQLError = new GraphQLError( + '', + undefined, + undefined, + undefined, + ['party'], + undefined, + { + type: 'NotFound', + } + ); + error.graphQLErrors = [graphQLError]; + const data: Data = []; + getData.mockReturnValueOnce(data); + await rejectQuery(error); + expect(getData).toHaveBeenCalledWith(null, undefined); + expect(callback.mock.calls.length).toBe(2); + expect(callback.mock.calls[1][0].data).toEqual(data); + expect(callback.mock.calls[1][0].error).toEqual(undefined); + expect(callback.mock.calls[1][0].loading).toBe(false); + subscription.unsubscribe(); + }); + it('calls update and callback on each update', async () => { const data: Item[] = []; const subscription = subscribe(callback, client); @@ -228,8 +288,7 @@ describe('data provider', () => { subscription.unsubscribe(); }); - it("don't calls callback on update if data doesn't", async () => { - callback.mockClear(); + it("don't calls callback on update if data doesn't change", async () => { const data: Item[] = []; const subscription = subscribe(callback, client); await resolveQuery({ data }); @@ -247,10 +306,6 @@ describe('data provider', () => { }); it('refetch data on reload', async () => { - clearPendingQueries(); - clientQuery.mockClear(); - clientSubscribeUnsubscribe.mockClear(); - clientSubscribeSubscribe.mockClear(); const data: Item[] = []; const subscription = subscribe(callback, client); await resolveQuery({ data }); @@ -263,9 +318,6 @@ describe('data provider', () => { }); it('refetch data and restart subscription on reload with force', async () => { - clientQuery.mockClear(); - clientSubscribeUnsubscribe.mockClear(); - clientSubscribeSubscribe.mockClear(); const data: Item[] = []; const subscription = subscribe(callback, client); await resolveQuery({ data }); @@ -278,7 +330,6 @@ describe('data provider', () => { }); it('calls callback on flush', async () => { - callback.mockClear(); const data: Item[] = []; const subscription = subscribe(callback, client); await resolveQuery({ data }); @@ -289,8 +340,6 @@ describe('data provider', () => { }); it('fills data with nulls if pagination is enabled', async () => { - callback.mockClear(); - clearPendingQueries(); const totalCount = 1000; const data: Item[] = new Array(first).fill(null).map((v, i) => ({ cursor: i.toString(), @@ -311,7 +360,6 @@ describe('data provider', () => { }); it('loads requested data blocks and inserts data with total count', async () => { - callback.mockClear(); const totalCount = 1000; const subscription = paginatedSubscribe(callback, client); await resolveQuery({ @@ -436,7 +484,6 @@ describe('data provider', () => { }); it('loads requested data blocks and inserts data without totalCount', async () => { - callback.mockClear(); const totalCount = undefined; const subscription = paginatedSubscribe(callback, client); await resolveQuery({ diff --git a/libs/react-helpers/src/lib/generic-data-provider.ts b/libs/react-helpers/src/lib/generic-data-provider.ts index fb0f84c3d..8813c6c8d 100644 --- a/libs/react-helpers/src/lib/generic-data-provider.ts +++ b/libs/react-helpers/src/lib/generic-data-provider.ts @@ -407,6 +407,15 @@ function makeDataProviderInternal< } }; + const onError = (e: Error) => { + error = e; + if (subscription) { + subscription.unsubscribe(); + subscription = undefined; + } + notifyAll(); + }; + const initialize = async () => { if (subscription) { if (resetTimer) { @@ -427,14 +436,7 @@ function makeDataProviderInternal< variables, fetchPolicy, }) - .subscribe(onNext, (e) => { - error = e as Error; - if (subscription) { - subscription.unsubscribe(); - subscription = undefined; - } - notifyAll(); - }); + .subscribe(onNext, onError); } await initialFetch(); };