test-scribe
Version:
Your sidekick for analog machine development. Run machines interactively and generate automated test scripts based on the outcome.
149 lines (121 loc) • 5.47 kB
JavaScript
/**
* Wire up an angular module.
*
* @required {[type]} moduleName [description]
* @required {[type]} def [description]
* @optional {[type]} angular [description]
* @optional {[type]} _ [description]
*/
function wireUp (moduleName, def, angular, _){
// Locate angular and lodash.
if (typeof _ === 'undefined') {
_ = window._;
}
if (typeof angular === 'undefined') {
angular = window.angular;
}
// Validate
if (!_.isString(moduleName)) {
throw new Error ('`moduleName` is a required string. Should be a valid angular module name.');
}
if (!_.isObject(def)) {
throw new Error('Invalid definition.');
}
// Handle dependencies
var angularModuleDependencyNames;
if (!_.isUndefined(def.dependencies)) {
if (!_.isArray(def.dependencies)) {
throw new Error('`dependencies` must be an array of strings; the names of other Angular modules this one depends on (this is where you put your `ngRoute` and your `negotiateKeyboardEvent` and your `ngTouch` and such).');
}
angularModuleDependencyNames = _.cloneDeep(def.dependencies||[]);
}
// Set up the module
angular.module(moduleName, angularModuleDependencyNames);
var angularModule = angular.module(moduleName);
// Handle afterRender
var afterRenderFn = function (){};
if (!_.isUndefined(def.afterRender)) {
if (!_.isFunction(def.afterRender)) {
throw new Error('`afterRender` must be a function with `imports` (an object) as its first and only argument.');
}
afterRenderFn = def.afterRender;
}
// Handle imports
var angularProviderNames = [];
if (!_.isUndefined(def.imports)) {
if (!_.isArray(def.imports)) {
throw new Error('`imports` must be an array of strings; the names of Angular providers to import (you know, for that Angular-compatible AMD definition thing).');
}
angularProviderNames = angularProviderNames.concat(def.imports);
}
// TODO: if $scope is not provided as an import, cannot set `variables` or `functions`
// Handle variables
var scopeVars = {};
if (!_.isUndefined(def.variables)) {
if (!_.isObject(def.variables)) {
throw new Error('`variables` must be an object of example values for $scope variables.');
}
// TODO: coerce/sanitize each initial variable
_.extend(scopeVars, def.variables);
}
// Handle functions
var scopeFnDefs = {};
if (!_.isUndefined(def.functions)) {
if (!_.isObject(def.functions)) {
throw new Error('`functions` must be an object of functions to attach to the $scope.');
}
_.extend(scopeFnDefs, def.functions);
}
// Set up controller
//
// (TODO: at some point, make this so that you don't have to manually attach the
// controller-- it should be automatically attached when you specify your ng-app.)
angularModule.controller('afterRender', angularProviderNames.concat(function () {
// TODO: should prbly do this on a method-by-method basis instead of doing it once
// Build `imports` by grabbing arguments and munging it with `angularProviderNames`
var imports = _.reduce(Array.prototype.slice.call(arguments), function (imports, instantiatedProvider, i){
var providerName = angularProviderNames[i];
imports[providerName] = instantiatedProvider;
return imports;
}, {});
// Always provide access to `_`, `$`, and `angular` (if they're not already there)
imports.angular = _.isUndefined(imports.angular) ? angular : imports.angular;
imports.$ = _.isUndefined(imports.angular.element) ? angular : imports.angular.element;
imports._ = _.isUndefined(imports._) ? _ : imports._;
if (imports.$scope) {
// hack (expose $scope on window for convenience during development)
SCOPE = imports.$scope;
// Bind initial values to scope
_.each(scopeVars, function (exampleVal, name){
// TODO: get base value for scope var examples and bind that to scope.
// var initialVal = rttc.coerce(exampleVal);
// imports.$scope[name] = initialVal;
// For now, just take care of the most critical stuff.
if (_.isArray(exampleVal)) {
imports.$scope[name] = [];
}
else if (_.isObject(exampleVal)) {
imports.$scope[name] = {};
}
});
// Bind declared functions to scope
_.each(scopeFnDefs, function (fnDef, name){
// TODO: validate each function
// TODO: allow definition to be EITHER a machine def OR a simple function
// TODO: if it's a machine def, make it callable as a function
// args: ['identity']
// ^^^ todo: allow something like this to be passed in machine def to facilitate easier usage from angular HTML templates. Otherwise always expects a single argument-- "inputs".
// Intercept function and attach to scope
imports.$scope[name] = function interceptScopeFn(){
var inputs = Array.prototype.slice.call(arguments)[0] || {};
return fnDef(inputs, imports);
};
// Note for fn implementation:
// imports.setScope('currentMachine.isDefined', true); <== consider sending in something like that instead of a direct reference to $scope.
// var isDefined = imports.getScope('currentMachine.isDefined'); <== consider sending in something like that instead of a direct reference to $scope.
});
}
// Run `afterRender` function
afterRenderFn(imports);
}));
}