diff --git a/src/decorators.js b/src/decorators.js index c8d910cb3..2eee9ec38 100644 --- a/src/decorators.js +++ b/src/decorators.js @@ -1,3 +1,57 @@ +import bind from 'lodash.bind' + +// =================================================================== + +const {defineProperty} = Object + +// =================================================================== + +// See: https://github.com/jayphelps/core-decorators.js#autobind +export function autobind (target, key, { + configurable, + enumerable, + value: fn, + writable +}) { + return { + configurable, + enumerable, + + get () { + const bounded = bind(fn, this) + + defineProperty(this, key, { + configurable: true, + enumerable: false, + value: bounded, + writable: true + }) + + return bounded + }, + set (newValue) { + if (this === target) { + // New value directly set on the prototype. + delete this[key] + this[key] = newValue + } else { + // New value set on a child object. + + // Cannot use assignment because it will call the setter on + // the prototype. + defineProperty(this, key, { + configurable: true, + enumerable: true, + value: newValue, + writable: true + }) + } + } + } +} + +// ------------------------------------------------------------------- + // Debounce decorator for methods. // // See: https://github.com/wycats/javascript-decorators diff --git a/src/decorators.spec.js b/src/decorators.spec.js new file mode 100644 index 000000000..7920b6bb8 --- /dev/null +++ b/src/decorators.spec.js @@ -0,0 +1,72 @@ +/* eslint-env mocha */ + +import {expect} from 'chai' + +// =================================================================== + +import {autobind, debounce} from './decorators' + +// =================================================================== + +describe('autobind', function () { + class Foo { + @autobind + getFoo () { + return this + } + } + + it('returns a bound instance for a method', function () { + const foo = new Foo() + const {getFoo} = foo + + expect(getFoo()).to.equal(foo) + }) + + it('works with multiple instances of the same class', function () { + const foo1 = new Foo() + const foo2 = new Foo() + + const {getFoo: getFoo1} = foo1 + const {getFoo: getFoo2} = foo2 + + expect(getFoo1()).to.equal(foo1) + expect(getFoo2()).to.equal(foo2) + }) +}) + +// ------------------------------------------------------------------- + +describe('debounce', function () { + let i + + class Foo { + @debounce(1e1) + foo () { + ++i + } + } + + beforeEach(function () { + i = 0 + }) + + it('works', function (done) { + const foo = new Foo() + + expect(i).to.equal(0) + + foo.foo() + expect(i).to.equal(1) + + foo.foo() + expect(i).to.equal(1) + + setTimeout(function () { + foo.foo() + expect(i).to.equal(2) + + done() + }, 2e1) + }) +})