├── .gitignore ├── README.md ├── lib ├── tweetit.js ├── picomodal.js ├── mocha.css ├── underscore-min.js ├── spec.js ├── expect.js └── jquery.js ├── spec-runner.html └── src.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cryptocurrency 2 | Create your own Cryptocurrency! 3 | 4 | http://www.michaelnielsen.org/ddi/how-the-bitcoin-protocol-actually-works/ 5 | -------------------------------------------------------------------------------- /lib/tweetit.js: -------------------------------------------------------------------------------- 1 | window.tweetit = function(){ 2 | 3 | var twitterHtml = { 4 | button: 'You finished!
Tweet', 5 | script: '' 6 | }; 7 | 8 | window.picoModal(twitterHtml.button); 9 | 10 | // twitter's script tag must be wrapped in a setTimeout and separated from 11 | // the above line, or it doesn't work. 12 | setTimeout(function(){ 13 | $('body').append(twitterHtml.script); 14 | }, 0); 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /spec-runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Algorithms Meetup Test Suite [Mocha] 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 37 | 38 | 39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /lib/picomodal.js: -------------------------------------------------------------------------------- 1 | window.picoModal=function(a,b){"use strict";var c=function(){var b=[];return{w:function(a){b.push(a)},t:function(){for(var c=0;c= 0){ 91 | thisClient.unvalidatedTransactions.splice(i, 1); 92 | } 93 | }); 94 | function deleteUsedInputTransactions(transaction){ 95 | transaction.inputs.forEach(function(inputTransaction){ 96 | delete thisClient.unusedValidTransactions[inputTransaction.id]; 97 | }); 98 | } 99 | } 100 | }; 101 | /* 102 | * PLEASE EDIT 103 | * params: null 104 | * returns: Number 105 | * behavior: iterates through unusedValidTransactions, summing the amounts transactions sent to thisClient. 106 | */ 107 | Client.prototype.balance = function(){ 108 | 109 | }; 110 | /* 111 | * PLEASE EDIT 112 | * params: Transaction 113 | * returns: Boolean 114 | * behavior: determines if Transaction's inputs and outputs are valid. 115 | */ 116 | Client.prototype.verify = function(transaction){ 117 | // each input must be valid, unused, and name the sender as a destination 118 | 119 | return isTransactionValid; 120 | }; 121 | /* 122 | * DO NOT EDIT 123 | */ 124 | Client.prototype.validateSolution = function(solution){ 125 | return solution < 0.2; 126 | // 127 | }; 128 | /* 129 | * DO NOT EDIT 130 | */ 131 | Client.prototype.generateRewardTransaction = function(solution, id, amount){ 132 | var txn = new Transaction('coinbase', 'reward'+solution); // same SHA for a given solution 133 | txn.addOutput(id, amount); 134 | return txn; 135 | }; 136 | 137 | /* 138 | * DO NOT EDIT any of the Transaction methods. 139 | */ 140 | function Transaction(sender){ 141 | this.sender = sender; 142 | this.id = 'transfer'+Math.random(); 143 | this.inputs = []; 144 | this.outputs = []; 145 | } 146 | Transaction.prototype.addInput = function(inputTransaction){ //should be valid and unused 147 | this.inputs.push(inputTransaction); 148 | // 149 | }; 150 | Transaction.prototype.addOutput = function(publicKey, amount){ 151 | this.outputs.push({amount:amount, destination:publicKey}); // destination can be thisClient 152 | // 153 | }; 154 | // txn verification helper functions 155 | Transaction.prototype.outputsValid = function(){ 156 | var outputsSum = this.outputs.reduce(function(sum, output){ 157 | return sum += output.amount; 158 | }, 0); 159 | return this.inputsSumToSender(this.sender.id) - outputsSum >= 0; 160 | // todo make === not >= ; difference would be fee to miner 161 | }; 162 | Transaction.prototype.inputsValid = function(unusedValidTransactions){ 163 | var sender = this.sender; 164 | // for each input 165 | return this.inputs.reduce(function(isValid, inputTransaction){ 166 | return isValid 167 | // input transaction is valid and hasn't been used to source another txn yet 168 | && unusedValidTransactions[inputTransaction.id] 169 | // input transactions sent > 0 coins to sender 170 | && inputTransaction.sumToDestination(sender.id) > 0; 171 | }, true); 172 | }; 173 | Transaction.prototype.inputsSumToSender = function(clientId){ 174 | return this.inputs.reduce(function(sum, inputTransaction){ 175 | return sum += inputTransaction.sumToDestination(clientId); 176 | }, 0); 177 | }; 178 | Transaction.prototype.sumToDestination = function(clientId){ 179 | return this.outputs.reduce(function(sum, output){ 180 | return sum += output.destination === clientId ? output.amount : 0; 181 | }, 0); 182 | }; 183 | 184 | // var initialTxn = alice.generateRewardTransaction(0, 'alice', 10); // how does this really happen? 185 | // alice.unusedValidTransactions[initialTxn.id] = initialTxn; 186 | // bob.unusedValidTransactions[initialTxn.id] = initialTxn; 187 | // carl.unusedValidTransactions[initialTxn.id] = initialTxn; 188 | // console.log('alice given initial amount 10 via',initialTxn.id); 189 | 190 | // alice.give('bob', 1); 191 | // alice.give('carl', 2); 192 | // alice.give('alice', 3); 193 | // carl.mine(); 194 | -------------------------------------------------------------------------------- /lib/underscore-min.js: -------------------------------------------------------------------------------- 1 | (function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,d=e.filter,g=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,_=Object.keys,j=i.bind,w=function(n){return n instanceof w?n:this instanceof w?(this._wrapped=n,void 0):new w(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=w),exports._=w):n._=w,w.VERSION="1.4.4";var A=w.each=w.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a in n)if(w.has(n,a)&&t.call(e,n[a],a,n)===r)return};w.map=w.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e[e.length]=t.call(r,n,u,i)}),e)};var O="Reduce of empty array with no initial value";w.reduce=w.foldl=w.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=w.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},w.reduceRight=w.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=w.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=w.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},w.find=w.detect=function(n,t,r){var e;return E(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},w.filter=w.select=function(n,t,r){var e=[];return null==n?e:d&&n.filter===d?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&(e[e.length]=n)}),e)},w.reject=function(n,t,r){return w.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},w.every=w.all=function(n,t,e){t||(t=w.identity);var u=!0;return null==n?u:g&&n.every===g?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var E=w.some=w.any=function(n,t,e){t||(t=w.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};w.contains=w.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:E(n,function(n){return n===t})},w.invoke=function(n,t){var r=o.call(arguments,2),e=w.isFunction(t);return w.map(n,function(n){return(e?t:n[t]).apply(n,r)})},w.pluck=function(n,t){return w.map(n,function(n){return n[t]})},w.where=function(n,t,r){return w.isEmpty(t)?r?null:[]:w[r?"find":"filter"](n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},w.findWhere=function(n,t){return w.where(n,t,!0)},w.max=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.max.apply(Math,n);if(!t&&w.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>=e.computed&&(e={value:n,computed:a})}),e.value},w.min=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.min.apply(Math,n);if(!t&&w.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;e.computed>a&&(e={value:n,computed:a})}),e.value},w.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=w.random(r++),e[r-1]=e[t],e[t]=n}),e};var k=function(n){return w.isFunction(n)?n:function(t){return t[n]}};w.sortBy=function(n,t,r){var e=k(t);return w.pluck(w.map(n,function(n,t,u){return{value:n,index:t,criteria:e.call(r,n,t,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.indexi;){var o=i+a>>>1;u>r.call(e,n[o])?i=o+1:a=o}return i},w.toArray=function(n){return n?w.isArray(n)?o.call(n):n.length===+n.length?w.map(n,w.identity):w.values(n):[]},w.size=function(n){return null==n?0:n.length===+n.length?n.length:w.keys(n).length},w.first=w.head=w.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:o.call(n,0,t)},w.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},w.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},w.rest=w.tail=w.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},w.compact=function(n){return w.filter(n,w.identity)};var R=function(n,t,r){return A(n,function(n){w.isArray(n)?t?a.apply(r,n):R(n,t,r):r.push(n)}),r};w.flatten=function(n,t){return R(n,t,[])},w.without=function(n){return w.difference(n,o.call(arguments,1))},w.uniq=w.unique=function(n,t,r,e){w.isFunction(t)&&(e=r,r=t,t=!1);var u=r?w.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:w.contains(a,r))||(a.push(r),i.push(n[e]))}),i},w.union=function(){return w.uniq(c.apply(e,arguments))},w.intersection=function(n){var t=o.call(arguments,1);return w.filter(w.uniq(n),function(n){return w.every(t,function(t){return w.indexOf(t,n)>=0})})},w.difference=function(n){var t=c.apply(e,o.call(arguments,1));return w.filter(n,function(n){return!w.contains(t,n)})},w.zip=function(){for(var n=o.call(arguments),t=w.max(w.pluck(n,"length")),r=Array(t),e=0;t>e;e++)r[e]=w.pluck(n,""+e);return r},w.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},w.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=w.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},w.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},w.range=function(n,t,r){1>=arguments.length&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=Array(e);e>u;)i[u++]=n,n+=r;return i},w.bind=function(n,t){if(n.bind===j&&j)return j.apply(n,o.call(arguments,1));var r=o.call(arguments,2);return function(){return n.apply(t,r.concat(o.call(arguments)))}},w.partial=function(n){var t=o.call(arguments,1);return function(){return n.apply(this,t.concat(o.call(arguments)))}},w.bindAll=function(n){var t=o.call(arguments,1);return 0===t.length&&(t=w.functions(n)),A(t,function(t){n[t]=w.bind(n[t],n)}),n},w.memoize=function(n,t){var r={};return t||(t=w.identity),function(){var e=t.apply(this,arguments);return w.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},w.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},w.defer=function(n){return w.delay.apply(w,[n,1].concat(o.call(arguments,1)))},w.throttle=function(n,t){var r,e,u,i,a=0,o=function(){a=new Date,u=null,i=n.apply(r,e)};return function(){var c=new Date,l=t-(c-a);return r=this,e=arguments,0>=l?(clearTimeout(u),u=null,a=c,i=n.apply(r,e)):u||(u=setTimeout(o,l)),i}},w.debounce=function(n,t,r){var e,u;return function(){var i=this,a=arguments,o=function(){e=null,r||(u=n.apply(i,a))},c=r&&!e;return clearTimeout(e),e=setTimeout(o,t),c&&(u=n.apply(i,a)),u}},w.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},w.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},w.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},w.after=function(n,t){return 0>=n?t():function(){return 1>--n?t.apply(this,arguments):void 0}},w.keys=_||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)w.has(n,r)&&(t[t.length]=r);return t},w.values=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push(n[r]);return t},w.pairs=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push([r,n[r]]);return t},w.invert=function(n){var t={};for(var r in n)w.has(n,r)&&(t[n[r]]=r);return t},w.functions=w.methods=function(n){var t=[];for(var r in n)w.isFunction(n[r])&&t.push(r);return t.sort()},w.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},w.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},w.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)w.contains(r,u)||(t[u]=n[u]);return t},w.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)null==n[r]&&(n[r]=t[r])}),n},w.clone=function(n){return w.isObject(n)?w.isArray(n)?n.slice():w.extend({},n):n},w.tap=function(n,t){return t(n),n};var I=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof w&&(n=n._wrapped),t instanceof w&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==t+"";case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;r.push(n),e.push(t);var a=0,o=!0;if("[object Array]"==u){if(a=n.length,o=a==t.length)for(;a--&&(o=I(n[a],t[a],r,e)););}else{var c=n.constructor,f=t.constructor;if(c!==f&&!(w.isFunction(c)&&c instanceof c&&w.isFunction(f)&&f instanceof f))return!1;for(var s in n)if(w.has(n,s)&&(a++,!(o=w.has(t,s)&&I(n[s],t[s],r,e))))break;if(o){for(s in t)if(w.has(t,s)&&!a--)break;o=!a}}return r.pop(),e.pop(),o};w.isEqual=function(n,t){return I(n,t,[],[])},w.isEmpty=function(n){if(null==n)return!0;if(w.isArray(n)||w.isString(n))return 0===n.length;for(var t in n)if(w.has(n,t))return!1;return!0},w.isElement=function(n){return!(!n||1!==n.nodeType)},w.isArray=x||function(n){return"[object Array]"==l.call(n)},w.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){w["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),w.isArguments(arguments)||(w.isArguments=function(n){return!(!n||!w.has(n,"callee"))}),"function"!=typeof/./&&(w.isFunction=function(n){return"function"==typeof n}),w.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},w.isNaN=function(n){return w.isNumber(n)&&n!=+n},w.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},w.isNull=function(n){return null===n},w.isUndefined=function(n){return n===void 0},w.has=function(n,t){return f.call(n,t)},w.noConflict=function(){return n._=t,this},w.identity=function(n){return n},w.times=function(n,t,r){for(var e=Array(n),u=0;n>u;u++)e[u]=t.call(r,u);return e},w.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))};var M={escape:{"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"}};M.unescape=w.invert(M.escape);var S={escape:RegExp("["+w.keys(M.escape).join("")+"]","g"),unescape:RegExp("("+w.keys(M.unescape).join("|")+")","g")};w.each(["escape","unescape"],function(n){w[n]=function(t){return null==t?"":(""+t).replace(S[n],function(t){return M[n][t]})}}),w.result=function(n,t){if(null==n)return null;var r=n[t];return w.isFunction(r)?r.call(n):r},w.mixin=function(n){A(w.functions(n),function(t){var r=w[t]=n[t];w.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),D.call(this,r.apply(w,n))}})};var N=0;w.uniqueId=function(n){var t=++N+"";return n?n+t:t},w.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var T=/(.)^/,q={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},B=/\\|'|\r|\n|\t|\u2028|\u2029/g;w.template=function(n,t,r){var e;r=w.defaults({},r,w.templateSettings);var u=RegExp([(r.escape||T).source,(r.interpolate||T).source,(r.evaluate||T).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(B,function(n){return"\\"+q[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,w);var c=function(n){return e.call(this,n,w)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},w.chain=function(n){return w(n).chain()};var D=function(n){return this._chain?w(n).chain():n};w.mixin(w),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];w.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],D.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];w.prototype[n]=function(){return D.call(this,t.apply(this._wrapped,arguments))}}),w.extend(w.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this); -------------------------------------------------------------------------------- /lib/spec.js: -------------------------------------------------------------------------------- 1 | var expect = chai.expect; 2 | 3 | describe('Client', function(){ 4 | context('.balance', function(){ 5 | it('calculates available balance that can be spent', function(){ 6 | var client1 = new Client('client1'); 7 | expect(client1.balance()).to.equal(0); 8 | 9 | var txn = client1.generateRewardTransaction(0, 'client1', 6); 10 | client1.unusedValidTransactions[txn.id] = txn; 11 | 12 | expect(client1.balance()).to.equal(6); 13 | }); 14 | it('only includes amount to self', function(){ 15 | var client1 = new Client('client1'); 16 | 17 | var txn1 = new Transaction('coinbase', 'txn1'); 18 | txn1.addOutput('client1', 1); 19 | txn1.addOutput('other', 2); 20 | client1.unusedValidTransactions[txn1.id] = txn1; 21 | var txn2 = new Transaction('coinbase', 'txn2'); 22 | txn2.addOutput('other', 5); 23 | client1.unusedValidTransactions[txn2.id] = txn2; 24 | 25 | expect(client1.balance()).to.equal(1); 26 | }); 27 | }); 28 | 29 | context('.give', function(){ 30 | it('creates a transaction to give an amount to other clients', function(){ 31 | var client1 = new Client('client1'); 32 | var txn = client1.give('client2', 4); 33 | expect(txn.sender).to.deep.equal(client1); 34 | expect(txn.inputs).to.not.equal(undefined); 35 | expect(txn.outputs).to.not.equal(undefined); 36 | }); 37 | it('uses all transactions that gave this client an amount as inputs to new transaction', function(){ 38 | var client1 = new Client('client1'); 39 | var txn1 = new Transaction('coinbase', 'txn1'); 40 | txn1.addOutput('client1', 5); 41 | client1.unusedValidTransactions[txn1.id] = txn1; 42 | 43 | var txn2 = new Transaction('coinbase', 'txn2'); 44 | txn2.addOutput('other', 4); 45 | client1.unusedValidTransactions[txn2.id] = txn2; 46 | var giftTxn = client1.give('client2', 3); 47 | 48 | expect(giftTxn.inputs).to.deep.equal([txn1]); 49 | expect(giftTxn.inputs).to.not.deep.equal(client1.unusedValidTransactions); 50 | }); 51 | it('lists destination and amount as an output to new transaction', function(){ 52 | var client1 = new Client('client1'); 53 | var txn = client1.give('client2', 4); 54 | expect(txn.outputs[0]).to.deep.equal({amount: 4, destination: 'client2'}); 55 | }); 56 | it('lists self and amount not sent to destination as an output to new transaction', function(){ 57 | var stub = sinon.stub(Client.prototype, 'balance', function(){ return 10; }); 58 | var client1 = new Client('client1'); 59 | 60 | var initialTxn = new Transaction('coinbase', 'initialTxn'); 61 | initialTxn.addOutput('client1', 10); 62 | client1.unusedValidTransactions[initialTxn.id] = initialTxn; 63 | 64 | var txn = client1.give('client2', 4); 65 | expect(txn.outputs[1]).to.deep.equal({amount: 6, destination: 'client1'}); 66 | stub.restore(); 67 | }); 68 | it('broadcasts the new transaction', function(){ 69 | // setup 70 | var spy = sinon.spy(Client.prototype, 'broadcastTransaction'); 71 | // test 72 | var client1 = new Client('client1'); 73 | var txn = client1.give('client2', 4); 74 | expect(spy.calledWith(txn)).to.be.true; 75 | // teardown 76 | Client.prototype.broadcastTransaction.restore(); 77 | }); 78 | }); 79 | 80 | context('.broadcastTransaction', function(){ 81 | it('passes the new transaction to other clients', function(){ 82 | // setup 83 | var spy = sinon.spy(Client.prototype, "onReceivingTransaction"); 84 | var client1 = new Client('client1'); 85 | var clients = [client1]; 86 | var txn = new Transaction('txn'); 87 | // test 88 | client1.broadcastTransaction(txn); 89 | expect(spy.calledWith(txn)).to.be.true; 90 | // teardown 91 | Client.prototype.onReceivingTransaction.restore(); 92 | }); 93 | xit('passes copy, not original new transaction'); 94 | it('passes own id to other clients', function(){ 95 | // setup 96 | var spy = sinon.spy(Client.prototype, "onReceivingTransaction"); 97 | var client1 = new Client('client1'); 98 | var clients = [client1]; 99 | var txn = new Transaction(client1); 100 | // test 101 | client1.broadcastTransaction(txn); 102 | expect(spy.args[0][1]).to.equal(client1.id); 103 | // teardown 104 | Client.prototype.onReceivingTransaction.restore(); 105 | }); 106 | }); 107 | 108 | context('.onReceivingTransaction', function(){ 109 | it('stores new transaction if valid', function(){ 110 | var stub = sinon.stub(Client.prototype, 'verify', function(){ return true; }); 111 | var client1 = new Client('client1'); 112 | var initialTxn = new Transaction('coinbase'); 113 | initialTxn.addOutput('client1', 7); 114 | client1.unusedValidTransactions[initialTxn.id] = initialTxn; 115 | var validTxn = new Transaction(client1); 116 | validTxn.addInput(initialTxn); 117 | validTxn.addOutput('client1', 7); 118 | 119 | client1.onReceivingTransaction(validTxn, 'client1') 120 | expect(client1.unvalidatedTransactions).to.deep.equal([validTxn]); 121 | stub.restore(); 122 | }); 123 | it('does not store new transaction if invalid', function(){ 124 | var stub = sinon.stub(Client.prototype, 'verify', function(){ return false; }); 125 | var invalidTxn = new Transaction('invalid'); 126 | invalidTxn.addOutput('otherClient', 10); 127 | var client1 = new Client('client1'); 128 | client1.onReceivingTransaction(invalidTxn, 'client1') 129 | expect(client1.unvalidatedTransactions).to.deep.equal([]); 130 | stub.restore(); 131 | }); 132 | }); 133 | 134 | context('.verify transaction', function(){ 135 | it('returns true for valid transactions', function(){ 136 | var client1 = new Client('client1'); 137 | var initialTxn = new Transaction('coinbase'); 138 | initialTxn.addOutput('client1', 7); 139 | client1.unusedValidTransactions[initialTxn.id] = initialTxn; 140 | 141 | var validTxn = new Transaction(client1); 142 | validTxn.addInput(initialTxn); 143 | validTxn.addOutput('client1', 7); 144 | expect(client1.verify(validTxn)).to.be.true; 145 | }); 146 | it('returns false for invalid transactions', function(){ 147 | var client1 = new Client('client1'); 148 | var invalidTxn = new Transaction('invalid'); 149 | invalidTxn.addOutput('overspend', 1); 150 | expect(client1.verify(invalidTxn)).to.be.false; 151 | }); 152 | }); 153 | 154 | context('.mine finds and broadcasts a solution to the proof-of-work problem', function(){ 155 | it('solves the proof-of-work problem', function(){ 156 | var client1 = new Client('client1'); 157 | var solution = client1.mine(); 158 | expect(client1.validateSolution(solution)).to.be.true; 159 | }); 160 | it('broadcasts proof-of-work solution when found', function(){ 161 | // setup 162 | var realFunc = Client.prototype.broadcastSolution; 163 | var calledWithArguments; 164 | Client.prototype.broadcastSolution = function(){ 165 | calledWithArguments = arguments; 166 | }; 167 | // test 168 | var client1 = new Client('client1'); 169 | var txns = ['some','unvalidated','transactions']; 170 | client1.unvalidatedTransactions = txns; 171 | var solution = client1.mine(); 172 | expect(calledWithArguments[0]).to.equal(solution); 173 | expect(calledWithArguments[1]).to.equal(txns); 174 | // teardown 175 | Client.prototype.broadcastSolution = realFunc; 176 | }); 177 | }); 178 | 179 | context('.broadcastSolution broadcasts a solution and a list of transactions', function(){ 180 | it('broadcasts solution and unvalidated transactions to clients', function(){ 181 | // setup 182 | var realFunc = Client.prototype.onReceivingSolution; 183 | var calledWithArguments; 184 | Client.prototype.onReceivingSolution = function(){ 185 | calledWithArguments = arguments; 186 | }; 187 | // test 188 | var client1 = new Client('client1'); 189 | var txns = ['some','unvalidated','transactions']; 190 | client1.broadcastSolution(0.15, txns) 191 | expect(calledWithArguments[0]).to.equal(0.15); 192 | expect(calledWithArguments[1]).to.deep.equal(txns); 193 | // teardown 194 | Client.prototype.onReceivingSolution = realFunc; 195 | }); 196 | it('sends a copy of the transactions, not the originals', function(){ 197 | // setup 198 | var realFunc = Client.prototype.onReceivingSolution; 199 | var calledWithArguments; 200 | Client.prototype.onReceivingSolution = function(){ 201 | calledWithArguments = arguments; 202 | }; 203 | // test 204 | var client1 = new Client('client1'); 205 | var txns = ['original','unvalidated','transactions']; 206 | client1.broadcastSolution(0.15, txns) 207 | txns = ['original','unvalidated','transactions']; 208 | expect(calledWithArguments[1]).to.deep.equal(['original','unvalidated','transactions']); 209 | // teardown 210 | Client.prototype.onReceivingSolution = realFunc; 211 | }); 212 | }); 213 | 214 | context('.onReceivingSolution validates solution, transactions, and updates blockchain', function(){ 215 | it('.validateSolution verifies received proof-of-work', function(){ 216 | var validSolution = 0.1; 217 | var invalidSolution = 0.9; 218 | var client1 = new Client('client1'); 219 | expect(client1.validateSolution(validSolution)).to.be.true; 220 | expect(client1.validateSolution(invalidSolution)).to.be.false; 221 | }); 222 | it('generates and records a reward for the solver', function(){ 223 | // setup 224 | var realFunc = Client.prototype.generateRewardTransaction; 225 | var rewardTxn; 226 | Client.prototype.generateRewardTransaction = function(soln,id,amt){ 227 | rewardTxn = realFunc(soln, id, amt); 228 | return rewardTxn; 229 | }; 230 | // test 231 | var client1 = new Client('client1'); 232 | client1.onReceivingSolution(0.1, [], client1.id); //todo allow mining on zero transactions? 233 | expect(rewardTxn.sender).to.equal('coinbase'); 234 | expect(rewardTxn.outputs[0].amount).to.be.above(0); 235 | expect(rewardTxn.outputs[0].destination).to.equal(client1.id); 236 | // teardown 237 | Client.prototype.generateRewardTransaction = realFunc; 238 | }); 239 | it('updates unusedValid & unvalidated transactions lists', function(){ 240 | // setup 241 | var stubVerify = sinon.stub(Client.prototype, 'verify', function(){ return true; }); 242 | var realFunc = Client.prototype.generateRewardTransaction; 243 | var rewardTxn; 244 | Client.prototype.generateRewardTransaction = function(soln,id,amt){ 245 | rewardTxn = realFunc(soln, id, amt); 246 | return rewardTxn; 247 | }; 248 | var client1 = new Client('client1'); 249 | var initialTxn = new Transaction('coinbase'); 250 | var unvalidatedTxn = new Transaction(client1); 251 | client1.unusedValidTransactions[initialTxn.id] = initialTxn; 252 | client1.unvalidatedTransactions = [unvalidatedTxn]; 253 | // test 254 | expect(client1.unusedValidTransactions[initialTxn.id]).to.deep.equal(initialTxn); 255 | expect(client1.unvalidatedTransactions).to.deep.equal([unvalidatedTxn]); 256 | client1.onReceivingSolution(0.1, [unvalidatedTxn], client1.id); 257 | expect(client1.unusedValidTransactions[rewardTxn.id]).to.deep.equal(rewardTxn); 258 | expect(client1.unvalidatedTransactions).to.deep.equal([]); 259 | // teardown 260 | Client.prototype.generateRewardTransaction = realFunc; 261 | stubVerify.restore(); 262 | }); 263 | it('does not update transactions lists if solution is invalid', function(){ 264 | var client1 = new Client('client1'); 265 | var unvalidatedTxn = new Transaction(client1); 266 | client1.unvalidatedTransactions = [unvalidatedTxn]; 267 | 268 | client1.onReceivingSolution('bad solution', [unvalidatedTxn], client1.id); 269 | expect(client1.unusedValidTransactions).to.deep.equal({}); 270 | expect(client1.unvalidatedTransactions).to.deep.equal([unvalidatedTxn]); 271 | }); 272 | it('does not update transactions lists if transactions are invalid', function(){ 273 | var client1 = new Client('client1'); 274 | var unvalidatedTxn = new Transaction(client1); 275 | unvalidatedTxn.addOutput('client1', 1); 276 | client1.unvalidatedTransactions = [unvalidatedTxn]; 277 | 278 | client1.onReceivingSolution(0.1, [unvalidatedTxn], client1.id); 279 | expect(client1.unusedValidTransactions).to.deep.equal({}); 280 | expect(client1.unvalidatedTransactions).to.deep.equal([unvalidatedTxn]); 281 | }); 282 | }); 283 | 284 | xcontext('Client scenarios', function(){ 285 | it('can give an amount to another client', function(){expect(false).to.be.true;}); 286 | it('cannot double-spend', function(){expect(false).to.be.true;}); 287 | it('handles receiving concurrent solutions', function(){expect(false).to.be.true;}); 288 | it('validates another proof-of-work solution', function(){expect(false).to.be.true;}); 289 | }); 290 | }); 291 | 292 | xdescribe('Transaction', function(){ 293 | it('uses previous transactions as inputs', function(){expect(false).to.be.true;}); 294 | it('has a list of destination/amounts as outputs', function(){expect(false).to.be.true;}); 295 | it('has a unique id', function(){expect(false).to.be.true;}); 296 | it('knows who created the transaction', function(){expect(false).to.be.true;}); 297 | }); 298 | -------------------------------------------------------------------------------- /lib/expect.js: -------------------------------------------------------------------------------- 1 | 2 | (function (global, module) { 3 | 4 | if ('undefined' == typeof module) { 5 | var module = { exports: {} } 6 | , exports = module.exports 7 | } 8 | 9 | /** 10 | * Exports. 11 | */ 12 | 13 | module.exports = expect; 14 | expect.Assertion = Assertion; 15 | 16 | /** 17 | * Exports version. 18 | */ 19 | 20 | expect.version = '0.1.2'; 21 | 22 | /** 23 | * Possible assertion flags. 24 | */ 25 | 26 | var flags = { 27 | not: ['to', 'be', 'have', 'include', 'only'] 28 | , to: ['be', 'have', 'include', 'only', 'not'] 29 | , only: ['have'] 30 | , have: ['own'] 31 | , be: ['an'] 32 | }; 33 | 34 | function expect (obj) { 35 | return new Assertion(obj); 36 | } 37 | 38 | /** 39 | * Constructor 40 | * 41 | * @api private 42 | */ 43 | 44 | function Assertion (obj, flag, parent) { 45 | this.obj = obj; 46 | this.flags = {}; 47 | 48 | if (undefined != parent) { 49 | this.flags[flag] = true; 50 | 51 | for (var i in parent.flags) { 52 | if (parent.flags.hasOwnProperty(i)) { 53 | this.flags[i] = true; 54 | } 55 | } 56 | } 57 | 58 | var $flags = flag ? flags[flag] : keys(flags) 59 | , self = this 60 | 61 | if ($flags) { 62 | for (var i = 0, l = $flags.length; i < l; i++) { 63 | // avoid recursion 64 | if (this.flags[$flags[i]]) continue; 65 | 66 | var name = $flags[i] 67 | , assertion = new Assertion(this.obj, name, this) 68 | 69 | if ('function' == typeof Assertion.prototype[name]) { 70 | // clone the function, make sure we dont touch the prot reference 71 | var old = this[name]; 72 | this[name] = function () { 73 | return old.apply(self, arguments); 74 | } 75 | 76 | for (var fn in Assertion.prototype) { 77 | if (Assertion.prototype.hasOwnProperty(fn) && fn != name) { 78 | this[name][fn] = bind(assertion[fn], assertion); 79 | } 80 | } 81 | } else { 82 | this[name] = assertion; 83 | } 84 | } 85 | } 86 | }; 87 | 88 | /** 89 | * Performs an assertion 90 | * 91 | * @api private 92 | */ 93 | 94 | Assertion.prototype.assert = function (truth, msg, error) { 95 | var msg = this.flags.not ? error : msg 96 | , ok = this.flags.not ? !truth : truth; 97 | 98 | if (!ok) { 99 | throw new Error(msg.call(this)); 100 | } 101 | 102 | this.and = new Assertion(this.obj); 103 | }; 104 | 105 | /** 106 | * Check if the value is truthy 107 | * 108 | * @api public 109 | */ 110 | 111 | Assertion.prototype.ok = function () { 112 | this.assert( 113 | !!this.obj 114 | , function(){ return 'expected ' + i(this.obj) + ' to be truthy' } 115 | , function(){ return 'expected ' + i(this.obj) + ' to be falsy' }); 116 | }; 117 | 118 | /** 119 | * Assert that the function throws. 120 | * 121 | * @param {Function|RegExp} callback, or regexp to match error string against 122 | * @api public 123 | */ 124 | 125 | Assertion.prototype.throwError = 126 | Assertion.prototype.throwException = function (fn) { 127 | expect(this.obj).to.be.a('function'); 128 | 129 | var thrown = false 130 | , not = this.flags.not 131 | 132 | try { 133 | this.obj(); 134 | } catch (e) { 135 | if ('function' == typeof fn) { 136 | fn(e); 137 | } else if ('object' == typeof fn) { 138 | var subject = 'string' == typeof e ? e : e.message; 139 | if (not) { 140 | expect(subject).to.not.match(fn); 141 | } else { 142 | expect(subject).to.match(fn); 143 | } 144 | } 145 | thrown = true; 146 | } 147 | 148 | if ('object' == typeof fn && not) { 149 | // in the presence of a matcher, ensure the `not` only applies to 150 | // the matching. 151 | this.flags.not = false; 152 | } 153 | 154 | var name = this.obj.name || 'fn'; 155 | this.assert( 156 | thrown 157 | , function(){ return 'expected ' + name + ' to throw an exception' } 158 | , function(){ return 'expected ' + name + ' not to throw an exception' }); 159 | }; 160 | 161 | /** 162 | * Checks if the array is empty. 163 | * 164 | * @api public 165 | */ 166 | 167 | Assertion.prototype.empty = function () { 168 | var expectation; 169 | 170 | if ('object' == typeof this.obj && null !== this.obj && !isArray(this.obj)) { 171 | if ('number' == typeof this.obj.length) { 172 | expectation = !this.obj.length; 173 | } else { 174 | expectation = !keys(this.obj).length; 175 | } 176 | } else { 177 | if ('string' != typeof this.obj) { 178 | expect(this.obj).to.be.an('object'); 179 | } 180 | 181 | expect(this.obj).to.have.property('length'); 182 | expectation = !this.obj.length; 183 | } 184 | 185 | this.assert( 186 | expectation 187 | , function(){ return 'expected ' + i(this.obj) + ' to be empty' } 188 | , function(){ return 'expected ' + i(this.obj) + ' to not be empty' }); 189 | return this; 190 | }; 191 | 192 | /** 193 | * Checks if the obj exactly equals another. 194 | * 195 | * @api public 196 | */ 197 | 198 | Assertion.prototype.be = 199 | Assertion.prototype.equal = function (obj) { 200 | this.assert( 201 | obj === this.obj 202 | , function(){ return 'expected ' + i(this.obj) + ' to equal ' + i(obj) } 203 | , function(){ return 'expected ' + i(this.obj) + ' to not equal ' + i(obj) }); 204 | return this; 205 | }; 206 | 207 | /** 208 | * Checks if the obj sortof equals another. 209 | * 210 | * @api public 211 | */ 212 | 213 | Assertion.prototype.eql = function (obj) { 214 | this.assert( 215 | expect.eql(obj, this.obj) 216 | , function(){ return 'expected ' + i(this.obj) + ' to sort of equal ' + i(obj) } 217 | , function(){ return 'expected ' + i(this.obj) + ' to sort of not equal ' + i(obj) }); 218 | return this; 219 | }; 220 | 221 | /** 222 | * Assert within start to finish (inclusive). 223 | * 224 | * @param {Number} start 225 | * @param {Number} finish 226 | * @api public 227 | */ 228 | 229 | Assertion.prototype.within = function (start, finish) { 230 | var range = start + '..' + finish; 231 | this.assert( 232 | this.obj >= start && this.obj <= finish 233 | , function(){ return 'expected ' + i(this.obj) + ' to be within ' + range } 234 | , function(){ return 'expected ' + i(this.obj) + ' to not be within ' + range }); 235 | return this; 236 | }; 237 | 238 | /** 239 | * Assert typeof / instance of 240 | * 241 | * @api public 242 | */ 243 | 244 | Assertion.prototype.a = 245 | Assertion.prototype.an = function (type) { 246 | if ('string' == typeof type) { 247 | // proper english in error msg 248 | var n = /^[aeiou]/.test(type) ? 'n' : ''; 249 | 250 | // typeof with support for 'array' 251 | this.assert( 252 | 'array' == type ? isArray(this.obj) : 253 | 'object' == type 254 | ? 'object' == typeof this.obj && null !== this.obj 255 | : type == typeof this.obj 256 | , function(){ return 'expected ' + i(this.obj) + ' to be a' + n + ' ' + type } 257 | , function(){ return 'expected ' + i(this.obj) + ' not to be a' + n + ' ' + type }); 258 | } else { 259 | // instanceof 260 | var name = type.name || 'supplied constructor'; 261 | this.assert( 262 | this.obj instanceof type 263 | , function(){ return 'expected ' + i(this.obj) + ' to be an instance of ' + name } 264 | , function(){ return 'expected ' + i(this.obj) + ' not to be an instance of ' + name }); 265 | } 266 | 267 | return this; 268 | }; 269 | 270 | /** 271 | * Assert numeric value above _n_. 272 | * 273 | * @param {Number} n 274 | * @api public 275 | */ 276 | 277 | Assertion.prototype.greaterThan = 278 | Assertion.prototype.above = function (n) { 279 | this.assert( 280 | this.obj > n 281 | , function(){ return 'expected ' + i(this.obj) + ' to be above ' + n } 282 | , function(){ return 'expected ' + i(this.obj) + ' to be below ' + n }); 283 | return this; 284 | }; 285 | 286 | /** 287 | * Assert numeric value below _n_. 288 | * 289 | * @param {Number} n 290 | * @api public 291 | */ 292 | 293 | Assertion.prototype.lessThan = 294 | Assertion.prototype.below = function (n) { 295 | this.assert( 296 | this.obj < n 297 | , function(){ return 'expected ' + i(this.obj) + ' to be below ' + n } 298 | , function(){ return 'expected ' + i(this.obj) + ' to be above ' + n }); 299 | return this; 300 | }; 301 | 302 | /** 303 | * Assert string value matches _regexp_. 304 | * 305 | * @param {RegExp} regexp 306 | * @api public 307 | */ 308 | 309 | Assertion.prototype.match = function (regexp) { 310 | this.assert( 311 | regexp.exec(this.obj) 312 | , function(){ return 'expected ' + i(this.obj) + ' to match ' + regexp } 313 | , function(){ return 'expected ' + i(this.obj) + ' not to match ' + regexp }); 314 | return this; 315 | }; 316 | 317 | /** 318 | * Assert property "length" exists and has value of _n_. 319 | * 320 | * @param {Number} n 321 | * @api public 322 | */ 323 | 324 | Assertion.prototype.length = function (n) { 325 | expect(this.obj).to.have.property('length'); 326 | var len = this.obj.length; 327 | this.assert( 328 | n == len 329 | , function(){ return 'expected ' + i(this.obj) + ' to have a length of ' + n + ' but got ' + len } 330 | , function(){ return 'expected ' + i(this.obj) + ' to not have a length of ' + len }); 331 | return this; 332 | }; 333 | 334 | /** 335 | * Assert property _name_ exists, with optional _val_. 336 | * 337 | * @param {String} name 338 | * @param {Mixed} val 339 | * @api public 340 | */ 341 | 342 | Assertion.prototype.property = function (name, val) { 343 | if (this.flags.own) { 344 | this.assert( 345 | Object.prototype.hasOwnProperty.call(this.obj, name) 346 | , function(){ return 'expected ' + i(this.obj) + ' to have own property ' + i(name) } 347 | , function(){ return 'expected ' + i(this.obj) + ' to not have own property ' + i(name) }); 348 | return this; 349 | } 350 | 351 | if (this.flags.not && undefined !== val) { 352 | if (undefined === this.obj[name]) { 353 | throw new Error(i(this.obj) + ' has no property ' + i(name)); 354 | } 355 | } else { 356 | var hasProp; 357 | try { 358 | hasProp = name in this.obj 359 | } catch (e) { 360 | hasProp = undefined !== this.obj[name] 361 | } 362 | 363 | this.assert( 364 | hasProp 365 | , function(){ return 'expected ' + i(this.obj) + ' to have a property ' + i(name) } 366 | , function(){ return 'expected ' + i(this.obj) + ' to not have a property ' + i(name) }); 367 | } 368 | 369 | if (undefined !== val) { 370 | this.assert( 371 | val === this.obj[name] 372 | , function(){ return 'expected ' + i(this.obj) + ' to have a property ' + i(name) 373 | + ' of ' + i(val) + ', but got ' + i(this.obj[name]) } 374 | , function(){ return 'expected ' + i(this.obj) + ' to not have a property ' + i(name) 375 | + ' of ' + i(val) }); 376 | } 377 | 378 | this.obj = this.obj[name]; 379 | return this; 380 | }; 381 | 382 | /** 383 | * Assert that the array contains _obj_ or string contains _obj_. 384 | * 385 | * @param {Mixed} obj|string 386 | * @api public 387 | */ 388 | 389 | Assertion.prototype.string = 390 | Assertion.prototype.contain = function (obj) { 391 | if ('string' == typeof this.obj) { 392 | this.assert( 393 | ~this.obj.indexOf(obj) 394 | , function(){ return 'expected ' + i(this.obj) + ' to contain ' + i(obj) } 395 | , function(){ return 'expected ' + i(this.obj) + ' to not contain ' + i(obj) }); 396 | } else { 397 | this.assert( 398 | ~indexOf(this.obj, obj) 399 | , function(){ return 'expected ' + i(this.obj) + ' to contain ' + i(obj) } 400 | , function(){ return 'expected ' + i(this.obj) + ' to not contain ' + i(obj) }); 401 | } 402 | return this; 403 | }; 404 | 405 | /** 406 | * Assert exact keys or inclusion of keys by using 407 | * the `.own` modifier. 408 | * 409 | * @param {Array|String ...} keys 410 | * @api public 411 | */ 412 | 413 | Assertion.prototype.key = 414 | Assertion.prototype.keys = function ($keys) { 415 | var str 416 | , ok = true; 417 | 418 | $keys = isArray($keys) 419 | ? $keys 420 | : Array.prototype.slice.call(arguments); 421 | 422 | if (!$keys.length) throw new Error('keys required'); 423 | 424 | var actual = keys(this.obj) 425 | , len = $keys.length; 426 | 427 | // Inclusion 428 | ok = every($keys, function (key) { 429 | return ~indexOf(actual, key); 430 | }); 431 | 432 | // Strict 433 | if (!this.flags.not && this.flags.only) { 434 | ok = ok && $keys.length == actual.length; 435 | } 436 | 437 | // Key string 438 | if (len > 1) { 439 | $keys = map($keys, function (key) { 440 | return i(key); 441 | }); 442 | var last = $keys.pop(); 443 | str = $keys.join(', ') + ', and ' + last; 444 | } else { 445 | str = i($keys[0]); 446 | } 447 | 448 | // Form 449 | str = (len > 1 ? 'keys ' : 'key ') + str; 450 | 451 | // Have / include 452 | str = (!this.flags.only ? 'include ' : 'only have ') + str; 453 | 454 | // Assertion 455 | this.assert( 456 | ok 457 | , function(){ return 'expected ' + i(this.obj) + ' to ' + str } 458 | , function(){ return 'expected ' + i(this.obj) + ' to not ' + str }); 459 | 460 | return this; 461 | }; 462 | /** 463 | * Assert a failure. 464 | * 465 | * @param {String ...} custom message 466 | * @api public 467 | */ 468 | Assertion.prototype.fail = function (msg) { 469 | msg = msg || "explicit failure"; 470 | this.assert(false, msg, msg); 471 | return this; 472 | }; 473 | 474 | /** 475 | * Function bind implementation. 476 | */ 477 | 478 | function bind (fn, scope) { 479 | return function () { 480 | return fn.apply(scope, arguments); 481 | } 482 | } 483 | 484 | /** 485 | * Array every compatibility 486 | * 487 | * @see bit.ly/5Fq1N2 488 | * @api public 489 | */ 490 | 491 | function every (arr, fn, thisObj) { 492 | var scope = thisObj || global; 493 | for (var i = 0, j = arr.length; i < j; ++i) { 494 | if (!fn.call(scope, arr[i], i, arr)) { 495 | return false; 496 | } 497 | } 498 | return true; 499 | }; 500 | 501 | /** 502 | * Array indexOf compatibility. 503 | * 504 | * @see bit.ly/a5Dxa2 505 | * @api public 506 | */ 507 | 508 | function indexOf (arr, o, i) { 509 | if (Array.prototype.indexOf) { 510 | return Array.prototype.indexOf.call(arr, o, i); 511 | } 512 | 513 | if (arr.length === undefined) { 514 | return -1; 515 | } 516 | 517 | for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0 518 | ; i < j && arr[i] !== o; i++); 519 | 520 | return j <= i ? -1 : i; 521 | }; 522 | 523 | // https://gist.github.com/1044128/ 524 | var getOuterHTML = function(element) { 525 | if ('outerHTML' in element) return element.outerHTML; 526 | var ns = "http://www.w3.org/1999/xhtml"; 527 | var container = document.createElementNS(ns, '_'); 528 | var elemProto = (window.HTMLElement || window.Element).prototype; 529 | var xmlSerializer = new XMLSerializer(); 530 | var html; 531 | if (document.xmlVersion) { 532 | return xmlSerializer.serializeToString(element); 533 | } else { 534 | container.appendChild(element.cloneNode(false)); 535 | html = container.innerHTML.replace('><', '>' + element.innerHTML + '<'); 536 | container.innerHTML = ''; 537 | return html; 538 | } 539 | }; 540 | 541 | // Returns true if object is a DOM element. 542 | var isDOMElement = function (object) { 543 | if (typeof HTMLElement === 'object') { 544 | return object instanceof HTMLElement; 545 | } else { 546 | return object && 547 | typeof object === 'object' && 548 | object.nodeType === 1 && 549 | typeof object.nodeName === 'string'; 550 | } 551 | }; 552 | 553 | /** 554 | * Inspects an object. 555 | * 556 | * @see taken from node.js `util` module (copyright Joyent, MIT license) 557 | * @api private 558 | */ 559 | 560 | function i (obj, showHidden, depth) { 561 | var seen = []; 562 | 563 | function stylize (str) { 564 | return str; 565 | }; 566 | 567 | function format (value, recurseTimes) { 568 | // Provide a hook for user-specified inspect functions. 569 | // Check that value is an object with an inspect function on it 570 | if (value && typeof value.inspect === 'function' && 571 | // Filter out the util module, it's inspect function is special 572 | value !== exports && 573 | // Also filter out any prototype objects using the circular check. 574 | !(value.constructor && value.constructor.prototype === value)) { 575 | return value.inspect(recurseTimes); 576 | } 577 | 578 | // Primitive types cannot have properties 579 | switch (typeof value) { 580 | case 'undefined': 581 | return stylize('undefined', 'undefined'); 582 | 583 | case 'string': 584 | var simple = '\'' + json.stringify(value).replace(/^"|"$/g, '') 585 | .replace(/'/g, "\\'") 586 | .replace(/\\"/g, '"') + '\''; 587 | return stylize(simple, 'string'); 588 | 589 | case 'number': 590 | return stylize('' + value, 'number'); 591 | 592 | case 'boolean': 593 | return stylize('' + value, 'boolean'); 594 | } 595 | // For some reason typeof null is "object", so special case here. 596 | if (value === null) { 597 | return stylize('null', 'null'); 598 | } 599 | 600 | if (isDOMElement(value)) { 601 | return getOuterHTML(value); 602 | } 603 | 604 | // Look up the keys of the object. 605 | var visible_keys = keys(value); 606 | var $keys = showHidden ? Object.getOwnPropertyNames(value) : visible_keys; 607 | 608 | // Functions without properties can be shortcutted. 609 | if (typeof value === 'function' && $keys.length === 0) { 610 | if (isRegExp(value)) { 611 | return stylize('' + value, 'regexp'); 612 | } else { 613 | var name = value.name ? ': ' + value.name : ''; 614 | return stylize('[Function' + name + ']', 'special'); 615 | } 616 | } 617 | 618 | // Dates without properties can be shortcutted 619 | if (isDate(value) && $keys.length === 0) { 620 | return stylize(value.toUTCString(), 'date'); 621 | } 622 | 623 | var base, type, braces; 624 | // Determine the object type 625 | if (isArray(value)) { 626 | type = 'Array'; 627 | braces = ['[', ']']; 628 | } else { 629 | type = 'Object'; 630 | braces = ['{', '}']; 631 | } 632 | 633 | // Make functions say that they are functions 634 | if (typeof value === 'function') { 635 | var n = value.name ? ': ' + value.name : ''; 636 | base = (isRegExp(value)) ? ' ' + value : ' [Function' + n + ']'; 637 | } else { 638 | base = ''; 639 | } 640 | 641 | // Make dates with properties first say the date 642 | if (isDate(value)) { 643 | base = ' ' + value.toUTCString(); 644 | } 645 | 646 | if ($keys.length === 0) { 647 | return braces[0] + base + braces[1]; 648 | } 649 | 650 | if (recurseTimes < 0) { 651 | if (isRegExp(value)) { 652 | return stylize('' + value, 'regexp'); 653 | } else { 654 | return stylize('[Object]', 'special'); 655 | } 656 | } 657 | 658 | seen.push(value); 659 | 660 | var output = map($keys, function (key) { 661 | var name, str; 662 | if (value.__lookupGetter__) { 663 | if (value.__lookupGetter__(key)) { 664 | if (value.__lookupSetter__(key)) { 665 | str = stylize('[Getter/Setter]', 'special'); 666 | } else { 667 | str = stylize('[Getter]', 'special'); 668 | } 669 | } else { 670 | if (value.__lookupSetter__(key)) { 671 | str = stylize('[Setter]', 'special'); 672 | } 673 | } 674 | } 675 | if (indexOf(visible_keys, key) < 0) { 676 | name = '[' + key + ']'; 677 | } 678 | if (!str) { 679 | if (indexOf(seen, value[key]) < 0) { 680 | if (recurseTimes === null) { 681 | str = format(value[key]); 682 | } else { 683 | str = format(value[key], recurseTimes - 1); 684 | } 685 | if (str.indexOf('\n') > -1) { 686 | if (isArray(value)) { 687 | str = map(str.split('\n'), function (line) { 688 | return ' ' + line; 689 | }).join('\n').substr(2); 690 | } else { 691 | str = '\n' + map(str.split('\n'), function (line) { 692 | return ' ' + line; 693 | }).join('\n'); 694 | } 695 | } 696 | } else { 697 | str = stylize('[Circular]', 'special'); 698 | } 699 | } 700 | if (typeof name === 'undefined') { 701 | if (type === 'Array' && key.match(/^\d+$/)) { 702 | return str; 703 | } 704 | name = json.stringify('' + key); 705 | if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { 706 | name = name.substr(1, name.length - 2); 707 | name = stylize(name, 'name'); 708 | } else { 709 | name = name.replace(/'/g, "\\'") 710 | .replace(/\\"/g, '"') 711 | .replace(/(^"|"$)/g, "'"); 712 | name = stylize(name, 'string'); 713 | } 714 | } 715 | 716 | return name + ': ' + str; 717 | }); 718 | 719 | seen.pop(); 720 | 721 | var numLinesEst = 0; 722 | var length = reduce(output, function (prev, cur) { 723 | numLinesEst++; 724 | if (indexOf(cur, '\n') >= 0) numLinesEst++; 725 | return prev + cur.length + 1; 726 | }, 0); 727 | 728 | if (length > 50) { 729 | output = braces[0] + 730 | (base === '' ? '' : base + '\n ') + 731 | ' ' + 732 | output.join(',\n ') + 733 | ' ' + 734 | braces[1]; 735 | 736 | } else { 737 | output = braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; 738 | } 739 | 740 | return output; 741 | } 742 | return format(obj, (typeof depth === 'undefined' ? 2 : depth)); 743 | }; 744 | 745 | function isArray (ar) { 746 | return Object.prototype.toString.call(ar) == '[object Array]'; 747 | }; 748 | 749 | function isRegExp(re) { 750 | var s; 751 | try { 752 | s = '' + re; 753 | } catch (e) { 754 | return false; 755 | } 756 | 757 | return re instanceof RegExp || // easy case 758 | // duck-type for context-switching evalcx case 759 | typeof(re) === 'function' && 760 | re.constructor.name === 'RegExp' && 761 | re.compile && 762 | re.test && 763 | re.exec && 764 | s.match(/^\/.*\/[gim]{0,3}$/); 765 | }; 766 | 767 | function isDate(d) { 768 | if (d instanceof Date) return true; 769 | return false; 770 | }; 771 | 772 | function keys (obj) { 773 | if (Object.keys) { 774 | return Object.keys(obj); 775 | } 776 | 777 | var keys = []; 778 | 779 | for (var i in obj) { 780 | if (Object.prototype.hasOwnProperty.call(obj, i)) { 781 | keys.push(i); 782 | } 783 | } 784 | 785 | return keys; 786 | } 787 | 788 | function map (arr, mapper, that) { 789 | if (Array.prototype.map) { 790 | return Array.prototype.map.call(arr, mapper, that); 791 | } 792 | 793 | var other= new Array(arr.length); 794 | 795 | for (var i= 0, n = arr.length; i= 2) { 821 | var rv = arguments[1]; 822 | } else { 823 | do { 824 | if (i in this) { 825 | rv = this[i++]; 826 | break; 827 | } 828 | 829 | // if array contains no values, no initial value to return 830 | if (++i >= len) 831 | throw new TypeError(); 832 | } while (true); 833 | } 834 | 835 | for (; i < len; i++) { 836 | if (i in this) 837 | rv = fun.call(null, rv, this[i], i, this); 838 | } 839 | 840 | return rv; 841 | }; 842 | 843 | /** 844 | * Asserts deep equality 845 | * 846 | * @see taken from node.js `assert` module (copyright Joyent, MIT license) 847 | * @api private 848 | */ 849 | 850 | expect.eql = function eql (actual, expected) { 851 | // 7.1. All identical values are equivalent, as determined by ===. 852 | if (actual === expected) { 853 | return true; 854 | } else if ('undefined' != typeof Buffer 855 | && Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) { 856 | if (actual.length != expected.length) return false; 857 | 858 | for (var i = 0; i < actual.length; i++) { 859 | if (actual[i] !== expected[i]) return false; 860 | } 861 | 862 | return true; 863 | 864 | // 7.2. If the expected value is a Date object, the actual value is 865 | // equivalent if it is also a Date object that refers to the same time. 866 | } else if (actual instanceof Date && expected instanceof Date) { 867 | return actual.getTime() === expected.getTime(); 868 | 869 | // 7.3. Other pairs that do not both pass typeof value == "object", 870 | // equivalence is determined by ==. 871 | } else if (typeof actual != 'object' && typeof expected != 'object') { 872 | return actual == expected; 873 | 874 | // 7.4. For all other Object pairs, including Array objects, equivalence is 875 | // determined by having the same number of owned properties (as verified 876 | // with Object.prototype.hasOwnProperty.call), the same set of keys 877 | // (although not necessarily the same order), equivalent values for every 878 | // corresponding key, and an identical "prototype" property. Note: this 879 | // accounts for both named and indexed properties on Arrays. 880 | } else { 881 | return objEquiv(actual, expected); 882 | } 883 | } 884 | 885 | function isUndefinedOrNull (value) { 886 | return value === null || value === undefined; 887 | } 888 | 889 | function isArguments (object) { 890 | return Object.prototype.toString.call(object) == '[object Arguments]'; 891 | } 892 | 893 | function objEquiv (a, b) { 894 | if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) 895 | return false; 896 | // an identical "prototype" property. 897 | if (a.prototype !== b.prototype) return false; 898 | //~~~I've managed to break Object.keys through screwy arguments passing. 899 | // Converting to array solves the problem. 900 | if (isArguments(a)) { 901 | if (!isArguments(b)) { 902 | return false; 903 | } 904 | a = pSlice.call(a); 905 | b = pSlice.call(b); 906 | return expect.eql(a, b); 907 | } 908 | try{ 909 | var ka = keys(a), 910 | kb = keys(b), 911 | key, i; 912 | } catch (e) {//happens when one is a string literal and the other isn't 913 | return false; 914 | } 915 | // having the same number of owned properties (keys incorporates hasOwnProperty) 916 | if (ka.length != kb.length) 917 | return false; 918 | //the same set of keys (although not necessarily the same order), 919 | ka.sort(); 920 | kb.sort(); 921 | //~~~cheap key test 922 | for (i = ka.length - 1; i >= 0; i--) { 923 | if (ka[i] != kb[i]) 924 | return false; 925 | } 926 | //equivalent values for every corresponding key, and 927 | //~~~possibly expensive deep test 928 | for (i = ka.length - 1; i >= 0; i--) { 929 | key = ka[i]; 930 | if (!expect.eql(a[key], b[key])) 931 | return false; 932 | } 933 | return true; 934 | } 935 | 936 | var json = (function () { 937 | "use strict"; 938 | 939 | if ('object' == typeof JSON && JSON.parse && JSON.stringify) { 940 | return { 941 | parse: nativeJSON.parse 942 | , stringify: nativeJSON.stringify 943 | } 944 | } 945 | 946 | var JSON = {}; 947 | 948 | function f(n) { 949 | // Format integers to have at least two digits. 950 | return n < 10 ? '0' + n : n; 951 | } 952 | 953 | function date(d, key) { 954 | return isFinite(d.valueOf()) ? 955 | d.getUTCFullYear() + '-' + 956 | f(d.getUTCMonth() + 1) + '-' + 957 | f(d.getUTCDate()) + 'T' + 958 | f(d.getUTCHours()) + ':' + 959 | f(d.getUTCMinutes()) + ':' + 960 | f(d.getUTCSeconds()) + 'Z' : null; 961 | }; 962 | 963 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 964 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 965 | gap, 966 | indent, 967 | meta = { // table of character substitutions 968 | '\b': '\\b', 969 | '\t': '\\t', 970 | '\n': '\\n', 971 | '\f': '\\f', 972 | '\r': '\\r', 973 | '"' : '\\"', 974 | '\\': '\\\\' 975 | }, 976 | rep; 977 | 978 | 979 | function quote(string) { 980 | 981 | // If the string contains no control characters, no quote characters, and no 982 | // backslash characters, then we can safely slap some quotes around it. 983 | // Otherwise we must also replace the offending characters with safe escape 984 | // sequences. 985 | 986 | escapable.lastIndex = 0; 987 | return escapable.test(string) ? '"' + string.replace(escapable, function (a) { 988 | var c = meta[a]; 989 | return typeof c === 'string' ? c : 990 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 991 | }) + '"' : '"' + string + '"'; 992 | } 993 | 994 | 995 | function str(key, holder) { 996 | 997 | // Produce a string from holder[key]. 998 | 999 | var i, // The loop counter. 1000 | k, // The member key. 1001 | v, // The member value. 1002 | length, 1003 | mind = gap, 1004 | partial, 1005 | value = holder[key]; 1006 | 1007 | // If the value has a toJSON method, call it to obtain a replacement value. 1008 | 1009 | if (value instanceof Date) { 1010 | value = date(key); 1011 | } 1012 | 1013 | // If we were called with a replacer function, then call the replacer to 1014 | // obtain a replacement value. 1015 | 1016 | if (typeof rep === 'function') { 1017 | value = rep.call(holder, key, value); 1018 | } 1019 | 1020 | // What happens next depends on the value's type. 1021 | 1022 | switch (typeof value) { 1023 | case 'string': 1024 | return quote(value); 1025 | 1026 | case 'number': 1027 | 1028 | // JSON numbers must be finite. Encode non-finite numbers as null. 1029 | 1030 | return isFinite(value) ? String(value) : 'null'; 1031 | 1032 | case 'boolean': 1033 | case 'null': 1034 | 1035 | // If the value is a boolean or null, convert it to a string. Note: 1036 | // typeof null does not produce 'null'. The case is included here in 1037 | // the remote chance that this gets fixed someday. 1038 | 1039 | return String(value); 1040 | 1041 | // If the type is 'object', we might be dealing with an object or an array or 1042 | // null. 1043 | 1044 | case 'object': 1045 | 1046 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 1047 | // so watch out for that case. 1048 | 1049 | if (!value) { 1050 | return 'null'; 1051 | } 1052 | 1053 | // Make an array to hold the partial results of stringifying this object value. 1054 | 1055 | gap += indent; 1056 | partial = []; 1057 | 1058 | // Is the value an array? 1059 | 1060 | if (Object.prototype.toString.apply(value) === '[object Array]') { 1061 | 1062 | // The value is an array. Stringify every element. Use null as a placeholder 1063 | // for non-JSON values. 1064 | 1065 | length = value.length; 1066 | for (i = 0; i < length; i += 1) { 1067 | partial[i] = str(i, value) || 'null'; 1068 | } 1069 | 1070 | // Join all of the elements together, separated with commas, and wrap them in 1071 | // brackets. 1072 | 1073 | v = partial.length === 0 ? '[]' : gap ? 1074 | '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : 1075 | '[' + partial.join(',') + ']'; 1076 | gap = mind; 1077 | return v; 1078 | } 1079 | 1080 | // If the replacer is an array, use it to select the members to be stringified. 1081 | 1082 | if (rep && typeof rep === 'object') { 1083 | length = rep.length; 1084 | for (i = 0; i < length; i += 1) { 1085 | if (typeof rep[i] === 'string') { 1086 | k = rep[i]; 1087 | v = str(k, value); 1088 | if (v) { 1089 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 1090 | } 1091 | } 1092 | } 1093 | } else { 1094 | 1095 | // Otherwise, iterate through all of the keys in the object. 1096 | 1097 | for (k in value) { 1098 | if (Object.prototype.hasOwnProperty.call(value, k)) { 1099 | v = str(k, value); 1100 | if (v) { 1101 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 1102 | } 1103 | } 1104 | } 1105 | } 1106 | 1107 | // Join all of the member texts together, separated with commas, 1108 | // and wrap them in braces. 1109 | 1110 | v = partial.length === 0 ? '{}' : gap ? 1111 | '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : 1112 | '{' + partial.join(',') + '}'; 1113 | gap = mind; 1114 | return v; 1115 | } 1116 | } 1117 | 1118 | // If the JSON object does not yet have a stringify method, give it one. 1119 | 1120 | JSON.stringify = function (value, replacer, space) { 1121 | 1122 | // The stringify method takes a value and an optional replacer, and an optional 1123 | // space parameter, and returns a JSON text. The replacer can be a function 1124 | // that can replace values, or an array of strings that will select the keys. 1125 | // A default replacer method can be provided. Use of the space parameter can 1126 | // produce text that is more easily readable. 1127 | 1128 | var i; 1129 | gap = ''; 1130 | indent = ''; 1131 | 1132 | // If the space parameter is a number, make an indent string containing that 1133 | // many spaces. 1134 | 1135 | if (typeof space === 'number') { 1136 | for (i = 0; i < space; i += 1) { 1137 | indent += ' '; 1138 | } 1139 | 1140 | // If the space parameter is a string, it will be used as the indent string. 1141 | 1142 | } else if (typeof space === 'string') { 1143 | indent = space; 1144 | } 1145 | 1146 | // If there is a replacer, it must be a function or an array. 1147 | // Otherwise, throw an error. 1148 | 1149 | rep = replacer; 1150 | if (replacer && typeof replacer !== 'function' && 1151 | (typeof replacer !== 'object' || 1152 | typeof replacer.length !== 'number')) { 1153 | throw new Error('JSON.stringify'); 1154 | } 1155 | 1156 | // Make a fake root object containing our value under the key of ''. 1157 | // Return the result of stringifying the value. 1158 | 1159 | return str('', {'': value}); 1160 | }; 1161 | 1162 | // If the JSON object does not yet have a parse method, give it one. 1163 | 1164 | JSON.parse = function (text, reviver) { 1165 | // The parse method takes a text and an optional reviver function, and returns 1166 | // a JavaScript value if the text is a valid JSON text. 1167 | 1168 | var j; 1169 | 1170 | function walk(holder, key) { 1171 | 1172 | // The walk method is used to recursively walk the resulting structure so 1173 | // that modifications can be made. 1174 | 1175 | var k, v, value = holder[key]; 1176 | if (value && typeof value === 'object') { 1177 | for (k in value) { 1178 | if (Object.prototype.hasOwnProperty.call(value, k)) { 1179 | v = walk(value, k); 1180 | if (v !== undefined) { 1181 | value[k] = v; 1182 | } else { 1183 | delete value[k]; 1184 | } 1185 | } 1186 | } 1187 | } 1188 | return reviver.call(holder, key, value); 1189 | } 1190 | 1191 | 1192 | // Parsing happens in four stages. In the first stage, we replace certain 1193 | // Unicode characters with escape sequences. JavaScript handles many characters 1194 | // incorrectly, either silently deleting them, or treating them as line endings. 1195 | 1196 | text = String(text); 1197 | cx.lastIndex = 0; 1198 | if (cx.test(text)) { 1199 | text = text.replace(cx, function (a) { 1200 | return '\\u' + 1201 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 1202 | }); 1203 | } 1204 | 1205 | // In the second stage, we run the text against regular expressions that look 1206 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 1207 | // because they can cause invocation, and '=' because it can cause mutation. 1208 | // But just to be safe, we want to reject all unexpected forms. 1209 | 1210 | // We split the second stage into 4 regexp operations in order to work around 1211 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 1212 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 1213 | // replace all simple value tokens with ']' characters. Third, we delete all 1214 | // open brackets that follow a colon or comma or that begin the text. Finally, 1215 | // we look to see that the remaining characters are only whitespace or ']' or 1216 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 1217 | 1218 | if (/^[\],:{}\s]*$/ 1219 | .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') 1220 | .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') 1221 | .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 1222 | 1223 | // In the third stage we use the eval function to compile the text into a 1224 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 1225 | // in JavaScript: it can begin a block or an object literal. We wrap the text 1226 | // in parens to eliminate the ambiguity. 1227 | 1228 | j = eval('(' + text + ')'); 1229 | 1230 | // In the optional fourth stage, we recursively walk the new structure, passing 1231 | // each name/value pair to a reviver function for possible transformation. 1232 | 1233 | return typeof reviver === 'function' ? 1234 | walk({'': j}, '') : j; 1235 | } 1236 | 1237 | // If the text is not JSON parseable, then a SyntaxError is thrown. 1238 | 1239 | throw new SyntaxError('JSON.parse'); 1240 | }; 1241 | 1242 | return JSON; 1243 | })(); 1244 | 1245 | if ('undefined' != typeof window) { 1246 | window.expect = module.exports; 1247 | } 1248 | 1249 | })( 1250 | this 1251 | , 'undefined' != typeof module ? module : {} 1252 | , 'undefined' != typeof exports ? exports : {} 1253 | ); 1254 | -------------------------------------------------------------------------------- /lib/jquery.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v1.9.0 | (c) 2005, 2012 jQuery Foundation, Inc. | jquery.org/license */(function(e,t){"use strict";function n(e){var t=e.length,n=st.type(e);return st.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}function r(e){var t=Tt[e]={};return st.each(e.match(lt)||[],function(e,n){t[n]=!0}),t}function i(e,n,r,i){if(st.acceptData(e)){var o,a,s=st.expando,u="string"==typeof n,l=e.nodeType,c=l?st.cache:e,f=l?e[s]:e[s]&&s;if(f&&c[f]&&(i||c[f].data)||!u||r!==t)return f||(l?e[s]=f=K.pop()||st.guid++:f=s),c[f]||(c[f]={},l||(c[f].toJSON=st.noop)),("object"==typeof n||"function"==typeof n)&&(i?c[f]=st.extend(c[f],n):c[f].data=st.extend(c[f].data,n)),o=c[f],i||(o.data||(o.data={}),o=o.data),r!==t&&(o[st.camelCase(n)]=r),u?(a=o[n],null==a&&(a=o[st.camelCase(n)])):a=o,a}}function o(e,t,n){if(st.acceptData(e)){var r,i,o,a=e.nodeType,u=a?st.cache:e,l=a?e[st.expando]:st.expando;if(u[l]){if(t&&(r=n?u[l]:u[l].data)){st.isArray(t)?t=t.concat(st.map(t,st.camelCase)):t in r?t=[t]:(t=st.camelCase(t),t=t in r?[t]:t.split(" "));for(i=0,o=t.length;o>i;i++)delete r[t[i]];if(!(n?s:st.isEmptyObject)(r))return}(n||(delete u[l].data,s(u[l])))&&(a?st.cleanData([e],!0):st.support.deleteExpando||u!=u.window?delete u[l]:u[l]=null)}}}function a(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(Nt,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:wt.test(r)?st.parseJSON(r):r}catch(o){}st.data(e,n,r)}else r=t}return r}function s(e){var t;for(t in e)if(("data"!==t||!st.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}function u(){return!0}function l(){return!1}function c(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}function f(e,t,n){if(t=t||0,st.isFunction(t))return st.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return st.grep(e,function(e){return e===t===n});if("string"==typeof t){var r=st.grep(e,function(e){return 1===e.nodeType});if(Wt.test(t))return st.filter(t,r,!n);t=st.filter(t,r)}return st.grep(e,function(e){return st.inArray(e,t)>=0===n})}function p(e){var t=zt.split("|"),n=e.createDocumentFragment();if(n.createElement)for(;t.length;)n.createElement(t.pop());return n}function d(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function h(e){var t=e.getAttributeNode("type");return e.type=(t&&t.specified)+"/"+e.type,e}function g(e){var t=nn.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function m(e,t){for(var n,r=0;null!=(n=e[r]);r++)st._data(n,"globalEval",!t||st._data(t[r],"globalEval"))}function y(e,t){if(1===t.nodeType&&st.hasData(e)){var n,r,i,o=st._data(e),a=st._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)st.event.add(t,n,s[n][r])}a.data&&(a.data=st.extend({},a.data))}}function v(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!st.support.noCloneEvent&&t[st.expando]){r=st._data(t);for(i in r.events)st.removeEvent(t,i,r.handle);t.removeAttribute(st.expando)}"script"===n&&t.text!==e.text?(h(t).text=e.text,g(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),st.support.html5Clone&&e.innerHTML&&!st.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Zt.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}function b(e,n){var r,i,o=0,a=e.getElementsByTagName!==t?e.getElementsByTagName(n||"*"):e.querySelectorAll!==t?e.querySelectorAll(n||"*"):t;if(!a)for(a=[],r=e.childNodes||e;null!=(i=r[o]);o++)!n||st.nodeName(i,n)?a.push(i):st.merge(a,b(i,n));return n===t||n&&st.nodeName(e,n)?st.merge([e],a):a}function x(e){Zt.test(e.type)&&(e.defaultChecked=e.checked)}function T(e,t){if(t in e)return t;for(var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Nn.length;i--;)if(t=Nn[i]+n,t in e)return t;return r}function w(e,t){return e=t||e,"none"===st.css(e,"display")||!st.contains(e.ownerDocument,e)}function N(e,t){for(var n,r=[],i=0,o=e.length;o>i;i++)n=e[i],n.style&&(r[i]=st._data(n,"olddisplay"),t?(r[i]||"none"!==n.style.display||(n.style.display=""),""===n.style.display&&w(n)&&(r[i]=st._data(n,"olddisplay",S(n.nodeName)))):r[i]||w(n)||st._data(n,"olddisplay",st.css(n,"display")));for(i=0;o>i;i++)n=e[i],n.style&&(t&&"none"!==n.style.display&&""!==n.style.display||(n.style.display=t?r[i]||"":"none"));return e}function C(e,t,n){var r=mn.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function k(e,t,n,r,i){for(var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;4>o;o+=2)"margin"===n&&(a+=st.css(e,n+wn[o],!0,i)),r?("content"===n&&(a-=st.css(e,"padding"+wn[o],!0,i)),"margin"!==n&&(a-=st.css(e,"border"+wn[o]+"Width",!0,i))):(a+=st.css(e,"padding"+wn[o],!0,i),"padding"!==n&&(a+=st.css(e,"border"+wn[o]+"Width",!0,i)));return a}function E(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=ln(e),a=st.support.boxSizing&&"border-box"===st.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=un(e,t,o),(0>i||null==i)&&(i=e.style[t]),yn.test(i))return i;r=a&&(st.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+k(e,t,n||(a?"border":"content"),r,o)+"px"}function S(e){var t=V,n=bn[e];return n||(n=A(e,t),"none"!==n&&n||(cn=(cn||st("