├── .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 |
--------------------------------------------------------------------------------