diff --git a/packages/strapi-plugin-content-type-builder/admin/src/components/RelationsWrapper/index.js b/packages/strapi-plugin-content-type-builder/admin/src/components/RelationsWrapper/index.js index 6e6008bbe1..bd9d002138 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/components/RelationsWrapper/index.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/components/RelationsWrapper/index.js @@ -2,9 +2,23 @@ import styled from 'styled-components'; const RelationsWrapper = styled.div` width: 100%; - display: flex; - padding: 2.7rem 1.5rem 3.3rem 1.5rem; - justify-content: space-between; + .relation-base { + display: flex; + padding: 2.7rem 1.5rem 3.3rem 1.5rem; + justify-content: space-between; + } + .relation-advanced { + .row { + margin: 0; + } + hr { + width: 100%; + margin: 1.3rem 1.5rem 2.1rem 1.5rem; + height: 1px; + background-color: rgba(14, 22, 34, 0.04); + border: 0; + } + } `; export default RelationsWrapper; diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/App/actions.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/App/actions.js index 1ea3da158f..95f13c288a 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/containers/App/actions.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/App/actions.js @@ -227,18 +227,17 @@ export function getDataSucceeded({ allModels, models }, connections, { data }) { }, {}); const initialDataGroup = data.reduce((acc, current) => { - const { - schema: { attributes, name }, - uid, - } = current; + const { schema, uid } = current; + const { attributes, name } = schema; const group = { - ...current, + ...schema, + attributes: buildGroupAttributes(attributes), + uid, name: name || uid, isTemporary: false, }; - set(group, ['schema', 'attributes'], buildGroupAttributes(attributes)); - acc[current.uid] = group; + acc[uid] = group; return acc; }, {}); @@ -251,13 +250,13 @@ export function getDataSucceeded({ allModels, models }, connections, { data }) { } = current; acc.push({ + uid, description: description || '', fields: Object.keys(attributes).length, icon: 'fa-cube', isTemporary: false, name: name || uid, - source, - uid, + source: source || null, }); return acc; @@ -292,12 +291,9 @@ export function onChangeExistingGroupMainInfos({ target }) { ? camelCase(target.value.trim()).toLowerCase() : target.value; - const array = target.name.split('.'); - array.splice(1, 0, 'schema'); - return { type: ON_CHANGE_EXISTING_GROUP_MAIN_INFOS, - keys: array, + keys: target.name.split('.'), value, }; } @@ -571,6 +567,7 @@ export function submitContentType(oldContentTypeName, data, context, source) { export function submitGroup(oldGroupName, data, context, source) { const attributes = formatGroupAttributes(data.attributes); + delete data['isTemporary']; const body = Object.assign(cloneDeep(data), { attributes }); return { @@ -612,8 +609,7 @@ export function submitTempContentTypeSucceeded() { } export function submitTempGroup(data, context) { - const attributes = formatGroupAttributes(data.schema.attributes); - delete data['schema']; + const attributes = formatGroupAttributes(data.attributes); const body = Object.assign(cloneDeep(data), { attributes }); return { @@ -700,9 +696,11 @@ export const buildGroupAttributes = attributes => export const formatGroupAttributes = attributes => { const formattedAttributes = attributes.reduce((acc, current) => { - acc[current.name] = current; - delete current['name']; + const name = current['name']; + let newAttribute = { ...current }; + delete newAttribute['name']; + acc[name] = newAttribute; return acc; }, {}); diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/App/index.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/App/index.js index 0d912f6b2f..d72b158cef 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/containers/App/index.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/App/index.js @@ -83,7 +83,6 @@ export class App extends React.Component { /* istanbul ignore next */ componentDidUpdate(prevProps) { if (prevProps.shouldRefetchData !== this.props.shouldRefetchData) { - console.log('UPDATE !!'); this.props.getData(); } } @@ -134,22 +133,9 @@ export class App extends React.Component { return newGroup; } - return get( - modifiedDataGroup, - [this.getFeatureNameFromSearch(), 'schema'], - {} - ); + return get(modifiedDataGroup, this.getFeatureNameFromSearch(), {}, {}); }; - // getFeatureName = () => { - // const { modifiedDataGroup } = this.props; - // return get( - // modifiedDataGroup, - // [this.getFeatureNameFromSearch(), 'schema', 'name'], - // {} - // ); - // }; - getFeatureNameFromSearch = () => getQueryParameters(this.getSearch(), `${this.getFeatureType()}Name`); @@ -221,7 +207,6 @@ export class App extends React.Component { } = this.props; if (isLoading) { - console.log('loading'); return ; } diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/App/reducer.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/App/reducer.js index 47e87f57ba..c065c021b6 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/containers/App/reducer.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/App/reducer.js @@ -123,22 +123,18 @@ export const initialState = fromJS({ initialDataGroup: {}, modifiedDataGroup: {}, newGroup: { + attributes: [], collectionName: '', connection: '', + description: '', name: '', - schema: { - attributes: [], - description: '', - }, }, newGroupClone: { + attributes: [], collectionName: '', connection: '', + description: '', name: '', - schema: { - attributes: [], - description: '', - }, }, }); @@ -196,7 +192,7 @@ function appReducer(state = initialState, action) { : ['modifiedDataGroup', groupName]; return state - .updateIn([...basePath, 'schema', 'attributes'], arr => + .updateIn([...basePath, 'attributes'], arr => arr.push(state.get('temporaryAttributeRelationGroup')) ) .update('temporaryAttributeRelationGroup', () => @@ -246,9 +242,8 @@ function appReducer(state = initialState, action) { .setIn(['type'], type); return state - .updateIn( - ['modifiedDataGroup', action.groupName, 'schema', 'attributes'], - arr => arr.push(newAttribute) + .updateIn(['modifiedDataGroup', action.groupName, 'attributes'], arr => + arr.push(newAttribute) ) .update('temporaryAttributeGroup', () => Map({})); } @@ -294,9 +289,7 @@ function appReducer(state = initialState, action) { .setIn(['type'], type); return state - .updateIn(['newGroup', 'schema', 'attributes'], arr => - arr.push(newAttribute) - ) + .updateIn(['newGroup', 'attributes'], arr => arr.push(newAttribute)) .update('temporaryAttributeGroup', () => Map({})); } case CANCEL_NEW_CONTENT_TYPE: @@ -581,9 +574,7 @@ function appReducer(state = initialState, action) { case RESET_EDIT_TEMP_CONTENT_TYPE: return state.updateIn(['newContentType', 'attributes'], () => Map({})); case RESET_EDIT_TEMP_GROUP: - return state.updateIn(['newGroup', 'schema', 'attributes'], () => - List([]) - ); + return state.updateIn(['newGroup', 'attributes'], () => List([])); case RESET_EXISTING_CONTENT_TYPE_MAIN_INFOS: return state.updateIn(['modifiedData', action.contentTypeName], () => { const initialContentType = state @@ -596,24 +587,16 @@ function appReducer(state = initialState, action) { return initialContentType; }); case RESET_EXISTING_GROUP_MAIN_INFOS: { - return state.updateIn( - ['modifiedDataGroup', action.groupName, 'schema'], - () => { - const initialGroup = state - .getIn(['initialDataGroup', action.groupName, 'schema']) - .set( - 'attributes', - state.getIn([ - 'modifiedDataGroup', - action.groupName, - 'schema', - 'attributes', - ]) - ); + return state.updateIn(['modifiedDataGroup', action.groupName], () => { + const initialGroup = state + .getIn(['initialDataGroup', action.groupName]) + .set( + 'attributes', + state.getIn(['modifiedDataGroup', action.groupName, 'attributes']) + ); - return initialGroup; - } - ); + return initialGroup; + }); } case RESET_NEW_CONTENT_TYPE_MAIN_INFOS: return state.updateIn(['newContentType'], () => { @@ -660,8 +643,8 @@ function appReducer(state = initialState, action) { case SAVE_EDITED_ATTRIBUTE_GROUP: { const basePath = action.isGroupTemporary - ? ['newGroup', 'schema'] - : ['modifiedDataGroup', action.groupName, 'schema']; + ? ['newGroup'] + : ['modifiedDataGroup', action.groupName]; const temporaryAttribute = state.get('temporaryAttributeGroup'); state.update('temporaryAttributeGroup', () => {}); @@ -791,8 +774,8 @@ function appReducer(state = initialState, action) { case SET_TEMPORARY_ATTRIBUTE_GROUP: return state.update('temporaryAttributeGroup', () => { const basePath = action.isGroupTemporary - ? ['newGroup', 'schema'] - : ['modifiedDataGroup', action.groupName, 'schema']; + ? ['newGroup'] + : ['modifiedDataGroup', action.groupName]; const attribute = state .getIn([...basePath, 'attributes', action.attributeIndex]) @@ -844,22 +827,12 @@ function appReducer(state = initialState, action) { return state .update('temporaryAttributeRelationGroup', () => state - .getIn([ - ...basePath, - 'schema', - 'attributes', - action.attributeName, - ]) + .getIn([...basePath, 'attributes', action.attributeName]) .set('name', action.attributeName) ) .update('initialTemporaryAttributeRelationGroup', () => state - .getIn([ - ...basePath, - 'schema', - 'attributes', - action.attributeName, - ]) + .getIn([...basePath, 'attributes', action.attributeName]) .set('name', action.attributeName) ); } @@ -885,24 +858,6 @@ function appReducer(state = initialState, action) { return state .update('isLoading', () => true) .update('shouldRefetchData', v => !v); - // { - // let modifiedGroup = state - // .get('modifiedDataGroup') - // .find( - // (group, key) => !group.equals(state.getIn(['initialDataGroup', key])) - // ); - - // const uid = modifiedGroup.get('uid'); - // const groupToUpdate = state.get('groups').findIndex(group => { - // return group.get('uid') === uid; - // }); - - // return state - // .updateIn(['initialDataGroup', uid], () => modifiedGroup) - // .updateIn(['groups', groupToUpdate, 'name'], () => - // modifiedGroup.getIn(['schema', 'name']) - // ); - // } case SUBMIT_TEMP_CONTENT_TYPE_SUCCEEDED: return state .update('isLoading', () => true) diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/App/tests/actions.test.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/App/tests/actions.test.js index 70cd0948eb..025633d3b3 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/containers/App/tests/actions.test.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/App/tests/actions.test.js @@ -501,9 +501,8 @@ describe('App actions', () => { data: [ { uid: 'ingredients', - name: 'ingredients', - source: null, schema: { + name: 'ingredients', connection: 'default', collectionName: 'ingredients', description: 'Little description', @@ -556,30 +555,27 @@ describe('App actions', () => { isTemporary: false, uid: 'ingredients', name: 'ingredients', - source: null, - schema: { - connection: 'default', - collectionName: 'ingredients', - description: 'Little description', - attributes: [ - { - name: 'name', - type: 'string', - required: true, - }, - { - name: 'quantity', - type: 'float', - required: true, - }, - { - name: 'picture', - model: 'file', - via: 'related', - plugin: 'upload', - }, - ], - }, + connection: 'default', + collectionName: 'ingredients', + description: 'Little description', + attributes: [ + { + name: 'name', + type: 'string', + required: true, + }, + { + name: 'quantity', + type: 'float', + required: true, + }, + { + name: 'picture', + model: 'file', + via: 'related', + plugin: 'upload', + }, + ], }, }; const connections = ['default']; @@ -596,8 +592,8 @@ describe('App actions', () => { icon: 'fa-cube', isTemporary: false, name: 'ingredients', - source: null, uid: 'ingredients', + source: null, }, ], }; diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/App/tests/reducer.test.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/App/tests/reducer.test.js index 681b37b761..f3e9b26a20 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/containers/App/tests/reducer.test.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/App/tests/reducer.test.js @@ -147,25 +147,31 @@ describe('appReducer', () => { target: '', unique: false, }, + temporaryAttributeRelationGroup: { + name: '', + columnName: '', + dominant: false, + targetColumnName: '', + key: '-', + nature: 'oneWay', + plugin: '', + target: '', + unique: false, + }, shouldRefetchData: false, - newGroup: { collectionName: '', connection: '', name: '', - schema: { - attributes: [], - description: '', - }, + attributes: [], + description: '', }, newGroupClone: { collectionName: '', connection: '', name: '', - schema: { - attributes: [], - description: '', - }, + attributes: [], + description: '', }, }); }); @@ -749,24 +755,21 @@ describe('appReducer', () => { tests: { uid: 'tests', name: 'tests', - source: null, - schema: { - connection: 'default', - collectionName: 'tests', - description: '', - attributes: [ - { - name: 'name', - type: 'string', - required: true, - }, - { - name: 'quantity', - type: 'float', - required: true, - }, - ], - }, + connection: 'default', + collectionName: 'tests', + description: '', + attributes: [ + { + name: 'name', + type: 'string', + required: true, + }, + { + name: 'quantity', + type: 'float', + required: true, + }, + ], isTemporary: false, }, }; @@ -776,7 +779,6 @@ describe('appReducer', () => { { uid: 'tests', name: 'tests', - source: null, schema: { connection: 'default', collectionName: 'tests', @@ -1046,24 +1048,31 @@ describe('appReducer', () => { target: '', unique: false, }, + temporaryAttributeRelationGroup: { + name: '', + columnName: '', + dominant: false, + targetColumnName: '', + key: '-', + nature: 'oneWay', + plugin: '', + target: '', + unique: false, + }, shouldRefetchData: false, newGroup: { collectionName: '', connection: '', name: '', - schema: { - attributes: [], - description: '', - }, + attributes: [], + description: '', }, newGroupClone: { collectionName: '', connection: '', name: '', - schema: { - attributes: [], - description: '', - }, + attributes: [], + description: '', }, initialDataGroup: {}, modifiedDataGroup: {}, diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/GroupPage/index.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/GroupPage/index.js index 05c3f48330..f88551caaa 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/containers/GroupPage/index.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/GroupPage/index.js @@ -104,9 +104,7 @@ export class GroupPage extends React.Component { return get(modifiedDataGroup, this.getFeatureName(), {}); }; - getFeatureSchema = () => get(this.getFeature(), 'schema', {}); - - getFeatureAttributes = () => get(this.getFeatureSchema(), 'attributes', []); + getFeatureAttributes = () => get(this.getFeature(), 'attributes', []); getFeatureAttributesNames = () => { return this.getFeatureAttributes().map(attribute => { @@ -139,25 +137,13 @@ export class GroupPage extends React.Component { return displayName; }; - getFeatureHeaderTitle = () => { - const { modifiedDataGroup, newGroup } = this.props; - const name = this.getFeatureName(); - - /* istanbul ignore if */ - const displayName = this.isUpdatingTempFeature() - ? get(newGroup, ['schema', 'name'], null) - : get(modifiedDataGroup, [name, 'schema', 'name'], null); - - return displayName; - }; - getFeatureHeaderDescription = () => { const { modifiedDataGroup, newGroup } = this.props; const name = this.getFeatureName(); const description = this.isUpdatingTempFeature() - ? get(newGroup, ['schema', 'description'], null) - : get(modifiedDataGroup, [name, 'schema', 'description'], null); + ? get(newGroup, 'description', null) + : get(modifiedDataGroup, [name, 'description'], null); /* istanbul ignore if */ /* eslint-disable indent */ @@ -190,7 +176,7 @@ export class GroupPage extends React.Component { ? submitTempGroup(newGroup, this.context) : submitGroup( featureName, - get(modifiedDataGroup, [featureName, 'schema']), + get(modifiedDataGroup, featureName), Object.assign(this.context, { history: this.props.history, }), @@ -287,11 +273,10 @@ export class GroupPage extends React.Component { const { attrToDelete } = this.state; const keys = this.isUpdatingTempFeature() - ? ['newGroup', 'schema', 'attributes', attrToDelete] + ? ['newGroup', 'attributes', attrToDelete] : [ 'modifiedDataGroup', this.getFeatureName(), - 'schema', 'attributes', attrToDelete, ]; @@ -444,7 +429,7 @@ export class GroupPage extends React.Component { ', () => { @@ -229,9 +237,9 @@ describe('CTB ', () => { }); it('should call the openAttributesModal when clicking on the EmptyAttributesBlock', () => { - props.initialDataGroup.tests.schema.attributes = []; - props.modifiedDataGroup.tests.schema.attributes = []; - props.newGroup.schema.attributes = []; + props.initialDataGroup.tests.attributes = []; + props.modifiedDataGroup.tests.attributes = []; + props.newGroup.attributes = []; const wrapper = shallow(); const spyOnClick = jest.spyOn(wrapper.instance(), 'openAttributesModal'); @@ -456,7 +464,7 @@ describe('CTB , lifecycle', () => { props.groups.find(item => item.name == 'tests').isTemporary = true; props.newGroup.name = 'tests'; - props.newGroup.schema.attributes = [ + props.newGroup.attributes = [ { name: 'name', type: 'string', @@ -479,7 +487,7 @@ describe('CTB , lifecycle', () => { it('should call submitGroup with modifiedDataGroup param when isTemporary is false', () => { props.groups.find(item => item.name == 'tests').isTemporary = false; - props.initialDataGroup.tests.schema.attributes = [ + props.initialDataGroup.tests.attributes = [ { name: 'name', type: 'string', @@ -491,7 +499,7 @@ describe('CTB , lifecycle', () => { required: true, }, ]; - props.modifiedDataGroup.tests.schema.attributes = [ + props.modifiedDataGroup.tests.attributes = [ { name: 'firstname', type: 'string', @@ -563,7 +571,7 @@ describe('CTB , lifecycle', () => { showWarning: true, }); handleDeleteAttribute(); - const keys = ['modifiedDataGroup', 'tests', 'schema', 'attributes', 0]; + const keys = ['modifiedDataGroup', 'tests', 'attributes', 0]; expect(props.deleteGroupAttribute).toHaveBeenCalledWith(keys); expect(context.emitEvent).toHaveBeenCalledWith('willDeleteFieldOfGroup'); }); @@ -582,7 +590,7 @@ describe('CTB , lifecycle', () => { handleClickOnTrashIcon(0); handleDeleteAttribute(); - const keys = ['newGroup', 'schema', 'attributes', 0]; + const keys = ['newGroup', 'attributes', 0]; expect(props.deleteGroupAttribute).toHaveBeenCalledWith(keys); expect(context.emitEvent).toHaveBeenCalledWith('willDeleteFieldOfGroup'); }); diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/RelationFormGroup/advanced.json b/packages/strapi-plugin-content-type-builder/admin/src/containers/RelationFormGroup/advanced.json new file mode 100644 index 0000000000..c201bf3a61 --- /dev/null +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/RelationFormGroup/advanced.json @@ -0,0 +1,36 @@ +[ + { + "customBootstrapClass": "col-md-12", + "label": { + "id": "content-type-builder.form.attribute.item.uniqueField" + }, + "name": "unique", + "type": "checkbox", + "value": false, + "validations": {}, + "inputDescription": { + "id": "content-type-builder.form.attribute.item.uniqueField.description" + } + }, + { + "addon": "name", + "label": { + "id": "content-type-builder.form.attribute.item.customColumnName" + }, + "name": "columnName", + "type": "string", + "value": "", + "validations": {}, + "inputDescription": { + "id": "content-type-builder.form.attribute.item.customColumnName.description" + } + }, + { + "addon": "key", + "label": "", + "name": "targetColumnName", + "type": "string", + "value": "", + "validations": {} + } +] diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/RelationFormGroup/index.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/RelationFormGroup/index.js index 067b3b6320..4e532ac765 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/containers/RelationFormGroup/index.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/RelationFormGroup/index.js @@ -9,6 +9,8 @@ import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import { get, isEmpty } from 'lodash'; +import { InputsIndex as Input } from 'strapi-helper-plugin'; + import pluginId from '../../pluginId'; import BodyModal from '../../components/BodyModal'; @@ -28,6 +30,8 @@ import WrapperModal from '../../components/WrapperModal'; import Icon from '../../assets/icons/icon_type_ct.png'; import IconGroup from '../../assets/icons/icon_type_groups.png'; +import formAdvanced from './advanced.json'; + const NAVLINKS = [{ id: 'base', custom: 'relation' }, { id: 'advanced' }]; class RelationFormGroup extends React.Component { @@ -110,6 +114,21 @@ class RelationFormGroup extends React.Component { onChangeRelationTarget(group, featureToEditName, actionType === 'edit'); }; + handleGoTo = to => { + const { emitEvent } = this.context; + const { actionType, attributeToEditName, push } = this.props; + const attributeName = + actionType === 'edit' ? `&attributeName=${attributeToEditName}` : ''; + + if (to === 'advanced') { + emitEvent('didSelectContentTypeFieldSettings'); + } + + push({ + search: `modalType=attributeForm&attributeType=relation&settingType=${to}&actionType=${actionType}${attributeName}`, + }); + }; + handleOnClosed = () => { const { onCancel } = this.props; @@ -146,21 +165,6 @@ class RelationFormGroup extends React.Component { push({ search: '' }); }; - handleGoTo = to => { - const { emitEvent } = this.context; - const { actionType, attributeToEditName, push } = this.props; - const attributeName = - actionType === 'edit' ? `&attributeName=${attributeToEditName}` : ''; - - if (to === 'advanced') { - emitEvent('didSelectContentTypeFieldSettings'); - } - - push({ - search: `modalType=attributeForm&attributeType=relation&settingType=${to}&actionType=${actionType}${attributeName}`, - }); - }; - handleSubmit = e => { e.preventDefault(); @@ -191,13 +195,39 @@ class RelationFormGroup extends React.Component { ); }; + renderAdvancedSettings = () => { + const { didCheckErrors } = this.state; + const { modifiedData, onChange } = this.props; + + return ( +
+
+ {formAdvanced.map((input, i) => { + return ( + + + {i === 0 &&
} +
+ ); + })} +
+
+ ); + }; + submit = (shouldContinue = false) => { const { actionType, onSubmit, onSubmitEdit } = this.props; if (actionType === 'edit') { onSubmitEdit(shouldContinue); } else { - console.log('SUUUBMIT'); onSubmit(shouldContinue); } }; @@ -213,7 +243,7 @@ class RelationFormGroup extends React.Component { } = this.props; const { formErrors, didCheckErrors } = this.state; return ( - +
- +
); }; @@ -287,7 +317,9 @@ class RelationFormGroup extends React.Component {
- {showForm && content} + + {showForm && content} +
{}, - onChangeRelationTarget: () => {}, - onSubmit: () => {}, source: null, }; RelationFormGroup.propTypes = { actionType: PropTypes.string, activeTab: PropTypes.string, + alreadyTakenAttributes: PropTypes.array, features: PropTypes.array, featureType: PropTypes.string, - featuereToEditName: PropTypes.string, + featureToEditName: PropTypes.string, isOpen: PropTypes.bool, isUpdatingTemporary: PropTypes.bool, modifiedData: PropTypes.object.isRequired, - onChange: PropTypes.func, - onChangeRelationTarget: PropTypes.func, - onSubmit: PropTypes.func, + onChange: PropTypes.func.isRequired, + onChangeRelationTarget: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired, push: PropTypes.func.isRequired, + setTempAttribute: PropTypes.func.isRequired, source: PropTypes.string, }; diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/RelationFormGroup/tests/index.test.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/RelationFormGroup/tests/index.test.js index 5222821ab6..8dcbb95f9e 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/containers/RelationFormGroup/tests/index.test.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/RelationFormGroup/tests/index.test.js @@ -1,7 +1,366 @@ import React from 'react'; +import mountWithIntl from 'testUtils/mountWithIntl'; +import formatMessagesWithPluginId from 'testUtils/formatMessages'; + +import pluginId from '../../../pluginId'; +import pluginTradsEn from '../../../translations/en.json'; +import RelationFormGroup from '../index'; + +const messages = formatMessagesWithPluginId(pluginId, pluginTradsEn); +const renderComponent = (props = {}, context = {}) => + mountWithIntl(, messages, context); describe('', () => { + let props; + let wrapper; + + beforeEach(() => { + props = { + activeTab: 'base', + alreadyTakenAttributes: [], + attributeToEditName: '', + setTempAttribute: jest.fn(), + isOpen: true, + features: [], + featureToEditName: '', + modifiedData: { + key: '', + name: '', + source: '', + }, + onCancel: jest.fn(), + onChange: jest.fn(), + onChangeRelationNature: jest.fn(), + onChangeRelationTarget: jest.fn(), + onSubmit: jest.fn(), + onSubmitEdit: jest.fn(), + setTempAttribute: jest.fn(), + push: jest.fn(), + source: null, + }; + }); + + afterEach(() => { + wrapper.unmount(); + }); + it('should not crash', () => { - expect(true).toBe(true); + wrapper = renderComponent(props); + }); + + it('should render the advanced tab if the active tab is advanced', () => { + props.activeTab = 'advanced'; + + wrapper = renderComponent(props); + const compo = wrapper.find(RelationFormGroup); + const spyOnRenderAdvancedSettings = jest.spyOn( + compo.instance(), + 'renderAdvancedSettings' + ); + compo.instance().forceUpdate(); + + expect(spyOnRenderAdvancedSettings).toHaveBeenCalled(); + }); + + describe('GetFormErrors', () => { + it('should return an object with the errors if the form is empty', () => { + wrapper = renderComponent(props); + const { getFormErrors } = wrapper.find(RelationFormGroup).instance(); + + expect(getFormErrors()).toEqual({ + name: [{ id: 'content-type-builder.error.validation.required' }], + key: [{ id: 'content-type-builder.error.validation.required' }], + }); + }); + + it('should return an object if a name is already taken', () => { + props.alreadyTakenAttributes = ['test']; + props.modifiedData.name = 'test'; + props.modifiedData.key = 'strapi'; + + wrapper = renderComponent(props); + const { getFormErrors } = wrapper.find(RelationFormGroup).instance(); + + expect(getFormErrors()).toEqual({ + name: [{ id: 'content-type-builder.error.attribute.key.taken' }], + }); + }); + + it('should return an error if the key is equal to the name', () => { + props.alreadyTakenAttributes = []; + props.modifiedData.name = 'test'; + props.modifiedData.key = 'test'; + + wrapper = renderComponent(props); + const { getFormErrors } = wrapper.find(RelationFormGroup).instance(); + + expect(getFormErrors()).toEqual({ + key: [{ id: 'content-type-builder.error.attribute.key.taken' }], + }); + }); + + it('should not return an error when editing', () => { + props.alreadyTakenAttributes = ['test', 'strapi']; + props.modifiedData.name = 'test'; + props.modifiedData.key = 'strapi'; + props.actionType = 'edit'; + props.attributeToEditName = 'test'; + + wrapper = renderComponent(props); + const { getFormErrors } = wrapper.find(RelationFormGroup).instance(); + + expect(getFormErrors()).toEqual({}); + }); + }); + + describe(', basic instances', () => { + describe('HandleChangeRelationTarget', () => { + it('should call the onChangeRelationTarget with the correct data (not editing)', () => { + props.featureToEditName = 'test'; + + wrapper = renderComponent(props); + const { handleChangeRelationTarget } = wrapper + .find(RelationFormGroup) + .instance(); + + handleChangeRelationTarget('strapi'); + + expect(props.onChangeRelationTarget).toHaveBeenLastCalledWith( + 'strapi', + 'test', + false + ); + }); + + it('should call the onChangeRelationTarget with the correct data (editing)', () => { + props.featureToEditName = 'test'; + props.actionType = 'edit'; + + wrapper = renderComponent(props); + const { handleChangeRelationTarget } = wrapper + .find(RelationFormGroup) + .instance(); + + handleChangeRelationTarget('strapi'); + + expect(props.onChangeRelationTarget).toHaveBeenLastCalledWith( + 'strapi', + 'test', + true + ); + }); + }); + + describe('HandleCancel', () => { + it('should clear the search', () => { + wrapper = renderComponent(props); + const { handleCancel } = wrapper.find(RelationFormGroup).instance(); + + handleCancel(); + + expect(props.push).toHaveBeenCalledWith({ search: '' }); + }); + }); + + describe('HandleGoTo', () => { + it('should emit the event didSelectContentTypeFieldSettings if the user clicks on the advanced tab', () => { + const context = { emitEvent: jest.fn() }; + + wrapper = renderComponent(props, context); + const { handleGoTo } = wrapper.find(RelationFormGroup).instance(); + + handleGoTo('advanced'); + + expect(props.push).toHaveBeenCalledWith({ + search: + 'modalType=attributeForm&attributeType=relation&settingType=advanced&actionType=create', + }); + expect(context.emitEvent).toHaveBeenCalledWith( + 'didSelectContentTypeFieldSettings' + ); + }); + + it('should add the keep the attribute name if the action is edit', () => { + const context = { emitEvent: jest.fn() }; + props.actionType = 'edit'; + props.attributeToEditName = 'test'; + wrapper = renderComponent(props, context); + const { handleGoTo } = wrapper.find(RelationFormGroup).instance(); + + handleGoTo('advanced'); + + expect(props.push).toHaveBeenCalledWith({ + search: + 'modalType=attributeForm&attributeType=relation&settingType=advanced&actionType=edit&attributeName=test', + }); + expect(context.emitEvent).toHaveBeenCalledWith( + 'didSelectContentTypeFieldSettings' + ); + }); + + it('should not emit the event if the tab is base', () => { + const context = { emitEvent: jest.fn() }; + + wrapper = renderComponent(props, context); + const { handleGoTo } = wrapper.find(RelationFormGroup).instance(); + + handleGoTo('base'); + + expect(props.push).toHaveBeenCalledWith({ + search: + 'modalType=attributeForm&attributeType=relation&settingType=base&actionType=create', + }); + expect(context.emitEvent).not.toHaveBeenCalled(); + }); + }); + + describe('HandleOnClosed', () => { + it('should update the state and call the onCancel prop', () => { + wrapper = renderComponent(props); + const compo = wrapper.find(RelationFormGroup); + compo.setState({ showForm: true, formErrors: { name: [] } }); + + expect(compo.state('showForm')).toBeTruthy(); + + const { handleOnClosed } = compo.instance(); + + handleOnClosed(); + + expect(compo.state('formErrors')).toEqual({}); + expect(compo.state('showForm')).toBeFalsy(); + expect(props.onCancel).toHaveBeenCalled(); + }); + }); + + describe('HandleOnOpened', () => { + it('should update the state and call the onCancel prop', () => { + props.features = [{ name: 'test', source: 'test' }]; + wrapper = renderComponent(props); + const compo = wrapper.find(RelationFormGroup); + + expect(compo.state('showForm')).toBeFalsy(); + + const { handleOnOpened } = compo.instance(); + + handleOnOpened(); + + expect(compo.state('showForm')).toBeTruthy(); + expect(props.setTempAttribute).toHaveBeenCalledWith( + 'test', + false, + 'test', + '', + false + ); + }); + it('should update the state and call the onCancel prop', () => { + props.features = [{ name: 'test' }]; + props.featureToEditName = 'strapi'; + props.actionType = 'edit'; + props.attributeToEditName = 'test'; + wrapper = renderComponent(props); + const compo = wrapper.find(RelationFormGroup); + + expect(compo.state('showForm')).toBeFalsy(); + + const { handleOnOpened } = compo.instance(); + + handleOnOpened(); + + expect(compo.state('showForm')).toBeTruthy(); + expect(props.setTempAttribute).toHaveBeenCalledWith( + 'strapi', + false, + undefined, + 'test', + true + ); + }); + }); + + describe('HandleSubmit', () => { + it('should call the submit prop if there is no error', () => { + props.modifiedData = { + name: 'test', + nature: 'oneWay', + target: 'test', + key: '-', + }; + wrapper = renderComponent(props); + const { handleSubmit } = wrapper.instance(); + + handleSubmit({ preventDefault: jest.fn() }); + + expect(props.onSubmit).toHaveBeenCalledWith(false); + }); + + it('should not call the submit if the form is empty', () => { + wrapper = renderComponent(props); + const { handleSubmit } = wrapper.instance(); + + handleSubmit({ preventDefault: jest.fn() }); + + expect(props.onSubmit).not.toHaveBeenCalled(); + }); + }); + + describe('HandleSubmitAndContinue', () => { + it('should call the submit prop if there is no error', () => { + props.modifiedData = { + name: 'test', + nature: 'oneWay', + target: 'test', + key: '-', + }; + wrapper = renderComponent(props); + const { handleSubmitAndContinue } = wrapper.instance(); + + handleSubmitAndContinue({ preventDefault: jest.fn() }); + + expect(props.onSubmit).toHaveBeenCalledWith(true); + }); + + it('should not call the submit if the form is empty', () => { + wrapper = renderComponent(props); + const { handleSubmitAndContinue } = wrapper.instance(); + + handleSubmitAndContinue({ preventDefault: jest.fn() }); + + expect(props.onSubmit).not.toHaveBeenCalled(); + }); + }); + + describe('HandleToggle', () => { + it('should clear the search', () => { + wrapper = renderComponent(props); + const { handleToggle } = wrapper.instance(); + + handleToggle(); + + expect(props.push).toHaveBeenCalledWith({ search: '' }); + }); + }); + describe('Submit', () => { + it('should call the onSubmitEditProp if the actionType is edit', () => { + props.actionType = 'edit'; + wrapper = renderComponent(props); + + const { submit } = wrapper.instance(); + + submit(); + + expect(props.onSubmitEdit).toHaveBeenCalledWith(false); + }); + + it('should call the onSubmitEdit if the actionType is create', () => { + wrapper = renderComponent(props); + + const { submit } = wrapper.instance(); + + submit(true); + + expect(props.onSubmit).toHaveBeenCalledWith(true); + }); + }); }); });