mirror of
https://github.com/strapi/strapi.git
synced 2025-09-26 17:00:55 +00:00
Add delete modal and tests for that
This commit is contained in:
parent
739bb9f04d
commit
ecb1b9facb
@ -67,7 +67,7 @@
|
|||||||
"prettier:code": "prettier \"**/*.js\"",
|
"prettier:code": "prettier \"**/*.js\"",
|
||||||
"prettier:other": "prettier \"**/*.{md,css,scss,yaml,yml}\"",
|
"prettier:other": "prettier \"**/*.{md,css,scss,yaml,yml}\"",
|
||||||
"test:clean": "rimraf ./coverage",
|
"test:clean": "rimraf ./coverage",
|
||||||
"test:front": "npm run test:clean && cross-env NODE_ENV=test IS_EE=true jest --config ./jest.config.front.js --coverage",
|
"test:front": "npm run test:clean && cross-env NODE_ENV=test IS_EE=true jest --config ./jest.config.front.js",
|
||||||
"test:front:watch": "cross-env NODE_ENV=test IS_EE=true jest --config ./jest.config.front.js --watchAll",
|
"test:front:watch": "cross-env NODE_ENV=test IS_EE=true jest --config ./jest.config.front.js --watchAll",
|
||||||
"test:front:update": "cross-env NODE_ENV=test IS_EE=true jest --config ./jest.config.front.js --u",
|
"test:front:update": "cross-env NODE_ENV=test IS_EE=true jest --config ./jest.config.front.js --u",
|
||||||
"test:front:ce": "npm run test:clean && cross-env NODE_ENV=test IS_EE=false jest --config ./jest.config.front.js --coverage",
|
"test:front:ce": "npm run test:clean && cross-env NODE_ENV=test IS_EE=false jest --config ./jest.config.front.js --coverage",
|
||||||
|
@ -12,7 +12,7 @@ import { getTrad } from '../../utils';
|
|||||||
const canUpdate = true;
|
const canUpdate = true;
|
||||||
const canDelete = true;
|
const canDelete = true;
|
||||||
|
|
||||||
const LocaleSettingsPage = ({ locale }) => {
|
const LocaleSettingsPage = ({ locale, onDelete }) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -34,12 +34,21 @@ const LocaleSettingsPage = ({ locale }) => {
|
|||||||
<IconLinks
|
<IconLinks
|
||||||
links={[
|
links={[
|
||||||
{
|
{
|
||||||
icon: canUpdate ? <Pencil fill="#0e1622" /> : null,
|
icon: canUpdate ? (
|
||||||
|
<span aria-label="Edit locale">
|
||||||
|
<Pencil fill="#0e1622" />
|
||||||
|
</span>
|
||||||
|
) : null,
|
||||||
onClick: () => console.log('edit'),
|
onClick: () => console.log('edit'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: canDelete && !locale.isDefault ? <FontAwesomeIcon icon="trash-alt" /> : null,
|
icon:
|
||||||
onClick: () => console.log('open delete modal'),
|
canDelete && !locale.isDefault ? (
|
||||||
|
<span aria-label="Delete locale">
|
||||||
|
<FontAwesomeIcon icon="trash-alt" />
|
||||||
|
</span>
|
||||||
|
) : null,
|
||||||
|
onClick: onDelete,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@ -54,6 +63,7 @@ LocaleSettingsPage.propTypes = {
|
|||||||
displayName: PropTypes.string,
|
displayName: PropTypes.string,
|
||||||
code: PropTypes.string.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
onDelete: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LocaleSettingsPage;
|
export default LocaleSettingsPage;
|
||||||
|
@ -1,17 +1,25 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { BaselineAlignment } from 'strapi-helper-plugin';
|
import { BaselineAlignment, ModalConfirm } from 'strapi-helper-plugin';
|
||||||
import { Header, List } from '@buffetjs/custom';
|
import { Header, List } from '@buffetjs/custom';
|
||||||
import { Button } from '@buffetjs/core';
|
import { Button, Text } from '@buffetjs/core';
|
||||||
|
|
||||||
import { LocaleRow } from '../../components';
|
import { LocaleRow } from '../../components';
|
||||||
import { useLocales } from '../../hooks';
|
import { useLocales } from '../../hooks';
|
||||||
import { getTrad } from '../../utils';
|
import { getTrad } from '../../utils';
|
||||||
|
import useDeleteLocale from '../../hooks/useDeleteLocale';
|
||||||
|
|
||||||
// Fake permissions
|
// Fake permissions
|
||||||
const canCreate = true;
|
const canCreate = true;
|
||||||
|
|
||||||
const LocaleSettingsPage = () => {
|
const LocaleSettingsPage = () => {
|
||||||
|
const {
|
||||||
|
isDeleting,
|
||||||
|
isDeleteModalOpen,
|
||||||
|
deleteLocale,
|
||||||
|
showDeleteModal,
|
||||||
|
hideDeleteModal,
|
||||||
|
} = useDeleteLocale();
|
||||||
|
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const { locales, isLoading } = useLocales();
|
const { locales, isLoading } = useLocales();
|
||||||
|
|
||||||
@ -59,8 +67,29 @@ const LocaleSettingsPage = () => {
|
|||||||
title={listTitle}
|
title={listTitle}
|
||||||
items={locales}
|
items={locales}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
customRowComponent={locale => <LocaleRow locale={locale} />}
|
customRowComponent={locale => (
|
||||||
|
<LocaleRow locale={locale} onDelete={() => showDeleteModal(locale)} />
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ModalConfirm
|
||||||
|
confirmButtonLabel={{
|
||||||
|
id: getTrad('Settings.locales.modal.delete.confirm'),
|
||||||
|
}}
|
||||||
|
showButtonLoader={isDeleting}
|
||||||
|
isOpen={isDeleteModalOpen}
|
||||||
|
toggle={hideDeleteModal}
|
||||||
|
onClosed={hideDeleteModal}
|
||||||
|
onConfirm={deleteLocale}
|
||||||
|
type="warning"
|
||||||
|
content={{
|
||||||
|
id: getTrad(`Settings.locales.modal.delete.message`),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text fontWeight="bold">
|
||||||
|
{formatMessage({ id: getTrad('Settings.locales.modal.delete.secondMessage') })}
|
||||||
|
</Text>
|
||||||
|
</ModalConfirm>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,85 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { fireEvent, render, screen, within, waitFor } from '@testing-library/react';
|
||||||
|
import { ThemeProvider } from 'styled-components';
|
||||||
|
import LocaleSettingsPage from '..';
|
||||||
|
import themes from '../../../../../../strapi-admin/admin/src/themes';
|
||||||
|
|
||||||
|
// TODO: we should not be forced to mock this module
|
||||||
|
// but it bugs somehow when run with jest
|
||||||
|
jest.mock('strapi-helper-plugin', () => ({
|
||||||
|
BaselineAlignment: () => <div />,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
|
ModalConfirm: ({ onConfirm, isOpen }) =>
|
||||||
|
isOpen ? (
|
||||||
|
<div role="dialog">
|
||||||
|
<button onClick={onConfirm} type="button">
|
||||||
|
Confirm
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : null,
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../../utils', () => ({
|
||||||
|
getTrad: x => x,
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('react-intl', () => ({
|
||||||
|
useIntl: () => ({
|
||||||
|
formatMessage: ({ id }) => id,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('i18n settings page', () => {
|
||||||
|
describe('initial state', () => {
|
||||||
|
it('shows default EN locale with edit button but no delete button', async () => {
|
||||||
|
render(
|
||||||
|
<ThemeProvider theme={themes}>
|
||||||
|
<LocaleSettingsPage />
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const row = await waitFor(() => screen.getByText('English').closest('tr'));
|
||||||
|
const rowUtils = within(row);
|
||||||
|
|
||||||
|
expect(rowUtils.queryByLabelText('Delete locale')).toBeFalsy();
|
||||||
|
expect(rowUtils.getByLabelText('Edit locale')).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(
|
||||||
|
<ThemeProvider theme={themes}>
|
||||||
|
<LocaleSettingsPage />
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const row = await waitFor(() => screen.getByText('French').closest('tr'));
|
||||||
|
const rowUtils = within(row);
|
||||||
|
|
||||||
|
expect(rowUtils.getByLabelText('Delete locale')).toBeVisible();
|
||||||
|
expect(rowUtils.getByLabelText('Edit locale')).toBeVisible();
|
||||||
|
expect(rowUtils.getByText('fr-FR')).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('delete', () => {
|
||||||
|
it('removes the locale when clicking the confirmation button', async () => {
|
||||||
|
render(
|
||||||
|
<ThemeProvider theme={themes}>
|
||||||
|
<LocaleSettingsPage />
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const row = await waitFor(() => screen.getByText('French').closest('tr'));
|
||||||
|
const rowUtils = within(row);
|
||||||
|
|
||||||
|
fireEvent.click(rowUtils.getByLabelText('Delete locale'));
|
||||||
|
|
||||||
|
const dialog = screen.getByRole('dialog');
|
||||||
|
expect(dialog).toBeVisible();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('Confirm'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,4 @@
|
|||||||
|
export const SHOW_MODAL = 'SHOW_MODAL';
|
||||||
|
export const HIDE_MODAL = 'HIDE_MODAL';
|
||||||
|
export const DELETE_LOCALE = 'DELETE_LOCALE';
|
||||||
|
export const RESOLVE_LOCALE = 'RESOLVE_LOCALE';
|
@ -0,0 +1,25 @@
|
|||||||
|
import { useReducer } from 'react';
|
||||||
|
import reducer, { initialState } from './reducer';
|
||||||
|
import { SHOW_MODAL, HIDE_MODAL, RESOLVE_LOCALE, DELETE_LOCALE } from './constants';
|
||||||
|
|
||||||
|
const useDeleteLocale = () => {
|
||||||
|
const [{ isDeleteModalOpen, isDeleting }, dispatch] = useReducer(reducer, initialState);
|
||||||
|
|
||||||
|
const deleteLocale = () => {
|
||||||
|
dispatch({ type: DELETE_LOCALE });
|
||||||
|
|
||||||
|
return new Promise(resolve =>
|
||||||
|
setTimeout(() => {
|
||||||
|
dispatch({ type: RESOLVE_LOCALE });
|
||||||
|
resolve();
|
||||||
|
}, 1000)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showDeleteModal = localeToDelete => dispatch({ type: SHOW_MODAL, localeToDelete });
|
||||||
|
const hideDeleteModal = () => dispatch({ type: HIDE_MODAL });
|
||||||
|
|
||||||
|
return { isDeleting, isDeleteModalOpen, deleteLocale, showDeleteModal, hideDeleteModal };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useDeleteLocale;
|
@ -0,0 +1,41 @@
|
|||||||
|
/* eslint-disable consistent-return */
|
||||||
|
import produce from 'immer';
|
||||||
|
import { SHOW_MODAL, HIDE_MODAL, RESOLVE_LOCALE, DELETE_LOCALE } from './constants';
|
||||||
|
|
||||||
|
export const initialState = {
|
||||||
|
localeToDelete: null,
|
||||||
|
isDeleteModalOpen: false,
|
||||||
|
isDeleting: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const reducer = (state, action) =>
|
||||||
|
produce(state, draftState => {
|
||||||
|
switch (action.type) {
|
||||||
|
case SHOW_MODAL: {
|
||||||
|
draftState.isDeleteModalOpen = true;
|
||||||
|
draftState.localeToDelete = action.localeToDelete;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case HIDE_MODAL: {
|
||||||
|
draftState.isDeleteModalOpen = false;
|
||||||
|
draftState.localeToDelete = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DELETE_LOCALE: {
|
||||||
|
draftState.isDeleting = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case RESOLVE_LOCALE: {
|
||||||
|
draftState.isDeleting = false;
|
||||||
|
draftState.isDeleteModalOpen = false;
|
||||||
|
draftState.localeToDelete = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return draftState;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default reducer;
|
@ -0,0 +1,105 @@
|
|||||||
|
import reducer from '../reducer';
|
||||||
|
import { SHOW_MODAL, HIDE_MODAL, RESOLVE_LOCALE, DELETE_LOCALE } from '../constants';
|
||||||
|
|
||||||
|
describe(`I18N Settings page reducer`, () => {
|
||||||
|
describe(`Initial state`, () => {
|
||||||
|
it('returns the initialState', () => {
|
||||||
|
const state = {
|
||||||
|
localeToDelete: null,
|
||||||
|
isDeleteModalOpen: false,
|
||||||
|
isDeleting: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(reducer(state, {})).toEqual(state);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(SHOW_MODAL, () => {
|
||||||
|
it('set the isDeleteModalOpen key to true', () => {
|
||||||
|
const state = {
|
||||||
|
localeToDelete: null,
|
||||||
|
isDeleteModalOpen: false,
|
||||||
|
isDeleting: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = {
|
||||||
|
type: SHOW_MODAL,
|
||||||
|
localeToDelete: 'en-EN',
|
||||||
|
};
|
||||||
|
|
||||||
|
const expected = {
|
||||||
|
localeToDelete: 'en-EN',
|
||||||
|
isDeleteModalOpen: true,
|
||||||
|
isDeleting: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(reducer(state, action)).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(HIDE_MODAL, () => {
|
||||||
|
it('sets the isDeleteModalOpen value to false when it was true', () => {
|
||||||
|
const state = {
|
||||||
|
localeToDelete: 'en-EN',
|
||||||
|
isDeleteModalOpen: true,
|
||||||
|
isDeleting: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = {
|
||||||
|
type: HIDE_MODAL,
|
||||||
|
};
|
||||||
|
|
||||||
|
const expected = {
|
||||||
|
localeToDelete: null,
|
||||||
|
isDeleteModalOpen: false,
|
||||||
|
isDeleting: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(reducer(state, action)).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(DELETE_LOCALE, () => {
|
||||||
|
it('sets the isDeleting value to true', () => {
|
||||||
|
const state = {
|
||||||
|
localeToDelete: 'en-EN',
|
||||||
|
isDeleteModalOpen: true,
|
||||||
|
isDeleting: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = {
|
||||||
|
type: DELETE_LOCALE,
|
||||||
|
};
|
||||||
|
|
||||||
|
const expected = {
|
||||||
|
localeToDelete: 'en-EN',
|
||||||
|
isDeleteModalOpen: true,
|
||||||
|
isDeleting: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(reducer(state, action)).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(RESOLVE_LOCALE, () => {
|
||||||
|
it('resets the state to its initial values when they were true', () => {
|
||||||
|
const state = {
|
||||||
|
localeToDelete: 'en-EN',
|
||||||
|
isDeleteModalOpen: true,
|
||||||
|
isDeleting: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = {
|
||||||
|
type: RESOLVE_LOCALE,
|
||||||
|
};
|
||||||
|
|
||||||
|
const expected = {
|
||||||
|
localeToDelete: null,
|
||||||
|
isDeleteModalOpen: false,
|
||||||
|
isDeleting: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(reducer(state, action)).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -5,5 +5,8 @@
|
|||||||
"Settings.list.description": "Configure the settings for the internationalization plugin",
|
"Settings.list.description": "Configure the settings for the internationalization plugin",
|
||||||
"Settings.locales.list.title.singular": "{number} Locale",
|
"Settings.locales.list.title.singular": "{number} Locale",
|
||||||
"Settings.locales.list.title.plural": "{number} Locales",
|
"Settings.locales.list.title.plural": "{number} Locales",
|
||||||
"Settings.locales.row.default-locale": "Default locale"
|
"Settings.locales.row.default-locale": "Default locale",
|
||||||
|
"Settings.locales.modal.delete.confirm": "Yes, delete",
|
||||||
|
"Settings.locales.modal.delete.message": "Deleting this locale will delete all associated content. If you want to keep some content, make sure to reallocate it to another locale first.",
|
||||||
|
"Settings.locales.modal.delete.secondMessage": "Do you want to delete this locale?"
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
// Setup the strapi functioon global variable
|
// Setup the strapi functioon global variable
|
||||||
|
|
||||||
|
import '@testing-library/jest-dom/extend-expect';
|
||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const hoistNonReactStatics = require('hoist-non-react-statics');
|
const hoistNonReactStatics = require('hoist-non-react-statics');
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user