Skip to content

Commit

Permalink
feat: add hook for download & item like
Browse files Browse the repository at this point in the history
fix: changes by review
  • Loading branch information
louisewang1 committed Mar 1, 2022
1 parent af2487b commit ce86263
Show file tree
Hide file tree
Showing 18 changed files with 537 additions and 1 deletion.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"hooks:install": "husky install",
"predeploy": "cd example && yarn install && yarn run build",
"test:watch": "jest --watchAll",
"test": "jest src --silent",
"test": "jest src/ --silent",
"test:ci": "yarn && cd example && yarn && cd .. && yarn test",
"deploy": "gh-pages -d example/build",
"release": "standard-version",
Expand Down
2 changes: 2 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ export * from './itemFlag';
export * from './chat';
export * from './category';
export * from './search';
export * from './itemDownload';
export * from './itemLike';
15 changes: 15 additions & 0 deletions src/api/itemDownload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { QueryClientConfig, UUID } from '../types';
import configureAxios, { verifyAuthentication } from './axios';
import { buildDownloadItemRoute } from './routes';

const axios = configureAxios();

/* eslint-disable import/prefer-default-export */
export const downloadItem = async (id: UUID, { API_HOST }: QueryClientConfig) =>
verifyAuthentication(() =>
axios({
url: `${API_HOST}/${buildDownloadItemRoute(id)}`,
method: 'GET',
responseType: 'blob',
}).then(({ data }) => data),
);
49 changes: 49 additions & 0 deletions src/api/itemLike.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { List } from 'immutable';
import {
buildDeleteItemLikeRoute,
buildGetLikeCountRoute,
buildGetLikedItemsRoute,
buildPostItemLikeRoute,
} from './routes';
import { QueryClientConfig, UUID } from '../types';
import configureAxios, { verifyAuthentication } from './axios';

const axios = configureAxios();

export const getLikedItems = async (
memberId: UUID,
{ API_HOST }: QueryClientConfig,
) =>
verifyAuthentication(() =>
axios
.get(`${API_HOST}/${buildGetLikedItemsRoute(memberId)}`)
.then(({ data }) => List(data)),
);

// TODO: make a public one
export const getLikeCount = async (id: UUID, { API_HOST }: QueryClientConfig) =>
verifyAuthentication(() =>
axios
.get(`${API_HOST}/${buildGetLikeCountRoute(id)}`)
.then(({ data }) => data),
);

export const postItemLike = async (
itemId: UUID,
{ API_HOST }: QueryClientConfig,
) =>
verifyAuthentication(() =>
axios
.post(`${API_HOST}/${buildPostItemLikeRoute(itemId)}`)
.then(({ data }) => data),
);

export const deleteItemLike = async (
id: UUID,
{ API_HOST }: QueryClientConfig,
) =>
verifyAuthentication(() =>
axios
.delete(`${API_HOST}/${buildDeleteItemLikeRoute(id)}`)
.then(({ data }) => data),
);
15 changes: 15 additions & 0 deletions src/api/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ export const buildCopyPublicItemRoute = (id: UUID) =>
export const buildCopyItemsRoute = (ids: UUID[]) =>
`${ITEMS_ROUTE}/copy?${qs.stringify({ id: ids }, { arrayFormat: 'repeat' })}`;
export const buildEditItemRoute = (id: UUID) => `${ITEMS_ROUTE}/${id}`;
export const buildDownloadItemRoute = (id: UUID) =>
`${ITEMS_ROUTE}/zip-export/${id}`;
export const buildShareItemWithRoute = (id: UUID) =>
`item-memberships?itemId=${id}`;
export const buildGetItemMembershipsForItemsRoute = (ids: UUID[]) =>
Expand Down Expand Up @@ -215,6 +217,14 @@ export const buildGetPublicApiAccessTokenRoute = (id: UUID) =>
export const buildGetItemsByKeywordRoute = (range: string, keywords: string) =>
`${PUBLIC_PREFIX}/${ITEMS_ROUTE}/search/${range}/${keywords}`;

export const buildGetLikedItemsRoute = (id: UUID) =>
`${ITEMS_ROUTE}/${id}/likes`;
export const buildGetLikeCountRoute = (id: UUID) =>
`${ITEMS_ROUTE}/${id}/like-count`;
export const buildPostItemLikeRoute = (id: UUID) => `${ITEMS_ROUTE}/${id}/like`;
export const buildDeleteItemLikeRoute = (id: UUID) =>
`${ITEMS_ROUTE}/likes/${id}`;

