From 3cd2541f7f27bfa8098569f4f32fd163cc9cb583 Mon Sep 17 00:00:00 2001 From: boudin Date: Mon, 6 Jul 2015 00:39:38 +0200 Subject: [PATCH] Initial commit --- README.md | 152 ++++++++ lib/modular.js | 538 +++++++++++++++++++++++++++ lib/synceventemitter.js | 39 ++ package.json | 24 ++ test/autoload/test.autoload.js | 4 + test/constructor/nested/four.js | 9 + test/constructor/nested/last/five.js | 9 + test/constructor/one.js | 9 + test/constructor/two.js | 9 + test/loading/module.js | 9 + test/loading/module2.js | 9 + test/loading/module3.js | 9 + test/loading/module5.js | 11 + test/loading/module6.js | 9 + test/loading/nested/module4.js | 9 + test/override/constructor/one.js | 9 + test/override/constructor/two.js | 9 + test/test.js | 244 ++++++++++++ 18 files changed, 1111 insertions(+) create mode 100644 README.md create mode 100644 lib/modular.js create mode 100644 lib/synceventemitter.js create mode 100644 package.json create mode 100644 test/autoload/test.autoload.js create mode 100644 test/constructor/nested/four.js create mode 100644 test/constructor/nested/last/five.js create mode 100644 test/constructor/one.js create mode 100644 test/constructor/two.js create mode 100644 test/loading/module.js create mode 100644 test/loading/module2.js create mode 100644 test/loading/module3.js create mode 100644 test/loading/module5.js create mode 100644 test/loading/module6.js create mode 100644 test/loading/nested/module4.js create mode 100644 test/override/constructor/one.js create mode 100644 test/override/constructor/two.js create mode 100644 test/test.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..155f093 --- /dev/null +++ b/README.md @@ -0,0 +1,152 @@ +# MODULAR +Path abstraction and mocks for NodeJS. + +## Functionalities +* Automatically load javascript modules +* Abstract modules path. Access any module using namespace and module name +* Easy mock +* Live reload of changes + +## Quickstart +```javascript +var Modular = require('modular.js'); + +var application = new Modular({ + files : './app', + liveReload : true, + restartOnChanges : true, +}).on ('start', function(modular) { + modular('main').start(); +}).on ('stop', function(modular) { + modular('main').stop(); +}).on ('log', function (modular, level, message, error) { + console.log(error); +}).start(); +``` + +## Settings + +The modular constructor accept several settings : + +```javascript +{ + /* files can be a string for one file or folder */ + files : 'app/module.js', + /* or an array to load several files or folders */ + files : [ 'app/module.js', 'app/module2.js', 'app/subfolder' ], + /* an object allowing to setup the way the modules are loaded */ + files : { + /* the path is either a string for one file or folder */ + path : 'app/module.js', + /* or an array */ + path : [ 'app/module.js', 'app/module2.js', 'app/subfolder' ] + override : true, /* modules override already loaded modules in case of collision + default value : false */ + namespace : 'namespace', /* namespace is appended before the deduced namespace */ + basePath : 'path/path2', /* the base path used to deduce the namespace of modules + default value : path value (when path is a directory) */ + fullName : 'namespace.name',/* specify the full name of the module + Only when path is one file. Doesn't work with namespace setting */ + liveReload : true, /* enable or disable liveReload on a file/folder basis + default value : false */ + }, + basePath : /* Path used to deduce the namespace + default value : path of the file that load Modular */ + liveReload : true, /* enable live modification reload for whole application + default value : false */ + restartOnChange : true, /* automaticaly restart application when a module is reloaded + default value : liveReload value */ + exclude : ['app/module3.js'], /* those files won't be loaded */ + default value : file that load Modular + logLevel : 0 /* level of logging. 0 : error, 1 : info, 2 : verbose + default value : 0 */ +``` + +## Events +Modular communicate through messages. Those event aren't asynchronous. +This way, modular won't try to start application before it stopped. +If start or stop event return a Promise object, modular will wait for the Promise to be fulfilled to continue. + +### start +Emitted when the application must be started. +* Parameter : modular +* Return : Promise (optional) + +### stop +Emitted when the application must be stopped. +This event is optional +* Parameter : modular +* Return : Promise (optional) + +### restart +Emitted when the application must be restarted. +This event is optional +If this event is not used, modular will emit the stop event followed by start +* Parameter : modular +* Return : Promise (optional) + +### log +Emitted whenever log occurs. +This event is optional. +If this event is not defined, logs are written on stdout. +* Parameter : modular, message, error +* Return : nothing + +### loadModule +Emitted whenever a module will be loaded. +This event is optional. +You can change the settings used to load the module. +* Parameter : modular, settings (module parameters) +* Return : false to cancel module loading + +## Modules +Modular modules must look likes +```javascript +'use strict'; + +module.exports = function (modular) { + var mod = modular('namespace.module'); +} +``` +Or +```javascript +'use strict'; + +module.exports.onLoad = function (modular) { + var mod = modular('namespace.module'); +} +``` + +Modular will simply call those functions each time the module is required by another. +Those function are never invoked using the new statement and are called even if the nodejs module is already cached. +This means that each module manage its instance (a module can act as a singleton for example, another can always return a new instance). + +If the module does not directly exports a function or does not export onLoad function, the module is return as it is, but won't be able to load any modular module. + +## Tips + +### Exclude your main javascript file +Loading the main javascript file with Modular is useless, watching it for changes can lead to weird behavior. +If no file is excluded, Modular will exclude its parent by default. + +You can still load the directory containing your main file by excluding it. The easiest way is to use the node __filename property : +```javascript +var Modular = require('modular.js'); + +var application = new Modular({ + files : './', + exclude : [ __filename], + liveReload : true, + restartOnChange : true, +}).on ('start', function(modular) { + modular('main').start(); +}).on ('stop', function(modular) { + modular('main').stop(); +}).on ('log', function (modular, level, message, err) { + if (err) { + console.log(err); + } +}).start(); +``` + +### diff --git a/lib/modular.js b/lib/modular.js new file mode 100644 index 0000000..38e5d1c --- /dev/null +++ b/lib/modular.js @@ -0,0 +1,538 @@ +'use strict'; + +var path = require('path'); +var fs = require('fs'); +var SyncEventEmitter = require(path.join(__dirname, 'synceventemitter.js')); + +function Modular (options) { + SyncEventEmitter.call(this); + + var settings; + var modules = new Map(); //Map fullName => Module + var instance = this; + var watchers; + var watchedFiles; + var autoloadModules = []; + + process.chdir(path.dirname(module.parent.filename)); + + //Default options if not provided + settings = options || {}; + + watchedFiles = new Map(); + watchers = new Map(); + settings.restartOnChange = settings.restartOnChange === true; + + if (settings.files === undefined) { + settings.files = []; + } + + if (settings.exclude === undefined) { + settings.exclude = [module.parent.filename]; + } + + if (settings.basePath === undefined) { + settings.basePath = path.dirname(module.parent.filename); + } + + if (settings.logLevel === undefined) { + settings.logLevel = 0; + } + +/* process.on('uncaughtException', function (err) { + log ('error', 'Unhandled exception', err); + }); + + process.on('unhandledRejection', function(reason, p) { + log('error', 'Unhandled rejection at: Promise ', p, ' reason: ', reason); + }); +*/ + + var applicationLoaded = false; + + //Start the application + this.start = function () { + //Load application files + if (!applicationLoaded && settings.files && settings.files !== undefined) { + if (!Array.isArray(settings.files)) { + settings.files = [ settings.files ]; + } + + for (let file of settings.files) { + if (file instanceof Object) { + load (file); + } + else { + load ({ path : file }); + } + } + + applicationLoaded = true; + } + + try { + //Load modules tagged as autoload + for (var mod of autoloadModules) { + loadModule(mod); + } + + autoloadModules = []; + return instance.emit('start', get.bind(undefined, undefined)); + } + catch (exception) { + log ('error', exception); + } + }; + + //Stop the application + this.stop = function () { + return instance.emit('stop', get.bind(undefined, undefined)); + }; + + //Restart the application + this.restart = function () { + if (instance.hasListener('restart')) { + return instance.emit('restart', get.bind(undefined, undefined)); + } + else if (instance.hasListener('stop')){ + + var promise = instance.stop(); + + if (promise && promise.then) { + promise.then(function() { + instance.start(); + }); + } + else { + instance.start(); + } + } + }; + + //Log + function log (level, message) { + if (instance.hasListener('log')) { + instance.emit('log', get.bind(undefined, undefined), message); + } + else { + console.log('['+level+']', message); + + if (level === 'error') { + if (message instanceof Error) { + throw message; + } + else if (message) { + throw new Error(message); + } + else { + throw new Error(); + } + } + } + } + + //Function passed to every modular module + function get (caller, name) { + var mod; + + //Search for the module in caller namespace first + if (caller && modules.has (caller.namespace && caller.namespace.length > 0 + ? caller.namespace + '.' + name + : name)) { + mod = modules.get ( + caller.namespace && caller.namespace.length > 0 + ? caller.namespace + '.' + name + : name); + } + else if (modules.has(name)) { + mod = modules.get(name); + } + + //If the module as been found + if (mod) { + if (caller) { + if (mod.childrenOf === undefined) { + mod.childrenOf = []; + } + + if (mod.childrenOf.indexOf(caller) < 0) { + mod.childrenOf.push(caller); + } + + if (caller.parentOf === undefined) { + caller.parentOf = []; + } + + if (caller.parentOf.indexOf(mod) < 0) { + caller.parentOf.push(mod); + } + } + + return loadModule(mod); + } + else { + log ('error', 'Unresolved dependency ' + name); + } + } + + function loadModule (mod) { + if (mod.module === undefined) { + mod.module = require(mod.fullPath); + } + + if (mod.module instanceof Function) { + let instanceModule = mod.module(get.bind(undefined,mod)); + return instanceModule ? instanceModule : mod.module; + } + else if (mod.module.onLoad instanceof Function){ + let instanceModule = mod.module.onLoad(get.bind(undefined, mod)); + return instanceModule ? instanceModule : mod.module; + } + else { + return mod.module; + } + } + + + //Register a new module + //file : path of the module + //name (optional) : override the full name of the module (namespace and name) + //override (optional) : if a module with the same full name is already registered, it's overriden + function register (options) { + var autoload = false; + var opts = options; + + //Default options + if (opts.override === undefined) { + opts.override = false; + } + + if (opts.liveReload === undefined) { + opts.liveReload = settings.liveReload; + } + + opts.fullPath = path.isAbsolute(opts.path) ? opts.path : path.resolve(opts.path); + + //If the file is excluded + if (settings.exclude.indexOf(opts.fullPath) >= 0) { + return; + } + + if (opts.fullPath.endsWith('autoload.js')) { + autoload=true; + } + + //Name parameter override module namespace and module name + if (opts.namespace === undefined && opts.name === undefined) { + //Every folder of the module path is a part of the namespace + opts.namespace = path.relative((opts.basePath || settings.basePath) , path.dirname(opts.fullPath)) + .replace(/\./g,'') + .replace(/\//g,'.') + .replace(/^\.*/g,''); + + //Name is the basename of the module + opts.name = path.basename(opts.fullPath, autoload ? '.autoload.js' : path.extname(opts.fullPath)); + + //Replace all . char in class name by _ + if (opts.name.indexOf('.') >= 0) { + opts.name = opts.name.replace(/\./g,'_'); + } + + //Adding the base namespace + if (opts.baseNamespace !== undefined) { + opts.namespace = opts.baseNamespace.replace('/\./g','_') + '.' + opts.namespace; + } + } + + //Calling the module loading hook + if (instance.hasListener('loadModule') && instance.emit('loadModule', opts) === false) { + return; + } + + var fullName = opts.namespace && opts.namespace.length > 0 ? opts.namespace + '.' + opts.name : opts.name; + + var alreadyLoaded = modules.has(fullName); + + if (!alreadyLoaded || opts.override) { + if (alreadyLoaded && opts.fullPath !== modules.get(fullName).fullPath) { + //Delete overriden module cache + delete require.cache[modules.get(fullName).fullPath]; + } + + if (settings.logLevel > 0) { + log ('info', (fullName + ' loaded from ' + opts.fullPath)); + } + + opts.module = undefined; + + //Clear the cache of the module if exists + if (require.cache[opts.fullPath]) { + delete require.cache[opts.fullPath]; + } + + //Autoload the module + if (autoload) { + autoloadModules.push(opts); + } + + modules.set(fullName, opts); + + if (settings.liveReload || opts.liveReload) { + watchedFiles.set(opts.fullPath, opts); + } + return true; + } + else if (settings.logLevel > 0) { + log ('info', fullName + ' skipped from ' + opts.fullPath); + } + return false; + } + + //Load all the modules and submodules from a specified path + function load (options) { + if (!Array.isArray(options.path)) { + options.path = [options.path]; + } + + for (var file of options.path) { + var opts = clone(options); + var fullPath; + + opts.path = file; + + if (!path.isAbsolute(file)) { + fullPath = path.resolve(file); + } + else { + fullPath = file; + } + + var stats = fs.statSync(fullPath); + + if (stats.isFile()) { + if (settings.liveReload || options.liveReload) { + watch (fullPath); + } + + register(opts); + } + else if (stats.isDirectory()) { + loadFolder(fullPath, opts); + } + } + } + + + //Load files from a path. + //Since it's only used before the application start, file operations are sync + function loadFolder (filePath, options) { + if (settings.liveReload || options.liveReload) { + watch(filePath); + } + + var files = fs.readdirSync(filePath); + for (var file of files) { + var fullPath = path.join(filePath, file); + var stats = fs.statSync(fullPath); + + if (stats.isFile && path.extname(fullPath) === '.js') { + let opts = clone(options); + opts.path = fullPath; + + register(opts); + } + else if (stats.isDirectory()) { + loadFolder(fullPath, options); + } + } + } + + //Watch a folder for changes + function watch(watchedPath) { + var reloadTimeout; + var modifiedFiles = []; + + //Search for module already watched in this directory + var itWatch = watchers.keys(); + var cur = itWatch.next(); + + var fileStats = fs.statSync(watchedPath); + var isFile = true; + + if (watchers.has(watchedPath)) { + //Path is already watched + return; + } + else if (fileStats.isDirectory()) { + //Close all watchers aiming files in the directory + while (cur && cur.value) { + if (path.extname(cur.value) === '.js' + && path.dirname(cur.value) === watchedPath) { + var watchedFile = watchers.get(cur.value); + watchedFile.close(); + watchers.delete(cur.value); + } + cur = itWatch.next(); + } + isFile = false; + } + else if (fileStats.isFile()){ + //Target is a file in a directory already watched + var directory = path.dirname(watchedPath); + if (watchers.has(directory)) { + return; + } + } + + //Watcher is used to detect file changes + var watcher = fs.watch(watchedPath, function (ev, fileName) { + var fullName = isFile ? watchedPath : path.join(watchedPath, fileName); + + //Some editors like vim will actually rename the file, then modify it. + //If temporary file is used, the last event can be on a file that does not exists any more + //We listen for all the events until 1 second occurs without anything. Then we treat all modified files + if (modifiedFiles.indexOf(fullName) < 0) { + modifiedFiles.push(fullName); + } + + if (reloadTimeout) { + clearTimeout(reloadTimeout); + } + + reloadTimeout = setTimeout(function() { + reloadTimeout = undefined; + var stats; + + for (let file of modifiedFiles) { + let exists = true; + + //Check if the file exists + try { + stats = fs.statSync(file); + } + catch (err) { + exists = false; + } + + var isModule = path.extname(file) === '.js'; + var module = isModule ? watchedFiles.get(file) : null; + var fileWatcher = watchers.has(file) ? watchers.get(file) : null; + var needRestart = false; + var clearParentsCache = false; + + if (exists) { + if (stats.isFile() && isModule) { + //This is a module + try { + if (module !== null) { + if (!module.override) { + module.override = true; + } + clearParentsCache = true; + } + register(module); + needRestart = settings.restartOnChange; + } + catch (exception) { + if (settings.logLevel >= 0) { + log ('error','Module '+file+ ' not reloaded : ' + exception ); + } + } + } + else if (stats.isDirectory() && !watchers.has(file)) { + watch(file); + } + + //Reset the watcher if needed + if (ev === 'rename' && fileWatcher !== null) { + fileWatcher.close(); + watchers.delete(file); + watch(file); + } + } + else if (module !== null) { + unRegister(module); + + if (settings.logLevel > 0) { + log ('info', module.fullName + ' unregistered'); + } + + clearParentsCache = true; + needRestart = settings.restartOnChange; + } + else if (fileWatcher !== null) { + fileWatcher.close(); + watchers.delete(file); + } + + if (clearParentsCache) { + if (module.childrenOf !== undefined) { + for (let parentMod of module.childrenOf) { + clearCache(parentMod); + } + } + } + + if (needRestart) { + instance.restart(); + } + } + + modifiedFiles = []; + + },1000); + }); + + watchers.set(watchedPath, watcher); + } + + //Remove a module and clear its nodejs cache + function unRegister (module) { + if (modules.has(module.fullName)){ + if (module.parentOf !== undefined) { + for (var children of module.parentOf) { + if (children.childrenOf !== undefined && children.childrenOf.length > 0) { + children.childrenOf.splice(children.childrenOf.indexOf(module), 1); + } + } + } + + modules.delete(module.fullName); + delete require.cache[module.file]; + } + } + + //Clear module and all depedencies cache + function clearCache(module) { + if (module.childrenOf !== undefined) { + for (let parentMod of module.chidrenOf) { + clearCache(parentMod); + } + } + module.override = true; + + register(module); + } +} + +function clone(obj) { + var object = {}; + + for (let prop in obj) { + if (Array.isArray(obj[prop])) { + object[prop] = obj[prop].slice(0); + } + else { + object[prop] = obj[prop]; + } + } + + return object; +} + +Modular.prototype = new SyncEventEmitter(); +Modular.prototype.constructor = SyncEventEmitter; + +module.exports = function (settings) { + return new Modular(settings); +}; diff --git a/lib/synceventemitter.js b/lib/synceventemitter.js new file mode 100644 index 0000000..3c88f47 --- /dev/null +++ b/lib/synceventemitter.js @@ -0,0 +1,39 @@ +'use strict'; + +function SyncEventEmitter () { + this.listeners = new Map(); +} + +SyncEventEmitter.prototype.on = function (ev, callback) { + if (this.listeners.has(ev)) { + this.listeners.remove(ev); + } + + this.listeners.set(ev, callback); + + return this; +}; + +SyncEventEmitter.prototype.emit = function (ev) { + if (this.listeners.has(ev)) { + let params = []; + + for (var i = 1 ; i < arguments.length ; ++i) { + params.push(arguments[i]); + } + + return this.listeners.get(ev).apply(undefined, params); + } +}; + +SyncEventEmitter.prototype.removeListener = function (ev) { + if (this.listeners.has(ev)) { + this.listeners.remove(ev); + } +}; + +SyncEventEmitter.prototype.hasListener = function (ev) { + return this.listeners.has(ev); +}; + +module.exports = SyncEventEmitter; diff --git a/package.json b/package.json new file mode 100644 index 0000000..2cbdca7 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "modular", + "version": "1.0.0", + "description": "Module path abstraction and mock framework", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://lab.cherboiche.org/boudin/modular.git" + }, + "keywords": [ + "module", + "mock", + "live", + "reload" + ], + "author": "boudin", + "license": "GPL-3.0", + "devDependencies": { + "mocha": "^2.2.5" + } +} diff --git a/test/autoload/test.autoload.js b/test/autoload/test.autoload.js new file mode 100644 index 0000000..23602fd --- /dev/null +++ b/test/autoload/test.autoload.js @@ -0,0 +1,4 @@ +'use strict'; +module.exports = function() { + throw new Error('autoloaded'); +}; diff --git a/test/constructor/nested/four.js b/test/constructor/nested/four.js new file mode 100644 index 0000000..d45a895 --- /dev/null +++ b/test/constructor/nested/four.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = function (modular) { + return { + run : function () { + return 4; + } + }; +}; diff --git a/test/constructor/nested/last/five.js b/test/constructor/nested/last/five.js new file mode 100644 index 0000000..5d5bd1c --- /dev/null +++ b/test/constructor/nested/last/five.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = function (modular) { + return { + run : function () { + return 5; + } + }; +}; diff --git a/test/constructor/one.js b/test/constructor/one.js new file mode 100644 index 0000000..a7494aa --- /dev/null +++ b/test/constructor/one.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = function (modular) { + return { + run : function () { + return 1; + } + }; +}; diff --git a/test/constructor/two.js b/test/constructor/two.js new file mode 100644 index 0000000..5ec6e03 --- /dev/null +++ b/test/constructor/two.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = function (modular) { + return { + run : function () { + return 2; + } + }; +}; diff --git a/test/loading/module.js b/test/loading/module.js new file mode 100644 index 0000000..94b10c1 --- /dev/null +++ b/test/loading/module.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = function (modular) { + return { + run: function () { + return modular('module2').run(); + } + }; +}; diff --git a/test/loading/module2.js b/test/loading/module2.js new file mode 100644 index 0000000..dd415b6 --- /dev/null +++ b/test/loading/module2.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = function() { + return { + run : function () { + return 1; + } + }; +}; diff --git a/test/loading/module3.js b/test/loading/module3.js new file mode 100644 index 0000000..c672280 --- /dev/null +++ b/test/loading/module3.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = function (modular) { + return { + run: function () { + return modular('nested.module4').run(); + } + }; +}; diff --git a/test/loading/module5.js b/test/loading/module5.js new file mode 100644 index 0000000..d906a29 --- /dev/null +++ b/test/loading/module5.js @@ -0,0 +1,11 @@ +'use strict'; + +var mod; + +module.exports.onLoad = function (modular) { + mod = modular('module6'); +}; + +module.exports.run = function () { + return mod.run(); +}; diff --git a/test/loading/module6.js b/test/loading/module6.js new file mode 100644 index 0000000..dd415b6 --- /dev/null +++ b/test/loading/module6.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = function() { + return { + run : function () { + return 1; + } + }; +}; diff --git a/test/loading/nested/module4.js b/test/loading/nested/module4.js new file mode 100644 index 0000000..dd415b6 --- /dev/null +++ b/test/loading/nested/module4.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = function() { + return { + run : function () { + return 1; + } + }; +}; diff --git a/test/override/constructor/one.js b/test/override/constructor/one.js new file mode 100644 index 0000000..56511c3 --- /dev/null +++ b/test/override/constructor/one.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = function (modular) { + return { + run : function () { + return 3; + } + }; +}; diff --git a/test/override/constructor/two.js b/test/override/constructor/two.js new file mode 100644 index 0000000..d45a895 --- /dev/null +++ b/test/override/constructor/two.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = function (modular) { + return { + run : function () { + return 4; + } + }; +}; diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000..60875ad --- /dev/null +++ b/test/test.js @@ -0,0 +1,244 @@ +/* + * Unit tests for modular + */ +'use strict'; + +var modular = require('../lib/modular.js'); + +var assert = require('assert'); + +//Load a file without any options +describe('Modular', function() { + describe('Settings', function() { + it('load a single module', function() { + modular({ + files : './constructor/one.js', + }).on('start', function(modular) { + assert.equal (1, modular('constructor.one').run()); + }).start(); + }); + + it('load a single module with settings', function() { + modular({ + files : { + path: './constructor/one.js', + } + }).on('start', function(modular) { + assert.equal (1, modular('constructor.one').run()); + }).start(); + }); + + it('load two modules', function() { + modular({ + files : [ './constructor/one.js', './constructor/two.js' ], + }).on('start', function(modular) { + assert.equal (1, modular('constructor.one').run()); + assert.equal (2, modular('constructor.two').run()); + }).start(); + }); + + it('load two modules with settings', function() { + modular({ + files : [ { + path: './constructor/one.js' + }, { + path: './constructor/two.js' + }], + }).on('start', function(modular) { + assert.equal (1, modular('constructor.one').run()); + assert.equal (2, modular('constructor.two').run()); + }).start(); + }); + + it('load a directory', function() { + modular({ + files : './constructor', + }).on('start', function(modular) { + assert.equal (1, modular('constructor.one').run()); + assert.equal (2, modular('constructor.two').run()); + }).start(); + }); + + it('load a directory with settings', function() { + modular({ + files : { + path: './constructor' + } + }).on('start', function(modular) { + assert.equal (1, modular('constructor.one').run()); + assert.equal (2, modular('constructor.two').run()); + }).start(); + }); + + it('load a single module with namespace and name', function() { + modular({ + files : { + path: './constructor/one.js', + namespace: 'namespace', + name: 'class' + } + }).on('start', function(modular) { + assert.equal (1, modular('namespace.class').run()); + }).start(); + }); + + it('load a directories with override', function() { + modular({ + files : ['./constructor', { + path: './override', + basePath: './override', + override: true + } + ] + }).on('start', function(modular) { + assert.equal (3, modular('constructor.one').run()); + assert.equal (4, modular('constructor.two').run()); + }).start(); + }); + + it('load a directories with no override', function() { + modular({ + files : ['./constructor', { + path: './override', + override: false + } + ] + }).on('start', function(modular) { + assert.equal (1, modular('constructor.one').run()); + assert.equal (2, modular('constructor.two').run()); + }).start(); + }); + + it('load nested directories', function() { + modular({ + files : './constructor' + }).on('start', function(modular) { + assert.equal (4, modular('constructor.nested.four').run()); + assert.equal (5, modular('constructor.nested.last.five').run()); + }).start(); + }); + + it('load directory with global base path', function() { + modular({ + files : './constructor', + basePath : './constructor' + }).on('start', function(modular) { + assert.equal (1, modular('one').run()); + assert.equal (2, modular('two').run()); + }).start(); + }); + + it('load directory with per directory base path', function() { + modular({ + files : [{ + path: './constructor', + basePath : './constructor' + },{ + path: './override' + }] + }).on('start', function(modular) { + assert.equal (1, modular('one').run()); + assert.equal (2, modular('two').run()); + assert.equal (3, modular('override.constructor.one').run()); + }).start(); + }); + + it('load file with global base path', function() { + modular({ + files : './constructor/one.js', + basePath : './constructor' + }).on('start', function(modular) { + assert.equal (1, modular('one').run()); + }).start(); + }); + + it('load file with per file base path', function() { + modular({ + files : { + path: './constructor/one.js', + basePath : './constructor' + } + }).on('start', function(modular) { + assert.equal (1, modular('one').run()); + }).start(); + }); + + it('load file with base namespace', function() { + modular({ + files : { + path: './constructor/one.js', + baseNamespace: 'namespace' + } + }).on('start', function(modular) { + assert.equal (1, modular('namespace.constructor.one').run()); + }).start(); + }); + + it('load directory with base namespace', function() { + modular({ + files : { + path: './constructor', + baseNamespace: 'namespace' + } + }).on('start', function(modular) { + assert.equal (1, modular('namespace.constructor.one').run()); + assert.equal (2, modular('namespace.constructor.two').run()); + }).start(); + }); + + it('autoload modules', function() { + assert.throws(function(){ + modular({ + files : { + path: './autoload', + } + }).start(); + }, + /autoloaded/); + }); + + it('exclude module', function() { + assert.throws(function(){ + modular({ + files : './constructor', + exclude: ['./constructor/two.js'] + }).on('start', function(modular) { + modular('two').run(); + }).start(); + }, + /Unresolved dependency two/); + }); + }); + + describe ('Modules', function() { + it('load modules in the same namespace', function() { + modular({ + files: './loading' + }).on('start', function (modular) { + assert.equal (1, modular('loading.module').run()); + }).start(); + }); + + it('load modules in sub namespace', function() { + modular({ + files: './loading' + }).on('start', function (modular) { + assert.equal (1, modular('loading.module3').run()); + }).start(); + }); + + it('access modules through onLoad function', function() { + modular({ + files: ['./loading/module5.js', + './loading/module6.js' ] + }).on('start', function (modular) { + assert.equal (1, modular('loading.module5').run()); + }).start(); + }); + }); +}); +/* A tester + * Live reload + * Autorestart + * Log + */