refactor: convert a couple files to TS and improve typings/tests (#1181)
* refactor: convert files to ts and improve typings/tests * fix: set return type to unkown for future fix
This commit is contained in:
@@ -15,7 +15,7 @@ const TagsSidebarHeader = () => {
|
||||
const {
|
||||
data: contentTagsCount,
|
||||
isSuccess: isContentTagsCountLoaded,
|
||||
} = useContentTagsCount(contentId || '');
|
||||
} = useContentTagsCount(contentId);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
|
||||
@@ -142,7 +142,7 @@ const CardHeader = ({
|
||||
{(isVertical || isSequential) && (
|
||||
<CardStatus status={status} showDiscussionsEnabledBadge={showDiscussionsEnabledBadge} />
|
||||
)}
|
||||
{ getConfig().ENABLE_TAGGING_TAXONOMY_PAGES === 'true' && contentTagCount > 0 && (
|
||||
{ getConfig().ENABLE_TAGGING_TAXONOMY_PAGES === 'true' && !!contentTagCount && (
|
||||
<TagCount count={contentTagCount} onClick={openManageTagsDrawer} />
|
||||
)}
|
||||
<Dropdown data-testid={`${namePrefix}-card-header__menu`} onClick={onClickMenuButton}>
|
||||
|
||||
@@ -86,8 +86,9 @@ describe('generic api calls', () => {
|
||||
expect(contentTagsCountMock[contentId]).toEqual(15);
|
||||
});
|
||||
|
||||
it('should get null on empty pattern', async () => {
|
||||
const result = await getTagsCount('');
|
||||
expect(result).toEqual(null);
|
||||
it('should throw an error if no pattern is provided', async () => {
|
||||
const pattern = undefined;
|
||||
expect(getTagsCount(pattern)).rejects.toThrow('contentPattern is required');
|
||||
expect(axiosMock.history.get.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// @ts-check
|
||||
import { camelCaseObject, getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
|
||||
@@ -6,16 +5,21 @@ import { convertObjectToSnakeCase } from '../../utils';
|
||||
|
||||
export const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL;
|
||||
export const getCreateOrRerunCourseUrl = () => new URL('course/', getApiBaseUrl()).href;
|
||||
export const getCourseRerunUrl = (courseId) => new URL(`/api/contentstore/v1/course_rerun/${courseId}`, getApiBaseUrl()).href;
|
||||
export const getCourseRerunUrl = (courseId: string) => new URL(
|
||||
`/api/contentstore/v1/course_rerun/${courseId}`,
|
||||
getApiBaseUrl(),
|
||||
).href;
|
||||
export const getOrganizationsUrl = () => new URL('organizations', getApiBaseUrl()).href;
|
||||
export const getClipboardUrl = () => `${getApiBaseUrl()}/api/content-staging/v1/clipboard/`;
|
||||
export const getTagsCountApiUrl = (contentPattern) => new URL(`api/content_tagging/v1/object_tag_counts/${contentPattern}/?count_implicit`, getApiBaseUrl()).href;
|
||||
export const getTagsCountApiUrl = (contentPattern: string) => new URL(
|
||||
`api/content_tagging/v1/object_tag_counts/${contentPattern}/?count_implicit`,
|
||||
getApiBaseUrl(),
|
||||
).href;
|
||||
|
||||
/**
|
||||
* Get's organizations data. Returns list of organization names.
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
export async function getOrganizations() {
|
||||
export async function getOrganizations(): Promise<string[]> {
|
||||
const { data } = await getAuthenticatedHttpClient().get(
|
||||
getOrganizationsUrl(),
|
||||
);
|
||||
@@ -24,9 +28,8 @@ export async function getOrganizations() {
|
||||
|
||||
/**
|
||||
* Get's course rerun data.
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export async function getCourseRerun(courseId) {
|
||||
export async function getCourseRerun(courseId: string): Promise<unknown> {
|
||||
const { data } = await getAuthenticatedHttpClient().get(
|
||||
getCourseRerunUrl(courseId),
|
||||
);
|
||||
@@ -35,10 +38,8 @@ export async function getCourseRerun(courseId) {
|
||||
|
||||
/**
|
||||
* Create or rerun course with data.
|
||||
* @param {object} courseData
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export async function createOrRerunCourse(courseData) {
|
||||
export async function createOrRerunCourse(courseData: Object): Promise<unknown> {
|
||||
const { data } = await getAuthenticatedHttpClient().post(
|
||||
getCreateOrRerunCourseUrl(),
|
||||
convertObjectToSnakeCase(courseData, true),
|
||||
@@ -48,9 +49,8 @@ export async function createOrRerunCourse(courseData) {
|
||||
|
||||
/**
|
||||
* Retrieves user's clipboard.
|
||||
* @returns {Promise<Object>} - A Promise that resolves clipboard data.
|
||||
*/
|
||||
export async function getClipboard() {
|
||||
export async function getClipboard(): Promise<unknown> {
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.get(getClipboardUrl());
|
||||
|
||||
@@ -59,10 +59,8 @@ export async function getClipboard() {
|
||||
|
||||
/**
|
||||
* Updates user's clipboard.
|
||||
* @param {string} usageKey - The ID of the block.
|
||||
* @returns {Promise<Object>} - A Promise that resolves clipboard data.
|
||||
*/
|
||||
export async function updateClipboard(usageKey) {
|
||||
export async function updateClipboard(usageKey: string): Promise<unknown> {
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.post(getClipboardUrl(), { usage_key: usageKey });
|
||||
|
||||
@@ -71,15 +69,14 @@ export async function updateClipboard(usageKey) {
|
||||
|
||||
/**
|
||||
* Gets the tags count of multiple content by id separated by commas or a pattern using a '*' wildcard.
|
||||
* @param {string} contentPattern
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export async function getTagsCount(contentPattern) {
|
||||
if (contentPattern) {
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.get(getTagsCountApiUrl(contentPattern));
|
||||
|
||||
return data;
|
||||
export async function getTagsCount(contentPattern?: string): Promise<Record<string, number>> {
|
||||
if (!contentPattern) {
|
||||
throw new Error('contentPattern is required');
|
||||
}
|
||||
return null;
|
||||
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.get(getTagsCountApiUrl(contentPattern));
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useContentTagsCount } from './apiHooks';
|
||||
|
||||
jest.mock('@tanstack/react-query', () => ({
|
||||
useQuery: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('./api', () => ({
|
||||
getTagsCount: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('useContentTagsCount', () => {
|
||||
it('should return success response', () => {
|
||||
useQuery.mockReturnValueOnce({ isSuccess: true, data: 'data' });
|
||||
const pattern = '123';
|
||||
const result = useContentTagsCount(pattern);
|
||||
|
||||
expect(result).toEqual({ isSuccess: true, data: 'data' });
|
||||
});
|
||||
|
||||
it('should return failure response', () => {
|
||||
useQuery.mockReturnValueOnce({ isSuccess: false });
|
||||
const pattern = '123';
|
||||
const result = useContentTagsCount(pattern);
|
||||
|
||||
expect(result).toEqual({ isSuccess: false });
|
||||
});
|
||||
});
|
||||
99
src/generic/data/apiHooks.test.tsx
Normal file
99
src/generic/data/apiHooks.test.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import React from 'react';
|
||||
import { initializeMockApp } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
|
||||
import { getTagsCountApiUrl } from './api';
|
||||
import { useContentTagsCount } from './apiHooks';
|
||||
|
||||
let axiosMock;
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const wrapper = ({ children }) => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
describe('useContentTagsCount', () => {
|
||||
beforeEach(() => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: 3,
|
||||
username: 'abc123',
|
||||
administrator: true,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
axiosMock.restore();
|
||||
});
|
||||
|
||||
it('should return success response', async () => {
|
||||
const courseId = 'course-v1:edX+TestX+Test_Course';
|
||||
axiosMock.onGet(getTagsCountApiUrl(courseId)).reply(200, { [courseId]: 10 });
|
||||
|
||||
const hook = renderHook(() => useContentTagsCount(courseId), { wrapper });
|
||||
await hook.waitForNextUpdate();
|
||||
const { data, isSuccess } = hook.result.current;
|
||||
|
||||
expect(axiosMock.history.get[0].url).toEqual(getTagsCountApiUrl(courseId));
|
||||
expect(isSuccess).toEqual(true);
|
||||
expect(data).toEqual(10);
|
||||
});
|
||||
|
||||
it('should return failure response', async () => {
|
||||
const courseId = 'course-v1:edX+TestX+Test_Course';
|
||||
axiosMock.onGet(getTagsCountApiUrl(courseId)).reply(500, 'error');
|
||||
|
||||
const hook = renderHook(() => useContentTagsCount(courseId), { wrapper });
|
||||
await hook.waitForNextUpdate();
|
||||
|
||||
const { isSuccess } = hook.result.current;
|
||||
|
||||
expect(axiosMock.history.get[0].url).toEqual(getTagsCountApiUrl(courseId));
|
||||
expect(isSuccess).toEqual(false);
|
||||
});
|
||||
|
||||
it('should use an wildcard if a block is provided', async () => {
|
||||
const blockId = 'block-v1:edX+TestX+Test_Course+type@chapter+block@123';
|
||||
const pattern = 'block-v1:edX+TestX+Test_Course*';
|
||||
axiosMock.onGet(getTagsCountApiUrl(pattern)).reply(200, {
|
||||
[blockId]: 10,
|
||||
'block-v1:edX+TestX+Test_Course+type@chapter+block@another_block': 5,
|
||||
});
|
||||
|
||||
const hook = renderHook(() => useContentTagsCount(blockId), { wrapper });
|
||||
await hook.waitForNextUpdate();
|
||||
|
||||
const { data, isSuccess } = hook.result.current;
|
||||
|
||||
expect(axiosMock.history.get[0].url).toEqual(getTagsCountApiUrl(pattern));
|
||||
expect(isSuccess).toEqual(true);
|
||||
expect(data).toEqual(10);
|
||||
});
|
||||
|
||||
it('shouldnt call api if no pattern is provided', () => {
|
||||
const hook = renderHook(() => useContentTagsCount(undefined), { wrapper });
|
||||
|
||||
hook.rerender();
|
||||
|
||||
const { isSuccess } = hook.result.current;
|
||||
|
||||
expect(axiosMock.history.get.length).toEqual(0);
|
||||
expect(isSuccess).toEqual(false);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,3 @@
|
||||
// @ts-check
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getOrganizations, getTagsCount } from './api';
|
||||
|
||||
@@ -15,11 +14,10 @@ export const useOrganizationListData = () => (
|
||||
/**
|
||||
* Builds the query to get tags count of the whole contentId course and
|
||||
* returns the tags count of the specific contentId.
|
||||
* @param {string} contentId
|
||||
*/
|
||||
export const useContentTagsCount = (contentId) => {
|
||||
let contentPattern;
|
||||
if (contentId.includes('course-v1')) {
|
||||
export const useContentTagsCount = (contentId?: string) => {
|
||||
let contentPattern: string | undefined;
|
||||
if (!contentId || contentId.includes('course-v1')) {
|
||||
// If the contentId is a course, we want to get the tags count only for the course
|
||||
contentPattern = contentId;
|
||||
} else {
|
||||
@@ -28,7 +26,8 @@ export const useContentTagsCount = (contentId) => {
|
||||
}
|
||||
return useQuery({
|
||||
queryKey: ['contentTagsCount', contentPattern],
|
||||
queryFn: /* istanbul ignore next */ () => getTagsCount(contentPattern),
|
||||
select: (data) => data[contentId] || 0, // Return the tags count of the specific contentId
|
||||
queryFn: () => getTagsCount(contentPattern),
|
||||
select: (data) => (contentId ? (data[contentId] || 0) : 0), // Return the tags count of the specific contentId
|
||||
enabled: !!contentId,
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user