Przeglądaj źródła

[MERGE #4832 @jackhorton] Update PlatformAgnostic case conversion functions to allow expanding-length strings

Merge pull request #4832 from jackhorton:icu/tocase

Fixes #421
Fixes #4526

Also fixes a previously untracked? bug where String.prototype.toLocale{Upper|Lower}Case.call(null) would print "null" rather than throwing a TypeError.
Jack Horton 8 lat temu
rodzic
commit
adf0ecb899

+ 46 - 153
lib/Runtime/Library/JavascriptString.cpp

@@ -2125,7 +2125,10 @@ case_2:
 
         Assert(!(callInfo.Flags & CallFlags_New));
 
-        return ToLocaleCaseHelper(args[0], false, scriptContext);
+        JavascriptString * pThis = nullptr;
+        GetThisStringArgument(args, scriptContext, _u("String.prototype.toLocaleLowerCase"), &pThis);
+
+        return ToLocaleCaseHelper<false /* toUpper */>(pThis);
     }
 
     Var JavascriptString::EntryToLocaleUpperCase(RecyclableObject* function, CallInfo callInfo, ...)
@@ -2137,9 +2140,20 @@ case_2:
 
         Assert(!(callInfo.Flags & CallFlags_New));
 
-        return ToLocaleCaseHelper(args[0], true, scriptContext);
+        JavascriptString * pThis = nullptr;
+        GetThisStringArgument(args, scriptContext, _u("String.prototype.toLocaleUpperCase"), &pThis);
+
+        return ToLocaleCaseHelper<true /* toUpper */>(pThis);
     }
 
+    template<bool toUpper>
+    JavascriptString* JavascriptString::ToLocaleCaseHelper(JavascriptString* pThis)
+    {
+        // TODO: implement locale-sensitive Intl versions of these functions
+        return ToCaseCore<toUpper, false>(pThis);
+    }
+
+
     Var JavascriptString::EntryToLowerCase(RecyclableObject* function, CallInfo callInfo, ...)
     {
         PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
@@ -2152,23 +2166,7 @@ case_2:
         JavascriptString * pThis = nullptr;
         GetThisStringArgument(args, scriptContext, _u("String.prototype.toLowerCase"), &pThis);
 
-        // Fast path for one character strings
-        if (pThis->GetLength() == 1)
-        {
-            char16 inChar = pThis->GetString()[0];
-            char16 outChar = inChar;
-#if DBG
-            DWORD converted =
-#endif
-                PlatformAgnostic::UnicodeText::ChangeStringCaseInPlace(
-                    PlatformAgnostic::UnicodeText::CaseFlags::CaseFlagsLower, &outChar, 1);
-
-            Assert(converted == 1);
-
-            return (inChar == outChar) ? pThis : scriptContext->GetLibrary()->GetCharStringCache().GetStringForChar(outChar);
-        }
-
-        return ToCaseCore(pThis, ToLower);
+        return ToCaseCore<false, true>(pThis);
     }
 
     Var JavascriptString::EntryToString(RecyclableObject* function, CallInfo callInfo, ...)
@@ -2216,113 +2214,50 @@ case_2:
         JavascriptString* pThis = nullptr;
         GetThisStringArgument(args, scriptContext, _u("String.prototype.toUpperCase"), &pThis);
 
-        // Fast path for one character strings
-        if (pThis->GetLength() == 1)
-        {
-            char16 inChar = pThis->GetString()[0];
-            char16 outChar = inChar;
-#if DBG
-            DWORD converted =
-#endif
-                PlatformAgnostic::UnicodeText::ChangeStringCaseInPlace(
-                    PlatformAgnostic::UnicodeText::CaseFlags::CaseFlagsUpper, &outChar, 1);
-
-            Assert(converted == 1);
-
-            return (inChar == outChar) ? pThis : scriptContext->GetLibrary()->GetCharStringCache().GetStringForChar(outChar);
-        }
-
-        return ToCaseCore(pThis, ToUpper);
+        return ToCaseCore<true, true>(pThis);
     }
 
-    Var JavascriptString::ToCaseCore(JavascriptString* pThis, ToCase toCase)
+    template<bool toUpper, bool useInvariant>
+    JavascriptString* JavascriptString::ToCaseCore(JavascriptString* pThis)
     {
-        Var resultVar = nullptr;
-        EnterPinnedScope((volatile void**)& pThis);
-        charcount_t count = pThis->GetLength();
-
-        const char16* inStr = pThis->GetString();
-        const char16* inStrLim = inStr + count;
-        const char16* i = inStr;
-
-        // Try to find out the chars that do not need casing (in the ASCII range)
-        if (toCase == ToUpper)
-        {
-            while (i < inStrLim)
-            {
-                // first range of ascii lower-case (97-122)
-                // second range of ascii lower-case (223-255)
-                // non-ascii chars (255+)
-                if (*i >= 'a')
-                {
-                    if (*i <= 'z') { break; }
-                    if (*i >= 223) { break; }
-                }
-                i++;
-            }
-        }
-        else
+        using namespace PlatformAgnostic::UnicodeText;
+        if (pThis->GetLength() == 0)
         {
-            Assert(toCase == ToLower);
-            while (i < inStrLim)
-            {
-                // first range of ascii uppercase (65-90)
-                // second range of ascii uppercase (192-222)
-                // non-ascii chars (255+)
-                if (*i >= 'A')
-                {
-                    if (*i <= 'Z') { break; }
-                    if (*i >= 192)
-                    {
-                        if (*i < 223) { break; }
-                        if (*i >= 255) { break; }
-                    }
-                }
-                i++;
-            }
+            return pThis;
         }
 
-        // If no char needs casing, return immediately
-        if (i == inStrLim) { return pThis; }
+        ScriptContext* scriptContext = pThis->type->GetScriptContext();
 
-        // Otherwise, copy the string and start casing
-        charcount_t countToCase = (charcount_t)(inStrLim - i);
-        BufferStringBuilder builder(count, pThis->type->GetScriptContext());
-        char16 *outStr = builder.DangerousGetWritableBuffer();
+        ApiError error = ApiError::NoError;
 
-        char16* outStrLim = outStr + count;
-        char16 *o = outStr;
+        // pre-flight to get the length required, as it may be longer than the original string
+        // NOTE: ICU and Win32(/POSIX) implementations of these functions differ slightly in how to get the required number of characters.
+        // For Win32 (LCMapStringEx), you must provide nullptr/0, as providing a buffer that is too small will cause an error and will *not*
+        // report the number of characters required. For ICU, however, you can provide a buffer that is too short, and it will still return
+        // the length it actually needs.
+        // TODO: Investigate pre-allocating buffers for ICU, as the ICU case will be the default going forward
+        charcount_t requiredStringLength = ChangeStringLinguisticCase<toUpper, useInvariant>(pThis->GetSz(), pThis->GetLength(), nullptr, 0, &error);
+        Assert(error == ApiError::NoError || error == ApiError::InsufficientBuffer);
 
-        while (o < outStrLim)
-        {
-            *o++ = *inStr++;
-        }
+        // REVIEW: this assert may be too defensive if strings can get shorter through upper/lower casing
+        Assert(requiredStringLength >= pThis->GetLength());
 
-        if (toCase == ToUpper)
+        if (requiredStringLength == 1)
         {
-#if DBG
-            DWORD converted =
-#endif
-                PlatformAgnostic::UnicodeText::ChangeStringCaseInPlace(
-                    PlatformAgnostic::UnicodeText::CaseFlags::CaseFlagsUpper, outStrLim - countToCase, countToCase);
-
-            Assert(converted == countToCase);
+            // Fast path for one-char strings
+            char16 buffer[2] = { pThis->GetSz()[0], 0 };
+            charcount_t actualStringLength = ChangeStringLinguisticCase<toUpper, useInvariant>(pThis->GetSz(), pThis->GetLength(), buffer, 2, &error);
+            Assert(actualStringLength == 1 && error == ApiError::NoError);
+            return scriptContext->GetLibrary()->GetCharStringCache().GetStringForChar(buffer[0]);
         }
         else
         {
-            Assert(toCase == ToLower);
-#if DBG
-            DWORD converted =
-#endif
-                PlatformAgnostic::UnicodeText::ChangeStringCaseInPlace(
-                    PlatformAgnostic::UnicodeText::CaseFlags::CaseFlagsLower, outStrLim - countToCase, countToCase);
-
-            Assert(converted == countToCase);
+            charcount_t bufferLength = requiredStringLength + 1;
+            char16* buffer = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), char16, bufferLength);
+            charcount_t actualStringLength = ChangeStringLinguisticCase<toUpper, useInvariant>(pThis->GetSz(), pThis->GetLength(), buffer, bufferLength, &error);
+            Assert(actualStringLength == requiredStringLength && error == ApiError::NoError);
+            return JavascriptString::NewWithBuffer(buffer, actualStringLength, scriptContext);
         }
-        resultVar = builder.ToString();
-        LeavePinnedScope();     //  pThis
-
-        return resultVar;
     }
 
     Var JavascriptString::EntryTrim(RecyclableObject* function, CallInfo callInfo, ...)
@@ -3320,48 +3255,6 @@ case_2:
 
         return builder.ToString();
     }
-    Var JavascriptString::ToLocaleCaseHelper(Var thisObj, bool toUpper, ScriptContext *scriptContext)
-    {
-        using namespace PlatformAgnostic::UnicodeText;
-
-        JavascriptString * pThis = JavascriptOperators::TryFromVar<JavascriptString>(thisObj);
-
-        if (!pThis)
-        {
-            pThis = JavascriptConversion::ToString(thisObj, scriptContext);
-        }
-
-        uint32 strLength = pThis->GetLength();
-        if (strLength == 0)
-        {
-            return pThis;
-        }
-
-        // Get the number of chars in the mapped string.
-        CaseFlags caseFlags = (toUpper ? CaseFlags::CaseFlagsUpper : CaseFlags::CaseFlagsLower);
-        const char16* str = pThis->GetString();
-        ApiError err = ApiError::NoError;
-        int32 count = PlatformAgnostic::UnicodeText::ChangeStringLinguisticCase(caseFlags, str, strLength, nullptr, 0, &err);
-
-        if (count <= 0)
-        {
-            AssertMsg(err != ApiError::NoError, "LCMapString failed");
-            Throw::InternalError();
-        }
-
-        BufferStringBuilder builder(count, scriptContext);
-        char16* stringBuffer = builder.DangerousGetWritableBuffer();
-
-        int count1 = PlatformAgnostic::UnicodeText::ChangeStringLinguisticCase(caseFlags, str, strLength, stringBuffer, count, &err);
-
-        if (count1 <= 0)
-        {
-            AssertMsg(err != ApiError::NoError, "LCMapString failed");
-            Throw::InternalError();
-        }
-
-        return builder.ToString();
-    }
 
     int JavascriptString::IndexOfUsingJmpTable(JmpTable jmpTable, const char16* inputStr, charcount_t len, const char16* searchStr, int searchLen, int position)
     {

+ 4 - 6
lib/Runtime/Library/JavascriptString.h

@@ -135,13 +135,10 @@ namespace Js
         static int strcmp(JavascriptString *string1, JavascriptString *string2);
 
     private:
-        enum ToCase{
-            ToLower,
-            ToUpper
-        };
         char16* GetSzCopy();   // get a copy of the inner string without compacting the chunks
 
-        static Var ToCaseCore(JavascriptString* pThis, ToCase toCase);
+        template<bool toUpper, bool useInvariant>
+        static JavascriptString* ToCaseCore(JavascriptString* pThis);
         static int IndexOfUsingJmpTable(JmpTable jmpTable, const char16* inputStr, charcount_t len, const char16* searchStr, int searchLen, int position);
         static int LastIndexOfUsingJmpTable(JmpTable jmpTable, const char16* inputStr, charcount_t len, const char16* searchStr, charcount_t searchLen, charcount_t position);
 
@@ -331,7 +328,8 @@ namespace Js
         static void SearchValueHelper(ScriptContext* scriptContext, Var aValue, JavascriptRegExp ** ppSearchRegEx, JavascriptString ** ppSearchString);
         static void ReplaceValueHelper(ScriptContext* scriptContext, Var aValue, JavascriptFunction ** ppReplaceFn, JavascriptString ** ppReplaceString);
 
-        static Var ToLocaleCaseHelper(Var thisObj, bool toUpper, ScriptContext *scriptContext);
+        template<bool toUpper>
+        static JavascriptString* ToLocaleCaseHelper(JavascriptString* thisObj);
 
         static void InstantiateForceInlinedMembers();
 

+ 2 - 1
lib/Runtime/PlatformAgnostic/Chakra.Runtime.PlatformAgnostic.vcxproj

@@ -57,7 +57,8 @@
     <ClInclude Include="PerfTrace.h" />
     <ClInclude Include="RuntimePlatformAgnosticPch.h" />
     <ClInclude Include="UnicodeText.h" />
-    <ClInclude Include="ChakraICU.h" Condition="'$(UseICU)'=='true'" />
+    <ClInclude Include="ChakraICU.h" />
+    <ClInclude Include="UnicodeTextInternal.h" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\JITIDL\Chakra.JITIDL.vcxproj">

+ 1 - 3
lib/Runtime/PlatformAgnostic/Chakra.Runtime.PlatformAgnostic.vcxproj.filters

@@ -31,11 +31,9 @@
     <ClInclude Include="PerfTrace.h">
       <Filter>Interfaces</Filter>
     </ClInclude>
-    <ClInclude Include="IPlatformAgnosticResource.h">
-      <Filter>Interfaces</Filter>
-    </ClInclude>
     <ClInclude Include="ChakraICU.h">
       <Filter>Interfaces</Filter>
     </ClInclude>
+    <ClInclude Include="UnicodeTextInternal.h" />
   </ItemGroup>
 </Project>

+ 2 - 2
lib/Runtime/PlatformAgnostic/Platform/Common/UnicodeText.Common.cpp

@@ -44,13 +44,13 @@ namespace PlatformAgnostic
         namespace Internal
         {
             template <typename CharType>
-            inline bool isDigit(__in CharType c)
+            bool isDigit(__in CharType c)
             {
                 return c >= '0' && c <= '9';
             }
 
             template <typename CharType>
-            inline int readNumber(__inout CharType* &str)
+            int readNumber(__inout CharType* &str)
             {
                 int num = 0;
 

+ 31 - 47
lib/Runtime/PlatformAgnostic/Platform/Common/UnicodeText.ICU.cpp

@@ -5,6 +5,7 @@
 
 #include "RuntimePlatformAgnosticPch.h"
 #include "UnicodeText.h"
+#include "UnicodeTextInternal.h"
 #include "ChakraICU.h"
 
 namespace PlatformAgnostic
@@ -107,17 +108,20 @@ namespace PlatformAgnostic
         {
             switch (icuError)
             {
-                case U_BUFFER_OVERFLOW_ERROR:
-                    return ApiError::InsufficientBuffer;
-                case U_ILLEGAL_ARGUMENT_ERROR:
-                case U_UNSUPPORTED_ERROR:
-                    return ApiError::InvalidParameter;
-                case U_INVALID_CHAR_FOUND:
-                case U_TRUNCATED_CHAR_FOUND:
-                case U_ILLEGAL_CHAR_FOUND:
-                    return ApiError::InvalidUnicodeText;
-                default:
-                    return ApiError::UntranslatedError;
+            case U_ZERO_ERROR:
+                return ApiError::NoError;
+            case U_BUFFER_OVERFLOW_ERROR:
+            case U_STRING_NOT_TERMINATED_WARNING:
+                return ApiError::InsufficientBuffer;
+            case U_ILLEGAL_ARGUMENT_ERROR:
+            case U_UNSUPPORTED_ERROR:
+                return ApiError::InvalidParameter;
+            case U_INVALID_CHAR_FOUND:
+            case U_TRUNCATED_CHAR_FOUND:
+            case U_ILLEGAL_CHAR_FOUND:
+                return ApiError::InvalidUnicodeText;
+            default:
+                return ApiError::UntranslatedError;
             }
         }
 
@@ -224,55 +228,35 @@ namespace PlatformAgnostic
             return u_isUWhiteSpace(ch) == 1;
         }
 
-        int32 ChangeStringLinguisticCase(CaseFlags caseFlags, const char16* sourceString, uint32 sourceLength, char16* destString, uint32 destLength, ApiError* pErrorOut)
+        template<bool toUpper, bool useInvariant>
+        charcount_t ChangeStringLinguisticCase(const char16* sourceString, charcount_t sourceLength, char16* destString, charcount_t destLength, ApiError* pErrorOut)
         {
+            Assert(sourceString != nullptr && sourceLength > 0);
+            Assert(destString != nullptr || destLength == 0);
+
             int32_t resultStringLength = 0;
             UErrorCode errorCode = U_ZERO_ERROR;
+            *pErrorOut = ApiError::NoError;
+
+            // u_strTo treats nullptr as the system default locale and "" as root
+            const char* locale = useInvariant ? "" : nullptr;
 
-            if (caseFlags == CaseFlagsUpper)
+            if (toUpper)
             {
                 resultStringLength = u_strToUpper((UChar*) destString, destLength,
-                    (UChar*) sourceString, sourceLength, NULL, &errorCode);
-            }
-            else if (caseFlags == CaseFlagsLower)
-            {
-                resultStringLength = u_strToLower((UChar*) destString, destLength,
-                    (UChar*) sourceString, sourceLength, NULL, &errorCode);
+                    (UChar*) sourceString, sourceLength, locale, &errorCode);
             }
             else
             {
-                Assert(false);
-            }
-
-            if (U_FAILURE(errorCode) &&
-                !(destLength == 0 && errorCode == U_BUFFER_OVERFLOW_ERROR))
-            {
-                *pErrorOut = TranslateUErrorCode(errorCode);
-                return -1;
+                resultStringLength = u_strToLower((UChar*) destString, destLength,
+                    (UChar*) sourceString, sourceLength, locale, &errorCode);
             }
 
-            // Todo: check for resultStringLength > destLength
-            // Return insufficient buffer in that case
-            return resultStringLength;
-        }
-
-        uint32 ChangeStringCaseInPlace(CaseFlags caseFlags, char16* stringToChange, uint32 bufferLength)
-        {
-            // Assert pointers
-            Assert(stringToChange != nullptr);
-            ApiError error = NoError;
-
-            if (bufferLength == 0 || stringToChange == nullptr)
-            {
-                return 0;
-            }
+            AssertMsg(resultStringLength > 0, "u_strToCase must return required destString length");
 
-            int32 ret = ChangeStringLinguisticCase(caseFlags, stringToChange, bufferLength, stringToChange, bufferLength, &error);
+            *pErrorOut = TranslateUErrorCode(errorCode);
 
-            // Callers to this function don't expect any errors
-            Assert(error == ApiError::NoError);
-            Assert(ret > 0);
-            return (uint32) ret;
+            return static_cast<charcount_t>(resultStringLength);
         }
 
         bool IsIdStart(codepoint_t ch)

+ 8 - 29
lib/Runtime/PlatformAgnostic/Platform/POSIX/UnicodeText.cpp

@@ -5,6 +5,8 @@
 
 #include "RuntimePlatformAgnosticPch.h"
 #include "UnicodeText.h"
+#include "UnicodeTextInternal.h"
+
 #include <cctype>
 #include <string.h>
 #define IS_CHAR(ch) \
@@ -46,7 +48,8 @@ namespace PlatformAgnostic
             return true;
         }
 
