Groups data formatting utils

This commit is contained in:
Virginie Ky 2019-06-13 18:24:03 +02:00
commit b595f64d3a
20 changed files with 389 additions and 314 deletions

View File

@ -9,8 +9,12 @@ describe('<Block />', () => {
}); });
it('should render his children', () => { it('should render his children', () => {
const Child = () => <div>I'm a child</div>; const Child = () => <div>I am a child</div>;
const wrapper = shallow(<Block><Child /></Block>); const wrapper = shallow(
<Block>
<Child />
</Block>
);
expect(wrapper.find(Child).exists()).toBe(true); expect(wrapper.find(Child).exists()).toBe(true);
}); });

View File

@ -19,11 +19,13 @@ function ButtonModalSuccess({ message, onClick, type }) {
} }
ButtonModalSuccess.defaultProps = { ButtonModalSuccess.defaultProps = {
onClick: () => {},
type: 'button', type: 'button',
}; };
ButtonModalSuccess.propTypes = { ButtonModalSuccess.propTypes = {
message: PropTypes.string.isRequired, message: PropTypes.string.isRequired,
onClick: PropTypes.func,
type: PropTypes.string, type: PropTypes.string,
}; };

View File

@ -23,6 +23,7 @@ const DocumentationSection = () => (
<a <a
href="http://strapi.io/documentation/3.0.0-beta.x/guides/models.html" href="http://strapi.io/documentation/3.0.0-beta.x/guides/models.html"
target="_blank" target="_blank"
rel="noopener noreferrer"
> >
{message} {message}
</a> </a>

View File

@ -9,8 +9,12 @@ describe('<Flex />', () => {
}); });
it('should render his children', () => { it('should render his children', () => {
const Child = () => <div>I'm a child</div>; const Child = () => <div>I am a child</div>;
const wrapper = shallow(<Flex><Child /></Flex>); const wrapper = shallow(
<Flex>
<Child />
</Flex>
);
expect(wrapper.find(Child).exists()).toBe(true); expect(wrapper.find(Child).exists()).toBe(true);
}); });

View File

@ -9,8 +9,12 @@ describe('<ListTitle />', () => {
}); });
it('should render his children', () => { it('should render his children', () => {
const Child = () => <div>I'm a child</div>; const Child = () => <div>I am a child</div>;
const wrapper = shallow(<ListTitle><Child /></ListTitle>); const wrapper = shallow(
<ListTitle>
<Child />
</ListTitle>
);
expect(wrapper.find(Child).exists()).toBe(true); expect(wrapper.find(Child).exists()).toBe(true);
}); });

View File

@ -9,8 +9,12 @@ describe('<Ul />', () => {
}); });
it('should render its children', () => { it('should render its children', () => {
const Child = () => <div>I'm a child</div>; const Child = () => <div>I am a child</div>;
const wrapper = shallow(<Ul><Child /></Ul>); const wrapper = shallow(
<Ul>
<Child />
</Ul>
);
expect(wrapper.find(Child).exists()).toBe(true); expect(wrapper.find(Child).exists()).toBe(true);
}); });

View File

