From 96dd5ec65bc57086924a02bd96eb9c8be3941eaa Mon Sep 17 00:00:00 2001 From: minecrawler Date: Tue, 4 Jul 2017 16:06:10 +0200 Subject: [PATCH 1/3] set up linting rules --- .eslintrc.json | 28 ++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 29 insertions(+) create mode 100644 .eslintrc.json diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..8d05479 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,28 @@ +{ + "extends": ["eslint:recommended"], + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module" + }, + "env": { + "node": true, + "es6": true, + "browser": true, + "mocha": true + }, + "rules": { + "eol-last": ["error", "always"], + "max-len": ["warn", { + "code": 120 + }], + "indent": ["warn", "tab"], + "newline-after-var": "warn", + "newline-per-chained-call": "warn", + "one-var-declaration-per-line": "warn", + "no-redeclare": "error", + "object-curly-spacing": "warn", + "arrow-spacing": "warn", + "no-multi-spaces": "warn", + "no-multiple-empty-lines": "warn" + } +} diff --git a/package.json b/package.json index 506ebfd..39424f6 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "babel-register": "^6.24.1", "chai": "^4.0.2", "cross-env": "^5.0.1", + "eslint": "^4.1.1", "mocha": "^3.4.2", "mocha-loader": "^1.1.1", "rollup": "^0.43.0", From 6c97db40b837975ae7c4ece77a1e0926a777fc30 Mon Sep 17 00:00:00 2001 From: minecrawler Date: Tue, 4 Jul 2017 16:07:13 +0200 Subject: [PATCH 2/3] change code according to linting --- src/index.js | 57 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/src/index.js b/src/index.js index 6263cdd..101a73c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,22 +1,32 @@ export { mix, is, like } function mix(...args) { - var superclass = !is(args[0], 'factory') && args.shift() || baseclass - var factory = (is(args[args.length-1], 'factory') && args.pop()) || derive + // todo: refactor to make const + let superclass = !is(args[0], 'factory') && args.shift() || baseclass + let factory = (is(args[args.length-1], 'factory') && args.pop()) || derive + superclass = is(superclass, 'mixin') ? superclass.class : derive(superclass) if (args.length) factory = (org => superclass => org(args.reduce((s,m) => m.mixin(s), superclass)))(factory) + function mixin(superclass) { - var result = is(superclass, mixin) ? superclass : factory(superclass) + const result = is(superclass, mixin) ? superclass : factory(superclass) + if (mixin.classes.indexOf(result) === -1) mixin.classes.push(result) return result } + Object.defineProperties(mixin, { classes: {value:[], writable:false}, mixins: {value:args, writable:false}, }) - var Class = mixin(superclass) - var constructor = Class.hasOwnProperty('constructor') ? Class.constructor.bind(Class) : (...args) => new Class(...args) + + const Class = mixin(superclass) + const constructor = Class.hasOwnProperty('constructor') + ? Class.constructor.bind(Class) + : (...args) => new Class(...args) + Object.getOwnPropertyNames(Class).forEach(k => Object.defineProperty(constructor, k, {value: Class[k]})) + return Object.defineProperties(constructor, { mixin: {value:mixin, writable:false}, class: {value: Class, writable:false}, @@ -26,11 +36,18 @@ function mix(...args) { function is(x, type) { if (typeof type == 'string') { - return type == 'class' ? is(x, 'function') && (s => /^class\s/.test(s) || /^.*classCallCheck\(/.test(s.replace(/^[^{]*{\s*/,'').replace(/\s*}[^}]*$/,'')))(x.toString()) : - type == 'mixin' ? is(x, 'function') && !!x.mixin : - type == 'factory' ? is(x, 'function') && !is(x, 'mixin') && !is(x, 'class') && x.length == 1 : - typeof x == type; + return type == 'class' + ? ( + is(x, 'function') && (s => /^class\s/.test(s) + || /^.*classCallCheck\(/.test(s.replace(/^[^{]*{\s*/,'').replace(/\s*}[^}]*$/,'')))(x.toString()) + ) + : type == 'mixin' + ? is(x, 'function') && !!x.mixin + : type == 'factory' + ? is(x, 'function') && !is(x, 'mixin') && !is(x, 'class') && x.length == 1 + : typeof x == type; } + if (typeof x == 'object') { if (x instanceof type) return true if (type.class && x instanceof type.class) return true @@ -38,38 +55,44 @@ function is(x, type) { } else if (typeof x == 'function') { if (x.mixin && x.mixin.mixins.indexOf(type) !== -1) return true - var c = x + + let c = x + while (c !== Object) { if (c === type || c === type.class) return true if (type.mixin && type.mixin.classes && type.mixin.classes.indexOf(c) !== -1) return true c = Object.getPrototypeOf(c.prototype).constructor } } + return false } function like(x, type) { if (is(x, type)) return true - var itf = type.interface || (is(type, 'function') && getInterface(type.prototype)) - var subject = is(x, 'function') ? x.interface || getInterface(x.prototype) : x + + const itf = type.interface || (is(type, 'function') && getInterface(type.prototype)) + const subject = is(x, 'function') ? x.interface || getInterface(x.prototype) : x + return itf && Object.keys(itf).reduce((f, k) => f && (is(itf[k], 'function') ? is(subject[k], 'function') : k in subject), true ) } function getInterface(proto) { - return getPropertyNames(proto).reduce((o,k) => {o[k] = proto[k]; return o}, {}) + return getPropertyNames(proto).reduce((o,k) => { o[k] = proto[k]; return o }, {}) } function getPropertyNames(proto) { - var results = [] + const results = [] + while (proto !== Object.prototype) { Object.getOwnPropertyNames(proto).reduce((arr,k) => arr.indexOf(k) === -1 ? arr.push(k) && arr : arr, results) proto = Object.getPrototypeOf(proto).constructor.prototype } + return results } -var baseclass = class Object{}, - derive = superclass => ({}[superclass.name || 'Object'] = class extends superclass {}) - +const baseclass = class Object{} +const derive = superclass => ({}[superclass.name || 'Object'] = class extends superclass {}) From 068ef364c6ed01418732d60840f66ca956b696a6 Mon Sep 17 00:00:00 2001 From: minecrawler Date: Tue, 4 Jul 2017 16:33:47 +0200 Subject: [PATCH 3/3] change tests according to lint, adjust linting rules, adjust src according to rule changes --- .eslintrc.json | 4 +- src/index.js | 136 ++++---- src/index.spec.js | 860 ++++++++++++++++++++++++---------------------- 3 files changed, 517 insertions(+), 483 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 8d05479..26516f1 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -15,12 +15,12 @@ "max-len": ["warn", { "code": 120 }], - "indent": ["warn", "tab"], + "indent": ["warn", 4], "newline-after-var": "warn", "newline-per-chained-call": "warn", "one-var-declaration-per-line": "warn", "no-redeclare": "error", - "object-curly-spacing": "warn", + "object-curly-spacing": ["warn", "always"], "arrow-spacing": "warn", "no-multi-spaces": "warn", "no-multiple-empty-lines": "warn" diff --git a/src/index.js b/src/index.js index 101a73c..5745443 100644 --- a/src/index.js +++ b/src/index.js @@ -1,97 +1,97 @@ export { mix, is, like } function mix(...args) { - // todo: refactor to make const - let superclass = !is(args[0], 'factory') && args.shift() || baseclass - let factory = (is(args[args.length-1], 'factory') && args.pop()) || derive + // todo: refactor to make const + let superclass = !is(args[0], 'factory') && args.shift() || baseclass + let factory = (is(args[args.length-1], 'factory') && args.pop()) || derive - superclass = is(superclass, 'mixin') ? superclass.class : derive(superclass) - if (args.length) factory = (org => superclass => org(args.reduce((s,m) => m.mixin(s), superclass)))(factory) + superclass = is(superclass, 'mixin') ? superclass.class : derive(superclass) + if (args.length) factory = (org => superclass => org(args.reduce((s,m) => m.mixin(s), superclass)))(factory) - function mixin(superclass) { - const result = is(superclass, mixin) ? superclass : factory(superclass) + function mixin(superclass) { + const result = is(superclass, mixin) ? superclass : factory(superclass) - if (mixin.classes.indexOf(result) === -1) mixin.classes.push(result) - return result - } + if (mixin.classes.indexOf(result) === -1) mixin.classes.push(result) + return result + } - Object.defineProperties(mixin, { - classes: {value:[], writable:false}, - mixins: {value:args, writable:false}, - }) + Object.defineProperties(mixin, { + classes: { value:[], writable:false }, + mixins: { value:args, writable:false }, + }) - const Class = mixin(superclass) - const constructor = Class.hasOwnProperty('constructor') - ? Class.constructor.bind(Class) - : (...args) => new Class(...args) + const Class = mixin(superclass) + const constructor = Class.hasOwnProperty('constructor') + ? Class.constructor.bind(Class) + : (...args) => new Class(...args) - Object.getOwnPropertyNames(Class).forEach(k => Object.defineProperty(constructor, k, {value: Class[k]})) + Object.getOwnPropertyNames(Class).forEach(k => Object.defineProperty(constructor, k, { value: Class[k] })) - return Object.defineProperties(constructor, { - mixin: {value:mixin, writable:false}, - class: {value: Class, writable:false}, - interface: {get:(x => () => x ? x : x = getInterface(Class.prototype))()}, - }) + return Object.defineProperties(constructor, { + mixin: { value:mixin, writable:false }, + class: { value: Class, writable:false }, + interface: { get:(x => () => x ? x : x = getInterface(Class.prototype))() }, + }) } function is(x, type) { - if (typeof type == 'string') { - return type == 'class' - ? ( - is(x, 'function') && (s => /^class\s/.test(s) - || /^.*classCallCheck\(/.test(s.replace(/^[^{]*{\s*/,'').replace(/\s*}[^}]*$/,'')))(x.toString()) - ) - : type == 'mixin' - ? is(x, 'function') && !!x.mixin - : type == 'factory' - ? is(x, 'function') && !is(x, 'mixin') && !is(x, 'class') && x.length == 1 - : typeof x == type; - } - - if (typeof x == 'object') { - if (x instanceof type) return true - if (type.class && x instanceof type.class) return true - if (type.mixin && type.mixin.classes) return type.mixin.classes.reduce((f,c) => f || is(x,c), false) - } - else if (typeof x == 'function') { - if (x.mixin && x.mixin.mixins.indexOf(type) !== -1) return true - - let c = x - - while (c !== Object) { - if (c === type || c === type.class) return true - if (type.mixin && type.mixin.classes && type.mixin.classes.indexOf(c) !== -1) return true - c = Object.getPrototypeOf(c.prototype).constructor - } - } - - return false + if (typeof type == 'string') { + return type == 'class' + ? ( + is(x, 'function') && (s => /^class\s/.test(s) + || /^.*classCallCheck\(/.test(s.replace(/^[^{]*{\s*/,'').replace(/\s*}[^}]*$/,'')))(x.toString()) + ) + : type == 'mixin' + ? is(x, 'function') && !!x.mixin + : type == 'factory' + ? is(x, 'function') && !is(x, 'mixin') && !is(x, 'class') && x.length == 1 + : typeof x == type; + } + + if (typeof x == 'object') { + if (x instanceof type) return true + if (type.class && x instanceof type.class) return true + if (type.mixin && type.mixin.classes) return type.mixin.classes.reduce((f,c) => f || is(x,c), false) + } + else if (typeof x == 'function') { + if (x.mixin && x.mixin.mixins.indexOf(type) !== -1) return true + + let c = x + + while (c !== Object) { + if (c === type || c === type.class) return true + if (type.mixin && type.mixin.classes && type.mixin.classes.indexOf(c) !== -1) return true + c = Object.getPrototypeOf(c.prototype).constructor + } + } + + return false } function like(x, type) { - if (is(x, type)) return true + if (is(x, type)) return true - const itf = type.interface || (is(type, 'function') && getInterface(type.prototype)) - const subject = is(x, 'function') ? x.interface || getInterface(x.prototype) : x + const itf = type.interface || (is(type, 'function') && getInterface(type.prototype)) + const subject = is(x, 'function') ? x.interface || getInterface(x.prototype) : x - return itf && Object.keys(itf).reduce((f, k) => - f && (is(itf[k], 'function') ? is(subject[k], 'function') : k in subject), true - ) + return itf && Object.keys(itf).reduce((f, k) => + f && (is(itf[k], 'function') ? is(subject[k], 'function') : k in subject), true + ) } function getInterface(proto) { - return getPropertyNames(proto).reduce((o,k) => { o[k] = proto[k]; return o }, {}) + return getPropertyNames(proto).reduce((o,k) => { o[k] = proto[k]; return o }, {}) } function getPropertyNames(proto) { - const results = [] + const results = [] - while (proto !== Object.prototype) { - Object.getOwnPropertyNames(proto).reduce((arr,k) => arr.indexOf(k) === -1 ? arr.push(k) && arr : arr, results) - proto = Object.getPrototypeOf(proto).constructor.prototype - } + while (proto !== Object.prototype) { + Object.getOwnPropertyNames(proto).reduce((arr,k) => arr.indexOf(k) === -1 ? arr.push(k) && arr : arr, results) + proto = Object.getPrototypeOf(proto).constructor.prototype + } - return results + return results } const baseclass = class Object{} diff --git a/src/index.spec.js b/src/index.spec.js index 964b022..b94f008 100644 --- a/src/index.spec.js +++ b/src/index.spec.js @@ -7,437 +7,471 @@ import { mix, is, like } from './' const log = ulog('mics:spec') -describe('mix([superclass] [, ...mixins] [, factory])', function(){ - it('is a function', function(){ - expect(mix).to.be.a('function') - }) - - it('creates a mixin from a class factory', function(){ - var M = mix(superclass => class M extends superclass {}) - expect(M).to.be.a('function') - }) - - it('creates a mixin from other mixins and a class factory', function(){ - var X = mix(superclass => class X extends superclass {}) - var Y = mix(superclass => class X extends superclass {}) - var M = mix(X, Y, superclass => class M extends superclass {}) - expect(M).to.be.a('function') - }) - - it('creates a mixin from other mixins', function(){ - var X = mix(superclass => class X extends superclass {}) - var Y = mix(superclass => class X extends superclass {}) - var M = mix(X, Y) - expect(is(M, 'mixin')).to.eq(true) - }) - - it('creates a mix from a superclass', function(){ - var C = mix(class Base {}) - expect(C).to.be.a('function') - expect(is(C, 'mixin')).to.eq(true) - // special case: class with one-arg constructor looks like a factory - class Base {constructor(arg){}} - C = mix(Base) - expect(C).to.be.a('function') - expect(is(C, 'mixin')).to.eq(true) - expect(new C() instanceof Base).to.eq(true) - }) - - it('creates a mix from a mixed superclass', function(){ - var C = mix(class Base {}) - expect(is(C, 'mixin')).to.eq(true) - var D = mix(C) - expect(is(D, 'mixin')).to.eq(true) - expect(new D() instanceof D).to.eq(true) - }) - - it('created mixins can be invoked with new to instantiate instances', function(){ - var M = mix(superclass => class M extends superclass {}) - var m = new M() - expect(m).to.be.an('object') - }) - - it('created mixins can be invoked without new to instantiate instances', function(){ - var M = mix(superclass => class M extends superclass {}) - var m = M() - expect(m).to.be.an('object') - }) - - it('arguments passed when invoking the mixin with new are passed on to the constructor', function(){ - var xarg,yarg,zarg - var M = mix(superclass => class M extends superclass { - constructor(x, y, z) { - super() - xarg = x - yarg = y - zarg = z - } - }) - var m = new M('x','y','z') - expect(xarg).to.eq('x') - expect(yarg).to.eq('y') - expect(zarg).to.eq('z') - }) - - it('arguments passed when invoking the mixin without new are passed on to the constructor', function(){ - var xarg,yarg,zarg - var M = mix(superclass => class M extends superclass { - constructor(x, y, z) { - super() - xarg = x - yarg = y - zarg = z - } - }) - var m = M('x','y','z') - expect(xarg).to.eq('x') - expect(yarg).to.eq('y') - expect(zarg).to.eq('z') - }) - - it('var args in constructor has correct length when invoking with new', function(){ - var argsarg - var M = mix(superclass => class M extends superclass { - constructor(...args) { - super() - argsarg = args - } - }) - var m = new M('x','y','z') - expect(argsarg.length).to.eq(3) - var m = new M() - expect(argsarg.length).to.eq(0) - }) - - it('var args in constructor has correct length when invoking without new', function(){ - var argsarg - var M = mix(superclass => class M extends superclass { - constructor(...args) { - super() - argsarg = args - } - }) - var m = M('x','y','z') - expect(argsarg.length).to.eq(3) - var m = M() - expect(argsarg.length).to.eq(0) - }) - - it('result of invoking constructor with new is instanceof mixin', function(){ - var M = mix(superclass => class M extends superclass {}) - var m = new M('x','y','z') - expect(m instanceof M).to.eq(true) - }) - - it('result of invoking constructor without new is instanceof mixin', function(){ - var M = mix(superclass => class M extends superclass {}) - var m = M('x','y','z') - expect(m instanceof M).to.eq(true) - }) - - it('picks up a static class method `constructor` and uses it in place of the default constructor', function(){ - var check = spy() - var M = mix(superclass => class M extends superclass { - static constructor(...args) { - log.log('Custom constructor') - check() - return new this(...args) - } - }) - var m = M() - expect(check.called).to.eq(true) - }) - - it('has no side effects on it\'s arguments', function(){ - class Test{} - expect(is(Test, 'mixin')).to.eq(false) - var M = mix(Test) - expect(is(M, 'mixin')).to.eq(true) - expect(is(Test, 'mixin')).to.eq(false) - var N = mix(Test, superclass => class N extends superclass {}) - expect(is(N, 'mixin')).to.eq(true) - expect(is(Test, 'mixin')).to.eq(false) - }) +describe('mix([superclass] [, ...mixins] [, factory])', function() { + it('is a function', function(){ + expect(mix).to.be.a('function') + }) + + it('creates a mixin from a class factory', function(){ + const M = mix(superclass => class M extends superclass {}) + + expect(M).to.be.a('function') + }) + + it('creates a mixin from other mixins and a class factory', function(){ + const X = mix(superclass => class X extends superclass {}) + const Y = mix(superclass => class X extends superclass {}) + const M = mix(X, Y, superclass => class M extends superclass {}) + + expect(M).to.be.a('function') + }) + + it('creates a mixin from other mixins', function(){ + const X = mix(superclass => class X extends superclass {}) + const Y = mix(superclass => class X extends superclass {}) + const M = mix(X, Y) + + expect(is(M, 'mixin')).to.eq(true) + }) + + it('creates a mix from a superclass', function(){ + let C = mix(class Base {}) + + expect(C).to.be.a('function') + expect(is(C, 'mixin')).to.eq(true) + // special case: class with one-arg constructor looks like a factory + class Base {constructor(){}} + C = mix(Base) + expect(C).to.be.a('function') + expect(is(C, 'mixin')).to.eq(true) + expect(new C() instanceof Base).to.eq(true) + }) + + it('creates a mix from a mixed superclass', function(){ + const C = mix(class Base {}) + const D = mix(C) + + expect(is(C, 'mixin')).to.eq(true) + expect(is(D, 'mixin')).to.eq(true) + expect(new D() instanceof D).to.eq(true) + }) + + it('created mixins can be invoked with new to instantiate instances', function(){ + const M = mix(superclass => class M extends superclass {}) + const m = new M() + + expect(m).to.be.an('object') + }) + + it('created mixins can be invoked without new to instantiate instances', function(){ + const M = mix(superclass => class M extends superclass {}) + const m = M() + + expect(m).to.be.an('object') + }) + + it('arguments passed when invoking the mixin with new are passed on to the constructor', function(){ + let xarg,yarg,zarg + const M = mix(superclass => class M extends superclass { + constructor(x, y, z) { + super() + xarg = x + yarg = y + zarg = z + } + }) + + new M('x','y','z') + + expect(xarg).to.eq('x') + expect(yarg).to.eq('y') + expect(zarg).to.eq('z') + }) + + it('arguments passed when invoking the mixin without new are passed on to the constructor', function(){ + let xarg,yarg,zarg + const M = mix(superclass => class M extends superclass { + constructor(x, y, z) { + super() + xarg = x + yarg = y + zarg = z + } + }) + + new M('x','y','z') + expect(xarg).to.eq('x') + expect(yarg).to.eq('y') + expect(zarg).to.eq('z') + }) + + it('var args in constructor has correct length when invoking with new', function(){ + let argsarg + const M = mix(superclass => class M extends superclass { + constructor(...args) { + super() + argsarg = args + } + }) + + new M('x','y','z') + expect(argsarg.length).to.eq(3) + + new M() + expect(argsarg.length).to.eq(0) + }) + + it('var args in constructor has correct length when invoking without new', function(){ + let argsarg + const M = mix(superclass => class M extends superclass { + constructor(...args) { + super() + argsarg = args + } + }) + + M('x','y','z') + expect(argsarg.length).to.eq(3) + + M() + expect(argsarg.length).to.eq(0) + }) + + it('result of invoking constructor with new is instanceof mixin', function(){ + const M = mix(superclass => class M extends superclass {}) + const m = new M('x','y','z') + + expect(m instanceof M).to.eq(true) + }) + + it('result of invoking constructor without new is instanceof mixin', function(){ + const M = mix(superclass => class M extends superclass {}) + const m = M('x','y','z') + + expect(m instanceof M).to.eq(true) + }) + + it('picks up a static class method `constructor` and uses it in place of the default constructor', function(){ + const check = spy() + const M = mix(superclass => class M extends superclass { + static constructor(...args) { + // todo: missing superclass constructor invocation? + log.log('Custom constructor') + check() + return new this(...args) + } + }) + + M() + expect(check.called).to.eq(true) + }) + + it('has no side effects on it\'s arguments', function(){ + class Test{} + expect(is(Test, 'mixin')).to.eq(false) + const M = mix(Test) + + expect(is(M, 'mixin')).to.eq(true) + expect(is(Test, 'mixin')).to.eq(false) + const N = mix(Test, superclass => class N extends superclass {}) + + expect(is(N, 'mixin')).to.eq(true) + expect(is(Test, 'mixin')).to.eq(false) + }) }) describe('is(x , type)', function(){ - it('is a function', function(){ - expect(is).to.be.a('function') - }) - - it('accepts one or two arguments', function(){ - expect(is.length).to.eq(2) - }) - - it('tests whether object `x` implements `type`', function(){ - var X = mix(superclass => class X extends superclass {}) - var x = new X() - expect(is(x, X)).to.eq(true) - expect(is(x, Date)).to.eq(false) - }) - it('tests whether class `x` implements `type`', function(){ - var Y = mix(superclass => class Y extends superclass {}) - var X = class X extends mix(Y) {} - expect(is(X, Y)).to.eq(true) - }) - it('tests whether mixin `x` implements `type`', function(){ - var Y = mix(superclass => class Y extends superclass {}) - var X = mix(Y, superclass => class X extends superclass {}) - expect(is(X, Y)).to.eq(true) - }) - it('for type == "mixin", tests whether `x` is a mixin', function(){ - expect(is(class X {}, 'mixin')).to.eq(false) - expect(is(mix(superclass => class Y extends superclass {}), 'mixin')).to.eq(true) - expect(is({}, 'mixin')).to.eq(false) - expect(is('Hi', 'mixin')).to.eq(false) - expect(is(function(){}, 'mixin')).to.eq(false) - expect(is(function(x){}, 'mixin')).to.eq(false) - expect(is(function(x,y){}, 'mixin')).to.eq(false) - }) - it('for type == "factory", tests whether `x` is a class factory', function(){ - expect(is(class X {}, 'factory')).to.eq(false) - expect(is(mix(class X {}), 'factory')).to.eq(false) - expect(is(class X extends mix(){}, 'factory')).to.eq(false) - expect(is(mix(superclass => class Y extends superclass {}), 'factory')).to.eq(false) - expect(is({}, 'factory')).to.eq(false) - expect(is('Hi', 'factory')).to.eq(false) - expect(is(function(){}, 'factory')).to.eq(false) - expect(is(function(x){}, 'factory')).to.eq(true) - expect(is(function(x,y){}, 'factory')).to.eq(false) - }) - it('for type == "function", tests whether `x` is a function', function(){ - expect(is(class X {}, 'function')).to.eq(true) - expect(is(mix(class X {}), 'function')).to.eq(true) - expect(is(class X extends mix(){}, 'function')).to.eq(true) - expect(is(mix(superclass => class Y extends superclass {}), 'function')).to.eq(true) - expect(is({}, 'function')).to.eq(false) - expect(is('Hi', 'function')).to.eq(false) - expect(is(function(){}, 'function')).to.eq(true) - expect(is(function(x){}, 'function')).to.eq(true) - expect(is(function(x,y){}, 'function')).to.eq(true) - }) - it('for type == "object", tests whether `x` is an object', function(){ - expect(is(class X {}, 'object')).to.eq(false) - expect(is(mix(class X {}), 'object')).to.eq(false) - expect(is(class X extends mix(){}, 'object')).to.eq(false) - expect(is(mix(superclass => class Y extends superclass {}), 'object')).to.eq(false) - expect(is({}, 'object')).to.eq(true) - expect(is('Hi', 'object')).to.eq(false) - expect(is(function(){}, 'object')).to.eq(false) - expect(is(function(x){}, 'object')).to.eq(false) - expect(is(function(x,y){}, 'object')).to.eq(false) - }) - it('for type == "string", tests whether `x` is a string', function(){ - expect(is(class X {}, 'string')).to.eq(false) - expect(is(mix(class X {}), 'string')).to.eq(false) - expect(is(class X extends mix(){}, 'string')).to.eq(false) - expect(is(mix(superclass => class Y extends superclass {}), 'string')).to.eq(false) - expect(is({}, 'string')).to.eq(false) - expect(is('Hi', 'string')).to.eq(true) - expect(is(function(){}, 'string')).to.eq(false) - expect(is(function(x){}, 'string')).to.eq(false) - expect(is(function(x,y){}, 'string')).to.eq(false) - }) + it('is a function', function(){ + expect(is).to.be.a('function') + }) + + it('accepts one or two arguments', function(){ + expect(is.length).to.eq(2) + }) + + it('tests whether object `x` implements `type`', function(){ + const X = mix(superclass => class X extends superclass {}) + const x = new X() + + expect(is(x, X)).to.eq(true) + expect(is(x, Date)).to.eq(false) + }) + it('tests whether class `x` implements `type`', function(){ + const Y = mix(superclass => class Y extends superclass {}) + const X = class X extends mix(Y) {} + + expect(is(X, Y)).to.eq(true) + }) + it('tests whether mixin `x` implements `type`', function(){ + const Y = mix(superclass => class Y extends superclass {}) + const X = mix(Y, superclass => class X extends superclass {}) + + expect(is(X, Y)).to.eq(true) + }) + it('for type == "mixin", tests whether `x` is a mixin', function(){ + expect(is(class X {}, 'mixin')).to.eq(false) + expect(is(mix(superclass => class Y extends superclass {}), 'mixin')).to.eq(true) + expect(is({}, 'mixin')).to.eq(false) + expect(is('Hi', 'mixin')).to.eq(false) + expect(is(function(){}, 'mixin')).to.eq(false) + expect(is(function(x){}, 'mixin')).to.eq(false) + expect(is(function(x,y){}, 'mixin')).to.eq(false) + }) + it('for type == "factory", tests whether `x` is a class factory', function(){ + expect(is(class X {}, 'factory')).to.eq(false) + expect(is(mix(class X {}), 'factory')).to.eq(false) + expect(is(class X extends mix(){}, 'factory')).to.eq(false) + expect(is(mix(superclass => class Y extends superclass {}), 'factory')).to.eq(false) + expect(is({}, 'factory')).to.eq(false) + expect(is('Hi', 'factory')).to.eq(false) + expect(is(function(){}, 'factory')).to.eq(false) + expect(is(function(x){}, 'factory')).to.eq(true) + expect(is(function(x,y){}, 'factory')).to.eq(false) + }) + it('for type == "function", tests whether `x` is a function', function(){ + expect(is(class X {}, 'function')).to.eq(true) + expect(is(mix(class X {}), 'function')).to.eq(true) + expect(is(class X extends mix(){}, 'function')).to.eq(true) + expect(is(mix(superclass => class Y extends superclass {}), 'function')).to.eq(true) + expect(is({}, 'function')).to.eq(false) + expect(is('Hi', 'function')).to.eq(false) + expect(is(function(){}, 'function')).to.eq(true) + expect(is(function(x){}, 'function')).to.eq(true) + expect(is(function(x,y){}, 'function')).to.eq(true) + }) + it('for type == "object", tests whether `x` is an object', function(){ + expect(is(class X {}, 'object')).to.eq(false) + expect(is(mix(class X {}), 'object')).to.eq(false) + expect(is(class X extends mix(){}, 'object')).to.eq(false) + expect(is(mix(superclass => class Y extends superclass {}), 'object')).to.eq(false) + expect(is({}, 'object')).to.eq(true) + expect(is('Hi', 'object')).to.eq(false) + expect(is(function(){}, 'object')).to.eq(false) + expect(is(function(x){}, 'object')).to.eq(false) + expect(is(function(x,y){}, 'object')).to.eq(false) + }) + it('for type == "string", tests whether `x` is a string', function(){ + expect(is(class X {}, 'string')).to.eq(false) + expect(is(mix(class X {}), 'string')).to.eq(false) + expect(is(class X extends mix(){}, 'string')).to.eq(false) + expect(is(mix(superclass => class Y extends superclass {}), 'string')).to.eq(false) + expect(is({}, 'string')).to.eq(false) + expect(is('Hi', 'string')).to.eq(true) + expect(is(function(){}, 'string')).to.eq(false) + expect(is(function(x){}, 'string')).to.eq(false) + expect(is(function(x,y){}, 'string')).to.eq(false) + }) }) describe('like(type)', function(){ - it('is a function', function(){ - expect(like).to.be.a('function') - }) - it('tests whether `x` can be treated as `type` (has the same interface)', function(){ - var Looker = mix(superclass => class Looker extends superclass { - look(){} - }) - expect(like('Hi', Looker)).to.eq(false) - expect(like(8, Looker)).to.eq(false) - expect(like({}, Looker)).to.eq(false) - expect(like(new Looker(), Looker)).to.eq(true) - expect(like({look(){}}, Looker)).to.eq(true) - expect(like({walk(){}}, Looker)).to.eq(false) - class Base {look(){}} - expect(like(Base, Looker)).to.eq(true) - expect(like(new Base(), Looker)).to.eq(true) - class Derived extends Base {} - expect(like(Derived, Looker)).to.eq(true) - expect(like(new Derived(), Looker)).to.eq(true) - }) - - it('allows mixins to be used as interfaces', (done) => { - var expected = 'Hello, World!' - var Thenable = mix(superclass => class Thenable extends superclass { - then(results) {} - }) - class MyPromise { - then(resolve, reject) { - resolve(expected) - } - } - var promise = new MyPromise() - expect(like(promise, Thenable)).to.eq(true) - Promise.resolve(promise).then((result) => { - expect(result).to.eq(expected) - done() - }) - }) + it('is a function', function(){ + expect(like).to.be.a('function') + }) + it('tests whether `x` can be treated as `type` (has the same interface)', function(){ + const Looker = mix(superclass => class Looker extends superclass { + look(){} + }) + + expect(like('Hi', Looker)).to.eq(false) + expect(like(8, Looker)).to.eq(false) + expect(like({}, Looker)).to.eq(false) + expect(like(new Looker(), Looker)).to.eq(true) + expect(like({ look(){} }, Looker)).to.eq(true) + expect(like({ walk(){} }, Looker)).to.eq(false) + class Base {look(){}} + expect(like(Base, Looker)).to.eq(true) + expect(like(new Base(), Looker)).to.eq(true) + class Derived extends Base {} + expect(like(Derived, Looker)).to.eq(true) + expect(like(new Derived(), Looker)).to.eq(true) + }) + + it('allows mixins to be used as interfaces', (done) => { + const expected = 'Hello, World!' + const Thenable = mix(superclass => class Thenable extends superclass { + then(results) {} + }) + + class MyPromise { + then(resolve, reject) { + resolve(expected) + } + } + + const promise = new MyPromise() + + expect(like(promise, Thenable)).to.eq(true) + Promise.resolve(promise).then((result) => { + expect(result).to.eq(expected) + done() + }) + }) }) describe('mix example', function(){ - it ('shows how to create a mixin using an es6 class', function(){ - var constr = spy(), look = spy() - - var Looker = mix(superclass => class Looker extends superclass { - constructor() { - super() - log.log('A looker is born!') - constr() - } - look() { - log.log('Looking good!') - look() - } - }) + it ('shows how to create a mixin using an es6 class', function(){ + const constr = spy() + const look = spy() + + const Looker = mix(superclass => class Looker extends superclass { + constructor() { + super() + log.log('A looker is born!') + constr() + } + look() { + log.log('Looking good!') + look() + } + }) - expect(Looker).to.be.a('function') - - var looker = new Looker() - - expect(looker).to.be.an('object') - expect(constr.called).to.eq(true) - - expect(looker).to.have.a.property('look') - expect(looker.look).to.be.a('function') - - looker.look() - - expect(look.called).to.eq(true) - }) - - it('shows how to composes multiple mixins', function(){ - var look = spy(), walk = spy(), talk = spy() - var Looker = mix(superclass => class Looker extends superclass { - look(){ - log.log('Looking good!') - look() - } - }) - - var Walker = mix(superclass => class Walker extends superclass { - walk(){ - log.log('Step, step, step...') - walk() - } - }) - - var Talker = mix(superclass => class Talker extends superclass { - talk(){ - log.log('Blah, blah, blah...') - talk() - } - }) - - var duckTalk = spy() - - var Duck = mix(Looker, Walker, Talker, superclass => class Duck extends superclass { - talk(){ - log.log('Quack, quack, quack!') - duckTalk() - super.talk() - } - }) - - var duck = new Duck() - expect(duck).to.be.an('object') - expect(duck instanceof Duck).to.eq(true) - expect(duck instanceof Looker).to.eq(true) - expect(duck instanceof Walker).to.eq(false) + expect(Looker).to.be.a('function') + + const looker = new Looker() + + expect(looker).to.be.an('object') + expect(constr.called).to.eq(true) + + expect(looker).to.have.a.property('look') + expect(looker.look).to.be.a('function') + + looker.look() + + expect(look.called).to.eq(true) + }) + + it('shows how to composes multiple mixins', function(){ + const look = spy() + const walk = spy() + const talk = spy() + const Looker = mix(superclass => class Looker extends superclass { + look(){ + log.log('Looking good!') + look() + } + }) + + const Walker = mix(superclass => class Walker extends superclass { + walk(){ + log.log('Step, step, step...') + walk() + } + }) + + const Talker = mix(superclass => class Talker extends superclass { + talk(){ + log.log('Blah, blah, blah...') + talk() + } + }) + + const duckTalk = spy() + + const Duck = mix(Looker, Walker, Talker, superclass => class Duck extends superclass { + talk(){ + log.log('Quack, quack, quack!') + duckTalk() + super.talk() + } + }) + + const duck = new Duck() + + expect(duck).to.be.an('object') + expect(duck instanceof Duck).to.eq(true) + expect(duck instanceof Looker).to.eq(true) + expect(duck instanceof Walker).to.eq(false) - expect(duck).to.have.a.property('look') - expect(duck.look).to.be.a('function') - duck.look() - expect(look.called).to.eq(true) - - expect(duck).to.have.a.property('walk') - expect(duck.walk).to.be.a('function') - duck.walk() - expect(walk.called).to.eq(true) - - expect(duck).to.have.a.property('talk') - expect(duck.talk).to.be.a('function') - duck.talk() - expect(talk.called).to.eq(true) - expect(duckTalk.called).to.eq(true) - }) + expect(duck).to.have.a.property('look') + expect(duck.look).to.be.a('function') + duck.look() + expect(look.called).to.eq(true) + + expect(duck).to.have.a.property('walk') + expect(duck.walk).to.be.a('function') + duck.walk() + expect(walk.called).to.eq(true) + + expect(duck).to.have.a.property('talk') + expect(duck.talk).to.be.a('function') + duck.talk() + expect(talk.called).to.eq(true) + expect(duckTalk.called).to.eq(true) + }) }) describe('type checked mixins with tcomb', function(){ - it ('shows how to create an immutable, type-checked mixin with tcomb', function(){ - // This is experimental... I think it shows we need to be able to hook into the - // mixin process itself. To enable this example I already added a hook for the - // ES5 constructor function: the `static constructor` will be picked up by `mix` - // and used as the result instead of the default generated constructor. - var a = spy(), b = spy() - var Person = mix(superclass => class Person extends superclass { - static get type() { - if (! this._tcomb) this._tcomb = t.struct({ - name: t.String, // required string - surname: t.maybe(t.String), // optional string - age: t.Integer, // required integer - tags: t.list(t.String) // a list of strings - }, 'Person') - return this._tcomb - } - - static testA() { - a() - log.log('A') - } - - static testB() { - this.testA() - b() - log.log('B') - } - - static constructor(...args) { - return this.type(new this(...args)) - } - - constructor(...args) { - super(...args) - Object.assign(this, ...args) - } - }) + it ('shows how to create an immutable, type-checked mixin with tcomb', function(){ + // This is experimental... I think it shows we need to be able to hook into the + // mixin process itself. To enable this example I already added a hook for the + // ES5 constructor function: the `static constructor` will be picked up by `mix` + // and used as the result instead of the default generated constructor. + const a = spy() + const b = spy() + const Person = mix(superclass => class Person extends superclass { + static get type() { + if (!this._tcomb) this._tcomb = t.struct({ + name: t.String, // required string + surname: t.maybe(t.String), // optional string + age: t.Integer, // required integer + tags: t.list(t.String) // a list of strings + }, 'Person') + return this._tcomb + } + + static testA() { + a() + log.log('A') + } + + static testB() { + this.testA() + b() + log.log('B') + } + + static constructor(...args) { + // todo: missing superclass constructor call? + return this.type(new this(...args)) + } + + constructor(...args) { + super(...args) + Object.assign(this, ...args) + } + }) - expect(function(){ - const person = Person({ - surname: 'Canti' - }); - }).to.throw(TypeError) // required fields missing + expect(function(){ + const person = Person({ + surname: 'Canti' + }); + }).to.throw(TypeError) // required fields missing - expect(function(){ - const person = Person({ - name: 'Stijn', - age: 40, - tags: ['developer'] - }); - }).to.not.throw() // ok + expect(function(){ + const person = Person({ + name: 'Stijn', + age: 40, + tags: ['developer'] + }); + }).to.not.throw() // ok - expect(function(){ - const person = Person({ - name: 'Stijn', - age: 40, - tags: ['developer'] - }); - person.age = 41 - }).to.throw(TypeError) // immutable - - expect(function(){ - Person.testB() - expect (a.called).to.eq(true) - expect (b.called).to.eq(true) - }).to.not.throw() // ok - }) + expect(function(){ + const person = Person({ + name: 'Stijn', + age: 40, + tags: ['developer'] + }); + + person.age = 41 + }).to.throw(TypeError) // immutable + + expect(function(){ + Person.testB() + expect (a.called).to.eq(true) + expect (b.called).to.eq(true) + }).to.not.throw() // ok + }) })