-        int32 ChangeStringLinguisticCase(CaseFlags caseFlags, const char16* sourceString, uint32 sourceLength, char16* destString, uint32 destLength, ApiError* pErrorOut)
+        template<bool toUpper, bool useInvariant>
+        charcount_t ChangeStringLinguisticCase(const char16* sourceString, charcount_t sourceLength, char16* destString, charcount_t destLength, ApiError* pErrorOut)
         {
             typedef WCHAR(*CaseConversionFunc)(WCHAR);
             *pErrorOut = ApiError::NoError;
@@ -55,14 +58,14 @@ namespace PlatformAgnostic
                 return sourceLength;
             }
 
-            int32 len = (int32)minm(minm(destLength, sourceLength), INT_MAX);
-            CaseConversionFunc fnc = caseFlags == CaseFlagsLower ? PAL_towlower : PAL_towupper;
-            for (int32 i = 0; i < len; i++)
+            charcount_t len = static_cast<charcount_t>(minm(minm(destLength, sourceLength), MaxCharCount));
+            CaseConversionFunc fnc = toUpper ? PAL_towupper : PAL_towlower;
+            for (charcount_t i = 0; i < len; i++)
             {
                 destString[i] = fnc(sourceString[i]);
             }
 
-            return len;
+            return static_cast<charcount_t>(len);
         }
 
         bool IsWhitespace(codepoint_t ch)
@@ -120,30 +123,6 @@ namespace PlatformAgnostic
             }
         }
 
-        uint32 ChangeStringCaseInPlace(CaseFlags caseFlags, char16* stringToChange, uint32 bufferLength)
-        {
-            // ASCII only
-            typedef int (*CaseFlipper)(int);
-            CaseFlipper flipper;
-            if (caseFlags == CaseFlagsUpper)
-            {
-                flipper = toupper;
-            }
-            else
-            {
-                flipper = tolower;
-            }
-            for(uint32 i = 0; i < bufferLength; i++)
-            {
-                if (stringToChange[i] > 0 && stringToChange[i] < 127)
-                {
-                    char ch = (char)stringToChange[i];
-                    stringToChange[i] = flipper(ch);
-                }
-            }
-            return bufferLength;
-        }
-
         int LogicalStringCompare(const char16* string1, const char16* string2)
         {
             return PlatformAgnostic::UnicodeText::Internal::LogicalStringCompareImpl(string1, string2);

+ 28 - 35
lib/Runtime/PlatformAgnostic/Platform/Windows/UnicodeText.cpp

@@ -5,6 +5,7 @@
 
 #include "RuntimePlatformAgnosticPch.h"
 #include "UnicodeText.h"
+#include "UnicodeTextInternal.h"
 
 #include <windows.h>
 #include "Runtime.h"
@@ -295,54 +296,46 @@ namespace PlatformAgnostic
             return (::IsNormalizedString(TranslateToWin32NormForm(normalizationForm), (LPCWSTR)testString, testStringLength) == TRUE);
         }
 
-        int32 ChangeStringLinguisticCase(CaseFlags caseFlags, const char16* sourceString, uint32 sourceLength, char16* destString, uint32 destLength, ApiError* pErrorOut)
+        template<bool toUpper, bool useInvariant>
+        charcount_t ChangeStringLinguisticCase(const char16* sourceString, charcount_t sourceLength, char16* destString, charcount_t destLength, ApiError* pErrorOut)
         {
-            // Assert pointers
-            Assert(sourceString != nullptr);
+            Assert(sourceString != nullptr && sourceLength > 0);
             Assert(destString != nullptr || destLength == 0);
 
-            // LCMapString does not allow the source length to be set to 0
-            Assert(sourceLength > 0);
-
             *pErrorOut = NoError;
 
-            DWORD dwFlags = caseFlags == CaseFlags::CaseFlagsUpper ? LCMAP_UPPERCASE : LCMAP_LOWERCASE;
+            DWORD dwFlags = toUpper ? LCMAP_UPPERCASE : LCMAP_LOWERCASE;
             dwFlags |= LCMAP_LINGUISTIC_CASING;
 
-            LCID lcid = GetUserDefaultLCID();
-
-            int translatedStringLength = LCMapStringW(lcid, dwFlags, sourceString, sourceLength, destString, destLength);
-
-            if (translatedStringLength == 0)
-            {
-                *pErrorOut = TranslateWin32Error(::GetLastError());
-            }
-
-            Assert(translatedStringLength >= 0);
-            return (uint32) translatedStringLength;
-        }
-
-        uint32 ChangeStringCaseInPlace(CaseFlags caseFlags, char16* sourceString, uint32 sourceLength)
-        {
-            // Assert pointers
-            Assert(sourceString != nullptr);
-
-            if (sourceLength == 0 || sourceString == nullptr)
+            // REVIEW: The documentation for LCMapStringEx says that it returns "the number of characters or bytes in the translated string
+            // or sort key, including a terminating null character, if successful." However, in testing, this does not seem to be the case,
+            // as it always returns the count of characters without the null terminator.
+            // See https://msdn.microsoft.com/en-us/library/windows/desktop/dd318702(v=vs.85).aspx
+            int required = LCMapStringEx(
+                useInvariant ? LOCALE_NAME_INVARIANT : LOCALE_NAME_USER_DEFAULT,
+                dwFlags,
+                sourceString,
+                sourceLength,
+                destString,
+                destLength,
+                nullptr,
+                nullptr,
+                0
+            );
+
+            Assert(required >= 0);
+            if (destString != nullptr)
             {
-                return 0;
+                Assert(static_cast<charcount_t>(required) == destLength - 1);
+                destString[required] = 0;
             }
 
-            if (caseFlags == CaseFlagsUpper)
-            {
-                return (uint32) CharUpperBuff(sourceString, sourceLength);
-            }
-            else if (caseFlags == CaseFlagsLower)
+            if (required == 0)
             {
-                return (uint32) CharLowerBuff(sourceString, sourceLength);
+                *pErrorOut = TranslateWin32Error(::GetLastError());
             }
 
-            AssertMsg(false, "Invalid flags passed to ChangeStringCaseInPlace");
-            return 0;
+            return static_cast<charcount_t>(required);
         }
 
         UnicodeGeneralCategoryClass GetGeneralCategoryClass(codepoint_t codepoint)

+ 0 - 11
lib/Runtime/PlatformAgnostic/RuntimePlatformAgnosticPch.h

@@ -50,14 +50,3 @@ public:
 
 #include <Core/Assertions.h>
 #endif
-
-namespace PlatformAgnostic
-{
-    namespace UnicodeText
-    {
-         namespace Internal
-         {
-             int LogicalStringCompareImpl(const char16* p1, const char16* p2);
-         }
-    }
-}

+ 5 - 20
lib/Runtime/PlatformAgnostic/UnicodeText.h

@@ -6,6 +6,7 @@
 
 #include "Core/CommonTypedefs.h"
 #include "ChakraICU.h"
