Skip to content

Commit

Permalink
fix!: consent screen use mutate over async mutate
Browse files Browse the repository at this point in the history
  • Loading branch information
jkdowdle committed Dec 1, 2023
1 parent bcfb860 commit e4357d3
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 56 deletions.
5 changes: 4 additions & 1 deletion src/hooks/useConsent.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,11 @@ describe('useUpdateProjectConsentDirective', () => {
test('updates the accepted consent', async () => {
axiosMock.onPatch('/v1/consent/directives/me/directive-id').reply(200, {});

const onSuccess = jest.fn();

const useTestHook = () => {
const { useUpdateProjectConsentDirective } = useConsent();
return useUpdateProjectConsentDirective();
return useUpdateProjectConsentDirective({ onSuccess });
};

const { result } = renderHookInContext(useTestHook);
Expand All @@ -91,6 +93,7 @@ describe('useUpdateProjectConsentDirective', () => {
});
});

expect(onSuccess).toHaveBeenCalledTimes(1);
expect(axiosMock.history.patch[0].url).toBe(
`/v1/consent/directives/me/directive-id`,
);
Expand Down
17 changes: 15 additions & 2 deletions src/hooks/useConsent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { useActiveProject } from './useActiveProject';
import { Consent, Questionnaire } from 'fhir/r3';
import { useRestQuery, useRestMutation } from './rest-api';
import { RestAPIEndpoints } from '../types/rest-types';
import { UseMutationOptions } from '@tanstack/react-query';

type PatchConsentDirectives =
RestAPIEndpoints['PATCH /v1/consent/directives/me/:directiveId'];

export const useConsent = () => {
const { activeProject } = useActiveProject();
Expand All @@ -13,9 +17,18 @@ export const useConsent = () => {
});
};

const useUpdateProjectConsentDirective = () => {
const useUpdateProjectConsentDirective = (
options: UseMutationOptions<
PatchConsentDirectives['Response'],
unknown,
PatchConsentDirectives['Request'] & {
directiveId: string;
}
> = {},
) => {
const mutation = useRestMutation(
'PATCH /v1/consent/directives/me/:directiveId',
options,
);

const getInput = ({ directiveId, accept }: ConsentPatch) => {
Expand All @@ -38,7 +51,7 @@ export const useConsent = () => {
},
],
},
} as RestAPIEndpoints['PATCH /v1/consent/directives/me/:directiveId']['Request'] & {
} as PatchConsentDirectives['Request'] & {
directiveId: string;
};
};
Expand Down
78 changes: 39 additions & 39 deletions src/screens/ConsentScreen.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { fireEvent, render, waitFor } from '@testing-library/react-native';
import { fireEvent, render } from '@testing-library/react-native';
import { Alert } from 'react-native';
import { useOAuthFlow, useConsent, useOnboardingCourse } from '../hooks';
import { ConsentScreen } from './ConsentScreen';
Expand Down Expand Up @@ -28,7 +28,7 @@ const useUpdateProjectConsentDirectiveMock = jest.fn();
const useOnboardingCourseMock = useOnboardingCourse as jest.Mock;
const useDeveloperConfigMock = useDeveloperConfig as jest.Mock;
const updateConsentDirectiveMutationMock = {
mutateAsync: jest.fn().mockResolvedValue({}),
mutate: jest.fn().mockResolvedValue({}),
};
const navigateMock = {
replace: jest.fn(),
Expand Down Expand Up @@ -101,7 +101,7 @@ test('render the activity indicator when loading', () => {
expect(getByTestId('activity-indicator-view')).toBeDefined();
});

test('renders custom consent screen if present in developer config', async () => {
test('renders custom consent screen if present in developer config', () => {
const CustomConsentScreen = jest.fn();

useUpdateProjectConsentDirectiveMock.mockReturnValue({
Expand Down Expand Up @@ -130,26 +130,28 @@ test('renders custom consent screen if present in developer config', async () =>
CustomConsentScreen.mock.calls[0][0];

acceptConsent();
await waitFor(() => expect(navigateMock.replace).toHaveBeenCalledWith('app'));
expect(updateConsentDirectiveMutationMock.mutateAsync).toHaveBeenCalledTimes(
1,
);
expect(updateConsentDirectiveMutationMock.mutateAsync).toHaveBeenCalledWith({
useUpdateProjectConsentDirectiveMock.mock.calls[0][0].onSuccess(undefined, {
status: 'active',
});
expect(navigateMock.replace).toHaveBeenCalledWith('app');
expect(updateConsentDirectiveMutationMock.mutate).toHaveBeenCalledTimes(1);
expect(updateConsentDirectiveMutationMock.mutate).toHaveBeenCalledWith({
directiveId: defaultConsentDirective.id,
accept: true,
});

// reset for later assertions
updateConsentDirectiveMutationMock.mutateAsync.mockClear();
updateConsentDirectiveMutationMock.mutate.mockClear();

declineConsent();
expect(alertSpy).toHaveBeenCalled();
alertSpy.mock.calls[0]?.[2]?.[1].onPress!();
await waitFor(() => expect(logoutMock).toHaveBeenCalledTimes(1));
expect(updateConsentDirectiveMutationMock.mutateAsync).toHaveBeenCalledTimes(
1,
);
expect(updateConsentDirectiveMutationMock.mutateAsync).toHaveBeenCalledWith({
useUpdateProjectConsentDirectiveMock.mock.calls[0][0].onSuccess(undefined, {
status: 'rejected',
});
() => expect(logoutMock).toHaveBeenCalledTimes(1);
expect(updateConsentDirectiveMutationMock.mutate).toHaveBeenCalledTimes(1);
expect(updateConsentDirectiveMutationMock.mutate).toHaveBeenCalledWith({
directiveId: defaultConsentDirective.id,
accept: false,
});
Expand All @@ -163,9 +165,7 @@ test('renders custom consent screen if present in developer config', async () =>
CustomConsentScreen.mockClear();
render(consentScreen);

await waitFor(() => {
expect(CustomConsentScreen).toHaveBeenCalled();
});
expect(CustomConsentScreen).toHaveBeenCalled();
expect(CustomConsentScreen).toHaveBeenCalledWith(
{
consentForm: defaultConsentDirective,
Expand All @@ -184,37 +184,35 @@ test('renders the consent body and acceptance verbiage', () => {
expect(getByText(defaultConsentDirective.form.item[2].text)).toBeDefined();
});

test('should accept the consent and navigate to the home screen', async () => {
test('should accept the consent and navigate to the home screen', () => {
const { getByText } = render(consentScreen);
fireEvent.press(getByText('Agree'));
await waitFor(() => {
expect(updateConsentDirectiveMutationMock.mutateAsync).toHaveBeenCalledWith(
{
directiveId: defaultConsentDirective.id,
accept: true,
},
);
expect(navigateMock.replace).toHaveBeenCalledWith('app');
expect(updateConsentDirectiveMutationMock.mutate).toHaveBeenCalledWith({
directiveId: defaultConsentDirective.id,
accept: true,
});
useUpdateProjectConsentDirectiveMock.mock.calls[0][0].onSuccess(undefined, {
status: 'active',
});
expect(navigateMock.replace).toHaveBeenCalledWith('app');
});

test('should accept the consent and navigate to the onboarding course screen', async () => {
test('should accept the consent and navigate to the onboarding course screen', () => {
useOnboardingCourseMock.mockReturnValue({
shouldLaunchOnboardingCourse: true,
});
const { getByText } = render(consentScreen);
fireEvent.press(getByText('Agree'));
await waitFor(() => {
expect(updateConsentDirectiveMutationMock.mutateAsync).toHaveBeenCalledWith(
{
directiveId: defaultConsentDirective.id,
accept: true,
},
);
expect(navigateMock.replace).toHaveBeenCalledWith(
'screens/OnboardingCourseScreen',
);
expect(updateConsentDirectiveMutationMock.mutate).toHaveBeenCalledWith({
directiveId: defaultConsentDirective.id,
accept: true,
});
useUpdateProjectConsentDirectiveMock.mock.calls[0][0].onSuccess(undefined, {
status: 'active',
});
expect(navigateMock.replace).toHaveBeenCalledWith(
'screens/OnboardingCourseScreen',
);
});

test('it should open an alert if consent is declined', async () => {
Expand All @@ -227,10 +225,12 @@ 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!();
await waitFor(() => {});
expect(updateConsentDirectiveMutationMock.mutateAsync).toHaveBeenCalledWith({
expect(updateConsentDirectiveMutationMock.mutate).toHaveBeenCalledWith({
directiveId: defaultConsentDirective.id,
accept: false,
});
useUpdateProjectConsentDirectiveMock.mock.calls[0][0].onSuccess(undefined, {
status: 'rejected',
});
expect(logoutMock).toHaveBeenCalled();
});
34 changes: 20 additions & 14 deletions src/screens/ConsentScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,18 @@ export const ConsentScreen = ({
useConsent();
const { consentDirectives, isLoading: loadingDirectives } =
useShouldRenderConsentScreen();
const updateConsentDirectiveMutation = useUpdateProjectConsentDirective();
const updateConsentDirectiveMutation = useUpdateProjectConsentDirective({
onSuccess: (_, { status }) => {
if (status === 'rejected') {
return logout({});
}

const route = shouldLaunchOnboardingCourse
? 'screens/OnboardingCourseScreen'
: 'app';
navigation.replace(route);
},
});
const { logout } = useOAuthFlow();
const { shouldLaunchOnboardingCourse } = useOnboardingCourse();

Expand All @@ -38,21 +49,17 @@ export const ConsentScreen = ({
if (!consentToPresent?.id) {
return;
}
updateConsentDirectiveMutation.mutateAsync({
updateConsentDirectiveMutation.mutate({
directiveId: consentToPresent.id,
accept,
});
},
[updateConsentDirectiveMutation, consentToPresent],
);

const acceptConsent = useCallback(async () => {
await updateConsentDirective(true);
const route = shouldLaunchOnboardingCourse
? 'screens/OnboardingCourseScreen'
: 'app';
navigation.replace(route);
}, [updateConsentDirective, navigation, shouldLaunchOnboardingCourse]);
const acceptConsent = useCallback(() => {
updateConsentDirective(true);
}, [updateConsentDirective]);

const declineConsent = useCallback(() => {
Alert.alert(
Expand All @@ -70,14 +77,13 @@ export const ConsentScreen = ({
{
text: t('logout', 'Logout'),
style: 'destructive',
onPress: async () => {
await updateConsentDirective(false);
await logout({});
onPress: () => {
updateConsentDirective(false);
},
},
],
);
}, [logout, updateConsentDirective]);
}, [updateConsentDirective]);

const consentText = consentToPresent?.form?.item?.find(
(f) => f.linkId === 'terms',
Expand Down Expand Up @@ -181,7 +187,7 @@ export type CustomConsentScreenProps = {
/** Mutation to accept consent is in flight */
isLoadingUpdateConsent: boolean;
/** Mutation to accept consent */
acceptConsent: () => Promise<void>;
acceptConsent: () => void;
/**
* Warns user with system alert that app can not be used without
* accepting consent and continuing will log them out
Expand Down

0 comments on commit e4357d3

Please sign in to comment.