Merge branch 'main' into jarred/process-change

This commit is contained in:
Jarred Sumner
2024-02-17 02:44:16 -08:00
61 changed files with 4545 additions and 1753 deletions

View File

@@ -1310,6 +1310,14 @@ describe("fs.exists", () => {
}
});
});
it("should work with util.promisify when path exists", async () => {
const fsexists = promisify(fs.exists);
expect(await fsexists(import.meta.path)).toBe(true);
});
it("should work with util.promisify when path doesn't exist", async () => {
const fsexists = promisify(fs.exists);
expect(await fsexists(`${tmpdir()}/test-fs-exists-${Date.now()}`)).toBe(false);
});
});
describe("rm", () => {

View File

@@ -0,0 +1,26 @@
import { describe, test } from "bun:test";
import assert from "node:assert";
import { URL } from "node:url";
describe("URL.canParse", () => {
// TODO: Support error code.
test.todo("invalid input", () => {
// One argument is required
assert.throws(
() => {
URL.canParse();
},
{
code: "ERR_MISSING_ARGS",
name: "TypeError",
},
);
});
test("repeatedly called produces same result", () => {
// This test is to ensure that the v8 fast api works.
for (let i = 0; i < 1e5; i++) {
assert(URL.canParse("https://www.example.com/path/?query=param#hash"));
}
});
});

View File

@@ -0,0 +1,31 @@
import { describe, test } from "bun:test";
import assert from "node:assert";
import url from "node:url";
const domainToASCII = url.domainToASCII;
const domainToUnicode = url.domainToUnicode;
// TODO: Support url.domainToASCII and url.domainToUnicode.
describe.todo("url.domainToASCII and url.domainToUnicode", () => {
test("convert from unicode to ascii and back", () => {
const domainWithASCII = [
["ıíd", "xn--d-iga7r"],
["يٴ", "xn--mhb8f"],
["www.ϧƽəʐ.com", "www.xn--cja62apfr6c.com"],
["новини.com", "xn--b1amarcd.com"],
["名がドメイン.com", "xn--v8jxj3d1dzdz08w.com"],
["افغانستا.icom.museum", "xn--mgbaal8b0b9b2b.icom.museum"],
["الجزائر.icom.fake", "xn--lgbbat1ad8j.icom.fake"],
["भारत.org", "xn--h2brj9c.org"],
];
domainWithASCII.forEach(pair => {
const domain = pair[0];
const ascii = pair[1];
const domainConvertedToASCII = domainToASCII(domain);
assert.strictEqual(domainConvertedToASCII, ascii);
const asciiConvertedToUnicode = domainToUnicode(ascii);
assert.strictEqual(asciiConvertedToUnicode, domain);
});
});
});

View File

@@ -0,0 +1,156 @@
import { describe, test } from "bun:test";
import assert from "node:assert";
import url, { URL } from "node:url";
const isWindows = process.platform === "win32";
describe("url.fileURLToPath", () => {
function testInvalidArgs(...args) {
for (const arg of args) {
assert.throws(() => url.fileURLToPath(arg), {
code: "ERR_INVALID_ARG_TYPE",
});
}
}
// TODO: Support error code.
test.todo("invalid input", () => {
// Input must be string or URL
testInvalidArgs(null, undefined, 1, {}, true);
// Input must be a file URL
assert.throws(() => url.fileURLToPath("https://a/b/c"), {
code: "ERR_INVALID_URL_SCHEME",
});
const withHost = new URL("file://host/a");
if (isWindows) {
assert.strictEqual(url.fileURLToPath(withHost), "\\\\host\\a");
} else {
assert.throws(() => url.fileURLToPath(withHost), {
code: "ERR_INVALID_FILE_URL_HOST",
});
}
if (isWindows) {
assert.throws(() => url.fileURLToPath("file:///C:/a%2F/"), {
code: "ERR_INVALID_FILE_URL_PATH",
});
assert.throws(() => url.fileURLToPath("file:///C:/a%5C/"), {
code: "ERR_INVALID_FILE_URL_PATH",
});
assert.throws(() => url.fileURLToPath("file:///?:/"), {
code: "ERR_INVALID_FILE_URL_PATH",
});
} else {
assert.throws(() => url.fileURLToPath("file:///a%2F/"), {
code: "ERR_INVALID_FILE_URL_PATH",
});
}
});
test("general", () => {
let testCases;
if (isWindows) {
testCases = [
// Lowercase ascii alpha
{ path: "C:\\foo", fileURL: "file:///C:/foo" },
// Uppercase ascii alpha
{ path: "C:\\FOO", fileURL: "file:///C:/FOO" },
// dir
{ path: "C:\\dir\\foo", fileURL: "file:///C:/dir/foo" },
// trailing separator
{ path: "C:\\dir\\", fileURL: "file:///C:/dir/" },
// dot
{ path: "C:\\foo.mjs", fileURL: "file:///C:/foo.mjs" },
// space
{ path: "C:\\foo bar", fileURL: "file:///C:/foo%20bar" },
// question mark
{ path: "C:\\foo?bar", fileURL: "file:///C:/foo%3Fbar" },
// number sign
{ path: "C:\\foo#bar", fileURL: "file:///C:/foo%23bar" },
// ampersand
{ path: "C:\\foo&bar", fileURL: "file:///C:/foo&bar" },
// equals
{ path: "C:\\foo=bar", fileURL: "file:///C:/foo=bar" },
// colon
{ path: "C:\\foo:bar", fileURL: "file:///C:/foo:bar" },
// semicolon
{ path: "C:\\foo;bar", fileURL: "file:///C:/foo;bar" },
// percent
{ path: "C:\\foo%bar", fileURL: "file:///C:/foo%25bar" },
// backslash
{ path: "C:\\foo\\bar", fileURL: "file:///C:/foo/bar" },
// backspace
{ path: "C:\\foo\bbar", fileURL: "file:///C:/foo%08bar" },
// tab
{ path: "C:\\foo\tbar", fileURL: "file:///C:/foo%09bar" },
// newline
{ path: "C:\\foo\nbar", fileURL: "file:///C:/foo%0Abar" },
// carriage return
{ path: "C:\\foo\rbar", fileURL: "file:///C:/foo%0Dbar" },
// latin1
{ path: "C:\\fóóbàr", fileURL: "file:///C:/f%C3%B3%C3%B3b%C3%A0r" },
// Euro sign (BMP code point)
{ path: "C:\\€", fileURL: "file:///C:/%E2%82%AC" },
// Rocket emoji (non-BMP code point)
{ path: "C:\\🚀", fileURL: "file:///C:/%F0%9F%9A%80" },
// UNC path (see https://docs.microsoft.com/en-us/archive/blogs/ie/file-uris-in-windows)
{ path: "\\\\nas\\My Docs\\File.doc", fileURL: "file://nas/My%20Docs/File.doc" },
];
} else {
testCases = [
// Lowercase ascii alpha
{ path: "/foo", fileURL: "file:///foo" },
// Uppercase ascii alpha
{ path: "/FOO", fileURL: "file:///FOO" },
// dir
{ path: "/dir/foo", fileURL: "file:///dir/foo" },
// trailing separator
{ path: "/dir/", fileURL: "file:///dir/" },
// dot
{ path: "/foo.mjs", fileURL: "file:///foo.mjs" },
// space
{ path: "/foo bar", fileURL: "file:///foo%20bar" },
// question mark
{ path: "/foo?bar", fileURL: "file:///foo%3Fbar" },
// number sign
{ path: "/foo#bar", fileURL: "file:///foo%23bar" },
// ampersand
{ path: "/foo&bar", fileURL: "file:///foo&bar" },
// equals
{ path: "/foo=bar", fileURL: "file:///foo=bar" },
// colon
{ path: "/foo:bar", fileURL: "file:///foo:bar" },
// semicolon
{ path: "/foo;bar", fileURL: "file:///foo;bar" },
// percent
{ path: "/foo%bar", fileURL: "file:///foo%25bar" },
// backslash
{ path: "/foo\\bar", fileURL: "file:///foo%5Cbar" },
// backspace
{ path: "/foo\bbar", fileURL: "file:///foo%08bar" },
// tab
{ path: "/foo\tbar", fileURL: "file:///foo%09bar" },
// newline
{ path: "/foo\nbar", fileURL: "file:///foo%0Abar" },
// carriage return
{ path: "/foo\rbar", fileURL: "file:///foo%0Dbar" },
// latin1
{ path: "/fóóbàr", fileURL: "file:///f%C3%B3%C3%B3b%C3%A0r" },
// Euro sign (BMP code point)
{ path: "/€", fileURL: "file:///%E2%82%AC" },
// Rocket emoji (non-BMP code point)
{ path: "/🚀", fileURL: "file:///%F0%9F%9A%80" },
];
}
for (const { path, fileURL } of testCases) {
const fromString = url.fileURLToPath(fileURL);
assert.strictEqual(fromString, path);
const fromURL = url.fileURLToPath(new URL(fileURL));
assert.strictEqual(fromURL, path);
}
});
});

View File

@@ -0,0 +1,28 @@
import { describe, test } from "bun:test";
import assert from "node:assert";
import url from "node:url";
describe("url.format", () => {
// TODO: Support error code.
test.todo("invalid input", () => {
const throwsObjsAndReportTypes = [undefined, null, true, false, 0, function () {}, Symbol("foo")];
for (const urlObject of throwsObjsAndReportTypes) {
assert.throws(
() => {
url.format(urlObject);
},
{
code: "ERR_INVALID_ARG_TYPE",
name: "TypeError",
message: 'The "urlObject" argument must be one of type object or string.',
},
);
}
});
test("empty", () => {
assert.strictEqual(url.format(""), "");
assert.strictEqual(url.format({}), "");
});
});

View File

@@ -0,0 +1,78 @@
import { describe, test } from "bun:test";
import assert from "node:assert";
import url, { URL } from "node:url";
describe("url.format", () => {
test("WHATWG", () => {
const myURL = new URL("http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c");
// TODO: Support these.
//
// assert.strictEqual(url.format(myURL), "http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c");
// assert.strictEqual(url.format(myURL, {}), "http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c");
// TODO: Support this kind of assert.throws.
// {
// [true, 1, "test", Infinity].forEach(value => {
// assert.throws(() => url.format(myURL, value), {
// code: "ERR_INVALID_ARG_TYPE",
// name: "TypeError",
// message: 'The "options" argument must be of type object.',
// });
// });
// }
// Any falsy value other than undefined will be treated as false.
// Any truthy value will be treated as true.
assert.strictEqual(url.format(myURL, { auth: false }), "http://xn--lck1c3crb1723bpq4a.com/a?a=b#c");
assert.strictEqual(url.format(myURL, { auth: "" }), "http://xn--lck1c3crb1723bpq4a.com/a?a=b#c");
assert.strictEqual(url.format(myURL, { auth: 0 }), "http://xn--lck1c3crb1723bpq4a.com/a?a=b#c");
// TODO: Support these.
//
// assert.strictEqual(url.format(myURL, { auth: 1 }), "http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c");
// assert.strictEqual(url.format(myURL, { auth: {} }), "http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c");
// assert.strictEqual(url.format(myURL, { fragment: false }), "http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b");
// assert.strictEqual(url.format(myURL, { fragment: "" }), "http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b");
// assert.strictEqual(url.format(myURL, { fragment: 0 }), "http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b");
// assert.strictEqual(url.format(myURL, { fragment: 1 }), "http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c");
// assert.strictEqual(url.format(myURL, { fragment: {} }), "http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c");
// assert.strictEqual(url.format(myURL, { search: false }), "http://user:pass@xn--lck1c3crb1723bpq4a.com/a#c");
// assert.strictEqual(url.format(myURL, { search: "" }), "http://user:pass@xn--lck1c3crb1723bpq4a.com/a#c");
// assert.strictEqual(url.format(myURL, { search: 0 }), "http://user:pass@xn--lck1c3crb1723bpq4a.com/a#c");
// assert.strictEqual(url.format(myURL, { search: 1 }), "http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c");
// assert.strictEqual(url.format(myURL, { search: {} }), "http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c");
// assert.strictEqual(url.format(myURL, { unicode: true }), "http://user:pass@理容ナカムラ.com/a?a=b#c");
// assert.strictEqual(url.format(myURL, { unicode: 1 }), "http://user:pass@理容ナカムラ.com/a?a=b#c");
// assert.strictEqual(url.format(myURL, { unicode: {} }), "http://user:pass@理容ナカムラ.com/a?a=b#c");
// assert.strictEqual(url.format(myURL, { unicode: false }), "http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c");
// assert.strictEqual(url.format(myURL, { unicode: 0 }), "http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c");
// assert.strictEqual(
// url.format(new URL("http://user:pass@xn--0zwm56d.com:8080/path"), { unicode: true }),
// "http://user:pass@测试.com:8080/path",
// );
assert.strictEqual(url.format(new URL("tel:123")), url.format(new URL("tel:123"), { unicode: true }));
});
});

View File

@@ -0,0 +1,281 @@
import { describe, test } from "bun:test";
import assert from "node:assert";
import url from "node:url";
describe("url.format", () => {
test("slightly wonky content", () => {
// Formatting tests to verify that it'll format slightly wonky content to a
// valid URL.
const formatTests = {
"http://example.com?": {
href: "http://example.com/?",
protocol: "http:",
slashes: true,
host: "example.com",
hostname: "example.com",
search: "?",
query: {},
pathname: "/",
},
"http://example.com?foo=bar#frag": {
href: "http://example.com/?foo=bar#frag",
protocol: "http:",
host: "example.com",
hostname: "example.com",
hash: "#frag",
search: "?foo=bar",
query: "foo=bar",
pathname: "/",
},
"http://example.com?foo=@bar#frag": {
href: "http://example.com/?foo=@bar#frag",
protocol: "http:",
host: "example.com",
hostname: "example.com",
hash: "#frag",
search: "?foo=@bar",
query: "foo=@bar",
pathname: "/",
},
"http://example.com?foo=/bar/#frag": {
href: "http://example.com/?foo=/bar/#frag",
protocol: "http:",
host: "example.com",
hostname: "example.com",
hash: "#frag",
search: "?foo=/bar/",
query: "foo=/bar/",
pathname: "/",
},
"http://example.com?foo=?bar/#frag": {
href: "http://example.com/?foo=?bar/#frag",
protocol: "http:",
host: "example.com",
hostname: "example.com",
hash: "#frag",
search: "?foo=?bar/",
query: "foo=?bar/",
pathname: "/",
},
"http://example.com#frag=?bar/#frag": {
href: "http://example.com/#frag=?bar/#frag",
protocol: "http:",
host: "example.com",
hostname: "example.com",
hash: "#frag=?bar/#frag",
pathname: "/",
},
'http://google.com" onload="alert(42)/': {
href: "http://google.com/%22%20onload=%22alert(42)/",
protocol: "http:",
host: "google.com",
pathname: "/%22%20onload=%22alert(42)/",
},
"http://a.com/a/b/c?s#h": {
href: "http://a.com/a/b/c?s#h",
protocol: "http",
host: "a.com",
pathname: "a/b/c",
hash: "h",
search: "s",
},
"xmpp:isaacschlueter@jabber.org": {
href: "xmpp:isaacschlueter@jabber.org",
protocol: "xmpp:",
host: "jabber.org",
auth: "isaacschlueter",
hostname: "jabber.org",
},
"http://atpass:foo%40bar@127.0.0.1/": {
href: "http://atpass:foo%40bar@127.0.0.1/",
auth: "atpass:foo@bar",
hostname: "127.0.0.1",
protocol: "http:",
pathname: "/",
},
"http://atslash%2F%40:%2F%40@foo/": {
href: "http://atslash%2F%40:%2F%40@foo/",
auth: "atslash/@:/@",
hostname: "foo",
protocol: "http:",
pathname: "/",
},
"svn+ssh://foo/bar": {
href: "svn+ssh://foo/bar",
hostname: "foo",
protocol: "svn+ssh:",
pathname: "/bar",
slashes: true,
},
"dash-test://foo/bar": {
href: "dash-test://foo/bar",
hostname: "foo",
protocol: "dash-test:",
pathname: "/bar",
slashes: true,
},
"dash-test:foo/bar": {
href: "dash-test:foo/bar",
hostname: "foo",
protocol: "dash-test:",
pathname: "/bar",
},
"dot.test://foo/bar": {
href: "dot.test://foo/bar",
hostname: "foo",
protocol: "dot.test:",
pathname: "/bar",
slashes: true,
},
"dot.test:foo/bar": {
href: "dot.test:foo/bar",
hostname: "foo",
protocol: "dot.test:",
pathname: "/bar",
},
// IPv6 support
"coap:u:p@[::1]:61616/.well-known/r?n=Temperature": {
href: "coap:u:p@[::1]:61616/.well-known/r?n=Temperature",
protocol: "coap:",
auth: "u:p",
hostname: "::1",
port: "61616",
pathname: "/.well-known/r",
search: "n=Temperature",
},
"coap:[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:61616/s/stopButton": {
href: "coap:[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:61616/s/stopButton",
protocol: "coap",
host: "[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:61616",
pathname: "/s/stopButton",
},
// TODO: Support this.
//
// "http://[::]/": {
// href: "http://[::]/",
// protocol: "http:",
// hostname: "[::]",
// pathname: "/",
// },
// Encode context-specific delimiters in path and query, but do not touch
// other non-delimiter chars like `%`.
// <https://github.com/nodejs/node-v0.x-archive/issues/4082>
// `#`,`?` in path
"/path/to/%%23%3F+=&.txt?foo=theA1#bar": {
href: "/path/to/%%23%3F+=&.txt?foo=theA1#bar",
pathname: "/path/to/%#?+=&.txt",
query: {
foo: "theA1",
},
hash: "#bar",
},
// `#`,`?` in path + `#` in query
"/path/to/%%23%3F+=&.txt?foo=the%231#bar": {
href: "/path/to/%%23%3F+=&.txt?foo=the%231#bar",
pathname: "/path/to/%#?+=&.txt",
query: {
foo: "the#1",
},
hash: "#bar",
},
// `#` in path end + `#` in query
"/path/to/%%23?foo=the%231#bar": {
href: "/path/to/%%23?foo=the%231#bar",
pathname: "/path/to/%#",
query: {
foo: "the#1",
},
hash: "#bar",
},
// `?` and `#` in path and search
"http://ex.com/foo%3F100%m%23r?abc=the%231?&foo=bar#frag": {
href: "http://ex.com/foo%3F100%m%23r?abc=the%231?&foo=bar#frag",
protocol: "http:",
hostname: "ex.com",
hash: "#frag",
search: "?abc=the#1?&foo=bar",
pathname: "/foo?100%m#r",
},
// `?` and `#` in search only
"http://ex.com/fooA100%mBr?abc=the%231?&foo=bar#frag": {
href: "http://ex.com/fooA100%mBr?abc=the%231?&foo=bar#frag",
protocol: "http:",
hostname: "ex.com",
hash: "#frag",
search: "?abc=the#1?&foo=bar",
pathname: "/fooA100%mBr",
},
// TODO: Support these.
//
// // Multiple `#` in search
// "http://example.com/?foo=bar%231%232%233&abc=%234%23%235#frag": {
// href: "http://example.com/?foo=bar%231%232%233&abc=%234%23%235#frag",
// protocol: "http:",
// slashes: true,
// host: "example.com",
// hostname: "example.com",
// hash: "#frag",
// search: "?foo=bar#1#2#3&abc=#4##5",
// query: {},
// pathname: "/",
// },
// More than 255 characters in hostname which exceeds the limit
// [`http://${"a".repeat(255)}.com/node`]: {
// href: "http:///node",
// protocol: "http:",
// slashes: true,
// host: "",
// hostname: "",
// pathname: "/node",
// path: "/node",
// },
// Greater than or equal to 63 characters after `.` in hostname
// [`http://www.${"z".repeat(63)}example.com/node`]: {
// href: `http://www.${"z".repeat(63)}example.com/node`,
// protocol: "http:",
// slashes: true,
// host: `www.${"z".repeat(63)}example.com`,
// hostname: `www.${"z".repeat(63)}example.com`,
// pathname: "/node",
// path: "/node",
// },
// https://github.com/nodejs/node/issues/3361
// "file:///home/user": {
// href: "file:///home/user",
// protocol: "file",
// pathname: "/home/user",
// path: "/home/user",
// },
// surrogate in auth
"http://%F0%9F%98%80@www.example.com/": {
href: "http://%F0%9F%98%80@www.example.com/",
protocol: "http:",
auth: "\uD83D\uDE00",
hostname: "www.example.com",
pathname: "/",
},
};
for (const u in formatTests) {
const expect = formatTests[u].href;
delete formatTests[u].href;
const actual = url.format(u);
const actualObj = url.format(formatTests[u]);
assert.strictEqual(actual, expect, `wonky format(${u}) == ${expect}\nactual:${actual}`);
assert.strictEqual(
actualObj,
expect,
`wonky format(${JSON.stringify(formatTests[u])}) == ${expect}\nactual: ${actualObj}`,
);
}
});
});

View File

@@ -0,0 +1,22 @@
// Flags: --expose-internals
import { describe, test } from "bun:test";
import assert from "node:assert";
import { URL, parse } from "node:url";
describe("internal/url", () => {
test.skip("isURL", () => {
const { isURL } = require("internal/url");
assert.strictEqual(isURL("https://www.nodejs.org"), true);
assert.strictEqual(isURL(new URL("https://www.nodejs.org")), true);
assert.strictEqual(isURL(parse("https://www.nodejs.org")), false);
assert.strictEqual(
isURL({
href: "https://www.nodejs.org",
protocol: "https:",
path: "/",
}),
false,
);
});
});

View File

@@ -0,0 +1,15 @@
import { describe, test } from "bun:test";
import assert from "node:assert";
import { URL } from "node:url";
describe("URL", () => {
// TODO: Fix error properties
test.skip("null character", () => {
assert.throws(
() => {
new URL("a\0b");
},
{ code: "ERR_INVALID_URL", input: "a\0b" },
);
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,121 @@
import { describe, test } from "bun:test";
import assert from "node:assert";
import url from "node:url";
describe("url.parse", () => {
// TODO: Support error code.
test.todo("invalid input", () => {
// https://github.com/joyent/node/issues/568
[
[undefined, "undefined"],
[null, "object"],
[true, "boolean"],
[false, "boolean"],
[0.0, "number"],
[0, "number"],
[[], "object"],
[{}, "object"],
[() => {}, "function"],
[Symbol("foo"), "symbol"],
].forEach(([val, type]) => {
assert.throws(
() => {
url.parse(val);
},
{
code: "ERR_INVALID_ARG_TYPE",
name: "TypeError",
message: 'The "url" argument must be of type string.',
},
);
});
assert.throws(
() => {
url.parse("http://%E0%A4%A@fail");
},
e => {
// The error should be a URIError.
if (!(e instanceof URIError)) return false;
// The error should be from the JS engine and not from Node.js.
// JS engine errors do not have the `code` property.
return e.code === undefined;
},
);
assert.throws(
() => {
url.parse("http://[127.0.0.1\x00c8763]:8000/");
},
{ code: "ERR_INVALID_URL", input: "http://[127.0.0.1\x00c8763]:8000/" },
);
if (common.hasIntl) {
// An array of Unicode code points whose Unicode NFKD contains a "bad
// character".
const badIDNA = (() => {
const BAD_CHARS = "#%/:?@[\\]^|";
const out = [];
for (let i = 0x80; i < 0x110000; i++) {
const cp = String.fromCodePoint(i);
for (const badChar of BAD_CHARS) {
if (cp.normalize("NFKD").includes(badChar)) {
out.push(cp);
}
}
}
return out;
})();
// The generation logic above should at a minimum produce these two
// characters.
assert(badIDNA.includes("℀"));
assert(badIDNA.includes(""));
for (const badCodePoint of badIDNA) {
const badURL = `http://fail${badCodePoint}fail.com/`;
assert.throws(
() => {
url.parse(badURL);
},
e => e.code === "ERR_INVALID_URL",
`parsing ${badURL}`,
);
}
assert.throws(
() => {
url.parse("http://\u00AD/bad.com/");
},
e => e.code === "ERR_INVALID_URL",
"parsing http://\u00AD/bad.com/",
);
}
{
const badURLs = ["https://evil.com:.example.com", "git+ssh://git@github.com:npm/npm"];
badURLs.forEach(badURL => {
common.spawnPromisified(process.execPath, ["-e", `url.parse(${JSON.stringify(badURL)})`]).then(
common.mustCall(({ code, stdout, stderr }) => {
assert.strictEqual(code, 0);
assert.strictEqual(stdout, "");
assert.match(stderr, /\[DEP0170\] DeprecationWarning:/);
}),
);
});
// Warning should only happen once per process.
const expectedWarning = [
`The URL ${badURLs[0]} is invalid. Future versions of Node.js will throw an error.`,
"DEP0170",
];
common.expectWarning({
DeprecationWarning: expectedWarning,
});
badURLs.forEach(badURL => {
url.parse(badURL);
});
}
});
});

View File

@@ -0,0 +1,93 @@
import { describe, test } from "bun:test";
import assert from "node:assert";
import url from "node:url";
describe("url.parse", () => {
// TODO: Support correct prototype and null values.
test.todo("with query string", () => {
function createWithNoPrototype(properties = []) {
const noProto = { __proto__: null };
properties.forEach(property => {
noProto[property.key] = property.value;
});
return noProto;
}
function check(actual, expected) {
assert.notStrictEqual(Object.getPrototypeOf(actual), Object.prototype);
assert.deepStrictEqual(Object.keys(actual).sort(), Object.keys(expected).sort());
Object.keys(expected).forEach(function (key) {
assert.deepStrictEqual(actual[key], expected[key]);
});
}
const parseTestsWithQueryString = {
"/foo/bar?baz=quux#frag": {
href: "/foo/bar?baz=quux#frag",
hash: "#frag",
search: "?baz=quux",
query: createWithNoPrototype([{ key: "baz", value: "quux" }]),
pathname: "/foo/bar",
path: "/foo/bar?baz=quux",
},
"http://example.com": {
href: "http://example.com/",
protocol: "http:",
slashes: true,
host: "example.com",
hostname: "example.com",
query: createWithNoPrototype(),
search: null,
pathname: "/",
path: "/",
},
"/example": {
protocol: null,
slashes: null,
auth: undefined,
host: null,
port: null,
hostname: null,
hash: null,
search: null,
query: createWithNoPrototype(),
pathname: "/example",
path: "/example",
href: "/example",
},
"/example?query=value": {
protocol: null,
slashes: null,
auth: undefined,
host: null,
port: null,
hostname: null,
hash: null,
search: "?query=value",
query: createWithNoPrototype([{ key: "query", value: "value" }]),
pathname: "/example",
path: "/example?query=value",
href: "/example?query=value",
},
};
for (const u in parseTestsWithQueryString) {
const actual = url.parse(u, true);
const expected = Object.assign(new url.Url(), parseTestsWithQueryString[u]);
for (const i in actual) {
if (actual[i] === null && expected[i] === undefined) {
expected[i] = null;
}
}
const properties = Object.keys(actual).sort();
assert.deepStrictEqual(properties, Object.keys(expected).sort());
properties.forEach(property => {
if (property === "query") {
check(actual[property], expected[property]);
} else {
assert.deepStrictEqual(actual[property], expected[property]);
}
});
}
});
});

View File

@@ -0,0 +1,203 @@
import { describe, test } from "bun:test";
import assert from "node:assert";
import url from "node:url";
const isWindows = process.platform === "win32";
describe("url.pathToFileURL", () => {
// TODO: Fix these asserts on Windows.
test.skipIf(isWindows)("dangling slashes and percent sign", () => {
{
const fileURL = url.pathToFileURL("test/").href;
assert.ok(fileURL.startsWith("file:///"));
assert.ok(fileURL.endsWith("/"));
}
// TODO: Support these.
//
// {
// const fileURL = url.pathToFileURL("test\\").href;
// assert.ok(fileURL.startsWith("file:///"));
// if (isWindows) assert.ok(fileURL.endsWith("/"));
// else assert.ok(fileURL.endsWith("%5C"));
// }
// {
// const fileURL = url.pathToFileURL("test/%").href;
// assert.ok(fileURL.includes("%25"));
// }
});
// TODO: Support UNC paths across platforms.
test.todo("UNC paths", () => {
if (isWindows) {
// UNC path: \\server\share\resource
// Missing server:
assert.throws(() => url.pathToFileURL("\\\\\\no-server"), {
code: "ERR_INVALID_ARG_VALUE",
});
// Missing share or resource:
assert.throws(() => url.pathToFileURL("\\\\host"), {
code: "ERR_INVALID_ARG_VALUE",
});
// Regression test for direct String.prototype.startsWith call
assert.throws(() => url.pathToFileURL(["\\\\", { [Symbol.toPrimitive]: () => "blep\\blop" }]), {
code: "ERR_INVALID_ARG_TYPE",
});
assert.throws(() => url.pathToFileURL(["\\\\", "blep\\blop"]), {
code: "ERR_INVALID_ARG_TYPE",
});
assert.throws(
() =>
url.pathToFileURL({
[Symbol.toPrimitive]: () => "\\\\blep\\blop",
}),
{
code: "ERR_INVALID_ARG_TYPE",
},
);
} else {
// UNC paths on posix are considered a single path that has backslashes:
const fileURL = url.pathToFileURL("\\\\nas\\share\\path.txt").href;
assert.match(fileURL, /file:\/\/.+%5C%5Cnas%5Cshare%5Cpath\.txt$/);
}
});
test("general", () => {
let testCases;
if (isWindows) {
testCases = [
// Lowercase ascii alpha
{ path: "C:\\foo", expected: "file:///C:/foo" },
// Uppercase ascii alpha
{ path: "C:\\FOO", expected: "file:///C:/FOO" },
// dir
{ path: "C:\\dir\\foo", expected: "file:///C:/dir/foo" },
// trailing separator
{ path: "C:\\dir\\", expected: "file:///C:/dir/" },
// dot
{ path: "C:\\foo.mjs", expected: "file:///C:/foo.mjs" },
// space
{ path: "C:\\foo bar", expected: "file:///C:/foo%20bar" },
// question mark
{ path: "C:\\foo?bar", expected: "file:///C:/foo%3Fbar" },
// number sign
{ path: "C:\\foo#bar", expected: "file:///C:/foo%23bar" },
// ampersand
{ path: "C:\\foo&bar", expected: "file:///C:/foo&bar" },
// equals
{ path: "C:\\foo=bar", expected: "file:///C:/foo=bar" },
// colon
{ path: "C:\\foo:bar", expected: "file:///C:/foo:bar" },
// semicolon
{ path: "C:\\foo;bar", expected: "file:///C:/foo;bar" },
// TODO: Support these.
//
// percent
// { path: "C:\\foo%bar", expected: "file:///C:/foo%25bar" },
// backslash
// { path: "C:\\foo\\bar", expected: "file:///C:/foo/bar" },
// backspace
// { path: "C:\\foo\bbar", expected: "file:///C:/foo%08bar" },
// tab
// { path: "C:\\foo\tbar", expected: "file:///C:/foo%09bar" },
// newline
// { path: "C:\\foo\nbar", expected: "file:///C:/foo%0Abar" },
// carriage return
// { path: "C:\\foo\rbar", expected: "file:///C:/foo%0Dbar" },
// latin1
{ path: "C:\\fóóbàr", expected: "file:///C:/f%C3%B3%C3%B3b%C3%A0r" },
// Euro sign (BMP code point)
{ path: "C:\\€", expected: "file:///C:/%E2%82%AC" },
// Rocket emoji (non-BMP code point)
{ path: "C:\\🚀", expected: "file:///C:/%F0%9F%9A%80" },
// UNC path (see https://docs.microsoft.com/en-us/archive/blogs/ie/file-uris-in-windows)
{ path: "\\\\nas\\My Docs\\File.doc", expected: "file://nas/My%20Docs/File.doc" },
];
} else {
testCases = [
// Lowercase ascii alpha
{ path: "/foo", expected: "file:///foo" },
// Uppercase ascii alpha
{ path: "/FOO", expected: "file:///FOO" },
// dir
{ path: "/dir/foo", expected: "file:///dir/foo" },
// trailing separator
{ path: "/dir/", expected: "file:///dir/" },
// dot
{ path: "/foo.mjs", expected: "file:///foo.mjs" },
// space
{ path: "/foo bar", expected: "file:///foo%20bar" },
// question mark
{ path: "/foo?bar", expected: "file:///foo%3Fbar" },
// number sign
{ path: "/foo#bar", expected: "file:///foo%23bar" },
// ampersand
{ path: "/foo&bar", expected: "file:///foo&bar" },
// equals
{ path: "/foo=bar", expected: "file:///foo=bar" },
// colon
{ path: "/foo:bar", expected: "file:///foo:bar" },
// semicolon
{ path: "/foo;bar", expected: "file:///foo;bar" },
// TODO: Support these.
//
// percent
// { path: "/foo%bar", expected: "file:///foo%25bar" },
// backslash
// { path: "/foo\\bar", expected: "file:///foo%5Cbar" },
// backspace
//{ path: "/foo\bbar", expected: "file:///foo%08bar" },
// tab
// { path: "/foo\tbar", expected: "file:///foo%09bar" },
// newline
// { path: "/foo\nbar", expected: "file:///foo%0Abar" },
// carriage return
// { path: "/foo\rbar", expected: "file:///foo%0Dbar" },
// latin1
{ path: "/fóóbàr", expected: "file:///f%C3%B3%C3%B3b%C3%A0r" },
// Euro sign (BMP code point)
{ path: "/€", expected: "file:///%E2%82%AC" },
// Rocket emoji (non-BMP code point)
{ path: "/🚀", expected: "file:///%F0%9F%9A%80" },
];
}
for (const { path, expected } of testCases) {
const actual = url.pathToFileURL(path).href;
assert.strictEqual(actual, expected);
}
});
// TODO: Support throwing correct exception for non-string params.
test.todo("non-string parameter", () => {
for (const badPath of [
undefined,
null,
true,
42,
42n,
Symbol("42"),
NaN,
{},
[],
() => {},
Promise.resolve("foo"),
new Date(),
new String("notPrimitive"),
{
toString() {
return "amObject";
},
},
{ [Symbol.toPrimitive]: hint => "amObject" },
]) {
assert.throws(() => url.pathToFileURL(badPath), {
code: "ERR_INVALID_ARG_TYPE",
});
}
});
});

View File

@@ -0,0 +1,420 @@
import { describe, test } from "bun:test";
import assert from "node:assert";
import { inspect } from "node:util";
import url from "node:url";
// [from, path, expected]
const relativeTests = [
["/foo/bar/baz", "quux", "/foo/bar/quux"],
["/foo/bar/baz", "quux/asdf", "/foo/bar/quux/asdf"],
["/foo/bar/baz", "quux/baz", "/foo/bar/quux/baz"],
["/foo/bar/baz", "../quux/baz", "/foo/quux/baz"],
["/foo/bar/baz", "/bar", "/bar"],
["/foo/bar/baz/", "quux", "/foo/bar/baz/quux"],
["/foo/bar/baz/", "quux/baz", "/foo/bar/baz/quux/baz"],
["/foo/bar/baz", "../../../../../../../../quux/baz", "/quux/baz"],
["/foo/bar/baz", "../../../../../../../quux/baz", "/quux/baz"],
["/foo", ".", "/"],
["/foo", "..", "/"],
["/foo/", ".", "/foo/"],
["/foo/", "..", "/"],
["/foo/bar", ".", "/foo/"],
["/foo/bar", "..", "/"],
["/foo/bar/", ".", "/foo/bar/"],
["/foo/bar/", "..", "/foo/"],
["foo/bar", "../../../baz", "../../baz"],
["foo/bar/", "../../../baz", "../baz"],
["http://example.com/b//c//d;p?q#blarg", "https:#hash2", "https:///#hash2"],
["http://example.com/b//c//d;p?q#blarg", "https:/p/a/t/h?s#hash2", "https://p/a/t/h?s#hash2"],
["http://example.com/b//c//d;p?q#blarg", "https://u:p@h.com/p/a/t/h?s#hash2", "https://u:p@h.com/p/a/t/h?s#hash2"],
["http://example.com/b//c//d;p?q#blarg", "https:/a/b/c/d", "https://a/b/c/d"],
["http://example.com/b//c//d;p?q#blarg", "http:#hash2", "http://example.com/b//c//d;p?q#hash2"],
["http://example.com/b//c//d;p?q#blarg", "http:/p/a/t/h?s#hash2", "http://example.com/p/a/t/h?s#hash2"],
["http://example.com/b//c//d;p?q#blarg", "http://u:p@h.com/p/a/t/h?s#hash2", "http://u:p@h.com/p/a/t/h?s#hash2"],
["http://example.com/b//c//d;p?q#blarg", "http:/a/b/c/d", "http://example.com/a/b/c/d"],
["/foo/bar/baz", "/../etc/passwd", "/etc/passwd"],
// TODO: Support this.
//
// ["http://localhost", "file:///Users/foo", "file:///Users/foo"],
["http://localhost", "file://foo/Users", "file://foo/Users"],
["https://registry.npmjs.org", "@foo/bar", "https://registry.npmjs.org/@foo/bar"],
];
describe("url.resolveObject", () => {
test("source is false", () => {
// When source is false
assert.strictEqual(url.resolveObject("", "foo"), "foo");
});
test("url.resolveObject and url.parse are inverse operations", () => {
// If format and parse are inverse operations then
// resolveObject(parse(x), y) == parse(resolve(x, y))
// format: [from, path, expected]
for (let i = 0; i < relativeTests.length; i++) {
const relativeTest = relativeTests[i];
let actual = url.resolveObject(url.parse(relativeTest[0]), relativeTest[1]);
let expected = url.parse(relativeTest[2]);
assert.deepStrictEqual(actual, expected);
expected = relativeTest[2];
actual = url.format(actual);
assert.strictEqual(actual, expected, `format(${actual}) == ${expected}\n` + `actual: ${actual}`);
}
});
});
describe("url.resolve", () => {
test("relative paths", () => {
for (let i = 0; i < relativeTests.length; i++) {
const relativeTest = relativeTests[i];
const a = url.resolve(relativeTest[0], relativeTest[1]);
const e = relativeTest[2];
assert.strictEqual(a, e, `resolve(${relativeTest[0]}, ${relativeTest[1]})` + ` == ${e}\n actual=${a}`);
}
});
});
describe("chiron url.resolve rests", () => {
//
// Tests below taken from Chiron
// http://code.google.com/p/chironjs/source/browse/trunk/src/test/http/url.js
//
// Copyright (c) 2002-2008 Kris Kowal <http://cixar.com/~kris.kowal>
// used with permission under MIT License
//
// Changes marked with @isaacs
const bases = [
"http://a/b/c/d;p?q",
"http://a/b/c/d;p?q=1/2",
"http://a/b/c/d;p=1/2?q",
"fred:///s//a/b/c",
"http:///s//a/b/c",
];
// [to, from, result]
const relativeTests2 = [
// http://lists.w3.org/Archives/Public/uri/2004Feb/0114.html
["../c", "foo:a/b", "foo:c"],
["foo:.", "foo:a", "foo:"],
["/foo/../../../bar", "zz:abc", "zz:/bar"],
["/foo/../bar", "zz:abc", "zz:/bar"],
// @isaacs Disagree. Not how web browsers resolve this.
["foo/../../../bar", "zz:abc", "zz:bar"],
// ['foo/../../../bar', 'zz:abc', 'zz:../../bar'], // @isaacs Added
["foo/../bar", "zz:abc", "zz:bar"],
["zz:.", "zz:abc", "zz:"],
["/.", bases[0], "http://a/"],
["/.foo", bases[0], "http://a/.foo"],
[".foo", bases[0], "http://a/b/c/.foo"],
// http://gbiv.com/protocols/uri/test/rel_examples1.html
// examples from RFC 2396
["g:h", bases[0], "g:h"],
["g", bases[0], "http://a/b/c/g"],
["./g", bases[0], "http://a/b/c/g"],
["g/", bases[0], "http://a/b/c/g/"],
["/g", bases[0], "http://a/g"],
["//g", bases[0], "http://g/"],
// Changed with RFC 2396bis
// ('?y', bases[0], 'http://a/b/c/d;p?y'],
["?y", bases[0], "http://a/b/c/d;p?y"],
["g?y", bases[0], "http://a/b/c/g?y"],
// Changed with RFC 2396bis
// ('#s', bases[0], CURRENT_DOC_URI + '#s'],
["#s", bases[0], "http://a/b/c/d;p?q#s"],
["g#s", bases[0], "http://a/b/c/g#s"],
["g?y#s", bases[0], "http://a/b/c/g?y#s"],
[";x", bases[0], "http://a/b/c/;x"],
["g;x", bases[0], "http://a/b/c/g;x"],
["g;x?y#s", bases[0], "http://a/b/c/g;x?y#s"],
// Changed with RFC 2396bis
// ('', bases[0], CURRENT_DOC_URI],
["", bases[0], "http://a/b/c/d;p?q"],
[".", bases[0], "http://a/b/c/"],
["./", bases[0], "http://a/b/c/"],
["..", bases[0], "http://a/b/"],
["../", bases[0], "http://a/b/"],
["../g", bases[0], "http://a/b/g"],
["../..", bases[0], "http://a/"],
["../../", bases[0], "http://a/"],
["../../g", bases[0], "http://a/g"],
["../../../g", bases[0], ("http://a/../g", "http://a/g")],
["../../../../g", bases[0], ("http://a/../../g", "http://a/g")],
// Changed with RFC 2396bis
// ('/./g', bases[0], 'http://a/./g'],
["/./g", bases[0], "http://a/g"],
// Changed with RFC 2396bis
// ('/../g', bases[0], 'http://a/../g'],
["/../g", bases[0], "http://a/g"],
["g.", bases[0], "http://a/b/c/g."],
[".g", bases[0], "http://a/b/c/.g"],
["g..", bases[0], "http://a/b/c/g.."],
["..g", bases[0], "http://a/b/c/..g"],
["./../g", bases[0], "http://a/b/g"],
["./g/.", bases[0], "http://a/b/c/g/"],
["g/./h", bases[0], "http://a/b/c/g/h"],
["g/../h", bases[0], "http://a/b/c/h"],
["g;x=1/./y", bases[0], "http://a/b/c/g;x=1/y"],
["g;x=1/../y", bases[0], "http://a/b/c/y"],
["g?y/./x", bases[0], "http://a/b/c/g?y/./x"],
["g?y/../x", bases[0], "http://a/b/c/g?y/../x"],
["g#s/./x", bases[0], "http://a/b/c/g#s/./x"],
["g#s/../x", bases[0], "http://a/b/c/g#s/../x"],
["http:g", bases[0], ("http:g", "http://a/b/c/g")],
["http:", bases[0], ("http:", bases[0])],
// Not sure where this one originated
["/a/b/c/./../../g", bases[0], "http://a/a/g"],
// http://gbiv.com/protocols/uri/test/rel_examples2.html
// slashes in base URI's query args
["g", bases[1], "http://a/b/c/g"],
["./g", bases[1], "http://a/b/c/g"],
["g/", bases[1], "http://a/b/c/g/"],
["/g", bases[1], "http://a/g"],
["//g", bases[1], "http://g/"],
// Changed in RFC 2396bis
// ('?y', bases[1], 'http://a/b/c/?y'],
["?y", bases[1], "http://a/b/c/d;p?y"],
["g?y", bases[1], "http://a/b/c/g?y"],
["g?y/./x", bases[1], "http://a/b/c/g?y/./x"],
["g?y/../x", bases[1], "http://a/b/c/g?y/../x"],
["g#s", bases[1], "http://a/b/c/g#s"],
["g#s/./x", bases[1], "http://a/b/c/g#s/./x"],
["g#s/../x", bases[1], "http://a/b/c/g#s/../x"],
["./", bases[1], "http://a/b/c/"],
["../", bases[1], "http://a/b/"],
["../g", bases[1], "http://a/b/g"],
["../../", bases[1], "http://a/"],
["../../g", bases[1], "http://a/g"],
// http://gbiv.com/protocols/uri/test/rel_examples3.html
// slashes in path params
// all of these changed in RFC 2396bis
["g", bases[2], "http://a/b/c/d;p=1/g"],
["./g", bases[2], "http://a/b/c/d;p=1/g"],
["g/", bases[2], "http://a/b/c/d;p=1/g/"],
["g?y", bases[2], "http://a/b/c/d;p=1/g?y"],
[";x", bases[2], "http://a/b/c/d;p=1/;x"],
["g;x", bases[2], "http://a/b/c/d;p=1/g;x"],
["g;x=1/./y", bases[2], "http://a/b/c/d;p=1/g;x=1/y"],
["g;x=1/../y", bases[2], "http://a/b/c/d;p=1/y"],
["./", bases[2], "http://a/b/c/d;p=1/"],
["../", bases[2], "http://a/b/c/"],
["../g", bases[2], "http://a/b/c/g"],
["../../", bases[2], "http://a/b/"],
["../../g", bases[2], "http://a/b/g"],
// http://gbiv.com/protocols/uri/test/rel_examples4.html
// double and triple slash, unknown scheme
["g:h", bases[3], "g:h"],
["g", bases[3], "fred:///s//a/b/g"],
["./g", bases[3], "fred:///s//a/b/g"],
["g/", bases[3], "fred:///s//a/b/g/"],
["/g", bases[3], "fred:///g"], // May change to fred:///s//a/g
["//g", bases[3], "fred://g"], // May change to fred:///s//g
["//g/x", bases[3], "fred://g/x"], // May change to fred:///s//g/x
["///g", bases[3], "fred:///g"],
["./", bases[3], "fred:///s//a/b/"],
["../", bases[3], "fred:///s//a/"],
["../g", bases[3], "fred:///s//a/g"],
["../../", bases[3], "fred:///s//"],
["../../g", bases[3], "fred:///s//g"],
["../../../g", bases[3], "fred:///s/g"],
// May change to fred:///s//a/../../../g
["../../../../g", bases[3], "fred:///g"],
// http://gbiv.com/protocols/uri/test/rel_examples5.html
// double and triple slash, well-known scheme
["g:h", bases[4], "g:h"],
["g", bases[4], "http:///s//a/b/g"],
["./g", bases[4], "http:///s//a/b/g"],
["g/", bases[4], "http:///s//a/b/g/"],
["/g", bases[4], "http:///g"], // May change to http:///s//a/g
["//g", bases[4], "http://g/"], // May change to http:///s//g
["//g/x", bases[4], "http://g/x"], // May change to http:///s//g/x
["///g", bases[4], "http:///g"],
["./", bases[4], "http:///s//a/b/"],
["../", bases[4], "http:///s//a/"],
["../g", bases[4], "http:///s//a/g"],
["../../", bases[4], "http:///s//"],
["../../g", bases[4], "http:///s//g"],
// May change to http:///s//a/../../g
["../../../g", bases[4], "http:///s/g"],
// May change to http:///s//a/../../../g
["../../../../g", bases[4], "http:///g"],
// From Dan Connelly's tests in http://www.w3.org/2000/10/swap/uripath.py
["bar:abc", "foo:xyz", "bar:abc"],
["../abc", "http://example/x/y/z", "http://example/x/abc"],
["http://example/x/abc", "http://example2/x/y/z", "http://example/x/abc"],
["../r", "http://ex/x/y/z", "http://ex/x/r"],
["q/r", "http://ex/x/y", "http://ex/x/q/r"],
["q/r#s", "http://ex/x/y", "http://ex/x/q/r#s"],
["q/r#s/t", "http://ex/x/y", "http://ex/x/q/r#s/t"],
["ftp://ex/x/q/r", "http://ex/x/y", "ftp://ex/x/q/r"],
["", "http://ex/x/y", "http://ex/x/y"],
["", "http://ex/x/y/", "http://ex/x/y/"],
["", "http://ex/x/y/pdq", "http://ex/x/y/pdq"],
["z/", "http://ex/x/y/", "http://ex/x/y/z/"],
["#Animal", "file:/swap/test/animal.rdf", "file:/swap/test/animal.rdf#Animal"],
["../abc", "file:/e/x/y/z", "file:/e/x/abc"],
["/example/x/abc", "file:/example2/x/y/z", "file:/example/x/abc"],
["../r", "file:/ex/x/y/z", "file:/ex/x/r"],
["/r", "file:/ex/x/y/z", "file:/r"],
["q/r", "file:/ex/x/y", "file:/ex/x/q/r"],
["q/r#s", "file:/ex/x/y", "file:/ex/x/q/r#s"],
["q/r#", "file:/ex/x/y", "file:/ex/x/q/r#"],
["q/r#s/t", "file:/ex/x/y", "file:/ex/x/q/r#s/t"],
["ftp://ex/x/q/r", "file:/ex/x/y", "ftp://ex/x/q/r"],
["", "file:/ex/x/y", "file:/ex/x/y"],
["", "file:/ex/x/y/", "file:/ex/x/y/"],
["", "file:/ex/x/y/pdq", "file:/ex/x/y/pdq"],
["z/", "file:/ex/x/y/", "file:/ex/x/y/z/"],
[
"file://meetings.example.com/cal#m1",
"file:/devel/WWW/2000/10/swap/test/reluri-1.n3",
"file://meetings.example.com/cal#m1",
],
[
"file://meetings.example.com/cal#m1",
"file:/home/connolly/w3ccvs/WWW/2000/10/swap/test/reluri-1.n3",
"file://meetings.example.com/cal#m1",
],
["./#blort", "file:/some/dir/foo", "file:/some/dir/#blort"],
["./#", "file:/some/dir/foo", "file:/some/dir/#"],
// Ryan Lee
["./", "http://example/x/abc.efg", "http://example/x/"],
// Graham Klyne's tests
// http://www.ninebynine.org/Software/HaskellUtils/Network/UriTest.xls
// 01-31 are from Connelly's cases
// 32-49
["./q:r", "http://ex/x/y", "http://ex/x/q:r"],
["./p=q:r", "http://ex/x/y", "http://ex/x/p=q:r"],
["?pp/rr", "http://ex/x/y?pp/qq", "http://ex/x/y?pp/rr"],
["y/z", "http://ex/x/y?pp/qq", "http://ex/x/y/z"],
["local/qual@domain.org#frag", "mailto:local", "mailto:local/qual@domain.org#frag"],
["more/qual2@domain2.org#frag", "mailto:local/qual1@domain1.org", "mailto:local/more/qual2@domain2.org#frag"],
["y?q", "http://ex/x/y?q", "http://ex/x/y?q"],
["/x/y?q", "http://ex?p", "http://ex/x/y?q"],
["c/d", "foo:a/b", "foo:a/c/d"],
["/c/d", "foo:a/b", "foo:/c/d"],
["", "foo:a/b?c#d", "foo:a/b?c"],
["b/c", "foo:a", "foo:b/c"],
["../b/c", "foo:/a/y/z", "foo:/a/b/c"],
["./b/c", "foo:a", "foo:b/c"],
["/./b/c", "foo:a", "foo:/b/c"],
["../../d", "foo://a//b/c", "foo://a/d"],
[".", "foo:a", "foo:"],
["..", "foo:a", "foo:"],
// 50-57[cf. TimBL comments --
// http://lists.w3.org/Archives/Public/uri/2003Feb/0028.html,
// http://lists.w3.org/Archives/Public/uri/2003Jan/0008.html)
["abc", "http://example/x/y%2Fz", "http://example/x/abc"],
["../../x%2Fabc", "http://example/a/x/y/z", "http://example/a/x%2Fabc"],
["../x%2Fabc", "http://example/a/x/y%2Fz", "http://example/a/x%2Fabc"],
["abc", "http://example/x%2Fy/z", "http://example/x%2Fy/abc"],
["q%3Ar", "http://ex/x/y", "http://ex/x/q%3Ar"],
["/x%2Fabc", "http://example/x/y%2Fz", "http://example/x%2Fabc"],
["/x%2Fabc", "http://example/x/y/z", "http://example/x%2Fabc"],
["/x%2Fabc", "http://example/x/y%2Fz", "http://example/x%2Fabc"],
// 70-77
["local2@domain2", "mailto:local1@domain1?query1", "mailto:local2@domain2"],
["local2@domain2?query2", "mailto:local1@domain1", "mailto:local2@domain2?query2"],
["local2@domain2?query2", "mailto:local1@domain1?query1", "mailto:local2@domain2?query2"],
["?query2", "mailto:local@domain?query1", "mailto:local@domain?query2"],
["local@domain?query2", "mailto:?query1", "mailto:local@domain?query2"],
["?query2", "mailto:local@domain?query1", "mailto:local@domain?query2"],
["http://example/a/b?c/../d", "foo:bar", "http://example/a/b?c/../d"],
["http://example/a/b#c/../d", "foo:bar", "http://example/a/b#c/../d"],
// 82-88
// @isaacs Disagree. Not how browsers do it.
// ['http:this', 'http://example.org/base/uri', 'http:this'],
// @isaacs Added
["http:this", "http://example.org/base/uri", "http://example.org/base/this"],
["http:this", "http:base", "http:this"],
[".//g", "f:/a", "f://g"],
["b/c//d/e", "f://example.org/base/a", "f://example.org/base/b/c//d/e"],
[
"m2@example.ord/c2@example.org",
"mid:m@example.ord/c@example.org",
"mid:m@example.ord/m2@example.ord/c2@example.org",
],
[
"mini1.xml",
"file:///C:/DEV/Haskell/lib/HXmlToolbox-3.01/examples/",
"file:///C:/DEV/Haskell/lib/HXmlToolbox-3.01/examples/mini1.xml",
],
["../b/c", "foo:a/y/z", "foo:a/b/c"],
// changeing auth
["http://diff:auth@www.example.com", "http://asdf:qwer@www.example.com", "http://diff:auth@www.example.com/"],
// TODO: Support this.
//
// changing port
//["https://example.com:81/", "https://example.com:82/", "https://example.com:81/"],
// TODO: Support these.
//
// https://github.com/nodejs/node/issues/1435
// ["https://another.host.com/", "https://user:password@example.org/", "https://another.host.com/"],
["//another.host.com/", "https://user:password@example.org/", "https://another.host.com/"],
["http://another.host.com/", "https://user:password@example.org/", "http://another.host.com/"],
// TODO: Support this.
//
// ["mailto:another.host.com", "mailto:user@example.org", "mailto:another.host.com"],
["https://example.com/foo", "https://user:password@example.com", "https://user:password@example.com/foo"],
// No path at all
["#hash1", "#hash2", "#hash1"],
];
test("relative paths", () => {
for (let i = 0; i < relativeTests2.length; i++) {
const relativeTest = relativeTests2[i];
const a = url.resolve(relativeTest[1], relativeTest[0]);
const e = url.format(relativeTest[2]);
assert.strictEqual(a, e, `resolve(${relativeTest[0]}, ${relativeTest[1]})` + ` == ${e}\n actual=${a}`);
}
});
test("special case relative paths", () => {
// format: [to, from, result]
// the test: ['.//g', 'f:/a', 'f://g'] is a fundamental problem
// url.parse('f:/a') does not have a host
// url.resolve('f:/a', './/g') does not have a host because you have moved
// down to the g directory. i.e. f: //g, however when this url is parsed
// f:// will indicate that the host is g which is not the case.
// it is unclear to me how to keep this information from being lost
// it may be that a pathname of ////g should collapse to /g but this seems
// to be a lot of work for an edge case. Right now I remove the test
if (relativeTests2[181][0] === ".//g" && relativeTests2[181][1] === "f:/a" && relativeTests2[181][2] === "f://g") {
relativeTests2.splice(181, 1);
}
for (let i = 0; i < relativeTests2.length; i++) {
const relativeTest = relativeTests2[i];
let actual = url.resolveObject(url.parse(relativeTest[1]), relativeTest[0]);
let expected = url.parse(relativeTest[2]);
assert.deepStrictEqual(actual, expected, `expected ${inspect(expected)} but got ${inspect(actual)}`);
expected = url.format(relativeTest[2]);
actual = url.format(actual);
assert.strictEqual(actual, expected, `format(${relativeTest[1]}) == ${expected}\n` + `actual: ${actual}`);
}
});
});

View File

@@ -0,0 +1,19 @@
import { describe, test } from "bun:test";
import assert from "node:assert";
import { URL } from "node:url";
// TODO: Support throwing appropriate error.
describe.todo("URL.revokeObjectURL", () => {
test("invalid input", () => {
// Test ensures that the function receives the url argument.
assert.throws(
() => {
URL.revokeObjectURL();
},
{
code: "ERR_MISSING_ARGS",
name: "TypeError",
},
);
});
});