+#include "sal.h"
 
 namespace PlatformAgnostic
 {
@@ -195,33 +196,17 @@ namespace PlatformAgnostic
         //
         // Change the case of a string using linguistic rules
         // Params:
-        //   caseFlags: the case to convert to
         //   sourceString: The string to convert
-        //   sourceLength: The number of characters in the source string. This must be provided, the function does not assume null-termination etc. Length should be greater than 0.
+        //   sourceLength: The number of characters in the source string. This must be provided, the function does not assume null-termination. Length should be greater than 0.
         //   destString:   Optional pointer to the destination string buffer. It can be null if destLength is 0, if you want the required buffer size
         //   destLength:   Size in characters of the destination buffer, or 0 if the function shuld just return the required character count for the dest buffer.
         //   pErrorOut:    Set to NoError, or the actual error if one occurred.
         //
         // Return Value:
-        //   length of the translated string in the destination buffer
-        //   If the return value is less than or equal to 0, then see the value of pErrorOut to understand the error
-        //
-        int32 ChangeStringLinguisticCase(CaseFlags caseFlags, const char16* sourceString, uint32 sourceLength, char16* destString, uint32 destLength, ApiError* pErrorOut);
-
-        //
-        // Change the case of a string using linguistic rules
-        // The string is changed in place
-        //
-        // Params:
-        //   caseFlags: the case to convert to
-        //   sourceString: The string to convert
-        //   sourceLength: The number of characters in the source string. This must be provided, the function does not assume null-termination etc. Length should be greater than 0.
-        //
-        // Return Value:
-        //   length of the translated string in the destination buffer
-        //   If the return value is less than or equal to 0, then see the value of pErrorOut to understand the error
+        //   The length required to convert sourceString to the given case, even if destString was not large enough to hold it, including the null terminator
         //
-        uint32 ChangeStringCaseInPlace(CaseFlags caseFlags, char16* stringToChange, uint32 bufferLength);
+        template<bool toUpper, bool useInvariant>
+        charcount_t ChangeStringLinguisticCase(_In_count_(sourceLength) const char16* sourceString, _In_ charcount_t sourceLength, _Out_writes_(destLength) char16* destString, _In_ charcount_t destLength, _Out_ ApiError* pErrorOut);
 
         //
         // Return the classification type of the character using Unicode 2.0 rules

+ 25 - 0
lib/Runtime/PlatformAgnostic/UnicodeTextInternal.h

@@ -0,0 +1,25 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+#pragma once
+
+namespace PlatformAgnostic
+{
+namespace UnicodeText
+{
+
+// Instantiate templates here rather than in each implementing file
+template charcount_t ChangeStringLinguisticCase<true, true>(const char16* sourceString, charcount_t sourceLength, char16* destString, charcount_t destLength, ApiError* pErrorOut);
+template charcount_t ChangeStringLinguisticCase<true, false>(const char16* sourceString, charcount_t sourceLength, char16* destString, charcount_t destLength, ApiError* pErrorOut);
+template charcount_t ChangeStringLinguisticCase<false, true>(const char16* sourceString, charcount_t sourceLength, char16* destString, charcount_t destLength, ApiError* pErrorOut);
+template charcount_t ChangeStringLinguisticCase<false, false>(const char16* sourceString, charcount_t sourceLength, char16* destString, charcount_t destLength, ApiError* pErrorOut);
+
+namespace Internal
+{
+
+int LogicalStringCompareImpl(const char16* p1, const char16* p2);
+
+}; // namespace Internal
+}; // namespace UnicodeText
+}; // namespace PlatformAgnostic

+ 1 - 3
test/Strings/rlexe.xml

@@ -90,9 +90,7 @@
   </test>
   <test>
     <default>
-      <files>toLowerCase.js</files>
-      <baseline>toLowerCase.baseline</baseline>
-      <compile-flags>-Intl-</compile-flags>
+      <files>toCase.js</files>
     </default>
   </test>
   <test>

+ 104 - 0
test/Strings/toCase.js

@@ -0,0 +1,104 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
+
+function testASCII(lower, upper, message) {
+    const toUppers = [ String.prototype.toUpperCase, String.prototype.toLocaleUpperCase ];
+    const toLowers = [ String.prototype.toLowerCase, String.prototype.toLocaleLowerCase ];
+    for (const func of toUppers) {
+        assert.areEqual(upper, func.call(lower), `lower.${func.name}(): ${message}`);
+        assert.areEqual(upper, func.call(upper), `upper.${func.name}(): ${message}`);
+    }
+    for (const func of toLowers) {
+        assert.areEqual(lower, func.call(upper), `upper.${func.name}(): ${message}`);
+        assert.areEqual(lower, func.call(lower), `lower.${func.name}(): ${message}`);
+    }
+}
+
+testRunner.runTests([
+    {
+        name: "Visible ASCII characters",
+        body() {
+            testASCII("", "", "Empty string");
+
+            let i = 32;
+            let upper = "";
+            let lower = "";
+            for (; i < 65; i++) {
+                upper += String.fromCharCode(i);
+                lower += String.fromCharCode(i);
+            }
+            for (; i < 91; i++) {
+                upper += String.fromCharCode(i);
+            }
+            for (i = 97; i < 123; i++) {
+                lower += String.fromCharCode(i);
+            }
+            for (i = 91; i < 97; i++) {
+                upper += String.fromCharCode(i);
+                lower += String.fromCharCode(i);
+            }
+            for (i = 123; i < 127; i++) {
+                upper += String.fromCharCode(i);
+                lower += String.fromCharCode(i);
+            }
+
+            testASCII(lower, upper, "Visible ASCII");
+        }
+    },
+    {
+        name: "Special characters",
+        body() {
+            const specialCharacters = {
+                "\n": "newline",
+                "\t": "tab",
+                "\r": "carriage return",
+                "\0": "null",
+                "\"": "double quote",
+                "\'": "single quote",
+                "\b": "backspace",
+            };
+
+            for (const c in specialCharacters) {
+                testASCII(`${c}microsoft`, `${c}MICROSOFT`, `string with ${specialCharacters[c]} at the beginning`);
+                testASCII(`micro${c}soft`, `MICRO${c}SOFT`, `string with ${specialCharacters[c]} in the middle`);
+                testASCII(`microsoft${c}`, `MICROSOFT${c}`, `string with ${specialCharacters[c]} at the end`);
+            }
+        }
+    },
+    {
+        name: "Type conversion",
+        body() {
+            const convertible = [
+                [new Number(123), "123", "123"],
+                [new Boolean(true), "true", "TRUE"],
+                [new String("aBc"), "abc", "ABC"],
+                [new Object(), "[object object]", "[OBJECT OBJECT]"],
+                [["Chakra", 2018, true], "chakra,2018,true", "CHAKRA,2018,TRUE"],
+                [{ toString: () => "Hello" }, "hello", "HELLO"]
+            ];
+
+            for (const test of convertible) {
+                for (const func of [String.prototype.toLowerCase, String.prototype.toLocaleLowerCase]) {
+                    assert.areEqual(test[1], func.call(test[0]), `${func.name}: type conversion of ${test[0]} to ${test[1]}`);
+                }
+                for (const func of [String.prototype.toUpperCase, String.prototype.toLocaleUpperCase]) {
+                    assert.areEqual(test[2], func.call(test[0]), `${func.name}: type conversion of ${test[0]} to ${test[2]}`);
+                }
+            }
+        }
+    },
+    {
+        name: "Correct errors are thrown",
+        body() {
+            for (const badThis of [null, undefined]) {
+                for (const func of [String.prototype.toUpperCase, String.prototype.toLocaleUpperCase, String.prototype.toLowerCase, String.prototype.toLocaleLowerCase]) {
+                    assert.throws(() => func.call(badThis), TypeError, `${func.name}.call(${Object.prototype.toString.call(badThis)})`);
+                }
+            }
+        }
+    },
+], { verbose: false });

+ 0 - 2
test/Strings/toLowerCase.baseline

@@ -1,2 +0,0 @@
-done
-3

+ 0 - 116
test/Strings/toLowerCase.js

@@ -1,116 +0,0 @@
-//-------------------------------------------------------------------------------------------------------
-// Copyright (C) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
-//-------------------------------------------------------------------------------------------------------
-
-//String.prototype.toLowerCase()
-//TO DO  : Need to add Unicode and Upper Ascii Characters test cases and also Test cases that would throw exception(NaN and undefined Objects)
-
-var id=0;
-function verify(get_actual,get_expected,testid,testdesc)
-{
-
-    if(get_actual!=get_expected)
-        WScript.Echo(testid+":"+testdesc+"\t"+"failed"+"\n"+"got"+get_actual+"\t for\t"+get_expected)
-}
-
-//test 1
-
-verify("\tMICROSOFT".toLowerCase(), "\tmicrosoft", id++, "\"Testing Escape character tab\"")
-
-//test 2
-verify("\nMICROSOFT".toLowerCase(), "\nmicrosoft", id++, "\"Testing Escape character new line\"")
-
-//test3
-
-verify("\rMICROSOFT".toLowerCase(), "\rmicrosoft", id++, "\"Testing Escape character return \"")
-
-//test 4
-verify("\'MICROSOFT\'".toLowerCase(), "\'microsoft\'", id++, "\"Testing Escape character single quote\"")
-
-//test 5
-verify("MICROO\bSOFT".toLowerCase(), "microo\bsoft", id++, "\"Testing Escape character backspace\"")
-
-//test 6
-
-verify("\"MICROSOFT\"".toLowerCase(), "\"microsoft\"", id++, "\"Testing Escape character double quote\"")
-
-//test 7
-
-verify("microsoft".toLowerCase(), "microsoft", id++, "\"Testing passing lower case characters\"")
-
-//test 8
-verify("ABCDEFGHIJKLMNOPQRSTUVWXYZ".toLowerCase(), "abcdefghijklmnopqrstuvwxyz", id++, "\"Testing passing uppercase case characters\"")
-
-//test 9
-verify("(!@#$%^&*<,()+;:>?/)".toLowerCase(), "(!@#$%^&*<,()+;:>?/)", id++, "\" Testing passing Special Characters \"")
-
-//test 10
-
-verify("[email protected]".toLowerCase(), "[email protected]", id++, "\"Testing mix of characters eg email id\"");
-
-//test 11
-
-verify("ONEMICROSOFTWAY,156THNE31STPL,WA98054".toLowerCase(), "onemicrosoftway,156thne31stpl,wa98054", id++, "\"Testing mix of characters eg address\"");
-
-//test 12
-
-verify("1-800-CALL-HSBC".toLowerCase(), "1-800-call-hsbc", id++, id++, "\"Testing mix of characters eg phone number\" ");
-
-//test 13: Coercing Other Object types : Arrays
-
-var arr=new Array(3);
-arr[0]="JSCRIPT";
-arr[1]=12345;
-arr[2]="[email protected]";
-Array.prototype.toLowerCase=String.prototype.toLowerCase;  //the prototype method of string  can now be called from the array object
-verify(arr.toLowerCase(), "jscript,12345,[email protected]", id++, "\"Testing Coercible Objects eg Array\" ");
-
-//test 14 Coercing Other Object types : Number
-
-var num=new Number();
-num=12345
-Number.prototype.toLowerCase=String.prototype.toLowerCase;
-verify(num.toLowerCase(), "12345", id++, "\"Testing Coercible Objects eg Number\" ");
-
-//test 15 Coercing Other Object types : Boolean
-
-var mybool=new Boolean(false);
-Boolean.prototype.toLowerCase=String.prototype.toLowerCase;
-verify(mybool.toLowerCase(), "false", id++, "\"Testing Coercible Objects eg Boolean\" ");
-
-//test 16 Coercing Other Object types : Object
-
-var obj=new Object()
-Object.prototype.toLowerCase=String.prototype.toLowerCase;
-verify(obj.toLowerCase(), "[object object]", id++, "\"Testing Coercible Objects eg Object\" ");
-
-//Need to test for null and undefined but have to know the error message
-
-//test 17 Concatenated String
-
-verify(("CONCATENATED"+"STRING").toLowerCase(), "concatenatedstring", id++, "\" Testing Concatenated String\"");
-
-//test 18 Indirect Call through Function
-
-var Foo=function(){}
-Foo.prototype.test=function(){return "MYSTRING";}
-var fun=new Foo()
-verify(fun.test().toLowerCase(), "mystring", id++, "\"Testing indirect calling eg function\"")
-
-//test 19 Indirect call through property
-
-var myobj=new Object();
-myobj.prop="STRING";
-verify(myobj.prop.toLowerCase(), "string", id++, "\"Testing indirect calling eg property\"");
-
-WScript.Echo("done");
-
-//test 20 implicit calls
-var a = 1;
-var b = 2;
-var obj = {toString: function(){ a=3; return "Hello World";}};
-a = b;
-Object.prototype.toLowerCase = String.prototype.toLowerCase;
-var f = obj.toLowerCase();
-WScript.Echo (a);

+ 337 - 321
test/Strings/unicode_toUpperCase_toLowerCase.js

@@ -6,327 +6,343 @@
 WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
 
 var tests = [
-  {
-    name: "Deseret alphabet toUpperCase",
-    body: function () {
-        assert.areEqual("\uD801\uDC00", "\uD801\uDC28".toUpperCase(), "Expecting Deseret alphabet upper-case long I");
-        assert.areEqual("\uD801\uDC01", "\uD801\uDC29".toUpperCase(), "Expecting Deseret alphabet upper-case long E");
-        assert.areEqual("\uD801\uDC02", "\uD801\uDC2A".toUpperCase(), "Expecting Deseret alphabet upper-case long A");
-        assert.areEqual("\uD801\uDC03", "\uD801\uDC2B".toUpperCase(), "Expecting Deseret alphabet upper-case long Ah");
-        assert.areEqual("\uD801\uDC04", "\uD801\uDC2C".toUpperCase(), "Expecting Deseret alphabet upper-case long O");
-        assert.areEqual("\uD801\uDC05", "\uD801\uDC2D".toUpperCase(), "Expecting Deseret alphabet upper-case long Oo");
-        assert.areEqual("\uD801\uDC06", "\uD801\uDC2E".toUpperCase(), "Expecting Deseret alphabet upper-case short I");
-        assert.areEqual("\uD801\uDC07", "\uD801\uDC2F".toUpperCase(), "Expecting Deseret alphabet upper-case short E");
-        assert.areEqual("\uD801\uDC08", "\uD801\uDC30".toUpperCase(), "Expecting Deseret alphabet upper-case short A");
-        assert.areEqual("\uD801\uDC09", "\uD801\uDC31".toUpperCase(), "Expecting Deseret alphabet upper-case short Ah");
-        assert.areEqual("\uD801\uDC0A", "\uD801\uDC32".toUpperCase(), "Expecting Deseret alphabet upper-case short O");
-        assert.areEqual("\uD801\uDC0B", "\uD801\uDC33".toUpperCase(), "Expecting Deseret alphabet upper-case short Oo");
-        assert.areEqual("\uD801\uDC0C", "\uD801\uDC34".toUpperCase(), "Expecting Deseret alphabet upper-case Ay");
-        assert.areEqual("\uD801\uDC0D", "\uD801\uDC35".toUpperCase(), "Expecting Deseret alphabet upper-case Ow");
-        assert.areEqual("\uD801\uDC0E", "\uD801\uDC36".toUpperCase(), "Expecting Deseret alphabet upper-case Wu");
-        assert.areEqual("\uD801\uDC0F", "\uD801\uDC37".toUpperCase(), "Expecting Deseret alphabet upper-case Yee");
-        assert.areEqual("\uD801\uDC10", "\uD801\uDC38".toUpperCase(), "Expecting Deseret alphabet upper-case H");
-        assert.areEqual("\uD801\uDC11", "\uD801\uDC39".toUpperCase(), "Expecting Deseret alphabet upper-case Pee");
-        assert.areEqual("\uD801\uDC12", "\uD801\uDC3A".toUpperCase(), "Expecting Deseret alphabet upper-case Bee");
-        assert.areEqual("\uD801\uDC13", "\uD801\uDC3B".toUpperCase(), "Expecting Deseret alphabet upper-case Tee");
-        assert.areEqual("\uD801\uDC14", "\uD801\uDC3C".toUpperCase(), "Expecting Deseret alphabet upper-case Dee");
-        assert.areEqual("\uD801\uDC15", "\uD801\uDC3D".toUpperCase(), "Expecting Deseret alphabet upper-case Chee");
-        assert.areEqual("\uD801\uDC16", "\uD801\uDC3E".toUpperCase(), "Expecting Deseret alphabet upper-case Jee");
-        assert.areEqual("\uD801\uDC17", "\uD801\uDC3F".toUpperCase(), "Expecting Deseret alphabet upper-case Kay");
-        assert.areEqual("\uD801\uDC18", "\uD801\uDC40".toUpperCase(), "Expecting Deseret alphabet upper-case Gay");
-        assert.areEqual("\uD801\uDC19", "\uD801\uDC41".toUpperCase(), "Expecting Deseret alphabet upper-case Ef");
-        assert.areEqual("\uD801\uDC1A", "\uD801\uDC42".toUpperCase(), "Expecting Deseret alphabet upper-case Vee");
-        assert.areEqual("\uD801\uDC1B", "\uD801\uDC43".toUpperCase(), "Expecting Deseret alphabet upper-case Eth");
-        assert.areEqual("\uD801\uDC1C", "\uD801\uDC44".toUpperCase(), "Expecting Deseret alphabet upper-case Thee");
-        assert.areEqual("\uD801\uDC1D", "\uD801\uDC45".toUpperCase(), "Expecting Deseret alphabet upper-case Es");
-        assert.areEqual("\uD801\uDC1E", "\uD801\uDC46".toUpperCase(), "Expecting Deseret alphabet upper-case Zee");
-        assert.areEqual("\uD801\uDC1F", "\uD801\uDC47".toUpperCase(), "Expecting Deseret alphabet upper-case Esh");
-        assert.areEqual("\uD801\uDC20", "\uD801\uDC48".toUpperCase(), "Expecting Deseret alphabet upper-case Zhee");
-        assert.areEqual("\uD801\uDC21", "\uD801\uDC49".toUpperCase(), "Expecting Deseret alphabet upper-case Er");
-        assert.areEqual("\uD801\uDC22", "\uD801\uDC4A".toUpperCase(), "Expecting Deseret alphabet upper-case El");
-        assert.areEqual("\uD801\uDC23", "\uD801\uDC4B".toUpperCase(), "Expecting Deseret alphabet upper-case Em");
-        assert.areEqual("\uD801\uDC24", "\uD801\uDC4C".toUpperCase(), "Expecting Deseret alphabet upper-case En");
-        assert.areEqual("\uD801\uDC25", "\uD801\uDC4D".toUpperCase(), "Expecting Deseret alphabet upper-case Eng");
-        assert.areEqual("\uD801\uDC26", "\uD801\uDC4E".toUpperCase(), "Expecting Deseret alphabet upper-case Oi");
-        assert.areEqual("\uD801\uDC27", "\uD801\uDC4F".toUpperCase(), "Expecting Deseret alphabet upper-case Ew");
-    }
-  },
-  {
-    name: "Deseret alphabet toLowerCase",
-    body: function () {
-        assert.areEqual("\uD801\uDC28", "\uD801\uDC00".toLowerCase(), "Expecting Deseret alphabet lower-case long I");
-        assert.areEqual("\uD801\uDC29", "\uD801\uDC01".toLowerCase(), "Expecting Deseret alphabet lower-case long E");
-        assert.areEqual("\uD801\uDC2A", "\uD801\uDC02".toLowerCase(), "Expecting Deseret alphabet lower-case long A");
-        assert.areEqual("\uD801\uDC2B", "\uD801\uDC03".toLowerCase(), "Expecting Deseret alphabet lower-case long Ah");
-        assert.areEqual("\uD801\uDC2C", "\uD801\uDC04".toLowerCase(), "Expecting Deseret alphabet lower-case long O");
-        assert.areEqual("\uD801\uDC2D", "\uD801\uDC05".toLowerCase(), "Expecting Deseret alphabet lower-case long Oo");
-        assert.areEqual("\uD801\uDC2E", "\uD801\uDC06".toLowerCase(), "Expecting Deseret alphabet lower-case short I");
-        assert.areEqual("\uD801\uDC2F", "\uD801\uDC07".toLowerCase(), "Expecting Deseret alphabet lower-case short E");
-        assert.areEqual("\uD801\uDC30", "\uD801\uDC08".toLowerCase(), "Expecting Deseret alphabet lower-case short A");
-        assert.areEqual("\uD801\uDC31", "\uD801\uDC09".toLowerCase(), "Expecting Deseret alphabet lower-case short Ah");
-        assert.areEqual("\uD801\uDC32", "\uD801\uDC0A".toLowerCase(), "Expecting Deseret alphabet lower-case short O");
-        assert.areEqual("\uD801\uDC33", "\uD801\uDC0B".toLowerCase(), "Expecting Deseret alphabet lower-case short Oo");
-        assert.areEqual("\uD801\uDC34", "\uD801\uDC0C".toLowerCase(), "Expecting Deseret alphabet lower-case Ay");
-        assert.areEqual("\uD801\uDC35", "\uD801\uDC0D".toLowerCase(), "Expecting Deseret alphabet lower-case Ow");
-        assert.areEqual("\uD801\uDC36", "\uD801\uDC0E".toLowerCase(), "Expecting Deseret alphabet lower-case Wu");
-        assert.areEqual("\uD801\uDC37", "\uD801\uDC0F".toLowerCase(), "Expecting Deseret alphabet lower-case Yee");
-        assert.areEqual("\uD801\uDC38", "\uD801\uDC10".toLowerCase(), "Expecting Deseret alphabet lower-case H");
-        assert.areEqual("\uD801\uDC39", "\uD801\uDC11".toLowerCase(), "Expecting Deseret alphabet lower-case Pee");
-        assert.areEqual("\uD801\uDC3A", "\uD801\uDC12".toLowerCase(), "Expecting Deseret alphabet lower-case Bee");
-        assert.areEqual("\uD801\uDC3B", "\uD801\uDC13".toLowerCase(), "Expecting Deseret alphabet lower-case Tee");
-        assert.areEqual("\uD801\uDC3C", "\uD801\uDC14".toLowerCase(), "Expecting Deseret alphabet lower-case Dee");
-        assert.areEqual("\uD801\uDC3D", "\uD801\uDC15".toLowerCase(), "Expecting Deseret alphabet lower-case Chee");
-        assert.areEqual("\uD801\uDC3E", "\uD801\uDC16".toLowerCase(), "Expecting Deseret alphabet lower-case Jee");
-        assert.areEqual("\uD801\uDC3F", "\uD801\uDC17".toLowerCase(), "Expecting Deseret alphabet lower-case Kay");
-        assert.areEqual("\uD801\uDC40", "\uD801\uDC18".toLowerCase(), "Expecting Deseret alphabet lower-case Gay");
-        assert.areEqual("\uD801\uDC41", "\uD801\uDC19".toLowerCase(), "Expecting Deseret alphabet lower-case Ef");
-        assert.areEqual("\uD801\uDC42", "\uD801\uDC1A".toLowerCase(), "Expecting Deseret alphabet lower-case Vee");
-        assert.areEqual("\uD801\uDC43", "\uD801\uDC1B".toLowerCase(), "Expecting Deseret alphabet lower-case Eth");
-        assert.areEqual("\uD801\uDC44", "\uD801\uDC1C".toLowerCase(), "Expecting Deseret alphabet lower-case Thee");
-        assert.areEqual("\uD801\uDC45", "\uD801\uDC1D".toLowerCase(), "Expecting Deseret alphabet lower-case Es");
-        assert.areEqual("\uD801\uDC46", "\uD801\uDC1E".toLowerCase(), "Expecting Deseret alphabet lower-case Zee");
-        assert.areEqual("\uD801\uDC47", "\uD801\uDC1F".toLowerCase(), "Expecting Deseret alphabet lower-case Esh");
-        assert.areEqual("\uD801\uDC48", "\uD801\uDC20".toLowerCase(), "Expecting Deseret alphabet lower-case Zhee");
-        assert.areEqual("\uD801\uDC49", "\uD801\uDC21".toLowerCase(), "Expecting Deseret alphabet lower-case Er");
-        assert.areEqual("\uD801\uDC4A", "\uD801\uDC22".toLowerCase(), "Expecting Deseret alphabet lower-case El");
-        assert.areEqual("\uD801\uDC4B", "\uD801\uDC23".toLowerCase(), "Expecting Deseret alphabet lower-case Em");
-        assert.areEqual("\uD801\uDC4C", "\uD801\uDC24".toLowerCase(), "Expecting Deseret alphabet lower-case En");
-        assert.areEqual("\uD801\uDC4D", "\uD801\uDC25".toLowerCase(), "Expecting Deseret alphabet lower-case Eng");
-        assert.areEqual("\uD801\uDC4E", "\uD801\uDC26".toLowerCase(), "Expecting Deseret alphabet lower-case Oi");
-        assert.areEqual("\uD801\uDC4F", "\uD801\uDC27".toLowerCase(), "Expecting Deseret alphabet lower-case Ew");
-    }
-  },
-  {
-    name: "Special casing toUpperCase",
-    body: function () {
-        //assert.areEqual("\u0053\u0053", "\u00DF".toUpperCase(), "Expecting Latin lower-case sharp s"); // not yet implemented
-        assert.areEqual("\u0130", "\u0130".toUpperCase(), "Expecting Latin upper-case i with dot above");
-        //assert.areEqual("\u0046\u0046", "\uFB00".toUpperCase(), "Expecting Latin small ligature ff"); // not yet implemented
-        //assert.areEqual("\u0046\u0049", "\uFB01".toUpperCase(), "Expecting Latin small ligature fi"); // not yet implemented
-        //assert.areEqual("\u0046\u004C", "\uFB02".toUpperCase(), "Expecting Latin small ligature fl"); // not yet implemented
-        //assert.areEqual("\u0046\u0046\u0049", "\uFB03".toUpperCase(), "Expecting Latin small ligature ffi"); // not yet implemented
-        //assert.areEqual("\u0046\u0046\u004C", "\uFB04".toUpperCase(), "Expecting Latin small ligature ffl"); // not yet implemented
-        //assert.areEqual("\u0053\u0054", "\uFB05".toUpperCase(), "Expecting Latin small ligature long s t"); // not yet implemented
-        //assert.areEqual("\u0053\u0054", "\uFB06".toUpperCase(), "Expecting Latin small ligature st"); // not yet implemented
-        //assert.areEqual("\u0535\u0552", "\u0587".toUpperCase(), "Expecting Armenian small ligature ech yiwn"); // not yet implemented
-        //assert.areEqual("\u0544\u0546", "\uFB13".toUpperCase(), "Expecting Armenian small ligature men now"); // not yet implemented
-        //assert.areEqual("\u0544\u0535", "\uFB14".toUpperCase(), "Expecting Armenian small ligature men ech"); // not yet implemented
-        //assert.areEqual("\u0544\u053B", "\uFB15".toUpperCase(), "Expecting Armenian small ligature men ini"); // not yet implemented
-        //assert.areEqual("\u054E\u0546", "\uFB16".toUpperCase(), "Expecting Armenian small ligature vew now"); // not yet implemented
-        //assert.areEqual("\u0544\u053D", "\uFB17".toUpperCase(), "Expecting Armenian small ligature men xeh"); // not yet implemented
-        //assert.areEqual("\u02BC\u004E", "\u0149".toUpperCase(), "Expecting Latin lower-case n preceded by apostrophe"); // not yet implemented
-        //assert.areEqual("\u0399\u0308\u0301", "\u0390".toUpperCase(), "Expecting Greek lower-case iota with dialytika and tonos"); // not yet implemented
-        //assert.areEqual("\u03A5\u0308\u0301", "\u03B0".toUpperCase(), "Expecting Greek lower-case upsilon with dialytika and tonos"); // not yet implemented
-        //assert.areEqual("\u004A\u030C", "\u01F0".toUpperCase(), "Expecting Latin lower-case j with caron"); // not yet implemented
-        //assert.areEqual("\u0048\u0331", "\u1E96".toUpperCase(), "Expecting Latin lower-case h with line below"); // not yet implemented
-        //assert.areEqual("\u0054\u0308", "\u1E97".toUpperCase(), "Expecting Latin lower-case t with diaeresis"); // not yet implemented
-        //assert.areEqual("\u0057\u030A", "\u1E98".toUpperCase(), "Expecting Latin lower-case w with ring above"); // not yet implemented
-        //assert.areEqual("\u0059\u030A", "\u1E99".toUpperCase(), "Expecting Latin lower-case y with ring above"); // not yet implemented
-        //assert.areEqual("\u0041\u02BE", "\u1E9A".toUpperCase(), "Expecting Latin lower-case a with right half ring"); // not yet implemented
-        //assert.areEqual("\u03A5\u0313", "\u1F50".toUpperCase(), "Expecting Greek lower-case upsilon with psili"); // not yet implemented
-        //assert.areEqual("\u03A5\u0313\u0300", "\u1F52".toUpperCase(), "Expecting Greek lower-case upsilon with psili and varia"); // not yet implemented
-        //assert.areEqual("\u03A5\u0313\u0301", "\u1F54".toUpperCase(), "Expecting Greek lower-case upsilon with psili and oxia"); // not yet implemented
-        //assert.areEqual("\u03A5\u0313\u0342", "\u1F56".toUpperCase(), "Expecting Greek lower-case upsilon with psili and perispomeni"); // not yet implemented
-        //assert.areEqual("\u0391\u0342", "\u1FB6".toUpperCase(), "Expecting Greek lower-case alpha with perispomeni"); // not yet implemented
-        //assert.areEqual("\u0397\u0342", "\u1FC6".toUpperCase(), "Expecting Greek lower-case eta with perispomeni"); // not yet implemented
-        //assert.areEqual("\u0399\u0308\u0300", "\u1FD2".toUpperCase(), "Expecting Greek lower-case iota with dialytika and varia"); // not yet implemented
-        //assert.areEqual("\u0399\u0308\u0301", "\u1FD3".toUpperCase(), "Expecting Greek lower-case iota with dialytika and oxia"); // not yet implemented
-        //assert.areEqual("\u0399\u0342", "\u1FD6".toUpperCase(), "Expecting Greek lower-case iota with perispomeni"); // not yet implemented
-        //assert.areEqual("\u0399\u0308\u0342", "\u1FD7".toUpperCase(), "Expecting Greek lower-case iota with dialytika and perispomeni"); // not yet implemented
-        //assert.areEqual("\u03A5\u0308\u0300", "\u1FE2".toUpperCase(), "Expecting Greek lower-case upsilon with dialytika and varia"); // not yet implemented
-        //assert.areEqual("\u03A5\u0308\u0301", "\u1FE3".toUpperCase(), "Expecting Greek lower-case upsilon with dialytika and oxia"); // not yet implemented
-        //assert.areEqual("\u03A1\u0313", "\u1FE4".toUpperCase(), "Expecting Greek lower-case rho with psili"); // not yet implemented
-        //assert.areEqual("\u03A5\u0342", "\u1FE6".toUpperCase(), "Expecting Greek lower-case upsilon with perispomeni"); // not yet implemented
-        //assert.areEqual("\u03A5\u0308\u0342", "\u1FE7".toUpperCase(), "Expecting Greek lower-case upsilon with dialytika and perispomeni"); // not yet implemented
-        //assert.areEqual("\u03A9\u0342", "\u1FF6".toUpperCase(), "Expecting Greek lower-case omega with perispomeni"); // not yet implemented
-        //assert.areEqual("\u1F08\u0399", "\u1F80".toUpperCase(), "Expecting Greek lower-case alpha with psili and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F09\u0399", "\u1F81".toUpperCase(), "Expecting Greek lower-case alpha with dasia and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F0A\u0399", "\u1F82".toUpperCase(), "Expecting Greek lower-case alpha with psili and varia and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F0B\u0399", "\u1F83".toUpperCase(), "Expecting Greek lower-case alpha with dasia and varia and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F0C\u0399", "\u1F84".toUpperCase(), "Expecting Greek lower-case alpha with psili and oxia and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F0D\u0399", "\u1F85".toUpperCase(), "Expecting Greek lower-case alpha with dasia and oxia and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F0E\u0399", "\u1F86".toUpperCase(), "Expecting Greek lower-case alpha with psili and perispomeni and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F0F\u0399", "\u1F87".toUpperCase(), "Expecting Greek lower-case alpha with dasia and perispomeni and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F08\u0399", "\u1F88".toUpperCase(), "Expecting Greek upper-case alpha with psili and prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F09\u0399", "\u1F89".toUpperCase(), "Expecting Greek upper-case alpha with dasia and prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F0A\u0399", "\u1F8A".toUpperCase(), "Expecting Greek upper-case alpha with psili and varia and prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F0B\u0399", "\u1F8B".toUpperCase(), "Expecting Greek upper-case alpha with dasia and varia and prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F0C\u0399", "\u1F8C".toUpperCase(), "Expecting Greek upper-case alpha with psili and oxia and prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F0D\u0399", "\u1F8D".toUpperCase(), "Expecting Greek upper-case alpha with dasia and oxia and prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F0E\u0399", "\u1F8E".toUpperCase(), "Expecting Greek upper-case alpha with psili and perispomeni and prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F0F\u0399", "\u1F8F".toUpperCase(), "Expecting Greek upper-case alpha with dasia and perispomeni and prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F28\u0399", "\u1F90".toUpperCase(), "Expecting Greek lower-case eta with psili and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F29\u0399", "\u1F91".toUpperCase(), "Expecting Greek lower-case eta with dasia and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F2A\u0399", "\u1F92".toUpperCase(), "Expecting Greek lower-case eta with psili and varia and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F2B\u0399", "\u1F93".toUpperCase(), "Expecting Greek lower-case eta with dasia and varia and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F2C\u0399", "\u1F94".toUpperCase(), "Expecting Greek lower-case eta with psili and oxia and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F2D\u0399", "\u1F95".toUpperCase(), "Expecting Greek lower-case eta with dasia and oxia and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F2E\u0399", "\u1F96".toUpperCase(), "Expecting Greek lower-case eta with psili and perispomeni and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F2F\u0399", "\u1F97".toUpperCase(), "Expecting Greek lower-case eta with dasia and perispomeni and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F28\u0399", "\u1F98".toUpperCase(), "Expecting Greek upper-case eta with psili and prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F29\u0399", "\u1F99".toUpperCase(), "Expecting Greek upper-case eta with dasia and prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F2A\u0399", "\u1F9A".toUpperCase(), "Expecting Greek upper-case eta with psili and varia and prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F2B\u0399", "\u1F9B".toUpperCase(), "Expecting Greek upper-case eta with dasia and varia and prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F2C\u0399", "\u1F9C".toUpperCase(), "Expecting Greek upper-case eta with psili and oxia and prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F2D\u0399", "\u1F9D".toUpperCase(), "Expecting Greek upper-case eta with dasia and oxia and prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F2E\u0399", "\u1F9E".toUpperCase(), "Expecting Greek upper-case eta with psili and perispomeni and prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F2F\u0399", "\u1F9F".toUpperCase(), "Expecting Greek upper-case eta with dasia and perispomeni and prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F68\u0399", "\u1FA0".toUpperCase(), "Expecting Greek lower-case omega with psili and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F69\u0399", "\u1FA1".toUpperCase(), "Expecting Greek lower-case omega with dasia and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F6A\u0399", "\u1FA2".toUpperCase(), "Expecting Greek lower-case omega with psili and varia and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F6B\u0399", "\u1FA3".toUpperCase(), "Expecting Greek lower-case omega with dasia and varia and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F6C\u0399", "\u1FA4".toUpperCase(), "Expecting Greek lower-case omega with psili and oxia and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F6D\u0399", "\u1FA5".toUpperCase(), "Expecting Greek lower-case omega with dasia and oxia and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F6E\u0399", "\u1FA6".toUpperCase(), "Expecting Greek lower-case omega with psili and perispomeni and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F6F\u0399", "\u1FA7".toUpperCase(), "Expecting Greek lower-case omega with dasia and perispomeni and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F68\u0399", "\u1FA8".toUpperCase(), "Expecting Greek upper-case omega with psili and prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F69\u0399", "\u1FA9".toUpperCase(), "Expecting Greek upper-case omega with dasia and prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F6A\u0399", "\u1FAA".toUpperCase(), "Expecting Greek upper-case omega with psili and varia and prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F6B\u0399", "\u1FAB".toUpperCase(), "Expecting Greek upper-case omega with dasia and varia and prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F6C\u0399", "\u1FAC".toUpperCase(), "Expecting Greek upper-case omega with psili and oxia and prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F6D\u0399", "\u1FAD".toUpperCase(), "Expecting Greek upper-case omega with dasia and oxia and prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F6E\u0399", "\u1FAE".toUpperCase(), "Expecting Greek upper-case omega with psili and perispomeni and prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u1F6F\u0399", "\u1FAF".toUpperCase(), "Expecting Greek upper-case omega with dasia and perispomeni and prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u0391\u0399", "\u1FB3".toUpperCase(), "Expecting Greek lower-case alpha with ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u0391\u0399", "\u1FBC".toUpperCase(), "Expecting Greek upper-case alpha with prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u0397\u0399", "\u1FC3".toUpperCase(), "Expecting Greek lower-case eta with ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u0397\u0399", "\u1FCC".toUpperCase(), "Expecting Greek upper-case eta with prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u03A9\u0399", "\u1FF3".toUpperCase(), "Expecting Greek lower-case omega with ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u03A9\u0399", "\u1FFC".toUpperCase(), "Expecting Greek upper-case omega with prosgegrammeni"); // not yet implemented
-        //assert.areEqual("\u1FBA\u0399", "\u1FB2".toUpperCase(), "Expecting Greek lower-case alpha with varia and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u0386\u0399", "\u1FB4".toUpperCase(), "Expecting Greek lower-case alpha with oxia and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1FCA\u0399", "\u1FC2".toUpperCase(), "Expecting Greek lower-case eta with varia and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u0389\u0399", "\u1FC4".toUpperCase(), "Expecting Greek lower-case eta with oxia and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u1FFA\u0399", "\u1FF2".toUpperCase(), "Expecting Greek lower-case omega with varia and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u038F\u0399", "\u1FF4".toUpperCase(), "Expecting Greek lower-case omega with oxia and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u0391\u0342\u0399", "\u1FB7".toUpperCase(), "Expecting Greek lower-case alpha with perispomeni and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u0397\u0342\u0399", "\u1FC7".toUpperCase(), "Expecting Greek lower-case eta with perispomeni and ypogegrammeni"); // not yet implemented
-        //assert.areEqual("\u03A9\u0342\u0399", "\u1FF7".toUpperCase(), "Expecting Greek lower-case omega with perispomeni and ypogegrammeni"); // not yet implemented
-    }
-  },
-  {
-    name: "Special casing toLowerCase",
-    body: function () {
-        assert.areEqual("\u00DF", "\u00DF".toLowerCase(), "Expecting Latin lower-case sharp s");
-        //assert.areEqual("\u0069\u0307", "\u0130".toLowerCase(), "Expecting Latin upper-case i with dot above"); // not yet implemented
-        assert.areEqual("\uFB00", "\uFB00".toLowerCase(), "Expecting Latin small ligature ff");
-        assert.areEqual("\uFB01", "\uFB01".toLowerCase(), "Expecting Latin small ligature fi");
-        assert.areEqual("\uFB02", "\uFB02".toLowerCase(), "Expecting Latin small ligature fl");
-        assert.areEqual("\uFB03", "\uFB03".toLowerCase(), "Expecting Latin small ligature ffi");
-        assert.areEqual("\uFB04", "\uFB04".toLowerCase(), "Expecting Latin small ligature ffl");
-        assert.areEqual("\uFB05", "\uFB05".toLowerCase(), "Expecting Latin small ligature long s t");
-        assert.areEqual("\uFB06", "\uFB06".toLowerCase(), "Expecting Latin small ligature st");
-        assert.areEqual("\u0587", "\u0587".toLowerCase(), "Expecting Armenian small ligature ech yiwn");
-        assert.areEqual("\uFB13", "\uFB13".toLowerCase(), "Expecting Armenian small ligature men now");
-        assert.areEqual("\uFB14", "\uFB14".toLowerCase(), "Expecting Armenian small ligature men ech");
-        assert.areEqual("\uFB15", "\uFB15".toLowerCase(), "Expecting Armenian small ligature men ini");
-        assert.areEqual("\uFB16", "\uFB16".toLowerCase(), "Expecting Armenian small ligature vew now");
-        assert.areEqual("\uFB17", "\uFB17".toLowerCase(), "Expecting Armenian small ligature men xeh");
-        assert.areEqual("\u0149", "\u0149".toLowerCase(), "Expecting Latin lower-case n preceded by apostrophe");
-        assert.areEqual("\u0390", "\u0390".toLowerCase(), "Expecting Greek lower-case iota with dialytika and tonos");
-        assert.areEqual("\u03B0", "\u03B0".toLowerCase(), "Expecting Greek lower-case upsilon with dialytika and tonos");
-        assert.areEqual("\u01F0", "\u01F0".toLowerCase(), "Expecting Latin lower-case j with caron");
-        assert.areEqual("\u1E96", "\u1E96".toLowerCase(), "Expecting Latin lower-case h with line below");
-        assert.areEqual("\u1E97", "\u1E97".toLowerCase(), "Expecting Latin lower-case t with diaeresis");
-        assert.areEqual("\u1E98", "\u1E98".toLowerCase(), "Expecting Latin lower-case w with ring above");
-        assert.areEqual("\u1E99", "\u1E99".toLowerCase(), "Expecting Latin lower-case y with ring above");
-        assert.areEqual("\u1E9A", "\u1E9A".toLowerCase(), "Expecting Latin lower-case a with right half ring");
-        assert.areEqual("\u1F50", "\u1F50".toLowerCase(), "Expecting Greek lower-case upsilon with psili");
-        assert.areEqual("\u1F52", "\u1F52".toLowerCase(), "Expecting Greek lower-case upsilon with psili and varia");
-        assert.areEqual("\u1F54", "\u1F54".toLowerCase(), "Expecting Greek lower-case upsilon with psili and oxia");
-        assert.areEqual("\u1F56", "\u1F56".toLowerCase(), "Expecting Greek lower-case upsilon with psili and perispomeni");
-        assert.areEqual("\u1FB6", "\u1FB6".toLowerCase(), "Expecting Greek lower-case alpha with perispomeni");
-        assert.areEqual("\u1FC6", "\u1FC6".toLowerCase(), "Expecting Greek lower-case eta with perispomeni");
-        assert.areEqual("\u1FD2", "\u1FD2".toLowerCase(), "Expecting Greek lower-case iota with dialytika and varia");
-        assert.areEqual("\u1FD3", "\u1FD3".toLowerCase(), "Expecting Greek lower-case iota with dialytika and oxia");
-        assert.areEqual("\u1FD6", "\u1FD6".toLowerCase(), "Expecting Greek lower-case iota with perispomeni");
-        assert.areEqual("\u1FD7", "\u1FD7".toLowerCase(), "Expecting Greek lower-case iota with dialytika and perispomeni");
-        assert.areEqual("\u1FE2", "\u1FE2".toLowerCase(), "Expecting Greek lower-case upsilon with dialytika and varia");
-        assert.areEqual("\u1FE3", "\u1FE3".toLowerCase(), "Expecting Greek lower-case upsilon with dialytika and oxia");
-        assert.areEqual("\u1FE4", "\u1FE4".toLowerCase(), "Expecting Greek lower-case rho with psili");
-        assert.areEqual("\u1FE6", "\u1FE6".toLowerCase(), "Expecting Greek lower-case upsilon with perispomeni");
-        assert.areEqual("\u1FE7", "\u1FE7".toLowerCase(), "Expecting Greek lower-case upsilon with dialytika and perispomeni");
-        assert.areEqual("\u1FF6", "\u1FF6".toLowerCase(), "Expecting Greek lower-case omega with perispomeni");
-        assert.areEqual("\u1F80", "\u1F80".toLowerCase(), "Expecting Greek lower-case alpha with psili and ypogegrammeni");
-        assert.areEqual("\u1F81", "\u1F81".toLowerCase(), "Expecting Greek lower-case alpha with dasia and ypogegrammeni");
-        assert.areEqual("\u1F82", "\u1F82".toLowerCase(), "Expecting Greek lower-case alpha with psili and varia and ypogegrammeni");
-        assert.areEqual("\u1F83", "\u1F83".toLowerCase(), "Expecting Greek lower-case alpha with dasia and varia and ypogegrammeni");
-        assert.areEqual("\u1F84", "\u1F84".toLowerCase(), "Expecting Greek lower-case alpha with psili and oxia and ypogegrammeni");
-        assert.areEqual("\u1F85", "\u1F85".toLowerCase(), "Expecting Greek lower-case alpha with dasia and oxia and ypogegrammeni");
-        assert.areEqual("\u1F86", "\u1F86".toLowerCase(), "Expecting Greek lower-case alpha with psili and perispomeni and ypogegrammeni");
-        assert.areEqual("\u1F87", "\u1F87".toLowerCase(), "Expecting Greek lower-case alpha with dasia and perispomeni and ypogegrammeni");
-        assert.areEqual("\u1F80", "\u1F88".toLowerCase(), "Expecting Greek upper-case alpha with psili and prosgegrammeni");
-        assert.areEqual("\u1F81", "\u1F89".toLowerCase(), "Expecting Greek upper-case alpha with dasia and prosgegrammeni");
-        assert.areEqual("\u1F82", "\u1F8A".toLowerCase(), "Expecting Greek upper-case alpha with psili and varia and prosgegrammeni");
-        assert.areEqual("\u1F83", "\u1F8B".toLowerCase(), "Expecting Greek upper-case alpha with dasia and varia and prosgegrammeni");
-        assert.areEqual("\u1F84", "\u1F8C".toLowerCase(), "Expecting Greek upper-case alpha with psili and oxia and prosgegrammeni");
-        assert.areEqual("\u1F85", "\u1F8D".toLowerCase(), "Expecting Greek upper-case alpha with dasia and oxia and prosgegrammeni");
-        assert.areEqual("\u1F86", "\u1F8E".toLowerCase(), "Expecting Greek upper-case alpha with psili and perispomeni and prosgegrammeni");
-        assert.areEqual("\u1F87", "\u1F8F".toLowerCase(), "Expecting Greek upper-case alpha with dasia and perispomeni and prosgegrammeni");
-        assert.areEqual("\u1F90", "\u1F90".toLowerCase(), "Expecting Greek lower-case eta with psili and ypogegrammeni");
-        assert.areEqual("\u1F91", "\u1F91".toLowerCase(), "Expecting Greek lower-case eta with dasia and ypogegrammeni");
-        assert.areEqual("\u1F92", "\u1F92".toLowerCase(), "Expecting Greek lower-case eta with psili and varia and ypogegrammeni");
-        assert.areEqual("\u1F93", "\u1F93".toLowerCase(), "Expecting Greek lower-case eta with dasia and varia and ypogegrammeni");
-        assert.areEqual("\u1F94", "\u1F94".toLowerCase(), "Expecting Greek lower-case eta with psili and oxia and ypogegrammeni");
-        assert.areEqual("\u1F95", "\u1F95".toLowerCase(), "Expecting Greek lower-case eta with dasia and oxia and ypogegrammeni");
-        assert.areEqual("\u1F96", "\u1F96".toLowerCase(), "Expecting Greek lower-case eta with psili and perispomeni and ypogegrammeni");
-        assert.areEqual("\u1F97", "\u1F97".toLowerCase(), "Expecting Greek lower-case eta with dasia and perispomeni and ypogegrammeni");
-        assert.areEqual("\u1F90", "\u1F98".toLowerCase(), "Expecting Greek upper-case eta with psili and prosgegrammeni");
-        assert.areEqual("\u1F91", "\u1F99".toLowerCase(), "Expecting Greek upper-case eta with dasia and prosgegrammeni");
-        assert.areEqual("\u1F92", "\u1F9A".toLowerCase(), "Expecting Greek upper-case eta with psili and varia and prosgegrammeni");
-        assert.areEqual("\u1F93", "\u1F9B".toLowerCase(), "Expecting Greek upper-case eta with dasia and varia and prosgegrammeni");
-        assert.areEqual("\u1F94", "\u1F9C".toLowerCase(), "Expecting Greek upper-case eta with psili and oxia and prosgegrammeni");
-        assert.areEqual("\u1F95", "\u1F9D".toLowerCase(), "Expecting Greek upper-case eta with dasia and oxia and prosgegrammeni");
-        assert.areEqual("\u1F96", "\u1F9E".toLowerCase(), "Expecting Greek upper-case eta with psili and perispomeni and prosgegrammeni");
-        assert.areEqual("\u1F97", "\u1F9F".toLowerCase(), "Expecting Greek upper-case eta with dasia and perispomeni and prosgegrammeni");
-        assert.areEqual("\u1FA0", "\u1FA0".toLowerCase(), "Expecting Greek lower-case omega with psili and ypogegrammeni");
-        assert.areEqual("\u1FA1", "\u1FA1".toLowerCase(), "Expecting Greek lower-case omega with dasia and ypogegrammeni");
-        assert.areEqual("\u1FA2", "\u1FA2".toLowerCase(), "Expecting Greek lower-case omega with psili and varia and ypogegrammeni");
-        assert.areEqual("\u1FA3", "\u1FA3".toLowerCase(), "Expecting Greek lower-case omega with dasia and varia and ypogegrammeni");
-        assert.areEqual("\u1FA4", "\u1FA4".toLowerCase(), "Expecting Greek lower-case omega with psili and oxia and ypogegrammeni");
-        assert.areEqual("\u1FA5", "\u1FA5".toLowerCase(), "Expecting Greek lower-case omega with dasia and oxia and ypogegrammeni");
-        assert.areEqual("\u1FA6", "\u1FA6".toLowerCase(), "Expecting Greek lower-case omega with psili and perispomeni and ypogegrammeni");
-        assert.areEqual("\u1FA7", "\u1FA7".toLowerCase(), "Expecting Greek lower-case omega with dasia and perispomeni and ypogegrammeni");
-        assert.areEqual("\u1FA0", "\u1FA8".toLowerCase(), "Expecting Greek upper-case omega with psili and prosgegrammeni");
-        assert.areEqual("\u1FA1", "\u1FA9".toLowerCase(), "Expecting Greek upper-case omega with dasia and prosgegrammeni");
-        assert.areEqual("\u1FA2", "\u1FAA".toLowerCase(), "Expecting Greek upper-case omega with psili and varia and prosgegrammeni");
-        assert.areEqual("\u1FA3", "\u1FAB".toLowerCase(), "Expecting Greek upper-case omega with dasia and varia and prosgegrammeni");
-        assert.areEqual("\u1FA4", "\u1FAC".toLowerCase(), "Expecting Greek upper-case omega with psili and oxia and prosgegrammeni");
-        assert.areEqual("\u1FA5", "\u1FAD".toLowerCase(), "Expecting Greek upper-case omega with dasia and oxia and prosgegrammeni");
-        assert.areEqual("\u1FA6", "\u1FAE".toLowerCase(), "Expecting Greek upper-case omega with psili and perispomeni and prosgegrammeni");
-        assert.areEqual("\u1FA7", "\u1FAF".toLowerCase(), "Expecting Greek upper-case omega with dasia and perispomeni and prosgegrammeni");
-        assert.areEqual("\u1FB3", "\u1FB3".toLowerCase(), "Expecting Greek lower-case alpha with ypogegrammeni");
-        assert.areEqual("\u1FB3", "\u1FBC".toLowerCase(), "Expecting Greek upper-case alpha with prosgegrammeni");
-        assert.areEqual("\u1FC3", "\u1FC3".toLowerCase(), "Expecting Greek lower-case eta with ypogegrammeni");
-        assert.areEqual("\u1FC3", "\u1FCC".toLowerCase(), "Expecting Greek upper-case eta with prosgegrammeni");
-        assert.areEqual("\u1FF3", "\u1FF3".toLowerCase(), "Expecting Greek lower-case omega with ypogegrammeni");
-        assert.areEqual("\u1FF3", "\u1FFC".toLowerCase(), "Expecting Greek upper-case omega with prosgegrammeni");
-        assert.areEqual("\u1FB2", "\u1FB2".toLowerCase(), "Expecting Greek lower-case alpha with varia and ypogegrammeni");
-        assert.areEqual("\u1FB4", "\u1FB4".toLowerCase(), "Expecting Greek lower-case alpha with oxia and ypogegrammeni");
-        assert.areEqual("\u1FC2", "\u1FC2".toLowerCase(), "Expecting Greek lower-case eta with varia and ypogegrammeni");
-        assert.areEqual("\u1FC4", "\u1FC4".toLowerCase(), "Expecting Greek lower-case eta with oxia and ypogegrammeni");
-        assert.areEqual("\u1FF2", "\u1FF2".toLowerCase(), "Expecting Greek lower-case omega with varia and ypogegrammeni");
-        assert.areEqual("\u1FF4", "\u1FF4".toLowerCase(), "Expecting Greek lower-case omega with oxia and ypogegrammeni");
-        assert.areEqual("\u1FB7", "\u1FB7".toLowerCase(), "Expecting Greek lower-case alpha with perispomeni and ypogegrammeni");
-        assert.areEqual("\u1FC7", "\u1FC7".toLowerCase(), "Expecting Greek lower-case eta with perispomeni and ypogegrammeni");
-        assert.areEqual("\u1FF7", "\u1FF7".toLowerCase(), "Expecting Greek lower-case omega with perispomeni and ypogegrammeni");
-        assert.areEqual("\u03C3", "\u03A3".toLowerCase(), "Expecting single Greek upper-case sigma");
-        //assert.areEqual("a\u03C2", "A\u03A3".toLowerCase(), "Expecting sigma preceded by Latin upper-case a");  // not yet implemented
-        //assert.areEqual("\uD835\uDCA2\u03C2", "\uD835\uDCA2\u03A3".toLowerCase(), "Expecting sigma preceded by mathematical script capital g (d835 dca2 = 1d4a2)");  // not yet implemented
-        //assert.areEqual("a.\u03C2", "A.\u03A3".toLowerCase(), "Expecting sigma preceded by full stop");  // not yet implemented
-        //assert.areEqual("a\u00AD\u03C2", "A\u00AD\u03A3".toLowerCase(), "Expecting sigma preceded by soft hyphen (00ad)");  // not yet implemented
-        //assert.areEqual("a\uD834\uDE42\u03C2", "A\uD834\uDE42\u03A3".toLowerCase(), "Expecting sigma preceded by combining Greek musical triseme (d834 de42 = 1d242)");  // not yet implemented
-        assert.areEqual("\u0345\u03C3", "\u0345\u03A3".toLowerCase(), "Expecting sigma preceded by combining Greek ypogegrammeni (0345)");
-        //assert.areEqual("\u03B1\u0345\u03C2", "\u0391\u0345\u03A3".toLowerCase(), "Expecting sigma preceded by Greek upper-case alpha (0391), combining Greek ypogegrammeni (0345)");  // not yet implemented
-        assert.areEqual("a\u03C3b", "A\u03A3B".toLowerCase(), "Expecting sigma followed by Latin upper-case b");
-        assert.areEqual("a\u03C3\uD835\uDCA2", "A\u03A3\uD835\uDCA2".toLowerCase(), "Expecting sigma followed by mathematical script capital g (d835 dca2 = 1d4a2)");
-        assert.areEqual("a\u03C3.b", "A\u03A3.b".toLowerCase(), "Expecting sigma followed by full stop");
-        assert.areEqual("a\u03C3\u00ADb", "A\u03A3\u00ADB".toLowerCase(), "Expecting sigma followed by soft hyphen (00ad)");
-        assert.areEqual("a\u03C3\uD834\uDE42b", "A\u03A3\uD834\uDE42B".toLowerCase(), "Expecting sigma followed by combining Greek musical triseme (d834 de42 = 1d242)");
-        //assert.areEqual("a\u03C2\u0345", "A\u03A3\u0345".toLowerCase(), "Expecting sigma followed by combining Greek ypogegrammeni (0345)");  // not yet implemented
-        assert.areEqual("a\u03C3\u0345\u03B1", "A\u03A3\u0345\u0391".toLowerCase(), "Expecting sigma followed by combining Greek ypogegrammeni (0345), Greek upper-case alpha (0391)");
-    }
-  },
+    {
+        name: "Edge cases",
+        body() {
+            assert.areEqual("\uDC37", "\uDC37".toUpperCase(), "Invalid unicode should be passed over (single character, toUpperCase)");
+            assert.areEqual("\uDC37", "\uDC37".toLowerCase(), "Invalid unicode should be passed over (single character, toLowerCase)");
+            assert.areEqual("ABC\uDC37DEF", "abc\uDC37def".toUpperCase(), "Invalid unicode should be passed over (mid-string, toUpperCase)");
+            assert.areEqual("abc\uDC37def", "ABC\uDC37DEF".toLowerCase(), "Invalid unicode should be passed over (mid-string, toLowerCase)");
+        }
+    },
+    {
+        name: "Deseret alphabet toUpperCase",
+        body: function () {
+            assert.areEqual("\uD801\uDC00", "\uD801\uDC28".toUpperCase(), "Expecting Deseret alphabet upper-case long I");
+            assert.areEqual("\uD801\uDC01", "\uD801\uDC29".toUpperCase(), "Expecting Deseret alphabet upper-case long E");
+            assert.areEqual("\uD801\uDC02", "\uD801\uDC2A".toUpperCase(), "Expecting Deseret alphabet upper-case long A");
+            assert.areEqual("\uD801\uDC03", "\uD801\uDC2B".toUpperCase(), "Expecting Deseret alphabet upper-case long Ah");
+            assert.areEqual("\uD801\uDC04", "\uD801\uDC2C".toUpperCase(), "Expecting Deseret alphabet upper-case long O");
+            assert.areEqual("\uD801\uDC05", "\uD801\uDC2D".toUpperCase(), "Expecting Deseret alphabet upper-case long Oo");
+            assert.areEqual("\uD801\uDC06", "\uD801\uDC2E".toUpperCase(), "Expecting Deseret alphabet upper-case short I");
+            assert.areEqual("\uD801\uDC07", "\uD801\uDC2F".toUpperCase(), "Expecting Deseret alphabet upper-case short E");
+            assert.areEqual("\uD801\uDC08", "\uD801\uDC30".toUpperCase(), "Expecting Deseret alphabet upper-case short A");
+            assert.areEqual("\uD801\uDC09", "\uD801\uDC31".toUpperCase(), "Expecting Deseret alphabet upper-case short Ah");
+            assert.areEqual("\uD801\uDC0A", "\uD801\uDC32".toUpperCase(), "Expecting Deseret alphabet upper-case short O");
+            assert.areEqual("\uD801\uDC0B", "\uD801\uDC33".toUpperCase(), "Expecting Deseret alphabet upper-case short Oo");
+            assert.areEqual("\uD801\uDC0C", "\uD801\uDC34".toUpperCase(), "Expecting Deseret alphabet upper-case Ay");
+            assert.areEqual("\uD801\uDC0D", "\uD801\uDC35".toUpperCase(), "Expecting Deseret alphabet upper-case Ow");
+            assert.areEqual("\uD801\uDC0E", "\uD801\uDC36".toUpperCase(), "Expecting Deseret alphabet upper-case Wu");
+            assert.areEqual("\uD801\uDC0F", "\uD801\uDC37".toUpperCase(), "Expecting Deseret alphabet upper-case Yee");
+            assert.areEqual("\uD801\uDC10", "\uD801\uDC38".toUpperCase(), "Expecting Deseret alphabet upper-case H");
+            assert.areEqual("\uD801\uDC11", "\uD801\uDC39".toUpperCase(), "Expecting Deseret alphabet upper-case Pee");
+            assert.areEqual("\uD801\uDC12", "\uD801\uDC3A".toUpperCase(), "Expecting Deseret alphabet upper-case Bee");
+            assert.areEqual("\uD801\uDC13", "\uD801\uDC3B".toUpperCase(), "Expecting Deseret alphabet upper-case Tee");
+            assert.areEqual("\uD801\uDC14", "\uD801\uDC3C".toUpperCase(), "Expecting Deseret alphabet upper-case Dee");
+            assert.areEqual("\uD801\uDC15", "\uD801\uDC3D".toUpperCase(), "Expecting Deseret alphabet upper-case Chee");
+            assert.areEqual("\uD801\uDC16", "\uD801\uDC3E".toUpperCase(), "Expecting Deseret alphabet upper-case Jee");
+            assert.areEqual("\uD801\uDC17", "\uD801\uDC3F".toUpperCase(), "Expecting Deseret alphabet upper-case Kay");
+            assert.areEqual("\uD801\uDC18", "\uD801\uDC40".toUpperCase(), "Expecting Deseret alphabet upper-case Gay");
+            assert.areEqual("\uD801\uDC19", "\uD801\uDC41".toUpperCase(), "Expecting Deseret alphabet upper-case Ef");
+            assert.areEqual("\uD801\uDC1A", "\uD801\uDC42".toUpperCase(), "Expecting Deseret alphabet upper-case Vee");
+            assert.areEqual("\uD801\uDC1B", "\uD801\uDC43".toUpperCase(), "Expecting Deseret alphabet upper-case Eth");
+            assert.areEqual("\uD801\uDC1C", "\uD801\uDC44".toUpperCase(), "Expecting Deseret alphabet upper-case Thee");
+            assert.areEqual("\uD801\uDC1D", "\uD801\uDC45".toUpperCase(), "Expecting Deseret alphabet upper-case Es");
+            assert.areEqual("\uD801\uDC1E", "\uD801\uDC46".toUpperCase(), "Expecting Deseret alphabet upper-case Zee");
+            assert.areEqual("\uD801\uDC1F", "\uD801\uDC47".toUpperCase(), "Expecting Deseret alphabet upper-case Esh");
+            assert.areEqual("\uD801\uDC20", "\uD801\uDC48".toUpperCase(), "Expecting Deseret alphabet upper-case Zhee");
+            assert.areEqual("\uD801\uDC21", "\uD801\uDC49".toUpperCase(), "Expecting Deseret alphabet upper-case Er");
+            assert.areEqual("\uD801\uDC22", "\uD801\uDC4A".toUpperCase(), "Expecting Deseret alphabet upper-case El");
+            assert.areEqual("\uD801\uDC23", "\uD801\uDC4B".toUpperCase(), "Expecting Deseret alphabet upper-case Em");
+            assert.areEqual("\uD801\uDC24", "\uD801\uDC4C".toUpperCase(), "Expecting Deseret alphabet upper-case En");
+            assert.areEqual("\uD801\uDC25", "\uD801\uDC4D".toUpperCase(), "Expecting Deseret alphabet upper-case Eng");
+            assert.areEqual("\uD801\uDC26", "\uD801\uDC4E".toUpperCase(), "Expecting Deseret alphabet upper-case Oi");
+            assert.areEqual("\uD801\uDC27", "\uD801\uDC4F".toUpperCase(), "Expecting Deseret alphabet upper-case Ew");
+        }
+    },
+    {
+        name: "Deseret alphabet toLowerCase",
+        body: function () {
+            assert.areEqual("\uD801\uDC28", "\uD801\uDC00".toLowerCase(), "Expecting Deseret alphabet lower-case long I");
+            assert.areEqual("\uD801\uDC29", "\uD801\uDC01".toLowerCase(), "Expecting Deseret alphabet lower-case long E");
+            assert.areEqual("\uD801\uDC2A", "\uD801\uDC02".toLowerCase(), "Expecting Deseret alphabet lower-case long A");
+            assert.areEqual("\uD801\uDC2B", "\uD801\uDC03".toLowerCase(), "Expecting Deseret alphabet lower-case long Ah");
+            assert.areEqual("\uD801\uDC2C", "\uD801\uDC04".toLowerCase(), "Expecting Deseret alphabet lower-case long O");
+            assert.areEqual("\uD801\uDC2D", "\uD801\uDC05".toLowerCase(), "Expecting Deseret alphabet lower-case long Oo");
+            assert.areEqual("\uD801\uDC2E", "\uD801\uDC06".toLowerCase(), "Expecting Deseret alphabet lower-case short I");
+            assert.areEqual("\uD801\uDC2F", "\uD801\uDC07".toLowerCase(), "Expecting Deseret alphabet lower-case short E");
+            assert.areEqual("\uD801\uDC30", "\uD801\uDC08".toLowerCase(), "Expecting Deseret alphabet lower-case short A");
+            assert.areEqual("\uD801\uDC31", "\uD801\uDC09".toLowerCase(), "Expecting Deseret alphabet lower-case short Ah");
+            assert.areEqual("\uD801\uDC32", "\uD801\uDC0A".toLowerCase(), "Expecting Deseret alphabet lower-case short O");
+            assert.areEqual("\uD801\uDC33", "\uD801\uDC0B".toLowerCase(), "Expecting Deseret alphabet lower-case short Oo");
+            assert.areEqual("\uD801\uDC34", "\uD801\uDC0C".toLowerCase(), "Expecting Deseret alphabet lower-case Ay");
+            assert.areEqual("\uD801\uDC35", "\uD801\uDC0D".toLowerCase(), "Expecting Deseret alphabet lower-case Ow");
+            assert.areEqual("\uD801\uDC36", "\uD801\uDC0E".toLowerCase(), "Expecting Deseret alphabet lower-case Wu");
+            assert.areEqual("\uD801\uDC37", "\uD801\uDC0F".toLowerCase(), "Expecting Deseret alphabet lower-case Yee");
+            assert.areEqual("\uD801\uDC38", "\uD801\uDC10".toLowerCase(), "Expecting Deseret alphabet lower-case H");
+            assert.areEqual("\uD801\uDC39", "\uD801\uDC11".toLowerCase(), "Expecting Deseret alphabet lower-case Pee");
+            assert.areEqual("\uD801\uDC3A", "\uD801\uDC12".toLowerCase(), "Expecting Deseret alphabet lower-case Bee");
+            assert.areEqual("\uD801\uDC3B", "\uD801\uDC13".toLowerCase(), "Expecting Deseret alphabet lower-case Tee");
+            assert.areEqual("\uD801\uDC3C", "\uD801\uDC14".toLowerCase(), "Expecting Deseret alphabet lower-case Dee");
+            assert.areEqual("\uD801\uDC3D", "\uD801\uDC15".toLowerCase(), "Expecting Deseret alphabet lower-case Chee");
+            assert.areEqual("\uD801\uDC3E", "\uD801\uDC16".toLowerCase(), "Expecting Deseret alphabet lower-case Jee");
+            assert.areEqual("\uD801\uDC3F", "\uD801\uDC17".toLowerCase(), "Expecting Deseret alphabet lower-case Kay");
+            assert.areEqual("\uD801\uDC40", "\uD801\uDC18".toLowerCase(), "Expecting Deseret alphabet lower-case Gay");
+            assert.areEqual("\uD801\uDC41", "\uD801\uDC19".toLowerCase(), "Expecting Deseret alphabet lower-case Ef");
+            assert.areEqual("\uD801\uDC42", "\uD801\uDC1A".toLowerCase(), "Expecting Deseret alphabet lower-case Vee");
+            assert.areEqual("\uD801\uDC43", "\uD801\uDC1B".toLowerCase(), "Expecting Deseret alphabet lower-case Eth");
+            assert.areEqual("\uD801\uDC44", "\uD801\uDC1C".toLowerCase(), "Expecting Deseret alphabet lower-case Thee");
+            assert.areEqual("\uD801\uDC45", "\uD801\uDC1D".toLowerCase(), "Expecting Deseret alphabet lower-case Es");
+            assert.areEqual("\uD801\uDC46", "\uD801\uDC1E".toLowerCase(), "Expecting Deseret alphabet lower-case Zee");
+            assert.areEqual("\uD801\uDC47", "\uD801\uDC1F".toLowerCase(), "Expecting Deseret alphabet lower-case Esh");
+            assert.areEqual("\uD801\uDC48", "\uD801\uDC20".toLowerCase(), "Expecting Deseret alphabet lower-case Zhee");
+            assert.areEqual("\uD801\uDC49", "\uD801\uDC21".toLowerCase(), "Expecting Deseret alphabet lower-case Er");
+            assert.areEqual("\uD801\uDC4A", "\uD801\uDC22".toLowerCase(), "Expecting Deseret alphabet lower-case El");
+            assert.areEqual("\uD801\uDC4B", "\uD801\uDC23".toLowerCase(), "Expecting Deseret alphabet lower-case Em");
+            assert.areEqual("\uD801\uDC4C", "\uD801\uDC24".toLowerCase(), "Expecting Deseret alphabet lower-case En");
+            assert.areEqual("\uD801\uDC4D", "\uD801\uDC25".toLowerCase(), "Expecting Deseret alphabet lower-case Eng");
+            assert.areEqual("\uD801\uDC4E", "\uD801\uDC26".toLowerCase(), "Expecting Deseret alphabet lower-case Oi");
+            assert.areEqual("\uD801\uDC4F", "\uD801\uDC27".toLowerCase(), "Expecting Deseret alphabet lower-case Ew");
+        }
+    },
+    {
+        name: "Special casing toUpperCase",
+        body: function () {
+            assert.areEqual("\u0130", "\u0130".toUpperCase(), "Expecting Latin upper-case i with dot above");
+            if (WScript.Platform.INTL_LIBRARY !== "icu") {
+                // Win32's version of toUpperCase does not support growing strings in the uppercase operation (see CharUpperBuff)
+                return;
+            }
+            assert.areEqual("\u0053\u0053", "\u00DF".toUpperCase(), "Expecting Latin lower-case sharp s");
+            assert.areEqual("\u0046\u0046", "\uFB00".toUpperCase(), "Expecting Latin small ligature ff");
+            assert.areEqual("\u0046\u0049", "\uFB01".toUpperCase(), "Expecting Latin small ligature fi");
+            assert.areEqual("\u0046\u004C", "\uFB02".toUpperCase(), "Expecting Latin small ligature fl");
+            assert.areEqual("\u0046\u0046\u0049", "\uFB03".toUpperCase(), "Expecting Latin small ligature ffi");
+            assert.areEqual("\u0046\u0046\u004C", "\uFB04".toUpperCase(), "Expecting Latin small ligature ffl");
+            assert.areEqual("\u0053\u0054", "\uFB05".toUpperCase(), "Expecting Latin small ligature long s t");
+            assert.areEqual("\u0053\u0054", "\uFB06".toUpperCase(), "Expecting Latin small ligature st");
+            assert.areEqual("\u0535\u0552", "\u0587".toUpperCase(), "Expecting Armenian small ligature ech yiwn");
+            assert.areEqual("\u0544\u0546", "\uFB13".toUpperCase(), "Expecting Armenian small ligature men now");
+            assert.areEqual("\u0544\u0535", "\uFB14".toUpperCase(), "Expecting Armenian small ligature men ech");
+            assert.areEqual("\u0544\u053B", "\uFB15".toUpperCase(), "Expecting Armenian small ligature men ini");
+            assert.areEqual("\u054E\u0546", "\uFB16".toUpperCase(), "Expecting Armenian small ligature vew now");
+            assert.areEqual("\u0544\u053D", "\uFB17".toUpperCase(), "Expecting Armenian small ligature men xeh");
+            assert.areEqual("\u02BC\u004E", "\u0149".toUpperCase(), "Expecting Latin lower-case n preceded by apostrophe");
+            assert.areEqual("\u0399\u0308\u0301", "\u0390".toUpperCase(), "Expecting Greek lower-case iota with dialytika and tonos");
+            assert.areEqual("\u03A5\u0308\u0301", "\u03B0".toUpperCase(), "Expecting Greek lower-case upsilon with dialytika and tonos");
+            assert.areEqual("\u004A\u030C", "\u01F0".toUpperCase(), "Expecting Latin lower-case j with caron");
+            assert.areEqual("\u0048\u0331", "\u1E96".toUpperCase(), "Expecting Latin lower-case h with line below");
+            assert.areEqual("\u0054\u0308", "\u1E97".toUpperCase(), "Expecting Latin lower-case t with diaeresis");
+            assert.areEqual("\u0057\u030A", "\u1E98".toUpperCase(), "Expecting Latin lower-case w with ring above");
+            assert.areEqual("\u0059\u030A", "\u1E99".toUpperCase(), "Expecting Latin lower-case y with ring above");
+            assert.areEqual("\u0041\u02BE", "\u1E9A".toUpperCase(), "Expecting Latin lower-case a with right half ring");
+            assert.areEqual("\u03A5\u0313", "\u1F50".toUpperCase(), "Expecting Greek lower-case upsilon with psili");
+            assert.areEqual("\u03A5\u0313\u0300", "\u1F52".toUpperCase(), "Expecting Greek lower-case upsilon with psili and varia");
+            assert.areEqual("\u03A5\u0313\u0301", "\u1F54".toUpperCase(), "Expecting Greek lower-case upsilon with psili and oxia");
+            assert.areEqual("\u03A5\u0313\u0342", "\u1F56".toUpperCase(), "Expecting Greek lower-case upsilon with psili and perispomeni");
+            assert.areEqual("\u0391\u0342", "\u1FB6".toUpperCase(), "Expecting Greek lower-case alpha with perispomeni");
+            assert.areEqual("\u0397\u0342", "\u1FC6".toUpperCase(), "Expecting Greek lower-case eta with perispomeni");
+            assert.areEqual("\u0399\u0308\u0300", "\u1FD2".toUpperCase(), "Expecting Greek lower-case iota with dialytika and varia");
+            assert.areEqual("\u0399\u0308\u0301", "\u1FD3".toUpperCase(), "Expecting Greek lower-case iota with dialytika and oxia");
+            assert.areEqual("\u0399\u0342", "\u1FD6".toUpperCase(), "Expecting Greek lower-case iota with perispomeni");
+            assert.areEqual("\u0399\u0308\u0342", "\u1FD7".toUpperCase(), "Expecting Greek lower-case iota with dialytika and perispomeni");
+            assert.areEqual("\u03A5\u0308\u0300", "\u1FE2".toUpperCase(), "Expecting Greek lower-case upsilon with dialytika and varia");
+            assert.areEqual("\u03A5\u0308\u0301", "\u1FE3".toUpperCase(), "Expecting Greek lower-case upsilon with dialytika and oxia");
+            assert.areEqual("\u03A1\u0313", "\u1FE4".toUpperCase(), "Expecting Greek lower-case rho with psili");
+            assert.areEqual("\u03A5\u0342", "\u1FE6".toUpperCase(), "Expecting Greek lower-case upsilon with perispomeni");
+            assert.areEqual("\u03A5\u0308\u0342", "\u1FE7".toUpperCase(), "Expecting Greek lower-case upsilon with dialytika and perispomeni");
+            assert.areEqual("\u03A9\u0342", "\u1FF6".toUpperCase(), "Expecting Greek lower-case omega with perispomeni");
+            assert.areEqual("\u1F08\u0399", "\u1F80".toUpperCase(), "Expecting Greek lower-case alpha with psili and ypogegrammeni");
+            assert.areEqual("\u1F09\u0399", "\u1F81".toUpperCase(), "Expecting Greek lower-case alpha with dasia and ypogegrammeni");
+            assert.areEqual("\u1F0A\u0399", "\u1F82".toUpperCase(), "Expecting Greek lower-case alpha with psili and varia and ypogegrammeni");
+            assert.areEqual("\u1F0B\u0399", "\u1F83".toUpperCase(), "Expecting Greek lower-case alpha with dasia and varia and ypogegrammeni");
+            assert.areEqual("\u1F0C\u0399", "\u1F84".toUpperCase(), "Expecting Greek lower-case alpha with psili and oxia and ypogegrammeni");
+            assert.areEqual("\u1F0D\u0399", "\u1F85".toUpperCase(), "Expecting Greek lower-case alpha with dasia and oxia and ypogegrammeni");
+            assert.areEqual("\u1F0E\u0399", "\u1F86".toUpperCase(), "Expecting Greek lower-case alpha with psili and perispomeni and ypogegrammeni");
+            assert.areEqual("\u1F0F\u0399", "\u1F87".toUpperCase(), "Expecting Greek lower-case alpha with dasia and perispomeni and ypogegrammeni");
+            assert.areEqual("\u1F08\u0399", "\u1F88".toUpperCase(), "Expecting Greek upper-case alpha with psili and prosgegrammeni");
+            assert.areEqual("\u1F09\u0399", "\u1F89".toUpperCase(), "Expecting Greek upper-case alpha with dasia and prosgegrammeni");
+            assert.areEqual("\u1F0A\u0399", "\u1F8A".toUpperCase(), "Expecting Greek upper-case alpha with psili and varia and prosgegrammeni");
+            assert.areEqual("\u1F0B\u0399", "\u1F8B".toUpperCase(), "Expecting Greek upper-case alpha with dasia and varia and prosgegrammeni");
+            assert.areEqual("\u1F0C\u0399", "\u1F8C".toUpperCase(), "Expecting Greek upper-case alpha with psili and oxia and prosgegrammeni");
+            assert.areEqual("\u1F0D\u0399", "\u1F8D".toUpperCase(), "Expecting Greek upper-case alpha with dasia and oxia and prosgegrammeni");
+            assert.areEqual("\u1F0E\u0399", "\u1F8E".toUpperCase(), "Expecting Greek upper-case alpha with psili and perispomeni and prosgegrammeni");
+            assert.areEqual("\u1F0F\u0399", "\u1F8F".toUpperCase(), "Expecting Greek upper-case alpha with dasia and perispomeni and prosgegrammeni");
+            assert.areEqual("\u1F28\u0399", "\u1F90".toUpperCase(), "Expecting Greek lower-case eta with psili and ypogegrammeni");
+            assert.areEqual("\u1F29\u0399", "\u1F91".toUpperCase(), "Expecting Greek lower-case eta with dasia and ypogegrammeni");
+            assert.areEqual("\u1F2A\u0399", "\u1F92".toUpperCase(), "Expecting Greek lower-case eta with psili and varia and ypogegrammeni");
+            assert.areEqual("\u1F2B\u0399", "\u1F93".toUpperCase(), "Expecting Greek lower-case eta with dasia and varia and ypogegrammeni");
+            assert.areEqual("\u1F2C\u0399", "\u1F94".toUpperCase(), "Expecting Greek lower-case eta with psili and oxia and ypogegrammeni");
+            assert.areEqual("\u1F2D\u0399", "\u1F95".toUpperCase(), "Expecting Greek lower-case eta with dasia and oxia and ypogegrammeni");
+            assert.areEqual("\u1F2E\u0399", "\u1F96".toUpperCase(), "Expecting Greek lower-case eta with psili and perispomeni and ypogegrammeni");
+            assert.areEqual("\u1F2F\u0399", "\u1F97".toUpperCase(), "Expecting Greek lower-case eta with dasia and perispomeni and ypogegrammeni");
+            assert.areEqual("\u1F28\u0399", "\u1F98".toUpperCase(), "Expecting Greek upper-case eta with psili and prosgegrammeni");
+            assert.areEqual("\u1F29\u0399", "\u1F99".toUpperCase(), "Expecting Greek upper-case eta with dasia and prosgegrammeni");
+            assert.areEqual("\u1F2A\u0399", "\u1F9A".toUpperCase(), "Expecting Greek upper-case eta with psili and varia and prosgegrammeni");
+            assert.areEqual("\u1F2B\u0399", "\u1F9B".toUpperCase(), "Expecting Greek upper-case eta with dasia and varia and prosgegrammeni");
+            assert.areEqual("\u1F2C\u0399", "\u1F9C".toUpperCase(), "Expecting Greek upper-case eta with psili and oxia and prosgegrammeni");
+            assert.areEqual("\u1F2D\u0399", "\u1F9D".toUpperCase(), "Expecting Greek upper-case eta with dasia and oxia and prosgegrammeni");
+            assert.areEqual("\u1F2E\u0399", "\u1F9E".toUpperCase(), "Expecting Greek upper-case eta with psili and perispomeni and prosgegrammeni");
+            assert.areEqual("\u1F2F\u0399", "\u1F9F".toUpperCase(), "Expecting Greek upper-case eta with dasia and perispomeni and prosgegrammeni");
+            assert.areEqual("\u1F68\u0399", "\u1FA0".toUpperCase(), "Expecting Greek lower-case omega with psili and ypogegrammeni");
+            assert.areEqual("\u1F69\u0399", "\u1FA1".toUpperCase(), "Expecting Greek lower-case omega with dasia and ypogegrammeni");
+            assert.areEqual("\u1F6A\u0399", "\u1FA2".toUpperCase(), "Expecting Greek lower-case omega with psili and varia and ypogegrammeni");
+            assert.areEqual("\u1F6B\u0399", "\u1FA3".toUpperCase(), "Expecting Greek lower-case omega with dasia and varia and ypogegrammeni");
+            assert.areEqual("\u1F6C\u0399", "\u1FA4".toUpperCase(), "Expecting Greek lower-case omega with psili and oxia and ypogegrammeni");
+            assert.areEqual("\u1F6D\u0399", "\u1FA5".toUpperCase(), "Expecting Greek lower-case omega with dasia and oxia and ypogegrammeni");
+            assert.areEqual("\u1F6E\u0399", "\u1FA6".toUpperCase(), "Expecting Greek lower-case omega with psili and perispomeni and ypogegrammeni");
+            assert.areEqual("\u1F6F\u0399", "\u1FA7".toUpperCase(), "Expecting Greek lower-case omega with dasia and perispomeni and ypogegrammeni");
+            assert.areEqual("\u1F68\u0399", "\u1FA8".toUpperCase(), "Expecting Greek upper-case omega with psili and prosgegrammeni");
+            assert.areEqual("\u1F69\u0399", "\u1FA9".toUpperCase(), "Expecting Greek upper-case omega with dasia and prosgegrammeni");
+            assert.areEqual("\u1F6A\u0399", "\u1FAA".toUpperCase(), "Expecting Greek upper-case omega with psili and varia and prosgegrammeni");
+            assert.areEqual("\u1F6B\u0399", "\u1FAB".toUpperCase(), "Expecting Greek upper-case omega with dasia and varia and prosgegrammeni");
+            assert.areEqual("\u1F6C\u0399", "\u1FAC".toUpperCase(), "Expecting Greek upper-case omega with psili and oxia and prosgegrammeni");
+            assert.areEqual("\u1F6D\u0399", "\u1FAD".toUpperCase(), "Expecting Greek upper-case omega with dasia and oxia and prosgegrammeni");
+            assert.areEqual("\u1F6E\u0399", "\u1FAE".toUpperCase(), "Expecting Greek upper-case omega with psili and perispomeni and prosgegrammeni");
+            assert.areEqual("\u1F6F\u0399", "\u1FAF".toUpperCase(), "Expecting Greek upper-case omega with dasia and perispomeni and prosgegrammeni");
+            assert.areEqual("\u0391\u0399", "\u1FB3".toUpperCase(), "Expecting Greek lower-case alpha with ypogegrammeni");
+            assert.areEqual("\u0391\u0399", "\u1FBC".toUpperCase(), "Expecting Greek upper-case alpha with prosgegrammeni");
+            assert.areEqual("\u0397\u0399", "\u1FC3".toUpperCase(), "Expecting Greek lower-case eta with ypogegrammeni");
+            assert.areEqual("\u0397\u0399", "\u1FCC".toUpperCase(), "Expecting Greek upper-case eta with prosgegrammeni");
+            assert.areEqual("\u03A9\u0399", "\u1FF3".toUpperCase(), "Expecting Greek lower-case omega with ypogegrammeni");
+            assert.areEqual("\u03A9\u0399", "\u1FFC".toUpperCase(), "Expecting Greek upper-case omega with prosgegrammeni");
+            assert.areEqual("\u1FBA\u0399", "\u1FB2".toUpperCase(), "Expecting Greek lower-case alpha with varia and ypogegrammeni");
+            assert.areEqual("\u0386\u0399", "\u1FB4".toUpperCase(), "Expecting Greek lower-case alpha with oxia and ypogegrammeni");
+            assert.areEqual("\u1FCA\u0399", "\u1FC2".toUpperCase(), "Expecting Greek lower-case eta with varia and ypogegrammeni");
+            assert.areEqual("\u0389\u0399", "\u1FC4".toUpperCase(), "Expecting Greek lower-case eta with oxia and ypogegrammeni");
+            assert.areEqual("\u1FFA\u0399", "\u1FF2".toUpperCase(), "Expecting Greek lower-case omega with varia and ypogegrammeni");
+            assert.areEqual("\u038F\u0399", "\u1FF4".toUpperCase(), "Expecting Greek lower-case omega with oxia and ypogegrammeni");
+            assert.areEqual("\u0391\u0342\u0399", "\u1FB7".toUpperCase(), "Expecting Greek lower-case alpha with perispomeni and ypogegrammeni");
+            assert.areEqual("\u0397\u0342\u0399", "\u1FC7".toUpperCase(), "Expecting Greek lower-case eta with perispomeni and ypogegrammeni");
+            assert.areEqual("\u03A9\u0342\u0399", "\u1FF7".toUpperCase(), "Expecting Greek lower-case omega with perispomeni and ypogegrammeni");
+        }
+    },
+    {
+        name: "Special casing toLowerCase",
+        body: function () {
+            assert.areEqual("\u00DF", "\u00DF".toLowerCase(), "Expecting Latin lower-case sharp s");
+            assert.areEqual("\uFB00", "\uFB00".toLowerCase(), "Expecting Latin small ligature ff");
+            assert.areEqual("\uFB01", "\uFB01".toLowerCase(), "Expecting Latin small ligature fi");
+            assert.areEqual("\uFB02", "\uFB02".toLowerCase(), "Expecting Latin small ligature fl");
+            assert.areEqual("\uFB03", "\uFB03".toLowerCase(), "Expecting Latin small ligature ffi");
+            assert.areEqual("\uFB04", "\uFB04".toLowerCase(), "Expecting Latin small ligature ffl");
+            assert.areEqual("\uFB05", "\uFB05".toLowerCase(), "Expecting Latin small ligature long s t");
+            assert.areEqual("\uFB06", "\uFB06".toLowerCase(), "Expecting Latin small ligature st");
+            assert.areEqual("\u0587", "\u0587".toLowerCase(), "Expecting Armenian small ligature ech yiwn");
+            assert.areEqual("\uFB13", "\uFB13".toLowerCase(), "Expecting Armenian small ligature men now");
+            assert.areEqual("\uFB14", "\uFB14".toLowerCase(), "Expecting Armenian small ligature men ech");
+            assert.areEqual("\uFB15", "\uFB15".toLowerCase(), "Expecting Armenian small ligature men ini");
+            assert.areEqual("\uFB16", "\uFB16".toLowerCase(), "Expecting Armenian small ligature vew now");
+            assert.areEqual("\uFB17", "\uFB17".toLowerCase(), "Expecting Armenian small ligature men xeh");
+            assert.areEqual("\u0149", "\u0149".toLowerCase(), "Expecting Latin lower-case n preceded by apostrophe");
+            assert.areEqual("\u0390", "\u0390".toLowerCase(), "Expecting Greek lower-case iota with dialytika and tonos");
+            assert.areEqual("\u03B0", "\u03B0".toLowerCase(), "Expecting Greek lower-case upsilon with dialytika and tonos");
+            assert.areEqual("\u01F0", "\u01F0".toLowerCase(), "Expecting Latin lower-case j with caron");
+            assert.areEqual("\u1E96", "\u1E96".toLowerCase(), "Expecting Latin lower-case h with line below");
+            assert.areEqual("\u1E97", "\u1E97".toLowerCase(), "Expecting Latin lower-case t with diaeresis");
+            assert.areEqual("\u1E98", "\u1E98".toLowerCase(), "Expecting Latin lower-case w with ring above");
+            assert.areEqual("\u1E99", "\u1E99".toLowerCase(), "Expecting Latin lower-case y with ring above");
+            assert.areEqual("\u1E9A", "\u1E9A".toLowerCase(), "Expecting Latin lower-case a with right half ring");
+            assert.areEqual("\u1F50", "\u1F50".toLowerCase(), "Expecting Greek lower-case upsilon with psili");
+            assert.areEqual("\u1F52", "\u1F52".toLowerCase(), "Expecting Greek lower-case upsilon with psili and varia");
+            assert.areEqual("\u1F54", "\u1F54".toLowerCase(), "Expecting Greek lower-case upsilon with psili and oxia");
+            assert.areEqual("\u1F56", "\u1F56".toLowerCase(), "Expecting Greek lower-case upsilon with psili and perispomeni");
+            assert.areEqual("\u1FB6", "\u1FB6".toLowerCase(), "Expecting Greek lower-case alpha with perispomeni");
+            assert.areEqual("\u1FC6", "\u1FC6".toLowerCase(), "Expecting Greek lower-case eta with perispomeni");
+            assert.areEqual("\u1FD2", "\u1FD2".toLowerCase(), "Expecting Greek lower-case iota with dialytika and varia");
+            assert.areEqual("\u1FD3", "\u1FD3".toLowerCase(), "Expecting Greek lower-case iota with dialytika and oxia");
+            assert.areEqual("\u1FD6", "\u1FD6".toLowerCase(), "Expecting Greek lower-case iota with perispomeni");
+            assert.areEqual("\u1FD7", "\u1FD7".toLowerCase(), "Expecting Greek lower-case iota with dialytika and perispomeni");
+            assert.areEqual("\u1FE2", "\u1FE2".toLowerCase(), "Expecting Greek lower-case upsilon with dialytika and varia");
+            assert.areEqual("\u1FE3", "\u1FE3".toLowerCase(), "Expecting Greek lower-case upsilon with dialytika and oxia");
+            assert.areEqual("\u1FE4", "\u1FE4".toLowerCase(), "Expecting Greek lower-case rho with psili");
+            assert.areEqual("\u1FE6", "\u1FE6".toLowerCase(), "Expecting Greek lower-case upsilon with perispomeni");
+            assert.areEqual("\u1FE7", "\u1FE7".toLowerCase(), "Expecting Greek lower-case upsilon with dialytika and perispomeni");
+            assert.areEqual("\u1FF6", "\u1FF6".toLowerCase(), "Expecting Greek lower-case omega with perispomeni");
+            assert.areEqual("\u1F80", "\u1F80".toLowerCase(), "Expecting Greek lower-case alpha with psili and ypogegrammeni");
+            assert.areEqual("\u1F81", "\u1F81".toLowerCase(), "Expecting Greek lower-case alpha with dasia and ypogegrammeni");
+            assert.areEqual("\u1F82", "\u1F82".toLowerCase(), "Expecting Greek lower-case alpha with psili and varia and ypogegrammeni");
+            assert.areEqual("\u1F83", "\u1F83".toLowerCase(), "Expecting Greek lower-case alpha with dasia and varia and ypogegrammeni");
+            assert.areEqual("\u1F84", "\u1F84".toLowerCase(), "Expecting Greek lower-case alpha with psili and oxia and ypogegrammeni");
+            assert.areEqual("\u1F85", "\u1F85".toLowerCase(), "Expecting Greek lower-case alpha with dasia and oxia and ypogegrammeni");
+            assert.areEqual("\u1F86", "\u1F86".toLowerCase(), "Expecting Greek lower-case alpha with psili and perispomeni and ypogegrammeni");
+            assert.areEqual("\u1F87", "\u1F87".toLowerCase(), "Expecting Greek lower-case alpha with dasia and perispomeni and ypogegrammeni");
+            assert.areEqual("\u1F80", "\u1F88".toLowerCase(), "Expecting Greek upper-case alpha with psili and prosgegrammeni");
+            assert.areEqual("\u1F81", "\u1F89".toLowerCase(), "Expecting Greek upper-case alpha with dasia and prosgegrammeni");
+            assert.areEqual("\u1F82", "\u1F8A".toLowerCase(), "Expecting Greek upper-case alpha with psili and varia and prosgegrammeni");
+            assert.areEqual("\u1F83", "\u1F8B".toLowerCase(), "Expecting Greek upper-case alpha with dasia and varia and prosgegrammeni");
+            assert.areEqual("\u1F84", "\u1F8C".toLowerCase(), "Expecting Greek upper-case alpha with psili and oxia and prosgegrammeni");
+            assert.areEqual("\u1F85", "\u1F8D".toLowerCase(), "Expecting Greek upper-case alpha with dasia and oxia and prosgegrammeni");
+            assert.areEqual("\u1F86", "\u1F8E".toLowerCase(), "Expecting Greek upper-case alpha with psili and perispomeni and prosgegrammeni");
+            assert.areEqual("\u1F87", "\u1F8F".toLowerCase(), "Expecting Greek upper-case alpha with dasia and perispomeni and prosgegrammeni");
+            assert.areEqual("\u1F90", "\u1F90".toLowerCase(), "Expecting Greek lower-case eta with psili and ypogegrammeni");
+            assert.areEqual("\u1F91", "\u1F91".toLowerCase(), "Expecting Greek lower-case eta with dasia and ypogegrammeni");
+            assert.areEqual("\u1F92", "\u1F92".toLowerCase(), "Expecting Greek lower-case eta with psili and varia and ypogegrammeni");
+            assert.areEqual("\u1F93", "\u1F93".toLowerCase(), "Expecting Greek lower-case eta with dasia and varia and ypogegrammeni");
+            assert.areEqual("\u1F94", "\u1F94".toLowerCase(), "Expecting Greek lower-case eta with psili and oxia and ypogegrammeni");
+            assert.areEqual("\u1F95", "\u1F95".toLowerCase(), "Expecting Greek lower-case eta with dasia and oxia and ypogegrammeni");
+            assert.areEqual("\u1F96", "\u1F96".toLowerCase(), "Expecting Greek lower-case eta with psili and perispomeni and ypogegrammeni");
+            assert.areEqual("\u1F97", "\u1F97".toLowerCase(), "Expecting Greek lower-case eta with dasia and perispomeni and ypogegrammeni");
+            assert.areEqual("\u1F90", "\u1F98".toLowerCase(), "Expecting Greek upper-case eta with psili and prosgegrammeni");
+            assert.areEqual("\u1F91", "\u1F99".toLowerCase(), "Expecting Greek upper-case eta with dasia and prosgegrammeni");
+            assert.areEqual("\u1F92", "\u1F9A".toLowerCase(), "Expecting Greek upper-case eta with psili and varia and prosgegrammeni");
+            assert.areEqual("\u1F93", "\u1F9B".toLowerCase(), "Expecting Greek upper-case eta with dasia and varia and prosgegrammeni");
+            assert.areEqual("\u1F94", "\u1F9C".toLowerCase(), "Expecting Greek upper-case eta with psili and oxia and prosgegrammeni");
+            assert.areEqual("\u1F95", "\u1F9D".toLowerCase(), "Expecting Greek upper-case eta with dasia and oxia and prosgegrammeni");
+            assert.areEqual("\u1F96", "\u1F9E".toLowerCase(), "Expecting Greek upper-case eta with psili and perispomeni and prosgegrammeni");
+            assert.areEqual("\u1F97", "\u1F9F".toLowerCase(), "Expecting Greek upper-case eta with dasia and perispomeni and prosgegrammeni");
+            assert.areEqual("\u1FA0", "\u1FA0".toLowerCase(), "Expecting Greek lower-case omega with psili and ypogegrammeni");
+            assert.areEqual("\u1FA1", "\u1FA1".toLowerCase(), "Expecting Greek lower-case omega with dasia and ypogegrammeni");
+            assert.areEqual("\u1FA2", "\u1FA2".toLowerCase(), "Expecting Greek lower-case omega with psili and varia and ypogegrammeni");
+            assert.areEqual("\u1FA3", "\u1FA3".toLowerCase(), "Expecting Greek lower-case omega with dasia and varia and ypogegrammeni");
+            assert.areEqual("\u1FA4", "\u1FA4".toLowerCase(), "Expecting Greek lower-case omega with psili and oxia and ypogegrammeni");
+            assert.areEqual("\u1FA5", "\u1FA5".toLowerCase(), "Expecting Greek lower-case omega with dasia and oxia and ypogegrammeni");
+            assert.areEqual("\u1FA6", "\u1FA6".toLowerCase(), "Expecting Greek lower-case omega with psili and perispomeni and ypogegrammeni");
+            assert.areEqual("\u1FA7", "\u1FA7".toLowerCase(), "Expecting Greek lower-case omega with dasia and perispomeni and ypogegrammeni");
+            assert.areEqual("\u1FA0", "\u1FA8".toLowerCase(), "Expecting Greek upper-case omega with psili and prosgegrammeni");
+            assert.areEqual("\u1FA1", "\u1FA9".toLowerCase(), "Expecting Greek upper-case omega with dasia and prosgegrammeni");
+            assert.areEqual("\u1FA2", "\u1FAA".toLowerCase(), "Expecting Greek upper-case omega with psili and varia and prosgegrammeni");
+            assert.areEqual("\u1FA3", "\u1FAB".toLowerCase(), "Expecting Greek upper-case omega with dasia and varia and prosgegrammeni");
+            assert.areEqual("\u1FA4", "\u1FAC".toLowerCase(), "Expecting Greek upper-case omega with psili and oxia and prosgegrammeni");
+            assert.areEqual("\u1FA5", "\u1FAD".toLowerCase(), "Expecting Greek upper-case omega with dasia and oxia and prosgegrammeni");
+            assert.areEqual("\u1FA6", "\u1FAE".toLowerCase(), "Expecting Greek upper-case omega with psili and perispomeni and prosgegrammeni");
+            assert.areEqual("\u1FA7", "\u1FAF".toLowerCase(), "Expecting Greek upper-case omega with dasia and perispomeni and prosgegrammeni");
+            assert.areEqual("\u1FB3", "\u1FB3".toLowerCase(), "Expecting Greek lower-case alpha with ypogegrammeni");
+            assert.areEqual("\u1FB3", "\u1FBC".toLowerCase(), "Expecting Greek upper-case alpha with prosgegrammeni");
+            assert.areEqual("\u1FC3", "\u1FC3".toLowerCase(), "Expecting Greek lower-case eta with ypogegrammeni");
+            assert.areEqual("\u1FC3", "\u1FCC".toLowerCase(), "Expecting Greek upper-case eta with prosgegrammeni");
+            assert.areEqual("\u1FF3", "\u1FF3".toLowerCase(), "Expecting Greek lower-case omega with ypogegrammeni");
+            assert.areEqual("\u1FF3", "\u1FFC".toLowerCase(), "Expecting Greek upper-case omega with prosgegrammeni");
+            assert.areEqual("\u1FB2", "\u1FB2".toLowerCase(), "Expecting Greek lower-case alpha with varia and ypogegrammeni");
+            assert.areEqual("\u1FB4", "\u1FB4".toLowerCase(), "Expecting Greek lower-case alpha with oxia and ypogegrammeni");
+            assert.areEqual("\u1FC2", "\u1FC2".toLowerCase(), "Expecting Greek lower-case eta with varia and ypogegrammeni");
+            assert.areEqual("\u1FC4", "\u1FC4".toLowerCase(), "Expecting Greek lower-case eta with oxia and ypogegrammeni");
+            assert.areEqual("\u1FF2", "\u1FF2".toLowerCase(), "Expecting Greek lower-case omega with varia and ypogegrammeni");
+            assert.areEqual("\u1FF4", "\u1FF4".toLowerCase(), "Expecting Greek lower-case omega with oxia and ypogegrammeni");
+            assert.areEqual("\u1FB7", "\u1FB7".toLowerCase(), "Expecting Greek lower-case alpha with perispomeni and ypogegrammeni");
+            assert.areEqual("\u1FC7", "\u1FC7".toLowerCase(), "Expecting Greek lower-case eta with perispomeni and ypogegrammeni");
+            assert.areEqual("\u1FF7", "\u1FF7".toLowerCase(), "Expecting Greek lower-case omega with perispomeni and ypogegrammeni");
+            assert.areEqual("\u03C3", "\u03A3".toLowerCase(), "Expecting single Greek upper-case sigma");
+            assert.areEqual("\u0345\u03C3", "\u0345\u03A3".toLowerCase(), "Expecting sigma preceded by combining Greek ypogegrammeni (0345)");
+            assert.areEqual("a\u03C3b", "A\u03A3B".toLowerCase(), "Expecting sigma followed by Latin upper-case b");
+            assert.areEqual("a\u03C3\uD835\uDCA2", "A\u03A3\uD835\uDCA2".toLowerCase(), "Expecting sigma followed by mathematical script capital g (d835 dca2 = 1d4a2)");
+            assert.areEqual("a\u03C3.b", "A\u03A3.b".toLowerCase(), "Expecting sigma followed by full stop");
+            assert.areEqual("a\u03C3\u00ADb", "A\u03A3\u00ADB".toLowerCase(), "Expecting sigma followed by soft hyphen (00ad)");
+            assert.areEqual("a\u03C3\uD834\uDE42b", "A\u03A3\uD834\uDE42B".toLowerCase(), "Expecting sigma followed by combining Greek musical triseme (d834 de42 = 1d242)");
+            assert.areEqual("a\u03C3\u0345\u03B1", "A\u03A3\u0345\u0391".toLowerCase(), "Expecting sigma followed by combining Greek ypogegrammeni (0345), Greek upper-case alpha (0391)");
+
+            if (WScript.Platform.INTL_LIBRARY === "icu") {
+                assert.areEqual("\u0069\u0307", "\u0130".toLowerCase(), "Expecting Latin upper-case i with dot above");
+                assert.areEqual("a\u03C2", "A\u03A3".toLowerCase(), "Expecting sigma preceded by Latin upper-case a");
+                assert.areEqual("\uD835\uDCA2\u03C2", "\uD835\uDCA2\u03A3".toLowerCase(), "Expecting sigma preceded by mathematical script capital g (d835 dca2 = 1d4a2)");
+                assert.areEqual("a.\u03C2", "A.\u03A3".toLowerCase(), "Expecting sigma preceded by full stop");
+                assert.areEqual("a\u00AD\u03C2", "A\u00AD\u03A3".toLowerCase(), "Expecting sigma preceded by soft hyphen (00ad)");
+                assert.areEqual("a\uD834\uDE42\u03C2", "A\uD834\uDE42\u03A3".toLowerCase(), "Expecting sigma preceded by combining Greek musical triseme (d834 de42 = 1d242)");
+                assert.areEqual("\u03B1\u0345\u03C2", "\u0391\u0345\u03A3".toLowerCase(), "Expecting sigma preceded by Greek upper-case alpha (0391), combining Greek ypogegrammeni (0345)");
+                assert.areEqual("a\u03C2\u0345", "A\u03A3\u0345".toLowerCase(), "Expecting sigma followed by combining Greek ypogegrammeni (0345)");
+            }
+        }
+    },
 ];
 
 testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });