From dcb7bfff564f55c6b639559ed46e062cf2830c15 Mon Sep 17 00:00:00 2001 From: Marcus Stade Date: Fri, 18 Apr 2014 11:47:25 +0200 Subject: [PATCH 01/23] Added `val` Returns `x()` if `x` is a function; otherwise returns `x`. --- lib/val.js | 7 +++++++ test/val.js | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 lib/val.js create mode 100644 test/val.js diff --git a/lib/val.js b/lib/val.js new file mode 100644 index 0000000..b68ab8d --- /dev/null +++ b/lib/val.js @@ -0,0 +1,7 @@ +module.exports = val + +function val(x) { + return is(Function, x)? x() : x +} + +const is = require('./is') \ No newline at end of file diff --git a/test/val.js b/test/val.js new file mode 100644 index 0000000..0e1e129 --- /dev/null +++ b/test/val.js @@ -0,0 +1,26 @@ +const constantly = require('../lib/constantly') + , expect = require('chai').expect + , type = require('../lib/type') + , val = require('../lib/val') + +describe('`val`', function() { + describe('given a value `x`', function() { + describe('when `x` is a function', function() { + it('should return the value of calling `x`', function() { + const three = constantly(3) + + expect(type(three)).to.equal('function') + expect(val(three)).to.equal(3) + }) + }) + + describe('when `x` is not a function', function() { + it('should return `x`', function() { + const x = {} + + expect(type(x)).to.not.equal('function') + expect(val(x)).to.equal(x) + }) + }) + }) +}) \ No newline at end of file From f12300400f1724b308c0b870900d3adb06316794 Mon Sep 17 00:00:00 2001 From: Marcus Stade Date: Fri, 18 Apr 2014 15:10:48 +0200 Subject: [PATCH 02/23] Improved `variadic` tests and change behavior of `rest` Previously, `rest` was always defined and passed, even if the original call didn't supply enough arguments to reach `rest`. This poses some problems when you expect the length of `arguments` to match that of the original call to the variadic function, so now `rest` is only supplied if there are actual arguments to fill it up with. This means it'll be undefined otherwise, and `arguments` will be identical to the original call. Additionally, I changed `variadic` so that it'll correctly report `length` for the wrapped function, provided the wrapped function `length` is 26 or below. I'd be hard pressed to remember ever seeing a function with more than even 10 parameters, but still. This isn't an ideal solution of course, but probably hits home 99.99% of the time which is enough. --- lib/variadic.js | 58 +++++++++++++++++------- test/variadic.js | 114 +++++++++++++++++++++++++++++++---------------- 2 files changed, 117 insertions(+), 55 deletions(-) diff --git a/lib/variadic.js b/lib/variadic.js index f2d2e27..4634a17 100644 --- a/lib/variadic.js +++ b/lib/variadic.js @@ -2,23 +2,49 @@ module.exports = variadic function variadic(fn) { if (isnt(Function, fn)) throw new TypeError('Parameter `fn` must be a function.') + + const argc = fn.length - 1 - const argc = fn.length - 1 - - if (argc) { - return function variadic() { - const head = slice(arguments, 0, argc) - , tail = slice(arguments, argc) - - return fn.apply(this, head.concat([tail])) - } - } else { - return function variadic() { - const rest = slice(arguments) - return fn.call(this, rest) - } + switch (argc) { + case -1 : return fn + case 0 : return function (rest) { return apply(fn, this, argc, slice(arguments)) } + case 1 : return function (a, rest) { return apply(fn, this, argc, slice(arguments)) } + case 2 : return function (a, b, rest) { return apply(fn, this, argc, slice(arguments)) } + case 3 : return function (a, b, c, rest) { return apply(fn, this, argc, slice(arguments)) } + case 4 : return function (a, b, c, d, rest) { return apply(fn, this, argc, slice(arguments)) } + case 5 : return function (a, b, c, d, e, rest) { return apply(fn, this, argc, slice(arguments)) } + case 6 : return function (a, b, c, d, e, f, rest) { return apply(fn, this, argc, slice(arguments)) } + case 7 : return function (a, b, c, d, e, f, g, rest) { return apply(fn, this, argc, slice(arguments)) } + case 8 : return function (a, b, c, d, e, f, g, h, rest) { return apply(fn, this, argc, slice(arguments)) } + case 9 : return function (a, b, c, d, e, f, g, h, i, rest) { return apply(fn, this, argc, slice(arguments)) } + case 10 : return function (a, b, c, d, e, f, g, h, i, j, rest) { return apply(fn, this, argc, slice(arguments)) } + case 11 : return function (a, b, c, d, e, f, g, h, i, j, k, rest) { return apply(fn, this, argc, slice(arguments)) } + case 12 : return function (a, b, c, d, e, f, g, h, i, j, k, l, rest) { return apply(fn, this, argc, slice(arguments)) } + case 13 : return function (a, b, c, d, e, f, g, h, i, j, k, l, m, rest) { return apply(fn, this, argc, slice(arguments)) } + case 14 : return function (a, b, c, d, e, f, g, h, i, j, k, l, m, n, rest) { return apply(fn, this, argc, slice(arguments)) } + case 15 : return function (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, rest) { return apply(fn, this, argc, slice(arguments)) } + case 16 : return function (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, rest) { return apply(fn, this, argc, slice(arguments)) } + case 17 : return function (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, rest) { return apply(fn, this, argc, slice(arguments)) } + case 18 : return function (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, rest) { return apply(fn, this, argc, slice(arguments)) } + case 19 : return function (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, rest) { return apply(fn, this, argc, slice(arguments)) } + case 20 : return function (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, rest) { return apply(fn, this, argc, slice(arguments)) } + case 21 : return function (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, rest) { return apply(fn, this, argc, slice(arguments)) } + case 22 : return function (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, rest) { return apply(fn, this, argc, slice(arguments)) } + case 23 : return function (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, rest) { return apply(fn, this, argc, slice(arguments)) } + case 24 : return function (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, rest) { return apply(fn, this, argc, slice(arguments)) } + case 25 : return function (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, rest) { return apply(fn, this, argc, slice(arguments)) } + default : return function (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, rest) { return apply(fn, this, argc, slice(arguments)) } } } -const slice = require('./slice') - , isnt = require('./isnt') \ No newline at end of file +function apply(fn, host, argc, argv) { + const rest = slice(argv, argc) + argv = slice(argv, 0, argc) + return isEmpty(rest)? fn.apply(host, argv) : fn.apply(host, argv.concat([rest])) +} + +const isEmpty = require('./isEmpty') + , slice = require('./slice') + , isnt = require('./isnt') + , min = require('./min') + , max = Math.max \ No newline at end of file diff --git a/test/variadic.js b/test/variadic.js index 5fe7def..9db8d6f 100644 --- a/test/variadic.js +++ b/test/variadic.js @@ -1,60 +1,96 @@ const variadic = require('../lib/variadic') , expect = require('chai').expect + , thunk = require('../lib/thunk') + , range = require('../lib/range') , slice = require('../lib/slice') , each = require('../lib/each') , src = require('../lib/src') -describe('variadic', function() { - describe('when called with a non-function parameter', function() { - it('should throw a TypeError', function() { - each( - [ 1, 0 - , '', 'hello' - , true, false - , null, undefined - ] - , function(test) { - expect(function() { variadic(test) }).to.throw(TypeError) - } - ) +describe('`variadic`', function() { + describe('given an argument `fn`', function() { + describe('when it is not a function', function() { + it('should throw a `TypeError`', function() { + each( + [ 1, 0 + , '', 'hello' + , true, false + , null, undefined + ] + , function(fn) { + + expect(function() { variadic(fn) }).to.throw(TypeError) + } + ) + }) }) - }) - var verify + describe('when it is a function', function() { + describe('of arity 0', function() { + it('should just return `fn`', function() { + const fn = function() {} + expect(variadic(fn)).to.equal(fn) + }) + }) - each( - [ [function(rest) { verify(arguments) }, [1, 2, 3], ['foo', false], [function() {}], []] - , [function foo(a, b, rest) { verify(arguments) }, [1, 2, 'hello', 'world!'], ['foo', 'bar', 'baz'], [1, 2]] - ] - , - function(test) { - const fn = test[0] + each(range(1, 26), function(n) { + describe('of arity '+n, function() { + const sig = slice('abcdefghijklmnopqrstuvwxyz', 0, n - 1).concat('rest') - describe('when given `' + src(fn) + '`', function() { - const varfn = variadic(fn) + it('should return a '+n+' arity function', function() { + const fn = Function.apply(Function, sig.concat('return')) - each(test.slice(1), function(args) { - const argsrc = args.length? '`' + src(args).slice(1, -1) + '`' : 'nothing' + expect(fn).to.have.length(n) + expect(variadic(fn)).to.have.length(n) + }) - describe('and when called with ' + argsrc, function() { - const expHead = slice(args, 0, fn.length - 1) - , expRest = slice(args, fn.length - 1) + describe('that when called with fewer than '+n+' arguments', function() { + it('should leave `rest` undefined', function() { + var args = slice(sig, 0, -1) - it('should set rest to `' + src(expRest) + '`', function(done) { - verify = function(actual) { - const head = slice(actual, 0, -1) - , rest = actual[actual.length - 1] + while (args.length) { + var fn = Function.apply(Function, sig.concat( + [ 'this.expect(arguments).to.have.length('+args.length+')' + , 'this.expect(rest).to.be.undefined' + ].join('\n') + )) - expect(head).to.eql(expHead) - expect(rest).to.eql(expRest) - done() + variadic(fn).apply({ expect: expect }, args) + args = slice(args, 0, -1) } + }) + }) + + describe('but when called with '+n+' arguments', function() { + it('should put the last argument in `rest`', function() { + const args = slice(sig, 0, -1).concat('wibble') - varfn.apply(null, args) + const fn = Function.apply(Function, sig.concat( + [ 'this.expect(arguments).to.have.length('+n+')' + , 'this.expect(rest).to.have.length(1)' + , 'this.expect(rest).to.contain("wibble")' + ].join('\n') + )) + + variadic(fn).apply({ expect: expect }, args) + }) + }) + + describe('and when called with more than '+n+' arguments', function() { + it('should put all variadic arguments in `rest`', function() { + const args = slice(sig, 0, -1).concat('wibble', 'wobble', 'bob') + + const fn = Function.apply(Function, sig.concat( + [ 'this.expect(arguments).to.have.length('+n+')' + , 'this.expect(rest).to.have.length(3)' + , 'this.expect(rest).to.eql(["wibble", "wobble", "bob"])' + ].join('\n') + )) + + variadic(fn).apply({ expect: expect }, args) }) }) }) }) - } - ) + }) + }) }) \ No newline at end of file From 5d0c7f8d7fe5a6979114d9be38c7128da7eda329 Mon Sep 17 00:00:00 2001 From: Marcus Stade Date: Fri, 18 Apr 2014 15:32:32 +0200 Subject: [PATCH 03/23] Added `and` for logical testing Will return the last value if all values are logically true; or the first logically false value. --- lib/and.js | 20 ++++++++++++++++++++ test/and.js | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 lib/and.js create mode 100644 test/and.js diff --git a/lib/and.js b/lib/and.js new file mode 100644 index 0000000..3b5c13e --- /dev/null +++ b/lib/and.js @@ -0,0 +1,20 @@ +module.exports = require('./variadic')(and) + +function and(x, rest) { + if (isEmpty(arguments)) return true + + var result = val(x) + + if (isnt(result) || isnt(rest)) return result + + rest.every(function(x) { + return is(result = val(x)) + }) + + return result +} + +const isEmpty = require('./isEmpty') + , isnt = require('./isnt') + , val = require('./val') + , is = require('./is') \ No newline at end of file diff --git a/test/and.js b/test/and.js new file mode 100644 index 0000000..fee5e6e --- /dev/null +++ b/test/and.js @@ -0,0 +1,50 @@ +const constantly = require('../lib/constantly') + , expect = require('chai').expect + , and = require('../lib/and') + +describe('`and`', function() { + describe('given zero arguments', function() { + it('should return `true`', function() { + expect(and()).to.equal(true) + }) + }) + + describe('given one argument', function() { + it('should return the value of that argument', function() { + expect(and(1)).to.equal(1) + expect(and(0)).to.equal(0) + expect(and(false)).to.equal(false) + }) + + 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') + }) + }) + }) + + 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) + }) + }) + + 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) + }) + }) + + describe('when an argument is a function', function() { + it('should call the function and evaluate its return value', function() { + var called = false + + expect(and(0, function() { return (called = true) }, 1, 'yup')).to.equal('yup') + expect(called).to.be.true + }) + }) + }) +}) \ No newline at end of file From ea41b2e35d6cc33e592627f626a58d96f9f4a003 Mon Sep 17 00:00:00 2001 From: Marcus Stade Date: Thu, 24 Apr 2014 19:28:29 +0200 Subject: [PATCH 04/23] Added `get` for getting the value of a property Very useful function for getting a property value out of an object. If the object is undefined, will return `notFound` which defaults to `undefined`. If the property doesn't exist on the target object, will return `notFound` as well. --- lib/get.js | 8 ++++++++ test/get.js | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 lib/get.js create mode 100644 test/get.js diff --git a/lib/get.js b/lib/get.js new file mode 100644 index 0000000..2afe144 --- /dev/null +++ b/lib/get.js @@ -0,0 +1,8 @@ +module.exports = get + +function get(map, key, notFound) { + const val = is(map)? map[key] : undefined + return is(undefined, val)? notFound : val +} + +const is = require('./is') \ No newline at end of file diff --git a/test/get.js b/test/get.js new file mode 100644 index 0000000..ff9d1eb --- /dev/null +++ b/test/get.js @@ -0,0 +1,52 @@ +const partial = require('../lib/partial') + , expect = require('chai').expect + , each = require('../lib/each') + , get = require('../lib/get') + +describe('`get`', function() { + describe('when given two parameters: `map` and `key`;', function() { + describe('and when `map` has `key`', function() { + it('should return its value', function () { + const map = { foo : 'wibble' } + expect(get(map, 'foo')).to.equal('wibble') + }) + }) + + describe('but when `map` does not have `key`', function() { + it('should return `undefined`', function () { + const map = { foo : 'wibble' } + expect(get(map, 'bar')).to.equal(undefined) + }) + }) + + describe('or when `map` is logically false', function() { + it('should return `undefined`', partial(each, [null, false, undefined], function (nil) { + expect(get(nil, 'whatever')).to.equal(undefined) + })) + }) + }) + + describe('when given three parameters: `map`, `key`, and `notFound`;', function() { + const nope = '¯\(º_o)/¯' + + describe('and when `map` has `key`', function() { + it('should return its value', function () { + const map = { foo : 'wibble' } + expect(get(map, 'foo', nope)).to.equal('wibble') + }) + }) + + describe('but when `map` does not have `key`', function() { + it('should return `notFound`', function () { + const map = { foo : 'wibble' } + expect(get(map, 'bar', nope)).to.equal(nope) + }) + }) + + describe('or when `map` is logically false', function() { + it('should return `notFound`', partial(each, [null, false, undefined], function (nil) { + expect(get(nil, 'whatever', nope)).to.equal(nope) + })) + }) + }) +}) \ No newline at end of file From 507a87654747c66dc14461d5bd362cb08fbabfb8 Mon Sep 17 00:00:00 2001 From: Marcus Stade Date: Thu, 24 Apr 2014 19:37:01 +0200 Subject: [PATCH 05/23] Fix a bug in `get` and improve tests Would return `notFound` even if `map` had `key`, but where the value was logically false. --- lib/get.js | 9 ++++++--- test/get.js | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/get.js b/lib/get.js index 2afe144..dc277b6 100644 --- a/lib/get.js +++ b/lib/get.js @@ -1,8 +1,11 @@ module.exports = get function get(map, key, notFound) { - const val = is(map)? map[key] : undefined - return is(undefined, val)? notFound : val + if (isnt(map)) return notFound + if (!map.hasOwnProperty(key)) return notFound + + return map[key] } -const is = require('./is') \ No newline at end of file +const isnt = require('./isnt') + , is = require('./is') \ No newline at end of file diff --git a/test/get.js b/test/get.js index ff9d1eb..7cbcc3b 100644 --- a/test/get.js +++ b/test/get.js @@ -12,6 +12,15 @@ describe('`get`', function() { }) }) + describe('and when the value of `key` is logically false', function() { + it('should still return the value of `key`', function() { + each([null, undefined, false], function(x) { + const map = { foo: x } + expect(get(map, 'foo')).to.equal(x) + }) + }) + }) + describe('but when `map` does not have `key`', function() { it('should return `undefined`', function () { const map = { foo : 'wibble' } @@ -36,6 +45,15 @@ describe('`get`', function() { }) }) + describe('and when the value of `key` is logically false', function() { + it('should still return the value of `key`', function() { + each([null, undefined, false], function(x) { + const map = { foo: x } + expect(get(map, 'foo', nope)).to.equal(x) + }) + }) + }) + describe('but when `map` does not have `key`', function() { it('should return `notFound`', function () { const map = { foo : 'wibble' } From 340be592e53df7cb0605e782e84187b93b1cbc5e Mon Sep 17 00:00:00 2001 From: Marcus Stade Date: Thu, 24 Apr 2014 21:52:20 +0200 Subject: [PATCH 06/23] Added `defprop` for adding properties to an object MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a wrapper around `Object.defineProperties` which makes it easier to add immutable and non-enumerable properties to objects. Just like `Object.defineProperties`, this function will mutate the object in place. It *is* possible to make the properties both mutable and enumerable (but not removable) – however this is by design made to be uncomfortable. Accessor functions or values that are functions will have their scope bound to a private scope, which is an extension of the target object. This allows functions to share data internally, without fear of exposing anything to the outside world. A caveat here is that subsequent calls to `defprop` will create a *different* private scope, with the assumption being that such calls imply that the properties are unrelated. I may want to rethink this at some point. --- lib/defprop.js | 46 +++++++++++++++ test/defprop.js | 148 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 lib/defprop.js create mode 100644 test/defprop.js diff --git a/lib/defprop.js b/lib/defprop.js new file mode 100644 index 0000000..7cc26d5 --- /dev/null +++ b/lib/defprop.js @@ -0,0 +1,46 @@ +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 = {}) + + const names = Object.getOwnPropertyNames(fields) + + if (isEmpty(names)) throw TypeError("Fields must not be empty.") + + const scope = Object.create(target) + + return Object.defineProperties(target, + names.reduce(function(p, k) { + const prop = describe(fields, k) + , opt = $(get, opts[k]) + + if (isnt(prop.get) && isnt(prop.set)) { + prop.writable = opt("writable", false) + + is(Function, prop.value) && (prop.value = prop.value.bind(scope)) + } else { + if (prop.get) { + if (opt("writable") && !prop.set) throw TypeError("The accessor `"+k+"` can't be writable without a setter.") + prop.get = prop.get.bind(scope) + } + + prop.set && (prop.set = prop.set.bind(scope)) + } + + prop.enumerable = opt("enumerable", false) + prop.configurable = false + + return (p[k] = prop), p + }, {}) + ) +} + +const describe = Object.getOwnPropertyDescriptor + , isEmpty = require("./isEmpty") + , isnt = require("./isnt") + , get = require("./get") + , is = require("./is") + , $ = require("./partial") \ No newline at end of file diff --git a/test/defprop.js b/test/defprop.js new file mode 100644 index 0000000..000e93d --- /dev/null +++ b/test/defprop.js @@ -0,0 +1,148 @@ +const defprop = require("../lib/defprop") + , expect = require("chai").expect + , 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 empty or logically false 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() { + const 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() { + const 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() { + const 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() { + it("should bind the field to a private scope", function() { + const target = defprop({}, + { get prop() { return this.wibble } + , set prop(x) { this.wibble = x } + , get scope() { return this } + , fun: function() { return this } + } + ) + + const scope = target.scope + expect(scope).to.not.equal(target) + expect(target.isPrototypeOf(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) + }) + + it("should create a new private scope for each subsequent call", function() { + const target = {} + + defprop(target, { get one() { return this }}) + defprop(target, { get two() { return this }}) + + expect(target.one).to.not.equal(target.two) + }) + }) + + describe("when a field has no options", function() { + it("should make the field immutable", function() { + const target = defprop({}, { foo: 1 }) + expect(desc(target, 'foo')).to.have.property('writable', false) + }) + + it("should not make the field enumerable", function() { + const 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() { + const 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() {} } + , { set field() {} } + , { field: 1 } + , { field: 'wibble' } + , { field: function() {} } + ] + , function(field) { + const target = {} + defprop(target, field, { field: { writable: true } }) + + const 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() {} } + , { set field() {} } + , { field: 1 } + , { field: 'wibble' } + , { field: function() {} } + ] + , function(field) { + const target = {} + defprop(target, field, { field: { enumerable: true } }) + expect(target).to.have.key('field') + } + ) + }) + }) + }) + }) +}) \ No newline at end of file From de6115bc31ccfe66d741a40627b63072c1e905be Mon Sep 17 00:00:00 2001 From: Marcus Stade Date: Thu, 24 Apr 2014 23:36:59 +0200 Subject: [PATCH 07/23] Added `when` If the first argument to `when` is logically true, or if the first argument is a function and the result of calling it is logically true, then `when` will return the value of the second argument; otherwise it returns null. --- lib/when.js | 9 +++++++++ test/when.js | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 lib/when.js create mode 100644 test/when.js diff --git a/lib/when.js b/lib/when.js new file mode 100644 index 0000000..c2fcab9 --- /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') diff --git a/test/when.js b/test/when.js new file mode 100644 index 0000000..64f9817 --- /dev/null +++ b/test/when.js @@ -0,0 +1,51 @@ +const constantly = require('../lib/constantly') + , expect = require('chai').expect + , 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 From 91afbc1567777757fdff6da6f229ccfda2569fab Mon Sep 17 00:00:00 2001 From: Marcus Stade Date: Thu, 24 Apr 2014 23:43:26 +0200 Subject: [PATCH 08/23] Fixed thunk errors --- lib/thunk.js | 14 ++++---------- test/thunk.js | 41 ++++++++++++----------------------------- 2 files changed, 16 insertions(+), 39 deletions(-) diff --git a/lib/thunk.js b/lib/thunk.js index 4d253ff..f64c530 100644 --- a/lib/thunk.js +++ b/lib/thunk.js @@ -1,19 +1,12 @@ module.exports = require('./variadic')(thunk) function thunk(fn, rest) { - if (isnt(Function, fn)) { - rest = [fn] - fn = identity - } + if (isnt(Function, fn)) throw new TypeError('Expected `fn` to be a function.') - if (rest.length > 1) { + if (is(rest)) { return function thunk() { return fn.apply(this, rest) } - } else if (rest.length) { - return function thunk() { - return fn.call(this, rest[0]) - } } else { return function thunk() { return fn.call(this) @@ -23,4 +16,5 @@ function thunk(fn, rest) { const identity = require('./identity') , slice = require('./slice') - , isnt = require('./isnt') \ No newline at end of file + , isnt = require('./isnt') + , is = require('./is') \ No newline at end of file diff --git a/test/thunk.js b/test/thunk.js index 0d0e47f..7ae9c3f 100644 --- a/test/thunk.js +++ b/test/thunk.js @@ -214,35 +214,18 @@ describe('thunk', function() { }) describe('when given a non-function `val` as the first parameter', function() { - it('should return a zero arity thunk', function() { - const th = thunk(function() {}) - - expect(th).to.be.a('function') - expect(th.length).to.equal(0) - expect(th.name).to.equal('thunk') - }) - - describe('and when called', function() { - it('should return `val` and nothing else', function() { - each( - [ 0 - , 1 - , true - , false - , '' - , 'hello' - , [] - , {} - , null - , undefined - ] - , - function(val) { - const th = thunk(val) - expect(th()).to.equal(val) - } - ) - }) + it('should throw a TypeError', function() { + each( + [ 0, 1 + , true, false + , '', 'hello' + , [], {} + , null, undefined + ] + , function(val) { + expect(thunk(thunk, val)).to.throw(TypeError) + } + ) }) }) }) \ No newline at end of file From 8083b493eb68667c0fa70d0b93a09a490905ff5c Mon Sep 17 00:00:00 2001 From: Marcus Stade Date: Fri, 25 Apr 2014 00:22:50 +0200 Subject: [PATCH 09/23] Fixed a bug in `apply` and improved tests `apply` always returned `undefined`, which seemed a tad wrong. Fixed this and made sure the tests cover that case. --- lib/apply.js | 3 +- test/apply.js | 103 +++++++++++++++++++++++++++++--------------------- 2 files changed, 60 insertions(+), 46 deletions(-) diff --git a/lib/apply.js b/lib/apply.js index ceb9476..06de333 100644 --- a/lib/apply.js +++ b/lib/apply.js @@ -2,8 +2,7 @@ module.exports = apply function apply(fn, args) { if (isnt(Function, fn)) throw new TypeError('First argument must be a function.') - - fn.apply(this, args) + return fn.apply(this, args) } const isnt = require('./isnt') \ No newline at end of file diff --git a/test/apply.js b/test/apply.js index 3fc100b..c5362cf 100644 --- a/test/apply.js +++ b/test/apply.js @@ -1,6 +1,12 @@ -describe('apply', function() { - describe('when not given a function', function() { - it('should throw a TypeError', function() { +const expect = require("chai").expect + , slice = require("../lib/slice") + , apply = require("../lib/apply") + , each = require("../lib/each") + , $ = require("../lib/partial") + +describe("`apply`", function() { + describe("given a non-function", function() { + it("should throw a TypeError", function() { each( [ 0 , 1 @@ -12,68 +18,77 @@ describe('apply', function() { , undefined ] , function(nonfn) { - expect(function() { - apply(nonfn) - }).to.throw(TypeError) + expect($(apply, nonfn)).to.throw(TypeError) } ) }) }) - describe('when given a function `fn`', function() { - describe('and no arguments', function(done) { - it('should call the function without arguments', function(done) { - const fn = function() { - expect(arguments.length).to.equal(0) - done() - } + describe("given a function `fn`", function() { + describe("and no arguments", function(done) { + describe("when called", function() { + it("should not pass any arguments", function(done) { + apply(fn) - apply(fn) - }) + function fn() { + expect(arguments.length).to.equal(0) + done() + } + }) - describe('and `fn` is bound', function() { - it('should not affect the binding', function(done) { - const owner = {} + it("should return the correct return value", function() { + expect(apply(fn)).to.equal("wibble") - const fn = function() { + function fn() { expect(arguments.length).to.equal(0) - expect(this).to.equal(owner) - done() + return "wibble" } + }) - apply(fn.bind(owner)) + it("should not affect the binding of `fn`", function() { + const that = { wibble: "wobble" } + expect(apply(fn.bind(that))).to.equal(that) + + function fn() { + expect(arguments.length).to.equal(0) + expect(this).to.equal(that) + return this + } }) }) }) - describe('and when given arguments', function(done) { - it('should call the function with arguments', function(done) { - const fn = function() { - expect(slice(arguments)).to.eql([1, true, 'wibble']) - done() - } + describe("and some arguments", function(done) { + describe("when called", function() { + it("should pass the arguments", function(done) { + apply(fn, [1, true, "wibble"]) - apply(fn, [1, true, 'wibble']) - }) + function fn() { + expect(slice(arguments)).to.eql([1, true, "wibble"]) + done() + } + }) - describe('and `fn` is bound', function() { - it('should not affect the binding', function(done) { - const owner = {} + it("should return the correct return value", function() { + expect(apply(fn, [1, true, "wibble"])).to.equal("bob") - const fn = function() { - expect(slice(arguments)).to.eql([1, true, 'wibble']) - expect(this).to.equal(owner) - done() + function fn() { + expect(slice(arguments)).to.eql([1, true, "wibble"]) + return "bob" } + }) + + it("should not affect the binding of `fn`", function() { + const that = { wibble: "wobble" } + expect(apply(fn.bind(that), [1, true, "wibble"])).to.equal("bob") - apply(fn.bind(owner), [1, true, 'wibble']) + function fn() { + expect(this).to.equal(that) + expect(slice(arguments)).to.eql([1, true, "wibble"]) + return "bob" + } }) }) }) }) -}) - -const expect = require('chai').expect - , slice = require('../lib/slice') - , apply = require('../lib/apply') - , each = require('../lib/each') \ No newline at end of file +}) \ No newline at end of file From 353c0a94307ea2936b04368567fdf611edc99499 Mon Sep 17 00:00:00 2001 From: Marcus Stade Date: Fri, 25 Apr 2014 00:24:56 +0200 Subject: [PATCH 10/23] When calling `val` with a function, pass additional arguments If `val` is called with a function, then any arguments past the first will be passed along to the function. --- lib/val.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/val.js b/lib/val.js index b68ab8d..d7a9c49 100644 --- a/lib/val.js +++ b/lib/val.js @@ -1,7 +1,8 @@ -module.exports = val +module.exports = require('./variadic')(val) -function val(x) { - return is(Function, x)? x() : x +function val(x, rest) { + return is(Function, x)? apply(x, rest) : x } -const is = require('./is') \ No newline at end of file +const apply = require('./apply') + , is = require('./is') \ No newline at end of file From 3130c0b0bea30449a7acfd0677abf75b223bea9b Mon Sep 17 00:00:00 2001 From: Marcus Stade Date: Fri, 25 Apr 2014 00:30:21 +0200 Subject: [PATCH 11/23] Fixed indent messup No idea where the tabs came from. --- lib/defprop.js | 21 +++-- test/defprop.js | 210 ++++++++++++++++++++++++------------------------ 2 files changed, 114 insertions(+), 117 deletions(-) diff --git a/lib/defprop.js b/lib/defprop.js index 7cc26d5..f9f5e78 100644 --- a/lib/defprop.js +++ b/lib/defprop.js @@ -7,27 +7,24 @@ function defprop(target, fields, opts) { opts || (opts = {}) const names = Object.getOwnPropertyNames(fields) - - if (isEmpty(names)) throw TypeError("Fields must not be empty.") - - const scope = Object.create(target) + , scope = Object.create(target) return Object.defineProperties(target, names.reduce(function(p, k) { const prop = describe(fields, k) - , opt = $(get, opts[k]) + , opt = $(get, opts[k]) if (isnt(prop.get) && isnt(prop.set)) { - prop.writable = opt("writable", false) + prop.writable = opt("writable", false) - is(Function, prop.value) && (prop.value = prop.value.bind(scope)) + is(Function, prop.value) && (prop.value = prop.value.bind(scope)) } else { - if (prop.get) { - if (opt("writable") && !prop.set) throw TypeError("The accessor `"+k+"` can't be writable without a setter.") - prop.get = prop.get.bind(scope) - } + if (prop.get) { + if (opt("writable") && !prop.set) throw TypeError("The accessor `"+k+"` can't be writable without a setter.") + prop.get = prop.get.bind(scope) + } - prop.set && (prop.set = prop.set.bind(scope)) + prop.set && (prop.set = prop.set.bind(scope)) } prop.enumerable = opt("enumerable", false) diff --git a/test/defprop.js b/test/defprop.js index 000e93d..6abc67b 100644 --- a/test/defprop.js +++ b/test/defprop.js @@ -8,140 +8,140 @@ 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) + expect($(defprop, null)).to.throw(TypeError) }) }) - describe("and an empty or logically false set of fields", function() { + 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) - }) + 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() { - const target = {} + const target = {} - expect(target).to.not.have.property('foo') - defprop(target, { foo: 'wobble' }) - expect(target).to.have.property('foo', 'wobble') + 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() { - const target = {} + const target = {} - expect(target).to.not.have.property('foo') - expect(defprop(target, { foo: 'wobble' })).to.equal(target) - expect(target).to.have.property('foo', 'wobble') + 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() { - const target = defprop({}, { foo: 'wobble' }) - expect(desc(target, 'foo')).to.have.property('configurable', false) + const 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() { - it("should bind the field to a private scope", function() { - const target = defprop({}, - { get prop() { return this.wibble } - , set prop(x) { this.wibble = x } - , get scope() { return this } - , fun: function() { return this } - } - ) - - const scope = target.scope - expect(scope).to.not.equal(target) - expect(target.isPrototypeOf(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) - }) - - it("should create a new private scope for each subsequent call", function() { - const target = {} - - defprop(target, { get one() { return this }}) - defprop(target, { get two() { return this }}) - - expect(target.one).to.not.equal(target.two) - }) + it("should bind the field to a private scope", function() { + const target = defprop({}, + { get prop() { return this.wibble } + , set prop(x) { this.wibble = x } + , get scope() { return this } + , fun: function() { return this } + } + ) + + const scope = target.scope + expect(scope).to.not.equal(target) + expect(target.isPrototypeOf(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) + }) + + it("should create a new private scope for each subsequent call", function() { + const target = {} + + defprop(target, { get one() { return this }}) + defprop(target, { get two() { return this }}) + + expect(target.one).to.not.equal(target.two) + }) }) describe("when a field has no options", function() { - it("should make the field immutable", function() { - const target = defprop({}, { foo: 1 }) - expect(desc(target, 'foo')).to.have.property('writable', false) - }) - - it("should not make the field enumerable", function() { - const target = defprop({}, { foo: 1 }) - expect(desc(target, 'foo')).to.have.property('enumerable', false) - }) + it("should make the field immutable", function() { + const target = defprop({}, { foo: 1 }) + expect(desc(target, 'foo')).to.have.property('writable', false) + }) + + it("should not make the field enumerable", function() { + const 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() { - const 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() {} } - , { set field() {} } - , { field: 1 } - , { field: 'wibble' } - , { field: function() {} } - ] - , function(field) { - const target = {} - defprop(target, field, { field: { writable: true } }) - - const prop = desc(target, 'field') - - if (prop.value) { - expect(prop).to.have.property('writable', true) - } else { - expect(prop.set).to.be.ok - } - } - ) - }) - }) + describe("and is just a getter", function() { + it("should throw a TypeError", function() { + const 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() {} } + , { set field() {} } + , { field: 1 } + , { field: 'wibble' } + , { field: function() {} } + ] + , function(field) { + const target = {} + defprop(target, field, { field: { writable: true } }) + + const 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() {} } - , { set field() {} } - , { field: 1 } - , { field: 'wibble' } - , { field: function() {} } - ] - , function(field) { - const target = {} - defprop(target, field, { field: { enumerable: true } }) - expect(target).to.have.key('field') - } - ) - }) + it("should make the field enumerable", function() { + each( + [ { get field() {}, set field() {} } + , { set field() {} } + , { field: 1 } + , { field: 'wibble' } + , { field: function() {} } + ] + , function(field) { + const target = {} + defprop(target, field, { field: { enumerable: true } }) + expect(target).to.have.key('field') + } + ) + }) }) }) }) From 045031ebc229c32055db54fc55f222d2b1dd8f43 Mon Sep 17 00:00:00 2001 From: Marcus Stade Date: Fri, 25 Apr 2014 23:22:17 +0200 Subject: [PATCH 12/23] Fixed `is` when checking for booleans, and improved tests. --- lib/is.js | 2 +- test/is.js | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/is.js b/lib/is.js index 3c89ea9..e8621d5 100644 --- a/lib/is.js +++ b/lib/is.js @@ -18,7 +18,7 @@ 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) || c.isPrototypeOf(x) } } diff --git a/test/is.js b/test/is.js index 72a7d7e..3bb700f 100644 --- a/test/is.js +++ b/test/is.js @@ -29,6 +29,38 @@ describe('is', function() { }) }) + 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 From 859d570b51675a9759d4c6cdeaba5692f034518e Mon Sep 17 00:00:00 2001 From: Marcus Stade Date: Sat, 26 Apr 2014 19:29:52 +0200 Subject: [PATCH 13/23] Fixed a bug when calling `is` with identical args `is(x, x)` could return false if `x` is an object --- lib/is.js | 2 ++ test/is.js | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/lib/is.js b/lib/is.js index e8621d5..2b491a6 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 + const cs = lowerCase(c) if (cs === 'null') { diff --git a/test/is.js b/test/is.js index 3bb700f..36f60ce 100644 --- a/test/is.js +++ b/test/is.js @@ -29,6 +29,24 @@ 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() { From 21f3be95dd4c9978ed9658e36b61deb0e2c60681 Mon Sep 17 00:00:00 2001 From: Marcus Stade Date: Sat, 26 Apr 2014 19:40:11 +0200 Subject: [PATCH 14/23] Changed how property binding works in `defprop` It was a bit too magical to make up a magical scope, so now it has to be specified by providing an object. Added global options for all properties as well, making it a bit easier to deal with property options. They can still be overridden for each field however. --- lib/defprop.js | 33 ++++++++++++++++++++------------ test/defprop.js | 50 ++++++++++++++++++++++--------------------------- 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/lib/defprop.js b/lib/defprop.js index f9f5e78..b6a40a7 100644 --- a/lib/defprop.js +++ b/lib/defprop.js @@ -6,8 +6,12 @@ function defprop(target, fields, opts) { opts || (opts = {}) - const names = Object.getOwnPropertyNames(fields) - , scope = Object.create(target) + const names = Object.getOwnPropertyNames(fields) + + const 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) { @@ -15,20 +19,24 @@ function defprop(target, fields, opts) { , opt = $(get, opts[k]) if (isnt(prop.get) && isnt(prop.set)) { - prop.writable = opt("writable", false) + prop.writable = opt("writable", isMutable) - is(Function, prop.value) && (prop.value = prop.value.bind(scope)) + when(is(Function, prop.value) && opt("bind", scope), function(scope) { + prop.value = prop.value.bind(scope) + }) } else { - if (prop.get) { - if (opt("writable") && !prop.set) throw TypeError("The accessor `"+k+"` can't be writable without a setter.") - prop.get = prop.get.bind(scope) - } - - prop.set && (prop.set = prop.set.bind(scope)) + 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", false) - prop.configurable = false + prop.enumerable = opt("enumerable", isEnum) + prop.configurable = opt("configurable", isConf) return (p[k] = prop), p }, {}) @@ -38,6 +46,7 @@ function defprop(target, fields, opts) { const 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/test/defprop.js b/test/defprop.js index 6abc67b..d816d7a 100644 --- a/test/defprop.js +++ b/test/defprop.js @@ -43,38 +43,32 @@ describe("`defprop`", function() { }) describe("when a field is a getter and/or setter, or the field is a function", function() { - it("should bind the field to a private scope", function() { - const target = defprop({}, - { get prop() { return this.wibble } - , set prop(x) { this.wibble = x } - , get scope() { return this } - , fun: function() { return this } - } - ) - - const scope = target.scope - expect(scope).to.not.equal(target) - expect(target.isPrototypeOf(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') + describe("and the `^bind` option is set to an object", function() { + it("should bind the field to the object", function() { + const scope = {} + const 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.fun()).to.equal(scope) - }) + expect(target).to.not.equal(scope) + expect(target.scope).to.equal(scope) - it("should create a new private scope for each subsequent call", function() { - const target = {} + expect(target.prop).to.equal(undefined) + expect(scope).to.not.have.property('wibble') + expect(target).to.not.have.property('wibble') - defprop(target, { get one() { return this }}) - defprop(target, { get two() { return this }}) + 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.one).to.not.equal(target.two) + expect(target.fun()).to.equal(scope) + }) }) }) From e0e36dea721555ae2bdc1b7571843a6877700d6c Mon Sep 17 00:00:00 2001 From: Marcus Stade Date: Mon, 28 Apr 2014 22:01:56 +0200 Subject: [PATCH 15/23] Added `describe` for describing object properties --- lib/describe.js | 27 +++++++++++++++ test/describe.js | 90 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 lib/describe.js create mode 100644 test/describe.js diff --git a/lib/describe.js b/lib/describe.js new file mode 100644 index 0000000..af71820 --- /dev/null +++ b/lib/describe.js @@ -0,0 +1,27 @@ +module.exports = describe + +function describe(obj, field) { + assert(isObject(obj), TypeError("Can't describe non-object: " + src(obj))) + + const descprop = $(Object.getOwnPropertyDescriptor, obj) + + if (is(field)) { + assert(obj.hasOwnProperty(field), TypeError("Can't describe non-existant property: " + field)) + return descprop(field) + } else { + return names(obj).reduce(function(fields, k) { + return fields[k] = descprop(k), fields + }, {}) + } +} + +const assert = require("./assert") + , names = Object.getOwnPropertyNames + , comp = require("./comp") + , type = require("./type") + , get = require("./get") + , src = require("./src") + , is = require("./is") + , $ = require("./partial") + +const isObject = comp(type, $(get, { "function": true, "object": true, "array": true }), Boolean) \ No newline at end of file diff --git a/test/describe.js b/test/describe.js new file mode 100644 index 0000000..044c085 --- /dev/null +++ b/test/describe.js @@ -0,0 +1,90 @@ +const expect = require("chai").expect + , desc = require("../lib/describe") + , each = require("../lib/each") + , $ = require("../lib/partial") + +describe("`describe`", function() { + describe("when given a non-object", function() { + it("should throw a TypeError", function() { + each( + [ -1, 0, 3.14 + , null, undefined + , true, false + , "", "wibble" + ] + , function(x) { + expect($(desc, x)).to.throw("TypeError") + } + ) + }) + }) + + describe("when given an array", function() { + it("should return a description of all of its properties", function() { + const arrdesc = desc([0, 1, 2]) + + expect(arrdesc).to.eql( + { '0': { value: 0, writable: true, enumerable: true, configurable: true } + , '1': { value: 1, writable: true, enumerable: true, configurable: true } + , '2': { value: 2, writable: true, enumerable: true, configurable: true } + , length: + { value: 3 + , writable: true + , enumerable: false + , configurable: false + } + } + ) + }) + + describe("and when also given a property name", function() { + it("should return a description of just that property", function() { + const arrdesc = desc([0, 1, 2], 1) + + expect(arrdesc).to.eql( + { value: 1 + , writable: true + , enumerable: true + , configurable: true + } + ) + }) + }) + }) + + describe("when given a map", function() { + it("should return a description of all of its properties", function() { + const mapdesc = desc({ foo: "bar", wibble: "wobble" }) + + expect(mapdesc).to.eql( + { foo: + { value: 'bar' + , writable: true + , enumerable: true + , configurable: true + } + , wibble: + { value: 'wobble' + , writable: true + , enumerable: true + , configurable: true + } + } + ) + }) + + describe("and when also given a property name", function() { + it("should return a description of just that property", function() { + const mapdesc = desc({ foo: "bar", wibble: "wobble" }, "foo") + + expect(mapdesc).to.eql( + { value: 'bar' + , writable: true + , enumerable: true + , configurable: true + } + ) + }) + }) + }) +}) \ No newline at end of file From 5996d0563e45add8f3485e92a1a667112d622aa9 Mon Sep 17 00:00:00 2001 From: Marcus Stade Date: Tue, 29 Apr 2014 02:37:58 +0200 Subject: [PATCH 16/23] Added `extend` for creating derivations of objects Extending objects means creating a new object instance, where the prototype is set to be the base, and the returned instance has the properties of any additional objects passed. If there are multiple objects passed, their properties will be merged with any conflicts resolved by picking the rightmost candidate. --- lib/extend.js | 19 +++++++++++++++ test/extend.js | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 lib/extend.js create mode 100644 test/extend.js diff --git a/lib/extend.js b/lib/extend.js new file mode 100644 index 0000000..5a65c70 --- /dev/null +++ b/lib/extend.js @@ -0,0 +1,19 @@ +module.exports = require("./variadic")(extend) + +function extend(base, rest) { + assert(isExtendable(base), TypeError("Base must be an extendable object.")) + assert(is(rest), TypeError("At least one extension object must be defined.")) + + const traits = apply(merge, rest) + return Object.create(base, describe(traits)) +} + +const describe = require("./describe") + , assert = require("./assert") + , apply = require("./apply") + , merge = require("./merge") + , comp = require("./comp") + , is = require("./is") + , $ = require("./partial") + +const isExtendable = $(is, Object.prototype) \ No newline at end of file diff --git a/test/extend.js b/test/extend.js new file mode 100644 index 0000000..34a42b7 --- /dev/null +++ b/test/extend.js @@ -0,0 +1,63 @@ +const expect = require("chai").expect + , extend = require("../lib/extend") + , each = require("../lib/each") + , $ = require("../lib/partial") + +describe("`extend`", function() { + describe("given a base object", function() { + describe("when it is not extendable", function() { + it("should throw a TypeError", function() { + each( + [ -1, 0, 1, 3.14 + , true, false + , null, undefined + ] + , function(base) { + expect($(extend, base, {})).to.throw("TypeError") + } + ) + }) + }) + + describe("when it is extendable", function() { + describe("and when given an extension object", function() { + it("should return a new object derived from the base and with the properties of the extension", function() { + const A = { foo: 1 } + , B = { foo: 2, bar: "wibble" } + , C = extend(A, B) + + expect(A.isPrototypeOf(C)).to.be.true + expect(C).to.have.property("foo", 2) + expect(C).to.have.property("bar", "wibble") + }) + }) + + describe("and when given more than one extension object", function() { + it("should return a new object derived from the base and with the merged properties of all extensions", function() { + const A = { foo: 1 } + , B = { foo: 2, bar: "wibble" } + , C = { baz: "wobble" } + , D = extend(A, B, C) + + expect(A.isPrototypeOf(D)).to.be.true + expect(D).to.have.property("foo", 2) + expect(D).to.have.property("bar", "wibble") + expect(D).to.have.property("baz", "wobble") + }) + + describe("and when the extension objects have identical property names", function() { + it("should choose the rightmost property for the merged extension", function() { + const A = { foo: 1 } + , B = { foo: 2, bar: "wibble" } + , C = { foo: "moo", bar: "wobble" } + , D = extend(A, B, C) + + expect(A.isPrototypeOf(D)).to.be.true + expect(D).to.have.property("foo", "moo") + expect(D).to.have.property("bar", "wobble") + }) + }) + }) + }) + }) +}) \ No newline at end of file From 8ccd711265cf2b9ad4cc0914ae356a2dbf69779c Mon Sep 17 00:00:00 2001 From: Marcus Stade Date: Tue, 29 Apr 2014 02:40:18 +0200 Subject: [PATCH 17/23] Added `merge` for merging object properties Creates a new object instance, derived from `Object.prototype` and with the merged properties of all passed objects. Property conflicts are resolved by picking the rightmost candidate. Properties are merged by looking at the actual property descriptors and not just values, meaning things like accessor functions and property metadata such as whether it's enumerable, writable, or configurable, are passed along. Thus a merge is about as identical to the input objects as it can get. --- lib/merge.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 lib/merge.js diff --git a/lib/merge.js b/lib/merge.js new file mode 100644 index 0000000..b887828 --- /dev/null +++ b/lib/merge.js @@ -0,0 +1,18 @@ +module.exports = require("./variadic")(merge) + +function merge(objects) { + if (isEmpty(objects)) return + + return create(Object.prototype, objects.reduce(function(fields, t) { + names(t).forEach(function(k) { + fields[k] = describe(t, k) + }) + + return fields + }, {})) +} + +const describe = Object.getOwnPropertyDescriptor + , isEmpty = require("./isEmpty") + , create = Object.create + , names = Object.getOwnPropertyNames \ No newline at end of file From 542a5a7f15a2017676bf7928d98c3c40c7bbf719 Mon Sep 17 00:00:00 2001 From: Marcus Stade Date: Tue, 29 Apr 2014 14:12:33 +0200 Subject: [PATCH 18/23] Changed `describe` to take a variable number of properties This makes it easy to filter properties from the description. Also, this change makes the output consistent, whereas before it would exclude the property name if it was given to the function. This is awkward when using things like `seq` to loop over descriptions. --- lib/describe.js | 39 +++++++++++++------------ test/describe.js | 76 +++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 84 insertions(+), 31 deletions(-) diff --git a/lib/describe.js b/lib/describe.js index af71820..e87622d 100644 --- a/lib/describe.js +++ b/lib/describe.js @@ -1,27 +1,30 @@ -module.exports = describe +module.exports = require("./variadic")(describe) -function describe(obj, field) { - assert(isObject(obj), TypeError("Can't describe non-object: " + src(obj))) +function describe(obj, fields) { + assert(is(Object.prototype, obj), TypeError("Can't describe non-object: " + src(obj))) const descprop = $(Object.getOwnPropertyDescriptor, obj) - if (is(field)) { - assert(obj.hasOwnProperty(field), TypeError("Can't describe non-existant property: " + field)) - return descprop(field) + if (is(fields)) { + const hasprop = obj.hasOwnProperty.bind(obj) + , props = {} + + each(fields, function(name) { + assert(hasprop(name), TypeError("Can't describe non-existant property: " + name)) + props[name] = descprop(name) + }) + + return props } else { - return names(obj).reduce(function(fields, k) { - return fields[k] = descprop(k), fields + return names(obj).reduce(function(fields, name) { + return fields[name] = descprop(name), fields }, {}) } } -const assert = require("./assert") - , names = Object.getOwnPropertyNames - , comp = require("./comp") - , type = require("./type") - , get = require("./get") - , src = require("./src") - , is = require("./is") - , $ = require("./partial") - -const isObject = comp(type, $(get, { "function": true, "object": true, "array": true }), Boolean) \ No newline at end of file +const assert = require("./assert") + , names = Object.getOwnPropertyNames + , each = require("./each") + , src = require("./src") + , is = require("./is") + , $ = require("./partial") \ No newline at end of file diff --git a/test/describe.js b/test/describe.js index 044c085..edd3a58 100644 --- a/test/describe.js +++ b/test/describe.js @@ -24,9 +24,9 @@ describe("`describe`", function() { const arrdesc = desc([0, 1, 2]) expect(arrdesc).to.eql( - { '0': { value: 0, writable: true, enumerable: true, configurable: true } - , '1': { value: 1, writable: true, enumerable: true, configurable: true } - , '2': { value: 2, writable: true, enumerable: true, configurable: true } + { "0": { value: 0, writable: true, enumerable: true, configurable: true } + , "1": { value: 1, writable: true, enumerable: true, configurable: true } + , "2": { value: 2, writable: true, enumerable: true, configurable: true } , length: { value: 3 , writable: true @@ -42,10 +42,35 @@ describe("`describe`", function() { const arrdesc = desc([0, 1, 2], 1) expect(arrdesc).to.eql( - { value: 1 - , writable: true - , enumerable: true - , configurable: true + { "1": + { value: 1 + , writable: true + , enumerable: true + , configurable: true + } + } + ) + }) + }) + + describe("and when given multiple property names", function() { + it("should return a description of each given name", function() { + const arrdesc = desc([0, 1, 2], 0, 2) + + + expect(arrdesc).to.eql( + { "0": + { value: 0 + , writable: true + , enumerable: true + , configurable: true + } + , "2": + { value: 2 + , writable: true + , enumerable: true + , configurable: true + } } ) }) @@ -58,13 +83,13 @@ describe("`describe`", function() { expect(mapdesc).to.eql( { foo: - { value: 'bar' + { value: "bar" , writable: true , enumerable: true , configurable: true } , wibble: - { value: 'wobble' + { value: "wobble" , writable: true , enumerable: true , configurable: true @@ -78,10 +103,35 @@ describe("`describe`", function() { const mapdesc = desc({ foo: "bar", wibble: "wobble" }, "foo") expect(mapdesc).to.eql( - { value: 'bar' - , writable: true - , enumerable: true - , configurable: true + { "foo": + { value: "bar" + , writable: true + , enumerable: true + , configurable: true + } + } + ) + }) + }) + + describe("and when given multiple property names", function() { + it("should return a description of each given name", function() { + const arrdesc = desc({ foo: "bar", baz: "nope", wibble: "wobble" }, "foo", "wibble") + + + expect(arrdesc).to.eql( + { "foo": + { value: "bar" + , writable: true + , enumerable: true + , configurable: true + } + , "wibble": + { value: "wobble" + , writable: true + , enumerable: true + , configurable: true + } } ) }) From cceb3421f71bb5dd9d86858a5c3ac69b6f986f2b Mon Sep 17 00:00:00 2001 From: Marcus Stade Date: Tue, 29 Apr 2014 21:04:38 +0200 Subject: [PATCH 19/23] Changed `describe` to work like `seq` over maps `describe` now returns an array of arrays, where each array is a key/value pair. I considered flattening this, but figured that might be tricky to deal with. Might revisit later. --- lib/describe.js | 12 +++----- test/describe.js | 80 ++++++++++-------------------------------------- 2 files changed, 21 insertions(+), 71 deletions(-) diff --git a/lib/describe.js b/lib/describe.js index e87622d..7434248 100644 --- a/lib/describe.js +++ b/lib/describe.js @@ -7,17 +7,15 @@ function describe(obj, fields) { if (is(fields)) { const hasprop = obj.hasOwnProperty.bind(obj) - , props = {} + , props = [] - each(fields, function(name) { + return fields.map(function(name) { assert(hasprop(name), TypeError("Can't describe non-existant property: " + name)) - props[name] = descprop(name) + return [name, descprop(name)] }) - - return props } else { - return names(obj).reduce(function(fields, name) { - return fields[name] = descprop(name), fields + return names(obj).map(function(name) { + return [name, descprop(name)] }, {}) } } diff --git a/test/describe.js b/test/describe.js index edd3a58..8b3015a 100644 --- a/test/describe.js +++ b/test/describe.js @@ -24,16 +24,11 @@ describe("`describe`", function() { const arrdesc = desc([0, 1, 2]) expect(arrdesc).to.eql( - { "0": { value: 0, writable: true, enumerable: true, configurable: true } - , "1": { value: 1, writable: true, enumerable: true, configurable: true } - , "2": { value: 2, writable: true, enumerable: true, configurable: true } - , length: - { value: 3 - , writable: true - , enumerable: false - , configurable: false - } - } + [ [ "0", { value: 0, writable: true, enumerable: true, configurable: true } ] + , [ "1", { value: 1, writable: true, enumerable: true, configurable: true } ] + , [ "2", { value: 2, writable: true, enumerable: true, configurable: true } ] + , [ "length", { value: 3, writable: true, enumerable: false, configurable: false } ] + ] ) }) @@ -42,13 +37,7 @@ describe("`describe`", function() { const arrdesc = desc([0, 1, 2], 1) expect(arrdesc).to.eql( - { "1": - { value: 1 - , writable: true - , enumerable: true - , configurable: true - } - } + [ [ 1, { value: 1, writable: true, enumerable: true, configurable: true } ] ] ) }) }) @@ -57,21 +46,10 @@ describe("`describe`", function() { it("should return a description of each given name", function() { const arrdesc = desc([0, 1, 2], 0, 2) - expect(arrdesc).to.eql( - { "0": - { value: 0 - , writable: true - , enumerable: true - , configurable: true - } - , "2": - { value: 2 - , writable: true - , enumerable: true - , configurable: true - } - } + [ [ 0, { value: 0, writable: true, enumerable: true, configurable: true } ] + , [ 2, { value: 2, writable: true, enumerable: true, configurable: true } ] + ] ) }) }) @@ -82,19 +60,9 @@ describe("`describe`", function() { const mapdesc = desc({ foo: "bar", wibble: "wobble" }) expect(mapdesc).to.eql( - { foo: - { value: "bar" - , writable: true - , enumerable: true - , configurable: true - } - , wibble: - { value: "wobble" - , writable: true - , enumerable: true - , configurable: true - } - } + [ [ "foo", { value: "bar", writable: true, enumerable: true, configurable: true } ] + , [ "wibble", { value: "wobble", writable: true, enumerable: true, configurable: true } ] + ] ) }) @@ -103,13 +71,7 @@ describe("`describe`", function() { const mapdesc = desc({ foo: "bar", wibble: "wobble" }, "foo") expect(mapdesc).to.eql( - { "foo": - { value: "bar" - , writable: true - , enumerable: true - , configurable: true - } - } + [ [ "foo", { value: "bar", writable: true, enumerable: true, configurable: true } ] ] ) }) }) @@ -120,19 +82,9 @@ describe("`describe`", function() { expect(arrdesc).to.eql( - { "foo": - { value: "bar" - , writable: true - , enumerable: true - , configurable: true - } - , "wibble": - { value: "wobble" - , writable: true - , enumerable: true - , configurable: true - } - } + [ [ "foo", { value: "bar", writable: true, enumerable: true, configurable: true } ] + , [ "wibble", { value: "wobble", writable: true, enumerable: true, configurable: true } ] + ] ) }) }) From 421111e854295b2ba33f8519be2eb58c88167671 Mon Sep 17 00:00:00 2001 From: Marcus Stade Date: Tue, 29 Apr 2014 23:35:35 +0200 Subject: [PATCH 20/23] Added some magic sauce to `partial` I find partial application from the left or right isn't enough to cover all cases, and puts a bit of an unfair burden on function definitions to consider such applications. So I added a think to `partial` where if you set one of the arguments of a partial application to be the `partial` function itself, that acts as a signal to `partial` to say that the specific argument hasn't been supplied yet, and should come later when applying the function. Is-a very nice. --- lib/partial.js | 23 +++++++++------ test/partial.js | 78 ++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 75 insertions(+), 26 deletions(-) diff --git a/lib/partial.js b/lib/partial.js index 30c83f2..af5084a 100644 --- a/lib/partial.js +++ b/lib/partial.js @@ -1,12 +1,17 @@ -module.exports = partial +const $ = module.exports = require("./variadic")(partial) -function partial(fun, args) { - if (arguments.length === 1) return fun +function partial(fun, part) { + if (isnt(part)) return fun - const part = [].slice.call(arguments, 1) + return variadic(function(rest) { + const args = part.map(function(arg) { + return arg === $? rest.shift() : arg + }) - return function partial() { - const rest = [].slice.call(arguments) - return fun.apply(this, part.concat(rest)) - } -} \ No newline at end of file + return fun.apply(this, args.concat(rest)) + }) +} + +const variadic = require("./variadic") + , each = require("./each") + , isnt = require("./isnt") \ No newline at end of file diff --git a/test/partial.js b/test/partial.js index dad03c6..5fbf961 100644 --- a/test/partial.js +++ b/test/partial.js @@ -1,27 +1,71 @@ -const partial = require('../lib/partial') - , expect = require('chai').expect +const expect = require("chai").expect + , $ = require("../lib/partial") -describe('partial', function() { - describe('when given a function and no other arguments', function() { - it('should just return the same function', function() { - const fun = partial(test) - - expect(fun).to.equal(test) - - function test(a, b) {} +describe("`partial`", function() { + describe("given a function", function() { + describe("and no other parameters", function() { + it("should just return the given function", function() { + const fun = $(test) + expect(fun).to.equal(test) + function test(a, b) {} + }) }) }) - describe('when given a function and some parameters', function() { - it('should return a partially applied function', function() { - expect(test(2, 2)).to.equal(4) + describe("and some parameters", function() { + it("should return a left-to-right partially applied function", function() { + expect(test(2, 5)).to.equal(7) - const fun = partial(test, 2) + const fun = $(test, 2) + + expect(fun(5)).to.equal(7) + + function test(a, b) { + expect(a).to.equal(2) + expect(b).to.equal(5) + return a + b + } + }) + + describe("when a parameter is identical to the `partial` function", function() { + describe("and when the partially applied function is called", function() { + it("should replace the `partial` parameter with the first argument passed", function() { + const π = Math.PI + + expect(test(1, "wibble", 3)).to.equal(π) + + const fun = $(test, 1, $, 3) + + expect(fun("wibble")).to.equal(π) + + function test(a, b, c) { + expect(a).to.equal(1) + expect(b).to.equal("wibble") + expect(c).to.equal(3) + return π + } + }) + }) + }) + + describe("when there are more than one `partial` parameter", function() { + describe("and when the partially applied function is called", function() { + it("should replace the `partial` parameters in a left-to-right fashion", function() { + expect(test(9, "7", false, null)).to.equal("wibble") + + const fun = $(test, $, "7", $, null) - expect(fun.name).to.equal('partial') - expect(fun(2)).to.equal(4) + expect(fun(9, false)).to.equal("wibble") - function test(a, b) { return a + b } + function test(a, b, c, d) { + expect(a).to.equal(9) + expect(b).to.equal("7") + expect(c).to.equal(false) + expect(d).to.equal(null) + return "wibble" + } + }) + }) }) }) }) \ No newline at end of file From d6f7842fcad3688e38c9b3ede1341648cfdf7c09 Mon Sep 17 00:00:00 2001 From: Marcus Stade Date: Mon, 28 Jul 2014 00:53:46 +0100 Subject: [PATCH 21/23] Fix `get` tests --- test/get.js | 48 ++++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/test/get.js b/test/get.js index 7cbcc3b..ca9774e 100644 --- a/test/get.js +++ b/test/get.js @@ -7,31 +7,33 @@ describe('`get`', function() { describe('when given two parameters: `map` and `key`;', function() { describe('and when `map` has `key`', function() { it('should return its value', function () { - const map = { foo : 'wibble' } - expect(get(map, 'foo')).to.equal('wibble') + const map = { foo : 'wibble' } + expect(get(map, 'foo')).to.equal('wibble') }) }) describe('and when the value of `key` is logically false', function() { it('should still return the value of `key`', function() { - each([null, undefined, false], function(x) { - const map = { foo: x } - expect(get(map, 'foo')).to.equal(x) - }) + each([null, undefined, false], function(x) { + const map = { foo: x } + expect(get(map, 'foo')).to.equal(x) + }) }) }) describe('but when `map` does not have `key`', function() { it('should return `undefined`', function () { - const map = { foo : 'wibble' } - expect(get(map, 'bar')).to.equal(undefined) + const map = { foo : 'wibble' } + expect(get(map, 'bar')).to.equal(undefined) }) }) describe('or when `map` is logically false', function() { - it('should return `undefined`', partial(each, [null, false, undefined], function (nil) { - expect(get(nil, 'whatever')).to.equal(undefined) - })) + it('should return `undefined`', function() { + each([null, false, undefined], function(nil) { + expect(get(nil, 'whatever')).to.equal(undefined) + }) + }) }) }) @@ -40,31 +42,33 @@ describe('`get`', function() { describe('and when `map` has `key`', function() { it('should return its value', function () { - const map = { foo : 'wibble' } - expect(get(map, 'foo', nope)).to.equal('wibble') + const map = { foo : 'wibble' } + expect(get(map, 'foo', nope)).to.equal('wibble') }) }) describe('and when the value of `key` is logically false', function() { it('should still return the value of `key`', function() { - each([null, undefined, false], function(x) { - const map = { foo: x } - expect(get(map, 'foo', nope)).to.equal(x) - }) + each([null, undefined, false], function(x) { + const map = { foo: x } + expect(get(map, 'foo', nope)).to.equal(x) + }) }) }) describe('but when `map` does not have `key`', function() { it('should return `notFound`', function () { - const map = { foo : 'wibble' } - expect(get(map, 'bar', nope)).to.equal(nope) + const map = { foo : 'wibble' } + expect(get(map, 'bar', nope)).to.equal(nope) }) }) describe('or when `map` is logically false', function() { - it('should return `notFound`', partial(each, [null, false, undefined], function (nil) { - expect(get(nil, 'whatever', nope)).to.equal(nope) - })) + it('should return `notFound`', function() { + each([null, false, undefined], function(nil) { + expect(get(nil, 'whatever', nope)).to.equal(nope) + }) + }) }) }) }) \ No newline at end of file From 065afe30cf694a092920a955cbd33363e907a84d Mon Sep 17 00:00:00 2001 From: Marcus Stade Date: Mon, 28 Jul 2014 00:54:00 +0100 Subject: [PATCH 22/23] Re-added missing `assert` --- lib/assert.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 lib/assert.js diff --git a/lib/assert.js b/lib/assert.js new file mode 100644 index 0000000..b016f25 --- /dev/null +++ b/lib/assert.js @@ -0,0 +1,15 @@ +module.exports = assert + +function assert(x, message, type) { + if (isnt(x)) { + message = val(message) + isnt(message) && (message = "Assertion failed.") + throw is(Error, message)? message : Error(message) + } + + return x +} + +const isnt = require("./isnt") + , val = require("./val") + , is = require("./is") \ No newline at end of file From f062623277fd59f2a14087e297e5f0d41dc8bf8d Mon Sep 17 00:00:00 2001 From: Marcus Stade Date: Mon, 28 Jul 2014 01:11:27 +0100 Subject: [PATCH 23/23] Fixed `extend` after changes to `describe` Not too happy about this... --- lib/extend.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/extend.js b/lib/extend.js index 5a65c70..02a7a74 100644 --- a/lib/extend.js +++ b/lib/extend.js @@ -4,8 +4,12 @@ function extend(base, rest) { assert(isExtendable(base), TypeError("Base must be an extendable object.")) assert(is(rest), TypeError("At least one extension object must be defined.")) - const traits = apply(merge, rest) - return Object.create(base, describe(traits)) + const traits = describe(apply(merge, rest)).reduce(function(traits, prop) { + traits[prop[0]] = prop[1] + return traits + }, {}) + + return Object.create(base, traits) } const describe = require("./describe")