diff --git a/packages/function-tree/package.json b/packages/function-tree/package.json index 83809ff57..69d24dc57 100644 --- a/packages/function-tree/package.json +++ b/packages/function-tree/package.json @@ -7,7 +7,7 @@ "demo:redux": "node demos/server --demo redux", "demo:mobx": "node demos/server --demo mobx", "demo:node": "node demos/node/main.js", - "test": "nodeunit tests", + "test": "../../node_modules/.bin/mocha --compilers js:../../node_modules/babel-register 'tests/**/*.js'", "build": "../../node_modules/.bin/babel src/ --out-dir=lib/ -s", "coverage": "../../node_modules/.bin/nyc --reporter=lcov --reporter=json npm run test", "prepublish": "npm run build" @@ -25,8 +25,6 @@ "html-webpack-plugin": "^2.22.0", "mobx": "^2.5.1", "mobx-react": "^3.5.5", - "nodeunit": "^0.10.2", - "picture-tube": "^1.0.0", "react": "^15.3.1", "react-dom": "^15.3.1", "react-redux": "^4.4.5", diff --git a/packages/function-tree/src/Abort.js b/packages/function-tree/src/Abort.js index d661e2c37..f3172ad33 100644 --- a/packages/function-tree/src/Abort.js +++ b/packages/function-tree/src/Abort.js @@ -1,3 +1 @@ -function Abort () {} - -module.exports = Abort +export default class Abort {} diff --git a/packages/function-tree/src/Path.js b/packages/function-tree/src/Path.js index fa58af34a..015c73259 100644 --- a/packages/function-tree/src/Path.js +++ b/packages/function-tree/src/Path.js @@ -1,13 +1,14 @@ -function Path (path, payload) { - this.path = path - this.payload = payload -} - -Path.prototype.toJS = function () { - return { - path: this.path, - payload: this.payload +class Path { + constructor (path, payload) { + this.path = path + this.payload = payload + } + toJS () { + return { + path: this.path, + payload: this.payload + } } } -module.exports = Path +export default Path diff --git a/packages/function-tree/src/executeTree.js b/packages/function-tree/src/executeTree.js index a28bb95d6..54ca8ca67 100644 --- a/packages/function-tree/src/executeTree.js +++ b/packages/function-tree/src/executeTree.js @@ -1,8 +1,8 @@ -'use strict' - -const assign = require('object-assign') - -module.exports = function executeTree (tree, resolveFunctionResult, initialPayload, end) { +/* + Runs through the tree providing a "next" callback to process next step + of execution +*/ +export default function executeTree (tree, resolveFunctionResult, initialPayload, end) { function runBranch (branch, index, payload, nextBranch) { function runNextItem (result) { runBranch(branch, index + 1, result, nextBranch) @@ -10,10 +10,11 @@ module.exports = function executeTree (tree, resolveFunctionResult, initialPaylo function processFunctionOutput (funcDetails, outputResult) { return function (result) { - let newPayload = assign({}, payload, result ? result.payload : {}) + const newPayload = Object.assign({}, payload, result ? result.payload : {}) if (result && funcDetails.outputs) { - let outputs = Object.keys(funcDetails.outputs) + const outputs = Object.keys(funcDetails.outputs) + if (~outputs.indexOf(result.path)) { runBranch(funcDetails.outputs[result.path], 0, newPayload, outputResult) } else { @@ -25,7 +26,8 @@ module.exports = function executeTree (tree, resolveFunctionResult, initialPaylo } } - let currentItem = branch[index] + const currentItem = branch[index] + if (!currentItem) { nextBranch ? nextBranch(payload) : end(payload) } else if (Array.isArray(currentItem)) { @@ -33,7 +35,7 @@ module.exports = function executeTree (tree, resolveFunctionResult, initialPaylo currentItem.reduce((payloads, action) => { resolveFunctionResult(action, payload, processFunctionOutput(action, (payload) => { payloads.push(payload) - if (payloads.length === itemLength) runNextItem(assign.apply(null, [{}].concat(payloads))) + if (payloads.length === itemLength) runNextItem(Object.assign.apply(Object, [{}].concat(payloads))) })) return payloads }, []) diff --git a/packages/function-tree/src/factories/debounce.js b/packages/function-tree/src/factories/debounce.js index ccd998de0..aab54ee9a 100644 --- a/packages/function-tree/src/factories/debounce.js +++ b/packages/function-tree/src/factories/debounce.js @@ -1,15 +1,15 @@ function createDebounce (time) { - var timer - var currentResolver + let timer + let currentResolver - function debounce (context) { + function debounce ({path}) { return new Promise(function (resolve) { if (timer) { - currentResolver(context.path.discarded()) + currentResolver(path.discarded()) currentResolver = resolve } else { timer = setTimeout(function () { - currentResolver(context.path.accepted()) + currentResolver(path.accepted()) timer = null currentResolver = null }, time) @@ -22,7 +22,7 @@ function createDebounce (time) { return debounce } -function debounceFactory (time, continueBranch) { +export default function debounceFactory (time, continueBranch) { return [ createDebounce(time), { accepted: continueBranch, @@ -30,5 +30,3 @@ function debounceFactory (time, continueBranch) { } ] } - -module.exports = debounceFactory diff --git a/packages/function-tree/src/index.js b/packages/function-tree/src/index.js index 7d512aca6..c0d778d54 100644 --- a/packages/function-tree/src/index.js +++ b/packages/function-tree/src/index.js @@ -1,19 +1,24 @@ -'use strict' - -const EventEmitter = require('events') -const executeTree = require('./executeTree') -const createStaticTree = require('./staticTree') -const ExecutionProvider = require('./providers/Execution') -const InputProvider = require('./providers/Input') -const PathProvider = require('./providers/Path') -const assign = require('object-assign') -const Path = require('./Path') -const Abort = require('./Abort') - +import EventEmitter from 'events' +import executeTree from './executeTree' +import createStaticTree from './staticTree' +import ExecutionProvider from './providers/Execution' +import InputProvider from './providers/Input' +import PathProvider from './providers/Path' +import Path from './Path' +import Abort from './Abort' + +/* + Need to create a unique ID for each execution to identify it + in debugger +*/ function createUniqueId () { return Date.now() + '_' + Math.floor(Math.random() * 10000) } +/* + Validate any returned value from a function. Has + to be nothing or an object +*/ function isValidResult (result) { return ( !result || @@ -25,172 +30,192 @@ function isValidResult (result) { ) } -function FunctionTreeExecution (name, staticTree, functionTree, errorCallback) { - this.id = createUniqueId() - this.name = name - this.staticTree = staticTree - this.functionTree = functionTree - this.datetime = Date.now() - this.errorCallback = errorCallback - - this.runFunction = this.runFunction.bind(this) +/* + If it walks like a duck and quacks like a duck... +*/ +function isPromise (result) { + return result && result.then && result.catch && typeof result.then === 'function' && typeof result.catch === 'function' } -FunctionTreeExecution.prototype.runFunction = function (funcDetails, payload, next) { - const context = this.createContext(funcDetails, payload) - const functionTree = this.functionTree - const errorCallback = this.errorCallback - const execution = this - - functionTree.emit('functionStart', execution, funcDetails, payload) - const result = funcDetails.function(context) - - if (result instanceof Abort) { - return functionTree.emit('abort', execution, funcDetails, payload) +class FunctionTreeExecution extends EventEmitter { + constructor (name, staticTree, functionTree, errorCallback) { + super() + this.id = createUniqueId() + this.name = name + this.staticTree = staticTree + this.functionTree = functionTree + this.datetime = Date.now() + this.errorCallback = errorCallback + + this.runFunction = this.runFunction.bind(this) } + /* + Creates the context for the current function to be run, + emits events and handles its returned value. Also handles + the returned value being a promise + */ + runFunction (funcDetails, payload, next) { + const context = this.createContext(funcDetails, payload) + const functionTree = this.functionTree + const errorCallback = this.errorCallback + const execution = this + + functionTree.emit('functionStart', execution, funcDetails, payload) + const result = funcDetails.function(context) + + if (result instanceof Abort) { + return functionTree.emit('abort', execution, funcDetails, payload) + } - if (result && result.then && result.catch && typeof result.then === 'function' && typeof result.catch === 'function') { - functionTree.emit('asyncFunction', execution, funcDetails, payload) - result - .then(function (result) { - if (result instanceof Path) { - functionTree.emit('functionEnd', execution, funcDetails, payload) - next(result.toJS()) - } else if (funcDetails.outputs) { - functionTree.emit('functionEnd', execution, funcDetails, payload) - throw new Error('The result ' + JSON.stringify(result) + ' from function ' + funcDetails.name + ' needs to be a path') - } else if (isValidResult(result)) { - functionTree.emit('functionEnd', execution, funcDetails, payload) - next({ - payload: result - }) - } else { - functionTree.emit('functionEnd', execution, funcDetails, payload) - throw new Error('The result ' + JSON.stringify(result) + ' from function ' + funcDetails.name + ' is not a valid result') - } - }) - .catch(function (result) { - if (result instanceof Error) { - errorCallback(result) - } else if (result instanceof Path) { - functionTree.emit('functionEnd', execution, funcDetails, payload) - next(result.toJS()) - } else if (funcDetails.outputs) { - let error = new Error('The result ' + JSON.stringify(result) + ' from function ' + funcDetails.name + ' needs to be a path') - - errorCallback(error) - } else if (isValidResult(result)) { - functionTree.emit('functionEnd', execution, funcDetails, payload) - next({ - payload: result - }) - } else { - let error = new Error('The result ' + JSON.stringify(result) + ' from function ' + funcDetails.name + ' is not a valid result') - - errorCallback(error) - } + /* + If result is a promise we want to emit an event and wait for it to resolve to + move on + */ + if (isPromise(result)) { + functionTree.emit('asyncFunction', execution, funcDetails, payload) + result + .then(function (result) { + if (result instanceof Path) { + functionTree.emit('functionEnd', execution, funcDetails, payload) + next(result.toJS()) + } else if (funcDetails.outputs) { + functionTree.emit('functionEnd', execution, funcDetails, payload) + throw new Error('The result ' + JSON.stringify(result) + ' from function ' + funcDetails.name + ' needs to be a path') + } else if (isValidResult(result)) { + functionTree.emit('functionEnd', execution, funcDetails, payload) + next({ + payload: result + }) + } else { + functionTree.emit('functionEnd', execution, funcDetails, payload) + throw new Error('The result ' + JSON.stringify(result) + ' from function ' + funcDetails.name + ' is not a valid result') + } + }) + .catch(function (result) { + if (result instanceof Error) { + errorCallback(result) + } else if (result instanceof Path) { + functionTree.emit('functionEnd', execution, funcDetails, payload) + next(result.toJS()) + } else if (funcDetails.outputs) { + let error = new Error('The result ' + JSON.stringify(result) + ' from function ' + funcDetails.name + ' needs to be a path') + + errorCallback(error) + } else if (isValidResult(result)) { + functionTree.emit('functionEnd', execution, funcDetails, payload) + next({ + payload: result + }) + } else { + let error = new Error('The result ' + JSON.stringify(result) + ' from function ' + funcDetails.name + ' is not a valid result') + + errorCallback(error) + } + }) + } else if (result instanceof Path) { + functionTree.emit('functionEnd', execution, funcDetails, payload) + next(result.toJS()) + } else if (funcDetails.outputs) { + let error = new Error('The result ' + JSON.stringify(result) + ' from function ' + funcDetails.name + ' needs to be a path') + + errorCallback(error) + } else if (isValidResult(result)) { + functionTree.emit('functionEnd', execution, funcDetails, payload) + next({ + payload: result }) - } else if (result instanceof Path) { - functionTree.emit('functionEnd', execution, funcDetails, payload) - next(result.toJS()) - } else if (funcDetails.outputs) { - let error = new Error('The result ' + JSON.stringify(result) + ' from function ' + funcDetails.name + ' needs to be a path') - - errorCallback(error) - } else if (isValidResult(result)) { - functionTree.emit('functionEnd', execution, funcDetails, payload) - next({ - payload: result - }) - } else { - let error = new Error('The result ' + JSON.stringify(result) + ' from function ' + funcDetails.name + ' is not a valid result') - errorCallback(error) - } -} - -FunctionTreeExecution.prototype.createContext = function (action, payload) { - return [ - ExecutionProvider(this, Abort), - InputProvider(), - PathProvider() - ].concat(this.functionTree.contextProviders).reduce(function (currentContext, contextProvider) { - var newContext = ( - typeof contextProvider === 'function' - ? contextProvider(currentContext, action, payload) - : assign(currentContext, contextProvider) - ) - - if (newContext !== currentContext) { - throw new Error('function-tree: You are not returning the context from a provider') + } else { + let error = new Error('The result ' + JSON.stringify(result) + ' from function ' + funcDetails.name + ' is not a valid result') + errorCallback(error) } - - return newContext - }, {}) + } + /* + Creates the context for the next running function + */ + createContext (funcDetails, payload) { + return [ + ExecutionProvider(this, Abort), + InputProvider(), + PathProvider() + ].concat(this.functionTree.contextProviders).reduce(function (currentContext, contextProvider) { + var newContext = ( + typeof contextProvider === 'function' + ? contextProvider(currentContext, funcDetails, payload) + : Object.assign(currentContext, contextProvider) + ) + + if (newContext !== currentContext) { + throw new Error('function-tree: You are not returning the context from a provider') + } + + return newContext + }, {}) + } } -function FunctionTree (contextProviders) { - if ( - !this || (typeof window !== 'undefined' && this === window) - ) { - return new FunctionTree(contextProviders) +class FunctionTree extends EventEmitter { + constructor (contextProviders) { + super() + this.cachedTrees = [] + this.cachedStaticTrees = [] + this.contextProviders = contextProviders || [] + this.runTree = this.runTree.bind(this) + this.runTree.on = this.on.bind(this) + this.runTree.once = this.once.bind(this) + this.runTree.off = this.removeListener.bind(this) + + return this.runTree } + /* + Analyses the tree to identify paths and its validity. This analysis + is cached. Then the method creates an execution for the tree to run. + */ + runTree () { + let name + let tree + let payload + let cb + let staticTree + const args = [].slice.call(arguments) + args.forEach(function (arg) { + if (typeof arg === 'string') { + name = arg + return + } else if (Array.isArray(arg)) { + tree = arg + } else if (typeof arg === 'function') { + cb = arg + } else { + payload = arg + } + }) - this.cachedTrees = [] - this.cachedStaticTrees = [] - this.contextProviders = contextProviders || [] - this.runTree = this.runTree.bind(this) - this.runTree.on = this.on.bind(this) - this.runTree.once = this.once.bind(this) - this.runTree.off = this.removeListener.bind(this) - - return this.runTree -} + if (!tree) { + throw new Error('function-tree - You did not pass in a function tree') + } -FunctionTree.prototype = Object.create(EventEmitter.prototype) - -FunctionTree.prototype.runTree = function () { - var name - var tree - var payload - var cb - var staticTree - var args = [].slice.call(arguments) - args.forEach(function (arg) { - if (typeof arg === 'string') { - name = arg - return - } else if (Array.isArray(arg)) { - tree = arg - } else if (typeof arg === 'function') { - cb = arg + if (this.cachedTrees.indexOf(tree) === -1) { + staticTree = createStaticTree(tree) + this.cachedTrees.push(tree) + this.cachedStaticTrees.push(staticTree) } else { - payload = arg + staticTree = this.cachedStaticTrees[this.cachedTrees.indexOf(tree)] } - }) - - if (!tree) { - throw new Error('function-tree - You did not pass in a function tree') - } + const execution = new FunctionTreeExecution(name, staticTree, this, (error) => { + cb && cb(error, execution, payload) + setTimeout(() => { + this.emit('error', error, execution, payload) + }) + }) - if (this.cachedTrees.indexOf(tree) === -1) { - staticTree = createStaticTree(tree) - this.cachedTrees.push(tree) - this.cachedStaticTrees.push(staticTree) - } else { - staticTree = this.cachedStaticTrees[this.cachedTrees.indexOf(tree)] + this.emit('start', execution, payload) + executeTree(execution.staticTree, execution.runFunction, payload, () => { + this.emit('end', execution, payload) + cb && cb(null, execution, payload) + }) } - var execution = new FunctionTreeExecution(name, staticTree, this, function (error) { - cb && cb(error, execution, payload) - setTimeout(function () { - this.emit('error', error, execution, payload) - }.bind(this)) - }.bind(this)) - - this.emit('start', execution, payload) - executeTree(execution.staticTree, execution.runFunction, payload, function () { - this.emit('end', execution, payload) - cb && cb(null, execution, payload) - }.bind(this)) } -module.exports = FunctionTree +export default (contextProviders) => { + return new FunctionTree(contextProviders) +} diff --git a/packages/function-tree/src/providers/Context.js b/packages/function-tree/src/providers/Context.js index cb609ec05..d13155365 100644 --- a/packages/function-tree/src/providers/Context.js +++ b/packages/function-tree/src/providers/Context.js @@ -1,44 +1,16 @@ -module.exports = function (extendedContext) { - return function (context, funcDetails, payload) { - return Object.keys(extendedContext).reduce(function (context, key) { - function proxy (sourceKeys, source, target) { - return sourceKeys.reduce(function (obj, objKey) { - if (typeof contextValue[objKey] === 'function') { - obj[objKey] = function () { - context.debugger.send({ - method: key + '.' + objKey, - color: context.debugger.getColor(key), - args: [].slice.call(arguments) - }) - return contextValue[objKey].apply(contextValue, arguments) - } - } else if (!(objKey in obj)) { - Object.defineProperty(obj, objKey, { - get () { - return contextValue[objKey] - }, - set (value) { - context.debugger.send({ - method: key + '.' + objKey + ' =', - color: context.debugger.getColor(key), - args: [value] - }) - contextValue[objKey] = value - } - }) - } - - return obj - }, target) - } - +export default function ContextProvider (extendedContext) { + return (context, funcDetails, payload) => { + return Object.keys(extendedContext).reduce((context, key) => { if (context.debugger) { context[key] = {} - // Grab the prototype to add methods to proxy. - // We only grab actual added prototypes on first level, not nested and not - // where prototype is base prototypes like Objects and Functions - var proto = null + /* + Grab the prototype to add methods to proxy. + We only grab actual added prototypes on first level, not nested and not + where prototype is base prototypes like Objects and Functions + */ + let proto = null + if ( extendedContext[key].constructor && extendedContext[key].constructor.prototype.constructor !== Object.prototype.constructor && @@ -48,17 +20,52 @@ module.exports = function (extendedContext) { } // The value might be a function that is already wrapped, try grabbing the original - var contextValue = extendedContext[key] + const contextValue = extendedContext[key] + + /* + Wraps methods and sends their payload through the debugger + */ + const proxy = (sourceKeys, source, target) => { + return sourceKeys.reduce(function (obj, objKey) { + if (typeof contextValue[objKey] === 'function') { + obj[objKey] = (...args) => { + context.debugger.send({ + method: `${key}.${objKey}`, + color: context.debugger.getColor(key), + args + }) + + return contextValue[objKey].apply(contextValue, args) + } + } else if (!(objKey in obj)) { + Object.defineProperty(obj, objKey, { + get () { + return contextValue[objKey] + }, + set (value) { + context.debugger.send({ + method: key + '.' + objKey + ' =', + color: context.debugger.getColor(key), + args: [value] + }) + contextValue[objKey] = value + } + }) + } + + return obj + }, target) + } // If the context value is a function, wrap it if (typeof contextValue === 'function') { - context[key] = function () { + context[key] = (...args) => { context.debugger.send({ method: key, color: context.debugger.getColor(key), - args: [].slice.call(arguments) + args }) - return contextValue.apply(null, arguments) + return contextValue.apply(null, args) } } diff --git a/packages/function-tree/src/providers/Debugger.js b/packages/function-tree/src/providers/Debugger.js index 1356ee41c..ddc06687d 100644 --- a/packages/function-tree/src/providers/Debugger.js +++ b/packages/function-tree/src/providers/Debugger.js @@ -1,7 +1,7 @@ /* global CustomEvent */ function safeStringify (obj) { - var cache = [] - var returnValue = JSON.stringify(obj || {}, function (key, value) { + let cache = [] + const returnValue = JSON.stringify(obj || {}, function (key, value) { if (typeof value === 'object' && value !== null) { if (cache.indexOf(value) === -1) { cache.push(value) @@ -12,14 +12,13 @@ function safeStringify (obj) { } return value }) + cache = null return returnValue } -module.exports = function (options) { - options = options || {} - +export default function DebuggerProvider (options = {}) { if (typeof window === 'undefined' || ( typeof window.chrome === 'undefined' && @@ -29,14 +28,14 @@ module.exports = function (options) { throw new Error('The debugger does not work in this environment, load up the Node debugger instead') } - var isConnected = false - var APP_ID = String(Date.now()) - var VERSION = 'v1' - var backlog = [] + let isConnected = false + const APP_ID = String(Date.now()) + const VERSION = 'v1' + const backlog = [] function send (debuggingData, context, functionDetails, payload) { - var type = 'execution' - var data = { + const type = 'execution' + const data = { name: context.execution.name, executionId: context.execution.id, functionIndex: functionDetails.functionIndex, @@ -50,25 +49,25 @@ module.exports = function (options) { backlog.push(data) return } - var detail = { - type: type, + const detail = { app: APP_ID, version: VERSION, - data: data + type, + data } - var event = new CustomEvent('function-tree.client.message', { + const event = new CustomEvent('function-tree.client.message', { detail: safeStringify(detail) }) window.dispatchEvent(event) } function sendInitial (type) { - var event = new CustomEvent('function-tree.client.message', { + const event = new CustomEvent('function-tree.client.message', { detail: safeStringify({ - type: type, app: APP_ID, version: VERSION, + type, data: { functionTrees: backlog } @@ -91,12 +90,12 @@ module.exports = function (options) { sendInitial('init') - return function (context, functionDetails, payload) { + return (context, functionDetails, payload) => { context.debugger = { - send: function (data) { + send (data) { send(data, context, functionDetails, payload) }, - getColor: function (key) { + getColor (key) { return options.colors[key] || '#333' } } diff --git a/packages/function-tree/src/providers/Execution.js b/packages/function-tree/src/providers/Execution.js index 8283eb10c..8e0c06e2e 100644 --- a/packages/function-tree/src/providers/Execution.js +++ b/packages/function-tree/src/providers/Execution.js @@ -1,16 +1,14 @@ -const executeTree = require('../executeTree') +import executeTree from '../executeTree' -module.exports = function ExecutionProvider (execution, Abort) { - return function (context) { +export default function ExecutionProvider (execution, Abort) { + return (context) => { context.execution = execution - context.execution.retry = function (payload) { - return new Promise(function (resolve) { - executeTree(execution.staticTree, execution.runFunction, payload, function () { - resolve() - }) + context.execution.retry = (payload) => { + return new Promise((resolve) => { + executeTree(execution.staticTree, execution.runFunction, payload, resolve) }) } - context.execution.abort = function () { + context.execution.abort = () => { return new Abort() } diff --git a/packages/function-tree/src/providers/Input.js b/packages/function-tree/src/providers/Input.js index 1dd06456e..a1a43723d 100644 --- a/packages/function-tree/src/providers/Input.js +++ b/packages/function-tree/src/providers/Input.js @@ -1,5 +1,5 @@ -module.exports = function () { - return function (context, funcDetails, payload) { +export default function InputProvider () { + return (context, funcDetails, payload) => { context.input = payload || {} return context diff --git a/packages/function-tree/src/providers/NodeDebugger.js b/packages/function-tree/src/providers/NodeDebugger.js index 402115e96..c85c191d4 100644 --- a/packages/function-tree/src/providers/NodeDebugger.js +++ b/packages/function-tree/src/providers/NodeDebugger.js @@ -1,8 +1,8 @@ -var chalk = require('chalk') +import chalk from 'chalk' function safeStringify (obj) { - var cache = [] - var returnValue = JSON.stringify(obj || {}, function (key, value) { + let cache = [] + const returnValue = JSON.stringify(obj || {}, function (key, value) { if (typeof value === 'object' && value !== null) { if (cache.indexOf(value) === -1) { cache.push(value) @@ -13,14 +13,13 @@ function safeStringify (obj) { } return value }) + cache = null return returnValue } -module.exports = function (options) { - options = options || {} - +export default function NodeDebuggerProvider (options = {}) { if ( typeof window !== 'undefined' && ( @@ -31,24 +30,23 @@ module.exports = function (options) { throw new Error('function-tree: You are running the Node debugger, but the Chrome debugger is supported') } - function padded (text, size) { - size = size || 0 - var padding = new Array(size + 1).join(' ') + function padded (text, size = 0) { + const padding = new Array(size + 1).join(' ') + return padding + text + padding } - var registeredFunctionTrees = {} + const registeredFunctionTrees = {} function send (debuggingData, context, functionDetails, payload) { - var id = context.execution.id + '_' + context.execution.executionId - var prevFunction = ( + const id = context.execution.id + '_' + context.execution.executionId + const prevFunction = ( registeredFunctionTrees[id] && registeredFunctionTrees[id].functions[functionDetails.functionIndex] ? registeredFunctionTrees[id].functions[functionDetails.functionIndex] : null ) - - var isExistingFunction = Boolean(prevFunction && prevFunction.functionIndex === functionDetails.functionIndex) + const isExistingFunction = Boolean(prevFunction && prevFunction.functionIndex === functionDetails.functionIndex) if (registeredFunctionTrees[id] && registeredFunctionTrees[id].functions[functionDetails.functionIndex]) { registeredFunctionTrees[id].functions[functionDetails.functionIndex].data.push(debuggingData) @@ -58,7 +56,7 @@ module.exports = function (options) { registeredFunctionTrees[id].functions[functionDetails.functionIndex] = { functionIndex: functionDetails.functionIndex, outputs: functionDetails.outputs, - payload: payload, + payload, data: [] } } else { @@ -70,14 +68,14 @@ module.exports = function (options) { registeredFunctionTrees[id].functions[functionDetails.functionIndex] = { functionIndex: functionDetails.functionIndex, outputs: functionDetails.outputs, - payload: payload, + payload, data: [] } } if (isExistingFunction) { - var data = prevFunction.data[prevFunction.data.length - 1] - var args = data ? data.args || [] : [] + const data = prevFunction.data[prevFunction.data.length - 1] + const args = data ? data.args || [] : [] console.log.apply(console, [padded(chalk[data.color || 'white'](data.method), registeredFunctionTrees[id].logLevel)].concat( @@ -94,7 +92,7 @@ module.exports = function (options) { } if (prevFunction && prevFunction.outputs) { - var chosenOutput = Object.keys(prevFunction.outputs).filter(function (outputKey) { + const chosenOutput = Object.keys(prevFunction.outputs).filter(function (outputKey) { if ( prevFunction.outputs[outputKey].length && ( @@ -114,7 +112,7 @@ module.exports = function (options) { registeredFunctionTrees[id].logLevel++ } - var payloadString = safeStringify(payload) + const payloadString = safeStringify(payload) console.log(padded(chalk.underline.white(functionDetails.name), registeredFunctionTrees[id].logLevel)) console.log(padded( @@ -124,12 +122,12 @@ module.exports = function (options) { } } - return function (context, functionDetails, payload) { + return (context, functionDetails, payload) => { context.debugger = { - send: function (data) { + send (data) { send(data, context, functionDetails, payload) }, - getColor: function (key) { + getColor (key) { return options.colors[key] || 'white' } } @@ -137,4 +135,5 @@ module.exports = function (options) { send(null, context, functionDetails, payload) return context - } } + } +} diff --git a/packages/function-tree/src/providers/Path.js b/packages/function-tree/src/providers/Path.js index 26a27a243..83f784f8d 100644 --- a/packages/function-tree/src/providers/Path.js +++ b/packages/function-tree/src/providers/Path.js @@ -1,15 +1,15 @@ -const Path = require('../Path') +import Path from '../Path' -const createNext = function createNext (next, path) { +function createNext (next, path) { return function (payload) { return new Path(path, payload) } } -module.exports = function () { - return function (context, functionDetails, payload, next) { +export default () => { + return (context, functionDetails, payload, next) => { if (functionDetails.outputs) { - context.path = Object.keys(functionDetails.outputs).reduce(function (output, outputPath) { + context.path = Object.keys(functionDetails.outputs).reduce((output, outputPath) => { output[outputPath] = createNext(next, outputPath) return output diff --git a/packages/function-tree/src/providers/Redux.js b/packages/function-tree/src/providers/Redux.js index 6e89cfa8f..090d602b2 100644 --- a/packages/function-tree/src/providers/Redux.js +++ b/packages/function-tree/src/providers/Redux.js @@ -1,6 +1,6 @@ -function ReduxProvider (store) { +export default function ReduxProvider (store) { return (context) => { - context.dispatch = function (action) { + context.dispatch = (action) => { context.debugger && context.debugger.send({ method: 'redux.dispatch', color: '#6333b1', @@ -8,12 +8,10 @@ function ReduxProvider (store) { }) store.dispatch(action) } - context.getState = function () { + context.getState = () => { return store.getState() } return context } } - -module.exports = ReduxProvider diff --git a/packages/function-tree/src/staticTree.js b/packages/function-tree/src/staticTree.js index 726604e1d..1d3625207 100644 --- a/packages/function-tree/src/staticTree.js +++ b/packages/function-tree/src/staticTree.js @@ -1,16 +1,15 @@ -'use strict' - function getFunctionName (fn) { - var ret = fn.toString() + let ret = fn.toString() ret = ret.substr('function '.length) ret = ret.substr(0, ret.indexOf('(')) + return ret } function traverse (functions, item, isChain) { if (Array.isArray(item) && typeof isChain === 'boolean') { item = item.slice() - return item.map(function (subItem, index) { + return item.map((subItem, index) => { if (typeof subItem === 'function') { let nextSubItem = item[index + 1] if (!Array.isArray(nextSubItem) && typeof nextSubItem === 'object') { @@ -23,20 +22,20 @@ function traverse (functions, item, isChain) { return traverse(functions, subItem, false) } throw new Error('Signal Tree - Unexpected entry in signal chain') - }).filter(function (func) { + }).filter((func) => { return !!func }) } else if (typeof item === 'function') { - let func = item - let outputs = isChain - let funcDetails = { + const func = item + const outputs = isChain + const funcDetails = { name: func.displayName || getFunctionName(func), functionIndex: functions.indexOf(func) === -1 ? (functions.push(func) - 1) : functions.indexOf(func), function: func } if (outputs) { funcDetails.outputs = {} - Object.keys(outputs).forEach(function (key) { + Object.keys(outputs).forEach((key) => { if (func.outputs && !~func.outputs.indexOf(key)) { throw new Error(`function-tree - Outputs object doesn\'t match list of possible outputs defined for function.`) } @@ -50,7 +49,7 @@ function traverse (functions, item, isChain) { } } -module.exports = function (tree) { +export default (tree) => { const functions = [] return traverse(functions, tree, true) diff --git a/packages/function-tree/tests/FunctionTree.js b/packages/function-tree/tests/FunctionTree.js index 6814c0d68..2f140f031 100644 --- a/packages/function-tree/tests/FunctionTree.js +++ b/packages/function-tree/tests/FunctionTree.js @@ -1,176 +1,157 @@ -'use strict' +/* eslint-env mocha */ +import FunctionTree from '../src' +import assert from 'assert' -const FunctionTree = require('../src') +describe('FunctionTree', () => { + it('should create a function tree extending event emitter', () => { + const execute = FunctionTree([]) -module.exports['should create a function tree extending event emitter'] = (test) => { - const execute = FunctionTree([]) - - test.equal(typeof execute.on, 'function') - test.equal(typeof execute.once, 'function') - test.equal(typeof execute.off, 'function') - test.done() -} - -module.exports['should run functions'] = (test) => { - const execute = FunctionTree() - - test.expect(1) - execute([ - function action () { - test.ok(true) - } - ]) - test.done() -} - -module.exports['should pass arguments to context creator and run it for each action'] = (test) => { - const execute = FunctionTree([ - function SomeProvider (context, functionDetails, payload) { - test.ok(context) - test.equal(functionDetails.functionIndex, 0) - test.deepEqual(payload, {foo: 'bar'}) - - return context - } - ]) - - test.expect(3) - execute([ - function action () {} - ], { - foo: 'bar' + assert.equal(typeof execute.on, 'function') + assert.equal(typeof execute.once, 'function') + assert.equal(typeof execute.off, 'function') }) - test.done() -} - -module.exports['should pass returned context into functions'] = (test) => { - const execute = FunctionTree([ - function SomeProvider (context) { - context.foo = 'bar' - - return context - } - ]) - - test.expect(1) - execute([ - function action (context) { - test.equal(context.foo, 'bar') - } - ]) - test.done() -} - -module.exports['should emit execution events in correct order'] = (test) => { - let eventsCount = 0 - const execute = FunctionTree() - - test.expect(6) - execute.once('start', function () { - eventsCount++ - test.equal(eventsCount, 1) + it('should run functions', () => { + const execute = FunctionTree() + + execute([ + () => { + assert.ok(true) + } + ]) + }) + it('should pass arguments to context creator and run it for each action', () => { + const execute = FunctionTree([ + function SomeProvider (context, functionDetails, payload) { + assert.ok(context) + assert.equal(functionDetails.functionIndex, 0) + assert.deepEqual(payload, {foo: 'bar'}) + + return context + } + ]) + + execute([ + () => {} + ], { + foo: 'bar' + }) + }) + it('should pass returned context into functions', () => { + const execute = FunctionTree([ + function SomeProvider (context) { + context.foo = 'bar' + + return context + } + ]) + + execute([ + ({foo}) => { + assert.equal(foo, 'bar') + } + ]) }) - execute.once('functionStart', function () { - eventsCount++ - test.equal(eventsCount, 2) + it('should emit execution events in correct order', () => { + let eventsCount = 0 + const execute = FunctionTree() + + execute.once('start', function () { + eventsCount++ + assert.equal(eventsCount, 1) + }) execute.once('functionStart', function () { eventsCount++ - test.equal(eventsCount, 4) + assert.equal(eventsCount, 2) + execute.once('functionStart', function () { + eventsCount++ + assert.equal(eventsCount, 4) + }) }) - }) - execute.once('functionEnd', function () { - eventsCount++ - test.equal(eventsCount, 3) execute.once('functionEnd', function () { eventsCount++ - test.equal(eventsCount, 5) + assert.equal(eventsCount, 3) + execute.once('functionEnd', function () { + eventsCount++ + assert.equal(eventsCount, 5) + }) }) + execute.on('end', function () { + eventsCount++ + assert.equal(eventsCount, 6) + }) + execute([ + function actionA () {}, + function actionB () {} + ]) }) - execute.on('end', function () { - eventsCount++ - test.equal(eventsCount, 6) - test.done() - }) - execute([ - function actionA () {}, - function actionB () {} - ]) -} - -module.exports['should pass action and payload on action events'] = (test) => { - const execute = FunctionTree() + it('should pass action and payload on action events', () => { + const execute = FunctionTree() - test.expect(6) - execute.once('functionStart', function (execution, functionDetails, payload) { - test.ok(execution.id) - test.equal(functionDetails.functionIndex, 0) - test.deepEqual(payload, {foo: 'bar'}) - }) - execute.once('functionEnd', function (execution, functionDetails, payload) { - test.ok(execution.id) - test.equal(functionDetails.functionIndex, 0) - test.deepEqual(payload, {foo: 'bar'}) - }) - execute([ - function action () {} - ], { - foo: 'bar' + execute.once('functionStart', function (execution, functionDetails, payload) { + assert.ok(execution.id) + assert.equal(functionDetails.functionIndex, 0) + assert.deepEqual(payload, {foo: 'bar'}) + }) + execute.once('functionEnd', function (execution, functionDetails, payload) { + assert.ok(execution.id) + assert.equal(functionDetails.functionIndex, 0) + assert.deepEqual(payload, {foo: 'bar'}) + }) + execute([ + () => {} + ], { + foo: 'bar' + }) }) - test.done() -} - -module.exports['should be able to reuse existing tree'] = (test) => { - function actionA (context) { - test.ok(true) - return context.path.success() - } - - function actionB (context) { - test.ok(true) - return context.path.success() - } + it('should be able to reuse existing tree', (done) => { + function actionA ({path}) { + assert.ok(true) + return path.success() + } - function actionC () { - test.ok(true) - } + function actionB ({path}) { + assert.ok(true) + return path.success() + } - const execute = FunctionTree([]) - const tree = [ - actionA, { - success: [ - actionB, { - success: [ - actionC - ] - } - ] + function actionC () { + assert.ok(true) } - ] - test.expect(6) - execute(tree, () => { + + const execute = FunctionTree([]) + const tree = [ + actionA, { + success: [ + actionB, { + success: [ + actionC + ] + } + ] + } + ] execute(tree, () => { - test.done() + execute(tree, () => { + done() + }) }) }) -} - -module.exports['should give error when path and no path returned'] = (test) => { - function actionA () { - return { - foo: 'bar' + it('should give error when path and no path returned', () => { + function actionA () { + return { + foo: 'bar' + } } - } - const execute = FunctionTree([]) - const tree = [ - actionA, { - success: [] - } - ] - test.expect(1) - execute.once('error', () => { - test.ok(true) - test.done() + const execute = FunctionTree([]) + const tree = [ + actionA, { + success: [] + } + ] + execute.once('error', () => { + assert.ok(true) + }) + execute(tree) }) - execute(tree) -} +}) diff --git a/packages/function-tree/tests/contextProvider.js b/packages/function-tree/tests/contextProvider.js index cfe041dfc..506da0467 100644 --- a/packages/function-tree/tests/contextProvider.js +++ b/packages/function-tree/tests/contextProvider.js @@ -1,19 +1,20 @@ -'use strict' -const FunctionTree = require('../src') -const ContextProvider = require('../src/providers/Context') +/* eslint-env mocha */ +import FunctionTree from '../src' +import ContextProvider from '../src/providers/Context' +import assert from 'assert' -module.exports['should add whatever is passed into the context'] = (test) => { - const execute = FunctionTree([ - ContextProvider({ - foo: 'bar' - }) - ]) +describe('ContextProvider', () => { + it('should add whatever is passed on to the context', () => { + const execute = FunctionTree([ + ContextProvider({ + foo: 'bar' + }) + ]) - test.expect(1) - execute([ - function func (context) { - test.equal(context.foo, 'bar') - } - ]) - test.done() -} + execute([ + ({foo}) => { + assert.equal(foo, 'bar') + } + ]) + }) +}) diff --git a/packages/function-tree/tests/executionProvider.js b/packages/function-tree/tests/executionProvider.js index cda6417cf..2ca00558d 100644 --- a/packages/function-tree/tests/executionProvider.js +++ b/packages/function-tree/tests/executionProvider.js @@ -1,70 +1,66 @@ -'use strict' +/* eslint-env mocha */ +import FunctionTree from '../src' +import assert from 'assert' -const FunctionTree = require('../src') +describe('ExecutionProvider', () => { + it('should expose the instance on the context', () => { + const execute = FunctionTree() -module.exports['should expose the instance on the context'] = (test) => { - const execute = FunctionTree() + execute('something', [ + ({execution}) => { + assert.equal(execution.name, 'something') + assert.ok(execution.id) + assert.ok(execution.datetime) + assert.ok(execution.staticTree) + } + ]) + }) + it('should be able to retry execution', () => { + const execute = FunctionTree() + let count = 0 - test.expect(4) - execute('something', [ - function action (context) { - test.equal(context.execution.name, 'something') - test.ok(context.execution.id) - test.ok(context.execution.datetime) - test.ok(context.execution.staticTree) + function funcA () { + return new Promise(resolve => { + resolve() + }) } - ]) - test.done() -} - -module.exports['should be able to retry execution'] = (test) => { - const execute = FunctionTree() - test.expect(1) - let count = 0 - function funcA () { - return new Promise(resolve => { - resolve() - }) - } - function funcB (context) { - if (context.input.retryCount < 3) { - count++ - return context.execution.retry({ - retryCount: context.input.retryCount + 1 - }) + function funcB ({input, execution}) { + if (input.retryCount < 3) { + count++ + return execution.retry({ + retryCount: input.retryCount + 1 + }) + } } - } - execute([ - funcA, - funcB - ], { - retryCount: 0 - }, () => { - test.equals(count, 3) - test.done() + execute([ + funcA, + funcB + ], { + retryCount: 0 + }, () => { + assert.equal(count, 3) + }) }) -} + it('should be able to abort execution', () => { + const execute = FunctionTree() + let count = 0 -module.exports['should be able to abort execution'] = (test) => { - const execute = FunctionTree() - test.expect(1) - let count = 0 - function funcA (context) { - return context.execution.abort() - } + function funcA ({execution}) { + return execution.abort() + } - function funcB () { - count++ - } + function funcB () { + count++ + } - execute.on('abort', () => { - test.equals(count, 0) - test.done() + execute.on('abort', () => { + assert.equal(count, 0) + }) + execute([ + funcA, + funcB + ]) }) - execute([ - funcA, - funcB - ]) -} +}) diff --git a/packages/function-tree/tests/inputProvider.js b/packages/function-tree/tests/inputProvider.js index dea1e1ac1..adf38b0f1 100644 --- a/packages/function-tree/tests/inputProvider.js +++ b/packages/function-tree/tests/inputProvider.js @@ -1,31 +1,28 @@ -'use strict' +/* eslint-env mocha */ +import FunctionTree from '../src' +import assert from 'assert' -const FunctionTree = require('../src') +describe('InputProvider', () => { + it('should have "input" on context', () => { + const execute = FunctionTree() -module.exports['should have "input" on context'] = (test) => { - const execute = FunctionTree() - - test.expect(1) - execute([ - function action (context) { - test.ok(context.input) - } - ]) - test.done() -} - -module.exports['should have initial payload on input'] = (test) => { - const execute = FunctionTree() + execute([ + ({input}) => { + assert.ok(input) + } + ]) + }) + it('should have initial payload on input', () => { + const execute = FunctionTree() - test.expect(1) - execute([ - function action (context) { - test.deepEqual(context.input, { - foo: 'bar' - }) - } - ], { - foo: 'bar' + execute([ + ({input}) => { + assert.deepEqual(input, { + foo: 'bar' + }) + } + ], { + foo: 'bar' + }) }) - test.done() -} +}) diff --git a/packages/function-tree/tests/nodeDebuggerProvider.js b/packages/function-tree/tests/nodeDebuggerProvider.js index 56fc222bf..eadfffefb 100644 --- a/packages/function-tree/tests/nodeDebuggerProvider.js +++ b/packages/function-tree/tests/nodeDebuggerProvider.js @@ -1,65 +1,60 @@ -'use strict' -const FunctionTree = require('../src') -const ContextProvider = require('../src/providers/Context') -const NodeDebuggerProvider = require('../src/providers/NodeDebugger') +/* eslint-env mocha */ +import FunctionTree from '../src' +import assert from 'assert' +import ContextProvider from '../src/providers/Context' +import NodeDebuggerProvider from '../src/providers/NodeDebugger' -module.exports['should expose debugger on context'] = (test) => { - const someLib = { - foo () {} - } - const execute = FunctionTree([ - NodeDebuggerProvider(), - ContextProvider({ - someLib - }) - ]) - - test.expect(1) - execute([ - function funcA (context) { - test.ok(context.debugger) +describe('NodeDebuggerProvider', () => { + it('should expose debugger on context', () => { + const someLib = { + foo () {} } - ]) - test.done() -} - -module.exports['should wrap methods on added object'] = (test) => { - const contextItem = { - foo () {} - } - const originalFunc = contextItem.foo - const execute = FunctionTree([ - NodeDebuggerProvider(), - ContextProvider({ - contextItem - }) - ]) + const execute = FunctionTree([ + NodeDebuggerProvider(), + ContextProvider({ + someLib + }) + ]) - test.expect(2) - execute([ - function funcA (context) { - test.equal(originalFunc, contextItem.foo) - test.notEqual(context.foo, originalFunc) + execute([ + function actionA (context) { + assert.ok(context.debugger) + } + ]) + }) + it('should wrap methods on added object', () => { + const contextItem = { + foo () {} } - ]) - test.done() -} + const originalFunc = contextItem.foo + const execute = FunctionTree([ + NodeDebuggerProvider(), + ContextProvider({ + contextItem + }) + ]) -module.exports['should wrap functions added to context'] = (test) => { - const contextItem = () => {} + execute([ + function actionA ({foo}) { + assert.equal(originalFunc, contextItem.foo) + assert.notEqual(foo, originalFunc) + } + ]) + }) + it('should wrap functions added to context', () => { + const contextItem = () => {} - const execute = FunctionTree([ - NodeDebuggerProvider(), - ContextProvider({ - contextItem - }) - ]) + const execute = FunctionTree([ + NodeDebuggerProvider(), + ContextProvider({ + contextItem + }) + ]) - test.expect(1) - execute([ - function funcA (context) { - test.notEqual(contextItem, context.contextItem) - } - ]) - test.done() -} + execute([ + function actionA (context) { + assert.notEqual(contextItem, context.contextItem) + } + ]) + }) +}) diff --git a/packages/function-tree/tests/pathProvider.js b/packages/function-tree/tests/pathProvider.js index a24b111bb..6481023e8 100644 --- a/packages/function-tree/tests/pathProvider.js +++ b/packages/function-tree/tests/pathProvider.js @@ -1,113 +1,101 @@ -'use strict' +/* eslint-env mocha */ +import FunctionTree from '../src' +import assert from 'assert' -const FunctionTree = require('../src') +describe('PathProvider', () => { + it('should add path function when paths can be taken', () => { + const execute = FunctionTree() -module.exports['should add path function when paths can be taken'] = (test) => { - const execute = FunctionTree() + execute([ + ({path}) => { + assert.ok(path.success) + assert.ok(path.error) + return path.success() + }, { + success: [], + error: [] + } + ]) + }) + it('should NOT add path function when paths can NOT be taken', () => { + const execute = FunctionTree() - test.expect(2) - execute([ - function action (context) { - test.ok(context.path.success) - test.ok(context.path.error) - return context.path.success() - }, { - success: [], - error: [] - } - ]) - test.done() -} - -module.exports['should NOT add path function when paths can NOT be taken'] = (test) => { - const execute = FunctionTree() - - test.expect(1) - execute([ - function action (context) { - test.ok(!context.path) - } - ]) - test.done() -} - -module.exports['should have possible outputs as methods'] = (test) => { - const execute = FunctionTree() + execute([ + ({path}) => { + assert.ok(!path) + } + ]) + }) + it('should have possible outputs as methods', () => { + const execute = FunctionTree() - test.expect(2) - execute([ - function action (context) { - test.ok(context.path.foo) - test.ok(context.path.bar) - return context.path.foo() - }, { - foo: [], - bar: [] - } - ]) - test.done() -} + execute([ + ({path}) => { + assert.ok(path.foo) + assert.ok(path.bar) + return path.foo() + }, { + foo: [], + bar: [] + } + ]) + }) + it('should go down path based on method used', (done) => { + const execute = FunctionTree() -module.exports['should go down path based on method used'] = (test) => { - const execute = FunctionTree() + execute([ + ({path}) => { + return path.foo() + }, { + foo: [ + () => { + assert.ok(true) + done() + } + ], + bar: [] + } + ]) + }) + it('should pass payload down paths', (done) => { + const execute = FunctionTree() - test.expect(1) - execute([ - function actionA (context) { - return context.path.foo() - }, { - foo: [ - function actionB () { - test.ok(true) - } - ], - bar: [] + execute([ + ({path}) => { + return path.foo({foo: 'bar'}) + }, { + foo: [ + ({input}) => { + assert.deepEqual(input, {foo: 'bar'}) + done() + } + ], + bar: [] + } + ]) + }) + it('should pass payload async', (done) => { + function actionA ({path}) { + return new Promise((resolve) => { + setTimeout(function () { + resolve(path.foo({foo: 'bar'})) + }) + }) } - ]) - test.done() -} - -module.exports['should pass payload down paths'] = (test) => { - const execute = FunctionTree() - test.expect(1) - execute([ - function actionA (context) { - return context.path.foo({foo: 'bar'}) - }, { - foo: [ - function actionB (context) { - test.deepEqual(context.input, {foo: 'bar'}) - } - ], - bar: [] + function actionB ({input}) { + assert.deepEqual(input, {foo: 'bar'}) } - ]) - test.done() -} - -module.exports['should pass payload async'] = (test) => { - function actionA (context) { - return new Promise((resolve) => { - setTimeout(function () { - resolve(context.path.foo({foo: 'bar'})) - }) - }) - } - - function actionB (context) { - test.deepEqual(context.input, {foo: 'bar'}) - } - const execute = FunctionTree() + const execute = FunctionTree() - test.expect(1) - execute([ - actionA, { - foo: [ - actionB - ], - bar: [] - } - ], test.done) -} + execute([ + actionA, { + foo: [ + actionB + ], + bar: [] + } + ], done) + }) +})