Skip to content

Commit

Permalink
Merge pull request #90 from graasp/89/category
Browse files Browse the repository at this point in the history
close ref #89
  • Loading branch information
louisewang1 authored Dec 1, 2021
2 parents fb43c3e + 443ae1b commit a9c7efe
Show file tree
Hide file tree
Showing 17 changed files with 2,252 additions and 1,683 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,5 @@ example/src/react-app-env.d.ts
!.yarn/releases
!.yarn/sdks
!.yarn/versions

package-lock.json
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"prettier": "2.2.1",
"pretty-quick": "3.1.0",
"react-test-renderer": "17.0.2",
"standard-version": "9.1.0",
"standard-version": "9.3.2",
"ts-jest": "27.0.3",
"ts-node": "10.1.0",
"typescript": "4.1.3",
Expand Down
67 changes: 67 additions & 0 deletions src/api/category.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { QueryClientConfig, UUID} from '../types';
import { buildGetCategoriesRoute, buildGetCategoryInfoRoute, buildGetItemCategoriesRoute,
buildGetItemsInCategoryRoute, buildPostItemCategoryRoute, buildDeleteItemCategoryRoute, GET_CATEGORY_TYPES_ROUTE } from './routes';
import { DEFAULT_DELETE, DEFAULT_GET, DEFAULT_POST, failOnError } from './utils';

export const getCategoryTypes = async ({ API_HOST }: QueryClientConfig) => {
const res = await fetch(`${API_HOST}/${GET_CATEGORY_TYPES_ROUTE}`, DEFAULT_GET).then(
failOnError,
);

return res.json();
};

export const getCategories = async ({ API_HOST }: QueryClientConfig, typeIds?: UUID[], ) => {
const res = await fetch(`${API_HOST}/${buildGetCategoriesRoute(typeIds)}`, DEFAULT_GET).then(
failOnError,
);

return res.json();
};

export const getCategory = async (categoryId: UUID, { API_HOST }: QueryClientConfig) => {
const res = await fetch(`${API_HOST}/${buildGetCategoryInfoRoute(categoryId)}`, DEFAULT_GET).then(
failOnError,
);

return res.json();
};

export const getItemCategories = async (itemId: UUID, { API_HOST }: QueryClientConfig) => {
const res = await fetch(`${API_HOST}/${buildGetItemCategoriesRoute(itemId)}`, DEFAULT_GET).then(
failOnError,
);
return res.json();
};

export const getItemsForCategories = async (categoryIds: UUID[], { API_HOST }: QueryClientConfig) => {
const res = await fetch(`${API_HOST}/${buildGetItemsInCategoryRoute(categoryIds)}`, DEFAULT_GET).then(
failOnError,
);

return res.json();
};

// payload: itemId, categoryId
export const postItemCategory = async (
{
itemId,
categoryId,
}: { itemId: UUID; categoryId: UUID },
{ API_HOST }: QueryClientConfig,
) => {
const res = await fetch(`${API_HOST}/${buildPostItemCategoryRoute(itemId)}`, {
...DEFAULT_POST,
body: JSON.stringify({ itemId, categoryId }),
}).then(failOnError);

return res.json();
};

export const deleteItemCategory = async (entryId: UUID, { API_HOST }: QueryClientConfig) => {
const res = await fetch(`${API_HOST}/${buildDeleteItemCategoryRoute(entryId)}`, {
...DEFAULT_DELETE,
}).then(failOnError);

return res.json();
};
1 change: 1 addition & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './itemTag';
export * from './itemLogin';
export * from './itemFlag';
export * from './chat';
export * from './category'
25 changes: 25 additions & 0 deletions src/api/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const MEMBERS_ROUTE = `members`;
export const GET_OWN_ITEMS_ROUTE = `${ITEMS_ROUTE}/own`;
export const GET_RECYCLED_ITEMS_ROUTE = `${ITEMS_ROUTE}/recycled`;
export const SHARE_ITEM_WITH_ROUTE = `${ITEMS_ROUTE}/shared-with`;
export const CATEGORIES_ROUTE = `${ITEMS_ROUTE}/categories`;

export const buildAppListRoute = `${APPS_ROUTE}/list`;

