From 9a8f36bfe4208146ca26bd05d2e624a7802337ab Mon Sep 17 00:00:00 2001 From: Swain Molster Date: Fri, 1 Dec 2023 11:32:54 -0500 Subject: [PATCH] fix: invalidate activities list when consent is accepted --- src/hooks/useActivities.tsx | 16 +++---- src/hooks/useConsent.test.tsx | 18 ++++---- src/hooks/useConsent.ts | 68 ++++++++++++------------------ src/screens/ConsentScreen.test.tsx | 43 ++++++++++--------- src/screens/ConsentScreen.tsx | 8 ++-- 5 files changed, 68 insertions(+), 85 deletions(-) diff --git a/src/hooks/useActivities.tsx b/src/hooks/useActivities.tsx index d66665ae..ee821f1d 100644 --- a/src/hooks/useActivities.tsx +++ b/src/hooks/useActivities.tsx @@ -1,4 +1,3 @@ -import { useCallback } from 'react'; import { gql } from 'graphql-request'; import { useGraphQLClient } from './useGraphQLClient'; import { useQuery } from '@tanstack/react-query'; @@ -364,15 +363,14 @@ type ActivitiesQueryResponse = { }; }; +export const ACTIVITIES_QUERY_KEY = ['activities']; + export const useActivities = (input: ActivitiesInput) => { const { graphQLClient } = useGraphQLClient(); - const queryForActivities = useCallback(async () => { - return graphQLClient.request( - getActivitiesQueryDocument, - { input }, - ); - }, [graphQLClient, input]); - - return useQuery(['activities'], queryForActivities); + return useQuery(ACTIVITIES_QUERY_KEY, () => + graphQLClient.request(getActivitiesQueryDocument, { + input, + }), + ); }; diff --git a/src/hooks/useConsent.test.tsx b/src/hooks/useConsent.test.tsx index 6b152465..3bd72ef4 100644 --- a/src/hooks/useConsent.test.tsx +++ b/src/hooks/useConsent.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { renderHook, waitFor, act } from '@testing-library/react-native'; -import { useConsent } from './useConsent'; +import { createConsentPatch, useConsent } from './useConsent'; import { useHttpClient } from './useHttpClient'; import MockAdapter from 'axios-mock-adapter'; import axios from 'axios'; @@ -25,7 +25,7 @@ jest.mock('./useHttpClient', () => ({ const useActiveProjectMock = useActiveProject as jest.Mock; const useHttpClientMock = useHttpClient as jest.Mock; -const renderHookInContext = (useHook: Function) => { +const renderHookInContext = (useHook: () => T) => { return renderHook(() => useHook(), { wrapper: ({ children }) => ( {children} @@ -87,10 +87,9 @@ describe('useUpdateProjectConsentDirective', () => { const { result } = renderHookInContext(useTestHook); await act(async () => { - await result.current.mutateAsync({ - directiveId: 'directive-id', - accept: true, - }); + await result.current.mutateAsync( + createConsentPatch('directive-id', true), + ); }); expect(onSuccess).toHaveBeenCalledTimes(1); @@ -127,10 +126,9 @@ describe('useUpdateProjectConsentDirective', () => { const { result } = renderHookInContext(useTestHook); await act(async () => { - await result.current.mutateAsync({ - directiveId: 'directive-id', - accept: false, - }); + await result.current.mutateAsync( + createConsentPatch('directive-id', false), + ); }); expect(axiosMock.history.patch[0].url).toBe( diff --git a/src/hooks/useConsent.ts b/src/hooks/useConsent.ts index 18e3b84b..68753967 100644 --- a/src/hooks/useConsent.ts +++ b/src/hooks/useConsent.ts @@ -2,11 +2,32 @@ import { useActiveProject } from './useActiveProject'; import { Consent, Questionnaire } from 'fhir/r3'; import { useRestQuery, useRestMutation } from './rest-api'; import { RestAPIEndpoints } from '../types/rest-types'; +import { RequestPayloadOf } from '@lifeomic/one-query'; +import { useQueryClient } from '@tanstack/react-query'; +import { ACTIVITIES_QUERY_KEY } from './useActivities'; type PatchConsentDirectives = RestAPIEndpoints['PATCH /v1/consent/directives/me/:directiveId']; +export const createConsentPatch = ( + directiveId: string, + accept: boolean, +): RequestPayloadOf< + RestAPIEndpoints, + 'PATCH /v1/consent/directives/me/:directiveId' +> => ({ + directiveId, + status: accept ? 'active' : 'rejected', + response: { + item: [ + { linkId: 'terms' }, + { linkId: 'acceptance', answer: [{ valueBoolean: accept }] }, + ], + }, +}); + export const useConsent = () => { + const queryClient = useQueryClient(); const { activeProject } = useActiveProject(); const useConsentDirectives = () => { @@ -24,42 +45,12 @@ export const useConsent = () => { }, ) => Promise | void; }) => { - const mutation = useRestMutation( - 'PATCH /v1/consent/directives/me/:directiveId', - options, - ); - - const getInput = ({ directiveId, accept }: ConsentPatch) => { - const status: 'active' | 'rejected' = accept ? 'active' : 'rejected'; - return { - directiveId, - status, - response: { - item: [ - { - linkId: 'terms', - }, - { - linkId: 'acceptance', - answer: [ - { - valueBoolean: accept, - }, - ], - }, - ], - }, - } as PatchConsentDirectives['Request'] & { - directiveId: string; - }; - }; - - return { - ...mutation, - mutate: (values: ConsentPatch) => mutation.mutate(getInput(values)), - mutateAsync: (values: ConsentPatch) => - mutation.mutateAsync(getInput(values)), - }; + return useRestMutation('PATCH /v1/consent/directives/me/:directiveId', { + onSuccess: async (data, variables) => { + queryClient.resetQueries({ queryKey: ACTIVITIES_QUERY_KEY }); + await options?.onSuccess?.(data, variables); + }, + }); }; const useShouldRenderConsentScreen = () => { @@ -88,11 +79,6 @@ export const useConsent = () => { }; }; -type ConsentPatch = { - directiveId: string; - accept: boolean; -}; - export type ConsentAndForm = Consent & { form: Questionnaire; }; diff --git a/src/screens/ConsentScreen.test.tsx b/src/screens/ConsentScreen.test.tsx index f156f9d1..06a09c1a 100644 --- a/src/screens/ConsentScreen.test.tsx +++ b/src/screens/ConsentScreen.test.tsx @@ -1,7 +1,12 @@ import React from 'react'; import { fireEvent, render } from '@testing-library/react-native'; import { Alert } from 'react-native'; -import { useOAuthFlow, useConsent, useOnboardingCourse } from '../hooks'; +import { + useOAuthFlow, + useConsent, + useOnboardingCourse, + createConsentPatch, +} from '../hooks'; import { ConsentScreen } from './ConsentScreen'; import { useDeveloperConfig } from '../hooks'; @@ -11,6 +16,7 @@ jest.mock('../hooks/useOAuthFlow', () => ({ useOAuthFlow: jest.fn(), })); jest.mock('../hooks/useConsent', () => ({ + ...jest.requireActual('../hooks/useConsent'), useConsent: jest.fn(), })); jest.mock('../hooks/useOnboardingCourse', () => ({ @@ -135,10 +141,9 @@ test('renders custom consent screen if present in developer config', () => { }); expect(navigateMock.replace).toHaveBeenCalledWith('app'); expect(updateConsentDirectiveMutationMock.mutate).toHaveBeenCalledTimes(1); - expect(updateConsentDirectiveMutationMock.mutate).toHaveBeenCalledWith({ - directiveId: defaultConsentDirective.id, - accept: true, - }); + expect(updateConsentDirectiveMutationMock.mutate).toHaveBeenCalledWith( + createConsentPatch(defaultConsentDirective.id, true), + ); // reset for later assertions updateConsentDirectiveMutationMock.mutate.mockClear(); @@ -151,10 +156,9 @@ test('renders custom consent screen if present in developer config', () => { }); () => expect(logoutMock).toHaveBeenCalledTimes(1); expect(updateConsentDirectiveMutationMock.mutate).toHaveBeenCalledTimes(1); - expect(updateConsentDirectiveMutationMock.mutate).toHaveBeenCalledWith({ - directiveId: defaultConsentDirective.id, - accept: false, - }); + expect(updateConsentDirectiveMutationMock.mutate).toHaveBeenCalledWith( + createConsentPatch(defaultConsentDirective.id, false), + ); // check passed loading state useUpdateProjectConsentDirectiveMock.mockReturnValue({ @@ -187,10 +191,9 @@ test('renders the consent body and acceptance verbiage', () => { test('should accept the consent and navigate to the home screen', () => { const { getByText } = render(consentScreen); fireEvent.press(getByText('Agree')); - expect(updateConsentDirectiveMutationMock.mutate).toHaveBeenCalledWith({ - directiveId: defaultConsentDirective.id, - accept: true, - }); + expect(updateConsentDirectiveMutationMock.mutate).toHaveBeenCalledWith( + createConsentPatch(defaultConsentDirective.id, true), + ); useUpdateProjectConsentDirectiveMock.mock.calls[0][0].onSuccess(undefined, { status: 'active', }); @@ -203,10 +206,9 @@ test('should accept the consent and navigate to the onboarding course screen', ( }); const { getByText } = render(consentScreen); fireEvent.press(getByText('Agree')); - expect(updateConsentDirectiveMutationMock.mutate).toHaveBeenCalledWith({ - directiveId: defaultConsentDirective.id, - accept: true, - }); + expect(updateConsentDirectiveMutationMock.mutate).toHaveBeenCalledWith( + createConsentPatch(defaultConsentDirective.id, true), + ); useUpdateProjectConsentDirectiveMock.mock.calls[0][0].onSuccess(undefined, { status: 'active', }); @@ -225,10 +227,9 @@ test('Pressing logout declines the consent and logs the the user out', async () const { getByText } = render(consentScreen); fireEvent.press(getByText('Decline')); alertSpy.mock.calls[0]?.[2]?.[1].onPress!(); - expect(updateConsentDirectiveMutationMock.mutate).toHaveBeenCalledWith({ - directiveId: defaultConsentDirective.id, - accept: false, - }); + expect(updateConsentDirectiveMutationMock.mutate).toHaveBeenCalledWith( + createConsentPatch(defaultConsentDirective.id, false), + ); useUpdateProjectConsentDirectiveMock.mock.calls[0][0].onSuccess(undefined, { status: 'rejected', }); diff --git a/src/screens/ConsentScreen.tsx b/src/screens/ConsentScreen.tsx index 2e5897a7..8d3bf4fe 100644 --- a/src/screens/ConsentScreen.tsx +++ b/src/screens/ConsentScreen.tsx @@ -9,6 +9,7 @@ import { useConsent, useOAuthFlow, useOnboardingCourse, + createConsentPatch, } from '../hooks'; import type { ConsentAndForm } from '../hooks/useConsent'; import { useDeveloperConfig } from '../hooks/useDeveloperConfig'; @@ -49,10 +50,9 @@ export const ConsentScreen = ({ if (!consentToPresent?.id) { return; } - updateConsentDirectiveMutation.mutate({ - directiveId: consentToPresent.id, - accept, - }); + updateConsentDirectiveMutation.mutate( + createConsentPatch(consentToPresent.id, accept), + ); }, [updateConsentDirectiveMutation, consentToPresent], );