Răsfoiți Sursa

Project BuiltInFunctionIDs to Intl.js and correctly respect the usage setting for Intl.Collator

Jack Horton (CHAKRA) 7 ani în urmă
părinte
comite
5cc1df9fa6

+ 7 - 0
lib/Runtime/Base/JnDirectFields.h

@@ -539,6 +539,12 @@ ENTRY(NumberFormatCurrencyDisplay)
 ENTRY(CollatorSensitivity)
 ENTRY(CollatorCaseFirst)
 ENTRY(LocaleDataKind)
+ENTRY(DateToLocaleString)
+ENTRY(DateToLocaleDateString)
+ENTRY(DateToLocaleTimeString)
+ENTRY(NumberToLocaleString)
+ENTRY(StringLocaleCompare)
+ENTRY(BuiltInFunctionID)
 
 // This symbol is not part of the regular Symbol API and is only used in rare circumstances in Intl.js for backwards compatibility
 // with the Intl v1 spec. It is visible to the user only using Object.getOwnPropertySymbols(Intl.NumberFormat.call(new Intl.NumberFormat())).
@@ -581,6 +587,7 @@ ENTRY(numeric)
 ENTRY(sensitivity)
 ENTRY(sensitivityEnum)
 ENTRY(caseFirstEnum)
+ENTRY(usage)
 
 ENTRY(DateTimeFormat)
 ENTRY(__boundFormat)

+ 29 - 43
lib/Runtime/Library/InJavascript/Intl.js

@@ -102,17 +102,6 @@
     var isFinite = platform.builtInGlobalObjectEntryIsFinite;
     var isNaN = platform.builtInGlobalObjectEntryIsNaN;
 
-    // Keep this "enum" in sync with IntlEngineInterfaceExtensionObject::EntryIntl_RegisterBuiltInFunction
-    const IntlBuiltInFunctionID = setPrototype({
-        MIN: 0,
-        DateToLocaleString: 0,
-        DateToLocaleDateString: 1,
-        DateToLocaleTimeString: 2,
-        NumberToLocaleString: 3,
-        StringLocaleCompare: 4,
-        MAX: 5
-    }, null);
-
     const _ = {
         toUpperCase(str) { return callInstanceFunc(StringInstanceToUpperCase, str); },
         toLowerCase(str) { return callInstanceFunc(StringInstanceToLowerCase, str); },
@@ -879,9 +868,16 @@
             const requestedLocales = CanonicalizeLocaleList(locales);
             options = options === undefined ? _.create() : Internal.ToObject(options);
 
+            // The spec says that usage dictates whether to use "[[SearchLocaleData]]" or "[[SortLocaleData]]"
+            // ICU has no concept of a difference between the two, and instead sort/search corresponds to
+            // collation = "standard" or collation = "search", respectively. Standard/sort is the default.
+            // Thus, when the usage is sort, we can accept and honor -u-co in the locale, while if usage is search,
+            // we are going to overwrite any -u-co value provided before giving the locale to ICU anyways.
+            // To make the logic simpler, we can simply pretend like we don't accept a -u-co value if the usage is search.
+            // See the lazy UCollator initialization in EntryIntl_LocaleCompare for where the collation value
+            // gets overwritten by "search".
             collator.usage = GetOption(options, "usage", "string", ["sort", "search"], "sort");
-            // TODO: determine the difference between sort and search locale data
-            // const collatorLocaleData = collator.usage === "sort" ? localeData : localeData;
+            const relevantExtensionKeys = collator.usage === "sort" ? ["co", "kn", "kf"] : ["kn", "kf"];
 
             const opt = _.create();
             opt.matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
@@ -889,9 +885,11 @@
             opt.kn = kn === undefined ? kn : Internal.ToString(kn);
             opt.kf = GetOption(options, "caseFirst", "string", ["upper", "lower", "false"], undefined);
 
-            const r = ResolveLocale(platform.isCollatorLocaleAvailable, requestedLocales, opt, ["co", "kn", "kf"]);
+            const r = ResolveLocale(platform.isCollatorLocaleAvailable, requestedLocales, opt, relevantExtensionKeys);
             collator.locale = r.locale;
-            collator.collation = r.co === null ? "default" : r.co;
+            // r.co is null when usage === "sort" and no -u-co is provided
+            // r.co is undefined when usage === "search", since relevantExtensionKeys doesn't even look for -co
+            collator.collation = r.co === null || r.co === undefined ? "default" : r.co;
             collator.numeric = r.kn === "true";
             collator.caseFirst = r.kf;
             collator.caseFirstEnum = platform.CollatorCaseFirst[collator.caseFirst];
@@ -932,7 +930,7 @@
             }
 
             return platform.localeCompare(thisStr, thatStr, stateObject, /* forStringPrototypeLocaleCompare */ true);
