shared/utils/urls.test.ts
Rama main · réplica de lectura del repositorio original.
import env from "../env";
import { isBrowser } from "./browser";
import * as urlsUtils from "./urls";
import { urlRegex } from "./urls";
describe("isUrl", () => {
it("should return false for invalid url", () => {
expect(urlsUtils.isUrl("")).toBe(false);
expect(urlsUtils.isUrl("#invalidurl")).toBe(false);
expect(urlsUtils.isUrl("mailto:")).toBe(false);
expect(urlsUtils.isUrl("sms:")).toBe(false);
expect(urlsUtils.isUrl("://")).toBe(false);
expect(urlsUtils.isUrl("www.example.com")).toBe(false);
});
it("should return true for valid urls", () => {
expect(urlsUtils.isUrl("http://example.com")).toBe(true);
expect(urlsUtils.isUrl("https://www.example.com")).toBe(true);
expect(urlsUtils.isUrl("seafile://openfile")).toBe(true);
expect(urlsUtils.isUrl("figma://launch")).toBe(true);
expect(urlsUtils.isUrl("outline:https://getoutline.com")).toBe(true);
expect(urlsUtils.isUrl("www.example.com", { requireProtocol: false })).toBe(
true
);
});
describe("requireHttps option", () => {
it("should reject HTTP URLs when requireHttps is true", () => {
expect(
urlsUtils.isUrl("http://example.com", { requireHttps: true })
).toBe(false);
expect(
urlsUtils.isUrl("http://example.com/callback", { requireHttps: true })
).toBe(false);
expect(
urlsUtils.isUrl("http://localhost:3000/auth", { requireHttps: true })
).toBe(false);
});
it("should accept HTTPS URLs when requireHttps is true", () => {
expect(
urlsUtils.isUrl("https://example.com", { requireHttps: true })
).toBe(true);
expect(
urlsUtils.isUrl("https://example.com/callback", { requireHttps: true })
).toBe(true);
expect(
urlsUtils.isUrl("https://localhost:3000/auth", { requireHttps: true })
).toBe(true);
});
it("should accept HTTP URLs when requireHttps is false or not specified", () => {
expect(urlsUtils.isUrl("http://example.com")).toBe(true);
expect(
urlsUtils.isUrl("http://example.com", { requireHttps: false })
).toBe(true);
});
it("should allow custom protocols when requireHttps is true", () => {
expect(
urlsUtils.isUrl("seafile://openfile", { requireHttps: true })
).toBe(true);
expect(urlsUtils.isUrl("figma://launch", { requireHttps: true })).toBe(
true
);
expect(urlsUtils.isUrl("myapp://callback", { requireHttps: true })).toBe(
true
);
});
});
});
describe("isPrivateUseSchemeUrl", () => {
it("should return true for private-use schemes", () => {
expect(
urlsUtils.isPrivateUseSchemeUrl("com.example.app:/oauth2redirect")
).toBe(true);
expect(urlsUtils.isPrivateUseSchemeUrl("myapp://callback")).toBe(true);
expect(urlsUtils.isPrivateUseSchemeUrl("myapp://callback?x=1")).toBe(true);
expect(urlsUtils.isPrivateUseSchemeUrl("MyApp://callback")).toBe(true);
expect(urlsUtils.isPrivateUseSchemeUrl("myapp:a")).toBe(true);
expect(urlsUtils.isPrivateUseSchemeUrl("myapp:/a")).toBe(true);
});
it("should return false for web schemes", () => {
expect(urlsUtils.isPrivateUseSchemeUrl("http://example.com")).toBe(false);
expect(urlsUtils.isPrivateUseSchemeUrl("https://example.com")).toBe(false);
expect(urlsUtils.isPrivateUseSchemeUrl("ftp://example.com")).toBe(false);
expect(urlsUtils.isPrivateUseSchemeUrl("wss://example.com")).toBe(false);
});
it("should return false for schemes that can execute or embed content", () => {
expect(urlsUtils.isPrivateUseSchemeUrl("javascript:alert(1)")).toBe(false);
expect(urlsUtils.isPrivateUseSchemeUrl("JavaScript:alert(1)")).toBe(false);
expect(urlsUtils.isPrivateUseSchemeUrl("data:text/html,hi")).toBe(false);
expect(urlsUtils.isPrivateUseSchemeUrl("vbscript:msgbox")).toBe(false);
expect(urlsUtils.isPrivateUseSchemeUrl("file:///etc/passwd")).toBe(false);
expect(urlsUtils.isPrivateUseSchemeUrl("blob:https://example.com/x")).toBe(
false
);
expect(urlsUtils.isPrivateUseSchemeUrl("about:blank")).toBe(false);
});
it("should return false for malformed or empty values", () => {
expect(urlsUtils.isPrivateUseSchemeUrl("")).toBe(false);
expect(urlsUtils.isPrivateUseSchemeUrl("myapp:")).toBe(false);
expect(urlsUtils.isPrivateUseSchemeUrl("myapp:/")).toBe(false);
expect(urlsUtils.isPrivateUseSchemeUrl("myapp://")).toBe(false);
expect(urlsUtils.isPrivateUseSchemeUrl("callback")).toBe(false);
expect(urlsUtils.isPrivateUseSchemeUrl("/relative/path")).toBe(false);
expect(urlsUtils.isPrivateUseSchemeUrl(" myapp://callback")).toBe(false);
expect(urlsUtils.isPrivateUseSchemeUrl("myapp://call back")).toBe(false);
});
});
describe("isBase64Url", () => {
it("should return false for invalid url", () => {
expect(urlsUtils.isBase64Url("")).toBe(false);
expect(urlsUtils.isBase64Url("#invalidurl")).toBe(false);
expect(urlsUtils.isBase64Url("http://example.com")).toBe(false);
expect(urlsUtils.isBase64Url("https://www.example.com")).toBe(false);
expect(urlsUtils.isBase64Url("seafile://openfile")).toBe(false);
expect(urlsUtils.isBase64Url("figma://launch")).toBe(false);
expect(urlsUtils.isBase64Url("outline:https://getoutline.com")).toBe(false);
expect(urlsUtils.isBase64Url("://")).toBe(false);
});
it("should return true for valid urls", () => {
expect(
urlsUtils.isBase64Url(
"data:image/png;base64,R0lGODlhPQBEAPeoAJosM//AwO/AwHVYZ/z595kzAP/s7P+goOXMv8+fhw/v739/f+8PD98fH/8mJl+fn/9ZWb8/PzWlwv///6wWGbImAPgTEMImIN9gUFCEm/gDALULDN8PAD6atYdCTX9gUNKlj8wZAKUsAOzZz+UMAOsJAP/Z2ccMDA8PD/95eX5NWvsJCOVNQPtfX/8zM8+QePLl38MGBr8JCP+zs9myn/8GBqwpAP/GxgwJCPny78lzYLgjAJ8vAP9fX/+MjMUcAN8zM/9wcM8ZGcATEL+QePdZWf/29uc/P9cmJu9MTDImIN+/r7+/vz8/P8VNQGNugV8AAF9fX8swMNgTAFlDOICAgPN"
)
).toBeTruthy();
expect(
urlsUtils.isBase64Url(
"data:image/gif;base64,npkM+fOqD6DDj1aZpITp0dtGCDhr+fVuCu3zlg49ijaokTZTo27uG7Gjn2P+hI8+PDPERoUB318bWbfAJ5sUNFcuGRTYUqV/3ogfXp1rWlMc6awJjiAAd2fm4ogXjz56aypOoIde4OE5u/F9x199dlXnnGiHZWEYbGpsAEA3QXYnHwEFliKAgswgJ8LPeiUXGwedCAKABACCN+EA1pYIIYaFlcDhytd51sGAJbo3onOpajiihlO92KHGaUXGwWjUBChjSPiWJuOO/LYIm4v1tXfE6J4gCSJEZ7YgRYUNrkji9P55sF/ogxw5ZkSqIDaZBV6aSGYq/lGZplndkckZ98xoICbTcIJGQAZc"
)
).toBeTruthy();
});
});
describe("isInternalUrl", () => {
beforeEach(() => {
env.URL = "https://example.com:3000";
});
it("should return false if empty string", () => {
expect(urlsUtils.isInternalUrl("")).toBe(false);
});
it("should return false if port is different", () => {
expect(urlsUtils.isInternalUrl("https://example.com:4000")).toBe(false);
});
it("should return false if port is missing", () => {
expect(urlsUtils.isInternalUrl("https://example.com")).toBe(false);
});
it("should return true if starting with relative path", () => {
expect(urlsUtils.isInternalUrl("/drafts")).toEqual(true);
});
});
describe("signedUrlToAttachmentUrl", () => {
const id = "4d3e6f2a-8b1c-4e5d-9f0a-1b2c3d4e5f60";
const sign = (payload: object) =>
[
btoa(JSON.stringify({ alg: "HS256", typ: "JWT" })),
btoa(JSON.stringify(payload))
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, ""),
"signature",
].join(".");
it("should return the redirect url for a signed attachment url", () => {
const origin = isBrowser ? window.location.origin : env.URL;
const sig = sign({
key: `uploads/${id}/${id}/image ünïcode.png`,
type: "attachment",
});
expect(
urlsUtils.signedUrlToAttachmentUrl(`${origin}/api/files.get?sig=${sig}`)
).toEqual(`/api/attachments.redirect?id=${id}`);
expect(
urlsUtils.signedUrlToAttachmentUrl(`/api/files.get?sig=${sig}`)
).toEqual(`/api/attachments.redirect?id=${id}`);
});
it("should return undefined for an external url", () => {
const sig = sign({
key: `uploads/${id}/${id}/image.png`,
type: "attachment",
});
expect(
urlsUtils.signedUrlToAttachmentUrl(
`https://other.example.com/api/files.get?sig=${sig}`
)
).toBeUndefined();
expect(
urlsUtils.signedUrlToAttachmentUrl(
`//other.example.com/api/files.get?sig=${sig}`
)
).toBeUndefined();
});
it("should return undefined for other urls", () => {
expect(
urlsUtils.signedUrlToAttachmentUrl(`/api/attachments.redirect?id=${id}`)
).toBeUndefined();
expect(
urlsUtils.signedUrlToAttachmentUrl("/api/files.get?key=public/a/b/c.png")
).toBeUndefined();
expect(
urlsUtils.signedUrlToAttachmentUrl("/api/files.get?sig=invalid")
).toBeUndefined();
expect(
urlsUtils.signedUrlToAttachmentUrl(
`/api/files.get?sig=${sign({ key: "uploads/a/b/c.png", type: "attachment" })}`
)
).toBeUndefined();
});
});
describe("addMissingUrlPort", () => {
const url = env.URL;
beforeEach(() => {
env.URL = "https://example.com:3000";
});
afterEach(() => {
env.URL = url;
});
it("should add the port to a url without one", () => {
expect(urlsUtils.addMissingUrlPort("https://example.com/drafts")).toBe(
"https://example.com:3000/drafts"
);
});
it("should not change a url that already has a port", () => {
expect(urlsUtils.addMissingUrlPort("https://example.com:4000/drafts")).toBe(
"https://example.com:4000/drafts"
);
});
it("should not change a relative url", () => {
expect(urlsUtils.addMissingUrlPort("/drafts")).toBe("/drafts");
});
it("should not change a url when no port is configured", () => {
env.URL = "https://example.com";
expect(urlsUtils.addMissingUrlPort("https://example.com/drafts")).toBe(
"https://example.com/drafts"
);
});
it("should not add the default https port", () => {
env.URL = "https://example.com:443";
expect(urlsUtils.addMissingUrlPort("https://example.com/drafts")).toBe(
"https://example.com/drafts"
);
});
it("should not add the default http port", () => {
env.URL = "http://example.com:80";
expect(urlsUtils.addMissingUrlPort("http://example.com/drafts")).toBe(
"http://example.com/drafts"
);
});
it("should add a port that is not the default for the protocol", () => {
env.URL = "http://example.com:443";
expect(urlsUtils.addMissingUrlPort("http://example.com/drafts")).toBe(
"http://example.com:443/drafts"
);
});
});
describe("isExternalUrl", () => {
it("should return false if empty url", () => {
expect(urlsUtils.isExternalUrl("")).toBe(false);
});
it("should return false if internal url", () => {
expect(urlsUtils.isExternalUrl("/drafts")).toBe(false);
});
});
describe("sanitizeUrl", () => {
it("should return undefined if not url", () => {
expect(urlsUtils.sanitizeUrl(undefined)).toBeUndefined();
expect(urlsUtils.sanitizeUrl(null)).toBeUndefined();
expect(urlsUtils.sanitizeUrl("")).toBeUndefined();
});
it("should append https:// to non-special urls", () => {
expect(urlsUtils.sanitizeUrl("www.google.com")).toEqual(
"https://www.google.com"
);
});
it("should trim surrounding whitespace rather than append a scheme", () => {
expect(urlsUtils.sanitizeUrl("https://www.google.com\n")).toEqual(
"https://www.google.com"
);
expect(urlsUtils.sanitizeUrl(" https://www.google.com ")).toEqual(
"https://www.google.com"
);
});
describe("special urls", () => {
it("should return the url as it's if starting with /", () => {
expect(urlsUtils.sanitizeUrl("/drafts")).toEqual("/drafts");
});
it("should return the url as it's if starting with #", () => {
expect(urlsUtils.sanitizeUrl("#home")).toEqual("#home");
});
it("should return the url as it's if it's mailto:", () => {
expect(urlsUtils.sanitizeUrl("mailto:outline@getoutline.com")).toEqual(
"mailto:outline@getoutline.com"
);
});
it("should return the url as it's if it's sms:, fax:, tel:", () => {
expect(urlsUtils.sanitizeUrl("mailto:outline@getoutline.com")).toEqual(
"mailto:outline@getoutline.com"
);
expect(urlsUtils.sanitizeUrl("tel:0123456789")).toEqual("tel:0123456789");
expect(urlsUtils.sanitizeUrl("fax:0123456789")).toEqual("fax:0123456789");
expect(urlsUtils.sanitizeUrl("sms:0123456789")).toEqual("sms:0123456789");
});
it("should return the url unchanged if it's geo:", () => {
expect(urlsUtils.sanitizeUrl("geo:37.786971,-122.399677")).toEqual(
"geo:37.786971,-122.399677"
);
});
it("should return the url unchanged if it's maps:", () => {
expect(urlsUtils.sanitizeUrl("maps:?q=Eiffel+Tower")).toEqual(
"maps:?q=Eiffel+Tower"
);
});
it("should return the url unchanged if it's magnet:", () => {
expect(
urlsUtils.sanitizeUrl("magnet:?xt=urn:btih:abc123&dn=file")
).toEqual("magnet:?xt=urn:btih:abc123&dn=file");
});
it("should handle uppercase scheme", () => {
expect(urlsUtils.sanitizeUrl("GEO:37.786971,-122.399677")).toEqual(
"GEO:37.786971,-122.399677"
);
});
it("should return the url as it's if it's a special protocol", () => {
expect(urlsUtils.sanitizeUrl("mqtt://getoutline.com")).toEqual(
"mqtt://getoutline.com"
);
});
});
describe("Blocked protocols", () => {
it("should sanitize base64-encoded image data URIs (links should not embed data)", () => {
expect(
urlsUtils.sanitizeUrl(
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="
)
).toEqual(
"https://data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="
);
});
it("should be sanitized", () => {
expect(urlsUtils.sanitizeUrl("file://localhost.com/outline.txt")).toEqual(
"https://file://localhost.com/outline.txt"
);
expect(urlsUtils.sanitizeUrl("javascript:whatever")).toEqual(
"https://javascript:whatever"
);
expect(
urlsUtils.sanitizeUrl("data:text/html,<script>alert('hi');</script>")
).toEqual("https://data:text/html,<script>alert('hi');</script>");
expect(urlsUtils.sanitizeUrl("vbscript:whatever")).toEqual(
"https://vbscript:whatever"
);
});
});
});
describe("sanitizeImageSrc", () => {
it("should return undefined if not a src", () => {
expect(urlsUtils.sanitizeImageSrc(undefined)).toBeUndefined();
expect(urlsUtils.sanitizeImageSrc(null)).toBeUndefined();
expect(urlsUtils.sanitizeImageSrc("")).toBeUndefined();
});
it("should return base64-encoded image data URIs unchanged", () => {
const png =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=";
expect(urlsUtils.sanitizeImageSrc(png)).toEqual(png);
const gif =
"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
expect(urlsUtils.sanitizeImageSrc(gif)).toEqual(gif);
const jpeg = "data:image/jpeg;base64,/9j/4AAQSkZJRg==";
expect(urlsUtils.sanitizeImageSrc(jpeg)).toEqual(jpeg);
const webp = "data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAA";
expect(urlsUtils.sanitizeImageSrc(webp)).toEqual(webp);
const avif = "data:image/avif;base64,AAAAHGZ0eXBhdmlmAAAAAGF2aWY";
expect(urlsUtils.sanitizeImageSrc(avif)).toEqual(avif);
});
it("should sanitize svg data URIs (can contain inline scripts)", () => {
expect(
urlsUtils.sanitizeImageSrc("data:image/svg+xml;base64,PHN2Zy8+")
).toEqual("https://data:image/svg+xml;base64,PHN2Zy8+");
expect(
urlsUtils.sanitizeImageSrc("data:image/svg;base64,PHN2Zy8+")
).toEqual("https://data:image/svg;base64,PHN2Zy8+");
expect(
urlsUtils.sanitizeImageSrc("data:image/SVG+XML;base64,PHN2Zy8+")
).toEqual("https://data:image/SVG+XML;base64,PHN2Zy8+");
expect(
urlsUtils.sanitizeImageSrc("data:image/svg+xml,<svg></svg>")
).toEqual("https://data:image/svg+xml,<svg></svg>");
});
it("should sanitize non-image data URIs", () => {
expect(
urlsUtils.sanitizeImageSrc("data:text/html,<script>alert('hi');</script>")
).toEqual("https://data:text/html,<script>alert('hi');</script>");
});
it("should fall through to sanitizeUrl behavior for non-data-URI input", () => {
expect(urlsUtils.sanitizeImageSrc("https://example.com/a.png")).toEqual(
"https://example.com/a.png"
);
expect(urlsUtils.sanitizeImageSrc("/uploads/a.png")).toEqual(
"/uploads/a.png"
);
expect(urlsUtils.sanitizeImageSrc("javascript:alert(1)")).toEqual(
"https://javascript:alert(1)"
);
});
});
describe("parseShareIdFromUrl", () => {
it("should return share id from url with doc path", () => {
expect(
urlsUtils.parseShareIdFromUrl(
"https://app.example.com/s/my-share/doc/test-abc123"
)
).toBe("my-share");
});
it("should return share uuid from url", () => {
expect(
urlsUtils.parseShareIdFromUrl(
"https://app.example.com/s/2767ba0e-ac5c-4533-b9cf-4f5fc456600e/doc/test-abc123"
)
).toBe("2767ba0e-ac5c-4533-b9cf-4f5fc456600e");
});
it("should return share id when no doc path is present", () => {
expect(
urlsUtils.parseShareIdFromUrl("https://app.example.com/s/my-share")
).toBe("my-share");
});
it("should return undefined for non-share urls", () => {
expect(
urlsUtils.parseShareIdFromUrl("https://app.example.com/doc/test-abc123")
).toBeUndefined();
});
it("should return undefined for invalid urls", () => {
expect(urlsUtils.parseShareIdFromUrl("not a url")).toBeUndefined();
});
});
describe("#urlRegex", () => {
it("should return undefined for invalid urls", () => {
expect(urlRegex(undefined)).toBeUndefined();
expect(urlRegex(null)).toBeUndefined();
expect(urlRegex("invalid url!")).toBeUndefined();
});
it("should return corresponding regex otherwise", () => {
const regex = urlRegex("https://docs.google.com");
expect(regex?.source).toBe(
/^https:\/\/docs\.google\.com(?::443)?(?=[/?#]|$)/.source
);
expect(regex?.test("https://docs.google.com")).toBe(true);
expect(regex?.test("https://docs.google.com/")).toBe(true);
expect(regex?.test("https://docs.google.com/d/123")).toBe(true);
expect(regex?.test("http://google.com")).toBe(false);
expect(regex?.test("http://docs.google.com")).toBe(false);
expect(regex?.test("http://docs.google.com/")).toBe(false);
expect(regex?.test("http://docs.google.com/d/123")).toBe(false);
expect(regex?.test("javascript:alert(1)//https://docs.google.com")).toBe(
false
);
});
it("should only match the exact origin", () => {
const regex = urlRegex("https://docs.google.com");
expect(regex?.test("https://docs.google.com?a=1")).toBe(true);
expect(regex?.test("https://docs.google.com#a")).toBe(true);
expect(regex?.test("https://docs.google.com.evil.net/x")).toBe(false);
expect(regex?.test("https://docs.google.com@evil.net/x")).toBe(false);
expect(regex?.test("https://docs.google.com\\@evil.net/x")).toBe(false);
expect(regex?.test("https://docs.google.com:8080/x")).toBe(false);
});
it("should match a url that states the default port", () => {
for (const base of [
"https://docs.google.com",
"https://docs.google.com:443",
]) {
const regex = urlRegex(base);
expect(regex?.test("https://docs.google.com/x")).toBe(true);
expect(regex?.test("https://docs.google.com:443")).toBe(true);
expect(regex?.test("https://docs.google.com:443/x")).toBe(true);
expect(regex?.test("https://docs.google.com:4430/x")).toBe(false);
expect(regex?.test("https://docs.google.com:80/x")).toBe(false);
}
const http = urlRegex("http://docs.google.com");
expect(http?.test("http://docs.google.com:80/x")).toBe(true);
expect(http?.test("http://docs.google.com:443/x")).toBe(false);
});
it("should only match a non-default port exactly", () => {
const regex = urlRegex("https://docs.google.com:8443");
expect(regex?.test("https://docs.google.com:8443/x")).toBe(true);
expect(regex?.test("https://docs.google.com/x")).toBe(false);
expect(regex?.test("https://docs.google.com:443/x")).toBe(false);
});
});
describe("#getHostname", () => {
it("should return the hostname", () => {
expect(urlsUtils.getHostname("https://Example.com:8080/a?b=c")).toBe(
"example.com"
);
});
it("should return internationalized hostnames as punycode", () => {
expect(urlsUtils.getHostname("https://аpple.com/client.json")).toBe(
"xn--pple-43d.com"
);
});
it("should keep brackets on IPv6 hostnames", () => {
expect(urlsUtils.getHostname("http://[::1]:3000/callback")).toBe("[::1]");
});
it("should return undefined for unparseable input", () => {
expect(urlsUtils.getHostname("not a url")).toBeUndefined();
expect(urlsUtils.getHostname("")).toBeUndefined();
expect(urlsUtils.getHostname(undefined)).toBeUndefined();
});
});
describe("#removeUrlFragment", () => {
it("should remove a hash fragment", () => {
expect(
urlsUtils.removeUrlFragment("https://example.com/doc/abc#h-my-heading")
).toBe("https://example.com/doc/abc");
});
it("should leave urls without a hash untouched", () => {
expect(urlsUtils.removeUrlFragment("https://example.com/doc/abc")).toBe(
"https://example.com/doc/abc"
);
});
it("should preserve query strings", () => {
expect(
urlsUtils.removeUrlFragment("https://example.com/doc/abc?foo=bar#heading")
).toBe("https://example.com/doc/abc?foo=bar");
});
it("should fall back to string stripping for non-parseable input", () => {
expect(urlsUtils.removeUrlFragment("/doc/abc#heading")).toBe("/doc/abc");
});
});
describe("#removeUrlPathSuffix", () => {
it("should remove a trailing path suffix", () => {
expect(
urlsUtils.removeUrlPathSuffix(
"https://example.com/doc/my-doc-abc123/edit",
"/edit"
)
).toBe("https://example.com/doc/my-doc-abc123");
});
it("should not corrupt a hostname containing the suffix", () => {
expect(
urlsUtils.removeUrlPathSuffix(
"https://editing.example.com/doc/my-doc-abc123",
"/edit"
)
).toBe("https://editing.example.com/doc/my-doc-abc123");
});
it("should only strip the trailing suffix, not a match in the hostname", () => {
expect(
urlsUtils.removeUrlPathSuffix(
"https://editing.example.com/doc/my-doc-abc123/edit",
"/edit"
)
).toBe("https://editing.example.com/doc/my-doc-abc123");
});
it("should leave urls without the suffix untouched", () => {
expect(
urlsUtils.removeUrlPathSuffix(
"https://example.com/doc/my-doc-abc123",
"/edit"
)
).toBe("https://example.com/doc/my-doc-abc123");
});
it("should not strip the suffix when it is not the final path segment", () => {
expect(
urlsUtils.removeUrlPathSuffix(
"https://example.com/edit/something",
"/edit"
)
).toBe("https://example.com/edit/something");
});
it("should preserve query strings", () => {
expect(
urlsUtils.removeUrlPathSuffix(
"https://example.com/doc/abc/edit?foo=bar",
"/edit"
)
).toBe("https://example.com/doc/abc?foo=bar");
});
it("should fall back to string stripping for non-parseable input", () => {
expect(
urlsUtils.removeUrlPathSuffix("/doc/my-doc-abc123/edit", "/edit")
).toBe("/doc/my-doc-abc123");
});
});