diff --git a/browser-test/global-chai.js b/browser-test/global-chai.js new file mode 100644 index 0000000..654bebf --- /dev/null +++ b/browser-test/global-chai.js @@ -0,0 +1,2 @@ +/* global chai */ +export const {expect} = chai; diff --git a/browser-test/index.html b/browser-test/index.html new file mode 100644 index 0000000..fe09726 --- /dev/null +++ b/browser-test/index.html @@ -0,0 +1,21 @@ + + + + + Mocha + + + + +
+ + + + + + + diff --git a/browser-test/tests.spec.js b/browser-test/tests.spec.js new file mode 100644 index 0000000..03aae21 --- /dev/null +++ b/browser-test/tests.spec.js @@ -0,0 +1,492 @@ +(() => { + // browser-test/global-chai.js + var { expect } = chai; + + // index.js + var DATA_URL_DEFAULT_MIME_TYPE = "text/plain"; + var DATA_URL_DEFAULT_CHARSET = "us-ascii"; + var testParameter = (name, filters) => filters.some((filter) => filter instanceof RegExp ? filter.test(name) : filter === name); + var normalizeDataURL = (urlString, { stripHash }) => { + const match = /^data:(?[^,]*?),(?[^#]*?)(?:#(?.*))?$/.exec(urlString); + if (!match) { + throw new Error(`Invalid URL: ${urlString}`); + } + let { type, data, hash } = match.groups; + const mediaType = type.split(";"); + hash = stripHash ? "" : hash; + let isBase64 = false; + if (mediaType[mediaType.length - 1] === "base64") { + mediaType.pop(); + isBase64 = true; + } + const mimeType = (mediaType.shift() || "").toLowerCase(); + const attributes = mediaType.map((attribute) => { + let [key, value = ""] = attribute.split("=").map((string) => string.trim()); + if (key === "charset") { + value = value.toLowerCase(); + if (value === DATA_URL_DEFAULT_CHARSET) { + return ""; + } + } + return `${key}${value ? `=${value}` : ""}`; + }).filter(Boolean); + const normalizedMediaType = [ + ...attributes + ]; + if (isBase64) { + normalizedMediaType.push("base64"); + } + if (normalizedMediaType.length > 0 || mimeType && mimeType !== DATA_URL_DEFAULT_MIME_TYPE) { + normalizedMediaType.unshift(mimeType); + } + return `data:${normalizedMediaType.join(";")},${isBase64 ? data.trim() : data}${hash ? `#${hash}` : ""}`; + }; + function normalizeUrl(urlString, options) { + options = { + defaultProtocol: "http:", + normalizeProtocol: true, + forceHttp: false, + forceHttps: false, + stripAuthentication: true, + stripHash: false, + stripTextFragment: true, + stripWWW: true, + removeQueryParameters: [/^utm_\w+/i], + removeTrailingSlash: true, + removeSingleSlash: true, + removeDirectoryIndex: false, + sortQueryParameters: true, + ...options + }; + urlString = urlString.trim(); + if (/^data:/i.test(urlString)) { + return normalizeDataURL(urlString, options); + } + if (/^view-source:/i.test(urlString)) { + throw new Error("`view-source:` is not supported as it is a non-standard protocol"); + } + const hasRelativeProtocol = urlString.startsWith("//"); + const isRelativeUrl = !hasRelativeProtocol && /^\.*\//.test(urlString); + if (!isRelativeUrl) { + urlString = urlString.replace(/^(?!(?:\w+:)?\/\/)|^\/\//, options.defaultProtocol); + } + const urlObject = new URL(urlString); + if (options.forceHttp && options.forceHttps) { + throw new Error("The `forceHttp` and `forceHttps` options cannot be used together"); + } + if (options.forceHttp && urlObject.protocol === "https:") { + urlObject.protocol = "http:"; + } + if (options.forceHttps && urlObject.protocol === "http:") { + urlObject.protocol = "https:"; + } + if (options.stripAuthentication) { + urlObject.username = ""; + urlObject.password = ""; + } + if (options.stripHash) { + urlObject.hash = ""; + } else if (options.stripTextFragment) { + urlObject.hash = urlObject.hash.replace(/#?:~:text.*?$/i, ""); + } + if (urlObject.pathname) { + try { + urlObject.pathname = urlObject.pathname.replace(new RegExp("(? { + if (/^(?!\/)/g.test(p1)) { + return `${p1}/`; + } + return "/"; + }); + } + } + if (urlObject.pathname) { + try { + urlObject.pathname = decodeURI(urlObject.pathname); + } catch { + } + } + if (options.removeDirectoryIndex === true) { + options.removeDirectoryIndex = [/^index\.[a-z]+$/]; + } + if (Array.isArray(options.removeDirectoryIndex) && options.removeDirectoryIndex.length > 0) { + let pathComponents = urlObject.pathname.split("/"); + const lastComponent = pathComponents[pathComponents.length - 1]; + if (testParameter(lastComponent, options.removeDirectoryIndex)) { + pathComponents = pathComponents.slice(0, -1); + urlObject.pathname = pathComponents.slice(1).join("/") + "/"; + } + } + if (urlObject.hostname) { + urlObject.hostname = urlObject.hostname.replace(/\.$/, ""); + if (options.stripWWW && /^www\.(?!www\.)[a-z\-\d]{1,63}\.[a-z.\-\d]{2,63}$/.test(urlObject.hostname)) { + urlObject.hostname = urlObject.hostname.replace(/^www\./, ""); + } + } + if (Array.isArray(options.removeQueryParameters)) { + for (const key of [...urlObject.searchParams.keys()]) { + if (testParameter(key, options.removeQueryParameters)) { + urlObject.searchParams.delete(key); + } + } + } + if (options.removeQueryParameters === true) { + urlObject.search = ""; + } + if (options.sortQueryParameters) { + urlObject.searchParams.sort(); + } + if (options.removeTrailingSlash) { + urlObject.pathname = urlObject.pathname.replace(/\/$/, ""); + } + const oldUrlString = urlString; + urlString = urlObject.toString(); + if (!options.removeSingleSlash && urlObject.pathname === "/" && !oldUrlString.endsWith("/") && urlObject.hash === "") { + urlString = urlString.replace(/\/$/, ""); + } + if ((options.removeTrailingSlash || urlObject.pathname === "/") && urlObject.hash === "" && options.removeSingleSlash) { + urlString = urlString.replace(/\/$/, ""); + } + if (hasRelativeProtocol && !options.normalizeProtocol) { + urlString = urlString.replace(/^http:\/\//, "//"); + } + if (options.stripProtocol) { + urlString = urlString.replace(/^(?:https?:)?\/\//, ""); + } + return urlString; + } + + // test.js + it("main", async () => { + expect(normalizeUrl("sindresorhus.com")).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("sindresorhus.com ")).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("sindresorhus.com.")).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("SindreSorhus.com")).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("sindresorhus.com", { defaultProtocol: "https:" })).to.deep.equal("https://sindresorhus.com"); + expect(normalizeUrl("HTTP://sindresorhus.com")).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("//sindresorhus.com")).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com")).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com:80")).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("https://sindresorhus.com:443")).to.deep.equal("https://sindresorhus.com"); + expect(normalizeUrl("ftp://sindresorhus.com:21")).to.deep.equal("ftp://sindresorhus.com"); + expect(normalizeUrl("http://www.sindresorhus.com")).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("www.com")).to.deep.equal("http://www.com"); + expect(normalizeUrl("http://www.www.sindresorhus.com")).to.deep.equal("http://www.www.sindresorhus.com"); + expect(normalizeUrl("www.sindresorhus.com")).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com/foo/")).to.deep.equal("http://sindresorhus.com/foo"); + expect(normalizeUrl("sindresorhus.com/?foo=bar baz")).to.deep.equal("http://sindresorhus.com/?foo=bar+baz"); + expect(normalizeUrl("https://foo.com/https://bar.com")).to.deep.equal("https://foo.com/https://bar.com"); + expect(normalizeUrl("https://foo.com/https://bar.com/foo//bar")).to.deep.equal("https://foo.com/https://bar.com/foo/bar"); + expect(normalizeUrl("https://foo.com/http://bar.com")).to.deep.equal("https://foo.com/http://bar.com"); + expect(normalizeUrl("https://foo.com/http://bar.com/foo//bar")).to.deep.equal("https://foo.com/http://bar.com/foo/bar"); + expect(normalizeUrl("http://sindresorhus.com/%7Efoo/")).to.deep.equal("http://sindresorhus.com/~foo", "decode URI octets"); + expect(normalizeUrl("https://foo.com/%FAIL%/07/94/ca/55.jpg")).to.deep.equal("https://foo.com/%FAIL%/07/94/ca/55.jpg"); + expect(normalizeUrl("http://sindresorhus.com/?")).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("\xEAxample.com")).to.deep.equal("http://xn--xample-hva.com"); + expect(normalizeUrl("http://sindresorhus.com/?b=bar&a=foo")).to.deep.equal("http://sindresorhus.com/?a=foo&b=bar"); + expect(normalizeUrl('http://sindresorhus.com/?foo=bar*|<>:"')).to.deep.equal("http://sindresorhus.com/?foo=bar*%7C%3C%3E%3A%22"); + expect(normalizeUrl("http://sindresorhus.com:5000")).to.deep.equal("http://sindresorhus.com:5000"); + expect(normalizeUrl("//sindresorhus.com/", { normalizeProtocol: false })).to.deep.equal("//sindresorhus.com"); + expect(normalizeUrl("//sindresorhus.com:80/", { normalizeProtocol: false })).to.deep.equal("//sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com/foo#bar")).to.deep.equal("http://sindresorhus.com/foo#bar"); + expect(normalizeUrl("http://sindresorhus.com/foo#bar", { stripHash: true })).to.deep.equal("http://sindresorhus.com/foo"); + expect(normalizeUrl("http://sindresorhus.com/foo#bar:~:text=hello%20world", { stripHash: true })).to.deep.equal("http://sindresorhus.com/foo"); + expect(normalizeUrl("http://sindresorhus.com/foo/bar/../baz")).to.deep.equal("http://sindresorhus.com/foo/baz"); + expect(normalizeUrl("http://sindresorhus.com/foo/bar/./baz")).to.deep.equal("http://sindresorhus.com/foo/bar/baz"); + expect(normalizeUrl("https://i.vimeocdn.com/filter/overlay?src0=https://i.vimeocdn.com/video/598160082_1280x720.jpg&src1=https://f.vimeocdn.com/images_v6/share/play_icon_overlay.png")).to.deep.equal("https://i.vimeocdn.com/filter/overlay?src0=https%3A%2F%2Fi.vimeocdn.com%2Fvideo%2F598160082_1280x720.jpg&src1=https%3A%2F%2Ff.vimeocdn.com%2Fimages_v6%2Fshare%2Fplay_icon_overlay.png"); + }); + it("main: non-special-protocol-schemes", async () => { + expect(normalizeUrl("sindre://www.sorhus.com")).to.deep.equal("sindre://sorhus.com"); + expect(normalizeUrl("sindre://www.sorhus.com/")).to.deep.equal("sindre://sorhus.com"); + expect(normalizeUrl("sindre://www.sorhus.com/foo/bar")).to.deep.equal("sindre://sorhus.com/foo/bar"); + }); + it("stripAuthentication option", async () => { + expect(normalizeUrl("http://user:password@www.sindresorhus.com")).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("https://user:password@www.sindresorhus.com")).to.deep.equal("https://sindresorhus.com"); + expect(normalizeUrl("https://user:password@www.sindresorhus.com/@user")).to.deep.equal("https://sindresorhus.com/@user"); + expect(normalizeUrl("user:password@sindresorhus.com")).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("http://user:password@www.\xEAxample.com")).to.deep.equal("http://xn--xample-hva.com"); + const options = { stripAuthentication: false }; + expect(normalizeUrl("http://user:password@www.sindresorhus.com", options)).to.deep.equal("http://user:password@sindresorhus.com"); + expect(normalizeUrl("https://user:password@www.sindresorhus.com", options)).to.deep.equal("https://user:password@sindresorhus.com"); + expect(normalizeUrl("https://user:password@www.sindresorhus.com/@user", options)).to.deep.equal("https://user:password@sindresorhus.com/@user"); + expect(normalizeUrl("user:password@sindresorhus.com", options)).to.deep.equal("http://user:password@sindresorhus.com"); + expect(normalizeUrl("http://user:password@www.\xEAxample.com", options)).to.deep.equal("http://user:password@xn--xample-hva.com"); + }); + it("stripAuthentication option: non-special-protocol-schemes", async () => { + expect(normalizeUrl("sindre://user:password@www.sorhus.com")).to.deep.equal("sindre://sorhus.com"); + const options = { stripAuthentication: false }; + expect(normalizeUrl("sindre://user:password@www.sorhus.com", options)).to.deep.equal("sindre://user:password@sorhus.com"); + }); + it("stripProtocol option", async () => { + const options = { stripProtocol: true }; + expect(normalizeUrl("http://www.sindresorhus.com", options)).to.deep.equal("sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com", options)).to.deep.equal("sindresorhus.com"); + expect(normalizeUrl("https://www.sindresorhus.com", options)).to.deep.equal("sindresorhus.com"); + expect(normalizeUrl("//www.sindresorhus.com", options)).to.deep.equal("sindresorhus.com"); + }); + it("stripProtocol option: non-special-protocol-schemes", async () => { + const options = { stripProtocol: true }; + expect(normalizeUrl("sindre://user:password@www.sorhus.com", options)).to.deep.equal("sindre://sorhus.com"); + expect(normalizeUrl("sindre://www.sorhus.com", options)).to.deep.equal("sindre://sorhus.com"); + }); + it("stripTextFragment option", async () => { + expect(normalizeUrl("http://sindresorhus.com")).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com/about#")).to.deep.equal("http://sindresorhus.com/about"); + expect(normalizeUrl("http://sindresorhus.com/about#:~:text=hello")).to.deep.equal("http://sindresorhus.com/about"); + expect(normalizeUrl("http://sindresorhus.com/about#main")).to.deep.equal("http://sindresorhus.com/about#main"); + expect(normalizeUrl("http://sindresorhus.com/about#main:~:text=hello")).to.deep.equal("http://sindresorhus.com/about#main"); + expect(normalizeUrl("http://sindresorhus.com/about#main:~:text=hello%20world")).to.deep.equal("http://sindresorhus.com/about#main"); + const options = { stripTextFragment: false }; + expect(normalizeUrl("http://sindresorhus.com", options)).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com/about#:~:text=hello", options)).to.deep.equal("http://sindresorhus.com/about#:~:text=hello"); + expect(normalizeUrl("http://sindresorhus.com/about#main", options)).to.deep.equal("http://sindresorhus.com/about#main"); + expect(normalizeUrl("http://sindresorhus.com/about#main:~:text=hello", options)).to.deep.equal("http://sindresorhus.com/about#main:~:text=hello"); + expect(normalizeUrl("http://sindresorhus.com/about#main:~:text=hello%20world", options)).to.deep.equal("http://sindresorhus.com/about#main:~:text=hello%20world"); + const options2 = { stripHash: true, stripTextFragment: false }; + expect(normalizeUrl("http://sindresorhus.com", options2)).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com/about#:~:text=hello", options2)).to.deep.equal("http://sindresorhus.com/about"); + expect(normalizeUrl("http://sindresorhus.com/about#main", options2)).to.deep.equal("http://sindresorhus.com/about"); + expect(normalizeUrl("http://sindresorhus.com/about#main:~:text=hello", options2)).to.deep.equal("http://sindresorhus.com/about"); + expect(normalizeUrl("http://sindresorhus.com/about#main:~:text=hello%20world", options2)).to.deep.equal("http://sindresorhus.com/about"); + }); + it("stripWWW option", async () => { + const options = { stripWWW: false }; + expect(normalizeUrl("http://www.sindresorhus.com", options)).to.deep.equal("http://www.sindresorhus.com"); + expect(normalizeUrl("www.sindresorhus.com", options)).to.deep.equal("http://www.sindresorhus.com"); + expect(normalizeUrl("http://www.\xEAxample.com", options)).to.deep.equal("http://www.xn--xample-hva.com"); + const options2 = { stripWWW: true }; + expect(normalizeUrl("http://www.vue.amsterdam", options2)).to.deep.equal("http://vue.amsterdam"); + expect(normalizeUrl("http://www.sorhus.xx--bck1b9a5dre4c", options2)).to.deep.equal("http://sorhus.xx--bck1b9a5dre4c"); + const tooLongTLDURL = "http://www.sorhus." + "".padEnd(64, "a"); + expect(normalizeUrl(tooLongTLDURL, options2)).to.deep.equal(tooLongTLDURL); + }); + it("stripWWW option: non-special-protocol-schemes", async () => { + const options = { stripWWW: false }; + expect(normalizeUrl("sindre://www.sorhus.com", options)).to.deep.equal("sindre://www.sorhus.com"); + }); + it("removeQueryParameters option", async () => { + const options = { + stripWWW: false, + removeQueryParameters: [/^utm_\w+/i, "ref"] + }; + expect(normalizeUrl("www.sindresorhus.com?foo=bar&utm_medium=test")).to.deep.equal("http://sindresorhus.com/?foo=bar"); + expect(normalizeUrl("http://www.sindresorhus.com", options)).to.deep.equal("http://www.sindresorhus.com"); + expect(normalizeUrl("www.sindresorhus.com?foo=bar", options)).to.deep.equal("http://www.sindresorhus.com/?foo=bar"); + expect(normalizeUrl("www.sindresorhus.com?foo=bar&utm_medium=test&ref=test_ref", options)).to.deep.equal("http://www.sindresorhus.com/?foo=bar"); + }); + it("removeQueryParameters boolean `true` option", async () => { + const options = { + stripWWW: false, + removeQueryParameters: true + }; + expect(normalizeUrl("http://www.sindresorhus.com", options)).to.deep.equal("http://www.sindresorhus.com"); + expect(normalizeUrl("www.sindresorhus.com?foo=bar", options)).to.deep.equal("http://www.sindresorhus.com"); + expect(normalizeUrl("www.sindresorhus.com?foo=bar&utm_medium=test&ref=test_ref", options)).to.deep.equal("http://www.sindresorhus.com"); + }); + it("removeQueryParameters boolean `false` option", async () => { + const options = { + stripWWW: false, + removeQueryParameters: false + }; + expect(normalizeUrl("http://www.sindresorhus.com", options)).to.deep.equal("http://www.sindresorhus.com"); + expect(normalizeUrl("www.sindresorhus.com?foo=bar", options)).to.deep.equal("http://www.sindresorhus.com/?foo=bar"); + expect(normalizeUrl("www.sindresorhus.com?foo=bar&utm_medium=test&ref=test_ref", options)).to.deep.equal("http://www.sindresorhus.com/?foo=bar&ref=test_ref&utm_medium=test"); + }); + it("forceHttp option", async () => { + const options = { forceHttp: true }; + expect(normalizeUrl("https://sindresorhus.com")).to.deep.equal("https://sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com", options)).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("https://www.sindresorhus.com", options)).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("//sindresorhus.com", options)).to.deep.equal("http://sindresorhus.com"); + }); + it("forceHttp option with forceHttps", async () => { + expect(() => { + normalizeUrl("https://www.sindresorhus.com", { forceHttp: true, forceHttps: true }); + }).to.throw("The `forceHttp` and `forceHttps` options cannot be used together"); + }); + it("forceHttps option", async () => { + const options = { forceHttps: true }; + expect(normalizeUrl("https://sindresorhus.com")).to.deep.equal("https://sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com", options)).to.deep.equal("https://sindresorhus.com"); + expect(normalizeUrl("https://www.sindresorhus.com", options)).to.deep.equal("https://sindresorhus.com"); + expect(normalizeUrl("//sindresorhus.com", options)).to.deep.equal("https://sindresorhus.com"); + }); + it("removeTrailingSlash option", async () => { + const options = { removeTrailingSlash: false }; + expect(normalizeUrl("http://sindresorhus.com")).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com/")).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com", options)).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com/", options)).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com/redirect")).to.deep.equal("http://sindresorhus.com/redirect"); + expect(normalizeUrl("http://sindresorhus.com/redirect/")).to.deep.equal("http://sindresorhus.com/redirect"); + expect(normalizeUrl("http://sindresorhus.com/redirect/", options)).to.deep.equal("http://sindresorhus.com/redirect/"); + expect(normalizeUrl("http://sindresorhus.com/redirect/", options)).to.deep.equal("http://sindresorhus.com/redirect/"); + expect(normalizeUrl("http://sindresorhus.com/#/")).to.deep.equal("http://sindresorhus.com/#/"); + expect(normalizeUrl("http://sindresorhus.com/#/", options)).to.deep.equal("http://sindresorhus.com/#/"); + expect(normalizeUrl("http://sindresorhus.com/?unicorns=true")).to.deep.equal("http://sindresorhus.com/?unicorns=true"); + expect(normalizeUrl("http://sindresorhus.com/?unicorns=true", options)).to.deep.equal("http://sindresorhus.com/?unicorns=true"); + }); + it("removeSingleSlash option", async () => { + const options = { removeSingleSlash: false }; + expect(normalizeUrl("https://sindresorhus.com", options)).to.deep.equal("https://sindresorhus.com"); + expect(normalizeUrl("https://sindresorhus.com/", options)).to.deep.equal("https://sindresorhus.com/"); + expect(normalizeUrl("https://sindresorhus.com/redirect", options)).to.deep.equal("https://sindresorhus.com/redirect"); + expect(normalizeUrl("https://sindresorhus.com/redirect/", options)).to.deep.equal("https://sindresorhus.com/redirect"); + expect(normalizeUrl("https://sindresorhus.com/#/", options)).to.deep.equal("https://sindresorhus.com/#/"); + expect(normalizeUrl("https://sindresorhus.com/?unicorns=true", options)).to.deep.equal("https://sindresorhus.com/?unicorns=true"); + }); + it("removeSingleSlash option combined with removeTrailingSlash option", async () => { + const options = { removeTrailingSlash: false, removeSingleSlash: false }; + expect(normalizeUrl("https://sindresorhus.com", options)).to.deep.equal("https://sindresorhus.com"); + expect(normalizeUrl("https://sindresorhus.com/", options)).to.deep.equal("https://sindresorhus.com/"); + expect(normalizeUrl("https://sindresorhus.com/redirect", options)).to.deep.equal("https://sindresorhus.com/redirect"); + expect(normalizeUrl("https://sindresorhus.com/redirect/", options)).to.deep.equal("https://sindresorhus.com/redirect/"); + expect(normalizeUrl("https://sindresorhus.com/#/", options)).to.deep.equal("https://sindresorhus.com/#/"); + expect(normalizeUrl("https://sindresorhus.com/?unicorns=true", options)).to.deep.equal("https://sindresorhus.com/?unicorns=true"); + }); + it("removeDirectoryIndex option", async () => { + const options1 = { removeDirectoryIndex: ["index.html", "index.php"] }; + expect(normalizeUrl("http://sindresorhus.com/index.html")).to.deep.equal("http://sindresorhus.com/index.html"); + expect(normalizeUrl("http://sindresorhus.com/index.html", options1)).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com/index.htm", options1)).to.deep.equal("http://sindresorhus.com/index.htm"); + expect(normalizeUrl("http://sindresorhus.com/index.php", options1)).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com/path/index.html")).to.deep.equal("http://sindresorhus.com/path/index.html"); + expect(normalizeUrl("http://sindresorhus.com/path/index.html", options1)).to.deep.equal("http://sindresorhus.com/path"); + expect(normalizeUrl("http://sindresorhus.com/path/index.htm", options1)).to.deep.equal("http://sindresorhus.com/path/index.htm"); + expect(normalizeUrl("http://sindresorhus.com/path/index.php", options1)).to.deep.equal("http://sindresorhus.com/path"); + expect(normalizeUrl("http://sindresorhus.com/foo/bar/index.html", options1)).to.deep.equal("http://sindresorhus.com/foo/bar"); + const options2 = { removeDirectoryIndex: [/^index\.[a-z]+$/, "remove.html"] }; + expect(normalizeUrl("http://sindresorhus.com/index.html")).to.deep.equal("http://sindresorhus.com/index.html"); + expect(normalizeUrl("http://sindresorhus.com/index.html", options2)).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com/index/index.html", options2)).to.deep.equal("http://sindresorhus.com/index"); + expect(normalizeUrl("http://sindresorhus.com/remove.html", options2)).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com/default.htm", options2)).to.deep.equal("http://sindresorhus.com/default.htm"); + expect(normalizeUrl("http://sindresorhus.com/index.php", options2)).to.deep.equal("http://sindresorhus.com"); + const options3 = { removeDirectoryIndex: true }; + expect(normalizeUrl("http://sindresorhus.com/index.html")).to.deep.equal("http://sindresorhus.com/index.html"); + expect(normalizeUrl("http://sindresorhus.com/index.html", options3)).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com/index.htm", options3)).to.deep.equal("http://sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com/index.php", options3)).to.deep.equal("http://sindresorhus.com"); + }); + it("removeTrailingSlash and removeDirectoryIndex options)", async () => { + const options1 = { + removeTrailingSlash: true, + removeDirectoryIndex: true + }; + expect(normalizeUrl("http://sindresorhus.com/path/", options1)).to.deep.equal("http://sindresorhus.com/path"); + expect(normalizeUrl("http://sindresorhus.com/path/index.html", options1)).to.deep.equal("http://sindresorhus.com/path"); + expect(normalizeUrl("http://sindresorhus.com/#/path/", options1)).to.deep.equal("http://sindresorhus.com/#/path/"); + expect(normalizeUrl("http://sindresorhus.com/foo/#/bar/", options1)).to.deep.equal("http://sindresorhus.com/foo#/bar/"); + const options2 = { + removeTrailingSlash: false, + removeDirectoryIndex: true + }; + expect(normalizeUrl("http://sindresorhus.com/path/", options2)).to.deep.equal("http://sindresorhus.com/path/"); + expect(normalizeUrl("http://sindresorhus.com/path/index.html", options2)).to.deep.equal("http://sindresorhus.com/path/"); + expect(normalizeUrl("http://sindresorhus.com/#/path/", options2)).to.deep.equal("http://sindresorhus.com/#/path/"); + }); + it("sortQueryParameters option", async () => { + const options1 = { + sortQueryParameters: true + }; + expect(normalizeUrl("http://sindresorhus.com/?a=Z&b=Y&c=X&d=W", options1)).to.deep.equal("http://sindresorhus.com/?a=Z&b=Y&c=X&d=W"); + expect(normalizeUrl("http://sindresorhus.com/?b=Y&c=X&a=Z&d=W", options1)).to.deep.equal("http://sindresorhus.com/?a=Z&b=Y&c=X&d=W"); + expect(normalizeUrl("http://sindresorhus.com/?a=Z&d=W&b=Y&c=X", options1)).to.deep.equal("http://sindresorhus.com/?a=Z&b=Y&c=X&d=W"); + expect(normalizeUrl("http://sindresorhus.com/", options1)).to.deep.equal("http://sindresorhus.com"); + const options2 = { + sortQueryParameters: false + }; + expect(normalizeUrl("http://sindresorhus.com/?a=Z&b=Y&c=X&d=W", options2)).to.deep.equal("http://sindresorhus.com/?a=Z&b=Y&c=X&d=W"); + expect(normalizeUrl("http://sindresorhus.com/?b=Y&c=X&a=Z&d=W", options2)).to.deep.equal("http://sindresorhus.com/?b=Y&c=X&a=Z&d=W"); + expect(normalizeUrl("http://sindresorhus.com/?a=Z&d=W&b=Y&c=X", options2)).to.deep.equal("http://sindresorhus.com/?a=Z&d=W&b=Y&c=X"); + expect(normalizeUrl("http://sindresorhus.com/", options2)).to.deep.equal("http://sindresorhus.com"); + }); + it("invalid urls", async () => { + const INVALID_URL_REGEXP = /^Invalid URL: .*$|^Failed to construct 'URL': Invalid URL$|^URL constructor: .* is not a valid URL\.$/; + expect(() => { + normalizeUrl("http://"); + }).to.throw(INVALID_URL_REGEXP); + expect(() => { + normalizeUrl("/"); + }).to.throw(INVALID_URL_REGEXP); + expect(() => { + normalizeUrl("/relative/path/"); + }).to.throw(INVALID_URL_REGEXP); + }); + it("remove duplicate pathname slashes", async () => { + expect(normalizeUrl("http://sindresorhus.com////foo/bar")).to.deep.equal("http://sindresorhus.com/foo/bar"); + expect(normalizeUrl("http://sindresorhus.com////foo////bar")).to.deep.equal("http://sindresorhus.com/foo/bar"); + expect(normalizeUrl("//sindresorhus.com//foo", { normalizeProtocol: false })).to.deep.equal("//sindresorhus.com/foo"); + expect(normalizeUrl("http://sindresorhus.com:5000///foo")).to.deep.equal("http://sindresorhus.com:5000/foo"); + expect(normalizeUrl("http://sindresorhus.com///foo")).to.deep.equal("http://sindresorhus.com/foo"); + expect(normalizeUrl("http://sindresorhus.com:5000//foo")).to.deep.equal("http://sindresorhus.com:5000/foo"); + expect(normalizeUrl("http://sindresorhus.com//foo")).to.deep.equal("http://sindresorhus.com/foo"); + expect(normalizeUrl("http://sindresorhus.com/s3://sindresorhus.com")).to.deep.equal("http://sindresorhus.com/s3://sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com/s3://sindresorhus.com//foo")).to.deep.equal("http://sindresorhus.com/s3://sindresorhus.com/foo"); + expect(normalizeUrl("http://sindresorhus.com//foo/s3://sindresorhus.com")).to.deep.equal("http://sindresorhus.com/foo/s3://sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com/git://sindresorhus.com")).to.deep.equal("http://sindresorhus.com/git://sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com/git://sindresorhus.com//foo")).to.deep.equal("http://sindresorhus.com/git://sindresorhus.com/foo"); + expect(normalizeUrl("http://sindresorhus.com//foo/git://sindresorhus.com//foo")).to.deep.equal("http://sindresorhus.com/foo/git://sindresorhus.com/foo"); + expect(normalizeUrl("http://sindresorhus.com/a://sindresorhus.com//foo")).to.deep.equal("http://sindresorhus.com/a:/sindresorhus.com/foo"); + expect(normalizeUrl("http://sindresorhus.com/alongprotocolwithin50charlimitxxxxxxxxxxxxxxxxxxxx://sindresorhus.com//foo")).to.deep.equal("http://sindresorhus.com/alongprotocolwithin50charlimitxxxxxxxxxxxxxxxxxxxx://sindresorhus.com/foo"); + expect(normalizeUrl("http://sindresorhus.com/alongprotocolexceeds50charlimitxxxxxxxxxxxxxxxxxxxxx://sindresorhus.com//foo")).to.deep.equal("http://sindresorhus.com/alongprotocolexceeds50charlimitxxxxxxxxxxxxxxxxxxxxx:/sindresorhus.com/foo"); + expect(normalizeUrl("http://sindresorhus.com/a2-.+://sindresorhus.com")).to.deep.equal("http://sindresorhus.com/a2-.+://sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com/a2-.+_://sindresorhus.com")).to.deep.equal("http://sindresorhus.com/a2-.+_:/sindresorhus.com"); + expect(normalizeUrl("http://sindresorhus.com/2abc://sindresorhus.com")).to.deep.equal("http://sindresorhus.com/2abc:/sindresorhus.com"); + }); + it("data URL", async () => { + expect(() => { + normalizeUrl("data:"); + }).to.throw("Invalid URL: data:"); + expect(normalizeUrl("data:text/plain,foo")).to.deep.equal("data:,foo"); + expect(normalizeUrl("data:;charset=us-ascii,foo")).to.deep.equal("data:,foo"); + expect(normalizeUrl("data:;charset=UTF-8;,foo")).to.deep.equal("data:;charset=utf-8,foo"); + expect(normalizeUrl("data:,")).to.deep.equal("data:,"); + expect(normalizeUrl("data:;charset=utf-8,foo")).to.deep.equal("data:;charset=utf-8,foo"); + expect(normalizeUrl("data:TEXT/HTML,foo")).to.deep.equal("data:text/html,foo"); + expect(normalizeUrl("data:,foo# ")).to.deep.equal("data:,foo"); + expect(normalizeUrl("data:;foo=;bar,")).to.deep.equal("data:;foo;bar,"); + expect(normalizeUrl("data:;charset=UTF-8,foo")).to.deep.equal("data:;charset=utf-8,foo"); + expect(normalizeUrl("data:;base64, Zm9v #foo #bar")).to.deep.equal("data:;base64,Zm9v#foo #bar"); + expect(normalizeUrl("data:, foo #bar")).to.deep.equal("data:, foo #bar"); + const options = { + defaultProtocol: "http:", + normalizeProtocol: true, + forceHttp: true, + stripHash: true, + stripWWW: true, + stripProtocol: true, + removeQueryParameters: [/^utm_\w+/i, "ref"], + sortQueryParameters: true, + removeTrailingSlash: true, + removeDirectoryIndex: true + }; + expect(normalizeUrl("data:,sindresorhus.com/", options)).to.deep.equal("data:,sindresorhus.com/"); + expect(normalizeUrl("data:,sindresorhus.com/index.html", options)).to.deep.equal("data:,sindresorhus.com/index.html"); + expect(normalizeUrl("data:,sindresorhus.com?foo=bar&a=a&utm_medium=test", options)).to.deep.equal("data:,sindresorhus.com?foo=bar&a=a&utm_medium=test"); + expect(normalizeUrl("data:,foo#bar", options)).to.deep.equal("data:,foo"); + expect(normalizeUrl("data:,www.sindresorhus.com", options)).to.deep.equal("data:,www.sindresorhus.com"); + }); + it("prevents homograph attack", async () => { + expect(normalizeUrl("https://eb\u0430y.com")).to.deep.equal("https://xn--eby-7cd.com"); + }); + it("view-source URL", async () => { + expect(() => { + normalizeUrl("view-source:https://www.sindresorhus.com"); + }).to.throw("`view-source:` is not supported as it is a non-standard protocol"); + }); + it("does not have exponential performance for data URLs", async () => { + for (let index = 0; index < 1e3; index += 50) { + const url = "data:" + Array.from({ length: index }).fill(",#").join("") + "\ra"; + const start = Date.now(); + try { + normalizeUrl(url); + } catch { + } + const difference = Date.now() - start; + expect(difference < 100, `Execution time: ${difference}`).to.deep.equal(true); + } + }); +})(); diff --git a/package.json b/package.json index 3c5542b..63397f9 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,15 @@ "engines": { "node": ">=12.20" }, + "browser": { + "chai": "browser-test/global-chai.js" + }, "scripts": { - "test": "xo && c8 ava && tsd" + "test": "npm run build && xo && c8 mocha && tsd && npm run browser:help", + "build": "esbuild test.js --bundle --outfile=browser-test/tests.spec.js", + "style:fix": "xo --fix index.js test.js", + "browser:help": "echo 'For real browser test, call\n npm run browser:test'", + "browser:test": "npx -y http-server -o browser-test" }, "files": [ "index.js", @@ -40,6 +47,9 @@ "devDependencies": { "ava": "^3.15.0", "c8": "^7.7.3", + "chai": "^4.3.4", + "esbuild": "0.12.18", + "mocha": "^9.0.3", "tsd": "^0.17.0", "typescript": "^4.3.5", "xo": "^0.41.0" @@ -49,5 +59,10 @@ "text", "lcov" ] + }, + "xo": { + "ignores": [ + "browser-test/tests.spec.js" + ] } } diff --git a/readme.md b/readme.md index f99f4f4..14a2960 100644 --- a/readme.md +++ b/readme.md @@ -12,6 +12,8 @@ $ npm install normalize-url *If you need to use this in the browser, use version 4: `npm i normalize-url@4`* +*If you want to test version >= 7.0.0 in real browser, visit [browser-test/index.html](browser-test/index.html)* + ## Usage ```js diff --git a/test.js b/test.js index 149b51a..22ca6e2 100644 --- a/test.js +++ b/test.js @@ -1,352 +1,368 @@ -import test from 'ava'; +/* global it */ +import {expect} from 'chai'; import normalizeUrl from './index.js'; -test('main', t => { - t.is(normalizeUrl('sindresorhus.com'), 'http://sindresorhus.com'); - t.is(normalizeUrl('sindresorhus.com '), 'http://sindresorhus.com'); - t.is(normalizeUrl('sindresorhus.com.'), 'http://sindresorhus.com'); - t.is(normalizeUrl('SindreSorhus.com'), 'http://sindresorhus.com'); - t.is(normalizeUrl('sindresorhus.com', {defaultProtocol: 'https:'}), 'https://sindresorhus.com'); - t.is(normalizeUrl('HTTP://sindresorhus.com'), 'http://sindresorhus.com'); - t.is(normalizeUrl('//sindresorhus.com'), 'http://sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com'), 'http://sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com:80'), 'http://sindresorhus.com'); - t.is(normalizeUrl('https://sindresorhus.com:443'), 'https://sindresorhus.com'); - t.is(normalizeUrl('ftp://sindresorhus.com:21'), 'ftp://sindresorhus.com'); - t.is(normalizeUrl('http://www.sindresorhus.com'), 'http://sindresorhus.com'); - t.is(normalizeUrl('www.com'), 'http://www.com'); - t.is(normalizeUrl('http://www.www.sindresorhus.com'), 'http://www.www.sindresorhus.com'); - t.is(normalizeUrl('www.sindresorhus.com'), 'http://sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com/foo/'), 'http://sindresorhus.com/foo'); - t.is(normalizeUrl('sindresorhus.com/?foo=bar baz'), 'http://sindresorhus.com/?foo=bar+baz'); - t.is(normalizeUrl('https://foo.com/https://bar.com'), 'https://foo.com/https://bar.com'); - t.is(normalizeUrl('https://foo.com/https://bar.com/foo//bar'), 'https://foo.com/https://bar.com/foo/bar'); - t.is(normalizeUrl('https://foo.com/http://bar.com'), 'https://foo.com/http://bar.com'); - t.is(normalizeUrl('https://foo.com/http://bar.com/foo//bar'), 'https://foo.com/http://bar.com/foo/bar'); - t.is(normalizeUrl('http://sindresorhus.com/%7Efoo/'), 'http://sindresorhus.com/~foo', 'decode URI octets'); - t.is(normalizeUrl('https://foo.com/%FAIL%/07/94/ca/55.jpg'), 'https://foo.com/%FAIL%/07/94/ca/55.jpg'); - t.is(normalizeUrl('http://sindresorhus.com/?'), 'http://sindresorhus.com'); - t.is(normalizeUrl('êxample.com'), 'http://xn--xample-hva.com'); - t.is(normalizeUrl('http://sindresorhus.com/?b=bar&a=foo'), 'http://sindresorhus.com/?a=foo&b=bar'); - t.is(normalizeUrl('http://sindresorhus.com/?foo=bar*|<>:"'), 'http://sindresorhus.com/?foo=bar*%7C%3C%3E%3A%22'); - t.is(normalizeUrl('http://sindresorhus.com:5000'), 'http://sindresorhus.com:5000'); - t.is(normalizeUrl('//sindresorhus.com/', {normalizeProtocol: false}), '//sindresorhus.com'); - t.is(normalizeUrl('//sindresorhus.com:80/', {normalizeProtocol: false}), '//sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com/foo#bar'), 'http://sindresorhus.com/foo#bar'); - t.is(normalizeUrl('http://sindresorhus.com/foo#bar', {stripHash: true}), 'http://sindresorhus.com/foo'); - t.is(normalizeUrl('http://sindresorhus.com/foo#bar:~:text=hello%20world', {stripHash: true}), 'http://sindresorhus.com/foo'); - t.is(normalizeUrl('http://sindresorhus.com/foo/bar/../baz'), 'http://sindresorhus.com/foo/baz'); - t.is(normalizeUrl('http://sindresorhus.com/foo/bar/./baz'), 'http://sindresorhus.com/foo/bar/baz'); - t.is(normalizeUrl('sindre://www.sorhus.com'), 'sindre://sorhus.com'); - t.is(normalizeUrl('sindre://www.sorhus.com/'), 'sindre://sorhus.com'); - t.is(normalizeUrl('sindre://www.sorhus.com/foo/bar'), 'sindre://sorhus.com/foo/bar'); - t.is(normalizeUrl('https://i.vimeocdn.com/filter/overlay?src0=https://i.vimeocdn.com/video/598160082_1280x720.jpg&src1=https://f.vimeocdn.com/images_v6/share/play_icon_overlay.png'), 'https://i.vimeocdn.com/filter/overlay?src0=https%3A%2F%2Fi.vimeocdn.com%2Fvideo%2F598160082_1280x720.jpg&src1=https%3A%2F%2Ff.vimeocdn.com%2Fimages_v6%2Fshare%2Fplay_icon_overlay.png'); +it('main', async () => { + expect(normalizeUrl('sindresorhus.com')).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('sindresorhus.com ')).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('sindresorhus.com.')).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('SindreSorhus.com')).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('sindresorhus.com', {defaultProtocol: 'https:'})).to.deep.equal('https://sindresorhus.com'); + expect(normalizeUrl('HTTP://sindresorhus.com')).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('//sindresorhus.com')).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com')).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com:80')).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('https://sindresorhus.com:443')).to.deep.equal('https://sindresorhus.com'); + expect(normalizeUrl('ftp://sindresorhus.com:21')).to.deep.equal('ftp://sindresorhus.com'); + expect(normalizeUrl('http://www.sindresorhus.com')).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('www.com')).to.deep.equal('http://www.com'); + expect(normalizeUrl('http://www.www.sindresorhus.com')).to.deep.equal('http://www.www.sindresorhus.com'); + expect(normalizeUrl('www.sindresorhus.com')).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com/foo/')).to.deep.equal('http://sindresorhus.com/foo'); + expect(normalizeUrl('sindresorhus.com/?foo=bar baz')).to.deep.equal('http://sindresorhus.com/?foo=bar+baz'); + expect(normalizeUrl('https://foo.com/https://bar.com')).to.deep.equal('https://foo.com/https://bar.com'); + expect(normalizeUrl('https://foo.com/https://bar.com/foo//bar')).to.deep.equal('https://foo.com/https://bar.com/foo/bar'); + expect(normalizeUrl('https://foo.com/http://bar.com')).to.deep.equal('https://foo.com/http://bar.com'); + expect(normalizeUrl('https://foo.com/http://bar.com/foo//bar')).to.deep.equal('https://foo.com/http://bar.com/foo/bar'); + expect(normalizeUrl('http://sindresorhus.com/%7Efoo/')).to.deep.equal('http://sindresorhus.com/~foo', 'decode URI octets'); + expect(normalizeUrl('https://foo.com/%FAIL%/07/94/ca/55.jpg')).to.deep.equal('https://foo.com/%FAIL%/07/94/ca/55.jpg'); + expect(normalizeUrl('http://sindresorhus.com/?')).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('êxample.com')).to.deep.equal('http://xn--xample-hva.com'); + expect(normalizeUrl('http://sindresorhus.com/?b=bar&a=foo')).to.deep.equal('http://sindresorhus.com/?a=foo&b=bar'); + expect(normalizeUrl('http://sindresorhus.com/?foo=bar*|<>:"')).to.deep.equal('http://sindresorhus.com/?foo=bar*%7C%3C%3E%3A%22'); + expect(normalizeUrl('http://sindresorhus.com:5000')).to.deep.equal('http://sindresorhus.com:5000'); + expect(normalizeUrl('//sindresorhus.com/', {normalizeProtocol: false})).to.deep.equal('//sindresorhus.com'); + expect(normalizeUrl('//sindresorhus.com:80/', {normalizeProtocol: false})).to.deep.equal('//sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com/foo#bar')).to.deep.equal('http://sindresorhus.com/foo#bar'); + expect(normalizeUrl('http://sindresorhus.com/foo#bar', {stripHash: true})).to.deep.equal('http://sindresorhus.com/foo'); + expect(normalizeUrl('http://sindresorhus.com/foo#bar:~:text=hello%20world', {stripHash: true})).to.deep.equal('http://sindresorhus.com/foo'); + expect(normalizeUrl('http://sindresorhus.com/foo/bar/../baz')).to.deep.equal('http://sindresorhus.com/foo/baz'); + expect(normalizeUrl('http://sindresorhus.com/foo/bar/./baz')).to.deep.equal('http://sindresorhus.com/foo/bar/baz'); + expect(normalizeUrl('https://i.vimeocdn.com/filter/overlay?src0=https://i.vimeocdn.com/video/598160082_1280x720.jpg&src1=https://f.vimeocdn.com/images_v6/share/play_icon_overlay.png')).to.deep.equal('https://i.vimeocdn.com/filter/overlay?src0=https%3A%2F%2Fi.vimeocdn.com%2Fvideo%2F598160082_1280x720.jpg&src1=https%3A%2F%2Ff.vimeocdn.com%2Fimages_v6%2Fshare%2Fplay_icon_overlay.png'); }); -test('stripAuthentication option', t => { - t.is(normalizeUrl('http://user:password@www.sindresorhus.com'), 'http://sindresorhus.com'); - t.is(normalizeUrl('https://user:password@www.sindresorhus.com'), 'https://sindresorhus.com'); - t.is(normalizeUrl('https://user:password@www.sindresorhus.com/@user'), 'https://sindresorhus.com/@user'); - t.is(normalizeUrl('user:password@sindresorhus.com'), 'http://sindresorhus.com'); - t.is(normalizeUrl('http://user:password@www.êxample.com'), 'http://xn--xample-hva.com'); - t.is(normalizeUrl('sindre://user:password@www.sorhus.com'), 'sindre://sorhus.com'); +// https://nodejs.org/api/url.html#url_special_schemes +it('main: non-special-protocol-schemes', async () => { + expect(normalizeUrl('sindre://www.sorhus.com')).to.deep.equal('sindre://sorhus.com'); + expect(normalizeUrl('sindre://www.sorhus.com/')).to.deep.equal('sindre://sorhus.com'); + expect(normalizeUrl('sindre://www.sorhus.com/foo/bar')).to.deep.equal('sindre://sorhus.com/foo/bar'); +}); + +it('stripAuthentication option', async () => { + expect(normalizeUrl('http://user:password@www.sindresorhus.com')).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('https://user:password@www.sindresorhus.com')).to.deep.equal('https://sindresorhus.com'); + expect(normalizeUrl('https://user:password@www.sindresorhus.com/@user')).to.deep.equal('https://sindresorhus.com/@user'); + expect(normalizeUrl('user:password@sindresorhus.com')).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('http://user:password@www.êxample.com')).to.deep.equal('http://xn--xample-hva.com'); const options = {stripAuthentication: false}; - t.is(normalizeUrl('http://user:password@www.sindresorhus.com', options), 'http://user:password@sindresorhus.com'); - t.is(normalizeUrl('https://user:password@www.sindresorhus.com', options), 'https://user:password@sindresorhus.com'); - t.is(normalizeUrl('https://user:password@www.sindresorhus.com/@user', options), 'https://user:password@sindresorhus.com/@user'); - t.is(normalizeUrl('user:password@sindresorhus.com', options), 'http://user:password@sindresorhus.com'); - t.is(normalizeUrl('http://user:password@www.êxample.com', options), 'http://user:password@xn--xample-hva.com'); - t.is(normalizeUrl('sindre://user:password@www.sorhus.com', options), 'sindre://user:password@sorhus.com'); + expect(normalizeUrl('http://user:password@www.sindresorhus.com', options)).to.deep.equal('http://user:password@sindresorhus.com'); + expect(normalizeUrl('https://user:password@www.sindresorhus.com', options)).to.deep.equal('https://user:password@sindresorhus.com'); + expect(normalizeUrl('https://user:password@www.sindresorhus.com/@user', options)).to.deep.equal('https://user:password@sindresorhus.com/@user'); + expect(normalizeUrl('user:password@sindresorhus.com', options)).to.deep.equal('http://user:password@sindresorhus.com'); + expect(normalizeUrl('http://user:password@www.êxample.com', options)).to.deep.equal('http://user:password@xn--xample-hva.com'); }); -test('stripProtocol option', t => { +it('stripAuthentication option: non-special-protocol-schemes', async () => { + expect(normalizeUrl('sindre://user:password@www.sorhus.com')).to.deep.equal('sindre://sorhus.com'); + + const options = {stripAuthentication: false}; + expect(normalizeUrl('sindre://user:password@www.sorhus.com', options)).to.deep.equal('sindre://user:password@sorhus.com'); +}); + +it('stripProtocol option', async () => { const options = {stripProtocol: true}; - t.is(normalizeUrl('http://www.sindresorhus.com', options), 'sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com', options), 'sindresorhus.com'); - t.is(normalizeUrl('https://www.sindresorhus.com', options), 'sindresorhus.com'); - t.is(normalizeUrl('//www.sindresorhus.com', options), 'sindresorhus.com'); - t.is(normalizeUrl('sindre://user:password@www.sorhus.com', options), 'sindre://sorhus.com'); - t.is(normalizeUrl('sindre://www.sorhus.com', options), 'sindre://sorhus.com'); + expect(normalizeUrl('http://www.sindresorhus.com', options)).to.deep.equal('sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com', options)).to.deep.equal('sindresorhus.com'); + expect(normalizeUrl('https://www.sindresorhus.com', options)).to.deep.equal('sindresorhus.com'); + expect(normalizeUrl('//www.sindresorhus.com', options)).to.deep.equal('sindresorhus.com'); }); -test('stripTextFragment option', t => { - t.is(normalizeUrl('http://sindresorhus.com'), 'http://sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com/about#'), 'http://sindresorhus.com/about'); - t.is(normalizeUrl('http://sindresorhus.com/about#:~:text=hello'), 'http://sindresorhus.com/about'); - t.is(normalizeUrl('http://sindresorhus.com/about#main'), 'http://sindresorhus.com/about#main'); - t.is(normalizeUrl('http://sindresorhus.com/about#main:~:text=hello'), 'http://sindresorhus.com/about#main'); - t.is(normalizeUrl('http://sindresorhus.com/about#main:~:text=hello%20world'), 'http://sindresorhus.com/about#main'); +it('stripProtocol option: non-special-protocol-schemes', async () => { + const options = {stripProtocol: true}; + expect(normalizeUrl('sindre://user:password@www.sorhus.com', options)).to.deep.equal('sindre://sorhus.com'); + expect(normalizeUrl('sindre://www.sorhus.com', options)).to.deep.equal('sindre://sorhus.com'); +}); + +it('stripTextFragment option', async () => { + expect(normalizeUrl('http://sindresorhus.com')).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com/about#')).to.deep.equal('http://sindresorhus.com/about'); + expect(normalizeUrl('http://sindresorhus.com/about#:~:text=hello')).to.deep.equal('http://sindresorhus.com/about'); + expect(normalizeUrl('http://sindresorhus.com/about#main')).to.deep.equal('http://sindresorhus.com/about#main'); + expect(normalizeUrl('http://sindresorhus.com/about#main:~:text=hello')).to.deep.equal('http://sindresorhus.com/about#main'); + expect(normalizeUrl('http://sindresorhus.com/about#main:~:text=hello%20world')).to.deep.equal('http://sindresorhus.com/about#main'); const options = {stripTextFragment: false}; - t.is(normalizeUrl('http://sindresorhus.com', options), 'http://sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com/about#:~:text=hello', options), 'http://sindresorhus.com/about#:~:text=hello'); - t.is(normalizeUrl('http://sindresorhus.com/about#main', options), 'http://sindresorhus.com/about#main'); - t.is(normalizeUrl('http://sindresorhus.com/about#main:~:text=hello', options), 'http://sindresorhus.com/about#main:~:text=hello'); - t.is(normalizeUrl('http://sindresorhus.com/about#main:~:text=hello%20world', options), 'http://sindresorhus.com/about#main:~:text=hello%20world'); + expect(normalizeUrl('http://sindresorhus.com', options)).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com/about#:~:text=hello', options)).to.deep.equal('http://sindresorhus.com/about#:~:text=hello'); + expect(normalizeUrl('http://sindresorhus.com/about#main', options)).to.deep.equal('http://sindresorhus.com/about#main'); + expect(normalizeUrl('http://sindresorhus.com/about#main:~:text=hello', options)).to.deep.equal('http://sindresorhus.com/about#main:~:text=hello'); + expect(normalizeUrl('http://sindresorhus.com/about#main:~:text=hello%20world', options)).to.deep.equal('http://sindresorhus.com/about#main:~:text=hello%20world'); const options2 = {stripHash: true, stripTextFragment: false}; - t.is(normalizeUrl('http://sindresorhus.com', options2), 'http://sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com/about#:~:text=hello', options2), 'http://sindresorhus.com/about'); - t.is(normalizeUrl('http://sindresorhus.com/about#main', options2), 'http://sindresorhus.com/about'); - t.is(normalizeUrl('http://sindresorhus.com/about#main:~:text=hello', options2), 'http://sindresorhus.com/about'); - t.is(normalizeUrl('http://sindresorhus.com/about#main:~:text=hello%20world', options2), 'http://sindresorhus.com/about'); + expect(normalizeUrl('http://sindresorhus.com', options2)).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com/about#:~:text=hello', options2)).to.deep.equal('http://sindresorhus.com/about'); + expect(normalizeUrl('http://sindresorhus.com/about#main', options2)).to.deep.equal('http://sindresorhus.com/about'); + expect(normalizeUrl('http://sindresorhus.com/about#main:~:text=hello', options2)).to.deep.equal('http://sindresorhus.com/about'); + expect(normalizeUrl('http://sindresorhus.com/about#main:~:text=hello%20world', options2)).to.deep.equal('http://sindresorhus.com/about'); }); -test('stripWWW option', t => { +it('stripWWW option', async () => { const options = {stripWWW: false}; - t.is(normalizeUrl('http://www.sindresorhus.com', options), 'http://www.sindresorhus.com'); - t.is(normalizeUrl('www.sindresorhus.com', options), 'http://www.sindresorhus.com'); - t.is(normalizeUrl('http://www.êxample.com', options), 'http://www.xn--xample-hva.com'); - t.is(normalizeUrl('sindre://www.sorhus.com', options), 'sindre://www.sorhus.com'); + expect(normalizeUrl('http://www.sindresorhus.com', options)).to.deep.equal('http://www.sindresorhus.com'); + expect(normalizeUrl('www.sindresorhus.com', options)).to.deep.equal('http://www.sindresorhus.com'); + expect(normalizeUrl('http://www.êxample.com', options)).to.deep.equal('http://www.xn--xample-hva.com'); const options2 = {stripWWW: true}; - t.is(normalizeUrl('http://www.vue.amsterdam', options2), 'http://vue.amsterdam'); - t.is(normalizeUrl('http://www.sorhus.xx--bck1b9a5dre4c', options2), 'http://sorhus.xx--bck1b9a5dre4c'); + expect(normalizeUrl('http://www.vue.amsterdam', options2)).to.deep.equal('http://vue.amsterdam'); + expect(normalizeUrl('http://www.sorhus.xx--bck1b9a5dre4c', options2)).to.deep.equal('http://sorhus.xx--bck1b9a5dre4c'); const tooLongTLDURL = 'http://www.sorhus.' + ''.padEnd(64, 'a'); - t.is(normalizeUrl(tooLongTLDURL, options2), tooLongTLDURL); + expect(normalizeUrl(tooLongTLDURL, options2)).to.deep.equal(tooLongTLDURL); +}); + +it('stripWWW option: non-special-protocol-schemes', async () => { + const options = {stripWWW: false}; + expect(normalizeUrl('sindre://www.sorhus.com', options)).to.deep.equal('sindre://www.sorhus.com'); }); -test('removeQueryParameters option', t => { +it('removeQueryParameters option', async () => { const options = { stripWWW: false, removeQueryParameters: [/^utm_\w+/i, 'ref'], }; - t.is(normalizeUrl('www.sindresorhus.com?foo=bar&utm_medium=test'), 'http://sindresorhus.com/?foo=bar'); - t.is(normalizeUrl('http://www.sindresorhus.com', options), 'http://www.sindresorhus.com'); - t.is(normalizeUrl('www.sindresorhus.com?foo=bar', options), 'http://www.sindresorhus.com/?foo=bar'); - t.is(normalizeUrl('www.sindresorhus.com?foo=bar&utm_medium=test&ref=test_ref', options), 'http://www.sindresorhus.com/?foo=bar'); + expect(normalizeUrl('www.sindresorhus.com?foo=bar&utm_medium=test')).to.deep.equal('http://sindresorhus.com/?foo=bar'); + expect(normalizeUrl('http://www.sindresorhus.com', options)).to.deep.equal('http://www.sindresorhus.com'); + expect(normalizeUrl('www.sindresorhus.com?foo=bar', options)).to.deep.equal('http://www.sindresorhus.com/?foo=bar'); + expect(normalizeUrl('www.sindresorhus.com?foo=bar&utm_medium=test&ref=test_ref', options)).to.deep.equal('http://www.sindresorhus.com/?foo=bar'); }); -test('removeQueryParameters boolean `true` option', t => { +it('removeQueryParameters boolean `true` option', async () => { const options = { stripWWW: false, removeQueryParameters: true, }; - t.is(normalizeUrl('http://www.sindresorhus.com', options), 'http://www.sindresorhus.com'); - t.is(normalizeUrl('www.sindresorhus.com?foo=bar', options), 'http://www.sindresorhus.com'); - t.is(normalizeUrl('www.sindresorhus.com?foo=bar&utm_medium=test&ref=test_ref', options), 'http://www.sindresorhus.com'); + expect(normalizeUrl('http://www.sindresorhus.com', options)).to.deep.equal('http://www.sindresorhus.com'); + expect(normalizeUrl('www.sindresorhus.com?foo=bar', options)).to.deep.equal('http://www.sindresorhus.com'); + expect(normalizeUrl('www.sindresorhus.com?foo=bar&utm_medium=test&ref=test_ref', options)).to.deep.equal('http://www.sindresorhus.com'); }); -test('removeQueryParameters boolean `false` option', t => { +it('removeQueryParameters boolean `false` option', async () => { const options = { stripWWW: false, removeQueryParameters: false, }; - t.is(normalizeUrl('http://www.sindresorhus.com', options), 'http://www.sindresorhus.com'); - t.is(normalizeUrl('www.sindresorhus.com?foo=bar', options), 'http://www.sindresorhus.com/?foo=bar'); - t.is(normalizeUrl('www.sindresorhus.com?foo=bar&utm_medium=test&ref=test_ref', options), 'http://www.sindresorhus.com/?foo=bar&ref=test_ref&utm_medium=test'); + expect(normalizeUrl('http://www.sindresorhus.com', options)).to.deep.equal('http://www.sindresorhus.com'); + expect(normalizeUrl('www.sindresorhus.com?foo=bar', options)).to.deep.equal('http://www.sindresorhus.com/?foo=bar'); + expect(normalizeUrl('www.sindresorhus.com?foo=bar&utm_medium=test&ref=test_ref', options)).to.deep.equal('http://www.sindresorhus.com/?foo=bar&ref=test_ref&utm_medium=test'); }); -test('forceHttp option', t => { +it('forceHttp option', async () => { const options = {forceHttp: true}; - t.is(normalizeUrl('https://sindresorhus.com'), 'https://sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com', options), 'http://sindresorhus.com'); - t.is(normalizeUrl('https://www.sindresorhus.com', options), 'http://sindresorhus.com'); - t.is(normalizeUrl('//sindresorhus.com', options), 'http://sindresorhus.com'); + expect(normalizeUrl('https://sindresorhus.com')).to.deep.equal('https://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com', options)).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('https://www.sindresorhus.com', options)).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('//sindresorhus.com', options)).to.deep.equal('http://sindresorhus.com'); }); -test('forceHttp option with forceHttps', t => { - t.throws(() => { +it('forceHttp option with forceHttps', async () => { + expect(() => { normalizeUrl('https://www.sindresorhus.com', {forceHttp: true, forceHttps: true}); - }, { - message: 'The `forceHttp` and `forceHttps` options cannot be used together', - }); + }).to.throw('The `forceHttp` and `forceHttps` options cannot be used together'); }); -test('forceHttps option', t => { +it('forceHttps option', async () => { const options = {forceHttps: true}; - t.is(normalizeUrl('https://sindresorhus.com'), 'https://sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com', options), 'https://sindresorhus.com'); - t.is(normalizeUrl('https://www.sindresorhus.com', options), 'https://sindresorhus.com'); - t.is(normalizeUrl('//sindresorhus.com', options), 'https://sindresorhus.com'); + expect(normalizeUrl('https://sindresorhus.com')).to.deep.equal('https://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com', options)).to.deep.equal('https://sindresorhus.com'); + expect(normalizeUrl('https://www.sindresorhus.com', options)).to.deep.equal('https://sindresorhus.com'); + expect(normalizeUrl('//sindresorhus.com', options)).to.deep.equal('https://sindresorhus.com'); }); -test('removeTrailingSlash option', t => { +it('removeTrailingSlash option', async () => { const options = {removeTrailingSlash: false}; - t.is(normalizeUrl('http://sindresorhus.com'), 'http://sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com/'), 'http://sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com', options), 'http://sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com/', options), 'http://sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com/redirect'), 'http://sindresorhus.com/redirect'); - t.is(normalizeUrl('http://sindresorhus.com/redirect/'), 'http://sindresorhus.com/redirect'); - t.is(normalizeUrl('http://sindresorhus.com/redirect/', options), 'http://sindresorhus.com/redirect/'); - t.is(normalizeUrl('http://sindresorhus.com/redirect/', options), 'http://sindresorhus.com/redirect/'); - t.is(normalizeUrl('http://sindresorhus.com/#/'), 'http://sindresorhus.com/#/'); - t.is(normalizeUrl('http://sindresorhus.com/#/', options), 'http://sindresorhus.com/#/'); - t.is(normalizeUrl('http://sindresorhus.com/?unicorns=true'), 'http://sindresorhus.com/?unicorns=true'); - t.is(normalizeUrl('http://sindresorhus.com/?unicorns=true', options), 'http://sindresorhus.com/?unicorns=true'); + expect(normalizeUrl('http://sindresorhus.com')).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com/')).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com', options)).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com/', options)).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com/redirect')).to.deep.equal('http://sindresorhus.com/redirect'); + expect(normalizeUrl('http://sindresorhus.com/redirect/')).to.deep.equal('http://sindresorhus.com/redirect'); + expect(normalizeUrl('http://sindresorhus.com/redirect/', options)).to.deep.equal('http://sindresorhus.com/redirect/'); + expect(normalizeUrl('http://sindresorhus.com/redirect/', options)).to.deep.equal('http://sindresorhus.com/redirect/'); + expect(normalizeUrl('http://sindresorhus.com/#/')).to.deep.equal('http://sindresorhus.com/#/'); + expect(normalizeUrl('http://sindresorhus.com/#/', options)).to.deep.equal('http://sindresorhus.com/#/'); + expect(normalizeUrl('http://sindresorhus.com/?unicorns=true')).to.deep.equal('http://sindresorhus.com/?unicorns=true'); + expect(normalizeUrl('http://sindresorhus.com/?unicorns=true', options)).to.deep.equal('http://sindresorhus.com/?unicorns=true'); }); -test('removeSingleSlash option', t => { +it('removeSingleSlash option', async () => { const options = {removeSingleSlash: false}; - t.is(normalizeUrl('https://sindresorhus.com', options), 'https://sindresorhus.com'); - t.is(normalizeUrl('https://sindresorhus.com/', options), 'https://sindresorhus.com/'); - t.is(normalizeUrl('https://sindresorhus.com/redirect', options), 'https://sindresorhus.com/redirect'); - t.is(normalizeUrl('https://sindresorhus.com/redirect/', options), 'https://sindresorhus.com/redirect'); - t.is(normalizeUrl('https://sindresorhus.com/#/', options), 'https://sindresorhus.com/#/'); - t.is(normalizeUrl('https://sindresorhus.com/?unicorns=true', options), 'https://sindresorhus.com/?unicorns=true'); + expect(normalizeUrl('https://sindresorhus.com', options)).to.deep.equal('https://sindresorhus.com'); + expect(normalizeUrl('https://sindresorhus.com/', options)).to.deep.equal('https://sindresorhus.com/'); + expect(normalizeUrl('https://sindresorhus.com/redirect', options)).to.deep.equal('https://sindresorhus.com/redirect'); + expect(normalizeUrl('https://sindresorhus.com/redirect/', options)).to.deep.equal('https://sindresorhus.com/redirect'); + expect(normalizeUrl('https://sindresorhus.com/#/', options)).to.deep.equal('https://sindresorhus.com/#/'); + expect(normalizeUrl('https://sindresorhus.com/?unicorns=true', options)).to.deep.equal('https://sindresorhus.com/?unicorns=true'); }); -test('removeSingleSlash option combined with removeTrailingSlash option', t => { +it('removeSingleSlash option combined with removeTrailingSlash option', async () => { const options = {removeTrailingSlash: false, removeSingleSlash: false}; - t.is(normalizeUrl('https://sindresorhus.com', options), 'https://sindresorhus.com'); - t.is(normalizeUrl('https://sindresorhus.com/', options), 'https://sindresorhus.com/'); - t.is(normalizeUrl('https://sindresorhus.com/redirect', options), 'https://sindresorhus.com/redirect'); - t.is(normalizeUrl('https://sindresorhus.com/redirect/', options), 'https://sindresorhus.com/redirect/'); - t.is(normalizeUrl('https://sindresorhus.com/#/', options), 'https://sindresorhus.com/#/'); - t.is(normalizeUrl('https://sindresorhus.com/?unicorns=true', options), 'https://sindresorhus.com/?unicorns=true'); + expect(normalizeUrl('https://sindresorhus.com', options)).to.deep.equal('https://sindresorhus.com'); + expect(normalizeUrl('https://sindresorhus.com/', options)).to.deep.equal('https://sindresorhus.com/'); + expect(normalizeUrl('https://sindresorhus.com/redirect', options)).to.deep.equal('https://sindresorhus.com/redirect'); + expect(normalizeUrl('https://sindresorhus.com/redirect/', options)).to.deep.equal('https://sindresorhus.com/redirect/'); + expect(normalizeUrl('https://sindresorhus.com/#/', options)).to.deep.equal('https://sindresorhus.com/#/'); + expect(normalizeUrl('https://sindresorhus.com/?unicorns=true', options)).to.deep.equal('https://sindresorhus.com/?unicorns=true'); }); -test('removeDirectoryIndex option', t => { +it('removeDirectoryIndex option', async () => { const options1 = {removeDirectoryIndex: ['index.html', 'index.php']}; - t.is(normalizeUrl('http://sindresorhus.com/index.html'), 'http://sindresorhus.com/index.html'); - t.is(normalizeUrl('http://sindresorhus.com/index.html', options1), 'http://sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com/index.htm', options1), 'http://sindresorhus.com/index.htm'); - t.is(normalizeUrl('http://sindresorhus.com/index.php', options1), 'http://sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com/path/index.html'), 'http://sindresorhus.com/path/index.html'); - t.is(normalizeUrl('http://sindresorhus.com/path/index.html', options1), 'http://sindresorhus.com/path'); - t.is(normalizeUrl('http://sindresorhus.com/path/index.htm', options1), 'http://sindresorhus.com/path/index.htm'); - t.is(normalizeUrl('http://sindresorhus.com/path/index.php', options1), 'http://sindresorhus.com/path'); - t.is(normalizeUrl('http://sindresorhus.com/foo/bar/index.html', options1), 'http://sindresorhus.com/foo/bar'); + expect(normalizeUrl('http://sindresorhus.com/index.html')).to.deep.equal('http://sindresorhus.com/index.html'); + expect(normalizeUrl('http://sindresorhus.com/index.html', options1)).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com/index.htm', options1)).to.deep.equal('http://sindresorhus.com/index.htm'); + expect(normalizeUrl('http://sindresorhus.com/index.php', options1)).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com/path/index.html')).to.deep.equal('http://sindresorhus.com/path/index.html'); + expect(normalizeUrl('http://sindresorhus.com/path/index.html', options1)).to.deep.equal('http://sindresorhus.com/path'); + expect(normalizeUrl('http://sindresorhus.com/path/index.htm', options1)).to.deep.equal('http://sindresorhus.com/path/index.htm'); + expect(normalizeUrl('http://sindresorhus.com/path/index.php', options1)).to.deep.equal('http://sindresorhus.com/path'); + expect(normalizeUrl('http://sindresorhus.com/foo/bar/index.html', options1)).to.deep.equal('http://sindresorhus.com/foo/bar'); const options2 = {removeDirectoryIndex: [/^index\.[a-z]+$/, 'remove.html']}; - t.is(normalizeUrl('http://sindresorhus.com/index.html'), 'http://sindresorhus.com/index.html'); - t.is(normalizeUrl('http://sindresorhus.com/index.html', options2), 'http://sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com/index/index.html', options2), 'http://sindresorhus.com/index'); - t.is(normalizeUrl('http://sindresorhus.com/remove.html', options2), 'http://sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com/default.htm', options2), 'http://sindresorhus.com/default.htm'); - t.is(normalizeUrl('http://sindresorhus.com/index.php', options2), 'http://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com/index.html')).to.deep.equal('http://sindresorhus.com/index.html'); + expect(normalizeUrl('http://sindresorhus.com/index.html', options2)).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com/index/index.html', options2)).to.deep.equal('http://sindresorhus.com/index'); + expect(normalizeUrl('http://sindresorhus.com/remove.html', options2)).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com/default.htm', options2)).to.deep.equal('http://sindresorhus.com/default.htm'); + expect(normalizeUrl('http://sindresorhus.com/index.php', options2)).to.deep.equal('http://sindresorhus.com'); const options3 = {removeDirectoryIndex: true}; - t.is(normalizeUrl('http://sindresorhus.com/index.html'), 'http://sindresorhus.com/index.html'); - t.is(normalizeUrl('http://sindresorhus.com/index.html', options3), 'http://sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com/index.htm', options3), 'http://sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com/index.php', options3), 'http://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com/index.html')).to.deep.equal('http://sindresorhus.com/index.html'); + expect(normalizeUrl('http://sindresorhus.com/index.html', options3)).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com/index.htm', options3)).to.deep.equal('http://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com/index.php', options3)).to.deep.equal('http://sindresorhus.com'); }); -test('removeTrailingSlash and removeDirectoryIndex options)', t => { +it('removeTrailingSlash and removeDirectoryIndex options)', async () => { const options1 = { removeTrailingSlash: true, removeDirectoryIndex: true, }; - t.is(normalizeUrl('http://sindresorhus.com/path/', options1), 'http://sindresorhus.com/path'); - t.is(normalizeUrl('http://sindresorhus.com/path/index.html', options1), 'http://sindresorhus.com/path'); - t.is(normalizeUrl('http://sindresorhus.com/#/path/', options1), 'http://sindresorhus.com/#/path/'); - t.is(normalizeUrl('http://sindresorhus.com/foo/#/bar/', options1), 'http://sindresorhus.com/foo#/bar/'); + expect(normalizeUrl('http://sindresorhus.com/path/', options1)).to.deep.equal('http://sindresorhus.com/path'); + expect(normalizeUrl('http://sindresorhus.com/path/index.html', options1)).to.deep.equal('http://sindresorhus.com/path'); + expect(normalizeUrl('http://sindresorhus.com/#/path/', options1)).to.deep.equal('http://sindresorhus.com/#/path/'); + expect(normalizeUrl('http://sindresorhus.com/foo/#/bar/', options1)).to.deep.equal('http://sindresorhus.com/foo#/bar/'); const options2 = { removeTrailingSlash: false, removeDirectoryIndex: true, }; - t.is(normalizeUrl('http://sindresorhus.com/path/', options2), 'http://sindresorhus.com/path/'); - t.is(normalizeUrl('http://sindresorhus.com/path/index.html', options2), 'http://sindresorhus.com/path/'); - t.is(normalizeUrl('http://sindresorhus.com/#/path/', options2), 'http://sindresorhus.com/#/path/'); + expect(normalizeUrl('http://sindresorhus.com/path/', options2)).to.deep.equal('http://sindresorhus.com/path/'); + expect(normalizeUrl('http://sindresorhus.com/path/index.html', options2)).to.deep.equal('http://sindresorhus.com/path/'); + expect(normalizeUrl('http://sindresorhus.com/#/path/', options2)).to.deep.equal('http://sindresorhus.com/#/path/'); }); -test('sortQueryParameters option', t => { +it('sortQueryParameters option', async () => { const options1 = { sortQueryParameters: true, }; - t.is(normalizeUrl('http://sindresorhus.com/?a=Z&b=Y&c=X&d=W', options1), 'http://sindresorhus.com/?a=Z&b=Y&c=X&d=W'); - t.is(normalizeUrl('http://sindresorhus.com/?b=Y&c=X&a=Z&d=W', options1), 'http://sindresorhus.com/?a=Z&b=Y&c=X&d=W'); - t.is(normalizeUrl('http://sindresorhus.com/?a=Z&d=W&b=Y&c=X', options1), 'http://sindresorhus.com/?a=Z&b=Y&c=X&d=W'); - t.is(normalizeUrl('http://sindresorhus.com/', options1), 'http://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com/?a=Z&b=Y&c=X&d=W', options1)).to.deep.equal('http://sindresorhus.com/?a=Z&b=Y&c=X&d=W'); + expect(normalizeUrl('http://sindresorhus.com/?b=Y&c=X&a=Z&d=W', options1)).to.deep.equal('http://sindresorhus.com/?a=Z&b=Y&c=X&d=W'); + expect(normalizeUrl('http://sindresorhus.com/?a=Z&d=W&b=Y&c=X', options1)).to.deep.equal('http://sindresorhus.com/?a=Z&b=Y&c=X&d=W'); + expect(normalizeUrl('http://sindresorhus.com/', options1)).to.deep.equal('http://sindresorhus.com'); const options2 = { sortQueryParameters: false, }; - t.is(normalizeUrl('http://sindresorhus.com/?a=Z&b=Y&c=X&d=W', options2), 'http://sindresorhus.com/?a=Z&b=Y&c=X&d=W'); - t.is(normalizeUrl('http://sindresorhus.com/?b=Y&c=X&a=Z&d=W', options2), 'http://sindresorhus.com/?b=Y&c=X&a=Z&d=W'); - t.is(normalizeUrl('http://sindresorhus.com/?a=Z&d=W&b=Y&c=X', options2), 'http://sindresorhus.com/?a=Z&d=W&b=Y&c=X'); - t.is(normalizeUrl('http://sindresorhus.com/', options2), 'http://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com/?a=Z&b=Y&c=X&d=W', options2)).to.deep.equal('http://sindresorhus.com/?a=Z&b=Y&c=X&d=W'); + expect(normalizeUrl('http://sindresorhus.com/?b=Y&c=X&a=Z&d=W', options2)).to.deep.equal('http://sindresorhus.com/?b=Y&c=X&a=Z&d=W'); + expect(normalizeUrl('http://sindresorhus.com/?a=Z&d=W&b=Y&c=X', options2)).to.deep.equal('http://sindresorhus.com/?a=Z&d=W&b=Y&c=X'); + expect(normalizeUrl('http://sindresorhus.com/', options2)).to.deep.equal('http://sindresorhus.com'); }); -test('invalid urls', t => { - t.throws(() => { +it('invalid urls', async () => { + /** + * + * Node.js: ^Invalid URL: .*$ + * Chrome: ^Failed to construct 'URL': Invalid URL$ + * Firefox: ^URL constructor: .* is not a valid URL\.$ + */ + const INVALID_URL_REGEXP = /^Invalid URL: .*$|^Failed to construct 'URL': Invalid URL$|^URL constructor: .* is not a valid URL\.$/; + + expect(() => { normalizeUrl('http://'); - }, { - message: /^Invalid URL/, - }); + }).to.throw(INVALID_URL_REGEXP); - t.throws(() => { + expect(() => { normalizeUrl('/'); - }, { - message: /^Invalid URL/, - }); + }).to.throw(INVALID_URL_REGEXP); - t.throws(() => { + expect(() => { normalizeUrl('/relative/path/'); - }, { - message: /^Invalid URL/, - }); + }).to.throw(INVALID_URL_REGEXP); }); -test('remove duplicate pathname slashes', t => { - t.is(normalizeUrl('http://sindresorhus.com////foo/bar'), 'http://sindresorhus.com/foo/bar'); - t.is(normalizeUrl('http://sindresorhus.com////foo////bar'), 'http://sindresorhus.com/foo/bar'); - t.is(normalizeUrl('//sindresorhus.com//foo', {normalizeProtocol: false}), '//sindresorhus.com/foo'); - t.is(normalizeUrl('http://sindresorhus.com:5000///foo'), 'http://sindresorhus.com:5000/foo'); - t.is(normalizeUrl('http://sindresorhus.com///foo'), 'http://sindresorhus.com/foo'); - t.is(normalizeUrl('http://sindresorhus.com:5000//foo'), 'http://sindresorhus.com:5000/foo'); - t.is(normalizeUrl('http://sindresorhus.com//foo'), 'http://sindresorhus.com/foo'); - t.is(normalizeUrl('http://sindresorhus.com/s3://sindresorhus.com'), 'http://sindresorhus.com/s3://sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com/s3://sindresorhus.com//foo'), 'http://sindresorhus.com/s3://sindresorhus.com/foo'); - t.is(normalizeUrl('http://sindresorhus.com//foo/s3://sindresorhus.com'), 'http://sindresorhus.com/foo/s3://sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com/git://sindresorhus.com'), 'http://sindresorhus.com/git://sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com/git://sindresorhus.com//foo'), 'http://sindresorhus.com/git://sindresorhus.com/foo'); - t.is(normalizeUrl('http://sindresorhus.com//foo/git://sindresorhus.com//foo'), 'http://sindresorhus.com/foo/git://sindresorhus.com/foo'); - t.is(normalizeUrl('http://sindresorhus.com/a://sindresorhus.com//foo'), 'http://sindresorhus.com/a:/sindresorhus.com/foo'); - t.is(normalizeUrl('http://sindresorhus.com/alongprotocolwithin50charlimitxxxxxxxxxxxxxxxxxxxx://sindresorhus.com//foo'), 'http://sindresorhus.com/alongprotocolwithin50charlimitxxxxxxxxxxxxxxxxxxxx://sindresorhus.com/foo'); - t.is(normalizeUrl('http://sindresorhus.com/alongprotocolexceeds50charlimitxxxxxxxxxxxxxxxxxxxxx://sindresorhus.com//foo'), 'http://sindresorhus.com/alongprotocolexceeds50charlimitxxxxxxxxxxxxxxxxxxxxx:/sindresorhus.com/foo'); - t.is(normalizeUrl('http://sindresorhus.com/a2-.+://sindresorhus.com'), 'http://sindresorhus.com/a2-.+://sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com/a2-.+_://sindresorhus.com'), 'http://sindresorhus.com/a2-.+_:/sindresorhus.com'); - t.is(normalizeUrl('http://sindresorhus.com/2abc://sindresorhus.com'), 'http://sindresorhus.com/2abc:/sindresorhus.com'); +it('remove duplicate pathname slashes', async () => { + expect(normalizeUrl('http://sindresorhus.com////foo/bar')).to.deep.equal('http://sindresorhus.com/foo/bar'); + expect(normalizeUrl('http://sindresorhus.com////foo////bar')).to.deep.equal('http://sindresorhus.com/foo/bar'); + expect(normalizeUrl('//sindresorhus.com//foo', {normalizeProtocol: false})).to.deep.equal('//sindresorhus.com/foo'); + expect(normalizeUrl('http://sindresorhus.com:5000///foo')).to.deep.equal('http://sindresorhus.com:5000/foo'); + expect(normalizeUrl('http://sindresorhus.com///foo')).to.deep.equal('http://sindresorhus.com/foo'); + expect(normalizeUrl('http://sindresorhus.com:5000//foo')).to.deep.equal('http://sindresorhus.com:5000/foo'); + expect(normalizeUrl('http://sindresorhus.com//foo')).to.deep.equal('http://sindresorhus.com/foo'); + expect(normalizeUrl('http://sindresorhus.com/s3://sindresorhus.com')).to.deep.equal('http://sindresorhus.com/s3://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com/s3://sindresorhus.com//foo')).to.deep.equal('http://sindresorhus.com/s3://sindresorhus.com/foo'); + expect(normalizeUrl('http://sindresorhus.com//foo/s3://sindresorhus.com')).to.deep.equal('http://sindresorhus.com/foo/s3://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com/git://sindresorhus.com')).to.deep.equal('http://sindresorhus.com/git://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com/git://sindresorhus.com//foo')).to.deep.equal('http://sindresorhus.com/git://sindresorhus.com/foo'); + expect(normalizeUrl('http://sindresorhus.com//foo/git://sindresorhus.com//foo')).to.deep.equal('http://sindresorhus.com/foo/git://sindresorhus.com/foo'); + expect(normalizeUrl('http://sindresorhus.com/a://sindresorhus.com//foo')).to.deep.equal('http://sindresorhus.com/a:/sindresorhus.com/foo'); + expect(normalizeUrl('http://sindresorhus.com/alongprotocolwithin50charlimitxxxxxxxxxxxxxxxxxxxx://sindresorhus.com//foo')).to.deep.equal('http://sindresorhus.com/alongprotocolwithin50charlimitxxxxxxxxxxxxxxxxxxxx://sindresorhus.com/foo'); + expect(normalizeUrl('http://sindresorhus.com/alongprotocolexceeds50charlimitxxxxxxxxxxxxxxxxxxxxx://sindresorhus.com//foo')).to.deep.equal('http://sindresorhus.com/alongprotocolexceeds50charlimitxxxxxxxxxxxxxxxxxxxxx:/sindresorhus.com/foo'); + expect(normalizeUrl('http://sindresorhus.com/a2-.+://sindresorhus.com')).to.deep.equal('http://sindresorhus.com/a2-.+://sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com/a2-.+_://sindresorhus.com')).to.deep.equal('http://sindresorhus.com/a2-.+_:/sindresorhus.com'); + expect(normalizeUrl('http://sindresorhus.com/2abc://sindresorhus.com')).to.deep.equal('http://sindresorhus.com/2abc:/sindresorhus.com'); }); -test('data URL', t => { +it('data URL', async () => { // Invalid URL. - t.throws(() => { + expect(() => { normalizeUrl('data:'); - }, { - message: 'Invalid URL: data:', - }); + }).to.throw('Invalid URL: data:'); // Strip default MIME type - t.is(normalizeUrl('data:text/plain,foo'), 'data:,foo'); + expect(normalizeUrl('data:text/plain,foo')).to.deep.equal('data:,foo'); // Strip default charset - t.is(normalizeUrl('data:;charset=us-ascii,foo'), 'data:,foo'); + expect(normalizeUrl('data:;charset=us-ascii,foo')).to.deep.equal('data:,foo'); // Normalize away trailing semicolon. - t.is(normalizeUrl('data:;charset=UTF-8;,foo'), 'data:;charset=utf-8,foo'); + expect(normalizeUrl('data:;charset=UTF-8;,foo')).to.deep.equal('data:;charset=utf-8,foo'); // Empty MIME type. - t.is(normalizeUrl('data:,'), 'data:,'); + expect(normalizeUrl('data:,')).to.deep.equal('data:,'); // Empty MIME type with charset. - t.is(normalizeUrl('data:;charset=utf-8,foo'), 'data:;charset=utf-8,foo'); + expect(normalizeUrl('data:;charset=utf-8,foo')).to.deep.equal('data:;charset=utf-8,foo'); // Lowercase the MIME type. - t.is(normalizeUrl('data:TEXT/HTML,foo'), 'data:text/html,foo'); + expect(normalizeUrl('data:TEXT/HTML,foo')).to.deep.equal('data:text/html,foo'); // Strip empty hash. - t.is(normalizeUrl('data:,foo# '), 'data:,foo'); + expect(normalizeUrl('data:,foo# ')).to.deep.equal('data:,foo'); // Key only mediaType attribute. - t.is(normalizeUrl('data:;foo=;bar,'), 'data:;foo;bar,'); + expect(normalizeUrl('data:;foo=;bar,')).to.deep.equal('data:;foo;bar,'); // Lowercase the charset. - t.is(normalizeUrl('data:;charset=UTF-8,foo'), 'data:;charset=utf-8,foo'); + expect(normalizeUrl('data:;charset=UTF-8,foo')).to.deep.equal('data:;charset=utf-8,foo'); // Remove spaces after the comma when it's base64. - t.is(normalizeUrl('data:;base64, Zm9v #foo #bar'), 'data:;base64,Zm9v#foo #bar'); + expect(normalizeUrl('data:;base64, Zm9v #foo #bar')).to.deep.equal('data:;base64,Zm9v#foo #bar'); // Keep spaces when it's not base64. - t.is(normalizeUrl('data:, foo #bar'), 'data:, foo #bar'); + expect(normalizeUrl('data:, foo #bar')).to.deep.equal('data:, foo #bar'); // Options. const options = { @@ -361,27 +377,25 @@ test('data URL', t => { removeTrailingSlash: true, removeDirectoryIndex: true, }; - t.is(normalizeUrl('data:,sindresorhus.com/', options), 'data:,sindresorhus.com/'); - t.is(normalizeUrl('data:,sindresorhus.com/index.html', options), 'data:,sindresorhus.com/index.html'); - t.is(normalizeUrl('data:,sindresorhus.com?foo=bar&a=a&utm_medium=test', options), 'data:,sindresorhus.com?foo=bar&a=a&utm_medium=test'); - t.is(normalizeUrl('data:,foo#bar', options), 'data:,foo'); - t.is(normalizeUrl('data:,www.sindresorhus.com', options), 'data:,www.sindresorhus.com'); + expect(normalizeUrl('data:,sindresorhus.com/', options)).to.deep.equal('data:,sindresorhus.com/'); + expect(normalizeUrl('data:,sindresorhus.com/index.html', options)).to.deep.equal('data:,sindresorhus.com/index.html'); + expect(normalizeUrl('data:,sindresorhus.com?foo=bar&a=a&utm_medium=test', options)).to.deep.equal('data:,sindresorhus.com?foo=bar&a=a&utm_medium=test'); + expect(normalizeUrl('data:,foo#bar', options)).to.deep.equal('data:,foo'); + expect(normalizeUrl('data:,www.sindresorhus.com', options)).to.deep.equal('data:,www.sindresorhus.com'); }); -test('prevents homograph attack', t => { +it('prevents homograph attack', async () => { // The input string uses Unicode to make it look like a valid `ebay.com` URL. - t.is(normalizeUrl('https://ebаy.com'), 'https://xn--eby-7cd.com'); + expect(normalizeUrl('https://ebаy.com')).to.deep.equal('https://xn--eby-7cd.com'); }); -test('view-source URL', t => { - t.throws(() => { +it('view-source URL', async () => { + expect(() => { normalizeUrl('view-source:https://www.sindresorhus.com'); - }, { - message: '`view-source:` is not supported as it is a non-standard protocol', - }); + }).to.throw('`view-source:` is not supported as it is a non-standard protocol'); }); -test('does not have exponential performance for data URLs', t => { +it('does not have exponential performance for data URLs', async () => { for (let index = 0; index < 1000; index += 50) { const url = 'data:' + Array.from({length: index}).fill(',#').join('') + '\ra'; const start = Date.now(); @@ -391,6 +405,6 @@ test('does not have exponential performance for data URLs', t => { } catch {} const difference = Date.now() - start; - t.true(difference < 100, `Execution time: ${difference}`); + expect(difference < 100, `Execution time: ${difference}`).to.deep.equal(true); } });