-        }), IntlBuiltInFunctionID.StringLocaleCompare);
+        }), platform.BuiltInFunctionID.StringLocaleCompare);
 
         // If we were only initializing Intl for String.prototype, don't initialize Intl.Collator
         if (InitType === "String") {
@@ -1128,7 +1126,7 @@
 
             const n = Internal.ToNumber(this);
             return platform.formatNumber(n, stateObject, /* toParts */ false, /* forNumberPrototypeToLocaleString */ true);
-        }), IntlBuiltInFunctionID.NumberToLocaleString);
+        }), platform.BuiltInFunctionID.NumberToLocaleString);
 
         if (InitType === "Number") {
             return;
@@ -1665,19 +1663,19 @@
             platform.registerBuiltInFunction(createPublicMethod(name, function date_toLocaleString_entryPoint(locales = undefined, options = undefined) {
                 return DateInstanceToLocaleStringImplementation.call(this, name, option1, option2, cacheSlot, locales, options);
             }), platformFunctionID);
-        })("Date.prototype.toLocaleString", "any", "all", __DateInstanceToLocaleStringDefaultCacheSlot.toLocaleString, IntlBuiltInFunctionID.DateToLocaleString);
+        })("Date.prototype.toLocaleString", "any", "all", __DateInstanceToLocaleStringDefaultCacheSlot.toLocaleString, platform.BuiltInFunctionID.DateToLocaleString);
 
         (function (name, option1, option2, cacheSlot, platformFunctionID) {
             platform.registerBuiltInFunction(createPublicMethod(name, function date_toLocaleDateString_entryPoint(locales = undefined, options = undefined) {
                 return DateInstanceToLocaleStringImplementation.call(this, name, option1, option2, cacheSlot, locales, options);
             }), platformFunctionID);
-        })("Date.prototype.toLocaleDateString", "date", "date", __DateInstanceToLocaleStringDefaultCacheSlot.toLocaleDateString, IntlBuiltInFunctionID.DateToLocaleDateString);
+        })("Date.prototype.toLocaleDateString", "date", "date", __DateInstanceToLocaleStringDefaultCacheSlot.toLocaleDateString, platform.BuiltInFunctionID.DateToLocaleDateString);
 
         (function (name, option1, option2, cacheSlot, platformFunctionID) {
             platform.registerBuiltInFunction(createPublicMethod(name, function date_toLocaleTimeString_entryPoint(locales = undefined, options = undefined) {
                 return DateInstanceToLocaleStringImplementation.call(this, name, option1, option2, cacheSlot, locales, options);
             }), platformFunctionID);
-        })("Date.prototype.toLocaleTimeString", "time", "time", __DateInstanceToLocaleStringDefaultCacheSlot.toLocaleTimeString, IntlBuiltInFunctionID.DateToLocaleTimeString);
+        })("Date.prototype.toLocaleTimeString", "time", "time", __DateInstanceToLocaleStringDefaultCacheSlot.toLocaleTimeString, platform.BuiltInFunctionID.DateToLocaleTimeString);
 
         // if we were only initializing Date, dont bother initializing Intl.DateTimeFormat
         if (InitType !== "Intl") {
@@ -2837,12 +2835,12 @@
                     thisArg,
                     that,
                     stateObject.__localeForCompare,
-                    toEnum(CollatorSensitivity, stateObject.__sensitivity),
+                    platform.CollatorSensitivity[stateObject.__sensitivity],
                     stateObject.__ignorePunctuation,
                     stateObject.__numeric,
-                    toEnum(CollatorCaseFirst, stateObject.__caseFirst)
+                    platform.CollatorCaseFirst[stateObject.__caseFirst]
                 ));
