diff --git a/features.txt b/features.txt index 478e419365201b7e06289a52dbcc5f45bf509616..07d42907ecf98e7e6ef4bd6826e947f74b907643 100644 --- a/features.txt +++ b/features.txt @@ -7,6 +7,10 @@ # # https://github.com/tc39/process-document +# Object.fromEntries +# https://github.com/tc39/proposal-object-from-entries +Object.fromEntries + # BigInt # https://github.com/tc39/proposal-bigint BigInt diff --git a/test/built-ins/Object/fromEntries/evaluation-order.js b/test/built-ins/Object/fromEntries/evaluation-order.js new file mode 100644 index 0000000000000000000000000000000000000000..4c9e07a4cb4ec1e74ea30dc7ab44bd2a8eac21ed --- /dev/null +++ b/test/built-ins/Object/fromEntries/evaluation-order.js @@ -0,0 +1,74 @@ +// Copyright (C) 2018 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Evaluation order is iterator.next(), get '0', get '1', toPropertyKey, repeat. +esid: sec-object.fromentries +includes: [compareArray.js] +features: [Symbol.iterator, Object.fromEntries] +---*/ + +var effects = []; + +function makeEntry(label) { + return { + get '0'() { + effects.push('access property "0" of ' + label + ' entry'); + return { + toString: function() { + effects.push('toString of ' + label + ' key'); + return label + ' key'; + }, + }; + }, + get '1'() { + effects.push('access property "1" of ' + label + ' entry'); + return label + ' value'; + }, + }; +} + +var iterable = { + [Symbol.iterator]: function() { + effects.push('get Symbol.iterator'); + var count = 0; + return { + next: function() { + effects.push('next ' + count); + if (count === 0) { + ++count; + return { + done: false, + value: makeEntry('first', 'first key', 'first value'), + }; + } else if (count === 1) { + ++count; + return { + done: false, + value: makeEntry('second', 'second key', 'second value'), + }; + } else { + return { + done: true, + }; + } + }, + }; + }, +}; + +var result = Object.fromEntries(iterable); +assert.compareArray(effects, [ + 'get Symbol.iterator', + 'next 0', + 'access property "0" of first entry', + 'access property "1" of first entry', + 'toString of first key', + 'next 1', + 'access property "0" of second entry', + 'access property "1" of second entry', + 'toString of second key', + 'next 2', +], 'Object.fromEntries evaluation order'); +assert.sameValue(result['first key'], 'first value'); +assert.sameValue(result['second key'], 'second value'); diff --git a/test/built-ins/Object/fromEntries/iterator-closed-for-null-entry.js.js b/test/built-ins/Object/fromEntries/iterator-closed-for-null-entry.js.js new file mode 100644 index 0000000000000000000000000000000000000000..b84d87feacd2e86ca15498aea131df38b98383bb --- /dev/null +++ b/test/built-ins/Object/fromEntries/iterator-closed-for-null-entry.js.js @@ -0,0 +1,39 @@ +// Copyright (C) 2018 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Closes iterators when they return entries which are null. +esid: sec-object.fromentries +features: [Symbol.iterator, Object.fromEntries] +---*/ + +var returned = false; +var iterable = { + [Symbol.iterator]: function() { + var advanced = false; + return { + next: function() { + if (advanced) { + throw new Test262Error('should only advance once'); + } + advanced = true; + return { + done: false, + value: 'null', + }; + }, + return: function() { + if (returned) { + throw new Test262Error('should only return once'); + } + returned = true; + }, + }; + }, +}; + +assert.throws(TypeError, function() { + Object.fromEntries(iterable); +}); + +assert(returned, 'iterator should be closed when entry is null'); diff --git a/test/built-ins/Object/fromEntries/iterator-closed-for-string-entry.js.js b/test/built-ins/Object/fromEntries/iterator-closed-for-string-entry.js.js new file mode 100644 index 0000000000000000000000000000000000000000..b0c5bc9f9098be28cda26f0d9408803e0cb7f2e8 --- /dev/null +++ b/test/built-ins/Object/fromEntries/iterator-closed-for-string-entry.js.js @@ -0,0 +1,39 @@ +// Copyright (C) 2018 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Closes iterators when they return entries which are strings. +esid: sec-object.fromentries +features: [Symbol.iterator, Object.fromEntries] +---*/ + +var returned = false; +var iterable = { + [Symbol.iterator]: function() { + var advanced = false; + return { + next: function() { + if (advanced) { + throw new Test262Error('should only advance once'); + } + advanced = true; + return { + done: false, + value: 'ab', + }; + }, + return: function() { + if (returned) { + throw new Test262Error('should only return once'); + } + returned = true; + }, + }; + }, +}; + +assert.throws(TypeError, function() { + Object.fromEntries(iterable); +}); + +assert(returned, 'iterator should be closed when entry is a string'); diff --git a/test/built-ins/Object/fromEntries/iterator-closed-for-throwing-entry-accessor.js b/test/built-ins/Object/fromEntries/iterator-closed-for-throwing-entry-accessor.js new file mode 100644 index 0000000000000000000000000000000000000000..9e7f9e4a2cfa961f51954c6c45305b73425aad55 --- /dev/null +++ b/test/built-ins/Object/fromEntries/iterator-closed-for-throwing-entry-accessor.js @@ -0,0 +1,45 @@ +// Copyright (C) 2018 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Closes iterators when accessing an entry's properties throws. +esid: sec-object.fromentries +features: [Symbol.iterator, Object.fromEntries] +---*/ + +function DummyError() {} + +var returned = false; +var iterable = { + [Symbol.iterator]: function() { + var advanced = false; + return { + next: function() { + if (advanced) { + throw new Test262Error('should only advance once'); + } + advanced = true; + return { + done: false, + value: { + get '0'() { + throw new DummyError(); + }, + }, + }; + }, + return: function() { + if (returned) { + throw new Test262Error('should only return once'); + } + returned = true; + }, + }; + }, +}; + +assert.throws(DummyError, function() { + Object.fromEntries(iterable); +}); + +assert(returned, 'iterator should be closed when entry property access throws'); diff --git a/test/built-ins/Object/fromEntries/iterator-closed-for-throwing-entry-key-tostring.js b/test/built-ins/Object/fromEntries/iterator-closed-for-throwing-entry-key-tostring.js new file mode 100644 index 0000000000000000000000000000000000000000..5a845b5b20657e87394db529b023b099d88561ec --- /dev/null +++ b/test/built-ins/Object/fromEntries/iterator-closed-for-throwing-entry-key-tostring.js @@ -0,0 +1,47 @@ +// Copyright (C) 2018 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Closes iterators when toString on a key throws. +esid: sec-object.fromentries +features: [Symbol.iterator, Object.fromEntries] +---*/ + +function DummyError() {} + +var returned = false; +var iterable = { + [Symbol.iterator]: function() { + var advanced = false; + return { + next: function() { + if (advanced) { + throw new Test262Error('should only advance once'); + } + advanced = true; + return { + done: false, + value: { + 0: { + toString: function() { + throw new DummyError(); + }, + }, + }, + }; + }, + return: function() { + if (returned) { + throw new Test262Error('should only return once'); + } + returned = true; + }, + }; + }, +}; + +assert.throws(DummyError, function() { + Object.fromEntries(iterable); +}); + +assert(returned, 'iterator should be closed when key toString throws'); diff --git a/test/built-ins/Object/fromEntries/iterator-not-closed-for-next-returning-non-object.js b/test/built-ins/Object/fromEntries/iterator-not-closed-for-next-returning-non-object.js new file mode 100644 index 0000000000000000000000000000000000000000..8f18c974f04f70af3e235c14515ee69ca8ed6bbd --- /dev/null +++ b/test/built-ins/Object/fromEntries/iterator-not-closed-for-next-returning-non-object.js @@ -0,0 +1,25 @@ +// Copyright (C) 2018 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Does not close iterators with a `next` method which returns a non-object. +esid: sec-object.fromentries +features: [Symbol.iterator, Object.fromEntries] +---*/ + +var iterable = { + [Symbol.iterator]: function() { + return { + next: function() { + return null; + }, + return: function() { + throw new Test262Error('should not call return'); + }, + }; + }, +}; + +assert.throws(TypeError, function() { + Object.fromEntries(iterable); +}); diff --git a/test/built-ins/Object/fromEntries/iterator-not-closed-for-throwing-done-accessor.js b/test/built-ins/Object/fromEntries/iterator-not-closed-for-throwing-done-accessor.js new file mode 100644 index 0000000000000000000000000000000000000000..5fd69c116f06e051e38e59a70fd133f1d149118f --- /dev/null +++ b/test/built-ins/Object/fromEntries/iterator-not-closed-for-throwing-done-accessor.js @@ -0,0 +1,32 @@ +// Copyright (C) 2018 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Does not close iterators with a `done` accessor which throws. +esid: sec-object.fromentries +features: [Symbol.iterator, Object.fromEntries] +---*/ + +function DummyError() {} + +var returned = false; +var iterable = { + [Symbol.iterator]: function() { + return { + next: function() { + return { + get done() { + throw new DummyError(); + }, + }; + }, + return: function() { + throw new Test262Error('should not call return'); + }, + }; + }, +}; + +assert.throws(DummyError, function() { + Object.fromEntries(iterable); +}); diff --git a/test/built-ins/Object/fromEntries/iterator-not-closed-for-throwing-next.js b/test/built-ins/Object/fromEntries/iterator-not-closed-for-throwing-next.js new file mode 100644 index 0000000000000000000000000000000000000000..b5ebcdf3f1505d5c458e5f9cdf9d0e1818d542c6 --- /dev/null +++ b/test/built-ins/Object/fromEntries/iterator-not-closed-for-throwing-next.js @@ -0,0 +1,27 @@ +// Copyright (C) 2018 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Does not close iterators with a `next` method which throws. +esid: sec-object.fromentries +features: [Symbol.iterator, Object.fromEntries] +---*/ + +function DummyError() {} + +var iterable = { + [Symbol.iterator]: function() { + return { + next: function() { + throw new DummyError(); + }, + return: function() { + throw new Test262Error('should not call return'); + }, + }; + }, +}; + +assert.throws(DummyError, function() { + Object.fromEntries(iterable); +}); diff --git a/test/built-ins/Object/fromEntries/iterator-not-closed-for-uncallable-next.js b/test/built-ins/Object/fromEntries/iterator-not-closed-for-uncallable-next.js new file mode 100644 index 0000000000000000000000000000000000000000..f9aa9d23e1038c99d3ef008d2a5a63b9c58da5a9 --- /dev/null +++ b/test/built-ins/Object/fromEntries/iterator-not-closed-for-uncallable-next.js @@ -0,0 +1,23 @@ +// Copyright (C) 2018 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Does not close iterators with an uncallable `next` property. +esid: sec-object.fromentries +features: [Symbol.iterator, Object.fromEntries] +---*/ + +var iterable = { + [Symbol.iterator]: function() { + return { + next: null, + return: function() { + throw new Test262Error('should not call return'); + }, + }; + }, +}; + +assert.throws(TypeError, function() { + Object.fromEntries(iterable); +}); diff --git a/test/built-ins/Object/fromEntries/key-order.js b/test/built-ins/Object/fromEntries/key-order.js new file mode 100644 index 0000000000000000000000000000000000000000..f8a08d3fae84d0c05a1ab9b954570cedf9a44098 --- /dev/null +++ b/test/built-ins/Object/fromEntries/key-order.js @@ -0,0 +1,22 @@ +// Copyright (C) 2018 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Key enumeration order of result objects matches the order of entries in the iterable. +esid: sec-object.fromentries +includes: [compareArray.js] +features: [Object.fromEntries] +---*/ + +var entries = [ + ['z', 1], + ['y', 2], + ['x', 3], + ['y', 4], +]; + +var result = Object.fromEntries(entries); +assert.sameValue(result['z'], 1); +assert.sameValue(result['y'], 4); +assert.sameValue(result['x'], 3); +assert.compareArray(Object.keys(result), ['z', 'y', 'x']); diff --git a/test/built-ins/Object/fromEntries/length.js b/test/built-ins/Object/fromEntries/length.js new file mode 100644 index 0000000000000000000000000000000000000000..56615b4d7b8d0e20a6078018fa5ab531d874ee6a --- /dev/null +++ b/test/built-ins/Object/fromEntries/length.js @@ -0,0 +1,15 @@ +// Copyright (C) 2018 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Object.fromEntries.length is 1. +esid: sec-object.fromentries +includes: [propertyHelper.js] +features: [Object.fromEntries] +---*/ + +assert.sameValue(Object.fromEntries.length, 1); + +verifyNotEnumerable(Object.fromEntries, "length"); +verifyNotWritable(Object.fromEntries, "length"); +verifyConfigurable(Object.fromEntries, "length"); diff --git a/test/built-ins/Object/fromEntries/name.js b/test/built-ins/Object/fromEntries/name.js new file mode 100644 index 0000000000000000000000000000000000000000..aa39c2277dd419de14d6b0d797cb67fb94c3e040 --- /dev/null +++ b/test/built-ins/Object/fromEntries/name.js @@ -0,0 +1,15 @@ +// Copyright (C) 2018 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Object.fromEntries.name is "fromEntries". +esid: sec-object.fromentries +includes: [propertyHelper.js] +features: [Object.fromEntries] +---*/ + +assert.sameValue(Object.fromEntries.name, "fromEntries"); + +verifyNotEnumerable(Object.fromEntries, "name"); +verifyNotWritable(Object.fromEntries, "name"); +verifyConfigurable(Object.fromEntries, "name"); diff --git a/test/built-ins/Object/fromEntries/requires-argument.js b/test/built-ins/Object/fromEntries/requires-argument.js new file mode 100644 index 0000000000000000000000000000000000000000..b65051b00395fc9cea98f317fd3707f2ba142308 --- /dev/null +++ b/test/built-ins/Object/fromEntries/requires-argument.js @@ -0,0 +1,12 @@ +// Copyright (C) 2018 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Throws when called without an argument. +esid: sec-object.fromentries +features: [Object.fromEntries] +---*/ + +assert.throws(TypeError, function() { + Object.fromEntries(); +}); diff --git a/test/built-ins/Object/fromEntries/simple-properties.js b/test/built-ins/Object/fromEntries/simple-properties.js new file mode 100644 index 0000000000000000000000000000000000000000..92663304bafb1d4430a715c72e6876fe95d0ed1b --- /dev/null +++ b/test/built-ins/Object/fromEntries/simple-properties.js @@ -0,0 +1,14 @@ +// Copyright (C) 2018 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Creates data properties which are enumerable, writable, and configurable. +esid: sec-object.fromentries +includes: [propertyHelper.js] +features: [Object.fromEntries] +---*/ + +var result = Object.fromEntries([['key', 'value']]); +verifyEnumerable(result, 'key'); +verifyWritable(result, 'key'); +verifyConfigurable(result, 'key'); diff --git a/test/built-ins/Object/fromEntries/string-entry-object-succeeds.js b/test/built-ins/Object/fromEntries/string-entry-object-succeeds.js new file mode 100644 index 0000000000000000000000000000000000000000..d616f4d47a02c22204d84493c41ce0dc8e973b4f --- /dev/null +++ b/test/built-ins/Object/fromEntries/string-entry-object-succeeds.js @@ -0,0 +1,11 @@ +// Copyright (C) 2018 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Succeeds when an entry object is a boxed string. +esid: sec-object.fromentries +features: [Object.fromEntries] +---*/ + +var result = Object.fromEntries([Object('ab')]); +assert.sameValue(result['a'], 'b'); diff --git a/test/built-ins/Object/fromEntries/string-entry-primitive-throws.js b/test/built-ins/Object/fromEntries/string-entry-primitive-throws.js new file mode 100644 index 0000000000000000000000000000000000000000..2a678e3d8aafb340924756daa1e8fb1e7ded4284 --- /dev/null +++ b/test/built-ins/Object/fromEntries/string-entry-primitive-throws.js @@ -0,0 +1,12 @@ +// Copyright (C) 2018 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Throws when an entry object is a primitive string. +esid: sec-object.fromentries +features: [Object.fromEntries] +---*/ + +assert.throws(TypeError, function() { + Object.fromEntries(['ab']); +}); diff --git a/test/built-ins/Object/fromEntries/supports-symbols.js b/test/built-ins/Object/fromEntries/supports-symbols.js new file mode 100644 index 0000000000000000000000000000000000000000..fc729961664ee49b195cc01c4a68bb77210cd8ec --- /dev/null +++ b/test/built-ins/Object/fromEntries/supports-symbols.js @@ -0,0 +1,12 @@ +// Copyright (C) 2018 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Allows symbol keys. +esid: sec-object.fromentries +features: [Symbol, Object.fromEntries] +---*/ + +var key = Symbol(); +var result = Object.fromEntries([[key, 'value']]); +assert.sameValue(result[key], 'value'); diff --git a/test/built-ins/Object/fromEntries/to-property-key.js b/test/built-ins/Object/fromEntries/to-property-key.js new file mode 100644 index 0000000000000000000000000000000000000000..a707787588804fd6ee98fbf3ca77f0147af73a65 --- /dev/null +++ b/test/built-ins/Object/fromEntries/to-property-key.js @@ -0,0 +1,17 @@ +// Copyright (C) 2018 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Coerces keys to strings using ToPropertyKey. +esid: sec-object.fromentries +features: [Symbol.toPrimitive, Object.fromEntries] +---*/ + +var key = { + [Symbol.toPrimitive]: function(hint) { + assert.sameValue(hint, 'string'); + return 'key'; + }, +}; +var result = Object.fromEntries([[key, 'value']]); +assert.sameValue(result.key, 'value'); diff --git a/test/built-ins/Object/fromEntries/uses-define-semantics.js b/test/built-ins/Object/fromEntries/uses-define-semantics.js new file mode 100644 index 0000000000000000000000000000000000000000..b5f56686d1f79865750e4a3841e5a250286a75d1 --- /dev/null +++ b/test/built-ins/Object/fromEntries/uses-define-semantics.js @@ -0,0 +1,20 @@ +// Copyright (C) 2018 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Uses [[DefineOwnProperty]] rather than [[Set]]. +esid: sec-object.fromentries +features: [Object.fromEntries] +---*/ + +Object.defineProperty(Object.prototype, 'property', { + get: function() { + throw new Test262Error('should not trigger getter on Object.prototype'); + }, + set: function() { + throw new Test262Error('should not trigger setter on Object.prototype'); + }, +}); + +var result = Object.fromEntries([['property', 'value']]); +assert.sameValue(result['property'], 'value', ''); diff --git a/test/built-ins/Object/fromEntries/uses-keys-not-iterator.js b/test/built-ins/Object/fromEntries/uses-keys-not-iterator.js new file mode 100644 index 0000000000000000000000000000000000000000..c4302074cb0c5171647b7489d065983fc115b621 --- /dev/null +++ b/test/built-ins/Object/fromEntries/uses-keys-not-iterator.js @@ -0,0 +1,48 @@ +// Copyright (C) 2018 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Reads properties rather than iterating. +esid: sec-object.fromentries +features: [Symbol.iterator, Object.fromEntries] +---*/ + +var iterable = { + [Symbol.iterator]: function() { + var count = 0; + return { + next: function() { + if (count === 0) { + ++count; + return { + done: false, + value: { + '0': 'first key', + '1': 'first value', + get [Symbol.iterator]() { + throw new Test262Error('Object.fromEntries should not access Symbol.iterator on entry objects'); + }, + }, + }; + } else if (count === 1) { + ++count; + Array.prototype[Symbol.iterator] = function() { + throw new Test262Error('Object.fromEntries should not access Symbol.iterator on entry arrays'); + }; + return { + done: false, + value: ['second key', 'second value'], + }; + } else { + return { + done: true, + }; + } + }, + }; + }, +}; + +var result = Object.fromEntries(iterable); +assert.sameValue(result['first key'], 'first value'); +assert.sameValue(result['second key'], 'second value');