@ -170,6 +170,7 @@ export function getDataSucceeded({ allModels, models }, connections, { data }) {
return acc; return acc;
}, {}); }, {});
<<<<<<< HEAD
const initialDataGroup = data.reduce((acc, current, i) => { const initialDataGroup = data.reduce((acc, current, i) => {
const { const {
@ -187,6 +188,9 @@ export function getDataSucceeded({ allModels, models }, connections, { data }) {
}, {}); }, {});
const groups = data.reduce((acc, current, i) => { const groups = data.reduce((acc, current, i) => {
=======
const groups = data.reduce((acc, current) => {
>>>>>>> 28c9339ace187ee1b88de0002851d3598f8e9def
const { const {
name, name,
schema: { attributes, description }, schema: { attributes, description },

View File

@ -9,7 +9,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { bindActionCreators, compose } from 'redux'; import { bindActionCreators, compose } from 'redux';
import { Switch, Route } from 'react-router-dom'; import { Switch, Route } from 'react-router-dom';
import { get, isEmpty } from 'lodash'; import { get } from 'lodash';
import { NotFound, getQueryParameters } from 'strapi-helper-plugin'; import { NotFound, getQueryParameters } from 'strapi-helper-plugin';
@ -184,7 +184,6 @@ export class App extends React.Component {
location: { pathname, search }, location: { pathname, search },
isLoading, isLoading,
models, models,
newContentType,
newGroup, newGroup,
onChangeExistingContentTypeMainInfos, onChangeExistingContentTypeMainInfos,
onChangeNewContentTypeMainInfos, onChangeNewContentTypeMainInfos,
@ -271,12 +270,19 @@ App.defaultProps = {
App.propTypes = { App.propTypes = {
addAttributeRelation: PropTypes.func.isRequired, addAttributeRelation: PropTypes.func.isRequired,
cancelNewContentType: PropTypes.func.isRequired, cancelNewContentType: PropTypes.func.isRequired,
connections: PropTypes.array.isRequired,
createTempContentType: PropTypes.func.isRequired,
createTempGroup: PropTypes.func.isRequired,
deleteModel: PropTypes.func.isRequired, deleteModel: PropTypes.func.isRequired,
getData: PropTypes.func.isRequired, getData: PropTypes.func.isRequired,
isLoading: PropTypes.bool.isRequired,
groups: PropTypes.array.isRequired, groups: PropTypes.array.isRequired,
history: PropTypes.object.isRequired, history: PropTypes.object.isRequired,
isLoading: PropTypes.bool.isRequired,
location: PropTypes.object.isRequired,
models: PropTypes.array.isRequired, models: PropTypes.array.isRequired,
modifiedData: PropTypes.object.isRequired,
newContentType: PropTypes.object.isRequired,
newGroup: PropTypes.object.isRequired,
onChangeExistingContentTypeMainInfos: PropTypes.func.isRequired, onChangeExistingContentTypeMainInfos: PropTypes.func.isRequired,
onChangeNewContentTypeMainInfos: PropTypes.func.isRequired, onChangeNewContentTypeMainInfos: PropTypes.func.isRequired,
onChangeNewGroupMainInfos: PropTypes.func.isRequired, onChangeNewGroupMainInfos: PropTypes.func.isRequired,
@ -285,7 +291,10 @@ App.propTypes = {
saveEditedAttributeRelation: PropTypes.func.isRequired, saveEditedAttributeRelation: PropTypes.func.isRequired,
setTemporaryAttribute: PropTypes.func.isRequired, setTemporaryAttribute: PropTypes.func.isRequired,
setTemporaryAttributeRelation: PropTypes.func.isRequired, setTemporaryAttributeRelation: PropTypes.func.isRequired,
resetExistingContentTypeMainInfos: PropTypes.func.isRequired,
resetNewContentTypeMainInfos: PropTypes.func.isRequired,
shouldRefetchData: PropTypes.bool, shouldRefetchData: PropTypes.bool,
updateTempContentType: PropTypes.func.isRequired,
}; };
const mapStateToProps = makeSelectApp(); const mapStateToProps = makeSelectApp();

View File

@ -13,6 +13,9 @@ describe('<App />', () => {
props = { props = {
addAttributeRelation: jest.fn(), addAttributeRelation: jest.fn(),
cancelNewContentType: jest.fn(), cancelNewContentType: jest.fn(),
connections: [],
createTempContentType: jest.fn(),
createTempGroup: jest.fn(),
deleteModel: jest.fn(), deleteModel: jest.fn(),
history: { history: {
push: jest.fn(), push: jest.fn(),
@ -76,6 +79,8 @@ describe('<App />', () => {
}, },
], ],
modifiedData: {}, modifiedData: {},
newContentType: {},
newGroup: {},
onChangeExistingContentTypeMainInfos: jest.fn(), onChangeExistingContentTypeMainInfos: jest.fn(),
onChangeNewContentTypeMainInfos: jest.fn(), onChangeNewContentTypeMainInfos: jest.fn(),
onChangeNewGroupMainInfos: jest.fn(), onChangeNewGroupMainInfos: jest.fn(),
@ -84,6 +89,9 @@ describe('<App />', () => {
setTemporaryAttribute: jest.fn(), setTemporaryAttribute: jest.fn(),
setTemporaryAttributeRelation: jest.fn(), setTemporaryAttributeRelation: jest.fn(),
resetProps: jest.fn(), resetProps: jest.fn(),
resetExistingContentTypeMainInfos: jest.fn(),
resetNewContentTypeMainInfos: jest.fn(),
updateTempContentType: jest.fn(),
}; };
}); });

View File

@ -294,6 +294,7 @@ AttributeForm.defaultProps = {
modifiedData: {}, modifiedData: {},
onCancel: () => {}, onCancel: () => {},
onChange: () => {}, onChange: () => {},
onSubmit: () => {},
push: () => {}, push: () => {},
}; };
@ -307,7 +308,7 @@ AttributeForm.propTypes = {
modifiedData: PropTypes.object, // TODO: Clearly define this object (It's working without it though) modifiedData: PropTypes.object, // TODO: Clearly define this object (It's working without it though)
onCancel: PropTypes.func, onCancel: PropTypes.func,
onChange: PropTypes.func, onChange: PropTypes.func,
onChange: PropTypes.func, onSubmit: PropTypes.func,
onSubmitEdit: PropTypes.func.isRequired, onSubmitEdit: PropTypes.func.isRequired,
push: PropTypes.func, push: PropTypes.func,
}; };

View File

@ -6,21 +6,17 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { isEmpty } from 'lodash';
import { import {
HeaderNav, HeaderNav,
ListWrapper, ListWrapper,
ListHeader, ListHeader,
List, List,
PluginHeader, PluginHeader,
getQueryParameters,
routerPropTypes, routerPropTypes,
} from 'strapi-helper-plugin'; } from 'strapi-helper-plugin';
import EmptyContentTypeView from '../../components/EmptyContentTypeView'; import EmptyContentTypeView from '../../components/EmptyContentTypeView';
import pluginId from '../../pluginId'; import pluginId from '../../pluginId';
import ModelForm from '../ModelForm';
import Row from './Row'; import Row from './Row';
import styles from './styles.scss'; import styles from './styles.scss';
@ -77,7 +73,6 @@ class HomePage extends React.Component {
handleGoTo = (to, source, shouldEdit = false) => { handleGoTo = (to, source, shouldEdit = false) => {
const { const {
canOpenModal,
history: { push }, history: { push },
match: { match: {
params: { type }, params: { type },
@ -97,25 +92,16 @@ class HomePage extends React.Component {
render() { render() {
const { const {
allGroupsAndModelsName,
cancelNewContentType,
canOpenModal, canOpenModal,
connections,
createTempContentType,
deleteGroup, deleteGroup,
deleteModel, deleteModel,
deleteTemporaryGroup, deleteTemporaryGroup,
deleteTemporaryModel, deleteTemporaryModel,
groups, groups,
history: { push },
location: { pathname, search },
match: { match: {
params: { type }, params: { type },
}, },
models, models,
modifiedData,
newContentType,
onChangeNewContentTypeMainInfos,
} = this.props; } = this.props;
const displayedData = type === 'groups' ? groups : models; const displayedData = type === 'groups' ? groups : models;
const availableNumber = type === 'groups' ? groups.length : models.length; const availableNumber = type === 'groups' ? groups.length : models.length;
@ -175,22 +161,6 @@ class HomePage extends React.Component {
</List> </List>
</ListWrapper> </ListWrapper>
)} )}
{/* <ModelForm
actionType="create"
activeTab={getQueryParameters(search, 'settingType')}
allTakenNames={allGroupsAndModelsName}
cancelNewContentType={cancelNewContentType}
connections={connections}
createTempContentType={createTempContentType}
currentData={modifiedData}
featureType={type}
modifiedData={newContentType}
onChangeNewContentTypeMainInfos={onChangeNewContentTypeMainInfos}
isOpen={!isEmpty(search)}
pathname={pathname}
push={push}
/> */}
</div> </div>
); );
} }

View File

@ -2,10 +2,8 @@ import React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import pluginId from '../../../pluginId'; import pluginId from '../../../pluginId';
import { ListHeader } from 'strapi-helper-plugin';
import EmptyContentTypeView from '../../../components/EmptyContentTypeView'; import EmptyContentTypeView from '../../../components/EmptyContentTypeView';
import TableList from '../../../components/TableList';
import ModelForm from '../../ModelForm';
import HomePage from '../index'; import HomePage from '../index';
@ -71,7 +69,7 @@ describe('CTB <HomePage />', () => {
}, },
location: { location: {
search: '', search: '',
pathname: `/plugins/${pluginId}`, pathname: `/plugins/${pluginId}/models`,
}, },
}; };
}); });
@ -82,93 +80,171 @@ describe('CTB <HomePage />', () => {
shallow(<HomePage {...props} />, { context }); shallow(<HomePage {...props} />, { context });
}); });
// describe('render', () => { describe('render', () => {
// it('should display the EmptyContentTypeView if there is no model in the application', () => { it('should display the EmptyContentTypeView if there is no model in the application', () => {
// props.models = []; props.models = [];
// const context = { emitEvent: jest.fn() }; const context = { emitEvent: jest.fn() };
// const wrapper = shallow(<HomePage {...props} />, { context }); const wrapper = shallow(<HomePage {...props} />, { context });
// const emptyView = wrapper.find(EmptyContentTypeView); const emptyView = wrapper.find(EmptyContentTypeView);
// expect(emptyView).toHaveLength(1); expect(emptyView).toHaveLength(1);
// }); });
// it('the tableList should have a plural title if there is more than 1 model', () => { it('should display the EmptyContentTypeView if there is no model in the application', () => {
// const context = { emitEvent: jest.fn() }; props.match.params.type = 'groups';
// const wrapper = shallow(<HomePage {...props} />, { context });
// const table = wrapper.find(TableList);
// expect(table).toHaveLength(1); const context = { emitEvent: jest.fn() };
// expect(table.prop('title')).toEqual( const wrapper = shallow(<HomePage {...props} />, { context });
// `${pluginId}.table.contentType.title.plural`, const emptyView = wrapper.find(EmptyContentTypeView);
// );
// });
// it('the tableList should have a singular title if there is more less 2 model', () => { expect(emptyView).toHaveLength(1);
// props.models = [ });
// {
// icon: 'fa-cube',
// name: 'permission',
// description: '',
// fields: 6,
// source: 'users-permissions',
// isTemporary: false,
// },
// ];
// const context = { emitEvent: jest.fn() }; it('Should handle the listheader title correctly if there is more than 1 model', () => {
// const wrapper = shallow(<HomePage {...props} />, { context }); const context = { emitEvent: jest.fn() };
// const table = wrapper.find(TableList); const wrapper = shallow(<HomePage {...props} />, { context });
const list = wrapper.find(ListHeader);
// expect(table).toHaveLength(1); expect(list).toHaveLength(1);
// expect(table.prop('title')).toEqual( expect(list.prop('title')).toBe(
// `${pluginId}.table.contentType.title.singular`, `${pluginId}.table.contentType.title.plural`
// ); );
// }); });
// });
// describe('workflow', () => { it('Should handle the listheader title correctly if there is more than 1 group', () => {
// it('should open the modelForm if there is no saved content type', () => { props.groups = props.models;
// props.canOpenModal = true; props.match.params.type = 'groups';
// props.history.push = jest.fn(({ search }) => { const context = { emitEvent: jest.fn() };
// props.location.search = `?${search}`; const wrapper = shallow(<HomePage {...props} />, { context });
// }); const list = wrapper.find(ListHeader);
// const context = { emitEvent: jest.fn() };
// const wrapper = shallow(<HomePage {...props} />, { context });
// const spyOnClick = jest.spyOn(wrapper.instance(), 'handleClick');
// wrapper.instance().forceUpdate(); expect(list).toHaveLength(1);
// // Simulate the click on button expect(list.prop('title')).toBe(`${pluginId}.table.groups.title.plural`);
// wrapper.find(TableList).prop('onButtonClick')(); });
// wrapper.instance().forceUpdate();
// const form = wrapper.find(ModelForm).first(); it('Should handle the listheader title correctly if there is less than 2 groups', () => {
props.groups = [
{
icon: 'fa-cube',
name: 'user',
description: '',
fields: 6,
source: 'users-permissions',
isTemporary: false,
},
];
props.match.params.type = 'groups';
const context = { emitEvent: jest.fn() };
const wrapper = shallow(<HomePage {...props} />, { context });
const list = wrapper.find(ListHeader);
// expect(spyOnClick).toHaveBeenCalled(); expect(list).toHaveLength(1);
// expect(context.emitEvent).toHaveBeenCalledWith('willCreateContentType'); expect(list.prop('title')).toBe(
// expect(props.history.push).toHaveBeenCalledWith({ `${pluginId}.table.groups.title.singular`
// search: 'modalType=model&settingType=base&actionType=create', );
// }); });
// expect(form.prop('isOpen')).toBe(true);
// });
// it('should not open the modal if the is one or more not saved content type and display a notification', () => { it('Should handle the listheader title correctly if there is less than 2 models', () => {
// props.canOpenModal = false; props.models = [
// const context = { emitEvent: jest.fn() }; {
// const wrapper = shallow(<HomePage {...props} />, { context }); icon: 'fa-cube',
name: 'user',
description: '',
fields: 6,
source: 'users-permissions',
isTemporary: false,
},
];
const context = { emitEvent: jest.fn() };
const wrapper = shallow(<HomePage {...props} />, { context });
const list = wrapper.find(ListHeader);
// wrapper.find(TableList).prop('onButtonClick')(); expect(list).toHaveLength(1);
// wrapper.instance().forceUpdate(); expect(list.prop('title')).toBe(
`${pluginId}.table.contentType.title.singular`
);
});
});
// const form = wrapper.find(ModelForm).first(); describe('workflow', () => {
it('should open the modelForm for the model if there is no saved content type', () => {
props.canOpenModal = true;
props.history.push = jest.fn(({ search }) => {
props.location.search = `?${search}`;
});
// expect(context.emitEvent).not.toHaveBeenCalled(); const context = { emitEvent: jest.fn() };
// expect(props.history.push).not.toHaveBeenCalled(); const wrapper = shallow(<HomePage {...props} />, { context });
// expect(strapi.notification.info).toHaveBeenCalled(); const spyOnClick = jest.spyOn(wrapper.instance(), 'handleClick');
// expect(strapi.notification.info).toHaveBeenCalledWith(
// `${pluginId}.notification.info.contentType.creating.notSaved`, wrapper.instance().forceUpdate();
// ); // Simulate the click on button
// expect(form.prop('isOpen')).toBe(false); wrapper
// }); .find(ListHeader)
// }); .prop('button')
.onClick();
wrapper.instance().forceUpdate();
expect(spyOnClick).toHaveBeenCalled();
expect(context.emitEvent).toHaveBeenCalledWith('willCreateContentType');
expect(props.history.push).toHaveBeenCalledWith({
search: 'modalType=model&settingType=base&actionType=create',
});
});
it('should open the modelForm for groups if there is no is no saved content type', () => {
props.canOpenModal = true;
props.groups = [
{
icon: 'fa-cube',
name: 'user',
description: '',
fields: 6,
source: 'users-permissions',
isTemporary: false,
},
];
props.location.pathname = `/plugins/${pluginId}/groups`;
props.history.push = jest.fn(({ search }) => {
props.location.search = `?${search}`;
});
const context = { emitEvent: jest.fn() };
const wrapper = shallow(<HomePage {...props} />, { context });
const spyOnClick = jest.spyOn(wrapper.instance(), 'handleClick');
wrapper.instance().forceUpdate();
// Simulate the click on button
wrapper
.find(ListHeader)
.prop('button')
.onClick();
wrapper.instance().forceUpdate();
expect(spyOnClick).toHaveBeenCalled();
expect(context.emitEvent).toHaveBeenCalledWith('willCreateContentType');
expect(props.history.push).toHaveBeenCalledWith({
search: 'modalType=model&settingType=base&actionType=create',
});
});
it('should not open the modal if there is one not saved content type and display a notification', () => {
props.canOpenModal = false;
const context = { emitEvent: jest.fn() };
const wrapper = shallow(<HomePage {...props} />, { context });
wrapper
.find(ListHeader)
.prop('button')
.onClick();
wrapper.instance().forceUpdate();
expect(context.emitEvent).not.toHaveBeenCalled();
expect(props.history.push).not.toHaveBeenCalled();
expect(strapi.notification.info).toHaveBeenCalled();
expect(strapi.notification.info).toHaveBeenCalledWith(
`${pluginId}.notification.info.work.notSaved`
);
});
});
}); });

