test: add tests for DynamicZone sub components

This commit is contained in:
Josh 2022-11-14 10:26:34 +00:00
parent 4bf9291ccd
commit c58d5a8692
9 changed files with 393 additions and 51 deletions

View File

@ -58,7 +58,7 @@ export default function ComponentCard({ children, icon, onClick }) {
<button type="button" onClick={onClick}>
<ComponentBox borderRadius="borderRadius">
<Stack spacing={1} style={{ justifyContent: 'center', alignItems: 'center' }}>
<StyledFontAwesomeIcon icon={icon} />
<StyledFontAwesomeIcon data-testid="component-card-icon" icon={icon} />
<Typography variant="pi" fontWeight="bold" textColor="neutral600">
{children}
</Typography>

View File

@ -42,13 +42,19 @@ const ComponentCategory = ({ category, components, variant, isOpen, onAddCompone
);
};
ComponentCategory.defaultProps = {
components: [],
isOpen: false,
variant: 'primary',
};
ComponentCategory.propTypes = {
category: PropTypes.string.isRequired,
components: PropTypes.array.isRequired,
isOpen: PropTypes.bool.isRequired,
components: PropTypes.array,
isOpen: PropTypes.bool,
onAddComponent: PropTypes.func.isRequired,
onToggle: PropTypes.func.isRequired,
variant: PropTypes.oneOf(['primary', 'secondary']).isRequired,
variant: PropTypes.oneOf(['primary', 'secondary']),
};
export default ComponentCategory;

View File

@ -34,7 +34,7 @@ const ComponentPicker = ({ components, isOpen, onClickAddComponent }) => {
}, [components, getComponentLayout]);
useEffect(() => {
if (isOpen && dynamicComponentCategories.length) {
if (isOpen && dynamicComponentCategories.length > 0) {
setCategoryToOpen(dynamicComponentCategories[0].category);
}
}, [isOpen, dynamicComponentCategories]);
@ -95,9 +95,14 @@ const ComponentPicker = ({ components, isOpen, onClickAddComponent }) => {
);
};
ComponentPicker.defaultProps = {
components: [],
isOpen: false,
};
ComponentPicker.propTypes = {
components: PropTypes.array.isRequired,
isOpen: PropTypes.bool.isRequired,
components: PropTypes.array,
isOpen: PropTypes.bool,
onClickAddComponent: PropTypes.func.isRequired,
};

View File

