Comparing version 3.5.35 to 3.5.36
@@ -53,3 +53,3 @@ (function(self) { | ||
if (e && e.notDefined && logic && logic.$external && typeof logic.$external === 'function') | ||
return logic[ref] = logic[ref] || clues(logic,function() { return logic.$external.call(logic,ref); },$global,caller,(fullref ? fullref+'.' : '')+ref); | ||
return logic[ref] = logic[ref] || clues(logic,function() { return logic.$external.call(logic,ref); },$global,ref,(fullref ? fullref+'.' : '')+ref); | ||
else throw e; | ||
@@ -56,0 +56,0 @@ }); |
{ | ||
"name": "clues", | ||
"version": "3.5.35", | ||
"version": "3.5.36", | ||
"description": "Lightweight logic tree solver using promises.", | ||
@@ -20,8 +20,8 @@ "keywords": [ | ||
"devDependencies": { | ||
"mocha": "~2.5.3" | ||
"tap": "~10.3.2" | ||
}, | ||
"license": "MIT", | ||
"scripts": { | ||
"test": "./node_modules/mocha/bin/mocha" | ||
"test": "tap test --jobs=10 -Rspec --coverage-report=html --no-browser" | ||
} | ||
} |
@@ -1,5 +0,5 @@ | ||
var clues = require('../clues'), | ||
assert = require('assert'); | ||
const clues = require('../clues'); | ||
const t = require('tap'); | ||
var Logic = { | ||
const Logic = { | ||
a : 41, | ||
@@ -9,3 +9,3 @@ b: 1, | ||
return function(b) { | ||
return a+b; | ||
return a + b; | ||
}; | ||
@@ -17,9 +17,4 @@ } | ||
// This test verifies the fix | ||
describe('Inner function arguments',function() { | ||
it('are not affecting function signature',function() { | ||
return clues(Logic,'c') | ||
.then(function(d) { | ||
assert.equal(d,'42'); | ||
}); | ||
}); | ||
t.test('Inner function arguments', async t => { | ||
t.same( await clues(Logic,'c'), 42, 'do not affect function signature'); | ||
}); |
@@ -1,8 +0,7 @@ | ||
var clues = require('../clues'), | ||
assert = require('assert'), | ||
Promise = require('bluebird'); | ||
const clues = require('../clues'); | ||
const Promise = require('bluebird'); | ||
const t = require('tap'); | ||
describe('Array functions',function() { | ||
var logic = { | ||
t.test('array functions', async t => { | ||
const logic = { | ||
M1 : [function() { return Promise.delay(100,10); }], | ||
@@ -31,44 +30,14 @@ M2 : function() { return Promise.delay(20,300); }, | ||
var facts = Object.create(logic); | ||
const facts = Object.create(logic); | ||
it('should resolve to the top',function() { | ||
return clues(facts,'M3') | ||
.then(function(d) { | ||
assert.equal(d,310); | ||
}); | ||
}); | ||
it('should work for individual functions',function() { | ||
return clues(facts,['M4',function(a) { | ||
assert.equal(a,310); | ||
}]); | ||
}); | ||
it('should work for nested structures',function() { | ||
return clues(facts,['nested',function(d) { | ||
assert.equal(d,15); | ||
}]); | ||
}); | ||
it('should not affect regular arrays',function() { | ||
return clues(facts,function(regular_array) { | ||
assert.equal(regular_array,logic.regular_array); | ||
}); | ||
}); | ||
it('should work with partial positional arguments',function() { | ||
return clues(facts, 'partial').then(function(r){ | ||
assert.equal(r, 42); | ||
}); | ||
}); | ||
it('should work recursively',function() { | ||
return clues(facts,'recursive').then(function(d) { | ||
assert.deepEqual(d,[10,[300,310]]); | ||
}); | ||
}); | ||
t.same(await clues(facts,'M3'),310, 'should resolve to the top'); | ||
t.same(await clues(facts,'M4'),310, 'should work for individual functions'); | ||
t.same(await clues(facts,'nested'),15,'should work for nested structures'); | ||
t.same(await clues(facts,'regular_array'),logic.regular_array,'should not affect regular array'); | ||
t.same(await clues(facts,'partial'),42,'partial positional arguments ok'); | ||
t.same(await clues(facts,'recursive'),[10,[300,310]],'should work recursively'); | ||
it('should only execute arrays once', function() { | ||
var counter = 0; | ||
var otherContext = { | ||
t.test('should only execute arrays once', async function(t) { | ||
let counter = 0; | ||
const otherContext = { | ||
M1: Promise.delay(100, 10), | ||
@@ -78,3 +47,3 @@ M2: Promise.delay(200, 20) | ||
var logic = { | ||
const facts = { | ||
M3: [otherContext, 'M1', 'M2', function(a,b) { | ||
@@ -84,13 +53,10 @@ counter++; | ||
}] | ||
} | ||
var facts = Object.create(logic); | ||
}; | ||
return Promise.all([clues(facts,'M3'), clues(facts,'M3')]) | ||
.then(function(results) { | ||
assert.equal(results[0],30); | ||
assert.equal(results[1],30); | ||
assert.equal(counter, 1); | ||
}); | ||
const d = await Promise.all([clues(facts,'M3'), clues(facts,'M3')]); | ||
t.same(d[0],30,'results ok'); | ||
t.same(d[1],30,'results ok'); | ||
t.same(counter,1,'fn only executed once'); | ||
}); | ||
}); | ||
}); |
@@ -1,19 +0,16 @@ | ||
var clues = require('../clues'), | ||
assert = require('assert'), | ||
crypto = require('crypto'); | ||
const clues = require('../clues'); | ||
const crypto = require('crypto'); | ||
const t = require('tap'); | ||
function shouldError() { throw 'Should not run';} | ||
describe('Array fn private scope',function() { | ||
t.test('Array fn private scope', {autoend:true}, t => { | ||
describe('With only array logic',function() { | ||
t.test('With only array logic', async t => { | ||
function privateObj() { | ||
return { | ||
answer : function(forty,two) { | ||
return forty+two; | ||
}, | ||
answer : (forty,two) => forty + two, | ||
forty : 40, | ||
two : 2, | ||
err : function() { | ||
err : () => { | ||
throw 'This is an error'; | ||
@@ -24,3 +21,3 @@ } | ||
var pub = { | ||
const pub = { | ||
M1 : 100, | ||
@@ -30,59 +27,31 @@ M2 : 200 | ||
it('works without array arguments',function() { | ||
var obj = privateObj(); | ||
return clues(null,[obj,function(answer) { | ||
assert.equal(answer,42); | ||
}]); | ||
}); | ||
const obj = privateObj(); | ||
it('works with array arguments',function() { | ||
var obj = privateObj(); | ||
return clues({},[obj,'answer',function(d) { | ||
assert.equal(d,42); | ||
}]); | ||
}); | ||
t.same(await clues(null,[obj,'answer',Number]),42,'works without array arguments'); | ||
t.same(await clues(null,[privateObj,'answer',Number]),42,'works with private scope'); | ||
it('works with private scope defined from function',function() { | ||
return clues({},[privateObj,function(answer) { | ||
assert.equal(answer,42); | ||
}]); | ||
}); | ||
let d1 = await clues({},[privateObj,'err',String]) | ||
.then(shouldError,e => e); | ||
it('handles errors correctly',function() { | ||
return clues({},[privateObj,'err',String]) | ||
.then(function() { console.log(arguments);throw 'Should Error';},function(e) { | ||
assert.equal(e.ref,'err'); | ||
assert.equal(e.fullref,'err'); | ||
assert.equal(e.message,'This is an error'); | ||
}); | ||
}); | ||
t.same(d1.message,'This is an error','handles errors correctly'); | ||
it('works recursively',function() { | ||
return clues({},[Object.create(pub),[['M1',Number],['M2',Number],[privateObj(),'forty',Number],Array],function(d) { | ||
assert.deepEqual(d,[100,200,40]); | ||
}]); | ||
}); | ||
let d2 = await clues(null,[Object.create(pub),[['M1',Number],['M2',Number],[privateObj(),'forty',Number],Array],Object]); | ||
t.same(d2,[100,200,40],'works recursively'); | ||
}); | ||
describe('public/private structure',function() { | ||
t.test('public/private structure', async t => { | ||
var PrivateLogic = { | ||
const PrivateLogic = { | ||
secret : 'Hidden secret', | ||
hash : function(secret,userid) { | ||
return crypto.createHash('sha1') | ||
hash : (secret,userid) => crypto.createHash('sha1') | ||
.update(secret) | ||
.update(userid) | ||
.digest('hex'); | ||
}, | ||
.digest('hex'), | ||
public : function(hash,_userid) { | ||
return { | ||
hash : hash, | ||
userid : _userid, | ||
}; | ||
} | ||
public : (hash,_userid) => ({hash, userid : _userid}) | ||
}; | ||
var Logic = { | ||
const Logic = { | ||
info : function(_userid) { | ||
@@ -97,20 +66,21 @@ return [ | ||
it('provides custom access to private segments',function() { | ||
var obj = Object.create(Logic,{userid: {value:'admin'}}); | ||
t.test('custom access to private segments',t => { | ||
const obj = Object.create(Logic,{userid: {value:'admin'}}); | ||
return clues(obj,function(info) { | ||
assert.equal(info.userid,'admin'); | ||
assert.equal(info.hash,'b21b8fd516dbb99584d651f040d970dba2245b2a'); | ||
assert.equal(obj.secret,undefined); | ||
assert.equal(info.secret,undefined); | ||
t.same(info.userid,'admin','username matches'); | ||
t.same(info.hash,'b21b8fd516dbb99584d651f040d970dba2245b2a','hash matches'); | ||
t.same(obj.secret,undefined,'no obj.secret'); | ||
t.same(info.secret,undefined,'no info.secret'); | ||
}); | ||
}); | ||
it('handles errors correctly',function() { | ||
var obj = Object.create(Logic); | ||
t.test('handles errors correctly',t => { | ||
const obj = Object.create(Logic); | ||
return clues(obj,'info') | ||
.then(shouldError,function(e) { | ||
assert.equal(e.ref,'userid'); | ||
assert.equal(e.message,'userid not defined'); | ||
assert.equal(e.caller,'hash'); | ||
assert.equal(e.fullref,'info.public.hash.userid'); | ||
t.same(e.error,true,'errors'); | ||
t.same(e.ref,'userid','userid as ref'); | ||
t.same(e.message,'userid not defined','right error message'); | ||
t.same(e.caller,'hash','caller is hash'); | ||
t.same(e.fullref,'info.public.hash.userid','fullref'); | ||
}); | ||
@@ -117,0 +87,0 @@ }); |
@@ -1,8 +0,7 @@ | ||
var clues = require('../clues'), | ||
assert = require('assert'), | ||
Promise = require('bluebird'); | ||
const clues = require('../clues'); | ||
const Promise = require('bluebird'); | ||
const t = require('tap'); | ||
describe('Arrow functions',function() { | ||
var logic = { | ||
t.test('Arrow functions', async t => { | ||
const logic = { | ||
M1 : () => Promise.delay(100,10), | ||
@@ -16,18 +15,11 @@ M2 : () => Promise.delay(20,300), | ||
var facts = Object.create(logic); | ||
const facts = Object.create(logic); | ||
const MTOP = await clues(facts,'MTOP'); | ||
it('should resolve to the top',function() { | ||
return clues(facts,'MTOP') | ||
.then(function(d) { | ||
assert.equal(d,380); | ||
}); | ||
}); | ||
it('should update the fact table',function() { | ||
assert.equal(facts.M1.value(),10); | ||
assert.equal(facts.M2.value(),300); | ||
assert.equal(facts.M3.value(),310); | ||
assert.equal(facts.M4.value(),70); | ||
assert.equal(facts.MTOP.value(),380); | ||
}); | ||
t.same(MTOP,380,'resolves tree to the top'); | ||
t.same(facts.M1.value(),10,'fact M1 resolved'); | ||
t.same(facts.M2.value(),300,'fact M2 resolved'); | ||
t.same(facts.M3.value(),310,'fact M3 resolved'); | ||
t.same(facts.M4.value(),70,'fact M4 resolved'); | ||
t.same(facts.MTOP.value(),380,'fact MTOP resolved'); | ||
}); |
@@ -1,17 +0,15 @@ | ||
var clues = require('../clues'), | ||
assert = require('assert'), | ||
Promise = require('bluebird'); | ||
const clues = require('../clues'); | ||
const Promise = require('bluebird'); | ||
const t = require('tap'); | ||
var Logic = { | ||
const facts = { | ||
answer : async function() { | ||
var value = await Promise.delay(100).then(function() { return 41;}); | ||
const value = await Promise.delay(100).then(() => 41); | ||
return value+1; | ||
} | ||
} | ||
}; | ||
describe('Async function',function() { | ||
it('should resolve',async function() { | ||
var answer = await clues(Logic,'answer'); | ||
assert.equal(answer,42); | ||
}) | ||
}); | ||
t.test('Async function', async t => { | ||
const answer = await clues(facts,'answer'); | ||
t.same(answer,42,'resolves to a value'); | ||
}); |
@@ -1,86 +0,41 @@ | ||
var clues = require('../clues'), | ||
assert = require('assert'); | ||
const clues = require('../clues'); | ||
const t = require('tap'); | ||
function obj() { | ||
return { | ||
a : { | ||
b : { | ||
caller : function($caller) { return $caller; }, | ||
fullref : function($fullref) { return $fullref; } | ||
} | ||
}, | ||
call : ['a.b.caller',String], | ||
fullref : ['a.b.fullref',String] | ||
}; | ||
} | ||
const facts = () => ({ | ||
a : { | ||
b : { | ||
caller : function($caller) { return $caller; }, | ||
fullref : function($fullref) { return $fullref; } | ||
} | ||
}, | ||
call : ['a.b.caller',String], | ||
fullref : ['a.b.fullref',String] | ||
}); | ||
describe('$caller',function() { | ||
describe('with fn called directly',function() { | ||
it('is null without provided caller',function() { | ||
return clues(obj,'a.b.caller') | ||
.then(function(caller) { | ||
assert.equal(caller,undefined); | ||
}); | ||
}); | ||
it('shows provided caller',function() { | ||
return clues(obj,'a.b.caller',{},'__user__') | ||
.then(function(caller) { | ||
assert.equal(caller,'__user__'); | ||
}); | ||
}); | ||
t.test('$caller', {autoend:true}, t => { | ||
t.test('with fn called directly', async t => { | ||
t.same(await clues(facts,'a.b.caller'),undefined,'is null without caller'); | ||
t.same(await clues(facts,'a.b.caller',{},'__user__'),'__user__','shows with caller'); | ||
}); | ||
}); | ||
describe('with fn called indirectly',function() { | ||
it('show last fn without provided caller',function() { | ||
return clues(obj,'call') | ||
.then(function(caller) { | ||
assert.equal(caller,'call'); | ||
}); | ||
}); | ||
t.test('with fn called indirectly', async t => { | ||
t.same(await clues(facts,'call'),'call','shows last fn without provided caller'); | ||
t.same(await clues(facts,'call',{},'__user__'),'call','shows last fn even with caller'); | ||
}); | ||
it('shows last fn even with provded caller',function() { | ||
return clues(obj,'call',{},'__user__') | ||
.then(function(caller) { | ||
assert.equal(caller,'call'); | ||
}); | ||
}); | ||
}); | ||
t.test('with $caller override in factsect', async t => { | ||
const o = facts(); | ||
o.a.b.$caller = 'CUSTOM_CALLER'; | ||
t.same(await clues(o,'call',{},'__user__'),'CUSTOM_CALLER','returns override'); | ||
}); | ||
describe('with $caller override in object',function() { | ||
it('returns override',function() { | ||
var o = obj(); | ||
o.a.b.$caller = 'CUSTOM_CALLER'; | ||
return clues(o,'call',{},'__user__') | ||
.then(function(caller) { | ||
assert.equal(caller,'CUSTOM_CALLER'); | ||
}); | ||
}); | ||
}); | ||
t.test('$fullref', async t => { | ||
t.same(await clues(facts,'a.b.fullref'),'a.b.fullref','direct call shows fullref'); | ||
t.same(await clues(facts,'fullref'),'fullref.a.b.fullref','indirect call shows fullref'); | ||
const o = facts(); | ||
o.a.b.$fullref = 'CUSTOM_FULLREF'; | ||
t.same(await clues(o,'fullref',{}),'CUSTOM_FULLREF','$fullref override returns override'); | ||
}); | ||
describe('$fullref',function() { | ||
it('direct call shows fullref',function() { | ||
return clues(obj,'a.b.fullref') | ||
.then(function(fullref) { | ||
assert.equal(fullref,'a.b.fullref'); | ||
}); | ||
}); | ||
it('indirect call shows fullref',function() { | ||
return clues(obj,'fullref') | ||
.then(function(fullref) { | ||
assert.equal(fullref,'fullref.a.b.fullref'); | ||
}); | ||
}); | ||
it('with $fullref override in object returns override',function() { | ||
var o = obj(); | ||
o.a.b.$fullref = 'CUSTOM_FULLREF'; | ||
return clues(o,'fullref',{}) | ||
.then(function(fullref) { | ||
assert.equal(fullref,'CUSTOM_FULLREF'); | ||
}); | ||
}); | ||
}); |
@@ -1,84 +0,63 @@ | ||
var clues = require('../clues'), | ||
Promise = clues.Promise, | ||
assert = require('assert'); | ||
const clues = require('../clues'); | ||
const Promise = require('bluebird'); | ||
const t = require('tap'); | ||
Promise.config({cancellation:true}); | ||
describe('cancellation',function() { | ||
var cancel = {}; | ||
t.test('cancellation', {autoend: true}, async t => { | ||
const cancel = {}; | ||
var logic = { | ||
M1 : function() { | ||
return new Promise(function(resolve,reject,onCancel) { | ||
onCancel(function() { | ||
cancel.M1 = true; | ||
}); | ||
const Logic = { | ||
M1 : () => new Promise(function(resolve,reject,onCancel) { | ||
onCancel(() => cancel.M1 = true); | ||
setTimeout(() => resolve(10),130); | ||
}), | ||
setTimeout(function() { | ||
resolve(10); | ||
},130); | ||
}); | ||
}, | ||
M2 : () => new Promise(function(resolve,reject,onCancel) { | ||
onCancel(() => cancel.M2 = true); | ||
setTimeout(() => resolve(50),170); | ||
}), | ||
M2 : function() { | ||
return new Promise(function(resolve,reject,onCancel) { | ||
onCancel(function() { | ||
cancel.M2 = true; | ||
}); | ||
M3 : () => new Promise(function(resolve,reject,onCancel) { | ||
onCancel(() => cancel.M3 = true); | ||
setTimeout(() => resolve(10)); | ||
}), | ||
setTimeout(function() { | ||
resolve(50); | ||
},170); | ||
}); | ||
}, | ||
M4 : (M1,M2,M3) => M1+M2+M3, | ||
M3 : function() { | ||
return new Promise(function(resolve,reject,onCancel) { | ||
onCancel(function() { | ||
cancel.M3 = true; | ||
}); | ||
MTOP : (M1,M4) => M1+M4 | ||
}; | ||
setTimeout(function() { | ||
resolve(10); | ||
},10); | ||
}); | ||
}, | ||
const facts = Object.create(Logic); | ||
let res; | ||
M4 : function(M1,M2,M3) { | ||
return M1+M2+M3; | ||
}, | ||
let gotResults; | ||
MTOP : function(M1,M4) { | ||
return M1+M4; | ||
} | ||
}; | ||
res = clues(facts,'MTOP') | ||
.then(function() { | ||
gotResults = true; | ||
}); | ||
var facts = Object.create(logic),res; | ||
// Cancelling in 100ms | ||
setTimeout(res.cancel.bind(res),100); | ||
it('should not return results',function() { | ||
var gotResults; | ||
await Promise.delay(200); | ||
res = clues(facts,'MTOP') | ||
.then(function() { | ||
gotResults = true; | ||
}); | ||
// Cancelling in 100ms | ||
setTimeout(res.cancel.bind(res),100); | ||
return Promise.delay(150) | ||
.then(function() { | ||
assert.equal(gotResults,undefined); | ||
}); | ||
t.test('should not return results', t => { | ||
t.same(gotResults,undefined); | ||
t.end(); | ||
}); | ||
it('should trigger the cancel higher up',function() { | ||
assert.equal(cancel.M1,true); | ||
assert.equal(cancel.M2,true); | ||
t.test('Cancellation affects dependencies', t => { | ||
t.same(cancel.M1,true,'M1 cancelled'); | ||
t.same(cancel.M2,true,'M2 cancelled'); | ||
t.end(); | ||
}); | ||
it('should not invalidate results aquired before cancellation',function() { | ||
assert.equal(cancel.M3,undefined); | ||
assert.equal(facts.M3.value(),10); | ||
t.test('Any result resolved before cancellation should not be invalidated',t => { | ||
t.same(cancel.M3,undefined,'M3 not cancelled'); | ||
t.same(facts.M3.value(),10,'M3 resolved to a promise with correct value'); | ||
t.end(); | ||
}); | ||
}); |
@@ -1,8 +0,8 @@ | ||
var clues = require('../clues'), | ||
assert = require('assert'), | ||
Promise = require('bluebird'); | ||
const clues = require('../clues'); | ||
const Promise = require('bluebird'); | ||
const t = require('tap'); | ||
describe('complex tree',function() { | ||
t.test('Arrow functions',async t => { | ||
var logic = { | ||
const Logic = { | ||
M1 : function() { return Promise.delay(100,10); }, | ||
@@ -18,18 +18,11 @@ M2 : function() { return Promise.delay(20,300); }, | ||
var facts = Object.create(logic); | ||
const facts = Object.create(Logic); | ||
const MTOP = await clues(facts,'MTOP'); | ||
it('should resolve to the top',function() { | ||
return clues(facts,'MTOP') | ||
.then(function(d) { | ||
assert.equal(d,380); | ||
}); | ||
}); | ||
it('should update the fact table',function() { | ||
assert.equal(facts.M1.value(),10); | ||
assert.equal(facts.M2.value(),300); | ||
assert.equal(facts.M3.value(),310); | ||
assert.equal(facts.M4.value(),70); | ||
assert.equal(facts.MTOP.value(),380); | ||
}); | ||
t.same(MTOP,380,'resolves tree to the top'); | ||
t.same(facts.M1.value(),10,'fact M1 resolved'); | ||
t.same(facts.M2.value(),300,'fact M2 resolved'); | ||
t.same(facts.M3.value(),310,'fact M3 resolved'); | ||
t.same(facts.M4.value(),70,'fact M4 resolved'); | ||
t.same(facts.MTOP.value(),380,'fact MTOP resolved'); | ||
}); |
@@ -1,160 +0,139 @@ | ||
var clues = require('../clues'), | ||
assert = require('assert'); | ||
const clues = require('../clues'); | ||
const t = require('tap'); | ||
function shouldErr() { throw 'Should throw an error'; } | ||
describe('error',function() { | ||
var c = {a: function(b) { return b;}}; | ||
t.test('error', {autoend: true},t => { | ||
const c = {a: function(b) { return b;}}; | ||
describe('when argument can´t be found',function() { | ||
it('should throw an error',function() { | ||
return clues({},'SOMETHING') | ||
.then(shouldErr,function(e) { | ||
assert.equal(e.ref,'SOMETHING'); | ||
assert.equal(e.message, 'SOMETHING not defined'); | ||
}); | ||
t.test('when argument is not found', async t => { | ||
t.test('direct call',async t => { | ||
const e = await clues({},'SOMETHING').then(shouldErr,Object); | ||
//t.equal(e.error,true,'shows errors'); | ||
t.same(e.message,'SOMETHING not defined','message: not defined'); | ||
t.same(e.ref,'SOMETHING','ref: missing variable'); | ||
}); | ||
it('should show caller if a named logic function',function() { | ||
return clues(c,'a') | ||
.then(shouldErr,function(e) { | ||
assert.equal(e.ref,'b'); | ||
assert.equal(e.message,'b not defined'); | ||
assert.equal(e.caller,'a'); | ||
}); | ||
t.test('caller is logic function', async t => { | ||
const e = await clues(c,'a').catch(Object); | ||
t.same(e.error,true,'errors'); | ||
t.same(e.ref,'b','ref is correct'); | ||
t.same(e.message,'b not defined','message is correct'); | ||
t.same(e.caller,'a','caller is correct'); | ||
}); | ||
}); | ||
describe('thrown',function() { | ||
var logic = { | ||
ERR : function() { throw 'Could not process'; }, | ||
DEP : function(ERR) { return 'Where is the error'; } | ||
}; | ||
var facts = Object.create(logic); | ||
const Logic = { | ||
ERR : function() { throw 'Could not process'; }, | ||
DEP : function(ERR) { return 'Where is the error'; } | ||
}; | ||
describe('directly',function() { | ||
it('should show up as first argument (err)',function() { | ||
return clues(facts,'ERR') | ||
.then(shouldErr,function(e) { | ||
assert.equal(e.ref,'ERR'); | ||
assert.equal(e.message,'Could not process'); | ||
}); | ||
}); | ||
t.test('throw', {autoend:true}, t => { | ||
it ('should update the facts',function() { | ||
var e = facts.ERR.reason(); | ||
assert.equal(e.ref,'ERR'); | ||
assert.equal(e.message,'Could not process'); | ||
}); | ||
t.test('directly', async t => { | ||
const facts = Object.create(Logic); | ||
const e = await clues(facts,'ERR').catch(Object); | ||
const reason = facts.ERR.reason(); | ||
t.equal(e.message,'Could not process','message ok'); | ||
t.same(e.ref,'ERR','ref ok'); | ||
t.same(reason.message,'Could not process','facts register message'); | ||
}); | ||
describe('indirectly',function() { | ||
it('should throw same error for dependent logic',function() { | ||
return clues(facts,'DEP') | ||
.then(shouldErr,function(e) { | ||
assert.equal(e.ref,'ERR'); | ||
assert.equal(e.message,'Could not process'); | ||
}); | ||
}); | ||
t.test('indirectly - dependent fn', async t => { | ||
const facts = Object.create(Logic); | ||
const e = await clues(facts,'DEP').catch(Object); | ||
t.equal(e.message,'Could not process','message ok'); | ||
t.same(e.ref,'ERR','ref ok'); | ||
t.same(e.caller,'DEP','Should reference the first caller'); | ||
}); | ||
}); | ||
it('should contain reference to the first caller',function() { | ||
facts = Object.create(logic); | ||
return clues(facts,'DEP') | ||
.catch(function() { | ||
return clues(facts,'ERR'); | ||
}) | ||
.then(shouldErr,function(e) { | ||
assert.equal(e.caller,'DEP'); | ||
}); | ||
}); | ||
t.test('function named $noThrow', {autoend: true}, t => { | ||
const facts = Object.create(Logic); | ||
t.test('thrown rejection', async t => { | ||
const e = await clues(facts,function $noThrow(DEP) { return DEP;}); | ||
t.same(e.error,true,'Returns an object with the error'); | ||
t.same(e.ref,'ERR','ref ok'); | ||
t.equal(e.message,'Could not process','message ok'); | ||
}); | ||
describe('function named $noThrow',function() { | ||
it('should return the error obj',function() { | ||
return clues(facts,function $noThrow(DEP) { | ||
return DEP; | ||
}) | ||
.then(function(e) { | ||
assert.equal(e.ref,'ERR'); | ||
assert.equal(e.message,'Could not process'); | ||
},function() { | ||
throw 'Should return the error object'; | ||
}); | ||
}); | ||
it('should return the error obj for subsequent fn',function() { | ||
return clues(facts,function $noThrow() { | ||
return ['DEP',Object]; | ||
}) | ||
.then(function(e) { | ||
assert.equal(e.ref,'ERR'); | ||
assert.equal(e.message,'Could not process'); | ||
},function() { | ||
throw 'Should return the error object'; | ||
}); | ||
}); | ||
t.test('error within subsequent fn', async t => { | ||
const fn = function $noThrow() { | ||
return ['DEP',Object]; | ||
}; | ||
const e = await clues(facts,fn); | ||
t.same(e.error,true,'Returns an object with the error'); | ||
t.same(e.ref,'ERR','ref ok'); | ||
t.equal(e.message,'Could not process','message ok'); | ||
}); | ||
}); | ||
describe('$logError',function() { | ||
var logic = { | ||
stack_error: function() { throw new Error('error');}, | ||
stack_error_promise: function() { return clues.Promise.reject(new Error('error'));}, | ||
rejection: function() { throw 'error';}, | ||
rejection_promise: function() { return Promise.reject('error');}, | ||
optional: function(_stack_error,_rejection) { | ||
return 'OK'; | ||
} | ||
}; | ||
t.test('$logError', {autoend: true}, t => { | ||
it('should log an error with stack',function() { | ||
var facts = Object.create(logic),error,fullref; | ||
return clues(facts,'stack_error',{$logError:function(e,f) {error = e;fullref = f;}}) | ||
.catch(Object) | ||
.then(function() { | ||
assert.equal(error && error.message,'error'); | ||
assert.equal(fullref,'stack_error'); | ||
}); | ||
}); | ||
const Global = { | ||
$logError : function(e,f) { | ||
this.error = e; | ||
this.fullref = f; | ||
} | ||
}; | ||
it('should log a rejected promise with stack',function() { | ||
var facts = Object.create(logic),error,fullref; | ||
return clues(facts,'stack_error_promise',{$logError:function(e,f) {error = e; fullref = f;}}) | ||
.catch(Object) | ||
.then(function() { | ||
assert.equal(error && error.message,'error'); | ||
assert.equal(fullref,'stack_error_promise'); | ||
}); | ||
}); | ||
t.test('error with a stack', async t => { | ||
const $global = Object.create(Global); | ||
const facts = { | ||
stack_error: function() { throw new Error('error');} | ||
}; | ||
await clues(facts,'stack_error',$global).then(shouldErr,Object); | ||
t.same($global.error.message,'error'); | ||
t.ok($global.error.stack,'contains a stack'); | ||
t.same($global.error.fullref,'stack_error','fullref ok'); | ||
}); | ||
it('should not log an error with no stack',function() { | ||
var facts = Object.create(logic),error; | ||
return clues(facts,'rejection',{$logError:function(e) {error = e;}}) | ||
.catch(Object) | ||
.then(function() { | ||
assert.equal(error,undefined); | ||
}); | ||
}); | ||
t.test('promise rejected with a stack', async t => { | ||
const $global = Object.create(Global); | ||
const facts = { | ||
stack_error_promise: () => clues.Promise.reject(new Error('error')) | ||
}; | ||
it('should not log a rejection with no stack',function() { | ||
var facts = Object.create(logic),error; | ||
return clues(facts,'rejection',{$logError:function(e) {error = e;}}) | ||
.catch(Object) | ||
.then(function() { | ||
assert.equal(error,undefined); | ||
}); | ||
}); | ||
it('should log an error even if only used optionally',function() { | ||
var facts = Object.create(logic),error; | ||
return clues(facts,'optional',{$logError:function(e) {error = e;}}) | ||
.catch(Object) | ||
.then(function() { | ||
assert.equal(error.message,'error'); | ||
}); | ||
}); | ||
await clues(facts,'stack_error_promise',$global).then(shouldErr,Object); | ||
t.same($global.error.message,'error','error passed to $logError'); | ||
t.ok($global.error.stack,'contains a stack'); | ||
t.same($global.error.fullref,'stack_error_promise','fullref ok'); | ||
}); | ||
t.test('error without a stack (rejection)', async t => { | ||
const $global = Object.create(Global); | ||
const facts = { | ||
rejection: function() { throw 'error';} | ||
}; | ||
await clues(facts,'rejection',$global).then(shouldErr,Object); | ||
t.same($global.error,undefined,'should not $logError'); | ||
}); | ||
t.test('Promise rejection without a stack', async t => { | ||
const $global = Object.create(Global); | ||
const facts = { | ||
rejection_promise: function() { return Promise.reject('error');} | ||
}; | ||
await clues(facts,'rejection_promise',$global).then(shouldErr,Object); | ||
t.same($global.error,undefined,'should not $logError'); | ||
}); | ||
t.test('stack error in an optional dependency', async t => { | ||
const $global = Object.create(Global); | ||
const facts = { | ||
stack_error: () => { throw new Error('error');}, | ||
rejection: () => { throw 'error';}, | ||
optional: (_stack_error,_rejection) => 'OK' | ||
}; | ||
await clues(facts,'optional',$global); | ||
t.same($global.error.message,'error','error passed to $logError'); | ||
t.ok($global.error.stack,'contains a stack'); | ||
t.same($global.error.fullref,'optional.stack_error','fullref ok'); | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -1,143 +0,144 @@ | ||
var clues = require('../clues'), | ||
assert = require('assert'); | ||
const clues = require('../clues'); | ||
const Promise = require('bluebird'); | ||
const t = require('tap'); | ||
describe('$external',function() { | ||
var logic = { | ||
a : 5, | ||
simple : Object.create({ | ||
count : 0, | ||
$external : function(ref) { | ||
this.count+=1; | ||
if (ref == 'STOP') throw {message:'STOP_ERROR',error:true}; | ||
return 'simple:'+ref; | ||
t.test('$external', {autoend:true, jobs: 10}, t => { | ||
t.test('in simple logic with $external', {autoend: true}, t => { | ||
const facts = { | ||
simple: { | ||
count : 0, | ||
$external : function(ref) { | ||
this.count+=1; | ||
if (ref == 'STOP') throw {message:'STOP_ERROR',error:true}; | ||
return 'simple:'+ref; | ||
} | ||
} | ||
}), | ||
shorthand : function $external(ref) { | ||
return 'answer:'+ref; | ||
}, | ||
shorthandThis : function $external() { | ||
return this; | ||
}, | ||
as_argument: function($external) { | ||
return 'answer:'+$external; | ||
}, | ||
as_argument_es6: $external => 'answer:'+$external, | ||
concurrent : function $external() { | ||
return clues.Promise.delay(Math.random()*1000) | ||
.then(function() { | ||
return new Date(); | ||
}); | ||
} | ||
}; | ||
}; | ||
var facts = Object.create(logic); | ||
describe('in simple logic',function() { | ||
it('runs $external function for non-existent property',function() { | ||
return clues(facts,'simple.test') | ||
.then(function(d) { | ||
assert.equal(d,'simple:test'); | ||
assert(facts.simple.test.isFulfilled()); | ||
assert.equal(facts.simple.test.value(),'simple:test'); | ||
assert.equal(facts.simple.count,1); | ||
}); | ||
t.test('asking for non-existent property', async t => { | ||
const d = await clues(facts,'simple.test.1'); | ||
t.same(d,'simple:test.1','returns the $external function value'); | ||
t.ok(facts.simple['test.1'].isFulfilled(),'resolved promise is placed on logic'); | ||
t.same(facts.simple['test.1'].value(),'simple:test.1','promise contains value'); | ||
t.same(facts.simple.count,1,'function is called once'); | ||
}); | ||
it('concurrent requests should return first response',function() { | ||
var requests = Array.apply(null, {length: 10}) | ||
.map(function() { | ||
return clues(facts,'concurrent.test_concurrent'); | ||
}); | ||
return clues.Promise.all(requests) | ||
.then(function(d) { | ||
return d.map(function(e) { | ||
assert.equal(e,d[0],'Return time should be the same'); | ||
}); | ||
}); | ||
t.test('asking for a different property',async t => { | ||
t.same(await clues(facts,'simple.test.2'),'simple:test.2','returns new value'); | ||
t.same(facts.simple.count,2,'$external has now been called twice'); | ||
}); | ||
it('passes the full path as argument',function() { | ||
return clues(facts,'simple.test.a.b.c') | ||
.then(function(d) { | ||
assert.equal(d,'simple:test.a.b.c'); | ||
assert.equal(facts.simple.count,2); | ||
}); | ||
t.test('results so far are cached', async t => { | ||
const d = await Promise.all([ clues(facts,'simple.test.1'), clues(facts,'simple.test.2')]); | ||
t.same(d[0],'simple:test.1','first is cached'); | ||
t.same(d[1],'simple:test.2','second is cached'); | ||
t.equal(facts.simple.count,2,'an $external is not run again'); | ||
}); | ||
it('caches previous results',function() { | ||
return clues(facts,['simple.test','simple.test.a.b.c',function(a,b) { | ||
assert.equal(a,'simple:test'); | ||
assert.equal(b,'simple:test.a.b.c'); | ||
assert.equal(facts.simple.count,2); | ||
}]); | ||
t.test('error raised in $external', async t => { | ||
const e = await clues(facts,'simple.STOP',{},'__test__').catch(Object); | ||
t.same(e.message,'STOP_ERROR','error message ok'); | ||
//t.same(e.ref,'abc','ref is ok'); | ||
t.same(e.fullref,'simple.STOP','fullref is ok'); | ||
t.same(e.caller,'STOP','caller is ok'); | ||
t.equal(facts.simple.STOP.isRejected(),true,'rejected promise in logic'); | ||
t.equal(facts.simple.STOP.reason().message,'STOP_ERROR','rejected promise has error'); | ||
t.equal(facts.simple.count,3,'$external has now been called 3 times'); | ||
}); | ||
}); | ||
it('handles errors correctly',function() { | ||
return clues(facts,'simple.STOP') | ||
.then(function() { | ||
throw 'Should Error'; | ||
},function(e) { | ||
assert.equal(e.error,true); | ||
assert.equal(e.message,'STOP_ERROR'); | ||
assert.equal(facts.simple.count,3); | ||
assert.equal(e.fullref,'simple.STOP'); | ||
}); | ||
t.test('concurrent request', async t => { | ||
const facts = { | ||
concurrent : { | ||
count: 0, | ||
$external : function() { | ||
return clues.Promise.delay(Math.random()*1000) | ||
.then(() => { | ||
this.count += 1; | ||
return new Date(); | ||
}); | ||
} | ||
} | ||
}; | ||
const d = await Promise.map([...Array(10)],() => clues(facts,'concurrent.test.value')); | ||
t.ok( d.every(e => e == d[0]),'should return the same value'); | ||
t.same(facts.concurrent.count,1,'and should not rerun the function'); | ||
}); | ||
t.test('$external inside a function', {autoend: true}, t => { | ||
const facts = { | ||
a : 'ok:', | ||
funct : a => ({ | ||
count : 0, | ||
$external : function(ref) { | ||
this.count+=1; | ||
return a+ref; | ||
} | ||
}) | ||
}; | ||
t.test('asking for non-existent property', async t => { | ||
t.same( await clues(facts,'funct.test.1'), 'ok:test.1', 'returns value'); | ||
t.same(facts.funct.value().count,1,'$external is called once'); | ||
t.same( await clues(facts,'funct.test.1'), 'ok:test.1', 'previous results are cached'); | ||
t.same(facts.funct.value().count,1,'$an fn not run again for same property'); | ||
}); | ||
}); | ||
describe('when function name is $external',function() { | ||
it('acts as a shorthand for empty object with $external',function() { | ||
return clues(facts,'shorthand.first') | ||
.then(function(d) { | ||
assert.equal(d,'answer:first'); | ||
assert.equal(facts.shorthand.value().first.value(),'answer:first'); | ||
return clues(facts,'shorthand.second'); | ||
}) | ||
.then(function(d) { | ||
assert.equal(d,'answer:second'); | ||
assert.equal(facts.shorthand.value().first.value(),'answer:first'); | ||
assert.equal(facts.shorthand.value().second.value(),'answer:second'); | ||
}); | ||
t.test('when function name is $external', {autoend: true}, t => { | ||
const facts = { | ||
a: 'ok:', | ||
shorthand : function $external(ref) { | ||
return this.a+ref; | ||
} | ||
}; | ||
t.test('asking for a property', async t => { | ||
t.same( await clues(facts,'shorthand.test.1'),'ok:test.1','returns right value'); | ||
t.same( facts.shorthand.value()['test.1'].value(),'ok:test.1','stores promise with value'); | ||
}); | ||
it('assumes this of the parent',function() { | ||
return clues(facts,'shorthandThis.test') | ||
.then(function(d) { | ||
assert.equal(d,facts); | ||
}); | ||
t.test('asking for another property', async t => { | ||
t.same( await clues(facts,'shorthand.test.2'),'ok:test.2','returns right value'); | ||
t.same( facts.shorthand.value()['test.2'].value(),'ok:test.2','stores promise with value'); | ||
}); | ||
}); | ||
describe('when argument name is $external',function() { | ||
it('acts as a shorthand for empty object with $external',function() { | ||
return clues(facts,'as_argument.first') | ||
.then(function(d) { | ||
assert.equal(d,'answer:first'); | ||
assert.equal(facts.shorthand.value().first.value(),'answer:first'); | ||
return clues(facts,'as_argument.second'); | ||
}) | ||
.then(function(d) { | ||
assert.equal(d,'answer:second'); | ||
assert.equal(facts.shorthand.value().first.value(),'answer:first'); | ||
assert.equal(facts.shorthand.value().second.value(),'answer:second'); | ||
}); | ||
t.test('when argument name is $external', {autoend: true}, t => { | ||
const facts = { | ||
a: 'ok:', | ||
as_argument: function ($external) { | ||
return this.a + $external; | ||
}, | ||
as_argument_es6 : a => $external => a + $external | ||
}; | ||
t.test('regular function', async t => { | ||
t.test('asking for a property', async t => { | ||
t.same( await clues(facts,'as_argument.test.1'),'ok:test.1','returns right value'); | ||
t.same( facts.as_argument.value()['test.1'].value(),'ok:test.1','stores promise with value'); | ||
}); | ||
t.test('asking for another property', async t => { | ||
t.same( await clues(facts,'as_argument.test.2'),'ok:test.2','returns right value'); | ||
t.same( facts.as_argument.value()['test.2'].value(),'ok:test.2','stores promise with value'); | ||
}); | ||
}); | ||
it('as ES6 acts as a shorthand for empty object with $external',function() { | ||
return clues(facts,'as_argument_es6.first') | ||
.then(function(d) { | ||
assert.equal(d,'answer:first'); | ||
assert.equal(facts.shorthand.value().first.value(),'answer:first'); | ||
return clues(facts,'as_argument_es6.second'); | ||
}) | ||
.then(function(d) { | ||
assert.equal(d,'answer:second'); | ||
assert.equal(facts.shorthand.value().first.value(),'answer:first'); | ||
assert.equal(facts.shorthand.value().second.value(),'answer:second'); | ||
}); | ||
t.test('ES6 fat arrow', async t => { | ||
t.test('asking for a property', async t => { | ||
t.same( await clues(facts,'as_argument_es6.test.1'),'ok:test.1','returns right value'); | ||
t.same( facts.as_argument_es6.value()['test.1'].value(),'ok:test.1','stores promise with value'); | ||
}); | ||
t.test('asking for another property', async t => { | ||
t.same( await clues(facts,'as_argument_es6.test.2'),'ok:test.2','returns right value'); | ||
t.same( facts.as_argument_es6.value()['test.2'].value(),'ok:test.2','stores promise with value'); | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -1,7 +0,7 @@ | ||
var clues = require('../clues'), | ||
assert = require('assert'), | ||
Promise = require('bluebird'); | ||
const clues = require('../clues'); | ||
const Promise = require('bluebird'); | ||
const t = require('tap'); | ||
describe('facts',function() { | ||
var logic = { | ||
t.test('facts',{autoend: true}, t => { | ||
const Logic = { | ||
response : function() { return Promise.delay(500,42);}, | ||
@@ -11,27 +11,22 @@ other : function() { return 5; } | ||
var facts = Object.create(logic); | ||
const facts = Object.create(Logic); | ||
it('should return the solved logic when determined',function() { | ||
var start = new Date(); | ||
return clues(facts,'response') | ||
.then(function(d) { | ||
var wait = (new Date()) - start; | ||
assert.equal(wait >= 500,true,'wait was '+wait); | ||
assert.equal(d,42); | ||
}); | ||
t.test('called first time', async t => { | ||
const start = new Date(); | ||
t.same(await clues(facts,'response'),42,'resolves function when called'); | ||
const wait = new Date() - start; | ||
t.ok( wait >= 500,true, `wait was ${wait}`); | ||
}); | ||
it('should return value immediately when solved for again',function() { | ||
var start = new Date(); | ||
return clues(facts,'response') | ||
.then(function(d) { | ||
var wait = (new Date()) - start; | ||
assert.equal(wait <= 20,true,'wait was '+wait); | ||
assert.equal(d,42); | ||
}); | ||
t.test('called second time', async t => { | ||
const start = new Date(); | ||
t.same(await clues(facts,'response'),42,'resolves function when called'); | ||
const wait = new Date() - start; | ||
t.ok( wait <= 100,true, 'should return immediately'); | ||
}); | ||
it('should not be solved for unrequested logic',function() { | ||
assert(typeof facts.other === 'function'); | ||
t.test('unrequested logic function', {autoend: true}, t => { | ||
t.same(typeof facts.other,'function','should not be resolved'); | ||
}); | ||
}); |
@@ -1,8 +0,10 @@ | ||
var clues = require('../clues'), | ||
assert = require('assert'); | ||
const clues = require('../clues'); | ||
const t = require('tap'); | ||
describe('Frozen logic',function() { | ||
var calls = 0; | ||
const shouldErr = () => { throw 'SHOULD_ERROR'; }; | ||
var Logic = { | ||
t.test('Frozen logic', {autoend: true}, t => { | ||
let calls = 0; | ||
const Logic = { | ||
a : b => b, | ||
@@ -16,36 +18,27 @@ b : () => { | ||
Object.freeze(Logic); | ||
describe('direct access',function() { | ||
it('should not run directly ',function() { | ||
return clues(Logic,'a') | ||
.then(function() { | ||
throw 'SHOULD_ERROR'; | ||
},function(e) { | ||
assert.equal(e.message,'Object immutable'); | ||
assert.equal(e.value,15); | ||
assert.equal(typeof Logic.a,'function'); | ||
}); | ||
}); | ||
t.test('solving property of a frozen object directly', async t => { | ||
const e = await clues(Logic,'a').then(shouldErr,Object); | ||
t.same(e.message,'Object immutable','errors as Object immutable'); | ||
//t.same(calls,0,'does not execute function'); | ||
t.same(typeof Logic.a,'function','does not modify function'); | ||
t.same(typeof Logic.b,'function','does not modify function'); | ||
}); | ||
describe('cloned access',function() { | ||
var facts = Object.create(Logic); | ||
it('should resolve',function() { | ||
return clues(facts,'a') | ||
.then(function(a) { | ||
assert.equal(a,15); | ||
assert.equal(calls,2); | ||
assert.equal(facts.a.value(),15); | ||
}); | ||
t.test('solving on a clone of a frozen object', async t => { | ||
const facts = Object.create(Logic); | ||
t.test('first time', async t => { | ||
const d = await clues(facts,'a'); | ||
t.same(d,15,'correct result'); | ||
t.same(calls,2,'two calls have been made'); | ||
t.same(facts.a.value(),15,'promise is registered'); | ||
}); | ||
it('should memoize',function() { | ||
return clues(facts,'a') | ||
.then(function(a) { | ||
assert.equal(a,15); | ||
assert.equal(calls,2); | ||
assert.equal(facts.a.value(),15); | ||
}); | ||
t.test('second time', async t => { | ||
const d = await clues(facts,'a'); | ||
t.same(d,15,'correct result'); | ||
t.same(calls,2,'two calls have been made'); | ||
t.same(facts.a.value(),15,'promise is registered'); | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -1,6 +0,8 @@ | ||
var clues = require('../clues'), | ||
assert = require('assert'); | ||
const clues = require('../clues'); | ||
const t = require('tap'); | ||
describe('Global variable',function() { | ||
var logic = { | ||
const shouldErr = () => { throw 'Should error'; }; | ||
t.test('Global variable', {autoend: true}, t => { | ||
const Logic = { | ||
a: 123, | ||
@@ -39,5 +41,5 @@ b: { | ||
var facts = Object.create(logic); | ||
const facts = Object.create(Logic); | ||
var global = { | ||
const global = { | ||
$input : { | ||
@@ -51,32 +53,19 @@ test: 10, | ||
it('should be applied where referenced',function() { | ||
return clues(facts,'b.c',global) | ||
.then(function(d) { | ||
assert.equal(d,133); | ||
assert.equal(facts.b.count,1); | ||
}); | ||
t.test('when referenced as a dependency of a function', async t => { | ||
t.same(await clues(facts,'b.c',global), 133, 'uses the global variable'); | ||
t.same(facts.b.count,1,'fn called once'); | ||
}); | ||
it('can not be applied directly',function() { | ||
return clues(facts,'$input',global) | ||
.then(function() { | ||
throw 'Should Error'; | ||
},function(e) { | ||
assert.equal(e.message,'$input not defined'); | ||
}); | ||
t.test('when referenced directly', async t => { | ||
const e = await clues(facts,'$input',global).then(shouldErr,Object); | ||
t.same(e.message,'$input not defined','should error'); | ||
}); | ||
it('can not be applied as part of dot notation',function() { | ||
return clues(facts,'d.c',global) | ||
.then(function(d) { | ||
assert.equal(d,29); | ||
}); | ||
t.test('inside a nested structure', async t => { | ||
t.same(await clues(facts,'d.c',global),29,'uses the global variable'); | ||
}); | ||
it('can be used as object $global',function() { | ||
return clues(facts,'e.f.g',global) | ||
.then(function(d) { | ||
assert.equal(d,4); | ||
}); | ||
t.test('$global object as dependency', async t => { | ||
t.same(await clues(facts,'e.f.g',global), 4, 'uses the $global object'); | ||
}); | ||
}); |
@@ -1,12 +0,13 @@ | ||
var clues = require('../clues'), | ||
inject = require('../util/inject'), | ||
assert = require('assert'); | ||
const clues = require('../clues'); | ||
const Promise = require('bluebird'); | ||
const inject = require('../util/inject'); | ||
const t = require('tap'); | ||
var Db = { | ||
user : function(userid) { | ||
return // database.query('users where usedid = ?',[userid]); | ||
} | ||
const shouldErr = () => { throw 'Should Error'; }; | ||
const Db = { | ||
user : (userid) => undefined // database.query('users where usedid = ?',[userid]); | ||
}; | ||
var Logic = { | ||
const Logic = { | ||
db : function(userid) { | ||
@@ -22,45 +23,47 @@ return Object.create(Db,{ | ||
var injected = function($global) { | ||
return inject(Object.create(Logic), { | ||
'userid': function(original_userid) { | ||
return 'test_'+original_userid; | ||
}, | ||
'db.user' : function(userid) { | ||
return { desc: 'this is an injected response', userid: userid}; | ||
}, | ||
'impossible.to.reach' : 43, | ||
'possible' : {}, | ||
'possible.value': 42, | ||
'never_traversed.value' : 11 | ||
},$global); | ||
}; | ||
const injected = $global => inject(Object.create(Logic), { | ||
'userid': function(original_userid) { | ||
return 'test_'+original_userid; | ||
}, | ||
'db.user' : function(userid) { | ||
return { desc: 'this is an injected response', userid: userid}; | ||
}, | ||
'impossible.to.reach' : 43, | ||
'possible' : {}, | ||
'possible.value': 42, | ||
'never_traversed.value' : 11 | ||
},$global); | ||
describe('inject',function() { | ||
it('modifies the api tree',function() { | ||
return clues(injected,'db.user',{input:{userid:'johndoe'}}) | ||
.then(function(d) { | ||
assert.equal(d.userid,'test_johndoe'); | ||
}); | ||
}); | ||
t.test('inject', {autoend: true}, t => { | ||
t.test('simple example', {autoend: true}, t => { | ||
t.test('modified api tree', async t => { | ||
const d = await clues(injected,'db.user',{input:{userid:'johndoe'}}); | ||
t.same(d.userid,'test_johndoe','username is now available'); | ||
}); | ||
it('allows sequential injections',function() { | ||
return clues(injected,'possible.value',{}) | ||
.then(function(d) { | ||
assert.equal(d,42); | ||
}); | ||
t.test('sequential injection into a nested value', async t => { | ||
const d = await clues(injected,'possible.value',{}); | ||
t.same(d,42,'resolves to injected value'); | ||
}); | ||
t.test('injecting base paths that do not exist', async t => { | ||
const d = await clues(injected,'impossible.to.reach',{}); | ||
t.same(d,43,'resolves to injected value'); | ||
}); | ||
t.test('injected paths that are not traversed', async t => { | ||
const d = await clues({},injected); | ||
t.same(typeof d.never_traversed,'function','are not executed'); | ||
}); | ||
}); | ||
it('creates base paths that do not exists',function() { | ||
return clues(injected,'impossible.to.reach',{}) | ||
.then(function(d) { | ||
assert.equal(d,43); | ||
}); | ||
t.test('injecting without $global', async t => { | ||
const d = await Promise.try(() =>inject(Object.create(Logic),{'possible.value': 42})).then(shouldErr,Object); | ||
t.same(d.message,'$global not supplied to inject','throws'); | ||
}); | ||
it('does not execute injected paths that are not traversed',function() { | ||
return clues({},injected) | ||
.then(function(injected) { | ||
assert(typeof injected.never_traversed === 'function'); | ||
}); | ||
t.test('injecting a non-object', async t => { | ||
const d = await Promise.try(() =>inject(Object.create(Logic),42,{})).then(shouldErr,Object); | ||
t.same(d.message,'Invalid properties passed to inject','throws'); | ||
}); | ||
}); |
@@ -1,5 +0,5 @@ | ||
var clues = require('../clues'), | ||
assert = require('assert'); | ||
const clues = require('../clues'); | ||
const t = require('tap'); | ||
function logic($global) { | ||
function facts($global) { | ||
$global.$constant = 380; | ||
@@ -9,22 +9,11 @@ return { a : { b : function($constant) { return $constant; } } }; | ||
describe('When logic is a function',function() { | ||
it('it is evaluated before solving',function() { | ||
return clues(logic,'a.b') | ||
.then(function(d) { | ||
assert.equal(d,380); | ||
}); | ||
}); | ||
function wrapped() { | ||
return function($global) { | ||
return facts; | ||
}; | ||
} | ||
it('even nested functions are evaluated before solving',function() { | ||
function wrapped() { | ||
return function($global) { | ||
return logic; | ||
}; | ||
} | ||
return clues(wrapped,'a.b') | ||
.then(function(d) { | ||
assert.equal(d,380); | ||
}); | ||
}); | ||
t.test('when logic is a function', async t => { | ||
t.same( await clues(facts,'a.b'),380,'direct call is evaluated before solving'); | ||
t.same( await clues(wrapped,'a.b'),380,'nested functions evaluated'); | ||
}); |
@@ -1,20 +0,14 @@ | ||
var clues = require('../clues'), | ||
assert = require('assert'), | ||
Promise = require('bluebird'); | ||
const clues = require('../clues'); | ||
const Promise = require('bluebird'); | ||
const t = require('tap'); | ||
var logic = Promise.delay(100).then(function() { | ||
const Logic = Promise.delay(100).then(function() { | ||
return { | ||
$constant : 380, | ||
a : function($constant) { return {b: $constant}; } | ||
a : function($constant) { return {b: $constant}; } | ||
}; | ||
}); | ||
describe('When logic is a promise',function() { | ||
it('is used when resolved',function() { | ||
return clues(logic,'a.b') | ||
.then(function(d) { | ||
assert.equal(d,380); | ||
}); | ||
}); | ||
t.test('When logic is a promise', async t => { | ||
t.same( await clues(Logic,'a.b'),380,'logic is resolved before solving'); | ||
}); |
@@ -1,7 +0,7 @@ | ||
var clues = require('../clues'), | ||
assert = require('assert'), | ||
Promise = require('bluebird'); | ||
const clues = require('../clues'); | ||
const Promise = require('bluebird'); | ||
const t = require('tap'); | ||
describe('function name',function() { | ||
var logic = { | ||
t.test('function name', {autoend: true}, t => { | ||
const logic = { | ||
M1 : function() { return Promise.delay(100,10); }, | ||
@@ -12,19 +12,15 @@ M2 : function $hide() { return Promise.delay(20,300); }, | ||
var facts = Object.create(logic); | ||
const facts = Object.create(logic); | ||
describe('before resolution ',function() { | ||
it('is defined in function',function() { | ||
assert.equal(facts.M2.name,'$hide'); | ||
}); | ||
t.test('before resolution', t => { | ||
t.same(facts.M2.name,'$hide','name is a property of function'); | ||
t.end(); | ||
}); | ||
describe('after resolution',function() { | ||
it('is retained in promise',function() { | ||
return clues(facts,'M3') | ||
.then(function() { | ||
assert.equal(facts.M3.value(),310); | ||
assert.equal(facts.M2.name,'$hide'); | ||
}); | ||
}); | ||
t.test('after resolution', {autoend:true}, async t => { | ||
await clues(facts,'M3'); | ||
t.same(facts.M2.name,'$hide','name is a property of promise'); | ||
t.same(facts.M3.value(),310,'and promise resolves to value'); | ||
}); | ||
}); |
@@ -1,54 +0,43 @@ | ||
var clues = require('../clues'), | ||
assert = require('assert'); | ||
const clues = require('../clues'); | ||
const Promise = require('bluebird'); | ||
const t = require('tap'); | ||
function shouldError(e) { throw 'Should error '+e;} | ||
function notDefined(e) { assert.deepEqual(e.message,'test not defined'); } | ||
[null,undefined,4,'a',0,Object,Array,Object].forEach(function(nothing) { | ||
describe('with value '+String(nothing),function() { | ||
describe(' as logic',function() { | ||
t.test('null logic', {autoend: true}, t => { | ||
const values = [null,undefined,4,'a',0,Object,Array,Object]; | ||
it('property not defined',function() { | ||
return clues(nothing,'test') | ||
.then(shouldError,notDefined); | ||
}); | ||
t.test('solving for a variable of null logic', async t => { | ||
await Promise.map(values, async nothing => { | ||
const value = await clues(nothing,'test').then(shouldError,Object); | ||
t.same(value.message,'test not defined',`${nothing}.test is undefined`); | ||
}); | ||
t.end(); | ||
}); | ||
it('path not defined',function() { | ||
return clues(nothing,'test.test') | ||
.then(shouldError,notDefined); | ||
}); | ||
t.test('solving for a nested variable of null logic', async t => { | ||
await Promise.map(values, async nothing => { | ||
const value = await clues(nothing,'test.test').then(shouldError,Object); | ||
t.same(value.message,'test not defined',`${nothing}.test.test is undefined`); | ||
}); | ||
t.end(); | ||
}); | ||
it('context of empty function is empty object',function() { | ||
return clues(nothing,function() { | ||
if (nothing == 'a') | ||
return assert.deepEqual(this,{0:'a'}); | ||
else | ||
assert.deepEqual(this,{}); | ||
}); | ||
}); | ||
it('property fallback to global (if exists)',function() { | ||
return clues(nothing,function(test) { | ||
assert.equal(test,'global'); | ||
},{test:'global'}); | ||
}); | ||
t.test('solving for context of null logic', async t => { | ||
await Promise.map(values, async nothing => { | ||
const context = await clues(nothing,function() { return this;}); | ||
t.same(context, nothing == 'a' ? {0:'a'} : {},`${nothing} has a context of ${JSON.stringify(context)}`); | ||
}); | ||
t.end(); | ||
}); | ||
describe('as a property',function() { | ||
it('should resolve correctly',function() { | ||
var obj = {}; | ||
obj.test = nothing; | ||
return clues(obj,'test') | ||
.then(function(d) { | ||
// functions are always resolved to objects | ||
return assert.deepEqual(d, typeof nothing === 'function' ? nothing() : nothing); | ||
},function(e) { | ||
// we only error if the property is `undefined` | ||
assert.equal(nothing,undefined); | ||
assert.equal(e.message,'test not defined'); | ||
}); | ||
}); | ||
t.test('null logic as a property', async t => { | ||
await Promise.map(values.filter(d => d !== undefined), async nothing => { | ||
const d = await clues({test: nothing},'test'); | ||
const expected = typeof nothing === 'function' ? nothing() : nothing; | ||
t.same(d, expected,`test.${nothing} == ${expected}`); | ||
}); | ||
t.end(); | ||
}); | ||
t.end(); | ||
}); |
// See optimization killers https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#1-tooling | ||
const Promise = require('bluebird'); | ||
const execAsync = Promise.promisify(require('child_process').exec); | ||
const t = require('tap'); | ||
var child_process = require('child_process'); | ||
var statusCodes = { | ||
const statusCodes = { | ||
1: ['OK','Clues Optimized'], | ||
@@ -14,3 +15,3 @@ 2: ['ERR','Clues Not Optimized'], | ||
var code = ` | ||
const code = ` | ||
var clues = require('../clues'); | ||
@@ -29,9 +30,5 @@ | ||
describe('V8 compiler',function() { | ||
it('optimize the clues function',function(cb) { | ||
child_process.exec('echo "'+code+'" | node --allow-natives-syntax',function(err,stdout,stderr) { | ||
var status = statusCodes[stdout]; | ||
cb(err || stderr || (status[0] === 'ERR' && status[1]),status); | ||
}); | ||
}); | ||
t.test('V8 compiler', async t => { | ||
const d = await execAsync('echo "'+code+'" | node --allow-natives-syntax'); | ||
t.same(statusCodes[d][0],'OK'); | ||
}); |
@@ -1,8 +0,10 @@ | ||
var clues = require('../clues'), | ||
assert = require('assert'), | ||
Promise = require('bluebird'); | ||
const clues = require('../clues'); | ||
const Promise = require('bluebird'); | ||
const t = require('tap'); | ||
describe('optional argument',function() { | ||
const shouldErr = () => { throw 'Should error'; }; | ||
var logic = { | ||
t.test('optional argument', {autoend: true}, t => { | ||
const Logic = { | ||
data : function() { return Promise.delay(1000,5); }, | ||
@@ -14,40 +16,22 @@ passthrough : function(_optional) { return _optional; }, | ||
describe('not supplied',function() { | ||
return it('should return undefined',function() { | ||
clues(Object.create(logic),'passthrough') | ||
.then(function(d) { | ||
assert.deepEqual(d,undefined); | ||
}); | ||
}); | ||
const facts = () => Object.create(Logic); | ||
t.test('not supplied', async t => { | ||
t.same(await clues(facts,'passthrough'),undefined,'returns undefined'); | ||
}); | ||
describe('with internal default',function() { | ||
it('should use the internal default',function() { | ||
return clues(Object.create(logic),'internalize') | ||
.then(function(d) { | ||
assert.equal(d,7); | ||
}); | ||
}); | ||
t.test('internal default', async t => { | ||
t.same(await clues(facts,'internalize'),7,'returns the default'); | ||
}); | ||
describe('with a set fact',function() { | ||
it('should return the right value',function() { | ||
return clues(Object.create(logic),'passthrough',{optional:10}) | ||
.then(function(d) { | ||
assert.equal(d,10); | ||
}); | ||
}); | ||
t.test('with a set global', async t => { | ||
t.same(await clues(facts,'passthrough',{optional:10}),10,'returns global'); | ||
}); | ||
describe('with a working function',function() { | ||
it('should return the function results',function() { | ||
return clues(Object.create(logic),'optional_data') | ||
.then(function(d) { | ||
assert.equal(d,5); | ||
}); | ||
}); | ||
t.test('with a working function', async t => { | ||
t.same(await clues(facts,'optional_data'),5,'returns value'); | ||
}); | ||
describe('as an error',function() { | ||
var logic2 = { | ||
t.test('as an error', async t => { | ||
const logic2 = { | ||
error : function() { throw '#Error'; }, | ||
@@ -59,30 +43,19 @@ optional : function(_error) { return _error; }, | ||
it('should return undefined, if optional',function() { | ||
return clues(Object.create(logic2),'optional') | ||
.then(function(d) { | ||
assert.equal(d,undefined); | ||
}); | ||
}); | ||
const facts2 = () => Object.create(logic2); | ||
it('should raise error if non-optonal',function() { | ||
return clues(Object.create(logic2),'regular') | ||
.then(function() { | ||
throw 'Should error'; | ||
},function(e) { | ||
assert.equal(e.message,'#Error'); | ||
}); | ||
}); | ||
let d = await clues(facts2,'optional'); | ||
t.same(d,undefined,'_ => undefined'); | ||
it('should return the error as an object when prefix is two underscores',function() { | ||
return clues(Object.create(logic2),'showerror') | ||
.then(function(e) { | ||
assert.equal(e.error,true); | ||
assert.equal(e.message,'#Error'); | ||
assert.equal(e.fullref,'showerror.error'); | ||
assert.equal(e.ref,'error'); | ||
}); | ||
}); | ||
d = await clues(facts2,'regular').then(shouldErr,Object); | ||
t.same(d.message,'#Error','raises error if not optional'); | ||
d = await clues(facts2,'showerror'); | ||
t.same(d.error,true); | ||
t.same(d.message,'#Error'); | ||
t.same(d.fullref,'showerror.error'); | ||
t.same(d.ref,'error'); | ||
}); | ||
}); | ||
@@ -1,9 +0,9 @@ | ||
var clues = require('../clues'), | ||
assert = require('assert'), | ||
Promise = require('bluebird'); | ||
const clues = require('../clues'); | ||
const Promise = require('bluebird'); | ||
const t = require('tap'); | ||
function shouldErr() { throw 'Should throw an error'; } | ||
describe('private functions',function() { | ||
var logic = { | ||
t.test('private functions', {autoend: true}, t => { | ||
const Logic = { | ||
M1 : function() { return Promise.delay(100,10); }, | ||
@@ -19,46 +19,30 @@ M2 : function $private() { return Promise.delay(20,300); }, | ||
var facts = Object.create(logic); | ||
describe('before resolution ',function() { | ||
it('regular fn not disclosed',function() { | ||
return clues(facts,'M2') | ||
.then(shouldErr,function(e) { | ||
assert.equal(e.message,'M2 not defined'); | ||
assert.equal(facts.M2,logic.M2); | ||
}); | ||
t.test('before resolution', {autoend: true}, t => { | ||
t.test('private regular function', async t => { | ||
const facts = Object.create(Logic); | ||
const e = await clues(facts,'M2').catch(Object); | ||
t.same(e.message,'M2 not defined','errors as not defined'); | ||
t.same(facts.M2,Logic.M2,'fn is not run'); | ||
}); | ||
it('array defined fn not disclosed',function() { | ||
return clues(facts,'M3') | ||
.then(shouldErr,function(e) { | ||
assert.equal(e.message,'M3 not defined'); | ||
assert.equal(facts.M3,logic.M3); | ||
}); | ||
}); | ||
t.test('private array function', async t => { | ||
const facts = Object.create(Logic); | ||
const e = await clues(facts,'M3').catch(Object); | ||
t.same(e.message,'M3 not defined','errors as not defined') | ||
t.same(facts.M3, Logic.M3,'fn is not run'); | ||
}) | ||
}); | ||
describe('after resolution',function() { | ||
it('should be accessible indirectly',function() { | ||
return clues(facts,'MTOP') | ||
.then(function(d) { | ||
assert.equal(d,380); | ||
}); | ||
}); | ||
t.test('after resolution', async t => { | ||
const facts = Object.create(Logic); | ||
const MTOP = await clues(facts,'MTOP'); | ||
const M2 = await clues(facts,'M2').catch(Object); | ||
const M3 = await clues(facts,'M3').catch(Object); | ||
it('regular fn promise not disclosed',function() { | ||
return clues(facts,'M2') | ||
.then(shouldErr,function(e) { | ||
assert.equal(e.message,'M2 not defined'); | ||
assert.equal(facts.M2.value(),300); | ||
}); | ||
}); | ||
it('array defined fn promise not disclosed',function() { | ||
return clues(facts,'M3') | ||
.then(shouldErr,function(e) { | ||
assert.equal(e.message,'M3 not defined'); | ||
assert.equal(facts.M3.value(),310); | ||
}); | ||
}); | ||
t.same(MTOP,380,'is available indirectly'); | ||
t.same(M2.message,'M2 not defined','private fn not available directly'); | ||
t.same(facts.M2.value(),300,'private fn promise has the resolved value'); | ||
t.same(M3.message,'M3 not defined','private array - not defined'); | ||
t.same(facts.M3.value(),310,'private fn promise has the resolved value'); | ||
}); | ||
}); |
@@ -1,7 +0,6 @@ | ||
var clues = require('../clues'), | ||
assert = require('assert'), | ||
Promise = require('bluebird'); | ||
const clues = require('../clues'); | ||
const Promise = require('bluebird'); | ||
const t = require('tap'); | ||
var logic = { | ||
const Logic = { | ||
a : 41, | ||
@@ -18,9 +17,4 @@ test : Promise.resolve(function(a) { | ||
describe('When promise resolve to a function',function() { | ||
it('is used when resolved',function() { | ||
return clues(logic,'test') | ||
.then(function(d) { | ||
assert.equal(d,42); | ||
}); | ||
}); | ||
}); | ||
t.test('when promise resolve to a function', async t => { | ||
t.same(await clues(Logic,'test'),42,'function is evaluated'); | ||
}); |
@@ -1,190 +0,167 @@ | ||
var clues = require('../clues'), | ||
assert = require('assert'); | ||
const clues = require('../clues'); | ||
const Promise = require('bluebird'); | ||
const t = require('tap'); | ||
describe('$property',function() { | ||
var logic = { | ||
a : 5, | ||
simple : Object.create({ | ||
count : 0, | ||
$property : function(ref) { | ||
this.count+=1; | ||
if (isNaN(ref)) throw 'NOT_A_NUMBER'; | ||
return +ref+2; | ||
} | ||
}), | ||
funct : function(a) { | ||
return { | ||
t.test('$property', {autoend:true, jobs: 10}, t => { | ||
t.test('in simple logic with $property', {autoend: true}, t => { | ||
const facts = { | ||
simple: { | ||
count : 0, | ||
$property : function(ref) { | ||
this.count+=1; | ||
return +ref+a; | ||
if (isNaN(ref)) throw 'NOT_A_NUMBER'; | ||
return +ref+2; | ||
} | ||
}; | ||
}, | ||
nested : { | ||
$property : function(ref) { | ||
return { a : { b: { c: function() { return +ref+10; } } } }; | ||
} | ||
}, | ||
shorthand : function $property(ref) { | ||
return +ref * this.a; | ||
}, | ||
as_argument: function ($property) { | ||
return $property * this.a; | ||
}, | ||
as_argument_es6 : a => $property => $property * a, | ||
concurrent : { | ||
$property : function() { | ||
return clues.Promise.delay(Math.random()*1000) | ||
.then(function() { | ||
return new Date(); | ||
}); | ||
}} | ||
}; | ||
}; | ||
var facts = Object.create(logic); | ||
describe('in simple logic',function() { | ||
it('runs $property function for non-existent property',function() { | ||
return clues(facts,'simple.1234') | ||
.then(function(d) { | ||
assert.equal(d,1236); | ||
assert(facts.simple['1234'].isFulfilled()); | ||
assert.equal(facts.simple['1234'].value(),1236); | ||
assert.equal(facts.simple.count,1); | ||
}); | ||
t.test('asking for non-existent property', async t => { | ||
const d = await clues(facts,'simple.1234'); | ||
t.same(d,1236,'returns the $property function value'); | ||
t.ok(facts.simple['1234'].isFulfilled(),'resolved promise is placed on logic'); | ||
t.same(facts.simple['1234'].value(),1236,'promise contains value'); | ||
t.same(facts.simple.count,1,'function is called once'); | ||
}); | ||
it('concurrent requests should return first response',function() { | ||
var requests = Array.apply(null, {length: 10}) | ||
.map(function() { | ||
return clues(facts,'concurrent.test'); | ||
}); | ||
return clues.Promise.all(requests) | ||
.then(function(d) { | ||
return d.map(function(e) { | ||
assert.equal(e,d[0],'Return time should be the same'); | ||
}); | ||
}); | ||
t.test('asking for a different property',async t => { | ||
t.same(await clues(facts,'simple.1235'),1237,'returns new value'); | ||
t.same(facts.simple.count,2,'$property has now been called twice'); | ||
}); | ||
it('runs $property again for a different property',function() { | ||
return clues(facts,'simple.1235') | ||
.then(function(d) { | ||
assert.equal(d,1237); | ||
assert.equal(facts.simple.count,2); | ||
}); | ||
t.test('results so far are cached', async t => { | ||
const d = await Promise.all([ clues(facts,'simple.1234'), clues(facts,'simple.1235')]); | ||
t.same(d[0],1236,'first is cached'); | ||
t.same(d[1],1237,'second is cached'); | ||
t.equal(facts.simple.count,2,'an $property is not run again'); | ||
}); | ||
it('previous results are cached',function() { | ||
return clues(facts,['simple.1234','simple.1235',function(a,b) { | ||
assert.equal(a,1236); | ||
assert.equal(b,1237); | ||
assert.equal(facts.simple.count,2); | ||
}]); | ||
t.test('error raised in $property', async t => { | ||
const e = await clues(facts,'simple.abc',{},'__test__').catch(Object); | ||
t.same(e.message,'NOT_A_NUMBER','error message ok'); | ||
t.same(e.ref,'abc','ref is ok'); | ||
t.same(e.fullref,'simple.abc','fullref is ok'); | ||
t.same(e.caller,'__test__','caller is ok'); | ||
t.equal(facts.simple.abc.isRejected(),true,'rejected promise in logic'); | ||
t.equal(facts.simple.abc.reason().message,'NOT_A_NUMBER','rejected promise has error'); | ||
t.equal(facts.simple.count,3,'$property has now been called 3 times'); | ||
}); | ||
}); | ||
it('handles errors correctly',function() { | ||
return clues(facts,'simple.abc',{},'__test__') | ||
.then(function() { | ||
throw 'Should Error'; | ||
},function(e) { | ||
assert.equal(e.message,'NOT_A_NUMBER'); | ||
assert.equal(e.ref,'abc'); | ||
assert.equal(e.fullref,'simple.abc'); | ||
assert.equal(e.caller,'__test__'); | ||
assert.equal(facts.simple.abc.isRejected(),true); | ||
assert.equal(facts.simple.abc.reason().message,'NOT_A_NUMBER'); | ||
assert.equal(facts.simple.count,3); | ||
}); | ||
t.test('concurrent request', async t => { | ||
const facts = { | ||
concurrent : { | ||
count: 0, | ||
$property : function() { | ||
return clues.Promise.delay(Math.random()*1000) | ||
.then(() => { | ||
this.count += 1; | ||
return new Date(); | ||
}); | ||
} | ||
} | ||
}; | ||
const d = await Promise.map([...Array(10)],() => clues(facts,'concurrent.test')); | ||
t.ok( d.every(e => e == d[0]),'should return the same value'); | ||
t.same(facts.concurrent.count,1,'and should not rerun the function'); | ||
}); | ||
t.test('$property inside a function', {autoend: true}, t => { | ||
const facts = { | ||
a : 5, | ||
funct : a => ({ | ||
count : 0, | ||
$property : function(ref) { | ||
this.count+=1; | ||
return +ref+a; | ||
} | ||
}) | ||
}; | ||
t.test('asking for non-existent property', async t => { | ||
t.same( await clues(facts,'funct.1234'), 1239, 'returns value'); | ||
t.same(facts.funct.value().count,1,'$property is called once'); | ||
t.same( await clues(facts,'funct.1234'), 1239, 'previous results are cached'); | ||
t.same(facts.funct.value().count,1,'$an fn not run again for same property'); | ||
}); | ||
}); | ||
describe('inside a function',function() { | ||
it('runs $property function for non-existent property',function() { | ||
return clues(facts,'funct.1234') | ||
.then(function(d) { | ||
assert.equal(d,1239); | ||
assert.equal(facts.funct.value().count,1); | ||
}); | ||
t.test('$property inside a nested structure', {autoend:true}, t => { | ||
const facts = { | ||
nested : { | ||
$property : function(ref) { | ||
return { a : { b: { c: function() { return +ref+10; } } } }; | ||
} | ||
} | ||
}; | ||
t.test('asking for non existing property nested', async t => { | ||
t.same( await clues(facts,'nested.1234.a.b.c'),1244,'returns correct value'); | ||
t.ok(facts.nested['1234'].value().a.b.c.isFulfilled(),'registers fulfilled promise'); | ||
t.same(facts.nested['1234'].value().a.b.c.value(),1244,'promise registers value'); | ||
}); | ||
it('caches previous results',function() { | ||
return clues(facts,'funct.1234') | ||
.then(function(d) { | ||
assert.equal(d,1239); | ||
assert.equal(facts.funct.value().count,1); | ||
}); | ||
t.test('error raised in $property', async t => { | ||
const e = await clues(facts,'nested.1234.a.b.d',{},'__test__').catch(Object); | ||
t.same(e.ref,'d','ref ok'); | ||
t.same(e.fullref,'nested.1234.a.b.d','fullref ok'); | ||
t.same(e.caller,'__test__','caller ok'); | ||
}); | ||
}); | ||
describe('inside a nested structure',function() { | ||
it('runs $property',function() { | ||
return clues(facts,'nested.1234.a.b.c') | ||
.then(function(d) { | ||
assert.equal(d,1244); | ||
assert(facts.nested['1234'].value().a.b.c.isFulfilled()); | ||
assert(facts.nested['1234'].value(),1244); | ||
}); | ||
t.test('when function name is $property', {autoend: true}, t => { | ||
const facts = { | ||
a: 5, | ||
shorthand : function $property(ref) { | ||
return +ref * this.a; | ||
} | ||
}; | ||
t.test('asking for a property', async t => { | ||
t.same( await clues(facts,'shorthand.2'),10,'returns right value'); | ||
t.same( facts.shorthand.value()['2'].value(),10,'stores promise with value'); | ||
}); | ||
it('handles error correctly',function() { | ||
return clues(facts,'nested.1234.a.b.d',{},'__test__') | ||
.then(function() { | ||
throw 'Should error'; | ||
},function(e) { | ||
assert.equal(e.ref,'d'); | ||
assert.equal(e.fullref,'nested.1234.a.b.d'); | ||
assert.equal(e.caller,'__test__'); | ||
}); | ||
t.test('asking for another property', async t => { | ||
t.same( await clues(facts,'shorthand.4'),20,'returns right value'); | ||
t.same( facts.shorthand.value()['4'].value(),20,'stores promise with value'); | ||
}); | ||
}); | ||
describe('when function name is $property',function() { | ||
it('acts as a shorthand for empty object with $property',function() { | ||
return clues(facts,'shorthand.2') | ||
.then(function(d) { | ||
assert.equal(d,10); | ||
assert.equal(facts.shorthand.value()['2'].value(),10); | ||
return clues(facts,'shorthand.4'); | ||
}) | ||
.then(function(d) { | ||
assert.equal(d,20); | ||
assert.equal(facts.shorthand.value()['4'].value(),20); | ||
assert.equal(facts.shorthand.value()['2'].value(),10); | ||
}); | ||
t.test('when argument name is $property', {autoend: true}, t => { | ||
const facts = { | ||
a: 5, | ||
as_argument: function ($property) { | ||
return $property * this.a; | ||
}, | ||
as_argument_es6 : a => $property => $property * a | ||
}; | ||
t.test('regular function', async t => { | ||
t.test('asking for a property', async t => { | ||
t.same( await clues(facts,'as_argument.2'),10,'returns right value'); | ||
t.same( facts.as_argument.value()['2'].value(),10,'stores promise with value'); | ||
}); | ||
t.test('asking for another property', async t => { | ||
t.same( await clues(facts,'as_argument.4'),20,'returns right value'); | ||
t.same( facts.as_argument.value()['4'].value(),20,'stores promise with value'); | ||
}); | ||
}); | ||
}); | ||
describe('when argument name is $property',function() { | ||
it('acts as a shorthand for empty object with $property',function() { | ||
return clues(facts,'as_argument.2') | ||
.then(function(d) { | ||
assert.equal(d,10); | ||
assert.equal(facts.shorthand.value()['2'].value(),10); | ||
return clues(facts,'as_argument.4'); | ||
}) | ||
.then(function(d) { | ||
assert.equal(d,20); | ||
assert.equal(facts.shorthand.value()['4'].value(),20); | ||
assert.equal(facts.shorthand.value()['2'].value(),10); | ||
}); | ||
t.test('ES6 fat arrow', async t => { | ||
t.test('asking for a property', async t => { | ||
t.same( await clues(facts,'as_argument_es6.2'),10,'returns right value'); | ||
t.same( facts.as_argument_es6.value()['2'].value(),10,'stores promise with value'); | ||
}); | ||
t.test('asking for another property', async t => { | ||
t.same( await clues(facts,'as_argument_es6.4'),20,'returns right value'); | ||
t.same( facts.as_argument_es6.value()['4'].value(),20,'stores promise with value'); | ||
}); | ||
}); | ||
it('as ES6 acts as a shorthand for empty object with $property',function() { | ||
return clues(facts,'as_argument_es6.2') | ||
.then(function(d) { | ||
assert.equal(d,10); | ||
assert.equal(facts.shorthand.value()['2'].value(),10); | ||
return clues(facts,'as_argument_es6.4'); | ||
}) | ||
.then(function(d) { | ||
assert.equal(d,20); | ||
assert.equal(facts.shorthand.value()['4'].value(),20); | ||
assert.equal(facts.shorthand.value()['2'].value(),10); | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -1,7 +0,9 @@ | ||
var clues = require('../clues'), | ||
assert = require('assert'), | ||
Promise = require('bluebird'); | ||
const clues = require('../clues'); | ||
const Promise = require('bluebird'); | ||
const t = require('tap'); | ||
describe('In recursive logic',function() { | ||
var logic = { | ||
const shouldErr = () => { throw 'Should Error'; }; | ||
t.test('In recursive logic',{autoend: true}, t => { | ||
const Logic = { | ||
simple : { | ||
@@ -23,11 +25,11 @@ value : 2, | ||
e : { | ||
f :12, | ||
g : function(f) { | ||
return 2*f; | ||
}, | ||
h : function() { | ||
return function(f) { | ||
return f+2; | ||
}; | ||
} | ||
f :12, | ||
g : function(f) { | ||
return 2*f; | ||
}, | ||
h : function() { | ||
return function(f) { | ||
return f+2; | ||
}; | ||
} | ||
} | ||
@@ -42,96 +44,60 @@ } | ||
var facts = Object.create(logic); | ||
const facts = Object.create(Logic); | ||
it('simple nesting works',function() { | ||
return clues(facts,'simple.value') | ||
.then(function(d) { | ||
assert.equal(d,2); | ||
}); | ||
t.test('simple nesting',async t => { | ||
t.same( await clues(facts,'simple.value'),2,'works'); | ||
}); | ||
t.test('medium nesting works', async t => { | ||
t.same( await clues(facts,'medium.bucket.value'),10,'works'); | ||
}); | ||
it('medium nesting works',function() { | ||
return clues(facts,'medium.bucket.value') | ||
.then(function(value) { | ||
assert.equal(value,10); | ||
}); | ||
t.test('ᐅ works as an alias for dot', async t => { | ||
t.same( await clues(facts,'mediumᐅbucketᐅvalue'), 10, 'works'); | ||
}); | ||
t.test('complex nesting', async t => { | ||
t.same( await clues(facts,'hard.a.b.c.d.id'),102,'works'); | ||
}); | ||
it('ᐅ works as an alias for dot',function() { | ||
return clues(facts,'mediumᐅbucketᐅvalue') | ||
.then(function(value) { | ||
assert.equal(value,10); | ||
}); | ||
t.test('ᐅ as an alias for dot',async t => { | ||
t.same( await clues(facts,'hardᐅaᐅbᐅcᐅdᐅid'),102,'works'); | ||
}); | ||
describe('complex nesting',function() { | ||
it('works', function() { | ||
return clues(facts,'hard.a.b.c.d.id').then(function(value) { | ||
assert.equal(value,102); | ||
}); | ||
}); | ||
t.test('on returned functions',async t => { | ||
t.same( await clues(facts,'hard.a.b.c.d.e.h'),14,'works'); | ||
}); | ||
it('ᐅ works as an alias for dot',function() { | ||
return clues(facts,'hardᐅaᐅbᐅcᐅdᐅid') | ||
.then(function(value) { | ||
assert.equal(value,102); | ||
}); | ||
}); | ||
t.test('works when fact repeats twice', async t => { | ||
const d = await clues(facts,['hard.a.b.c.d.e','hard.a.b.c.d.e.f','hard.a.b.c.d.e.g',Array]); | ||
t.same(d[1],12,'works'); | ||
t.same(d[2],24,'works'); | ||
}); | ||
it('works on returned functions',function() { | ||
return clues(facts,'hard.a.b.c.d.e.h') | ||
.then(function(h) { | ||
assert.equal(h,14); | ||
}); | ||
}); | ||
t.test('supports optional', async t => { | ||
t.same( await clues(facts,['_hard.a.b.c.d.id',Number]),102,'works'); | ||
}); | ||
it('works when clue repeats twice',function() { | ||
return clues(facts,['hard.a.b.c.d.e','hard.a.b.c.d.e.f','hard.a.b.c.d.e.g',function(a,b,c) { | ||
assert.equal(b,12); | ||
assert.equal(c,24); | ||
}]); | ||
}); | ||
it('supports optional',function() { | ||
return clues(facts,['_hard.a.b.c.d.id',function(value) { | ||
assert.equal(value,102); | ||
}]); | ||
}); | ||
it('can be resolved manually',function() { | ||
return clues(facts,function(hard) { | ||
return clues(hard.a.b,function(c) { | ||
return clues(c.d,function(id) { | ||
assert.equal(id,102); | ||
}); | ||
}); | ||
t.test('path traversed manually', async t => { | ||
const d = await clues(facts,hard => { | ||
return clues(hard.a.b,c => { | ||
return clues(c.d,'id'); | ||
}); | ||
}); | ||
t.same(d,102,'works'); | ||
}); | ||
it('bad path returns an error',function() { | ||
return clues(facts,'hard.a.b.c.d.i.oo.oo') | ||
.then(function() { | ||
throw 'We should not arrive here'; | ||
},function(e) { | ||
assert.equal(e.ref, 'i'); | ||
assert.equal(e.fullref, 'hard.a.b.c.d.i'); | ||
}); | ||
}); | ||
t.test('bad path returns an error', async t => { | ||
const e = await clues(facts,'hard.a.b.c.d.i.oo.oo').then(shouldErr,Object); | ||
t.same(e.message,'i not defined','errors'); | ||
t.same(e.ref,'i','ref ok'); | ||
t.same(e.fullref,'hard.a.b.c.d.i','fullref ok'); | ||
}); | ||
it('optional bad path returns undefined',function() { | ||
return clues(facts,['_hard.a.b.e','simple.value',function(a,b) { | ||
assert.equal(a,undefined); | ||
assert.equal(b,2); | ||
}]); | ||
}); | ||
it('optional bad path returns undefined',function() { | ||
return clues(facts,'hard.a.b.e') | ||
.then(function() { | ||
throw 'This function should return an error'; | ||
},function(e) { | ||
assert.equal(e.message,'e not defined'); | ||
}); | ||
}); | ||
t.test('optional bad path returns undefined',async t => { | ||
const d = await clues(facts,['_hard.a.b.e','simple.value',Array]); | ||
t.same(d[0],undefined,'bad path is undefined'); | ||
t.same(d[1],2,'good path returns value'); | ||
}); | ||
}); |
@@ -1,59 +0,34 @@ | ||
var clues = require('../clues'), | ||
assert = require('assert'); | ||
const clues = require('../clues'); | ||
const t = require('tap'); | ||
describe('$ as a first letter',function() { | ||
t.test('$ as a first letter', {autoend:true}, t => { | ||
var logic = { | ||
const Logic = { | ||
a : 10, | ||
b : 11, | ||
$logic_service : function(a) { | ||
return a; | ||
}, | ||
top : function(a) { | ||
return { | ||
$nested_service : function(b) { | ||
return a+b; | ||
} | ||
}; | ||
} | ||
$logic_service : a => a, | ||
top : a => ({ $nested_service : b => a + b }) | ||
}; | ||
var global = { | ||
$global_service : function(b) { | ||
return b; | ||
} | ||
}; | ||
const global = { $global_service : b => b }; | ||
describe('in logic',function() { | ||
it ('should return a function',function() { | ||
return clues(Object.create(logic),'$logic_service',Object.create(global)) | ||
.then(function($logic_service) { | ||
assert.equal(typeof $logic_service,'function'); | ||
assert.equal($logic_service(20),20); | ||
}); | ||
}); | ||
t.test('in logic', async t => { | ||
const d = await clues(Object.create(Logic),'$logic_service',Object.create(global)); | ||
t.same(typeof d, 'function','returns a function'); | ||
t.same(d(20),20,'function works'); | ||
}); | ||
t.test('in nested logic', async t => { | ||
const d = await clues(Object.create(Logic),'top.$nested_service',Object.create(global)); | ||
t.same(typeof d, 'function','returns a function'); | ||
t.same(d(20),30,'function works'); | ||
}); | ||
describe('in nested logic',function() { | ||
it ('should return a function',function() { | ||
return clues(Object.create(logic),'top.$nested_service',Object.create(global)) | ||
.then(function($nested_service) { | ||
assert.equal(typeof $nested_service,'function'); | ||
assert.equal($nested_service(20),30); | ||
}); | ||
}); | ||
t.test('in global', async t => { | ||
await clues(Object.create(Logic),$global_service => { | ||
t.same(typeof $global_service, 'function','returns a function'); | ||
t.same($global_service(20),20,'function works'); | ||
},Object.create(global)); | ||
}); | ||
describe('in global',function() { | ||
it('should return a function',function() { | ||
return clues(Object.create(logic),function($logic_service) { | ||
assert.equal(typeof $logic_service,'function'); | ||
assert.equal($logic_service(20),20); | ||
},Object.create(global)); | ||
}); | ||
}); | ||
}); | ||
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
37
96989
1676