| Leo Repp | 58b9f11 | 2021-11-22 11:57:47 +0100 | [diff] [blame^] | 1 | 'use strict'; |
| 2 | |
| 3 | var hasSymbols = require('has-symbols/shams')(); |
| 4 | var forEach = require('for-each'); |
| 5 | var has = require('has'); |
| 6 | |
| 7 | module.exports = function (assign, t) { |
| 8 | t.test('error cases', function (st) { |
| 9 | st['throws'](function () { assign(null); }, TypeError, 'target must be an object'); |
| 10 | st['throws'](function () { assign(undefined); }, TypeError, 'target must be an object'); |
| 11 | st['throws'](function () { assign(null, {}); }, TypeError, 'target must be an object'); |
| 12 | st['throws'](function () { assign(undefined, {}); }, TypeError, 'target must be an object'); |
| 13 | st.end(); |
| 14 | }); |
| 15 | |
| 16 | t.test('non-object target, no sources', function (st) { |
| 17 | var bool = assign(true); |
| 18 | st.equal(typeof bool, 'object', 'bool is object'); |
| 19 | st.equal(Boolean.prototype.valueOf.call(bool), true, 'bool coerces to `true`'); |
| 20 | |
| 21 | var number = assign(1); |
| 22 | st.equal(typeof number, 'object', 'number is object'); |
| 23 | st.equal(Number.prototype.valueOf.call(number), 1, 'number coerces to `1`'); |
| 24 | |
| 25 | var string = assign('1'); |
| 26 | st.equal(typeof string, 'object', 'number is object'); |
| 27 | st.equal(String.prototype.valueOf.call(string), '1', 'number coerces to `"1"`'); |
| 28 | |
| 29 | st.end(); |
| 30 | }); |
| 31 | |
| 32 | t.test('non-object target, with sources', function (st) { |
| 33 | var signal = {}; |
| 34 | |
| 35 | st.test('boolean', function (st2) { |
| 36 | var bool = assign(true, { a: signal }); |
| 37 | st2.equal(typeof bool, 'object', 'bool is object'); |
| 38 | st2.equal(Boolean.prototype.valueOf.call(bool), true, 'bool coerces to `true`'); |
| 39 | st2.equal(bool.a, signal, 'source properties copied'); |
| 40 | st2.end(); |
| 41 | }); |
| 42 | |
| 43 | st.test('number', function (st2) { |
| 44 | var number = assign(1, { a: signal }); |
| 45 | st2.equal(typeof number, 'object', 'number is object'); |
| 46 | st2.equal(Number.prototype.valueOf.call(number), 1, 'number coerces to `1`'); |
| 47 | st2.equal(number.a, signal, 'source properties copied'); |
| 48 | st2.end(); |
| 49 | }); |
| 50 | |
| 51 | st.test('string', function (st2) { |
| 52 | var string = assign('1', { a: signal }); |
| 53 | st2.equal(typeof string, 'object', 'number is object'); |
| 54 | st2.equal(String.prototype.valueOf.call(string), '1', 'number coerces to `"1"`'); |
| 55 | st2.equal(string.a, signal, 'source properties copied'); |
| 56 | st2.end(); |
| 57 | }); |
| 58 | |
| 59 | st.end(); |
| 60 | }); |
| 61 | |
| 62 | t.test('non-object sources', function (st) { |
| 63 | st.deepEqual(assign({ a: 1 }, null, { b: 2 }), { a: 1, b: 2 }, 'ignores null source'); |
| 64 | st.deepEqual(assign({ a: 1 }, { b: 2 }, undefined), { a: 1, b: 2 }, 'ignores undefined source'); |
| 65 | st.end(); |
| 66 | }); |
| 67 | |
| 68 | t.test('returns the modified target object', function (st) { |
| 69 | var target = {}; |
| 70 | var returned = assign(target, { a: 1 }); |
| 71 | st.equal(returned, target, 'returned object is the same reference as the target object'); |
| 72 | st.end(); |
| 73 | }); |
| 74 | |
| 75 | t.test('has the right length', function (st) { |
| 76 | st.equal(assign.length, 2, 'length is 2 => 2 required arguments'); |
| 77 | st.end(); |
| 78 | }); |
| 79 | |
| 80 | t.test('merge two objects', function (st) { |
| 81 | var target = { a: 1 }; |
| 82 | var returned = assign(target, { b: 2 }); |
| 83 | st.deepEqual(returned, { a: 1, b: 2 }, 'returned object has properties from both'); |
| 84 | st.end(); |
| 85 | }); |
| 86 | |
| 87 | t.test('works with functions', function (st) { |
| 88 | var target = function () {}; |
| 89 | target.a = 1; |
| 90 | var returned = assign(target, { b: 2 }); |
| 91 | st.equal(target, returned, 'returned object is target'); |
| 92 | st.equal(returned.a, 1); |
| 93 | st.equal(returned.b, 2); |
| 94 | st.end(); |
| 95 | }); |
| 96 | |
| 97 | t.test('works with primitives', function (st) { |
| 98 | var target = 2; |
| 99 | var source = { b: 42 }; |
| 100 | var returned = assign(target, source); |
| 101 | st.equal(Object.prototype.toString.call(returned), '[object Number]', 'returned is object form of number primitive'); |
| 102 | st.equal(Number(returned), target, 'returned and target have same valueOf'); |
| 103 | st.equal(returned.b, source.b); |
| 104 | st.end(); |
| 105 | }); |
| 106 | |
| 107 | /* globals window */ |
| 108 | t.test('works with window.location', { skip: typeof window === 'undefined' }, function (st) { |
| 109 | var target = {}; |
| 110 | assign(target, window.location); |
| 111 | for (var prop in window.location) { |
| 112 | if (has(window.location, prop)) { |
| 113 | st.deepEqual(target[prop], window.location[prop], prop + ' is copied'); |
| 114 | } |
| 115 | } |
| 116 | st.end(); |
| 117 | }); |
| 118 | |
| 119 | t.test('merge N objects', function (st) { |
| 120 | var target = { a: 1 }; |
| 121 | var source1 = { b: 2 }; |
| 122 | var source2 = { c: 3 }; |
| 123 | var returned = assign(target, source1, source2); |
| 124 | st.deepEqual(returned, { a: 1, b: 2, c: 3 }, 'returned object has properties from all sources'); |
| 125 | st.end(); |
| 126 | }); |
| 127 | |
| 128 | t.test('only iterates over own keys', function (st) { |
| 129 | var Foo = function () {}; |
| 130 | Foo.prototype.bar = true; |
| 131 | var foo = new Foo(); |
| 132 | foo.baz = true; |
| 133 | var target = { a: 1 }; |
| 134 | var returned = assign(target, foo); |
| 135 | st.equal(returned, target, 'returned object is the same reference as the target object'); |
| 136 | st.deepEqual(target, { a: 1, baz: true }, 'returned object has only own properties from both'); |
| 137 | st.end(); |
| 138 | }); |
| 139 | |
| 140 | t.test('includes enumerable symbols, after keys', { skip: !hasSymbols }, function (st) { |
| 141 | var visited = []; |
| 142 | var obj = {}; |
| 143 | Object.defineProperty(obj, 'a', { enumerable: true, get: function () { visited.push('a'); return 42; } }); |
| 144 | var symbol = Symbol('enumerable'); |
| 145 | Object.defineProperty(obj, symbol, { |
| 146 | enumerable: true, |
| 147 | get: function () { visited.push(symbol); return Infinity; } |
| 148 | }); |
| 149 | var nonEnumSymbol = Symbol('non-enumerable'); |
| 150 | Object.defineProperty(obj, nonEnumSymbol, { |
| 151 | enumerable: false, |
| 152 | get: function () { visited.push(nonEnumSymbol); return -Infinity; } |
| 153 | }); |
| 154 | var target = assign({}, obj); |
| 155 | st.deepEqual(visited, ['a', symbol], 'key is visited first, then symbol'); |
| 156 | st.equal(target.a, 42, 'target.a is 42'); |
| 157 | st.equal(target[symbol], Infinity, 'target[symbol] is Infinity'); |
| 158 | st.notEqual(target[nonEnumSymbol], -Infinity, 'target[nonEnumSymbol] is not -Infinity'); |
| 159 | st.end(); |
| 160 | }); |
| 161 | |
| 162 | t.test('does not fail when symbols are not present', { skip: !Object.isFrozen || Object.isFrozen(Object) }, function (st) { |
| 163 | var getSyms; |
| 164 | if (hasSymbols) { |
| 165 | getSyms = Object.getOwnPropertySymbols; |
| 166 | delete Object.getOwnPropertySymbols; |
| 167 | } |
| 168 | |
| 169 | var visited = []; |
| 170 | var obj = {}; |
| 171 | Object.defineProperty(obj, 'a', { enumerable: true, get: function () { visited.push('a'); return 42; } }); |
| 172 | var keys = ['a']; |
| 173 | if (hasSymbols) { |
| 174 | var symbol = Symbol('sym'); |
| 175 | Object.defineProperty(obj, symbol, { |
| 176 | enumerable: true, |
| 177 | get: function () { visited.push(symbol); return Infinity; } |
| 178 | }); |
| 179 | keys.push(symbol); |
| 180 | } |
| 181 | var target = assign({}, obj); |
| 182 | st.deepEqual(visited, keys, 'assign visits expected keys'); |
| 183 | st.equal(target.a, 42, 'target.a is 42'); |
| 184 | |
| 185 | if (hasSymbols) { |
| 186 | st.equal(target[symbol], Infinity); |
| 187 | |
| 188 | Object.getOwnPropertySymbols = getSyms; |
| 189 | } |
| 190 | st.end(); |
| 191 | }); |
| 192 | |
| 193 | t.test('preserves correct property enumeration order', function (st) { |
| 194 | var str = 'abcdefghijklmnopqrst'; |
| 195 | var letters = {}; |
| 196 | forEach(str.split(''), function (letter) { |
| 197 | letters[letter] = letter; |
| 198 | }); |
| 199 | |
| 200 | var n = 5; |
| 201 | st.comment('run the next test ' + n + ' times'); |
| 202 | var object = assign({}, letters); |
| 203 | var actual = ''; |
| 204 | for (var k in object) { |
| 205 | actual += k; |
| 206 | } |
| 207 | for (var i = 0; i < n; ++i) { |
| 208 | st.equal(actual, str, 'property enumeration order should be followed'); |
| 209 | } |
| 210 | st.end(); |
| 211 | }); |
| 212 | |
| 213 | t.test('checks enumerability and existence, in case of modification during [[Get]]', { skip: !Object.defineProperty }, function (st) { |
| 214 | var targetBvalue = {}; |
| 215 | var targetCvalue = {}; |
| 216 | var target = { b: targetBvalue, c: targetCvalue }; |
| 217 | var source = {}; |
| 218 | Object.defineProperty(source, 'a', { |
| 219 | enumerable: true, |
| 220 | get: function () { |
| 221 | delete this.b; |
| 222 | Object.defineProperty(this, 'c', { enumerable: false }); |
| 223 | return 'a'; |
| 224 | } |
| 225 | }); |
| 226 | var sourceBvalue = {}; |
| 227 | var sourceCvalue = {}; |
| 228 | source.b = sourceBvalue; |
| 229 | source.c = sourceCvalue; |
| 230 | var result = assign(target, source); |
| 231 | st.equal(result, target, 'sanity check: result is === target'); |
| 232 | st.equal(result.b, targetBvalue, 'target key not overwritten by deleted source key'); |
| 233 | st.equal(result.c, targetCvalue, 'target key not overwritten by non-enumerable source key'); |
| 234 | |
| 235 | st.end(); |
| 236 | }); |
| 237 | }; |