-            }), IntlBuiltInFunctionID.StringLocaleCompare);
+            }), platform.BuiltInFunctionID.StringLocaleCompare);
 
             if (InitType === 'Intl') {
 
@@ -2890,10 +2888,10 @@
                         a,
                         b,
                         hiddenObject.__localeForCompare,
-                        toEnum(CollatorSensitivity, hiddenObject.__sensitivity),
+                        platform.CollatorSensitivity[hiddenObject.__sensitivity],
                         hiddenObject.__ignorePunctuation,
                         hiddenObject.__numeric,
-                        toEnum(CollatorCaseFirst, hiddenObject.__caseFirst)
+                        platform.CollatorCaseFirst[hiddenObject.__caseFirst]
                     ));
                 }
                 tagPublicFunction("Intl.Collator.prototype.compare", compare);
@@ -2973,12 +2971,7 @@
                 var matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
                 var style = GetOption(options, "style", "string", ["decimal", "percent", "currency"], "decimal");
 
-                var formatterToUse = NumberFormatStyle.DECIMAL; // DEFAULT
-                if (style === 'percent') {
-                    formatterToUse = NumberFormatStyle.PERCENT;
-                } else if (style === 'currency') {
-                    formatterToUse = NumberFormatStyle.CURRENCY;
-                }
+                var formatterToUse = platform.NumberFormatStyle[style];
 
                 var currency = GetOption(options, "currency", "string", undefined, undefined);
                 var currencyDisplay = GetOption(options, 'currencyDisplay', 'string', ['code', 'symbol', 'name'], 'symbol');
@@ -3045,14 +3038,7 @@
 
                 if (currencyDisplay !== undefined) {
                     numberFormat.__currencyDisplay = currencyDisplay;
-                    numberFormat.__currencyDisplayToUse = NumberFormatCurrencyDisplay.DEFAULT;
-                    if (currencyDisplay === "symbol") {
-                        numberFormat.__currencyDisplayToUse = NumberFormatCurrencyDisplay.SYMBOL;
-                    } else if (currencyDisplay === "code") {
-                        numberFormat.__currencyDisplayToUse = NumberFormatCurrencyDisplay.CODE;
-                    } else if (currencyDisplay === "name") {
-                        numberFormat.__currencyDisplayToUse = NumberFormatCurrencyDisplay.NAME;
-                    }
+                    numberFormat.__currencyDisplayToUse = platform.NumberFormatCurrencyDisplay[currencyDisplay];
                 }
 
                 numberFormat.__minimumIntegerDigits = minimumIntegerDigits;
@@ -3096,7 +3082,7 @@
 
                 var n = Internal.ToNumber(this);
                 return String(platform.formatNumber(n, stateObject));
