blob: aaee91b804fb004b85e4e9bd6a61b95e2b5a0cce [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.
//-------------------------------------------------------------------------------------------------------
WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
var primitives = [
{ tag: "Undefined", instance: undefined },
{ tag: "Null", instance: null }
];
var explicitTagExoticBuiltIns = [
{ tag: "Math", instance: Math, prototype: Math },
{ tag: "JSON", instance: JSON, prototype: JSON }
];
var explicitTagExtensibleBuiltIns = [
{ tag: "Map", instance: new Map(), prototype: Map.prototype },
{ tag: "Set", instance: new Set(), prototype: Set.prototype },
{ tag: "WeakMap", instance: new WeakMap(), prototype: WeakMap.prototype },
{ tag: "WeakSet", instance: new WeakSet(), prototype: WeakSet.prototype },
{ tag: "ArrayBuffer", instance: new ArrayBuffer(), prototype: ArrayBuffer.prototype },
{ tag: "DataView", instance: new DataView(new ArrayBuffer()), prototype: DataView.prototype }
];
var explicitTagOtherBuiltIns = [
{ tag: "Promise", instance: new Promise(() => {}), prototype: Promise.prototype },
{ tag: "GeneratorFunction", instance: function* () { }, prototype: Object.getPrototypeOf(function* () { }) },
{ tag: "Generator", instance: (function* () { })(), prototype: Object.getPrototypeOf((function* () { }).prototype) },
{ tag: "String Iterator", instance: ""[Symbol.iterator](), prototype: Object.getPrototypeOf(""[Symbol.iterator]()) },
{ tag: "Array Iterator", instance: [][Symbol.iterator](), prototype: Object.getPrototypeOf([][Symbol.iterator]()) },
{ tag: "Map Iterator", instance: (new Map())[Symbol.iterator](), prototype: Object.getPrototypeOf((new Map())[Symbol.iterator]()) },
{ tag: "Set Iterator", instance: (new Set())[Symbol.iterator](), prototype: Object.getPrototypeOf((new Set())[Symbol.iterator]()) }
];
var explicitTagBuiltIns = [...explicitTagExoticBuiltIns, ...explicitTagExtensibleBuiltIns, ...explicitTagOtherBuiltIns];
// 22.2 TypedArray Objects Table 50: The TypedArray Constructors
var explicitTagTypedArrayBuiltIns = [
{ tag: 'Int8Array', instance: new Int8Array(), prototype: Object.getPrototypeOf(Int8Array).prototype },
{ tag: 'Uint8Array', instance: new Uint8Array(), prototype: Object.getPrototypeOf(Uint8Array).prototype },
{ tag: 'Uint8ClampedArray', instance: new Uint8ClampedArray(), prototype: Object.getPrototypeOf(Uint8ClampedArray).prototype },
{ tag: 'Int16Array', instance: new Int16Array(), prototype: Object.getPrototypeOf(Int16Array).prototype },
{ tag: 'Uint16Array', instance: new Uint16Array(), prototype: Object.getPrototypeOf(Uint16Array).prototype },
{ tag: 'Int32Array', instance: new Int32Array(), prototype: Object.getPrototypeOf(Int32Array).prototype },
{ tag: 'Uint32Array', instance: new Uint32Array(), prototype: Object.getPrototypeOf(Uint32Array).prototype },
{ tag: 'Float32Array', instance: new Float32Array(), prototype: Object.getPrototypeOf(Float32Array).prototype },
{ tag: 'Float64Array', instance: new Float64Array(), prototype: Object.getPrototypeOf(Float64Array).prototype }
];
// A subset of builtins use the presence of an internal slot rather than a default @@toStringTag value.
// ES2017 19.1.3.6 Object. prototype.toString ( )
// 5. If isArray is true, let builtinTag be "Array".
// ...
// 14. Else, let builtinTag be "Object"
var arrayInternalSlotBuiltIn = { tag: "Array", instance: [], prototype: Array.prototype };
var internalSlotBuiltInsNoArray = [
{ tag: "String", instance: new String(), prototype: String.prototype },
{ tag: "Function", instance: function () {}, prototype: Function.prototype },
{ tag: "Error", instance: new Error(), prototype: Error.prototype },
{ tag: "Boolean", instance: new Boolean(), prototype: Boolean.prototype },
{ tag: "Number", instance: new Number(), prototype: Number.prototype },
{ tag: "Date", instance: new Date(), prototype: Date.prototype },
{ tag: "RegExp", instance: /a/, prototype: RegExp.prototype },
{ tag: "Object", instance: {}, prototype: Object.prototype },
];
var internalSlotBuiltIns = [...internalSlotBuiltInsNoArray, arrayInternalSlotBuiltIn];
var argumentsInternalSlotBuiltIn =
{ tag: "Arguments", instance: (function () { return arguments; })(), prototype: (function () { return arguments; })() }
var tests = [
{
name: "Built-in prototype @@toStringTag properties",
body: function () {
assert.areEqual("Symbol", Symbol.prototype[Symbol.toStringTag], "check String is Symbol");
var o = Object.getOwnPropertyDescriptor(Symbol.prototype, Symbol.toStringTag);
assert.isFalse(o.writable, "Symbol @@toStringTag is not writable");
assert.isFalse(o.enumerable, "Symbol @@toStringTag is not enumerable");
assert.isTrue(o.configurable, "Symbol @@toStringTag is configurable");
function checkBuiltInPrototype(builtInName, prototype) {
// @@toStringTag default value
assert.areEqual(builtInName, prototype[Symbol.toStringTag], builtInName + "[Symbol.toStringTag] defaults to '" + builtInName + "'");
assert.areEqual("string", typeof prototype[Symbol.toStringTag], builtInName + "[Symbol.toStringTag] is a String")
// @@toStringTag property descriptor
var o = Object.getOwnPropertyDescriptor(prototype, Symbol.toStringTag);
assert.isFalse(o.writable, builtInName + "[@@toStringTag] is not writable");
assert.isFalse(o.enumerable, builtInName + "[@@toStringTag] is not enumerable");
assert.isTrue(o.configurable, builtInName + "[@@toStringTag] is configurable");
}
// The following built-ins have an explicit @@toStringTag property on their prototype
for (var { tag, prototype } of explicitTagBuiltIns) {
checkBuiltInPrototype(tag, prototype);
}
}
},
{
name: "Built-ins with no @@toStringTag property that have a default tag",
body: function () {
// Verify there is no @@toStringTag property.
for (var { tag, prototype } of [...internalSlotBuiltIns, argumentsInternalSlotBuiltIn]) {
assert.isFalse(prototype.hasOwnProperty(Symbol.toStringTag), tag + " does not have a @@toStringTag property");
}
}
},
{
name: "Default @@toStringTag built ins",
body: function () {
for (var { tag, instance } of [...primitives, ...explicitTagBuiltIns, ...explicitTagTypedArrayBuiltIns, ...internalSlotBuiltIns, argumentsInternalSlotBuiltIn]) {
assert.areEqual("[object "+ tag + "]", Object.prototype.toString.call(instance), tag + " toString uses default tag");
}
}
},
{
name: "Proxy forwarding behavior",
body: function () {
function testBuiltIn({ tag, instance }, expectedTag) {
assert.areEqual("[object " + expectedTag + "]", Object.prototype.toString.call(new Proxy(instance, {})), tag + " proxy toString should have tag " + expectedTag);
assert.areEqual("[object " + expectedTag + "]", Object.prototype.toString.call(new Proxy(new Proxy(instance, {}), {})), tag + " chained proxy toString should have tag " + expectedTag);
}
for (var row of [...explicitTagBuiltIns, arrayInternalSlotBuiltIn]) {
testBuiltIn(row, row['tag']);
}
for (var row of internalSlotBuiltInsNoArray) {
testBuiltIn(row, "Object");
}
}
},
{
name: "Subclass @@toStringTag override of internal slot based default tag",
body: function () {
function testSubclassedBuiltInOverride(builtInName, instance) {
var c = class extends eval(builtInName) {};
assert.areEqual("[object " + builtInName + "]", Object.prototype.toString.call(new c), builtInName + " subclass toString uses default internal slot based tag before overriding");
var instance = new c();
var newName = builtInName + "Override";
c[Symbol.toStringTag] = newName;
assert.areEqual("[object " + newName + "]", Object.prototype.toString.call(c), builtInName + " toString uses overridden @@toStringTag after assignment");
assert.areEqual("[object " + builtInName + "]", Object.prototype.toString.call(instance), builtInName + " instance created before override does not pick up overridden class @@toStringTag");
var newInstanceName = builtInName + "Instance";
instance[Symbol.toStringTag] = newInstanceName;
assert.areEqual("[object " + newInstanceName + "]", Object.prototype.toString.call(instance), builtInName + " instance picks up @@toStringTag over the internal slot based default tag");
}
for (var { tag, instance } of internalSlotBuiltIns) {
testSubclassedBuiltInOverride(tag, instance);
}
}
},
{
name: "TypedArray @@toStringTag behavior",
body: function () {
function checkGetterOnPrototype(builtInName, prototype) {
// This property has the attributes { [[Enumerable]]: false, [[Configurable]]: true }.
var o = Object.getOwnPropertyDescriptor(prototype, Symbol.toStringTag);
assert.isFalse(o.enumerable, builtInName + " symbol @@toStringTag is not enumerable");
assert.isTrue(o.configurable, builtInName + " symbol @@toStringTag is configurable");
assert.areEqual("function", typeof o.get, builtInName + " @@toStringtag is a getter function");
}
for (var { tag, prototype } of explicitTagTypedArrayBuiltIns) {
checkGetterOnPrototype(tag, prototype);
}
}
},
{
name: "Invalid @@toStringtag (non-String coercible)",
body: function () {
function testBuiltIn(builtInName) {
var c, i;
if (builtInName == "DataView") {
c = class extends DataView {};
i = new c(new ArrayBuffer());
} else {
c = class extends eval(builtInName) {};
i = new c();
}
i[Symbol.toStringTag] = null;
assert.areEqual("[object " + builtInName + "]", Object.prototype.toString.call(i), builtInName + " defaults to internal slot based tag when @@toStringTag override is not string coercible");
}
for (var { tag } of [...explicitTagExtensibleBuiltIns, ...explicitTagTypedArrayBuiltIns, ...internalSlotBuiltIns]) {
testBuiltIn(tag);
}
}
},
{
name: "@@toStringTag lookup does not use HasProperty",
body: function() {
var proxy = new Proxy({ [Symbol.toStringTag] : "Proxied" }, {
// Previous versions of the spec called HasProperty
has: function (target, key) {
assert.fail("Object.prototype.toString should not call HasProperty");
}
});
assert.areEqual("[object Proxied]", Object.prototype.toString.call(proxy), "Proxied @@toStringTag passes through value without calling HasProperty");
}
},
{
name: "@@toStringTag properties with side effects",
body: function () {
var tagThrows = { get [Symbol.toStringTag]() { throw new Error(); } };
assert.throws(function () { Object.prototype.toString.call(tagThrows) }, Error, "@@toStringTag throws as a side effect");
}
},
{
name: "VSO OS Bug 1160433: embedded null characters in toStringTag property value",
body: function () {
var o = {
[Symbol.toStringTag]: "before\0after"
};
assert.areEqual("[object before\0after]", o.toString(), "ToString implementation handles embedded null characters in @@toStringTag property value");
}
}
];
testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });