blob: dc1089c3c71e8d26ed70553be502d8d25b0e9199 [file] [log] [blame]
// 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.
defines:
- testCoercibleToIndexZero
- testCoercibleToIndexOne
- testCoercibleToIndexFromIndex
- testCoercibleToIntegerZero
- testCoercibleToIntegerOne
- testCoercibleToNumberZero
- testCoercibleToNumberNan
- testCoercibleToNumberOne
- testCoercibleToIntegerFromInteger
- testPrimitiveWrappers
- testCoercibleToPrimitiveWithMethod
- testNotCoercibleToIndex
- testNotCoercibleToInteger
- testNotCoercibleToNumber
- testNotCoercibleToPrimitive
- testCoercibleToString
- testNotCoercibleToString
- testCoercibleToBooleanTrue
- testCoercibleToBooleanFalse
- testCoercibleToBigIntZero
- testCoercibleToBigIntOne
- testCoercibleToBigIntFromBigInt
- testNotCoercibleToBigInt
---*/
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);
}
function testCoercibleToIntegerZero(test) {
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) {
function testPrimitiveValue(value) {
test(value);
// ToPrimitive
testPrimitiveWrappers(value, "number", test);
}
testPrimitiveValue(null);
testPrimitiveValue(false);
testPrimitiveValue(0);
testPrimitiveValue("0");
}
function testCoercibleToNumberNan(test) {
function testPrimitiveValue(value) {
test(value);
// ToPrimitive
testPrimitiveWrappers(value, "number", test);
}
testPrimitiveValue(undefined);
testPrimitiveValue(NaN);
testPrimitiveValue("");
testPrimitiveValue("foo");
testPrimitiveValue("true");
}
function testCoercibleToNumberOne(test) {
function testPrimitiveValue(value) {
test(value);
// ToPrimitive
testPrimitiveWrappers(value, "number", test);
}
testPrimitiveValue(true);
testPrimitiveValue(1);
testPrimitiveValue("1");
}
function testCoercibleToIntegerFromInteger(nominalInteger, test) {
assert(Number.isInteger(nominalInteger));
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);
// ToInteger: floor(abs(number))
if (nominalInteger >= 0) {
testPrimitiveNumber(nominalInteger + 0.9);
}
if (nominalInteger <= 0) {
testPrimitiveNumber(nominalInteger - 0.9);
}
}
function testPrimitiveWrappers(primitiveValue, hint, test) {
if (primitiveValue != null) {
// null and undefined result in {} rather than a proper wrapper,
// so skip this case for those values.
test(Object(primitiveValue));
}
testCoercibleToPrimitiveWithMethod(hint, function() {
return primitiveValue;
}, test);
}
function testCoercibleToPrimitiveWithMethod(hint, method, test) {
var methodNames;
if (hint === "number") {
methodNames = ["valueOf", "toString"];
} else if (hint === "string") {
methodNames = ["toString", "valueOf"];
} else {
throw new Test262Error();
}
// 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(); },
});
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,
});
}
// 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,
});
}
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);
}
function testNotCoercibleToInteger(test) {
// ToInteger only throws from ToNumber.
testNotCoercibleToNumber(test);
}
function testNotCoercibleToNumber(test) {
function testPrimitiveValue(value) {
test(TypeError, value);
// ToPrimitive
testPrimitiveWrappers(value, "number", function(value) {
test(TypeError, value);
});
}
// ToNumber: Symbol -> TypeError
testPrimitiveValue(Symbol("1"));
if (typeof BigInt !== "undefined") {
// ToNumber: BigInt -> TypeError
testPrimitiveValue(BigInt(0));
}
// ToPrimitive
testNotCoercibleToPrimitive("number", test);
}
function testNotCoercibleToPrimitive(hint, test) {
function MyError() {}
// ToPrimitive: input[@@toPrimitive] is not callable (and non-null)
test(TypeError, {[Symbol.toPrimitive]: 1});
test(TypeError, {[Symbol.toPrimitive]: {}});
// ToPrimitive: input[@@toPrimitive] returns object
test(TypeError, {[Symbol.toPrimitive]: function() { return Object(1); }});
test(TypeError, {[Symbol.toPrimitive]: function() { return {}; }});
// ToPrimitive: input[@@toPrimitive] throws
test(MyError, {[Symbol.toPrimitive]: function() { throw new MyError(); }});
// OrdinaryToPrimitive: method throws
testCoercibleToPrimitiveWithMethod(hint, function() {
throw new MyError();
}, function(value) {
test(MyError, value);
});
// 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 {}; });
}
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);
}
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("");
}
function testCoercibleToBigIntZero(test) {
function testPrimitiveValue(value) {
test(value);
// ToPrimitive
testPrimitiveWrappers(value, "number", test);
}
testCoercibleToBigIntFromBigInt(BigInt(0), test);
testPrimitiveValue(-BigInt(0));
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);
}
testCoercibleToBigIntFromBigInt(BigInt(1), test);
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");
}