diff --git a/README.md b/README.md index 40cead91..b818d551 100644 --- a/README.md +++ b/README.md @@ -245,6 +245,19 @@ isEqual('/foo bar', '/foo%20bar', { encoding: true }) // false ``` +### `withFragment` + +Add a fragment (or hash) to a URL: + +```ts +withFragment('/foo', 'bar') +// /foo#bar +withFragment('/foo#bar', 'baz') +// /foo#baz +withFragment('/foo#bar', '') +// /foo +``` + ## License [MIT](./LICENSE) diff --git a/src/utils.ts b/src/utils.ts index 8fa951bf..dc074f6d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -256,3 +256,9 @@ export function isEqual(a: string, b: string, options: CompareURLOptions = {}) { } return a === b; } + +export function withFragment(input: string, hash: string): string { + const parsed = parseURL(input); + parsed.hash = hash === "" ? "" : "#" + encodeURI(hash); + return stringifyParsedURL(parsed); +} diff --git a/test/utilities.test.ts b/test/utilities.test.ts index 81240089..ba9a3af0 100644 --- a/test/utilities.test.ts +++ b/test/utilities.test.ts @@ -10,6 +10,7 @@ import { withoutProtocol, withProtocol, isScriptProtocol, + withFragment, } from "../src"; describe("hasProtocol", () => { @@ -256,3 +257,50 @@ describe("isEqual", () => { }); } }); + +describe("withFragment", () => { + const tests = [ + { + input: "https://example.com", + fragment: "foo", + out: "https://example.com#foo", + }, + { + input: "https://example.com#bar", + fragment: "foo", + out: "https://example.com#foo", + }, + { input: "https://example.com", fragment: "", out: "https://example.com" }, + { + input: "https://example.com#bar", + fragment: "", + out: "https://example.com", + }, + { + input: "https://example.com#bar", + fragment: "0", + out: "https://example.com#0", + }, + { + input: "https://example.com#bar", + fragment: "foo bar", + out: "https://example.com#foo%20bar", + }, + { + input: "https://example.com#bar", + fragment: "foo/bar", + out: "https://example.com#foo/bar", + }, + { + input: "https://example.com?foo=bar", + fragment: "baz", + out: "https://example.com?foo=bar#baz", + }, + ]; + + for (const t of tests) { + test(`${t.input} + ${t.fragment}`, () => { + expect(withFragment(t.input, t.fragment)).toBe(t.out); + }); + } +});