Skip to content
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

feat: library profile mutations #514

Merged
merged 14 commits into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ export * from './membership';
export * from './mentions';
export * from './search';
export * from './subscription';
export * from './publicProfile';
export * from './shortLink';
58 changes: 58 additions & 0 deletions src/api/publicProfile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { UUID } from '@graasp/sdk';

import { PartialQueryConfigForApi } from '../types';
import {
GET_OWN_PROFILE,
MEMBERS_ROUTE,
PUBLIC_PROFILE_ROUTE,
buildGetPublicProfileRoute,
} from './routes';

export const getOwnProfile = ({ API_HOST, axios }: PartialQueryConfigForApi) =>
axios
.get(`${API_HOST}/${MEMBERS_ROUTE}/${GET_OWN_PROFILE}`)
.then(({ data }) => data);

export const getPublicProfile = (
memberId: UUID,
{ API_HOST, axios }: PartialQueryConfigForApi,
) =>
axios
.get(`${API_HOST}/${MEMBERS_ROUTE}/${buildGetPublicProfileRoute(memberId)}`)
.then(({ data }) => data);

export type PostPublicProfilePayloadType = {
bio: string;
twitterID?: string;
facebookID?: string;
linkedinID?: string;
visibility?: boolean;
};

export const postPublicProfile = async (
{
bio,
twitterID,
facebookID,
linkedinID,
visibility = false,
}: PostPublicProfilePayloadType,
{ API_HOST, axios }: PartialQueryConfigForApi,
) =>
axios
.post(`${API_HOST}/${MEMBERS_ROUTE}/${PUBLIC_PROFILE_ROUTE}`, {
bio,
twitterID,
facebookID,
linkedinID,
visibility,
})
.then(({ data }) => data);

export const patchPublicProfile = async (
arg: Partial<PostPublicProfilePayloadType>,
{ API_HOST, axios }: PartialQueryConfigForApi,
) =>
axios
.patch(`${API_HOST}/${MEMBERS_ROUTE}/${PUBLIC_PROFILE_ROUTE}`, arg)
.then(({ data }) => data);
7 changes: 7 additions & 0 deletions src/api/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,10 @@ export const buildGetEtherpadRoute = (itemId: UUID) =>

export const SEARCH_PUBLISHED_ITEMS_ROUTE = `${ITEMS_ROUTE}/${COLLECTIONS_ROUTE}/search`;