export const API_ROUTES = {
APPS_ROUTE,
ITEMS_ROUTE,
Expand Down Expand Up @@ -252,6 +262,7 @@ export const API_ROUTES = {
buildCopyItemRoute,
buildCopyPublicItemRoute,
buildCopyItemsRoute,
buildDownloadItemRoute,
buildPatchMember,
buildPostItemFlagRoute,
buildEditItemMembershipRoute,
Expand Down Expand Up @@ -284,4 +295,8 @@ export const API_ROUTES = {
buildGetPublicApiAccessTokenRoute,
buildPublicDownloadFilesRoute,
buildGetPublicItemMembershipsForItemsRoute,
buildGetLikedItemsRoute,
buildGetLikeCountRoute,
buildPostItemLikeRoute,
buildDeleteItemLikeRoute,
};
10 changes: 10 additions & 0 deletions src/config/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ export const buildAvatarKey = ({
size?: string;
}) => [MEMBERS_KEY, id, 'avatars', size];

export const buildGetLikedItemsKey = (id: UUID) => [
MEMBERS_KEY,
id,
'likedItems',
];
export const buildGetLikeCountKey = (id: UUID) => [ITEMS_KEY, id, 'likeCount'];

export const MUTATION_KEYS = {
POST_ITEM: 'postItem',
EDIT_ITEM: 'editItem',
Expand Down Expand Up @@ -127,4 +134,7 @@ export const MUTATION_KEYS = {
UPLOAD_ITEM_THUMBNAIL: 'uploadItemThumbnail',
UPLOAD_AVATAR: 'uploadAvatar',
IMPORT_ZIP: 'importZip',
EXPORT_ZIP: 'downloadItem',
POST_ITEM_LIKE: 'postItemLike',
DELETE_ITEM_LIKE: 'deleteItemLike',
};
2 changes: 2 additions & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import configureMemberHooks from './member';
import configureAppsHooks from './apps';
import configureCategoryHooks from './category';
import configureKeywordSearchHooks from './search';
import configureItemLikeHooks from './itemLike';

export default (
queryClient: QueryClient,
Expand All @@ -29,6 +30,7 @@ export default (
...configureItemFlagHooks(queryConfig),
...configureCategoryHooks(queryConfig),
...configureKeywordSearchHooks(queryConfig),
...configureItemLikeHooks(queryConfig),
...configureAppsHooks(queryConfig),
...memberHooks,
};
Expand Down
97 changes: 97 additions & 0 deletions src/hooks/itemLike.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import nock from 'nock';
import Cookies from 'js-cookie';
import { StatusCodes } from 'http-status-codes';
import { List } from 'immutable';
import { buildGetLikeCountRoute, buildGetLikedItemsRoute } from '../api/routes';
import { mockHook, setUpTest } from '../../test/utils';
import { ITEMS, LIKE_COUNT, ITEM_LIKES, UNAUTHORIZED_RESPONSE } from '../../test/constants';
import { buildGetLikeCountKey, buildGetLikedItemsKey } from '../config/keys';

const { hooks, wrapper, queryClient } = setUpTest();
jest.spyOn(Cookies, 'get').mockReturnValue({ session: 'somesession' });

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

describe('useLikedItems', () => {
const memberId = 'member-id';
const route = `/${buildGetLikedItemsRoute(memberId)}`;
const key = buildGetLikedItemsKey(memberId);

const hook = () => hooks.useLikedItems(memberId);

it(`Receive item likes`, async () => {
const response = ITEM_LIKES;
const endpoints = [{ route, response }];
const { data } = await mockHook({ endpoints, hook, wrapper });

expect((data as List<typeof ITEM_LIKES[0]>).toJS()).toEqual(response);

// verify cache keys
expect(queryClient.getQueryData(key)).toEqual(List(response));
});

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

expect(data).toBeFalsy();
expect(isError).toBeTruthy();
// verify cache keys
expect(queryClient.getQueryData(key)).toBeFalsy();
});
});

describe('useLikeCount', () => {
const itemId = ITEMS[0].id;
const route = `/${buildGetLikeCountRoute(itemId)}`;
const key = buildGetLikeCountKey(itemId);

const hook = () => hooks.useLikeCount(itemId);

it(`Receive item like count`, async () => {
const response = LIKE_COUNT;
const endpoints = [{ route, response }];
const { data } = await mockHook({ endpoints, hook, wrapper });

expect(data as typeof LIKE_COUNT).toEqual(response);

// verify cache keys
expect(queryClient.getQueryData(key)).toEqual(response);
});

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

expect(data).toBeFalsy();
expect(isError).toBeTruthy();
// verify cache keys
expect(queryClient.getQueryData(key)).toBeFalsy();
});
});
});
33 changes: 33 additions & 0 deletions src/hooks/itemLike.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useQuery } from 'react-query';
import { List } from 'immutable';
import { QueryClientConfig, UUID } from '../types';
import * as Api from '../api';
import { buildGetLikeCountKey, buildGetLikedItemsKey } from '../config/keys';

