Skip to content

Commit

Permalink
fix(js-runtime): bring URL closer to the standard
Browse files Browse the repository at this point in the history
  • Loading branch information
cyco130 committed Dec 17, 2022
1 parent 6e72f67 commit ff0782c
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 10 deletions.
5 changes: 5 additions & 0 deletions .changeset/giant-cats-mate.md
@@ -0,0 +1,5 @@
---
'@lagon/js-runtime': patch
---

Bring URL class closer to the standard
104 changes: 104 additions & 0 deletions packages/js-runtime/src/__tests__/url.test.ts
Expand Up @@ -135,6 +135,13 @@ describe('URL', () => {
it('should return the hash', () => {
expect(new URL('https://developer.mozilla.org/en-US/docs/Web/API/URL/href#Examples').hash).toEqual('#Examples');
});

it('should set the hash', () => {
const url = new URL('https://developer.mozilla.org/en-US/docs/Web/API/URL/href#Examples');
url.hash = '#Examples2';
expect(url.hash).toEqual('#Examples2');
expect(url.href).toEqual('https://developer.mozilla.org/en-US/docs/Web/API/URL/href#Examples2');
});
});

describe('host', () => {
Expand All @@ -155,6 +162,20 @@ describe('URL', () => {
'developer.mozilla.org:4097',
);
});

it('should set the host', () => {
const url = new URL('https://developer.mozilla.org/en-US/docs/Web/API/URL/host');
url.host = 'developer.mozilla.org:4097';
expect(url.host).toEqual('developer.mozilla.org:4097');
expect(url.href).toEqual('https://developer.mozilla.org:4097/en-US/docs/Web/API/URL/host');
});

it('should update the host when setting the port', () => {
const url = new URL('https://developer.mozilla.org/en-US/docs/Web/API/URL/host');
url.port = '4097';
expect(url.host).toEqual('developer.mozilla.org:4097');
expect(url.href).toEqual('https://developer.mozilla.org:4097/en-US/docs/Web/API/URL/host');
});
});

describe('hostname', () => {
Expand All @@ -163,6 +184,20 @@ describe('URL', () => {
'developer.mozilla.org',
);
});

it('should set the hostname', () => {
const url = new URL('https://developer.mozilla.org/en-US/docs/Web/API/URL/hostname');
url.hostname = 'developer.mozilla.org';
expect(url.hostname).toEqual('developer.mozilla.org');
expect(url.href).toEqual('https://developer.mozilla.org/en-US/docs/Web/API/URL/hostname');
});

it('should update the hostname when setting the host', () => {
const url = new URL('https://developer.mozilla.org/en-US/docs/Web/API/URL/hostname');
url.host = 'www.mozilla.org:4097';
expect(url.hostname).toEqual('www.mozilla.org');
expect(url.href).toEqual('https://www.mozilla.org:4097/en-US/docs/Web/API/URL/hostname');
});
});

describe('href', () => {
Expand All @@ -171,6 +206,12 @@ describe('URL', () => {
'https://developer.mozilla.org/en-US/docs/Web/API/URL/href',
);
});

it('should set the href', () => {
const url = new URL('https://developer.mozilla.org/en-US/docs/Web/API/URL/href');
url.href = 'https://developer.mozilla.org/en-US/docs/Web/API/URL';
expect(url.href).toEqual('https://developer.mozilla.org/en-US/docs/Web/API/URL');
});
});

describe('origin', () => {
Expand Down Expand Up @@ -212,6 +253,13 @@ describe('URL', () => {
new URL('https://anonymous:flabada@developer.mozilla.org/en-US/docs/Web/API/URL/password').password,
).toEqual('flabada');
});

it('should set the password', () => {
const url = new URL('https://anonymous:flabada@developer.mozilla.org/en-US/docs/Web/API/URL/password');
url.password = 'foo';
expect(url.password).toEqual('foo');
expect(url.href).toEqual('https://anonymous:foo@developer.mozilla.org/en-US/docs/Web/API/URL/password');
});
});

describe('pathname', () => {
Expand All @@ -220,24 +268,52 @@ describe('URL', () => {
'/en-US/docs/Web/API/URL/pathname',
);
});

it('should set the pathname', () => {
const url = new URL('https://developer.mozilla.org/en-US/docs/Web/API/URL/pathname?q=value');
url.pathname = '/en-US/docs/Web/API/URL';
expect(url.pathname).toEqual('/en-US/docs/Web/API/URL');
expect(url.href).toEqual('https://developer.mozilla.org/en-US/docs/Web/API/URL?q=value');
});
});

describe('port', () => {
it('should return the port', () => {
expect(new URL('https://mydomain.com:80/svn/Repos/').port).toEqual('80');
});

it('should set the port', () => {
const url = new URL('https://mydomain.com:80/svn/Repos/');
url.port = '8080';
expect(url.port).toEqual('8080');
expect(url.href).toEqual('https://mydomain.com:8080/svn/Repos/');
});
});

describe('protocol', () => {
it('should return the protocol', () => {
expect(new URL('https://developer.mozilla.org/en-US/docs/Web/API/URL/protocol').protocol).toEqual('https:');
});

it('should set the protocol', () => {
const url = new URL('https://developer.mozilla.org/en-US/docs/Web/API/URL/protocol');
url.protocol = 'http:';
expect(url.protocol).toEqual('http:');
expect(url.href).toEqual('http://developer.mozilla.org/en-US/docs/Web/API/URL/protocol');
});
});