export const PUBLIC_PROFILE_ROUTE = `profile`;
export const GET_OWN_PROFILE = `${PUBLIC_PROFILE_ROUTE}/own`;
export const buildGetPublicProfileRoute = (memberId: UUID) =>
`${PUBLIC_PROFILE_ROUTE}/${memberId}`;
export const buildGetShortLinkAvailableRoute = (alias: string) =>
`${SHORT_LINKS_ROUTE}/available/${alias}`;
export const buildGetShortLinksItemRoute = (itemId: string) =>
Expand Down Expand Up @@ -516,4 +520,7 @@ export const API_ROUTES = {
SIGN_OUT_ROUTE,
SIGN_UP_ROUTE,
VALIDATION_ROUTE,
PUBLIC_PROFILE_ROUTE,
GET_OWN_PROFILE,
buildGetPublicProfileRoute,
};
4 changes: 4 additions & 0 deletions src/config/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,8 @@ export const buildSearchPublishedItemsKey = (args: {

export const CURRENT_MEMBER_STORAGE_KEY = [MEMBERS_KEY, 'current', 'storage'];

export const OWN_PUBLIC_PROFILE_KEY = ['own-profile'];
export const buildPublicProfileKey = (memberId: UUID) => ['profile', memberId];
export const DATA_KEYS = {
APPS_KEY,
ITEMS_KEY,
Expand Down Expand Up @@ -305,4 +307,6 @@ export const DATA_KEYS = {
buildPublishedItemsKey,
buildEtherpadKey,
buildSearchPublishedItemsKey,
OWN_PUBLIC_PROFILE_KEY,
buildPublicProfileKey,
};
2 changes: 2 additions & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import configureMemberHooks from './member';
import configureMembershipHooks from './membership';
import configureMentionsHooks from './mention';
import configurePlanHooks from './plan';
import configurePublicProfileHooks from './publicProfile';
import configureKeywordSearchHooks from './search';
import configureShortLinkHooks from './shortLink';

Expand Down Expand Up @@ -52,6 +53,7 @@ export default (
...configureInvitationHooks(queryConfig),
...memberHooks,
...configurePlanHooks(queryConfig),
...configurePublicProfileHooks(queryConfig),
...configureShortLinkHooks(queryConfig),
};
};
81 changes: 81 additions & 0 deletions src/hooks/publicProfile.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { StatusCodes } from 'http-status-codes';
import nock from 'nock';

import {
MEMBER_PUBLIC_PROFILE,
UNAUTHORIZED_RESPONSE,
} from '../../test/constants';
import { mockHook, setUpTest } from '../../test/utils';
import {
GET_OWN_PROFILE,
MEMBERS_ROUTE,
buildGetPublicProfileRoute,
} from '../api/routes';
import { OWN_PUBLIC_PROFILE_KEY, buildPublicProfileKey } from '../config/keys';

const { hooks, wrapper, queryClient } = setUpTest();

describe('Public Profile Hooks', () => {
afterEach(() => {
nock.cleanAll();
queryClient.clear();
});

describe('useOwnProfile', () => {
const route = `/${MEMBERS_ROUTE}/${GET_OWN_PROFILE}`;

const response = MEMBER_PUBLIC_PROFILE;

const hook = () => hooks.useOwnProfile();
it(`Receive actions for item id`, async () => {
const endpoints = [{ route, response }];
const { data } = await mockHook({ endpoints, hook, wrapper });
expect(data).toEqual(response);
expect(queryClient.getQueryData(OWN_PUBLIC_PROFILE_KEY)).toEqual(
response,
);
});

it(`Unauthorized`, async () => {
const endpoints = [
{
route,
response: UNAUTHORIZED_RESPONSE,
statusCode: StatusCodes.UNAUTHORIZED,
},
];
const { isError } = await mockHook({
endpoints,
hook,
wrapper,
});

expect(isError).toBeTruthy();
expect(queryClient.getQueryData(OWN_PUBLIC_PROFILE_KEY)).toBeFalsy();
});
});

describe('usePublicProfile', () => {
const id = 'member-id';
const response = MEMBER_PUBLIC_PROFILE;

it(`Receive member public profile for member-id = ${id}`, async () => {
const endpoints = [
{
route: `/${MEMBERS_ROUTE}/${buildGetPublicProfileRoute(id)}`,
response,
},
];
const hook = () => hooks.usePublicProfile(id);
const { data } = await mockHook({
hook,
wrapper,
endpoints,
});
expect(queryClient.getQueryData(buildPublicProfileKey(id))).toEqual(
response,
);
expect(data).toMatchObject(response);
});
});
});
34 changes: 34 additions & 0 deletions src/hooks/publicProfile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { UUID } from '@graasp/sdk';

import { useQuery } from 'react-query';

import * as Api from '../api';
import { UndefinedArgument } from '../config/errors';
import { OWN_PUBLIC_PROFILE_KEY, buildPublicProfileKey } from '../config/keys';
import { QueryClientConfig } from '../types';

export default (queryConfig: QueryClientConfig) => {
const { defaultQueryOptions } = queryConfig;

return {
useOwnProfile: () =>
useQuery({
queryKey: OWN_PUBLIC_PROFILE_KEY,
queryFn: () => Api.getOwnProfile(queryConfig),
...defaultQueryOptions,
}),

usePublicProfile: (memberId: UUID) =>
useQuery({
queryKey: buildPublicProfileKey(memberId),
queryFn: () => {
if (!memberId) {
throw new UndefinedArgument();
}
return Api.getPublicProfile(memberId, queryConfig);
},
enabled: Boolean(memberId),
...defaultQueryOptions,
}),
};
};
2 changes: 2 additions & 0 deletions src/mutations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import memberMutations from './member';
import itemMembershipMutations from './membership';
import mentionMutations from './mention';
import subscriptionMutations from './plan';
import publicProfileMutations from './publicProfile';
import shortLinksMutations from './shortLink';

const configureMutations = (queryConfig: QueryClientConfig) => ({
Expand All @@ -38,6 +39,7 @@ const configureMutations = (queryConfig: QueryClientConfig) => ({
...authenticationMutations(queryConfig),
...subscriptionMutations(queryConfig),
...itemPublishMutations(queryConfig),
...publicProfileMutations(queryConfig),
...shortLinksMutations(queryConfig),
});

Expand Down
102 changes: 102 additions & 0 deletions src/mutations/publicProfile.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { HttpMethod } from '@graasp/sdk';

import { act } from '@testing-library/react';
import nock from 'nock';

import { MEMBER_RESPONSE } from '../../test/constants';
import { mockMutation, setUpTest, waitForMutation } from '../../test/utils';
import { MEMBERS_ROUTE, PUBLIC_PROFILE_ROUTE } from '../api/routes';
import { OWN_PUBLIC_PROFILE_KEY } from '../config/keys';

const mockedNotifier = jest.fn();
const { wrapper, queryClient, mutations } = setUpTest({
notifier: mockedNotifier,
});
const newProfile = {
bio: 'text',
twitterID: 'twitter_handle',
facebookID: 'fb_handle',
linkedinID: 'linkedin_handle',
visibility: false,
};
describe('Public Profile Mutations', () => {
afterEach(() => {
queryClient.clear();
nock.cleanAll();
mockedNotifier.mockClear();
});

describe('usePostPublicProfile', () => {
const mutation = mutations.usePostPublicProfile;

it('Post profile', async () => {
const route = `/${MEMBERS_ROUTE}/${PUBLIC_PROFILE_ROUTE}`;
const response = { ...newProfile, id: 'someid', member: MEMBER_RESPONSE };

queryClient.setQueryData(OWN_PUBLIC_PROFILE_KEY, response);

const endpoints = [
{
response,
method: HttpMethod.POST,
route,
},
];

const mockedMutation = await mockMutation({
endpoints,
mutation,
wrapper,
});

await act(async () => {
await mockedMutation.mutate(newProfile);
await waitForMutation();
});

expect(queryClient.getQueryState(OWN_PUBLIC_PROFILE_KEY)?.data).toEqual(
response,
);
});
});

describe('usePatchPublicProfile', () => {
const mutation = mutations.usePatchPublicProfile;
const payload = { bio: 'new description' };
const result = {
...newProfile,
bio: 'new description',
id: 'someid',
member: MEMBER_RESPONSE,
};

it('Edit public profile', async () => {
const route = `/${MEMBERS_ROUTE}/${PUBLIC_PROFILE_ROUTE}`;
const response = null;
const endpoints = [
{
response,
method: HttpMethod.PATCH,
route,
},
];
queryClient.setQueryData(OWN_PUBLIC_PROFILE_KEY, result);

const mockedMutation = await mockMutation({
endpoints,
mutation,
wrapper,
});

await act(async () => {
await mockedMutation.mutate(payload);
await waitForMutation();
});

expect(queryClient.getQueryState(OWN_PUBLIC_PROFILE_KEY)?.data).toEqual(
result,
);
expect(queryClient.getQueryData(OWN_PUBLIC_PROFILE_KEY)).toEqual(result);
});
});
});
Loading