diff --git a/src/taxonomy/TaxonomyListPage.jsx b/src/taxonomy/TaxonomyListPage.jsx index 481a7943ae..c9751b2f6e 100644 --- a/src/taxonomy/TaxonomyListPage.jsx +++ b/src/taxonomy/TaxonomyListPage.jsx @@ -11,7 +11,7 @@ import Header from '../header'; import SubHeader from '../generic/sub-header/SubHeader'; import messages from './messages'; import TaxonomyCard from './taxonomy-card'; -import { useTaxonomyListDataResponse, useIsTaxonomyListDataLoaded } from './api/hooks/selectors'; +import { useTaxonomyListDataResponse, useIsTaxonomyListDataLoaded } from './hooks'; const TaxonomyListPage = () => { const intl = useIntl(); diff --git a/src/taxonomy/TaxonomyListPage.test.jsx b/src/taxonomy/TaxonomyListPage.test.jsx index 99c947b75e..34be17a1ee 100644 --- a/src/taxonomy/TaxonomyListPage.test.jsx +++ b/src/taxonomy/TaxonomyListPage.test.jsx @@ -7,11 +7,11 @@ import { act, render } from '@testing-library/react'; import initializeStore from '../store'; import TaxonomyListPage from './TaxonomyListPage'; -import { useTaxonomyListDataResponse, useIsTaxonomyListDataLoaded } from './api/hooks/selectors'; +import { useTaxonomyListDataResponse, useIsTaxonomyListDataLoaded } from './hooks'; let store; -jest.mock('./api/hooks/selectors', () => ({ +jest.mock('./hooks', () => ({ useTaxonomyListDataResponse: jest.fn(), useIsTaxonomyListDataLoaded: jest.fn(), })); diff --git a/src/taxonomy/__mocks__/index.js b/src/taxonomy/__mocks__/index.js new file mode 100644 index 0000000000..bbbeb62a2d --- /dev/null +++ b/src/taxonomy/__mocks__/index.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export { default as taxonomyListMock } from './taxonomyListMock'; diff --git a/src/taxonomy/__mocks__/taxonomyListMock.js b/src/taxonomy/__mocks__/taxonomyListMock.js new file mode 100644 index 0000000000..5eab0af023 --- /dev/null +++ b/src/taxonomy/__mocks__/taxonomyListMock.js @@ -0,0 +1,50 @@ +module.exports = { + next: null, + previous: null, + count: 4, + numPages: 1, + currentPage: 1, + start: 0, + results: [ + { + id: -2, + name: 'Content Authors', + description: 'Allows tags for any user ID created on the instance.', + enabled: true, + allowMultiple: false, + allowFreeText: false, + systemDefined: true, + visibleToAuthors: false, + }, + { + id: -1, + name: 'Languages', + description: 'lang lang lang lang lang lang lang lang', + enabled: true, + allowMultiple: false, + allowFreeText: false, + systemDefined: true, + visibleToAuthors: true, + }, + { + id: 1, + name: 'Taxonomy', + description: 'This is a Description', + enabled: true, + allowMultiple: false, + allowFreeText: false, + systemDefined: false, + visibleToAuthors: true, + }, + { + id: 2, + name: 'Taxonomy long long long long long long long long long long long long long long long long long long long', + description: 'This is a Description long lon', + enabled: true, + allowMultiple: false, + allowFreeText: false, + systemDefined: false, + visibleToAuthors: true, + }, + ], +}; diff --git a/src/taxonomy/api/hooks/api.js b/src/taxonomy/api/hooks/api.js deleted file mode 100644 index b524bd197f..0000000000 --- a/src/taxonomy/api/hooks/api.js +++ /dev/null @@ -1,26 +0,0 @@ -// @ts-check -import { useQuery } from '@tanstack/react-query'; -import { camelCaseObject, getConfig } from '@edx/frontend-platform'; -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; - -const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL; -const getTaxonomyListApiUrl = () => new URL('api/content_tagging/v1/taxonomies/?enabled=true', getApiBaseUrl()).href; -export const getExportTaxonomyApiUrl = (pk, format) => new URL( - `api/content_tagging/v1/taxonomies/${pk}/export/?output_format=${format}&download=1`, - getApiBaseUrl(), -).href; - -/** - * @returns {import("../types.mjs").UseQueryResult} - */ -export const useTaxonomyListData = () => ( - useQuery({ - queryKey: ['taxonomyList'], - queryFn: () => getAuthenticatedHttpClient().get(getTaxonomyListApiUrl()) - .then(camelCaseObject), - }) -); - -export const exportTaxonomy = (pk, format) => { - window.location.href = getExportTaxonomyApiUrl(pk, format); -}; diff --git a/src/taxonomy/api/hooks/api.test.js b/src/taxonomy/api/hooks/api.test.js deleted file mode 100644 index b3dc0045d1..0000000000 --- a/src/taxonomy/api/hooks/api.test.js +++ /dev/null @@ -1,56 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { useTaxonomyListData, exportTaxonomy } from './api'; - -const mockHttpClient = { - get: jest.fn(), -}; - -jest.mock('@tanstack/react-query', () => ({ - useQuery: jest.fn(), -})); - -jest.mock('@edx/frontend-platform/auth', () => ({ - getAuthenticatedHttpClient: jest.fn(() => mockHttpClient), -})); - -describe('useTaxonomyListData', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should call useQuery with the correct parameters', () => { - useTaxonomyListData(); - - expect(useQuery).toHaveBeenCalledWith({ - queryKey: ['taxonomyList'], - queryFn: expect.any(Function), - }); - }); -}); - -describe('exportTaxonomy', () => { - const { location } = window; - - beforeAll(() => { - delete window.location; - window.location = { - href: '', - }; - }); - - afterAll(() => { - window.location = location; - }); - - it('should set window.location.href correctly', () => { - const pk = 1; - const format = 'json'; - - exportTaxonomy(pk, format); - - expect(window.location.href).toEqual( - 'http://localhost:18010/api/content_tagging/' - + 'v1/taxonomies/1/export/?output_format=json&download=1', - ); - }); -}); diff --git a/src/taxonomy/api/hooks/selectors.js b/src/taxonomy/api/hooks/selectors.js deleted file mode 100644 index b2c678be78..0000000000 --- a/src/taxonomy/api/hooks/selectors.js +++ /dev/null @@ -1,27 +0,0 @@ -// @ts-check -import { - useTaxonomyListData, - exportTaxonomy, -} from './api'; - -/** - * @returns {import("../types.mjs").TaxonomyListData | undefined} - */ -export const useTaxonomyListDataResponse = () => { - const response = useTaxonomyListData(); - if (response.status === 'success') { - return response.data.data; - } - return undefined; -}; - -/** - * @returns {boolean} - */ -export const useIsTaxonomyListDataLoaded = () => ( - useTaxonomyListData().status === 'success' -); - -export const callExportTaxonomy = (pk, format) => ( - exportTaxonomy(pk, format) -); diff --git a/src/taxonomy/data/api.js b/src/taxonomy/data/api.js new file mode 100644 index 0000000000..513f7fdaf9 --- /dev/null +++ b/src/taxonomy/data/api.js @@ -0,0 +1,29 @@ +// @ts-check +import { camelCaseObject, getConfig } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; + +const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL; +export const getTaxonomyListApiUrl = () => new URL('api/content_tagging/v1/taxonomies/?enabled=true', getApiBaseUrl()).href; +export const getExportTaxonomyApiUrl = (pk, format) => new URL( + `api/content_tagging/v1/taxonomies/${pk}/export/?output_format=${format}&download=1`, + getApiBaseUrl(), +).href; + +/** + * Get list of taxonomies. + * @returns {Promise} + */ +export async function getTaxonomyListData() { + const { data } = await getAuthenticatedHttpClient().get(getTaxonomyListApiUrl()); + return camelCaseObject(data); +} + +/** + * Downloads the file of the exported taxonomy + * @param {number} pk + * @param {string} format + * @returns {void} + */ +export function getTaxonomyExportFile(pk, format) { + window.location.href = getExportTaxonomyApiUrl(pk, format); +} diff --git a/src/taxonomy/data/api.test.js b/src/taxonomy/data/api.test.js new file mode 100644 index 0000000000..5928c70098 --- /dev/null +++ b/src/taxonomy/data/api.test.js @@ -0,0 +1,61 @@ +import MockAdapter from 'axios-mock-adapter'; +import { initializeMockApp } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; + +import { taxonomyListMock } from '../__mocks__'; + +import { + getTaxonomyListApiUrl, + getExportTaxonomyApiUrl, + getTaxonomyListData, + getTaxonomyExportFile, +} from './api'; + +let axiosMock; + +describe('taxonomy api calls', () => { + const { location } = window; + beforeEach(() => { + initializeMockApp({ + authenticatedUser: { + userId: 3, + username: 'abc123', + administrator: true, + roles: [], + }, + }); + axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + beforeAll(() => { + delete window.location; + window.location = { + href: '', + }; + }); + + afterAll(() => { + window.location = location; + }); + + it('should get taxonomy list data', async () => { + axiosMock.onGet(getTaxonomyListApiUrl()).reply(200, taxonomyListMock); + const result = await getTaxonomyListData(); + + expect(axiosMock.history.get[0].url).toEqual(getTaxonomyListApiUrl()); + expect(result).toEqual(taxonomyListMock); + }); + + it('should set window.location.href correctly', () => { + const pk = 1; + const format = 'json'; + + getTaxonomyExportFile(pk, format); + + expect(window.location.href).toEqual(getExportTaxonomyApiUrl(pk, format)); + }); +}); diff --git a/src/taxonomy/data/thunks.js b/src/taxonomy/data/thunks.js new file mode 100644 index 0000000000..44afd37722 --- /dev/null +++ b/src/taxonomy/data/thunks.js @@ -0,0 +1,14 @@ +// @ts-check +import { getTaxonomyExportFile } from './api'; + +/** + * Downloads the file of the exported taxonomy + * @param {number} pk + * @param {string} format + * @returns {void} + */ +const exportTaxonomy = (pk, format) => ( + getTaxonomyExportFile(pk, format) +); + +export default exportTaxonomy; diff --git a/src/taxonomy/api/types.mjs b/src/taxonomy/data/types.mjs similarity index 100% rename from src/taxonomy/api/types.mjs rename to src/taxonomy/data/types.mjs diff --git a/src/taxonomy/export-modal/ExportModal.test.jsx b/src/taxonomy/export-modal/ExportModal.test.jsx index 9ddcfdea6f..c9f4908911 100644 --- a/src/taxonomy/export-modal/ExportModal.test.jsx +++ b/src/taxonomy/export-modal/ExportModal.test.jsx @@ -5,14 +5,15 @@ import { AppProvider } from '@edx/frontend-platform/react'; import { render, fireEvent } from '@testing-library/react'; import ExportModal from '.'; import initializeStore from '../../store'; -import { callExportTaxonomy } from '../api/hooks/selectors'; +import exportTaxonomy from '../data/thunks'; const onClose = jest.fn(); let store; const taxonomyId = 1; -jest.mock('../api/hooks/selectors', () => ({ - callExportTaxonomy: jest.fn(), +jest.mock('../data/thunks', () => ({ + __esModule: true, + default: jest.fn(), })); const ExportModalComponent = () => ( @@ -48,6 +49,6 @@ describe('', async () => { fireEvent.click(getByText('Export')); expect(onClose).toHaveBeenCalled(); - expect(callExportTaxonomy).toHaveBeenCalledWith(taxonomyId, 'json'); + expect(exportTaxonomy).toHaveBeenCalledWith(taxonomyId, 'json'); }); }); diff --git a/src/taxonomy/export-modal/index.jsx b/src/taxonomy/export-modal/index.jsx index 27bc8987a3..7fea965225 100644 --- a/src/taxonomy/export-modal/index.jsx +++ b/src/taxonomy/export-modal/index.jsx @@ -8,7 +8,7 @@ import { import PropTypes from 'prop-types'; import { useIntl } from '@edx/frontend-platform/i18n'; import messages from './messages'; -import { callExportTaxonomy } from '../api/hooks/selectors'; +import exportTaxonomy from '../data/thunks'; const ExportModal = ({ taxonomyId, @@ -20,7 +20,7 @@ const ExportModal = ({ const onClickExport = () => { onClose(); - callExportTaxonomy(taxonomyId, outputFormat); + exportTaxonomy(taxonomyId, outputFormat); }; return ( diff --git a/src/taxonomy/hooks.jsx b/src/taxonomy/hooks.jsx new file mode 100644 index 0000000000..1958830f82 --- /dev/null +++ b/src/taxonomy/hooks.jsx @@ -0,0 +1,34 @@ +// @ts-check +import { useQuery } from '@tanstack/react-query'; +import { getTaxonomyListData } from './data/api'; + +/** + * Builds the query yo get the taxonomy list + * @returns {import("./data/types.mjs").UseQueryResult} + */ +const useTaxonomyListData = () => ( + useQuery({ + queryKey: ['taxonomyList'], + queryFn: getTaxonomyListData, + }) +); + +/** + * Gets the taxonomy list data + * @returns {import("./data/types.mjs").TaxonomyListData | undefined} + */ +export const useTaxonomyListDataResponse = () => { + const response = useTaxonomyListData(); + if (response.status === 'success') { + return response.data; + } + return undefined; +}; + +/** + * Returns the status of the taxonomy list query + * @returns {boolean} + */ +export const useIsTaxonomyListDataLoaded = () => ( + useTaxonomyListData().status === 'success' +); diff --git a/src/taxonomy/api/hooks/selectors.test.js b/src/taxonomy/hooks.test.jsx similarity index 60% rename from src/taxonomy/api/hooks/selectors.test.js rename to src/taxonomy/hooks.test.jsx index 772b39c4d8..d915f55c99 100644 --- a/src/taxonomy/api/hooks/selectors.test.js +++ b/src/taxonomy/hooks.test.jsx @@ -1,27 +1,24 @@ +import { useQuery } from '@tanstack/react-query'; import { useTaxonomyListDataResponse, useIsTaxonomyListDataLoaded, - callExportTaxonomy, -} from './selectors'; -import { useTaxonomyListData, exportTaxonomy } from './api'; - -jest.mock('./api', () => ({ - __esModule: true, - useTaxonomyListData: jest.fn(), - exportTaxonomy: jest.fn(), +} from './hooks'; + +jest.mock('@tanstack/react-query', () => ({ + useQuery: jest.fn(), })); describe('useTaxonomyListDataResponse', () => { it('should return data when status is success', () => { - useTaxonomyListData.mockReturnValueOnce({ status: 'success', data: { data: 'data' } }); + useQuery.mockReturnValueOnce({ status: 'success', data: { data: 'data' } }); const result = useTaxonomyListDataResponse(); - expect(result).toEqual('data'); + expect(result).toEqual({ data: 'data' }); }); it('should return undefined when status is not success', () => { - useTaxonomyListData.mockReturnValueOnce({ status: 'error' }); + useQuery.mockReturnValueOnce({ status: 'error' }); const result = useTaxonomyListDataResponse(); @@ -31,7 +28,7 @@ describe('useTaxonomyListDataResponse', () => { describe('useIsTaxonomyListDataLoaded', () => { it('should return true when status is success', () => { - useTaxonomyListData.mockReturnValueOnce({ status: 'success' }); + useQuery.mockReturnValueOnce({ status: 'success' }); const result = useIsTaxonomyListDataLoaded(); @@ -39,7 +36,7 @@ describe('useIsTaxonomyListDataLoaded', () => { }); it('should return false when status is not success', () => { - useTaxonomyListData.mockReturnValueOnce({ status: 'error' }); + useQuery.mockReturnValueOnce({ status: 'error' }); const result = useIsTaxonomyListDataLoaded(); @@ -47,10 +44,11 @@ describe('useIsTaxonomyListDataLoaded', () => { }); }); -describe('callExportTaxonomy', () => { +/* describe('callExportTaxonomy', () => { it('should trigger exportTaxonomy', () => { callExportTaxonomy(1, 'csv'); expect(exportTaxonomy).toHaveBeenCalled(); }); }); +*/