Expand Down Expand Up @@ -139,6 +140,24 @@ export const buildRestoreItemsRoute = (ids: UUID[]) =>
{ arrayFormat: 'repeat' },
)}`;

export const GET_CATEGORY_TYPES_ROUTE = `${ITEMS_ROUTE}/category-types`
export const buildGetCategoriesRoute = (ids?: UUID[]) =>
`${CATEGORIES_ROUTE}?${qs.stringify(
{ type: ids },
{ arrayFormat: 'repeat' }
)}`;
export const buildGetCategoryInfoRoute = (id: UUID) => `${CATEGORIES_ROUTE}/${id}`;
export const buildGetItemCategoriesRoute = (id: UUID) => `${ITEMS_ROUTE}/${id}/categories`;
export const buildGetItemsInCategoryRoute = (ids: UUID[]) =>
`${ITEMS_ROUTE}/withCategories?${qs.stringify(
{ category: ids },
{ arrayFormat: 'repeat' }
)}`;
export const buildPostItemCategoryRoute = (id: UUID) =>
`${ITEMS_ROUTE}/${id}/categories`;
export const buildDeleteItemCategoryRoute = (id: UUID) =>
`${ITEMS_ROUTE}/item-category/${id}`;

export const API_ROUTES = {
APPS_ROUTE,
ITEMS_ROUTE,
Expand All @@ -149,6 +168,7 @@ export const API_ROUTES = {
GET_CURRENT_MEMBER_ROUTE,
GET_TAGS_ROUTE,
GET_FLAGS_ROUTE,
GET_CATEGORY_TYPES_ROUTE,
buildAppListRoute,
buildGetS3MetadataRoute,
buildGetMember,
Expand Down Expand Up @@ -192,4 +212,9 @@ export const API_ROUTES = {
buildGetPublicMember,
buildGetPublicMembersRoute,
buildRestoreItemsRoute,
buildGetCategoriesRoute,
buildGetCategoryInfoRoute,
buildGetItemCategoriesRoute,
buildPostItemCategoryRoute,
buildDeleteItemCategoryRoute,
};
8 changes: 8 additions & 0 deletions src/config/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ export const buildS3FileContentKey = (id?: UUID) => [ITEMS_KEY, id, 'content'];
export const ITEM_FLAGS_KEY = 'itemFlags';
export const buildItemFlagsKey = (id: UUID) => [ITEMS_KEY, id, 'flags'];

export const CATEGORY_TYPES_KEY = 'categoryTypes'
export const buildCategoryKey = (id: UUID) => ['category', id];
export const buildCategoriesKey = (typeId?: UUID[]) => ['categories', hashItemsIds(typeId)];
export const buildItemCategoriesKey = (id?: UUID) => [ITEMS_KEY, id, 'categories'];
export const buildItemsByCategoriesKey = (ids: UUID[]) => ['itemsInCategories', hashItemsIds(ids)];

export const buildPublicItemsWithTagKey = (id?: UUID) => [
ITEMS_KEY,
ITEM_TAGS_KEY,
Expand Down Expand Up @@ -80,4 +86,6 @@ export const MUTATION_KEYS = {
RECYCLE_ITEM: 'recycleItem',
RECYCLE_ITEMS: 'recycleItems',
RESTORE_ITEMS: 'restoreItems',
POST_ITEM_CATEGORY: 'postItemCategory',
DELETE_ITEM_CATEGORY: 'deleteItemCategory',
};
210 changes: 210 additions & 0 deletions src/hooks/category.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import nock from 'nock';
import { List } from 'immutable';
import { mockHook, setUpTest } from '../../test/utils';
import { buildGetCategoriesRoute, buildGetCategoryInfoRoute, buildGetItemCategoriesRoute, buildGetItemsInCategoryRoute, GET_CATEGORY_TYPES_ROUTE } from '../api/routes';
import { buildCategoriesKey, buildCategoryKey, buildItemCategoriesKey, buildItemsByCategoriesKey, CATEGORY_TYPES_KEY } from '../config/keys';
import { CATEGORIES, CATEGORY_TYPES, ITEM_CATEGORIES, UNAUTHORIZED_RESPONSE } from '../../test/constants';
import { Category, CategoryType, ItemCategory } from '../types';
import { StatusCodes } from 'http-status-codes';

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

type ItemId = {
itemId: string,
}

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

describe('useCategoryTypes', () => {
const route = `/${GET_CATEGORY_TYPES_ROUTE}`;
const key = CATEGORY_TYPES_KEY;

const hook = () => hooks.useCategoryTypes();

it(`Receive category types`, async () => {
const response = CATEGORY_TYPES;
const endpoints = [{ route, response }];
const { data } = await mockHook({ endpoints, hook, wrapper });

expect((data as List<CategoryType>).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('useCategories', () => {
const typeIds = ['type-id1', 'type-id2'];
const route = `/${buildGetCategoriesRoute(typeIds)}`;
const key = buildCategoriesKey(typeIds);

const hook = () => hooks.useCategories(typeIds);

it(`Receive category types`, async () => {
const response = CATEGORIES;
const endpoints = [{ route, response }];
const { data } = await mockHook({ endpoints, hook, wrapper });

expect((data as List<Category>).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('useCategory', () => {
const categoryId = 'id';
const route = `/${buildGetCategoryInfoRoute(categoryId)}`;
const key = buildCategoryKey(categoryId);

const hook = () => hooks.useCategory(categoryId);

it(`Receive category info`, async () => {
const response = CATEGORIES[0];
const endpoints = [{ route, response }];
const { data } = await mockHook({ endpoints, hook, wrapper });

expect((data as Category)).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();
});
});

describe('useItemCategories', () => {
const itemId = 'item-id';
const route = `/${buildGetItemCategoriesRoute(itemId)}`;
const key = buildItemCategoriesKey(itemId);

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

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

expect((data as List<ItemCategory>).toJS()).toEqual(response);

// verify cache keys
expect((queryClient.getQueryData(key) as List<ItemCategory>).toJS()).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();
});
});

describe('useItemsInCategories', () => {
const categoryIds = ['id1'];
const route = `/${buildGetItemsInCategoryRoute(categoryIds)}`;
const key = buildItemsByCategoriesKey(categoryIds);

const hook = () => hooks.useItemsInCategories(categoryIds);

it(`Receive items in categories`, async () => {
const response = [{itemId: 'id1'}, {itemId: 'id2'}];
const endpoints = [{ route, response }];
const { data } = await mockHook({ endpoints, hook, wrapper });

expect((data as List<ItemId>).toJS()).toEqual(response);

// verify cache keys
expect((queryClient.getQueryData(key) as List<ItemId>)?.toJS()).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();
});
});
});
Loading

0 comments on commit a9c7efe

Please sign in to comment.