start splitting code, reuse options handler

This commit is contained in:
Eric Burel 2018-10-29 22:08:41 +01:00
parent 6114ee9da6
commit 284cdbae18
23 changed files with 278 additions and 229 deletions

View file

@ -30,7 +30,7 @@ import React, { Component } from 'react';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import { createClientTemplate } from 'meteor/vulcan:core';
import { extractCollectionInfo, extractFragmentInfo } from './handleOptions';
import { extractCollectionInfo, extractFragmentInfo } from 'meteor/vulcan:lib';
const withCreate = options => {
const { collectionName, collection } = extractCollectionInfo(options);

View file

@ -30,7 +30,7 @@ import React, { Component } from 'react';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import { deleteClientTemplate } from 'meteor/vulcan:core';
import { extractCollectionInfo, extractFragmentInfo } from './handleOptions';
import { extractCollectionInfo, extractFragmentInfo } from 'meteor/vulcan:lib';
const withDelete = options => {
const { collectionName, collection } = extractCollectionInfo(options);

View file

@ -38,14 +38,12 @@ import React, { Component } from 'react';
import { withApollo, graphql } from 'react-apollo';
import gql from 'graphql-tag';
import update from 'immutability-helper';
import { getSetting, Utils, multiClientTemplate } from 'meteor/vulcan:lib';
import { getSetting, Utils, multiClientTemplate, extractCollectionInfo, extractFragmentInfo } from 'meteor/vulcan:lib';
import Mingo from 'mingo';
import compose from 'recompose/compose';
import withState from 'recompose/withState';
import find from 'lodash/find';
import { extractCollectionInfo, extractFragmentInfo } from './handleOptions';
export default function withMulti(options) {
// console.log(options)
@ -172,8 +170,8 @@ export default function withMulti(options) {
typeof providedTerms === 'undefined'
? {
/*...props.ownProps.terms,*/ ...props.ownProps.paginationTerms,
limit: results.length + props.ownProps.paginationTerms.itemsPerPage
}
limit: results.length + props.ownProps.paginationTerms.itemsPerPage
}
: providedTerms;
props.ownProps.setPaginationTerms(newTerms);

View file

@ -2,9 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import { getSetting, singleClientTemplate, Utils } from 'meteor/vulcan:lib';
import { extractCollectionInfo, extractFragmentInfo } from './handleOptions';
import { getSetting, singleClientTemplate, Utils, extractCollectionInfo, extractFragmentInfo } from 'meteor/vulcan:lib';
export default function withSingle(options) {
const { pollInterval = getSetting('pollInterval', 20000), enableCache = false, extraQueries } = options;

View file

@ -30,11 +30,9 @@ Child Props:
import React, { Component } from 'react';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import { updateClientTemplate } from 'meteor/vulcan:lib';
import { updateClientTemplate, extractCollectionInfo, extractFragmentInfo } from 'meteor/vulcan:lib';
import clone from 'lodash/clone';
import { extractCollectionInfo, extractFragmentInfo } from './handleOptions';
const withUpdate = options => {
const { collectionName, collection } = extractCollectionInfo(options);
const { fragmentName, fragment } = extractFragmentInfo(options, collectionName);

View file

@ -33,7 +33,7 @@ import gql from 'graphql-tag';
import { upsertClientTemplate } from 'meteor/vulcan:core';
import clone from 'lodash/clone';
import { extractCollectionInfo, extractFragmentInfo } from './handleOptions';
import { extractCollectionInfo, extractFragmentInfo } from 'meteor/vulcan:lib';
const withUpsert = options => {
const { collectionName, collection } = extractCollectionInfo(options);

View file

@ -1,46 +0,0 @@
import { extractCollectionInfo, extractFragmentInfo } from '../lib/modules/containers/handleOptions';
import expect from 'expect';
describe('vulcan-core/containers', function() {
describe('handleOptions', function() {
const expectedCollectionName = 'COLLECTION_NAME';
const expectedCollection = { options: { collectionName: expectedCollectionName } };
it('get collectionName from collection', function() {
const options = { collection: expectedCollection };
const { collection, collectionName } = extractCollectionInfo(options);
expect(collection).toEqual(expectedCollection);
expect(collectionName).toEqual(expectedCollectionName);
});
it.skip('get collection from collectionName', function() {
// TODO: mock getCollection
const options = { collectionName: expectedCollectionName };
const { collection, collectionName } = extractCollectionInfo(options);
expect(collection).toEqual(expectedCollection);
expect(collectionName).toEqual(expectedCollectionName);
});
const expectedFragmentName = 'FRAGMENT_NAME';
const expectedFragment = { definitions: [{ name: { value: expectedFragmentName } }] };
it.skip('get fragment from fragmentName', function() {
// TODO: mock getCollection
const options = { fragmentName: expectedFragmentName };
const { fragment, fragmentName } = extractFragmentInfo(options);
expect(fragment).toEqual(expectedFragment);
expect(fragmentName).toEqual(expectedFragmentName);
});
it('get fragmentName from fragment', function() {
const options = { fragment: expectedFragment };
const { fragment, fragmentName } = extractFragmentInfo(options);
expect(fragment).toEqual(expectedFragment);
expect(fragmentName).toEqual(expectedFragmentName);
});
it.skip('get fragmentName and fragment from collectionName', function() {
// TODO: mock getFragment
// if options does not contain fragment, we get the collection default fragment based on its name
const options = {};
const { fragment, fragmentName } = extractFragmentInfo(options, expectedCollectionName);
expect(fragment).toEqual(expectedFragment);
expect(fragmentName).toEqual(expectedFragmentName);
});
});
});

View file

@ -1 +1 @@
import './containers.test.js';
//import './containers.test.js';

View file

@ -37,7 +37,6 @@ import SimpleSchema from 'simpl-schema';
import PropTypes from 'prop-types';
import { intlShape } from 'meteor/vulcan:i18n';
import Formsy from 'formsy-react';
import { getEditableFields, getInsertableFields } from '../modules/utils.js';
import cloneDeep from 'lodash/cloneDeep';
import get from 'lodash/get';
import set from 'lodash/set';
@ -59,6 +58,11 @@ import { convertSchema, formProperties } from '../modules/schema_utils';
import { isEmptyValue } from '../modules/utils';
import { getParentPath } from '../modules/path_utils';
import mergeWithComponents from '../modules/mergeWithComponents';
import {
getEditableFields,
getInsertableFields
} from '../modules/fieldsUtils.js';
import withCollectionProps from './withCollectionProps';
const compactParent = (object, path) => {
const parentPath = getParentPath(path);
@ -1055,19 +1059,9 @@ class SmartForm extends Component {
SmartForm.propTypes = {
// main options
collection: PropTypes.object,
collectionName: (props, propName, componentName) => {
if (!props.collection && !props.collectionName) {
return new Error(
`One of props 'collection' or 'collectionName' was not specified in '${componentName}'.`
);
}
if (!props.collection && typeof props['collectionName'] !== 'string') {
return new Error(
`Prop collectionName was not of type string in '${componentName}`
);
}
},
collection: PropTypes.object.isRequired,
collectionName: PropTypes.string.isRequired,
typeName: PropTypes.string.isRequired,
document: PropTypes.object, // if a document is passed, this will be an edit form
schema: PropTypes.object, // usually not needed
@ -1137,4 +1131,8 @@ SmartForm.childContextTypes = {
module.exports = SmartForm;
registerComponent('Form', SmartForm);
registerComponent({
name: 'Form',
component: SmartForm,
hocs: [withCollectionProps]
});

View file

@ -37,12 +37,18 @@ import {
withNew,
withUpdate,
withDelete,
getFragment,
getCollection
getFragment
} from 'meteor/vulcan:core';
import gql from 'graphql-tag';
import { withSingle } from 'meteor/vulcan:core';
import { graphql } from 'react-apollo';
import {
getReadableFields,
getCreateableFields,
getUpdateableFields
} from '../modules/schema_utils';
import withCollectionProps from './withCollectionProps';
class FormWrapper extends PureComponent {
constructor(props) {
@ -51,16 +57,11 @@ class FormWrapper extends PureComponent {
// see https://reactjs.org/docs/higher-order-components.html#dont-use-hocs-inside-the-render-method
this.FormComponent = this.getComponent(props);
}
getCollection() {
return this.props.collection || getCollection(this.props.collectionName);
}
// return the current schema based on either the schema or collection prop
getSchema() {
return this.props.schema
? this.props.schema
: this.getCollection().simpleSchema()._schema;
: this.props.collection.simpleSchema()._schema;
}
// if a document is being passed, this is an edit form
@ -68,49 +69,18 @@ class FormWrapper extends PureComponent {
return this.props.documentId || this.props.slug ? 'edit' : 'new';
}
// filter out fields with "." or "$"
getValidFields() {
return Object.keys(this.getSchema()).filter(
fieldName => !fieldName.includes('$') && !fieldName.includes('.')
);
}
getReadableFields() {
const schema = this.getSchema();
// OpenCRUD backwards compatibility
return this.getValidFields().filter(
fieldName => schema[fieldName].canRead || schema[fieldName].viewableBy
);
}
getCreateableFields() {
const schema = this.getSchema();
// OpenCRUD backwards compatibility
return this.getValidFields().filter(
fieldName => schema[fieldName].canCreate || schema[fieldName].insertableBy
);
}
getUpdatetableFields() {
const schema = this.getSchema();
// OpenCRUD backwards compatibility
return this.getValidFields().filter(
fieldName => schema[fieldName].canUpdate || schema[fieldName].editableBy
);
}
// get fragment used to decide what data to load from the server to populate the form,
// as well as what data to ask for as return value for the mutation
getFragments() {
const prefix = `${this.getCollection()._name}${Utils.capitalize(
const prefix = `${this.props.collectionName}${Utils.capitalize(
this.getFormType()
)}`;
const fragmentName = `${prefix}FormFragment`;
const fields = this.props.fields;
const readableFields = this.getReadableFields();
const createableFields = this.getCreateableFields();
const updatetableFields = this.getUpdatetableFields();
const readableFields = getReadableFields(this.getSchema());
const createableFields = getCreateableFields(this.getSchema());
const updatetableFields = getUpdateableFields(this.getSchema());
// get all editable/insertable fields (depending on current form type)
let queryFields =
@ -133,7 +103,7 @@ class FormWrapper extends PureComponent {
// generate query fragment based on the fields that can be edited. Note: always add _id.
const generatedQueryFragment = gql`
fragment ${fragmentName} on ${this.getCollection().typeName} {
fragment ${fragmentName} on ${this.props.typeName} {
_id
${queryFields.map(convertFields).join('\n')}
}
@ -141,7 +111,7 @@ class FormWrapper extends PureComponent {
// generate mutation fragment based on the fields that can be edited and/or viewed. Note: always add _id.
const generatedMutationFragment = gql`
fragment ${fragmentName} on ${this.getCollection().typeName} {
fragment ${fragmentName} on ${this.props.typeName} {
_id
${mutationFields.map(convertFields).join('\n')}
}
@ -196,7 +166,7 @@ class FormWrapper extends PureComponent {
getComponent() {
let WrappedComponent;
const prefix = `${this.getCollection()._name}${Utils.capitalize(
const prefix = `${this.props.collectionName}${Utils.capitalize(
this.getFormType()
)}`;
@ -215,7 +185,7 @@ class FormWrapper extends PureComponent {
// options for withSingle HoC
const queryOptions = {
queryName: `${prefix}FormQuery`,
collection: this.getCollection(),
collection: this.props.collection,
fragment: queryFragment,
extraQueries,
fetchPolicy: 'network-only', // we always want to load a fresh copy of the document
@ -225,7 +195,7 @@ class FormWrapper extends PureComponent {
// options for withNew, withUpdate, and withDelete HoCs
const mutationOptions = {
collection: this.getCollection(),
collection: this.props.collection,
fragment: mutationFragment
};
@ -303,19 +273,10 @@ class FormWrapper extends PureComponent {
FormWrapper.propTypes = {
// main options
collection: PropTypes.object,
collectionName: (props, propName, componentName) => {
if (!props.collection && !props.collectionName) {
return new Error(
`One of props 'collection' or 'collectionName' was not specified in '${componentName}'.`
);
}
if (!props.collection && typeof props['collectionName'] !== 'string') {
return new Error(
`Prop collectionName was not of type string in '${componentName}`
);
}
},
collection: PropTypes.object.isRequired,
collectionName: PropTypes.string.isRequired,
typeName: PropTypes.string.isRequired,
documentId: PropTypes.string, // if a document is passed, this will be an edit form
schema: PropTypes.object, // usually not needed
queryFragment: PropTypes.object,
@ -362,10 +323,8 @@ FormWrapper.contextTypes = {
intl: intlShape
};
registerComponent(
'SmartForm',
FormWrapper,
withCurrentUser,
withApollo,
withRouter
);
registerComponent({
name: 'SmartForm',
component: FormWrapper,
hocs: [withCurrentUser, withApollo, withRouter, withCollectionProps]
});

View file

@ -33,3 +33,5 @@ const groupProps = {
order: PropTypes.number,
fields: PropTypes.arrayOf(PropTypes.shape(fieldProps))
};
const callbacksProps = {};

View file

@ -0,0 +1,31 @@
import React from 'react';
import { extractCollectionInfo } from 'meteor/vulcan:lib';
import PropTypes from 'prop-types';
/**
* Handle the collection or collectionName and pass down other related
* props (typeName, collectionName, etc.)
*/
const withCollectionProps = C => {
const CollectionPropsWrapper = ({ collection: _collection, collectionName: _collectionName, ...otherProps }) => {
const { collection, collectionName } = extractCollectionInfo({
collection: _collection,
collectionName: _collectionName
});
const typeName = collection.options.typeName;
return <C {...otherProps} collection={collection} collectionName={collectionName} typeName={typeName} />;
};
CollectionPropsWrapper.propTypes = {
collection: PropTypes.object,
collectionName: (props, propName, componentName) => {
if (!props.collection && !props.collectionName) {
return new Error(`One of props 'collection' or 'collectionName' was not specified in '${componentName}'.`);
}
if (!props.collection && typeof props['collectionName'] !== 'string') {
return new Error(`Prop collectionName was not of type string in '${componentName}`);
}
}
};
return CollectionPropsWrapper;
};
export default withCollectionProps;

View file

@ -0,0 +1,26 @@
import Users from 'meteor/vulcan:users';
/**
* @method Mongo.Collection.getInsertableFields
* Get an array of all fields editable by a specific user for a given collection
* @param {Object} user the user for which to check field permissions
*/
export const getInsertableFields = function(schema, user) {
const fields = _.filter(_.keys(schema), function(fieldName) {
var field = schema[fieldName];
return Users.canCreateField(user, field);
});
return fields;
};
/**
* @method Mongo.Collection.getEditableFields
* Get an array of all fields editable by a specific user for a given collection (and optionally document)
* @param {Object} user the user for which to check field permissions
*/
export const getEditableFields = function(schema, user, document) {
const fields = _.filter(_.keys(schema), function(fieldName) {
var field = schema[fieldName];
return Users.canUpdateField(user, field, document);
});
return fields;
};

View file

@ -1,3 +1,28 @@
/**
* Schema converter/getters
* @param {*} schema
*/
// filter out fields with "." or "$"
export const getValidFields = schema => {
return Object.keys(schema).filter(fieldName => !fieldName.includes('$') && !fieldName.includes('.'));
};
export const getReadableFields = schema => {
// OpenCRUD backwards compatibility
return getValidFields(schema).filter(fieldName => schema[fieldName].canRead || schema[fieldName].viewableBy);
};
export const getCreateableFields = schema => {
// OpenCRUD backwards compatibility
return getValidFields(schema).filter(fieldName => schema[fieldName].canCreate || schema[fieldName].insertableBy);
};
export const getUpdateableFields = schema => {
// OpenCRUD backwards compatibility
return getValidFields(schema).filter(fieldName => schema[fieldName].canUpdate || schema[fieldName].editableBy);
};
/*
Convert a nested SimpleSchema schema into a JSON object
@ -27,7 +52,7 @@ export const convertSchema = (schema, flatten = false) => {
// or a schema on its own with subfields (convertedSchema will return smth)
if (!convertedSubSchema) {
// subSchema is a simple field in this case (eg array of numbers)
jsonSchema[fieldName].field = getFieldSchema(`${fieldName}.$`, schema)
jsonSchema[fieldName].field = getFieldSchema(`${fieldName}.$`, schema);
} else {
// subSchema is a full schema with multiple fields (eg array of objects)
if (flatten) {
@ -35,7 +60,6 @@ export const convertSchema = (schema, flatten = false) => {
} else {
jsonSchema[fieldName].schema = convertedSubSchema;
}
}
}
});
@ -61,44 +85,43 @@ export const getFieldSchema = (fieldName, schema) => {
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
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
}
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)
const isNestedSchemaField = fieldSchema => {
const fieldType = getSchemaType(fieldSchema);
//console.log('fieldType', typeof fieldType, fieldType._schema)
return 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
}
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
If the field is not an object, this will return the subfield type instead
*/
export const getNestedFieldSchemaOrType = (fieldName, schema) => {
const arrayItemSchema = getArrayNestedSchema(fieldName, schema)
const arrayItemSchema = getArrayNestedSchema(fieldName, schema);
if (!arrayItemSchema) {
// look for an object schema
const objectItemSchema = getObjectNestedSchema(fieldName, schema)
const objectItemSchema = getObjectNestedSchema(fieldName, schema);
// no schema was found
if (!objectItemSchema) return null
return objectItemSchema
if (!objectItemSchema) return null;
return objectItemSchema;
}
return arrayItemSchema
return arrayItemSchema;
};
export const schemaProperties = [
@ -147,7 +170,7 @@ export const schemaProperties = [
'options',
'query',
'fieldProperties',
'intl',
'intl'
];
export const formProperties = [
@ -179,5 +202,5 @@ export const formProperties = [
'placeholder',
'options',
'query',
'fieldProperties',
'fieldProperties'
];

View file

@ -1,4 +1,3 @@
import Users from 'meteor/vulcan:users';
import merge from 'lodash/merge';
import find from 'lodash/find';
import isPlainObject from 'lodash/isPlainObject';
@ -8,10 +7,10 @@ import size from 'lodash/size';
import { removePrefix, filterPathsByPrefix } from './path_utils';
// add support for nested properties
export const deepValue = function(obj, path){
export const deepValue = function(obj, path) {
const pathArray = path.split('.');
for (var i=0; i < pathArray.length; i++) {
for (var i = 0; i < pathArray.length; i++) {
obj = obj[pathArray[i]];
}
@ -21,56 +20,27 @@ export const deepValue = function(obj, path){
// see http://stackoverflow.com/questions/19098797/fastest-way-to-flatten-un-flatten-nested-json-objects
export const flatten = function(data) {
var result = {};
function recurse (cur, prop) {
function recurse(cur, prop) {
if (Object.prototype.toString.call(cur) !== '[object Object]') {
result[prop] = cur;
} else if (Array.isArray(cur)) {
for(var i=0, l=cur.length; i<l; i++)
recurse(cur[i], prop + '[' + i + ']');
if (l == 0)
result[prop] = [];
for (var i = 0, l = cur.length; i < l; i++) recurse(cur[i], prop + '[' + i + ']');
if (l == 0) result[prop] = [];
} else {
var isEmpty = true;
for (var p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop+'.'+p : p);
recurse(cur[p], prop ? prop + '.' + p : p);
}
if (isEmpty && prop)
result[prop] = {};
if (isEmpty && prop) result[prop] = {};
}
}
recurse(data, '');
return result;
}
/**
* @method Mongo.Collection.getInsertableFields
* Get an array of all fields editable by a specific user for a given collection
* @param {Object} user the user for which to check field permissions
*/
export const getInsertableFields = function (schema, user) {
const fields = _.filter(_.keys(schema), function (fieldName) {
var field = schema[fieldName];
return Users.canCreateField(user, field);
});
return fields;
};
/**
* @method Mongo.Collection.getEditableFields
* Get an array of all fields editable by a specific user for a given collection (and optionally document)
* @param {Object} user the user for which to check field permissions
*/
export const getEditableFields = function (schema, user, document) {
const fields = _.filter(_.keys(schema), function (fieldName) {
var field = schema[fieldName];
return Users.canUpdateField(user, field, document);
});
return fields;
};
export const isEmptyValue = value => (typeof value === 'undefined' || value === null || value === '' || Array.isArray(value) && value.length === 0);
export const isEmptyValue = value =>
typeof value === 'undefined' || value === null || value === '' || (Array.isArray(value) && value.length === 0);
/**
* Merges values. It takes into account the current, original and deleted values,
@ -85,14 +55,7 @@ export const isEmptyValue = value => (typeof value === 'undefined' || value ===
* @return {*|undefined}
* Merged value or undefined if no merge was performed
*/
export const mergeValue = ({
currentValue,
documentValue,
deletedValues: deletedFields,
path,
locale,
datatype,
}) => {
export const mergeValue = ({ currentValue, documentValue, deletedValues: deletedFields, path, locale, datatype }) => {
if (locale) {
// note: intl fields are of type Object but should be treated as Strings
return currentValue || documentValue || '';
@ -132,10 +95,7 @@ export const mergeValue = ({
* // => { 'field': { 'subField': null, 'subFieldArray': [null] }, 'fieldArray': [null, undefined, { name: null } }
*/
export const getDeletedValues = (deletedFields, accumulator = {}) =>
deletedFields.reduce(
(deletedValues, path) => set(deletedValues, path, null),
accumulator,
);
deletedFields.reduce((deletedValues, path) => set(deletedValues, path, null), accumulator);
/**
* Filters the given field names by prefix, removes it from each one of them
@ -164,16 +124,13 @@ export const getDeletedValues = (deletedFields, accumulator = {}) =>
* // => [null, undefined, { 'name': null } ]
*/
export const getNestedDeletedValues = (prefix, deletedFields, accumulator = {}) =>
getDeletedValues(
removePrefix(prefix, filterPathsByPrefix(prefix, deletedFields)),
accumulator,
);
getDeletedValues(removePrefix(prefix, filterPathsByPrefix(prefix, deletedFields)), accumulator);
export const getFieldType = datatype => datatype[0].type;
/**
* Get appropriate null value for various field types
*
* @param {Array} datatype
*
* @param {Array} datatype
* Field's datatype property
*/
export const getNullValue = datatype => {
@ -190,4 +147,4 @@ export const getNullValue = datatype => {
// normalize to null
return null;
}
}
};

View file

@ -1,3 +1,5 @@
import './schema_utils.test.js';
import './components.test.js';
import './mergeWithComponents.test.js';
import './fieldUtils.test.js';

View file

@ -1,4 +1,10 @@
import { getNestedFieldSchemaOrType } from '../lib/modules/schema_utils.js';
import {
getNestedFieldSchemaOrType,
getValidFields,
getCreateableFields,
getReadableFields,
getUpdateableFields
} from '../lib/modules/schema_utils.js';
import SimpleSchema from 'simpl-schema';
import expect from 'expect';
@ -48,4 +54,44 @@ describe('schema_utils', function() {
expect(nestedSchema).toBeNull();
});
});
describe('fields extraction', function() {
describe('valid', function() {
it('remove invalid fields', function() {
const schema = {
validField: {},
arrayField: {},
// array child
'arrayField.$': {}
};
expect(getValidFields(schema)).toEqual(['validField', 'arrayField']);
});
});
describe('readable', function() {
it('get readable field', function() {
const schema = {
readable: { canRead: [] },
notReadble: {}
};
expect(getReadableFields(schema)).toEqual(['readable']);
});
});
describe('creatable', function() {
it('get creatable field', function() {
const schema = {
creatable: { canCreate: [] },
notCreatable: {}
};
expect(getCreateableFields(schema)).toEqual(['creatable']);
});
});
describe('updatable', function() {
it('get updatable field', function() {
const schema = {
updatable: { canUpdate: [] },
notUpdatable: {}
};
expect(getUpdateableFields(schema)).toEqual(['updatable']);
});
});
});
});

View file

@ -1,4 +1,10 @@
import { getFragment, getCollection, getFragmentName } from 'meteor/vulcan:core';
/** Helpers to get values depending on name
* E.g. retrieving a collection and its name when only one value is provided
*
*/
import { getCollection } from './collections';
import { getFragment, getFragmentName } from './fragments';
/**
* Extract collectionName from collection
* or collection from collectionName

View file

@ -30,4 +30,5 @@ export * from './intl.js';
export * from './detect_locale.js';
export * from './graphql_templates.js';
export * from './validation.js';
export * from './handleOptions';
// export * from './resolvers.js';

View file

@ -2,7 +2,7 @@ Package.describe({
name: 'vulcan:lib',
summary: 'Vulcan libraries.',
version: '1.12.8',
git: 'https://github.com/VulcanJS/Vulcan.git',
git: 'https://github.com/VulcanJS/Vulcan.git'
});
Package.onUse(function(api) {
@ -46,7 +46,7 @@ Package.onUse(function(api) {
// 'aldeed:collection2-core@2.0.0',
'meteorhacks:picker@1.0.3',
'percolatestudio:synced-cron@1.1.0',
'meteorhacks:inject-initial@1.0.4',
'meteorhacks:inject-initial@1.0.4'
];
api.use(packages);
@ -58,3 +58,8 @@ Package.onUse(function(api) {
api.mainModule('lib/server/main.js', 'server');
api.mainModule('lib/client/main.js', 'client');
});
Package.onTest(function(api) {
api.use(['ecmascript', 'meteortesting:mocha']);
api.mainModule('./test/index.js');
});

View file

@ -0,0 +1,44 @@
import { extractCollectionInfo, extractFragmentInfo } from '../lib/modules/containers/handleOptions';
import expect from 'expect';
describe('vulcan:lib/handleOptions', function() {
const expectedCollectionName = 'COLLECTION_NAME';
const expectedCollection = { options: { collectionName: expectedCollectionName } };
it('get collectionName from collection', function() {
const options = { collection: expectedCollection };
const { collection, collectionName } = extractCollectionInfo(options);
expect(collection).toEqual(expectedCollection);
expect(collectionName).toEqual(expectedCollectionName);
});
it.skip('get collection from collectionName', function() {
// TODO: mock getCollection
const options = { collectionName: expectedCollectionName };
const { collection, collectionName } = extractCollectionInfo(options);
expect(collection).toEqual(expectedCollection);
expect(collectionName).toEqual(expectedCollectionName);
});
const expectedFragmentName = 'FRAGMENT_NAME';
const expectedFragment = { definitions: [{ name: { value: expectedFragmentName } }] };
it.skip('get fragment from fragmentName', function() {
// TODO: mock getCollection
const options = { fragmentName: expectedFragmentName };
const { fragment, fragmentName } = extractFragmentInfo(options);
expect(fragment).toEqual(expectedFragment);
expect(fragmentName).toEqual(expectedFragmentName);
});
it('get fragmentName from fragment', function() {
const options = { fragment: expectedFragment };
const { fragment, fragmentName } = extractFragmentInfo(options);
expect(fragment).toEqual(expectedFragment);
expect(fragmentName).toEqual(expectedFragmentName);
});
it.skip('get fragmentName and fragment from collectionName', function() {
// TODO: mock getFragment
// if options does not contain fragment, we get the collection default fragment based on its name
const options = {};
const { fragment, fragmentName } = extractFragmentInfo(options, expectedCollectionName);
expect(fragment).toEqual(expectedFragment);
expect(fragmentName).toEqual(expectedFragmentName);
});
});

View file

@ -0,0 +1 @@
import './handleOptions.test.js';