diff --git a/.travis.yml b/.travis.yml index 2104b26..5ef363a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: node_js node_js: - - "0.11" - - "0.10" + - "iojs" after_script: NODE_ENV=test istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage \ No newline at end of file diff --git a/index.js b/index.js index 285cddf..0e7250a 100644 --- a/index.js +++ b/index.js @@ -1,41 +1,46 @@ module.exports = - { and : require('./lib/and') - , apply : require('./lib/apply') - , assert : require('./lib/assert') - , call : require('./lib/call') - , compose : require('./lib/compose') - , constantly : require('./lib/constantly') - , count : require('./lib/count') - , dec : require('./lib/dec') - , each : require('./lib/each') - , eq : require('./lib/eq') - , gt : require('./lib/gt') - , get : require('./lib/get') - , identity : require('./lib/identity') - , inc : require('./lib/inc') - , is : require('./lib/is') - , isEmpty : require('./lib/isEmpty') - , isnt : require('./lib/isnt') - , log : require('./lib/log') - , lowerCase : require('./lib/lowerCase') - , lt : require('./lib/lt') - , merge : require('./lib/merge') - , min : require('./lib/min') - , not : require('./lib/not') - , nth : require('./lib/nth') - , once : require('./lib/once') - , partial : require('./lib/partial') - , pipe : require('./lib/pipe') - , range : require('./lib/range') - , repeatedly : require('./lib/repeatedly') - , seq : require('./lib/seq') - , slice : require('./lib/slice') - , src : require('./lib/src') - , take : require('./lib/take') - , thunk : require('./lib/thunk') - , trampoline : require('./lib/trampoline') - , type : require('./lib/type') - , val : require('./lib/val') - , variadic : require('./lib/variadic') - , vec : require('./lib/vec') + { and : require('./lib/and') + , apply : require('./lib/apply') + , assert : require('./lib/assert') + , call : require('./lib/call') + , compose : require('./lib/compose') + , constantly : require('./lib/constantly') + , count : require('./lib/count') + , dec : require('./lib/dec') + , defprop : require('./lib/defprop') + , deftype : require('./lib/deftype') + , defprotocol : require('./lib/defprotocol') + , each : require('./lib/each') + , every : require('./lib/every') + , eq : require('./lib/eq') + , gt : require('./lib/gt') + , get : require('./lib/get') + , identity : require('./lib/identity') + , inc : require('./lib/inc') + , is : require('./lib/is') + , isEmpty : require('./lib/isEmpty') + , isnt : require('./lib/isnt') + , log : require('./lib/log') + , lowerCase : require('./lib/lowerCase') + , lt : require('./lib/lt') + , merge : require('./lib/merge') + , min : require('./lib/min') + , not : require('./lib/not') + , nth : require('./lib/nth') + , once : require('./lib/once') + , partial : require('./lib/partial') + , pipe : require('./lib/pipe') + , range : require('./lib/range') + , repeatedly : require('./lib/repeatedly') + , seq : require('./lib/seq') + , slice : require('./lib/slice') + , src : require('./lib/src') + , take : require('./lib/take') + , thunk : require('./lib/thunk') + , trampoline : require('./lib/trampoline') + , type : require('./lib/type') + , val : require('./lib/val') + , variadic : require('./lib/variadic') + , vec : require('./lib/vec') + , when : require('./lib/when') } \ No newline at end of file diff --git a/lib/assert.js b/lib/assert.js index d9f4ac1..308e5ac 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -1,6 +1,6 @@ module.exports = assert -function assert(x, message, type) { +function assert(x, message) { if (isnt(x)) { message = val(message) isnt(message) && (message = "Assertion failed.") diff --git a/lib/async/buffer/fixed.js b/lib/async/buffer/fixed.js new file mode 100644 index 0000000..1ec339e --- /dev/null +++ b/lib/async/buffer/fixed.js @@ -0,0 +1,5 @@ +module.exports = fixed + +function fixed() { + +} \ No newline at end of file diff --git a/lib/async/buffer/index.js b/lib/async/buffer/index.js new file mode 100644 index 0000000..b80d575 --- /dev/null +++ b/lib/async/buffer/index.js @@ -0,0 +1,35 @@ +var defprotocol = require('../../defprotocol') + +module.exports = defprotocol('Buffer', + { add : [Object] + , isFull : [] + , remove : [] + } +) + +module.exports = deftype(FixedBuffer, [LinkedList, Number], Buffer) + +function FixedBuffer(size) { + var list = [] + + return Buffer(FixedBuffer, + { add : add + , remove : remove + , isFull : isFull + } + ) + + function add(item) { + !isFull() && list.unshift(item) + } + + function remove() { + return list.pop() + } + + function isFull() { + return list.length >= size + } +} + +Buffer(obj).add() \ No newline at end of file diff --git a/lib/async/chan.js b/lib/async/chan.js new file mode 100644 index 0000000..263a195 --- /dev/null +++ b/lib/async/chan.js @@ -0,0 +1,46 @@ +module.exports = chan + +function chan() { + return create( + { put : { value: put } + , take : { value: take } + } + ) + + function put(value) { + } + + function take() { + } +} + +var create = Object.create + +// var ReadPort = protocol({ +// 'take' : [protocol, { returns: Promise }] +// }) + +// var WritePort = protocol({ +// 'put' : [protocol, Object, { returns: Promise }] +// }) + +// var Channel = protocol({ +// 'isClosed' : [protocol, { returns: Boolean }] +// 'close' : [protocol] +// }) + +// var Channel = protocol({ +// 'take' : { returns: Promise } +// 'put' : { sig: [Object], returns: Promise } +// }) + +// var Channel = protocol({ +// 'take' : { returns: Promise } +// 'put' : { sig: [Object], returns: Promise } +// }) + +// Channel(thing).take() + +// go(function *() { +// var search = yield get('www.google.com').take() +// }) \ No newline at end of file diff --git a/lib/async/go.js b/lib/async/go.js new file mode 100644 index 0000000..cc874a7 --- /dev/null +++ b/lib/async/go.js @@ -0,0 +1,34 @@ +module.exports = require('../variadic')(go) + +function go(process, rest) { + if (isnt(Generator, process)) throw new TypeError('Co-routine must be a generator function') + + var routine = process.apply(undefined, rest) + , error = routine.throw.bind(routine) + , next = routine.next.bind(routine) + + tick(function() { + step(next()) + }) + + function step(state) { + if (is(Promise, state.value) || is(Function, state.value.then)) { + console.log('step:promise', state) + Promise.resolve(state.value).then(next).then(step).catch(error) + } else if (state.done) { + return /* Put state.value on return channel */ + } else { + console.log('step:value', state) + tick(function() { + step(next(state.value)) + }) + } + } +} + +var tick = setImmediate + , comp = require('../compose') + , isnt = require('../isnt') + , is = require('../is') + +var Generator = (function *(){}).constructor \ No newline at end of file diff --git a/lib/async/index.js b/lib/async/index.js new file mode 100644 index 0000000..7c6d6c7 --- /dev/null +++ b/lib/async/index.js @@ -0,0 +1 @@ +module.exports = {} \ No newline at end of file diff --git a/lib/defprop.js b/lib/defprop.js new file mode 100644 index 0000000..55fa0c6 --- /dev/null +++ b/lib/defprop.js @@ -0,0 +1,52 @@ +module.exports = defprop + +function defprop(target, fields, opts) { + if (isnt(target)) throw TypeError("Target must exist.") + if (isnt(fields)) throw TypeError("Fields must exist.") + + opts || (opts = {}) + + var names = Object.getOwnPropertyNames(fields) + + var isMutable = get(opts, "^writable", false) + , isEnum = get(opts, "^enumerable", false) + , isConf = get(opts, "^configurable", false) + , scope = get(opts, "^bind", false) + + return Object.defineProperties(target, + names.reduce(function(p, k) { + var prop = describe(fields, k) + , opt = $(get, opts[k]) + + if (isnt(prop.get) && isnt(prop.set)) { + prop.writable = opt("writable", isMutable) + + when(is(Function, prop.value) && opt("bind", scope), function(scope) { + prop.value = prop.value.bind(scope) + }) + } else { + when(prop.get && opt("writable") && !prop.set, function() { + throw TypeError("The accessor `"+k+"` can't be writable without a setter.") + }) + + when(opt("bind", scope), function(scope) { + prop.get && (prop.get = prop.get.bind(scope)) + prop.set && (prop.set = prop.set.bind(scope)) + }) + } + + prop.enumerable = opt("enumerable", isEnum) + prop.configurable = opt("configurable", isConf) + + return (p[k] = prop), p + }, {}) + ) +} + +var describe = Object.getOwnPropertyDescriptor + , isEmpty = require("./isEmpty") + , isnt = require("./isnt") + , when = require("./when") + , get = require("./get") + , is = require("./is") + , $ = require("./partial") \ No newline at end of file diff --git a/lib/defprotocol.js b/lib/defprotocol.js new file mode 100644 index 0000000..efc74a0 --- /dev/null +++ b/lib/defprotocol.js @@ -0,0 +1,89 @@ +module.exports = defprotocol + +function defprotocol(name, sigs) { + assert(is(String, name), 'Name must be a string.') + assert(is(Object, sigs), 'Signatures must be an object.') + + var map = new WeakMap() + + defprop(Protocol, { toString : constantly('[protocol ' + name + ']') }) + + return Protocol + + function Protocol(target, impl) { + return arguments.length > 1 + ? reify(map, target, sigs, impl) + : fetch(map, target, Protocol) + } +} + +function fetch(map, target, Protocol) { + var T = typeOf(target) + , impl = map.get(T) + + if (!impl) return + + var exec = Object.create(Protocol, {}) + + each(impl, function(sig) { + var name = sig[0] + , fun = sig[1] + + exec[name] = fun.bind(target) + }) + + return exec +} + +function reify(map, target, sigs, impl) { + var T = is(Function, target)? target : typeOf(target) + + assert(not(map.has(T)), 'Protocol already implemented for type ' + type(target)) + + var exec = {} + + map.set(T, exec) + + each(sigs, function(sig) { + var name = sig[0] + , args = sig[1] + , fun = impl[name] + + assert(fun, 'Missing implementation for ' + name) + assert(every(args, $(is, Function)), 'Argument signature must be a function') + + exec[name] = function() { + var vals = new Array(args.length) + , raw = [].slice.call(arguments) + + for (var i = 1; i < args.length; i++) { + vals[i] = args[i](raw) + } + + return fun.apply(this, vals) + } + }) +} + +function typeOf(x) { + if (isnt(x)) { + return nil + } else { + return x.constructor || Object.getPrototypeOf(x) + } +} + +var constantly = require('./constantly') + , variadic = require('./variadic') + , defprop = require('./defprop') + , assert = require('./assert') + , lower = require('./lowerCase') + , every = require('./every') + , each = require('./each') + , type = require('./type') + , isnt = require('./isnt') + , src = require('./src') + , not = require('./not') + , nil = {} + , is = require('./is') + , $ = require('./partial') \ No newline at end of file diff --git a/lib/deftype.js b/lib/deftype.js new file mode 100644 index 0000000..f752a11 --- /dev/null +++ b/lib/deftype.js @@ -0,0 +1,5 @@ +module.exports = deftype + +function deftype() { + +} \ No newline at end of file diff --git a/lib/every.js b/lib/every.js new file mode 100644 index 0000000..19ed952 --- /dev/null +++ b/lib/every.js @@ -0,0 +1,16 @@ +module.exports = every + +function every(coll, it) { + coll = seq(coll) + + while (is(coll)) { + if (!it(coll.first)) return false + coll = coll.rest + } + + return true +} + +var type = require('./type') + , seq = require('./seq') + , is = require('./is') \ No newline at end of file diff --git a/lib/is.js b/lib/is.js index 0836262..457c3ce 100644 --- a/lib/is.js +++ b/lib/is.js @@ -5,6 +5,8 @@ function is(c, x) { return !not(c) } + if (c === x) return true + var cs = lowerCase(c) if (cs === 'null') { @@ -18,7 +20,9 @@ function is(c, x) { } else if (type(c) === 'string') { return cs === type(x) } else { - return is(x) && x.constructor === c || c.isPrototypeOf(x) + return (x != null && x.constructor === c) + || (type(c) === 'function' && x instanceof c) + || c.isPrototypeOf(x) } } diff --git a/lib/nth.js b/lib/nth.js index 45eec16..534d548 100644 --- a/lib/nth.js +++ b/lib/nth.js @@ -1,6 +1,11 @@ module.exports = nth function nth(coll, i) { + if (is(Array, coll)) { + assert(0 <= i && i < coll.length, bounds(i)) + return coll[i] + } + coll = seq(coll) var n = i @@ -9,11 +14,17 @@ function nth(coll, i) { coll = coll.rest } - if (n > 0 || isnt(coll)) throw new RangeError('Index '+i+' is out of bounds.') + assert(n < 0 && coll, bounds(i)) return coll.first } -var isnt = require('./isnt') - , seq = require('./seq') - , is = require('./is') \ No newline at end of file +var assert = require("./assert") + , isnt = require("./isnt") + , seq = require("./seq") + , is = require("./is") + , $ = require("./partial") + +function bounds(i) { + return RangeError("Index "+i+" is out of bounds.") +} \ No newline at end of file diff --git a/lib/src.js b/lib/src.js index f4166a6..1a21417 100644 --- a/lib/src.js +++ b/lib/src.js @@ -10,11 +10,12 @@ function src(x) { return s(x) } -var type = require('./type') +var keys = Object.getOwnPropertyNames + , type = require("./type") , from = - { 'array' : fromArray - , 'string' : fromString - , 'object' : fromObject + { "array" : fromArray + , "string" : fromString + , "object" : fromObject } function toString(x) { @@ -26,11 +27,15 @@ function fromString(x) { } function fromArray(x) { - return '[' + x.map(src).join(',') + ']' + return "[" + x.map(src).join(",") + "]" } function fromObject(x) { - return '{' + Object.keys(x).map(prop).join(',') + '}' + return "{" + keys(x).filter(ignored).map(prop).join(",") + "}" + + function ignored(k) { + return !(k === "constructor" || k === "prototype") + } function prop(k) { return '"' + k + '":' + src(x[k]) diff --git a/lib/type.js b/lib/type.js index 5fcd069..ce474ea 100644 --- a/lib/type.js +++ b/lib/type.js @@ -3,15 +3,24 @@ module.exports = type function type(x) { if (x !== x) return 'nan' - var t = str.call(x).slice(8, -1).toLowerCase() + var t = lower(str.call(x).slice(8, -1)) if (t === 'object') { - // Check if prototype is a function and has a name - var proto = Object.getPrototypeOf(x) - if (type(proto) === 'function' && proto.name) return proto.name + if (x.constructor === Object) { + return t + } else { + var proto = Object.getPrototypeOf(x) + + if (type(proto) === 'function' && proto.name) { + return proto.name + } else if (x.constructor && x.constructor.name) { + return x.constructor.name + } + } } return t } -var str = ({}).toString \ No newline at end of file +var lower = require('./lowerCase') + , str = ({}).toString \ No newline at end of file diff --git a/lib/when.js b/lib/when.js new file mode 100644 index 0000000..1ca798c --- /dev/null +++ b/lib/when.js @@ -0,0 +1,9 @@ +module.exports = when + +function when(cond, body) { + const it = val(cond) + return is(it)? val(body, it) : null +} + +const val = require('./val') + , is = require('./is') \ No newline at end of file diff --git a/package.json b/package.json index 8323107..0c91d9a 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,9 @@ }, "author": "Marcus Stade", "license": "MIT", + "dependencies": { + "feature-detect-generators": "^1.0.1" + }, "devDependencies": { "coveralls": "~2.8.0", "expect.js": "^0.3.1", diff --git a/repl.js b/repl old mode 100644 new mode 100755 similarity index 79% rename from repl.js rename to repl index 9540ad2..51d5af8 --- a/repl.js +++ b/repl @@ -1,3 +1,5 @@ +#!/usr/bin/env node + ;(function() { var global = this , libdir = __dirname + '/lib' @@ -17,4 +19,8 @@ } reload() -}()) \ No newline at end of file +}()) + +var repl = require('repl') + +repl.start({ prompt: 'fun > ', useGlobal: true }) \ No newline at end of file diff --git a/test/and.js b/test/and.js index 95fa90f..e88b9a1 100644 --- a/test/and.js +++ b/test/and.js @@ -18,7 +18,7 @@ describe('`and`', function() { describe('and when that argument is a function', function() { it('should return the value of calling that function', function() { - expect(and(constantly('wibble'))).to.equal('wibble') + expect(and(constantly('wibble'))).to.equal('wibble') }) }) }) @@ -26,24 +26,24 @@ describe('`and`', function() { describe('given two or more arguments', function() { describe('when the values of all arguments are logically true', function() { it('should return the last supplied value', function() { - expect(and(0, 1, 2, 3)).to.equal(3) + expect(and(0, 1, 2, 3)).to.equal(3) }) }) describe('when any one argument is logically false', function() { it('should return the value that was logically false', function() { - expect(and(0, null, 1)).to.equal(null) - expect(and(false, true, 1)).to.equal(false) - expect(and(0, true, undefined)).to.equal(undefined) + expect(and(0, null, 1)).to.equal(null) + expect(and(false, true, 1)).to.equal(false) + expect(and(0, true, undefined)).to.equal(undefined) }) }) describe('when an argument is a function', function() { it('should call the function and evaluate its return value', function() { - var called = false + var called = false - expect(and(0, function() { return (called = true) }, 1, 'yup')).to.equal('yup') - expect(called).to.be.true + expect(and(0, function() { return (called = true) }, 1, 'yup')).to.equal('yup') + expect(called).to.be.true }) }) }) diff --git a/test/call.js b/test/call.js index 796f267..6a8be5c 100644 --- a/test/call.js +++ b/test/call.js @@ -23,7 +23,7 @@ describe('call', function() { describe('when given a function `fn`', function() { describe('and no arguments', function(done) { it('should call the function without arguments', function(done) { - var fn = function() { + var fn = function() { expect(arguments.length).to.equal(0) done() } @@ -33,9 +33,9 @@ describe('call', function() { describe('and `fn` is bound', function() { it('should not affect the binding', function(done) { - var owner = {} + var owner = {} - var fn = function() { + var fn = function() { expect(arguments.length).to.equal(0) expect(this).to.equal(owner) done() @@ -48,7 +48,7 @@ describe('call', function() { describe('and when given arguments', function(done) { it('should call the function with arguments', function(done) { - var fn = function() { + var fn = function() { expect(slice(arguments)).to.eql([1, true, 'wibble']) done() } @@ -58,9 +58,9 @@ describe('call', function() { describe('and `fn` is bound', function() { it('should not affect the binding', function(done) { - var owner = {} + var owner = {} - var fn = function() { + var fn = function() { expect(slice(arguments)).to.eql([1, true, 'wibble']) expect(this).to.equal(owner) done() diff --git a/test/constantly.js b/test/constantly.js index 9887b99..c2fcb88 100644 --- a/test/constantly.js +++ b/test/constantly.js @@ -14,7 +14,7 @@ describe('constantly', function() { ] , function(val) { describe('when called with the value `' + src(val) + '`', function() { - var v = constantly(val) + var v = constantly(val) it('should return a function called `constant`', function() { expect(v.name).to.equal('constant') diff --git a/test/count.js b/test/count.js index 5eb81fc..5ead9ff 100644 --- a/test/count.js +++ b/test/count.js @@ -42,14 +42,14 @@ describe('count', function() { describe('when given a seq', function () { describe('and when the length is defined', function() { it('should return it', function() { - var s = seq([1, 2, 3]) + var s = seq([1, 2, 3]) expect(count(s)).to.equal(3) }) }) describe('but when the length is undefined', function() { it('should return undefined', function() { - var s = seq(Math.random) + var s = seq(Math.random) expect(count(s)).to.equal(undefined) }) }) diff --git a/test/defprop.js b/test/defprop.js new file mode 100644 index 0000000..b709db5 --- /dev/null +++ b/test/defprop.js @@ -0,0 +1,142 @@ +var defprop = require("../lib/defprop") + , expect = require("must") + , desc = Object.getOwnPropertyDescriptor + , each = require("../lib/each") + , $ = require("../lib/partial") + +describe("`defprop`", function() { + describe("given a target object", function() { + describe("which doesn't exist", function() { + it("should throw a TypeError", function() { + expect($(defprop, null)).to.throw(TypeError) + }) + }) + + describe("and an invalid set of fields", function() { + it("should throw a TypeError", function() { + each([null, undefined, false], function(fields) { + expect($(defprop, {}, fields)).to.throw(TypeError) + }) + }) + }) + + describe("and a valid set of fields", function() { + it("should mutate the target object", function() { + var target = {} + + expect(target).to.not.have.property('foo') + defprop(target, { foo: 'wobble' }) + expect(target).to.have.property('foo', 'wobble') + }) + + it("should return the mutated target object", function() { + var target = {} + + expect(target).to.not.have.property('foo') + expect(defprop(target, { foo: 'wobble' })).to.equal(target) + expect(target).to.have.property('foo', 'wobble') + }) + + it("should not make the fields configurable", function() { + var target = defprop({}, { foo: 'wobble' }) + expect(desc(target, 'foo')).to.have.property('configurable', false) + }) + + describe("when a field is a getter and/or setter, or the field is a function", function() { + describe("and the `^bind` option is set to an object", function() { + it("should bind the field to the object", function() { + var scope = {} + var target = defprop({}, + { get prop() { return this.wibble } + , set prop(x) { this.wibble = x } + , get scope() { return this } + , fun: function() { return this } + } + , { "^bind" : scope } + ) + + expect(target).to.not.equal(scope) + expect(target.scope).to.equal(scope) + + expect(target.prop).to.equal(undefined) + expect(scope).to.not.have.property('wibble') + expect(target).to.not.have.property('wibble') + + target.prop = 'wobble' + expect(target.prop).to.equal('wobble') + expect(scope).to.have.property('wibble', 'wobble') + expect(target).to.not.have.property('wibble') + + expect(target.fun()).to.equal(scope) + }) + }) + }) + + describe("when a field has no options", function() { + it("should make the field immutable", function() { + var target = defprop({}, { foo: 1 }) + expect(desc(target, 'foo')).to.have.property('writable', false) + }) + + it("should not make the field enumerable", function() { + var target = defprop({}, { foo: 1 }) + expect(desc(target, 'foo')).to.have.property('enumerable', false) + }) + }) + + describe("when a field is set to be writable", function() { + describe("and is just a getter", function() { + it("should throw a TypeError", function() { + var field = { get field() {} } + , opt = { field: { writable : true } } + + expect($(defprop, {}, field, opt)).to.throw(TypeError) + }) + }) + + describe("but when it is any other value", function() { + it("should make the field mutable", function() { + each( + [ { get field() {}, set field(val) {} } + , { set field(val) {} } + , { field: 1 } + , { field: 'wibble' } + , { field: function() {} } + ] + , function(field) { + var target = {} + defprop(target, field, { field: { writable: true } }) + + var prop = desc(target, 'field') + + if (prop.value) { + expect(prop).to.have.property('writable', true) + } else { + expect(prop.set).to.be.ok + } + } + ) + }) + }) + }) + + describe("when a field is set to be enumerable", function() { + it("should make the field enumerable", function() { + each( + [ { get field() {}, set field(val) {} } + , { set field(val) {} } + , { field: 1 } + , { field: 'wibble' } + , { field: function() {} } + ] + , function(field) { + var target = {} + defprop(target, field, { field: { enumerable: true } }) + expect(target).to.have.property('field') + } + ) + }) + }) + }) + }) +}) \ No newline at end of file diff --git a/test/defprotocol.js b/test/defprotocol.js new file mode 100644 index 0000000..66fac02 --- /dev/null +++ b/test/defprotocol.js @@ -0,0 +1,50 @@ +var expect = require('must') + , defprotocol = require('../lib/defprotocol') + +describe('defprotocol', function() { + describe('when given an object', function() { + it('should verify the signature of each key') + + describe('and all signatures are ok', function() { + var Seq + + it('should return a protocol', function() { + Seq = defprotocol('Seq', + { first : [] + , rest : [] + }) + + expect(Seq).to.be.a(Function) + expect(Seq.name).to.equal('Protocol') + }) + + describe('and when implemented', function() { + it('should do what it says on the tin', function() { + var Node = function(data, left, right){ + this.data = data + this.left = left + this.right = right + } + + Seq(Node, + { first : function() { return this.data } + , rest : function() { return [this.left, this.right] } + }) + + var one = new Node(1) + , two = new Node(2) + , tre = new Node(3) + + one.left = tre + one.right = two + two.left = one + two.right = tre + tre.left = two + tre.right = one + + expect(Seq(one).first()).to.equal(1) + }) + }) + }) + }) +}) \ No newline at end of file diff --git a/test/each.js b/test/each.js index e739982..4965907 100644 --- a/test/each.js +++ b/test/each.js @@ -7,7 +7,7 @@ describe('each', function() { describe('when given a seq', function() { it('should call `fn` once for each item', function() { var l = [1, 2, 3] - , s = seq(l) + , s = seq(l) var i = 0 diff --git a/test/every.js b/test/every.js new file mode 100644 index 0000000..5edd408 --- /dev/null +++ b/test/every.js @@ -0,0 +1,48 @@ +var expect = require('must') + , every = require('../lib/every') + , each = require('../lib/each') + , src = require('../lib/src') + , seq = require('../lib/seq') + +describe('every', function() { + describe('when given a seq', function() { + it('should call `fn` so long as it keeps returning `true`', function() { + var l = [1, 2, 3] + , s = seq(l) + + var i = 0 + + var fn = function(x) { + expect(x).to.equal(l[i++]) + return true + } + + every(s, fn) + expect(i).to.equal(l.length) + }) + + it('should stop calling `fn` when it returns `false`', function() { + var l = [1, 2, 3] + , s = seq(l) + + var i = 0 + + var fn = function(x) { + if (x < 3) { + expect(x).to.equal(l[i++]) + return true + } + } + + every(s, fn) + expect(i).to.equal(2) + }) + }) + + describe('when given an empty sequence', function() { + it('should do nothing', function() { + var fn = function(x) { throw new Error("shouldn't get here!") } + each(null, fn) + }) + }) +}) \ No newline at end of file diff --git a/test/funkis.js b/test/funkis.js index d3021fc..ab512b6 100644 --- a/test/funkis.js +++ b/test/funkis.js @@ -1,14 +1,16 @@ -var fs = require('fs') - , path = require('path') - , each = require('../lib/each') - , funkis = require('../') +var funkis = require('../') , expect = require('must') + , each = require('../lib/each') + , path = require('path') + , fs = require('fs') describe('funkis', function() { it('should export every function in lib/', function() { var names = fs.readdirSync(path.join(__dirname, '../lib')).map(function(file) { - return path.basename(file, '.js') - }) + if (path.extname(file) === '.js') { + return path.basename(file, '.js') + } + }).filter(function(x) { return !!x }) each(names, function(name) { expect(funkis).to.have.property(name) diff --git a/test/identity.js b/test/identity.js index a76fcaa..6a7e1ac 100644 --- a/test/identity.js +++ b/test/identity.js @@ -21,7 +21,7 @@ describe('identity', function() { describe('when given `' + str + '`', function() { it('should return `' + str + '`', function() { - var id = identity(x) + var id = identity(x) if (x !== x) { // NaN *sigh* expect(id).to.not.equal(id) diff --git a/test/is.js b/test/is.js index 8147df1..2562f95 100644 --- a/test/is.js +++ b/test/is.js @@ -1,9 +1,13 @@ -var expect = require('must') - , range = require('../lib/range') - , each = require('../lib/each') - , seq = require('../lib/seq') - , src = require('../lib/src') - , is = require('../lib/is') +var defprotocol = require('../lib/defprotocol') + , assert = require('../lib/assert') + , expect = require('must') + , range = require('../lib/range') + , each = require('../lib/each') + , type = require('../lib/type') + , isnt = require('../lib/isnt') + , seq = require('../lib/seq') + , src = require('../lib/src') + , is = require('../lib/is') describe('is', function() { describe('when given a single argument `x`', function() { @@ -29,6 +33,56 @@ describe('is', function() { }) }) + describe('when given two arguments that are identical', function() { + it('should return `true`', function() { + each( + [ true, false, + , -1, 0, 1, 3.14 + , '', 'wibble' + , [], [1, 2, 3] + , {}, { foo: 1 } + , Function + , null, undefined + ] + , function(x) { + expect(is(x, x)).to.be.true + } + ) + }) + }) + + describe('when testing for booleans', function() { + each([ true, false ], function(x) { + describe('and when `x = '+x+'`', function() { + it('should return true', function() { + expect(is(Boolean, x)).to.be.true + expect(is('boolean', x)).to.be.true + }) + }) + } + ) + + each( + [ NaN + , '', 'wobble', 'true', 'false' + , {} + , [] + , -1, 0, 1, 3.14 + , function() {} + , undefined, null + ] + , + function(x) { + describe('and when `x = '+src(x)+'`', function() { + it('should return false', function() { + expect(is(Boolean, x)).to.be.false + expect(is('boolean', x)).to.be.false + }) + }) + } + ) + }) + describe('when testing for numbers', function() { each( [ 0 @@ -45,7 +99,7 @@ describe('is', function() { ] , function(x) { - var n = is(Array, x)? x[1] : src(x) + var n = is(Array, x)? x[1] : src(x) is(Array, x) && (x = x[0]) @@ -202,7 +256,7 @@ describe('is', function() { ] , function(x) { - var n = is(Array, x)? x[1] : src(x) + var n = is(Array, x)? x[1] : src(x) is(Array, x) && (x = x[0]) @@ -234,7 +288,7 @@ describe('is', function() { ] , function(x) { - var n = is(Array, x)? x[1] : src(x) + var n = is(Array, x)? x[1] : src(x) is(Array, x) && (x = x[0]) @@ -263,7 +317,7 @@ describe('is', function() { ] , function(x) { - var n = is(Array, x)? x[1] : src(x) + var n = is(Array, x)? x[1] : src(x) is(Array, x) && (x = x[0]) @@ -363,4 +417,53 @@ describe('is', function() { } ) }) + + describe('when testing for protocols', function() { + describe('and when `x` implements the protocol', function() { + it('should return true', function() { + var Test = defprotocol('Test', {}) + + each( + [ [Number, 3] + , [Array, []] + , [Function, function() {}] + , [Boolean, true] + , [String, 'hello'] + , [Object, {}] + , [Foo, new Foo] + ] + , function (data) { + var T = data[0] + , i = data[1] + + Test(T, {}) + + assert(is(Test, Test(i)), 'Expected type `' + T.name + '` to implement protocol') + } + ) + }) + + function Foo() {} + }) + + describe('but when `x` does not implement the protocol', function() { + it('should return false', function() { + var Test = defprotocol('Test', {}) + + each( + [ 3 + , [] + , function() {} + , true + , 'hello' + , {} + , new (function Foo() {}) + ] + , function (i) { + assert(isnt(Test, Test(i)), 'Expected type `' + type(i) + '` to not implement protocol') + } + ) + }) + }) + }) }) \ No newline at end of file diff --git a/test/nth.js b/test/nth.js index a628f34..7f0d6fa 100644 --- a/test/nth.js +++ b/test/nth.js @@ -1,15 +1,19 @@ var partial = require('../lib/partial') , expect = require('must') + , range = require('../lib/range') , nth = require('../lib/nth') + , $ = require('../lib/partial') -describe('nth', function() { - describe('when given a seqable object and an index', function() { - it('should return the item at the given index', function() { +describe("`nth`", function() { + describe("when given a seqable object and an index", function() { + it("should return the item at the given index", function() { expect(nth([1, 2, 3], 1)).to.equal(2) + expect(nth(range(), 1)).to.equal(1) }) - it('should throw an error if the index is out of bounds', function() { - expect(partial(nth, [1, 2, 3], 3)).to.throw(RangeError) + it("should throw an error if the index is out of bounds", function() { + expect($(nth, [1, 2, 3], 3)).to.throw(RangeError) + expect($(nth, range(3), 5)).to.throw(RangeError) }) }) }) \ No newline at end of file diff --git a/test/seq.js b/test/seq.js index 25d1191..b25aa4c 100644 --- a/test/seq.js +++ b/test/seq.js @@ -32,7 +32,7 @@ describe('seq', function() { , [undefined] ] , function(arr) { - var s = seq(arr) + var s = seq(arr) var i = 0 diff --git a/test/slice.js b/test/slice.js index 8a7b9e8..e846ecb 100644 --- a/test/slice.js +++ b/test/slice.js @@ -16,7 +16,7 @@ describe('slice', function() { describe('and no other arguments', function() { it('should return an exact copy', function() { - var copy = slice(arr) + var copy = slice(arr) expect(copy).to.not.equal(arr) expect(copy).to.eql([1, 2, 3, 4, 5]) diff --git a/test/take.js b/test/take.js index c137d48..4506317 100644 --- a/test/take.js +++ b/test/take.js @@ -16,7 +16,7 @@ describe('take', function() { , function(coll) { describe('and when given the collection `' + src(coll) + '`', function() { it('should return a lazy sequence of the first `n` items', function() { - var s = take(3, coll) + var s = take(3, coll) expect(vec(s)).to.eql(slice(coll, 0, 3)) }) diff --git a/test/thunk.js b/test/thunk.js index 92deea5..805bf51 100644 --- a/test/thunk.js +++ b/test/thunk.js @@ -8,7 +8,7 @@ describe('thunk', function() { describe('when given a function `fn`', function() { describe('and no parameters', function() { it('should return a zero arity thunk', function() { - var th = thunk(function() {}) + var th = thunk(function() {}) expect(th).to.be.a(Function) expect(th.length).to.equal(0) @@ -17,7 +17,7 @@ describe('thunk', function() { describe('when called', function() { it('should call `fn` with no parameters', function(done) { - var th = thunk(function() { + var th = thunk(function() { expect(arguments.length).to.equal(0) done() }) @@ -26,7 +26,7 @@ describe('thunk', function() { }) it('should ignore any parameters given to it', function (done) { - var th = thunk(function() { + var th = thunk(function() { expect(arguments.length).to.equal(0) done() }) @@ -35,7 +35,7 @@ describe('thunk', function() { }) it('should return whatever `fn` returns', function() { - var th = thunk(function() { return 'hello' }) + var th = thunk(function() { return 'hello' }) expect(th()).to.equal('hello') }) }) @@ -45,7 +45,7 @@ describe('thunk', function() { , function(param) { describe('and a single parameter `' + src(param) + '`', function() { it('should return a zero arity thunk', function() { - var th = thunk(function() {}) + var th = thunk(function() {}) expect(th).to.be.a(Function) expect(th.length).to.equal(0) @@ -53,7 +53,7 @@ describe('thunk', function() { describe('when called', function() { it('should call `fn` with `' + src(param) + '` as the sole parameter', function(done) { - var th = thunk(function(a) { + var th = thunk(function(a) { expect(arguments.length).to.equal(1) expect(a).to.equal(param) done() @@ -63,7 +63,7 @@ describe('thunk', function() { }) it('should ignore any parameters given to it', function (done) { - var th = thunk(function(a) { + var th = thunk(function(a) { expect(arguments.length).to.equal(1) expect(a).to.equal(param) done() @@ -73,7 +73,7 @@ describe('thunk', function() { }) it('should return whatever `fn` returns', function() { - var th = thunk(function(a) { return a }, 'hello') + var th = thunk(function(a) { return a }, 'hello') expect(th()).to.equal('hello') }) }) @@ -83,7 +83,7 @@ describe('thunk', function() { describe('and more than one parameter', function() { it('should return a zero arity function', function() { - var th = thunk(function() {}, 1, 2, 3) + var th = thunk(function() {}, 1, 2, 3) expect(th).to.be.a(Function) expect(th.length).to.equal(0) expect(th.name).to.equal('thunk') @@ -91,7 +91,7 @@ describe('thunk', function() { describe('and when called', function() { it('should call the given function, with the given parameters', function(done) { - var th = thunk(function(a, b, c) { + var th = thunk(function(a, b, c) { expect(arguments.length).to.equal(3) expect(a).to.equal(1) expect(b).to.equal(false) @@ -103,7 +103,7 @@ describe('thunk', function() { }) it('should ignore any parameters given to it', function (done) { - var th = thunk(function(a, b, c) { + var th = thunk(function(a, b, c) { expect(arguments.length).to.equal(3) expect(a).to.equal(1) expect(b).to.equal(false) @@ -115,7 +115,7 @@ describe('thunk', function() { }) it('should return whatever `fn` returns', function() { - var th = thunk(function(a, b) { return a + ' ' + b }, 'hello', 'world') + var th = thunk(function(a, b) { return a + ' ' + b }, 'hello', 'world') expect(th()).to.equal('hello world') }) }) @@ -126,12 +126,12 @@ describe('thunk', function() { describe('when given an unbound function `fn`', function() { describe('and when called', function() { it('should bind `this` of `fn` to the global object', function(done) { - var noargs = thunk(function() { + var noargs = thunk(function() { expect(this).to.equal(global) args() }) - var args = thunk(function(a, b, c) { + var args = thunk(function(a, b, c) { expect(this).to.equal(global) expect(a).to.equal(1) expect(b).to.equal(2) @@ -147,14 +147,14 @@ describe('thunk', function() { describe('when given a bound function `fn`', function() { describe('and when called', function() { it('should not affect the `this` of `fn`', function(done) { - var that = {} + var that = {} - var noargs = thunk(function() { + var noargs = thunk(function() { expect(this).to.equal(that) args() }.bind(that)) - var args = thunk(function(a, b, c) { + var args = thunk(function(a, b, c) { expect(this).to.equal(that) expect(a).to.equal(1) expect(b).to.equal(2) @@ -171,14 +171,14 @@ describe('thunk', function() { describe('and given an unbound function `fn`', function() { describe('and when called', function() { it('should bind `this` of `fn` to the thunk\'s `this`', function(done) { - var that = {} + var that = {} - var noargs = thunk(function() { + var noargs = thunk(function() { expect(this).to.equal(that) args() }).bind(that) - var args = thunk(function(a, b, c) { + var args = thunk(function(a, b, c) { expect(this).to.equal(that) expect(a).to.equal(1) expect(b).to.equal(2) @@ -193,14 +193,14 @@ describe('thunk', function() { describe('and given a bound function `fn`', function() { it('should not affect the `this` of `fn`', function(done) { - var that = {} + var that = {} - var noargs = thunk(function() { + var noargs = thunk(function() { expect(this).to.not.equal(that) args() }.bind({})).bind(that) - var args = thunk(function(a, b, c) { + var args = thunk(function(a, b, c) { expect(this).to.not.equal(that) expect(a).to.equal(1) expect(b).to.equal(2) @@ -238,7 +238,7 @@ describe('thunk', function() { ] , function(val) { - var th = thunk(val) + var th = thunk(val) expect(th()).to.equal(val) } ) diff --git a/test/type.js b/test/type.js index 8dead7a..a46c67e 100644 --- a/test/type.js +++ b/test/type.js @@ -2,6 +2,7 @@ var expect = require('must') , range = require('../lib/range') , type = require('../lib/type') , seq = require('../lib/seq') + , src = require('../lib/src') describe('type', function() { [ ['string', 'hello'] @@ -14,8 +15,9 @@ describe('type', function() { , ['undefined', void 0] , ['seq', seq([1, 2, 3])] , ['nan', NaN] + , ['Foo', new (function Foo() {})] ].forEach(function(test) { - describe('when called with ' + test[1], function() { + describe('when called with ' + src(test[1]), function() { it('should return \'' + test[0] + '\'', function() { expect(type(test[1])).to.equal(test[0]) }) diff --git a/test/vec.js b/test/vec.js index 00b96bd..f1be7b9 100644 --- a/test/vec.js +++ b/test/vec.js @@ -26,7 +26,7 @@ describe('vec', function() { ], function(t) { describe('when given the ' + type(t[0]) + ' `' + src(t[0]) + '`', function() { it('should turn it into `' + src(t[1]) + '`', function() { - var v = vec(t[0]) + var v = vec(t[0]) expect(v).to.eql(t[1]) }) }) diff --git a/test/when.js b/test/when.js new file mode 100644 index 0000000..7ab0f46 --- /dev/null +++ b/test/when.js @@ -0,0 +1,51 @@ +const constantly = require('../lib/constantly') + , expect = require('must') + , when = require('../lib/when') + +describe('`when`', function() { + describe('given a `test` and a `body`', function() { + describe('when `test` is a function', function() { + describe('and when calling `test` returns logically true', function () { + describe('when `body` is a function', function() { + it('then return the value of calling `body`', function() { + const tru = constantly(true) + , wbl = constantly('wibble') + + expect(when(tru, wbl)).to.equal('wibble') + }) + }) + + describe('when `body` is not a function', function() { + it('then return `body`', function() { + const tru = constantly(true) + expect(when(tru, 'wibble')).to.equal('wibble') + }) + }) + }) + + describe('but when calling `test` returns logically false', function() { + it('then return `undefined`', function() { + const no = constantly(null) + expect(when(no, 'wibble')).to.equal.undefined + }) + }) + }) + + describe('when `test` is not a function', function() { + describe('but when the value is logically true', function() { + describe('when `body` is a function', function() { + it('then return the value of calling `body`', function() { + const wbl = constantly('wibble') + expect(when(0, wbl)).to.equal('wibble') + }) + }) + + describe('when `body` is not a function', function() { + it('then return `body`', function() { + expect(when(1, 'wibble')).to.equal('wibble') + }) + }) + }) + }) + }) +}) \ No newline at end of file