From 689071c1210b4624d1e97d4f75325f214ef35013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Tue, 28 Nov 2023 18:45:50 -0300 Subject: [PATCH 1/2] refactor: merges TaxonomyCardMenu and TaxonomyDetailMenu --- src/taxonomy/export-modal/index.jsx | 107 ++++++++------- .../taxonomy-card/TaxonomyCard.test.jsx | 78 +---------- .../taxonomy-card/TaxonomyCardMenu.jsx | 59 -------- src/taxonomy/taxonomy-card/index.jsx | 88 +++++------- src/taxonomy/taxonomy-card/messages.js | 12 -- .../taxonomy-detail/TaxonomyDetailMenu.jsx | 44 ------ .../taxonomy-detail/TaxonomyDetailPage.jsx | 29 +--- .../TaxonomyDetailPage.test.jsx | 58 +------- src/taxonomy/taxonomy-detail/messages.js | 16 --- src/taxonomy/taxonomy-menu/TaxonomyMenu.jsx | 80 +++++++++++ .../taxonomy-menu/TaxonomyMenu.test.jsx | 128 ++++++++++++++++++ src/taxonomy/taxonomy-menu/index.js | 5 + src/taxonomy/taxonomy-menu/messages.js | 23 ++++ 13 files changed, 328 insertions(+), 399 deletions(-) delete mode 100644 src/taxonomy/taxonomy-card/TaxonomyCardMenu.jsx delete mode 100644 src/taxonomy/taxonomy-detail/TaxonomyDetailMenu.jsx create mode 100644 src/taxonomy/taxonomy-menu/TaxonomyMenu.jsx create mode 100644 src/taxonomy/taxonomy-menu/TaxonomyMenu.test.jsx create mode 100644 src/taxonomy/taxonomy-menu/index.js create mode 100644 src/taxonomy/taxonomy-menu/messages.js diff --git a/src/taxonomy/export-modal/index.jsx b/src/taxonomy/export-modal/index.jsx index 76fd71c2fc..c83f94105f 100644 --- a/src/taxonomy/export-modal/index.jsx +++ b/src/taxonomy/export-modal/index.jsx @@ -1,7 +1,8 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { ActionRow, Button, + Container, Form, ModalDialog, } from '@edx/paragon'; @@ -24,59 +25,61 @@ const ExportModal = ({ }; return ( - - - - {intl.formatMessage(messages.exportModalTitle)} - - - - - - {intl.formatMessage(messages.exportModalBodyDescription)} - - setOutputFormat(e.target.value)} - > - e.stopPropagation() /* This prevents calling onClick handler from the parent */}> + + + + {intl.formatMessage(messages.exportModalTitle)} + + + + + + {intl.formatMessage(messages.exportModalBodyDescription)} + + setOutputFormat(e.target.value)} > - {intl.formatMessage(messages.taxonomyCSVFormat)} - - + {intl.formatMessage(messages.taxonomyCSVFormat)} + + + {intl.formatMessage(messages.taxonomyJSONFormat)} + + + + + + + + {intl.formatMessage(messages.taxonomyModalsCancelLabel)} + + - - - + {intl.formatMessage(messages.exportModalSubmitButtonLabel)} + + + + + ); }; diff --git a/src/taxonomy/taxonomy-card/TaxonomyCard.test.jsx b/src/taxonomy/taxonomy-card/TaxonomyCard.test.jsx index 8033de6383..19fe513ebb 100644 --- a/src/taxonomy/taxonomy-card/TaxonomyCard.test.jsx +++ b/src/taxonomy/taxonomy-card/TaxonomyCard.test.jsx @@ -2,21 +2,15 @@ import React from 'react'; import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n'; import { initializeMockApp } from '@edx/frontend-platform'; import { AppProvider } from '@edx/frontend-platform/react'; -import { render, fireEvent } from '@testing-library/react'; +import { render } from '@testing-library/react'; import PropTypes from 'prop-types'; import initializeStore from '../../store'; -import { getTaxonomyExportFile } from '../data/api'; -import { importTaxonomyTags } from '../import-tags'; import TaxonomyCard from '.'; let store; const taxonomyId = 1; -jest.mock('../import-tags', () => ({ - importTaxonomyTags: jest.fn().mockResolvedValue({}), -})); - const data = { id: taxonomyId, name: 'Taxonomy 1', @@ -92,74 +86,4 @@ describe('', async () => { const { getByText } = render(); expect(getByText('Assigned to 6 orgs')).toBeInTheDocument(); }); - - test('should open and close menu on button click', () => { - const { getByTestId } = render(); - - // Menu closed/doesn't exist yet - expect(() => getByTestId('taxonomy-card-menu-1')).toThrow(); - - // Click on the menu button to open - fireEvent.click(getByTestId('taxonomy-card-menu-button-1')); - - // Menu opened - expect(getByTestId('taxonomy-card-menu-1')).toBeVisible(); - - // Click on button again to close the menu - fireEvent.click(getByTestId('taxonomy-card-menu-button-1')); - - // Menu closed - // Jest bug: toBeVisible() isn't checking opacity correctly - // expect(getByTestId('taxonomy-card-menu-1')).not.toBeVisible(); - expect(getByTestId('taxonomy-card-menu-1').style.opacity).toEqual('0'); - - // Menu button still visible - expect(getByTestId('taxonomy-card-menu-button-1')).toBeVisible(); - }); - - test('should open export modal on export menu click', () => { - const { getByTestId, getByText } = render(); - - // Modal closed - expect(() => getByText('Select format to export')).toThrow(); - - // Click on export menu - fireEvent.click(getByTestId('taxonomy-card-menu-button-1')); - fireEvent.click(getByTestId('taxonomy-card-menu-export-1')); - - // Modal opened - expect(getByText('Select format to export')).toBeInTheDocument(); - - // Click on cancel button - fireEvent.click(getByText('Cancel')); - - // Modal closed - expect(() => getByText('Select format to export')).toThrow(); - }); - - test('should call import tags when menu click', () => { - const { getByTestId } = render(); - - // Click on import menu - fireEvent.click(getByTestId('taxonomy-card-menu-button-1')); - fireEvent.click(getByTestId('taxonomy-card-menu-import-1')); - - expect(importTaxonomyTags).toHaveBeenCalled(); - }); - - test('should export a taxonomy', () => { - const { getByTestId, getByText } = render(); - - // Click on export menu - fireEvent.click(getByTestId('taxonomy-card-menu-button-1')); - fireEvent.click(getByTestId('taxonomy-card-menu-export-1')); - - // Select JSON format and click on export - fireEvent.click(getByText('JSON file')); - fireEvent.click(getByTestId('export-button-1')); - - // Modal closed - expect(() => getByText('Select format to export')).toThrow(); - expect(getTaxonomyExportFile).toHaveBeenCalledWith(taxonomyId, 'json'); - }); }); diff --git a/src/taxonomy/taxonomy-card/TaxonomyCardMenu.jsx b/src/taxonomy/taxonomy-card/TaxonomyCardMenu.jsx deleted file mode 100644 index 6fef8b672e..0000000000 --- a/src/taxonomy/taxonomy-card/TaxonomyCardMenu.jsx +++ /dev/null @@ -1,59 +0,0 @@ -import React from 'react'; -import { - Dropdown, - IconButton, - Icon, -} from '@edx/paragon'; -import { MoreVert } from '@edx/paragon/icons'; -import PropTypes from 'prop-types'; -import { useIntl } from '@edx/frontend-platform/i18n'; -import messages from './messages'; - -const TaxonomyCardMenu = ({ - id, name, onClickMenuItem, -}) => { - const intl = useIntl(); - - const onClickItem = (e, menuName) => { - e.preventDefault(); - onClickMenuItem(menuName); - }; - - return ( - ev.preventDefault()}> - - - onClickItem(e, 'import')} - > - {intl.formatMessage(messages.taxonomyCardImportMenu)} - - onClickItem(e, 'export')} - > - {intl.formatMessage(messages.taxonomyCardExportMenu)} - - - - ); -}; - -TaxonomyCardMenu.propTypes = { - id: PropTypes.number.isRequired, - name: PropTypes.string.isRequired, - onClickMenuItem: PropTypes.func.isRequired, -}; - -export default TaxonomyCardMenu; diff --git a/src/taxonomy/taxonomy-card/index.jsx b/src/taxonomy/taxonomy-card/index.jsx index 09072ca1ab..b8aed93178 100644 --- a/src/taxonomy/taxonomy-card/index.jsx +++ b/src/taxonomy/taxonomy-card/index.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +// ts-check import { Badge, Card, @@ -10,10 +10,8 @@ import { Link } from 'react-router-dom'; import classNames from 'classnames'; import { useIntl } from '@edx/frontend-platform/i18n'; -import { importTaxonomyTags } from '../import-tags'; +import { TaxonomyMenu } from '../taxonomy-menu'; import messages from './messages'; -import TaxonomyCardMenu from './TaxonomyCardMenu'; -import ExportModal from '../export-modal'; const orgsCountEnabled = (orgsCount) => orgsCount !== undefined && orgsCount !== 0; @@ -72,16 +70,6 @@ const TaxonomyCard = ({ className, original }) => { } = original; const intl = useIntl(); - const [isExportModalOpen, setIsExportModalOpen] = useState(false); - - const menuItemActions = { - import: () => importTaxonomyTags(id, intl).then(), - export: () => setIsExportModalOpen(true), - }; - - const onClickMenuItem = (menuName) => ( - menuItemActions[menuName]?.() - ); const getHeaderActions = () => { if (systemDefined) { @@ -93,56 +81,46 @@ const TaxonomyCard = ({ className, original }) => { // TODO When adding more menus, change this logic to hide only the export menu. return undefined; } + return ( - ); }; - const renderExportModal = () => isExportModalOpen && ( - setIsExportModalOpen(false)} - taxonomyId={id} - /> - ); - return ( - <> - + + )} + actions={getHeaderActions()} + /> + - - )} - actions={getHeaderActions()} - /> - - - {description} - - - - {renderExportModal()} - + + {description} + + + ); }; diff --git a/src/taxonomy/taxonomy-card/messages.js b/src/taxonomy/taxonomy-card/messages.js index def583a96d..93e90aefa9 100644 --- a/src/taxonomy/taxonomy-card/messages.js +++ b/src/taxonomy/taxonomy-card/messages.js @@ -17,18 +17,6 @@ const messages = defineMessages({ id: 'course-authoring.taxonomy-list.orgs-count.label', defaultMessage: 'Assigned to {orgsCount} orgs', }, - taxonomyCardImportMenu: { - id: 'course-authoring.taxonomy-list.menu.import.label', - defaultMessage: 'Re-import', - }, - taxonomyCardExportMenu: { - id: 'course-authoring.taxonomy-list.menu.export.label', - defaultMessage: 'Export', - }, - taxonomyMenuAlt: { - id: 'course-authoring.taxonomy-list.menu.alt', - defaultMessage: '{name} menu', - }, }); export default messages; diff --git a/src/taxonomy/taxonomy-detail/TaxonomyDetailMenu.jsx b/src/taxonomy/taxonomy-detail/TaxonomyDetailMenu.jsx deleted file mode 100644 index 38ce2492d9..0000000000 --- a/src/taxonomy/taxonomy-detail/TaxonomyDetailMenu.jsx +++ /dev/null @@ -1,44 +0,0 @@ -// ts-check -import { useIntl } from '@edx/frontend-platform/i18n'; -import { - Dropdown, - DropdownButton, -} from '@edx/paragon'; -import PropTypes from 'prop-types'; - -import messages from './messages'; - -const TaxonomyDetailMenu = ({ - id, name, disabled, onClickMenuItem, -}) => { - const intl = useIntl(); - - return ( - - onClickMenuItem('import')}> - {intl.formatMessage(messages.importMenu)} - - onClickMenuItem('export')}> - {intl.formatMessage(messages.exportMenu)} - - - ); -}; - -TaxonomyDetailMenu.propTypes = { - id: PropTypes.number.isRequired, - name: PropTypes.string.isRequired, - disabled: PropTypes.bool, - onClickMenuItem: PropTypes.func.isRequired, -}; - -TaxonomyDetailMenu.defaultProps = { - disabled: false, -}; - -export default TaxonomyDetailMenu; diff --git a/src/taxonomy/taxonomy-detail/TaxonomyDetailPage.jsx b/src/taxonomy/taxonomy-detail/TaxonomyDetailPage.jsx index 4a900b74db..d55d927f95 100644 --- a/src/taxonomy/taxonomy-detail/TaxonomyDetailPage.jsx +++ b/src/taxonomy/taxonomy-detail/TaxonomyDetailPage.jsx @@ -1,5 +1,4 @@ // ts-check -import React, { useState } from 'react'; import { useIntl } from '@edx/frontend-platform/i18n'; import { Breadcrumb, @@ -13,12 +12,10 @@ import ConnectionErrorAlert from '../../generic/ConnectionErrorAlert'; import Loading from '../../generic/Loading'; import getPageHeadTitle from '../../generic/utils'; import SubHeader from '../../generic/sub-header/SubHeader'; -import { importTaxonomyTags } from '../import-tags'; import taxonomyMessages from '../messages'; -import TaxonomyDetailMenu from './TaxonomyDetailMenu'; +import { TaxonomyMenu } from '../taxonomy-menu'; import TaxonomyDetailSideCard from './TaxonomyDetailSideCard'; import { TagListTable } from '../tag-list'; -import ExportModal from '../export-modal'; import { useTaxonomyDetailDataResponse, useTaxonomyDetailDataStatus } from './data/apiHooks'; const TaxonomyDetailPage = () => { @@ -29,8 +26,6 @@ const TaxonomyDetailPage = () => { const taxonomy = useTaxonomyDetailDataResponse(taxonomyId); const { isError, isFetched } = useTaxonomyDetailDataStatus(taxonomyId); - const [isExportModalOpen, setIsExportModalOpen] = useState(false); - if (!isFetched) { return ( @@ -43,26 +38,8 @@ const TaxonomyDetailPage = () => { ); } - const renderModals = () => isExportModalOpen && ( - setIsExportModalOpen(false)} - taxonomyId={taxonomy.id} - taxonomyName={taxonomy.name} - /> - ); - - const menuItemActions = { - import: () => importTaxonomyTags(taxonomyId, intl).then(), - export: () => setIsExportModalOpen(true), - }; - - const onClickMenuItem = (menuName) => ( - menuItemActions[menuName]?.() - ); - const getHeaderActions = () => ( - { // ToDo: When adding more menus, change this logic to hide only the export menu. taxonomy.systemDefined } - onClickMenuItem={onClickMenuItem} /> ); @@ -116,7 +92,6 @@ const TaxonomyDetailPage = () => { - {renderModals()} ); }; diff --git a/src/taxonomy/taxonomy-detail/TaxonomyDetailPage.test.jsx b/src/taxonomy/taxonomy-detail/TaxonomyDetailPage.test.jsx index 23af888835..3ae58bbafa 100644 --- a/src/taxonomy/taxonomy-detail/TaxonomyDetailPage.test.jsx +++ b/src/taxonomy/taxonomy-detail/TaxonomyDetailPage.test.jsx @@ -2,9 +2,8 @@ import React from 'react'; import { initializeMockApp } from '@edx/frontend-platform'; import { IntlProvider } from '@edx/frontend-platform/i18n'; import { AppProvider } from '@edx/frontend-platform/react'; -import { fireEvent, render } from '@testing-library/react'; +import { render } from '@testing-library/react'; -import { importTaxonomyTags } from '../import-tags'; import { useTaxonomyDetailData } from './data/api'; import initializeStore from '../../store'; import TaxonomyDetailPage from './TaxonomyDetailPage'; @@ -14,9 +13,6 @@ let store; jest.mock('./data/api', () => ({ useTaxonomyDetailData: jest.fn(), })); -jest.mock('../import-tags', () => ({ - importTaxonomyTags: jest.fn().mockResolvedValue({}), -})); jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), // use actual for all non-hook parts useParams: () => ({ @@ -81,56 +77,4 @@ describe('', async () => { const { getByRole } = render(); expect(getByRole('heading')).toHaveTextContent('Test taxonomy'); }); - - it('should open export modal on export menu click', () => { - useTaxonomyDetailData.mockReturnValue({ - isSuccess: true, - isFetched: true, - isError: false, - data: { - id: 1, - name: 'Test taxonomy', - description: 'This is a description', - }, - }); - - const { getByRole, getByText } = render(); - - // Modal closed - expect(() => getByText('Select format to export')).toThrow(); - - // Click on export menu - fireEvent.click(getByRole('button')); - fireEvent.click(getByText('Export')); - - // Modal opened - expect(getByText('Select format to export')).toBeInTheDocument(); - - // Click on cancel button - fireEvent.click(getByText('Cancel')); - - // Modal closed - expect(() => getByText('Select format to export')).toThrow(); - }); - - it('should call import tags when menu clicked', () => { - useTaxonomyDetailData.mockReturnValue({ - isSuccess: true, - isFetched: true, - isError: false, - data: { - id: 1, - name: 'Test taxonomy', - description: 'This is a description', - }, - }); - - const { getByRole, getByText } = render(); - - // Click on import menu - fireEvent.click(getByRole('button')); - fireEvent.click(getByText('Re-import')); - - expect(importTaxonomyTags).toHaveBeenCalled(); - }); }); diff --git a/src/taxonomy/taxonomy-detail/messages.js b/src/taxonomy/taxonomy-detail/messages.js index 0abf7bf119..e8ac8851d3 100644 --- a/src/taxonomy/taxonomy-detail/messages.js +++ b/src/taxonomy/taxonomy-detail/messages.js @@ -14,22 +14,6 @@ const messages = defineMessages({ id: 'course-authoring.taxonomy-detail.side-card.description', defaultMessage: 'Description', }, - actionsButtonLabel: { - id: 'course-authoring.taxonomy-detail.action.button.label', - defaultMessage: 'Actions', - }, - actionsButtonAlt: { - id: 'course-authoring.taxonomy-detail.action.button.alt', - defaultMessage: '{name} actions', - }, - importMenu: { - id: 'course-authoring.taxonomy-detail.menu.import.label', - defaultMessage: 'Re-import', - }, - exportMenu: { - id: 'course-authoring.taxonomy-detail.action.export', - defaultMessage: 'Export', - }, }); export default messages; diff --git a/src/taxonomy/taxonomy-menu/TaxonomyMenu.jsx b/src/taxonomy/taxonomy-menu/TaxonomyMenu.jsx new file mode 100644 index 0000000000..3aba9d4d7d --- /dev/null +++ b/src/taxonomy/taxonomy-menu/TaxonomyMenu.jsx @@ -0,0 +1,80 @@ +// ts-check +import { useIntl } from '@edx/frontend-platform/i18n'; +import { + useToggle, + Button, + Dropdown, + Icon, + IconButton, +} from '@edx/paragon'; +import { MoreVert } from '@edx/paragon/icons'; +import PropTypes from 'prop-types'; + +import ExportModal from '../export-modal'; +import { importTaxonomyTags } from '../import-tags'; +import messages from './messages'; + +const TaxonomyMenu = ({ + id, name, iconMenu, disabled, +}) => { + const intl = useIntl(); + + const [isExportModalOpen, exportModalOpen, exportModalClose] = useToggle(false); + + const menuItemActions = { + import: () => importTaxonomyTags(id, intl), + export: exportModalOpen, + }; + + const onClickMenuItem = (e, menuName) => { + e.preventDefault(); + menuItemActions[menuName]?.(); + }; + + const renderModals = () => isExportModalOpen && ( + + ); + + return ( + ev.preventDefault()}> + + {intl.formatMessage(messages.actionsButtonLabel)} + + + onClickMenuItem(e, 'import')}> + {intl.formatMessage(messages.importMenu)} + + onClickMenuItem(e, 'export')}> + {intl.formatMessage(messages.exportMenu)} + + + {renderModals()} + + ); +}; + +TaxonomyMenu.propTypes = { + id: PropTypes.number.isRequired, + name: PropTypes.string.isRequired, + iconMenu: PropTypes.bool.isRequired, + disabled: PropTypes.bool, +}; + +TaxonomyMenu.defaultProps = { + disabled: false, +}; + +export default TaxonomyMenu; diff --git a/src/taxonomy/taxonomy-menu/TaxonomyMenu.test.jsx b/src/taxonomy/taxonomy-menu/TaxonomyMenu.test.jsx new file mode 100644 index 0000000000..dd1300ba04 --- /dev/null +++ b/src/taxonomy/taxonomy-menu/TaxonomyMenu.test.jsx @@ -0,0 +1,128 @@ +import { IntlProvider } from '@edx/frontend-platform/i18n'; +import { initializeMockApp } from '@edx/frontend-platform'; +import { AppProvider } from '@edx/frontend-platform/react'; +import { render, fireEvent } from '@testing-library/react'; +import PropTypes from 'prop-types'; + +import initializeStore from '../../store'; +import { getTaxonomyExportFile } from '../data/api'; +import { importTaxonomyTags } from '../import-tags'; +import { TaxonomyMenu } from '.'; + +let store; +const taxonomyId = 1; +const taxonomyName = 'Taxonomy 1'; + +jest.mock('../import-tags', () => ({ + importTaxonomyTags: jest.fn().mockResolvedValue({}), +})); + +jest.mock('../data/api', () => ({ + getTaxonomyExportFile: jest.fn(), +})); + +const TaxonomyMenuComponent = ({ + iconMenu, + disabled, +}) => ( + + + + + +); + +TaxonomyMenuComponent.propTypes = { + iconMenu: PropTypes.bool.isRequired, + disabled: PropTypes.bool, +}; + +TaxonomyMenuComponent.defaultProps = { + disabled: false, +}; + +describe('', async () => { + beforeEach(async () => { + initializeMockApp({ + authenticatedUser: { + userId: 3, + username: 'abc123', + administrator: true, + roles: [], + }, + }); + store = initializeStore(); + }); + + [true, false].forEach((iconMenu) => { + test('should open and close menu on button click', () => { + const { getByTestId } = render(); + + // Menu closed/doesn't exist yet + expect(() => getByTestId('taxonomy-menu')).toThrow(); + + // Click on the menu button to open + fireEvent.click(getByTestId('taxonomy-menu-button')); + + // Menu opened + expect(getByTestId('taxonomy-menu')).toBeVisible(); + + // Click on button again to close the menu + fireEvent.click(getByTestId('taxonomy-menu-button')); + + // Menu closed + // Jest bug: toBeVisible() isn't checking opacity correctly + // expect(getByTestId('taxonomy-menu')).not.toBeVisible(); + expect(getByTestId('taxonomy-menu').style.opacity).toEqual('0'); + + // Menu button still visible + expect(getByTestId('taxonomy-menu-button')).toBeVisible(); + }); + + test('should open export modal on export menu click', () => { + const { getByTestId, getByText } = render(); + + // Modal closed + expect(() => getByText('Select format to export')).toThrow(); + + // Click on export menu + fireEvent.click(getByTestId('taxonomy-menu-button')); + fireEvent.click(getByTestId('taxonomy-menu-export')); + + // Modal opened + expect(getByText('Select format to export')).toBeInTheDocument(); + + // Click on cancel button + fireEvent.click(getByText('Cancel')); + + // Modal closed + expect(() => getByText('Select format to export')).toThrow(); + }); + + test('should call import tags when menu click', () => { + const { getByTestId } = render(); + + // Click on import menu + fireEvent.click(getByTestId('taxonomy-menu-button')); + fireEvent.click(getByTestId('taxonomy-menu-import')); + + expect(importTaxonomyTags).toHaveBeenCalled(); + }); + + test('should export a taxonomy', () => { + const { getByTestId, getByText } = render(); + + // Click on export menu + fireEvent.click(getByTestId('taxonomy-menu-button')); + fireEvent.click(getByTestId('taxonomy-menu-export')); + + // Select JSON format and click on export + fireEvent.click(getByText('JSON file')); + fireEvent.click(getByTestId('export-button-1')); + + // Modal closed + expect(() => getByText('Select format to export')).toThrow(); + expect(getTaxonomyExportFile).toHaveBeenCalledWith(taxonomyId, 'json'); + }); + }); +}); diff --git a/src/taxonomy/taxonomy-menu/index.js b/src/taxonomy/taxonomy-menu/index.js new file mode 100644 index 0000000000..a812a2d785 --- /dev/null +++ b/src/taxonomy/taxonomy-menu/index.js @@ -0,0 +1,5 @@ +import TaxonomyMenu from './TaxonomyMenu'; + +export { + TaxonomyMenu, // eslint-disable-line import/prefer-default-export +}; diff --git a/src/taxonomy/taxonomy-menu/messages.js b/src/taxonomy/taxonomy-menu/messages.js new file mode 100644 index 0000000000..15d0238501 --- /dev/null +++ b/src/taxonomy/taxonomy-menu/messages.js @@ -0,0 +1,23 @@ +// ts-check +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + actionsButtonLabel: { + id: 'course-authoring.taxonomy-menu.action.button.label', + defaultMessage: 'Actions', + }, + actionsButtonAlt: { + id: 'course-authoring.taxonomy-menu.action.button.alt', + defaultMessage: '{name} actions', + }, + importMenu: { + id: 'course-authoring.taxonomy-menu.import.label', + defaultMessage: 'Re-import', + }, + exportMenu: { + id: 'course-authoring.taxonomy-menu.export.label', + defaultMessage: 'Export', + }, +}); + +export default messages; From 0fe4b3c767aa1051c7e0bc5b65d2c255f04606e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Wed, 29 Nov 2023 12:12:51 -0300 Subject: [PATCH 2/2] refactor: change menu item list --- src/taxonomy/taxonomy-card/TaxonomyCard.scss | 17 ------ src/taxonomy/taxonomy-card/index.jsx | 25 ++------ .../taxonomy-detail/TaxonomyDetailPage.jsx | 11 +--- src/taxonomy/taxonomy-menu/TaxonomyMenu.jsx | 61 +++++++++++++------ .../taxonomy-menu/TaxonomyMenu.test.jsx | 51 ++++++++++++++-- 5 files changed, 96 insertions(+), 69 deletions(-) diff --git a/src/taxonomy/taxonomy-card/TaxonomyCard.scss b/src/taxonomy/taxonomy-card/TaxonomyCard.scss index b4839bd1c2..35e198abe8 100644 --- a/src/taxonomy/taxonomy-card/TaxonomyCard.scss +++ b/src/taxonomy/taxonomy-card/TaxonomyCard.scss @@ -29,21 +29,4 @@ text-overflow: ellipsis; white-space: nowrap; } - - .taxonomy-menu-item:focus { - /** - * There is a bug in the menu that auto focus the first item. - * We convert the focus style to a normal style. - */ - background-color: white !important; - font-weight: normal !important; - } - - .taxonomy-menu-item:focus:hover { - /** - * Check the previous block about the focus. - * This enable a normal hover to focused items. - */ - background-color: $light-500 !important; - } } diff --git a/src/taxonomy/taxonomy-card/index.jsx b/src/taxonomy/taxonomy-card/index.jsx index b8aed93178..81cd8cbba9 100644 --- a/src/taxonomy/taxonomy-card/index.jsx +++ b/src/taxonomy/taxonomy-card/index.jsx @@ -71,25 +71,12 @@ const TaxonomyCard = ({ className, original }) => { const intl = useIntl(); - const getHeaderActions = () => { - if (systemDefined) { - // We don't show the export menu, because the system-taxonomies - // can't be exported. The API returns and error. - // The entire menu has been hidden because currently only - // the export menu exists. - // - // TODO When adding more menus, change this logic to hide only the export menu. - return undefined; - } - - return ( - - ); - }; + const getHeaderActions = () => ( + + ); return ( { const getHeaderActions = () => ( ); diff --git a/src/taxonomy/taxonomy-menu/TaxonomyMenu.jsx b/src/taxonomy/taxonomy-menu/TaxonomyMenu.jsx index 3aba9d4d7d..ad87601952 100644 --- a/src/taxonomy/taxonomy-menu/TaxonomyMenu.jsx +++ b/src/taxonomy/taxonomy-menu/TaxonomyMenu.jsx @@ -15,17 +15,38 @@ import { importTaxonomyTags } from '../import-tags'; import messages from './messages'; const TaxonomyMenu = ({ - id, name, iconMenu, disabled, + taxonomy, iconMenu, }) => { const intl = useIntl(); + const getTaxonomyMenuItems = () => { + const { systemDefined, allowFreeText } = taxonomy; + const menuItems = ['import', 'export']; + if (systemDefined) { + // System defined taxonomies cannot be imported + return menuItems.filter((item) => !['import'].includes(item)); + } + if (allowFreeText) { + // Free text taxonomies cannot be imported + return menuItems.filter((item) => !['import'].includes(item)); + } + return menuItems; + }; + + const menuItems = getTaxonomyMenuItems(); + const [isExportModalOpen, exportModalOpen, exportModalClose] = useToggle(false); const menuItemActions = { - import: () => importTaxonomyTags(id, intl), + import: () => importTaxonomyTags(taxonomy.id, intl), export: exportModalOpen, }; + const menuItemMessages = { + import: messages.importMenu, + export: messages.exportMenu, + }; + const onClickMenuItem = (e, menuName) => { e.preventDefault(); menuItemActions[menuName]?.(); @@ -35,8 +56,8 @@ const TaxonomyMenu = ({ ); @@ -47,19 +68,22 @@ const TaxonomyMenu = ({ src={MoreVert} iconAs={Icon} variant="primary" - alt={intl.formatMessage(messages.actionsButtonAlt, { name })} + alt={intl.formatMessage(messages.actionsButtonAlt, { name: taxonomy.name })} data-testid="taxonomy-menu-button" - disabled={disabled} + disabled={menuItems.length === 0} > {intl.formatMessage(messages.actionsButtonLabel)} - onClickMenuItem(e, 'import')}> - {intl.formatMessage(messages.importMenu)} - - onClickMenuItem(e, 'export')}> - {intl.formatMessage(messages.exportMenu)} - + {menuItems.map((item) => ( + onClickMenuItem(e, item)} + > + {intl.formatMessage(menuItemMessages[item])} + + ))} {renderModals()} @@ -67,14 +91,13 @@ const TaxonomyMenu = ({ }; TaxonomyMenu.propTypes = { - id: PropTypes.number.isRequired, - name: PropTypes.string.isRequired, + taxonomy: PropTypes.shape({ + id: PropTypes.number.isRequired, + name: PropTypes.string.isRequired, + systemDefined: PropTypes.bool.isRequired, + allowFreeText: PropTypes.bool.isRequired, + }).isRequired, iconMenu: PropTypes.bool.isRequired, - disabled: PropTypes.bool, -}; - -TaxonomyMenu.defaultProps = { - disabled: false, }; export default TaxonomyMenu; diff --git a/src/taxonomy/taxonomy-menu/TaxonomyMenu.test.jsx b/src/taxonomy/taxonomy-menu/TaxonomyMenu.test.jsx index dd1300ba04..142049f2ac 100644 --- a/src/taxonomy/taxonomy-menu/TaxonomyMenu.test.jsx +++ b/src/taxonomy/taxonomy-menu/TaxonomyMenu.test.jsx @@ -22,23 +22,34 @@ jest.mock('../data/api', () => ({ })); const TaxonomyMenuComponent = ({ + systemDefined, + allowFreeText, iconMenu, - disabled, }) => ( - + ); TaxonomyMenuComponent.propTypes = { iconMenu: PropTypes.bool.isRequired, - disabled: PropTypes.bool, + systemDefined: PropTypes.bool, + allowFreeText: PropTypes.bool, }; TaxonomyMenuComponent.defaultProps = { - disabled: false, + systemDefined: false, + allowFreeText: false, }; describe('', async () => { @@ -79,6 +90,38 @@ describe('', async () => { expect(getByTestId('taxonomy-menu-button')).toBeVisible(); }); + test('doesnt show systemDefined taxonomies disabled menus', () => { + const { getByTestId } = render(); + + // Menu closed/doesn't exist yet + expect(() => getByTestId('taxonomy-menu')).toThrow(); + + // Click on the menu button to open + fireEvent.click(getByTestId('taxonomy-menu-button')); + + // Menu opened + expect(getByTestId('taxonomy-menu')).toBeVisible(); + + // Check that the import menu is not show + expect(() => getByTestId('taxonomy-menu-import')).toThrow(); + }); + + test('doesnt show freeText taxonomies disabled menus', () => { + const { getByTestId } = render(); + + // Menu closed/doesn't exist yet + expect(() => getByTestId('taxonomy-menu')).toThrow(); + + // Click on the menu button to open + fireEvent.click(getByTestId('taxonomy-menu-button')); + + // Menu opened + expect(getByTestId('taxonomy-menu')).toBeVisible(); + + // Check that the import menu is not show + expect(() => getByTestId('taxonomy-menu-import')).toThrow(); + }); + test('should open export modal on export menu click', () => { const { getByTestId, getByText } = render();