mirror of
https://github.com/tc39/test262.git
synced 2025-04-08 19:35:28 +02:00
I made a mistake with one of the signs in one of the time zones that we use for verifying DST handling. Luckily this didn't affect any previously existing tests, but it affected some new tests that I'm going to add in the next commit. How do I know that _this_ arithmetic is correct? I feel reasonably confident with the added test.
1459 lines
55 KiB
JavaScript
1459 lines
55 KiB
JavaScript
// Copyright (C) 2021 Igalia, S.L. All rights reserved.
|
|
// This code is governed by the BSD license found in the LICENSE file.
|
|
/*---
|
|
description: |
|
|
This defines helper objects and functions for testing Temporal.
|
|
defines: [TemporalHelpers]
|
|
features: [Symbol.species, Symbol.iterator, Temporal]
|
|
---*/
|
|
|
|
var TemporalHelpers = {
|
|
/*
|
|
* assertDuration(duration, years, ..., nanoseconds[, description]):
|
|
*
|
|
* Shorthand for asserting that each field of a Temporal.Duration is equal to
|
|
* an expected value.
|
|
*/
|
|
assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
|
|
assert(duration instanceof Temporal.Duration, `${description} instanceof`);
|
|
assert.sameValue(duration.years, years, `${description} years result`);
|
|
assert.sameValue(duration.months, months, `${description} months result`);
|
|
assert.sameValue(duration.weeks, weeks, `${description} weeks result`);
|
|
assert.sameValue(duration.days, days, `${description} days result`);
|
|
assert.sameValue(duration.hours, hours, `${description} hours result`);
|
|
assert.sameValue(duration.minutes, minutes, `${description} minutes result`);
|
|
assert.sameValue(duration.seconds, seconds, `${description} seconds result`);
|
|
assert.sameValue(duration.milliseconds, milliseconds, `${description} milliseconds result`);
|
|
assert.sameValue(duration.microseconds, microseconds, `${description} microseconds result`);
|
|
assert.sameValue(duration.nanoseconds, nanoseconds, `${description} nanoseconds result`);
|
|
},
|
|
|
|
/*
|
|
* assertDurationsEqual(actual, expected[, description]):
|
|
*
|
|
* Shorthand for asserting that each field of a Temporal.Duration is equal to
|
|
* the corresponding field in another Temporal.Duration.
|
|
*/
|
|
assertDurationsEqual(actual, expected, description = "") {
|
|
assert(expected instanceof Temporal.Duration, `${description} expected value should be a Temporal.Duration`);
|
|
TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
|
|
},
|
|
|
|
/*
|
|
* assertInstantsEqual(actual, expected[, description]):
|
|
*
|
|
* Shorthand for asserting that two Temporal.Instants are of the correct type
|
|
* and equal according to their equals() methods.
|
|
*/
|
|
assertInstantsEqual(actual, expected, description = "") {
|
|
assert(expected instanceof Temporal.Instant, `${description} expected value should be a Temporal.Instant`);
|
|
assert(actual instanceof Temporal.Instant, `${description} instanceof`);
|
|
assert(actual.equals(expected), `${description} equals method`);
|
|
},
|
|
|
|
/*
|
|
* assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
|
|
*
|
|
* Shorthand for asserting that each field of a Temporal.PlainDate is equal to
|
|
* an expected value. (Except the `calendar` property, since callers may want
|
|
* to assert either object equality with an object they put in there, or the
|
|
* result of date.calendar.toString().)
|
|
*/
|
|
assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
|
|
assert(date instanceof Temporal.PlainDate, `${description} instanceof`);
|
|
assert.sameValue(date.era, era, `${description} era result`);
|
|
assert.sameValue(date.eraYear, eraYear, `${description} eraYear result`);
|
|
assert.sameValue(date.year, year, `${description} year result`);
|
|
assert.sameValue(date.month, month, `${description} month result`);
|
|
assert.sameValue(date.monthCode, monthCode, `${description} monthCode result`);
|
|
assert.sameValue(date.day, day, `${description} day result`);
|
|
},
|
|
|
|
/*
|
|
* assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
|
|
*
|
|
* Shorthand for asserting that each field of a Temporal.PlainDateTime is
|
|
* equal to an expected value. (Except the `calendar` property, since callers
|
|
* may want to assert either object equality with an object they put in there,
|
|
* or the result of datetime.calendar.toString().)
|
|
*/
|
|
assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
|
|
assert(datetime instanceof Temporal.PlainDateTime, `${description} instanceof`);
|
|
assert.sameValue(datetime.era, era, `${description} era result`);
|
|
assert.sameValue(datetime.eraYear, eraYear, `${description} eraYear result`);
|
|
assert.sameValue(datetime.year, year, `${description} year result`);
|
|
assert.sameValue(datetime.month, month, `${description} month result`);
|
|
assert.sameValue(datetime.monthCode, monthCode, `${description} monthCode result`);
|
|
assert.sameValue(datetime.day, day, `${description} day result`);
|
|
assert.sameValue(datetime.hour, hour, `${description} hour result`);
|
|
assert.sameValue(datetime.minute, minute, `${description} minute result`);
|
|
assert.sameValue(datetime.second, second, `${description} second result`);
|
|
assert.sameValue(datetime.millisecond, millisecond, `${description} millisecond result`);
|
|
assert.sameValue(datetime.microsecond, microsecond, `${description} microsecond result`);
|
|
assert.sameValue(datetime.nanosecond, nanosecond, `${description} nanosecond result`);
|
|
},
|
|
|
|
/*
|
|
* assertPlainDateTimesEqual(actual, expected[, description]):
|
|
*
|
|
* Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
|
|
* type, equal according to their equals() methods, and additionally that
|
|
* their calendars are the same value.
|
|
*/
|
|
assertPlainDateTimesEqual(actual, expected, description = "") {
|
|
assert(expected instanceof Temporal.PlainDateTime, `${description} expected value should be a Temporal.PlainDateTime`);
|
|
assert(actual instanceof Temporal.PlainDateTime, `${description} instanceof`);
|
|
assert(actual.equals(expected), `${description} equals method`);
|
|
assert.sameValue(actual.calendar, expected.calendar, `${description} calendar same value`);
|
|
},
|
|
|
|
/*
|
|
* assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
|
|
*
|
|
* Shorthand for asserting that each field of a Temporal.PlainMonthDay is
|
|
* equal to an expected value. (Except the `calendar` property, since callers
|
|
* may want to assert either object equality with an object they put in there,
|
|
* or the result of monthDay.calendar.toString().)
|
|
*/
|
|
assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
|
|
assert(monthDay instanceof Temporal.PlainMonthDay, `${description} instanceof`);
|
|
assert.sameValue(monthDay.monthCode, monthCode, `${description} monthCode result`);
|
|
assert.sameValue(monthDay.day, day, `${description} day result`);
|
|
assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${description} referenceISOYear result`);
|
|
},
|
|
|
|
/*
|
|
* assertPlainTime(time, hour, ..., nanosecond[, description]):
|
|
*
|
|
* Shorthand for asserting that each field of a Temporal.PlainTime is equal to
|
|
* an expected value.
|
|
*/
|
|
assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
|
|
assert(time instanceof Temporal.PlainTime, `${description} instanceof`);
|
|
assert.sameValue(time.hour, hour, `${description} hour result`);
|
|
assert.sameValue(time.minute, minute, `${description} minute result`);
|
|
assert.sameValue(time.second, second, `${description} second result`);
|
|
assert.sameValue(time.millisecond, millisecond, `${description} millisecond result`);
|
|
assert.sameValue(time.microsecond, microsecond, `${description} microsecond result`);
|
|
assert.sameValue(time.nanosecond, nanosecond, `${description} nanosecond result`);
|
|
},
|
|
|
|
/*
|
|
* assertPlainTimesEqual(actual, expected[, description]):
|
|
*
|
|
* Shorthand for asserting that two Temporal.PlainTimes are of the correct
|
|
* type and equal according to their equals() methods.
|
|
*/
|
|
assertPlainTimesEqual(actual, expected, description = "") {
|
|
assert(expected instanceof Temporal.PlainTime, `${description} expected value should be a Temporal.PlainTime`);
|
|
assert(actual instanceof Temporal.PlainTime, `${description} instanceof`);
|
|
assert(actual.equals(expected), `${description} equals method`);
|
|
},
|
|
|
|
/*
|
|
* assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear]]):
|
|
*
|
|
* Shorthand for asserting that each field of a Temporal.PlainYearMonth is
|
|
* equal to an expected value. (Except the `calendar` property, since callers
|
|
* may want to assert either object equality with an object they put in there,
|
|
* or the result of yearMonth.calendar.toString().)
|
|
*/
|
|
assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined) {
|
|
assert(yearMonth instanceof Temporal.PlainYearMonth, `${description} instanceof`);
|
|
assert.sameValue(yearMonth.era, era, `${description} era result`);
|
|
assert.sameValue(yearMonth.eraYear, eraYear, `${description} eraYear result`);
|
|
assert.sameValue(yearMonth.year, year, `${description} year result`);
|
|
assert.sameValue(yearMonth.month, month, `${description} month result`);
|
|
assert.sameValue(yearMonth.monthCode, monthCode, `${description} monthCode result`);
|
|
},
|
|
|
|
/*
|
|
* assertZonedDateTimesEqual(actual, expected[, description]):
|
|
*
|
|
* Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
|
|
* type, equal according to their equals() methods, and additionally that
|
|
* their time zones and calendars are the same value.
|
|
*/
|
|
assertZonedDateTimesEqual(actual, expected, description = "") {
|
|
assert(expected instanceof Temporal.ZonedDateTime, `${description} expected value should be a Temporal.ZonedDateTime`);
|
|
assert(actual instanceof Temporal.ZonedDateTime, `${description} instanceof`);
|
|
assert(actual.equals(expected), `${description} equals method`);
|
|
assert.sameValue(actual.timeZone, expected.timeZone, `${description} time zone same value`);
|
|
assert.sameValue(actual.calendar, expected.calendar, `${description} calendar same value`);
|
|
},
|
|
|
|
/*
|
|
* assertUnreachable(description):
|
|
*
|
|
* Helper for asserting that code is not executed. This is useful for
|
|
* assertions that methods of user calendars and time zones are not called.
|
|
*/
|
|
assertUnreachable(description) {
|
|
let message = "This code should not be executed";
|
|
if (description) {
|
|
message = `${message}: ${description}`;
|
|
}
|
|
throw new Test262Error(message);
|
|
},
|
|
|
|
/*
|
|
* checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
|
|
*
|
|
* When an options object with a largestUnit property is synthesized inside
|
|
* Temporal and passed to user code such as calendar.dateUntil(), the value of
|
|
* the largestUnit property should be in the singular form, even if the input
|
|
* was given in the plural form.
|
|
* (This doesn't apply when the options object is passed through verbatim.)
|
|
*
|
|
* func(calendar, largestUnit, index) is the operation under test. It's called
|
|
* with an instance of a calendar that keeps track of which largestUnit is
|
|
* passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
|
|
* the key's numerical index in case the function needs to generate test data
|
|
* based on the index. At the end, the actual values passed to dateUntil() are
|
|
* compared with the array values of expectedLargestUnitCalls.
|
|
*/
|
|
checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
|
|
const actual = [];
|
|
|
|
class DateUntilOptionsCalendar extends Temporal.Calendar {
|
|
constructor() {
|
|
super("iso8601");
|
|
}
|
|
|
|
dateUntil(earlier, later, options) {
|
|
actual.push(options.largestUnit);
|
|
return super.dateUntil(earlier, later, options);
|
|
}
|
|
|
|
toString() {
|
|
return "date-until-options";
|
|
}
|
|
}
|
|
|
|
const calendar = new DateUntilOptionsCalendar();
|
|
Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
|
|
func(calendar, largestUnit, index);
|
|
assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
|
|
actual.splice(0, actual.length); // empty it for the next check
|
|
});
|
|
},
|
|
|
|
/*
|
|
* checkFractionalSecondDigitsOptionWrongType(temporalObject):
|
|
*
|
|
* Checks the string-or-number type handling of the fractionalSecondDigits
|
|
* option to the various types' toString() methods. temporalObject is an
|
|
* instance of the Temporal type under test.
|
|
*/
|
|
checkFractionalSecondDigitsOptionWrongType(temporalObject) {
|
|
// null is not a number, and converts to the string "null", which is an invalid string value
|
|
assert.throws(RangeError, () => temporalObject.toString({ fractionalSecondDigits: null }), "null");
|
|
// Booleans are not numbers, and convert to the strings "true" or "false", which are invalid
|
|
assert.throws(RangeError, () => temporalObject.toString({ fractionalSecondDigits: true }), "true");
|
|
assert.throws(RangeError, () => temporalObject.toString({ fractionalSecondDigits: false }), "false");
|
|
// Symbols are not numbers and cannot convert to strings
|
|
assert.throws(TypeError, () => temporalObject.toString({ fractionalSecondDigits: Symbol() }), "symbol");
|
|
// BigInts are not numbers and convert to strings which are invalid
|
|
assert.throws(RangeError, () => temporalObject.toString({ fractionalSecondDigits: 2n }), "bigint");
|
|
|
|
// Objects are not numbers and prefer their toString() methods when converting to a string
|
|
assert.throws(RangeError, () => temporalObject.toString({ fractionalSecondDigits: {} }), "plain object");
|
|
|
|
const toStringExpected = temporalObject.toString({ fractionalSecondDigits: 'auto' });
|
|
const expected = [
|
|
"get fractionalSecondDigits.toString",
|
|
"call fractionalSecondDigits.toString",
|
|
];
|
|
const actual = [];
|
|
const observer = TemporalHelpers.toPrimitiveObserver(actual, "auto", "fractionalSecondDigits");
|
|
const result = temporalObject.toString({ fractionalSecondDigits: observer });
|
|
assert.sameValue(result, toStringExpected, "object with toString");
|
|
assert.compareArray(actual, expected, "order of operations");
|
|
},
|
|
|
|
/*
|
|
* checkPlainDateTimeConversionFastPath(func):
|
|
*
|
|
* ToTemporalDate and ToTemporalTime should both, if given a
|
|
* Temporal.PlainDateTime instance, convert to the desired type by reading the
|
|
* PlainDateTime's internal slots, rather than calling any getters.
|
|
*
|
|
* func(datetime, calendar) is the actual operation to test, that must
|
|
* internally call the abstract operation ToTemporalDate or ToTemporalTime.
|
|
* It is passed a Temporal.PlainDateTime instance, as well as the instance's
|
|
* calendar object (so that it doesn't have to call the calendar getter itself
|
|
* if it wants to make any assertions about the calendar.)
|
|
*/
|
|
checkPlainDateTimeConversionFastPath(func) {
|
|
const actual = [];
|
|
const expected = [];
|
|
|
|
const calendar = new Temporal.Calendar("iso8601");
|
|
const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
|
|
const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
|
|
["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
|
|
Object.defineProperty(datetime, property, {
|
|
get() {
|
|
actual.push(`get ${property}`);
|
|
const value = prototypeDescrs[property].get.call(this);
|
|
return {
|
|
toString() {
|
|
actual.push(`toString ${property}`);
|
|
return value.toString();
|
|
},
|
|
valueOf() {
|
|
actual.push(`valueOf ${property}`);
|
|
return value;
|
|
},
|
|
};
|
|
},
|
|
});
|
|
});
|
|
Object.defineProperty(datetime, "calendar", {
|
|
get() {
|
|
actual.push("get calendar");
|
|
return calendar;
|
|
},
|
|
});
|
|
|
|
func(datetime, calendar);
|
|
assert.compareArray(actual, expected, "property getters not called");
|
|
},
|
|
|
|
/*
|
|
* Check that an options bag that accepts units written in the singular form,
|
|
* also accepts the same units written in the plural form.
|
|
* func(unit) should call the method with the appropriate options bag
|
|
* containing unit as a value. This will be called twice for each element of
|
|
* validSingularUnits, once with singular and once with plural, and the
|
|
* results of each pair should be the same (whether a Temporal object or a
|
|
* primitive value.)
|
|
*/
|
|
checkPluralUnitsAccepted(func, validSingularUnits) {
|
|
const plurals = {
|
|
year: 'years',
|
|
month: 'months',
|
|
week: 'weeks',
|
|
day: 'days',
|
|
hour: 'hours',
|
|
minute: 'minutes',
|
|
second: 'seconds',
|
|
millisecond: 'milliseconds',
|
|
microsecond: 'microseconds',
|
|
nanosecond: 'nanoseconds',
|
|
};
|
|
|
|
validSingularUnits.forEach((unit) => {
|
|
const singularValue = func(unit);
|
|
const pluralValue = func(plurals[unit]);
|
|
const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
|
|
if (singularValue instanceof Temporal.Duration) {
|
|
TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
|
|
} else if (singularValue instanceof Temporal.Instant) {
|
|
TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
|
|
} else if (singularValue instanceof Temporal.PlainDateTime) {
|
|
TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
|
|
} else if (singularValue instanceof Temporal.PlainTime) {
|
|
TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
|
|
} else if (singularValue instanceof Temporal.ZonedDateTime) {
|
|
TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
|
|
} else {
|
|
assert.sameValue(pluralValue, singularValue);
|
|
}
|
|
});
|
|
},
|
|
|
|
/*
|
|
* checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
|
|
*
|
|
* Checks the type handling of the roundingIncrement option.
|
|
* checkFunc(roundingIncrement) is a function which takes the value of
|
|
* roundingIncrement to test, and calls the method under test with it,
|
|
* returning the result. assertTrueResultFunc(result, description) should
|
|
* assert that result is the expected result with roundingIncrement: true, and
|
|
* assertObjectResultFunc(result, description) should assert that result is
|
|
* the expected result with roundingIncrement being an object with a valueOf()
|
|
* method.
|
|
*/
|
|
checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
|
|
// null converts to 0, which is out of range
|
|
assert.throws(RangeError, () => checkFunc(null), "null");
|
|
// Booleans convert to either 0 or 1, and 1 is allowed
|
|
const trueResult = checkFunc(true);
|
|
assertTrueResultFunc(trueResult, "true");
|
|
assert.throws(RangeError, () => checkFunc(false), "false");
|
|
// Symbols and BigInts cannot convert to numbers
|
|
assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
|
|
assert.throws(TypeError, () => checkFunc(2n), "bigint");
|
|
|
|
// Objects prefer their valueOf() methods when converting to a number
|
|
assert.throws(RangeError, () => checkFunc({}), "plain object");
|
|
|
|
const expected = [
|
|
"get roundingIncrement.valueOf",
|
|
"call roundingIncrement.valueOf",
|
|
];
|
|
const actual = [];
|
|
const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
|
|
const objectResult = checkFunc(observer);
|
|
assertObjectResultFunc(objectResult, "object with valueOf");
|
|
assert.compareArray(actual, expected, "order of operations");
|
|
},
|
|
|
|
/*
|
|
* checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
|
|
*
|
|
* Checks the type handling of a string option, of which there are several in
|
|
* Temporal.
|
|
* propertyName is the name of the option, and value is the value that
|
|
* assertFunc should expect it to have.
|
|
* checkFunc(value) is a function which takes the value of the option to test,
|
|
* and calls the method under test with it, returning the result.
|
|
* assertFunc(result, description) should assert that result is the expected
|
|
* result with the option value being an object with a toString() method
|
|
* which returns the given value.
|
|
*/
|
|
checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
|
|
// null converts to the string "null", which is an invalid string value
|
|
assert.throws(RangeError, () => checkFunc(null), "null");
|
|
// Booleans convert to the strings "true" or "false", which are invalid
|
|
assert.throws(RangeError, () => checkFunc(true), "true");
|
|
assert.throws(RangeError, () => checkFunc(false), "false");
|
|
// Symbols cannot convert to strings
|
|
assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
|
|
// Numbers convert to strings which are invalid
|
|
assert.throws(RangeError, () => checkFunc(2), "number");
|
|
// BigInts convert to strings which are invalid
|
|
assert.throws(RangeError, () => checkFunc(2n), "bigint");
|
|
|
|
// Objects prefer their toString() methods when converting to a string
|
|
assert.throws(RangeError, () => checkFunc({}), "plain object");
|
|
|
|
const expected = [
|
|
`get ${propertyName}.toString`,
|
|
`call ${propertyName}.toString`,
|
|
];
|
|
const actual = [];
|
|
const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
|
|
const result = checkFunc(observer);
|
|
assertFunc(result, "object with toString");
|
|
assert.compareArray(actual, expected, "order of operations");
|
|
},
|
|
|
|
/*
|
|
* checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
|
|
* resultAssertions):
|
|
*
|
|
* Methods of Temporal classes that return a new instance of the same class,
|
|
* must not take the constructor of a subclass into account, nor the @@species
|
|
* property. This helper runs tests to ensure this.
|
|
*
|
|
* construct(...constructArgs) must yield a valid instance of the Temporal
|
|
* class. instance[method](...methodArgs) is the method call under test, which
|
|
* must also yield a valid instance of the same Temporal class, not a
|
|
* subclass. See below for the individual tests that this runs.
|
|
* resultAssertions() is a function that performs additional assertions on the
|
|
* instance returned by the method under test.
|
|
*/
|
|
checkSubclassingIgnored(...args) {
|
|
this.checkSubclassConstructorNotObject(...args);
|
|
this.checkSubclassConstructorUndefined(...args);
|
|
this.checkSubclassConstructorThrows(...args);
|
|
this.checkSubclassConstructorNotCalled(...args);
|
|
this.checkSubclassSpeciesInvalidResult(...args);
|
|
this.checkSubclassSpeciesNotAConstructor(...args);
|
|
this.checkSubclassSpeciesNull(...args);
|
|
this.checkSubclassSpeciesUndefined(...args);
|
|
this.checkSubclassSpeciesThrows(...args);
|
|
},
|
|
|
|
/*
|
|
* Checks that replacing the 'constructor' property of the instance with
|
|
* various primitive values does not affect the returned new instance.
|
|
*/
|
|
checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
|
|
function check(value, description) {
|
|
const instance = new construct(...constructArgs);
|
|
instance.constructor = value;
|
|
const result = instance[method](...methodArgs);
|
|
assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
|
|
resultAssertions(result);
|
|
}
|
|
|
|
check(null, "null");
|
|
check(true, "true");
|
|
check("test", "string");
|
|
check(Symbol(), "Symbol");
|
|
check(7, "number");
|
|
check(7n, "bigint");
|
|
},
|
|
|
|
/*
|
|
* Checks that replacing the 'constructor' property of the subclass with
|
|
* undefined does not affect the returned new instance.
|
|
*/
|
|
checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
|
|
let called = 0;
|
|
|
|
class MySubclass extends construct {
|
|
constructor() {
|
|
++called;
|
|
super(...constructArgs);
|
|
}
|
|
}
|
|
|
|
const instance = new MySubclass();
|
|
assert.sameValue(called, 1);
|
|
|
|
MySubclass.prototype.constructor = undefined;
|
|
|
|
const result = instance[method](...methodArgs);
|
|
assert.sameValue(called, 1);
|
|
assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
|
|
resultAssertions(result);
|
|
},
|
|
|
|
/*
|
|
* Checks that making the 'constructor' property of the instance throw when
|
|
* called does not affect the returned new instance.
|
|
*/
|
|
checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
|
|
function CustomError() {}
|
|
const instance = new construct(...constructArgs);
|
|
Object.defineProperty(instance, "constructor", {
|
|
get() {
|
|
throw new CustomError();
|
|
}
|
|
});
|
|
const result = instance[method](...methodArgs);
|
|
assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
|
|
resultAssertions(result);
|
|
},
|
|
|
|
/*
|
|
* Checks that when subclassing, the subclass constructor is not called by
|
|
* the method under test.
|
|
*/
|
|
checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
|
|
let called = 0;
|
|
|
|
class MySubclass extends construct {
|
|
constructor() {
|
|
++called;
|
|
super(...constructArgs);
|
|
}
|
|
}
|
|
|
|
const instance = new MySubclass();
|
|
assert.sameValue(called, 1);
|
|
|
|
const result = instance[method](...methodArgs);
|
|
assert.sameValue(called, 1);
|
|
assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
|
|
resultAssertions(result);
|
|
},
|
|
|
|
/*
|
|
* Check that the constructor's @@species property is ignored when it's a
|
|
* constructor that returns a non-object value.
|
|
*/
|
|
checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
|
|
function check(value, description) {
|
|
const instance = new construct(...constructArgs);
|
|
instance.constructor = {
|
|
[Symbol.species]: function() {
|
|
return value;
|
|
},
|
|
};
|
|
const result = instance[method](...methodArgs);
|
|
assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
|
|
resultAssertions(result);
|
|
}
|
|
|
|
check(undefined, "undefined");
|
|
check(null, "null");
|
|
check(true, "true");
|
|
check("test", "string");
|
|
check(Symbol(), "Symbol");
|
|
check(7, "number");
|
|
check(7n, "bigint");
|
|
check({}, "plain object");
|
|
},
|
|
|
|
/*
|
|
* Check that the constructor's @@species property is ignored when it's not a
|
|
* constructor.
|
|
*/
|
|
checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
|
|
function check(value, description) {
|
|
const instance = new construct(...constructArgs);
|
|
instance.constructor = {
|
|
[Symbol.species]: value,
|
|
};
|
|
const result = instance[method](...methodArgs);
|
|
assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
|
|
resultAssertions(result);
|
|
}
|
|
|
|
check(true, "true");
|
|
check("test", "string");
|
|
check(Symbol(), "Symbol");
|
|
check(7, "number");
|
|
check(7n, "bigint");
|
|
check({}, "plain object");
|
|
},
|
|
|
|
/*
|
|
* Check that the constructor's @@species property is ignored when it's null.
|
|
*/
|
|
checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
|
|
let called = 0;
|
|
|
|
class MySubclass extends construct {
|
|
constructor() {
|
|
++called;
|
|
super(...constructArgs);
|
|
}
|
|
}
|
|
|
|
const instance = new MySubclass();
|
|
assert.sameValue(called, 1);
|
|
|
|
MySubclass.prototype.constructor = {
|
|
[Symbol.species]: null,
|
|
};
|
|
|
|
const result = instance[method](...methodArgs);
|
|
assert.sameValue(called, 1);
|
|
assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
|
|
resultAssertions(result);
|
|
},
|
|
|
|
/*
|
|
* Check that the constructor's @@species property is ignored when it's
|
|
* undefined.
|
|
*/
|
|
checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
|
|
let called = 0;
|
|
|
|
class MySubclass extends construct {
|
|
constructor() {
|
|
++called;
|
|
super(...constructArgs);
|
|
}
|
|
}
|
|
|
|
const instance = new MySubclass();
|
|
assert.sameValue(called, 1);
|
|
|
|
MySubclass.prototype.constructor = {
|
|
[Symbol.species]: undefined,
|
|
};
|
|
|
|
const result = instance[method](...methodArgs);
|
|
assert.sameValue(called, 1);
|
|
assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
|
|
resultAssertions(result);
|
|
},
|
|
|
|
/*
|
|
* Check that the constructor's @@species property is ignored when it throws,
|
|
* i.e. it is not called at all.
|
|
*/
|
|
checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
|
|
function CustomError() {}
|
|
|
|
const instance = new construct(...constructArgs);
|
|
instance.constructor = {
|
|
get [Symbol.species]() {
|
|
throw new CustomError();
|
|
},
|
|
};
|
|
|
|
const result = instance[method](...methodArgs);
|
|
assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
|
|
},
|
|
|
|
/*
|
|
* checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
|
|
*
|
|
* Static methods of Temporal classes that return a new instance of the class,
|
|
* must not use the this-value as a constructor. This helper runs tests to
|
|
* ensure this.
|
|
*
|
|
* construct[method](...methodArgs) is the static method call under test, and
|
|
* must yield a valid instance of the Temporal class, not a subclass. See
|
|
* below for the individual tests that this runs.
|
|
* resultAssertions() is a function that performs additional assertions on the
|
|
* instance returned by the method under test.
|
|
*/
|
|
checkSubclassingIgnoredStatic(...args) {
|
|
this.checkStaticInvalidReceiver(...args);
|
|
this.checkStaticReceiverNotCalled(...args);
|
|
this.checkThisValueNotCalled(...args);
|
|
},
|
|
|
|
/*
|
|
* Check that calling the static method with a receiver that's not callable,
|
|
* still calls the intrinsic constructor.
|
|
*/
|
|
checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
|
|
function check(value, description) {
|
|
const result = construct[method].apply(value, methodArgs);
|
|
assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
|
|
resultAssertions(result);
|
|
}
|
|
|
|
check(undefined, "undefined");
|
|
check(null, "null");
|
|
check(true, "true");
|
|
check("test", "string");
|
|
check(Symbol(), "symbol");
|
|
check(7, "number");
|
|
check(7n, "bigint");
|
|
check({}, "Non-callable object");
|
|
},
|
|
|
|
/*
|
|
* Check that calling the static method with a receiver that returns a value
|
|
* that's not callable, still calls the intrinsic constructor.
|
|
*/
|
|
checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
|
|
function check(value, description) {
|
|
const receiver = function () {
|
|
return value;
|
|
};
|
|
const result = construct[method].apply(receiver, methodArgs);
|
|
assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
|
|
resultAssertions(result);
|
|
}
|
|
|
|
check(undefined, "undefined");
|
|
check(null, "null");
|
|
check(true, "true");
|
|
check("test", "string");
|
|
check(Symbol(), "symbol");
|
|
check(7, "number");
|
|
check(7n, "bigint");
|
|
check({}, "Non-callable object");
|
|
},
|
|
|
|
/*
|
|
* Check that the receiver isn't called.
|
|
*/
|
|
checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
|
|
let called = false;
|
|
|
|
class MySubclass extends construct {
|
|
constructor(...args) {
|
|
called = true;
|
|
super(...args);
|
|
}
|
|
}
|
|
|
|
const result = MySubclass[method](...methodArgs);
|
|
assert.sameValue(called, false);
|
|
assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
|
|
resultAssertions(result);
|
|
},
|
|
|
|
/*
|
|
* Check that any iterable returned from a custom time zone's
|
|
* getPossibleInstantsFor() method is exhausted.
|
|
* The custom time zone object is passed in to func().
|
|
* expected is an array of strings representing the expected calls to the
|
|
* getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
|
|
* are compared (using their toString() results) with the array.
|
|
*/
|
|
checkTimeZonePossibleInstantsIterable(func, expected) {
|
|
// A custom time zone that returns an iterable instead of an array from its
|
|
// getPossibleInstantsFor() method, and for testing purposes skips
|
|
// 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
|
|
// January 3, 2030. Otherwise identical to the UTC time zone.
|
|
class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
|
|
constructor() {
|
|
super("UTC");
|
|
this.getPossibleInstantsForCallCount = 0;
|
|
this.getPossibleInstantsForCalledWith = [];
|
|
this.getPossibleInstantsForReturns = [];
|
|
this.iteratorExhausted = [];
|
|
}
|
|
|
|
toString() {
|
|
return "Custom/Iterable";
|
|
}
|
|
|
|
getOffsetNanosecondsFor(instant) {
|
|
if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
|
|
Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
|
|
return 3600_000_000_000;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
getPossibleInstantsFor(dateTime) {
|
|
this.getPossibleInstantsForCallCount++;
|
|
this.getPossibleInstantsForCalledWith.push(dateTime);
|
|
|
|
// Fake DST transition
|
|
let retval = super.getPossibleInstantsFor(dateTime);
|
|
if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
|
|
retval = [];
|
|
} else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
|
|
retval.push(retval[0].subtract({ hours: 1 }));
|
|
} else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
|
|
retval[0] = retval[0].subtract({ hours: 1 });
|
|
}
|
|
|
|
this.getPossibleInstantsForReturns.push(retval);
|
|
this.iteratorExhausted.push(false);
|
|
return {
|
|
callIndex: this.getPossibleInstantsForCallCount - 1,
|
|
timeZone: this,
|
|
*[Symbol.iterator]() {
|
|
yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
|
|
this.timeZone.iteratorExhausted[this.callIndex] = true;
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
const timeZone = new TimeZonePossibleInstantsIterable();
|
|
func(timeZone);
|
|
|
|
assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
|
|
|
|
for (let index = 0; index < expected.length; index++) {
|
|
assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
|
|
assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
|
|
}
|
|
},
|
|
|
|
/*
|
|
* Check that any calendar-carrying Temporal object has its [[Calendar]]
|
|
* internal slot read by ToTemporalCalendar, and does not fetch the calendar
|
|
* by calling getters.
|
|
* The custom calendar object is passed in to func() so that it can do its
|
|
* own additional assertions involving the calendar if necessary. (Sometimes
|
|
* there is nothing to assert as the calendar isn't stored anywhere that can
|
|
* be asserted about.)
|
|
*/
|
|
checkToTemporalCalendarFastPath(func) {
|
|
class CalendarFastPathCheck extends Temporal.Calendar {
|
|
constructor() {
|
|
super("iso8601");
|
|
}
|
|
|
|
toString() {
|
|
return "fast-path-check";
|
|
}
|
|
}
|
|
const calendar = new CalendarFastPathCheck();
|
|
|
|
const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
|
|
const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
|
|
const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
|
|
const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
|
|
const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
|
|
|
|
[plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
|
|
const actual = [];
|
|
const expected = [];
|
|
|
|
Object.defineProperty(temporalObject, "calendar", {
|
|
get() {
|
|
actual.push("get calendar");
|
|
return calendar;
|
|
},
|
|
});
|
|
|
|
func(temporalObject, calendar);
|
|
assert.compareArray(actual, expected, "calendar getter not called");
|
|
});
|
|
},
|
|
|
|
checkToTemporalInstantFastPath(func) {
|
|
const actual = [];
|
|
const expected = [];
|
|
|
|
const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
|
|
Object.defineProperty(datetime, 'toString', {
|
|
get() {
|
|
actual.push("get toString");
|
|
return function (options) {
|
|
actual.push("call toString");
|
|
return Temporal.ZonedDateTime.prototype.toString.call(this, options);
|
|
};
|
|
},
|
|
});
|
|
|
|
func(datetime);
|
|
assert.compareArray(actual, expected, "toString not called");
|
|
},
|
|
|
|
checkToTemporalPlainDateTimeFastPath(func) {
|
|
const actual = [];
|
|
const expected = [];
|
|
|
|
const calendar = new Temporal.Calendar("iso8601");
|
|
const date = new Temporal.PlainDate(2000, 5, 2, calendar);
|
|
const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
|
|
["year", "month", "monthCode", "day"].forEach((property) => {
|
|
Object.defineProperty(date, property, {
|
|
get() {
|
|
actual.push(`get ${property}`);
|
|
const value = prototypeDescrs[property].get.call(this);
|
|
return TemporalHelpers.toPrimitiveObserver(actual, value, property);
|
|
},
|
|
});
|
|
});
|
|
["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
|
|
Object.defineProperty(date, property, {
|
|
get() {
|
|
actual.push(`get ${property}`);
|
|
return undefined;
|
|
},
|
|
});
|
|
});
|
|
Object.defineProperty(date, "calendar", {
|
|
get() {
|
|
actual.push("get calendar");
|
|
return calendar;
|
|
},
|
|
});
|
|
|
|
func(date, calendar);
|
|
assert.compareArray(actual, expected, "property getters not called");
|
|
},
|
|
|
|
/*
|
|
* A custom calendar that asserts its dateAdd() method is called with the
|
|
* options parameter having the value undefined.
|
|
*/
|
|
calendarDateAddUndefinedOptions() {
|
|
class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
|
|
constructor() {
|
|
super("iso8601");
|
|
this.dateAddCallCount = 0;
|
|
}
|
|
|
|
toString() {
|
|
return "dateadd-undef-options";
|
|
}
|
|
|
|
dateAdd(date, duration, options) {
|
|
this.dateAddCallCount++;
|
|
assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
|
|
return super.dateAdd(date, duration, options);
|
|
}
|
|
}
|
|
return new CalendarDateAddUndefinedOptions();
|
|
},
|
|
|
|
/*
|
|
* A custom calendar that asserts its dateAdd() method is called with a
|
|
* PlainDate instance. Optionally, it also asserts that the PlainDate instance
|
|
* is the specific object `this.specificPlainDate`, if it is set by the
|
|
* calling code.
|
|
*/
|
|
calendarDateAddPlainDateInstance() {
|
|
class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
|
|
constructor() {
|
|
super("iso8601");
|
|
this.dateAddCallCount = 0;
|
|
this.specificPlainDate = undefined;
|
|
}
|
|
|
|
toString() {
|
|
return "dateadd-plain-date-instance";
|
|
}
|
|
|
|
dateAdd(date, duration, options) {
|
|
this.dateAddCallCount++;
|
|
assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
|
|
if (this.dateAddCallCount === 1 && this.specificPlainDate) {
|
|
assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
|
|
}
|
|
return super.dateAdd(date, duration, options);
|
|
}
|
|
}
|
|
return new CalendarDateAddPlainDateInstance();
|
|
},
|
|
|
|
/*
|
|
* A custom calendar that returns @returnValue from its dateUntil() method,
|
|
* recording the call in @calls.
|
|
*/
|
|
calendarDateUntilObservable(calls, returnValue) {
|
|
class CalendarDateUntilObservable extends Temporal.Calendar {
|
|
constructor() {
|
|
super("iso8601");
|
|
}
|
|
|
|
dateUntil() {
|
|
calls.push("call dateUntil");
|
|
return returnValue;
|
|
}
|
|
}
|
|
|
|
return new CalendarDateUntilObservable();
|
|
},
|
|
|
|
/*
|
|
* A custom calendar that returns an iterable instead of an array from its
|
|
* fields() method, otherwise identical to the ISO calendar.
|
|
*/
|
|
calendarFieldsIterable() {
|
|
class CalendarFieldsIterable extends Temporal.Calendar {
|
|
constructor() {
|
|
super("iso8601");
|
|
this.fieldsCallCount = 0;
|
|
this.fieldsCalledWith = [];
|
|
this.iteratorExhausted = [];
|
|
}
|
|
|
|
toString() {
|
|
return "fields-iterable";
|
|
}
|
|
|
|
fields(fieldNames) {
|
|
this.fieldsCallCount++;
|
|
this.fieldsCalledWith.push(fieldNames.slice());
|
|
this.iteratorExhausted.push(false);
|
|
return {
|
|
callIndex: this.fieldsCallCount - 1,
|
|
calendar: this,
|
|
*[Symbol.iterator]() {
|
|
yield* this.calendar.fieldsCalledWith[this.callIndex];
|
|
this.calendar.iteratorExhausted[this.callIndex] = true;
|
|
},
|
|
};
|
|
}
|
|
}
|
|
return new CalendarFieldsIterable();
|
|
},
|
|
|
|
/*
|
|
* A custom calendar that modifies the fields object passed in to
|
|
* dateFromFields, sabotaging its time properties.
|
|
*/
|
|
calendarMakeInfinityTime() {
|
|
class CalendarMakeInfinityTime extends Temporal.Calendar {
|
|
constructor() {
|
|
super("iso8601");
|
|
}
|
|
|
|
dateFromFields(fields, options) {
|
|
const retval = super.dateFromFields(fields, options);
|
|
fields.hour = Infinity;
|
|
fields.minute = Infinity;
|
|
fields.second = Infinity;
|
|
fields.millisecond = Infinity;
|
|
fields.microsecond = Infinity;
|
|
fields.nanosecond = Infinity;
|
|
return retval;
|
|
}
|
|
}
|
|
return new CalendarMakeInfinityTime();
|
|
},
|
|
|
|
/*
|
|
* A custom calendar that defines getters on the fields object passed into
|
|
* dateFromFields that throw, sabotaging its time properties.
|
|
*/
|
|
calendarMakeInvalidGettersTime() {
|
|
class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
|
|
constructor() {
|
|
super("iso8601");
|
|
}
|
|
|
|
dateFromFields(fields, options) {
|
|
const retval = super.dateFromFields(fields, options);
|
|
const throwingDescriptor = {
|
|
get() {
|
|
throw new Test262Error("reading a sabotaged time field");
|
|
},
|
|
};
|
|
Object.defineProperties(fields, {
|
|
hour: throwingDescriptor,
|
|
minute: throwingDescriptor,
|
|
second: throwingDescriptor,
|
|
millisecond: throwingDescriptor,
|
|
microsecond: throwingDescriptor,
|
|
nanosecond: throwingDescriptor,
|
|
});
|
|
return retval;
|
|
}
|
|
}
|
|
return new CalendarMakeInvalidGettersTime();
|
|
},
|
|
|
|
/*
|
|
* A custom calendar whose mergeFields() method returns a proxy object with
|
|
* all of its Get and HasProperty operations observable, as well as adding a
|
|
* "shouldNotBeCopied": true property.
|
|
*/
|
|
calendarMergeFieldsGetters() {
|
|
class CalendarMergeFieldsGetters extends Temporal.Calendar {
|
|
constructor() {
|
|
super("iso8601");
|
|
this.mergeFieldsReturnOperations = [];
|
|
}
|
|
|
|
toString() {
|
|
return "merge-fields-getters";
|
|
}
|
|
|
|
dateFromFields(fields, options) {
|
|
assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
|
|
return super.dateFromFields(fields, options);
|
|
}
|
|
|
|
yearMonthFromFields(fields, options) {
|
|
assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
|
|
return super.yearMonthFromFields(fields, options);
|
|
}
|
|
|
|
monthDayFromFields(fields, options) {
|
|
assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
|
|
return super.monthDayFromFields(fields, options);
|
|
}
|
|
|
|
mergeFields(fields, additionalFields) {
|
|
const retval = super.mergeFields(fields, additionalFields);
|
|
retval._calendar = this;
|
|
retval.shouldNotBeCopied = true;
|
|
return new Proxy(retval, {
|
|
get(target, key) {
|
|
target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
|
|
const result = target[key];
|
|
if (result === undefined) {
|
|
return undefined;
|
|
}
|
|
return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
|
|
},
|
|
has(target, key) {
|
|
target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
|
|
return key in target;
|
|
},
|
|
});
|
|
}
|
|
}
|
|
return new CalendarMergeFieldsGetters();
|
|
},
|
|
|
|
/*
|
|
* A custom calendar whose mergeFields() method returns a primitive value,
|
|
* given by @primitive, and which records the number of calls made to its
|
|
* dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
|
|
*/
|
|
calendarMergeFieldsReturnsPrimitive(primitive) {
|
|
class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
|
|
constructor(mergeFieldsReturnValue) {
|
|
super("iso8601");
|
|
this._mergeFieldsReturnValue = mergeFieldsReturnValue;
|
|
this.dateFromFieldsCallCount = 0;
|
|
this.monthDayFromFieldsCallCount = 0;
|
|
this.yearMonthFromFieldsCallCount = 0;
|
|
}
|
|
|
|
toString() {
|
|
return "merge-fields-primitive";
|
|
}
|
|
|
|
dateFromFields(fields, options) {
|
|
this.dateFromFieldsCallCount++;
|
|
return super.dateFromFields(fields, options);
|
|
}
|
|
|
|
yearMonthFromFields(fields, options) {
|
|
this.yearMonthFromFieldsCallCount++;
|
|
return super.yearMonthFromFields(fields, options);
|
|
}
|
|
|
|
monthDayFromFields(fields, options) {
|
|
this.monthDayFromFieldsCallCount++;
|
|
return super.monthDayFromFields(fields, options);
|
|
}
|
|
|
|
mergeFields() {
|
|
return this._mergeFieldsReturnValue;
|
|
}
|
|
}
|
|
return new CalendarMergeFieldsPrimitive(primitive);
|
|
},
|
|
|
|
/*
|
|
* observeProperty(calls, object, propertyName, value):
|
|
*
|
|
* Defines an own property @object.@propertyName with value @value, that
|
|
* will log any calls to its accessors to the array @calls.
|
|
*/
|
|
observeProperty(calls, object, propertyName, value) {
|
|
let displayName = propertyName;
|
|
if (typeof propertyName === 'symbol') {
|
|
if (Symbol.keyFor(propertyName) !== undefined) {
|
|
displayName = `[Symbol.for('${Symbol.keyFor(propertyName)}')]`;
|
|
} else if (propertyName.description.startsWith('Symbol.')) {
|
|
displayName = `[${propertyName.description}]`;
|
|
} else {
|
|
displayName = `[Symbol('${propertyName.description}')]`
|
|
}
|
|
}
|
|
Object.defineProperty(object, propertyName, {
|
|
get() {
|
|
calls.push(`get ${displayName}`);
|
|
return value;
|
|
},
|
|
set(v) {
|
|
calls.push(`set ${displayName}`);
|
|
}
|
|
});
|
|
},
|
|
|
|
/*
|
|
* A custom calendar that does not allow any of its methods to be called, for
|
|
* the purpose of asserting that a particular operation does not call into
|
|
* user code.
|
|
*/
|
|
calendarThrowEverything() {
|
|
class CalendarThrowEverything extends Temporal.Calendar {
|
|
constructor() {
|
|
super("iso8601");
|
|
}
|
|
toString() {
|
|
TemporalHelpers.assertUnreachable("toString should not be called");
|
|
}
|
|
dateFromFields() {
|
|
TemporalHelpers.assertUnreachable("dateFromFields should not be called");
|
|
}
|
|
yearMonthFromFields() {
|
|
TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
|
|
}
|
|
monthDayFromFields() {
|
|
TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
|
|
}
|
|
dateAdd() {
|
|
TemporalHelpers.assertUnreachable("dateAdd should not be called");
|
|
}
|
|
dateUntil() {
|
|
TemporalHelpers.assertUnreachable("dateUntil should not be called");
|
|
}
|
|
era() {
|
|
TemporalHelpers.assertUnreachable("era should not be called");
|
|
}
|
|
eraYear() {
|
|
TemporalHelpers.assertUnreachable("eraYear should not be called");
|
|
}
|
|
year() {
|
|
TemporalHelpers.assertUnreachable("year should not be called");
|
|
}
|
|
month() {
|
|
TemporalHelpers.assertUnreachable("month should not be called");
|
|
}
|
|
monthCode() {
|
|
TemporalHelpers.assertUnreachable("monthCode should not be called");
|
|
}
|
|
day() {
|
|
TemporalHelpers.assertUnreachable("day should not be called");
|
|
}
|
|
fields() {
|
|
TemporalHelpers.assertUnreachable("fields should not be called");
|
|
}
|
|
mergeFields() {
|
|
TemporalHelpers.assertUnreachable("mergeFields should not be called");
|
|
}
|
|
}
|
|
|
|
return new CalendarThrowEverything();
|
|
},
|
|
|
|
/*
|
|
* oneShiftTimeZone(shiftInstant, shiftNanoseconds):
|
|
*
|
|
* In the case of a spring-forward time zone offset transition (skipped time),
|
|
* and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
|
|
* negative number of nanoseconds from a PlainDateTime, which should balance
|
|
* with the microseconds field.
|
|
*
|
|
* This returns an instance of a custom time zone class which skips a length
|
|
* of time equal to shiftNanoseconds (a number), at the Temporal.Instant
|
|
* shiftInstant. Before shiftInstant, it's identical to UTC, and after
|
|
* shiftInstant it's a constant-offset time zone.
|
|
*
|
|
* It provides a getPossibleInstantsForCalledWith member which is an array
|
|
* with the result of calling toString() on any PlainDateTimes passed to
|
|
* getPossibleInstantsFor().
|
|
*/
|
|
oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
|
|
class OneShiftTimeZone extends Temporal.TimeZone {
|
|
constructor(shiftInstant, shiftNanoseconds) {
|
|
super("+00:00");
|
|
this._shiftInstant = shiftInstant;
|
|
this._epoch1 = shiftInstant.epochNanoseconds;
|
|
this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
|
|
this._shiftNanoseconds = shiftNanoseconds;
|
|
this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
|
|
this.getPossibleInstantsForCalledWith = [];
|
|
}
|
|
|
|
_isBeforeShift(instant) {
|
|
return instant.epochNanoseconds < this._epoch1;
|
|
}
|
|
|
|
getOffsetNanosecondsFor(instant) {
|
|
return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
|
|
}
|
|
|
|
getPossibleInstantsFor(plainDateTime) {
|
|
this.getPossibleInstantsForCalledWith.push(plainDateTime.toString());
|
|
const [instant] = super.getPossibleInstantsFor(plainDateTime);
|
|
if (this._shiftNanoseconds > 0) {
|
|
if (this._isBeforeShift(instant)) return [instant];
|
|
if (instant.epochNanoseconds < this._epoch2) return [];
|
|
return [instant.subtract(this._shift)];
|
|
}
|
|
if (instant.epochNanoseconds < this._epoch2) return [instant];
|
|
const shifted = instant.subtract(this._shift);
|
|
if (this._isBeforeShift(instant)) return [instant, shifted];
|
|
return [shifted];
|
|
}
|
|
|
|
getNextTransition(instant) {
|
|
return this._isBeforeShift(instant) ? this._shiftInstant : null;
|
|
}
|
|
|
|
getPreviousTransition(instant) {
|
|
return this._isBeforeShift(instant) ? null : this._shiftInstant;
|
|
}
|
|
|
|
toString() {
|
|
return "Custom/One_Shift";
|
|
}
|
|
}
|
|
return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
|
|
},
|
|
|
|
/*
|
|
* specificOffsetTimeZone():
|
|
*
|
|
* This returns an instance of a custom time zone class, which returns a
|
|
* specific custom value from its getOffsetNanosecondsFrom() method. This is
|
|
* for the purpose of testing the validation of what this method returns.
|
|
*
|
|
* It also returns an empty array from getPossibleInstantsFor(), so as to
|
|
* trigger calls to getOffsetNanosecondsFor() when used from the
|
|
* BuiltinTimeZoneGetInstantFor operation.
|
|
*/
|
|
specificOffsetTimeZone(offsetValue) {
|
|
class SpecificOffsetTimeZone extends Temporal.TimeZone {
|
|
constructor(offsetValue) {
|
|
super("UTC");
|
|
this._offsetValue = offsetValue;
|
|
}
|
|
|
|
getOffsetNanosecondsFor() {
|
|
return this._offsetValue;
|
|
}
|
|
|
|
getPossibleInstantsFor() {
|
|
return [];
|
|
}
|
|
}
|
|
return new SpecificOffsetTimeZone(offsetValue);
|
|
},
|
|
|
|
/*
|
|
* springForwardFallBackTimeZone():
|
|
*
|
|
* This returns an instance of a custom time zone class that implements one
|
|
* single spring-forward/fall-back transition, for the purpose of testing the
|
|
* disambiguation option, without depending on system time zone data.
|
|
*
|
|
* The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
|
|
* local) and goes from offset -08:00 to -07:00.
|
|
*
|
|
* The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
|
|
* goes from offset -07:00 to -08:00.
|
|
*/
|
|
springForwardFallBackTimeZone() {
|
|
const { compare } = Temporal.PlainDateTime;
|
|
const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
|
|
const springForwardEpoch = 954669600_000_000_000n;
|
|
const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
|
|
const fallBackEpoch = 972810000_000_000_000n;
|
|
const winterOffset = new Temporal.TimeZone('-08:00');
|
|
const summerOffset = new Temporal.TimeZone('-07:00');
|
|
|
|
class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
|
|
constructor() {
|
|
super("-08:00");
|
|
}
|
|
|
|
getOffsetNanosecondsFor(instant) {
|
|
if (instant.epochNanoseconds < springForwardEpoch ||
|
|
instant.epochNanoseconds >= fallBackEpoch) {
|
|
return winterOffset.getOffsetNanosecondsFor(instant);
|
|
}
|
|
return summerOffset.getOffsetNanosecondsFor(instant);
|
|
}
|
|
|
|
getPossibleInstantsFor(datetime) {
|
|
if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
|
|
return [];
|
|
}
|
|
if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
|
|
return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
|
|
}
|
|
if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
|
|
return [winterOffset.getInstantFor(datetime)];
|
|
}
|
|
return [summerOffset.getInstantFor(datetime)];
|
|
}
|
|
|
|
getPreviousTransition(instant) {
|
|
if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
|
|
if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
|
|
return null;
|
|
}
|
|
|
|
getNextTransition(instant) {
|
|
if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
|
|
if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
|
|
return null;
|
|
}
|
|
|
|
toString() {
|
|
return "Custom/Spring_Fall";
|
|
}
|
|
}
|
|
return new SpringForwardFallBackTimeZone();
|
|
},
|
|
|
|
/*
|
|
* Returns an object that will append logs of any Gets or Calls of its valueOf
|
|
* or toString properties to the array calls. Both valueOf and toString will
|
|
* return the actual primitiveValue. propertyName is used in the log.
|
|
*/
|
|
toPrimitiveObserver(calls, primitiveValue, propertyName) {
|
|
return {
|
|
get valueOf() {
|
|
calls.push(`get ${propertyName}.valueOf`);
|
|
return function () {
|
|
calls.push(`call ${propertyName}.valueOf`);
|
|
return primitiveValue;
|
|
};
|
|
},
|
|
get toString() {
|
|
calls.push(`get ${propertyName}.toString`);
|
|
return function () {
|
|
calls.push(`call ${propertyName}.toString`);
|
|
if (primitiveValue === undefined) return undefined;
|
|
return primitiveValue.toString();
|
|
};
|
|
},
|
|
};
|
|
},
|
|
};
|