@ -6,62 +6,41 @@ import { IntlProvider } from 'react-intl';
import AddComponentButton from '../AddComponentButton';
describe('<AddComponentButton />', () => {
it('should render the label by default', () => {
const setup = (props) =>
render(
<ThemeProvider theme={lightTheme}>
<IntlProvider locale="en" messages={{}} defaultLocale="en">
<AddComponentButton label="test" name="name" onClick={jest.fn()} />
<AddComponentButton label="test" name="name" onClick={jest.fn()} {...props} />
</IntlProvider>
</ThemeProvider>
);
it('should render the label by default', () => {
setup();
expect(screen.getByText(/test/)).toBeInTheDocument();
});
it('should render the close label if the isOpen prop is true', () => {
render(
<ThemeProvider theme={lightTheme}>
<IntlProvider locale="en" messages={{}} defaultLocale="en">
<AddComponentButton label="test" isOpen name="name" onClick={jest.fn()} />
</IntlProvider>
</ThemeProvider>
);
setup({ isOpen: true });
expect(screen.getByText(/Close/)).toBeInTheDocument();
});
it('should render the name of the field when the label is an empty string', () => {
render(
<ThemeProvider theme={lightTheme}>
<IntlProvider locale="en" messages={{}} defaultLocale="en">
<AddComponentButton label="" name="name" onClick={jest.fn()} />
</IntlProvider>
</ThemeProvider>
);
setup({ label: '' });
expect(screen.getByText(/name/)).toBeInTheDocument();
});
it('should render a too high error if there is hasMaxError is true and the component is not open', () => {
render(
<ThemeProvider theme={lightTheme}>
<IntlProvider locale="en" messages={{}} defaultLocale="en">
<AddComponentButton hasMaxError label="test" name="name" onClick={jest.fn()} />
</IntlProvider>
</ThemeProvider>
);
setup({ hasMaxError: true });
expect(screen.getByText(/The value is too high./)).toBeInTheDocument();
});
it('should render a label telling the user there are X missing components if hasMinError is true and the component is not open', () => {
render(
<ThemeProvider theme={lightTheme}>
<IntlProvider locale="en" messages={{}} defaultLocale="en">
<AddComponentButton hasMinError label="test" name="name" onClick={jest.fn()} />
</IntlProvider>
</ThemeProvider>
);
setup({ hasMinError: true });
expect(screen.getByText(/missing components/)).toBeInTheDocument();
});
@ -69,13 +48,7 @@ describe('<AddComponentButton />', () => {
it('should call the onClick handler when the button is clicked', () => {
const onClick = jest.fn();
render(
<ThemeProvider theme={lightTheme}>
<IntlProvider locale="en" messages={{}} defaultLocale="en">
<AddComponentButton label="test" name="name" onClick={onClick} />
</IntlProvider>
</ThemeProvider>
);
setup({ onClick });
screen.getByText(/test/).click();
@ -85,13 +58,7 @@ describe('<AddComponentButton />', () => {
it('should not call the onClick handler when the button is disabled', () => {
const onClick = jest.fn();
render(
<ThemeProvider theme={lightTheme}>
<IntlProvider locale="en" messages={{}} defaultLocale="en">
<AddComponentButton isDisabled label="test" name="name" onClick={onClick} />
</IntlProvider>
</ThemeProvider>
);
setup({ onClick, disabled: true });
screen.getByText(/test/).click();

View File

@ -0,0 +1,114 @@
import React from 'react';
import { fireEvent, render } from '@testing-library/react';
import { ThemeProvider, lightTheme } from '@strapi/design-system';
import GlobalStyle from '../../../../../components/GlobalStyle';
import ComponentCard from '../ComponentCard';
describe('ComponentCard', () => {
const setup = (props) =>
render(
<ThemeProvider theme={lightTheme}>
<ComponentCard {...props}>test</ComponentCard>
<GlobalStyle />
</ThemeProvider>
);
it('should render children by default', () => {
const { getByTestId, getByText } = setup();
expect(getByText('test')).toBeInTheDocument();
expect(getByTestId('component-card-icon')).toMatchInlineSnapshot(`
.c1 {
width: 2rem !important;
height: 2rem !important;
padding: 0.5625rem;
border-radius: 4rem;
background: #eaeaef;
}
.c1 path {
fill: #8e8ea9;
}
.c2.active .c0,
.c2:hover .c0 {
background: #d9d8ff;
}
.c2.active .c0 path,
.c2:hover .c0 path {
fill: #4945ff;
}
<svg
aria-hidden="true"
class="svg-inline--fa fa-dice-d6 c0 c1"
data-icon="dice-d6"
data-prefix="fas"
data-testid="component-card-icon"
focusable="false"
role="img"
viewBox="0 0 448 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M422.19 109.95L256.21 9.07c-19.91-12.1-44.52-12.1-64.43 0L25.81 109.95c-5.32 3.23-5.29 11.27.06 14.46L224 242.55l198.14-118.14c5.35-3.19 5.38-11.22.05-14.46zm13.84 44.63L240 271.46v223.82c0 12.88 13.39 20.91 24.05 14.43l152.16-92.48c19.68-11.96 31.79-33.94 31.79-57.7v-197.7c0-6.41-6.64-10.43-11.97-7.25zM0 161.83v197.7c0 23.77 12.11 45.74 31.79 57.7l152.16 92.47c10.67 6.48 24.05-1.54 24.05-14.43V271.46L11.97 154.58C6.64 151.4 0 155.42 0 161.83z"
fill="currentColor"
/>
</svg>
`);
});
it('should render a valid icon when passed its name', () => {
const { getByTestId } = setup({ icon: 'fa-camera' });
expect(getByTestId('component-card-icon')).toMatchInlineSnapshot(`
.c1 {
width: 2rem !important;
height: 2rem !important;
padding: 0.5625rem;
border-radius: 4rem;
background: #eaeaef;
}
.c1 path {
fill: #8e8ea9;
}
.c2.active .c0,
.c2:hover .c0 {
background: #d9d8ff;
}
.c2.active .c0 path,
.c2:hover .c0 path {
fill: #4945ff;
}
<svg
aria-hidden="true"
class="svg-inline--fa fa-camera c0 c1"
data-icon="camera"
data-prefix="fas"
data-testid="component-card-icon"
focusable="false"
role="img"
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M512 144v288c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V144c0-26.5 21.5-48 48-48h88l12.3-32.9c7-18.7 24.9-31.1 44.9-31.1h125.5c20 0 37.9 12.4 44.9 31.1L376 96h88c26.5 0 48 21.5 48 48zM376 288c0-66.2-53.8-120-120-120s-120 53.8-120 120 53.8 120 120 120 120-53.8 120-120zm-32 0c0 48.5-39.5 88-88 88s-88-39.5-88-88 39.5-88 88-88 88 39.5 88 88z"
fill="currentColor"
/>
</svg>
`);
});
it('should call the onClick handler when passed', () => {
const onClick = jest.fn();
const { getByText } = setup({ onClick });
fireEvent.click(getByText('test'));
expect(onClick).toHaveBeenCalled();
});
});

View File

@ -0,0 +1,83 @@
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import { ThemeProvider, lightTheme } from '@strapi/design-system';
import { IntlProvider } from 'react-intl';
import ComponentCategory from '../ComponentCategory';
jest.mock('@fortawesome/react-fontawesome', () => ({
FontAwesomeIcon: () => null,
}));
describe('ComponentCategory', () => {
const setup = (props) =>
render(
<ThemeProvider theme={lightTheme}>
<IntlProvider locale="en" messages={{}} defaultLocale="en">
<ComponentCategory
onAddComponent={jest.fn()}
onToggle={jest.fn()}
category="testing"
{...props}
/>
</IntlProvider>
</ThemeProvider>
);
it('should render my array of components when passed and the accordion is open', () => {
setup({
isOpen: true,
components: [
{
componentUid: 'test',
info: {
displayName: 'myComponent',
icon: 'test',
},
},
],
});
expect(screen.getByText(/myComponent/)).toBeInTheDocument();
});
it('should render the category as the accordion buttons label', () => {
setup({
category: 'myCategory',
});
expect(screen.getByText(/myCategory/)).toBeInTheDocument();
});
it('should call the onToggle callback when the accordion trigger is pressed', () => {
const onToggle = jest.fn();
setup({
onToggle,
});
fireEvent.click(screen.getByText(/testing/));
expect(onToggle).toHaveBeenCalledWith('testing');
});
it('should call onAddComponent with the componentUid when a ComponentCard is clicked', () => {
const onAddComponent = jest.fn();
setup({
isOpen: true,
onAddComponent,
components: [
{
componentUid: 'test',
info: {
displayName: 'myComponent',
icon: 'test',
},
},
],
});
fireEvent.click(screen.getByText(/myComponent/));
expect(onAddComponent).toHaveBeenCalledWith('test');
});
});

View File

@ -0,0 +1,75 @@
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import { ThemeProvider, lightTheme } from '@strapi/design-system';
import { IntlProvider } from 'react-intl';
import ComponentPicker from '../ComponentPicker';
import { layoutData } from './fixtures';
jest.mock('@fortawesome/react-fontawesome', () => ({
FontAwesomeIcon: () => null,
}));
jest.mock('../../../../hooks', () => ({
useContentTypeLayout: jest.fn().mockReturnValue({
getComponentLayout: jest.fn().mockImplementation((componentUid) => layoutData[componentUid]),
}),
}));
describe('ComponentPicker', () => {
afterEach(() => {
jest.restoreAllMocks();
});
const Component = (props) => (
<ThemeProvider theme={lightTheme}>
<IntlProvider locale="en" messages={{}} defaultLocale="en">
<ComponentPicker isOpen onClickAddComponent={jest.fn()} {...props} />
</IntlProvider>
</ThemeProvider>
);
const setup = (props) => render(<Component {...props} />);
it('should by default give me the instruction to Pick one Component', () => {
setup();
expect(screen.getByText(/Pick one component/)).toBeInTheDocument();
});
it('should render null if isOpen is false', () => {
setup({ isOpen: false });
expect(screen.queryByText(/Pick one component/)).not.toBeInTheDocument();
});
it('should render the category names by default', () => {
setup({ components: ['component1', 'component2'] });
expect(screen.getByText(/myComponents/)).toBeInTheDocument();
});
it('should open the first category of components when isOpen changes to true from false', () => {
const { rerender } = setup({
isOpen: false,
});
rerender(<Component isOpen components={['component1', 'component2', 'component3']} />);
expect(screen.getByText(/component1/)).toBeInTheDocument();
expect(screen.queryByText(/component3/)).not.toBeInTheDocument();
});
it('should call onClickAddComponent with the componentUid when a Component is clicked', () => {
const onClickAddComponent = jest.fn();
setup({
components: ['component1', 'component2'],
onClickAddComponent,
});
fireEvent.click(screen.getByText(/component1/));
expect(onClickAddComponent).toHaveBeenCalledWith('component1');
});
});

View File

@ -0,0 +1,69 @@
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import { ThemeProvider, lightTheme } from '@strapi/design-system';
import { IntlProvider } from 'react-intl';
import DynamicComponent from '../DynamicComponent';
import { layoutData } from './fixtures';
jest.mock('@fortawesome/react-fontawesome', () => ({
FontAwesomeIcon: () => null,
}));
jest.mock('../../../../hooks', () => ({
useContentTypeLayout: jest.fn().mockReturnValue({
getComponentLayout: jest.fn().mockImplementation((componentUid) => layoutData[componentUid]),
}),
}));
/**
* We _could_ unmock this and use it, but it requires more
* harnessing then is necessary and it's not worth it for these
* tests when really we're focussing on dynamic zone behaviour.
*/
jest.mock('../../../FieldComponent', () => () => null);
describe('DynamicComponent', () => {
afterEach(() => {
jest.restoreAllMocks();
});
const TestComponent = (props) => (
<ThemeProvider theme={lightTheme}>
<IntlProvider locale="en" messages={{}} defaultLocale="en">
<DynamicComponent
componentUid="component1"
name="dynamiczone"
onMoveComponentDownClick={jest.fn()}
onMoveComponentUpClick={jest.fn()}
onRemoveComponentClick={jest.fn()}
{...props}
/>
</IntlProvider>
</ThemeProvider>
);
const setup = (props) => render(<TestComponent {...props} />);
it('should by default render the name of the component in the accordion trigger', () => {
setup();
expect(screen.getByRole('button', { name: 'component1' })).toBeInTheDocument();
});
it('should allow removal of the component & call the onRemoveComponentClick callback when the field isAllowed', () => {
const onRemoveComponentClick = jest.fn();
setup({ isFieldAllowed: true, onRemoveComponentClick });
fireEvent.click(screen.getByRole('button', { name: 'Delete component1' }));
expect(onRemoveComponentClick).toHaveBeenCalled();
});
it('should not show you the delete component button if isFieldAllowed is false', () => {
setup({ isFieldAllowed: false });
expect(screen.queryByRole('button', { name: 'Delete component1' })).not.toBeInTheDocument();
});
});

View File

@ -0,0 +1,23 @@
export const layoutData = {
component1: {
category: 'myComponents',
info: {
displayName: 'component1',
icon: undefined,
},
},
component2: {
category: 'myComponents',
info: {
displayName: 'component2',
icon: undefined,
},
},
component3: {
category: 'otherComponents',
info: {
displayName: 'component3',
icon: undefined,
},
},
};