Repository: sindresorhus/normalize-url (js)
Issue: sortQueryParameters encodes the query string
Is it supposed to be like this?

**sortQueryParameters: true**

```js
normalizeUrl('http://sindresorhus.com/?url=http://example.com', {
    sortQueryParameters: true
});
//=> 'http://sindresorhus.com/?url=http%3A%2F%2Fexample.com'
```

**sortQueryParameters: false**

```js
normalizeUrl('http://sindresorhus.com/?url=http://example.com', {
    sortQueryParameters: false
});
//=> 'http://sindresorhus.com/?url=http://example.com'
```
Pull request description: Fix encoding consistency with sorted and non-sorted URL parameters
Fix #149

Currently `normalize-url` encodes URL parameters when `sortQueryParameters` is `true` and leaves them as they are when not. This behaviour is confusing as a user would not expect sorting the parameters to affect the encoding. This PR tries to ensure the search parameters are always decoded, since `normalize-url` already always decodes the pathname and does not intend to sanitize the URL; https://github.com/sindresorhus/normalize-url/blob/6ea4038783d298d499583db07a820e5ca8ff2721/index.js#L160 

Patch:
diff --git a/index.js b/index.js
index 4727186..70673a8 100644
--- a/index.js
+++ b/index.js
@@ -206,6 +206,11 @@ export default function normalizeUrl(urlString, options) {
 	// Sort query parameters
 	if (options.sortQueryParameters) {
 		urlObject.searchParams.sort();
+
+		// Calling `.sort()` encodes the search parameters, so we need to decode them again.
+		try {
+			urlObject.search = decodeURIComponent(urlObject.search);
+		} catch {}
 	}
 
 	if (options.removeTrailingSlash) {


Test patch:
diff --git a/test.js b/test.js
index 149b51a..4483b5b 100644
--- a/test.js
+++ b/test.js
@@ -28,7 +28,7 @@ test('main', t => {
 	t.is(normalizeUrl('http://sindresorhus.com/?'), 'http://sindresorhus.com');
 	t.is(normalizeUrl('êxample.com'), 'http://xn--xample-hva.com');
 	t.is(normalizeUrl('http://sindresorhus.com/?b=bar&a=foo'), 'http://sindresorhus.com/?a=foo&b=bar');
-	t.is(normalizeUrl('http://sindresorhus.com/?foo=bar*|<>:"'), 'http://sindresorhus.com/?foo=bar*%7C%3C%3E%3A%22');
+	t.is(normalizeUrl('http://sindresorhus.com/?foo=bar*|<>:"'), 'http://sindresorhus.com/?foo=bar*|%3C%3E:%22');
 	t.is(normalizeUrl('http://sindresorhus.com:5000'), 'http://sindresorhus.com:5000');
 	t.is(normalizeUrl('//sindresorhus.com/', {normalizeProtocol: false}), '//sindresorhus.com');
 	t.is(normalizeUrl('//sindresorhus.com:80/', {normalizeProtocol: false}), '//sindresorhus.com');
@@ -40,7 +40,7 @@ test('main', t => {
 	t.is(normalizeUrl('sindre://www.sorhus.com'), 'sindre://sorhus.com');
 	t.is(normalizeUrl('sindre://www.sorhus.com/'), 'sindre://sorhus.com');
 	t.is(normalizeUrl('sindre://www.sorhus.com/foo/bar'), 'sindre://sorhus.com/foo/bar');
-	t.is(normalizeUrl('https://i.vimeocdn.com/filter/overlay?src0=https://i.vimeocdn.com/video/598160082_1280x720.jpg&src1=https://f.vimeocdn.com/images_v6/share/play_icon_overlay.png'), 'https://i.vimeocdn.com/filter/overlay?src0=https%3A%2F%2Fi.vimeocdn.com%2Fvideo%2F598160082_1280x720.jpg&src1=https%3A%2F%2Ff.vimeocdn.com%2Fimages_v6%2Fshare%2Fplay_icon_overlay.png');
+	t.is(normalizeUrl('https://i.vimeocdn.com/filter/overlay?src0=https://i.vimeocdn.com/video/598160082_1280x720.jpg&src1=https://f.vimeocdn.com/images_v6/share/play_icon_overlay.png'), 'https://i.vimeocdn.com/filter/overlay?src0=https://i.vimeocdn.com/video/598160082_1280x720.jpg&src1=https://f.vimeocdn.com/images_v6/share/play_icon_overlay.png');
 });
 
 test('stripAuthentication option', t => {
@@ -263,6 +263,7 @@ test('sortQueryParameters option', t => {
 	t.is(normalizeUrl('http://sindresorhus.com/?b=Y&c=X&a=Z&d=W', options2), 'http://sindresorhus.com/?b=Y&c=X&a=Z&d=W');
 	t.is(normalizeUrl('http://sindresorhus.com/?a=Z&d=W&b=Y&c=X', options2), 'http://sindresorhus.com/?a=Z&d=W&b=Y&c=X');
 	t.is(normalizeUrl('http://sindresorhus.com/', options2), 'http://sindresorhus.com');
+	t.is(normalizeUrl('http://sindresorhus.com/?a=/path', options1), normalizeUrl('http://sindresorhus.com/?a=/path', options2));
 });
 
 test('invalid urls', t => {