Comparing version 0.0.2 to 1.0.0
285
cyclone.js
@@ -1,9 +0,11 @@ | ||
// Cyclone.js: An Adaptation of the HTML5 structured cloning alogrithm. | ||
// | ||
// Can recursively clone objects, including those containing number, boolean, | ||
// string, date, and regex objects. It can also clone objects which included | ||
// cyclic references to itself, including nested cyclic references. | ||
// | ||
// Works in ES5-compatible environments. | ||
/** | ||
* Cyclone.js: An Adaptation of the HTML5 structured cloning alogrithm. | ||
* @author Travis Kaufman <travis.kaufman@gmail.com> | ||
* @license MIT. | ||
*/ | ||
// This module can recursively clone objects, including those containing | ||
// number, boolean, string, date, and regex objects. It can also clone objects | ||
// which include cyclic references to itself, including nested cyclic | ||
// references. It is tested in all ES5-compatible environments. | ||
(function(root) { | ||
@@ -13,61 +15,95 @@ 'use strict'; | ||
var __call__ = Function.prototype.call; | ||
// Many environments seem to not support nativeBind as of now so because of | ||
// this we'll use our own implementation. | ||
var _bind = function(fn, ctx) { | ||
var slice = [].slice; | ||
var _hasOwn = _bind(__call__, {}.hasOwnProperty); | ||
var _toString = _bind(__call__, {}.toString); | ||
var _slice = _bind(__call__, [].slice); | ||
// Many environments seem to not support ES5's native bind as of now. | ||
// Because of this, we'll use our own implementation. | ||
function _bind(fn, ctx) { | ||
// Get a locally-scoped version of _slice here. | ||
var _slice = [].slice; | ||
// Like native bind, an arbitrary amount of arguments can be passed into | ||
// this function which will automatically be bound to it whenever it's | ||
// called. | ||
var boundArgs = slice.call(arguments, 2); | ||
var boundArgs = _slice.call(arguments, 2); | ||
return function() { | ||
return fn.apply(ctx, boundArgs.concat(slice.call(arguments))); | ||
return fn.apply(ctx, boundArgs.concat(_slice.call(arguments))); | ||
}; | ||
}; | ||
var _hasProp = _bind(__call__, {}.hasOwnProperty); | ||
var _toString = _bind(__call__, {}.toString); | ||
} | ||
// Utilities for working with transfer maps. A transfer map is defined as an | ||
// object that has two properties, `inputs` and `outputs`, each of which | ||
// are arrays where for any object at inputs[i], the output value that should | ||
// be mapped to the cloned object for that object resides at outputs[i]. See | ||
// the W3C spec for more details. This was the closest I could get without | ||
// having to set custom properties on objects, which wouldn't work for | ||
// immutable objects anyway. | ||
function TransferMap() { | ||
this.inputs = []; | ||
this.outputs = []; | ||
function _isFunc(obj) { | ||
return (typeof obj === 'function'); | ||
} | ||
// Map a given `input` object to a given `output` object. Relatively | ||
// straightforward. | ||
TransferMap.prototype.set = function(input, output) { | ||
// We only want to set a reference if the reference already doesn't exist. | ||
// This is included for defensive reasons. | ||
if (this.inputs.indexOf(input) === -1) { | ||
this.inputs.push(input); | ||
this.outputs.push(output); | ||
} | ||
// Quick and dirty shallow-copy functionality for options hash | ||
function _mergeParams(src/*, target1, ..., targetN*/) { | ||
return _slice(arguments, 1).reduce(function(target, mixin) { | ||
for (var key in mixin) { | ||
if (_hasOwn(mixin, key) && !_hasOwn(target, key)) { | ||
target[key] = mixin[key]; | ||
} | ||
} | ||
return target; | ||
}, src); | ||
} | ||
// We shim ES6's Map here if it's not in the environment already. Although | ||
// it would be better to use WeakMaps here, this is impossible to do with ES5 | ||
// since references to objects won't be garbage collected if they're still | ||
// in the map, so it's better to keep the implementation consistent. | ||
var Map = _isFunc(root.Map) ? root.Map : function Map() { | ||
Object.defineProperties(this, { | ||
inputs: { | ||
value: [], | ||
enumerable: false | ||
}, | ||
outputs: { | ||
value: [], | ||
enumerable: false | ||
} | ||
}); | ||
}; | ||
// Retrieve the object that's mapped to `input`, or null if input is not | ||
// found within the transfer map. | ||
TransferMap.prototype.get = function(input) { | ||
var idx = this.inputs.indexOf(input); | ||
var output = null; | ||
// All we need are the `get` and `set` public-facing methods so we shim just | ||
// them. | ||
if (idx > -1) { | ||
output = this.outputs[idx]; | ||
} | ||
if (!_isFunc(Map.prototype.set)) { | ||
// Map a given `input` object to a given `output` object. Relatively | ||
// straightforward. | ||
Map.prototype.set = function(input, output) { | ||
var inputIdx = this.inputs.indexOf(input); | ||
if (inputIdx === -1) { | ||
this.inputs.push(input); | ||
this.outputs.push(output); | ||
} else { | ||
// Associate this input with the new output. | ||
this.outputs[inputIdx] = output; | ||
} | ||
}; | ||
} | ||
return output; | ||
}; | ||
if (!_isFunc(Map.prototype.get)) { | ||
// Retrieve the object that's mapped to `input`, or null if input is not | ||
// found within the transfer map. | ||
Map.prototype.get = function(input) { | ||
var idx = this.inputs.indexOf(input); | ||
var output = null; | ||
// Regex used to test whether or not an object could be an HTML Element. | ||
var _htmlElementRE = /^\[object\sHTML(.*?)Element\]$/; | ||
if (idx > -1) { | ||
output = this.outputs[idx]; | ||
} | ||
return output; | ||
}; | ||
} | ||
// Any custom cloning procedures defined by the client will be stored here. | ||
var _customCloneProcedures = []; | ||
// Performs the "internal structured clone" portion of the structured cloning | ||
// algorithm. `input` is any valid object, and `tMap` is a(n empty) | ||
// TransferMap instance. | ||
function _iSClone(input, tMap) { | ||
// algorithm. `input` is any valid object, and `mMap` is a(n empty) | ||
// Map instance. `options` is the same as it is for `clone` | ||
function _iSClone(input, mMap, options) { | ||
if (input === null) { | ||
@@ -77,15 +113,16 @@ return null; | ||
if (typeof input === 'object') { | ||
return _handleObjectClone(input, tMap); | ||
if (Object(input) === input) { | ||
return _handleObjectClone(input, mMap, options); | ||
} | ||
// If the value is a primitive, simply return it. | ||
return input; | ||
} | ||
// Here lies the meat and potatoes of the algorithm. _handleObjectClone | ||
// is responsible for creating deep copies of complex data. Its parameters | ||
// are the same as for _isClone. | ||
function _handleObjectClone(input, tMap) { | ||
// Here lies the meat and potatoes of the algorithm. `_handleObjectClone` | ||
// is responsible for creating deep copies of complex objects. Its parameters | ||
// are the same as for `_isClone`. | ||
function _handleObjectClone(input, mMap, options) { | ||
// First we make sure that we aren't dealing with a circular reference. | ||
var _selfRef = tMap.get(input); | ||
var _selfRef = mMap.get(input); | ||
if (_selfRef !== null) { | ||
@@ -95,3 +132,11 @@ return _selfRef; | ||
// Most supported object types can be copied just be creating a new | ||
// We also check up front to make sure that a client-defined custom | ||
// procedure has not been registered for this type of object. If it has, | ||
// it takes priority over any of the implementations below. | ||
var _cloneAttempt = _attemptCustomClone(input); | ||
if (typeof _cloneAttempt !== 'undefined') { | ||
return _cloneAttempt; | ||
} | ||
// Most supported object types can be copied simply by creating a new | ||
// instance of the object using its current value, so we save that in this | ||
@@ -102,5 +147,5 @@ // variable. | ||
var output; | ||
// We defined a collection as either an array of Object other than String, | ||
// Number, Boolean, Date, or RegExp objects. Basically any structure where | ||
// recursive cloning may be necessary. | ||
// We define a collection as either an array of Objects other than String, | ||
// Number, Boolean, Date, or RegExp objects. Basically it's any structure | ||
// where recursive cloning may be necessary. | ||
var isCollection = false; | ||
@@ -110,5 +155,5 @@ | ||
// These cases follow the W3C's specification for how certain objects | ||
// are handled. Note that jshint will complain about using Object wrappers | ||
// for primitives (as it should), but we have to handle this case should | ||
// the client pass one in. | ||
// are handled. Note that jshint will complain about using Object | ||
// wrappers for primitives (as it should), but we have to handle this | ||
// case should the client pass one in. | ||
@@ -151,9 +196,7 @@ /*jshint -W053 */ | ||
default: | ||
// If it's an HTML Element, try to clone it. | ||
if (_htmlElementRE.test(obType) && | ||
typeof input.cloneNode === 'function') { | ||
output = input.cloneNode(); | ||
// If `options.allowFunctions` is set to true, we allow functions to | ||
// be passed directly into the copied object. | ||
if (_isFunc(input) && (options.allowFunctions === true)) { | ||
output = input; | ||
} else { | ||
// Otherwise just throw an error. | ||
throw new TypeError( | ||
@@ -163,9 +206,10 @@ "Don't know how to clone object of type " + obType | ||
} | ||
break; | ||
} | ||
// Map this specific object to its output in case its cyclically referenced | ||
tMap.set(input, output); | ||
mMap.set(input, output); | ||
if (isCollection) { | ||
_handleCollectionClone(input, output, tMap); | ||
_handleCollectionClone(input, output, mMap, options); | ||
} | ||
@@ -177,3 +221,3 @@ | ||
// Handles the safe cloning of RegExp objects, where we explicitly pass the | ||
// regex object the source and flags separately, as this prevents bugs | ||
// regex object, the source, and flags separately, as this prevents bugs | ||
// within phantomJS (and possibly other environments as well). | ||
@@ -196,23 +240,86 @@ function _handleRegExpClone(re) { | ||
// Handles the recursive portion of structured cloning. | ||
function _handleCollectionClone(input, output, tMap) { | ||
var prop; | ||
function _handleCollectionClone(input, output, mMap, options) { | ||
// Note that we use own property names here since we've already | ||
// used `Object.create()` to create the duplicate, so we have | ||
// already acquired the original object's prototype. Note that the W3C | ||
// spec explicitly states that this algorithm does *not* walk the | ||
// prototype chain, and therefore all Object prototypes are live | ||
// (assigned as a reference). | ||
Object.getOwnPropertyNames(input).forEach(function(prop) { | ||
var desc = Object.getOwnPropertyDescriptor(input, prop); | ||
// We only clone if the property is a non-accessor. We can't really clone | ||
// getters and setters, we can only pass them through. | ||
if (desc.value !== undefined) { | ||
desc.value = _iSClone(desc.value, mMap, options); | ||
} | ||
for (prop in input) { | ||
// Note that we use the hasOwnProperty guard here since we've already | ||
// used either Object.create() to create the duplicate, so we have | ||
// already acquired the original object's prototype. Note that the W3C | ||
// spec explicitly states that this algorithm does *not* walk the | ||
// prototype chain, and therefore all Object prototypes are live | ||
// (assigned as a reference). | ||
if (_hasProp(input, prop)) { | ||
output[prop] = _iSClone(input[prop], tMap); | ||
Object.defineProperty(output, prop, desc); | ||
}); | ||
} | ||
function _attemptCustomClone(obj) { | ||
var proc; | ||
var copy; | ||
var procIdx = _customCloneProcedures.length; | ||
// Note that if two procedures passed in detect the same type of object, | ||
// the latest procedure will take priority. | ||
while (procIdx--) { | ||
proc = _customCloneProcedures[procIdx]; | ||
if (proc.detect(obj)) { | ||
copy = proc.copy(obj); | ||
break; | ||
} | ||
} | ||
return copy; | ||
} | ||
// This is the module that we expose to the rest of the world, with one | ||
// singular method. CY.clone...get it? :) | ||
// This is the module that we expose to the rest of the world. | ||
// CY.clone...get it? :) | ||
var CY = { | ||
clone: function(input) { | ||
return _iSClone(input, new TransferMap()); | ||
clone: function(input, options) { | ||
var result, map = new Map(); | ||
options = _mergeParams(((typeof options === 'object') ? options : {}), { | ||
// If set to true, this will simply pass a function through to the | ||
// copied object instead of throwing. | ||
allowFunctions: false, | ||
// If set to true, this will stop CY.clone() from throwing *any* errors | ||
// at all if it can't clone the object. Instead, it will simply return | ||
// `null`. This is useful if you don't want a bad clone to halt program | ||
// execution. | ||
suppressErrors: false | ||
}); | ||
// Don't enter try/catch unless suppressErrors is given. | ||
// We want to try to avoid context switches if we can to get the most | ||
// performance possible out of this function. | ||
if (options.suppressErrors === true) { | ||
try { | ||
result = _iSClone(input, map, options); | ||
} catch (err) { | ||
result = null; | ||
} finally { | ||
return result; | ||
} | ||
} | ||
return _iSClone(input, map, options); | ||
}, | ||
// Returns true if the procedure is successfullly defined, false otherwise. | ||
defineCloneProcedure: function(procObj) { | ||
// Make sure we can use this procedure | ||
if (typeof procObj === 'object' && | ||
_isFunc(procObj.detect) && | ||
_isFunc(procObj.copy)) { | ||
_customCloneProcedures.push(procObj); | ||
return true; | ||
} | ||
return false; | ||
}, | ||
clearCustomCloneProcedures: function() { | ||
_customCloneProcedures = []; | ||
} | ||
@@ -226,3 +333,3 @@ }; | ||
module.exports = CY; | ||
} else if (typeof define === 'function' && typeof require === 'function') { | ||
} else if (_isFunc(define) && _isFunc(require)) { | ||
// AMD/RequireJS | ||
@@ -229,0 +336,0 @@ define([], function() { return CY; }); |
{ | ||
"name": "cyclonejs", | ||
"version": "0.0.2", | ||
"version": "1.0.0", | ||
"description": "A pure-javascript adaptation of the W3C's structured cloning algorithm, designed to provide an easy interface for deep copying of complex objects", | ||
"main": "cyclone.js", | ||
"scripts": { | ||
"test": "./node_modules/.bin/jshint *.js test/*.js && ./node_modules/.bin/mocha --reporter spec test/cyclone.mspec.js" | ||
"pretest": "./node_modules/.bin/jshint *.js test/*.js", | ||
"test": "./node_modules/.bin/mocha --reporter spec test/*.mspec.js" | ||
}, | ||
@@ -25,3 +26,4 @@ "repository": { | ||
"expect.js": "~0.2.0", | ||
"jshint": "~2.1.4" | ||
"jshint": "~2.1.4", | ||
"docco": "~0.6.2" | ||
}, | ||
@@ -28,0 +30,0 @@ "testling": { |
[![Build Status](https://travis-ci.org/traviskaufman/cycloneJS.png)](https://travis-ci.org/traviskaufman/cycloneJS) | ||
[![browser support](https://ci.testling.com/traviskaufman/cycloneJS.png)](https://ci.testling.com/traviskaufman/cycloneJS) | ||
@@ -19,3 +18,2 @@ Cyclone is an attempt to implement an adaptation of the HTML5 [Structured | ||
See above about serving the *majority* of use cases. | ||
* Able to copy DOM objects via `cloneNode` | ||
@@ -37,3 +35,4 @@ It can handle objects containing: | ||
* Cyclic references to objects within itself, including nested cyclic references to those objects | ||
* DOM Objects | ||
* Non-enumerable properties, or properties with custom descriptors | ||
* Accessor properties | ||
@@ -63,3 +62,3 @@ ## Usage | ||
Note that cycloneJS also supports AMD loading as well as use within nodeJS, so with node you could do something like | ||
Note that cycloneJS also supports AMD loading as well as use within nodeJS/CJS environments, so with node you could do something like | ||
```javascript | ||
@@ -70,2 +69,72 @@ var CY = require('cyclonejs'); | ||
### Cloning options | ||
`CY.clone` takes an options hash as a second argument, which accepts the following parameters: | ||
* `allowFunctions`: (default: `false`) If set to true, `CY.clone` will simply pass functions through to the copied object, instead of throwing an error saying it can't clone a function. | ||
```javascript | ||
var fnObject = { | ||
f: function() {} | ||
}; | ||
var copy = CY.clone(fnObject, { | ||
allowFunctions: true | ||
}); | ||
console.log(copy.f === fnObject.f) // true | ||
``` | ||
* `suppressErrors`: (default: `false`) If set to true, `CY.clone` will return `null` instead of throwing an Error if it comes across an object it doesn't know how to clone. If you need `CY.clone` to be extremely forgiving, this is the option for you. | ||
```javascript | ||
var mixedBag = { | ||
htmlElement: document.createElement('div'), | ||
ctx: document.getElementsByTagName('canvas')[0].getContext('2d') | ||
}; | ||
var result = CY.clone(mixedBag, { | ||
suppressErrors: true | ||
}); | ||
console.log(result); // null | ||
``` | ||
### Extending `CY.clone()`'s functionality with `defineCloneProcedure` | ||
Because cyclone is built to be environment-agnostic, it is incapable of handling certain cloneable host objects, such as DOM elements, out-of-the-box. However, that doesn't mean that these objects themselves aren't cloneable! For example, most DOM elements can be cloned using `cloneNode()`. Cyclone comes with a method called `defineCloneProcedure` that allows you to add in your own cloning methods for host objects, or other objects, that can't be handled in a standard way by `CY.clone`. Here's how you can add support for cloning DOM nodes using `CY.clone`: | ||
```javascript | ||
var elementTagRE = /^HTML\w*Element$/; | ||
CY.defineCloneProcedure({ | ||
detect: function(obj) { | ||
var klass = Object.prototype.toString.call(obj).slice(8, -1); | ||
return elementTagRE.test(klass) && typeof obj.cloneNode === 'function'; | ||
}, | ||
copy: function(el) { | ||
return el.cloneNode() | ||
} | ||
}); | ||
var orig = document.createElement('div'); | ||
var clone = CY.clone(orig); | ||
console.log(clone !== orig); // True | ||
``` | ||
Here's another example of how to handle most jQuery objects: | ||
```javascript | ||
CY.defineCloneProcedure({ | ||
detect: function(obj) { | ||
return 'jquery' in obj; | ||
}, | ||
copy: function($el) { | ||
return $el.clone(); | ||
} | ||
}); | ||
CY.clone($('#main')); // Returns a cloned jQuery object. | ||
``` | ||
`defineCloneProcedure` takes an object that _must_ contain two properties `detect` and `copy`. | ||
`detect` should be a function that takes an object and returns `true` if and only if `obj` is of the type that you want to define your custom cloning procedure for. In the above example, `detect` ensures that the `[[Class]]` of `obj` is specified as some kind of HTML Element, and that it has a function called `cloneNode`. | ||
`copy` should be a function that takes an object and returns a copy of that object. You are responsible for providing the procedure used to copy the object. In the case of an HTML Element, we simply call its `cloneNode` method. | ||
`defineCloneProcedure` will return `true` if the cloning procedure is successfully defined, and `false` otherwise. Note that `defineCloneProcedure` gives priority to procedures that were defined most recently. That means if you define two cloning procedures whose `detect` functions both return true for a given type of object, the latter's `copy` function will be used. | ||
You can erase all custom cloning procedures defined by calling `CY.clearCustomCloneProcedures()`. | ||
## Contributing/Testing | ||
@@ -81,5 +150,2 @@ First install the module | ||
## Coming Soon | ||
* Ability to define your own protocols for copying unsupported and/or custom | ||
objects. | ||
* Features other people contribute. | ||
Issues and Pull Requests are widely encouraged!! |
@@ -9,9 +9,2 @@ /** | ||
var original; | ||
var number = Math.random(); | ||
var regex = /^someRE$/g; | ||
var bool = false; | ||
var string = 'hey'; | ||
var date = new Date(); | ||
var array = [1, 2, 3]; | ||
var object = {}; | ||
@@ -24,10 +17,10 @@ // http://stackoverflow.com/questions/10776600/ | ||
if (r1 instanceof RegExp && r2 instanceof RegExp) { | ||
props = ["global", "multiline", "ignoreCase", "source"]; | ||
for (var i = 0; i < props.length; i++) { | ||
prop = props[i]; | ||
if (r1[prop] !== r2[prop]) { | ||
return false; | ||
} | ||
props = ['global', 'multiline', 'ignoreCase', 'source']; | ||
for (var i = 0; i < props.length; i++) { | ||
prop = props[i]; | ||
if (r1[prop] !== r2[prop]) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
return true; | ||
} | ||
@@ -38,2 +31,3 @@ return false; | ||
beforeEach(function() { | ||
var val = 0; | ||
/*jshint -W053 */ | ||
@@ -58,2 +52,16 @@ original = { | ||
}; | ||
Object.defineProperty(original, 'nonEnumerable', { | ||
value: 'sup', | ||
enumerable: false, | ||
writable: true, | ||
configurable: true | ||
}); | ||
Object.defineProperty(original, 'accessor', { | ||
get: function() { return val; }, | ||
set: function(v) { val = v; }, | ||
configurable: true, | ||
enumerable: true | ||
}); | ||
}); | ||
@@ -91,3 +99,5 @@ | ||
it('clones number objects', function() { | ||
expect(_isClonedWrapper(original.numberObj, clone.numberObj)).to.be(true); | ||
expect( | ||
_isClonedWrapper(original.numberObj, clone.numberObj) | ||
).to.be(true); | ||
}); | ||
@@ -105,3 +115,5 @@ | ||
it('clones string objects', function() { | ||
expect(_isClonedWrapper(original.stringObj, clone.stringObj)).to.be(true); | ||
expect( | ||
_isClonedWrapper(original.stringObj, clone.stringObj) | ||
).to.be(true); | ||
}); | ||
@@ -139,5 +151,15 @@ }); | ||
// WE CAN GO DEEPER! | ||
expect(original.object.nested.array).not.to.be(clone.object.nested.array); | ||
expect(original.object.nested.array).not.to.be( | ||
clone.object.nested.array | ||
); | ||
expect(original.object.nested.array).to.eql(clone.object.nested.array); | ||
}); | ||
it("throws an error if it doesn't know how to clone an object", | ||
function() { | ||
original.f = function() {}; | ||
expect(function() { | ||
CY.clone(original); | ||
}).to.throwError(); | ||
}); | ||
}); | ||
@@ -164,4 +186,19 @@ }); // Basic Functionality | ||
// Not yet implemented | ||
xdescribe('Custom cloning procedures', function() { | ||
describe('ES5 Considerations', function() { | ||
it('clones non-enumerable properties', function() { | ||
expect(clone.nonEnumerable).to.be(original.nonEnumerable); | ||
}); | ||
it('preserves descriptor values on copied properties', function() { | ||
expect( | ||
Object.getOwnPropertyDescriptor(clone, 'nonEnumerable').enumerable | ||
).to.be(false); | ||
}); | ||
it('can handle accessor properties', function() { | ||
expect(clone.accessor).to.be(0); | ||
}); | ||
}); | ||
describe('Custom cloning procedures', function() { | ||
function Person(name) { | ||
@@ -173,6 +210,7 @@ this.name = name; | ||
}; | ||
var eminem = 'The Real Shady'; | ||
var eminem; | ||
var theRealSlimShady; | ||
beforeEach(function() { | ||
eminem = 'The Real Shady'; | ||
theRealSlimShady = new Person(eminem); | ||
@@ -190,3 +228,3 @@ }); | ||
}, | ||
copy: function(obj) { | ||
copy: function() { | ||
return new Person('Otha Slim Shady'); | ||
@@ -200,6 +238,57 @@ } | ||
}); | ||
it('gives precedence to procs defined at a later time', function() { | ||
var detectFn = function(obj) { | ||
return (obj.name === eminem); | ||
}; | ||
CY.defineCloneProcedure({ | ||
detect: detectFn, | ||
copy: function() { | ||
return 'Kim'; | ||
} | ||
}); | ||
CY.defineCloneProcedure({ | ||
detect: detectFn, | ||
copy: function() { | ||
return eminem; | ||
} | ||
}); | ||
expect(CY.clone(theRealSlimShady)).to.be(eminem); | ||
}); | ||
it('returns true on successful definition', function() { | ||
expect(CY.defineCloneProcedure({ | ||
detect: function(obj) { return obj === true; }, | ||
copy: function(obj) { return 'true'; } | ||
})).to.be(true); | ||
}); | ||
it('returns false on unsuccessful definition', function() { | ||
expect(CY.defineCloneProcedure({})).to.be(false); | ||
}); | ||
it("will fail if an object isn't passed in", function() { | ||
expect(CY.defineCloneProcedure('herp')).to.be(false); | ||
}); | ||
it('will fail if the object lacks a `detect` function', function() { | ||
expect(CY.defineCloneProcedure({ | ||
detect: 'nope', | ||
copy: function() {} | ||
})).to.be(false); | ||
}); | ||
it('will fail if the object lacks a `copy` function', function() { | ||
expect(CY.defineCloneProcedure({ | ||
detect: function() {}, | ||
copy: 'nope' | ||
})).to.be(false); | ||
}); | ||
}); | ||
describe('edge cases', function() { | ||
it('returns the value of a primitive if it is explicitly passed one', function() { | ||
it('returns the value of a primitive if explicitly passed one', function() { | ||
expect(CY.clone(1)).to.be(1); | ||
@@ -218,2 +307,30 @@ expect(CY.clone('hey')).to.be('hey'); | ||
}); | ||
describe('options for CY.clone', function() { | ||
it('has an `allowFunctions` option that will pass functions ' + | ||
'through', function() { | ||
var copy; | ||
original.f = function() {}; | ||
copy = CY.clone(original, { | ||
allowFunctions: true | ||
}); | ||
expect(original.f).to.be(copy.f); | ||
}); | ||
it('has a `suppressErrors` option that will return null on bad clone ' + | ||
'instead of throwing an error', function() { | ||
original.f = function() {}; | ||
expect(function() { | ||
CY.clone(original, { | ||
suppressErrors: true | ||
}); | ||
}).to.not.throwException(); | ||
}); | ||
it('returns null if `suppressErrors` is true and an error is thrown', function() { | ||
original.f = function() {}; | ||
expect(CY.clone(original, {suppressErrors: true})).to.be(null); | ||
}); | ||
}); | ||
}); |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
423852
20
1290
1
147
4
1