diff --git a/src/generic/loading-button/LoadingButton.test.jsx b/src/generic/loading-button/LoadingButton.test.jsx index 2157bb46b6..5a50e82c20 100644 --- a/src/generic/loading-button/LoadingButton.test.jsx +++ b/src/generic/loading-button/LoadingButton.test.jsx @@ -48,7 +48,7 @@ describe('', () => { }); it('renders the spinner correctly even with error', () => { - const longFunction = () => new Promise((resolve, reject) => { + const longFunction = () => new Promise((_resolve, reject) => { setTimeout(reject, 1000); }); const { getByRole, getByText, getByTestId } = render(RootWrapper(longFunction)); diff --git a/src/generic/loading-button/index.jsx b/src/generic/loading-button/index.jsx index 911951729d..55e88cad97 100644 --- a/src/generic/loading-button/index.jsx +++ b/src/generic/loading-button/index.jsx @@ -7,6 +7,14 @@ import { Stack, } from '@edx/paragon'; +/** + * A button that shows a loading spinner when clicked. + * @param {object} props + * @param {React.ReactNode=} props.children + * @param {boolean=} props.disabled + * @param {function=} props.onClick + * @returns {JSX.Element} + */ const LoadingButton = ({ onClick, children, @@ -48,7 +56,6 @@ LoadingButton.propTypes = { LoadingButton.defaultProps = { ...Button.defaultProps, - disabled: null, }; export default LoadingButton; diff --git a/src/taxonomy/import-tags/ImportTagsWizard.jsx b/src/taxonomy/import-tags/ImportTagsWizard.jsx index ccd313e5bc..0f6308c6eb 100644 --- a/src/taxonomy/import-tags/ImportTagsWizard.jsx +++ b/src/taxonomy/import-tags/ImportTagsWizard.jsx @@ -1,3 +1,4 @@ +// @ts-check import React, { useState } from 'react'; import { useIntl } from '@edx/frontend-platform/i18n'; import { @@ -209,23 +210,25 @@ const ImportTagsWizard = ({ const [file, setFile] = useState(/** @type {null|File} */ (null)); - const [importPlan, setImportPlan] = useState(null); + const [importPlan, setImportPlan] = useState(/** @type {null|string[]} */ (null)); const [importPlanError, setImportPlanError] = useState(null); const importTagsMutation = useImportTags(); const generatePlan = async () => { try { - const plan = await planImportTags(taxonomy.id, file); - let planArrayTemp = plan.split('\n'); - planArrayTemp = planArrayTemp.slice(2); // Removes the first two lines - planArrayTemp = planArrayTemp.slice(0, -1); // Removes the last line - const planArray = planArrayTemp - .filter((line) => !(line.includes('No changes'))) // Removes the "No changes" lines - .map((line) => line.split(':')[1].trim()); // Get only the action message - setImportPlan(planArray); - setImportPlanError(null); - setCurrentStep('plan'); + if (file) { + const plan = await planImportTags(taxonomy.id, file); + let planArrayTemp = plan.split('\n'); + planArrayTemp = planArrayTemp.slice(2); // Removes the first two lines + planArrayTemp = planArrayTemp.slice(0, -1); // Removes the last line + const planArray = planArrayTemp + .filter((line) => !(line.includes('No changes'))) // Removes the "No changes" lines + .map((line) => line.split(':')[1].trim()); // Get only the action message + setImportPlan(planArray); + setImportPlanError(null); + setCurrentStep('plan'); + } } catch (/** @type {any} */ error) { setImportPlanError(error.message); } @@ -233,11 +236,13 @@ const ImportTagsWizard = ({ const confirmImportTags = async () => { try { - await importTagsMutation.mutateAsync({ - taxonomyId: taxonomy.id, - file, - }); - close(); + if (file) { + await importTagsMutation.mutateAsync({ + taxonomyId: taxonomy.id, + file, + }); + close(); + } // ToDo: show success toast } catch (error) { // ToDo: show error message diff --git a/src/taxonomy/import-tags/data/api.js b/src/taxonomy/import-tags/data/api.js index 103424a289..f4a063e23f 100644 --- a/src/taxonomy/import-tags/data/api.js +++ b/src/taxonomy/import-tags/data/api.js @@ -33,9 +33,10 @@ export const getTagsPlanImportApiUrl = (taxonomyId) => new URL( * @param {string} taxonomyName * @param {string} taxonomyDescription * @param {File} file - * @returns {Promise} + * @returns {Promise { const formData = new FormData(); formData.append('file', file); - await getAuthenticatedHttpClient().put( + const { data } = await getAuthenticatedHttpClient().put( getTagsImportApiUrl(taxonomyId), formData, ); + + return camelCaseObject(data); }, - onSuccess: (_data, variables) => { + onSuccess: (data, variables) => { queryClient.invalidateQueries({ queryKey: ['tagList', variables.taxonomyId], }); - queryClient.invalidateQueries({ - queryKey: ['taxonomyDetail', variables.taxonomyId], - }); + queryClient.setQueryData(['taxonomyDetail', variables.taxonomyId], data); }, }); }; @@ -89,7 +90,7 @@ export const useImportTags = () => { * Plan import tags to an existing taxonomy, overwriting existing tags * @param {number} taxonomyId * @param {File} file - * @returns {Promise} + * @returns {Promise} */ export async function planImportTags(taxonomyId, file) { const formData = new FormData(); @@ -101,13 +102,8 @@ export async function planImportTags(taxonomyId, file) { formData, ); - return camelCaseObject(data.plan); - } catch (err) { - // @ts-ignore - if (err.response?.data?.error) { - // @ts-ignore - throw new Error(err.response.data.error); - } - throw err; + return data.plan; + } catch (/** @type {any} */ err) { + throw new Error(err.response?.data?.error || err.message); } } diff --git a/src/taxonomy/import-tags/data/api.test.jsx b/src/taxonomy/import-tags/data/api.test.jsx index 2170d3b9ec..6461e6808b 100644 --- a/src/taxonomy/import-tags/data/api.test.jsx +++ b/src/taxonomy/import-tags/data/api.test.jsx @@ -58,8 +58,10 @@ describe('import taxonomy api calls', () => { }); it('should call import tags', async () => { - axiosMock.onPut(getTagsImportApiUrl(1)).reply(200); + const taxonomy = { id: 1, name: 'taxonomy name' }; + axiosMock.onPut(getTagsImportApiUrl(1)).reply(200, taxonomy); const mockInvalidateQueries = jest.spyOn(queryClient, 'invalidateQueries'); + const mockSetQueryData = jest.spyOn(queryClient, 'setQueryData'); const { result } = renderHook(() => useImportTags(), { wrapper }); @@ -68,9 +70,7 @@ describe('import taxonomy api calls', () => { expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['tagList', 1], }); - expect(mockInvalidateQueries).toHaveBeenCalledWith({ - queryKey: ['taxonomyDetail', 1], - }); + expect(mockSetQueryData).toHaveBeenCalledWith(['taxonomyDetail', 1], taxonomy); }); it('should call plan import tags', async () => { @@ -81,10 +81,8 @@ describe('import taxonomy api calls', () => { it('should handle errors in plan import tags', async () => { axiosMock.onPut(getTagsPlanImportApiUrl(1)).reply(400, { error: 'test error' }); - const mockInvalidateQueries = jest.spyOn(queryClient, 'invalidateQueries'); expect(planImportTags(1)).rejects.toEqual(Error('test error')); expect(axiosMock.history.put[0].url).toEqual(getTagsPlanImportApiUrl(1)); - expect(mockInvalidateQueries).not.toHaveBeenCalled(); }); });