Skip to content
This repository was archived by the owner on Jun 21, 2019. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"clean": "rimraf lib",
"lint": "eslint src test",
"prepublish": "npm run test && npm run build",
"test": "npm run lint && babel-node test/index",
"test": "npm run lint && babel-node node_modules/.bin/check-compose src/utils/compose.js && babel-node test/index",
"transpile": "babel src --out-dir lib"
},
"repository": {
Expand All @@ -36,6 +36,7 @@
"homepage": "https://github.com/stampit-org/react-stamp#readme",
"dependencies": {
"lodash": "^4.6.1",
"stamp-specification": "^1.0.5",
"stamp-utils": "^1.2.4"
},
"devDependencies": {
Expand All @@ -45,6 +46,7 @@
"babel-loader": "^6.2.4",
"babel-preset-es2015": "^6.6.0",
"babel-preset-stage-2": "^6.5.0",
"check-compose": "^1.0.0",
"eslint": "~2.2.0",
"react": "^0.14.7",
"react-addons-test-utils": "^0.14.7",
Expand Down
98 changes: 62 additions & 36 deletions src/utils/compose.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
import assign from 'lodash/assign';
import forEach from 'lodash/forEach';
import merge from 'lodash/merge';
import { isStamp } from 'stamp-utils';
import isFunction from 'lodash/isFunction';
import isObject from 'lodash/isObject';
import mergeWith from 'lodash/mergeWith';
//import compose from 'stamp-specification';

import {
parseDesc,
wrapMethods,
} from './';

const isDescriptor = isObject;
const merge = (dst, src) => mergeWith(dst, src, (dstValue, srcValue) => {
if (Array.isArray(dstValue)) {
if (Array.isArray(srcValue)) return dstValue.concat(srcValue);
if (isObject(srcValue)) return merge({}, srcValue);
}

return undefined;
});

/**
* Given a description object, return a stamp aka composable.
*
* (desc?: SpecDesc) => Stamp
*/
function createStamp (specDesc = {}) {
function createStamp (specDesc = {}, composeFunction) {
const Component = (options, ...args) => {
let instance = Object.create(specDesc.methods || {});

Expand All @@ -32,52 +43,67 @@ function createStamp (specDesc = {}) {
return instance;
};

merge(Component, specDesc.deepStaticProperties);
merge(Component, specDesc.staticDeepProperties);
assign(Component, specDesc.staticProperties);
Object.defineProperties(Component, specDesc.staticPropertyDescriptors || {});

!Component.displayName && (Component.displayName = 'Component');

const composeImplementation = isFunction(Component.compose) ? Component.compose : composeFunction;
Component.compose = function () {
return composeImplementation.apply(this, arguments);
};
assign(Component.compose, specDesc);

return Component;
}

/**
* Take any number of stamps or descriptors. Return a new stamp
* that encapsulates combined behavior. If nothing is passed in,
* an empty stamp is returned.
*
* (...args?: Stamp|ReactDesc|SpecDesc[]) => Stamp
* Mutates the dstDescriptor by merging the srcComposable data into it.
* @param {object} dstDescriptor The descriptor object to merge into.
* @param {object} [srcComposable] The composable (either descriptor or stamp) to merge data form.
* @returns {object} Returns the dstDescriptor argument.
*/
export default function compose (...args) {
const descs = args.map(arg => parseDesc(arg));
const compDesc = {};

isStamp(this) && descs.unshift(this.compose);

forEach(descs, desc => {
const {
initializers, methods, properties, staticProperties, propertyDescriptors,
staticPropertyDescriptors, deepProperties, deepStaticProperties, configuration,
} = desc;
function mergeComposable (dstDescriptor, srcComposable) {
const srcDescriptor = parseDesc(srcComposable && srcComposable.compose || srcComposable);
if (!isDescriptor(srcDescriptor)) return dstDescriptor;

const combineProperty = (propName, action) => {
if (!isObject(srcDescriptor[propName])) return;
if (!isObject(dstDescriptor[propName])) dstDescriptor[propName] = {};
action(dstDescriptor[propName], srcDescriptor[propName]);
};

// Wrap React lifecycle methods
compDesc.methods = wrapMethods(compDesc.methods, methods);
console.log(dstDescriptor.methods);
combineProperty('methods', wrapMethods); // Wrap React lifecycle methods

// Stamp spec
compDesc.initializers = (compDesc.initializers || []).concat(initializers)
.filter(initializer => typeof initializer === 'function');
//console.log(dstDescriptor.methods);

forEach({ properties, staticProperties, propertyDescriptors, staticPropertyDescriptors },
(val, key) => val && (compDesc[key] = assign(compDesc[key] || {}, val))
);
combineProperty('properties', assign);
combineProperty('deepProperties', merge);
combineProperty('propertyDescriptors', assign);
combineProperty('staticProperties', assign);
combineProperty('staticDeepProperties', merge);
combineProperty('staticPropertyDescriptors', assign);
combineProperty('configuration', assign);
combineProperty('deepConfiguration', merge);
if (Array.isArray(srcDescriptor.initializers)) {
if (!Array.isArray(dstDescriptor.initializers)) dstDescriptor.initializers = [];
dstDescriptor.initializers.push.apply(dstDescriptor.initializers, srcDescriptor.initializers.filter(isFunction));
}

forEach({ deepProperties, deepStaticProperties, configuration },
(val, key) => val && (compDesc[key] = merge(compDesc[key] || {}, val))
);
});
//console.log(dstDescriptor);

const stamp = createStamp(compDesc);
stamp.compose = assign(compose.bind(stamp), compDesc);
return dstDescriptor;
}

return stamp;
/**
* Given the list of composables (stamp descriptors and stamps) returns a new stamp (composable factory function).
* @param {...(object|Function)} [composables] The list of composables.
* @returns {Function} A new stamp (aka composable factory function).
*/
export default function compose (...composables) {
const descriptor = [this].concat(composables).filter(isObject).reduce(mergeComposable, {});
//console.log(descriptor);
return createStamp(descriptor, compose);
}
35 changes: 25 additions & 10 deletions src/utils/descriptor.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
import forEach from 'lodash/forEach';
import isEmpty from 'lodash/isEmpty';
import { isDescriptor, isStamp } from 'stamp-utils';
import has from 'lodash/has';

function isSpecDescriptor (obj) {
const properties = [
'methods',
'properties',
'deepProperties',
'propertyDescriptors',
'staticProperties',
'staticDeepProperties',
'staticPropertyDescriptors',
'initializers',
'configuration',
'deepConfiguration'
];

return properties.filter(property => has(obj, property)).length;
}

/**
* Create a stamp spec compliant desc object.
*
* (desc?: Stamp | ReactDesc | SpecDesc) => SpecDesc
* (desc?: ReactDesc | SpecDesc) => SpecDesc
*/
export default function parseDesc (desc = {}) {
if (isStamp(desc)) {
return desc.compose;
} else if (isDescriptor(desc) || isEmpty(desc)) {
return desc;
}
//console.log(desc);
if (isSpecDescriptor(desc)) return desc;

let {
displayName, init, state, statics,
Expand All @@ -21,14 +34,16 @@ export default function parseDesc (desc = {}) {
} = desc;
const parsedDesc = {};

//console.log(methods);

displayName && (parsedDesc.staticProperties = { displayName });
init && (parsedDesc.initializers = [ init ]);
state && (parsedDesc.deepProperties = { state });
methods && (parsedDesc.methods = methods);
parsedDesc.deepStaticProperties = { ...statics };
parsedDesc.staticDeepProperties = { ...statics };

forEach({ contextTypes, childContextTypes, propTypes, defaultProps },
(val, key) => val && (parsedDesc.deepStaticProperties[key] = val)
(val, key) => val && (parsedDesc.staticDeepProperties[key] = val)
);

return parsedDesc;
Expand Down
4 changes: 3 additions & 1 deletion src/utils/react.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ const lifecycle = {
* (targ?: Object, src?: Object) => new: Object
*/
export default function wrapMethods (targ = {}, src = {}) {
//console.log(targ);
//console.log(src);
const methods = mapValues(src, (val, key) => {
switch (lifecycle[key]) {
case 'wrap':
Expand Down Expand Up @@ -51,5 +53,5 @@ export default function wrapMethods (targ = {}, src = {}) {
}
});

return assign({ ...targ }, methods);
return assign(targ, methods);
}
34 changes: 0 additions & 34 deletions test/compose.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,49 +129,15 @@ test('composing objects with methods', (t) => {
t.plan(4);

const obj1 = {
state: {
stamp: false,
mixin: false,
},

getChildContext () {
return {
foo: true,
bar: false,
};
},

componentDidMount () {
this.state.stamp = true;
},

shouldComponentUpdate () {
return true;
},

render () {
return false;
},
};

const obj2 = {
getChildContext () {
return {
bar: true,
};
},

componentDidMount () {
this.state.mixin = true;
},

shouldComponentUpdate () {
return false;
},

render () {
return true;
},
};

const stamp = compose(obj1, obj2);
Expand Down
1 change: 0 additions & 1 deletion test/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import './spec';
import './basics';
import './state';
import './statics';
Expand Down
80 changes: 0 additions & 80 deletions test/spec/assignment-tests.js

This file was deleted.

Loading