Test(widgets-list): add tests (#3697)

This commit is contained in:
Erez Rokah 2020-05-04 15:17:19 +03:00 committed by GitHub
parent 628eee4d1c
commit 0504c584fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 793 additions and 1 deletions

View File

@ -6,6 +6,7 @@ module.exports = {
'netlify-cms-ui-default': '<rootDir>/packages/netlify-cms-ui-default/src/index.js',
'netlify-cms-backend-github': '<rootDir>/packages/netlify-cms-backend-github/src/index.ts',
'netlify-cms-lib-widgets': '<rootDir>/packages/netlify-cms-lib-widgets/src/index.ts',
'netlify-cms-widget-object': '<rootDir>/packages/netlify-cms-widget-object/src/index.js',
},
testURL: 'http://localhost:8080',
};

View File

@ -109,7 +109,7 @@ class ObjectWidgetTopBar extends React.Component {
return (
<TopBarContainer>
<ExpandButtonContainer hasHeading={!!heading}>
<ExpandButton onClick={onCollapseToggle}>
<ExpandButton onClick={onCollapseToggle} data-testid="expand-button">
<Icon type="chevron" direction={collapsed ? 'right' : 'down'} size="small" />
</ExpandButton>
{heading}

View File

@ -388,6 +388,7 @@ export default class ListControl extends React.Component {
onCollapseToggle={partial(this.handleItemCollapseToggle, index)}
onRemove={partial(this.handleRemove, index, key)}
dragHandleHOC={SortableHandle}
data-testid={`styled-list-item-top-bar-${key}`}
/>
<NestedObjectLabel collapsed={collapsed}>{this.objectLabel(item)}</NestedObjectLabel>
<ClassNames>
@ -412,6 +413,7 @@ export default class ListControl extends React.Component {
controlRef={controlRef}
validationKey={key}
collapsed={collapsed}
data-testid={`object-control-${key}`}
/>
)}
</ClassNames>

View File

@ -0,0 +1,273 @@
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import { fromJS } from 'immutable';
import ListControl from '../ListControl';
jest.mock('netlify-cms-widget-object', () => {
const React = require('react');
class MockObjectControl extends React.Component {
render() {
return <mock-object-control {...this.props}>{this.props.children}</mock-object-control>;
}
}
return {
controlComponent: MockObjectControl,
};
});
jest.mock('netlify-cms-ui-default', () => {
const actual = jest.requireActual('netlify-cms-ui-default');
const ListItemTopBar = props => (
<mock-list-item-top-bar {...props} onClick={props.onCollapseToggle}>
{props.children}
</mock-list-item-top-bar>
);
return {
...actual,
ListItemTopBar,
};
});
jest.mock('uuid/v4');
describe('ListControl', () => {
const props = {
onChange: jest.fn(),
onChangeObject: jest.fn(),
onValidateObject: jest.fn(),
validate: jest.fn(),
mediaPaths: fromJS({}),
getAsset: jest.fn(),
onOpenMediaLibrary: jest.fn(),
onAddAsset: jest.fn(),
onRemoveInsertedMedia: jest.fn(),
classNameWrapper: 'classNameWrapper',
setActiveStyle: jest.fn(),
setInactiveStyle: jest.fn(),
editorControl: jest.fn(),
resolveWidget: jest.fn(),
clearFieldErrors: jest.fn(),
fieldsErrors: fromJS({}),
};
beforeEach(() => {
jest.clearAllMocks();
const uuid = require('uuid/v4');
let id = 0;
uuid.mockImplementation(() => {
return id++;
});
});
it('should render empty list', () => {
const field = fromJS({ name: 'list', label: 'List' });
const { asFragment } = render(<ListControl {...props} field={field} />);
expect(asFragment()).toMatchSnapshot();
});
it('should render list with string array', () => {
const field = fromJS({ name: 'list', label: 'List' });
const { asFragment } = render(
<ListControl {...props} field={field} value={fromJS(['item 1', 'item 2'])} />,
);
expect(asFragment()).toMatchSnapshot();
});
it('should render list with nested object', () => {
const field = fromJS({
name: 'list',
label: 'List',
field: {
name: 'object',
widget: 'object',
label: 'Object',
fields: [{ name: 'title', widget: 'string', label: 'Title' }],
},
});
const { asFragment, getByTestId } = render(
<ListControl
{...props}
field={field}
value={fromJS([{ object: { title: 'item 1' } }, { object: { title: 'item 2' } }])}
/>,
);
expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'true');
expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'true');
expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'true');
expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'true');
expect(asFragment()).toMatchSnapshot();
});
it('should render list with nested object with collapse = false', () => {
const field = fromJS({
name: 'list',
label: 'List',
collapsed: false,
field: {
name: 'object',
widget: 'object',
label: 'Object',
fields: [{ name: 'title', widget: 'string', label: 'Title' }],
},
});
const { asFragment, getByTestId } = render(
<ListControl
{...props}
field={field}
value={fromJS([{ object: { title: 'item 1' } }, { object: { title: 'item 2' } }])}
/>,
);
expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'false');
expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'false');
expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'false');
expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'false');
expect(asFragment()).toMatchSnapshot();
});
it('should collapse all items on top bar collapse click', () => {
const field = fromJS({
name: 'list',
label: 'List',
collapsed: false,
field: {
name: 'object',
widget: 'object',
label: 'Object',
fields: [{ name: 'title', widget: 'string', label: 'Title' }],
},
});
const { getByTestId } = render(
<ListControl
{...props}
field={field}
value={fromJS([{ object: { title: 'item 1' } }, { object: { title: 'item 2' } }])}
/>,
);
expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'false');
expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'false');
expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'false');
expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'false');
fireEvent.click(getByTestId('expand-button'));
expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'true');
expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'true');
expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'true');
expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'true');
});
it('should collapse a single item on collapse item click', () => {
const field = fromJS({
name: 'list',
label: 'List',
collapsed: false,
field: {
name: 'object',
widget: 'object',
label: 'Object',
fields: [{ name: 'title', widget: 'string', label: 'Title' }],
},
});
const { getByTestId } = render(
<ListControl
{...props}
field={field}
value={fromJS([{ object: { title: 'item 1' } }, { object: { title: 'item 2' } }])}
/>,
);
expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'false');
expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'false');
expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'false');
expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'false');
fireEvent.click(getByTestId('styled-list-item-top-bar-0'));
expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'true');
expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'false');
expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'true');
expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'false');
});
it('should expand all items on top bar expand click', () => {
const field = fromJS({
name: 'list',
label: 'List',
collapsed: true,
field: {
name: 'object',
widget: 'object',
label: 'Object',
fields: [{ name: 'title', widget: 'string', label: 'Title' }],
},
});
const { getByTestId } = render(
<ListControl
{...props}
field={field}
value={fromJS([{ object: { title: 'item 1' } }, { object: { title: 'item 2' } }])}
/>,
);
expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'true');
expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'true');
expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'true');
expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'true');
fireEvent.click(getByTestId('expand-button'));
expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'false');
expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'false');
expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'false');
expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'false');
});
it('should expand a single item on expand item click', () => {
const field = fromJS({
name: 'list',
label: 'List',
collapsed: true,
field: {
name: 'object',
widget: 'object',
label: 'Object',
fields: [{ name: 'title', widget: 'string', label: 'Title' }],
},
});
const { getByTestId } = render(
<ListControl
{...props}
field={field}
value={fromJS([{ object: { title: 'item 1' } }, { object: { title: 'item 2' } }])}
/>,
);
expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'true');
expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'true');
expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'true');
expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'true');
fireEvent.click(getByTestId('styled-list-item-top-bar-0'));
expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'false');
expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'true');
expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'false');
expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'true');
});
});

