mirror of
https://github.com/Hopiu/postal.js.git
synced 2026-04-23 16:14:52 +00:00
Initial add
This commit is contained in:
commit
6dabb5795c
17 changed files with 10626 additions and 0 deletions
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
*.suo
|
||||
*.csproj.user
|
||||
bin
|
||||
obj
|
||||
*.pdb
|
||||
_ReSharper*
|
||||
*.ReSharper.user
|
||||
*.ReSharper
|
||||
desktop.ini
|
||||
.eprj
|
||||
.idea
|
||||
2
build/NodeExports.js
Normal file
2
build/NodeExports.js
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
exports.Postal = MessageBroker;
|
||||
2
build/SourceManifest.txt
Normal file
2
build/SourceManifest.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
src/languageExtensions.js
|
||||
src/broker.js
|
||||
12
build/linux-build-node.sh
Executable file
12
build/linux-build-node.sh
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
#!/bin/sh
|
||||
|
||||
OutFile='output/nodejs/postal.js'
|
||||
|
||||
cp version-header.js $OutFile
|
||||
|
||||
# Combine the source files
|
||||
while read line; do
|
||||
cat ../$line >> $OutFile
|
||||
done < SourceManifest.txt
|
||||
|
||||
cat NodeExports.js >> $OutFile
|
||||
111
build/output/nodejs/postal.js
Normal file
111
build/output/nodejs/postal.js
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
// Postal.js
|
||||
// Author: Jim Cowart
|
||||
// License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
|
||||
// Version 0.0.1
|
||||
|
||||
if(!Object.prototype.forEach) {
|
||||
Object.prototype.forEach = function (callback) {
|
||||
var self = this;
|
||||
for(var x in self) {
|
||||
if(self.hasOwnProperty(x)) {
|
||||
callback(self[x]);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
if(!Object.prototype.forEachKeyValue) {
|
||||
Object.prototype.forEachKeyValue = function (callback) {
|
||||
var self = this;
|
||||
for(var x in self) {
|
||||
if(self.hasOwnProperty(x)) {
|
||||
callback(x, self[x]);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function isArray(value) {
|
||||
var s = typeof value;
|
||||
if (s === 'object') {
|
||||
if (value) {
|
||||
if (typeof value.length === 'number' &&
|
||||
!(value.propertyIsEnumerable('length')) &&
|
||||
typeof value.splice === 'function') {
|
||||
s = 'array';
|
||||
}
|
||||
}
|
||||
}
|
||||
return s === 'array';
|
||||
}
|
||||
|
||||
var slice = [].slice;var MessageBroker = function() {
|
||||
var subscriptions = {},
|
||||
regexify = function(topic) {
|
||||
if(!this[topic]) {
|
||||
this[topic] = topic.replace(".", "\.").replace("*", ".*");
|
||||
}
|
||||
return this[topic];
|
||||
}.bind(this),
|
||||
isTopicMatch = function(topic, comparison) {
|
||||
if(!this[topic + '_' + comparison]) {
|
||||
this[topic + '_' + comparison] = topic === comparison ||
|
||||
(comparison.indexOf("*") !== -1 && topic.search(regexify(comparison)) !== -1) ||
|
||||
(topic.indexOf("*") !== -1 && comparison.search(regexify(topic)) !== -1);
|
||||
}
|
||||
return this[topic + '_' + comparison];
|
||||
}.bind(this);
|
||||
|
||||
this.subscribe = function(topic, callback) {
|
||||
var topicList = topic.split(/\s/), // we allow multiple topics to be subscribed in one call.
|
||||
subIdx = 0,
|
||||
exists;
|
||||
topicList.forEach(function(topic) {
|
||||
exists = false;
|
||||
if(!subscriptions[topic]) {
|
||||
subscriptions[topic] = [callback];
|
||||
}
|
||||
else {
|
||||
subscriptions[topic].forEach(function(sub) {
|
||||
if(subscriptions[topic][subIdx] === callback) {
|
||||
exists = true;
|
||||
}
|
||||
});
|
||||
if(!exists) {
|
||||
subscriptions[topic].push(callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
// return callback for un-subscribing...
|
||||
return function() {
|
||||
this.unsubscribe(topic, callback);
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
this.publish = function(topic, data) {
|
||||
subscriptions.forEachKeyValue(function(subNm, subs) {
|
||||
if(isTopicMatch(topic, subNm)) {
|
||||
subs.forEach(function(callback) {
|
||||
if(typeof callback === 'function') {
|
||||
callback(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.unsubscribe = function(topic, callback) {
|
||||
if ( !subscriptions[ topic ] ) {
|
||||
return;
|
||||
}
|
||||
var length = subscriptions[ topic ].length,
|
||||
idx = 0;
|
||||
for ( ; idx < length; idx++ ) {
|
||||
if (subscriptions[topic][idx] === callback) {
|
||||
subscriptions[topic].splice( idx, 1 );
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
exports.Postal = MessageBroker;
|
||||
4
build/source-references.js
Normal file
4
build/source-references.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
simplifyDebugCallback([
|
||||
'src/languageExtensions.js',
|
||||
'src/broker.js',
|
||||
]);
|
||||
BIN
build/tools/curl.exe
Normal file
BIN
build/tools/curl.exe
Normal file
Binary file not shown.
5
build/version-header.js
Normal file
5
build/version-header.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Postal.js
|
||||
// Author: Jim Cowart
|
||||
// License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
|
||||
// Version 0.0.1
|
||||
|
||||
8374
lib/jquery-1.5.2.js
vendored
Normal file
8374
lib/jquery-1.5.2.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
576
lib/pavlov.js
Normal file
576
lib/pavlov.js
Normal file
|
|
@ -0,0 +1,576 @@
|
|||
/**
|
||||
* Pavlov - Behavioral API over QUnit
|
||||
*
|
||||
* version 0.2.3
|
||||
*
|
||||
* http://michaelmonteleone.net/projects/pavlov
|
||||
* http://github.com/mmonteleone/pavlov
|
||||
*
|
||||
* Copyright (c) 2009 Michael Monteleone
|
||||
* Licensed under terms of the MIT License (README.markdown)
|
||||
*/
|
||||
(function(){
|
||||
// capture reference to global scope
|
||||
var globalScope = this;
|
||||
|
||||
// ===========
|
||||
// = Helpers =
|
||||
// ===========
|
||||
|
||||
// Trimmed versions of jQuery helpers for use only within pavlov
|
||||
|
||||
/**
|
||||
* Iterates over an object or array
|
||||
* @param {Object|Array} object object or array to iterate
|
||||
* @param {Function} callback callback for each iterated item
|
||||
*/
|
||||
var each = function(object, callback) {
|
||||
var name;
|
||||
var i = 0;
|
||||
var length = object.length;
|
||||
|
||||
if ( length === undefined ) {
|
||||
for ( name in object ) {
|
||||
if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for ( var value = object[0];
|
||||
i < length && callback.call( value, i, value ) !== false;
|
||||
value = object[++i] ) {}
|
||||
}
|
||||
|
||||
return object;
|
||||
};
|
||||
|
||||
/**
|
||||
* converts an array-like object to an array
|
||||
* @param {Object} array array-like object
|
||||
* @returns array
|
||||
*/
|
||||
var makeArray = function(array) {
|
||||
var ret = [];
|
||||
|
||||
var i = array.length;
|
||||
while( i ) { ret[--i] = array[i]; }
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* returns whether or not an object is an array
|
||||
* @param {Object} obj object to test
|
||||
* @returns whether or not object is array
|
||||
*/
|
||||
var isArray = function(obj) {
|
||||
return Object.prototype.toString.call(obj) === "[object Array]";
|
||||
};
|
||||
|
||||
/**
|
||||
* merges properties form one object to another
|
||||
* @param {Object} dest object to receive merged properties
|
||||
* @param {Object} src object containing properies to merge
|
||||
*/
|
||||
var extend = function(dest, src) {
|
||||
for(var prop in src) {
|
||||
dest[prop] = src[prop];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* minimalist (and yes, non-optimal/leaky) event binder
|
||||
* not meant for wide use. only for jquery-less internal use in pavlov
|
||||
* @param {Element} elem Event-triggering Element
|
||||
* @param {String} type name of event
|
||||
* @param {Function} fn callback
|
||||
*/
|
||||
var addEvent = function(elem, type, fn){
|
||||
if ( elem.addEventListener ) {
|
||||
elem.addEventListener( type, fn, false );
|
||||
} else if ( elem.attachEvent ) {
|
||||
elem.attachEvent( "on" + type, fn );
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// ====================
|
||||
// = Example Building =
|
||||
// ====================
|
||||
|
||||
var examples = [];
|
||||
var currentExample;
|
||||
|
||||
/**
|
||||
* Example Class
|
||||
* Represents an instance of an example (a describe)
|
||||
* contains references to parent and nested examples
|
||||
* exposes methods for returning combined lists of before, after, and names
|
||||
* @constructor
|
||||
* @param {example} parent example to append self as child to (optional)
|
||||
*/
|
||||
function example(parent) {
|
||||
// private
|
||||
|
||||
if(parent) {
|
||||
// if there's a parent, append self as nested example
|
||||
parent.children.push(this);
|
||||
} else {
|
||||
// otherwise, add this as a new root example
|
||||
examples.push(this);
|
||||
}
|
||||
|
||||
var thisExample = this;
|
||||
|
||||
/**
|
||||
* Rolls up list of current and ancestors values for given prop name
|
||||
* @param {String} prop Name of property to roll up
|
||||
* @returns array of values corresponding to prop name
|
||||
*/
|
||||
var rollup = function(prop) {
|
||||
var items = [];
|
||||
var node = thisExample;
|
||||
while(node !== null) {
|
||||
items.push(node[prop]);
|
||||
node = node.parent;
|
||||
}
|
||||
return items;
|
||||
};
|
||||
|
||||
// public
|
||||
|
||||
// parent example
|
||||
this.parent = parent ? parent : null;
|
||||
// nested examples
|
||||
this.children = [];
|
||||
// name of this description
|
||||
this.name = '';
|
||||
// function to happen before all contained specs
|
||||
this.before = function() {};
|
||||
// function to happen after all contained specs
|
||||
this.after = function() {};
|
||||
// array of it() tests
|
||||
this.specs = [];
|
||||
|
||||
/**
|
||||
* rolls up this and ancestor's before functions
|
||||
* @returns arrayt of functions
|
||||
*/
|
||||
this.befores = function(){
|
||||
return rollup('before').reverse();
|
||||
};
|
||||
/**
|
||||
* Rolls up this and ancestor's after functions
|
||||
* @returns array of functions
|
||||
*/
|
||||
this.afters = function(){
|
||||
return rollup('after');
|
||||
};
|
||||
/**
|
||||
* Rolls up this and ancestor's description names, joined
|
||||
* @returns string of joined description names
|
||||
*/
|
||||
this.names = function(){
|
||||
return rollup('name').reverse().join(', ');
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// = Assertions =
|
||||
// ==============
|
||||
|
||||
/**
|
||||
* Collection of default-bundled assertion implementations
|
||||
*/
|
||||
var assertions = {
|
||||
equals: function(actual, expected, message) {
|
||||
equals(actual, expected, message);
|
||||
},
|
||||
isEqualTo: function(actual, expected, message) {
|
||||
equals(actual, expected, message);
|
||||
},
|
||||
isNotEqualTo: function(actual, expected, message) {
|
||||
ok(actual !== expected, message);
|
||||
},
|
||||
isSameAs: function(actual, expected, message) {
|
||||
same(actual, expected, message);
|
||||
},
|
||||
isNotSameAs: function(actual, expected, message) {
|
||||
ok(!QUnit.equiv(actual, expected), message);
|
||||
},
|
||||
isTrue: function(actual, message) {
|
||||
ok(actual, message);
|
||||
},
|
||||
isFalse: function(actual, message) {
|
||||
ok(!actual, message);
|
||||
},
|
||||
isNull: function(actual, message) {
|
||||
ok(actual === null, message);
|
||||
},
|
||||
isNotNull: function(actual, message) {
|
||||
ok(actual !== null, message);
|
||||
},
|
||||
isDefined: function(actual, message) {
|
||||
ok(typeof(actual) !== 'undefined', message);
|
||||
},
|
||||
isUndefined: function(actual, message) {
|
||||
ok(typeof(actual) === 'undefined', message);
|
||||
},
|
||||
pass: function(actual, message) {
|
||||
ok(true, message);
|
||||
},
|
||||
fail: function(actual, message) {
|
||||
ok(!true, message);
|
||||
},
|
||||
throwsException: function(actual, expectedErrorDescription, message) {
|
||||
/* can optionally accept expected error message */
|
||||
try{
|
||||
actual();
|
||||
ok(!true, message);
|
||||
} catch(e) {
|
||||
if(arguments.length > 1) {
|
||||
ok(e === expectedErrorDescription, message);
|
||||
} else {
|
||||
ok(true, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* AssertionHandler
|
||||
* represents instance of an assertion regarding a particular
|
||||
* actual value, and provides an api around asserting that value
|
||||
* against any of the bundled assertion handlers and custom ones.
|
||||
* @constructor
|
||||
* @param {Object} value A test-produced value to assert against
|
||||
*/
|
||||
var assertHandler = function(value) {
|
||||
this.value = value;
|
||||
};
|
||||
/**
|
||||
* Appends assertion methods to the assertHandler prototype
|
||||
* For each provided assertion implementation, adds an identically named
|
||||
* assertion function to assertionHandler prototype which can run impl
|
||||
* @param {Object} asserts Object containing assertion implementations
|
||||
*/
|
||||
var addAssertions = function(asserts) {
|
||||
each(asserts, function(name, fn){
|
||||
assertHandler.prototype[name] = function() {
|
||||
// implement this handler against backend
|
||||
// by pre-pending assertHandler's current value to args
|
||||
var args = makeArray(arguments);
|
||||
args.unshift(this.value);
|
||||
fn.apply(this, args);
|
||||
};
|
||||
});
|
||||
};
|
||||
// pre-add all the default bundled assertions
|
||||
addAssertions(assertions);
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// = Pavlov Public API =
|
||||
// =====================
|
||||
|
||||
|
||||
/**
|
||||
* Object containing methods to be made available as public API
|
||||
*/
|
||||
var api = {
|
||||
/**
|
||||
* Initiates a new Example context
|
||||
* @param {String} description Name of what's being "described"
|
||||
* @param {Function} fn Function containing description (before, after, specs, nested examples)
|
||||
*/
|
||||
describe: function(description, fn) {
|
||||
if(arguments.length < 2) {
|
||||
throw("both 'description' and 'fn' arguments are required");
|
||||
}
|
||||
|
||||
// capture reference to current example before construction
|
||||
var originalExample = currentExample;
|
||||
try{
|
||||
// create new current example for construction
|
||||
currentExample = new example(currentExample);
|
||||
currentExample.name = description;
|
||||
fn();
|
||||
} finally {
|
||||
// restore original reference after construction
|
||||
currentExample = originalExample;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets a function to occur before all contained specs and nested examples' specs
|
||||
* @param {Function} fn Function to be executed
|
||||
*/
|
||||
before: function(fn) {
|
||||
if(arguments.length === 0) {
|
||||
throw("'fn' argument is required");
|
||||
}
|
||||
currentExample.before = fn;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets a function to occur after all contained tests and nested examples' tests
|
||||
* @param {Function} fn Function to be executed
|
||||
*/
|
||||
after: function(fn) {
|
||||
if(arguments.length === 0) {
|
||||
throw("'fn' argument is required");
|
||||
}
|
||||
currentExample.after = fn;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a spec (test) to occur within an example
|
||||
* When not passed fn, creates a spec-stubbing fn which asserts fail "Not Implemented"
|
||||
* @param {String} specification Description of what "it" "should do"
|
||||
* @param {Function} fn Function containing a test to assert that it does indeed do it (optional)
|
||||
*/
|
||||
it: function(specification, fn) {
|
||||
if(arguments.length === 0) {
|
||||
throw("'specification' argument is required");
|
||||
}
|
||||
thisApi = this;
|
||||
if(fn) {
|
||||
currentExample.specs.push([specification, fn]);
|
||||
} else {
|
||||
// if not passed an implementation, create an implementation that simply asserts fail
|
||||
thisApi.it(specification, function(){thisApi.assert.fail('Not Implemented');});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Generates a row spec for each argument passed, applying
|
||||
* each argument to a new call against the spec
|
||||
* @returns an object with an it() function for defining
|
||||
* function to be called for each of given's arguments
|
||||
* @param {Array} arguments either list of values or list of arrays of values
|
||||
*/
|
||||
given: function() {
|
||||
if(arguments.length === 0) {
|
||||
throw("at least one argument is required");
|
||||
}
|
||||
var args = makeArray(arguments);
|
||||
var thisIt = this.it;
|
||||
|
||||
return {
|
||||
/**
|
||||
* Defines a row spec (test) which is applied against each
|
||||
* of the given's arguments.
|
||||
*/
|
||||
it: function(specification, fn) {
|
||||
each(args, function(){
|
||||
var arg = this;
|
||||
thisIt("given " + arg + ", " + specification, function(){
|
||||
fn.apply(this, isArray(arg) ? arg : [arg]);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Assert a value against any of the bundled or custom assertions
|
||||
* @param {Object} value A value to be asserted
|
||||
* @returns an assertHandler instance to fluently perform an assertion with
|
||||
*/
|
||||
assert: function(value) {
|
||||
return new assertHandler(value);
|
||||
},
|
||||
|
||||
/**
|
||||
* specifies test runner to synchronously wait
|
||||
* @param {Number} ms Milliseconds to wait
|
||||
* @param {Function} fn Function to execute after ms has
|
||||
* passed before resuming
|
||||
*/
|
||||
wait: function(ms, fn) {
|
||||
if(arguments.length < 2) {
|
||||
throw("both 'ms' and 'fn' arguments are required");
|
||||
}
|
||||
stop();
|
||||
QUnit.specify.globalObject.setTimeout(function(){
|
||||
fn();
|
||||
start();
|
||||
}, ms);
|
||||
}
|
||||
};
|
||||
|
||||
// extend api's assert function for easier syntax for blank pass and fail
|
||||
extend(api.assert, {
|
||||
/**
|
||||
* Shortcuts assert().pass() with assert.pass()
|
||||
* @param {String} message Assertion message (optional)
|
||||
*/
|
||||
pass: function(message){
|
||||
(new assertHandler()).pass(message);
|
||||
},
|
||||
/**
|
||||
* Shortcuts assert().fail() with assert.fail()
|
||||
* @param {String} message Assertion message (optional)
|
||||
*/
|
||||
fail: function(message){
|
||||
(new assertHandler()).fail(message);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Extends a function's scope
|
||||
* applies the extra scope to the function returns un-run new version of fn
|
||||
* inspired by Yehuda Katz's metaprogramming Screw.Unit
|
||||
* different in that new function can still accept all parameters original function could
|
||||
* @param {Function} fn Target function for extending
|
||||
* @param {Object} thisArg Object for the function's "this" to refer
|
||||
* @param {Object} extraScope object whose members will be added to fn's scope
|
||||
* @returns Modified version of original function with extra scope. Can still
|
||||
* accept parameters of original function
|
||||
*/
|
||||
var extendScope = function(fn, thisArg, extraScope) {
|
||||
|
||||
// get a string of the fn's parameters
|
||||
var params = fn.toString().match(/\(([^\)]*)\)/)[1];
|
||||
// get a string of fn's body
|
||||
var source = fn.toString().match(/^[^\{]*\{((.*\s*)*)\}/m)[1];
|
||||
|
||||
// create a new function with same parameters and
|
||||
// body wrapped in a with(extraScope){ }
|
||||
fn = new Function(
|
||||
"extraScope" + (params ? ", " + params : ""),
|
||||
"with(extraScope){" + source + "}");
|
||||
|
||||
// returns a fn wrapper which takes passed args,
|
||||
// pre-pends extraScope arg, and applies to modified fn
|
||||
return function(){
|
||||
var args = [extraScope];
|
||||
each(arguments,function(){
|
||||
args.push(this);
|
||||
});
|
||||
fn.apply(thisArg, args);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Top-level Specify method. Declares a new QUnit.specify context
|
||||
* @param {String} name Name of what's being specified
|
||||
* @param {Function} fn Function containing exmaples and specs
|
||||
*/
|
||||
var specify = function(name, fn) {
|
||||
if(arguments.length < 2) {
|
||||
throw("both 'name' and 'fn' arguments are required")
|
||||
}
|
||||
examples = [];
|
||||
currentExample = null;
|
||||
|
||||
// set the test suite title
|
||||
document.title = name + " Specifications";
|
||||
addEvent(window,'load',function(){
|
||||
// document.getElementsByTag('h1').innerHTML = name;
|
||||
var h1s = document.getElementsByTagName('h1');
|
||||
if(h1s.length > 0)
|
||||
h1s[0].innerHTML = document.title;
|
||||
});
|
||||
|
||||
if(QUnit.specify.globalApi) {
|
||||
// if set to extend global api,
|
||||
// extend global api and run example builder
|
||||
extend(globalScope, api);
|
||||
fn();
|
||||
} else {
|
||||
// otherwise, extend example builder's scope with api
|
||||
// and run example builder
|
||||
extendScope(fn, this, api)();
|
||||
}
|
||||
|
||||
// compile examples into flat qunit statements
|
||||
var qunitStatements = compile(examples);
|
||||
|
||||
// run qunit tests
|
||||
each(qunitStatements, function(){ this(); });
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// ==========================================
|
||||
// = Example-to-QUnit Statement Compilation =
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Compiles nested set of examples into flat array of QUnit statements
|
||||
* @param {Array} examples Array of possibly nested Example instances
|
||||
* @returns array of QUnit statements each wrapped in an anonymous fn
|
||||
*/
|
||||
var compile = function(examples) {
|
||||
var statements = [];
|
||||
|
||||
/**
|
||||
* Comples a single example and its children into QUnit statements
|
||||
* @param {Example} example Single example instance
|
||||
* possibly with nested instances
|
||||
*/
|
||||
var compileDescription = function(example) {
|
||||
|
||||
// get before and after rollups
|
||||
var befores = example.befores();
|
||||
var afters = example.afters();
|
||||
|
||||
// create a module with setup and teardown
|
||||
// that executes all current befores/afters
|
||||
statements.push(function(){
|
||||
module(example.names(), {
|
||||
setup: function(){
|
||||
each(befores, function(){ this(); });
|
||||
},
|
||||
teardown: function(){
|
||||
each(afters, function(){ this(); });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// create a test for each spec/"it" in the example
|
||||
each(example.specs, function(){
|
||||
var spec = this;
|
||||
statements.push(function(){
|
||||
test(spec[0],spec[1]);
|
||||
});
|
||||
});
|
||||
|
||||
// recurse through example's nested examples
|
||||
each(example.children, function() {
|
||||
compileDescription(this);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// compile all root examples
|
||||
each(examples, function() {
|
||||
compileDescription(this, statements);
|
||||
});
|
||||
|
||||
return statements;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// = Expose Public API =
|
||||
// =====================
|
||||
|
||||
// extend QUnit
|
||||
QUnit.specify = specify;
|
||||
// add global settings onto QUnit.specify
|
||||
extend(specify, {
|
||||
version: '0.2.3',
|
||||
globalApi: false, // when true, adds api to global scope
|
||||
extendAssertions: addAssertions, // function for adding custom assertions
|
||||
globalObject: window // injectable global containing setTimeout and pals
|
||||
});
|
||||
|
||||
})();
|
||||
|
||||
119
lib/qunit.css
Normal file
119
lib/qunit.css
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
|
||||
ol#qunit-tests {
|
||||
font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
|
||||
margin:0;
|
||||
padding:0;
|
||||
list-style-position:inside;
|
||||
|
||||
font-size: smaller;
|
||||
}
|
||||
ol#qunit-tests li{
|
||||
padding:0.4em 0.5em 0.4em 2.5em;
|
||||
border-bottom:1px solid #fff;
|
||||
font-size:small;
|
||||
list-style-position:inside;
|
||||
}
|
||||
ol#qunit-tests li ol{
|
||||
box-shadow: inset 0px 2px 13px #999;
|
||||
-moz-box-shadow: inset 0px 2px 13px #999;
|
||||
-webkit-box-shadow: inset 0px 2px 13px #999;
|
||||
margin-top:0.5em;
|
||||
margin-left:0;
|
||||
padding:0.5em;
|
||||
background-color:#fff;
|
||||
border-radius:15px;
|
||||
-moz-border-radius: 15px;
|
||||
-webkit-border-radius: 15px;
|
||||
}
|
||||
ol#qunit-tests li li{
|
||||
border-bottom:none;
|
||||
margin:0.5em;
|
||||
background-color:#fff;
|
||||
list-style-position: inside;
|
||||
padding:0.4em 0.5em 0.4em 0.5em;
|
||||
}
|
||||
|
||||
ol#qunit-tests li li.pass{
|
||||
border-left:26px solid #C6E746;
|
||||
background-color:#fff;
|
||||
color:#5E740B;
|
||||
}
|
||||
ol#qunit-tests li li.fail{
|
||||
border-left:26px solid #EE5757;
|
||||
background-color:#fff;
|
||||
color:#710909;
|
||||
}
|
||||
ol#qunit-tests li.pass{
|
||||
background-color:#D2E0E6;
|
||||
color:#528CE0;
|
||||
}
|
||||
ol#qunit-tests li.fail{
|
||||
background-color:#EE5757;
|
||||
color:#000;
|
||||
}
|
||||
ol#qunit-tests li strong {
|
||||
cursor:pointer;
|
||||
}
|
||||
h1#qunit-header{
|
||||
background-color:#0d3349;
|
||||
margin:0;
|
||||
padding:0.5em 0 0.5em 1em;
|
||||
color:#fff;
|
||||
font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
|
||||
border-top-right-radius:15px;
|
||||
border-top-left-radius:15px;
|
||||
-moz-border-radius-topright:15px;
|
||||
-moz-border-radius-topleft:15px;
|
||||
-webkit-border-top-right-radius:15px;
|
||||
-webkit-border-top-left-radius:15px;
|
||||
text-shadow: rgba(0, 0, 0, 0.5) 4px 4px 1px;
|
||||
}
|
||||
h2#qunit-banner{
|
||||
font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
|
||||
height:5px;
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
h2#qunit-banner.qunit-pass{
|
||||
background-color:#C6E746;
|
||||
}
|
||||
h2#qunit-banner.qunit-fail, #qunit-testrunner-toolbar {
|
||||
background-color:#EE5757;
|
||||
}
|
||||
#qunit-testrunner-toolbar {
|
||||
font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
|
||||
padding:0;
|
||||
/*width:80%;*/
|
||||
padding:0em 0 0.5em 2em;
|
||||
font-size: small;
|
||||
}
|
||||
h2#qunit-userAgent {
|
||||
font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
|
||||
background-color:#2b81af;
|
||||
margin:0;
|
||||
padding:0;
|
||||
color:#fff;
|
||||
font-size: small;
|
||||
padding:0.5em 0 0.5em 2.5em;
|
||||
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
|
||||
}
|
||||
p#qunit-testresult{
|
||||
font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
|
||||
margin:0;
|
||||
font-size: small;
|
||||
color:#2b81af;
|
||||
border-bottom-right-radius:15px;
|
||||
border-bottom-left-radius:15px;
|
||||
-moz-border-radius-bottomright:15px;
|
||||
-moz-border-radius-bottomleft:15px;
|
||||
-webkit-border-bottom-right-radius:15px;
|
||||
-webkit-border-bottom-left-radius:15px;
|
||||
background-color:#D2E0E6;
|
||||
padding:0.5em 0.5em 0.5em 2.5em;
|
||||
}
|
||||
strong b.fail{
|
||||
color:#710909;
|
||||
}
|
||||
strong b.pass{
|
||||
color:#5E740B;
|
||||
}
|
||||
1042
lib/qunit.js
Normal file
1042
lib/qunit.js
Normal file
File diff suppressed because it is too large
Load diff
199
spec/broker.spec.js
Normal file
199
spec/broker.spec.js
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
QUnit.specify("postal.js", function(){
|
||||
describe("broker", function(){
|
||||
describe("When publishing a message to a specific one level topic", function() {
|
||||
describe("with one recipient", function() {
|
||||
var broker = new MessageBroker(),
|
||||
objA = {
|
||||
messageReceived: false
|
||||
};
|
||||
|
||||
broker.subscribe("Test", function() { objA.messageReceived = true; });
|
||||
broker.publish("Test", {});
|
||||
|
||||
it("the subscription callback should be invoked", function(){
|
||||
assert(objA.messageReceived).isTrue();
|
||||
});
|
||||
});
|
||||
describe("with two recipients", function() {
|
||||
var broker = new MessageBroker(),
|
||||
ObjA = function() {
|
||||
this.messageReceived = false;
|
||||
|
||||
broker.subscribe("TwoRecipients", function() {
|
||||
this.messageReceived = true;
|
||||
}.bind(this));
|
||||
},
|
||||
ObjB = function() {
|
||||
this.messageReceived = false;
|
||||
|
||||
broker.subscribe("TwoRecipients", function() {
|
||||
this.messageReceived = true;
|
||||
}.bind(this));
|
||||
};
|
||||
var a = new ObjA(),
|
||||
b = new ObjB();
|
||||
|
||||
broker.publish("TwoRecipients", {});
|
||||
|
||||
it("the subscription callback should be invoked on 'a'", function(){
|
||||
assert(a.messageReceived).isTrue();
|
||||
});
|
||||
|
||||
it("the subscription callback should be invoked on 'b'", function(){
|
||||
assert(b.messageReceived).isTrue();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("When publishing a message to a specific multi-level topic", function() {
|
||||
describe("with one recipient", function() {
|
||||
var broker = new MessageBroker(),
|
||||
objA = {
|
||||
messageReceived: false
|
||||
};
|
||||
|
||||
broker.subscribe("Test.Topic", function() { objA.messageReceived = true; });
|
||||
broker.publish("Test.Topic", {});
|
||||
|
||||
it("the subscription callback should be invoked", function(){
|
||||
assert(objA.messageReceived).isTrue();
|
||||
});
|
||||
});
|
||||
describe("with two recipients", function() {
|
||||
var broker = new MessageBroker(),
|
||||
ObjA = function() {
|
||||
this.messageReceived = false;
|
||||
|
||||
broker.subscribe("TwoRecipients.Listening", function() {
|
||||
this.messageReceived = true;
|
||||
}.bind(this));
|
||||
},
|
||||
ObjB = function() {
|
||||
this.messageReceived = false;
|
||||
|
||||
broker.subscribe("TwoRecipients.Listening", function() {
|
||||
this.messageReceived = true;
|
||||
}.bind(this));
|
||||
};
|
||||
var a = new ObjA(),
|
||||
b = new ObjB();
|
||||
|
||||
broker.publish("TwoRecipients.Listening", {});
|
||||
|
||||
it("the subscription callback should be invoked on 'a'", function(){
|
||||
assert(a.messageReceived).isTrue();
|
||||
});
|
||||
|
||||
it("the subscription callback should be invoked on 'b'", function(){
|
||||
assert(b.messageReceived).isTrue();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("When publishing a wildcard message to a multi-level topic", function() {
|
||||
describe("with one recipient", function() {
|
||||
var broker = new MessageBroker(),
|
||||
objA = {
|
||||
messageReceived: false
|
||||
};
|
||||
|
||||
broker.subscribe("Test.*", function() { objA.messageReceived = true; });
|
||||
broker.publish("Test.Topic", {});
|
||||
|
||||
it("the subscription callback should be invoked", function(){
|
||||
assert(objA.messageReceived).isTrue();
|
||||
});
|
||||
});
|
||||
describe("with two recipients", function() {
|
||||
var broker = new MessageBroker(),
|
||||
ObjA = function() {
|
||||
this.messageReceived = false;
|
||||
|
||||
broker.subscribe("TwoRecipients.Listening.*", function() {
|
||||
this.messageReceived = true;
|
||||
}.bind(this));
|
||||
},
|
||||
ObjB = function() {
|
||||
this.messageReceived = false;
|
||||
|
||||
broker.subscribe("TwoRecipients.*", function() {
|
||||
this.messageReceived = true;
|
||||
}.bind(this));
|
||||
};
|
||||
var a = new ObjA(),
|
||||
b = new ObjB();
|
||||
|
||||
broker.publish("TwoRecipients.Listening.Closely", {});
|
||||
|
||||
it("the subscription callback should be invoked on 'a'", function(){
|
||||
assert(a.messageReceived).isTrue();
|
||||
});
|
||||
|
||||
it("the subscription callback should be invoked on 'b'", function(){
|
||||
assert(b.messageReceived).isTrue();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("When unsubscribing using provided callback", function() {
|
||||
describe("with one callback", function() {
|
||||
var broker = new MessageBroker(),
|
||||
objA = {
|
||||
messageCount: 0
|
||||
};
|
||||
|
||||
var unsubscribe = broker.subscribe("Test.*", function() { objA.messageCount++; });
|
||||
broker.publish("Test.Topic", {});
|
||||
unsubscribe();
|
||||
broker.publish("Test.Topic", {});
|
||||
|
||||
it("the subscription callback should be invoked", function(){
|
||||
assert(objA.messageCount).equals(1);
|
||||
});
|
||||
});
|
||||
describe("with two callbacks", function() {
|
||||
var broker = new MessageBroker(),
|
||||
ObjA = function() {
|
||||
var _unsubscribe;
|
||||
|
||||
this.messageCount = 0;
|
||||
|
||||
this.unsubscribe = function() {
|
||||
if(_unsubscribe) {
|
||||
_unsubscribe();
|
||||
}
|
||||
};
|
||||
|
||||
_unsubscribe = broker.subscribe("TwoRecipients.Listening.*", function() {
|
||||
this.messageCount++;
|
||||
}.bind(this));
|
||||
},
|
||||
ObjB = function() {
|
||||
var _unsubscribe;
|
||||
|
||||
this.messageCount = 0;
|
||||
|
||||
this.unsubscribe = function() {
|
||||
if(_unsubscribe) {
|
||||
_unsubscribe();
|
||||
}
|
||||
};
|
||||
|
||||
_unsubscribe = broker.subscribe("TwoRecipients.*", function() {
|
||||
this.messageCount++;
|
||||
}.bind(this));
|
||||
};
|
||||
var a = new ObjA(),
|
||||
b = new ObjB();
|
||||
broker.publish("TwoRecipients.Listening", {});
|
||||
a.unsubscribe();
|
||||
broker.publish("TwoRecipients.Listening", {});
|
||||
|
||||
it("First object message count should be 1", function(){
|
||||
assert(a.messageCount).equals(1);
|
||||
});
|
||||
|
||||
it("Second object message count should be 2", function(){
|
||||
assert(b.messageCount).equals(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
42
spec/languageExtensions.spec.js
Normal file
42
spec/languageExtensions.spec.js
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
QUnit.specify("postal.js", function(){
|
||||
describe("LanguageExtensions", function(){
|
||||
describe("Object forEach extension", function() {
|
||||
describe("When testing an empty object", function() {
|
||||
var emptyObj = {};
|
||||
var counter = 0;
|
||||
var testClosure = function() {
|
||||
counter++;
|
||||
};
|
||||
|
||||
it("should not invoke the callback when no members exist to iterate over", function(){
|
||||
emptyObj.forEach(testClosure);
|
||||
assert(counter).equals(0);
|
||||
});
|
||||
});
|
||||
describe("When testing an object with one member", function() {
|
||||
var emptyObj = { test: "Test Value"};
|
||||
var counter = 0;
|
||||
var testClosure = function() {
|
||||
counter++;
|
||||
};
|
||||
|
||||
it("should invoke the callback once", function(){
|
||||
emptyObj.forEach(testClosure);
|
||||
assert(counter).equals(1);
|
||||
});
|
||||
});
|
||||
describe("When testing an object with two members", function() {
|
||||
var emptyObj = { test: "Test Value", other: "Moar"};
|
||||
var counter = 0;
|
||||
var testClosure = function() {
|
||||
counter++;
|
||||
};
|
||||
|
||||
it("should invoke the callback twice", function(){
|
||||
emptyObj.forEach(testClosure);
|
||||
assert(counter).equals(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
21
spec/runner.html
Normal file
21
spec/runner.html
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<script type="text/javascript" src="../lib/jquery-1.5.2.js"></script>
|
||||
<script type="text/javascript" src="../lib/qunit.js"></script>
|
||||
<script type="text/javascript" src="../lib/pavlov.js"></script>
|
||||
<script type="text/javascript" src="../src/languageExtensions.js"></script>
|
||||
<script type="text/javascript" src="../src/broker.js"></script>
|
||||
<script type="text/javascript" src="languageExtensions.spec.js"></script>
|
||||
<script type="text/javascript" src="broker.spec.js"></script>
|
||||
<link rel="stylesheet" href="../lib/qunit.css" type="text/css" media="screen" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1 id="qunit-header"></h1>
|
||||
<h2 id="qunit-banner"></h2>
|
||||
<div id="qunit-testrunner-toolbar"></div>
|
||||
<h2 id="qunit-userAgent"></h2>
|
||||
<ol id="qunit-tests"></ol>
|
||||
</body>
|
||||
</html>
|
||||
69
src/broker.js
Normal file
69
src/broker.js
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
var MessageBroker = function() {
|
||||
var subscriptions = {},
|
||||
regexify = function(topic) {
|
||||
if(!this[topic]) {
|
||||
this[topic] = topic.replace(".", "\.").replace("*", ".*");
|
||||
}
|
||||
return this[topic];
|
||||
}.bind(this),
|
||||
isTopicMatch = function(topic, comparison) {
|
||||
if(!this[topic + '_' + comparison]) {
|
||||
this[topic + '_' + comparison] = topic === comparison ||
|
||||
(comparison.indexOf("*") !== -1 && topic.search(regexify(comparison)) !== -1) ||
|
||||
(topic.indexOf("*") !== -1 && comparison.search(regexify(topic)) !== -1);
|
||||
}
|
||||
return this[topic + '_' + comparison];
|
||||
}.bind(this);
|
||||
|
||||
this.subscribe = function(topic, callback) {
|
||||
var topicList = topic.split(/\s/), // we allow multiple topics to be subscribed in one call.
|
||||
subIdx = 0,
|
||||
exists;
|
||||
topicList.forEach(function(topic) {
|
||||
exists = false;
|
||||
if(!subscriptions[topic]) {
|
||||
subscriptions[topic] = [callback];
|
||||
}
|
||||
else {
|
||||
subscriptions[topic].forEach(function(sub) {
|
||||
if(subscriptions[topic][subIdx] === callback) {
|
||||
exists = true;
|
||||
}
|
||||
});
|
||||
if(!exists) {
|
||||
subscriptions[topic].push(callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
// return callback for un-subscribing...
|
||||
return function() {
|
||||
this.unsubscribe(topic, callback);
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
this.publish = function(topic, data) {
|
||||
subscriptions.forEachKeyValue(function(subNm, subs) {
|
||||
if(isTopicMatch(topic, subNm)) {
|
||||
subs.forEach(function(callback) {
|
||||
if(typeof callback === 'function') {
|
||||
callback(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.unsubscribe = function(topic, callback) {
|
||||
if ( !subscriptions[ topic ] ) {
|
||||
return;
|
||||
}
|
||||
var length = subscriptions[ topic ].length,
|
||||
idx = 0;
|
||||
for ( ; idx < length; idx++ ) {
|
||||
if (subscriptions[topic][idx] === callback) {
|
||||
subscriptions[topic].splice( idx, 1 );
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
37
src/languageExtensions.js
Normal file
37
src/languageExtensions.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
if(!Object.prototype.forEach) {
|
||||
Object.prototype.forEach = function (callback) {
|
||||
var self = this;
|
||||
for(var x in self) {
|
||||
if(self.hasOwnProperty(x)) {
|
||||
callback(self[x]);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
if(!Object.prototype.forEachKeyValue) {
|
||||
Object.prototype.forEachKeyValue = function (callback) {
|
||||
var self = this;
|
||||
for(var x in self) {
|
||||
if(self.hasOwnProperty(x)) {
|
||||
callback(x, self[x]);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var isArray = function(value) {
|
||||
var s = typeof value;
|
||||
if (s === 'object') {
|
||||
if (value) {
|
||||
if (typeof value.length === 'number' &&
|
||||
!(value.propertyIsEnumerable('length')) &&
|
||||
typeof value.splice === 'function') {
|
||||
s = 'array';
|
||||
}
|
||||
}
|
||||
}
|
||||
return s === 'array';
|
||||
};
|
||||
|
||||
var slice = [].slice;
|
||||
Loading…
Reference in a new issue