useless
Version:
Use Less. Do More. JavaScript on steroids.
388 lines (276 loc) • 17.9 kB
JavaScript
;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
------------------------------------------------------------------------
Unit tests (bootstrap code)
------------------------------------------------------------------------
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
_.hasAsserts = true
_.extend (_, {
/* a namespace where you put tests (for enumeration purposes)
======================================================================== */
tests: _.tests || {},
/* A degenerate case of a test shell. We use it to bootstrap most critical
useless.js internals, where real shell (Testosterone.js) is not available,
as it itself depends on these utility. It takes test and test's subject as
arguments (test before code, embodying test-driven philosophy) and executes
test immediately, throwing exception if anything fails - which is simply
the default behavior of $assert. So expect no advanced error reporting
and no controlled execution by using this API.
======================================================================== */
withTest: function (name, test, defineSubject) {
defineSubject ()
_.runTest (name, test)
_.publishToTestsNamespace (name, test) },
/* Publishes to _.tests namespace, but does not run
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
deferTest: function (name, test, defineSubject) {
defineSubject ()
_.publishToTestsNamespace (name, test) },
/* INTERNALS (you won't need that)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
runTest: function (name, test) {
try {
if (_.isFunction (test)) { test () }
else { _.each (test, function (fn) { fn () }) } }
catch (e) {
if (_.isAssertionError (e)) { var printedName = ((_.isArray (name) && name) || [name]).join ('.')
console.log (printedName + ':', e.message, '\n' + _.times (printedName.length, _.constant ('~')).join ('') + '\n')
_.each (e.notMatching, function (x) { console.log (' •', x) }) }
throw e } },
publishToTestsNamespace: function (name, test) {
if (_.isArray (name)) { // [suite, name] case
(_.tests[name[0]] || (_.tests[name[0]] = {}))[name[1]] = test }
else {
_.tests[name] = test } } })
/* TEST ITSELF
======================================================================== */
_.withTest ('assert.js bootstrap', function () {
/* One-argument $assert (requires its argument to be strictly 'true')
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
$assert (true)
$assert ( // public front end, may be replaced by environment)
_.assert === // member of _ namespace (original implementation, do not mess with that)
_.assertions.assert) // member of _.assertions (for enumeration purposes)
$assertNot (false)
$assertNot (5) // NB: assertNot means 'assert not true', hence this will pass
/* Multi-argument assert (requires its arguments be strictly equal to each other)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
$assert (2 + 2, 2 * 2, 4) // any number of arguments
$assert ({ foo: [1,2,3] }, { foo: [1,2,3] }) // compares objects (deep match)
$assert ({ foo: { bar: 1 }, baz: 2 }, // ignores order of properties
{ baz: 2, foo: { bar: 1 } })
$assertNot (2 + 2, 5)
/* Nonstrict matching (a wrapup over _.matches)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
$assertMatches ({ foo: 1, bar: 2 },
{ foo: 1 })
/* Nonstrict matching against complex objects (stdlib.js feature)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
if (_.hasStdlib) { $assertMatches ( { foo: [1,2], bar: 3 },
{ foo: [1] }) }
/* Regex matching (stdlib.js feature)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
if (_.hasStdlib) { $assertMatches ('123', /[0-9]+/) }
/* Type matching (plain)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
if (_.hasStdlib) { $assertTypeMatches (42, 'number')
$assertFails (function () {
$assertTypeMatches ('foo', 'number') }) }
/* Type matching (array type)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
if (_.hasStdlib) { $assertTypeMatches ([1,2], [])
$assertTypeMatches ([], [])
$assertTypeMatches ([1,2,3], ['number'])
$assertTypeMatches ([], ['number'])
$assertFails (function () {
$assertTypeMatches ([1,2,3], ['string'])
$assertTypeMatches ([1,2,'foo'], ['number']) }) }
/* Type matching (deep)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
if (_.hasStdlib) { $assertTypeMatches ({
/* Input object */
foo: 42,
bar: {
even: 4,
many: ['foo','bar'] } }, {
/* Type contract */
foo: 'number', // simple type check
qux: 'undefined', // nonexisting match 'undefined'
bar: { // breakdown of complex object
even: function (n) { return (n % 2) === 0 }, // custom contract predicate
many: ['string'] } }) } // array contract (here, 'array of strings')
/* Type matching ($prototype)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
if (_.hasOOP) { var Foo = $prototype (),
Bar = $prototype ()
$assertTypeMatches ({ foo: new Foo (),
bar: new Bar () },
{ foo: Foo,
bar: Bar })
$assertFails (function () {
$assertTypeMatches (new Bar (), Foo) }) };
/* Ensuring throw (and no throw)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
$assertThrows (function () { throw 42 })
$assertNotThrows (function () {})
/* Ensuring throw (strict version)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
$assertThrows ( function () { throw 42 }, 42) // accepts either plain value or predicate
$assertThrows ( function () { throw new Error ('42') }, _.matches ({ message: '42' }))
$assertFails (function () {
$assertThrows ( function () { throw 42 }, 24)
$assertThrows ( function () { throw new Error ('42') }, _.matches ({ message: '24' })) })
/* Ensuring execution
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
$assertEveryCalled (function (a, b, c) { a (); a (); b (); c () })
$assertEveryCalledOnce (function (a, b, c) { a (); b (); c () })
$assertEveryCalled (function (x__3) { x__3 (); x__3 (); x__3 (); })
/*$assertFails (function () {
$assertEveryCalled (function (a, b, c) { a (); b () })
$assertEveryCalledOnce (function (a, b, c) { a (); b (); b (); c (); })
$assertEveryCalled (function (x__3) { x__3 (); x__3 (); }) })*/
/* TODO: 1) add CPS support
2) replace $assertCPS with this
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
if (_.hasStdlib) {
$assertCalledWithArguments ( ['foo',
['foo', 'bar']], function (fn) {
fn ('foo')
fn ('foo', 'bar') }) }
/* Ensuring CPS routine result (DEPRECATED)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
$assertCPS (function (then) { then ('foo', 'bar') }, ['foo', 'bar'])
$assertCPS (function (then) { then ('foo') }, 'foo')
$assertCPS (function (then) { then () })
/* Ensuring assertion failure
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
$assertFails (function () {
$fail // simplest way to generate assertion
$stub // to mark code stubs (throws error)
$assert ('not true') // remember that assert is more strict than JavaScript if clauses
$assert ({ foo: 1, bar: 2 }, { foo: 1 }) // not be confused with _.matches behavior (use $assertMatches for that)
$assert ([1,2,3,4], [1,2,3]) // same for arrays
$assert (['foo'], { 0: 'foo', length: 1 }) // array-like objects not gonna pass (regression test)
$assertFails (function () {}) }) // $assertFails fails if passed code don't
/* Default fail behavior (never depend on that, as it's environment-dependent)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
if ($assert === _.assertions.assert) {
$assertThrows (function () { $fail }) }
/* IMPLEMENTATION
======================================================================== */
}, function () {
var assertImpl = function (positive) {
return function (__) { var args = [].splice.call (arguments, 0)
if (args.length === 1) {
if (positive && (args[0] !== true)) {
_.assertionFailed ({ notMatching: args }) } }
else if (positive && (_.allEqual (args) !== true)) {
_.assertionFailed ({ notMatching: args }) }
return true } }
/* Fix for _.matches semantics (should not be true for _.matches (42) (24))
*/
;(function () {
var _matches = _.matches
_.matches = function (a) { return _.isObject (a) ? _matches (a) : function (b) { return a === b } } }) ();
_.extend (_, _.assertions = {
assert: assertImpl (true),
assertNot: assertImpl (false),
assertCPS: function (fn, args, then) { var requiredResult = (args && (_.isArray (args) ? args : [args])) || []
fn (function () {
$assert ([].splice.call (arguments, 0), requiredResult)
if (then) { then (); return true; } }) },
assertNotCalled: function (context) {
var inContext = true; context (function () { if (inContext) { $fail } }); inContext = false },
assertEveryCalledOnce: function (fn, then) {
return _.assertEveryCalled ($once (fn), then) },
assertEveryCalled: function (fn_, then) { const fn = $untag (fn_),
async = $async.is (fn_),
once = $once.is (fn_)
var match = once ? null : fn.toString ().match (/.*function[^\(]\(([^\)]+)\)/)
var contracts = once ? _.times (fn.length, _.constant (1)) :
_.map (match[1].split (','), function (arg) {
var parts = (arg.trim ().match (/^(.+)__(\d+)$/))
var num = (parts && parseInt (parts[2], 10))
return _.isFinite (num) ? (num || false) : true })
var status = _.times (fn.length, _.constant (false))
var callbacks = _.times (fn.length, function (i) {
return function () {
status[i] =
_.isNumber (contracts[i]) ?
((status[i] || 0) + 1) : true
if (async && _.isEqual (status, contracts))
then () } })
fn.apply (null, callbacks)
if (!async) { _.assert (status, contracts)
if (then) { then () } } },
assertCalledWithArguments: function (argsPattern, generateCalls) {
return _.assert (_.arr (generateCalls), argsPattern) },
assertCallOrder: function (fn) {
var callIndex = 0
var callbacks = _.times (fn.length, i => function callee () { callee.callIndex = callIndex++ })
fn.apply (null, callbacks)
return _.assert (_.pluck (callbacks, 'callIndex'), _.times (callbacks.length, _.identity.arity1)) },
assertMatches: function (value, ...args) {
const pattern = args[0]
try { return _.assert (_.matches.apply (null, args) (value)) }
catch (e) { throw _.isAssertionError (e) ? _.extend (e, { notMatching: [value, pattern] }) : e } },
assertNotMatches: function (value, ...args) {
const pattern = args[0]
try { return _.assert (!_.matches.apply (null, args) (value)) }
catch (e) { throw _.isAssertionError (e) ? _.extend (e, { notMatching: [value, pattern] }) : e } },
assertType: function (value, contract) {
return _.assert (_.decideType (value), contract) },
assertTypeMatches: function (value, contract) { const mismatches = _.typeMismatches (contract, value)
return _.isEmpty (mismatches)
? true
: _.assertionFailed ({
message: 'provided value type not matches required contract',
asColumns: true,
notMatching: [
{ provided: value },
{ required: contract },
{ mismatches: mismatches }] }) },
assertFails: function (what) {
return _.assertThrows.call (this, what, _.isAssertionError) },
assertThrows: function (what, errorPattern) {
var e = undefined, thrown = false
try { what.call (this) }
catch (__) { e = __; thrown = true }
_.assert.call (this, thrown)
if (arguments.length > 1) {
_.assertMatches.call (this, e, errorPattern) } },
assertNotThrows: function (what) {
return _.assertEveryCalled (function (ok) { what (); ok () }) },
fail: function () {
_.assertionFailed () },
fails: _.constant (function () { // higher order version
_.assertionFailed () }),
stub: function () {
_.assertionFailed () } })
/* DEFAULT FAILURE IMPL.
---------------------
We do not subclass Error, because _.isTypeOf currently does not support
inhertitance (UPDATE: now does) and it would cause troubles in test shell
and logging facility. Thus a subclass is defined that way.
======================================================================== */
_.extend (_, {
assertionError: function (additionalInfo) {
return _.extend (new Error (
(additionalInfo && additionalInfo.message) || 'assertion failed'), additionalInfo, { assertion: true }) },
assertionFailed: function (additionalInfo) {
throw _.extend (_.assertionError (additionalInfo), {
stack: (new Error ()).stack.split ('\n').slice (3).join ('\n') }) },
isAssertionError: function (e) {
return e && (e.assertion === true) } })
/* $assert helper
======================================================================== */
_.allEqual = function (values) {
return _.reduce (values, function (prevEqual, x) {
return prevEqual && _.isEqual (values[0], x) }, true) }
/* Publish asserts as $-things (will be replaced by Testosterone.js onwards,
thus configurable=true)
======================================================================== */
_.each (_.keys (_.assertions), function (name) {
var define = ((_[name].length === 0) ? $global.property : $global.const)
define ('$' + name, _[name], { configurable: true }) })
})