diff --git a/examples/spec/url.js b/examples/spec/url.js index 5133aca..0a89c03 100644 --- a/examples/spec/url.js +++ b/examples/spec/url.js @@ -1,47 +1,314 @@ -import { test, summary } from './helpers.js'; +import { test, testThrows, testDeep, summary } from './helpers.js'; -console.log('URL Tests\n'); +console.log('URL constructor\n'); const url = new URL('https://user:pass@example.com:8080/path/to?query=value#hash'); - -test('URL href', url.href, 'https://user:pass@example.com:8080/path/to?query=value#hash'); -test('URL protocol', url.protocol, 'https:'); -test('URL username', url.username, 'user'); -test('URL password', url.password, 'pass'); -test('URL host', url.host, 'example.com:8080'); -test('URL hostname', url.hostname, 'example.com'); -test('URL port', url.port, '8080'); -test('URL pathname', url.pathname, '/path/to'); -test('URL search', url.search, '?query=value'); -test('URL hash', url.hash, '#hash'); -test('URL origin', url.origin, 'https://example.com:8080'); +test('full URL href', url.href, 'https://user:pass@example.com:8080/path/to?query=value#hash'); const url2 = new URL('/other', 'https://example.com'); test('URL with base', url2.href, 'https://example.com/other'); -const url3 = new URL('https://example.com'); -url3.pathname = '/new/path'; -test('URL set pathname', url3.pathname, '/new/path'); +const url2b = new URL('foo', 'https://example.com/bar/baz'); +test('URL with base resolves relative', url2b.href, 'https://example.com/bar/foo'); + +const url2c = new URL('//other.com/path', 'https://example.com'); +test('URL with base protocol-relative', url2c.href, 'https://other.com/path'); + +testThrows('requires new', () => URL()); +testThrows('invalid URL throws TypeError', () => new URL('not a url')); +testThrows('invalid URL with base throws', () => new URL('http://%', 'https://example.com')); + +test('toString() returns href', url.toString(), url.href); +test('toJSON() returns href', url.toJSON(), url.href); + +test('URL.canParse valid', URL.canParse('https://example.com'), true); +test('URL.canParse invalid', URL.canParse('not a url'), false); +test('URL.canParse with base', URL.canParse('/path', 'https://example.com'), true); + +const parsed = URL.parse('https://example.com/test'); +test('URL.parse valid returns URL', parsed instanceof URL, true); +test('URL.parse valid href', parsed.href, 'https://example.com/test'); +test('URL.parse invalid returns null', URL.parse('not a url'), null); + +const parsedBase = URL.parse('/path', 'https://example.com'); +test('URL.parse with base', parsedBase.href, 'https://example.com/path'); + +console.log('\nURL properties (getters)\n'); + +test('protocol', url.protocol, 'https:'); +test('username', url.username, 'user'); +test('password', url.password, 'pass'); +test('hostname', url.hostname, 'example.com'); +test('host (hostname:port)', url.host, 'example.com:8080'); +test('port', url.port, '8080'); +test('pathname', url.pathname, '/path/to'); +test('search', url.search, '?query=value'); +test('hash', url.hash, '#hash'); +test('origin', url.origin, 'https://example.com:8080'); + +const urlNoSearch = new URL('https://example.com/path'); +test('search empty string when absent', urlNoSearch.search, ''); + +const urlNoHash = new URL('https://example.com/path'); +test('hash empty string when absent', urlNoHash.hash, ''); + +const urlDefaultHost = new URL('https://example.com/'); +test('host without port', urlDefaultHost.host, 'example.com'); + +const urlNoUser = new URL('https://example.com/'); +test('username empty when absent', urlNoUser.username, ''); +test('password empty when absent', urlNoUser.password, ''); + +console.log('\nURL property setters\n'); + +const us1 = new URL('https://example.com/old'); +us1.href = 'https://other.com/new?q=1'; +test('set href reparses', us1.hostname, 'other.com'); +test('set href pathname', us1.pathname, '/new'); +test('set href search', us1.search, '?q=1'); + +const us2 = new URL('https://example.com/path'); +us2.protocol = 'http:'; +test('set protocol', us2.protocol, 'http:'); + +const us3 = new URL('https://example.com/path'); +us3.hostname = 'other.com'; +test('set hostname', us3.hostname, 'other.com'); +test('set hostname updates host', us3.host, 'other.com'); + +const us4 = new URL('https://example.com/path'); +us4.port = '9090'; +test('set port', us4.port, '9090'); +test('set port updates host', us4.host, 'example.com:9090'); + +const us4b = new URL('https://example.com:9090/path'); +us4b.port = '443'; +test('set default port elides', us4b.port, ''); + +const us5 = new URL('https://example.com/old'); +us5.pathname = '/new/path'; +test('set pathname', us5.pathname, '/new/path'); + +const us6 = new URL('https://example.com/path'); +us6.search = '?x=1&y=2'; +test('set search', us6.search, '?x=1&y=2'); + +const us6b = new URL('https://example.com/path?old=1'); +us6b.search = ''; +test('set search empty clears', us6b.search, ''); + +const us7 = new URL('https://example.com/path'); +us7.hash = '#section'; +test('set hash', us7.hash, '#section'); + +const us7b = new URL('https://example.com/path#old'); +us7b.hash = ''; +test('set hash empty clears', us7b.hash, ''); + +const us8 = new URL('https://example.com/path'); +us8.username = 'alice'; +test('set username', us8.username, 'alice'); + +const us9 = new URL('https://example.com/path'); +us9.password = 'secret'; +test('set password', us9.password, 'secret'); + +console.log('\ndefault port handling\n'); + +const httpDefault = new URL('http://example.com:80/'); +test('http port 80 elided', httpDefault.port, ''); +test('http port 80 host omits port', httpDefault.host, 'example.com'); + +const httpsDefault = new URL('https://example.com:443/'); +test('https port 443 elided', httpsDefault.port, ''); +test('https port 443 host omits port', httpsDefault.host, 'example.com'); + +const httpNonDefault = new URL('http://example.com:3000/'); +test('http non-default port shown', httpNonDefault.port, '3000'); +test('http non-default host includes port', httpNonDefault.host, 'example.com:3000'); + +const httpsNonDefault = new URL('https://example.com:8443/'); +test('https non-default port shown', httpsNonDefault.port, '8443'); + +console.log('\nURLSearchParams constructor\n'); + +const sp0 = new URLSearchParams(); +test('empty constructor toString', sp0.toString(), ''); +test('empty constructor size', sp0.size, 0); + +const sp1 = new URLSearchParams('a=1&b=2'); +test('from string get a', sp1.get('a'), '1'); +test('from string get b', sp1.get('b'), '2'); +test('from string size', sp1.size, 2); + +const sp1b = new URLSearchParams('?a=1&b=2'); +test('from string with leading ?', sp1b.get('a'), '1'); +test('from string with leading ? get b', sp1b.get('b'), '2'); + +const sp2 = new URLSearchParams([ + ['a', '1'], + ['b', '2'] +]); +test('from array get a', sp2.get('a'), '1'); +test('from array get b', sp2.get('b'), '2'); +test('from array size', sp2.size, 2); + +const sp3 = new URLSearchParams({ a: '1', b: '2' }); +test('from object get a', sp3.get('a'), '1'); +test('from object get b', sp3.get('b'), '2'); + +const sp4 = new URLSearchParams(new URLSearchParams('x=9&y=8')); +test('from another URLSearchParams get x', sp4.get('x'), '9'); +test('from another URLSearchParams get y', sp4.get('y'), '8'); + +testThrows('pair with 1 element throws', () => new URLSearchParams([['only_key']])); +testThrows('pair with 3 elements throws', () => new URLSearchParams([['a', 'b', 'c']])); + +console.log('\nURLSearchParams methods\n'); + +const sp = new URLSearchParams('a=1&b=2&a=3'); + +test('get returns first value', sp.get('a'), '1'); +test('get missing returns null', sp.get('z'), null); + +testDeep('getAll returns all values', sp.getAll('a'), ['1', '3']); +testDeep('getAll missing returns empty', sp.getAll('z'), []); + +test('has returns true', sp.has('a'), true); +test('has missing returns false', sp.has('z'), false); + +test('has with matching value', sp.has('a', '1'), true); +test('has with non-matching value', sp.has('a', '999'), false); + +const spSet = new URLSearchParams('a=1&b=2&a=3'); +spSet.set('a', '99'); +test('set replaces all', spSet.get('a'), '99'); +test('set getAll length', spSet.getAll('a').length, 1); +test('set preserves others', spSet.get('b'), '2'); + +const spApp = new URLSearchParams('a=1'); +spApp.append('a', '2'); +test('append adds entry', spApp.getAll('a').length, 2); +test('append second value', spApp.getAll('a')[1], '2'); + +const spDel = new URLSearchParams('a=1&b=2&a=3'); +spDel.delete('a'); +test('delete removes all with name', spDel.has('a'), false); +test('delete preserves others', spDel.has('b'), true); + +const spDelVal = new URLSearchParams('a=1&a=2&a=3'); +spDelVal.delete('a', '2'); +test('delete with value removes specific', spDelVal.getAll('a').length, 2); +test('delete with value keeps others', spDelVal.get('a'), '1'); + +const spSort = new URLSearchParams('c=3&a=1&b=2&a=4'); +spSort.sort(); +test('sort first key', [...spSort.keys()][0], 'a'); +test('sort second key', [...spSort.keys()][1], 'a'); +test('sort third key', [...spSort.keys()][2], 'b'); +test('sort fourth key', [...spSort.keys()][3], 'c'); +test('sort stable: first a value', spSort.getAll('a')[0], '1'); +test('sort stable: second a value', spSort.getAll('a')[1], '4'); + +test('toString serializes', new URLSearchParams('a=1&b=2').toString(), 'a=1&b=2'); +test('toString empty', new URLSearchParams().toString(), ''); + +const spFE = new URLSearchParams('a=1&b=2'); +const feResults = []; +spFE.forEach((value, name) => feResults.push(`${name}=${value}`)); +test('forEach visits all', feResults.length, 2); +test('forEach first', feResults[0], 'a=1'); +test('forEach second', feResults[1], 'b=2'); + +test('size getter', new URLSearchParams('a=1&b=2&c=3').size, 3); +test( + 'size after append', + (() => { + const s = new URLSearchParams('a=1'); + s.append('b', '2'); + return s.size; + })(), + 2 +); + +console.log('\nURLSearchParams encoding\n'); + +test('space encoded as +', new URLSearchParams([['k', 'hello world']]).toString(), 'k=hello+world'); +test('+ in value encoded as %2B', new URLSearchParams([['k', 'a+b']]).toString(), 'k=a%2Bb'); +test('= in value encoded', new URLSearchParams([['k', 'a=b']]).toString(), 'k=a%3Db'); +test('& in value encoded', new URLSearchParams([['k', 'a&b']]).toString(), 'k=a%26b'); + +test('* passes through', new URLSearchParams([['k', '*']]).toString(), 'k=*'); +test('- passes through', new URLSearchParams([['k', '-']]).toString(), 'k=-'); +test('. passes through', new URLSearchParams([['k', '.']]).toString(), 'k=.'); +test('_ passes through', new URLSearchParams([['k', '_']]).toString(), 'k=_'); + +test('~ is percent-encoded', new URLSearchParams([['k', '~']]).toString(), 'k=%7E'); + +test('parsing: + becomes space', new URLSearchParams('k=hello+world').get('k'), 'hello world'); +test('parsing: %20 becomes space', new URLSearchParams('k=hello%20world').get('k'), 'hello world'); +test('parsing: %2B becomes +', new URLSearchParams('k=a%2Bb').get('k'), 'a+b'); + +console.log('\nURLSearchParams iterator protocol\n'); + +const spi = new URLSearchParams('a=1&b=2'); + +const entries = [...spi.entries()]; +test('entries length', entries.length, 2); +test('entries first', entries[0][0], 'a'); +test('entries first value', entries[0][1], '1'); + +const keys = [...spi.keys()]; +test('keys length', keys.length, 2); +test('keys first', keys[0], 'a'); +test('keys second', keys[1], 'b'); + +const vals = [...spi.values()]; +test('values length', vals.length, 2); +test('values first', vals[0], '1'); +test('values second', vals[1], '2'); + +test('Symbol.iterator is entries', spi[Symbol.iterator] === spi.entries, true); + +const forOfResults = []; +for (const [name, value] of new URLSearchParams('x=10&y=20')) { + forOfResults.push(`${name}=${value}`); +} +test('for...of first', forOfResults[0], 'x=10'); +test('for...of second', forOfResults[1], 'y=20'); + +const spread = [...new URLSearchParams('p=1&q=2')]; +test('spread length', spread.length, 2); +test('spread first pair', spread[0][0], 'p'); +test('spread first value', spread[0][1], '1'); + +console.log('\nURL-URLSearchParams bidirectional sync\n'); + +const syncUrl = new URL('https://example.com/?a=1'); +syncUrl.searchParams.set('b', '2'); +test('searchParams mutation updates search', syncUrl.search, '?a=1&b=2'); -const params = new URLSearchParams('a=1&b=2&a=3'); -test('URLSearchParams get', params.get('a'), '1'); -test('URLSearchParams getAll', params.getAll('a').length, 2); -test('URLSearchParams has', params.has('b'), true); -test('URLSearchParams has missing', params.has('c'), false); +syncUrl.searchParams.delete('a'); +test('searchParams delete updates search', syncUrl.search, '?b=2'); -params.set('c', '4'); -test('URLSearchParams set', params.get('c'), '4'); +syncUrl.searchParams.append('c', '3'); +test('searchParams append updates search', syncUrl.search, '?b=2&c=3'); -params.append('d', '5'); -test('URLSearchParams append', params.get('d'), '5'); +const syncUrl2 = new URL('https://example.com/?x=1'); +syncUrl2.search = '?y=2&z=3'; +test('set search updates searchParams get y', syncUrl2.searchParams.get('y'), '2'); +test('set search updates searchParams get z', syncUrl2.searchParams.get('z'), '3'); +test('set search removes old param', syncUrl2.searchParams.has('x'), false); -params.delete('b'); -test('URLSearchParams delete', params.has('b'), false); +const syncUrl3 = new URL('https://example.com/?a=1'); +const paramsRef1 = syncUrl3.searchParams; +syncUrl3.search = '?b=2'; +const paramsRef2 = syncUrl3.searchParams; +test('searchParams identity preserved', paramsRef1 === paramsRef2, true); -test('URLSearchParams toString', params.toString().includes('a=1'), true); +console.log('\ntoStringTag\n'); -const url4 = new URL('https://example.com?x=1'); -url4.searchParams.set('y', '2'); -test('URL searchParams', url4.search, '?x=1&y=2'); +test('URL toStringTag', Object.prototype.toString.call(new URL('https://example.com')), '[object URL]'); +test('URLSearchParams toStringTag', Object.prototype.toString.call(new URLSearchParams()), '[object URLSearchParams]'); summary(); diff --git a/vendor/google-brotli.wrap b/vendor/google-brotli.wrap index 658204c..3b06f09 100644 --- a/vendor/google-brotli.wrap +++ b/vendor/google-brotli.wrap @@ -7,6 +7,7 @@ source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/goo patch_filename = google-brotli_1.1.0-3_patch.zip patch_url = https://wrapdb.mesonbuild.com/v2/google-brotli_1.1.0-3/get_patch patch_hash = 77e75a3bb019a4b653195a15af17cc65c56fb8cced2519154fce4e6bb43c74e2 +diff_files = patches/google-brotli-static-link.patch wrapdb_version = 1.1.0-3 [provide] diff --git a/vendor/packagefiles/patches/google-brotli-static-link.patch b/vendor/packagefiles/patches/google-brotli-static-link.patch new file mode 100644 index 0000000..c536b73 --- /dev/null +++ b/vendor/packagefiles/patches/google-brotli-static-link.patch @@ -0,0 +1,67 @@ +--- a/meson.build ++++ b/meson.build +@@ -65,16 +65,6 @@ + bcs = [] + bds = [] + bes = [] +-install = false +-if get_option('default_library') == 'shared' +- c_args += ['-DBROTLI_SHARED_COMPILATION'] +- install = true +- bcs = '-DBROTLICOMMON_SHARED_COMPILATION' +- bds = '-DBROTLIDEC_SHARED_COMPILATION' +- bes = '-DBROTLIENC_SHARED_COMPILATION' +-elif get_option('default_library') == 'both' +- error('both not yet supported.') +-endif + + common_src = files( + 'c/common/constants.c', +@@ -85,14 +75,12 @@ + 'c/common/transform.c', + ) + +-libbrotli_common = library( ++libbrotli_common = static_library( + 'brotli_common', + common_src, + gnu_symbol_visibility: 'hidden', + c_args: [c_args, bcs], + include_directories: inc, +- version: meson.project_version(), +- install: install, + ) + + brotli_common_dep = declare_dependency( +@@ -107,14 +95,12 @@ + 'c/dec/state.c', + ) + +-libbrotli_decoder = library( ++libbrotli_decoder = static_library( + 'brotli_decoder', + dec_src, + gnu_symbol_visibility: 'hidden', + c_args: [c_args, bds], + dependencies: brotli_common_dep, +- version: meson.project_version(), +- install: install, + ) + + enc_src = files( +@@ -141,14 +127,12 @@ + 'c/enc/utf8_util.c', + ) + +-libbrotli_encoder = library( ++libbrotli_encoder = static_library( + 'brotli_encoder', + enc_src, + c_args: [c_args, bes], + gnu_symbol_visibility: 'hidden', + dependencies: [dep_m, brotli_common_dep], +- version: meson.project_version(), +- install: install, + ) + + meson.override_dependency('libbrotlicommon', brotli_common_dep)