import { describe, test } from "bun:test"; import { isWindows } from "harness"; import assert from "node:assert"; import url from "node:url"; 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", }); } }); });