View File

@ -0,0 +1,516 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ListControl should render empty list 1`] = `
<DocumentFragment>
<input
class="classNameWrapper"
type="text"
value=""
/>
</DocumentFragment>
`;
exports[`ListControl should render list with nested object 1`] = `
<DocumentFragment>
.emotion-22 {
padding: 0 14px 14px;
}
.emotion-12 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
background-color: #dfdfe3;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
margin: 0 -14px;
padding: 13px;
}
.emotion-5 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
font-size: 14px;
font-weight: 500;
line-height: 1;
}
.emotion-3 {
border: 0;
border-radius: 5px;
cursor: pointer;
padding: 4px;
background-color: transparent;
color: inherit;
}
.emotion-3:last-of-type {
margin-right: 4px;
}
.emotion-1 {
display: inline-block;
line-height: 0;
width: 18px;
height: 18px;
-webkit-transform: rotate(-90deg);
-ms-transform: rotate(-90deg);
transform: rotate(-90deg);
}
.emotion-1 path:not(.no-fill),
.emotion-1 circle:not(.no-fill),
.emotion-1 polygon:not(.no-fill),
.emotion-1 rect:not(.no-fill) {
fill: currentColor;
}
.emotion-1 path.clipped {
fill: transparent;
}
.emotion-1 svg {
width: 100%;
height: 100%;
}
.emotion-10 {
border: 0;
border-radius: 5px;
cursor: pointer;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
padding: 2px 12px;
font-size: 12px;
font-weight: bold;
border-radius: 3px;
}
.emotion-10 .emotion-0 {
margin-left: 6px;
}
.emotion-8 {
display: inline-block;
line-height: 0;
width: 12px;
height: 12px;
-webkit-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}
.emotion-8 path:not(.no-fill),
.emotion-8 circle:not(.no-fill),
.emotion-8 polygon:not(.no-fill),
.emotion-8 rect:not(.no-fill) {
fill: currentColor;
}
.emotion-8 path.clipped {
fill: transparent;
}
.emotion-8 svg {
width: 100%;
height: 100%;
}
.emotion-16 {
margin-top: 18px;
padding-bottom: 0;
}
.emotion-16:first-of-type {
margin-top: 26px;
}
.emotion-14 {
display: block;
border-top: 0;
color: inherit;
background-color: #dfdfe3;
padding: 13px;
border-radius: 0 0 5px 5px;
}
<div
class="classNameWrapper emotion-22"
>
<div
class="emotion-12 emotion-13"
>
<div
class="emotion-5 emotion-6"
>
<button
class="emotion-3 emotion-4"
data-testid="expand-button"
>
<span
class="emotion-0 emotion-1 emotion-2"
>
<svg
viewBox="0 0 24 24"
>
<path
d="M5.123 6.33l-2.26 2.137 8.656 9.15 9.344-9.105-2.17-2.228-7.084 6.902z"
/>
</svg>
</span>
</button>
2 list
</div>
<button
class="emotion-10 emotion-11"
>
Add list
<span
class="emotion-0 emotion-8 emotion-2"
>
<svg
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 14h-4a1 1 0 0 0-1 1v4a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-4a1 1 0 0 0-1-1H5a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1h4a1 1 0 0 0 1-1V5a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v4a1 1 0 0 0 1 1h4a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1z"
/>
</svg>
</span>
</button>
</div>
<div>
<div
class="emotion-16 emotion-17"
>
<mock-list-item-top-bar
classname="css-1e1b0lr-StyledListItemTopBar e14bfka81"
collapsed="true"
data-testid="styled-list-item-top-bar-0"
/>
<div
class="emotion-14 emotion-15"
>
Object
</div>
<mock-object-control
classnamewrapper="classNameWrapper css-1pznrxi"
collapsed="true"
data-testid="object-control-0"
field="Map { \\"name\\": \\"list\\", \\"label\\": \\"List\\", \\"field\\": Map { \\"name\\": \\"object\\", \\"widget\\": \\"object\\", \\"label\\": \\"Object\\", \\"fields\\": List [ Map { \\"name\\": \\"title\\", \\"widget\\": \\"string\\", \\"label\\": \\"Title\\" } ] } }"
fieldserrors="Map {}"
forlist="true"
validationkey="0"
value="Map { \\"object\\": Map { \\"title\\": \\"item 1\\" } }"
/>
</div>
<div
class="emotion-16 emotion-17"
>
<mock-list-item-top-bar
classname="css-1e1b0lr-StyledListItemTopBar e14bfka81"
collapsed="true"
data-testid="styled-list-item-top-bar-1"
/>
<div
class="emotion-14 emotion-15"
>
Object
</div>
<mock-object-control
classnamewrapper="classNameWrapper css-1pznrxi"
collapsed="true"
data-testid="object-control-1"
field="Map { \\"name\\": \\"list\\", \\"label\\": \\"List\\", \\"field\\": Map { \\"name\\": \\"object\\", \\"widget\\": \\"object\\", \\"label\\": \\"Object\\", \\"fields\\": List [ Map { \\"name\\": \\"title\\", \\"widget\\": \\"string\\", \\"label\\": \\"Title\\" } ] } }"
fieldserrors="Map {}"
forlist="true"
validationkey="1"
value="Map { \\"object\\": Map { \\"title\\": \\"item 2\\" } }"
/>
</div>
</div>
</div>
</DocumentFragment>
`;
exports[`ListControl should render list with nested object with collapse = false 1`] = `
<DocumentFragment>
.emotion-22 {
padding: 0 14px 14px;
}
.emotion-12 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
background-color: #dfdfe3;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
margin: 0 -14px;
padding: 13px;
}
.emotion-5 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
font-size: 14px;
font-weight: 500;
line-height: 1;
}
.emotion-3 {
border: 0;
border-radius: 5px;
cursor: pointer;
padding: 4px;
background-color: transparent;
color: inherit;
}
.emotion-3:last-of-type {
margin-right: 4px;
}
.emotion-10 {
border: 0;
border-radius: 5px;
cursor: pointer;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
padding: 2px 12px;
font-size: 12px;
font-weight: bold;
border-radius: 3px;
}
.emotion-10 .emotion-0 {
margin-left: 6px;
}
.emotion-8 {
display: inline-block;
line-height: 0;
width: 12px;
height: 12px;
-webkit-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}
.emotion-8 path:not(.no-fill),
.emotion-8 circle:not(.no-fill),
.emotion-8 polygon:not(.no-fill),
.emotion-8 rect:not(.no-fill) {
fill: currentColor;
}
.emotion-8 path.clipped {
fill: transparent;
}
.emotion-8 svg {
width: 100%;
height: 100%;
}
.emotion-1 {
display: inline-block;
line-height: 0;
width: 18px;
height: 18px;
-webkit-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}
.emotion-1 path:not(.no-fill),
.emotion-1 circle:not(.no-fill),
.emotion-1 polygon:not(.no-fill),
.emotion-1 rect:not(.no-fill) {
fill: currentColor;
}
.emotion-1 path.clipped {
fill: transparent;
}
.emotion-1 svg {
width: 100%;
height: 100%;
}
.emotion-16 {
margin-top: 18px;
}
.emotion-16:first-of-type {
margin-top: 26px;
}
.emotion-14 {
display: none;
border-top: 0;
color: inherit;
background-color: #dfdfe3;
padding: 13px;
border-radius: 0 0 5px 5px;
}
<div
class="classNameWrapper emotion-22"
>
<div
class="emotion-12 emotion-13"
>
<div
class="emotion-5 emotion-6"
>
<button
class="emotion-3 emotion-4"
data-testid="expand-button"
>
<span
class="emotion-0 emotion-1 emotion-2"
>
<svg
viewBox="0 0 24 24"
>
<path
d="M5.123 6.33l-2.26 2.137 8.656 9.15 9.344-9.105-2.17-2.228-7.084 6.902z"
/>
</svg>
</span>
</button>
2 list
</div>
<button
class="emotion-10 emotion-11"
>
Add list
<span
class="emotion-0 emotion-8 emotion-2"
>
<svg
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 14h-4a1 1 0 0 0-1 1v4a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-4a1 1 0 0 0-1-1H5a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1h4a1 1 0 0 0 1-1V5a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v4a1 1 0 0 0 1 1h4a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1z"
/>
</svg>
</span>
</button>
</div>
<div>
<div
class="emotion-16 emotion-17"
>
<mock-list-item-top-bar
classname="css-1e1b0lr-StyledListItemTopBar e14bfka81"
collapsed="false"
data-testid="styled-list-item-top-bar-0"
/>
<div
class="emotion-14 emotion-15"
>
Object
</div>
<mock-object-control
classnamewrapper="classNameWrapper"
collapsed="false"
data-testid="object-control-0"
field="Map { \\"name\\": \\"list\\", \\"label\\": \\"List\\", \\"collapsed\\": false, \\"field\\": Map { \\"name\\": \\"object\\", \\"widget\\": \\"object\\", \\"label\\": \\"Object\\", \\"fields\\": List [ Map { \\"name\\": \\"title\\", \\"widget\\": \\"string\\", \\"label\\": \\"Title\\" } ] } }"
fieldserrors="Map {}"
forlist="true"
validationkey="0"
value="Map { \\"object\\": Map { \\"title\\": \\"item 1\\" } }"
/>
</div>
<div
class="emotion-16 emotion-17"
>
<mock-list-item-top-bar
classname="css-1e1b0lr-StyledListItemTopBar e14bfka81"
collapsed="false"
data-testid="styled-list-item-top-bar-1"
/>
<div
class="emotion-14 emotion-15"
>
Object
</div>
<mock-object-control
classnamewrapper="classNameWrapper"
collapsed="false"
data-testid="object-control-1"
field="Map { \\"name\\": \\"list\\", \\"label\\": \\"List\\", \\"collapsed\\": false, \\"field\\": Map { \\"name\\": \\"object\\", \\"widget\\": \\"object\\", \\"label\\": \\"Object\\", \\"fields\\": List [ Map { \\"name\\": \\"title\\", \\"widget\\": \\"string\\", \\"label\\": \\"Title\\" } ] } }"
fieldserrors="Map {}"
forlist="true"
validationkey="1"
value="Map { \\"object\\": Map { \\"title\\": \\"item 2\\" } }"
/>
</div>
</div>
</div>
</DocumentFragment>
`;
exports[`ListControl should render list with string array 1`] = `
<DocumentFragment>
<input
class="classNameWrapper"
type="text"
value="item 1, item 2"
/>
</DocumentFragment>
`;