UNPKG

object-assign-shim

Version:

ES6 spec-compliant Object.assign shim. Shims Object constructor automatically.

175 lines (148 loc) 5.94 kB
'use strict'; var test = require('tape'); var origObjectAssign = Object.assign; delete Object.assign; require('../index.js'); var assign = Object.assign; var hasSymbols = typeof Symbol === 'function' && typeof Symbol() === 'symbol'; test('error cases', function (t) { t.throws(function () { assign(null); }, TypeError, 'target must be an object'); t.end(); }); test('non-object sources', function (t) { t.deepEqual(assign({ a: 1 }, null, { b: 2 }), { a: 1, b: 2 }, 'ignores null source'); t.deepEqual(assign({ a: 1 }, { b: 2 }, undefined), { a: 1, b: 2 }, 'ignores undefined source'); t.end(); }); test('returns the modified target object', function (t) { var target = {}; var returned = assign(target, { a: 1 }); t.equal(returned, target, 'returned object is the same reference as the target object'); t.end(); }); test('has the right length', function (t) { t.equal(assign.length, 2, 'length is 2 => 2 required arguments'); t.end(); }); test('merge two objects', function (t) { var target = { a: 1 }; var returned = assign(target, { b: 2 }); t.deepEqual(returned, { a: 1, b: 2 }, 'returned object has properties from both'); t.end(); }); test('works with functions', function (t) { var target = function () {}; target.a = 1; var returned = assign(target, { b: 2 }); t.equal(target, returned, 'returned object is target'); t.equal(returned.a, 1); t.equal(returned.b, 2); t.end(); }); test('works with primitives', function (t) { var target = 2; var source = { b: 42 }; var returned = assign(target, source); t.equal(Object.prototype.toString.call(returned), '[object Number]', 'returned is object form of number primitive'); t.equal(Number(returned), target, 'returned and target have same valueOf'); t.equal(returned.b, source.b); t.end(); }); test('merge N objects', function (t) { var target = { a: 1 }; var source1 = { b: 2 }; var source2 = { c: 3 }; var returned = assign(target, source1, source2); t.deepEqual(returned, { a: 1, b: 2, c: 3 }, 'returned object has properties from all sources'); t.end(); }); test('only iterates over own keys', function (t) { var Foo = function () {}; Foo.prototype.bar = true; var foo = new Foo(); foo.baz = true; var target = { a: 1 }; var returned = assign(target, foo); t.equal(returned, target, 'returned object is the same reference as the target object'); t.deepEqual(target, { baz: true, a: 1 }, 'returned object has only own properties from both'); t.end(); }); test('includes enumerable symbols, after keys', { skip: !hasSymbols }, function (t) { var visited = []; var obj = {}; Object.defineProperty(obj, 'a', { get: function () { visited.push('a'); return 42; }, enumerable: true }); var symbol = Symbol('enumerable'); Object.defineProperty(obj, symbol, { get: function () { visited.push(symbol); return Infinity; }, enumerable: true }); var nonEnumSymbol = Symbol('non-enumerable'); Object.defineProperty(obj, nonEnumSymbol, { get: function () { visited.push(nonEnumSymbol); return -Infinity; }, enumerable: false }); var target = assign({}, obj); t.deepEqual(visited, ['a', symbol], 'key is visited first, then symbol'); t.equal(target.a, 42, 'target.a is 42'); t.equal(target[symbol], Infinity, 'target[symbol] is Infinity'); t.notEqual(target[nonEnumSymbol], -Infinity, 'target[nonEnumSymbol] is not -Infinity'); t.end(); }); test('does not fail when symbols are not present', function (t) { var getSyms; if (hasSymbols) { getSyms = Object.getOwnPropertySymbols; delete Object.getOwnPropertySymbols; } var visited = []; var obj = {}; Object.defineProperty(obj, 'a', { get: function () { visited.push('a'); return 42; }, enumerable: true }); if (hasSymbols) { var symbol = Symbol(); Object.defineProperty(obj, symbol, { get: function () { visited.push(symbol); return Infinity; }, enumerable: true }); } var target = assign({}, obj); t.equal(target.a, 42, 'target.a is 42'); t.deepEqual(visited, ['a'], 'only key is visited'); if (hasSymbols) { // sanity check for "visited" array t.equal(obj[symbol], Infinity); t.deepEqual(visited, ['a', symbol], 'symbol is visited manually'); Object.getOwnPropertySymbols = getSyms; } t.end(); }); /*eslint-disable no-new-func */ test('shims the "assign" function on Object ctor', function (t) { var reapplyAssignShim = new Function(require('fs').readFileSync('index.js', {encoding: 'utf8'})); t.test('when Object.assign is present', function (st) { var currObjectAssign = Object.assign; var pseudoOrigAssign = function () {}; Object.assign = pseudoOrigAssign; reapplyAssignShim(); st.equal(Object.assign, pseudoOrigAssign, 'Object.assign is not overridden'); Object.assign = currObjectAssign; st.end(); }); t.test('when Object.assign is not present', function (st) { var currObjectAssign = Object.assign; delete Object.assign; reapplyAssignShim(); st.equal(typeof Object.assign, 'function', 'Object.assign is shimmed'); if (Object.getOwnPropertyDescriptor) { st.equal(Object.getOwnPropertyDescriptor(Object, 'assign').enumerable, false, 'is not enumerable'); } Object.assign = currObjectAssign; st.end(); }); t.test('when Object.assign is present and has pending exceptions', { skip: !origObjectAssign || !Object.preventExtensions }, function (st) { // Firefox 37 still has "pending exception" logic in its Object.assign implementation, // which is 72% slower than our shim, and Firefox 40's native implementation. var thrower = Object.preventExtensions({ 1: 2 }); var error; try { Object.assign(thrower, 'xy'); } catch (e) { error = e; } st.equal(error instanceof TypeError, true, 'error is TypeError'); st.equal(thrower[1], 2, 'thrower[1] === 2'); st.end(); }); t.end(); }); /*eslint-enable no-new-func */ test('working with actual shim', function (t) { t.notEqual(Object.assign, origObjectAssign, 'assign shim is not native Object.assign'); t.end(); });