mirror of
https://github.com/vale981/Vulcan
synced 2025-03-06 10:01:40 -05:00
Merge branch 'feature/nested-schema' of https://github.com/lbke/Vulcan into lbke-feature/nested-schema
This commit is contained in:
commit
96a396fd8c
12 changed files with 414 additions and 125 deletions
|
@ -393,6 +393,7 @@ class SmartForm extends Component {
|
||||||
if (fieldSchema.schema) {
|
if (fieldSchema.schema) {
|
||||||
field.nestedSchema = fieldSchema.schema;
|
field.nestedSchema = fieldSchema.schema;
|
||||||
field.nestedInput = true;
|
field.nestedInput = true;
|
||||||
|
|
||||||
// get nested schema
|
// get nested schema
|
||||||
// for each nested field, get field object by calling createField recursively
|
// for each nested field, get field object by calling createField recursively
|
||||||
field.nestedFields = this.getFieldNames({ schema: field.nestedSchema }).map(subFieldName => {
|
field.nestedFields = this.getFieldNames({ schema: field.nestedSchema }).map(subFieldName => {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { registerComponent } from 'meteor/vulcan:core';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEqual from 'lodash/isEqual';
|
import isEqual from 'lodash/isEqual';
|
||||||
import { isEmptyValue, mergeValue } from '../modules/utils.js';
|
import { isEmptyValue, mergeValue } from '../modules/utils.js';
|
||||||
|
import SimpleSchema from 'simpl-schema'
|
||||||
|
|
||||||
class FormComponent extends Component {
|
class FormComponent extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -287,11 +288,24 @@ class FormComponent extends Component {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getFieldType = () => {
|
||||||
|
return this.props.datatype[0].type
|
||||||
|
}
|
||||||
|
isArrayField = () => {
|
||||||
|
return this.getFieldType() === Array
|
||||||
|
}
|
||||||
|
isObjectField = () => {
|
||||||
|
return this.getFieldType() instanceof SimpleSchema
|
||||||
|
}
|
||||||
render() {
|
render() {
|
||||||
if (this.props.intlInput) {
|
if (this.props.intlInput) {
|
||||||
return <Components.FormIntl {...this.props} />;
|
return <Components.FormIntl {...this.props} />;
|
||||||
} else if (this.props.nestedInput) {
|
} else if (this.props.nestedInput) {
|
||||||
return <Components.FormNested {...this.props} />;
|
if (this.isArrayField()) {
|
||||||
|
return <Components.FormNestedArray {...this.props} />;
|
||||||
|
} else if (this.isObjectField()) {
|
||||||
|
return <Components.FormNestedObject {...this.props} />;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Components.FormComponentInner
|
<Components.FormComponentInner
|
||||||
|
@ -337,4 +351,6 @@ FormComponent.contextTypes = {
|
||||||
getDocument: PropTypes.func.isRequired,
|
getDocument: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports = FormComponent
|
||||||
|
|
||||||
registerComponent('FormComponent', FormComponent);
|
registerComponent('FormComponent', FormComponent);
|
||||||
|
|
|
@ -80,6 +80,8 @@ FormGroup.propTypes = {
|
||||||
currentUser: PropTypes.object,
|
currentUser: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports = FormGroup
|
||||||
|
|
||||||
registerComponent('FormGroup', FormGroup);
|
registerComponent('FormGroup', FormGroup);
|
||||||
|
|
||||||
const IconRight = ({ width = 24, height = 24 }) => (
|
const IconRight = ({ width = 24, height = 24 }) => (
|
||||||
|
|
|
@ -1,49 +1,9 @@
|
||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Components, registerComponent } from 'meteor/vulcan:core';
|
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||||
|
import "./FormNestedItem"
|
||||||
|
|
||||||
const FormNestedItem = ({ nestedFields, name, path, removeItem, itemIndex, ...props }, { errors }) => {
|
class FormNestedArray extends PureComponent {
|
||||||
return (
|
|
||||||
<div className="form-nested-item">
|
|
||||||
<div className="form-nested-item-inner">
|
|
||||||
{nestedFields.map((field, i) => {
|
|
||||||
return (
|
|
||||||
<Components.FormComponent
|
|
||||||
key={i}
|
|
||||||
{...props}
|
|
||||||
{...field}
|
|
||||||
path={`${path}.${field.name}`}
|
|
||||||
itemIndex={itemIndex}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<div className="form-nested-item-remove">
|
|
||||||
<Components.Button
|
|
||||||
className="form-nested-button"
|
|
||||||
variant="danger"
|
|
||||||
size="small"
|
|
||||||
onClick={() => {
|
|
||||||
removeItem(name);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Components.IconRemove height={12} width={12} />
|
|
||||||
</Components.Button>
|
|
||||||
</div>
|
|
||||||
<div className="form-nested-item-deleted-overlay" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
FormNestedItem.contextTypes = {
|
|
||||||
errors: PropTypes.array,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
registerComponent('FormNestedItem', FormNestedItem);
|
|
||||||
|
|
||||||
class FormNested extends PureComponent {
|
|
||||||
getCurrentValue() {
|
getCurrentValue() {
|
||||||
return this.props.currentValues[this.props.path] || []
|
return this.props.currentValues[this.props.path] || []
|
||||||
}
|
}
|
||||||
|
@ -78,7 +38,7 @@ class FormNested extends PureComponent {
|
||||||
{value.map(
|
{value.map(
|
||||||
(subDocument, i) =>
|
(subDocument, i) =>
|
||||||
!this.isDeleted(i) && (
|
!this.isDeleted(i) && (
|
||||||
<FormNestedItem
|
<Components.FormNestedItem
|
||||||
{...properties}
|
{...properties}
|
||||||
key={i}
|
key={i}
|
||||||
itemIndex={i}
|
itemIndex={i}
|
||||||
|
@ -98,15 +58,15 @@ class FormNested extends PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FormNested.propTypes = {
|
FormNestedArray.propTypes = {
|
||||||
currentValues: PropTypes.object,
|
currentValues: PropTypes.object,
|
||||||
path: PropTypes.string,
|
path: PropTypes.string,
|
||||||
label: PropTypes.string
|
label: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = FormNested
|
module.exports = FormNestedArray
|
||||||
|
|
||||||
registerComponent('FormNested', FormNested);
|
registerComponent('FormNestedArray', FormNestedArray);
|
||||||
|
|
||||||
const IconAdd = ({ width = 24, height = 24 }) => (
|
const IconAdd = ({ width = 24, height = 24 }) => (
|
||||||
<svg width={width} height={height} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
|
<svg width={width} height={height} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
|
53
packages/vulcan-forms/lib/components/FormNestedItem.jsx
Normal file
53
packages/vulcan-forms/lib/components/FormNestedItem.jsx
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import React from "react"
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
|
const FormNestedItem = ({ nestedFields, name, path, removeItem, itemIndex, ...props }, { errors }) => {
|
||||||
|
const isArray = typeof itemIndex !== 'undefined'
|
||||||
|
return (
|
||||||
|
<div className="form-nested-item">
|
||||||
|
<div className="form-nested-item-inner">
|
||||||
|
{nestedFields.map((field, i) => {
|
||||||
|
return (
|
||||||
|
<Components.FormComponent
|
||||||
|
key={i}
|
||||||
|
{...props}
|
||||||
|
{...field}
|
||||||
|
path={`${path}.${field.name}`}
|
||||||
|
itemIndex={itemIndex}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
isArray && [
|
||||||
|
<div key="remove-button" className="form-nested-item-remove">
|
||||||
|
<Components.Button
|
||||||
|
className="form-nested-button"
|
||||||
|
variant="danger"
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
removeItem(name);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Components.IconRemove height={12} width={12} />
|
||||||
|
</Components.Button>
|
||||||
|
</div>,
|
||||||
|
<div key="remove-button-overlay" className="form-nested-item-deleted-overlay" />
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
FormNestedItem.propTypes = {
|
||||||
|
path: PropTypes.string.isRequired,
|
||||||
|
itemIndex: PropTypes.number
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FormNestedItem.contextTypes = {
|
||||||
|
errors: PropTypes.array,
|
||||||
|
};
|
||||||
|
|
||||||
|
registerComponent('FormNestedItem', FormNestedItem);
|
37
packages/vulcan-forms/lib/components/FormNestedObject.jsx
Normal file
37
packages/vulcan-forms/lib/components/FormNestedObject.jsx
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||||
|
import "./FormNestedItem"
|
||||||
|
|
||||||
|
class FormNestedObject extends PureComponent {
|
||||||
|
/*getCurrentValue() {
|
||||||
|
return this.props.currentValues[this.props.path] || {}
|
||||||
|
}*/
|
||||||
|
render() {
|
||||||
|
//const value = this.getCurrentValue()
|
||||||
|
// do not pass FormNested's own value, input and inputProperties props down
|
||||||
|
const properties = _.omit(this.props, 'value', 'input', 'inputProperties', 'nestedInput');
|
||||||
|
return (
|
||||||
|
<div className="form-group row form-nested">
|
||||||
|
<label className="control-label col-sm-3">{this.props.label}</label>
|
||||||
|
<div className="col-sm-9">
|
||||||
|
<Components.FormNestedItem
|
||||||
|
{...properties}
|
||||||
|
path={`${this.props.path}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FormNestedObject.propTypes = {
|
||||||
|
currentValues: PropTypes.object,
|
||||||
|
path: PropTypes.string,
|
||||||
|
label: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = FormNestedObject
|
||||||
|
|
||||||
|
registerComponent('FormNestedObject', FormNestedObject);
|
||||||
|
|
|
@ -2,7 +2,8 @@ import '../components/FieldErrors.jsx';
|
||||||
import '../components/FormErrors.jsx';
|
import '../components/FormErrors.jsx';
|
||||||
import '../components/FormError.jsx';
|
import '../components/FormError.jsx';
|
||||||
import '../components/FormComponent.jsx';
|
import '../components/FormComponent.jsx';
|
||||||
import '../components/FormNested.jsx';
|
import '../components/FormNestedArray.jsx';
|
||||||
|
import '../components/FormNestedObject.jsx';
|
||||||
import '../components/FormIntl.jsx';
|
import '../components/FormIntl.jsx';
|
||||||
import '../components/FormGroup.jsx';
|
import '../components/FormGroup.jsx';
|
||||||
import '../components/FormSubmit.jsx';
|
import '../components/FormSubmit.jsx';
|
||||||
|
|
|
@ -17,7 +17,7 @@ export const convertSchema = (schema, flatten = false) => {
|
||||||
// extract schema
|
// extract schema
|
||||||
jsonSchema[fieldName] = getFieldSchema(fieldName, schema);
|
jsonSchema[fieldName] = getFieldSchema(fieldName, schema);
|
||||||
|
|
||||||
// check for existence of nested schema on corresponding array field
|
// check for existence of nested schema
|
||||||
const subSchema = getNestedSchema(fieldName, schema);
|
const subSchema = getNestedSchema(fieldName, schema);
|
||||||
// if nested schema exists, call convertSchema recursively
|
// if nested schema exists, call convertSchema recursively
|
||||||
if (subSchema) {
|
if (subSchema) {
|
||||||
|
@ -51,15 +51,43 @@ export const getFieldSchema = (fieldName, schema) => {
|
||||||
return fieldSchema;
|
return fieldSchema;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// type is an array due to the possibility of using SimpleSchema.oneOf
|
||||||
|
// right now we support only fields with one type
|
||||||
|
export const getSchemaType = schema => schema.type.definitions[0].type
|
||||||
|
const getArrayNestedSchema = (fieldName, schema) => {
|
||||||
|
const arrayItemSchema = schema._schema[`${fieldName}.$`];
|
||||||
|
const nestedSchema = arrayItemSchema && getSchemaType(arrayItemSchema)
|
||||||
|
return nestedSchema
|
||||||
|
}
|
||||||
|
// nested object fields type is of the form "type: new SimpleSchema({...})"
|
||||||
|
// so they should possess a "_schema" prop
|
||||||
|
const isNestedSchemaField = (fieldSchema) => {
|
||||||
|
const fieldType = getSchemaType(fieldSchema)
|
||||||
|
//console.log('fieldType', typeof fieldType, fieldType._schema)
|
||||||
|
return fieldType && !!fieldType._schema
|
||||||
|
}
|
||||||
|
const getObjectNestedSchema = (fieldName, schema) => {
|
||||||
|
const fieldSchema = schema._schema[fieldName]
|
||||||
|
if (!isNestedSchemaField(fieldSchema)) return null
|
||||||
|
const nestedSchema = fieldSchema && getSchemaType(fieldSchema)
|
||||||
|
return nestedSchema
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
|
|
||||||
Given an array field, get its nested schema
|
Given an array field, get its nested schema
|
||||||
|
|
||||||
*/
|
*/
|
||||||
export const getNestedSchema = (fieldName, schema) => {
|
export const getNestedSchema = (fieldName, schema) => {
|
||||||
const arrayItemSchema = schema._schema[`${fieldName}.$`];
|
const arrayItemSchema = getArrayNestedSchema(fieldName, schema)
|
||||||
const nestedSchema = arrayItemSchema && arrayItemSchema.type.definitions[0].type;
|
if (!arrayItemSchema) {
|
||||||
return nestedSchema;
|
// look for an object schema
|
||||||
|
const objectItemSchema = getObjectNestedSchema(fieldName, schema)
|
||||||
|
// no schema was found
|
||||||
|
if (!objectItemSchema) return null
|
||||||
|
return objectItemSchema
|
||||||
|
}
|
||||||
|
return arrayItemSchema
|
||||||
};
|
};
|
||||||
|
|
||||||
export const schemaProperties = [
|
export const schemaProperties = [
|
||||||
|
|
|
@ -8,7 +8,7 @@ Package.describe({
|
||||||
Package.onUse(function (api) {
|
Package.onUse(function (api) {
|
||||||
api.versionsFrom("1.6.1");
|
api.versionsFrom("1.6.1");
|
||||||
|
|
||||||
api.use(["vulcan:core@1.11.2", "fourseven:scss@4.5.0"]);
|
api.use(["vulcan:core@1.11.2", "vulcan:ui-bootstrap@1.11.2", "fourseven:scss@4.5.0"]);
|
||||||
|
|
||||||
api.addFiles(["lib/stylesheets/style.scss", "lib/stylesheets/datetime.scss"], "client");
|
api.addFiles(["lib/stylesheets/style.scss", "lib/stylesheets/datetime.scss"], "client");
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,32 @@
|
||||||
// setup JSDOM server side for testing (necessary for Enzyme to mount)
|
// setup JSDOM server side for testing (necessary for Enzyme to mount)
|
||||||
import 'jsdom-global/register'
|
import 'jsdom-global/register'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
// TODO: should be loaded from Components instead?
|
||||||
import Form from '../lib/components/Form'
|
import Form from '../lib/components/Form'
|
||||||
import FormNested from '../lib/components/FormNested'
|
import FormGroup from "../lib/components/FormGroup"
|
||||||
|
import FormComponent from "../lib/components/FormComponent"
|
||||||
|
import '../lib/components/FormNestedArray'
|
||||||
import expect from 'expect'
|
import expect from 'expect'
|
||||||
import Enzyme, { mount, shallow } from 'enzyme'
|
import Enzyme, { mount, shallow } from 'enzyme'
|
||||||
import Adapter from 'enzyme-adapter-react-16';
|
import Adapter from 'enzyme-adapter-react-16';
|
||||||
// we must import all the other components, so that "registerComponent" is called
|
import { Components } from "meteor/vulcan:core"
|
||||||
import "../lib/modules/components"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// setup enzyme
|
// setup enzyme
|
||||||
// TODO: write a reusable helper and move this to the tests setup
|
// TODO: write a reusable helper and move this to the tests setup
|
||||||
Enzyme.configure({ adapter: new Adapter() })
|
Enzyme.configure({ adapter: new Adapter() })
|
||||||
|
|
||||||
|
// we must import all the other components, so that "registerComponent" is called
|
||||||
|
import "../lib/modules/components"
|
||||||
|
// and then load them in the app so that <Component.Whatever /> is defined
|
||||||
|
import { populateComponentsApp, initializeFragments } from "meteor/vulcan:lib"
|
||||||
|
// we need registered fragments to be initialized because populateComponentsApp will run
|
||||||
|
// hocs, like withUpdate, that rely on fragments
|
||||||
|
initializeFragments()
|
||||||
|
// actually fills the Components object
|
||||||
|
populateComponentsApp()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// fixtures
|
// fixtures
|
||||||
import SimpleSchema from "simpl-schema";
|
import SimpleSchema from "simpl-schema";
|
||||||
const addressGroup = {
|
const addressGroup = {
|
||||||
|
@ -22,61 +34,52 @@ const addressGroup = {
|
||||||
label: "Addresses",
|
label: "Addresses",
|
||||||
order: 10
|
order: 10
|
||||||
};
|
};
|
||||||
const addressSchema = new SimpleSchema({
|
const addressSchema = {
|
||||||
street: {
|
street: {
|
||||||
type: String,
|
type: String,
|
||||||
optional: true,
|
optional: true,
|
||||||
viewableBy: ["guests"],
|
viewableBy: ["guests"],
|
||||||
editableBy: ["members"],
|
editableBy: ["quests"],
|
||||||
insertableBy: ["members"],
|
insertableBy: ["quests"],
|
||||||
max: 100 // limit street address to 100 characters
|
max: 100 // limit street address to 100 characters
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
const schema = {
|
const arraySchema = {
|
||||||
_id: {
|
|
||||||
type: String,
|
|
||||||
optional: true,
|
|
||||||
viewableBy: ["guests"]
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
type: Date,
|
|
||||||
optional: true,
|
|
||||||
onInsert: (document, currentUser) => {
|
|
||||||
return new Date();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
userId: {
|
|
||||||
type: String,
|
|
||||||
optional: true
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
optional: false,
|
|
||||||
viewableBy: ["guests"],
|
|
||||||
editableBy: ["members"],
|
|
||||||
insertableBy: ["members"],
|
|
||||||
searchable: true // make field searchable
|
|
||||||
},
|
|
||||||
addresses: {
|
addresses: {
|
||||||
type: Array,
|
type: Array,
|
||||||
viewableBy: ["guests"],
|
viewableBy: ["guests"],
|
||||||
editableBy: ["members"],
|
editableBy: ["quests"],
|
||||||
insertableBy: ["members"],
|
insertableBy: ["quests"],
|
||||||
group: addressGroup
|
group: addressGroup
|
||||||
},
|
},
|
||||||
"addresses.$": {
|
"addresses.$": {
|
||||||
type: addressSchema
|
type: new SimpleSchema(addressSchema)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const objectSchema = {
|
||||||
|
addresses: {
|
||||||
|
type: new SimpleSchema(addressSchema),
|
||||||
|
viewableBy: ["guests"],
|
||||||
|
editableBy: ["quests"],
|
||||||
|
insertableBy: ["quests"],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// stub collection
|
// stub collection
|
||||||
import { createCollection, getDefaultResolvers, getDefaultMutations } from 'meteor/vulcan:core'
|
import { createCollection, getDefaultResolvers, getDefaultMutations } from 'meteor/vulcan:core'
|
||||||
const Customers = createCollection({
|
const WithArrays = createCollection({
|
||||||
collectionName: 'Customers',
|
collectionName: 'WithArrays',
|
||||||
typeName: 'Customer',
|
typeName: 'WithArray',
|
||||||
schema,
|
schema: arraySchema,
|
||||||
resolvers: getDefaultResolvers('Customers'),
|
resolvers: getDefaultResolvers('WithArrays'),
|
||||||
mutations: getDefaultMutations('Customers'),
|
mutations: getDefaultMutations('WithArrays'),
|
||||||
|
});
|
||||||
|
const WithObjects = createCollection({
|
||||||
|
collectionName: 'WithObjects',
|
||||||
|
typeName: 'WithObject',
|
||||||
|
schema: objectSchema,
|
||||||
|
resolvers: getDefaultResolvers('WithObjects'),
|
||||||
|
mutations: getDefaultMutations('WithObjects'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const Addresses = createCollection({
|
const Addresses = createCollection({
|
||||||
|
@ -87,24 +90,26 @@ const Addresses = createCollection({
|
||||||
mutations: getDefaultMutations('Addresses'),
|
mutations: getDefaultMutations('Addresses'),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// tests
|
||||||
describe('vulcan-forms/components', function () {
|
describe('vulcan-forms/components', function () {
|
||||||
describe('Form', function () {
|
describe('Form', function () {
|
||||||
const mountWithContext = C => mount(C, {
|
const context = {
|
||||||
context: {
|
|
||||||
intl: {
|
|
||||||
formatMessage: () => "",
|
|
||||||
formatDate: () => ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const shallowWithContext = C => shallow(C, {
|
|
||||||
context: {
|
|
||||||
intl: {
|
intl: {
|
||||||
formatMessage: () => "",
|
formatMessage: () => "",
|
||||||
formatDate: () => "",
|
formatDate: () => "",
|
||||||
formatTime: () => ""
|
formatTime: () => "",
|
||||||
|
formatRelative: () => "",
|
||||||
|
formatNumber: () => "",
|
||||||
|
formatPlural: () => "",
|
||||||
|
formatHTMLMessage: () => ""
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
const mountWithContext = C => mount(C, {
|
||||||
|
context
|
||||||
|
})
|
||||||
|
const shallowWithContext = C => shallow(C, {
|
||||||
|
context
|
||||||
})
|
})
|
||||||
describe('basic', function () {
|
describe('basic', function () {
|
||||||
it('shallow render', function () {
|
it('shallow render', function () {
|
||||||
|
@ -112,19 +117,165 @@ describe('vulcan-forms/components', function () {
|
||||||
expect(wrapper).toBeDefined()
|
expect(wrapper).toBeDefined()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('nested forms', function () {
|
describe('nested array', function () {
|
||||||
it('shallow render', () => {
|
it('shallow render', () => {
|
||||||
const wrapper = shallowWithContext(<Form collection={Customers} />)
|
const wrapper = shallowWithContext(<Form collection={WithArrays} />)
|
||||||
expect(wrapper).toBeDefined()
|
expect(wrapper).toBeDefined()
|
||||||
})
|
})
|
||||||
|
it('render a FormGroup for addresses', function () {
|
||||||
|
const wrapper = shallowWithContext(<Form collection={WithArrays} />)
|
||||||
|
const formGroup = wrapper.find('FormGroup').find({ name: 'addresses' })
|
||||||
|
expect(formGroup).toBeDefined()
|
||||||
|
expect(formGroup).toHaveLength(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('nested object', function () {
|
||||||
|
it('shallow render', () => {
|
||||||
|
const wrapper = shallowWithContext(<Form collection={WithObjects} />)
|
||||||
|
expect(wrapper).toBeDefined()
|
||||||
|
})
|
||||||
|
it('define one field', () => {
|
||||||
|
const wrapper = shallowWithContext(<Form collection={WithObjects} />)
|
||||||
|
const defaultGroup = wrapper.find('FormGroup').first()
|
||||||
|
const fields = defaultGroup.prop('fields')
|
||||||
|
expect(fields).toHaveLength(1) // addresses field
|
||||||
|
})
|
||||||
|
|
||||||
|
const getFormFields = (wrapper) => {
|
||||||
|
const defaultGroup = wrapper.find('FormGroup').first()
|
||||||
|
const fields = defaultGroup.prop('fields')
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
const getFirstField = () => {
|
||||||
|
const wrapper = shallowWithContext(<Form collection={WithObjects} />)
|
||||||
|
const fields = getFormFields(wrapper)
|
||||||
|
return fields[0]
|
||||||
|
}
|
||||||
|
it('define the nestedSchema', () => {
|
||||||
|
const addressField = getFirstField()
|
||||||
|
expect(addressField.nestedSchema.street).toBeDefined()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('FormNested', function () {
|
describe('FormComponent', function () {
|
||||||
it('mount', function () {
|
const shallowWithContext = C => shallow(C, {
|
||||||
const wrapper = shallow(<FormNested path="foobar" currentValues={{}} />)
|
context: {
|
||||||
|
getDocument: () => { }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const defaultProps = {
|
||||||
|
"disabled": false,
|
||||||
|
"optional": true,
|
||||||
|
"document": {},
|
||||||
|
"name": "meetingPlace",
|
||||||
|
"path": "meetingPlace",
|
||||||
|
"datatype": [{ type: Object }],
|
||||||
|
"layout": "horizontal",
|
||||||
|
"label": "Meeting place",
|
||||||
|
"currentValues": {},
|
||||||
|
"formType": "new",
|
||||||
|
deletedValues: [],
|
||||||
|
throwError: () => { },
|
||||||
|
updateCurrentValues: () => { },
|
||||||
|
errors: [],
|
||||||
|
clearFieldErrors: () => { },
|
||||||
|
}
|
||||||
|
it('shallow render', function () {
|
||||||
|
const wrapper = shallowWithContext(<FormComponent {...defaultProps} />)
|
||||||
expect(wrapper).toBeDefined()
|
expect(wrapper).toBeDefined()
|
||||||
|
})
|
||||||
|
describe('nested array', function () {
|
||||||
|
const props = {
|
||||||
|
...defaultProps,
|
||||||
|
"datatype": [{ type: Array }],
|
||||||
|
"nestedSchema": {
|
||||||
|
"street": {},
|
||||||
|
"country": {},
|
||||||
|
"zipCode": {}
|
||||||
|
},
|
||||||
|
"nestedInput": true,
|
||||||
|
"nestedFields": [
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{}
|
||||||
|
],
|
||||||
|
"currentValues": {},
|
||||||
|
}
|
||||||
|
it('render a FormNestedArray', function () {
|
||||||
|
const wrapper = shallowWithContext(<FormComponent {...props} />)
|
||||||
|
const formNested = wrapper.find('FormNestedArray')
|
||||||
|
expect(formNested).toHaveLength(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('nested object', function () {
|
||||||
|
const props = {
|
||||||
|
...defaultProps,
|
||||||
|
"datatype": [{ type: new SimpleSchema({}) }],
|
||||||
|
"nestedSchema": {
|
||||||
|
"street": {},
|
||||||
|
"country": {},
|
||||||
|
"zipCode": {}
|
||||||
|
},
|
||||||
|
"nestedInput": true,
|
||||||
|
"nestedFields": [
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{}
|
||||||
|
],
|
||||||
|
"currentValues": {},
|
||||||
|
}
|
||||||
|
it('shallow render', function () {
|
||||||
|
const wrapper = shallowWithContext(<FormComponent {...props} />)
|
||||||
|
expect(wrapper).toBeDefined()
|
||||||
|
})
|
||||||
|
it('render a FormNestedObject', function () {
|
||||||
|
const wrapper = shallowWithContext(<FormComponent {...props} />)
|
||||||
|
const formNested = wrapper.find('FormNestedObject')
|
||||||
|
expect(formNested).toHaveLength(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('FormNestedArray', function () {
|
||||||
|
it('shallow render', function () {
|
||||||
|
const wrapper = shallow(<Components.FormNestedArray path="foobar" currentValues={{}} />)
|
||||||
|
expect(wrapper).toBeDefined()
|
||||||
|
})
|
||||||
|
it('shows a button', function () {
|
||||||
|
const wrapper = shallow(<Components.FormNestedArray path="foobar" currentValues={{}} />)
|
||||||
|
const button = wrapper.find('BootstrapButton')
|
||||||
|
expect(button).toHaveLength(1)
|
||||||
|
})
|
||||||
|
it('shows an add button', function () {
|
||||||
|
const wrapper = shallow(<Components.FormNestedArray path="foobar" currentValues={{}} />)
|
||||||
|
const addButton = wrapper.find('IconAdd')
|
||||||
|
expect(addButton).toHaveLength(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('FormNestedObject', function () {
|
||||||
|
it('shallow render', function () {
|
||||||
|
const wrapper = shallow(<Components.FormNestedObject path="foobar" currentValues={{}} />)
|
||||||
|
expect(wrapper).toBeDefined()
|
||||||
|
})
|
||||||
|
it.skip('render a form for the object', function () {
|
||||||
|
const wrapper = shallow(<Components.FormNestedObject path="foobar" currentValues={{}} />)
|
||||||
|
expect(false).toBe(true)
|
||||||
|
})
|
||||||
|
it('does not show any button', function () {
|
||||||
|
const wrapper = shallow(<Components.FormNestedObject path="foobar" currentValues={{}} />)
|
||||||
|
const button = wrapper.find('BootstrapButton')
|
||||||
|
expect(button).toHaveLength(0)
|
||||||
|
})
|
||||||
|
it('does not show add button', function () {
|
||||||
|
const wrapper = shallow(<Components.FormNestedObject path="foobar" currentValues={{}} />)
|
||||||
|
const addButton = wrapper.find('IconAdd')
|
||||||
|
expect(addButton).toHaveLength(0)
|
||||||
|
})
|
||||||
|
it('does not show remove button', function () {
|
||||||
|
const wrapper = shallow(<Components.FormNestedObject path="foobar" currentValues={{}} />)
|
||||||
|
const removeButton = wrapper.find('IconRemove')
|
||||||
|
expect(removeButton).toHaveLength(0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
|
@ -1,3 +1,2 @@
|
||||||
import './schema_utils.test.js'
|
import './schema_utils.test.js'
|
||||||
import './package.test.js'
|
|
||||||
import './components.test.js'
|
import './components.test.js'
|
|
@ -1,10 +1,51 @@
|
||||||
//import { convertSchema } from '../lib/modules/schema_utils.js'
|
import { convertSchema, getSchemaType, getNestedSchema } from '../lib/modules/schema_utils.js'
|
||||||
|
import SimpleSchema from 'simpl-schema'
|
||||||
import expect from 'expect'
|
import expect from 'expect'
|
||||||
|
|
||||||
|
const addressSchema = {
|
||||||
|
street: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
country: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const addressSimpleSchema = new SimpleSchema(addressSchema)
|
||||||
|
|
||||||
describe('schema_utils', function () {
|
describe('schema_utils', function () {
|
||||||
describe('convertSchema', function () {
|
describe('getNestedSchema', function () {
|
||||||
it('run a test', function () {
|
it('get nested schema of an array', function () {
|
||||||
expect(true).toBe(true)
|
const simpleSchema = new SimpleSchema({
|
||||||
|
addresses: {
|
||||||
|
type: Array
|
||||||
|
},
|
||||||
|
"addresses.$": {
|
||||||
|
// this is due to SimpleSchema objects structure
|
||||||
|
type: addressSimpleSchema
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const nestedSchema = getNestedSchema('addresses', simpleSchema)
|
||||||
|
// nestedSchema is a complex SimpleSchema object, so we can only
|
||||||
|
// test its type instead (might not be the simplest way though)
|
||||||
|
expect(Object.keys(nestedSchema._schema)).toEqual(Object.keys(addressSchema))
|
||||||
|
})
|
||||||
|
it('get nested schema of an object', function () {
|
||||||
|
const simpleSchema = new SimpleSchema({
|
||||||
|
meetingPlace: {
|
||||||
|
type: addressSimpleSchema
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const nestedSchema = getNestedSchema('meetingPlace', simpleSchema)
|
||||||
|
expect(Object.keys(nestedSchema._schema)).toEqual(Object.keys(addressSchema))
|
||||||
|
})
|
||||||
|
it('return null for other types', function () {
|
||||||
|
const simpleSchema = new SimpleSchema({
|
||||||
|
createdAt: {
|
||||||
|
type: Date
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const nestedSchema = getNestedSchema('createdAt', simpleSchema)
|
||||||
|
expect(nestedSchema).toBeNull()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
Loading…
Add table
Reference in a new issue