-            }), IntlBuiltInFunctionID.NumberToLocaleString);
+            }), platform.BuiltInFunctionID.NumberToLocaleString);
 
             if (InitType === 'Intl') {
                 function NumberFormat(locales = undefined, options = undefined) {
@@ -3618,19 +3604,19 @@
                 platform.registerBuiltInFunction(tagPublicFunction(name, function date_toLocaleString_entryPoint(locales = undefined, options = undefined) {
                     return DateInstanceToLocaleStringImplementation.call(this, name, option1, option2, cacheSlot, locales, options);
                 }), platformFunctionID);
-            })("Date.prototype.toLocaleString", "any", "all", __DateInstanceToLocaleStringDefaultCacheSlot.toLocaleString, IntlBuiltInFunctionID.DateToLocaleString);
+            })("Date.prototype.toLocaleString", "any", "all", __DateInstanceToLocaleStringDefaultCacheSlot.toLocaleString, platform.BuiltInFunctionID.DateToLocaleString);
 
             (function (name, option1, option2, cacheSlot, platformFunctionID) {
                 platform.registerBuiltInFunction(tagPublicFunction(name, function date_toLocaleDateString_entryPoint(locales = undefined, options = undefined) {
                     return DateInstanceToLocaleStringImplementation.call(this, name, option1, option2, cacheSlot, locales, options);
                 }), platformFunctionID);
-            })("Date.prototype.toLocaleDateString", "date", "date", __DateInstanceToLocaleStringDefaultCacheSlot.toLocaleDateString, IntlBuiltInFunctionID.DateToLocaleDateString);
+            })("Date.prototype.toLocaleDateString", "date", "date", __DateInstanceToLocaleStringDefaultCacheSlot.toLocaleDateString, platform.BuiltInFunctionID.DateToLocaleDateString);
 
             (function (name, option1, option2, cacheSlot, platformFunctionID) {
                 platform.registerBuiltInFunction(tagPublicFunction(name, function date_toLocaleTimeString_entryPoint(locales = undefined, options = undefined) {
                     return DateInstanceToLocaleStringImplementation.call(this, name, option1, option2, cacheSlot, locales, options);
                 }), platformFunctionID);
-            })("Date.prototype.toLocaleTimeString", "time", "time", __DateInstanceToLocaleStringDefaultCacheSlot.toLocaleTimeString, IntlBuiltInFunctionID.DateToLocaleTimeString);
+            })("Date.prototype.toLocaleTimeString", "time", "time", __DateInstanceToLocaleStringDefaultCacheSlot.toLocaleTimeString, platform.BuiltInFunctionID.DateToLocaleTimeString);
 
             if (InitType === 'Intl') {
                 function DateTimeFormat(locales = undefined, options = undefined) {

+ 49 - 31
lib/Runtime/Library/IntlEngineInterfaceExtensionObject.cpp

@@ -90,6 +90,14 @@ VALUE(Calendar, ca, 3) \
 VALUE(NumberingSystem, nu, 4) \
 VALUE(HourCycle, hc, 5)
 
+//BuiltInFunctionID intentionally has no Default value
+#define BUILTINFUNCTIONID_VALUES(VALUE) \
+VALUE(DateToLocaleString, DateToLocaleString, 0) \
+VALUE(DateToLocaleDateString, DateToLocaleDateString, 1) \
+VALUE(DateToLocaleTimeString, DateToLocaleTimeString, 2) \
+VALUE(NumberToLocaleString, NumberToLocaleString, 3) \
+VALUE(StringLocaleCompare, StringLocaleCompare, 4)
+
 #define ENUM_VALUE(enumName, propId, value) enumName = value,
 #define PROJECTED_ENUM(ClassName, VALUES) \
 enum class ClassName \
@@ -103,7 +111,8 @@ PROJECT(LocaleDataKind, LOCALEDATAKIND_VALUES) \
 PROJECT(CollatorCaseFirst, COLLATORCASEFIRST_VALUES) \
 PROJECT(CollatorSensitivity, COLLATORSENSITIVITY_VALUES) \
 PROJECT(NumberFormatCurrencyDisplay, NUMBERFORMATCURRENCYDISPLAY_VALUES) \
-PROJECT(NumberFormatStyle, NUMBERFORMATSTYLE_VALUES)
+PROJECT(NumberFormatStyle, NUMBERFORMATSTYLE_VALUES) \
+PROJECT(BuiltInFunctionID, BUILTINFUNCTIONID_VALUES)
 
 PROJECTED_ENUMS(PROJECTED_ENUM)
 
@@ -450,7 +459,7 @@ namespace Js
     template <size_t N>
     static void LangtagToLocaleID(_In_ JavascriptString *langtag, _Out_ char(&localeID)[N])
     {
-        LangtagToLocaleID(langtag->GetSz(), langtag->GetLength(), localeID);
+        LangtagToLocaleID(langtag->GetString(), langtag->GetLength(), localeID);
     }
 
     template <typename Callback>
@@ -552,17 +561,27 @@ namespace Js
 
     bool IntlEngineInterfaceExtensionObject::InitializeIntlNativeInterfaces(DynamicObject* intlNativeInterfaces, DeferredTypeHandlerBase * typeHandler, DeferredInitializeMode mode)
     {
-        typeHandler->Convert(intlNativeInterfaces, mode, 16);
+        int initSlotCapacity = 0;
+
+        // automatically get the initSlotCapacity from everything we are about to add to intlNativeInterfaces
+#define INTL_ENTRY(id, func) initSlotCapacity++;
+#include "IntlExtensionObjectBuiltIns.h"
+#undef INTL_ENTRY
+
+#define PROJECTED_ENUM(ClassName, VALUES) initSlotCapacity++;
+PROJECTED_ENUMS(PROJECTED_ENUM)
+#undef PROJECTED_ENUM
+
+        // add capacity for platform.winglob and platform.FallbackSymbol
+        initSlotCapacity += 2;
+
+        typeHandler->Convert(intlNativeInterfaces, mode, initSlotCapacity);
 
         ScriptContext* scriptContext = intlNativeInterfaces->GetScriptContext();
         JavascriptLibrary* library = scriptContext->GetLibrary();
 
 // gives each entrypoint a property ID on the intlNativeInterfaces library object
-#ifdef INTL_ENTRY
-#undef INTL_ENTRY
-#endif
-#define INTL_ENTRY(id, func) \
-    library->AddFunctionToLibraryObject(intlNativeInterfaces, Js::PropertyIds::##id, &IntlEngineInterfaceExtensionObject::EntryInfo::Intl_##func, 1);
+#define INTL_ENTRY(id, func) library->AddFunctionToLibraryObject(intlNativeInterfaces, Js::PropertyIds::##id, &IntlEngineInterfaceExtensionObject::EntryInfo::Intl_##func, 1);
 #include "IntlExtensionObjectBuiltIns.h"
 #undef INTL_ENTRY
 
@@ -571,7 +590,7 @@ namespace Js
         DynamicObject * enumObj = nullptr;
 
 // Projects the exact layout of our C++ enums into Intl.js so that we dont have to remember to keep them in sync
-#define ENUM_VALUE(enumName, unicodeKey, value) library->AddMember(enumObj, PropertyIds::##unicodeKey, JavascriptNumber::ToVar(value, scriptContext));
+#define ENUM_VALUE(enumName, propId, value) library->AddMember(enumObj, PropertyIds::##propId, JavascriptNumber::ToVar(value, scriptContext));
 #define PROJECTED_ENUM(ClassName, VALUES) \
     enumObj = library->CreateObject(); \
     VALUES(ENUM_VALUE) \
@@ -1113,11 +1132,13 @@ DEFINE_ISXLOCALEAVAILABLE(PR, uloc)
         if (kind == LocaleDataKind::Collation)
         {
             ScopedUEnumeration collations(ucol_getKeywordValuesForLocale("collation", localeID, false, &status));
-            ICU_ASSERT(status, true);
+            int collationsCount = uenum_count(collations, &status);
+
+            // we expect collationsCount to have at least "standard" and "search" in it
+            ICU_ASSERT(status, collationsCount > 2);
 
             // the return array can't include "standard" and "search", but must have its first element be null (count - 2 + 1) [#sec-intl-collator-internal-slots]
-            ret = library->CreateArray(uenum_count(collations, &status) - 1);
-            ICU_ASSERT(status, true);
+            ret = library->CreateArray(collationsCount - 1);
             ret->SetItem(0, library->GetNull(), flag);
 
             int collationLen = 0;
@@ -1137,7 +1158,7 @@ DEFINE_ISXLOCALEAVAILABLE(PR, uloc)
                 const size_t unicodeCollationLen = strlen(unicodeCollation);
 
                 // we only need strlen(unicodeCollation) + 1 char16s because unicodeCollation will always be ASCII (funnily enough)
-                char16 *unicodeCollation16 = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), char16, strlen(unicodeCollation) + 1);
+                char16 *unicodeCollation16 = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), char16, unicodeCollationLen + 1);
                 charcount_t unicodeCollation16Len = 0;
                 HRESULT hr = utf8::NarrowStringToWideNoAlloc(
                     unicodeCollation,
@@ -1862,10 +1883,18 @@ DEFINE_ISXLOCALEAVAILABLE(PR, uloc)
             bool ignorePunctuation = AssertBooleanProperty(state, PropertyIds::ignorePunctuation);
             bool numeric = AssertBooleanProperty(state, PropertyIds::numeric);
             CollatorCaseFirst caseFirst = AssertEnumProperty<CollatorCaseFirst>(state, PropertyIds::caseFirstEnum);
+            JavascriptString *usage = AssertStringProperty(state, PropertyIds::usage);
 
             char localeID[ULOC_FULLNAME_CAPACITY] = { 0 };
             LangtagToLocaleID(langtag, localeID);
 
+            const char16 searchString[] = _u("search");
+            if (usage->BufferEquals(searchString, _countof(searchString) - 1)) // minus the null terminator
+            {
+                uloc_setKeywordValue("collation", "search", localeID, _countof(localeID), &status);
+                ICU_ASSERT(status, true);
+            }
+
             coll = FinalizableUCollator::New(scriptContext->GetRecycler(), ucol_open(localeID, &status));
             ICU_ASSERT(status, true);
 
@@ -3140,17 +3169,6 @@ DEFINE_ISXLOCALEAVAILABLE(PR, uloc)
     */
     Var IntlEngineInterfaceExtensionObject::EntryIntl_RegisterBuiltInFunction(RecyclableObject* function, CallInfo callInfo, ...)
     {
-        // Don't put this in a header or add it to the namespace even in this file. Keep it to the minimum scope needed.
-        enum class IntlBuiltInFunctionID : int32 {
-            Min = 0,
-            DateToLocaleString = Min,
-            DateToLocaleDateString,
-            DateToLocaleTimeString,
-            NumberToLocaleString,
-            StringLocaleCompare,
-            Max
-        };
-
         EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
 
         // This function will only be used during the construction of the Intl object, hence Asserts are in place.
@@ -3158,27 +3176,27 @@ DEFINE_ISXLOCALEAVAILABLE(PR, uloc)
 
         JavascriptFunction *func = JavascriptFunction::FromVar(args.Values[1]);
         int32 id = TaggedInt::ToInt32(args.Values[2]);
-        Assert(id >= (int32)IntlBuiltInFunctionID::Min && id < (int32)IntlBuiltInFunctionID::Max);
+        Assert(id >= 0 && id < (int32)BuiltInFunctionID::Max);
 
         EngineInterfaceObject* nativeEngineInterfaceObj = scriptContext->GetLibrary()->GetEngineInterfaceObject();
         IntlEngineInterfaceExtensionObject* extensionObject = static_cast<IntlEngineInterfaceExtensionObject*>(nativeEngineInterfaceObj->GetEngineExtension(EngineInterfaceExtensionKind_Intl));
 
-        IntlBuiltInFunctionID functionID = static_cast<IntlBuiltInFunctionID>(id);
+        BuiltInFunctionID functionID = static_cast<BuiltInFunctionID>(id);
         switch (functionID)
         {
-        case IntlBuiltInFunctionID::DateToLocaleString:
+        case BuiltInFunctionID::DateToLocaleString:
             extensionObject->dateToLocaleString = func;
             break;
-        case IntlBuiltInFunctionID::DateToLocaleDateString:
+        case BuiltInFunctionID::DateToLocaleDateString:
             extensionObject->dateToLocaleDateString = func;
             break;
-        case IntlBuiltInFunctionID::DateToLocaleTimeString:
+        case BuiltInFunctionID::DateToLocaleTimeString:
             extensionObject->dateToLocaleTimeString = func;
             break;
-        case IntlBuiltInFunctionID::NumberToLocaleString:
+        case BuiltInFunctionID::NumberToLocaleString:
             extensionObject->numberToLocaleString = func;
             break;
-        case IntlBuiltInFunctionID::StringLocaleCompare:
+        case BuiltInFunctionID::StringLocaleCompare:
             extensionObject->stringLocaleCompare = func;
             break;
         default:

+ 22 - 0
test/Intl/Collator.js

@@ -188,5 +188,27 @@ testRunner.runTests([
                 assert.areEqual(0, coll.compare("" + test, test), `${test} did not compare equal to itself using Collator.prototype.compare`);
             });
         }
+    },
+    {
+        name: "Usage option should be respected",
+        body() {
+            if (WScript.Platform.INTL_LIBRARY === "winglob") {
+                return;
+            }
+
+            function test(locale, usage, expectedLocale, expectedUsage, expectedCollation, expectedArray) {
+                const input = ["AE", "A", "B", "Ä"];
+                const collator = new Intl.Collator(locale, { usage });
+                assert.areEqual(expectedLocale, collator.resolvedOptions().locale);
+                assert.areEqual(expectedUsage, collator.resolvedOptions().usage);
+                assert.areEqual(expectedCollation, collator.resolvedOptions().collation);
+                assert.areEqual(expectedArray, input.sort(collator.compare).join(","));
+            }
+
+            test("de", "sort", "de", "sort", "default", "A,Ä,AE,B");
+            test("de", "search", "de", "search", "default", "A,AE,Ä,B");
+            test("de-u-co-phonebk", "sort", "de-u-co-phonebk", "sort", "phonebk", "A,AE,Ä,B");
+            test("de-u-co-phonebk", "search", "de", "search", "default", "A,AE,Ä,B");
+        }
     }
 ], { verbose: !WScript.Arguments.includes("summary") });