From 4a99170cd2b0778b3445ade45f268d25f4822226 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Wed, 8 Sep 2021 14:55:39 -0400 Subject: [PATCH] LibJS: Implement Intl.Collator.prototype.resolvedOptions --- .../LibJS/Runtime/Intl/CollatorPrototype.cpp | 35 ++++++ .../LibJS/Runtime/Intl/CollatorPrototype.h | 3 + .../Collator.prototype.resolvedOptions.js | 108 ++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 Userland/Libraries/LibJS/Tests/builtins/Intl/Collator/Collator.prototype.resolvedOptions.js diff --git a/Userland/Libraries/LibJS/Runtime/Intl/CollatorPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Intl/CollatorPrototype.cpp index a5ef624274..7c39726ab1 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/CollatorPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/CollatorPrototype.cpp @@ -5,6 +5,7 @@ */ #include +#include #include namespace JS::Intl { @@ -23,6 +24,40 @@ void CollatorPrototype::initialize(GlobalObject& global_object) // 10.3.2 Intl.Collator.prototype [ @@toStringTag ], https://tc39.es/ecma402/#sec-intl.collator.prototype-@@tostringtag define_direct_property(*vm.well_known_symbol_to_string_tag(), js_string(vm, "Intl.Collator"), Attribute::Configurable); + + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function(vm.names.resolvedOptions, resolved_options, 0, attr); +} + +// 10.3.4 Intl.Collator.prototype.resolvedOptions ( ), https://tc39.es/ecma402/#sec-intl.collator.prototype.resolvedoptions +JS_DEFINE_NATIVE_FUNCTION(CollatorPrototype::resolved_options) +{ + // 1. Let collator be the this value. + // 2. Perform ? RequireInternalSlot(collator, [[InitializedCollator]]). + auto* collator = 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 3, 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 collator's internal slot whose name is the Internal Slot value of the current row. + // c. If the current row has an Extension Key value, then + // i. Let extensionKey be the Extension Key value of the current row. + // ii. If %Collator%.[[RelevantExtensionKeys]] does not contain extensionKey, then + // 1. Let v be undefined. + // d. If v is not undefined, then + // i. Perform ! CreateDataPropertyOrThrow(options, p, v). + MUST(options->create_data_property_or_throw(vm.names.locale, js_string(vm, collator->locale()))); + MUST(options->create_data_property_or_throw(vm.names.usage, js_string(vm, collator->usage_string()))); + MUST(options->create_data_property_or_throw(vm.names.sensitivity, js_string(vm, collator->sensitivity_string()))); + MUST(options->create_data_property_or_throw(vm.names.ignorePunctuation, Value(collator->ignore_punctuation()))); + MUST(options->create_data_property_or_throw(vm.names.collation, js_string(vm, collator->collation()))); + MUST(options->create_data_property_or_throw(vm.names.numeric, Value(collator->numeric()))); + MUST(options->create_data_property_or_throw(vm.names.caseFirst, js_string(vm, collator->case_first_string()))); + + // 5. Return options. + return options; } } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/CollatorPrototype.h b/Userland/Libraries/LibJS/Runtime/Intl/CollatorPrototype.h index 24b3806b85..5dda42be89 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/CollatorPrototype.h +++ b/Userland/Libraries/LibJS/Runtime/Intl/CollatorPrototype.h @@ -18,6 +18,9 @@ public: explicit CollatorPrototype(GlobalObject&); virtual void initialize(GlobalObject&) override; virtual ~CollatorPrototype() override = default; + +private: + JS_DECLARE_NATIVE_FUNCTION(resolved_options); }; } diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/Collator/Collator.prototype.resolvedOptions.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/Collator/Collator.prototype.resolvedOptions.js new file mode 100644 index 0000000000..ef03056b62 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/Collator/Collator.prototype.resolvedOptions.js @@ -0,0 +1,108 @@ +describe("correct behavior", () => { + test("length is 0", () => { + expect(Intl.Collator.prototype.resolvedOptions).toHaveLength(0); + }); + + test("locale only contains relevant extension keys", () => { + const en1 = new Intl.Collator("en-u-ca-islamicc"); + expect(en1.resolvedOptions().locale).toBe("en"); + + const en2 = new Intl.Collator("en-u-kf-upper"); + expect(en2.resolvedOptions().locale).toBe("en-u-kf-upper"); + + const en3 = new Intl.Collator("en-u-ca-islamicc-kf-upper"); + expect(en3.resolvedOptions().locale).toBe("en-u-kf-upper"); + }); + + test("usage", () => { + const en1 = new Intl.Collator("en"); + expect(en1.resolvedOptions().usage).toBe("sort"); + + ["sort", "search"].forEach(usage => { + const en2 = new Intl.Collator("en", { usage: usage }); + expect(en2.resolvedOptions().usage).toBe(usage); + }); + }); + + test("sensitivity", () => { + const en1 = new Intl.Collator("en"); + expect(en1.resolvedOptions().sensitivity).toBe("variant"); + + ["base", "accent", "case", "variant"].forEach(sensitivity => { + const en2 = new Intl.Collator("en", { sensitivity: sensitivity }); + expect(en2.resolvedOptions().sensitivity).toBe(sensitivity); + }); + }); + + test("ignorePunctuation", () => { + const en1 = new Intl.Collator("en"); + expect(en1.resolvedOptions().ignorePunctuation).toBeFalse(); + + [true, false].forEach(ignorePunctuation => { + const en2 = new Intl.Collator("en", { ignorePunctuation: ignorePunctuation }); + expect(en2.resolvedOptions().ignorePunctuation).toBe(ignorePunctuation); + }); + }); + + test("collation", () => { + // Only "default" collation is parsed for now. + const en = new Intl.Collator("en"); + expect(en.resolvedOptions().collation).toBe("default"); + + const el = new Intl.Collator("el", { collation: "foo" }); + expect(el.resolvedOptions().collation).toBe("default"); + }); + + test("numeric may be set by locale extension", () => { + const en = new Intl.Collator("en-u-kn"); + expect(en.resolvedOptions().numeric).toBeTrue(); + + const el = new Intl.Collator("el-u-kn-false"); + expect(el.resolvedOptions().numeric).toBeFalse(); + }); + + test("numeric option overrides locale extension", () => { + const el = new Intl.Collator("el-u-kn", { numeric: false }); + expect(el.resolvedOptions().numeric).toBeFalse(); + }); + + test("numeric option limited to known 'kn' values", () => { + ["true", "foo"].forEach(numeric => { + const en = new Intl.Collator(`en-u-kn-${numeric}`); + expect(en.resolvedOptions().numeric).toBeTrue(); + }); + + ["true", "foo"].forEach(numeric => { + const el = new Intl.Collator(`el-u-kn-${numeric}`); + expect(el.resolvedOptions().numeric).toBeTrue(); + }); + }); + + test("caseFirst may be set by locale extension", () => { + const en = Intl.Collator("en-u-kf-upper"); + expect(en.resolvedOptions().caseFirst).toBe("upper"); + + const el = Intl.Collator("el-u-kf-lower"); + expect(el.resolvedOptions().caseFirst).toBe("lower"); + + const ar = Intl.Collator("ar-u-kf-false"); + expect(ar.resolvedOptions().caseFirst).toBe("false"); + }); + + test("caseFirst option overrides locale extension", () => { + const el = Intl.Collator("el-u-kf-upper", { caseFirst: "lower" }); + expect(el.resolvedOptions().caseFirst).toBe("lower"); + }); + + test("caseFirst option limited to known 'kf' values", () => { + ["upper", "foo"].forEach(caseFirst => { + const en = Intl.Collator(`en-u-kf-${caseFirst}`); + expect(en.resolvedOptions().caseFirst).toBe("upper"); + }); + + ["upper", "foo"].forEach(caseFirst => { + const el = Intl.Collator(`el-u-kf-${caseFirst}`); + expect(el.resolvedOptions().caseFirst).toBe("upper"); + }); + }); +});