From 0c60c376d19057050ddbe69f0ae18ee6c985eb2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Thu, 9 Nov 2023 09:34:47 -0300 Subject: [PATCH 01/16] feat: add import taxonomy feature --- src/taxonomy/TaxonomyListPage.jsx | 30 ++++++--- src/taxonomy/import-tags/data/actions.js | 85 ++++++++++++++++++++++++ src/taxonomy/import-tags/data/api.js | 28 ++++++++ src/taxonomy/import-tags/index.js | 5 ++ src/taxonomy/import-tags/messages.js | 26 ++++++++ 5 files changed, 166 insertions(+), 8 deletions(-) create mode 100644 src/taxonomy/import-tags/data/actions.js create mode 100644 src/taxonomy/import-tags/data/api.js create mode 100644 src/taxonomy/import-tags/index.js create mode 100644 src/taxonomy/import-tags/messages.js diff --git a/src/taxonomy/TaxonomyListPage.jsx b/src/taxonomy/TaxonomyListPage.jsx index 5d73ef6644..bce0a4f2da 100644 --- a/src/taxonomy/TaxonomyListPage.jsx +++ b/src/taxonomy/TaxonomyListPage.jsx @@ -1,18 +1,38 @@ import React from 'react'; import { + Button, CardView, Container, DataTable, Spinner, } from '@edx/paragon'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { + Add, +} from '@edx/paragon/icons'; +import { injectIntl, intlShape, useIntl } from '@edx/frontend-platform/i18n'; import { StudioFooter } from '@edx/frontend-component-footer'; + import Header from '../header'; import SubHeader from '../generic/sub-header/SubHeader'; +import { actions as importActions } from './import-tags'; import messages from './messages'; import TaxonomyCard from './TaxonomyCard'; import { useTaxonomyListDataResponse, useIsTaxonomyListDataLoaded } from './api/hooks/selectors'; +const TaxonomyListHeaderButtons = () => { + const intl = useIntl(); + return ( + <> + + + + ); +}; + const TaxonomyListPage = ({ intl }) => { const useTaxonomyListData = () => { const taxonomyListData = useTaxonomyListDataResponse(); @@ -22,12 +42,6 @@ const TaxonomyListPage = ({ intl }) => { const { taxonomyListData, isLoaded } = useTaxonomyListData(); - const getHeaderButtons = () => ( - // Download template and import buttons. - // TODO Add functionality to this buttons. - undefined - ); - const getOrgSelect = () => ( // Organization select component // TODO Add functionality to this component @@ -49,7 +63,7 @@ const TaxonomyListPage = ({ intl }) => { } hideBorder /> diff --git a/src/taxonomy/import-tags/data/actions.js b/src/taxonomy/import-tags/data/actions.js new file mode 100644 index 0000000000..b905138ad7 --- /dev/null +++ b/src/taxonomy/import-tags/data/actions.js @@ -0,0 +1,85 @@ +import messages from '../messages'; +import { importNewTaxonomy } from './api'; + +const importTaxonomy = async (intl) => { + /* + * This function is a temporary "Barebones" implementation of the import + * functionality with `prompt` and `alert`. It is intended to be replaced + * with a component that shows a `ModalDialog` in the future. + * See: https://github.com/openedx/modular-learning/issues/116 + */ + /* eslint-disable no-alert */ + /* eslint-disable no-console */ + + const selectFile = async () => new Promise((resolve) => { + /* + * This function get a file from the user. It does this by creating a + * file input element, and then clicking it. This allows us to get a file + * from the user without using a form. The file input element is created + * and appended to the DOM, then clicked. When the user selects a file, + * the change event is fired, and the file is resolved. + * The file input element is then removed from the DOM. + */ + const fileInput = document.createElement('input'); + fileInput.type = 'file'; + fileInput.accept = '.json,.csv'; + fileInput.addEventListener('change', (event) => { + const file = event.target.files[0]; + if (!file) { + resolve(null); + } + resolve(file); + document.body.removeChild(fileInput); + }); + + document.body.appendChild(fileInput); + fileInput.click(); + }); + + const getTaxonomyName = () => { + let taxonomyName = null; + while (!taxonomyName) { + taxonomyName = prompt(intl.formatMessage(messages.promptTaxonomyName)); + + if (taxonomyName == null) { + break; + } + + if (!taxonomyName) { + alert(intl.formatMessage(messages.promptTaxonomyNameRequired)); + } + } + return taxonomyName; + }; + + const getTaxonomyDescription = () => prompt(intl.formatMessage(messages.promptTaxonomyDescription)); + + const file = await selectFile(); + + if (!file) { + return; + } + + const taxonomyName = getTaxonomyName(); + if (taxonomyName == null) { + return; + } + + const taxonomyDescription = getTaxonomyDescription(); + if (taxonomyDescription == null) { + return; + } + + importNewTaxonomy(taxonomyName, taxonomyDescription, file) + .then(() => { + alert(intl.formatMessage(messages.importTaxonomySuccess)); + }) + .catch((error) => { + alert(intl.formatMessage(messages.importTaxonomyError)); + console.error(error.response); + }); +}; + +export default { + importTaxonomy, +}; diff --git a/src/taxonomy/import-tags/data/api.js b/src/taxonomy/import-tags/data/api.js new file mode 100644 index 0000000000..2ac634287a --- /dev/null +++ b/src/taxonomy/import-tags/data/api.js @@ -0,0 +1,28 @@ +// @ts-check +import { camelCaseObject, getConfig } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; + +const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL; + +const getTaxonomyImportApiUrl = () => new URL( + 'api/content_tagging/v1/taxonomies/import/', + getApiBaseUrl(), +).href; + +/** + * Import a new taxonomy + * @param {string} taxonomyName + * @param {string} taxonomyDescription + * @param {File} file + * @returns {Promise} + */ // eslint-disable-next-line import/prefer-default-export +export async function importNewTaxonomy(taxonomyName, taxonomyDescription, file) { + const formData = new FormData(); + formData.append('taxonomy_name', taxonomyName); + formData.append('taxonomy_description', taxonomyDescription); + formData.append('file', file); + + const { data } = await getAuthenticatedHttpClient().post(getTaxonomyImportApiUrl(), formData); + + return camelCaseObject(data); +} diff --git a/src/taxonomy/import-tags/index.js b/src/taxonomy/import-tags/index.js new file mode 100644 index 0000000000..831d75323e --- /dev/null +++ b/src/taxonomy/import-tags/index.js @@ -0,0 +1,5 @@ +import actions from './data/actions'; + +export { + actions, // eslint-disable-line import/prefer-default-export +}; diff --git a/src/taxonomy/import-tags/messages.js b/src/taxonomy/import-tags/messages.js new file mode 100644 index 0000000000..f9a1ff273b --- /dev/null +++ b/src/taxonomy/import-tags/messages.js @@ -0,0 +1,26 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + promptTaxonomyName: { + id: 'course-authoring.import-tags.prompt.taxonomy-name', + defaultMessage: 'Enter a name for the new taxonomy', + }, + promptTaxonomyNameRequired: { + id: 'course-authoring.import-tags.prompt.taxonomy-name.required', + defaultMessage: 'You must enter a name for the new taxonomy', + }, + promptTaxonomyDescription: { + id: 'course-authoring.import-tags.prompt.taxonomy-description', + defaultMessage: 'Enter a description for the new taxonomy', + }, + importTaxonomySuccess: { + id: 'course-authoring.import-tags.success', + defaultMessage: 'Taxonomy imported successfully', + }, + importTaxonomyError: { + id: 'course-authoring.import-tags.error', + defaultMessage: 'Import failed - see details in the browser console', + }, +}); + +export default messages; From c273af164d8f85aa9e591009ae8d009b0163c21c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Thu, 9 Nov 2023 10:33:59 -0300 Subject: [PATCH 02/16] test: add api.test.js --- jest.config.js | 1 + src/taxonomy/import-tags/__mocks__/index.js | 2 + .../__mocks__/taxonomyImportMock.js | 4 ++ src/taxonomy/import-tags/data/api.js | 4 +- src/taxonomy/import-tags/data/api.test.js | 53 +++++++++++++++++++ 5 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 src/taxonomy/import-tags/__mocks__/index.js create mode 100644 src/taxonomy/import-tags/__mocks__/taxonomyImportMock.js create mode 100644 src/taxonomy/import-tags/data/api.test.js diff --git a/jest.config.js b/jest.config.js index 4e5f6ce264..52d2ad26a3 100644 --- a/jest.config.js +++ b/jest.config.js @@ -14,4 +14,5 @@ module.exports = createConfig('jest', { moduleNameMapper: { '^lodash-es$': 'lodash', }, + modulePathIgnorePatterns: ["/.*/__mocks__"], }); diff --git a/src/taxonomy/import-tags/__mocks__/index.js b/src/taxonomy/import-tags/__mocks__/index.js new file mode 100644 index 0000000000..84c5b352ac --- /dev/null +++ b/src/taxonomy/import-tags/__mocks__/index.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export { default as taxonomyImportMock } from './taxonomyImportMock'; diff --git a/src/taxonomy/import-tags/__mocks__/taxonomyImportMock.js b/src/taxonomy/import-tags/__mocks__/taxonomyImportMock.js new file mode 100644 index 0000000000..9db45b4a5e --- /dev/null +++ b/src/taxonomy/import-tags/__mocks__/taxonomyImportMock.js @@ -0,0 +1,4 @@ +export default { + name: 'Taxonomy name', + description: 'Taxonomy description', +}; diff --git a/src/taxonomy/import-tags/data/api.js b/src/taxonomy/import-tags/data/api.js index 2ac634287a..ed0553c3ed 100644 --- a/src/taxonomy/import-tags/data/api.js +++ b/src/taxonomy/import-tags/data/api.js @@ -4,7 +4,7 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL; -const getTaxonomyImportApiUrl = () => new URL( +export const getTaxonomyImportApiUrl = () => new URL( 'api/content_tagging/v1/taxonomies/import/', getApiBaseUrl(), ).href; @@ -15,7 +15,7 @@ const getTaxonomyImportApiUrl = () => new URL( * @param {string} taxonomyDescription * @param {File} file * @returns {Promise} - */ // eslint-disable-next-line import/prefer-default-export + */ export async function importNewTaxonomy(taxonomyName, taxonomyDescription, file) { const formData = new FormData(); formData.append('taxonomy_name', taxonomyName); diff --git a/src/taxonomy/import-tags/data/api.test.js b/src/taxonomy/import-tags/data/api.test.js new file mode 100644 index 0000000000..1edf08f3f9 --- /dev/null +++ b/src/taxonomy/import-tags/data/api.test.js @@ -0,0 +1,53 @@ +import MockAdapter from 'axios-mock-adapter'; +import { initializeMockApp } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; + +import { taxonomyImportMock } from '../__mocks__'; + +import { + getTaxonomyImportApiUrl, + importNewTaxonomy, +} from './api'; + +let axiosMock; + +describe('import taxonomy api calls', () => { + beforeEach(() => { + initializeMockApp({ + authenticatedUser: { + userId: 3, + username: 'abc123', + administrator: true, + roles: [], + }, + }); + axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should call import taxonomy', async () => { + const tags = { + tags: [ + { id: 'tag_1', value: 'Tag 1' }, + ], + }; + const str = JSON.stringify(tags); + const blob = new Blob([str]); + const file = new File([blob], 'taxonomy.json', { + type: 'application/JSON', + }); + const formData = new FormData(); + formData.append('taxonomy_name', 'Taxonomy name'); + formData.append('taxonomy_description', 'Taxonomy description'); + formData.append('file', file); + formData.asymmetricMatch = () => true; + axiosMock.onPost(getTaxonomyImportApiUrl(), formData).reply(201, taxonomyImportMock); + const result = await importNewTaxonomy('Taxonomy name', 'Taxonomy description', file); + + expect(axiosMock.history.post[0].url).toEqual(getTaxonomyImportApiUrl()); + expect(result).toEqual(taxonomyImportMock); + }); +}); From 047530d8cc23a3cfbad163ee381f8e4f993b41ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Thu, 9 Nov 2023 11:11:08 -0300 Subject: [PATCH 03/16] test: add import button click test --- src/taxonomy/TaxonomyListPage.jsx | 6 +++++- src/taxonomy/TaxonomyListPage.test.jsx | 23 +++++++++++++++++++++++ src/taxonomy/import-tags/data/actions.js | 7 ++----- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/taxonomy/TaxonomyListPage.jsx b/src/taxonomy/TaxonomyListPage.jsx index bce0a4f2da..34aaf739cc 100644 --- a/src/taxonomy/TaxonomyListPage.jsx +++ b/src/taxonomy/TaxonomyListPage.jsx @@ -26,7 +26,11 @@ const TaxonomyListHeaderButtons = () => { - diff --git a/src/taxonomy/TaxonomyListPage.test.jsx b/src/taxonomy/TaxonomyListPage.test.jsx index 99c947b75e..92a2378da0 100644 --- a/src/taxonomy/TaxonomyListPage.test.jsx +++ b/src/taxonomy/TaxonomyListPage.test.jsx @@ -8,6 +8,7 @@ import initializeStore from '../store'; import TaxonomyListPage from './TaxonomyListPage'; import { useTaxonomyListDataResponse, useIsTaxonomyListDataLoaded } from './api/hooks/selectors'; +import { importTaxonomy } from './import-tags/data/actions'; let store; @@ -16,6 +17,10 @@ jest.mock('./api/hooks/selectors', () => ({ useIsTaxonomyListDataLoaded: jest.fn(), })); +jest.mock('./import-tags/data/actions', () => ({ + importTaxonomy: jest.fn(), +})); + const RootWrapper = () => ( @@ -65,4 +70,22 @@ describe('', async () => { expect(getByTestId('taxonomy-card-1')).toBeInTheDocument(); }); }); + + it('calls the import taxonomy action when the import button is clicked', async () => { + useIsTaxonomyListDataLoaded.mockReturnValue(true); + useTaxonomyListDataResponse.mockReturnValue({ + results: [{ + id: 1, + name: 'Taxonomy', + description: 'This is a description', + }], + }); + await act(async () => { + const { getByTestId } = render(); + const importButton = getByTestId('taxonomy-import-button'); + expect(importButton).toBeInTheDocument(); + importButton.click(); + expect(importTaxonomy).toHaveBeenCalled(); + }); + }); }); diff --git a/src/taxonomy/import-tags/data/actions.js b/src/taxonomy/import-tags/data/actions.js index b905138ad7..027ff5065b 100644 --- a/src/taxonomy/import-tags/data/actions.js +++ b/src/taxonomy/import-tags/data/actions.js @@ -1,7 +1,8 @@ import messages from '../messages'; import { importNewTaxonomy } from './api'; -const importTaxonomy = async (intl) => { +// eslint-disable-next-line import/prefer-default-export +export const importTaxonomy = async (intl) => { /* * This function is a temporary "Barebones" implementation of the import * functionality with `prompt` and `alert`. It is intended to be replaced @@ -79,7 +80,3 @@ const importTaxonomy = async (intl) => { console.error(error.response); }); }; - -export default { - importTaxonomy, -}; From 303e7ba559bb6991fb8c5292ffad4859681b508c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Thu, 9 Nov 2023 17:16:53 -0300 Subject: [PATCH 04/16] test: add action.test.js --- src/taxonomy/import-tags/data/actions.test.js | 96 +++++++++++++++++++ src/taxonomy/import-tags/data/api.js | 5 +- 2 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 src/taxonomy/import-tags/data/actions.test.js diff --git a/src/taxonomy/import-tags/data/actions.test.js b/src/taxonomy/import-tags/data/actions.test.js new file mode 100644 index 0000000000..3ea6c0b349 --- /dev/null +++ b/src/taxonomy/import-tags/data/actions.test.js @@ -0,0 +1,96 @@ +import { importTaxonomy } from './actions'; + +jest.mock('./api', () => ({ + importNewTaxonomy: jest.fn().mockResolvedValue({}), +})); + +const mockAddEventListener = jest.fn(); + +const intl = { + formatMessage: jest.fn(), +}; + +describe('import taxonomy actions', () => { + let createElement; + let appendChild; + let removeChild; + + beforeEach(() => { + createElement = document.createElement; + document.createElement = jest.fn().mockImplementation((element) => { + if (element === 'input') { + return { + click: jest.fn(), + addEventListener: mockAddEventListener, + }; + } + return createElement(element); + }); + + appendChild = document.body.appendChild; + document.body.appendChild = jest.fn(); + + removeChild = document.body.removeChild; + document.body.removeChild = jest.fn(); + }); + + afterEach(() => { + jest.clearAllMocks(); + document.createElement = createElement; + document.body.appendChild = appendChild; + document.body.removeChild = removeChild; + }); + + it('should call the api', async () => { + jest.spyOn(window, 'prompt') + .mockReturnValueOnce('test taxonomy name') + .mockReturnValueOnce('test taxonomy description'); + jest.spyOn(window, 'alert').mockImplementation(() => {}); + + importTaxonomy(intl).then(); + + // Capture the onChange handler from the file input element + const onChange = mockAddEventListener.mock.calls[0][1]; + const mockTarget = { + target: { + files: [ + 'mockFile', + ], + }, + }; + + onChange(mockTarget); + }); + + it('should abort the call to the api without file', async () => { + importTaxonomy(intl).then(); + + // Capture the onChange handler from the file input element + const onChange = mockAddEventListener.mock.calls[0][1]; + const mockTarget = { + target: { + files: [null], + }, + }; + + onChange(mockTarget); + }); + + it('should abort the call to the api without name', async () => { + jest.spyOn(window, 'prompt').mockReturnValueOnce(null); + + importTaxonomy(intl).then(); + + // Capture the onChange handler from the file input element + const onChange = mockAddEventListener.mock.calls[0][1]; + const mockTarget = { + target: { + files: [ + 'mockFile', + ], + }, + }; + + onChange(mockTarget); + }); +}); diff --git a/src/taxonomy/import-tags/data/api.js b/src/taxonomy/import-tags/data/api.js index ed0553c3ed..53cb1cb0f3 100644 --- a/src/taxonomy/import-tags/data/api.js +++ b/src/taxonomy/import-tags/data/api.js @@ -22,7 +22,10 @@ export async function importNewTaxonomy(taxonomyName, taxonomyDescription, file) formData.append('taxonomy_description', taxonomyDescription); formData.append('file', file); - const { data } = await getAuthenticatedHttpClient().post(getTaxonomyImportApiUrl(), formData); + const { data } = await getAuthenticatedHttpClient().post( + getTaxonomyImportApiUrl(), + formData, + ); return camelCaseObject(data); } From 3955e91c30061f34f9ea514e8d9431390fb8e5b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Thu, 9 Nov 2023 19:54:59 -0300 Subject: [PATCH 05/16] test: add more tests to action.tests.js --- src/taxonomy/import-tags/data/actions.test.js | 81 ++++++++++++++++--- 1 file changed, 71 insertions(+), 10 deletions(-) diff --git a/src/taxonomy/import-tags/data/actions.test.js b/src/taxonomy/import-tags/data/actions.test.js index 3ea6c0b349..74cbca2b51 100644 --- a/src/taxonomy/import-tags/data/actions.test.js +++ b/src/taxonomy/import-tags/data/actions.test.js @@ -1,15 +1,16 @@ import { importTaxonomy } from './actions'; - -jest.mock('./api', () => ({ - importNewTaxonomy: jest.fn().mockResolvedValue({}), -})); +import { importNewTaxonomy } from './api'; const mockAddEventListener = jest.fn(); const intl = { - formatMessage: jest.fn(), + formatMessage: jest.fn().mockImplementation((message) => message.defaultMessage), }; +jest.mock('./api', () => ({ + importNewTaxonomy: jest.fn().mockResolvedValue({}), +})); + describe('import taxonomy actions', () => { let createElement; let appendChild; @@ -41,13 +42,16 @@ describe('import taxonomy actions', () => { document.body.removeChild = removeChild; }); - it('should call the api', async () => { + it('should call the api and show success alert', async () => { jest.spyOn(window, 'prompt') .mockReturnValueOnce('test taxonomy name') .mockReturnValueOnce('test taxonomy description'); jest.spyOn(window, 'alert').mockImplementation(() => {}); - importTaxonomy(intl).then(); + const promise = importTaxonomy(intl).then(() => { + expect(importNewTaxonomy).toHaveBeenCalledWith('test taxonomy name', 'test taxonomy description', 'mockFile'); + expect(window.alert).toHaveBeenCalledWith('Taxonomy imported successfully'); + }); // Capture the onChange handler from the file input element const onChange = mockAddEventListener.mock.calls[0][1]; @@ -60,10 +64,39 @@ describe('import taxonomy actions', () => { }; onChange(mockTarget); + + return promise; + }); + + it('should call the api and return error alert', async () => { + jest.spyOn(window, 'prompt') + .mockReturnValueOnce('test taxonomy name') + .mockReturnValueOnce('test taxonomy description'); + importNewTaxonomy.mockRejectedValue(new Error('test error')); + + const promise = importTaxonomy(intl).then(() => { + expect(importNewTaxonomy).toHaveBeenCalledWith('test taxonomy name', 'test taxonomy description', 'mockFile'); + }); + + // Capture the onChange handler from the file input element + const onChange = mockAddEventListener.mock.calls[0][1]; + const mockTarget = { + target: { + files: [ + 'mockFile', + ], + }, + }; + + onChange(mockTarget); + + return promise; }); it('should abort the call to the api without file', async () => { - importTaxonomy(intl).then(); + const promise = importTaxonomy(intl).then(() => { + expect(importNewTaxonomy).not.toHaveBeenCalled(); + }); // Capture the onChange handler from the file input element const onChange = mockAddEventListener.mock.calls[0][1]; @@ -74,12 +107,39 @@ describe('import taxonomy actions', () => { }; onChange(mockTarget); + return promise; }); - it('should abort the call to the api without name', async () => { + it('should abort the call to the api when cancel name prompt', async () => { jest.spyOn(window, 'prompt').mockReturnValueOnce(null); - importTaxonomy(intl).then(); + const promise = importTaxonomy(intl).then(() => { + expect(importNewTaxonomy).not.toHaveBeenCalled(); + }); + + // Capture the onChange handler from the file input element + const onChange = mockAddEventListener.mock.calls[0][1]; + const mockTarget = { + target: { + files: [ + 'mockFile', + ], + }, + }; + + onChange(mockTarget); + + return promise; + }); + + it('should abort the call to the api when cancel description prompt', async () => { + jest.spyOn(window, 'prompt') + .mockReturnValueOnce('test taxonomy name') + .mockReturnValueOnce(null); + + const promise = importTaxonomy(intl).then(() => { + expect(importNewTaxonomy).not.toHaveBeenCalled(); + }); // Capture the onChange handler from the file input element const onChange = mockAddEventListener.mock.calls[0][1]; @@ -92,5 +152,6 @@ describe('import taxonomy actions', () => { }; onChange(mockTarget); + return promise; }); }); From 8176973cf17cd3bb425a1052fe0c0c4e14fb721d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Thu, 9 Nov 2023 20:29:44 -0300 Subject: [PATCH 06/16] test: add more tests to action.tests.js 2 --- src/taxonomy/import-tags/data/actions.test.js | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/taxonomy/import-tags/data/actions.test.js b/src/taxonomy/import-tags/data/actions.test.js index 74cbca2b51..40a1b732a4 100644 --- a/src/taxonomy/import-tags/data/actions.test.js +++ b/src/taxonomy/import-tags/data/actions.test.js @@ -68,6 +68,34 @@ describe('import taxonomy actions', () => { return promise; }); + it('should ask for taxonomy name again if not provided', async () => { + jest.spyOn(window, 'prompt') + .mockReturnValueOnce('') + .mockReturnValueOnce('test taxonomy name') + .mockReturnValueOnce('test taxonomy description'); + jest.spyOn(window, 'alert').mockImplementation(() => {}); + + const promise = importTaxonomy(intl).then(() => { + expect(importNewTaxonomy).toHaveBeenCalledWith('test taxonomy name', 'test taxonomy description', 'mockFile'); + expect(window.alert).toHaveBeenCalledWith('You must enter a name for the new taxonomy'); + expect(window.alert).toHaveBeenCalledWith('Taxonomy imported successfully'); + }); + + // Capture the onChange handler from the file input element + const onChange = mockAddEventListener.mock.calls[0][1]; + const mockTarget = { + target: { + files: [ + 'mockFile', + ], + }, + }; + + onChange(mockTarget); + + return promise; + }); + it('should call the api and return error alert', async () => { jest.spyOn(window, 'prompt') .mockReturnValueOnce('test taxonomy name') From df441fbb5dcda29fabc923250bde4ce119f1861b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Thu, 9 Nov 2023 21:48:51 -0300 Subject: [PATCH 07/16] fix: import --- src/taxonomy/import-tags/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/taxonomy/import-tags/index.js b/src/taxonomy/import-tags/index.js index 831d75323e..ebfa7d10e4 100644 --- a/src/taxonomy/import-tags/index.js +++ b/src/taxonomy/import-tags/index.js @@ -1,4 +1,4 @@ -import actions from './data/actions'; +import * as actions from './data/actions'; export { actions, // eslint-disable-line import/prefer-default-export From a0e92f0cbfc37574e3fb8bf81522f6f836c066fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Fri, 10 Nov 2023 16:15:38 -0300 Subject: [PATCH 08/16] test: simplify import test Co-authored-by: Jillian --- src/taxonomy/import-tags/data/api.test.js | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/taxonomy/import-tags/data/api.test.js b/src/taxonomy/import-tags/data/api.test.js index 1edf08f3f9..206a24d931 100644 --- a/src/taxonomy/import-tags/data/api.test.js +++ b/src/taxonomy/import-tags/data/api.test.js @@ -29,22 +29,7 @@ describe('import taxonomy api calls', () => { }); it('should call import taxonomy', async () => { - const tags = { - tags: [ - { id: 'tag_1', value: 'Tag 1' }, - ], - }; - const str = JSON.stringify(tags); - const blob = new Blob([str]); - const file = new File([blob], 'taxonomy.json', { - type: 'application/JSON', - }); - const formData = new FormData(); - formData.append('taxonomy_name', 'Taxonomy name'); - formData.append('taxonomy_description', 'Taxonomy description'); - formData.append('file', file); - formData.asymmetricMatch = () => true; - axiosMock.onPost(getTaxonomyImportApiUrl(), formData).reply(201, taxonomyImportMock); + axiosMock.onPost(getTaxonomyImportApiUrl()).reply(201, taxonomyImportMock); const result = await importNewTaxonomy('Taxonomy name', 'Taxonomy description', file); expect(axiosMock.history.post[0].url).toEqual(getTaxonomyImportApiUrl()); From dc8ed9e744eb19f8efb8a94d5db210febdc165a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Fri, 10 Nov 2023 16:23:38 -0300 Subject: [PATCH 09/16] fix: remove undefined var --- src/taxonomy/import-tags/data/api.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/taxonomy/import-tags/data/api.test.js b/src/taxonomy/import-tags/data/api.test.js index 206a24d931..c40053cc53 100644 --- a/src/taxonomy/import-tags/data/api.test.js +++ b/src/taxonomy/import-tags/data/api.test.js @@ -30,7 +30,7 @@ describe('import taxonomy api calls', () => { it('should call import taxonomy', async () => { axiosMock.onPost(getTaxonomyImportApiUrl()).reply(201, taxonomyImportMock); - const result = await importNewTaxonomy('Taxonomy name', 'Taxonomy description', file); + const result = await importNewTaxonomy('Taxonomy name', 'Taxonomy description'); expect(axiosMock.history.post[0].url).toEqual(getTaxonomyImportApiUrl()); expect(result).toEqual(taxonomyImportMock); From 120154e41829660e025fe4d56e4faa85379918f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Fri, 10 Nov 2023 16:37:27 -0300 Subject: [PATCH 10/16] refactor: rename actions.js -> utils.js --- src/taxonomy/TaxonomyListPage.jsx | 4 ++-- src/taxonomy/TaxonomyListPage.test.jsx | 4 ++-- src/taxonomy/import-tags/data/{actions.js => utils.js} | 0 .../import-tags/data/{actions.test.js => utils.test.js} | 4 ++-- src/taxonomy/import-tags/index.js | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) rename src/taxonomy/import-tags/data/{actions.js => utils.js} (100%) rename src/taxonomy/import-tags/data/{actions.test.js => utils.test.js} (98%) diff --git a/src/taxonomy/TaxonomyListPage.jsx b/src/taxonomy/TaxonomyListPage.jsx index 34aaf739cc..6dd81e4b40 100644 --- a/src/taxonomy/TaxonomyListPage.jsx +++ b/src/taxonomy/TaxonomyListPage.jsx @@ -14,7 +14,7 @@ import { StudioFooter } from '@edx/frontend-component-footer'; import Header from '../header'; import SubHeader from '../generic/sub-header/SubHeader'; -import { actions as importActions } from './import-tags'; +import { importTaxonomy } from './import-tags'; import messages from './messages'; import TaxonomyCard from './TaxonomyCard'; import { useTaxonomyListDataResponse, useIsTaxonomyListDataLoaded } from './api/hooks/selectors'; @@ -28,7 +28,7 @@ const TaxonomyListHeaderButtons = () => {