describe('search', () => {
it('should return the search', () => {
expect(new URL('https://developer.mozilla.org/en-US/docs/Web/API/URL/search?q=123').search).toEqual('?q=123');
});

it('should set the search', () => {
const url = new URL('https://developer.mozilla.org/en-US/docs/Web/API/URL/search?q=123');
url.search = '?q=456';
expect(url.search).toEqual('?q=456');
expect(url.href).toEqual('https://developer.mozilla.org/en-US/docs/Web/API/URL/search?q=456');
});
});

describe('searchParams', () => {
Expand All @@ -246,6 +322,27 @@ describe('URL', () => {
expect(searchParams?.get('name')).toEqual('Jonathan Smith');
expect(searchParams?.get('age')).toEqual('18');
});

it('should set the searchParams', () => {
const url = new URL('https://example.com/?name=Jonathan%20Smith&age=18');
url.searchParams.set('name', 'John');
expect(url.search).toEqual('?name=John&age=18');
expect(url.href).toEqual('https://example.com/?name=John&age=18');
});

it('should update the searchParams when the search is updated', () => {
const url = new URL('https://example.com/?name=Jonathan%20Smith&age=18');
url.search = '?name=John';
expect(url.searchParams.get('name')).toEqual('John');
expect(url.href).toEqual('https://example.com/?name=John');
});

it('should return the same searchParams instance', () => {
const url = new URL('https://example.com/?name=Jonathan%20Smith&age=18');
const searchParams = url.searchParams;
url.search = '?name=John';
expect(url.searchParams).toBe(searchParams);
});
});

describe('username', () => {
Expand All @@ -254,5 +351,12 @@ describe('URL', () => {
new URL('https://anonymous:flabada@developer.mozilla.org/en-US/docs/Web/API/URL/username').username,
).toEqual('anonymous');
});

it('should set the username', () => {
const url = new URL('https://anonymous:flabada@developer.mozilla.org/en-US/docs/Web/API/URL/password');
url.username = 'foo';
expect(url.username).toEqual('foo');
expect(url.href).toEqual('https://foo:flabada@developer.mozilla.org/en-US/docs/Web/API/URL/password');
});
});
});
78 changes: 68 additions & 10 deletions packages/js-runtime/src/runtime/http/URL.ts
Expand Up @@ -10,19 +10,21 @@

globalThis.URL = class {
public hash = '';
public host = '';
public hostname = '';
public href = '';
public origin = '';
public password = '';
public pathname = '';
public port = '';
public protocol = '';
public search = '';
// @ts-expect-error: TypeScript isn't smart enough to know this is always defined
public searchParams: URLSearchParams;
public username = '';

constructor(url: string | URL, base?: string | URL) {
this.initialize(url, base);
}

private initialize(url: string | URL, base?: string | URL) {
let finalUrl = url.toString();

if (base) {
Expand All @@ -38,17 +40,17 @@

const result =
// eslint-disable-next-line
/((?:blob|file):)?(https?\:)\/\/(?:(.*):(.*)@)?(([^:\/?#]*)(?:\:([0-9]+))?)([\/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/.exec(
/((?:blob|file):)?(https?\:)\/\/(?:(.*):(.*)@)?([^:\/?#]*)(?:\:([0-9]+))?([\/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/.exec(
finalUrl,
);

if (result) {
const [href, origin, protocol, username, password, host, hostname, port, pathname, search, hash] = result;
const [_href, origin, protocol, username, password, hostname, port, pathname, search, hash] = result;

void _href;

this.hash = hash;
this.host = host;
this.hostname = hostname;
this.href = href;

this.port = port === DEFAULT_PORTS[protocol] ? '' : port;

Expand All @@ -59,12 +61,11 @@
}
}

this.password = password;
this.password = password || '';
this.pathname = pathname === '' ? '/' : pathname;
this.protocol = protocol;
this.search = search;
this.searchParams = new URLSearchParams(search);
this.username = username;
this.username = username || '';
} else {
this.searchParams = new URLSearchParams();
}
Expand All @@ -80,6 +81,63 @@
throw new Error('Not implemented');
}

get href(): string {
// TODO: username and password
const credentials = this.username + (this.password ? ':' + this.password : '');

let href =
this.protocol +
'//' +
(credentials ? credentials + '@' : '') +
this.host +
this.pathname +
this.search +
this.hash;

if (this.protocol === 'file:') {
href = href.replace('//', '');
}

return href;
}

set href(href: string) {
this.initialize(href);
}

get host(): string {
return this.hostname + (this.port ? ':' + this.port : '');
}

set host(host: string) {
// eslint-disable-next-line no-useless-escape
const result = /^([^:\/?#]*)(?:\:([0-9]+))$/.exec(host);
if (result) {
const [, hostname, port] = result;
this.hostname = hostname;
this.port = port;
} else {
this.hostname = host;
this.port = '';
}
}

get search(): string {
const search = this.searchParams.toString();
return search ? '?' + search : '';
}

set search(search: string) {
const newSearchParams = new URLSearchParams(search);
for (const key of this.searchParams.keys()) {
this.searchParams.delete(key);
}

for (const [key, value] of newSearchParams) {
this.searchParams.append(key, value);
}
}

toString(): string {
return this.href;
}
Expand Down

0 comments on commit ff0782c

Please sign in to comment.