diff --git a/packages/react-native/Libraries/Blob/URLSearchParams.js b/packages/react-native/Libraries/Blob/URLSearchParams.js index 3b7877ebce38..5d05022a769a 100644 --- a/packages/react-native/Libraries/Blob/URLSearchParams.js +++ b/packages/react-native/Libraries/Blob/URLSearchParams.js @@ -121,7 +121,9 @@ export class URLSearchParams { sort(): void { this._searchParams = new Map( - [...this._searchParams.entries()].sort(([a], [b]) => a.localeCompare(b)), + [...this._searchParams.entries()].sort(([a], [b]) => + a < b ? -1 : a > b ? 1 : 0, + ), ); } diff --git a/packages/react-native/Libraries/Blob/__tests__/URL-itest.js b/packages/react-native/Libraries/Blob/__tests__/URL-itest.js index 2cf5e9d86924..a8366268df4f 100644 --- a/packages/react-native/Libraries/Blob/__tests__/URL-itest.js +++ b/packages/react-native/Libraries/Blob/__tests__/URL-itest.js @@ -133,10 +133,6 @@ describe('URL', function () { expect(urlParams.has('query')).toBe(true); expect(urlParams.has('key')).toBe(false); - // Sorting URLSearchParams is not exercised here: URLSearchParams.sort() - // relies on String.prototype.localeCompare, which requires ICU collation - // support that is unavailable in Fantom's Hermes build. - // searchParams.set() should replace values not duplicate them const urlWithSearchParams = new URL( 'https://example.com/api/data?foo=bar&baz=qux', @@ -166,3 +162,43 @@ describe('URL', function () { ); }); }); + +describe('URLSearchParams.sort', () => { + it('sorts names by code units while preserving the order of duplicate values', () => { + const params = new URLSearchParams([ + ['a', 'first'], + ['Z', 'uppercase'], + ['a', 'second'], + ['z', 'lowercase'], + ['ä', 'umlaut'], + ]); + + params.sort(); + + expect(Array.from(params.entries())).toEqual([ + ['Z', 'uppercase'], + ['a', 'first'], + ['a', 'second'], + ['z', 'lowercase'], + ['ä', 'umlaut'], + ]); + }); + + it('compares supplementary characters using their UTF-16 code units', () => { + const params = new URLSearchParams([ + ['\uE000', 'private use'], + ['\uD83D\uDE02', 'second supplementary'], + ['\uFF03', 'fullwidth'], + ['\uD83D\uDE00', 'first supplementary'], + ]); + + params.sort(); + + expect(Array.from(params.entries())).toEqual([ + ['\uD83D\uDE00', 'first supplementary'], + ['\uD83D\uDE02', 'second supplementary'], + ['\uE000', 'private use'], + ['\uFF03', 'fullwidth'], + ]); + }); +});