View File

@ -159,7 +159,11 @@ class ModelForm extends React.Component {
link: ( link: (
<FormattedMessage id={input.inputDescriptionParams.id}> <FormattedMessage id={input.inputDescriptionParams.id}>
{msg => ( {msg => (
<a href={input.inputDescriptionParams.href} target="_blank"> <a
href={input.inputDescriptionParams.href}
target="_blank"
rel="noopener noreferrer"
>
{msg} {msg}
</a> </a>
)} )}

View File

@ -32,7 +32,6 @@ import Ul from '../../components/Ul';
import AttributeForm from '../AttributeForm'; import AttributeForm from '../AttributeForm';
import AttributesModalPicker from '../AttributesPickerModal'; import AttributesModalPicker from '../AttributesPickerModal';
import ModelForm from '../ModelForm';
import RelationForm from '../RelationForm'; import RelationForm from '../RelationForm';
import LeftMenu from '../LeftMenu'; import LeftMenu from '../LeftMenu';
@ -101,18 +100,18 @@ export class ModelPage extends React.Component {
getAttributeType = () => getAttributeType = () =>
getQueryParameters(this.getSearch(), 'attributeType'); getQueryParameters(this.getSearch(), 'attributeType');
getFormData = () => { // getFormData = () => {
const { modifiedData, newContentType } = this.props; // const { modifiedData, newContentType } = this.props;
if ( // if (
this.getActionType() === 'create' || // this.getActionType() === 'create' ||
this.isUpdatingTemporaryContentType() // this.isUpdatingTemporaryContentType()
) { // ) {
return newContentType; // return newContentType;
} // }
return get(modifiedData, this.getModelName()); // return get(modifiedData, this.getModelName());
}; // };
getModalType = () => getQueryParameters(this.getSearch(), 'modalType'); getModalType = () => getQueryParameters(this.getSearch(), 'modalType');
@ -503,27 +502,18 @@ export class ModelPage extends React.Component {
render() { render() {
const listTitleMessageIdBasePrefix = `${pluginId}.modelPage.contentType.list.title`; const listTitleMessageIdBasePrefix = `${pluginId}.modelPage.contentType.list.title`;
const { const {
cancelNewContentType,
connections,
clearTemporaryAttribute, clearTemporaryAttribute,
clearTemporaryAttributeRelation, clearTemporaryAttributeRelation,
createTempContentType,
history: { push }, history: { push },
location: { pathname, search }, location: { search },
models, models,
modifiedData,
onChangeAttribute, onChangeAttribute,
onChangeExistingContentTypeMainInfos,
onChangeNewContentTypeMainInfos,
onChangeRelation, onChangeRelation,
onChangeRelationNature, onChangeRelationNature,
onChangeRelationTarget, onChangeRelationTarget,
resetExistingContentTypeMainInfos,
resetNewContentTypeMainInfos,
setTemporaryAttributeRelation, setTemporaryAttributeRelation,
temporaryAttribute, temporaryAttribute,
temporaryAttributeRelation, temporaryAttributeRelation,
updateTempContentType,
} = this.props; } = this.props;
const { showWarning, removePrompt } = this.state; const { showWarning, removePrompt } = this.state;
@ -641,28 +631,6 @@ export class ModelPage extends React.Component {
onSubmitEdit={this.handleSubmitEdit} onSubmitEdit={this.handleSubmitEdit}
push={push} push={push}
/> />
{/* <ModelForm
actionType={actionType}
activeTab={settingType}
allTakenNames={allGroupsAndModelsName}
cancelNewContentType={cancelNewContentType}
connections={connections}
createTempContentType={createTempContentType}
currentData={modifiedData}
modifiedData={this.getFormData()}
modelToEditName={getQueryParameters(search, 'modelName')}
onChangeExistingContentTypeMainInfos={
onChangeExistingContentTypeMainInfos
}
onChangeNewContentTypeMainInfos={onChangeNewContentTypeMainInfos}
isOpen={modalType === 'model'}
isUpdatingTemporaryContentType={this.isUpdatingTemporaryContentType()}
pathname={pathname}
push={push}
resetExistingContentTypeMainInfos={resetExistingContentTypeMainInfos}
resetNewContentTypeMainInfos={resetNewContentTypeMainInfos}
updateTempContentType={updateTempContentType}
/> */}
<PopUpWarning <PopUpWarning
isOpen={showWarning} isOpen={showWarning}
toggleModal={this.toggleModalWarning} toggleModal={this.toggleModalWarning}
@ -725,15 +693,11 @@ ModelPage.propTypes = {
modifiedData: PropTypes.object.isRequired, modifiedData: PropTypes.object.isRequired,
newContentType: PropTypes.object.isRequired, newContentType: PropTypes.object.isRequired,
onChangeAttribute: PropTypes.func.isRequired, onChangeAttribute: PropTypes.func.isRequired,
onChangeExistingContentTypeMainInfos: PropTypes.func.isRequired,
onChangeNewContentTypeMainInfos: PropTypes.func.isRequired,
onChangeRelation: PropTypes.func.isRequired, onChangeRelation: PropTypes.func.isRequired,
onChangeRelationNature: PropTypes.func.isRequired, onChangeRelationNature: PropTypes.func.isRequired,
onChangeRelationTarget: PropTypes.func.isRequired, onChangeRelationTarget: PropTypes.func.isRequired,
resetEditExistingContentType: PropTypes.func.isRequired, resetEditExistingContentType: PropTypes.func.isRequired,
resetEditTempContentType: PropTypes.func.isRequired, resetEditTempContentType: PropTypes.func.isRequired,
resetExistingContentTypeMainInfos: PropTypes.func.isRequired,
resetNewContentTypeMainInfos: PropTypes.func.isRequired,
saveEditedAttribute: PropTypes.func.isRequired, saveEditedAttribute: PropTypes.func.isRequired,
saveEditedAttributeRelation: PropTypes.func.isRequired, saveEditedAttributeRelation: PropTypes.func.isRequired,
setTemporaryAttribute: PropTypes.func.isRequired, setTemporaryAttribute: PropTypes.func.isRequired,
@ -742,7 +706,6 @@ ModelPage.propTypes = {
submitTempContentType: PropTypes.func.isRequired, submitTempContentType: PropTypes.func.isRequired,
temporaryAttribute: PropTypes.object.isRequired, temporaryAttribute: PropTypes.object.isRequired,
temporaryAttributeRelation: PropTypes.object.isRequired, temporaryAttributeRelation: PropTypes.object.isRequired,
updateTempContentType: PropTypes.func.isRequired,
...routerPropTypes({ params: PropTypes.string }).isRequired, ...routerPropTypes({ params: PropTypes.string }).isRequired,
}; };

View File

@ -126,16 +126,13 @@ describe('<ModelPage />', () => {
name: '', name: '',
attributes: {}, attributes: {},
}, },
onChangeExistingContentTypeMainInfos: jest.fn(),
onChangeNewContentTypeMainInfos: jest.fn(),
onChangeAttribute: jest.fn(), onChangeAttribute: jest.fn(),
onChangeRelation: jest.fn(), onChangeRelation: jest.fn(),
onChangeRelationNature: jest.fn(), onChangeRelationNature: jest.fn(),
onChangeRelationTarget: jest.fn(), onChangeRelationTarget: jest.fn(),
resetEditExistingContentType: jest.fn(), resetEditExistingContentType: jest.fn(),
resetEditTempContentType: jest.fn(), resetEditTempContentType: jest.fn(),
resetExistingContentTypeMainInfos: jest.fn(),
resetNewContentTypeMainInfos: jest.fn(),
saveEditedAttribute: jest.fn(), saveEditedAttribute: jest.fn(),
saveEditedAttributeRelation: jest.fn(), saveEditedAttributeRelation: jest.fn(),
setTemporaryAttribute: jest.fn(), setTemporaryAttribute: jest.fn(),
@ -154,7 +151,6 @@ describe('<ModelPage />', () => {
target: '', target: '',
unique: false, unique: false,
}, },
updateTempContentType: jest.fn(),
}; };
}); });
@ -427,16 +423,12 @@ describe('<ModelPage /> lifecycle', () => {
name: '', name: '',
attributes: {}, attributes: {},
}, },
onChangeExistingContentTypeMainInfos: jest.fn(),
onChangeNewContentTypeMainInfos: jest.fn(),
onChangeAttribute: jest.fn(), onChangeAttribute: jest.fn(),
onChangeRelation: jest.fn(), onChangeRelation: jest.fn(),
onChangeRelationNature: jest.fn(), onChangeRelationNature: jest.fn(),
onChangeRelationTarget: jest.fn(), onChangeRelationTarget: jest.fn(),
resetEditExistingContentType: jest.fn(), resetEditExistingContentType: jest.fn(),
resetEditTempContentType: jest.fn(), resetEditTempContentType: jest.fn(),
resetExistingContentTypeMainInfos: jest.fn(),
resetNewContentTypeMainInfos: jest.fn(),
saveEditedAttribute: jest.fn(), saveEditedAttribute: jest.fn(),
saveEditedAttributeRelation: jest.fn(), saveEditedAttributeRelation: jest.fn(),
setTemporaryAttribute: jest.fn(), setTemporaryAttribute: jest.fn(),
@ -455,7 +447,6 @@ describe('<ModelPage /> lifecycle', () => {
target: '', target: '',
unique: false, unique: false,
}, },
updateTempContentType: jest.fn(),
}; };
}); });

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { camelCase, truncate } from 'lodash'; import { truncate } from 'lodash';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import pluralize from 'pluralize'; import pluralize from 'pluralize';
@ -57,14 +57,14 @@ const NaturePicker = ({ modelName, onClick, nature, target }) => {
leftName: pluralize(modelName, nature === 'manyToMany' ? 2 : 1), leftName: pluralize(modelName, nature === 'manyToMany' ? 2 : 1),
rightName: pluralize( rightName: pluralize(
target, target,
['manyToMany', 'oneToMany', 'manyToOne'].includes(nature) ? 2 : 1, ['manyToMany', 'oneToMany', 'manyToOne'].includes(nature) ? 2 : 1
), ),
} }
: { : {
leftName: target, leftName: target,
rightName: pluralize( rightName: pluralize(
modelName, modelName,
['manyToMany', 'oneToMany', 'manyToOne'].includes(nature) ? 2 : 1, ['manyToMany', 'oneToMany', 'manyToOne'].includes(nature) ? 2 : 1
), ),
}; };

