diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index f135090149..64098d67f3 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -125,6 +125,7 @@ namespace JS { P(dayOfYear) \ P(dayPeriod) \ P(days) \ + P(daysDisplay) \ P(daysInMonth) \ P(daysInWeek) \ P(daysInYear) \ @@ -256,6 +257,7 @@ namespace JS { P(hour12) \ P(hourCycle) \ P(hours) \ + P(hoursDisplay) \ P(hoursInDay) \ P(hypot) \ P(id) \ @@ -320,8 +322,10 @@ namespace JS { P(message) \ P(microsecond) \ P(microseconds) \ + P(microsecondsDisplay) \ P(millisecond) \ P(milliseconds) \ + P(millisecondsDisplay) \ P(min) \ P(minimize) \ P(maximumFractionDigits) \ @@ -331,15 +335,18 @@ namespace JS { P(minimumSignificantDigits) \ P(minute) \ P(minutes) \ + P(minutesDisplay) \ P(month) \ P(monthCode) \ P(monthDayFromFields) \ P(months) \ + P(monthsDisplay) \ P(monthsInYear) \ P(multiline) \ P(name) \ P(nanosecond) \ P(nanoseconds) \ + P(nanosecondsDisplay) \ P(negated) \ P(next) \ P(normalize) \ @@ -395,6 +402,7 @@ namespace JS { P(seal) \ P(second) \ P(seconds) \ + P(secondsDisplay) \ P(segment) \ P(sensitivity) \ P(set) \ @@ -522,6 +530,7 @@ namespace JS { P(weekOfYear) \ P(weekday) \ P(weeks) \ + P(weeksDisplay) \ P(with) \ P(withCalendar) \ P(withPlainDate) \ @@ -531,6 +540,7 @@ namespace JS { P(year) \ P(yearMonthFromFields) \ P(years) \ + P(yearsDisplay) \ P(zonedDateTime) \ P(zonedDateTimeISO) diff --git a/Userland/Libraries/LibJS/Runtime/Intl/DurationFormatPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Intl/DurationFormatPrototype.cpp index eea463b13b..b269d68e24 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/DurationFormatPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/DurationFormatPrototype.cpp @@ -23,6 +23,53 @@ void DurationFormatPrototype::initialize(GlobalObject& global_object) // 1.4.2 Intl.DurationFormat.prototype [ @@toStringTag ], https://tc39.es/proposal-intl-duration-format/#sec-Intl.DurationFormat.prototype-@@tostringtag define_direct_property(*vm.well_known_symbol_to_string_tag(), js_string(vm, "Intl.DurationFormat"), Attribute::Configurable); + + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function(vm.names.resolvedOptions, resolved_options, 0, attr); +} + +// 1.4.5 Intl.DurationFormat.prototype.resolvedOptions ( ), https://tc39.es/proposal-intl-duration-format/#sec-Intl.DurationFormat.prototype.resolvedOptions +JS_DEFINE_NATIVE_FUNCTION(DurationFormatPrototype::resolved_options) +{ + // 1. Let df be the this value. + // 2. Perform ? RequireInternalSlot(df, [[InitializedDurationFormat]]). + auto* duration_format = TRY(typed_this_object(global_object)); + + // 3. Let options be ! OrdinaryObjectCreate(%Object.prototype%). + auto* options = Object::create(global_object, global_object.object_prototype()); + + // 4. For each row of Table 2, except the header row, in table order, do + // a. Let p be the Property value of the current row. + // b. Let v be the value of df's internal slot whose name is the Internal Slot value of the current row. + // c. Assert: v is not undefined. + // d. Perform ! CreateDataPropertyOrThrow(options, p, v). + MUST(options->create_data_property_or_throw(vm.names.locale, js_string(vm, duration_format->locale()))); + MUST(options->create_data_property_or_throw(vm.names.style, js_string(vm, duration_format->style_string()))); + MUST(options->create_data_property_or_throw(vm.names.years, js_string(vm, duration_format->years_style_string()))); + MUST(options->create_data_property_or_throw(vm.names.yearsDisplay, js_string(vm, duration_format->years_display_string()))); + MUST(options->create_data_property_or_throw(vm.names.months, js_string(vm, duration_format->months_style_string()))); + MUST(options->create_data_property_or_throw(vm.names.monthsDisplay, js_string(vm, duration_format->months_display_string()))); + MUST(options->create_data_property_or_throw(vm.names.weeks, js_string(vm, duration_format->weeks_style_string()))); + MUST(options->create_data_property_or_throw(vm.names.weeksDisplay, js_string(vm, duration_format->weeks_display_string()))); + MUST(options->create_data_property_or_throw(vm.names.days, js_string(vm, duration_format->days_style_string()))); + MUST(options->create_data_property_or_throw(vm.names.daysDisplay, js_string(vm, duration_format->days_display_string()))); + MUST(options->create_data_property_or_throw(vm.names.hours, js_string(vm, duration_format->hours_style_string()))); + MUST(options->create_data_property_or_throw(vm.names.hoursDisplay, js_string(vm, duration_format->hours_display_string()))); + MUST(options->create_data_property_or_throw(vm.names.minutes, js_string(vm, duration_format->minutes_style_string()))); + MUST(options->create_data_property_or_throw(vm.names.minutesDisplay, js_string(vm, duration_format->minutes_display_string()))); + MUST(options->create_data_property_or_throw(vm.names.seconds, js_string(vm, duration_format->seconds_style_string()))); + MUST(options->create_data_property_or_throw(vm.names.secondsDisplay, js_string(vm, duration_format->seconds_display_string()))); + MUST(options->create_data_property_or_throw(vm.names.milliseconds, js_string(vm, duration_format->milliseconds_style_string()))); + MUST(options->create_data_property_or_throw(vm.names.millisecondsDisplay, js_string(vm, duration_format->milliseconds_display_string()))); + MUST(options->create_data_property_or_throw(vm.names.microseconds, js_string(vm, duration_format->microseconds_style_string()))); + MUST(options->create_data_property_or_throw(vm.names.microsecondsDisplay, js_string(vm, duration_format->microseconds_display_string()))); + MUST(options->create_data_property_or_throw(vm.names.nanoseconds, js_string(vm, duration_format->nanoseconds_style_string()))); + MUST(options->create_data_property_or_throw(vm.names.nanosecondsDisplay, js_string(vm, duration_format->nanoseconds_display_string()))); + MUST(options->create_data_property_or_throw(vm.names.fractionalDigits, duration_format->has_fractional_digits() ? Value(duration_format->fractional_digits()) : js_undefined())); + MUST(options->create_data_property_or_throw(vm.names.numberingSystem, js_string(vm, duration_format->numbering_system()))); + + // 5. Return options. + return options; } } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/DurationFormatPrototype.h b/Userland/Libraries/LibJS/Runtime/Intl/DurationFormatPrototype.h index bc0af4b805..de460b4937 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/DurationFormatPrototype.h +++ b/Userland/Libraries/LibJS/Runtime/Intl/DurationFormatPrototype.h @@ -18,6 +18,9 @@ public: explicit DurationFormatPrototype(GlobalObject&); virtual void initialize(GlobalObject&) override; virtual ~DurationFormatPrototype() override = default; + +private: + JS_DECLARE_NATIVE_FUNCTION(resolved_options); }; } diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/DurationFormat/DurationFormat.prototype.resolvedOptions.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/DurationFormat/DurationFormat.prototype.resolvedOptions.js new file mode 100644 index 0000000000..2678782770 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/DurationFormat/DurationFormat.prototype.resolvedOptions.js @@ -0,0 +1,294 @@ +describe("correct behavior", () => { + test("length is 0", () => { + expect(Intl.DurationFormat.prototype.resolvedOptions).toHaveLength(0); + }); + + test("locale only contains relevant extension keys", () => { + const en1 = new Intl.DurationFormat("en-u-ca-islamicc"); + expect(en1.resolvedOptions().locale).toBe("en"); + + const en3 = new Intl.DurationFormat("en-u-ca-islamicc-nu-latn"); + expect(en3.resolvedOptions().locale).toBe("en-u-nu-latn"); + }); + + test("numberingSystem may be set by option", () => { + const en = new Intl.DurationFormat("en", { numberingSystem: "latn" }); + expect(en.resolvedOptions().numberingSystem).toBe("latn"); + + const el = new Intl.DurationFormat("el", { numberingSystem: "latn" }); + expect(el.resolvedOptions().numberingSystem).toBe("latn"); + }); + + test("numberingSystem may be set by locale extension", () => { + const en = new Intl.DurationFormat("en-u-nu-latn"); + expect(en.resolvedOptions().numberingSystem).toBe("latn"); + + const el = new Intl.DurationFormat("el-u-nu-latn"); + expect(el.resolvedOptions().numberingSystem).toBe("latn"); + }); + + test("numberingSystem option overrides locale extension", () => { + const el = new Intl.DurationFormat("el-u-nu-latn", { numberingSystem: "grek" }); + expect(el.resolvedOptions().numberingSystem).toBe("grek"); + }); + + test("numberingSystem option limited to known 'nu' values", () => { + ["latn", "arab"].forEach(numberingSystem => { + const en = new Intl.DurationFormat("en", { numberingSystem: numberingSystem }); + expect(en.resolvedOptions().numberingSystem).toBe("latn"); + }); + + ["latn", "arab"].forEach(numberingSystem => { + const en = new Intl.DurationFormat(`en-u-nu-${numberingSystem}`); + expect(en.resolvedOptions().numberingSystem).toBe("latn"); + }); + + ["latn", "grek"].forEach(numberingSystem => { + const el = new Intl.DurationFormat("el", { numberingSystem: numberingSystem }); + expect(el.resolvedOptions().numberingSystem).toBe(numberingSystem); + }); + + ["latn", "grek"].forEach(numberingSystem => { + const el = new Intl.DurationFormat(`el-u-nu-${numberingSystem}`); + expect(el.resolvedOptions().numberingSystem).toBe(numberingSystem); + }); + }); + + test("style", () => { + const en1 = new Intl.DurationFormat("en"); + expect(en1.resolvedOptions().style).toBe("long"); + + ["long", "short", "narrow", "digital"].forEach(style => { + const en2 = new Intl.DurationFormat("en", { style: style }); + expect(en2.resolvedOptions().style).toBe(style); + }); + }); + + test("years", () => { + const en1 = new Intl.DurationFormat("en"); + expect(en1.resolvedOptions().years).toBe("long"); + + ["long", "short", "narrow"].forEach(years => { + const en2 = new Intl.DurationFormat("en", { years: years }); + expect(en2.resolvedOptions().years).toBe(years); + }); + }); + + test("yearsDisplay", () => { + const en1 = new Intl.DurationFormat("en"); + expect(en1.resolvedOptions().yearsDisplay).toBe("auto"); + + ["always", "auto"].forEach(yearsDisplay => { + const en2 = new Intl.DurationFormat("en", { yearsDisplay: yearsDisplay }); + expect(en2.resolvedOptions().yearsDisplay).toBe(yearsDisplay); + }); + }); + + test("months", () => { + const en1 = new Intl.DurationFormat("en"); + expect(en1.resolvedOptions().months).toBe("long"); + + ["long", "short", "narrow"].forEach(months => { + const en2 = new Intl.DurationFormat("en", { months: months }); + expect(en2.resolvedOptions().months).toBe(months); + }); + }); + + test("monthsDisplay", () => { + const en1 = new Intl.DurationFormat("en"); + expect(en1.resolvedOptions().monthsDisplay).toBe("auto"); + + ["always", "auto"].forEach(monthsDisplay => { + const en2 = new Intl.DurationFormat("en", { monthsDisplay: monthsDisplay }); + expect(en2.resolvedOptions().monthsDisplay).toBe(monthsDisplay); + }); + }); + + test("weeks", () => { + const en1 = new Intl.DurationFormat("en"); + expect(en1.resolvedOptions().weeks).toBe("long"); + + ["long", "short", "narrow"].forEach(weeks => { + const en2 = new Intl.DurationFormat("en", { weeks: weeks }); + expect(en2.resolvedOptions().weeks).toBe(weeks); + }); + }); + + test("weeksDisplay", () => { + const en1 = new Intl.DurationFormat("en"); + expect(en1.resolvedOptions().weeksDisplay).toBe("auto"); + + ["always", "auto"].forEach(weeksDisplay => { + const en2 = new Intl.DurationFormat("en", { weeksDisplay: weeksDisplay }); + expect(en2.resolvedOptions().weeksDisplay).toBe(weeksDisplay); + }); + }); + + test("days", () => { + const en1 = new Intl.DurationFormat("en"); + expect(en1.resolvedOptions().days).toBe("long"); + + ["long", "short", "narrow"].forEach(days => { + const en2 = new Intl.DurationFormat("en", { days: days }); + expect(en2.resolvedOptions().days).toBe(days); + }); + }); + + test("daysDisplay", () => { + const en1 = new Intl.DurationFormat("en"); + expect(en1.resolvedOptions().daysDisplay).toBe("auto"); + + ["always", "auto"].forEach(daysDisplay => { + const en2 = new Intl.DurationFormat("en", { daysDisplay: daysDisplay }); + expect(en2.resolvedOptions().daysDisplay).toBe(daysDisplay); + }); + }); + + test("hours", () => { + const en1 = new Intl.DurationFormat("en"); + expect(en1.resolvedOptions().hours).toBe("long"); + + ["long", "short", "narrow"].forEach(hours => { + const en2 = new Intl.DurationFormat("en", { hours: hours }); + expect(en2.resolvedOptions().hours).toBe(hours); + }); + ["numeric", "2-digit"].forEach(hours => { + const en2 = new Intl.DurationFormat("en", { style: "digital", hours: hours }); + expect(en2.resolvedOptions().hours).toBe(hours); + }); + }); + + test("hoursDisplay", () => { + const en1 = new Intl.DurationFormat("en"); + expect(en1.resolvedOptions().hoursDisplay).toBe("auto"); + + ["always", "auto"].forEach(hoursDisplay => { + const en2 = new Intl.DurationFormat("en", { hoursDisplay: hoursDisplay }); + expect(en2.resolvedOptions().hoursDisplay).toBe(hoursDisplay); + }); + }); + + test("minutes", () => { + const en1 = new Intl.DurationFormat("en"); + expect(en1.resolvedOptions().minutes).toBe("long"); + + ["long", "short", "narrow"].forEach(minutes => { + const en2 = new Intl.DurationFormat("en", { minutes: minutes }); + expect(en2.resolvedOptions().minutes).toBe(minutes); + }); + ["numeric", "2-digit"].forEach(minutes => { + const en2 = new Intl.DurationFormat("en", { style: "digital", minutes: minutes }); + expect(en2.resolvedOptions().minutes).toBe("2-digit"); + }); + }); + + test("minutesDisplay", () => { + const en1 = new Intl.DurationFormat("en"); + expect(en1.resolvedOptions().minutesDisplay).toBe("auto"); + + ["always", "auto"].forEach(minutesDisplay => { + const en2 = new Intl.DurationFormat("en", { minutesDisplay: minutesDisplay }); + expect(en2.resolvedOptions().minutesDisplay).toBe(minutesDisplay); + }); + }); + + test("seconds", () => { + const en1 = new Intl.DurationFormat("en"); + expect(en1.resolvedOptions().seconds).toBe("long"); + + ["long", "short", "narrow"].forEach(seconds => { + const en2 = new Intl.DurationFormat("en", { seconds: seconds }); + expect(en2.resolvedOptions().seconds).toBe(seconds); + }); + ["numeric", "2-digit"].forEach(seconds => { + const en2 = new Intl.DurationFormat("en", { style: "digital", seconds: seconds }); + expect(en2.resolvedOptions().seconds).toBe("2-digit"); + }); + }); + + test("secondsDisplay", () => { + const en1 = new Intl.DurationFormat("en"); + expect(en1.resolvedOptions().secondsDisplay).toBe("auto"); + + ["always", "auto"].forEach(secondsDisplay => { + const en2 = new Intl.DurationFormat("en", { secondsDisplay: secondsDisplay }); + expect(en2.resolvedOptions().secondsDisplay).toBe(secondsDisplay); + }); + }); + + test("milliseconds", () => { + const en1 = new Intl.DurationFormat("en"); + expect(en1.resolvedOptions().milliseconds).toBe("long"); + + ["long", "short", "narrow"].forEach(milliseconds => { + const en2 = new Intl.DurationFormat("en", { milliseconds: milliseconds }); + expect(en2.resolvedOptions().milliseconds).toBe(milliseconds); + }); + const en2 = new Intl.DurationFormat("en", { style: "digital", milliseconds: "numeric" }); + expect(en2.resolvedOptions().milliseconds).toBe("numeric"); + }); + + test("millisecondsDisplay", () => { + const en1 = new Intl.DurationFormat("en"); + expect(en1.resolvedOptions().millisecondsDisplay).toBe("auto"); + + ["always", "auto"].forEach(millisecondsDisplay => { + const en2 = new Intl.DurationFormat("en", { millisecondsDisplay: millisecondsDisplay }); + expect(en2.resolvedOptions().millisecondsDisplay).toBe(millisecondsDisplay); + }); + }); + + test("microseconds", () => { + const en1 = new Intl.DurationFormat("en"); + expect(en1.resolvedOptions().microseconds).toBe("long"); + + ["long", "short", "narrow"].forEach(microseconds => { + const en2 = new Intl.DurationFormat("en", { microseconds: microseconds }); + expect(en2.resolvedOptions().microseconds).toBe(microseconds); + }); + const en2 = new Intl.DurationFormat("en", { style: "digital", microseconds: "numeric" }); + expect(en2.resolvedOptions().microseconds).toBe("numeric"); + }); + + test("microsecondsDisplay", () => { + const en1 = new Intl.DurationFormat("en"); + expect(en1.resolvedOptions().microsecondsDisplay).toBe("auto"); + + ["always", "auto"].forEach(microsecondsDisplay => { + const en2 = new Intl.DurationFormat("en", { microsecondsDisplay: microsecondsDisplay }); + expect(en2.resolvedOptions().microsecondsDisplay).toBe(microsecondsDisplay); + }); + }); + + test("nanoseconds", () => { + const en1 = new Intl.DurationFormat("en"); + expect(en1.resolvedOptions().nanoseconds).toBe("long"); + + ["long", "short", "narrow", "numeric"].forEach(nanoseconds => { + const en2 = new Intl.DurationFormat("en", { nanoseconds: nanoseconds }); + expect(en2.resolvedOptions().nanoseconds).toBe(nanoseconds); + }); + const en2 = new Intl.DurationFormat("en", { style: "digital", nanoseconds: "numeric" }); + expect(en2.resolvedOptions().nanoseconds).toBe("numeric"); + }); + + test("nanosecondsDisplay", () => { + const en1 = new Intl.DurationFormat("en"); + expect(en1.resolvedOptions().nanosecondsDisplay).toBe("auto"); + + ["always", "auto"].forEach(nanosecondsDisplay => { + const en2 = new Intl.DurationFormat("en", { nanosecondsDisplay: nanosecondsDisplay }); + expect(en2.resolvedOptions().nanosecondsDisplay).toBe(nanosecondsDisplay); + }); + }); + + test("fractionalDigits", () => { + const en1 = new Intl.DurationFormat("en"); + expect(en1.resolvedOptions().fractionalDigits).toBeUndefined(); + + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(fractionalDigits => { + const en2 = new Intl.DurationFormat("en", { fractionalDigits: fractionalDigits }); + expect(en2.resolvedOptions().fractionalDigits).toBe(fractionalDigits); + }); + }); +});