blob: 998129e3c01f136aaf51a2bd0b4c106b51e528a1 [file] [log] [blame]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
// ES6 Symbol tests -- verifies the API shape and basic functionality
WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
function VerifyToPropertyKey(key) {
var obj = {};
assert.isFalse(obj.hasOwnProperty(key), "Object#hasOwnProperty uses ToPropertyKey. Initially we don't have the property.");
assert.doesNotThrow(function() { Object.defineProperty(obj, key, { value: 'something', enumerable: true }); }, "Object.defineProperty uses ToPropertyKey. Property is added to the object");
assert.isTrue(obj.hasOwnProperty(key), "Object#hasOwnProperty uses ToPropertyKey on argument. We should have the property now.");
assert.isTrue(obj.propertyIsEnumerable(key), "Object#propertyIsEnumerable uses ToPropertyKey.");
assert.areNotEqual(undefined, Object.getOwnPropertyDescriptor(obj, key), "Object.getOwnPropertyDescriptor uses ToPropertyKey");
obj = {};
obj.__defineGetter__(key, () => { return 2;} );
assert.isTrue(obj.hasOwnProperty(key), "Object#__defineGetter__ uses ToPropertyKey. Property is added to the object");
obj = {};
obj.__defineSetter__(key, () => { return 2;} );
assert.isTrue(obj.hasOwnProperty(key), "Object#__defineSetter__ uses ToPropertyKey. Property is added to the object");
var count = 0;
obj = Object.defineProperty({}, key, { set(v) { assert.areEqual('abc', v, "Setter called with correct arg"); count++; } });
var set = obj.__lookupSetter__(key);
assert.areEqual('function', typeof set, "Object#__lookupSetter__ uses ToPropertyKey. Make sure we added a setter.");
set('abc');
assert.areEqual(1, count, "Correct setter was called.");
obj = Object.defineProperty({}, key, { get() { return 'abc'; } });
var get = obj.__lookupGetter__(key);
assert.areEqual('function', typeof get, "Object#__lookupGetter__ uses ToPropertyKey. Make sure we added a getter.");
assert.areEqual('abc', get(), "Correct getter was called.");
obj = {};
assert.doesNotThrow(function() { Reflect.set(obj, key, 'abc'); }, "Reflect.set uses ToPropertyKey on argument. We should have set property");
assert.areEqual('abc', Reflect.get(obj, key), "Reflect.get also uses ToPropertyKey. We should return the same property");
assert.isTrue(Reflect.deleteProperty(obj, key), "Reflect.deleteProperty uses ToPropertyKey. We should successfully remove the property here");
assert.isFalse(Reflect.has(obj, key), "Reflect.has uses ToPropertyKey. We deleted this property, so we should not have it now.");
assert.doesNotThrow(function() { Reflect.defineProperty(obj, key, { value: 'def', enumerable: true }); }, "Reflect.defineProperty uses ToPropertyKey. It should have created a new property");
assert.areEqual('def', Reflect.get(obj, key), "Reflect.get also uses ToPropertyKey. We should return the same property");
assert.areNotEqual(undefined, Reflect.getOwnPropertyDescriptor(obj, key), "Reflect.getOwnPropertyDescriptor uses ToPropertyKey. It should return a real descriptor object");
obj = {};
assert.doesNotThrow(function() { obj[key] = 123; }, "Ordinary [[Set]] eventually uses ToPropertyKey. Property is added to the object");
assert.areEqual(123, obj[key], "Ordinary [[Get]] also eventually goes down to ToPropertyKey which should return us the same value");
assert.isTrue(obj.hasOwnProperty(key), "Verify we added the property under key and not some stringified version of key");
}
var tests = [
{
name: "Symbol is a constructor object and has correct shape",
body: function () {
assert.isTrue(Symbol !== undefined, "Symbol is defined");
assert.areEqual('function', typeof Symbol, "typeof Symbol === 'function'");
assert.areEqual(0, Symbol.length, "Symbol.length === 0");
assert.areEqual('function', typeof Symbol.toString, "typeof Symbol.toString === 'function'");
assert.areEqual('function', typeof Symbol.valueOf, "typeof Symbol.valueOf === 'function'");
assert.areEqual('function', typeof Symbol.for, "typeof Symbol.for === 'function'");
assert.areEqual(1, Symbol.for.length, "Symbol.for.length === 1");
descriptor = Object.getOwnPropertyDescriptor(Symbol, 'for');
assert.isTrue(descriptor.writable, 'Symbol.for.descriptor.writable == true');
assert.isFalse(descriptor.enumerable, 'Symbol.for.descriptor.enumerable == false');
assert.isTrue(descriptor.configurable, 'Symbol.for.descriptor.configurable == true');
assert.areEqual('function', typeof Symbol.keyFor, "typeof Symbol.keyFor === 'function'");
assert.areEqual(1, Symbol.keyFor.length, "Symbol.keyFor.length === 1");
descriptor = Object.getOwnPropertyDescriptor(Symbol, 'keyFor');
assert.isTrue(descriptor.writable, 'Symbol.keyFor.descriptor.writable == true');
assert.isFalse(descriptor.enumerable, 'Symbol.keyFor.descriptor.enumerable == false');
assert.isTrue(descriptor.configurable, 'Symbol.keyFor.descriptor.configurable == true');
}
},
{
name: "Symbol prototype has expected shape",
body: function() {
assert.isTrue(Symbol === Symbol.prototype.constructor, "Symbol === Symbol.prototype.constructor");
descriptor = Object.getOwnPropertyDescriptor(Symbol, 'prototype');
assert.isFalse(descriptor.writable, 'Symbol.prototype.descriptor.writable == false');
assert.isFalse(descriptor.enumerable, 'Symbol.prototype.descriptor.enumerable == false');
assert.isFalse(descriptor.configurable, 'Symbol.prototype.descriptor.configurable == false');
assert.areEqual('function', typeof Symbol.prototype.toString, "typeof Symbol.prototype.toString === 'function'");
descriptor = Object.getOwnPropertyDescriptor(Symbol.prototype, 'toString');
assert.isTrue(descriptor.writable, 'Symbol.prototype.toString.descriptor.writable == true');
assert.isFalse(descriptor.enumerable, 'Symbol.prototype.toString.descriptor.enumerable == false');
assert.isTrue(descriptor.configurable, 'Symbol.prototype.toString.descriptor.configurable == true');
assert.areEqual('function', typeof Symbol.prototype.valueOf, "typeof Symbol.prototype.valueOf === 'function'");
descriptor = Object.getOwnPropertyDescriptor(Symbol.prototype, 'valueOf');
assert.isTrue(descriptor.writable, 'Symbol.prototype.valueOf.descriptor.writable == true');
assert.isFalse(descriptor.enumerable, 'Symbol.prototype.valueOf.descriptor.enumerable == false');
assert.isTrue(descriptor.configurable, 'Symbol.prototype.valueOf.descriptor.configurable == true');
assert.areEqual('function', typeof Symbol.prototype[Symbol.toPrimitive], "typeof Symbol.prototype[@@toPrimitive] === 'function'");
assert.areEqual(1, Symbol.prototype[Symbol.toPrimitive].length, "Symbol.prototype[@@toPrimitive].length === 1");
descriptor = Object.getOwnPropertyDescriptor(Symbol.prototype, Symbol.toPrimitive);
assert.isFalse(descriptor.writable, 'Symbol.prototype[@@toPrimitive].descriptor.writable == false');
assert.isFalse(descriptor.enumerable, 'Symbol.prototype[@@toPrimitive].descriptor.enumerable == false');
assert.isTrue(descriptor.configurable, 'Symbol.prototype[@@toPrimitive].descriptor.configurable == true');
var functionToString = Symbol.prototype[Symbol.toPrimitive].toString();
var actualName = functionToString.substring(9, functionToString.indexOf('('));
assert.areEqual('[Symbol.toPrimitive]', actualName, "Symbol[@@toPrimitive].name == '[Symbol.toPrimitive]'");
assert.areEqual('string', typeof Symbol.prototype[Symbol.toStringTag], "typeof Symbol.prototype[@@toStringTag] === 'string'");
descriptor = Object.getOwnPropertyDescriptor(Symbol.prototype, Symbol.toStringTag);
assert.isFalse(descriptor.writable, 'Symbol.prototype[@@toStringTag].descriptor.writable == false');
assert.isFalse(descriptor.enumerable, 'Symbol.prototype[@@toStringTag].descriptor.enumerable == false');
assert.isTrue(descriptor.configurable, 'Symbol.prototype[@@toStringTag].descriptor.configurable == true');
assert.areEqual('Symbol', Symbol.prototype[Symbol.toStringTag], "Symbol.prototype[@@toStringTag] === 'Symbol'");
}
},
{
name: "Symbol constructor and prototype built-ins",
body: function() {
var x = Symbol("x");
var y = Symbol("y");
// toPrimitive() behavior
assert.areEqual(x, x[Symbol.toPrimitive](), "x == x[Symbol.toPrimitive]()");
assert.areEqual(x, x[Symbol.toPrimitive].call(x), "x == x[Symbol.toPrimitive].call(x)");
assert.areEqual(y, x[Symbol.toPrimitive].call(y), "y == x[Symbol.toPrimitive].call(y)");
assert.isFalse(x == x[Symbol.toPrimitive].call(y), "x != x[Symbol.toPrimitive].call(y)");
assert.areEqual(x, Symbol.prototype[Symbol.toPrimitive].call(x), "x == Symbol.prototype[Symbol.toPrimitive].call(x)");
// TypeError scenarios
assert.throws(function () { x[Symbol.toPrimitive].call("x") }, TypeError, "x[Symbol.toPrimitive].call('x'), toPrimitive throws TypeError for values that does not have SymbolData", "Symbol[Symbol.toPrimitive]: 'this' is not a Symbol object");
assert.throws(function () { Symbol.prototype[Symbol.toPrimitive]() }, TypeError, "toPrimitive throws TypeError if no arguments are passed", "Symbol[Symbol.toPrimitive]: 'this' is not a Symbol object");
assert.throws(function () { Symbol.prototype[Symbol.toPrimitive].call(true) }, TypeError, "toPrimitive throws TypeError for boolean true", "Symbol[Symbol.toPrimitive]: 'this' is not a Symbol object");
assert.throws(function () { Symbol.prototype[Symbol.toPrimitive].call(false) }, TypeError, "toPrimitive throws TypeError for boolean false", "Symbol[Symbol.toPrimitive]: 'this' is not a Symbol object");
assert.throws(function () { Symbol.prototype[Symbol.toPrimitive].call(0) }, TypeError, "toPrimitive throws TypeError for number", "Symbol[Symbol.toPrimitive]: 'this' is not a Symbol object");
assert.throws(function () { Symbol.prototype[Symbol.toPrimitive].call(NaN) }, TypeError, "toPrimitive throws TypeError for NaN", "Symbol[Symbol.toPrimitive]: 'this' is not a Symbol object");
assert.throws(function () { Symbol.prototype[Symbol.toPrimitive].call("") }, TypeError, "toPrimitive throws TypeError for values that does not have SymbolData", "Symbol[Symbol.toPrimitive]: 'this' is not a Symbol object");
assert.throws(function () { Symbol.prototype[Symbol.toPrimitive].call("abc") }, TypeError, "toPrimitive throws TypeError for values that does not have SymbolData", "Symbol[Symbol.toPrimitive]: 'this' is not a Symbol object");
assert.throws(function () { Symbol.prototype[Symbol.toPrimitive].call(null) }, TypeError, "toPrimitive throws TypeError for null", "Symbol[Symbol.toPrimitive]: 'this' is not a Symbol object");
assert.throws(function () { Symbol.prototype[Symbol.toPrimitive].call(undefined) }, TypeError, "toPrimitive throws TypeError for undefined", "Symbol[Symbol.toPrimitive]: 'this' is not a Symbol object");
assert.throws(function () { Symbol.prototype[Symbol.toPrimitive].call({}) }, TypeError, "toPrimitive throws TypeError for object", "Symbol[Symbol.toPrimitive]: 'this' is not a Symbol object");
var z = Object(y);
assert.areEqual(y, Symbol.prototype[Symbol.toPrimitive].call(z), "y == Symbol.prototype[Symbol.toPrimitive].call(z)");
assert.isFalse(Object(x) == Symbol.prototype[Symbol.toPrimitive].call(z), "Object(x) != Symbol.prototype[Symbol.toPrimitive].call(z)");
}
},
{
name: "Symbol constructor has the well-known symbols as properties",
body: function() {
function verifySymbol(propertyName) {
var fullName = "Symbol[" + propertyName + "]";
assert.isTrue(Symbol[propertyName] !== undefined, fullName + " !== undefined");
assert.areEqual('symbol', typeof Symbol[propertyName], "typeof " + fullName + " === 'symbol'");
var descriptor = Object.getOwnPropertyDescriptor(Symbol, propertyName);
assert.isFalse(descriptor.writable, fullName + '.descriptor.writable == false');
assert.isFalse(descriptor.enumerable, fullName + 'descriptor.enumerable == false');
assert.isFalse(descriptor.configurable, fullName + 'descriptor.configurable == false');
}
verifySymbol("hasInstance");
verifySymbol("isConcatSpreadable");
verifySymbol("iterator");
verifySymbol("toPrimitive");
verifySymbol("toStringTag");
verifySymbol("unscopables");
verifySymbol("species");
verifySymbol("replace");
verifySymbol("search");
verifySymbol("match");
verifySymbol("split");
}
},
{
name: "Symbol primitive toString should throw a type error",
body: function() {
assert.throws(function() { 'string' + Symbol.iterator; }, TypeError, "Symbol primitives throw on implicit string conversion", "Object doesn't support property or method 'ToString'");
}
},
{
name: "String(symbol) behavior",
body: function() {
assert.areEqual('Symbol(description)', String(Symbol('description')), "String(Symbol('description')) === 'Symbol(description)'");
assert.throws(function () { new String(Symbol('description')); }, TypeError, "Symbol as an argument to new String() throws", "Object doesn't support property or method 'ToString'");
}
},
{
name: "Symbol object toString produces a human-readable name",
body: function() {
assert.areEqual('Symbol(Symbol.hasInstance)', Object(Symbol.hasInstance).toString(), "Object(Symbol.hasInstance).toString() === 'Symbol(Symbol.hasInstance)'");
assert.areEqual('Symbol(Symbol.isConcatSpreadable)', Object(Symbol.isConcatSpreadable).toString(), "Object(Symbol.isConcatSpreadable).toString() === 'Symbol(Symbol.isConcatSpreadable)'");
assert.areEqual('Symbol(Symbol.iterator)', Object(Symbol.iterator).toString(), "Object(Symbol.iterator).toString() === 'Symbol(Symbol.iterator)'");
assert.areEqual('Symbol(Symbol.toPrimitive)', Object(Symbol.toPrimitive).toString(), "Object(Symbol.toPrimitive).toString() === 'Symbol(Symbol.toPrimitive)'");
assert.areEqual('Symbol(Symbol.toStringTag)', Object(Symbol.toStringTag).toString(), "Object(Symbol.toStringTag).toString() === 'Symbol(Symbol.toStringTag)'");
assert.areEqual('Symbol(Symbol.unscopables)', Object(Symbol.unscopables).toString(), "Object(Symbol.unscopables).toString() === 'Symbol(Symbol.unscopables)'");
assert.areEqual('Symbol()', Object(Symbol()).toString(), "Object(Symbol()).toString() === 'Symbol()'");
assert.areEqual("Symbol(Some kind of long string description\n\n)", Object(Symbol("Some kind of long string description\n\n")).toString(), "Object(Symbol(\"Some kind of long string description\n\n\")).toString() === 'Symbol(Some kind of long string description\n\n)'");
}
},
{
name: "typeof a symbol primitive is 'symbol'",
body: function() {
assert.areEqual('symbol', typeof Symbol('mysymbol'), "typeof Symbol('mysymbol') === 'symbol'");
assert.areEqual('symbol', typeof Symbol(''), "typeof Symbol('') === 'symbol'");
assert.areEqual('symbol', typeof Symbol(), "typeof Symbol() === 'symbol'");
}
},
{
name: "new Symbol throws",
body: function() {
assert.throws(function () { new Symbol() }, TypeError, "new Symbol throws TypeError when it has no parameter", "Function is not a constructor");
assert.throws(function () { new Symbol('anything') }, TypeError, "new Symbol throws TypeError when it has a string parameter", "Function is not a constructor");
}
},
{
name: "Symbols with single-character descriptions (these are special-cased in ThreadContext)",
body: function() {
assert.isTrue(Symbol('s') !== Symbol('s'), "We are able to create multiple symbols with the same single-character description and they are not equal");
}
},
{
name: "Symbol strict equality with other symbols",
body: function() {
assert.isTrue(Symbol('something') !== Symbol('something'), "Symbol('something') !== Symbol('something')");
assert.isTrue(Symbol('') !== Symbol(''), "Symbol('') !== Symbol('')");
assert.isTrue(Symbol() !== Symbol(), "Symbol() !== Symbol()");
var my1 = Symbol('my');
assert.isTrue(my1 === my1, "Generated symbol should equal itself");
var my2 = my1;
assert.isTrue(my1 === my2, "Assignment to another Var should still equal the original symbol");
var o1 = Object(my1);
var o2 = Object(my1);
assert.isTrue(o1 !== o2, "Box objects should not be equal for the same symbol");
assert.isTrue(o1 !== my1, "Box object should not be equal to the symbol primitive");
assert.isTrue(o1.valueOf() === o2.valueOf(), "Unboxing objects wrapping the same symbol primitive should result in the same symbol returned from valueOf()");
var o3 = Object(Symbol('another'));
assert.isTrue(o1 !== o3, "Box objects should not be equal for different symbol primitives");
var my3 = o1.valueOf();
assert.isTrue(my1 === my3, "Unboxed symbol should be equal to original primitive");
assert.isTrue(Symbol.iterator !== Symbol('iterator'), "Symbol.iterator !== Symbol('iterator')");
assert.isTrue(Object(Symbol('sym')).valueOf() !== Object(Symbol('sym')).valueOf(), "Different symbol primitives boxed and unboxed should not be equal to each other");
}
},
{
name: "Symbol strict equality with other types",
body: function() {
var sym = Symbol('my');
assert.isFalse(sym === 'string', "sym !== 'string'");
assert.isFalse(sym === undefined, "sym !== undefined");
assert.isFalse(sym === null, "sym !== null");
assert.isFalse(sym === true, "sym !== true");
assert.isFalse(sym === false, "sym !== true");
assert.isFalse(sym === [], "sym !== []");
assert.isFalse(sym === {}, "sym !== {}");
assert.isTrue(sym === sym, "sym === sym");
assert.isFalse('string' === sym, "'string' !== sym");
assert.isFalse(undefined === sym, "undefined !== sym");
assert.isFalse(null === sym, "null !== sym");
assert.isFalse(true === sym, "true !== sym");
assert.isFalse(false === sym, "false !== sym");
assert.isFalse([] === sym, "[] !== sym");
assert.isFalse({} === sym, "{} !== sym");
}
},
{
name: "Symbol equality with other types",
body: function() {
var sym = Symbol('my');
assert.isFalse(sym == 'string', "ToString(symbol) throws so this should be false");
assert.isFalse(sym == undefined, "sym != undefined");
assert.isFalse(sym == null, "sym != null");
assert.isFalse(sym == true, "symbol != true");
assert.isFalse(sym == false, "symbol != false");
assert.isFalse(sym == [], "sym != []");
assert.isFalse(sym == {}, "sym != {}");
assert.isTrue(sym == sym, "sym == sym");
assert.isFalse('string' == sym, "ToString(symbol) throws so this should be false");
assert.isFalse(undefined == sym, "undefined != sym");
assert.isFalse(null == sym, "null != sym");
assert.isFalse(true == sym, "true != sym");
assert.isFalse(false == sym, "false != sym");
assert.isFalse([] == sym, "[] != sym");
assert.isFalse({} == sym, "{} != sym");
}
},
{
name: "Symbol equality with auto-boxed Symbols",
body: function() {
var sym = Symbol('my');
assert.isTrue(sym == Object(sym), "Auto-boxed symbol is equal to that symbol");
assert.isTrue(Object(sym) == sym, "Auto-boxed symbol is equal to that symbol");
assert.isFalse(Object(sym) == Object(sym), "Two different auto-boxed symbols of the same symbol are never equal to each other");
assert.isFalse(sym === Object(sym), "Auto-boxed symbol is not strict-equal to that symbol");
assert.isFalse(Object(sym) === sym, "Auto-boxed symbol is not strict-equal to that symbol");
assert.isFalse(Object(sym) === Object(sym), "Two different auto-boxed symbols of the same symbol are never strict-equal to each other");
}
},
{
name: "Symbol auto-boxing",
body: function() {
assert.areEqual('Symbol()', Symbol().toString(), "Autoboxing for toString()");
var sym = Symbol();
assert.isTrue(sym.valueOf() === sym.valueOf(), "Autoboxing for valueOf()");
}
},
{
name: "Symbol primitives work as property keys",
body: function() {
var o = {};
o[Symbol.iterator] = 'some string';
assert.areEqual('some string', o[Symbol.iterator], "o[Symbol.iterator] === 'some string'");
assert.isTrue(o[Symbol.iterator.toString()] === undefined, "o[Symbol.iterator] uses the property id stored in Symbol.iterator (not the value created by Symbol.iterator.toString())");
// use functions to wrap property access to ensure we hit JIT code
function getProperty(obj, sym) {
return obj[sym];
}
function setProperty(obj, sym, val) {
obj[sym] = val;
}
o = {};
var my = Symbol();
for (var i = 0; i < 5; i++) {
setProperty(o, my, i);
assert.areEqual(i, getProperty(o, my), "Property keyed by symbol is able to be set and get");
}
var sym = Symbol('sym');
o = {};
o[sym] = 'test';
assert.areEqual('test', o[sym], "Symbol converted to property key works");
assert.areEqual(undefined, o['sym'], "Symbol description is not added as a property to the object");
var s1 = Symbol('uniquevalue');
var s2 = Symbol('uniquevalue');
o = {};
o[s1] = 's1';
o[s2] = 's2';
assert.areEqual('s1', o[s1], "Simple test of symbol producing a property on an object");
assert.areEqual('s2', o[s2], "Simple test of symbol producing a property on an object");
assert.isTrue(o[s1] != o[s2], "Different symbols with the same description create different properties on an object");
delete o[s1];
assert.areEqual(undefined, o[s1], "deleting properties from objects also works");
assert.areEqual('s2', o[s2], "deleting a property doesn't affect other properties");
// Needs ES6 object literal improvements
o = { [sym] : 'string' };
assert.areEqual('string', o[sym], "Object literal declared with symbol property works");
}
},
{
name: "Object.prototype.hasOwnProperty works for symbols",
body: function() {
var o = {};
assert.isFalse(o.hasOwnProperty(Symbol.iterator), "Property defined with a symbol initially is not in the object");
o[Symbol.iterator] = 'a string';
assert.isTrue(o.hasOwnProperty(Symbol.iterator), "Property defined with a symbol can be looked up via o.hasOwnProperty");
}
},
{
name: "Symbols handled by type conversion operations",
body: function() {
assert.throws(function () { Number(Symbol.iterator).valueOf() }, TypeError, "ToNumber(symbol) throws TypeError", "Number expected");
assert.areEqual(true, Boolean(Symbol.iterator), "ToBoolean(symbol) === true");
assert.areEqual('object', typeof Object(Symbol.iterator), "ToObject(symbol) is not a symbol object");
}
},
{
name: "API shape of Object.getOwnPropertySymbols",
body: function() {
assert.isTrue(Object.getOwnPropertySymbols !== undefined, "Object.getOwnPropertySymbols is defined");
assert.areEqual('function', typeof Object.getOwnPropertySymbols, "Object.getOwnPropertySymbols is a function");
assert.areEqual(1, Object.getOwnPropertySymbols.length, "Object.getOwnPropertySymbols.length === 1");
}
},
{
name: "Object.getOwnPropertySymbols does ToObject on its argument",
body: function() {
assert.throws(function () { Object.getOwnPropertySymbols(); }, TypeError, "ToObject(undefined) throws TypeError", "Object expected");
assert.throws(function () { Object.getOwnPropertySymbols(undefined); }, TypeError, "ToObject(undefined) throws TypeError", "Object expected");
assert.throws(function () { Object.getOwnPropertySymbols(null); }, TypeError, "ToObject(null) throws TypeError", "Object expected");
assert.areEqual([], Object.getOwnPropertySymbols(true), "Object.getOwnPropertySymbols does ToObject on boolean");
assert.areEqual([], Object.getOwnPropertySymbols(1), "Object.getOwnPropertySymbols does ToObject on number");
assert.areEqual([], Object.getOwnPropertySymbols("a"), "Object.getOwnPropertySymbols does ToObject on string");
assert.areEqual([], Object.getOwnPropertySymbols(Symbol('a')), "Object.getOwnPropertySymbols does ToObject on symbol");
assert.areEqual([], Object.getOwnPropertySymbols({}), "Object.getOwnPropertySymbols returns an empty array for an empty object");
}
},
{
name: "Object.getOwnPropertySymbols only returns symbols",
body: function() {
var sym = Symbol('c');
var o = {};
o['a'] = 'alpha';
Object.defineProperty(o, 'b', { value: 'beta', enumerable: false } );
o[sym] = 'gamma';
o['d'] = 'delta';
var symbols = Object.getOwnPropertySymbols(o);
assert.areEqual(1, symbols.length, "symbols.length === 1");
for(var i = 0; i < symbols.length; i++) {
assert.isTrue(typeof symbols[i] === 'symbol', "The symbols array only includes entries of type symbol");
assert.isTrue(symbols[i].toString() != 'a', "The symbols array does not include an entry with the name of any of our string properties");
assert.isTrue(symbols[i].toString() != 'b', "The symbols array does not include an entry with the name of any of our string properties");
assert.isTrue(symbols[i].toString() != 'd', "The symbols array does not include an entry with the name of any of our string properties");
assert.isTrue(symbols[i] === sym, "The symbols array includes our symbol");
assert.isTrue(symbols[i].toString() === sym.toString(), "The symbols array includes an entry with our symbol.toString()");
}
var s1 = Symbol('name');
var s2 = Symbol('name');
o = {};
o[s1] = 'something';
o[s2] = 'something else';
symbols = Object.getOwnPropertySymbols(o);
assert.areEqual(2, symbols.length, "symbols.length === 2");
assert.isTrue(symbols[0] === s1, "symbols[0] === s1");
assert.isTrue(symbols[1] === s2, "symbols[1] === s2");
o = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
symbols = Object.getOwnPropertySymbols(o);
assert.areEqual(0, symbols.length, "Object with no symbol properties returns empty array from Object.getOwnPropertySymbols");
}
},
{
name: "Object.getOwnPropertyNames doesn't return symbols",
body: function() {
var sym = Symbol('c');
var o = {};
o['a'] = 'alpha';
Object.defineProperty(o, 'b', { value: 'beta', enumerable: false } );
o[sym] = 'gamma';
o['d'] = 'delta';
var names = Object.getOwnPropertyNames(o);
assert.areEqual(3, names.length, "names.length === 3");
for(var i = 0; i < names.length; i++) {
assert.isFalse(typeof names[i] === 'symbol', "The names array does not include an entry of type symbol");
assert.isTrue(names[i] != 'c', "The names array does not include an entry with the description of our symbol");
assert.isTrue(names[i] != sym, "The names array does not include any symbols");
assert.isTrue(names[i] != sym.toString(), "The names array does not include an entry with our symbol.toString()");
}
o = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
names = Object.getOwnPropertyNames(o);
assert.areEqual(11, names.length, "Object with no symbol properties returns correct array");
o = {};
o[sym] = 'something';
names = Object.getOwnPropertyNames(o);
assert.areEqual(0, names.length, "Object with only symbol properties returns empty array");
}
},
{
name: "Object.keys should not return property keys which are symbols",
body: function() {
var sym = Symbol('c');
var o = {};
o['a'] = 'alpha';
o['b'] = 'beta';
o[sym] = 'gamma';
o['d'] = 'delta';
var keys = Object.keys(o);
assert.areEqual(3, keys.length, "keys.length === 3");
for(var i = 0; i < keys.length; i++) {
assert.isFalse(typeof keys[i] === 'symbol', "The keys array does not include an entry of type symbol");
assert.isTrue(keys[i] != 'c', "The keys array does not include an entry with the description of our symbol");
assert.isTrue(keys[i] != sym, "The keys array does not include any symbols");
assert.isTrue(keys[i] != sym.toString(), "The keys array does not include an entry with our symbol.toString()");
}
}
},
{
name: "for ... in enumeration does not surface symbols",
body: function() {
var sym = Symbol('c');
var o = {};
o['a'] = 'alpha';
o['b'] = 'beta';
o[sym] = 'gamma';
o['d'] = 'delta';
for (k in o)
{
assert.isFalse(typeof k === 'symbol', "for ... in does not enumerate symbol types");
assert.isTrue(o[k] !== 'gamma', "for ... in does not enumerate properties keyed by symbols");
assert.isTrue(k != sym, "for ... in does not enumerate our symbol");
assert.isTrue(k != sym.toString(), "for ... in does not enumerate a property named sym.toString()");
}
}
},
{
name: "Object.defineProperty with symbol as property key",
body: function() {
var sym = Symbol();
var o = {};
Object.defineProperty(o, sym, { value: 'some value' } );
assert.areEqual('some value', o[sym], "Property keyed off symbol and set via Object.defineProperty should be reachable by the same symbol");
assert.areEqual(undefined, o['sym'], "defineProperty does not create a property based on symbol name");
assert.areEqual(undefined, o[''], "defineProperty does not create a property based on symbol description");
assert.areEqual(undefined, o[sym.toString()], "defineProperty does not create a property based on symbol toString() value");
}
},
{
name: "Object.defineProperties with symbols as property keys",
body: function() {
var props = {};
var s1 = Symbol('symbol 1');
var s2 = Symbol('symbol 2');
props['a'] = { value: 'alpha', enumerable: true };
props[s1] = { value: 'beta', enumerable: true };
props[s2] = { value: 'gamma', enumerable: true };
props['d'] = { value: 'delta', enumerable: true };
var o = {};
Object.defineProperties(o, props);
assert.areEqual('alpha', o['a'], "Property keyed off string is added as expected");
assert.areEqual('delta', o['d'], "Property keyed off string is added as expected");
assert.areEqual('beta', o[s1], "Property keyed off symbol set via Object.defineProperties should be reachable by the same symbol");
assert.areEqual('gamma', o[s2], "Property keyed off symbol set via Object.defineProperties should be reachable by the same symbol");
assert.areEqual(undefined, o['s1'], "defineProperties does not create a property based on symbol name");
assert.areEqual(undefined, o['s2'], "defineProperties does not create a property based on symbol name");
assert.areEqual(undefined, o['symbol 1'], "defineProperties does not create a property based on symbol description");
assert.areEqual(undefined, o['symbol 2'], "defineProperties does not create a property based on symbol description");
assert.areEqual(undefined, o[s1.toString()], "defineProperty does not create a property based on symbol toString() value");
assert.areEqual(undefined, o[s2.toString()], "defineProperty does not create a property based on symbol toString() value");
}
},
{
name: "Object.create should work for symbol properties",
body: function() {
var props = {};
var s1 = Symbol('symbol 1');
var s2 = Symbol('symbol 2');
props['a'] = { value: 'alpha', enumerable: true };
props[s1] = { value: 'beta', enumerable: true };
props[s2] = { value: 'gamma', enumerable: true };
props['d'] = { value: 'delta', enumerable: true };
var o = Object.create(Object.prototype, props);
assert.areEqual('alpha', o['a'], "Property keyed off string is added as expected");
assert.areEqual('delta', o['d'], "Property keyed off string is added as expected");
assert.areEqual('beta', o[s1], "Property keyed off symbol set via Object.create should be reachable by the same symbol");
assert.areEqual('gamma', o[s2], "Property keyed off symbol set via Object.create should be reachable by the same symbol");
assert.areEqual(undefined, o['s1'], "Object.create does not create a property based on symbol name");
assert.areEqual(undefined, o['s2'], "Object.create does not create a property based on symbol name");
assert.areEqual(undefined, o['symbol 1'], "Object.create does not create a property based on symbol description");
assert.areEqual(undefined, o['symbol 2'], "Object.create does not create a property based on symbol description");
assert.areEqual(undefined, o[s1.toString()], "Object.create does not create a property based on symbol toString() value");
assert.areEqual(undefined, o[s2.toString()], "Object.create does not create a property based on symbol toString() value");
}
},
{
name: "Object.getOwnPropertyDescriptor with symbol as property key",
body: function() {
var sym = Symbol();
var o = {};
Object.defineProperty(o, sym, { value: 100000, writable: false, enumerable: true, configurable: false } );
var descriptor = Object.getOwnPropertyDescriptor(o, sym);
assert.isFalse(descriptor.writable, 'o[sym].descriptor.writable == false');
assert.isTrue(descriptor.enumerable, 'o[sym].descriptor.enumerable == true');
assert.isFalse(descriptor.configurable, 'o[sym].descriptor.configurable == false');
}
},
{
name: "Object.prototype.propertyIsEnumerable should work for symbol properties",
body: function() {
var sym1 = Symbol();
var sym2 = Symbol();
var o = {};
Object.defineProperty(o, sym1, { value: 10, enumerable: true});
Object.defineProperty(o, sym2, { value: 10, enumerable: false});
assert.isTrue(o.propertyIsEnumerable(sym1), 'o.propertyIsEnumerable[sym1]');
assert.isFalse(o.propertyIsEnumerable(sym2), 'o.propertyIsEnumerable[sym2]');
}
},
{
name: "Object.prototype.__defineSetter__ with a property keyed by a symbol",
body: function() {
var sym = Symbol();
var o = {};
var helpme;
o.__defineSetter__(sym, function() { helpme = 'useful string'; });
o[sym] = 'anything';
assert.areEqual('useful string', helpme, "Object.prototype.__defineSetter__ works when we use a symbol");
}
},
{
name: "Object.prototype.__defineGetter__ with a property keyed by a symbol",
body: function() {
var sym = Symbol();
var o = {};
o.__defineGetter__(sym, function() { return 'anything'; });
assert.areEqual('anything', o[sym], "Object.prototype.__defineGetter__ works when we use a symbol");
}
},
{
name: "Object.prototype.__lookupSetter__ with a property keyed by a symbol",
body: function() {
var sym = Symbol();
var o = {};
var helpme;
var setter = function() { helpme = 'useful string'; };
o.__defineSetter__(sym, setter);
var f = o.__lookupSetter__(sym);
assert.areEqual(undefined, helpme, "setter has not yet been called");
assert.isTrue(f === setter, "Object.prototype.__lookupSetter__ returns correct function when we use a symbol");
f();
assert.areEqual('useful string', helpme, "calling setter returned from Object.prototype.__lookupSetter__ works as expected");
helpme = undefined;
o[sym] = 'anything';
assert.areEqual('useful string', helpme, "Object.prototype.__lookupSetter__ works when we use a symbol");
}
},
{
name: "Object.prototype.__lookupGetter__ with a property keyed by a symbol",
body: function() {
var sym = Symbol();
var o = {};
var getter = function() { return 'anything'; };
o.__defineGetter__(sym, getter);
var f = o.__lookupGetter__(sym);
assert.isTrue(f === getter, "Object.prototype.__lookupGetter__ returns correct function when we use a symbol");
assert.areEqual('anything', f(), "function returned via Object.prototype.__lookupGetter__ works as expected");
assert.areEqual('anything', o[sym], "Object.prototype.__lookupGetter__ works when we use a symbol");
}
},
{
name: 'Symbol with numeric description does not create a numeric property',
body: function() {
var sym = Symbol('1');
var o = {};
o[sym] = 'a string';
assert.areEqual(undefined, o[1], "Object should not contain numeric property at index == symbol description");
assert.areEqual('a string', o[sym], "Object should contain the symbol property");
o = [];
o[1] = 'the number 1';
o[sym] = 'the symbol 1';
assert.areEqual(2, o.length, "Object has correct length");
assert.areEqual('the number 1', o[1], "Object with numeric property has correct value");
assert.areEqual('the symbol 1', o[sym], "Object with symbol property has correct value");
}
},
{
name: 'BLUE: 539472 BLUE: 541467 - Symbol.prototype should be TypeIds_Object',
body: function() {
assert.throws(function () { Symbol.prototype.valueOf(); }, TypeError, "Calling prototype methods directly fails since Symbol.prototype is not a SymbolObject", "Symbol.prototype.valueOf: 'this' is not a Symbol object");
assert.throws(function () { Symbol.prototype.toString(); }, TypeError, "Calling prototype methods directly fails since Symbol.prototype is not a SymbolObject", "Symbol.prototype.toString: 'this' is not a Symbol object");
}
},
{
name: 'Symbol objects and properties passed cross-context',
body: function() {
var child = WScript.LoadScriptFile("ES6Symbol_cross_context_child.js", "samethread");
assert.isFalse(Symbol('child symbol') === child.sym, "Symbol created in another context does not equal symbol with same description from this context");
assert.areEqual('symbol', typeof child.sym, "Symbol created in another context has correct type");
assert.areEqual(undefined, child.o[Symbol('child symbol')], "Object from another context with a symbol-keyed property doesn't contain a property named the same as a different symbol");
assert.areEqual('Symbol(child symbol)', child.sym.toString(), "Symbol from another context has correct toString behavior");
assert.areEqual('child value', child.o[child.sym], "Symbol from another context can be used to lookup properties in objects from another context");
var o = {};
o[child.sym] = 'parent value';
assert.areEqual('parent value', o[child.sym], "Symbol from another context can be used to index objects from this context");
var symbols = Object.getOwnPropertySymbols(child.o);
assert.areEqual(1, symbols.length, "Object.getOwnPropertySymbols works for objects from another context");
assert.isTrue(symbols[0] === child.sym, "Object.getOwnPropertySymbols returns the correct symbols for objects from another context");
}
},
{
name: 'Symbol registration within a single realm',
body: function() {
var sym = Symbol.for('my string');
var sym2 = Symbol.for('my string');
assert.areEqual('symbol', typeof sym, "Object returned from Symbol.for is actually a symbol");
assert.areEqual('Symbol(my string)', sym.toString(), "Symbol returned from Symbol.for has the right description");
assert.isTrue(sym === sym2, "Two symbols returned from Symbol.for with the same parameter are the same symbol");
var key = Symbol.keyFor(sym);
assert.areEqual('my string', key, "Symbol created by Symbol.for can be passed to Symbol.keyFor to return the same key");
}
},
{
name: 'Symbol registration cross-realm',
body: function() {
var parent_sym = Symbol.for('parent symbol');
var child = WScript.LoadScriptFile("ES6Symbol_cross_context_registration_child.js", "samethread");
var child_sym = Symbol.for('child symbol');
assert.isTrue(child.child_sym === child_sym, "Symbol registered in child is returned correctly in parent");
assert.isTrue(child.parent_sym === parent_sym, "Symbol registered in parent is returned correctly in child");
assert.isTrue(child.parent_string === Symbol.keyFor(parent_sym), "Symbol registered in parent is returned correctly in child");
}
},
{
name: 'Registered Symbols should have their PropertyRecords pinned',
body: function() {
var sym = Symbol.for('my string');
sym = undefined;
// After cleaning up sym, there shouldn't be anyone pinning the PropertyRecord
// except for the Symbol registration map.
// If the reference to the PropertyRecord created above gets cleaned-up we will
// cause an AV below when we try to reference it again.
CollectGarbage();
sym = Symbol.for('my string');
assert.areEqual('symbol', typeof sym, "Object returned from Symbol.for is actually a symbol");
assert.areEqual('Symbol(my string)', sym.toString(), "Symbol returned from Symbol.for has the right description");
}
},
{
name: 'Registered Symbol should not be returned by unregistered Symbol with the same description',
body: function() {
var sym = Symbol.for('my string');
var sym2 = Symbol('my string');
assert.isFalse(sym === sym2, "Symbols created via Symbol.for and Symbol constructor should not be equal even if they have the same description");
assert.areEqual('my string', Symbol.keyFor(sym), "Symbol.keyFor returns correct key for registered symbol");
assert.areEqual(undefined, Symbol.keyFor(sym2), "Symbol.keyFor returns undefined for symbols not registered with Symbol.for");
}
},
{
name: 'Throwing TypeError when trying to add with a string or a number',
body: function() {
var x = Symbol();
assert.throws(function() { "str" + x; }, TypeError, "Adding a string and a symbol throws TypeError", "Object doesn't support property or method 'ToString'");
assert.throws(function() { x + "str"; }, TypeError, "Adding a symbol and a string throws TypeError", "Object doesn't support property or method 'ToString'");
assert.throws(function() { 10 + x; }, TypeError, "Adding a number and a symbol throws TypeError", "Number expected");
assert.throws(function() { x + 10; }, TypeError, "Adding a symbol and a number throws TypeError", "Number expected");
}
},
{
name: 'ToPropertyKey accepts Symbol wrapper objects, and unboxes the Symbol primitive inside',
body: function() {
var sym = Symbol('sym');
var sym_object = Object(sym);
var obj = { [sym_object] : 'value' };
assert.areEqual('value', obj[sym], "Object created with Symbol wrapper object passed as computed property creates a symbol-keyed property from the unboxed symbol");
assert.areEqual('value', obj[sym_object], "Looking up a property by passing a Symbol wrapper object actually returns the property keyed off of the unboxed symbol");
assert.areEqual([], Object.getOwnPropertyNames(obj), "Object has no string-keyed properties");
assert.areEqual([sym], Object.getOwnPropertySymbols(obj), "Object only has one symbol-keyed property - sym");
var obj2 = {};
obj2[sym_object] = 'value2';
assert.areEqual('value2', obj2[sym], "Object created with Symbol wrapper object passed to property index set creates a symbol-keyed property from the unboxed symbol");
assert.areEqual('value2', obj2[sym_object], "Looking up a property by passing a Symbol wrapper object actually returns the property keyed off of the unboxed symbol");
assert.areEqual([], Object.getOwnPropertyNames(obj2), "Object has no string-keyed properties");
assert.areEqual([sym], Object.getOwnPropertySymbols(obj2), "Object only has one symbol-keyed property - sym");
}
},
{
name: 'ToPropertyKey performs ToPrimitive on argument which unwraps Symbol objects',
body: function() {
var sym = Symbol('sym');
var symbol_object = Object(sym);
VerifyToPropertyKey(symbol_object);
}
},
{
name: 'ToPropertyKey performs ToPrimitive on argument which returns symbol primitive via toString',
body: function() {
var sym = Symbol('sym');
var tostring_object = {
toString() {
return sym;
},
valueOf() {
assert.isTrue(false, "We should never call the valueOf method of this object");
}
};
VerifyToPropertyKey(tostring_object);
}
},
{
name: 'ToPropertyKey performs ToPrimitive on argument which returns symbol primitive via valueOf',
body: function() {
var sym = Symbol('sym');
var obj = { [sym] : 'value' };
var valueof_object = {
toString : null,
valueOf() {
return sym;
}
};
VerifyToPropertyKey(valueof_object);
}
},
{
name: 'ToNumber called with a symbol primitive should throw a TypeError',
body: function() {
var x = Symbol();
var obj = { 'length': x };
// We can't use parseInt directly here as that does ToString(obj) - we want something which calls ToNumber directly
assert.throws(function() { Array.prototype.lastIndexOf.call(obj, 1); }, TypeError, "Array.prototype.lastIndexOf performs ToLength(obj) which should throw TypeError if obj is a symbol primitive.", "Number expected");
}
},
{
name: 'Assigning to a property of a symbol primitive in strict mode should throw a TypeError',
body: function() {
var x = Symbol();
assert.throws(function() { "use strict"; x.a = 1; }, TypeError, "Assigning to a property of a symbol primitive should throw a TypeError.", "Assignment to read-only properties is not allowed in strict mode");
}
},
{
name: 'Assigning to a property of a symbol primitive in strict mode should throw a TypeError',
body: function() {
var x = Symbol();
assert.throws(function() { "use strict"; x['a'+'b'] = 1; }, TypeError, "Assigning to a property of a symbol primitive should throw a TypeError.", "Assignment to read-only properties is not allowed in strict mode");
}
},
{
name: 'Assigning to an index of a symbol primitive in strict mode should throw a TypeError',
body: function() {
var x = Symbol();
assert.throws(function() { "use strict"; x[12] = 1; }, TypeError, "Assigning to an index of a symbol primitive should throw a TypeError.", "Assignment to read-only properties is not allowed in strict mode");
}
},
{
name: 'Assigning to a property of a symbol primitive should be ignored',
body: function() {
var x = Symbol();
x.a = 1;
assert.areEqual(x.a, undefined);
}
},
{
name: 'Assigning to a property of a symbol primitive should be ignored',
body: function() {
var x = Symbol();
x['a'+'b'] = 1;
assert.areEqual(x['ab'], undefined);
}
},
{
name: 'Assigning to an index of a symbol primitive should be ignored',
body: function() {
var x = Symbol();
x[10086] = 1;
assert.areEqual(x[10086], undefined);
}
},
{
name: '[OS: Bug 4533235] JSON.stringify should ignore symbols (kangax)',
body: function() {
var object = {foo: Symbol()};
var sym = Symbol("a");
object[Symbol()] = 1;
var array = [Symbol()];
assert.areEqual('{}', JSON.stringify(object));
assert.areEqual('[null]', JSON.stringify(array));
assert.areEqual(undefined, JSON.stringify(Symbol()));
assert.areEqual(undefined, JSON.stringify(sym));
}
},
{
name: '[OS: Bug 5950493] Symbol(undefined).toString() produces "Symbol(undefined)" instead of "Symbol()".',
body: function() {
assert.areEqual('Symbol()', Symbol().toString(), 'Symbol().toString() === "Symbol()"');
assert.areEqual('Symbol()', Symbol(undefined).toString(), 'Symbol(undefined).toString() === "Symbol()"');
assert.areEqual('Symbol()', Symbol("").toString(), 'Symbol("").toString() === "Symbol()"');
}
}
];
testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });