├── .gitignore ├── .npmrc ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── babel.config.js ├── lib ├── constants.ts └── immurl.ts ├── package-lock.json ├── package.json ├── tests ├── ImmutableHeaders.test.ts ├── ImmutableURL.test.ts └── ImmutableURLSearchParams.test.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | docs 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.com/ 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | You can view the changelog in GitHub Releases here: https://github.com/tom-sherman/immurl/releases 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Tom Sherman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # immurl 2 | 3 | 🔗 A tiny (< 500B), 0-dependency, immutable URL library, backed by the native whatwg URL. 🎉 Now with immutable `Headers` support! 4 | 5 | ## Install 6 | 7 | ``` 8 | npm install immurl 9 | ``` 10 | 11 | Because immurl uses the native whatwg URL API under the hood you'll need a polyfill to support environments that [don't implement this API](https://developer.mozilla.org/en-US/docs/Web/API/URL#Browser_compatibility) eg. IE11. 12 | 13 | ## Usage 14 | 15 | ### `ImmutableURL` 16 | 17 | `ImmutableURL` works as you expect, it contains all of the properties of the native [URL API](https://developer.mozilla.org/en-US/docs/Web/API/URL). 18 | 19 | ```typescript 20 | import { ImmutableURL } from 'immurl'; 21 | 22 | const url = new ImmutableURL('https://example.com'); 23 | 24 | console.log(url.href); // 'https://example.com' 25 | 26 | // Set properties with the .set() method 27 | 28 | let newUrl = url.set('pathname', '/login'); 29 | 30 | // Because the set API is immutable you can chain calls to .set() 31 | 32 | newUrl = url.set('pathname', '/bar').set('hash', '#heading'); // https://example.com/bar#heading 33 | ``` 34 | 35 | ### `ImmutableURLSearchParams` 36 | 37 | immurl also contains an immutable version of the [`URLSearchParams` API](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams); `ImmutableURLSearchParams`. 38 | 39 | The API for `ImmutableURLSearchParams` is exactly the same as the native version except the methods that usually mutate (`.append()`, `.delete()`, `.sort()`) return a new `ImmutableURLSearchParams` instance. 40 | 41 | ```typescript 42 | import { ImmutableURLSearchParams } from 'immurl'; 43 | 44 | let params = new ImmutableURLSearchParams('q=URLUtils.searchParams&topic=api'); 45 | 46 | params = params.append('foo', 'bar').delete('q'); // topic=api&foo=bar 47 | ``` 48 | 49 | The `searchParams` property of `ImmutableURL` returns an `ImmutableURLSearchParams`. 50 | 51 | ```typescript 52 | const url = new ImmutableURL('https://example.com?foo=bar'); 53 | 54 | const newParams = url.searchParams 55 | .append('q', 'search-term') 56 | .set('foo', 'fuz') 57 | .sort(); 58 | 59 | // url.searchParams is unaffected (thanks to immutability 🎉) 60 | 61 | // We can pass our newParams into url.set() to create a new url with the updated params 62 | const newUrl = url.set('searchParams', newParams); 63 | 64 | // The following code is equvalent to the above 65 | 66 | const newUrl2 = url.set( 67 | 'searchParams', 68 | url.searchParams.append('q', 'search-term').set('foo', 'fuz').sort() 69 | ); 70 | ``` 71 | 72 | ### `ImmutableHeaders` 73 | 74 | Not strictly related to whatg URLs, but it's shoehorned in here because it's kinda related and they're usually used together. 75 | 76 | ```typescript 77 | import { ImmutableHeaders } from 'immurl'; 78 | 79 | const headers = new ImmutableHeaders({ 80 | foo: 'bar' 81 | }); 82 | 83 | const newHeaders = headers.set('foo', 'fuz'); 84 | 85 | console.log(headers.get('foo')); // Logs "bar" 86 | console.log(newHeaders.get('foo')); // Logs "fuz" 87 | ``` 88 | 89 | ## API 90 | 91 | See the docs at https://immurl.netlify.app/ 92 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-env', { targets: { node: 'current' } }], 4 | '@babel/preset-typescript', 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /lib/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @internal 3 | */ 4 | export const URL_PROPERTIES = [ 5 | 'hash', 6 | 'host', 7 | 'hostname', 8 | 'href', 9 | 'origin', 10 | 'password', 11 | 'pathname', 12 | 'port', 13 | 'protocol', 14 | 'search', 15 | 'searchParams', 16 | 'username', 17 | ] as const; 18 | -------------------------------------------------------------------------------- /lib/immurl.ts: -------------------------------------------------------------------------------- 1 | import { URL_PROPERTIES } from './constants'; 2 | 3 | /** 4 | * Identical to the native URL interface except that all mutable properties are 5 | * readonly. To change any of these properties instead you should call the 6 | * `.set()` method which will return a new ImmutableURL object. 7 | * 8 | * All properties are identical to their native counterparts except 9 | * `searchParams` which instead is a `ImmutableURLSearchParams` whose methods 10 | * are all fully immutable. 11 | * 12 | * ```typescript 13 | * let url = new ImmutableURL('http://example.com'); 14 | * url = url.set('path', '/foo'); // = ImmutableURL { http://example.com/foo } 15 | *``` 16 | * 17 | * To modify the search params you can call the immutable methods on 18 | * searchParams to return a new ImmutableURLSearchParams which can then be 19 | * passed to .set() 20 | * 21 | * ```typescript 22 | * const newSearchParams = url.searchParams.append('foo', 'bar'); 23 | * 24 | * url = url.set('searchParams', newSearchParams); 25 | * 26 | * // Or a shorthand 27 | * 28 | * url = url.set('searchParams', url.searchParams.append('foo', 'bar')); 29 | * ``` 30 | */ 31 | export class ImmutableURL implements Readonly { 32 | private readonly _url: URL; 33 | readonly hash!: string; 34 | readonly host!: string; 35 | readonly hostname!: string; 36 | readonly href!: string; 37 | readonly origin!: string; 38 | readonly password!: string; 39 | readonly pathname!: string; 40 | readonly port!: string; 41 | readonly protocol!: string; 42 | readonly search!: string; 43 | readonly searchParams!: ImmutableURLSearchParams; 44 | readonly username!: string; 45 | 46 | constructor(url: URL); 47 | constructor(url: string, base?: string | URL); 48 | constructor(url: string | URL, base?: string | URL) { 49 | this._url = typeof url === 'object' ? url : new URL(url, base); 50 | 51 | for (const key of URL_PROPERTIES) { 52 | this[key] = this._url[key] as any; 53 | } 54 | 55 | this.searchParams = new ImmutableURLSearchParams(this.searchParams); 56 | } 57 | 58 | toString() { 59 | return this._url.toString(); 60 | } 61 | 62 | toJSON() { 63 | return this._url.toJSON(); 64 | } 65 | 66 | /** 67 | * @param property The property of the URL to set 68 | * @param newValue The new value 69 | */ 70 | set

