Skip to content

Commit 680b5ff

Browse files
authored
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
1 parent cba85ab commit 680b5ff

File tree

7 files changed

+132
-64
lines changed

7 files changed

+132
-64
lines changed

src/content-tags-drawer/tags-sidebar-controls/TagsSidebarHeader.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const TagsSidebarHeader = () => {
1515
const {
1616
data: contentTagsCount,
1717
isSuccess: isContentTagsCountLoaded,
18-
} = useContentTagsCount(contentId || '');
18+
} = useContentTagsCount(contentId);
1919

2020
return (
2121
<Stack

src/course-outline/card-header/CardHeader.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ const CardHeader = ({
142142
{(isVertical || isSequential) && (
143143
<CardStatus status={status} showDiscussionsEnabledBadge={showDiscussionsEnabledBadge} />
144144
)}
145-
{ getConfig().ENABLE_TAGGING_TAXONOMY_PAGES === 'true' && contentTagCount > 0 && (
145+
{ getConfig().ENABLE_TAGGING_TAXONOMY_PAGES === 'true' && !!contentTagCount && (
146146
<TagCount count={contentTagCount} onClick={openManageTagsDrawer} />
147147
)}
148148
<Dropdown data-testid={`${namePrefix}-card-header__menu`} onClick={onClickMenuButton}>

src/generic/data/api.test.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,9 @@ describe('generic api calls', () => {
8686
expect(contentTagsCountMock[contentId]).toEqual(15);
8787
});
8888

89-
it('should get null on empty pattern', async () => {
90-
const result = await getTagsCount('');
91-
expect(result).toEqual(null);
89+
it('should throw an error if no pattern is provided', async () => {
90+
const pattern = undefined;
91+
expect(getTagsCount(pattern)).rejects.toThrow('contentPattern is required');
92+
expect(axiosMock.history.get.length).toEqual(0);
9293
});
9394
});
Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
1-
// @ts-check
21
import { camelCaseObject, getConfig } from '@edx/frontend-platform';
32
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
43

54
import { convertObjectToSnakeCase } from '../../utils';
65

76
export const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL;
87
export const getCreateOrRerunCourseUrl = () => new URL('course/', getApiBaseUrl()).href;
9-
export const getCourseRerunUrl = (courseId) => new URL(`/api/contentstore/v1/course_rerun/${courseId}`, getApiBaseUrl()).href;
8+
export const getCourseRerunUrl = (courseId: string) => new URL(
9+
`/api/contentstore/v1/course_rerun/${courseId}`,
10+
getApiBaseUrl(),
11+
).href;
1012
export const getOrganizationsUrl = () => new URL('organizations', getApiBaseUrl()).href;
1113
export const getClipboardUrl = () => `${getApiBaseUrl()}/api/content-staging/v1/clipboard/`;
12-
export const getTagsCountApiUrl = (contentPattern) => new URL(`api/content_tagging/v1/object_tag_counts/${contentPattern}/?count_implicit`, getApiBaseUrl()).href;
14+
export const getTagsCountApiUrl = (contentPattern: string) => new URL(
15+
`api/content_tagging/v1/object_tag_counts/${contentPattern}/?count_implicit`,
16+
getApiBaseUrl(),
17+
).href;
1318

1419
/**
1520
* Get's organizations data. Returns list of organization names.
16-
* @returns {Promise<string[]>}
1721
*/
18-
export async function getOrganizations() {
22+
export async function getOrganizations(): Promise<string[]> {
1923
const { data } = await getAuthenticatedHttpClient().get(
2024
getOrganizationsUrl(),
2125
);
@@ -24,9 +28,8 @@ export async function getOrganizations() {
2428

2529
/**
2630
* Get's course rerun data.
27-
* @returns {Promise<Object>}
2831
*/
29-
export async function getCourseRerun(courseId) {
32+
export async function getCourseRerun(courseId: string): Promise<unknown> {
3033
const { data } = await getAuthenticatedHttpClient().get(
3134
getCourseRerunUrl(courseId),
3235
);
@@ -35,10 +38,8 @@ export async function getCourseRerun(courseId) {
3538

3639
/**
3740
* Create or rerun course with data.
38-
* @param {object} courseData
39-
* @returns {Promise<Object>}
4041
*/
41-
export async function createOrRerunCourse(courseData) {
42+
export async function createOrRerunCourse(courseData: Object): Promise<unknown> {
4243
const { data } = await getAuthenticatedHttpClient().post(
4344
getCreateOrRerunCourseUrl(),
4445
convertObjectToSnakeCase(courseData, true),
@@ -48,9 +49,8 @@ export async function createOrRerunCourse(courseData) {
4849

4950
/**
5051
* Retrieves user's clipboard.
51-
* @returns {Promise<Object>} - A Promise that resolves clipboard data.
5252
*/
53-
export async function getClipboard() {
53+
export async function getClipboard(): Promise<unknown> {
5454
const { data } = await getAuthenticatedHttpClient()
5555
.get(getClipboardUrl());
5656

@@ -59,10 +59,8 @@ export async function getClipboard() {
5959

6060
/**
6161
* Updates user's clipboard.
62-
* @param {string} usageKey - The ID of the block.
63-
* @returns {Promise<Object>} - A Promise that resolves clipboard data.
6462
*/
65-
export async function updateClipboard(usageKey) {
63+
export async function updateClipboard(usageKey: string): Promise<unknown> {
6664
const { data } = await getAuthenticatedHttpClient()
6765
.post(getClipboardUrl(), { usage_key: usageKey });
6866

@@ -71,15 +69,14 @@ export async function updateClipboard(usageKey) {
7169

7270
/**
7371
* Gets the tags count of multiple content by id separated by commas or a pattern using a '*' wildcard.
74-
* @param {string} contentPattern
75-
* @returns {Promise<Object>}
7672
*/
77-
export async function getTagsCount(contentPattern) {
78-
if (contentPattern) {
79-
const { data } = await getAuthenticatedHttpClient()
80-
.get(getTagsCountApiUrl(contentPattern));
81-
82-
return data;
73+
export async function getTagsCount(contentPattern?: string): Promise<Record<string, number>> {
74+
if (!contentPattern) {
75+
throw new Error('contentPattern is required');
8376
}
84-
return null;
77+
78+
const { data } = await getAuthenticatedHttpClient()
79+
.get(getTagsCountApiUrl(contentPattern));
80+
81+
return data;
8582
}

src/generic/data/apiHooks.test.js

Lines changed: 0 additions & 28 deletions
This file was deleted.

src/generic/data/apiHooks.test.tsx

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import React from 'react';
2+
import { initializeMockApp } from '@edx/frontend-platform';
3+
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
4+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
5+
import { renderHook } from '@testing-library/react-hooks';
6+
import MockAdapter from 'axios-mock-adapter';
7+
8+
import { getTagsCountApiUrl } from './api';
9+
import { useContentTagsCount } from './apiHooks';
10+
11+
let axiosMock;
12+
13+
const queryClient = new QueryClient({
14+
defaultOptions: {
15+
queries: {
16+
retry: false,
17+
},
18+
},
19+
});
20+
21+
const wrapper = ({ children }) => (
22+
<QueryClientProvider client={queryClient}>
23+
{children}
24+
</QueryClientProvider>
25+
);
26+
27+
describe('useContentTagsCount', () => {
28+
beforeEach(() => {
29+
initializeMockApp({
30+
authenticatedUser: {
31+
userId: 3,
32+
username: 'abc123',
33+
administrator: true,
34+
roles: [],
35+
},
36+
});
37+
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
38+
});
39+
40+
afterEach(() => {
41+
jest.clearAllMocks();
42+
axiosMock.restore();
43+
});
44+
45+
it('should return success response', async () => {
46+
const courseId = 'course-v1:edX+TestX+Test_Course';
47+
axiosMock.onGet(getTagsCountApiUrl(courseId)).reply(200, { [courseId]: 10 });
48+
49+
const hook = renderHook(() => useContentTagsCount(courseId), { wrapper });
50+
await hook.waitForNextUpdate();
51+
const { data, isSuccess } = hook.result.current;
52+
53+
expect(axiosMock.history.get[0].url).toEqual(getTagsCountApiUrl(courseId));
54+
expect(isSuccess).toEqual(true);
55+
expect(data).toEqual(10);
56+
});
57+
58+
it('should return failure response', async () => {
59+
const courseId = 'course-v1:edX+TestX+Test_Course';
60+
axiosMock.onGet(getTagsCountApiUrl(courseId)).reply(500, 'error');
61+
62+
const hook = renderHook(() => useContentTagsCount(courseId), { wrapper });
63+
await hook.waitForNextUpdate();
64+
65+
const { isSuccess } = hook.result.current;
66+
67+
expect(axiosMock.history.get[0].url).toEqual(getTagsCountApiUrl(courseId));
68+
expect(isSuccess).toEqual(false);
69+
});
70+
71+
it('should use an wildcard if a block is provided', async () => {
72+
const blockId = 'block-v1:edX+TestX+Test_Course+type@chapter+block@123';
73+
const pattern = 'block-v1:edX+TestX+Test_Course*';
74+
axiosMock.onGet(getTagsCountApiUrl(pattern)).reply(200, {
75+
[blockId]: 10,
76+
'block-v1:edX+TestX+Test_Course+type@chapter+block@another_block': 5,
77+
});
78+
79+
const hook = renderHook(() => useContentTagsCount(blockId), { wrapper });
80+
await hook.waitForNextUpdate();
81+
82+
const { data, isSuccess } = hook.result.current;
83+
84+
expect(axiosMock.history.get[0].url).toEqual(getTagsCountApiUrl(pattern));
85+
expect(isSuccess).toEqual(true);
86+
expect(data).toEqual(10);
87+
});
88+
89+
it('shouldnt call api if no pattern is provided', () => {
90+
const hook = renderHook(() => useContentTagsCount(undefined), { wrapper });
91+
92+
hook.rerender();
93+
94+
const { isSuccess } = hook.result.current;
95+
96+
expect(axiosMock.history.get.length).toEqual(0);
97+
expect(isSuccess).toEqual(false);
98+
});
99+
});

src/generic/data/apiHooks.js renamed to src/generic/data/apiHooks.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// @ts-check
21
import { useQuery } from '@tanstack/react-query';
32
import { getOrganizations, getTagsCount } from './api';
43

@@ -15,11 +14,10 @@ export const useOrganizationListData = () => (
1514
/**
1615
* Builds the query to get tags count of the whole contentId course and
1716
* returns the tags count of the specific contentId.
18-
* @param {string} contentId
1917
*/
20-
export const useContentTagsCount = (contentId) => {
21-
let contentPattern;
22-
if (contentId.includes('course-v1')) {
18+
export const useContentTagsCount = (contentId?: string) => {
19+
let contentPattern: string | undefined;
20+
if (!contentId || contentId.includes('course-v1')) {
2321
// If the contentId is a course, we want to get the tags count only for the course
2422
contentPattern = contentId;
2523
} else {
@@ -28,7 +26,8 @@ export const useContentTagsCount = (contentId) => {
2826
}
2927
return useQuery({
3028
queryKey: ['contentTagsCount', contentPattern],
31-
queryFn: /* istanbul ignore next */ () => getTagsCount(contentPattern),
32-
select: (data) => data[contentId] || 0, // Return the tags count of the specific contentId
29+
queryFn: () => getTagsCount(contentPattern),
30+
select: (data) => (contentId ? (data[contentId] || 0) : 0), // Return the tags count of the specific contentId
31+
enabled: !!contentId,
3332
});
3433
};

0 commit comments

Comments
 (0)