/* eslint-disable react/prop-types */ import React from 'react'; import { request, useUserPermissions } from 'strapi-helper-plugin'; import { fireEvent, render, screen, within, waitFor } from '@testing-library/react'; import { ThemeProvider } from 'styled-components'; import { QueryClient, QueryClientProvider } from 'react-query'; import LocaleSettingsPage from '..'; import themes from '../../../../../../strapi-admin/admin/src/themes'; const TestWrapper = ({ children }) => { const queryClient = new QueryClient(); return ( {children} ); }; // TODO: we should not be forced to mock this module // but it bugs somehow when run with jest jest.mock('strapi-helper-plugin', () => ({ EmptyState: ({ title, description }) => (

{title}

{description}

), BaselineAlignment: () =>
, ModalConfirm: ({ onConfirm, isOpen }) => isOpen ? (
) : null, Modal: ({ isOpen, children }) => isOpen &&
{children}
, ModalHeader: ({ children }) =>
{children}
, ModalSection: ({ children }) =>
{children}
, ModalFooter: ({ children }) =>
{children}
, HeaderModal: ({ children }) =>
{children}
, HeaderModalTitle: ({ children }) =>
{children}
, ModalForm: ({ children }) =>
{children}
, ListButton: () =>
, Tabs: ({ children }) =>
{children}
, TabsNav: ({ children }) =>
{children}
, Tab: ({ children }) =>
{children}
, TabsPanel: ({ children }) =>
{children}
, TabPanel: ({ children }) =>
{children}
, useUserPermissions: jest.fn(), request: jest.fn(), })); jest.mock('../../../utils', () => ({ getTrad: x => x, })); jest.mock('react-intl', () => ({ useIntl: () => ({ formatMessage: ({ id }) => id, }), })); describe('i18n settings page', () => { beforeEach(() => { request.mockImplementation(() => Promise.resolve([ { id: 1, name: 'French', code: 'fr-FR', isDefault: false, }, { id: 2, name: 'English', code: 'en-US', isDefault: true, }, ]) ); useUserPermissions.mockImplementation(() => ({ isLoading: false, allowedActions: { canRead: true, canUpdate: true, canCreate: true, canDelete: true }, })); strapi.notification.toggle = jest.fn(); }); afterEach(() => { jest.resetAllMocks(); }); describe('initial state', () => { it('shows default EN locale with edit button but no delete button', async () => { render( ); const row = await waitFor(() => screen.getByText('English').closest('tr')); const rowUtils = within(row); expect(rowUtils.queryByLabelText('Settings.list.actions.delete')).toBeFalsy(); expect(rowUtils.getByLabelText('Settings.list.actions.edit')).toBeVisible(); expect(rowUtils.getByText('Settings.locales.row.default-locale')).toBeVisible(); expect(rowUtils.getByText('en-US')).toBeVisible(); }); it('shows FR locale with edit button and delete button', async () => { render( ); const row = await waitFor(() => screen.getByText('French').closest('tr')); const rowUtils = within(row); expect(rowUtils.getByLabelText('Settings.list.actions.delete')).toBeVisible(); expect(rowUtils.getByLabelText('Settings.list.actions.edit')).toBeVisible(); expect(rowUtils.getByText('fr-FR')).toBeVisible(); }); }); describe('delete', () => { it('removes the locale when clicking the confirmation button', async () => { render( ); const row = await waitFor(() => screen.getByText('French').closest('tr')); const rowUtils = within(row); fireEvent.click(rowUtils.getByLabelText('Settings.list.actions.delete')); fireEvent.click(screen.getByText('Confirm')); await waitFor(() => expect(strapi.notification.toggle).toBeCalledWith({ type: 'success', message: { id: 'Settings.locales.modal.delete.success' }, }) ); }); }); describe('edit', () => { it('shows the default edit modal layout with disabled value', async () => { render( ); const row = await waitFor(() => screen.getByText('English').closest('tr')); const rowUtils = within(row); fireEvent.click(rowUtils.getByLabelText('Settings.list.actions.edit')); expect(screen.getByText(`Settings.locales.modal.edit.confirmation`)).toBeVisible(); expect(screen.getByLabelText(`Settings.locales.modal.edit.locales.label`)).toBeDisabled(); }); it('shows a warning and disabled the confirmation button when display name length is over 50', async () => { render( ); const row = await waitFor(() => screen.getByText('English').closest('tr')); const rowUtils = within(row); fireEvent.click(rowUtils.getByLabelText('Settings.list.actions.edit')); fireEvent.change(screen.getByLabelText('Settings.locales.modal.locales.displayName'), { target: { value: 'a very very very very long string that has more than fifty characters in order to show a warning', }, }); fireEvent.blur(screen.getByLabelText('Settings.locales.modal.locales.displayName')); await waitFor(() => expect(screen.getByText('Settings.locales.modal.edit.confirmation')).toBeDisabled() ); expect(screen.getByText(`Settings.locales.modal.locales.displayName.error`)).toBeVisible(); }); it('closes the edit modal when clicking on cancel', async () => { render( ); const row = await waitFor(() => screen.getByText('English').closest('tr')); const rowUtils = within(row); fireEvent.click(rowUtils.getByLabelText('Settings.list.actions.edit')); fireEvent.click(screen.getByText('app.components.Button.cancel')); expect(screen.queryByText(`Settings.list.actions.edit`)).toBeFalsy(); }); it('shows an error when something went wrong when editing', async () => { const requestGetResponse = [ { id: 1, name: 'French', code: 'fr-FR', isDefault: false, }, { id: 2, name: 'English', code: 'en-US', isDefault: true, }, ]; request.mockImplementation((_, opts) => opts.method === 'PUT' ? Promise.reject(new Error('Something wrong occured')) : Promise.resolve(requestGetResponse) ); render( ); const row = await waitFor(() => screen.getByText('English').closest('tr')); const rowUtils = within(row); fireEvent.click(rowUtils.getByLabelText('Settings.list.actions.edit')); fireEvent.click(screen.getByText('Settings.locales.modal.edit.confirmation')); await waitFor(() => expect(strapi.notification.toggle).toBeCalledWith({ type: 'warning', message: { id: 'notification.error' }, }) ); }); it('shows a success message when editing succeeds', async () => { const requestGetResponse = [ { id: 1, name: 'French', code: 'fr-FR', isDefault: false, }, { id: 2, name: 'English', code: 'en-US', isDefault: true, }, ]; request.mockImplementation((_, opts) => opts.method === 'PUT' ? Promise.resolve({ id: 2, name: 'Frenchie', code: 'fr-FR', isDefault: false, }) : Promise.resolve(requestGetResponse) ); render( ); const row = await waitFor(() => screen.getByText('English').closest('tr')); const rowUtils = within(row); fireEvent.click(rowUtils.getByLabelText('Settings.list.actions.edit')); fireEvent.click(screen.getByText('Settings.locales.modal.edit.confirmation')); await waitFor(() => expect(strapi.notification.toggle).toBeCalledWith({ type: 'success', message: { id: 'Settings.locales.modal.edit.success' }, }) ); expect(request).toBeCalledWith('/i18n/locales/2', { method: 'PUT', body: { name: 'English' }, }); }); it('shows edits the locale with code as displayName when displayName is empty', async () => { const requestGetResponse = [ { id: 1, name: 'French', code: 'fr-FR', isDefault: false, }, { id: 2, name: 'English', code: 'en-US', isDefault: true, }, ]; request.mockImplementation((_, opts) => opts.method === 'PUT' ? Promise.resolve({ id: 2, name: 'Frenchie', code: 'fr-FR', isDefault: false, }) : Promise.resolve(requestGetResponse) ); render( ); const row = await waitFor(() => screen.getByText('English').closest('tr')); const rowUtils = within(row); fireEvent.click(rowUtils.getByLabelText('Settings.list.actions.edit')); fireEvent.change(screen.getByLabelText('Settings.locales.modal.locales.displayName'), { target: { value: '', }, }); fireEvent.click(screen.getByText('Settings.locales.modal.edit.confirmation')); await waitFor(() => expect(strapi.notification.toggle).toBeCalledWith({ type: 'success', message: { id: 'Settings.locales.modal.edit.success' }, }) ); expect(request).toBeCalledWith('/i18n/locales/2', { method: 'PUT', body: { name: 'en-US' }, }); }); }); describe('retrieve', () => { it('shows an error when something went wrong when fetching', async () => { request.mockImplementation(() => Promise.reject(new Error('Something went wrong on the server')) ); render( ); await waitFor(() => expect(strapi.notification.toggle).toBeCalledWith({ type: 'warning', message: { id: 'notification.error' }, }) ); }); it('shows an empty state when the array of locale is empty', async () => { request.mockImplementation(() => Promise.resolve([])); render( ); await waitFor(() => expect(screen.getByTestId('empty-list')).toBeVisible()); }); }); describe('permissions', () => { it('shows a loading information when resolving the permissions', () => { useUserPermissions.mockImplementation(() => ({ isLoading: true, allowedActions: { canRead: false, canUpdate: true, canCreate: true, canDelete: true }, })); render( ); expect(screen.getByText(`Settings.permissions.loading`)); }); it("shows nothing when the user doesn't have read permission", () => { const canRead = false; useUserPermissions.mockImplementation(() => ({ isLoading: false, allowedActions: { canRead, canUpdate: true, canCreate: true, canDelete: true }, })); const { container } = render( ); expect(container).toMatchSnapshot(); }); it('hides "Add locale" buttons when the user is not allowed to create a locale', async () => { const canCreate = false; request.mockImplementation(() => Promise.resolve([])); useUserPermissions.mockImplementation(() => ({ isLoading: false, allowedActions: { canRead: true, canUpdate: true, canCreate, canDelete: true }, })); render( ); await waitFor(() => expect(screen.queryAllByText(`Settings.list.actions.add`).length).toBe(0) ); }); it('hides the "Edit locale" button (pencil) when the user is not allowed to update a locale', async () => { const canUpdate = false; useUserPermissions.mockImplementation(() => ({ isLoading: false, allowedActions: { canRead: true, canUpdate, canCreate: true, canDelete: true }, })); render( ); await waitFor(() => expect(screen.getByText('English')).toBeVisible()); expect(screen.queryAllByLabelText(`Settings.list.actions.edit`).length).toBe(0); }); it('hides the "Delete locale" button (garbage) when the user is not allowed to delete a locale', async () => { const canDelete = false; useUserPermissions.mockImplementation(() => ({ isLoading: false, allowedActions: { canRead: true, canUpdate: false, canCreate: true, canDelete }, })); render( ); await waitFor(() => expect(screen.getByText('English')).toBeVisible()); expect(screen.queryAllByLabelText(`Settings.list.actions.delete`).length).toBe(0); }); }); describe('create', () => { beforeEach(() => { request.mockImplementation(url => url.includes('/i18n/locales') ? Promise.resolve([]) : Promise.resolve([ { code: 'fr-FR', name: 'Francais' }, { code: 'en-EN', name: 'English' }, ]) ); }); it('shows the default create modal layout', async () => { render( ); fireEvent.click(screen.getByText('Settings.list.actions.add')); expect( screen.getByText(`Settings.locales.modal.create.defaultLocales.loading`) ).toBeVisible(); await waitFor(() => expect(screen.getByText(`Settings.locales.modal.create.confirmation`)).toBeVisible() ); expect(screen.getByText(`fr-FR`)).toBeVisible(); expect(screen.getByLabelText('Settings.locales.modal.locales.displayName')).toHaveValue( 'Francais' ); }); it('closes the create modal when clicking on cancel', async () => { render( ); fireEvent.click(screen.getByText('Settings.list.actions.add')); await waitFor(() => expect(screen.getByText(`Settings.locales.modal.create.confirmation`)).toBeVisible() ); fireEvent.click(screen.getByText('app.components.Button.cancel')); expect(screen.queryByText(`Settings.locales.modal.create.confirmation`)).toBeFalsy(); }); it('shows a warning and disabled the confirmation button when display name length is over 50', async () => { render( ); fireEvent.click(screen.getByText('Settings.list.actions.add')); await waitFor(() => expect(screen.getByText(`Settings.locales.modal.create.confirmation`)).toBeVisible() ); fireEvent.change(screen.getByLabelText('Settings.locales.modal.locales.displayName'), { target: { value: 'a very very very very long string that has more than fifty characters in order to show a warning', }, }); fireEvent.blur(screen.getByLabelText('Settings.locales.modal.locales.displayName')); await waitFor(() => expect(screen.getByText(`Settings.locales.modal.locales.displayName.error`)).toBeVisible() ); }); it('sync the select and the text input', async () => { render( ); fireEvent.click(screen.getByText('Settings.list.actions.add')); await waitFor(() => expect(screen.getByText(`Settings.locales.modal.create.confirmation`)).toBeVisible() ); // Put some data in the input in order to make sure it resets well when changing locale fireEvent.change(screen.getByLabelText('Settings.locales.modal.locales.displayName'), { target: { value: 'a very very very very long string that has more than fifty characters in order to show a warning', }, }); const DOWN_ARROW = { keyCode: 40 }; fireEvent.keyDown(screen.getByLabelText('Settings.locales.modal.locales.label'), DOWN_ARROW); fireEvent.click(screen.getByText('en-EN')); expect(screen.getByLabelText('Settings.locales.modal.locales.displayName')).toHaveValue( 'English' ); }); it('shows an error when something went wrong when adding a locale', async () => { request.mockImplementation((url, opts) => { if (opts.method === 'POST') { return Promise.reject(new Error('Something went wrong when adding a locale')); } if (url.includes('/i18n/locales')) return Promise.resolve([]); return Promise.resolve([ { code: 'fr-FR', name: 'Francais' }, { code: 'en-EN', name: 'English' }, ]); }); render( ); fireEvent.click(screen.getByText('Settings.list.actions.add')); const confirmationButton = await waitFor(() => screen.getByText(`Settings.locales.modal.create.confirmation`) ); fireEvent.click(confirmationButton); await waitFor(() => expect(strapi.notification.toggle).toBeCalledWith({ type: 'warning', message: { id: 'notification.error' }, }) ); }); it('shows an success toast when adding a locale is successful', async () => { request.mockImplementation((url, opts) => { if (opts.method === 'POST') { return Promise.resolve({ id: 3, code: 'en-CA', name: 'Canadien' }); } if (url.includes('/i18n/locales')) return Promise.resolve([]); return Promise.resolve([ { code: 'fr-FR', name: 'Francais' }, { code: 'en-EN', name: 'English' }, ]); }); render( ); fireEvent.click(screen.getByText('Settings.list.actions.add')); const confirmationButton = await waitFor(() => screen.getByText(`Settings.locales.modal.create.confirmation`) ); fireEvent.click(confirmationButton); await waitFor(() => expect(strapi.notification.toggle).toBeCalledWith({ type: 'success', message: { id: 'Settings.locales.modal.create.success' }, }) ); }); }); });