>( 71 | property: P, 72 | newValue: URL[P] 73 | ): ImmutableURL { 74 | const newUrl = new URL(this.toString()); 75 | 76 | if (property === 'searchParams') { 77 | // searchParams is a readonly property of URL, but we allow to set it anyway for ergonomics reasons 78 | // We do this by setting the "search" property as the stringified URLSearchParams. 79 | newUrl.search = (newValue as URLSearchParams).toString(); 80 | } else { 81 | newUrl[property] = newValue; 82 | } 83 | 84 | return new ImmutableURL(newUrl); 85 | } 86 | } 87 | 88 | /** 89 | * Properties on URLSearchParams that mutate the instance 90 | * @internal 91 | */ 92 | type SearchParamsMutatingProperties = 'sort' | 'set' | 'delete' | 'append'; 93 | 94 | /** 95 | * Methods that should return a new ImmutableURLSearchParams 96 | * @internal 97 | */ 98 | type SearchParamsModifiers = { 99 | [method in SearchParamsMutatingProperties]: ( 100 | ...args: any[] 101 | ) => ImmutableURLSearchParams; 102 | }; 103 | 104 | /** 105 | * Identical to the native URLSearchParams interface except that all mutable methods instead return a new `ImmutableURLSearchParams`. 106 | */ 107 | export class ImmutableURLSearchParams 108 | implements 109 | Omit, 110 | SearchParamsModifiers { 111 | private readonly _params: URLSearchParams; 112 | 113 | constructor( 114 | init?: string[][] | Record | string | URLSearchParams 115 | ) { 116 | this._params = new URLSearchParams(init); 117 | } 118 | 119 | append(key: string, value: string): ImmutableURLSearchParams { 120 | const newParams = new URLSearchParams(this.toString()); 121 | 122 | newParams.append(key, value); 123 | 124 | return new ImmutableURLSearchParams(newParams); 125 | } 126 | 127 | delete(key: string): ImmutableURLSearchParams { 128 | const newParams = new URLSearchParams(this.toString()); 129 | 130 | newParams.delete(key); 131 | 132 | return new ImmutableURLSearchParams(newParams); 133 | } 134 | 135 | set(key: string, value: string): ImmutableURLSearchParams { 136 | const newParams = new URLSearchParams(this.toString()); 137 | 138 | newParams.set(key, value); 139 | 140 | return new ImmutableURLSearchParams(newParams); 141 | } 142 | 143 | sort(): ImmutableURLSearchParams { 144 | const newParams = new URLSearchParams(this.toString()); 145 | 146 | newParams.sort(); 147 | 148 | return new ImmutableURLSearchParams(newParams); 149 | } 150 | 151 | forEach( 152 | callbackfn: (value: string, key: string, parent: URLSearchParams) => void, 153 | thisArg?: any 154 | ) { 155 | return this._params.forEach(callbackfn, thisArg); 156 | } 157 | 158 | get(name: string) { 159 | return this._params.get(name); 160 | } 161 | 162 | getAll(name: string) { 163 | return this._params.getAll(name); 164 | } 165 | 166 | has(name: string) { 167 | return this._params.has(name); 168 | } 169 | 170 | toString() { 171 | return this._params.toString(); 172 | } 173 | 174 | entries() { 175 | return this._params.entries(); 176 | } 177 | 178 | keys() { 179 | return this._params.keys(); 180 | } 181 | 182 | values() { 183 | return this._params.values(); 184 | } 185 | 186 | [Symbol.iterator]() { 187 | return this._params.entries(); 188 | } 189 | } 190 | 191 | /** 192 | * Properties on Headers that mutate the instance 193 | * @internal 194 | */ 195 | type HeadersMutatingProperties = 'append' | 'delete' | 'set'; 196 | 197 | /** 198 | * Methods that should return a new Headers 199 | * @internal 200 | */ 201 | type HeadersModifiers = { 202 | [method in HeadersMutatingProperties]: (...args: any[]) => ImmutableHeaders; 203 | }; 204 | 205 | /** 206 | * Identical to the native Headers interface except that all mutable methods instead return a new `ImmutableHeaders`. 207 | */ 208 | export class ImmutableHeaders 209 | implements Omit, HeadersModifiers { 210 | private readonly _headers: Headers; 211 | 212 | constructor(headersInit?: HeadersInit) { 213 | this._headers = new Headers(headersInit); 214 | } 215 | 216 | private clone(): Headers { 217 | return new Headers(this._headers); 218 | } 219 | 220 | entries() { 221 | return this._headers.entries(); 222 | } 223 | 224 | keys() { 225 | return this._headers.keys(); 226 | } 227 | 228 | values() { 229 | return this._headers.values(); 230 | } 231 | 232 | [Symbol.iterator]() { 233 | return this._headers.entries(); 234 | } 235 | 236 | forEach( 237 | callbackfn: (value: string, key: string, parent: Headers) => void, 238 | thisArg?: any 239 | ) { 240 | return this._headers.forEach(callbackfn, thisArg); 241 | } 242 | 243 | get(name: string): string | null { 244 | return this._headers.get(name); 245 | } 246 | 247 | has(name: string) { 248 | return this._headers.has(name); 249 | } 250 | 251 | set(name: string, value: string): ImmutableHeaders { 252 | const newHeaders = this.clone(); 253 | 254 | newHeaders.set(name, value); 255 | 256 | return new ImmutableHeaders(newHeaders); 257 | } 258 | 259 | delete(name: string): ImmutableHeaders { 260 | const newHeaders = this.clone(); 261 | 262 | newHeaders.delete(name); 263 | 264 | return new ImmutableHeaders(newHeaders); 265 | } 266 | 267 | append(name: string, value: string): ImmutableHeaders { 268 | const newHeaders = this.clone(); 269 | 270 | newHeaders.append(name, value); 271 | 272 | return new ImmutableHeaders(newHeaders); 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "immurl", 3 | "version": "1.2.0", 4 | "description": "", 5 | "source": "lib/immurl.ts", 6 | "main": "dist/immurl.js", 7 | "module": "dist/immurl.module.js", 8 | "unpkg": "dist/immurl.umd.js", 9 | "types": "dist/immurl.d.ts", 10 | "scripts": { 11 | "test": "jest", 12 | "typecheck": "tsc --noEmit", 13 | "build": "microbundle", 14 | "dev": "microbundle watch", 15 | "prepublishOnly": "npm run build", 16 | "docs": "typedoc --stripInternal --excludePrivate --mode file --out docs lib" 17 | }, 18 | "files": [ 19 | "dist" 20 | ], 21 | "author": { 22 | "email": "the.tomsherman@gmail.com", 23 | "name": "Tom Sherman", 24 | "url": "https://github.com/tom-sherman" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/tom-sherman/immurl.git" 29 | }, 30 | "homepage": "https://github.com/tom-sherman/immurl", 31 | "license": "MIT", 32 | "devDependencies": { 33 | "@babel/core": "^7.10.4", 34 | "@babel/preset-env": "^7.10.4", 35 | "@babel/preset-typescript": "^7.10.4", 36 | "@types/jest": "^26.0.3", 37 | "@types/lodash": "^4.14.157", 38 | "babel-jest": "^26.1.0", 39 | "jest": "^26.1.0", 40 | "lodash": "^4.17.15", 41 | "microbundle": "^0.12.2", 42 | "prettier": "^2.0.5", 43 | "typedoc": "^0.17.8", 44 | "typescript": "^3.9.6" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/ImmutableHeaders.test.ts: -------------------------------------------------------------------------------- 1 | import { ImmutableHeaders } from '../lib/immurl'; 2 | 3 | describe.each([ 4 | { 5 | foo: '1', 6 | bar: '2', 7 | }, 8 | [ 9 | ['foo', '1'], 10 | ['bar', '2'], 11 | ], 12 | new Headers({ 13 | foo: '1', 14 | bar: '2', 15 | }), 16 | ])('equivalent to the native Headers object for input %p', (input) => { 17 | test('entries()', () => { 18 | const immutable = new ImmutableHeaders(input); 19 | const headers = new Headers(input); 20 | 21 | expect([...immutable.entries()]).toEqual([...headers.entries()]); 22 | }); 23 | 24 | test('keys()', () => { 25 | const immutable = new ImmutableHeaders(input); 26 | const headers = new Headers(input); 27 | 28 | expect([...immutable.keys()]).toEqual([...headers.keys()]); 29 | }); 30 | 31 | test('values()', () => { 32 | const immutable = new ImmutableHeaders(input); 33 | const headers = new Headers(input); 34 | 35 | expect([...immutable.values()]).toEqual([...headers.values()]); 36 | }); 37 | }); 38 | 39 | test('append() is immutable', () => { 40 | const headers = new ImmutableHeaders(); 41 | 42 | expect(headers.append('foo', 'bar')).not.toBe(headers); 43 | expect([...headers.append('foo', 'bar')]).not.toEqual([...headers]); 44 | }); 45 | 46 | test('delete() is immutable', () => { 47 | const headers = new ImmutableHeaders({ foo: 'bar' }); 48 | 49 | expect(headers.delete('foo')).not.toBe(headers); 50 | expect([...headers.delete('foo')]).not.toEqual([...headers]); 51 | }); 52 | 53 | test('set() is immutable', () => { 54 | const headers = new ImmutableHeaders({ foo: 'bar' }); 55 | 56 | expect(headers.set('foo', 'baz')).not.toBe(headers); 57 | expect([...headers.set('foo', 'baz')]).not.toEqual([...headers]); 58 | }); 59 | -------------------------------------------------------------------------------- /tests/ImmutableURL.test.ts: -------------------------------------------------------------------------------- 1 | import { ImmutableURL, ImmutableURLSearchParams } from '../lib/immurl'; 2 | import { URL_PROPERTIES } from '../lib/constants'; 3 | import flatMap from 'lodash/flatMap'; 4 | 5 | const URLS = [ 6 | 'https://example.com', 7 | 'https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hash', 8 | 'https://測試', 9 | ]; 10 | 11 | const propertiesExcludingSearchParams = URL_PROPERTIES.filter( 12 | (p) => p !== 'searchParams' 13 | ); 14 | 15 | describe('equivalent to the native URL object', () => { 16 | test.each<[string, typeof URL_PROPERTIES[number]]>( 17 | flatMap(propertiesExcludingSearchParams, (property) => 18 | URLS.map( 19 | (url) => [url, property] as [string, typeof URL_PROPERTIES[number]] 20 | ) 21 | ) 22 | )( 23 | '"%s" has the same "%s" property as a native URL object', 24 | (input, property) => { 25 | const immutable = new ImmutableURL(input); 26 | const url = new URL(input); 27 | 28 | expect(immutable[property]).toEqual(url[property]); 29 | } 30 | ); 31 | 32 | test.each(URLS)('toString - %s', (input) => { 33 | const immutable = new ImmutableURL(input); 34 | const url = new URL(input); 35 | 36 | expect(immutable.toString()).toBe(url.toString()); 37 | }); 38 | 39 | test.each(URLS)('seachParams - %s', (input) => { 40 | const immutable = new ImmutableURL(input); 41 | const url = new URL(input); 42 | 43 | expect(immutable.searchParams.toString()).toBe(url.searchParams.toString()); 44 | }); 45 | }); 46 | 47 | test('set() is immutable', () => { 48 | const url = new ImmutableURL('https://example.com'); 49 | 50 | expect(url.set('hash', 'foo').hash).not.toEqual(url.hash); 51 | }); 52 | 53 | test('searchParams property is an ImmutableURLSearchParams', () => { 54 | const url = new ImmutableURL('https://example.com'); 55 | 56 | expect(url.searchParams).toBeInstanceOf(ImmutableURLSearchParams); 57 | }); 58 | 59 | test('can update searchParams', () => { 60 | const url = new ImmutableURL('https://example.com'); 61 | const newUrl = url.set( 62 | 'search', 63 | url.searchParams 64 | .append('q', 'search-term') 65 | .set('foo', 'fuz') 66 | .sort() 67 | .toString() 68 | ); 69 | 70 | expect(newUrl.toString()).toEqual( 71 | 'https://example.com/?foo=fuz&q=search-term' 72 | ); 73 | }); 74 | -------------------------------------------------------------------------------- /tests/ImmutableURLSearchParams.test.ts: -------------------------------------------------------------------------------- 1 | import { ImmutableURLSearchParams } from '../lib/immurl'; 2 | 3 | /** 4 | * Array of search params defined without the leading ? 5 | */ 6 | export const SEARCH_PARAMS = [ 7 | 'q=URLUtils.searchParams&topic=api', 8 | 'query', 9 | 'foo=1&bar=2&foo=4', 10 | ]; 11 | 12 | const paramNamesToTest = ['topic', 'q', 'query', 'foo', 'bar']; 13 | 14 | const searchParamStrings = [ 15 | ...SEARCH_PARAMS, 16 | ...SEARCH_PARAMS.map((q) => `?${q}`), 17 | ]; 18 | 19 | describe('equivalent to the native URLSearchParams object', () => { 20 | test.each(searchParamStrings)('iterable - %s', (input) => { 21 | const immutable = new ImmutableURLSearchParams(input)[Symbol.iterator](); 22 | const native = new URLSearchParams(input)[Symbol.iterator](); 23 | 24 | // Spread operator here accomplishes everything we need to test about the iterators being equivalent: 25 | // - The lengths are the same 26 | // - Each value is the same and in the correct position 27 | expect([...immutable]).toEqual([...native]); 28 | }); 29 | 30 | test.each(searchParamStrings)('toString() - %s', (input) => { 31 | const immutable = new ImmutableURLSearchParams(input); 32 | const native = new URLSearchParams(input); 33 | 34 | expect(immutable.toString()).toBe(native.toString()); 35 | }); 36 | 37 | test.each(searchParamStrings)('entries() - %s', (input) => { 38 | const immutable = new ImmutableURLSearchParams(input); 39 | const native = new URLSearchParams(input); 40 | 41 | expect(immutable.entries()).toEqual(native.entries()); 42 | }); 43 | 44 | test.each(searchParamStrings)('keys() - %s', (input) => { 45 | const immutable = new ImmutableURLSearchParams(input); 46 | const native = new URLSearchParams(input); 47 | 48 | expect(immutable.keys()).toEqual(native.keys()); 49 | }); 50 | 51 | test.each(searchParamStrings)('values() - %s', (input) => { 52 | const immutable = new ImmutableURLSearchParams(input); 53 | const native = new URLSearchParams(input); 54 | 55 | expect(immutable.values()).toEqual(native.values()); 56 | }); 57 | 58 | test.each(searchParamStrings)('forEach() - %s', (input) => { 59 | const immutable = new ImmutableURLSearchParams(input); 60 | const native = new URLSearchParams(input); 61 | 62 | const immutablesCallbackArgs: unknown[] = []; 63 | const nativeCallbackArgs: unknown[] = []; 64 | 65 | const immutableCallback = jest.fn((...args) => 66 | immutablesCallbackArgs.push(args) 67 | ); 68 | const nativeCallback = jest.fn((...args) => nativeCallbackArgs.push(args)); 69 | 70 | immutable.forEach(immutableCallback); 71 | native.forEach(nativeCallback); 72 | 73 | expect(immutablesCallbackArgs).toEqual(nativeCallbackArgs); 74 | }); 75 | 76 | test.each(searchParamStrings)('get() - %s', (input) => { 77 | const immutable = new ImmutableURLSearchParams(input); 78 | const native = new URLSearchParams(input); 79 | 80 | for (const paramName of paramNamesToTest) { 81 | expect(immutable.get(paramName)).toBe(native.get(paramName)); 82 | } 83 | }); 84 | 85 | test.each(searchParamStrings)('getAll() - %s', (input) => { 86 | const immutable = new ImmutableURLSearchParams(input); 87 | const native = new URLSearchParams(input); 88 | 89 | for (const paramName of paramNamesToTest) { 90 | expect(immutable.getAll(paramName)).toEqual(native.getAll(paramName)); 91 | } 92 | }); 93 | 94 | test.each(searchParamStrings)('has() - %s', (input) => { 95 | const immutable = new ImmutableURLSearchParams(input); 96 | const native = new URLSearchParams(input); 97 | 98 | for (const paramName of paramNamesToTest) { 99 | expect(immutable.has(paramName)).toBe(native.has(paramName)); 100 | } 101 | }); 102 | }); 103 | 104 | test('append() is immutable', () => { 105 | const params = new ImmutableURLSearchParams(); 106 | 107 | expect(params.append('foo', 'bar')).not.toBe(params); 108 | expect(params.append('foo', 'bar').toString()).not.toEqual(params.toString()); 109 | }); 110 | 111 | test('delete() is immutable', () => { 112 | const params = new ImmutableURLSearchParams('foo=bar'); 113 | 114 | expect(params.delete('foo')).not.toBe(params); 115 | expect(params.delete('foo').toString()).not.toEqual(params.toString()); 116 | }); 117 | 118 | test('set() is immutable', () => { 119 | const params = new ImmutableURLSearchParams('foo=bar'); 120 | 121 | expect(params.set('foo', 'baz')).not.toBe(params); 122 | expect(params.set('foo', 'baz').toString()).not.toEqual(params.toString()); 123 | }); 124 | 125 | test('sort() is immutable', () => { 126 | const params = new ImmutableURLSearchParams('foo=bar&bar=foo'); 127 | 128 | expect(params.sort()).not.toBe(params); 129 | expect(params.sort().toString()).not.toEqual(params.toString()); 130 | }); 131 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "skipLibCheck": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "outDir": "dist", 10 | "declaration": true 11 | } 12 | } 13 | --------------------------------------------------------------------------------