LibJS: Implement Temporal.PlainTime.prototype.with
This commit is contained in:
parent
85eef698b9
commit
a0df194528
Notes:
github-actions[bot]
2024-11-24 00:37:35 +00:00
Author: https://github.com/trflynn89 Commit: https://github.com/LadybirdBrowser/ladybird/commit/a0df194528e Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2535
3 changed files with 271 additions and 0 deletions
|
@ -38,6 +38,7 @@ void PlainTimePrototype::initialize(Realm& realm)
|
|||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
define_native_function(realm, vm.names.add, add, 1, attr);
|
||||
define_native_function(realm, vm.names.subtract, subtract, 1, attr);
|
||||
define_native_function(realm, vm.names.with, with, 1, attr);
|
||||
define_native_function(realm, vm.names.until, until, 1, attr);
|
||||
define_native_function(realm, vm.names.since, since, 1, attr);
|
||||
define_native_function(realm, vm.names.toString, to_string, 0, attr);
|
||||
|
@ -98,6 +99,72 @@ JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::subtract)
|
|||
return TRY(add_duration_to_time(vm, ArithmeticOperation::Subtract, temporal_time, temporal_duration_like));
|
||||
}
|
||||
|
||||
// 4.3.11 Temporal.PlainTime.prototype.with ( temporalTimeLike [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.with
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::with)
|
||||
{
|
||||
auto temporal_time_like = vm.argument(0);
|
||||
auto options = vm.argument(1);
|
||||
|
||||
// 1. Let temporalTime be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
|
||||
auto temporal_time = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. If ? IsPartialTemporalObject(temporalTimeLike) is false, throw a TypeError exception.
|
||||
if (!TRY(is_partial_temporal_object(vm, temporal_time_like)))
|
||||
return vm.throw_completion<TypeError>(ErrorType::TemporalObjectMustBePartialTemporalObject);
|
||||
|
||||
// 4. Let partialTime be ? ToTemporalTimeRecord(temporalTimeLike, PARTIAL).
|
||||
auto partial_time = TRY(to_temporal_time_record(vm, temporal_time_like.as_object(), Completeness::Partial));
|
||||
|
||||
// 5. If partialTime.[[Hour]] is not undefined, then
|
||||
// a. Let hour be partialTime.[[Hour]].
|
||||
// 6. Else,
|
||||
// a. Let hour be temporalTime.[[Time]].[[Hour]].
|
||||
auto hour = partial_time.hour.value_or(temporal_time->time().hour);
|
||||
|
||||
// 7. If partialTime.[[Minute]] is not undefined, then
|
||||
// a. Let minute be partialTime.[[Minute]].
|
||||
// 8. Else,
|
||||
// a. Let minute be temporalTime.[[Time]].[[Minute]].
|
||||
auto minute = partial_time.minute.value_or(temporal_time->time().minute);
|
||||
|
||||
// 9. If partialTime.[[Second]] is not undefined, then
|
||||
// a. Let second be partialTime.[[Second]].
|
||||
// 10. Else,
|
||||
// a. Let second be temporalTime.[[Time]].[[Second]].
|
||||
auto second = partial_time.second.value_or(temporal_time->time().second);
|
||||
|
||||
// 11. If partialTime.[[Millisecond]] is not undefined, then
|
||||
// a. Let millisecond be partialTime.[[Millisecond]].
|
||||
// 12. Else,
|
||||
// a. Let millisecond be temporalTime.[[Time]].[[Millisecond]].
|
||||
auto millisecond = partial_time.millisecond.value_or(temporal_time->time().millisecond);
|
||||
|
||||
// 13. If partialTime.[[Microsecond]] is not undefined, then
|
||||
// a. Let microsecond be partialTime.[[Microsecond]].
|
||||
// 14. Else,
|
||||
// a. Let microsecond be temporalTime.[[Time]].[[Microsecond]].
|
||||
auto microsecond = partial_time.microsecond.value_or(temporal_time->time().microsecond);
|
||||
|
||||
// 15. If partialTime.[[Nanosecond]] is not undefined, then
|
||||
// a. Let nanosecond be partialTime.[[Nanosecond]].
|
||||
// 16. Else,
|
||||
// a. Let nanosecond be temporalTime.[[Time]].[[Nanosecond]].
|
||||
auto nanosecond = partial_time.nanosecond.value_or(temporal_time->time().nanosecond);
|
||||
|
||||
// 17. Let resolvedOptions be ? GetOptionsObject(options).
|
||||
auto resolved_options = TRY(get_options_object(vm, options));
|
||||
|
||||
// 18. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
|
||||
auto overflow = TRY(get_temporal_overflow_option(vm, resolved_options));
|
||||
|
||||
// 19. Let result be ? RegulateTime(hour, minute, second, millisecond, microsecond, nanosecond, overflow).
|
||||
auto result = TRY(regulate_time(vm, hour, minute, second, millisecond, microsecond, nanosecond, overflow));
|
||||
|
||||
// 20. Return ! CreateTemporalTime(result).
|
||||
return MUST(create_temporal_time(vm, result));
|
||||
}
|
||||
|
||||
// 4.3.12 Temporal.PlainTime.prototype.until ( other [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.until
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::until)
|
||||
{
|
||||
|
|
|
@ -31,6 +31,7 @@ private:
|
|||
JS_DECLARE_NATIVE_FUNCTION(nanosecond_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(add);
|
||||
JS_DECLARE_NATIVE_FUNCTION(subtract);
|
||||
JS_DECLARE_NATIVE_FUNCTION(with);
|
||||
JS_DECLARE_NATIVE_FUNCTION(until);
|
||||
JS_DECLARE_NATIVE_FUNCTION(since);
|
||||
JS_DECLARE_NATIVE_FUNCTION(to_string);
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
const PLAIN_TIME_PROPERTIES = [
|
||||
"hour",
|
||||
"minute",
|
||||
"second",
|
||||
"millisecond",
|
||||
"microsecond",
|
||||
"nanosecond",
|
||||
];
|
||||
|
||||
const REJECTED_CALENDAR_TYPES_THREE_ARGUMENTS = [
|
||||
Temporal.PlainDate,
|
||||
// Temporal.PlainDateTime,
|
||||
Temporal.PlainTime,
|
||||
];
|
||||
|
||||
const REJECTED_CALENDAR_TYPES_TWO_ARGUMENTS = [Temporal.PlainMonthDay, Temporal.PlainYearMonth];
|
||||
|
||||
describe("correct behavior", () => {
|
||||
test("length is 1", () => {
|
||||
expect(Temporal.PlainTime.prototype.with).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const plainTime = new Temporal.PlainTime(1, 2, 3).with({ hour: 4, foo: 5, second: 6 });
|
||||
expect(plainTime.hour).toBe(4);
|
||||
expect(plainTime.minute).toBe(2);
|
||||
expect(plainTime.second).toBe(6);
|
||||
});
|
||||
|
||||
test("each property is looked up from the object", () => {
|
||||
for (const property of PLAIN_TIME_PROPERTIES) {
|
||||
const plainTime = new Temporal.PlainTime().with({ [property]: 1 });
|
||||
expect(plainTime[property]).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
test("each property is coerced to number", () => {
|
||||
for (const property of PLAIN_TIME_PROPERTIES) {
|
||||
const plainTime = new Temporal.PlainTime().with({ [property]: "1" });
|
||||
expect(plainTime[property]).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
test("argument can have a calendar property as long as it's undefined", () => {
|
||||
expect(() => {
|
||||
new Temporal.PlainTime().with({
|
||||
calendar: undefined,
|
||||
});
|
||||
}).not.toThrowWithMessage(TypeError, "Object must be a partial Temporal object");
|
||||
});
|
||||
|
||||
test("argument can have a timeZone property as long as it's undefined", () => {
|
||||
expect(() => {
|
||||
new Temporal.PlainTime().with({
|
||||
timeZone: undefined,
|
||||
});
|
||||
}).not.toThrowWithMessage(TypeError, "Object must be a partial Temporal object");
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.PlainTime object", () => {
|
||||
expect(() => {
|
||||
Temporal.PlainTime.prototype.with.call("foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainTime");
|
||||
});
|
||||
|
||||
test("argument is not an object", () => {
|
||||
expect(() => {
|
||||
new Temporal.PlainTime().with("foo");
|
||||
}).toThrowWithMessage(TypeError, "Object must be a partial Temporal object");
|
||||
expect(() => {
|
||||
new Temporal.PlainTime().with(42);
|
||||
}).toThrowWithMessage(TypeError, "Object must be a partial Temporal object");
|
||||
});
|
||||
|
||||
test("options is not an object", () => {
|
||||
expect(() => {
|
||||
new Temporal.PlainTime().with({ hour: 1 }, "foo");
|
||||
}).toThrowWithMessage(TypeError, "Options is not an object");
|
||||
expect(() => {
|
||||
new Temporal.PlainTime().with({ hour: 1 }, 42);
|
||||
}).toThrowWithMessage(TypeError, "Options is not an object");
|
||||
});
|
||||
|
||||
test("invalid overflow option", () => {
|
||||
expect(() => {
|
||||
new Temporal.PlainTime().with({ hour: 1 }, { overflow: "a" });
|
||||
}).toThrowWithMessage(RangeError, "a is not a valid value for option overflow");
|
||||
});
|
||||
|
||||
test("argument is an invalid plain time-like object", () => {
|
||||
expect(() => {
|
||||
new Temporal.PlainTime().with({});
|
||||
}).toThrowWithMessage(TypeError, "Invalid time");
|
||||
expect(() => {
|
||||
new Temporal.PlainTime().with({ foo: 1, bar: 2 });
|
||||
}).toThrowWithMessage(TypeError, "Invalid time");
|
||||
});
|
||||
|
||||
test("error when coercing property to number", () => {
|
||||
for (const property of PLAIN_TIME_PROPERTIES) {
|
||||
expect(() => {
|
||||
new Temporal.PlainTime().with({
|
||||
[property]: {
|
||||
valueOf() {
|
||||
throw new Error("error occurred");
|
||||
},
|
||||
},
|
||||
});
|
||||
}).toThrowWithMessage(Error, "error occurred");
|
||||
}
|
||||
});
|
||||
|
||||
test("property must be finite", () => {
|
||||
for (const property of PLAIN_TIME_PROPERTIES) {
|
||||
expect(() => {
|
||||
new Temporal.PlainTime().with({ [property]: Infinity });
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
`Invalid value Infinity for time field '${property}'`
|
||||
);
|
||||
expect(() => {
|
||||
new Temporal.PlainTime().with({ [property]: -Infinity });
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
`Invalid value -Infinity for time field '${property}'`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("error when getting property", () => {
|
||||
for (const property of PLAIN_TIME_PROPERTIES) {
|
||||
expect(() => {
|
||||
new Temporal.PlainTime().with({
|
||||
get [property]() {
|
||||
throw new Error("error occurred");
|
||||
},
|
||||
});
|
||||
}).toThrowWithMessage(Error, "error occurred");
|
||||
}
|
||||
});
|
||||
|
||||
test("argument must not have a defined calendar property", () => {
|
||||
expect(() => {
|
||||
new Temporal.PlainTime().with({
|
||||
calendar: null,
|
||||
});
|
||||
}).toThrowWithMessage(TypeError, "Object must be a partial Temporal object");
|
||||
expect(() => {
|
||||
new Temporal.PlainTime().with({
|
||||
calendar: 1,
|
||||
});
|
||||
}).toThrowWithMessage(TypeError, "Object must be a partial Temporal object");
|
||||
});
|
||||
|
||||
test("argument must not have a defined timeZone property", () => {
|
||||
expect(() => {
|
||||
new Temporal.PlainTime().with({
|
||||
timeZone: null,
|
||||
});
|
||||
}).toThrowWithMessage(TypeError, "Object must be a partial Temporal object");
|
||||
expect(() => {
|
||||
new Temporal.PlainTime().with({
|
||||
timeZone: 1,
|
||||
});
|
||||
}).toThrowWithMessage(TypeError, "Object must be a partial Temporal object");
|
||||
});
|
||||
|
||||
test("error when getting calendar", () => {
|
||||
expect(() => {
|
||||
new Temporal.PlainTime().with({
|
||||
get calendar() {
|
||||
throw new Error("error occurred");
|
||||
},
|
||||
});
|
||||
}).toThrowWithMessage(Error, "error occurred");
|
||||
});
|
||||
|
||||
test("error when getting timeZone", () => {
|
||||
expect(() => {
|
||||
new Temporal.PlainTime().with({
|
||||
get timeZone() {
|
||||
throw new Error("error occurred");
|
||||
},
|
||||
});
|
||||
}).toThrowWithMessage(Error, "error occurred");
|
||||
});
|
||||
|
||||
test("rejects calendar types", () => {
|
||||
for (const typeWithCalendar of REJECTED_CALENDAR_TYPES_THREE_ARGUMENTS) {
|
||||
expect(() => {
|
||||
new Temporal.PlainTime().with(new typeWithCalendar(1, 1, 1));
|
||||
}).toThrowWithMessage(TypeError, "Object must be a partial Temporal object");
|
||||
}
|
||||
|
||||
for (const typeWithCalendar of REJECTED_CALENDAR_TYPES_TWO_ARGUMENTS) {
|
||||
expect(() => {
|
||||
new Temporal.PlainTime().with(new typeWithCalendar(1, 1));
|
||||
}).toThrowWithMessage(TypeError, "Object must be a partial Temporal object");
|
||||
}
|
||||
});
|
||||
});
|
Loading…
Add table
Reference in a new issue