export default (queryConfig: QueryClientConfig) => {
const { retry, cacheTime, staleTime } = queryConfig;
const defaultOptions = {
retry,
cacheTime,
staleTime,
};

const useLikedItems = (memberId: UUID) =>
useQuery({
queryKey: buildGetLikedItemsKey(memberId),
queryFn: () =>
Api.getLikedItems(memberId, queryConfig).then((data) => List(data)),
...defaultOptions,
enabled: Boolean(memberId),
});

const useLikeCount = (itemId: UUID) =>
useQuery({
queryKey: buildGetLikeCountKey(itemId),
queryFn: () => Api.getLikeCount(itemId, queryConfig).then((data) => data),
...defaultOptions,
enabled: Boolean(itemId),
});

return { useLikeCount, useLikedItems };
};
4 changes: 4 additions & 0 deletions src/mutations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import itemMembershipMutations from './membership';
import chatMutations from './chat';
import itemCategoryMutations from './itemCategory';
import { QueryClientConfig } from '../types';
import itemDownloadMutations from './itemDownload';
import itemLikeMutations from './itemLike';

const configureMutations = (
queryClient: QueryClient,
Expand All @@ -21,6 +23,8 @@ const configureMutations = (
itemLoginMutations(queryClient, queryConfig);
chatMutations(queryClient, queryConfig);
itemCategoryMutations(queryClient, queryConfig);
itemDownloadMutations(queryClient, queryConfig);
itemLikeMutations(queryClient, queryConfig);
};

export default configureMutations;
54 changes: 54 additions & 0 deletions src/mutations/itemDownload.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/* eslint-disable import/no-extraneous-dependencies */
import nock from 'nock';
import Cookies from 'js-cookie';
import { act } from 'react-test-renderer';
import { mockMutation, setUpTest, waitForMutation } from '../../test/utils';
import { REQUEST_METHODS } from '../api/utils';
import { MUTATION_KEYS } from '../config/keys';
import { buildDownloadItemRoute } from '../api/routes';
import { downloadItemRoutine } from '../routines';

const mockedNotifier = jest.fn();
const { wrapper, queryClient, useMutation } = setUpTest({
notifier: mockedNotifier,
});

jest.spyOn(Cookies, 'get').mockReturnValue({ session: 'somesession' });

describe('Download Item', () => {
afterEach(() => {
queryClient.clear();
nock.cleanAll();
});

describe(MUTATION_KEYS.EXPORT_ZIP, () => {
const itemId = 'item-id';
const route = `/${buildDownloadItemRoute(itemId)}`;
const mutation = () => useMutation(MUTATION_KEYS.EXPORT_ZIP);

it('download item', async () => {
const endpoints = [
{
response: { id: 'id', content: 'content' },
method: REQUEST_METHODS.GET,
route,
},
];

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

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

expect(mockedNotifier).toHaveBeenCalledWith({
type: downloadItemRoutine.SUCCESS,
});
});
});
});
20 changes: 20 additions & 0 deletions src/mutations/itemDownload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { QueryClient } from 'react-query';
import * as Api from '../api';
import { MUTATION_KEYS } from '../config/keys';
import { downloadItemRoutine } from '../routines';
import { QueryClientConfig } from '../types';

export default (queryClient: QueryClient, queryConfig: QueryClientConfig) => {
const { notifier } = queryConfig;

queryClient.setMutationDefaults(MUTATION_KEYS.EXPORT_ZIP, {
mutationFn: (id) =>
Api.downloadItem(id, queryConfig).then((data) => data),
onSuccess: () => {
notifier?.({ type: downloadItemRoutine.SUCCESS });
},
onError: (error) => {
notifier?.({ type: downloadItemRoutine.FAILURE, payload: { error } });
},
});
};
Loading

0 comments on commit ce86263

Please sign in to comment.