Comparing version 0.2.5 to 0.3.0
var yuidoc2md = require("yuidoc2md"); | ||
var docs = yuidoc2md.getMarkdown("lib/nature.js") + yuidoc2md.getMarkdown("lib/Thing.js") + yuidoc2md.getMarkdown("lib/PropertyDefinition.js"); | ||
module.exports = function(grunt){ | ||
@@ -8,20 +10,17 @@ grunt.initConfig({ | ||
options: { | ||
templateData: { | ||
APIdocs: yuidoc2md.getMarkdown([ | ||
"lib/nature.js", | ||
"lib/Thing.js", | ||
"lib/PropertyDefinition.js" | ||
]) | ||
} | ||
data: { APIdocs: docs } | ||
}, | ||
create: { | ||
name: "README.md", | ||
content: grunt.file.read("tasks/boil/readme.hbs") | ||
} | ||
src: "boil/README.hbs", | ||
dest: "README.md" | ||
} | ||
}, | ||
jshint: { | ||
options: { jshintrc: true }, | ||
all: ['Gruntfile.js', 'lib/*.js', 'test/*.js'] | ||
} | ||
}); | ||
grunt.loadNpmTasks("grunt-boil"); | ||
grunt.loadNpmTasks("grunt-boil"); | ||
grunt.loadNpmTasks("grunt-contrib-jshint"); | ||
grunt.registerTask("default", "boil"); | ||
}; |
@@ -1,2 +0,3 @@ | ||
var util = require("util"); | ||
"use strict"; | ||
var wodge = require("wodge"); | ||
@@ -10,12 +11,84 @@ /** | ||
var self = this, | ||
_value, | ||
_valueTest, | ||
_type, | ||
_validType, | ||
_validValue, | ||
_required = properties.required || false, | ||
_invalidMsg = properties.invalidMsg, | ||
_type = properties.type, | ||
_valueTest = properties.valueTest, | ||
_value = properties.value, | ||
_valid, | ||
_validationMessages = []; | ||
if (!properties.name) | ||
throw new Error("must specify a name on the PropertyDefinition"); | ||
function addValidationMessage(msg){ | ||
_validationMessages.push(msg); | ||
} | ||
function validateValue(){ | ||
if (_required){ | ||
if (!_value) { | ||
addValidationMessage("Missing required value"); | ||
} | ||
} | ||
if (_value !== undefined){ | ||
if (typeof _type === "string"){ | ||
if (typeof _value !== _type){ | ||
addValidationMessage(_value + " is not a string"); | ||
} | ||
} else if (typeof _type === "function"){ | ||
if (!(_value instanceof _type)){ | ||
addValidationMessage("Value is not an instance of " + _type.name); | ||
} | ||
} | ||
wodge.arrayify(_valueTest).forEach(function(valueTest){ | ||
if(valueTest instanceof RegExp){ | ||
/* | ||
tested value must not be null or undefined, as `/\w+/.test(undefined)` returns true | ||
*/ | ||
if (_value){ | ||
if (!valueTest.test(_value)){ | ||
addValidationMessage("Failed valueTest: " + _value); | ||
} | ||
} else { | ||
addValidationMessage("Failed valueTest: no value to test"); | ||
} | ||
} else if(typeof valueTest === "function"){ | ||
try{ | ||
var extras = { | ||
addValidationMessage: addValidationMessage, | ||
thing: properties.parent | ||
}; | ||
var result = valueTest.call(extras, _value); | ||
if (!result){ | ||
addValidationMessage("Failed valueTest function: " + _value); | ||
} | ||
} catch(e){ | ||
addValidationMessage("valueTest function crashed: " + e); | ||
} | ||
} else { | ||
if (_value !== valueTest){ | ||
addValidationMessage("value does not match valueTest"); | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
function validate(){ | ||
_valid = true; | ||
_validationMessages.splice(0); | ||
validateValue(); | ||
if (_validationMessages.length){ | ||
_valid = false; | ||
if (self.invalidMsg) addValidationMessage(self.invalidMsg); | ||
if (self.throwOnInvalid){ | ||
console.log("Property: " + self.name); | ||
console.log(_validationMessages.join("\n")); | ||
throw new Error("INVALID"); | ||
} | ||
} | ||
} | ||
/** | ||
@@ -26,33 +99,33 @@ Gets/sets the property value. Will attempt to convert values to Number for definitions of `type` "number". | ||
*/ | ||
Object.defineProperty(this, "value", { get: getValue, set: setValue, enumerable: true }); | ||
function getValue(){ | ||
return _value; | ||
} | ||
function setValue(newValue){ | ||
// attempt to convert newValue to Number | ||
if (this.type === "number" && !isNaN(parseInt(newValue))){ | ||
_value = Number(newValue); | ||
Object.defineProperty(this, "value", { | ||
enumerable: true, | ||
get: function(){ return _value; }, | ||
set: function(newValue){ | ||
// typecast to Number | ||
if (this.type === "number" && wodge.isNumber(newValue)){ | ||
_value = Number(newValue); | ||
// on an Array, attempt to convert newValue to string and split it | ||
} else if (this.type === Array && newValue && !Array.isArray(newValue)){ | ||
if (typeof newValue === "object"){ | ||
_value = [ newValue ]; | ||
// typecast to Array | ||
} else if (this.type === Array && newValue && !Array.isArray(newValue)){ | ||
if (typeof newValue === "object"){ | ||
_value = [ newValue ]; | ||
} else { | ||
_value = newValue.toString().split(",").map(function(val){ return val.trim(); }); | ||
} | ||
// typecast to RegExp | ||
} else if (this.type === RegExp && typeof newValue === "string"){ | ||
try{ | ||
_value = new RegExp(newValue); | ||
} catch(e) { | ||
_value = newValue; | ||
} | ||
// every other case, just set the value | ||
} else { | ||
_value = newValue.toString().split(",").map(function(val){ return val.trim() }); | ||
} | ||
// cast newValue to RegExp | ||
} else if (this.type === RegExp && typeof newValue === "string"){ | ||
try{ | ||
_value = RegExp(newValue); | ||
} catch(e) { | ||
_value = newValue; | ||
} | ||
// every other case, just set the value | ||
} else { | ||
_value = newValue; | ||
validate(); | ||
} | ||
validate(); | ||
} | ||
}); | ||
@@ -70,10 +143,10 @@ /** | ||
*/ | ||
Object.defineProperty(this, "type", { enumerable: true, get: getType, set: setType }); | ||
function setType(newValue){ | ||
_type = newValue; | ||
validate(); | ||
} | ||
function getType(){ | ||
return _type; | ||
} | ||
Object.defineProperty(this, "type", { | ||
enumerable: true, | ||
get: function(){ return _type; }, | ||
set: function(newValue){ | ||
_type = newValue; | ||
validate(); | ||
} | ||
}); | ||
@@ -99,39 +172,37 @@ /** | ||
*/ | ||
Object.defineProperty(this, "valueTest", { get: getValueTest, set: setValueTest, enumerable: true }); | ||
function getValueTest(){ | ||
return _valueTest; | ||
} | ||
function setValueTest(newValue){ | ||
_valueTest = newValue; | ||
validate(); | ||
} | ||
Object.defineProperty(this, "valueTest", { | ||
enumerable: true, | ||
get: function(){ return _valueTest; }, | ||
set: function(newValue){ | ||
_valueTest = newValue; | ||
validate(); | ||
} | ||
}); | ||
function validate(){ | ||
_validationMessages.splice(0); | ||
_validType = isValidType(self.value, self.type); | ||
if (!_validType){ | ||
_validationMessages.push(self.typeFailMsg || "Invalid type: " + self.value); | ||
/** | ||
Thing instance will remain invalid until a value is set | ||
@property required | ||
@type Boolean | ||
*/ | ||
Object.defineProperty(this, "required", { | ||
enumerable: true, | ||
get: function(){ return _required; }, | ||
set: function(newValue){ | ||
_required = newValue; | ||
validate(); | ||
} | ||
}); | ||
if (self.value === undefined){ | ||
if (self.required){ | ||
_validValue = false; | ||
_validationMessages.push(self.valueFailMsg || "Missing required value"); | ||
} else { | ||
_validValue = true; | ||
} | ||
} else { | ||
_validValue = isValidValue(self.value, self.valueTest, properties.parent, _validationMessages); | ||
if (!_validValue){ | ||
_validationMessages.push(self.valueFailMsg || "Invalid value: " + self.value); | ||
} | ||
/** | ||
@property invalidMsg | ||
@type string | ||
*/ | ||
Object.defineProperty(this, "invalidMsg", { | ||
enumerable: true, | ||
get: function(){ return _invalidMsg; }, | ||
set: function(newValue){ | ||
_invalidMsg = newValue; | ||
validate(); | ||
} | ||
if (self.throwOnInvalid && (!_validValue || !_validType)){ | ||
console.log("Property: " + self.name); | ||
console.log(_validationMessages.join("\n")); | ||
throw new Error("INVALID"); | ||
} | ||
} | ||
}); | ||
@@ -142,19 +213,15 @@ /** | ||
*/ | ||
Object.defineProperty(this, "valid", { get: getValid, enumerable: true }); | ||
function getValid(){ | ||
return _validType && _validValue; | ||
} | ||
Object.defineProperty(this, "valid", { | ||
enumerable: true, | ||
get: function(){ return _valid; }, | ||
}); | ||
/** | ||
The default value to set on instantiation | ||
@property default | ||
@type Any | ||
*/ | ||
this.default = properties.default; | ||
/** | ||
@property validationMessages | ||
@type Array | ||
*/ | ||
this.validationMessages = _validationMessages; | ||
Object.defineProperty(this, "validationMessages", { | ||
enumerable: true, | ||
get: function(){ return _validationMessages; }, | ||
}); | ||
@@ -172,12 +239,6 @@ /** | ||
*/ | ||
if (!properties.name) throw new Error("must specify a name on the PropertyDefinition"); | ||
this.name = properties.name; | ||
/** | ||
Thing instance will remain invalid until a value is set | ||
@property required | ||
@type Boolean | ||
*/ | ||
this.required = properties.required || false; | ||
/** | ||
if unnamed values are passed to thing.set(), set them AS AN ARRAY on this property | ||
@@ -195,17 +256,2 @@ @property defaultOption | ||
/** | ||
@property typeFailMsg | ||
@type string | ||
*/ | ||
this.typeFailMsg = properties.typeFailMsg; | ||
/** | ||
@property valueFailMsg | ||
@type string | ||
*/ | ||
this.valueFailMsg = properties.valueFailMsg; | ||
this.type = properties.type; | ||
this.valueTest = properties.valueTest; | ||
this.value = properties.value || properties.default; | ||
this.throwOnInvalid = properties.throwOnInvalid; | ||
@@ -216,41 +262,2 @@ | ||
function isValidType(value, type){ | ||
if (value === undefined){ | ||
return true; | ||
} else if (typeof type === "string"){ | ||
return typeof value === type; | ||
} else if (typeof type === "function"){ | ||
return value instanceof type; | ||
} else { | ||
return true; | ||
} | ||
} | ||
function isValidValue(value, validTests, thing, validationMessages){ | ||
if (!Array.isArray(validTests)) validTests = [ validTests ]; | ||
function addValidationMessage(msg){ | ||
validationMessages.push(msg); | ||
} | ||
return validTests.every(function(validTest){ | ||
if(validTest instanceof RegExp){ | ||
return validTest.test(value); | ||
} else if(typeof validTest === "function"){ | ||
try{ | ||
var extras = { | ||
addValidationMessage: addValidationMessage, | ||
thing: thing | ||
}; | ||
return validTest.call(extras, value); | ||
} catch(e){ | ||
addValidationMessage("valid test function crashed: " + e); | ||
return false; | ||
} | ||
} else { | ||
return true; | ||
} | ||
}); | ||
} | ||
module.exports = PropertyDefinition; | ||
module.exports = PropertyDefinition; |
322
lib/Thing.js
@@ -6,2 +6,3 @@ "use strict"; | ||
_ = require("underscore"), | ||
wodge = require("wodge"), | ||
PropertyDefinition = require("./PropertyDefinition"), | ||
@@ -12,2 +13,21 @@ EventEmitter = require("events").EventEmitter; | ||
function initInstanceVars(thing, options){ | ||
if (!thing._definitions){ | ||
Object.defineProperty(thing, "_definitions", { enumerable: false, configurable: false, value: {} }); | ||
} | ||
if (!thing._errors) { | ||
Object.defineProperty(thing, "_errors", { enumerable: false, configurable: false, value: [] }); | ||
} | ||
Object.defineProperty(thing, "_throwOnInvalid", { | ||
enumerable: false, configurable: false, writable: false, | ||
value: options && options.throwOnInvalid | ||
}); | ||
} | ||
function error(thing, err){ | ||
initInstanceVars(thing); | ||
thing._errors.push(err); | ||
thing.emit("error", err); | ||
} | ||
/** | ||
@@ -120,3 +140,3 @@ The base class for some Thing. Example things: | ||
function Thing(options){ | ||
initInstanceVars.call(this, options); | ||
initInstanceVars(this, options); | ||
} | ||
@@ -140,10 +160,10 @@ util.inherits(Thing, EventEmitter); | ||
Thing.prototype.define = function(){ | ||
var definition, groupName, | ||
var definition, | ||
self = this; | ||
initInstanceVars.call(this); | ||
initInstanceVars(this); | ||
if (arguments.length === 0){ | ||
error.call(this, new Error("missing definition")); | ||
} else if (arguments.length == 1 ){ | ||
error(this, new Error("missing definition")); | ||
} else if (arguments.length === 1 ){ | ||
// define(propertyDefinition) | ||
@@ -162,6 +182,6 @@ if (typeof arguments[0] === "object" && !Array.isArray(arguments[0])){ | ||
} else { | ||
error.call(this, new Error("Please pass a single or array of property definitions")); | ||
error(this, new Error("Please pass a single or array of property definitions")); | ||
} | ||
} else if (arguments.length == 2){ | ||
} else if (arguments.length === 2){ | ||
var groups = Array.isArray(arguments[0]) | ||
@@ -187,5 +207,5 @@ ? arguments[0] | ||
definition.parent = this; | ||
definition.throwOnInvalid = this.throwOnInvalid; | ||
definition.throwOnInvalid = this._throwOnInvalid; | ||
if (!(definition instanceof PropertyDefinition)){ | ||
@@ -200,7 +220,7 @@ definition = new PropertyDefinition(definition); | ||
if (typeof existingDef !== "undefined"){ | ||
delete this._definitions[existingDef.alias] | ||
delete this._definitions[existingDef.alias]; | ||
delete this._definitions[name]; | ||
} | ||
if (definition.alias && typeof this._definitions[definition.alias] !== "undefined"){ | ||
error.call(this, Error("Cannot create property, alias already exists: " + definition.alias)); | ||
error(this, new Error("Cannot create property, alias already exists: " + definition.alias)); | ||
} | ||
@@ -215,12 +235,10 @@ | ||
function getter(){ | ||
return this.get(name); | ||
} | ||
function setter(val){ | ||
return this.set(name, val); | ||
} | ||
Object.defineProperty(this, name, { enumerable: true, configurable: true, get: getter, set: setter }); | ||
Object.defineProperty(this, name, { | ||
enumerable: true, | ||
configurable: true, | ||
get: function(){ return this.get(name); }, | ||
set: function(val){ return this.set(name, val); } | ||
}); | ||
return this; | ||
} | ||
}; | ||
@@ -233,4 +251,4 @@ /** | ||
Thing.prototype.definition = function(propertyName){ | ||
initInstanceVars.call(this); | ||
if (!propertyName) error.call(this, new Error("please supply an propertyName")); | ||
initInstanceVars(this); | ||
if (!propertyName) error(this, new Error("please supply an propertyName")); | ||
@@ -250,15 +268,70 @@ var item = this._definitions[propertyName]; | ||
*/ | ||
Object.defineProperty(Thing.prototype, "definitions", { get: definitions, enumerable: true }); | ||
function definitions(){ | ||
initInstanceVars.call(this); | ||
var output = {}; | ||
for (var prop in this._definitions){ | ||
var def = this._definitions[prop]; | ||
if (typeof def !== "string"){ | ||
output[prop] = def; | ||
Object.defineProperty(Thing.prototype, "definitions", { | ||
enumerable: true, | ||
get: function(){ | ||
initInstanceVars(this); | ||
var output = {}; | ||
for (var prop in this._definitions){ | ||
var def = this._definitions[prop]; | ||
if (typeof def !== "string"){ | ||
output[prop] = def; | ||
} | ||
} | ||
return output; | ||
} | ||
return output; | ||
}; | ||
}); | ||
function setThing(incoming, target){ | ||
incoming.properties.forEach(function(propertyName){ | ||
target.set(propertyName, incoming.get(propertyName)); | ||
}); | ||
} | ||
function setPlainObject(incoming, target){ | ||
Object.keys(incoming).forEach(function(key){ | ||
target.set(key, incoming[key]); | ||
}); | ||
} | ||
function setArray(incoming, target){ | ||
var arrayItems = incoming.slice(0), | ||
item, | ||
defaultValues = []; | ||
if (incoming === process.argv && path.basename(arrayItems[0]) === "node"){ | ||
arrayItems.splice(0, 2); | ||
} | ||
while (typeof (item = arrayItems.shift()) !== "undefined"){ | ||
var propertyPattern = /^-{1,2}/, | ||
property = ""; | ||
if(propertyPattern.test(item)){ | ||
property = item.replace(propertyPattern, ""); | ||
var def = target.definition(property); | ||
if (def){ | ||
if(def.type === "boolean"){ | ||
target.set(property, true); | ||
} else if (arrayItems.length) { | ||
target.set(property, arrayItems.shift()); | ||
} | ||
} else { | ||
error(target, new Error("invalid property: " + property)); | ||
} | ||
} else { | ||
defaultValues.push(item); | ||
} | ||
} | ||
if (defaultValues.length > 0){ | ||
wodge.each(target._definitions, function(definition, propertyName){ | ||
if (definition.defaultOption){ | ||
// always sets an array - need a "setter" property on PropertyDefinition to fix this | ||
if (definition.type === Array){ | ||
target.set(propertyName, defaultValues); | ||
} else { | ||
target.set(propertyName, defaultValues[0]); | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
/** | ||
@@ -273,59 +346,14 @@ Set a value on the specified property | ||
var self = this; | ||
initInstanceVars.call(this); | ||
initInstanceVars(this); | ||
if (property){ | ||
if (property instanceof Thing){ | ||
var thing = property; | ||
thing.properties.forEach(function(propertyName){ | ||
self.set(propertyName, thing.get(propertyName)); | ||
}); | ||
setThing(property, this); | ||
} else if (wodge.isPlainObject(property)){ | ||
setPlainObject(property, this); | ||
} else if (typeof property === "object" && !Array.isArray(property)){ | ||
var properties = property; | ||
_.each(properties, function(value, key){ | ||
self.set(key, value); | ||
}) | ||
} else if (Array.isArray(property)){ | ||
var arrayItems = property.slice(0), | ||
item, | ||
defaultValues = []; | ||
if (property === process.argv && path.basename(arrayItems[0]) === "node"){ | ||
arrayItems.splice(0, 2); | ||
} | ||
while (typeof (item = arrayItems.shift()) !== "undefined"){ | ||
var propertyPattern = /^-{1,2}/; | ||
if(propertyPattern.test(item)){ | ||
property = item.replace(propertyPattern, ""); | ||
var def = this.definition(property); | ||
if (def){ | ||
if(def.type === "boolean"){ | ||
this.set(property, true); | ||
} else if (arrayItems.length) { | ||
this.set(property, arrayItems.shift()); | ||
} | ||
} else { | ||
error.call(this, new Error("invalid property: " + property)); | ||
} | ||
} else { | ||
defaultValues.push(item); | ||
} | ||
} | ||
if (defaultValues.length > 0){ | ||
var defaultOptionName = ""; | ||
_.each(this._definitions, function(definition, propertyName){ | ||
if (definition.defaultOption){ | ||
// always sets an array - need a "setter" property on PropertyDefinition to fix this | ||
if (definition.type === Array){ | ||
self.set(propertyName, defaultValues); | ||
} else { | ||
self.set(propertyName, defaultValues[0]); | ||
} | ||
} | ||
}); | ||
} | ||
setArray(property, this); | ||
} else { | ||
@@ -344,3 +372,3 @@ if (this._definitions[property] !== undefined){ | ||
} else { | ||
error.call(this, new Error("invalid property: " + property)); | ||
error(this, new Error("invalid property: " + property)); | ||
} | ||
@@ -357,9 +385,12 @@ } | ||
*/ | ||
Object.defineProperty(Thing.prototype, "valid", { get: getValid, enumerable: true, configurable: true }); | ||
function getValid() { | ||
initInstanceVars.call(this); | ||
return this._errors.length === 0 && _.every(this.definitions, function(def){ | ||
return def.valid; | ||
}); | ||
} | ||
Object.defineProperty(Thing.prototype, "valid", { | ||
enumerable: true, | ||
configurable: true, | ||
get: function() { | ||
initInstanceVars(this); | ||
return this._errors.length === 0 && wodge.every(this.definitions, function(def, name){ | ||
return def.valid; | ||
}); | ||
} | ||
}); | ||
@@ -373,31 +404,30 @@ /** | ||
enumerable: true, | ||
get: getValidationMessages | ||
}); | ||
function getValidationMessages(){ | ||
var output = [], | ||
self = this, | ||
validationMessages; | ||
this.properties.forEach(function(property){ | ||
validationMessages = self.definition(property).validationMessages; | ||
if (validationMessages.length){ | ||
output = output.concat({ | ||
property: property, | ||
validationMessages: validationMessages | ||
get: function(){ | ||
var output = [], | ||
self = this, | ||
validationMessages; | ||
this.properties.forEach(function(property){ | ||
validationMessages = self.definition(property).validationMessages; | ||
if (validationMessages.length){ | ||
output = output.concat({ | ||
property: property, | ||
validationMessages: validationMessages | ||
}); | ||
} | ||
}); | ||
output.toString = function(){ | ||
var toString = ""; | ||
this.forEach(function(prop){ | ||
prop.validationMessages.forEach(function(msg){ | ||
toString += prop.property + ":\t" + msg + "\n"; | ||
}); | ||
}); | ||
} | ||
}); | ||
return toString; | ||
}; | ||
output.toString = function(){ | ||
var toString = ""; | ||
this.forEach(function(prop){ | ||
prop.validationMessages.forEach(function(msg){ | ||
toString += prop.property + ":\t" + msg + "\n"; | ||
}); | ||
}); | ||
return toString; | ||
return output; | ||
} | ||
}); | ||
return output; | ||
} | ||
/** | ||
@@ -408,8 +438,7 @@ A annoy of defined properties | ||
*/ | ||
Object.defineProperty(Thing.prototype, "properties", { enumerable: true, get: getProperties }); | ||
function getProperties(){ | ||
return Object.keys(this.definitions); | ||
} | ||
Object.defineProperty(Thing.prototype, "properties", { | ||
enumerable: true, | ||
get: function(){ return Object.keys(this.definitions); } | ||
}); | ||
/** | ||
@@ -444,3 +473,3 @@ Groups a property | ||
return this; | ||
} | ||
}; | ||
@@ -467,3 +496,3 @@ /** | ||
} else { | ||
error.call(this, new Error("property does not exist: " + propertyName)); | ||
error(this, new Error("property does not exist: " + propertyName)); | ||
} | ||
@@ -483,7 +512,14 @@ }); | ||
@method where | ||
@param Object filterOptions | ||
@return Thing | ||
@param {Object} filterOptions Mongo style query | ||
@return {Thing} A new Thing instance with the filters applied | ||
@example | ||
var excludeProperties = thing.where({ | ||
name: { $ne: ["preset-list", "help", "scan", "title" ] } | ||
}); | ||
var certainGroup = thing.where({ group: "handbrake" }); | ||
*/ | ||
Thing.prototype.where = function(filterOptions){ | ||
var result = new Thing(); | ||
var result = new Thing(), | ||
isTruthy = function(i){ return i; }; | ||
@@ -501,7 +537,7 @@ for (var propertyName in this.definitions){ | ||
if ("$ne" in query){ | ||
tests.push(query["$ne"].indexOf(definition.name) == -1) | ||
tests.push(query.$ne.indexOf(definition.name) === -1); | ||
} | ||
} | ||
if (tests.every(function(i){ return i; })){ | ||
if (tests.every(isTruthy)){ | ||
result.define(definition); | ||
@@ -512,3 +548,3 @@ } | ||
return result; | ||
} | ||
}; | ||
@@ -529,3 +565,3 @@ /** | ||
} | ||
if (quote) pair[1] = '"' + pair[1] + '"'; | ||
if (quote) pair[1] = "\"" + pair[1] + "\""; | ||
}); | ||
@@ -586,6 +622,6 @@ output = _.flatten(output); | ||
} else { | ||
error.call(this, new Error("mixIn: must pass in an instance of Thing")); | ||
error(this, new Error("mixIn: must pass in an instance of Thing")); | ||
} | ||
return this; | ||
} | ||
}; | ||
@@ -599,3 +635,3 @@ /** | ||
var clone = new Thing(); | ||
_.each(this.definitions, function(def, propertyName){ | ||
_.each(this.definitions, function(def){ | ||
clone.define(_.clone(def)); | ||
@@ -630,18 +666,2 @@ }); | ||
function error(err){ | ||
initInstanceVars.call(this); | ||
this._errors.push(err); | ||
this.emit("error", err); | ||
} | ||
function initInstanceVars(options){ | ||
if (!this._definitions){ | ||
Object.defineProperty(this, "_definitions", { enumerable: false, configurable: false, value: {} }); | ||
} | ||
if (!this._errors) { | ||
Object.defineProperty(this, "_errors", { enumerable: false, configurable: false, value: [] }); | ||
} | ||
this.throwOnInvalid = options && options.throwOnInvalid; | ||
} | ||
module.exports = Thing; |
{ | ||
"name": "nature", | ||
"version": "0.2.5", | ||
"version": "0.3.0", | ||
"description": "Classify the things in your world and how they interact.", | ||
@@ -14,3 +14,4 @@ "main": "lib/nature.js", | ||
"dependencies": { | ||
"underscore": "~1.5.2" | ||
"underscore": "~1.5.2", | ||
"wodge": "~0.1.0" | ||
}, | ||
@@ -21,4 +22,5 @@ "devDependencies": { | ||
"yuidoc2md": "~0.2.2", | ||
"grunt-boil": "~0.5.0" | ||
"grunt-boil": "~0.6.0", | ||
"grunt-contrib-jshint": "~0.8.0" | ||
} | ||
} |
239
README.md
@@ -53,97 +53,53 @@ [![NPM version](https://badge.fury.io/js/nature.png)](http://badge.fury.io/js/nature) | ||
================= | ||
#PropertyDefinition | ||
#nature | ||
Enforces strict type and value checking on thing properties | ||
Nature, a library to help classify the things (models) in your world (app). Take charge of Nature, design Things that do stuff. | ||
##Properties | ||
* Things can be instantiated with data directly from the command line, environment, an object literal or file. | ||
* Once classified, Things can be re-used across your apps | ||
* Removes all input validation code from your app | ||
###value | ||
Synopsis | ||
Gets/sets the property value. Will attempt to convert values to Number for definitions of `type` "number". | ||
Enables you to write API methods like: (`YoungMan` and `FlammableCar` are derivitives of `Thing`) | ||
**type**: Any | ||
###type | ||
**type**: String | Function | ||
####Example | ||
thing.define({ name: "name", type: "string" }); | ||
thing.define({ name: "created", type: Date }); | ||
thing.define({ name: "onComplete", type: "function" }); | ||
thing.define({ name: "data", type: JobData }); | ||
thing.define({ name: "properties", type: Object }); | ||
thing.define({ name: "files", type: Array }); | ||
###valueTest | ||
A regular expression, function or Array containing one or more of each. A value | ||
must return true for all to be valid. | ||
**type**: Regexp | Function | Array | ||
####Example | ||
thing.define({ name: "name", type: "string", valueTest: /\w{3}/ }) | ||
thing.define({ name: "age", type: "number", valueTest: function(value){ return value > 16; } }) | ||
thing.define({ | ||
name: "colours", | ||
type: Array, | ||
valueTest: [ | ||
/red/, | ||
function(colours){ | ||
return colours.length > 0; | ||
exports.burnCar = function(arsonist){ | ||
var youngMan = new YoungMan(arsonist); | ||
if (!youngMan.valid){ | ||
throw new Error(youngMan.validationMessages.join("\n")); | ||
// throws: | ||
// The arsonist supplied must be a young man. | ||
// Invalid age 29: Must be between 11 and 19. | ||
// Over 5 litres Gasoline required | ||
// Less compassion necessary. | ||
} else { | ||
var car = new FlammableCar(process.env); | ||
if (car.valid){ | ||
// the Vauxhall Astra supplied in the environment is indeed flammable | ||
youngMan.igniteCar(astra); | ||
} | ||
] | ||
}); | ||
} | ||
} | ||
###valid | ||
Client code for the above is straight forward: | ||
**type**: Boolean | ||
var outdoors = require("outdoor-activity"); | ||
outdoors.burnCar(process.argv); | ||
###default | ||
Then a simple command executes the outdoor activity: | ||
The default value to set on instantiation | ||
$ CAR_MAKE=Vauxhall CAR_MODEL=Astra node outdoors.js --name Alfie --age 11 --litres 13 --compassion unknown | ||
**type**: Any | ||
See the Thing docs for more detail.. | ||
###validationMessages | ||
##Properties | ||
**type**: Array | ||
###Thing | ||
###groups | ||
**type**: Thing | ||
tags | ||
###PropertyDefinition | ||
**type**: Array | ||
**type**: PropertyDefinition | ||
###name | ||
**type**: string | ||
###required | ||
Thing instance will remain invalid until a value is set | ||
**type**: Boolean | ||
###defaultOption | ||
if unnamed values are passed to thing.set(), set them AS AN ARRAY on this property | ||
**type**: Boolean | ||
###alias | ||
**type**: string | ||
###typeFailMsg | ||
**type**: string | ||
###valueFailMsg | ||
**type**: string | ||
#Thing | ||
@@ -359,10 +315,17 @@ | ||
**Returns**: __ - Thing | ||
**Returns**: _Thing_ - A new Thing instance with the filters applied | ||
**Params**: | ||
* Object __ | ||
* filterOptions _Object_ | ||
filterOptions | ||
Mongo style query | ||
####Example | ||
var excludeProperties = thing.where({ | ||
name: { $ne: ["preset-list", "help", "scan", "title" ] } | ||
}); | ||
var certainGroup = thing.where({ group: "handbrake" }); | ||
###toArray | ||
@@ -440,53 +403,99 @@ | ||
#nature | ||
#PropertyDefinition | ||
Nature, a library to help classify the things (models) in your world (app). Take charge of Nature, design Things that do stuff. | ||
Enforces strict type and value checking on thing properties | ||
* Things can be instantiated with data directly from the command line, environment, an object literal or file. | ||
* Once classified, Things can be re-used across your apps | ||
* Removes all input validation code from your app | ||
##Properties | ||
Synopsis | ||
###value | ||
Enables you to write API methods like: (`YoungMan` and `FlammableCar` are derivitives of `Thing`) | ||
Gets/sets the property value. Will attempt to convert values to Number for definitions of `type` "number". | ||
exports.burnCar = function(arsonist){ | ||
var youngMan = new YoungMan(arsonist); | ||
if (!youngMan.valid){ | ||
throw new Error(youngMan.validationMessages.join("\n")); | ||
// throws: | ||
// The arsonist supplied must be a young man. | ||
// Invalid age 29: Must be between 11 and 19. | ||
// Over 5 litres Gasoline required | ||
// Less compassion necessary. | ||
} else { | ||
var car = new FlammableCar(process.env); | ||
if (car.valid){ | ||
// the Vauxhall Astra supplied in the environment is indeed flammable | ||
youngMan.igniteCar(astra); | ||
**type**: Any | ||
###type | ||
**type**: String | Function | ||
####Example | ||
thing.define({ name: "name", type: "string" }); | ||
thing.define({ name: "created", type: Date }); | ||
thing.define({ name: "onComplete", type: "function" }); | ||
thing.define({ name: "data", type: JobData }); | ||
thing.define({ name: "properties", type: Object }); | ||
thing.define({ name: "files", type: Array }); | ||
###valueTest | ||
A regular expression, function or Array containing one or more of each. A value | ||
must return true for all to be valid. | ||
**type**: Regexp | Function | Array | ||
####Example | ||
thing.define({ name: "name", type: "string", valueTest: /\w{3}/ }) | ||
thing.define({ name: "age", type: "number", valueTest: function(value){ return value > 16; } }) | ||
thing.define({ | ||
name: "colours", | ||
type: Array, | ||
valueTest: [ | ||
/red/, | ||
function(colours){ | ||
return colours.length > 0; | ||
} | ||
} | ||
} | ||
] | ||
}); | ||
Client code for the above is straight forward: | ||
###valid | ||
var outdoors = require("outdoor-activity"); | ||
outdoors.burnCar(process.argv); | ||
**type**: Boolean | ||
Then a simple command executes the outdoor activity: | ||
###default | ||
$ CAR_MAKE=Vauxhall CAR_MODEL=Astra node outdoors.js --name Alfie --age 11 --litres 13 --compassion unknown | ||
The default value to set on instantiation | ||
See the Thing docs for more detail.. | ||
**type**: Any | ||
##Properties | ||
###validationMessages | ||
###Thing | ||
**type**: Array | ||
**type**: Thing | ||
###groups | ||
###PropertyDefinition | ||
tags | ||
**type**: PropertyDefinition | ||
**type**: Array | ||
###name | ||
**type**: string | ||
###required | ||
Thing instance will remain invalid until a value is set | ||
**type**: Boolean | ||
###defaultOption | ||
if unnamed values are passed to thing.set(), set them AS AN ARRAY on this property | ||
**type**: Boolean | ||
###alias | ||
**type**: string | ||
###typeFailMsg | ||
**type**: string | ||
###valueFailMsg | ||
**type**: string | ||
[![NPM](https://nodei.co/npm-dl/nature.png?months=3)](https://nodei.co/npm/nature/) |
@@ -0,154 +1,66 @@ | ||
"use strict"; | ||
var assert = require("assert"), | ||
Definition = require("../lib/PropertyDefinition"); | ||
Definition = require("../lib/PropertyDefinition"), | ||
def = null, | ||
l = console.log, | ||
d = function(def){ return JSON.stringify(def, null, "\t"); }; | ||
var CustomClass = function(){}; | ||
function lessThan10(value) { | ||
return value < 10; | ||
} | ||
function allLessThan10(arr){ | ||
return arr.every(lessThan10); | ||
} | ||
function allFamily(arr){ | ||
return arr.every(function(member){ | ||
return (/^(dad|sister|brother|mother)$/).test(member); | ||
}); | ||
} | ||
function allFamilyElse(arr){ | ||
var self = this; | ||
return arr.every(function(member){ | ||
if(/^(dad|sister|brother|mother)$/.test(member)){ | ||
return true; | ||
} else { | ||
self.addValidationMessage("this one is invalid: " + member); | ||
return false; | ||
} | ||
}); | ||
} | ||
function broken(){ | ||
throw new Error("error"); | ||
} | ||
function factory(name){ | ||
var definitions = { | ||
name: { name: "one" }, | ||
string: { name: "one", type: "string" }, | ||
number: { name: "one", type: "number" }, | ||
bool: { name: "one", type: "boolean" }, | ||
func: { name: "one", type: "function" }, | ||
obj: { name: "one", type: "object" }, | ||
array: { name: "one", type: Array }, | ||
custom: { name: "one", type: CustomClass }, | ||
date: { name: "one", type: Date }, | ||
regex: { name: "one", type: RegExp } | ||
}; | ||
return new Definition(definitions[name]); | ||
} | ||
describe("PropertyDefinition", function(){ | ||
describe("properties: ", function(){ | ||
it("access to `this.config` in a `valid` function must fail if config is not set"); | ||
it("should be ok to have an option with no defined type"); | ||
it("should be ok to have an definition with no defined type"); | ||
it("type: [Array, Function] should allow a type of either"); | ||
describe("validation", function(){ | ||
it("type validation summary", function(){ | ||
var def = new Definition({ name: "one", type: "string", value: "ok" }); | ||
assert.strictEqual(def.valid, true); | ||
assert.strictEqual(def.validationMessages.length, 0); | ||
def = new Definition({ name: "one", type: "number", value: "not ok" }); | ||
assert.strictEqual(def.valid, false); | ||
assert.strictEqual(def.validationMessages.length, 1); | ||
def = new Definition({ name: "one", type: RegExp, value: /ok/ }); | ||
describe(".valid", function(){ | ||
it("valid if no type specified", function(){ | ||
def = factory("name"); | ||
assert.strictEqual(def.valid, true); | ||
assert.strictEqual(def.validationMessages.length, 0); | ||
// "ok" parses by RegExp | ||
def = new Definition({ name: "one", type: RegExp, typeFail: "pass a regex", value: "ok" }); | ||
assert.strictEqual(def.valid, true); | ||
assert.strictEqual(def.validationMessages.length, 0); | ||
// "+++" does not parse by RegExp | ||
def = new Definition({ name: "one", type: RegExp, typeFail: "pass a regex", value: "+++" }); | ||
assert.strictEqual(def.valid, false); | ||
assert.strictEqual(def.validationMessages.length, 1); | ||
}); | ||
it("a number string should typecast to a Number type automatically", function(){ | ||
var def = new Definition({ name: "one", type: "number" }) ; | ||
def.value = "3"; | ||
assert.strictEqual(def.value, 3); | ||
assert.strictEqual(def.valid, true); | ||
def.value = "0"; | ||
assert.strictEqual(def.value, 0); | ||
assert.strictEqual(def.valid, true); | ||
def.value = "-1"; | ||
assert.strictEqual(def.value, -1); | ||
assert.strictEqual(def.valid, true); | ||
def.value = -1.5345; | ||
assert.strictEqual(def.value, -1.5345); | ||
assert.strictEqual(def.valid, true); | ||
def.value = "-1.5345"; | ||
assert.strictEqual(def.value, -1.5345); | ||
assert.strictEqual(def.valid, true); | ||
def.value = "a"; | ||
assert.strictEqual(def.value, "a"); | ||
assert.strictEqual(def.valid, false); | ||
def.value = ""; | ||
assert.strictEqual(def.value, ""); | ||
assert.strictEqual(def.valid, false); | ||
def.value = true; | ||
assert.strictEqual(def.value, true); | ||
assert.strictEqual(def.valid, false); | ||
def.value = function(){}; | ||
assert.strictEqual(def.valid, false); | ||
def.value = null; | ||
assert.strictEqual(def.value, null); | ||
assert.strictEqual(def.valid, false); | ||
def.value = undefined; | ||
assert.strictEqual(def.value, undefined); | ||
assert.strictEqual(def.valid, true); // if an option is not required an undefined value is ok | ||
}); | ||
it("a regex string should typecast to a RegExp", function(){ | ||
var def = new Definition({ name: "one", type: RegExp, value: "\\w{4}" }); | ||
assert.ok(def.value instanceof RegExp, def.value); | ||
assert.deepEqual(def.value, /\w{4}/); | ||
}); | ||
it("value validation summary", function(){ | ||
var def = new Definition({ | ||
name: "relative", | ||
type: "string", | ||
value: "dog", | ||
valueTest: /^(dad|sister|brother|mother)$/, | ||
valueFail: "invalid relative" | ||
}); | ||
assert.strictEqual(def.valid, false); | ||
assert.strictEqual(def.validationMessages.length, 1); | ||
def.value = "dad"; | ||
assert.strictEqual(def.valid, true); | ||
assert.strictEqual(def.validationMessages.length, 0); | ||
def = new Definition({ | ||
name: "family", | ||
type: Array, | ||
value: ["dad", "sister", "dog"], | ||
valueTest: /(dad|sister|brother|mother)/ | ||
}); | ||
assert.strictEqual(def.valid, true); | ||
def = new Definition({ | ||
name: "family", | ||
type: Array, | ||
value: ["dad", "sister", "dog"], | ||
valueTest: function(family){ | ||
return family.every(function(member){ | ||
return /^(dad|sister|brother|mother)$/.test(member); | ||
}); | ||
}, | ||
valueFail: "every member must be valid" | ||
}); | ||
assert.strictEqual(def.valid, false); | ||
assert.strictEqual(def.validationMessages.length, 1); | ||
def = new Definition({ | ||
name: "family", | ||
type: Array, | ||
value: ["dad", "sister", "dog"], | ||
valueTest: function(family){ | ||
var self = this; | ||
return family.every(function(member){ | ||
if (/^(dad|sister|brother|mother)$/.test(member)){ | ||
return true; | ||
} else { | ||
self.addValidationMessage("this one is invalid: " + member); | ||
return false; | ||
} | ||
}); | ||
}, | ||
valueFail: "every member must be valid" | ||
}); | ||
assert.strictEqual(def.valid, false); | ||
assert.strictEqual(def.validationMessages.length, 2); | ||
}); | ||
it("`valid` should return true if no type specified", function(){ | ||
var def = new Definition({ name: "one" }); | ||
assert.strictEqual(def.valid, true); | ||
}); | ||
it("`valid` should return true when `typeof value === type`", function(){ | ||
var def = new Definition({ name: "one", type: "string" }); | ||
assert.strictEqual(def.valid, true); | ||
it("valid when value matches type", function(){ | ||
def = factory("string"); | ||
def.value = 123; | ||
@@ -166,5 +78,4 @@ assert.strictEqual(def.valid, false); | ||
assert.strictEqual(def.valid, false); | ||
def = new Definition({name: "one", type: "number"}); | ||
assert.strictEqual(def.valid, true); | ||
def = factory("number"); | ||
def.value = 123; | ||
@@ -185,4 +96,3 @@ assert.strictEqual(def.valid, true); | ||
def = new Definition({name: "one", type: "boolean"}); | ||
assert.strictEqual(def.valid, true); | ||
def = factory("bool"); | ||
def.value = 123; | ||
@@ -203,4 +113,3 @@ assert.strictEqual(def.valid, false); | ||
def = new Definition({name: "one", type: "function"}); | ||
assert.strictEqual(def.valid, true); | ||
def = factory("func"); | ||
def.value = 123; | ||
@@ -220,5 +129,4 @@ assert.strictEqual(def.valid, false); | ||
assert.strictEqual(def.valid, false); | ||
def = new Definition({name: "one", type: "object"}); | ||
assert.strictEqual(def.valid, true); | ||
def = factory("obj"); | ||
def.value = 123; | ||
@@ -239,6 +147,5 @@ assert.strictEqual(def.valid, false); | ||
}); | ||
it("`valid` should return true if `value instanceof type`", function(){ | ||
var def = new Definition({ name: "one", type: Array }); | ||
assert.strictEqual(def.valid, true); | ||
it("valid when value instanceof type", function(){ | ||
def = factory("array"); | ||
def.value = 123; | ||
@@ -260,5 +167,3 @@ assert.strictEqual(def.valid, true); // converted to Array | ||
var CustomClass = function(){}; | ||
var def = new Definition({ name: "one", type: CustomClass }); | ||
assert.strictEqual(def.valid, true); | ||
def = factory("custom"); | ||
def.value = 123; | ||
@@ -273,80 +178,184 @@ assert.strictEqual(def.valid, false); | ||
var def = new Definition({ name: "one", type: Date }); | ||
assert.strictEqual(def.valid, true); | ||
def = factory("date"); | ||
def.value = new Date(); | ||
assert.strictEqual(def.valid, true); | ||
}); | ||
it("`valid` should be true with an empty value and not `required`", function(){ | ||
var def = new Definition({ name: "one", type: "string" }); | ||
it("valid with .required", function(){ | ||
/* | ||
required means 'value should be truthy'. | ||
not required permits a value to be undefined. | ||
*/ | ||
def = factory("string"); | ||
assert.strictEqual(def.valid, true); | ||
def = new Definition({ name: "one", type: "string", required: true }); | ||
def.required = true; | ||
assert.strictEqual(def.valid, false); | ||
def.value = ""; | ||
assert.strictEqual(def.valid, false); | ||
def.value = " "; | ||
assert.strictEqual(def.valid, true); | ||
var def = new Definition({ name: "one", type: "number" }); | ||
def = factory("number"); | ||
assert.strictEqual(def.valid, true); | ||
def = new Definition({ name: "one", type: "number", required: true }); | ||
def.required = true; | ||
assert.strictEqual(def.valid, false); | ||
def.value = 0; | ||
assert.strictEqual(def.valid, false); | ||
def.value = 1; | ||
assert.strictEqual(def.valid, true); | ||
def = factory("array"); | ||
assert.strictEqual(def.valid, true); | ||
def.required = true; | ||
assert.strictEqual(def.valid, false); | ||
def = factory("custom"); | ||
assert.strictEqual(def.valid, true); | ||
def.required = true; | ||
assert.strictEqual(def.valid, false); | ||
}); | ||
it("valid with .required and .valueTest", function(){ | ||
def = factory("string"); | ||
assert.strictEqual(def.valid, true); | ||
def.valueTest = /test/; | ||
assert.strictEqual(def.valid, true); | ||
def.value = "clive" | ||
assert.strictEqual(def.valid, false); | ||
def.value = "test" | ||
assert.strictEqual(def.valid, true); | ||
def.value = ""; | ||
assert.strictEqual(def.valid, false); | ||
def.value = undefined; | ||
assert.strictEqual(def.valid, true); | ||
def.required = true; | ||
assert.strictEqual(def.valid, false); | ||
def = factory("number"); | ||
assert.strictEqual(def.valid, true); | ||
def.valueTest = lessThan10; | ||
assert.strictEqual(def.valid, true); | ||
def.value = "clive" | ||
assert.strictEqual(def.valid, false); | ||
def.value = 9 | ||
assert.strictEqual(def.valid, true); | ||
def.value = 0; | ||
assert.strictEqual(def.valid, true); | ||
def.value = undefined; | ||
assert.strictEqual(def.valid, true); | ||
def.required = true; | ||
assert.strictEqual(def.valid, false); | ||
def.value = 0; | ||
assert.strictEqual(def.valid, false); | ||
var def = new Definition({ name: "one", type: Array }); | ||
def = factory("string"); | ||
assert.strictEqual(def.valid, true); | ||
def.valueTest = /test/; | ||
assert.strictEqual(def.valid, true); | ||
def.valueTest = 1; | ||
assert.strictEqual(def.valid, true); | ||
def.valueTest = lessThan10; | ||
assert.strictEqual(def.valid, true); | ||
def = new Definition({ name: "one", type: Array, required: true }); | ||
assert.strictEqual(def.valid, false); | ||
def = factory("number"); | ||
assert.strictEqual(def.valid, true); | ||
def.valueTest = /test/; | ||
assert.strictEqual(def.valid, true); | ||
def.valueTest = 1; | ||
assert.strictEqual(def.valid, true); | ||
def.valueTest = lessThan10; | ||
assert.strictEqual(def.valid, true); | ||
var def = new Definition({ name: "one", type: "string", valueTest: /test/ }); | ||
def = factory("func"); | ||
assert.strictEqual(def.valid, true); | ||
def.valueTest = /test/; | ||
assert.strictEqual(def.valid, true); | ||
def.valueTest = 1; | ||
assert.strictEqual(def.valid, true); | ||
def.valueTest = lessThan10; | ||
assert.strictEqual(def.valid, true); | ||
def = new Definition({ name: "one", type: "string", valueTest: /test/, required: true }); | ||
assert.strictEqual(def.valid, false); | ||
def = factory("array"); | ||
assert.strictEqual(def.valid, true); | ||
def.valueTest = /test/; | ||
assert.strictEqual(def.valid, true); | ||
def.valueTest = 1; | ||
assert.strictEqual(def.valid, true); | ||
def.valueTest = lessThan10; | ||
assert.strictEqual(def.valid, true); | ||
}); | ||
it("`valid` RegExp should work with primitive types", function(){ | ||
var def = new Definition({ name: "one", type: "string", default: "test", valueTest: /es/ }); | ||
assert.strictEqual(def.valid, true, JSON.stringify(def)); | ||
def.valueTest = /as/; | ||
it("valid with RegExp .valueTest", function(){ | ||
/* | ||
any definition with a valueTest should always be tested. Required is implied. | ||
*/ | ||
def = factory("string"); | ||
def.valueTest = /test/; | ||
def.required = true; | ||
assert.strictEqual(def.valid, false); | ||
def.value = "test"; | ||
assert.strictEqual(def.valid, true); | ||
def.valueTest = /tast/; | ||
assert.strictEqual(def.valid, false); | ||
def = new Definition({ name: "one", type: "boolean", default: false, valueTest: /false/ }); | ||
def.valueTest = /^(dad|sister|brother|mother)$/; | ||
def.value = "dog"; | ||
assert.strictEqual(def.valid, false); | ||
def.value = "dad"; | ||
assert.strictEqual(def.valid, true); | ||
def.type = Array; | ||
def.valueTest = /(dad|sister|brother|mother)/; | ||
def.value = ["dad", "sister", "dog"]; | ||
assert.strictEqual(def.valid, true); | ||
}); | ||
it("valid with primitive .valueTest", function(){ | ||
def = factory("bool"); | ||
def.valueTest = false; | ||
def.value = false; | ||
assert.strictEqual(def.valid, true); | ||
def.valueTest = /true/; | ||
assert.strictEqual(def.valid, false); | ||
def.valueTest = "false"; | ||
assert.strictEqual(def.valid, false); | ||
def = factory("number"); | ||
def.valueTest = 5; | ||
def.value = 5; | ||
assert.strictEqual(def.valid, true); | ||
def.valueTest = 4; | ||
assert.strictEqual(def.valid, false); | ||
def.valueTest = "5"; | ||
assert.strictEqual(def.valid, false); | ||
}); | ||
it("`valid` Function should work with primitive and object types", function(){ | ||
function smallNumber(value) { | ||
return value < 10; | ||
} | ||
var def = new Definition({ name: "one", type: "number", default: 4, valueTest: smallNumber }); | ||
it("valid with function .validTest", function(){ | ||
def = factory("number"); | ||
def.valueTest = lessThan10; | ||
def.value = 4; | ||
assert.strictEqual(def.valid, true); | ||
def.value = 11; | ||
assert.strictEqual(def.valid, false); | ||
function smallArray(a){ | ||
return a.length < 10; | ||
} | ||
def = new Definition({ name: "one", type: Array, default: [0,4,6], valueTest: smallArray }); | ||
def = factory("array"); | ||
def.valueTest = allLessThan10; | ||
def.value = [0,4,6]; | ||
assert.strictEqual(def.valid, true); | ||
def.value = [1,2,3,4,5,6,7,5,4,3,2,1,0]; | ||
def.value = [0,4,16]; | ||
assert.strictEqual(def.valid, false); | ||
def.valueTest = allFamily; | ||
def.value = ["dad", "sister", "dog"]; | ||
assert.strictEqual(def.valid, false); | ||
}); | ||
it("`valid` should accept and test an array of functions"); | ||
it("`valid` should return false if option `required` with no value set", function(){ | ||
var def = new Definition({ name: "one", type: "number", required: true }); | ||
assert.strictEqual(def.valid, false); | ||
def = new Definition({ name: "one", type: "number", required: true, default: 1 }); | ||
assert.strictEqual(def.valid, true); | ||
}); | ||
describe("bad usage", function(){ | ||
it("`valid` should return `false` if `valueTest` function threw", function(){ | ||
var def = new Definition({ name: "one", type: Array, required: true, valueTest: function(files){ | ||
throw new Error("error"); | ||
}}); | ||
it("valid with function .validTest - function throws", function(){ | ||
def = factory("array"); | ||
def.valueTest = broken; | ||
def.value = ["test"]; | ||
@@ -356,62 +365,92 @@ assert.strictEqual(def.valid, false); | ||
}); | ||
it("custom invalid msg with type `number`", function(){ | ||
function validTest(value){ | ||
return value > 10; | ||
} | ||
var def = new Definition({name: "one", type: "number", valueTest: validTest, valueFailMsg: "must supply a value over 10" }); | ||
def.value = 1; | ||
assert.deepEqual(def.validationMessages, ["must supply a value over 10"]); | ||
}); | ||
def.value = 11; | ||
assert.deepEqual(def.validationMessages, []); | ||
describe(".validationMessages", function(){ | ||
it("with no .invalidMsg", function(){ | ||
def = factory("string"); | ||
def.value = "ok"; | ||
assert.strictEqual(def.validationMessages.length, 0); | ||
def.type = "number"; | ||
assert.strictEqual(def.validationMessages.length, 1); | ||
def.type = RegExp; | ||
assert.strictEqual(def.validationMessages.length, 1); | ||
}); | ||
it("custom invalid msg with type `Array`", function(){ | ||
function validArray(values){ | ||
return values.every(function(val){ | ||
return val > 10; | ||
}); | ||
} | ||
var def = new Definition({ | ||
name: "one", | ||
type: Array, | ||
valueTest: validArray, | ||
valueFailMsg: "every value must be over 10" | ||
}); | ||
def.value = [1]; | ||
assert.deepEqual(def.validationMessages, ["every value must be over 10"]); | ||
it("with .invalidMsg", function(){ | ||
def = factory("array"); | ||
def.valueTest = allLessThan10; | ||
def.invalidMsg = "every value must be less than 10"; | ||
def.value = [11]; | ||
assert.strictEqual(def.validationMessages[1], "every value must be less than 10"); | ||
def.value = [1]; | ||
assert.deepEqual(def.validationMessages, []); | ||
def = factory("regex"); | ||
def.invalidMsg = "pass a regex"; | ||
def.value = "ok"; | ||
assert.strictEqual(def.validationMessages.length, 0); | ||
def.value = "+++"; | ||
assert.strictEqual(def.validationMessages.length, 2); | ||
assert.strictEqual(def.validationMessages[1], "pass a regex"); | ||
}); | ||
it("inserting error messages from a `valid` function", function(){ | ||
function validArray(values){ | ||
var valid = true, self = this; | ||
values.forEach(function(val){ | ||
if (val < 10){ | ||
valid = false; | ||
self.addValidationMessage("less than 10: " + val); | ||
} | ||
}); | ||
return valid; | ||
} | ||
var def = new Definition({ | ||
name: "one", | ||
type: Array, | ||
valueTest: validArray, | ||
valueFailMsg: "every value must be over 10" | ||
}); | ||
def.value = [1]; | ||
assert.deepEqual(def.validationMessages, ["less than 10: 1", "every value must be over 10"]); | ||
it("raised from .validTest function", function(){ | ||
def = factory("array"); | ||
def.value = ["dad", "sister", "dog"]; | ||
def.valueTest = allFamilyElse; | ||
def.invalidMsg = "every member must be valid"; | ||
assert.strictEqual(def.valid, false); | ||
assert.strictEqual(def.validationMessages.length, 3); | ||
}); | ||
}); | ||
def.value = [11]; | ||
assert.deepEqual(def.validationMessages, []); | ||
describe(".value", function(){ | ||
it("typecasting with number", function(){ | ||
def = factory("number"); | ||
def.value = "3"; | ||
assert.strictEqual(def.value, 3); | ||
def.value = "0"; | ||
assert.strictEqual(def.value, 0); | ||
def.value = "-1"; | ||
assert.strictEqual(def.value, -1); | ||
def.value = -1.5345; | ||
assert.strictEqual(def.value, -1.5345); | ||
def.value = "-1.5345"; | ||
assert.strictEqual(def.value, -1.5345); | ||
def.value = "a"; | ||
assert.strictEqual(def.value, "a"); | ||
def.value = ""; | ||
assert.strictEqual(def.value, ""); | ||
def.value = true; | ||
assert.strictEqual(def.value, true); | ||
def.value = CustomClass; | ||
assert.strictEqual(def.value, CustomClass); | ||
def.value = null; | ||
assert.strictEqual(def.value, null); | ||
def.value = undefined; | ||
assert.strictEqual(def.value, undefined); | ||
}); | ||
it("typecasting with RegExp", function(){ | ||
def = factory("regex"); | ||
def.value = "\\w{4}"; | ||
assert.ok(def.value instanceof RegExp, def.value); | ||
assert.deepEqual(def.value, /\w{4}/); | ||
def.value = "ok"; | ||
assert.ok(def.value instanceof RegExp); | ||
assert.deepEqual(def.value, /ok/); | ||
def.value = "+++"; | ||
assert.ok(!(def.value instanceof RegExp)); | ||
assert.strictEqual(def.value, "+++"); | ||
}); | ||
}); | ||
}); | ||
}); |
var assert = require("assert"), | ||
_ = require("underscore"), | ||
Thing = require("../lib/Thing"), | ||
PropertyDefinition = require("../lib/PropertyDefinition"); | ||
PropertyDefinition = require("../lib/PropertyDefinition"), | ||
l = console.log; | ||
it("should toArray()"); | ||
it("should list defined properties"); | ||
it("should be compatible with --property=value style"); | ||
it("passing required:true should test for defaultOption.length > 0") | ||
it("this case should be invalid straight after definition: { type: Array, valueTest: function(a){ return a.length > 0; }}"); | ||
it("should throw when a none-existent property is accessed, e.g. console.log(properties.dfkdshl)"); | ||
it("free usage on --help"); | ||
it("should protect from defining properties with reserved names like clone, toJSON, mixIn etc"); | ||
describe("Thing", function(){ | ||
it("should toArray()"); | ||
it("should list defined properties"); | ||
it("should be compatible with --property=value style"); | ||
it("passing required:true should test for defaultOption.length > 0"); | ||
it("this case should be invalid straight after definition: { type: Array, valueTest: function(a){ return a.length > 0; }}"); | ||
it("should throw when a none-existent property is accessed, e.g. console.log(properties.dfkdshl)"); | ||
it("free usage on --help"); | ||
it("should protect from defining properties with reserved names like clone, toJSON, mixIn etc"); | ||
it("should throw on empty property, i.e. 'rename -' or '--'"); | ||
it("should expose _errors array on Thing instance API, same as 'validationMessages'"); | ||
it("needs consistent error handling.. some errors emit, some throw (like 'renamer -').. should all errors just set valid=false?"); | ||
it("should throw on empty property, i.e. 'rename -' or '--'"); | ||
it("should expose _errors array on Thing instance API, same as 'validationMessages'"); | ||
it("needs consistent error handling.. some errors emit, some throw (like 'renamer -').. should all errors just set valid=false?"); | ||
it("should valueTest run on get, so function can refer to other set values in this.config? e.g. if (this.config.regex) then ..."); | ||
it("a 'post-set' hook, so setting '**/*.txt' on 'files' can be expanded"); | ||
it("scrap constructor to remove need for Thing.call(this)"); | ||
it("should emit 'invalid' when thing changes from valid to invalid"); | ||
it("should valueTest run on get, so function can refer to other set values in this.config? e.g. if (this.config.regex) then ..."); | ||
it("a 'post-set' hook, so setting '**/*.txt' on 'files' can be expanded"); | ||
it("scrap constructor to remove need for Thing.call(this)"); | ||
it("should emit 'invalid' when thing changes from valid to invalid"); | ||
var _thing; | ||
beforeEach(function(){ | ||
_thing = new Thing(); | ||
}); | ||
var _thing; | ||
beforeEach(function(){ | ||
_thing = new Thing(); | ||
}); | ||
describe("Thing API", function(){ | ||
describe("properties:", function(){ | ||
it("valid - should return true only when all property values are valid", function(){ | ||
_thing.define({ name: "one", type: "number", default: 1 }); | ||
assert.strictEqual(_thing.valid, true); | ||
_thing.define({ name: "two", type: "number", default: -1034.1 }); | ||
assert.strictEqual(_thing.valid, true); | ||
_thing.define({ name: "three", type: "number", default: "Cazzo" }); | ||
assert.strictEqual(_thing.valid, false); | ||
describe(".valid", function(){ | ||
it("return true when all values valid", function(){ | ||
_thing.define({ name: "one", type: "number", value: 1 }); | ||
assert.strictEqual(_thing.valid, true); | ||
_thing.define({ name: "two", type: "number", value: -1034.1 }); | ||
assert.strictEqual(_thing.valid, true); | ||
_thing.define({ name: "three", type: "number", value: "Cazzo" }); | ||
assert.strictEqual(_thing.valid, false); | ||
}); | ||
}); | ||
it("validationMessages - should return an array of msgs", function(){ | ||
_thing.define({ name: "one", type: Array, default: 1 }); | ||
_thing.define({ name: "two", type: "string", default: 1 }); | ||
_thing.define({ name: "three", type: RegExp, default: 1 }); | ||
_thing.define({ name: "four", type: "string", default: "clive", valueTest: /test/ }); | ||
_thing.define({ name: "five", type: Array, default: "clive", valueTest: function (val){ | ||
return val.length == 0; | ||
}}); | ||
_thing.define({ name: "six",type: "number", value: 1 }); | ||
assert.ok(_thing.validationMessages.length == 4, JSON.stringify(_thing.validationMessages)); | ||
describe(".validationMessages", function(){ | ||
it("return array of invalid messages", function(){ | ||
_thing.define({ name: "one", type: Array, value: 1 }); | ||
_thing.define({ name: "two", type: "string", value: 1 }); | ||
_thing.define({ name: "three", type: RegExp, value: 1 }); | ||
_thing.define({ name: "four", type: "string", value: "clive", valueTest: /test/ }); | ||
_thing.define({ name: "five", type: Array, value: "clive", valueTest: function (val){ | ||
return val.length === 0; | ||
}}); | ||
_thing.define({ name: "six",type: "number", value: 1 }); | ||
assert.ok(_thing.validationMessages.length === 5, JSON.stringify(_thing.validationMessages)); | ||
}); | ||
}); | ||
it("definitions", function(){ | ||
var def1 = new PropertyDefinition({ name: "one", type: Array, default: 1 }), | ||
def2 = new PropertyDefinition({ name: "two", type: "string", default: 1 }), | ||
def3 = new PropertyDefinition({ name: "three", type: RegExp, default: 1 }); | ||
_thing.define([ def1, def2, def3 ]); | ||
assert.strictEqual(Object.keys(_thing.definitions).length, 3); | ||
assert.strictEqual(_thing.definitions.one, def1); | ||
assert.strictEqual(_thing.definitions.two, def2); | ||
assert.strictEqual(_thing.definitions.three, def3); | ||
describe(".definitions", function(){ | ||
it("return hash of PropertyDefinitions", function(){ | ||
var def1 = new PropertyDefinition({ name: "one", type: Array, value: 1 }), | ||
def2 = new PropertyDefinition({ name: "two", type: "string", value: 1 }), | ||
def3 = new PropertyDefinition({ name: "three", type: RegExp, value: 1 }); | ||
_thing.define([ def1, def2, def3 ]); | ||
assert.strictEqual(Object.keys(_thing.definitions).length, 3); | ||
assert.strictEqual(_thing.definitions.one, def1); | ||
assert.strictEqual(_thing.definitions.two, def2); | ||
assert.strictEqual(_thing.definitions.three, def3); | ||
}); | ||
}); | ||
it("properties", function(){ | ||
_thing.define({ name: "one", type: Array, default: 1 }); | ||
_thing.define({ name: "two", type: "string", default: 1 }); | ||
_thing.define({ name: "three", type: RegExp, default: 1 }); | ||
assert.deepEqual(_thing.properties, [ "one", "two", "three" ]); | ||
}) | ||
describe(".properties", function(){ | ||
it("return array of property name strings", function(){ | ||
_thing.define({ name: "one", type: Array, value: 1 }); | ||
_thing.define({ name: "two", type: "string", value: 1 }); | ||
_thing.define({ name: "three", type: RegExp, value: 1 }); | ||
assert.deepEqual(_thing.properties, [ "one", "two", "three" ]); | ||
}); | ||
}); | ||
}); | ||
describe("methods: ", function(){ | ||
describe("defining properties", function(){ | ||
describe(".define", function(){ | ||
it("define(definition) and definition(name) should set and retrieve", function(){ | ||
var definition = { name: "one", type: "string", default: "one" }; | ||
var definition = { name: "one", type: "string", value: "one" }; | ||
_thing.define(definition); | ||
assert.strictEqual(definition.type, _thing.definition("one").type); | ||
assert.strictEqual(definition.default, _thing.definition("one").default); | ||
assert.strictEqual(definition.value, _thing.definition("one").value); | ||
assert.strictEqual(_thing.get("one"), "one"); | ||
}); | ||
it("define(existingDefinition) should redefine definition", function(){ | ||
@@ -92,7 +101,7 @@ _thing.define({ name: "one", type: "number", alias: "a" }); | ||
}); | ||
it("define(PropertyDefinition) and retrieve with definition(name)", function(){ | ||
var def = new PropertyDefinition({ name: "one", "type": "number" }); | ||
_thing.define(def); | ||
assert.strictEqual(def, _thing.definition("one")); | ||
@@ -104,3 +113,3 @@ }); | ||
_thing.define({ name: "one", "type": "number", alias: "o", valueTest: testValid }); | ||
assert.strictEqual(_thing.definition("one").type, "number"); | ||
@@ -111,8 +120,38 @@ assert.strictEqual(_thing.definition("o").type, "number"); | ||
}); | ||
it("define() should work the same with a `definition.value` as set()"); | ||
it("grouping", function(){ | ||
_thing | ||
.define("group1", [ | ||
{ name: "one", type: "boolean" }, | ||
{ name: "two", type: "string" } | ||
]) | ||
.define("group2", { name: "three", type: "number"}); | ||
assert.deepEqual(_thing.definition("one").groups, ["group1"]); | ||
assert.deepEqual(_thing.definition("two").groups, ["group1"]); | ||
assert.deepEqual(_thing.definition("three").groups, ["group2"]); | ||
}); | ||
it("define(definition) should not throw on duplicate property, updating it instead", function(){ | ||
_thing.define({ name: "yeah", type: "string" }); | ||
assert.doesNotThrow(function(){ | ||
_thing.define({ name: "yeah", type: "boolean", validTest: /\w+/ }); | ||
}); | ||
assert.strictEqual(_thing.definition("yeah").type, "boolean"); | ||
// assert.strictEqual(_thing.definition("yeah").validTest, /\w+/); | ||
}); | ||
it("define(definition) should throw on duplicate alias", function(){ | ||
_thing.define({ name: "three", alias: "t" }); | ||
assert.throws(function(){ | ||
_thing.define({ name: "four", alias: "t" }); | ||
}); | ||
}); | ||
}); | ||
describe("hasValue()", function(){ | ||
describe(".hasValue", function(){ | ||
it("hasValue(propertyName) should return true if property has value", function(){ | ||
@@ -130,3 +169,3 @@ _thing.define({ name: "one" }); | ||
assert.strictEqual(_thing.hasValue(["one", "two"]), false); | ||
_thing.set("one", 1); | ||
@@ -139,24 +178,42 @@ assert.strictEqual(_thing.hasValue(["one", "two"]), true); | ||
}); | ||
it("should unset() an property, and its alias", function(){ | ||
_thing.define({ name: "one", type: "number", default: 1, alias: "K" }); | ||
assert.strictEqual(_thing.get("one"), 1); | ||
assert.strictEqual(_thing.get("K"), 1); | ||
_thing.unset("one"); | ||
assert.strictEqual(_thing.get("one"), undefined); | ||
assert.strictEqual(_thing.get("K"), undefined); | ||
describe(".unset", function(){ | ||
it("should set a property and its alias to undefined", function(){ | ||
_thing.define({ name: "one", type: "number", alias: "K", value: 1 }); | ||
assert.strictEqual(_thing.get("one"), 1); | ||
assert.strictEqual(_thing.get("K"), 1); | ||
_thing.unset("one"); | ||
assert.strictEqual(_thing.get("one"), undefined); | ||
assert.strictEqual(_thing.get("K"), undefined); | ||
}); | ||
}); | ||
it("should remove() an property and its alias"); | ||
describe("setting and getting values", function(){ | ||
it("should set(property, value) and get(property) an array", function(){ | ||
describe(".get", function(){ | ||
it("get(property) with no value set", function(){ | ||
_thing.define({ name: "one", type: "number" }); | ||
assert.strictEqual(_thing.get("one"), undefined); | ||
}); | ||
it("get(property, default)", function(){ | ||
_thing.define({ name: "one", type: "number", value: 1 }); | ||
assert.strictEqual(_thing.get("one"), 1); | ||
}); | ||
it("get(property) should return undefined on unregistered property", function(){ | ||
assert.strictEqual(_thing.get("yeah", "test"), undefined); | ||
}); | ||
}); | ||
describe(".set", function(){ | ||
it("set(property, array)", function(){ | ||
_thing.define({ name: "one", type: Array }); | ||
_thing.set("one", [0, 1]); | ||
assert.deepEqual(_thing.get("one"), [0, 1]); | ||
}) | ||
it("should set(property, value) and get(property) a string", function(){ | ||
_thing.define({ name: "test", type: "string", alias: "d" }); | ||
it("set(property, string)", function(){ | ||
_thing.define({ name: "test", type: "string" }); | ||
_thing.set("test", "testset"); | ||
@@ -167,160 +224,188 @@ | ||
it("should set(alias, value) then get(alias) and get(property)", function(){ | ||
_thing.define({ name: "archiveDirectory", type: "string", alias: "d" }); | ||
_thing.set("d", "testset"); | ||
it("set(alias, string)", function(){ | ||
var val = "test"; | ||
_thing.define({ name: "test", type: "string", alias: "d" }); | ||
_thing.set("d", val); | ||
assert.strictEqual(_thing.get("d"), "testset"); | ||
assert.strictEqual(_thing.get("archiveDirectory"), "testset"); | ||
assert.strictEqual(_thing.get("d"), val); | ||
assert.strictEqual(_thing.get("test"), val); | ||
}); | ||
it("should set default property() value", function(){ | ||
_thing.define({ name: "one", type: "number", default: 1 }); | ||
assert.strictEqual(_thing.get("one"), 1); | ||
it("set(string) on a type Array", function(){ | ||
_thing.define({ name:"one", type: Array }); | ||
_thing.set(["--one", "test ,1 , false"]); | ||
assert.deepEqual(_thing.get("one"), ["test", "1", "false"]); | ||
_thing.set("one", "test , 2 , false"); | ||
assert.deepEqual(_thing.get("one"), ["test", "2", "false"]); | ||
}); | ||
it("set(propertiesHash) should set properties in bulk", function(){ | ||
_thing.define({ name: "one", type: "number", alias: "1" }) | ||
.define({ name: "two", type: "number", alias: "t" }) | ||
.define({ name: "three", type: "number", alias: "3" }); | ||
assert.strictEqual(_thing.get("one"), undefined); | ||
assert.strictEqual(_thing.get("t"), undefined); | ||
assert.strictEqual(_thing.get("3"), undefined); | ||
describe("set values in bulk", function(){ | ||
it("set(object)", function(){ | ||
_thing.define({ name: "one", type: "number" }) | ||
.define({ name: "two", type: "number" }) | ||
.define({ name: "three", type: "number" }); | ||
_thing.set({ one: 1, t: 2, 3: 3 }); | ||
assert.strictEqual(_thing.get("one"), 1); | ||
assert.strictEqual(_thing.get("two"), 2); | ||
assert.strictEqual(_thing.get("three"), 3); | ||
}); | ||
_thing.set({ one: 1, two: 2, three: 3 }); | ||
it("set(configInstance) should set properties in bulk", function(){ | ||
_thing.define({ name: "one", type: "number", alias: "1" }) | ||
.define({ name: "two", type: "number", alias: "t" }) | ||
.define({ name: "three", type: "number", alias: "3" }); | ||
assert.strictEqual(_thing.get("one"), undefined); | ||
assert.strictEqual(_thing.get("t"), undefined); | ||
assert.strictEqual(_thing.get("3"), undefined); | ||
assert.strictEqual(_thing.get("one"), 1); | ||
assert.strictEqual(_thing.get("two"), 2); | ||
assert.strictEqual(_thing.get("three"), 3); | ||
}); | ||
var config2 = new Thing() | ||
.define({ name: "one", type: "number", default: -1 }) | ||
.define({ name: "two", type: "number", default: -2 }) | ||
.define({ name: "three", type: "number", default: -3 }) | ||
it("set(thing)", function(){ | ||
_thing.define({ name: "one", type: "number" }) | ||
.define({ name: "two", type: "number" }) | ||
.define({ name: "three", type: "number" }); | ||
_thing.set(config2); | ||
assert.strictEqual(_thing.get("one"), -1); | ||
assert.strictEqual(_thing.get("two"), -2); | ||
assert.strictEqual(_thing.get("three"), -3); | ||
}); | ||
it("set(propertiesArray) should set properties in bulk", function(){ | ||
var argv = ["-d", "--preset", "dope", "--recurse", "music", "film", "documentary"]; | ||
_thing | ||
.define({ name: "detailed", alias: "d", type: "boolean" }) | ||
.define({ name: "recurse", type: "boolean" }) | ||
.define({ name: "preset", type: "string" }) | ||
.define({ name: "files", type: Array, defaultOption: true }) | ||
.set(argv); | ||
assert.strictEqual(_thing.get("detailed"), true, JSON.stringify(_thing.toJSON())); | ||
assert.strictEqual(_thing.get("recurse"), true, JSON.stringify(_thing.toJSON())); | ||
assert.strictEqual(_thing.get("preset"), "dope"); | ||
assert.deepEqual(_thing.get("files"), ["music", "film", "documentary"]); | ||
}); | ||
it("set(process.argv) should set properties in bulk", function(){ | ||
process.argv = ["node", "test.js", "-d", "--preset", "dope", "--recurse", "music", "film", "documentary"]; | ||
_thing | ||
.define({ name: "detailed", alias: "d", type: "boolean" }) | ||
.define({ name: "recurse", type: "boolean" }) | ||
.define({ name: "preset", type: "string" }) | ||
.define({ name: "files", type: Array, defaultOption: true }) | ||
.set(process.argv); | ||
var thing2 = new Thing() | ||
.define({ name: "one", type: "number", value: -1 }) | ||
.define({ name: "two", type: "number", value: -2 }) | ||
.define({ name: "three", type: "number", value: -3 }) | ||
assert.strictEqual(_thing.get("detailed"), true, JSON.stringify(_thing.toJSON())); | ||
assert.strictEqual(_thing.get("recurse"), true, JSON.stringify(_thing.toJSON())); | ||
assert.strictEqual(_thing.get("preset"), "dope"); | ||
assert.deepEqual(_thing.get("files"), ["music", "film", "documentary"]); | ||
}); | ||
_thing.set(thing2); | ||
it("set(process.argv) should set correct defaults", function(){ | ||
process.argv = [ | ||
"/usr/local/bin/node", "/usr/local/bin/rename", "file1.test","file2.test", | ||
"file3.test", "file4.test", "file5.test", | ||
"-d", "-r" | ||
]; | ||
_thing | ||
.define({ | ||
name: "files", | ||
type: Array, | ||
required: true, | ||
defaultOption: true, | ||
default: [] | ||
}) | ||
.define({ name: "find", type: "string", alias: "f" }) | ||
.define({ name: "make", type: "string", alias: "m", default: "pie" }) | ||
.define({ name: "num", type: "number" }) | ||
.define({ name: "num2", type: "number", default: 10 }) | ||
.define({ name: "replace", type: "string", alias: "r", default: "" }) | ||
.define({ name: "dry-run", type: "boolean", alias: "d" }) | ||
.set(process.argv); | ||
assert.strictEqual(_thing.make, "pie", JSON.stringify(_thing.toJSON())); | ||
assert.strictEqual(_thing.replace, "", JSON.stringify(_thing.toJSON())); | ||
assert.strictEqual(_thing.num, undefined, JSON.stringify(_thing.toJSON())); | ||
assert.strictEqual(_thing.num2, 10, JSON.stringify(_thing.toJSON())); | ||
assert.strictEqual(_thing["dry-run"], true, JSON.stringify(_thing.toJSON())); | ||
assert.deepEqual(_thing.files, ["file1.test", "file2.test", "file3.test", "file4.test", "file5.test"]); | ||
}); | ||
it("set(propertiesArray) with a type Array", function(){ | ||
_thing.define({ name:"one", type: Array }); | ||
assert.strictEqual(_thing.get("one"), -1); | ||
assert.strictEqual(_thing.get("two"), -2); | ||
assert.strictEqual(_thing.get("three"), -3); | ||
_thing.set(["--one", "test", 1, false]); | ||
assert.deepEqual(_thing.get("one"), ["test"]); | ||
}); | ||
_thing.set(["--one", "test ,1 , false"]); | ||
assert.deepEqual(_thing.get("one"), ["test", "1", "false"]); | ||
}); | ||
it("set(['--property', '--value']) should set correctly, where 'property' expects a value", function(){ | ||
_thing.define({ name: "one", type: "string" }); | ||
_thing.set([ "--one", "--23" ]); | ||
assert.deepEqual(_thing.one, "--23"); | ||
_thing.set([ "--one", "" ]); | ||
assert.deepEqual(_thing.one, ""); | ||
}); | ||
it("set(array)", function(){ | ||
var argv = ["-d", "--preset", "dope", "--recurse", "music", "film", "documentary"]; | ||
_thing | ||
.define({ name: "detailed", alias: "d", type: "boolean" }) | ||
.define({ name: "recurse", type: "boolean" }) | ||
.define({ name: "preset", type: "string" }) | ||
.define({ name: "files", type: Array, defaultOption: true }) | ||
.set(argv); | ||
it("set(propertiesArray) with a `defaultOption` of type Array", function(){ | ||
_thing.define({ name: "one", type: Array, defaultOption: true }); | ||
_thing.set(["test", 1, false]); | ||
assert.deepEqual(_thing.get("one"), ["test", 1, false]); | ||
}); | ||
assert.strictEqual(_thing.get("detailed"), true); | ||
assert.strictEqual(_thing.get("recurse"), true); | ||
assert.strictEqual(_thing.get("preset"), "dope"); | ||
assert.deepEqual(_thing.get("files"), ["music", "film", "documentary"]); | ||
}); | ||
it("set(propertiesArray) with a `defaultOption` of type 'string'", function(){ | ||
_thing.define({ name: "one", type: "string", defaultOption: true }); | ||
_thing.set(["test", 1, false]); | ||
assert.strictEqual(_thing.get("one"), "test"); | ||
it("set(process.argv)", function(){ | ||
process.argv = ["node", "test.js", "-d", "--preset", "dope", "--recurse", "music", "film", "documentary"]; | ||
_thing | ||
.define({ name: "detailed", alias: "d", type: "boolean" }) | ||
.define({ name: "recurse", type: "boolean" }) | ||
.define({ name: "preset", type: "string" }) | ||
.define({ name: "files", type: Array, defaultOption: true }) | ||
.set(process.argv); | ||
assert.strictEqual(_thing.get("detailed"), true); | ||
assert.strictEqual(_thing.get("recurse"), true); | ||
assert.strictEqual(_thing.get("preset"), "dope"); | ||
assert.deepEqual(_thing.get("files"), ["music", "film", "documentary"]); | ||
}); | ||
it("set(process.argv) should set correct defaults", function(){ | ||
process.argv = [ | ||
"/usr/local/bin/node", "/usr/local/bin/rename", "file1.test","file2.test", | ||
"file3.test", "file4.test", "file5.test", | ||
"-d", "-r" | ||
]; | ||
_thing | ||
.define({ | ||
name: "files", | ||
type: Array, | ||
required: true, | ||
defaultOption: true, | ||
value: [] | ||
}) | ||
.define({ name: "find", type: "string" }) | ||
.define({ name: "make", type: "string", value: "pie" }) | ||
.define({ name: "num", type: "number" }) | ||
.define({ name: "num2", type: "number", value: 10 }) | ||
.define({ name: "replace", type: "string", alias: "r", value: "" }) | ||
.define({ name: "dry-run", type: "boolean", alias: "d" }) | ||
.set(process.argv); | ||
assert.strictEqual(_thing.make, "pie"); | ||
assert.strictEqual(_thing.replace, ""); | ||
assert.strictEqual(_thing.num, undefined); | ||
assert.strictEqual(_thing.num2, 10); | ||
assert.strictEqual(_thing["dry-run"], true); | ||
assert.deepEqual(_thing.files, ["file1.test", "file2.test", "file3.test", "file4.test", "file5.test"]); | ||
}); | ||
it("set(['--property', '--value']) should set correctly, where 'property' expects a value", function(){ | ||
_thing.define({ name: "one", type: "string" }); | ||
_thing.set([ "--one", "--23" ]); | ||
assert.deepEqual(_thing.one, "--23"); | ||
_thing.set([ "--one", "" ]); | ||
assert.deepEqual(_thing.one, ""); | ||
}); | ||
it("set(array) with a defaultOption of type Array", function(){ | ||
_thing.define({ name: "one", type: Array, defaultOption: true }); | ||
_thing.set(["test", 1, false]); | ||
assert.deepEqual(_thing.get("one"), ["test", 1, false]); | ||
}); | ||
it("set(array) with a defaultOption of type string", function(){ | ||
_thing.define({ name: "one", type: "string", defaultOption: true }); | ||
_thing.set(["test", 1, false]); | ||
assert.strictEqual(_thing.get("one"), "test"); | ||
}); | ||
it("set(array) with a `defaultOption` of type number", function(){ | ||
_thing.define({ name: "one", type: "number", defaultOption: true }); | ||
_thing.set([1, 4, 5]); | ||
assert.strictEqual(_thing.get("one"), 1); | ||
}); | ||
it("warn if set(array) produces defaultValues with no defaultOption set"); | ||
}); | ||
it("set(propertiesArray) with a `defaultOption` of type number", function(){ | ||
_thing.define({ name: "one", type: "number", defaultOption: true }); | ||
_thing.set([1, 4, 5]); | ||
assert.strictEqual(_thing.get("one"), 1); | ||
describe("Error handling", function(){ | ||
it("set(property, value) should emit 'error' on unregistered property", function(){ | ||
assert.throws(function(){ | ||
_thing.set("yeah", "test"); | ||
}); | ||
assert.strictEqual(_thing.valid, false); | ||
assert.strictEqual(_thing._errors.length, 1); | ||
}); | ||
it("catching 'error' surpresses throw on bad set()", function(){ | ||
_thing.on("error", function(err){ | ||
assert.ok(err); | ||
}); | ||
assert.doesNotThrow(function(){ | ||
_thing.set("yeah", "test"); | ||
}); | ||
assert.strictEqual(_thing.valid, false); | ||
assert.strictEqual(_thing._errors.length, 1); | ||
}); | ||
it("set([--property, value]) should emit 'error' on unregistered property", function(){ | ||
assert.throws(function(){ | ||
_thing.set(["--asdklfjlkd"]); | ||
}); | ||
assert.strictEqual(_thing.valid, false); | ||
assert.strictEqual(_thing._errors.length, 1); | ||
}); | ||
it("set(propertiesArray) should not alter propertiesArray itself", function(){ | ||
var args = [ "--one", 1, "--two", 2, "--three", 3 ]; | ||
_thing | ||
.define({ name: "one", type: "number", value: -1 }) | ||
.define({ name: "two", type: "number", value: -2 }) | ||
.define({ name: "three", type: "number", value: -3 }); | ||
assert.deepEqual(args, [ "--one", 1, "--two", 2, "--three", 3 ]); | ||
_thing.set(args); | ||
assert.deepEqual(args, [ "--one", 1, "--two", 2, "--three", 3 ]); | ||
}); | ||
}); | ||
it("warn if set(propertiesArray) produces defaultValues with no defaultOption set"); | ||
}) | ||
}); | ||
it("should clone()", function(){ | ||
_thing.define({ name: "one", type: "number", default: 1 }) | ||
.define({ name: "two", type: "number", default: 2 }); | ||
it(".clone()", function(){ | ||
_thing.define({ name: "one", type: "number", value: 1 }) | ||
.define({ name: "two", type: "number", value: 2 }); | ||
var config2 = _thing.clone(); | ||
@@ -331,52 +416,55 @@ assert.notStrictEqual(_thing, config2); | ||
}); | ||
it("properties() should return Array of property names"); | ||
it("mixin(config)", function(){ | ||
_thing.define({ name: "year", type: "number", default: 2013 }); | ||
var config2 = new Thing().define({ name: "month", type: "string", default: "feb", alias: "m" }); | ||
var config3 = new Thing().define({ name: "day", type: "string", default: "Sunday", alias: "d" }) | ||
_thing.mixIn(config2); | ||
_thing.mixIn(config3); | ||
assert.strictEqual(_thing.get("year"), 2013); | ||
assert.strictEqual(_thing.get("month"), "feb"); | ||
assert.strictEqual(_thing.get("day"), "Sunday"); | ||
assert.strictEqual(_thing.get("m"), "feb"); | ||
assert.strictEqual(_thing.get("d"), "Sunday"); | ||
}); | ||
it("mixin() must fail on duplicate propertyName or alias"); | ||
it("mixin(config, groups)", function(){ | ||
_thing.define({ name: "year", type: "number", default: 2013 }); | ||
var config2 = new Thing().define({ name: "month", type: "string", default: "feb", alias: "m" }); | ||
var config3 = new Thing().define({ name: "day", type: "string", default: "Sunday", alias: "d" }) | ||
_thing.mixIn(config2, "config2"); | ||
_thing.mixIn(config3, ["config2", "config3"]); | ||
assert.strictEqual(_thing.get("year"), 2013); | ||
assert.deepEqual(_thing.definition("year").groups, []); | ||
assert.strictEqual(_thing.get("month"), "feb"); | ||
assert.deepEqual(_thing.definition("month").groups, ["config2"]); | ||
assert.strictEqual(_thing.get("day"), "Sunday"); | ||
assert.deepEqual(_thing.definition("day").groups, ["config2", "config3"]); | ||
it(".properties() should return Array of property names"); | ||
describe(".mixin", function(){ | ||
it("mixin(thing)", function(){ | ||
_thing.define({ name: "year", type: "number", value: 2013 }); | ||
var config2 = new Thing().define({ name: "month", type: "string", value: "feb", alias: "m" }); | ||
var config3 = new Thing().define({ name: "day", type: "string", value: "Sunday", alias: "d" }) | ||
_thing.mixIn(config2); | ||
_thing.mixIn(config3); | ||
assert.strictEqual(_thing.get("year"), 2013); | ||
assert.strictEqual(_thing.get("month"), "feb"); | ||
assert.strictEqual(_thing.get("day"), "Sunday"); | ||
assert.strictEqual(_thing.get("m"), "feb"); | ||
assert.strictEqual(_thing.get("d"), "Sunday"); | ||
}); | ||
it("mixin() must fail on duplicate propertyName or alias"); | ||
it("mixin(thing, groups)", function(){ | ||
_thing.define({ name: "year", type: "number", value: 2013 }); | ||
var config2 = new Thing().define({ name: "month", type: "string", value: "feb", alias: "m" }); | ||
var config3 = new Thing().define({ name: "day", type: "string", value: "Sunday", alias: "d" }) | ||
_thing.mixIn(config2, "config2"); | ||
_thing.mixIn(config3, ["config2", "config3"]); | ||
assert.strictEqual(_thing.get("year"), 2013); | ||
assert.deepEqual(_thing.definition("year").groups, []); | ||
assert.strictEqual(_thing.get("month"), "feb"); | ||
assert.deepEqual(_thing.definition("month").groups, ["config2"]); | ||
assert.strictEqual(_thing.get("day"), "Sunday"); | ||
assert.deepEqual(_thing.definition("day").groups, ["config2", "config3"]); | ||
}); | ||
}); | ||
it("definition(property) should return correct def for full name and alias", function(){ | ||
_thing.define({ name: "one", type: Array, default: [1,2], required: true, alias: "a" }); | ||
assert.strictEqual(_thing.definition("one").type, Array); | ||
assert.strictEqual(_thing.definition("a").type, Array); | ||
assert.deepEqual(_thing.definition("one").default, [1,2]); | ||
assert.deepEqual(_thing.definition("a").default, [1,2]); | ||
assert.strictEqual(_thing.definition("one").required, true); | ||
assert.strictEqual(_thing.definition("a").required, true); | ||
assert.strictEqual(_thing.definition("one").alias, "a"); | ||
assert.strictEqual(_thing.definition("a").alias, "a"); | ||
describe(".definition", function(){ | ||
it("definition(property) should return correct def for full name and alias", function(){ | ||
_thing.define({ name: "one", type: Array, value: [1,2], required: true, alias: "a" }); | ||
assert.strictEqual(_thing.definition("one").type, Array); | ||
assert.strictEqual(_thing.definition("a").type, Array); | ||
assert.deepEqual(_thing.definition("one").value, [1,2]); | ||
assert.deepEqual(_thing.definition("a").value, [1,2]); | ||
assert.strictEqual(_thing.definition("one").required, true); | ||
assert.strictEqual(_thing.definition("a").required, true); | ||
assert.strictEqual(_thing.definition("one").alias, "a"); | ||
assert.strictEqual(_thing.definition("a").alias, "a"); | ||
}); | ||
}); | ||
describe("grouping", function(){ | ||
describe(".group", function(){ | ||
it("grouping summary", function(){ | ||
@@ -389,3 +477,3 @@ // set group after defining | ||
.group("group1", ["one", "two", "three"]); | ||
// group during define | ||
@@ -399,22 +487,22 @@ _thing | ||
.define("group3", { name: "title", type: "number"}); | ||
// group during mixin | ||
var config2 = new Thing().define({ name: "seven" }); | ||
_thing.mixIn(config2, "group4"); | ||
// ungroup specific properties | ||
_thing.ungroup("group1", ["one", "two"]); | ||
// ungroup all | ||
_thing.ungroup("group2"); | ||
// retrieve group | ||
_thing.where({ group: "group3" }).toJSON(); | ||
}); | ||
it("group(groupName, propertyNameArray)", function(){ | ||
_thing | ||
.define({ name: "one", type: "number", alias: "1", default: 1 }) | ||
.define({ name: "two", type: "number", alias: "t", default: 2 }) | ||
.define({ name: "three", type: "number", alias: "3", default: 3 }) | ||
.define({ name: "one", type: "number", alias: "1", value: 1 }) | ||
.define({ name: "two", type: "number", alias: "t", value: 2 }) | ||
.define({ name: "three", type: "number", alias: "3", value: 3 }) | ||
.group("everything", ["one", "two", "three"]) | ||
@@ -424,3 +512,3 @@ .group("everything2", ["one", "two", "three"]) | ||
.group("not the smallest", ["two", "three"]); | ||
assert.deepEqual(_thing.where({ group: "everything" }).toJSON(), {one: 1, two:2, three:3 }); | ||
@@ -431,13 +519,13 @@ assert.deepEqual(_thing.where({ group: "everything2" }).toJSON(), {one: 1, two:2, three:3 }); | ||
}); | ||
it("group(groupName) groups all properties", function(){ | ||
_thing | ||
.define({ name: "one", type: "number", alias: "1", default: 1 }) | ||
.define({ name: "two", type: "number", alias: "t", default: 2 }) | ||
.define({ name: "three", type: "number", alias: "3", default: 3 }) | ||
.define({ name: "one", type: "number", alias: "1", value: 1 }) | ||
.define({ name: "two", type: "number", alias: "t", value: 2 }) | ||
.define({ name: "three", type: "number", alias: "3", value: 3 }) | ||
.group("everything"); | ||
assert.deepEqual(_thing.where({ group: "everything" }).toJSON(), {one: 1, two:2, three:3 }); | ||
}) | ||
it("ungroup(groupName) should remove all properties from groupName", function(){ | ||
@@ -449,6 +537,6 @@ _thing | ||
assert.deepEqual(_thing.where({ group: "group1"}).properties, ["one", "two"]); | ||
_thing.ungroup("group1"); | ||
assert.deepEqual(_thing.where({ group: "group1"}).properties, []); | ||
}); | ||
@@ -463,3 +551,3 @@ | ||
assert.deepEqual(_thing.where({ group: "group1"}).properties, ["one", "two", "four"]); | ||
_thing.ungroup("group1", "one"); | ||
@@ -471,10 +559,10 @@ assert.deepEqual(_thing.where({ group: "group1"}).properties, ["two", "four"]); | ||
}); | ||
it("where({group: groupName}) returns a Thing clone, with reduced properties", function(){ | ||
_thing | ||
.define({ name: "one", type: "number", alias: "1", default: 1 }) | ||
.define({ name: "two", type: "number", alias: "t", default: 2 }) | ||
.define({ name: "three", type: "number", alias: "3", default: 3 }) | ||
.define({ name: "one", type: "number", alias: "1", value: 1 }) | ||
.define({ name: "two", type: "number", alias: "t", value: 2 }) | ||
.define({ name: "three", type: "number", alias: "3", value: 3 }) | ||
.group("group", ["two", "three"]); | ||
assert.strictEqual(_thing.where({ group: "group" }).get("one"), undefined); | ||
@@ -487,9 +575,9 @@ assert.strictEqual(_thing.where({ group: "group" }).get("two"), 2); | ||
}); | ||
it("where({ name: {$ne: []}}) should exclude named properties", function(){ | ||
_thing | ||
.define({ name: "one", type: "number", alias: "1", default: 1 }) | ||
.define({ name: "two", type: "number", alias: "t", default: 2 }) | ||
.define({ name: "three", type: "number", alias: "3", default: 3 }); | ||
.define({ name: "one", type: "number", alias: "1", value: 1 }) | ||
.define({ name: "two", type: "number", alias: "t", value: 2 }) | ||
.define({ name: "three", type: "number", alias: "3", value: 3 }); | ||
assert.throws(function(){ | ||
@@ -503,5 +591,5 @@ assert.strictEqual(_thing.where({ name: { $ne: ["one", "two"] }}).get("one"), 1); | ||
}); | ||
it("define() with groups and subgroups"); | ||
it("define(groupName, definitionArray) with groups", function(){ | ||
@@ -522,5 +610,5 @@ _thing | ||
{ | ||
name: "start-at", | ||
type: "string", | ||
valid: /duration|frame|pts/, | ||
name: "start-at", | ||
type: "string", | ||
valid: /duration|frame|pts/, | ||
invalidMsg: "please specify the unit, e.g. --start-at duration:10 or --start-at frame:2000" | ||
@@ -532,6 +620,6 @@ }, | ||
valid: /duration|frame|pts/, | ||
invalidMsg: "please specify the unit, e.g. --stop-at duration:100 or --stop-at frame:3000" | ||
invalidMsg: "please specify the unit, e.g. --stop-at duration:100 or --stop-at frame:3000" | ||
} | ||
]); | ||
assert.deepEqual(_thing.definition("no group").groups, []); | ||
@@ -542,65 +630,5 @@ assert.deepEqual(_thing.definition("title").groups, ["source"], JSON.stringify(_thing.definition("title"))); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe("Error handling", function(){ | ||
it("define(definition) should not throw on duplicate property", function(){ | ||
_thing.define({ name: "yeah", type: "string" }); | ||
assert.doesNotThrow(function(){ | ||
_thing.define({ name: "yeah", type: "boolean" }); | ||
}); | ||
assert.strictEqual(_thing.definition("yeah").type, "boolean"); | ||
}); | ||
it("define(definition) should throw on duplicate alias", function(){ | ||
_thing.define({ name: "three", alias: "t" }); | ||
assert.throws(function(){ | ||
_thing.define({ name: "four", alias: "t" }); | ||
}); | ||
}); | ||
it("set(property, value) should emit 'error' on unregistered property", function(){ | ||
assert.throws(function(){ | ||
_thing.set("yeah", "test"); | ||
}); | ||
assert.strictEqual(_thing.valid, false); | ||
assert.strictEqual(_thing._errors.length, 1); | ||
}); | ||
it("catching 'error' surpresses throw on bad set()", function(){ | ||
_thing.on("error", function(err){ | ||
assert.ok(err); | ||
}); | ||
assert.doesNotThrow(function(){ | ||
_thing.set("yeah", "test"); | ||
}); | ||
assert.strictEqual(_thing.valid, false); | ||
assert.strictEqual(_thing._errors.length, 1); | ||
}); | ||
it("set([--property, value]) should emit 'error' on unregistered property", function(){ | ||
assert.throws(function(){ | ||
_thing.set(["--asdklfjlkd"]); | ||
}); | ||
assert.strictEqual(_thing.valid, false); | ||
assert.strictEqual(_thing._errors.length, 1); | ||
}); | ||
it("get(property) should return undefined on unregistered property", function(){ | ||
assert.strictEqual(_thing.get("yeah", "test"), undefined); | ||
}); | ||
it("set(propertiesArray) should not alter propertiesArray itself", function(){ | ||
var args = [ "--one", 1, "--two", 2, "--three", 3 ]; | ||
_thing | ||
.define({ name: "one", type: "number", default: -1 }) | ||
.define({ name: "two", type: "number", default: -2 }) | ||
.define({ name: "three", type: "number", default: -3 }); | ||
assert.deepEqual(args, [ "--one", 1, "--two", 2, "--three", 3 ]); | ||
_thing.set(args); | ||
assert.deepEqual(args, [ "--one", 1, "--two", 2, "--three", 3 ]); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
91252
1749
499
2
5
+ Addedwodge@~0.1.0
+ Addedwodge@0.1.5(transitive)