Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(js-runtime): bring URL closer to the standard #253

Merged
merged 1 commit into from Dec 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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