View File

@ -9,11 +9,11 @@ describe('<InlineBlock />', () => {
}); });
it('should render his children', () => { it('should render his children', () => {
const Child = () => <div>I'm a child</div>; const Child = () => <div>I am a child</div>;
const wrapper = shallow( const wrapper = shallow(
<InlineBlock> <InlineBlock>
<Child /> <Child />
</InlineBlock>, </InlineBlock>
); );
expect(wrapper.find(Child).exists()).toBe(true); expect(wrapper.find(Child).exists()).toBe(true);

View File

@ -62,7 +62,7 @@ function* submit() {
return yield put( return yield put(
setFormErrors({ setFormErrors({
password: [{ id: 'components.Input.error.validation.required' }], password: [{ id: 'components.Input.error.validation.required' }],
}), })
); );
} }
@ -95,12 +95,16 @@ function* updateDoc(action) {
// Individual exports for testing // Individual exports for testing
export function* defaultSaga() { export function* defaultSaga() {
yield all([ try {
fork(takeLatest, GET_DOC_INFOS, getData), yield all([
fork(takeLatest, ON_CONFIRM_DELETE_DOC, deleteDoc), fork(takeLatest, GET_DOC_INFOS, getData),
fork(takeLatest, ON_SUBMIT, submit), fork(takeLatest, ON_CONFIRM_DELETE_DOC, deleteDoc),
fork(takeLatest, ON_UPDATE_DOC, updateDoc), fork(takeLatest, ON_SUBMIT, submit),
]); fork(takeLatest, ON_UPDATE_DOC, updateDoc),
]);
} catch (err) {
// Do nothing
}
} }
// All sagas to be loaded // All sagas to be loaded

View File

@ -1,12 +1,22 @@
/** /**
* *
* WithFormSection * WithFormSection
* *
*/ */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { findIndex, forEach, has, isObject , join, pullAt, split, includes} from 'lodash'; import {
findIndex,
forEach,
has,
isObject,
join,
pullAt,
split,
includes,
} from 'lodash';
/* eslint-disable */
import InputNumber from '../InputNumber'; import InputNumber from '../InputNumber';
import InputText from '../InputText'; import InputText from '../InputText';
@ -18,128 +28,146 @@ import config from './config.json';
import styles from './styles.scss'; import styles from './styles.scss';
/* eslint-disable react/require-default-props */ /* eslint-disable react/require-default-props */
const WithFormSection = (InnerComponent) => class extends React.Component { const WithFormSection = InnerComponent =>
static propTypes = { class extends React.Component {
addRequiredInputDesign: PropTypes.bool, static propTypes = {
cancelAction: PropTypes.bool, addRequiredInputDesign: PropTypes.bool,
formErrors: PropTypes.array, cancelAction: PropTypes.bool,
onChange: PropTypes.func, formErrors: PropTypes.array,
section: PropTypes.oneOfType([ onChange: PropTypes.func,
PropTypes.object, section: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
PropTypes.array, values: PropTypes.object,
]),
values: PropTypes.object,
}
constructor(props) {
super(props);
this.state = {
hasNestedInput: false,
showNestedForm: false,
inputWithNestedForm: '',
}; };
this.inputs = { constructor(props) {
string: InputText, super(props);
password: InputPassword, this.state = {
number: InputNumber, hasNestedInput: false,
boolean: InputToggle, showNestedForm: false,
enum: InputEnum, inputWithNestedForm: '',
select: InputSelect, };
};
}
componentDidMount() { this.inputs = {
// check if there is inside a section an input that requires nested input to display it on the entire line string: InputText,
if (isObject(this.props.section)) { password: InputPassword,
this.checkForNestedForm(this.props); number: InputNumber,
boolean: InputToggle,
enum: InputEnum,
select: InputSelect,
};
} }
}
componentWillReceiveProps(nextProps) { componentDidMount() {
if (nextProps.section !== this.props.section || nextProps.cancelAction !== this.props.cancelAction) { // check if there is inside a section an input that requires nested input to display it on the entire line
this.setState({ showNestedForm: false, hasNestedInput: false, inputWithNestedForm: '' }); if (isObject(this.props.section)) {
if (isObject(nextProps.section)) { this.checkForNestedForm(this.props);
this.checkForNestedForm(nextProps);
} }
} }
}
checkForNestedForm(props) { componentWillReceiveProps(nextProps) {
forEach(props.section.items, (input) => { if (
if(has(input, 'items')) { nextProps.section !== this.props.section ||
this.setState({ hasNestedInput: true, inputWithNestedForm: input.target }); nextProps.cancelAction !== this.props.cancelAction
) {
if (props.values[input.target]) { this.setState({
this.setState({ showNestedForm: true }); showNestedForm: false,
hasNestedInput: false,
inputWithNestedForm: '',
});
if (isObject(nextProps.section)) {
this.checkForNestedForm(nextProps);
} }
} }
});
}
handleChange = ({ target }) => {
// display nestedForm if the selected input has a nested form
if (target.name === this.state.inputWithNestedForm) {
this.setState({ showNestedForm: target.value });
} }
this.props.onChange({ target }); checkForNestedForm(props) {
} forEach(props.section.items, input => {
if (has(input, 'items')) {
this.setState({
hasNestedInput: true,
inputWithNestedForm: input.target,
});
renderInput = (props, key) => { if (props.values[input.target]) {
const Input = this.inputs[props.type]; this.setState({ showNestedForm: true });
const inputValue = this.props.values[props.target]; }
// retrieve options for the select input }
const selectOptions = props.type === 'enum' || props.type === 'select' ? props.items : []; });
}
// custom check for dynamic keys used for databases handleChange = ({ target }) => {
const dynamicTarget = join(pullAt(split(props.target, '.'),['0', '1', '3', '4']), '.'); // display nestedForm if the selected input has a nested form
if (target.name === this.state.inputWithNestedForm) {
this.setState({ showNestedForm: target.value });
}
// check if the input has a nested form so it is displayed on the entire line this.props.onChange({ target });
const customBootstrapClass = this.state.hasNestedInput ? };
// bootstrap class to make the input displayed on the entire line
'col-md-6 offset-md-6 mr-md-5' :
// if the input hasn't a nested form but the config requires him to be displayed differently
config[props.target] || config[dynamicTarget] || '';
// custom handleChange props for nested input form renderInput = (props, key) => {
const handleChange = this.state.hasNestedInput ? this.handleChange : this.props.onChange; const Input = this.inputs[props.type];
let hiddenLabel = includes(props.name, 'enabled'); const inputValue = this.props.values[props.target];
// retrieve options for the select input
const selectOptions =
props.type === 'enum' || props.type === 'select' ? props.items : [];
if (includes(config.showInputLabel, props.name)) hiddenLabel = false; // custom check for dynamic keys used for databases
const dynamicTarget = join(
pullAt(split(props.target, '.'), ['0', '1', '3', '4']),
'.'
);
const errorIndex = findIndex(this.props.formErrors, ['target', props.target]); // check if the input has a nested form so it is displayed on the entire line
const errors = errorIndex !== -1 ? this.props.formErrors[errorIndex].errors : []; const customBootstrapClass = this.state.hasNestedInput
? // bootstrap class to make the input displayed on the entire line
'col-md-6 offset-md-6 mr-md-5'
: // if the input hasn't a nested form but the config requires him to be displayed differently
config[props.target] || config[dynamicTarget] || '';
return ( // custom handleChange props for nested input form
<Input const handleChange = this.state.hasNestedInput
customBootstrapClass={customBootstrapClass} ? this.handleChange
key={key} : this.props.onChange;
handleChange={handleChange} let hiddenLabel = includes(props.name, 'enabled');
name={props.name}
target={props.target}
isChecked={inputValue}
selectOptions={selectOptions}
validations={props.validations}
value={inputValue}
addRequiredInputDesign={this.props.addRequiredInputDesign}
hiddenLabel={hiddenLabel}
inputDescription={props.description}
errors={errors}
/>
);
}
render() { if (includes(config.showInputLabel, props.name)) hiddenLabel = false;
return (
<InnerComponent const errorIndex = findIndex(this.props.formErrors, [
{...this.props} 'target',
showNestedForm={this.state.showNestedForm} props.target,
renderInput={this.renderInput} ]);
styles={styles} const errors =
/> errorIndex !== -1 ? this.props.formErrors[errorIndex].errors : [];
);
} return (
}; <Input
customBootstrapClass={customBootstrapClass}
key={key}
handleChange={handleChange}
name={props.name}
target={props.target}
isChecked={inputValue}
selectOptions={selectOptions}
validations={props.validations}
value={inputValue}
addRequiredInputDesign={this.props.addRequiredInputDesign}
hiddenLabel={hiddenLabel}
inputDescription={props.description}
errors={errors}
/>
);
};
render() {
return (
<InnerComponent
{...this.props}
showNestedForm={this.state.showNestedForm}
renderInput={this.renderInput}
styles={styles}
/>
);
}
};
export default WithFormSection; export default WithFormSection;

View File

@ -1,23 +1,21 @@
/** /**
* *
* WithInput * WithInput
* *
*/ */
import React from 'react'; import React from 'react';
import styles from './styles.scss'; import styles from './styles.scss';
/* eslint-disable */
/* eslint-disable react/require-default-props */ /* eslint-disable react/require-default-props */
const WithInput = (InnerInput) => class extends React.Component { // eslint-disable-line react/prefer-stateless-function const WithInput = InnerInput =>
render() { class extends React.Component {
return ( // eslint-disable-line react/prefer-stateless-function
<InnerInput render() {
{...this.props} return <InnerInput {...this.props} {...this.state} styles={styles} />;
{...this.state} }
styles={styles} };
/>
);
}
};
export default WithInput; export default WithInput;