Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

class-autobind-decorator

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

class-autobind-decorator - npm Package Compare versions

Comparing version 2.3.0 to 3.0.0

build/lib/isObject.js

284

build/index.js

@@ -1,115 +0,221 @@

'use strict';
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(['exports', './lib/ReactComponentSpecMethods', './lib/isObject', './lib/uniqueConcatArrays'], factory);
} else if (typeof exports !== "undefined") {
factory(exports, require('./lib/ReactComponentSpecMethods'), require('./lib/isObject'), require('./lib/uniqueConcatArrays'));
} else {
var mod = {
exports: {}
};
factory(mod.exports, global.ReactComponentSpecMethods, global.isObject, global.uniqueConcatArrays);
global.index = mod.exports;
}
})(this, function (exports, _ReactComponentSpecMethods, _isObject, _uniqueConcatArrays) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.autoBindMethods = undefined;
exports.default = autoBindMethods;
exports.autoBindMethodsForReact = autoBindMethodsForReact;
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var _ReactComponentSpecMethods2 = _interopRequireDefault(_ReactComponentSpecMethods);
exports.default = autoBindMethods;
/**
* Overloaded function for auto-binding the methods of a class to the class's relevant instance. If
* the first argument is a function, as it will be when this is used as a "bare" or "unconfigured"
* decorator -- as in `@autoBindMethods class SomeClass {}` -- it does the auto-bind decorating by
* delegating to `autoBindMethodsDecorator`. If the first argument is *not* a function, as happens
* when this is used as a "configured" decorator -- as in `@autoBindMethods(options) class SomeClass
* {}` -- it returns a function that *itself* accepts a function (the class constructor) as its
* first argument and that does the auto-bind decorating by delegating to
* `autoBindMethodsDecorator`.
*
* The delegate method `autoBindMethodsDecorator` is `call`ed in order to avoid changing the context
* from whatever it would ordinarily be in the case of a non-overloaded decorator, while still
* allowing us to pass on any received `options`.
*
* @param {Object} [options] - optional options
* @param {String[]} [options.methodsToIgnore] - names of methods to skip auto-binding
* @param {boolean} [options.dontOptimize] - if truthy, turns off the decorator's default
* optimization behavior, which is to define the bound method directly on the class instance
* in order to prevent lookups and re-binding on every access
* @returns {*}
*/
function autoBindMethods(input) {
if (typeof input !== 'function') {
return function (target) {
autoBindMethodsDecorator.call(this, target, input);
var _isObject2 = _interopRequireDefault(_isObject);
var _uniqueConcatArrays2 = _interopRequireDefault(_uniqueConcatArrays);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
autoBindMethodsDecorator.call(this, input);
}
var _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
/**
* A "legacy"-style "class" decorator function for auto-binding the methods of the "class."
*
* @param {Function} target - an ES2015 "class" or -- what is effectively the same thing -- a
* constructor function.
* @param {Object} [options] - optional options
* @param {string[]} [options.methodsToIgnore] - names of methods to skip auto-binding
* @param {boolean} [options.dontOptimize] - if truthy, turns off the decorator's default
* optimization behavior, which is to define the bound method directly on the class instance
* in order to prevent lookups and re-binding on every access
*/
function autoBindMethodsDecorator(target) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
if (typeof target !== 'function') {
throw new TypeError('The autoBindMethods decorator must be passed a function as the first argument. ' + ('It received an argument of type ' + (typeof target === 'undefined' ? 'undefined' : _typeof(target)) + '.'));
return target;
};
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
/**
* Overloaded function for auto-binding the methods of a class to the class's relevant instance. If
* the first argument is a function, as it will be when this is used as a "bare" or "unconfigured"
* decorator -- as in `@autoBindMethods class SomeClass {}` -- it does the auto-bind decorating by
* delegating to `autoBindMethodsDecorator`. If the first argument is *not* a function, as happens
* when this is used as a "configured" decorator -- as in `@autoBindMethods(options) class SomeClass
* {}` -- it returns a function that *itself* accepts a function (the class constructor) as its
* first argument, and that does the auto-bind decorating by delegating to
* `autoBindMethodsDecorator`.
*
* The delegate method `autoBindMethodsDecorator` is `call`ed in order to avoid changing the context
* from whatever it would ordinarily be in the case of a non-overloaded decorator, while still
* allowing us to pass on any received `options`.
*
* @param {Object|Function|undefined} [input] - optional options or the function/class to decorate
* @param {String[]} [input.methodsToIgnore] - names of methods to skip auto-binding; applicable
* only if `input` is not a function
* @param {boolean} [options.dontOptimize] - if truthy, turns off the decorator's default
* optimization behavior, which is to define the bound method directly on the class instance
* when first accessed, in order to prevent re-binding on every access and traversing the
* prototype chain; applicable only if `input` is not a function
* @returns {Function|undefined}
*/
function autoBindMethods(input) {
if (typeof input !== 'function') {
return function (target) {
autoBindMethodsDecorator.call(this, target, input);
};
}
autoBindMethodsDecorator.call(this, input);
}
var prototype = target.prototype;
var _options$methodsToIgn = options.methodsToIgnore,
methodsToIgnore = _options$methodsToIgn === undefined ? [] : _options$methodsToIgn,
_options$dontOptimize = options.dontOptimize,
dontOptimize = _options$dontOptimize === undefined ? false : _options$dontOptimize;
exports.autoBindMethods = autoBindMethods;
var ownProps = typeof Object.getOwnPropertySymbols === 'function' ? Object.getOwnPropertyNames(prototype).concat(Object.getOwnPropertySymbols(prototype)) : Object.getOwnPropertyNames(prototype);
/**
* Convenience decorator that operates the same as above, but that automatically skips all
* methods in the React Component Spec, since they do not need auto-binding on React/Preact
* components. Useful to those using this decorator with React, as there is no need to list
* all of the React Component Spec methods as `methodsToIgnore`.
*
* @param {Object|Function|undefined} [input] - optional options or the function/class to decorate
* @param {String[]} [input.methodsToIgnore] - names of methods to skip auto-binding; applicable
* only if `input` is not a function
* @param {boolean} [options.dontOptimize] - if truthy, turns off the decorator's default
* optimization behavior, which is to define the bound method directly on the class instance
* when first accessed, in order to prevent re-binding on every access and traversing the
* prototype chain; applicable only if `input` is not a function
* @returns {Function|undefined}
*/
function autoBindMethodsForReact(input) {
if (typeof input === 'undefined') {
return autoBindMethods({ methodsToIgnore: _ReactComponentSpecMethods2.default });
}
if (methodsToIgnore.length > 0) {
ownProps = ownProps.filter(function (prop) {
return methodsToIgnore.indexOf(prop) === -1;
});
}
if (typeof input !== 'function') {
if (!(0, _isObject2.default)(input)) {
throw new TypeError('autoBindMethodsForReact was passed an input of type ' + (typeof input === 'undefined' ? 'undefined' : _typeof(input)) + '. The input ' + 'argument must be either a function, a plain JS object, or undefined.');
}
ownProps.forEach(function (ownPropIdentifier) {
var propDescriptor = Object.getOwnPropertyDescriptor(prototype, ownPropIdentifier);
var value = propDescriptor.value,
configurable = propDescriptor.configurable;
return autoBindMethods(_extends({}, input, {
methodsToIgnore: (0, _uniqueConcatArrays2.default)(input.methodsToIgnore || [], _ReactComponentSpecMethods2.default)
}));
}
return autoBindMethods({ methodsToIgnore: _ReactComponentSpecMethods2.default })(input);
};
if (typeof value !== 'function' || !configurable) {
// We can only do our work with configurable functions, so bail early here.
return;
/**
* A "legacy"-style "class" decorator function for auto-binding the methods of the "class."
*
* @param {Function} target - an ES2015 "class" or -- what is effectively the same thing -- a
* constructor function.
* @param {Object} [options] - optional options
* @param {string[]} [options.methodsToIgnore] - names of methods to skip auto-binding
* @param {boolean} [options.dontOptimize] - if truthy, turns off the decorator's default
* optimization behavior, which is to define the bound method directly on the class instance
* in order to prevent lookups and re-binding on every access
*/
function autoBindMethodsDecorator(target) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
if (typeof target !== 'function') {
throw new TypeError('The autoBindMethods decorator must be passed a function as the first argument. ' + ('It received an argument of type ' + (typeof target === 'undefined' ? 'undefined' : _typeof(target)) + '.'));
}
Object.defineProperty(prototype, ownPropIdentifier, {
get: function get() {
if (this.hasOwnProperty(ownPropIdentifier)) {
// Don't bind the prototype's method to the prototype, or we can't re-bind it.
return value;
}
var prototype = target.prototype;
var _options$methodsToIgn = options.methodsToIgnore,
methodsToIgnore = _options$methodsToIgn === undefined ? [] : _options$methodsToIgn,
_options$dontOptimize = options.dontOptimize,
dontOptimize = _options$dontOptimize === undefined ? false : _options$dontOptimize;
var boundMethod = value.bind(this);
if (!dontOptimize) {
var writable = propDescriptor.writable,
enumerable = propDescriptor.enumerable; // use same values as prototype for consistency
var ownProps = typeof Object.getOwnPropertySymbols === 'function' ? Object.getOwnPropertyNames(prototype).concat(Object.getOwnPropertySymbols(prototype)) : Object.getOwnPropertyNames(prototype);
// `defineProperty` must be used here rather than a standard assignment because
// assignments will first check for getters/setters up the prototype chain and
// thus reject the assignment (since the property on the prototype has a getter
// but no setter (see: http://www.2ality.com/2012/08/property-definition-assignment.html))
if (methodsToIgnore.length > 0) {
ownProps = ownProps.filter(function (prop) {
return methodsToIgnore.indexOf(prop) === -1;
});
}
Object.defineProperty(this, ownPropIdentifier, {
value: boundMethod,
ownProps.forEach(function (ownPropIdentifier) {
if (ownPropIdentifier === 'constructor') {
// This decorator should not muck around with constructors, for fear of introducing
// unexpected side effects.
return;
}
var propDescriptor = Object.getOwnPropertyDescriptor(prototype, ownPropIdentifier);
var value = propDescriptor.value,
configurable = propDescriptor.configurable,
enumerable = propDescriptor.enumerable;
if (typeof value !== 'function' || !configurable) {
// We can only do our work with configurable functions, so bail early here.
return;
}
Object.defineProperty(prototype, ownPropIdentifier, {
// Keep the same enumerability/configurability settings.
enumerable: enumerable,
configurable: configurable,
get: function get() {
if (this.hasOwnProperty(ownPropIdentifier)) {
// Don't bind the prototype's method to the prototype, or we can't re-bind it to instances.
return value;
}
var boundMethod = value.bind(this);
if (!dontOptimize) {
// `defineProperty` must be used here rather than a standard assignment because
// assignments will first check for getters/setters up the prototype chain and
// thus reject the assignment (since the property on the prototype has a getter
// but no setter (see: http://www.2ality.com/2012/08/property-definition-assignment.html))
Object.defineProperty(this, ownPropIdentifier, {
enumerable: enumerable,
configurable: configurable,
value: boundMethod,
writable: propDescriptor.writable !== false ? true : false
});
}
return boundMethod;
},
set: function set(newValue) {
if (propDescriptor.writable === false) {
// If the original property wasn't writable, don't change that.
return;
}
// Re-assigning a property on the prototype *after* the property has been bound by
// the decorator should simply overwrite that property entirely; it is weird (IMO)
// for it to magically be auto-bound to instances when assigned.
Object.defineProperty(prototype, ownPropIdentifier, {
value: newValue,
configurable: true,
writable: writable,
enumerable: enumerable
enumerable: true,
writable: true
});
}
return boundMethod;
}
});
});
});
}
}
});
# CHANGELOG
## v3.0.0 (11/16/2017)
- Ensures that the decorator does not prevent overwriting autobound
methods on a class/prototype (closes #4), for React 16 interop
- Exports `autoBindMethodsForReact` convenience decorator for easier use
with React/Preact classes (see #4)
- Does not attempt to autoBind the `constructor` method, ever (no need)
- Better guarantees that property descriptors for autobound properties
have the same settings with respect to enumerability, writability, etc.
- Built files are now UMD modules
- Additional tests
## v2.3.0 (8/4/2017)

@@ -4,0 +16,0 @@

{
"name": "class-autobind-decorator",
"version": "2.3.0",
"version": "3.0.0",
"description": "A small framework-agnostic utility for auto-binding \"class\" methods to instances (with customization options) using either \"legacy\" decorator syntax or plain old ES5 (without needing ES2015+ polyfills).",

@@ -8,4 +8,4 @@ "main": "build/index.js",

"scripts": {
"build": "./node_modules/.bin/babel src -d build && cp ./src/index.d.ts ./build",
"test": "npm run build && ./node_modules/.bin/mocha --compilers js:babel-core/register -- test"
"build": "rm -rf ./build && ./node_modules/.bin/babel src -d build && cp ./src/index.d.ts ./build",
"test": "./node_modules/.bin/mocha --compilers js:babel-core/register -- test"
},

@@ -35,2 +35,4 @@ "keywords": [

"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-es2015-modules-umd": "^6.24.1",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-polyfill": "^6.13.0",

@@ -37,0 +39,0 @@ "babel-preset-es2015": "^6.14.0",

@@ -11,3 +11,4 @@ # class-autobind-decorator

- Usable with ES6+ [React](https://facebook.github.io/react/) and [Preact](https://preactjs.com) classes,
but also elsewhere
but also elsewhere (exports a default `autoBindMethods` decorator, as well as an `autoBindMethodsForReact`
convenience decorator that skips autobinding methods from the React Component Spec that do not require binding)
- Built version is fully ES5-compatible, requiring no ES6+ polyfills

@@ -21,2 +22,5 @@ - Supports class methods that have ES6 Symbols as keys

(checks for configurability first!)
- Keeps property descriptors (e.g., writability, enumerability, etc.) consistent between original
properties and the corresponding autobound properties
- Does not "magically" autobind properties set on classes/prototypes *after* the decorator has been applied
- Conforms to [the ES6+ "legacy" decorator pattern](https://babeljs.io/docs/plugins/transform-decorators/),

@@ -64,4 +68,8 @@ and hence is usable as an ES6+ legacy decorator

import autoBindMethods from 'class-autobind-decorator';
// NOTE: For React classes, you can use this alternative:
// import { autoBindMethodsForReact } from 'class-autobind-decorator';
@autoBindMethods({ methodsToIgnore: ['unboundMethod', 'render'] })
// or:
// @autoBindMethodsForReact({ methodsToIgnore: ['unboundMethod'] })
class MyComponent extends React.Component {

@@ -68,0 +76,0 @@ someComponentMethod() {

@@ -0,1 +1,5 @@

import ReactComponentSpecMethods from './lib/ReactComponentSpecMethods';
import isObject from './lib/isObject';
import uniqueConcatArrays from './lib/uniqueConcatArrays';
/**

@@ -8,3 +12,3 @@ * Overloaded function for auto-binding the methods of a class to the class's relevant instance. If

* {}` -- it returns a function that *itself* accepts a function (the class constructor) as its
* first argument and that does the auto-bind decorating by delegating to
* first argument, and that does the auto-bind decorating by delegating to
* `autoBindMethodsDecorator`.

@@ -16,8 +20,10 @@ *

*
* @param {Object} [options] - optional options
* @param {String[]} [options.methodsToIgnore] - names of methods to skip auto-binding
* @param {Object|Function|undefined} [input] - optional options or the function/class to decorate
* @param {String[]} [input.methodsToIgnore] - names of methods to skip auto-binding; applicable
* only if `input` is not a function
* @param {boolean} [options.dontOptimize] - if truthy, turns off the decorator's default
* optimization behavior, which is to define the bound method directly on the class instance
* in order to prevent lookups and re-binding on every access
* @returns {*}
* when first accessed, in order to prevent re-binding on every access and traversing the
* prototype chain; applicable only if `input` is not a function
* @returns {Function|undefined}
*/

@@ -34,3 +40,42 @@ export default function autoBindMethods(input) {

export { autoBindMethods };
/**
* Convenience decorator that operates the same as above, but that automatically skips all
* methods in the React Component Spec, since they do not need auto-binding on React/Preact
* components. Useful to those using this decorator with React, as there is no need to list
* all of the React Component Spec methods as `methodsToIgnore`.
*
* @param {Object|Function|undefined} [input] - optional options or the function/class to decorate
* @param {String[]} [input.methodsToIgnore] - names of methods to skip auto-binding; applicable
* only if `input` is not a function
* @param {boolean} [options.dontOptimize] - if truthy, turns off the decorator's default
* optimization behavior, which is to define the bound method directly on the class instance
* when first accessed, in order to prevent re-binding on every access and traversing the
* prototype chain; applicable only if `input` is not a function
* @returns {Function|undefined}
*/
export function autoBindMethodsForReact(input) {
if (typeof input === 'undefined') {
return autoBindMethods({ methodsToIgnore: ReactComponentSpecMethods });
}
if (typeof input !== 'function') {
if (!isObject(input)) {
throw new TypeError(
`autoBindMethodsForReact was passed an input of type ${typeof input}. The input ` +
'argument must be either a function, a plain JS object, or undefined.'
);
}
return autoBindMethods({
...input,
methodsToIgnore: uniqueConcatArrays(input.methodsToIgnore || [], ReactComponentSpecMethods)
});
}
return autoBindMethods({ methodsToIgnore: ReactComponentSpecMethods })(input);
};
/**
* A "legacy"-style "class" decorator function for auto-binding the methods of the "class."

@@ -66,4 +111,10 @@ *

ownProps.forEach((ownPropIdentifier) => {
if (ownPropIdentifier === 'constructor') {
// This decorator should not muck around with constructors, for fear of introducing
// unexpected side effects.
return;
}
const propDescriptor = Object.getOwnPropertyDescriptor(prototype, ownPropIdentifier);
const { value, configurable } = propDescriptor;
const { value, configurable, enumerable } = propDescriptor;

@@ -76,5 +127,8 @@ if (typeof value !== 'function' || !configurable) {

Object.defineProperty(prototype, ownPropIdentifier, {
// Keep the same enumerability/configurability settings.
enumerable,
configurable,
get() {
if (this.hasOwnProperty(ownPropIdentifier)) {
// Don't bind the prototype's method to the prototype, or we can't re-bind it.
// Don't bind the prototype's method to the prototype, or we can't re-bind it to instances.
return value;

@@ -86,4 +140,2 @@ }

if (!dontOptimize) {
const { writable, enumerable } = propDescriptor; // use same values as prototype for consistency
// `defineProperty` must be used here rather than a standard assignment because

@@ -94,6 +146,6 @@ // assignments will first check for getters/setters up the prototype chain and

Object.defineProperty(this, ownPropIdentifier, {
enumerable,
configurable,
value: boundMethod,
configurable: true,
writable,
enumerable
writable: propDescriptor.writable !== false ? true : false
});

@@ -103,2 +155,18 @@ }

return boundMethod;
},
set(newValue) {
if (propDescriptor.writable === false) {
// If the original property wasn't writable, don't change that.
return;
}
// Re-assigning a property on the prototype *after* the property has been bound by
// the decorator should simply overwrite that property entirely; it is weird (IMO)
// for it to magically be auto-bound to instances when assigned.
Object.defineProperty(prototype, ownPropIdentifier, {
value: newValue,
configurable: true,
enumerable: true,
writable: true
});
}

@@ -105,0 +173,0 @@ });

import 'babel-polyfill';
import { expect } from 'chai';
import autoBindMethods from '../build';
import autoBindMethods, { autoBindMethodsForReact } from '../src';

@@ -8,12 +8,23 @@ const symbolKey = Symbol('symbolKey');

/**
* Helper method for performing tests.
* Helper method for performing tests. TODO: This is probably overkill.
*
* @param {Function} MyFirstClass
* @param {Function} MySecondClass
* @param {Function} UnboundClass
* @param {Function} FullyBoundClass
* @param {Array.<string|Symbol>} [ignoredMethods]
*/
function testBindings(MyFirstClass, MySecondClass, MyThirdClass, ignoredMethods = []) {
const unboundInstance = new MyFirstClass();
const boundInstance = new MySecondClass();
const boundConfiguredInstance = new MyThirdClass();
function testBindings(
{
UnboundClass,
FullyBoundClass,
PartiallyBoundClass,
FullyBoundReactClass,
PartiallyBoundReactClass
},
ignoredMethods = []
) {
const unboundInstance = new UnboundClass();
const boundInstance = new FullyBoundClass();
const boundConfiguredInstance = new PartiallyBoundClass();
const boundReactInstance = new FullyBoundReactClass();
const boundConfiguredReactInstance = new PartiallyBoundReactClass();
const unboundTestMethodOne = unboundInstance.testMethodOne;

@@ -26,10 +37,20 @@ const unboundSymbolMethod = unboundInstance[symbolKey];

const boundConfiguredSymbolMethod = boundConfiguredInstance[symbolKey];
const boundReactTestMethodOne = boundReactInstance.testMethodOne;
const boundReactSymbolMethod = boundReactInstance[symbolKey];
const boundConfiguredReactTestMethodOne = boundConfiguredReactInstance.testMethodOne;
const boundConfiguredReactTestMethodTwo = boundConfiguredReactInstance.testMethodTwo;
const boundConfiguredReactSymbolMethod = boundConfiguredReactInstance[symbolKey];
expect(unboundTestMethodOne(unboundInstance)).to.equal(false);
expect(unboundSymbolMethod(unboundInstance)).to.equal(false);
expect(boundTestMethodOne(boundInstance)).to.equal(true);
expect(boundSymbolMethod(boundInstance)).to.equal(true);
expect(unboundTestMethodOne(unboundInstance)).to.be.false;
expect(unboundSymbolMethod(unboundInstance)).to.be.false;
expect(boundTestMethodOne(boundInstance)).to.be.true;
expect(boundSymbolMethod(boundInstance)).to.be.true;
expect(boundConfiguredTestMethodOne(boundConfiguredInstance)).to.equal(ignoredMethods.includes('testMethodOne') ? false : true);
expect(boundConfiguredTestMethodTwo(boundConfiguredInstance)).to.equal(ignoredMethods.includes('testMethodTwo') ? false : true);
expect(boundConfiguredSymbolMethod(boundConfiguredInstance)).to.equal(ignoredMethods.includes(symbolKey) ? false : true);
expect(boundReactTestMethodOne(boundReactInstance)).to.be.true;
expect(boundReactSymbolMethod(boundReactInstance)).to.be.true;
expect(boundConfiguredReactTestMethodOne(boundConfiguredReactInstance)).to.equal(ignoredMethods.includes('testMethodOne') ? false : true);
expect(boundConfiguredReactTestMethodTwo(boundConfiguredReactInstance)).to.equal(ignoredMethods.includes('testMethodTwo') ? false : true);
expect(boundConfiguredReactSymbolMethod(boundConfiguredReactInstance)).to.equal(ignoredMethods.includes(symbolKey) ? false : true);
}

@@ -42,7 +63,13 @@

/**
* Retrieves three classes for testing -- one unbound, one auto-bound, and one
* auto-bound-with-options (some methods ignored).
* Retrieves five classes for testing -- one unbound, one auto-bound, one
* auto-bound-with-options (some methods ignored), and one auto-bound-for-React,
* and one auto-bound-for-React-with-options (some additional methods ignored).
* The classes are defined inline so that new ones are returned for each test
* that uses this method.
*
* TODO: This also seems to be overkill; does far more than is necessary for
* most tests below.
*/
function getClasses(autoBindOptions) {
class MyFirstClass {
class UnboundClass {
testMethodOne(instance) {

@@ -58,3 +85,3 @@ return isBound(instance, this);

@autoBindMethods
class MySecondClass {
class FullyBoundClass {
testMethodOne(instance) {

@@ -70,3 +97,3 @@ return isBound(instance, this);

@autoBindMethods(autoBindOptions)
class MyThirdClass {
class PartiallyBoundClass {
testMethodOne(instance) {

@@ -85,3 +112,39 @@ return isBound(instance, this);

return [MyFirstClass, MySecondClass, MyThirdClass];
@autoBindMethodsForReact
class FullyBoundReactClass {
testMethodOne(instance) {
return isBound(instance, this);
}
[symbolKey](instance) {
return isBound(instance, this);
}
render() {}
}
@autoBindMethodsForReact(autoBindOptions)
class PartiallyBoundReactClass {
testMethodOne(instance) {
return isBound(instance, this);
}
testMethodTwo(instance) {
return isBound(instance, this);
}
[symbolKey](instance) {
return isBound(instance, this);
}
render() {}
}
return {
UnboundClass,
FullyBoundClass,
PartiallyBoundClass,
FullyBoundReactClass,
PartiallyBoundReactClass
};
}

@@ -105,5 +168,103 @@

it('should not redefine constructors', function () {
const { UnboundClass } = getClasses();
const firstInstance = new UnboundClass();
const untouchedConstructor = UnboundClass.prototype.constructor;
autoBindMethods(UnboundClass);
const secondInstance = new UnboundClass();
expect(firstInstance.constructor).to.equal(UnboundClass.prototype.constructor);
expect(secondInstance.constructor).to.equal(UnboundClass.prototype.constructor);
expect(UnboundClass.prototype.constructor).to.equal(untouchedConstructor);
});
// This test turns out to be important for React 16 interop.
// See https://github.com/jmrog/class-autobind-decorator/issues/4
it('should allow overwriting autobound properties', function () {
const { FullyBoundClass } = getClasses();
const instance = new FullyBoundClass();
instance.testMethodOne(); // autobind with optimization to write property on instance
function test() {
instance.testMethodOne = 're-assigned';
FullyBoundClass.prototype.constructor = 'nope';
FullyBoundClass.prototype.testMethodOne = function () { return 'x'; };
return true;
}
expect(test).not.to.throw;
test(); // ensure this is run before the next two tests; the prior `expect` line does not do that (there's probably a better way to do this)
expect(FullyBoundClass.prototype.testMethodOne()).to.equal('x');
expect(instance.testMethodOne).to.equal('re-assigned');
});
it('should maintain property descriptor behavior as much as possible', function () {
const { UnboundClass } = getClasses();
function testEnumeration(testee) {
for (let item in testee) {
if (item === 'specialProperty') {
throw new Error();
}
}
}
function testWritability(testee) {
testee.specialProperty = false;
}
Object.defineProperty(UnboundClass.prototype, 'specialProperty', {
value: () => 'unwritable and unenumerable!',
configurable: true
});
autoBindMethods(UnboundClass);
const instance = new UnboundClass();
expect(() => testEnumeration(instance)).not.to.throw;
expect(() => testWritability(instance)).not.to.throw;
expect(instance.specialProperty()).to.equal('unwritable and unenumerable!');
expect(() => testEnumeration(UnboundClass.prototype)).not.to.throw;
expect(() => testWritability(UnboundClass.prototype)).to.throw;
expect(UnboundClass.prototype.specialProperty()).to.equal('unwritable and unenumerable!');
});
describe('overwriting an autobound property', function () {
it('should behave like normal assignment (descriptor-wise, plus no magic autobinding)', function () {
const { FullyBoundClass } = getClasses();
const instance = new FullyBoundClass();
FullyBoundClass.prototype.testMethodOne = function() {
return this === FullyBoundClass.prototype;
};
instance.testMethodOne(); // trigger binding to instance
instance.testMethodOne = function() {
return this === instance;
};
const prototypeDescriptor = Object.getOwnPropertyDescriptor(FullyBoundClass.prototype, 'testMethodOne');
const instanceDescriptor = Object.getOwnPropertyDescriptor(instance, 'testMethodOne');
const prototypeMethod = FullyBoundClass.prototype.testMethodOne;
const instanceMethod = instance.testMethodOne;
const newInstance = new FullyBoundClass();
newInstance.testMethodOne(); // should not magically autobind
const newInstanceMethod = newInstance.testMethodOne;
[prototypeDescriptor, instanceDescriptor].forEach((descriptor) => {
['configurable', 'enumerable', 'writable'].forEach((setting) => {
expect(descriptor[setting]).to.be.true;
});
});
expect(prototypeMethod()).to.be.false;
expect(instanceMethod()).to.be.false;
expect(newInstanceMethod()).to.be.false;
});
});
describe('when returned from a call that specifies no options', function () {
it('should autobind all methods', function () {
testBindings(...getClasses());
testBindings(getClasses());
});

@@ -115,3 +276,3 @@ });

it('should bind only the unignored methods', function () {
testBindings(...getClasses(autoBindOptions), autoBindOptions.methodsToIgnore);
testBindings(getClasses(autoBindOptions), autoBindOptions.methodsToIgnore);
});

@@ -122,7 +283,7 @@ });

it('should not alter the instance and should re-bind on every access', function () {
const [MyFirstClass] = getClasses();
const { UnboundClass } = getClasses();
const customAutoBinder = autoBindMethods({ dontOptimize: true });
customAutoBinder(MyFirstClass);
const myFirstInstance = new MyFirstClass();
customAutoBinder(UnboundClass);
const myFirstInstance = new UnboundClass();

@@ -132,4 +293,4 @@ const a = myFirstInstance.testMethodOne;

expect(a).not.to.equal(b);
expect(myFirstInstance.hasOwnProperty('testMethodOne')).to.equal(false);
expect(b(myFirstInstance)).to.equal(true);
expect(myFirstInstance.hasOwnProperty('testMethodOne')).to.be.false;
expect(b(myFirstInstance)).to.be.true;
});

@@ -140,4 +301,4 @@ });

it('should alter the instance and not re-bind on every access', function () {
const [, MySecondClass] = getClasses();
const myFirstInstance = new MySecondClass();
const { FullyBoundClass } = getClasses();
const myFirstInstance = new FullyBoundClass();

@@ -147,3 +308,3 @@ const a = myFirstInstance.testMethodOne;

expect(a).to.equal(b);
expect(myFirstInstance.hasOwnProperty('testMethodOne')).to.equal(true);
expect(myFirstInstance.hasOwnProperty('testMethodOne')).to.be.true;
});

@@ -155,5 +316,5 @@ });

it('should skip binding those functions and not throw', function () {
const [MyFirstClass] = getClasses();
const { UnboundClass } = getClasses();
Object.defineProperty(MyFirstClass.prototype, 'nonConfigurable', {
Object.defineProperty(UnboundClass.prototype, 'nonConfigurable', {
value: function (instance) {

@@ -165,9 +326,10 @@ return instance === this;

expect(autoBindMethods(MyFirstClass)).not.to.throw;
expect(() => autoBindMethods(UnboundClass)).not.to.throw;
const myInstance = new MyFirstClass();
autoBindMethods(UnboundClass); // ensure this happens before next lines; prior `expect` line doesn't do this (there's probably a better way to do this)
const myInstance = new UnboundClass();
const { testMethodOne, nonConfigurable } = myInstance;
expect(testMethodOne(myInstance)).to.equal(true);
expect(nonConfigurable(myInstance)).to.equal(false);
expect(testMethodOne(myInstance)).to.be.true;
expect(nonConfigurable(myInstance)).to.be.false;
});

@@ -178,10 +340,10 @@ });

it('should not bind the method to the prototype', function () {
const [ , MySecondClass] = getClasses();
MySecondClass.prototype.testMethodOne; // `get` via prototype, not instance, should not bind
const { FullyBoundClass } = getClasses();
FullyBoundClass.prototype.testMethodOne; // `get` via prototype, not instance, should not bind
const myInstance = new MySecondClass();
const myInstance = new FullyBoundClass();
myInstance.__proto__.testMethodOne; // similarly, should not bind
const { testMethodOne } = myInstance;
expect(testMethodOne(myInstance)).to.equal(true); // should be bound to the instance now, not the prototype
expect(testMethodOne(myInstance)).to.be.true; // should be bound to the instance now, not the prototype
});

@@ -192,20 +354,46 @@ });

it('should bind the method to each individual instance', function () {
const [ , MySecondClass] = getClasses();
const myFirstInstance = new MySecondClass();
const mySecondInstance = new MySecondClass();
const { FullyBoundClass } = getClasses();
const myFirstInstance = new FullyBoundClass();
const mySecondInstance = new FullyBoundClass();
let { testMethodOne } = myFirstInstance;
expect(testMethodOne(myFirstInstance)).to.equal(true);
expect(testMethodOne(mySecondInstance)).to.equal(false);
expect(myFirstInstance.hasOwnProperty('testMethodOne')).to.equal(true);
expect(MySecondClass.prototype.hasOwnProperty('testMethodOne')).to.equal(true);
expect(mySecondInstance.hasOwnProperty('testMethodOne')).to.equal(false);
expect(testMethodOne(myFirstInstance)).to.be.true;
expect(testMethodOne(mySecondInstance)).to.be.false;
expect(myFirstInstance.hasOwnProperty('testMethodOne')).to.be.true;
expect(FullyBoundClass.prototype.hasOwnProperty('testMethodOne')).to.be.true;
expect(mySecondInstance.hasOwnProperty('testMethodOne')).to.be.false;
testMethodOne = mySecondInstance.testMethodOne;
expect(testMethodOne(mySecondInstance)).to.equal(true);
expect(mySecondInstance.hasOwnProperty('testMethodOne')).to.equal(true);
expect(testMethodOne(mySecondInstance)).to.be.true;
expect(mySecondInstance.hasOwnProperty('testMethodOne')).to.be.true;
});
});
});
describe('autoBindMethodsForReact', function () {
it('should throw if input is neither a function nor a plain object nor falsey', function () {
expect(() => autoBindMethodsForReact()).not.to.throw;
expect(() => autoBindMethodsForReact({})).not.to.throw;
expect(() => autoBindMethodsForReact(function () {})).not.to.throw;
expect(() => autoBindMethodsForReact([])).to.throw;
expect(() => autoBindMethodsForReact(null)).to.throw;
expect(() => autoBindMethodsForReact(false)).to.throw;
expect(() => autoBindMethodsForReact(Symbol())).to.throw;
});
it('should skip binding methods in the React Component Spec to instances', function () {
@autoBindMethodsForReact
class TestClass {
render() {}
}
const testInstance = new TestClass();
testInstance.render();
// the method would be on the instance if the decorator had applied
expect('render' in testInstance).to.be.true;
expect(testInstance.hasOwnProperty('render')).to.be.false;
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc