| // This test suite compares the behavior of setting the prototype on various values |
| // (using Object.setPrototypeOf(), obj.__proto__ assignment, and Reflect.setPrototypeOf()) |
| // against what is specified in the ES spec. The expected behavior specified according |
| // to the spec is expressed in expectationsForObjectSetPrototypeOf, |
| // expectationsForSetUnderscoreProto, and expectationsForReflectSetPrototypeOf. |
| |
| import * as namespace from "./namespace-prototype-assignment.js" |
| |
| var inBrowser = (typeof window != "undefined"); |
| |
| // Test configuration options: |
| var verbose = false; |
| var maxIterations = 1; |
| var throwOnEachError = true; |
| |
| var testUndefined = true; |
| var testNull = true; |
| var testTrue = true; |
| var testFalse = true; |
| var testNumbers = true; |
| var testString = true; |
| var testSymbol = true; |
| var testObject = true; |
| var testGlobal = true; |
| var testWindowProtos = inBrowser; |
| |
| var engine; |
| |
| //==================================================================================== |
| // Error messages: |
| |
| if (inBrowser) { |
| let userAgent = navigator.userAgent; |
| if (userAgent.match(/ Chrome\/[0-9]+/)) engine = "chrome"; |
| else if (userAgent.match(/ Firefox\/[0-9]+/)) engine = "default"; |
| else engine = "safari"; |
| } else |
| engine = "jsc"; |
| |
| // Set default error messages and then override with engine specific ones below. |
| var DefaultTypeError = "TypeError: "; |
| var CannotSetPrototypeOfImmutablePrototypeObject = DefaultTypeError; |
| var CannotSetPrototypeOfThisObject = DefaultTypeError; |
| var CannotSetPrototypeOfUndefinedOrNull = DefaultTypeError; |
| var CannotSetPrototypeOfNonObject = DefaultTypeError; |
| var PrototypeValueCanOnlyBeAnObjectOrNull = DefaultTypeError; |
| var ObjectProtoCalledOnNullOrUndefinedError = DefaultTypeError; |
| var ReflectSetPrototypeOfRequiresTheFirstArgumentBeAnObject = DefaultTypeError; |
| var ReflectSetPrototypeOfRequiresTheSecondArgumentBeAnObjectOrNull = DefaultTypeError; |
| |
| if (engine == "jsc" || engine === "safari") { |
| CannotSetPrototypeOfImmutablePrototypeObject = "TypeError: Cannot set prototype of immutable prototype object"; |
| CannotSetPrototypeOfThisObject = "TypeError: Cannot set prototype of this object"; |
| CannotSetPrototypeOfUndefinedOrNull = "TypeError: Cannot set prototype of undefined or null"; |
| CannotSetPrototypeOfNonObject = "TypeError: Cannot set prototype of non-object"; |
| PrototypeValueCanOnlyBeAnObjectOrNull = "TypeError: Prototype value can only be an object or null"; |
| ObjectProtoCalledOnNullOrUndefinedError = "TypeError: Object.prototype.__proto__ called on null or undefined"; |
| ReflectSetPrototypeOfRequiresTheFirstArgumentBeAnObject = "TypeError: Reflect.setPrototypeOf requires the first argument be an object"; |
| ReflectSetPrototypeOfRequiresTheSecondArgumentBeAnObjectOrNull = "TypeError: Reflect.setPrototypeOf requires the second argument be either an object or null"; |
| } else if (engine === "chrome") { |
| CannotSetPrototypeOfImmutablePrototypeObject = "TypeError: Immutable prototype object "; |
| } |
| |
| //==================================================================================== |
| // Utility functions: |
| |
| if (inBrowser) |
| print = console.log; |
| |
| function reportError(errorMessage) { |
| if (throwOnEachError) |
| throw errorMessage; |
| else |
| print(errorMessage); |
| } |
| |
| function shouldEqual(testID, resultType, actual, expected) { |
| if (actual != expected) |
| reportError("ERROR in " + resultType |
| + ": expect " + stringify(expected) + ", actual " + stringify(actual) |
| + " in test: " + testID.signature + " on iteration " + testID.iteration); |
| } |
| |
| function shouldThrow(testID, resultType, actual, expected) { |
| let actualStr = "" + actual; |
| if (!actualStr.startsWith(expected)) |
| reportError("ERROR in " + resultType |
| + ": expect " + expected + ", actual " + actual |
| + " in test: " + testID.signature + " on iteration " + testID.iteration); |
| } |
| |
| function stringify(value) { |
| if (typeof value == "string") return '"' + value + '"'; |
| if (typeof value == "symbol") return value.toString(); |
| |
| if (value === origGlobalProto) return "origGlobalProto"; |
| if (value === origObjectProto) return "origObjectProto"; |
| if (value === newObjectProto) return "newObjectProto"; |
| if (value === proxyObject) return "proxyObject"; |
| |
| if (value === null) return "null"; |
| if (typeof value == "object") return "object"; |
| return "" + value; |
| } |
| |
| function makeTestID(index, iteration, targetName, protoToSet, protoSetter, expected) { |
| let testID = {}; |
| testID.signature = "[" + index + "] " |
| + protoSetter.actionName + "|" |
| + targetName + "|" |
| + stringify(protoToSet) + "|" |
| + stringify(expected.result) + "|" |
| + stringify(expected.proto) + "|" |
| + stringify(expected.exception); |
| testID.iteration = iteration; |
| return testID; |
| } |
| |
| //==================================================================================== |
| // Functions to express the expectations of the ES specification: |
| |
| function doInternalSetPrototypeOf(result, target, origProto, newProto) { |
| if (!target.setPrototypeOf) { |
| result.success = true; |
| result.exception = undefined; |
| return; |
| } |
| target.setPrototypeOf(result, origProto, newProto); |
| } |
| |
| // 9.1.2.1 OrdinarySetPrototypeOf ( O, V ) |
| // https://tc39.github.io/ecma262/#sec-ordinarysetprototypeof |
| function ordinarySetPrototypeOf(result, currentProto, newProto) { |
| // 9.1.2.1-4 If SameValue(V, current) is true, return true. |
| if (newProto === currentProto) { |
| result.success = true; |
| return; |
| } |
| // 9.1.2.1-5 [extensibility check not tested here] |
| // 9.1.2.1-8 [cycle check not tested here] |
| result.success = true; |
| } |
| |
| // 9.4.7.2 SetImmutablePrototype ( O, V ) |
| // https://tc39.github.io/ecma262/#sec-set-immutable-prototype |
| function setImmutablePrototype(result, currentProto, newProto) { |
| if (newProto === currentProto) { |
| result.success = true; |
| return; |
| } |
| result.success = false; |
| result.exception = CannotSetPrototypeOfImmutablePrototypeObject; |
| } |
| |
| // HTML spec: 7.4.2 [[SetPrototypeOf]] ( V ) |
| // https://html.spec.whatwg.org/#windowproxy-setprototypeof |
| function windowProxySetPrototypeOf(result, currentProto, newProto) { |
| result.success = false; |
| result.exception = CannotSetPrototypeOfThisObject; |
| } |
| |
| |
| var count = 0; |
| function initSetterExpectation(target, newProto) { |
| var targetValue = target.value(); |
| var origProto = undefined; |
| if (targetValue != null && targetValue != undefined) |
| origProto = targetValue.__proto__; // Default to old proto. |
| |
| var expected = {}; |
| expected.targetValue = targetValue; |
| expected.origProto = origProto; |
| expected.exception = undefined; |
| expected.proto = origProto; |
| expected.result = undefined; |
| |
| return expected; |
| } |
| |
| // 19.1.2.21 Object.setPrototypeOf ( O, proto ) |
| // https://tc39.github.io/ecma262/#sec-object.setprototypeof |
| function objectSetPrototypeOf(target, newProto) { |
| let expected = initSetterExpectation(target, newProto); |
| var targetValue = expected.targetValue; |
| var origProto = expected.origProto; |
| |
| function throwIfNoExceptionPending(e) { |
| if (!expected.exception) |
| expected.exception = e; |
| } |
| |
| // 19.1.2.21-1 Let O be ? RequireObjectCoercible(O). |
| if (targetValue == undefined || targetValue == null) |
| throwIfNoExceptionPending(CannotSetPrototypeOfUndefinedOrNull); |
| |
| // 19.1.2.21-2 If Type(proto) is neither Object nor Null, throw a TypeError exception. |
| if (typeof newProto != "object") |
| throwIfNoExceptionPending(PrototypeValueCanOnlyBeAnObjectOrNull); |
| |
| // 19.1.2.21-3 If Type(O) is not Object, return O. |
| else if (typeof targetValue != "object") |
| expected.result = targetValue; |
| |
| // 19.1.2.21-4 Let status be ? O.[[SetPrototypeOf]](proto). |
| else { |
| // 19.1.2.21-5 If status is false, throw a TypeError exception. |
| let result = {}; |
| doInternalSetPrototypeOf(result, target, origProto, newProto); |
| if (result.success) |
| expected.proto = newProto; |
| else |
| throwIfNoExceptionPending(result.exception); |
| |
| // 19.1.2.21-6 Return O. |
| expected.result = targetValue; |
| } |
| |
| return expected; |
| } |
| objectSetPrototypeOf.action = (obj, newProto) => Object.setPrototypeOf(obj, newProto); |
| objectSetPrototypeOf.actionName = "Object.setPrototypeOf"; |
| |
| |
| // B.2.2.1.2 set Object.prototype.__proto__ |
| // https://tc39.github.io/ecma262/#sec-set-object.prototype.__proto__ |
| function setUnderscoreProto(target, newProto) { |
| let expected = initSetterExpectation(target, newProto); |
| var targetValue = expected.targetValue; |
| var origProto = expected.origProto; |
| |
| function throwIfNoExceptionPending(e) { |
| if (!expected.exception) |
| expected.exception = e; |
| } |
| |
| // B.2.2.1.2-1 Let O be ? RequireObjectCoercible(this value). |
| if (targetValue == undefined || targetValue == null) |
| throwIfNoExceptionPending(DefaultTypeError); |
| |
| // B.2.2.1.2-2 If Type(proto) is neither Object nor Null, return undefined. |
| if (typeof newProto != "object") |
| expected.result = undefined; |
| |
| // B.2.2.1.2-3 If Type(O) is not Object, return undefined. |
| else if (typeof targetValue != "object") |
| expected.result = undefined; |
| |
| // B.2.2.1.2-4 Let status be ? O.[[SetPrototypeOf]](proto). |
| else { |
| // B.2.2.1.2-5 If status is false, throw a TypeError exception. |
| let result = {}; |
| doInternalSetPrototypeOf(result, target, origProto, newProto); |
| if (result.success) |
| expected.proto = newProto; |
| else |
| throwIfNoExceptionPending(result.exception); |
| |
| // B.2.2.1.2-6 Return undefined. |
| expected.result = undefined; |
| } |
| |
| // Override the result to be the newProto value because the statement obj.__proto__ = value |
| // will produce the rhs value, not the result of the obj.__proto__ setter. |
| expected.result = newProto; |
| return expected; |
| } |
| setUnderscoreProto.action = (obj, newProto) => (obj.__proto__ = newProto); |
| setUnderscoreProto.actionName = "obj.__proto__"; |
| |
| |
| // 26.1.13 Reflect.setPrototypeOf ( target, proto ) |
| // https://tc39.github.io/ecma262/#sec-reflect.setprototypeof |
| // function expectationsForReflectSetPrototypeOf(target, newProto, targetExpectation) { |
| function reflectSetPrototypeOf(target, newProto) { |
| let expected = initSetterExpectation(target, newProto); |
| var targetValue = expected.targetValue; |
| var origProto = expected.origProto; |
| |
| function throwIfNoExceptionPending(e) { |
| if (!expected.exception) |
| expected.exception = e; |
| } |
| |
| // 26.1.13-1 If Type(target) is not Object, throw a TypeError exception. |
| if (targetValue === null || typeof targetValue != "object") |
| throwIfNoExceptionPending(ReflectSetPrototypeOfRequiresTheFirstArgumentBeAnObject); |
| |
| // 26.1.13-2 If Type(proto) is not Object and proto is not null, throw a TypeError exception. |
| if (typeof newProto != "object") |
| throwIfNoExceptionPending(ReflectSetPrototypeOfRequiresTheSecondArgumentBeAnObjectOrNull); |
| |
| // 26.1.13-3 Return ? target.[[SetPrototypeOf]](proto). |
| let result = {}; |
| doInternalSetPrototypeOf(result, target, origProto, newProto); |
| expected.result = result.success; |
| if (result.success) |
| expected.proto = newProto; |
| |
| return expected; |
| } |
| reflectSetPrototypeOf.action = (obj, newProto) => Reflect.setPrototypeOf(obj, newProto); |
| reflectSetPrototypeOf.actionName = "Reflect.setPrototypeOf"; |
| |
| |
| //==================================================================================== |
| // Test Data: |
| |
| var global = new Function('return this')(); |
| var origGlobalProto = global.__proto__; |
| var origObjectProto = {}.__proto__; |
| var proxyObject = new Proxy({ }, { |
| setPrototypeOf(target, value) { |
| throw "Thrown from proxy"; |
| } |
| }); |
| var newObjectProto = { toString() { return "newObjectProto"; } }; |
| var origNamespaceProto = namespace.__proto__; |
| |
| var targets = []; |
| |
| targets.push({ |
| name: "namespace", |
| value: () => origNamespaceProto, |
| setPrototypeOf: setImmutablePrototype |
| }); |
| |
| var newProtos = [ |
| undefined, |
| null, |
| true, |
| false, |
| 0, |
| 11, |
| 123.456, |
| "doh", |
| Symbol("doh"), |
| {}, |
| origObjectProto, |
| origGlobalProto, |
| newObjectProto |
| ]; |
| |
| var protoSetters = [ |
| objectSetPrototypeOf, |
| setUnderscoreProto, |
| reflectSetPrototypeOf, |
| ]; |
| |
| |
| //==================================================================================== |
| // Test driver functions: |
| |
| function test(testID, targetValue, newProto, setterAction, expected) { |
| let exception = undefined; |
| let result = undefined; |
| try { |
| result = setterAction(targetValue, newProto); |
| } catch (e) { |
| exception = e; |
| } |
| shouldThrow(testID, "exception", exception, expected.exception); |
| if (!expected.exception) { |
| shouldEqual(testID, "__proto__", targetValue.__proto__, expected.proto); |
| shouldEqual(testID, "result", result, expected.result); |
| } |
| } |
| |
| function runTests() { |
| let testIndex = 0; |
| for (let protoSetter of protoSetters) { |
| for (let target of targets) { |
| for (let newProto of newProtos) { |
| let currentTestIndex = testIndex++; |
| for (var i = 0; i < maxIterations; i++) { |
| let expected = protoSetter(target, newProto); |
| let targetValue = expected.targetValue; |
| |
| let testID = makeTestID(currentTestIndex, i, target.name, newProto, protoSetter, expected); |
| if (verbose && i == 0) |
| print("test: " + testID.signature); |
| |
| test(testID, targetValue, newProto, protoSetter.action, expected); |
| } |
| } |
| } |
| } |
| } |
| |
| runTests(); |