Merge pull request #16107 from strapi/feature/review-workflow-settings-papercuts

Review Workflows Settings: Fix papercut issues
This commit is contained in:
Gustav Hansen 2023-03-16 18:12:40 +01:00 committed by GitHub
commit 079449c2ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 93 additions and 77 deletions

View File

@ -14,7 +14,7 @@ if (window.strapi.features.isEnabled(window.strapi.features.SSO)) {
if (window.strapi.features.isEnabled(window.strapi.features.REVIEW_WORKFLOWS)) { if (window.strapi.features.isEnabled(window.strapi.features.REVIEW_WORKFLOWS)) {
items.push({ items.push({
intlLabel: { id: 'Settings.review-workflows.title', defaultMessage: 'Review Workflow' }, intlLabel: { id: 'Settings.review-workflows.page.title', defaultMessage: 'Review Workflows' },
to: '/settings/review-workflows', to: '/settings/review-workflows',
id: 'review-workflows', id: 'review-workflows',
isDisplayed: false, isDisplayed: false,

View File

@ -1,13 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Admin | Settings | Review Workflow | AddStage should render a list of stages 1`] = ` exports[`Admin | Settings | Review Workflow | AddStage should render a list of stages 1`] = `
.c6 {
font-size: 0.75rem;
line-height: 1.33;
font-weight: 600;
color: #8e8ea9;
}
.c0 { .c0 {
background: #ffffff; background: #ffffff;
padding-top: 12px; padding-top: 12px;
@ -32,6 +25,13 @@ exports[`Admin | Settings | Review Workflow | AddStage should render a list of s
gap: 8px; gap: 8px;
} }
.c6 {
font-size: 0.75rem;
line-height: 1.33;
font-weight: 600;
color: #8e8ea9;
}
.c7 { .c7 {
border: 0; border: 0;
-webkit-clip: rect(0 0 0 0); -webkit-clip: rect(0 0 0 0);
@ -104,9 +104,9 @@ exports[`Admin | Settings | Review Workflow | AddStage should render a list of s
aria-hidden="true" aria-hidden="true"
class="c3 c4" class="c3 c4"
fill="none" fill="none"
height="1em" height="1rem"
viewBox="0 0 24 24" viewBox="0 0 24 24"
width="1em" width="1rem"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<circle <circle
@ -116,7 +116,7 @@ exports[`Admin | Settings | Review Workflow | AddStage should render a list of s
r="12" r="12"
/> />
<path <path
d="M17 12.569c0 .124-.1.224-.225.224h-3.981v3.982c0 .124-.101.225-.226.225h-1.136a.225.225 0 01-.226-.225v-3.981H7.226A.225.225 0 017 12.567v-1.136c0-.125.1-.226.225-.226h3.982V7.226c0-.124.1-.225.224-.225h1.138c.124 0 .224.1.224.225v3.982h3.982c.124 0 .225.1.225.224v1.138z" d="M17 12.569c0 .124-.1.224-.225.224h-3.981v3.982c0 .124-.101.225-.226.225h-1.136a.225.225 0 0 1-.226-.225v-3.981H7.226A.225.225 0 0 1 7 12.567v-1.136c0-.125.1-.226.225-.226h3.982V7.226c0-.124.1-.225.224-.225h1.138c.124 0 .224.1.224.225v3.982h3.982c.124 0 .225.1.225.224v1.138Z"
fill="#F6F6F9" fill="#F6F6F9"
/> />
</svg> </svg>

View File

@ -1,6 +1,5 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled from 'styled-components';
import { useField } from 'formik'; import { useField } from 'formik';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
@ -8,30 +7,15 @@ import {
Accordion, Accordion,
AccordionToggle, AccordionToggle,
AccordionContent, AccordionContent,
Box,
Grid, Grid,
GridItem, GridItem,
IconButton, IconButton,
TextInput, TextInput,
} from '@strapi/design-system'; } from '@strapi/design-system';
import { Trash } from '@strapi/icons'; import { Trash } from '@strapi/icons';
import { deleteStage, updateStage } from '../../../actions'; import { deleteStage, updateStage } from '../../../actions';
// TODO: Delete once https://github.com/strapi/design-system/pull/858
// is merged and released.
const StyledAccordion = styled(Box)`
> div:first-child {
box-shadow: ${({ theme }) => theme.shadows.tableShadow};
}
`;
// TODO: Keep an eye on https://github.com/strapi/design-system/pull/878
const DeleteButton = styled(IconButton)`
background-color: transparent;
`;
function Stage({ id, name, index, canDelete, isOpen: isOpenDefault = false }) { function Stage({ id, name, index, canDelete, isOpen: isOpenDefault = false }) {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const [isOpen, setIsOpen] = useState(isOpenDefault); const [isOpen, setIsOpen] = useState(isOpenDefault);
@ -40,44 +24,52 @@ function Stage({ id, name, index, canDelete, isOpen: isOpenDefault = false }) {
const dispatch = useDispatch(); const dispatch = useDispatch();
return ( return (
<StyledAccordion> <Accordion
<Accordion size="S" variant="primary" onToggle={() => setIsOpen(!isOpen)} expanded={isOpen}> size="S"
<AccordionToggle variant="primary"
title={name} onToggle={() => setIsOpen(!isOpen)}
togglePosition="left" expanded={isOpen}
action={ shadow="tableShadow"
canDelete ? ( >
<DeleteButton <AccordionToggle
noBorder title={name}
onClick={() => dispatch(deleteStage(id))} togglePosition="left"
label={formatMessage({ action={
id: 'Settings.review-workflows.stage.delete', canDelete ? (
defaultMessage: 'Delete stage', <IconButton
})} backgroundColor="transparent"
icon={<Trash />} noBorder
/> onClick={() => dispatch(deleteStage(id))}
) : null label={formatMessage({
} id: 'Settings.review-workflows.stage.delete',
/> defaultMessage: 'Delete stage',
<AccordionContent padding={6} background="neutral0"> })}
<Grid gap={4}> icon={<Trash />}
<GridItem col={6}> />
<TextInput ) : null
{...field} }
id={fieldIdentifier} />
value={name} <AccordionContent padding={6} background="neutral0">
label={formatMessage({ <Grid gap={4}>
id: 'Settings.review-workflows.stage.name.label', <GridItem col={6}>
defaultMessage: 'Stage name', <TextInput
})} {...field}
error={meta.error ?? false} id={fieldIdentifier}
onBlur={(event) => dispatch(updateStage(id, { name: event.target.value }))} value={name}
/> label={formatMessage({
</GridItem> id: 'Settings.review-workflows.stage.name.label',
</Grid> defaultMessage: 'Stage name',
</AccordionContent> })}
</Accordion> error={meta.error ?? false}
</StyledAccordion> onChange={(event) => {
field.onChange(event);
dispatch(updateStage(id, { name: event.target.value }));
}}
/>
</GridItem>
</Grid>
</AccordionContent>
</Accordion>
); );
} }

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import styled from 'styled-components'; import styled from 'styled-components';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { Box, Flex, Stack } from '@strapi/design-system'; import { Box, Flex } from '@strapi/design-system';
import { addStage } from '../../actions'; import { addStage } from '../../actions';
import { AddStage } from '../AddStage'; import { AddStage } from '../AddStage';
@ -29,7 +29,14 @@ function Stages({ stages }) {
<StagesContainer spacing={4} width="100%"> <StagesContainer spacing={4} width="100%">
<Background background="neutral200" height="100%" width={2} zIndex={1} /> <Background background="neutral200" height="100%" width={2} zIndex={1} />
<Stack spacing={6} zIndex={2} position="relative" as="ol"> <Flex
direction="column"
alignItems="stretch"
gap={6}
zIndex={2}
position="relative"
as="ol"
>
{stages.map((stage, index) => { {stages.map((stage, index) => {
const id = stage?.id ?? stage.__temp_key__; const id = stage?.id ?? stage.__temp_key__;
@ -45,10 +52,10 @@ function Stages({ stages }) {
</Box> </Box>
); );
})} })}
</Stack> </Flex>
</StagesContainer> </StagesContainer>
<Flex spacing={6}> <Flex direction="column" gap={6}>
<AddStage type="button" onClick={() => dispatch(addStage({ name: '' }))}> <AddStage type="button" onClick={() => dispatch(addStage({ name: '' }))}>
{formatMessage({ {formatMessage({
id: 'Settings.review-workflows.stage.add', id: 'Settings.review-workflows.stage.add',

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { render } from '@testing-library/react'; import { fireEvent, render } from '@testing-library/react';
import { IntlProvider } from 'react-intl'; import { IntlProvider } from 'react-intl';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { FormikProvider, useFormik } from 'formik'; import { FormikProvider, useFormik } from 'formik';
@ -10,12 +10,13 @@ import { ThemeProvider, lightTheme } from '@strapi/design-system';
import configureStore from '../../../../../../../../../admin/src/core/store/configureStore'; import configureStore from '../../../../../../../../../admin/src/core/store/configureStore';
import { Stages } from '../Stages'; import { Stages } from '../Stages';
import { reducer } from '../../../reducer'; import { reducer } from '../../../reducer';
import { ACTION_SET_WORKFLOWS, ACTION_ADD_STAGE } from '../../../constants'; import { ACTION_SET_WORKFLOWS } from '../../../constants';
import { addStage } from '../../../actions'; import * as actions from '../../../actions';
// without mocking actions as ESM it is impossible to spy on named exports
jest.mock('../../../actions', () => ({ jest.mock('../../../actions', () => ({
__esModule: true,
...jest.requireActual('../../../actions'), ...jest.requireActual('../../../actions'),
addStage: jest.fn(),
})); }));
const STAGES_FIXTURE = [ const STAGES_FIXTURE = [
@ -87,8 +88,7 @@ describe('Admin | Settings | Review Workflow | Stages', () => {
it('should append a new stage when clicking "add new stage"', async () => { it('should append a new stage when clicking "add new stage"', async () => {
const { getByRole } = setup(); const { getByRole } = setup();
const spy = jest.spyOn(actions, 'addStage');
addStage.mockReturnValue({ type: ACTION_ADD_STAGE });
await user.click( await user.click(
getByRole('button', { getByRole('button', {
@ -96,7 +96,24 @@ describe('Admin | Settings | Review Workflow | Stages', () => {
}) })
); );
expect(addStage).toBeCalledTimes(1); expect(spy).toBeCalledTimes(1);
expect(addStage).toBeCalledWith({ name: '' }); expect(spy).toBeCalledWith({ name: '' });
});
it('should update the name of a stage by changing the input value', async () => {
const { queryByRole, getByRole } = setup();
const spy = jest.spyOn(actions, 'updateStage');
await user.click(getByRole('button', { name: /stage-2/i }));
const input = queryByRole('textbox', {
name: /stage name/i,
});
fireEvent.change(input, { target: { value: 'New name' } });
expect(spy).toBeCalledWith(2, {
name: 'New name',
});
}); });
}); });