blob: a8af4b824ba72798caab343b4f7fdf8426893822 [file] [log] [blame]
// Copyright 2021 the V8 project authors. All rights reserved.
// Copyright 2021 Apple Inc. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
function shouldBe(actual, expected) {
if (actual !== expected)
throw new Error('bad value: ' + actual + " " + expected);
}
function shouldThrow(func, errorType) {
let error;
try {
func();
} catch (e) {
error = e;
}
if (!(error instanceof errorType))
throw new Error(`Expected ${errorType.name} but got ${String(error)}!`);
}
function shouldNotThrow(func) {
func();
}
const validRanges = [[-12345, -5678], [-12345, 56789], [12345, 56789]];
const nf = new Intl.NumberFormat("en", {signDisplay: "exceptZero"});
if (nf.formatRange || nf.formatRangeToParts) {
let methods = [];
if (nf.formatRange)
methods.push("formatRange");
if (nf.formatRangeToParts)
methods.push("formatRangeToParts");
methods.forEach(function(method) {
shouldBe("function", typeof nf[method]);
// 2. Perform ? RequireInternalSlot(nf, [[InitializedNumberFormat]]).
// Assert if called without nf
let f = nf[method];
shouldThrow(() => { f(1, 23) }, TypeError);
shouldBe(f.length, 2);
// Assert normal call success
shouldNotThrow(() => nf[method](1, 23));
// 3. If start is undefined ..., throw a TypeError exception.
shouldThrow(() => { nf[method](undefined, 23) }, TypeError);
// 3. If ... end is undefined, throw a TypeError exception.
shouldThrow(() => { nf[method](1, undefined) }, TypeError);
// 4. Let x be ? ToNumeric(start).
// Verify it won't throw error
shouldNotThrow(() => nf[method](null, 23));
shouldNotThrow(() => nf[method](false, 23));
shouldNotThrow(() => nf[method](true, 23));
shouldNotThrow(() => nf[method]("12", 23));
shouldNotThrow(() => nf[method]("-123456789012345678901234567890", 23));
shouldThrow(() => { nf[method](Symbol(12), 23) }, TypeError);
shouldNotThrow(() => nf[method](12, 23));
shouldNotThrow(() => nf[method](12n, 23));
shouldThrow(() => { nf[method]({}, -23) }, RangeError);
shouldNotThrow(() => { nf[method]([], 23) });
// 5. Let y be ? ToNumeric(end).
// Verify it won't throw error
shouldNotThrow(() => nf[method](-12, null));
shouldNotThrow(() => nf[method](-12, false));
shouldNotThrow(() => nf[method](-12, true));
shouldNotThrow(() => nf[method](12, "23"));
shouldNotThrow(() => nf[method](12, "23456789012345678901234567890"));
shouldThrow(() => { nf[method](12, Symbol(23)) }, TypeError);
shouldNotThrow(() => nf[method](12, 23));
shouldNotThrow(() => nf[method](12, 23n));
shouldThrow(() => { nf[method](-12, {}) }, RangeError);
shouldNotThrow(() => { nf[method](-12, []) });
// 6. If x is NaN ..., throw a RangeError exception.
shouldThrow(() => { nf[method](NaN, 23) }, RangeError);
shouldThrow(() => { nf[method]("NaN", 23) }, RangeError);
// 6. If ... y is NaN, throw a RangeError exception.
shouldThrow(() => { nf[method](12, NaN) }, RangeError);
shouldThrow(() => { nf[method](12, "NaN") }, RangeError);
shouldThrow(() => { nf[method](12, "xyz") }, RangeError);
// 7. If x is a non-finite Number ..., throw a RangeError exception.
shouldNotThrow(() => { nf[method](-12/0, 12/0) });
shouldThrow(() => { nf[method](12/0, -12/0) }, RangeError);
// 8. If x is greater than y, throw a RangeError exception.
// neither x nor y are bigint.
shouldThrow(() => { nf[method](23, 12) }, RangeError);
shouldNotThrow(() => nf[method](12, 23));
// x is not bigint but y is.
shouldThrow(() => { nf[method](23, 12n) }, RangeError);
shouldNotThrow(() => nf[method](12, 23n));
// x is bigint but y is not.
shouldThrow(() => { nf[method](23n, 12) }, RangeError);
shouldNotThrow(() => nf[method](12n, 23));
// both x and y are bigint.
shouldThrow(() => { nf[method](23n, 12n) }, RangeError);
shouldNotThrow(() => nf[method](12n, 23n));
validRanges.forEach(
function([x, y]) {
const X = BigInt(x);
const Y = BigInt(y);
const formatted_x_y = nf[method](x, y);
const formatted_X_y = nf[method](X, y);
const formatted_x_Y = nf[method](x, Y);
const formatted_X_Y = nf[method](X, Y);
shouldBe(JSON.stringify(formatted_x_y), JSON.stringify(formatted_X_y));
shouldBe(JSON.stringify(formatted_x_y), JSON.stringify(formatted_x_Y));
shouldBe(JSON.stringify(formatted_x_y), JSON.stringify(formatted_X_Y));
});
});
}
// Check the number of part with type: "plusSign" and "minusSign" are corre
if (nf.formatRangeToParts) {
validRanges.forEach(
function([x, y]) {
const expectedPlus = (x > 0) ? ((y > 0) ? 2 : 1) : ((y > 0) ? 1 : 0);
const expectedMinus = (x < 0) ? ((y < 0) ? 2 : 1) : ((y < 0) ? 1 : 0);
let actualPlus = 0;
let actualMinus = 0;
const parts = nf.formatRangeToParts(x, y);
parts.forEach(function(part) {
if (part.type == "plusSign") actualPlus++;
if (part.type == "minusSign") actualMinus++;
});
const method = "formatRangeToParts(" + x + ", " + y + "): ";
shouldBe(expectedPlus, actualPlus,
method + "Number of type: 'plusSign' in parts is incorrect");
shouldBe(expectedMinus, actualMinus,
method + "Number of type: 'minusSign' in parts is incorrect");
});
}
// From https://github.com/tc39/proposal-intl-numberformat-v3#formatrange-ecma-402-393
const nf2 = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "EUR",
maximumFractionDigits: 0,
});
// README.md said it expect "€3–5"
if (nf2.formatRange)
shouldBe("€3 – €5", nf2.formatRange(3, 5));
const nf3 = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "EUR",
maximumFractionDigits: 0,
});
if (nf3.formatRangeToParts) {
const actual3 = nf3.formatRangeToParts(3, 5);
/*
[
{type: "currency", value: "€", source: "startRange"}
{type: "integer", value: "3", source: "startRange"}
{type: "literal", value: "–", source: "shared"}
{type: "integer", value: "5", source: "endRange"}
]
*/
shouldBe(5, actual3.length);
shouldBe("currency", actual3[0].type);
shouldBe("€", actual3[0].value);
shouldBe("startRange", actual3[0].source);
shouldBe("integer", actual3[1].type);
shouldBe("3", actual3[1].value);
shouldBe("startRange", actual3[1].source);
shouldBe("literal", actual3[2].type);
shouldBe(" – ", actual3[2].value);
shouldBe("shared", actual3[2].source);
shouldBe("currency", actual3[3].type);
shouldBe("€", actual3[3].value);
shouldBe("endRange", actual3[3].source);
shouldBe("integer", actual3[4].type);
shouldBe("5", actual3[4].value);
shouldBe("endRange", actual3[4].source);
}
const nf4 = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "EUR",
maximumFractionDigits: 0,
});
if (nf4.formatRange)
shouldBe("~€3", nf4.formatRange(2.9, 3.1));
const nf5 = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "EUR",
signDisplay: "always",
});
if (nf5.formatRange)
shouldBe("~+€3.00", nf5.formatRange(2.999, 3.001));
const nf6 = new Intl.NumberFormat("en");
if (nf6.formatRange) {
shouldBe("3–∞", nf6.formatRange(3, 1/0));
shouldThrow(() => { nf6.formatRange(3, 0/0); }, RangeError);
}