Files
frontend-app-authoring/src/taxonomy/taxonomy-menu/TaxonomyMenu.test.jsx
Braden MacDonald ced2c0e891 Fix the TaxonomyMenu test, which wasn't getting run at all (#930)
* test: fix the TaxonomyMenu test, which wasn't getting run at all
Co-authored-by: Rômulo Penido <romulo@opencraft.com>
2024-04-08 13:04:40 +05:30

323 lines
10 KiB
JavaScript

// @ts-check
/* eslint-disable react/prop-types */
// ^ eslint doesn't 'see' JSDoc types; remove this lint directive when converting this to .tsx
import React, { useMemo } from 'react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { initializeMockApp } from '@edx/frontend-platform';
import { AppProvider } from '@edx/frontend-platform/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { render, fireEvent, waitFor } from '@testing-library/react';
import { TaxonomyContext } from '../common/context';
import initializeStore from '../../store';
import { deleteTaxonomy, getTaxonomy, getTaxonomyExportFile } from '../data/api';
import { TaxonomyMenu } from '.';
let store;
const taxonomyId = 1;
const taxonomyName = 'Taxonomy 1';
jest.mock('../data/api', () => ({
...jest.requireActual('../data/api'),
getTaxonomyExportFile: jest.fn(),
deleteTaxonomy: jest.fn(),
getTaxonomy: jest.fn(),
}));
const queryClient = new QueryClient();
const mockSetToastMessage = jest.fn();
/**
* @type {React.FC<{
* iconMenu: boolean,
* systemDefined?: boolean,
* canChangeTaxonomy?: boolean,
* canDeleteTaxonomy?: boolean,
* }>}
*/
const TaxonomyMenuComponent = ({
iconMenu,
systemDefined = false,
canChangeTaxonomy = true,
canDeleteTaxonomy = true,
}) => {
const context = useMemo(() => ({
toastMessage: null,
setToastMessage: mockSetToastMessage,
alertProps: null,
setAlertProps: null,
}), []);
return (
<AppProvider store={store}>
<IntlProvider locale="en" messages={{}}>
<QueryClientProvider client={queryClient}>
<TaxonomyContext.Provider value={context}>
<TaxonomyMenu
taxonomy={{
id: taxonomyId,
name: taxonomyName,
tagsCount: 0,
systemDefined,
canChangeTaxonomy,
canDeleteTaxonomy,
}}
iconMenu={iconMenu}
/>
</TaxonomyContext.Provider>
</QueryClientProvider>
</IntlProvider>
</AppProvider>
);
};
describe.each([true, false])('<TaxonomyMenu iconMenu=%s />', (iconMenu) => {
beforeEach(async () => {
initializeMockApp({
authenticatedUser: {
userId: 3,
username: 'abc123',
administrator: true,
roles: [],
},
});
store = initializeStore();
});
afterEach(() => {
jest.clearAllMocks();
});
test('should open and close menu on button click', () => {
const { getByRole, getByTestId, queryByLabelText } = render(
<TaxonomyMenuComponent iconMenu={iconMenu} />,
);
const menuLabelText = 'Actions';
const menuAltText = `${taxonomyName} actions`;
const menuButtonText = iconMenu ? menuAltText : menuLabelText;
// Menu closed/doesn't exist yet
expect(queryByLabelText(menuLabelText)).not.toBeInTheDocument();
// Click on the menu button to open
fireEvent.click(getByRole('button', { name: menuButtonText }));
// Menu opened
expect(getByTestId('taxonomy-menu')).toBeVisible();
// Click on button again to close the menu
fireEvent.click(getByRole('button', { name: menuButtonText }));
// Menu closed
// Jest bug: toBeVisible() isn't checking opacity correctly
// expect(getByTestId('taxonomy-menu')).not.toBeVisible();
expect(getByTestId('taxonomy-menu').style.opacity).toEqual('0');
// TODO: the above should be getByLabelText(menuButtonText) but there is a conflict
// when iconMenu={true} because the <button> has aria-label in that case.
// Menu button still visible
expect(getByRole('button', { name: menuButtonText })).toBeVisible();
});
test('Shows menu actions that user is permitted', async () => {
const { findByTestId, getByTestId, queryByTestId } = render(
<TaxonomyMenuComponent
iconMenu={iconMenu}
/>,
);
// Click on the menu button to open
fireEvent.click(await findByTestId('taxonomy-menu-button'));
// Menu opened
expect(getByTestId('taxonomy-menu')).toBeVisible();
// Ensure expected menu items are found
expect(queryByTestId('taxonomy-menu-export')).toBeInTheDocument();
expect(queryByTestId('taxonomy-menu-import')).toBeInTheDocument();
expect(queryByTestId('taxonomy-menu-manageOrgs')).toBeInTheDocument();
expect(queryByTestId('taxonomy-menu-delete')).toBeInTheDocument();
});
test('Hides menu actions that user is not permitted', async () => {
const { findByTestId, queryByTestId } = render(
<TaxonomyMenuComponent
iconMenu={iconMenu}
systemDefined={false}
canChangeTaxonomy={false}
canDeleteTaxonomy={false}
/>,
);
// Click on the menu button to open
fireEvent.click(await findByTestId('taxonomy-menu-button'));
// Ensure expected menu items are found
expect(queryByTestId('taxonomy-menu-export')).toBeInTheDocument();
expect(queryByTestId('taxonomy-menu-import')).not.toBeInTheDocument();
expect(queryByTestId('taxonomy-menu-manageOrgs')).not.toBeInTheDocument();
expect(queryByTestId('taxonomy-menu-delete')).not.toBeInTheDocument();
});
test('Hides import/delete actions for system-defined taxonomies', () => {
const systemDefined = true;
const { getByTestId, queryByTestId } = render(
<TaxonomyMenuComponent
iconMenu={iconMenu}
systemDefined={systemDefined}
/>,
);
// Click on the menu button to open
fireEvent.click(getByTestId('taxonomy-menu-button'));
// Ensure expected menu items are found
expect(queryByTestId('taxonomy-menu-export')).toBeInTheDocument();
expect(queryByTestId('taxonomy-menu-import')).not.toBeInTheDocument();
expect(queryByTestId('taxonomy-menu-manageOrgs')).toBeInTheDocument();
expect(queryByTestId('taxonomy-menu-delete')).not.toBeInTheDocument();
});
test('should open export modal on export menu click', async () => {
const { findByTestId, getByText, queryByText } = render(
<TaxonomyMenuComponent iconMenu={iconMenu} />,
);
// Modal closed
expect(queryByText('Select format to export')).not.toBeInTheDocument();
// Click on export menu
fireEvent.click(await findByTestId('taxonomy-menu-button'));
fireEvent.click(await findByTestId('taxonomy-menu-export'));
// Modal opened
expect(getByText('Select format to export')).toBeInTheDocument();
// Click on cancel button
fireEvent.click(getByText('Cancel'));
// Modal closed
expect(queryByText('Select format to export')).not.toBeInTheDocument();
});
test('should call import tags when menu click', async () => {
const { findByTestId, getByText } = render(<TaxonomyMenuComponent iconMenu={iconMenu} />);
// Click on import menu
fireEvent.click(await findByTestId('taxonomy-menu-button'));
fireEvent.click(await findByTestId('taxonomy-menu-import'));
expect(getByText('Update "Taxonomy 1"')).toBeInTheDocument();
});
test('should export a taxonomy', async () => {
const {
findByTestId, getByTestId, getByText, queryByText,
} = render(<TaxonomyMenuComponent iconMenu={iconMenu} />);
// Click on export menu
fireEvent.click(await findByTestId('taxonomy-menu-button'));
fireEvent.click(await findByTestId('taxonomy-menu-export'));
// Select JSON format and click on export
fireEvent.click(getByText('JSON file'));
fireEvent.click(getByTestId('export-button-1'));
// Modal closed
expect(queryByText('Select format to export')).not.toBeInTheDocument();
expect(getTaxonomyExportFile).toHaveBeenCalledWith(taxonomyId, 'json');
});
test('should open delete dialog on delete menu click', async () => {
const { findByTestId, getByText, queryByText } = render(
<TaxonomyMenuComponent iconMenu={iconMenu} />,
);
// Modal closed
expect(queryByText(`Delete "${taxonomyName}"`)).not.toBeInTheDocument();
// Click on delete menu
fireEvent.click(await findByTestId('taxonomy-menu-button'));
fireEvent.click(await findByTestId('taxonomy-menu-delete'));
// Modal opened
expect(getByText(`Delete "${taxonomyName}"`)).toBeInTheDocument();
// Click on cancel button
fireEvent.click(getByText('Cancel'));
// Modal closed
expect(queryByText(`Delete "${taxonomyName}"`)).not.toBeInTheDocument();
});
test('should delete a taxonomy', async () => {
const {
getByTestId, getByLabelText, findByTestId, queryByText,
} = render(<TaxonomyMenuComponent iconMenu={iconMenu} />);
// Click on delete menu
fireEvent.click(await findByTestId('taxonomy-menu-button'));
fireEvent.click(await findByTestId('taxonomy-menu-delete'));
const deleteButton = getByTestId('delete-button');
// The delete button must to be disabled
expect(deleteButton).toBeDisabled();
// Testing delete button enabled/disabled changes
const input = getByLabelText('Type DELETE to confirm');
fireEvent.change(input, { target: { value: 'DELETE_INVALID' } });
expect(deleteButton).toBeDisabled();
fireEvent.change(input, { target: { value: 'DELETE' } });
expect(deleteButton).toBeEnabled();
// @ts-ignore
deleteTaxonomy.mockResolvedValueOnce({});
// Click on delete button
fireEvent.click(deleteButton);
// Modal closed
expect(queryByText(`Delete "${taxonomyName}"`)).not.toBeInTheDocument();
await waitFor(async () => {
expect(deleteTaxonomy).toBeCalledTimes(1);
});
// Toast message shown
expect(mockSetToastMessage).toBeCalledWith(`"${taxonomyName}" deleted`);
});
it('should open manage orgs dialog menu click', async () => {
const {
findByTestId, findByText, getByText, queryByText,
} = render(<TaxonomyMenuComponent iconMenu={iconMenu} />);
// We need to provide a taxonomy or the modal will not open
// @ts-ignore
getTaxonomy.mockResolvedValue({
id: 1,
name: 'Taxonomy 1',
orgs: [],
allOrgs: true,
});
// Modal closed
expect(queryByText('Assign to organizations')).not.toBeInTheDocument();
// Click on delete menu
fireEvent.click(await findByTestId('taxonomy-menu-button'));
fireEvent.click(await findByTestId('taxonomy-menu-manageOrgs'));
// Modal opened
expect(await findByText('Assign to organizations')).toBeInTheDocument();
// Click on cancel button
fireEvent.click(getByText('Cancel'));
// Modal closed
expect(queryByText('Assign to organizations')).not.toBeInTheDocument();
});
});