├── .eslintrc ├── .github ├── SECURITY.md └── workflows │ ├── node-aught.yml │ ├── node-pretest.yml │ ├── node-tens.yml │ ├── rebase.yml │ └── require-allow-edits.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── .nycrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── component.json ├── index.js ├── package.json └── test └── index.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | 4 | "extends": "@ljharb", 5 | 6 | "rules": { 7 | "eqeqeq": [2, "allow-null"], 8 | "func-name-matching": [2, "always"], 9 | "max-depth": [1, 4], 10 | "no-magic-numbers": [2, { 11 | "ignore": [0, 1, 2], 12 | }], 13 | "no-restricted-syntax": [2, "BreakStatement", "ContinueStatement", "DebuggerStatement", "LabeledStatement", "WithStatement"], 14 | "sort-keys": [0], 15 | }, 16 | 17 | "overrides": [ 18 | { 19 | "files": "test/**", 20 | "rules": { 21 | "max-lines-per-function": 0, 22 | "no-magic-numbers": 0, 23 | }, 24 | }, 25 | ], 26 | } 27 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | Please email [@ljharb](https://github.com/ljharb) or see https://tidelift.com/security if you have a potential security vulnerability to report. 4 | -------------------------------------------------------------------------------- /.github/workflows/node-aught.yml: -------------------------------------------------------------------------------- 1 | name: 'Tests: node.js < 10' 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | tests: 7 | uses: ljharb/actions/.github/workflows/node.yml@main 8 | with: 9 | range: '< 10' 10 | type: minors 11 | command: npm run tests-only 12 | 13 | node: 14 | name: 'node < 10' 15 | needs: [tests] 16 | runs-on: ubuntu-latest 17 | steps: 18 | - run: 'echo tests completed' 19 | -------------------------------------------------------------------------------- /.github/workflows/node-pretest.yml: -------------------------------------------------------------------------------- 1 | name: 'Tests: pretest/posttest' 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | tests: 7 | uses: ljharb/actions/.github/workflows/pretest.yml@main 8 | -------------------------------------------------------------------------------- /.github/workflows/node-tens.yml: -------------------------------------------------------------------------------- 1 | name: 'Tests: node.js >= 10' 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | tests: 7 | uses: ljharb/actions/.github/workflows/node.yml@main 8 | with: 9 | range: '>= 10' 10 | type: minors 11 | command: npm run tests-only 12 | 13 | node: 14 | name: 'node >= 10' 15 | needs: [tests] 16 | runs-on: ubuntu-latest 17 | steps: 18 | - run: 'echo tests completed' 19 | -------------------------------------------------------------------------------- /.github/workflows/rebase.yml: -------------------------------------------------------------------------------- 1 | name: Automatic Rebase 2 | 3 | on: [pull_request_target] 4 | 5 | jobs: 6 | _: 7 | name: "Automatic Rebase" 8 | 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: ljharb/rebase@master 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | -------------------------------------------------------------------------------- /.github/workflows/require-allow-edits.yml: -------------------------------------------------------------------------------- 1 | name: Require “Allow Edits” 2 | 3 | on: [pull_request_target] 4 | 5 | jobs: 6 | _: 7 | name: "Require “Allow Edits”" 8 | 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: ljharb/require-allow-edits@main 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # gitignore 2 | node_modules 3 | 4 | # Only apps should have lockfiles 5 | npm-shrinkwrap.json 6 | package-lock.json 7 | yarn.lock 8 | 9 | .nyc_output/ 10 | coverage/ 11 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # gitignore 2 | node_modules 3 | 4 | # Only apps should have lockfiles 5 | npm-shrinkwrap.json 6 | package-lock.json 7 | yarn.lock 8 | 9 | .nyc_output/ 10 | coverage/ 11 | 12 | .github/workflows 13 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "all": true, 3 | "check-coverage": false, 4 | "reporter": ["text-summary", "text", "html", "json"], 5 | "lines": 86, 6 | "statements": 85.93, 7 | "functions": 82.43, 8 | "branches": 76.06, 9 | "exclude": [ 10 | "coverage", 11 | "dist" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 3.0.2 / 2018-07-19 2 | ================== 3 | * [Fix] Prevent merging `__proto__` property (#48) 4 | * [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `tape` 5 | * [Tests] up to `node` `v10.7`, `v9.11`, `v8.11`, `v7.10`, `v6.14`, `v4.9`; use `nvm install-latest-npm` 6 | 7 | 3.0.1 / 2017-04-27 8 | ================== 9 | * [Fix] deep extending should work with a non-object (#46) 10 | * [Dev Deps] update `tape`, `eslint`, `@ljharb/eslint-config` 11 | * [Tests] up to `node` `v7.9`, `v6.10`, `v4.8`; improve matrix 12 | * [Docs] Switch from vb.teelaun.ch to versionbadg.es for the npm version badge SVG. 13 | * [Docs] Add example to readme (#34) 14 | 15 | 3.0.0 / 2015-07-01 16 | ================== 17 | * [Possible breaking change] Use global "strict" directive (#32) 18 | * [Tests] `int` is an ES3 reserved word 19 | * [Tests] Test up to `io.js` `v2.3` 20 | * [Tests] Add `npm run eslint` 21 | * [Dev Deps] Update `covert`, `jscs` 22 | 23 | 2.0.2 / 2018-07-19 24 | ================== 25 | * [Fix] Prevent merging `__proto__` property (#48) 26 | * [Fix] deep extending should work with a non-object (#46) 27 | * [Docs] Switch from vb.teelaun.ch to versionbadg.es for the npm version badge SVG. 28 | * [Docs] Add example to readme (#34) 29 | * [Dev Deps] update `tape`, `eslint`, `@ljharb/eslint-config`, `covert`, `jscs` 30 | * [Tests] up to `node` `v10.7`, `v9.11`, `v8.11`, `v7.10`, `v6.14`, `v4.9`; `io.js` `v2.3`; use `nvm install-latest-npm` 31 | * [Tests] Add `npm run eslint` 32 | * [Tests] `int` is an ES3 reserved word 33 | 34 | 2.0.1 / 2015-04-25 35 | ================== 36 | * Use an inline `isArray` check, for ES3 browsers. (#27) 37 | * Some old browsers fail when an identifier is `toString` 38 | * Test latest `node` and `io.js` versions on `travis-ci`; speed up builds 39 | * Add license info to package.json (#25) 40 | * Update `tape`, `jscs` 41 | * Adding a CHANGELOG 42 | 43 | 2.0.0 / 2014-10-01 44 | ================== 45 | * Increase code coverage to 100%; run code coverage as part of tests 46 | * Add `npm run lint`; Run linter as part of tests 47 | * Remove nodeType and setInterval checks in isPlainObject 48 | * Updating `tape`, `jscs`, `covert` 49 | * General style and README cleanup 50 | 51 | 1.3.0 / 2014-06-20 52 | ================== 53 | * Add component.json for browser support (#18) 54 | * Use SVG for badges in README (#16) 55 | * Updating `tape`, `covert` 56 | * Updating travis-ci to work with multiple node versions 57 | * Fix `deep === false` bug (returning target as {}) (#14) 58 | * Fixing constructor checks in isPlainObject 59 | * Adding additional test coverage 60 | * Adding `npm run coverage` 61 | * Add LICENSE (#13) 62 | * Adding a warning about `false`, per #11 63 | * General style and whitespace cleanup 64 | 65 | 1.2.1 / 2013-09-14 66 | ================== 67 | * Fixing hasOwnProperty bugs that would only have shown up in specific browsers. Fixes #8 68 | * Updating `tape` 69 | 70 | 1.2.0 / 2013-09-02 71 | ================== 72 | * Updating the README: add badges 73 | * Adding a missing variable reference. 74 | * Using `tape` instead of `buster` for tests; add more tests (#7) 75 | * Adding node 0.10 to Travis CI (#6) 76 | * Enabling "npm test" and cleaning up package.json (#5) 77 | * Add Travis CI. 78 | 79 | 1.1.3 / 2012-12-06 80 | ================== 81 | * Added unit tests. 82 | * Ensure extend function is named. (Looks nicer in a stack trace.) 83 | * README cleanup. 84 | 85 | 1.1.1 / 2012-11-07 86 | ================== 87 | * README cleanup. 88 | * Added installation instructions. 89 | * Added a missing semicolon 90 | 91 | 1.0.0 / 2012-04-08 92 | ================== 93 | * Initial commit 94 | 95 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Stefan Thomas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # extend() for Node.js [![Version Badge][npm-version-svg]][npm-url] 2 | 3 | [![github actions][actions-image]][actions-url] 4 | [![coverage][codecov-image]][codecov-url] 5 | [![License][license-image]][license-url] 6 | [![Downloads][downloads-image]][downloads-url] 7 | 8 | [![npm badge][npm-badge-png]][npm-url] 9 | 10 | `node-extend` is a port of the classic extend() method from jQuery. It behaves as you expect. It is simple, tried and true. 11 | 12 | Notes: 13 | 14 | * Since Node.js >= 4, 15 | [`Object.assign`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) 16 | now offers the same functionality natively (but without the "deep copy" option). 17 | See [ECMAScript 2015 (ES6) in Node.js](https://nodejs.org/en/docs/es6). 18 | * Some native implementations of `Object.assign` in both Node.js and many 19 | browsers (since NPM modules are for the browser too) may not be fully 20 | spec-compliant. 21 | Check [`object.assign`](https://www.npmjs.com/package/object.assign) module for 22 | a compliant candidate. 23 | 24 | ## Installation 25 | 26 | This package is available on [npm][npm-url] as: `extend` 27 | 28 | ``` sh 29 | npm install extend 30 | ``` 31 | 32 | ## Usage 33 | 34 | **Syntax:** extend **(** [`deep`], `target`, `object1`, [`objectN`] **)** 35 | 36 | *Extend one object with one or more others, returning the modified object.* 37 | 38 | **Example:** 39 | 40 | ``` js 41 | var extend = require('extend'); 42 | extend(targetObject, object1, object2); 43 | ``` 44 | 45 | Keep in mind that the target object will be modified, and will be returned from extend(). 46 | 47 | If a boolean true is specified as the first argument, extend performs a deep copy, recursively copying any objects it finds. Otherwise, the copy will share structure with the original object(s). 48 | Undefined properties are not copied. However, properties inherited from the object's prototype will be copied over. 49 | Warning: passing `false` as the first argument is not supported. 50 | 51 | ### Arguments 52 | 53 | * `deep` *Boolean* (optional) 54 | If set, the merge becomes recursive (i.e. deep copy). 55 | * `target` *Object* 56 | The object to extend. 57 | * `object1` *Object* 58 | The object that will be merged into the first. 59 | * `objectN` *Object* (Optional) 60 | More objects to merge into the first. 61 | 62 | ## License 63 | 64 | `node-extend` is licensed under the [MIT License][mit-license-url]. 65 | 66 | ## Acknowledgements 67 | 68 | All credit to the jQuery authors for perfecting this amazing utility. 69 | 70 | Ported to Node.js by [Stefan Thomas][github-justmoon] with contributions by [Jonathan Buchanan][github-insin] and [Jordan Harband][github-ljharb]. 71 | 72 | [npm-url]: https://npmjs.org/package/extend 73 | [npm-version-svg]: https://versionbadg.es/justmoon/node-extend.svg 74 | [npm-badge-png]: https://nodei.co/npm/extend.png?downloads=true&stars=true 75 | [license-image]: https://img.shields.io/npm/l/extend.svg 76 | [license-url]: LICENSE 77 | [downloads-image]: https://img.shields.io/npm/dm/extend.svg 78 | [downloads-url]: https://npm-stat.com/charts.html?package=extend 79 | [codecov-image]: https://codecov.io/gh/justmoon/node-extend/branch/main/graphs/badge.svg 80 | [codecov-url]: https://app.codecov.io/gh/justmoon/node-extend/ 81 | [actions-image]: https://img.shields.io/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/justmoon/node-extend 82 | [actions-url]: https://github.com/justmoon/node-extend/actions 83 | [github-justmoon]: https://github.com/justmoon 84 | [github-insin]: https://github.com/insin 85 | [github-ljharb]: https://github.com/ljharb 86 | [mit-license-url]: http://opensource.org/licenses/MIT 87 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "extend", 3 | "author": "Stefan Thomas (http://www.justmoon.net)", 4 | "version": "3.0.2", 5 | "description": "Port of jQuery.extend for node.js and the browser.", 6 | "scripts": [ 7 | "index.js" 8 | ], 9 | "contributors": [ 10 | { 11 | "name": "Jordan Harband", 12 | "url": "https://github.com/ljharb" 13 | } 14 | ], 15 | "keywords": [ 16 | "extend", 17 | "clone", 18 | "merge" 19 | ], 20 | "repository" : { 21 | "type": "git", 22 | "url": "https://github.com/justmoon/node-extend.git" 23 | }, 24 | "dependencies": { 25 | }, 26 | "devDependencies": { 27 | "@ljharb/eslint-config": "^20.1.0", 28 | "aud": "^1.1.5", 29 | "eslint": "^8.6.0", 30 | "nyc": "^10.3.2", 31 | "safe-publish-latest": "^2.0.0", 32 | "tape": "^4.14.0" 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var hasOwn = Object.prototype.hasOwnProperty; 4 | var toStr = Object.prototype.toString; 5 | var defineProperty = Object.defineProperty; 6 | var gOPD = Object.getOwnPropertyDescriptor; 7 | 8 | var isArray = function isArray(arr) { 9 | if (typeof Array.isArray === 'function') { 10 | return Array.isArray(arr); 11 | } 12 | 13 | return toStr.call(arr) === '[object Array]'; 14 | }; 15 | 16 | var isPlainObject = function isPlainObject(obj) { 17 | if (!obj || toStr.call(obj) !== '[object Object]') { 18 | return false; 19 | } 20 | 21 | var hasOwnConstructor = hasOwn.call(obj, 'constructor'); 22 | var hasIsPrototypeOf = obj.constructor && obj.constructor.prototype && hasOwn.call(obj.constructor.prototype, 'isPrototypeOf'); 23 | // Not own constructor property must be Object 24 | if (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) { 25 | return false; 26 | } 27 | 28 | // Own properties are enumerated firstly, so to speed up, if last one is own, then all properties are own. 29 | var key; 30 | for (key in obj) { /**/ } 31 | 32 | return typeof key === 'undefined' || hasOwn.call(obj, key); 33 | }; 34 | 35 | // If name is '__proto__', and Object.defineProperty is available, define __proto__ as an own property on target 36 | var setProperty = function setProperty(target, options) { 37 | if (defineProperty && options.name === '__proto__') { 38 | defineProperty(target, options.name, { 39 | enumerable: true, 40 | configurable: true, 41 | value: options.newValue, 42 | writable: true 43 | }); 44 | } else { 45 | // eslint-disable-next-line no-param-reassign 46 | target[options.name] = options.newValue; 47 | } 48 | }; 49 | 50 | // Return undefined instead of __proto__ if '__proto__' is not an own property 51 | var getProperty = function getProperty(obj, name) { 52 | if (name === '__proto__') { 53 | if (!hasOwn.call(obj, name)) { 54 | return void 0; 55 | } else if (gOPD) { 56 | // In early versions of node, obj['__proto__'] is buggy when obj has __proto__ as an own property. Object.getOwnPropertyDescriptor() works. 57 | return gOPD(obj, name).value; 58 | } 59 | } 60 | 61 | return obj[name]; 62 | }; 63 | 64 | module.exports = function extend() { 65 | var options, name, src, copy, copyIsArray, clone; 66 | var target = arguments[0]; 67 | var i = 1; 68 | var length = arguments.length; 69 | var deep = false; 70 | 71 | // Handle a deep copy situation 72 | if (typeof target === 'boolean') { 73 | deep = target; 74 | target = arguments[1] || {}; 75 | // skip the boolean and the target 76 | i = 2; 77 | } 78 | if (target == null || (typeof target !== 'object' && typeof target !== 'function')) { 79 | target = {}; 80 | } 81 | 82 | for (; i < length; ++i) { 83 | options = arguments[i]; 84 | // Only deal with non-null/undefined values 85 | if (options != null) { 86 | // Extend the base object 87 | for (name in options) { 88 | src = getProperty(target, name); 89 | copy = getProperty(options, name); 90 | 91 | // Prevent never-ending loop 92 | if (target !== copy) { 93 | // Recurse if we're merging plain objects or arrays 94 | if (deep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) { 95 | if (copyIsArray) { 96 | copyIsArray = false; 97 | clone = src && isArray(src) ? src : []; 98 | } else { 99 | clone = src && isPlainObject(src) ? src : {}; 100 | } 101 | 102 | // Never move original objects, clone them 103 | setProperty(target, { name: name, newValue: extend(deep, clone, copy) }); 104 | 105 | // Don't bring in undefined values 106 | } else if (typeof copy !== 'undefined') { 107 | setProperty(target, { name: name, newValue: copy }); 108 | } 109 | } 110 | } 111 | } 112 | } 113 | 114 | // Return the modified object 115 | return target; 116 | }; 117 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "extend", 3 | "author": "Stefan Thomas (http://www.justmoon.net)", 4 | "version": "3.0.2", 5 | "description": "Port of jQuery.extend for node.js and the browser", 6 | "main": "index", 7 | "scripts": { 8 | "prepublishOnly": "safe-publish-latest", 9 | "prepublish": "not-in-publish || npm run prepublishOnly", 10 | "pretest": "npm run lint", 11 | "test": "npm run tests-only", 12 | "posttest": "npx npm@\">= 10.2\" audit --production", 13 | "tests-only": "nyc tape 'test/**/*.js'", 14 | "lint": "eslint --ext=js,mjs ." 15 | }, 16 | "contributors": [ 17 | { 18 | "name": "Jordan Harband", 19 | "url": "https://github.com/ljharb" 20 | } 21 | ], 22 | "keywords": [ 23 | "extend", 24 | "clone", 25 | "merge" 26 | ], 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/justmoon/node-extend.git" 30 | }, 31 | "devDependencies": { 32 | "@ljharb/eslint-config": "^21.1.1", 33 | "eslint": "=8.8.0", 34 | "nyc": "^10.3.2", 35 | "safe-publish-latest": "^2.0.0", 36 | "tape": "^4.17.0" 37 | }, 38 | "license": "MIT", 39 | "engines": { 40 | "node": ">= 0.4" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var extend = require('../'); 4 | var test = require('tape'); 5 | 6 | var str = 'me a test'; 7 | var integer = 10; 8 | var arr = [1, 'what', new Date(81, 8, 4)]; 9 | var date = new Date(81, 4, 13); 10 | 11 | var Foo = function () {}; 12 | 13 | var obj = { 14 | str: str, 15 | integer: integer, 16 | arr: arr, 17 | date: date, 18 | constructor: 'fake', 19 | isPrototypeOf: 'not a function', 20 | foo: new Foo() 21 | }; 22 | 23 | var deep = { 24 | ori: obj, 25 | layer: { 26 | integer: 10, 27 | str: 'str', 28 | date: new Date(84, 5, 12), 29 | arr: [101, 'dude', new Date(82, 10, 4)], 30 | deep: { 31 | str: obj.str, 32 | integer: integer, 33 | arr: obj.arr, 34 | date: new Date(81, 7, 4) 35 | } 36 | } 37 | }; 38 | 39 | test('missing arguments', function (t) { 40 | t.deepEqual(extend(undefined, { a: 1 }), { a: 1 }, 'missing first argument is second argument'); 41 | t.deepEqual(extend({ a: 1 }), { a: 1 }, 'missing second argument is first argument'); 42 | t.deepEqual(extend(true, undefined, { a: 1 }), { a: 1 }, 'deep: missing first argument is second argument'); 43 | t.deepEqual(extend(true, { a: 1 }), { a: 1 }, 'deep: missing second argument is first argument'); 44 | t.deepEqual(extend(), {}, 'no arguments is object'); 45 | t.end(); 46 | }); 47 | 48 | test('merge string with string', function (t) { 49 | var ori = 'what u gonna say'; 50 | var target = extend(ori, str); 51 | var expectedTarget = { 52 | 0: 'm', 53 | 1: 'e', 54 | 2: ' ', 55 | 3: 'a', 56 | 4: ' ', 57 | 5: 't', 58 | 6: 'e', 59 | 7: 's', 60 | 8: 't' 61 | }; 62 | 63 | t.equal(ori, 'what u gonna say', 'original string 1 is unchanged'); 64 | t.equal(str, 'me a test', 'original string 2 is unchanged'); 65 | t.deepEqual(target, expectedTarget, 'string + string is merged object form of string'); 66 | t.end(); 67 | }); 68 | 69 | test('merge string with number', function (t) { 70 | var ori = 'what u gonna say'; 71 | var target = extend(ori, 10); 72 | 73 | t.equal(ori, 'what u gonna say', 'original string is unchanged'); 74 | t.deepEqual(target, {}, 'string + number is empty object'); 75 | 76 | t.end(); 77 | }); 78 | 79 | test('merge string with array', function (t) { 80 | var ori = 'what u gonna say'; 81 | var target = extend(ori, arr); 82 | 83 | t.equal(ori, 'what u gonna say', 'original string is unchanged'); 84 | t.deepEqual(arr, [1, 'what', new Date(81, 8, 4)], 'array is unchanged'); 85 | t.deepEqual(target, { 86 | 0: 1, 87 | 1: 'what', 88 | 2: new Date(81, 8, 4) 89 | }, 'string + array is array'); 90 | t.end(); 91 | }); 92 | 93 | test('merge string with date', function (t) { 94 | var ori = 'what u gonna say'; 95 | var target = extend(ori, date); 96 | 97 | var testDate = new Date(81, 4, 13); 98 | t.equal(ori, 'what u gonna say', 'original string is unchanged'); 99 | t.deepEqual(date, testDate, 'date is unchanged'); 100 | t.deepEqual(target, testDate, 'string + date is date'); 101 | t.end(); 102 | }); 103 | 104 | test('merge string with obj', function (t) { 105 | var ori = 'what u gonna say'; 106 | var target = extend(ori, obj); 107 | 108 | t.equal(ori, 'what u gonna say', 'original string is unchanged'); 109 | var testObj = { 110 | str: 'me a test', 111 | integer: 10, 112 | arr: [1, 'what', new Date(81, 8, 4)], 113 | date: new Date(81, 4, 13), 114 | constructor: 'fake', 115 | isPrototypeOf: 'not a function', 116 | foo: new Foo() 117 | }; 118 | t.deepEqual(obj, testObj, 'original obj is unchanged'); 119 | t.deepEqual(target, testObj, 'string + obj is obj'); 120 | t.end(); 121 | }); 122 | 123 | test('merge number with string', function (t) { 124 | var ori = 20; 125 | var target = extend(ori, str); 126 | 127 | t.equal(ori, 20, 'number is unchanged'); 128 | t.equal(str, 'me a test', 'string is unchanged'); 129 | t.deepEqual(target, { 130 | 0: 'm', 131 | 1: 'e', 132 | 2: ' ', 133 | 3: 'a', 134 | 4: ' ', 135 | 5: 't', 136 | 6: 'e', 137 | 7: 's', 138 | 8: 't' 139 | }, 'number + string is object form of string'); 140 | t.end(); 141 | }); 142 | 143 | test('merge number with number', function (t) { 144 | t.deepEqual(extend(20, 10), {}, 'number + number is empty object'); 145 | t.end(); 146 | }); 147 | 148 | test('merge number with array', function (t) { 149 | var target = extend(20, arr); 150 | 151 | t.deepEqual(arr, [1, 'what', new Date(81, 8, 4)], 'array is unchanged'); 152 | t.deepEqual(target, { 153 | 0: 1, 154 | 1: 'what', 155 | 2: new Date(81, 8, 4) 156 | }, 'number + arr is object with array contents'); 157 | t.end(); 158 | }); 159 | 160 | test('merge number with date', function (t) { 161 | var target = extend(20, date); 162 | var testDate = new Date(81, 4, 13); 163 | 164 | t.deepEqual(date, testDate, 'original date is unchanged'); 165 | t.deepEqual(target, testDate, 'number + date is date'); 166 | t.end(); 167 | }); 168 | 169 | test('merge number with object', function (t) { 170 | var target = extend(20, obj); 171 | var testObj = { 172 | str: 'me a test', 173 | integer: 10, 174 | arr: [1, 'what', new Date(81, 8, 4)], 175 | date: new Date(81, 4, 13), 176 | constructor: 'fake', 177 | isPrototypeOf: 'not a function', 178 | foo: new Foo() 179 | }; 180 | 181 | t.deepEqual(obj, testObj, 'obj is unchanged'); 182 | t.deepEqual(target, testObj, 'number + obj is obj'); 183 | t.end(); 184 | }); 185 | 186 | test('merge array with string', function (t) { 187 | var ori = [1, 2, 3, 4, 5, 6]; 188 | var target = extend(ori, str); 189 | 190 | t.deepEqual(ori, str.split(''), 'array is changed to be an array of string chars'); 191 | t.equal(str, 'me a test', 'string is unchanged'); 192 | t.deepEqual(target, { 193 | 0: 'm', 194 | 1: 'e', 195 | 2: ' ', 196 | 3: 'a', 197 | 4: ' ', 198 | 5: 't', 199 | 6: 'e', 200 | 7: 's', 201 | 8: 't' 202 | }, 'array + string is object form of string'); 203 | t.end(); 204 | }); 205 | 206 | test('merge array with number', function (t) { 207 | var ori = [1, 2, 3, 4, 5, 6]; 208 | var target = extend(ori, 10); 209 | 210 | t.deepEqual(ori, [1, 2, 3, 4, 5, 6], 'array is unchanged'); 211 | t.deepEqual(target, ori, 'array + number is array'); 212 | t.end(); 213 | }); 214 | 215 | test('merge array with array', function (t) { 216 | var ori = [1, 2, 3, 4, 5, 6]; 217 | var target = extend(ori, arr); 218 | var testDate = new Date(81, 8, 4); 219 | var expectedTarget = [1, 'what', testDate, 4, 5, 6]; 220 | 221 | t.deepEqual(ori, expectedTarget, 'array + array merges arrays; changes first array'); 222 | t.deepEqual(arr, [1, 'what', testDate], 'second array is unchanged'); 223 | t.deepEqual(target, expectedTarget, 'array + array is merged array'); 224 | t.end(); 225 | }); 226 | 227 | test('merge array with date', function (t) { 228 | var ori = [1, 2, 3, 4, 5, 6]; 229 | var target = extend(ori, date); 230 | var testDate = new Date(81, 4, 13); 231 | var testArray = [1, 2, 3, 4, 5, 6]; 232 | 233 | t.deepEqual(ori, testArray, 'array is unchanged'); 234 | t.deepEqual(date, testDate, 'date is unchanged'); 235 | t.deepEqual(target, testArray, 'array + date is array'); 236 | t.end(); 237 | }); 238 | 239 | test('merge array with object', function (t) { 240 | var ori = [1, 2, 3, 4, 5, 6]; 241 | var target = extend(ori, obj); 242 | var testObject = { 243 | str: 'me a test', 244 | integer: 10, 245 | arr: [1, 'what', new Date(81, 8, 4)], 246 | date: new Date(81, 4, 13), 247 | constructor: 'fake', 248 | isPrototypeOf: 'not a function', 249 | foo: new Foo() 250 | }; 251 | 252 | t.deepEqual(obj, testObject, 'obj is unchanged'); 253 | t.equal(ori.length, 6, 'array has proper length'); 254 | t.equal(ori.str, obj.str, 'array has obj.str property'); 255 | t.equal(ori.integer, obj.integer, 'array has obj.integer property'); 256 | t.deepEqual(ori.arr, obj.arr, 'array has obj.arr property'); 257 | t.equal(ori.date, obj.date, 'array has obj.date property'); 258 | 259 | t.equal(target.length, 6, 'target has proper length'); 260 | t.equal(target.str, obj.str, 'target has obj.str property'); 261 | t.equal(target.integer, obj.integer, 'target has obj.integer property'); 262 | t.deepEqual(target.arr, obj.arr, 'target has obj.arr property'); 263 | t.equal(target.date, obj.date, 'target has obj.date property'); 264 | t.end(); 265 | }); 266 | 267 | test('merge date with string', function (t) { 268 | var ori = new Date(81, 9, 20); 269 | var target = extend(ori, str); 270 | var testObject = { 271 | 0: 'm', 272 | 1: 'e', 273 | 2: ' ', 274 | 3: 'a', 275 | 4: ' ', 276 | 5: 't', 277 | 6: 'e', 278 | 7: 's', 279 | 8: 't' 280 | }; 281 | 282 | t.deepEqual(ori, testObject, 'date is changed to object form of string'); 283 | t.equal(str, 'me a test', 'string is unchanged'); 284 | t.deepEqual(target, testObject, 'date + string is object form of string'); 285 | t.end(); 286 | }); 287 | 288 | test('merge date with number', function (t) { 289 | var ori = new Date(81, 9, 20); 290 | var target = extend(ori, 10); 291 | 292 | t.deepEqual(ori, {}, 'date is changed to empty object'); 293 | t.deepEqual(target, {}, 'date + number is empty object'); 294 | t.end(); 295 | }); 296 | 297 | test('merge date with array', function (t) { 298 | var ori = new Date(81, 9, 20); 299 | var target = extend(ori, arr); 300 | var testDate = new Date(81, 9, 20); 301 | var testArray = [1, 'what', new Date(81, 8, 4)]; 302 | 303 | t.deepEqual(ori, testDate, 'date is unchanged'); 304 | t.deepEqual(arr, testArray, 'array is unchanged'); 305 | t.deepEqual(target, testDate, 'date + array is date'); 306 | t.end(); 307 | }); 308 | 309 | test('merge date with date', function (t) { 310 | var ori = new Date(81, 9, 20); 311 | var target = extend(ori, date); 312 | 313 | t.deepEqual(ori, {}, 'date is empty object'); 314 | t.deepEqual(target, {}, 'date + date is empty object'); 315 | t.end(); 316 | }); 317 | 318 | test('merge date with object', function (t) { 319 | var ori = new Date(81, 9, 20); 320 | var target = extend(ori, obj); 321 | var testDate = new Date(81, 8, 4); 322 | var testObject = { 323 | str: 'me a test', 324 | integer: 10, 325 | arr: [1, 'what', testDate], 326 | date: new Date(81, 4, 13), 327 | constructor: 'fake', 328 | isPrototypeOf: 'not a function', 329 | foo: new Foo() 330 | }; 331 | 332 | t.deepEqual(obj, testObject, 'original object is unchanged'); 333 | t.deepEqual(ori, testObject, 'date becomes original object'); 334 | t.deepEqual(target, testObject, 'date + object is object'); 335 | t.end(); 336 | }); 337 | 338 | test('merge object with string', function (t) { 339 | var testDate = new Date(81, 7, 26); 340 | var ori = { 341 | str: 'no shit', 342 | integer: 76, 343 | arr: [1, 2, 3, 4], 344 | date: testDate 345 | }; 346 | var target = extend(ori, str); 347 | var testObj = { 348 | 0: 'm', 349 | 1: 'e', 350 | 2: ' ', 351 | 3: 'a', 352 | 4: ' ', 353 | 5: 't', 354 | 6: 'e', 355 | 7: 's', 356 | 8: 't', 357 | str: 'no shit', 358 | integer: 76, 359 | arr: [1, 2, 3, 4], 360 | date: testDate 361 | }; 362 | 363 | t.deepEqual(ori, testObj, 'original object updated'); 364 | t.equal(str, 'me a test', 'string is unchanged'); 365 | t.deepEqual(target, testObj, 'object + string is object + object form of string'); 366 | t.end(); 367 | }); 368 | 369 | test('merge object with number', function (t) { 370 | var ori = { 371 | str: 'no shit', 372 | integer: 76, 373 | arr: [1, 2, 3, 4], 374 | date: new Date(81, 7, 26) 375 | }; 376 | var testObject = { 377 | str: 'no shit', 378 | integer: 76, 379 | arr: [1, 2, 3, 4], 380 | date: new Date(81, 7, 26) 381 | }; 382 | var target = extend(ori, 10); 383 | t.deepEqual(ori, testObject, 'object is unchanged'); 384 | t.deepEqual(target, testObject, 'object + number is object'); 385 | t.end(); 386 | }); 387 | 388 | test('merge object with array', function (t) { 389 | var ori = { 390 | str: 'no shit', 391 | integer: 76, 392 | arr: [1, 2, 3, 4], 393 | date: new Date(81, 7, 26) 394 | }; 395 | var target = extend(ori, arr); 396 | var testObject = { 397 | 0: 1, 398 | 1: 'what', 399 | 2: new Date(81, 8, 4), 400 | str: 'no shit', 401 | integer: 76, 402 | arr: [1, 2, 3, 4], 403 | date: new Date(81, 7, 26) 404 | }; 405 | 406 | t.deepEqual(ori, testObject, 'original object is merged'); 407 | t.deepEqual(arr, [1, 'what', testObject[2]], 'array is unchanged'); 408 | t.deepEqual(target, testObject, 'object + array is merged object'); 409 | t.end(); 410 | }); 411 | 412 | test('merge object with date', function (t) { 413 | var ori = { 414 | str: 'no shit', 415 | integer: 76, 416 | arr: [1, 2, 3, 4], 417 | date: new Date(81, 7, 26) 418 | }; 419 | var target = extend(ori, date); 420 | var testObject = { 421 | str: 'no shit', 422 | integer: 76, 423 | arr: [1, 2, 3, 4], 424 | date: new Date(81, 7, 26) 425 | }; 426 | 427 | t.deepEqual(ori, testObject, 'original object is unchanged'); 428 | t.deepEqual(date, new Date(81, 4, 13), 'date is unchanged'); 429 | t.deepEqual(target, testObject, 'object + date is object'); 430 | t.end(); 431 | }); 432 | 433 | test('merge object with object', function (t) { 434 | var ori = { 435 | str: 'no shit', 436 | integer: 76, 437 | arr: [1, 2, 3, 4], 438 | date: new Date(81, 7, 26), 439 | foo: 'bar' 440 | }; 441 | var target = extend(ori, obj); 442 | var expectedObj = { 443 | str: 'me a test', 444 | integer: 10, 445 | arr: [1, 'what', new Date(81, 8, 4)], 446 | date: new Date(81, 4, 13), 447 | constructor: 'fake', 448 | isPrototypeOf: 'not a function', 449 | foo: new Foo() 450 | }; 451 | var expectedTarget = { 452 | str: 'me a test', 453 | integer: 10, 454 | arr: [1, 'what', new Date(81, 8, 4)], 455 | date: new Date(81, 4, 13), 456 | constructor: 'fake', 457 | isPrototypeOf: 'not a function', 458 | foo: new Foo() 459 | }; 460 | 461 | t.deepEqual(obj, expectedObj, 'obj is unchanged'); 462 | t.deepEqual(ori, expectedTarget, 'original has been merged'); 463 | t.deepEqual(target, expectedTarget, 'object + object is merged object'); 464 | t.end(); 465 | }); 466 | 467 | test('deep clone', function (t) { 468 | var ori = { 469 | str: 'no shit', 470 | integer: 76, 471 | arr: [1, 2, 3, 4], 472 | date: new Date(81, 7, 26), 473 | layer: { deep: { integer: 42 } } 474 | }; 475 | var target = extend(true, ori, deep); 476 | 477 | t.deepEqual(ori, { 478 | str: 'no shit', 479 | integer: 76, 480 | arr: [1, 2, 3, 4], 481 | date: new Date(81, 7, 26), 482 | ori: { 483 | str: 'me a test', 484 | integer: 10, 485 | arr: [1, 'what', new Date(81, 8, 4)], 486 | date: new Date(81, 4, 13), 487 | constructor: 'fake', 488 | isPrototypeOf: 'not a function', 489 | foo: new Foo() 490 | }, 491 | layer: { 492 | integer: 10, 493 | str: 'str', 494 | date: new Date(84, 5, 12), 495 | arr: [101, 'dude', new Date(82, 10, 4)], 496 | deep: { 497 | str: 'me a test', 498 | integer: 10, 499 | arr: [1, 'what', new Date(81, 8, 4)], 500 | date: new Date(81, 7, 4) 501 | } 502 | } 503 | }, 'original object is merged'); 504 | t.deepEqual(deep, { 505 | ori: { 506 | str: 'me a test', 507 | integer: 10, 508 | arr: [1, 'what', new Date(81, 8, 4)], 509 | date: new Date(81, 4, 13), 510 | constructor: 'fake', 511 | isPrototypeOf: 'not a function', 512 | foo: new Foo() 513 | }, 514 | layer: { 515 | integer: 10, 516 | str: 'str', 517 | date: new Date(84, 5, 12), 518 | arr: [101, 'dude', new Date(82, 10, 4)], 519 | deep: { 520 | str: 'me a test', 521 | integer: 10, 522 | arr: [1, 'what', new Date(81, 8, 4)], 523 | date: new Date(81, 7, 4) 524 | } 525 | } 526 | }, 'deep is unchanged'); 527 | t.deepEqual(target, { 528 | str: 'no shit', 529 | integer: 76, 530 | arr: [1, 2, 3, 4], 531 | date: new Date(81, 7, 26), 532 | ori: { 533 | str: 'me a test', 534 | integer: 10, 535 | arr: [1, 'what', new Date(81, 8, 4)], 536 | date: new Date(81, 4, 13), 537 | constructor: 'fake', 538 | isPrototypeOf: 'not a function', 539 | foo: new Foo() 540 | }, 541 | layer: { 542 | integer: 10, 543 | str: 'str', 544 | date: new Date(84, 5, 12), 545 | arr: [101, 'dude', new Date(82, 10, 4)], 546 | deep: { 547 | str: 'me a test', 548 | integer: 10, 549 | arr: [1, 'what', new Date(81, 8, 4)], 550 | date: new Date(81, 7, 4) 551 | } 552 | } 553 | }, 'deep + object + object is deeply merged object'); 554 | 555 | target.layer.deep = 339; 556 | t.deepEqual(deep, { 557 | ori: { 558 | str: 'me a test', 559 | integer: 10, 560 | arr: [1, 'what', new Date(81, 8, 4)], 561 | date: new Date(81, 4, 13), 562 | constructor: 'fake', 563 | isPrototypeOf: 'not a function', 564 | foo: new Foo() 565 | }, 566 | layer: { 567 | integer: 10, 568 | str: 'str', 569 | date: new Date(84, 5, 12), 570 | arr: [101, 'dude', new Date(82, 10, 4)], 571 | deep: { 572 | str: 'me a test', 573 | integer: 10, 574 | arr: [1, 'what', new Date(81, 8, 4)], 575 | date: new Date(81, 7, 4) 576 | } 577 | } 578 | }, 'deep is unchanged after setting target property'); 579 | // ----- NEVER USE EXTEND WITH THE ABOVE SITUATION ------------------------------ 580 | t.end(); 581 | }); 582 | 583 | test('deep clone; arrays are merged', function (t) { 584 | var defaults = { arr: [1, 2, 3] }; 585 | var override = { arr: ['x'] }; 586 | var expectedTarget = { arr: ['x', 2, 3] }; 587 | 588 | var target = extend(true, defaults, override); 589 | 590 | t.deepEqual(target, expectedTarget, 'arrays are merged'); 591 | t.end(); 592 | }); 593 | 594 | test('deep clone === false; objects merged normally', function (t) { 595 | var defaults = { a: 1 }; 596 | var override = { a: 2 }; 597 | var target = extend(false, defaults, override); 598 | t.deepEqual(target, override, 'deep === false handled normally'); 599 | t.end(); 600 | }); 601 | 602 | test('pass in null; should create a valid object', function (t) { 603 | var override = { a: 1 }; 604 | var target = extend(null, override); 605 | t.deepEqual(target, override, 'null object handled normally'); 606 | t.end(); 607 | }); 608 | 609 | test('works without Array.isArray', function (t) { 610 | var savedIsArray = Array.isArray; 611 | Array.isArray = false; // don't delete, to preserve enumerability 612 | var target = []; 613 | var source = [1, [2], { 3: true }]; 614 | t.deepEqual( 615 | extend(true, target, source), 616 | [1, [2], { 3: true }], 617 | 'It works without Array.isArray' 618 | ); 619 | Array.isArray = savedIsArray; 620 | t.end(); 621 | }); 622 | 623 | test('non-object target', function (t) { 624 | t.deepEqual(extend(3.14, { a: 'b' }), { a: 'b' }); 625 | t.deepEqual(extend(true, 3.14, { a: 'b' }), { a: 'b' }); 626 | 627 | t.end(); 628 | }); 629 | 630 | test('__proto__ is merged as an own property', function (t) { 631 | var malicious = { fred: 1 }; 632 | Object.defineProperty(malicious, '__proto__', { value: { george: 1 }, enumerable: true }); 633 | var target = {}; 634 | extend(true, target, malicious); 635 | t.notOk(target.george); 636 | t.ok(Object.prototype.hasOwnProperty.call(target, '__proto__')); 637 | t.deepEqual(Object.getOwnPropertyDescriptor(target, '__proto__').value, { george: 1 }); 638 | 639 | t.end(); 640 | }); 641 | --------------------------------------------------------------------------------