2017-08-28 19:37:38 +02:00
|
|
|
// Copyright (C) 2017 Josh Wolfe. All rights reserved.
|
|
|
|
// This code is governed by the BSD license found in the LICENSE file.
|
|
|
|
/*---
|
|
|
|
description: |
|
|
|
|
Functions to help generate test cases for testing type coercion abstract
|
|
|
|
operations like ToNumber.
|
|
|
|
---*/
|
|
|
|
|
2017-09-13 03:38:29 +02:00
|
|
|
function testCoercibleToIndexZero(test) {
|
|
|
|
testCoercibleToIntegerZero(test);
|
|
|
|
}
|
|
|
|
|
|
|
|
function testCoercibleToIndexOne(test) {
|
|
|
|
testCoercibleToIntegerOne(test);
|
|
|
|
}
|
|
|
|
|
|
|
|
function testCoercibleToIndexFromIndex(nominalIndex, test) {
|
|
|
|
assert(Number.isInteger(nominalIndex));
|
|
|
|
assert(0 <= nominalIndex && nominalIndex <= 2**53 - 1);
|
|
|
|
testCoercibleToIntegerFromInteger(nominalIndex, test);
|
|
|
|
}
|
|
|
|
|
2017-08-29 22:53:38 +02:00
|
|
|
function testCoercibleToIntegerZero(test) {
|
2017-08-30 02:28:55 +02:00
|
|
|
testCoercibleToNumberZero(test);
|
|
|
|
|
|
|
|
testCoercibleToIntegerFromInteger(0, test);
|
|
|
|
|
|
|
|
// NaN -> +0
|
|
|
|
testCoercibleToNumberNan(test);
|
|
|
|
|
|
|
|
// When toString() returns a string that parses to NaN:
|
|
|
|
test({});
|
|
|
|
test([]);
|
|
|
|
}
|
|
|
|
|
|
|
|
function testCoercibleToIntegerOne(test) {
|
|
|
|
testCoercibleToNumberOne(test);
|
|
|
|
|
|
|
|
testCoercibleToIntegerFromInteger(1, test);
|
|
|
|
|
|
|
|
// When toString() returns "1"
|
|
|
|
test([1]);
|
|
|
|
test(["1"]);
|
|
|
|
}
|
|
|
|
|
|
|
|
function testCoercibleToNumberZero(test) {
|
2017-08-29 22:53:38 +02:00
|
|
|
function testPrimitiveValue(value) {
|
|
|
|
test(value);
|
|
|
|
// ToPrimitive
|
|
|
|
testPrimitiveWrappers(value, "number", test);
|
|
|
|
}
|
2017-08-28 19:37:38 +02:00
|
|
|
|
2017-08-29 22:53:38 +02:00
|
|
|
testPrimitiveValue(null);
|
|
|
|
testPrimitiveValue(false);
|
|
|
|
testPrimitiveValue(0);
|
|
|
|
testPrimitiveValue("0");
|
2017-08-30 02:28:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function testCoercibleToNumberNan(test) {
|
|
|
|
function testPrimitiveValue(value) {
|
|
|
|
test(value);
|
|
|
|
// ToPrimitive
|
|
|
|
testPrimitiveWrappers(value, "number", test);
|
|
|
|
}
|
2017-08-29 22:53:38 +02:00
|
|
|
|
|
|
|
testPrimitiveValue(undefined);
|
|
|
|
testPrimitiveValue(NaN);
|
|
|
|
testPrimitiveValue("");
|
|
|
|
testPrimitiveValue("foo");
|
|
|
|
testPrimitiveValue("true");
|
2017-08-28 19:37:38 +02:00
|
|
|
}
|
|
|
|
|
2017-08-30 02:28:55 +02:00
|
|
|
function testCoercibleToNumberOne(test) {
|
2017-08-29 22:53:38 +02:00
|
|
|
function testPrimitiveValue(value) {
|
|
|
|
test(value);
|
|
|
|
// ToPrimitive
|
|
|
|
testPrimitiveWrappers(value, "number", test);
|
|
|
|
}
|
2017-08-28 19:37:38 +02:00
|
|
|
|
2017-08-29 22:53:38 +02:00
|
|
|
testPrimitiveValue(true);
|
|
|
|
testPrimitiveValue(1);
|
|
|
|
testPrimitiveValue("1");
|
2017-08-28 19:37:38 +02:00
|
|
|
}
|
|
|
|
|
2017-08-29 22:53:38 +02:00
|
|
|
function testCoercibleToIntegerFromInteger(nominalInteger, test) {
|
2017-08-28 19:37:38 +02:00
|
|
|
assert(Number.isInteger(nominalInteger));
|
|
|
|
|
2017-08-29 22:53:38 +02:00
|
|
|
function testPrimitiveValue(value) {
|
|
|
|
test(value);
|
|
|
|
// ToPrimitive
|
|
|
|
testPrimitiveWrappers(value, "number", test);
|
|
|
|
|
|
|
|
// Non-primitive values that coerce to the nominal integer:
|
|
|
|
// toString() returns a string that parsers to a primitive value.
|
|
|
|
test([value]);
|
|
|
|
}
|
|
|
|
|
|
|
|
function testPrimitiveNumber(number) {
|
|
|
|
testPrimitiveValue(number);
|
|
|
|
// ToNumber: String -> Number
|
|
|
|
testPrimitiveValue(number.toString());
|
|
|
|
}
|
|
|
|
|
|
|
|
testPrimitiveNumber(nominalInteger);
|
2017-08-28 19:37:38 +02:00
|
|
|
|
|
|
|
// ToInteger: floor(abs(number))
|
|
|
|
if (nominalInteger >= 0) {
|
2017-08-29 22:53:38 +02:00
|
|
|
testPrimitiveNumber(nominalInteger + 0.9);
|
2017-08-28 19:37:38 +02:00
|
|
|
}
|
|
|
|
if (nominalInteger <= 0) {
|
2017-08-29 22:53:38 +02:00
|
|
|
testPrimitiveNumber(nominalInteger - 0.9);
|
2017-08-28 19:37:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-29 22:53:38 +02:00
|
|
|
function testPrimitiveWrappers(primitiveValue, hint, test) {
|
2017-08-28 19:37:38 +02:00
|
|
|
if (primitiveValue != null) {
|
|
|
|
// null and undefined result in {} rather than a proper wrapper,
|
|
|
|
// so skip this case for those values.
|
2017-08-29 22:53:38 +02:00
|
|
|
test(Object(primitiveValue));
|
2017-08-28 19:37:38 +02:00
|
|
|
}
|
|
|
|
|
2017-08-29 22:53:38 +02:00
|
|
|
testCoercibleToPrimitiveWithMethod(hint, function() {
|
2017-08-28 19:37:38 +02:00
|
|
|
return primitiveValue;
|
2017-08-29 22:53:38 +02:00
|
|
|
}, test);
|
2017-08-28 19:37:38 +02:00
|
|
|
}
|
|
|
|
|
2017-08-29 22:53:38 +02:00
|
|
|
function testCoercibleToPrimitiveWithMethod(hint, method, test) {
|
2017-08-28 19:37:38 +02:00
|
|
|
var methodNames;
|
|
|
|
if (hint === "number") {
|
|
|
|
methodNames = ["valueOf", "toString"];
|
2017-08-29 22:53:38 +02:00
|
|
|
} else if (hint === "string") {
|
2017-08-28 19:37:38 +02:00
|
|
|
methodNames = ["toString", "valueOf"];
|
2017-08-29 22:53:38 +02:00
|
|
|
} else {
|
|
|
|
throw new Test262Error();
|
2017-08-28 19:37:38 +02:00
|
|
|
}
|
2017-08-29 22:53:38 +02:00
|
|
|
// precedence order
|
|
|
|
test({
|
|
|
|
[Symbol.toPrimitive]: method,
|
|
|
|
[methodNames[0]]: function() { throw new Test262Error(); },
|
|
|
|
[methodNames[1]]: function() { throw new Test262Error(); },
|
|
|
|
});
|
|
|
|
test({
|
|
|
|
[methodNames[0]]: method,
|
|
|
|
[methodNames[1]]: function() { throw new Test262Error(); },
|
|
|
|
});
|
2017-08-30 03:06:37 +02:00
|
|
|
if (hint === "number") {
|
|
|
|
// The default valueOf returns an object, which is unsuitable.
|
|
|
|
// The default toString returns a String, which is suitable.
|
|
|
|
// Therefore this test only works for valueOf falling back to toString.
|
|
|
|
test({
|
|
|
|
// this is toString:
|
|
|
|
[methodNames[1]]: method,
|
|
|
|
});
|
|
|
|
}
|
2017-08-29 22:53:38 +02:00
|
|
|
|
|
|
|
// GetMethod: if func is undefined or null, return undefined.
|
|
|
|
test({
|
|
|
|
[Symbol.toPrimitive]: undefined,
|
|
|
|
[methodNames[0]]: method,
|
|
|
|
[methodNames[1]]: method,
|
|
|
|
});
|
|
|
|
test({
|
|
|
|
[Symbol.toPrimitive]: null,
|
|
|
|
[methodNames[0]]: method,
|
|
|
|
[methodNames[1]]: method,
|
|
|
|
});
|
|
|
|
|
|
|
|
// if methodNames[0] is not callable, fallback to methodNames[1]
|
|
|
|
test({
|
|
|
|
[methodNames[0]]: null,
|
|
|
|
[methodNames[1]]: method,
|
|
|
|
});
|
|
|
|
test({
|
|
|
|
[methodNames[0]]: 1,
|
|
|
|
[methodNames[1]]: method,
|
|
|
|
});
|
|
|
|
test({
|
|
|
|
[methodNames[0]]: {},
|
|
|
|
[methodNames[1]]: method,
|
|
|
|
});
|
|
|
|
|
|
|
|
// if methodNames[0] returns an object, fallback to methodNames[1]
|
|
|
|
test({
|
|
|
|
[methodNames[0]]: function() { return {}; },
|
|
|
|
[methodNames[1]]: method,
|
|
|
|
});
|
|
|
|
test({
|
|
|
|
[methodNames[0]]: function() { return Object(1); },
|
|
|
|
[methodNames[1]]: method,
|
|
|
|
});
|
2017-08-28 19:37:38 +02:00
|
|
|
}
|
|
|
|
|
2017-09-13 03:38:29 +02:00
|
|
|
function testNotCoercibleToIndex(test) {
|
|
|
|
function testPrimitiveValue(value) {
|
|
|
|
test(RangeError, value);
|
|
|
|
// ToPrimitive
|
|
|
|
testPrimitiveWrappers(value, "number", function(value) {
|
|
|
|
test(RangeError, value);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Let integerIndex be ? ToInteger(value).
|
|
|
|
testNotCoercibleToInteger(test);
|
|
|
|
|
|
|
|
// If integerIndex < 0, throw a RangeError exception.
|
|
|
|
testPrimitiveValue(-1);
|
|
|
|
testPrimitiveValue(-2.5);
|
|
|
|
testPrimitiveValue("-2.5");
|
|
|
|
testPrimitiveValue(-Infinity);
|
|
|
|
|
|
|
|
// Let index be ! ToLength(integerIndex).
|
|
|
|
// If SameValueZero(integerIndex, index) is false, throw a RangeError exception.
|
|
|
|
testPrimitiveValue(2 ** 53);
|
|
|
|
testPrimitiveValue(Infinity);
|
|
|
|
}
|
|
|
|
|
2017-08-29 22:53:38 +02:00
|
|
|
function testNotCoercibleToInteger(test) {
|
2017-08-28 19:37:38 +02:00
|
|
|
// ToInteger only throws from ToNumber.
|
2017-09-13 03:38:29 +02:00
|
|
|
testNotCoercibleToNumber(test);
|
2017-08-28 19:37:38 +02:00
|
|
|
}
|
2017-09-13 03:38:29 +02:00
|
|
|
|
2017-08-29 22:53:38 +02:00
|
|
|
function testNotCoercibleToNumber(test) {
|
|
|
|
function testPrimitiveValue(value) {
|
|
|
|
test(TypeError, value);
|
|
|
|
// ToPrimitive
|
|
|
|
testPrimitiveWrappers(value, "number", function(value) {
|
|
|
|
test(TypeError, value);
|
|
|
|
});
|
|
|
|
}
|
2017-08-28 19:37:38 +02:00
|
|
|
|
|
|
|
// ToNumber: Symbol -> TypeError
|
2017-08-29 22:53:38 +02:00
|
|
|
testPrimitiveValue(Symbol("1"));
|
|
|
|
|
2017-08-28 19:37:38 +02:00
|
|
|
if (typeof BigInt !== "undefined") {
|
|
|
|
// ToNumber: BigInt -> TypeError
|
2017-08-29 22:53:38 +02:00
|
|
|
testPrimitiveValue(BigInt(0));
|
2017-08-28 19:37:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// ToPrimitive
|
2017-08-29 22:53:38 +02:00
|
|
|
testNotCoercibleToPrimitive("number", test);
|
2017-08-28 19:37:38 +02:00
|
|
|
}
|
|
|
|
|
2017-08-29 22:53:38 +02:00
|
|
|
function testNotCoercibleToPrimitive(hint, test) {
|
2017-08-28 19:37:38 +02:00
|
|
|
function MyError() {}
|
|
|
|
|
|
|
|
// ToPrimitive: input[@@toPrimitive] is not callable (and non-null)
|
2017-08-29 22:53:38 +02:00
|
|
|
test(TypeError, {[Symbol.toPrimitive]: 1});
|
|
|
|
test(TypeError, {[Symbol.toPrimitive]: {}});
|
2017-08-28 19:37:38 +02:00
|
|
|
|
|
|
|
// ToPrimitive: input[@@toPrimitive] returns object
|
2017-08-29 22:53:38 +02:00
|
|
|
test(TypeError, {[Symbol.toPrimitive]: function() { return Object(1); }});
|
|
|
|
test(TypeError, {[Symbol.toPrimitive]: function() { return {}; }});
|
2017-08-28 19:37:38 +02:00
|
|
|
|
|
|
|
// ToPrimitive: input[@@toPrimitive] throws
|
2017-08-29 22:53:38 +02:00
|
|
|
test(MyError, {[Symbol.toPrimitive]: function() { throw new MyError(); }});
|
2017-08-28 19:37:38 +02:00
|
|
|
|
|
|
|
// OrdinaryToPrimitive: method throws
|
2017-08-29 22:53:38 +02:00
|
|
|
testCoercibleToPrimitiveWithMethod(hint, function() {
|
2017-08-28 19:37:38 +02:00
|
|
|
throw new MyError();
|
2017-08-29 22:53:38 +02:00
|
|
|
}, function(value) {
|
|
|
|
test(MyError, value);
|
2017-08-28 19:37:38 +02:00
|
|
|
});
|
|
|
|
|
2017-08-29 22:53:38 +02:00
|
|
|
// OrdinaryToPrimitive: both methods are unsuitable
|
|
|
|
function testUnsuitableMethod(method) {
|
|
|
|
test(TypeError, {valueOf:method, toString:method});
|
|
|
|
}
|
|
|
|
// not callable:
|
|
|
|
testUnsuitableMethod(null);
|
|
|
|
testUnsuitableMethod(1);
|
|
|
|
testUnsuitableMethod({});
|
|
|
|
// returns object:
|
|
|
|
testUnsuitableMethod(function() { return Object(1); });
|
|
|
|
testUnsuitableMethod(function() { return {}; });
|
2017-08-28 19:37:38 +02:00
|
|
|
}
|
2017-08-30 03:06:37 +02:00
|
|
|
|
|
|
|
function testCoercibleToString(test) {
|
|
|
|
function testPrimitiveValue(value, expectedString) {
|
|
|
|
test(value, expectedString);
|
|
|
|
// ToPrimitive
|
|
|
|
testPrimitiveWrappers(value, "string", function(value) {
|
|
|
|
test(value, expectedString);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
testPrimitiveValue(undefined, "undefined");
|
|
|
|
testPrimitiveValue(null, "null");
|
|
|
|
testPrimitiveValue(true, "true");
|
|
|
|
testPrimitiveValue(false, "false");
|
|
|
|
testPrimitiveValue(0, "0");
|
|
|
|
testPrimitiveValue(-0, "0");
|
|
|
|
testPrimitiveValue(Infinity, "Infinity");
|
|
|
|
testPrimitiveValue(-Infinity, "-Infinity");
|
|
|
|
testPrimitiveValue(123.456, "123.456");
|
|
|
|
testPrimitiveValue(-123.456, "-123.456");
|
|
|
|
testPrimitiveValue("", "");
|
|
|
|
testPrimitiveValue("foo", "foo");
|
|
|
|
|
|
|
|
if (typeof BigInt !== "undefined") {
|
|
|
|
// BigInt -> TypeError
|
|
|
|
testPrimitiveValue(BigInt(0), "0");
|
|
|
|
}
|
|
|
|
|
|
|
|
// toString of a few objects
|
|
|
|
test([], "");
|
|
|
|
test(["foo", "bar"], "foo,bar");
|
|
|
|
test({}, "[object Object]");
|
|
|
|
}
|
|
|
|
|
|
|
|
function testNotCoercibleToString(test) {
|
|
|
|
function testPrimitiveValue(value) {
|
|
|
|
test(TypeError, value);
|
|
|
|
// ToPrimitive
|
|
|
|
testPrimitiveWrappers(value, "string", function(value) {
|
|
|
|
test(TypeError, value);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Symbol -> TypeError
|
|
|
|
testPrimitiveValue(Symbol("1"));
|
|
|
|
|
|
|
|
// ToPrimitive
|
|
|
|
testNotCoercibleToPrimitive("string", test);
|
|
|
|
}
|
2017-09-13 03:38:29 +02:00
|
|
|
|
2017-10-24 17:48:34 +02:00
|
|
|
function testCoercibleToBooleanTrue(test) {
|
|
|
|
test(true);
|
|
|
|
test(1);
|
|
|
|
test("string");
|
|
|
|
test(Symbol("1"));
|
|
|
|
test({});
|
|
|
|
}
|
|
|
|
|
|
|
|
function testCoercibleToBooleanFalse(test) {
|
|
|
|
test(undefined);
|
|
|
|
test(null);
|
|
|
|
test(false);
|
|
|
|
test(0);
|
|
|
|
test(-0);
|
|
|
|
test(NaN);
|
|
|
|
test("");
|
|
|
|
}
|
|
|
|
|
2017-09-13 03:38:29 +02:00
|
|
|
function testCoercibleToBigIntZero(test) {
|
|
|
|
function testPrimitiveValue(value) {
|
|
|
|
test(value);
|
|
|
|
// ToPrimitive
|
|
|
|
testPrimitiveWrappers(value, "number", test);
|
|
|
|
}
|
|
|
|
|
2017-10-03 20:22:03 +02:00
|
|
|
testCoercibleToBigIntFromBigInt(BigInt(0), test);
|
|
|
|
testPrimitiveValue(-BigInt(0));
|
2017-09-13 03:38:29 +02:00
|
|
|
testPrimitiveValue("-0");
|
|
|
|
testPrimitiveValue(false);
|
|
|
|
testPrimitiveValue("");
|
|
|
|
testPrimitiveValue(" ");
|
|
|
|
|
|
|
|
// toString() returns ""
|
|
|
|
test([]);
|
|
|
|
|
|
|
|
// toString() returns "0"
|
|
|
|
test([0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
function testCoercibleToBigIntOne(test) {
|
|
|
|
function testPrimitiveValue(value) {
|
|
|
|
test(value);
|
|
|
|
// ToPrimitive
|
|
|
|
testPrimitiveWrappers(value, "number", test);
|
|
|
|
}
|
|
|
|
|
2017-10-03 20:22:03 +02:00
|
|
|
testCoercibleToBigIntFromBigInt(BigInt(1), test);
|
2017-09-13 03:38:29 +02:00
|
|
|
testPrimitiveValue(true);
|
|
|
|
|
|
|
|
// toString() returns "1"
|
|
|
|
test([1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
function testCoercibleToBigIntFromBigInt(nominalBigInt, test) {
|
|
|
|
function testPrimitiveValue(value) {
|
|
|
|
test(value);
|
|
|
|
// ToPrimitive
|
|
|
|
testPrimitiveWrappers(value, "number", test);
|
|
|
|
}
|
|
|
|
|
|
|
|
testPrimitiveValue(nominalBigInt);
|
|
|
|
testPrimitiveValue(nominalBigInt.toString());
|
|
|
|
testPrimitiveValue("0b" + nominalBigInt.toString(2));
|
|
|
|
testPrimitiveValue("0o" + nominalBigInt.toString(8));
|
|
|
|
testPrimitiveValue("0x" + nominalBigInt.toString(16));
|
|
|
|
testPrimitiveValue(" " + nominalBigInt.toString() + " ");
|
|
|
|
|
|
|
|
// toString() returns the decimal string representation
|
|
|
|
test([nominalBigInt]);
|
|
|
|
test([nominalBigInt.toString()]);
|
|
|
|
}
|
|
|
|
|
|
|
|
function testNotCoercibleToBigInt(test) {
|
|
|
|
function testPrimitiveValue(error, value) {
|
|
|
|
test(error, value);
|
|
|
|
// ToPrimitive
|
|
|
|
testPrimitiveWrappers(value, "number", function(value) {
|
|
|
|
test(error, value);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Undefined, Null, Number, Symbol -> TypeError
|
|
|
|
testPrimitiveValue(TypeError, undefined);
|
|
|
|
testPrimitiveValue(TypeError, null);
|
|
|
|
testPrimitiveValue(TypeError, 0);
|
|
|
|
testPrimitiveValue(TypeError, NaN);
|
|
|
|
testPrimitiveValue(TypeError, Infinity);
|
|
|
|
testPrimitiveValue(TypeError, Symbol("1"));
|
|
|
|
|
|
|
|
// when a String parses to NaN -> SyntaxError
|
|
|
|
function testStringValue(string) {
|
|
|
|
testPrimitiveValue(SyntaxError, string);
|
|
|
|
testPrimitiveValue(SyntaxError, " " + string);
|
|
|
|
testPrimitiveValue(SyntaxError, string + " ");
|
|
|
|
testPrimitiveValue(SyntaxError, " " + string + " ");
|
|
|
|
}
|
|
|
|
testStringValue("a");
|
|
|
|
testStringValue("0b2");
|
|
|
|
testStringValue("0o8");
|
|
|
|
testStringValue("0xg");
|
|
|
|
testStringValue("1n");
|
|
|
|
}
|