From 5fb826749fc04141655b615ab5868ed63b52db17 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Fri, 6 May 2022 17:48:04 +0200 Subject: [PATCH] feat: `isEqual` utility --- README.md | 17 +++++++++++++++++ src/utils.ts | 22 ++++++++++++++++++++++ test/utilities.test.ts | 19 +++++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/README.md b/README.md index 2327806..5e12ec8 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,23 @@ Removes url protocol withoutProtocol('http://example.com') ``` +### `isEqual` + +Compare two URLs regardless of their slash condition or encoding: + +```ts +// Result: true +isEqual('/foo', 'foo') +isEqual('foo/', 'foo') +isEqual('/foo bar', '/foo%20bar') + +// Strict compare +// Result: false +isEqual('/foo', 'foo', { leadingSlash: true }) +isEqual('foo/', 'foo', { trailingSlash: true }) +isEqual('/foo bar', '/foo%20bar', { encoding: true }) +``` + ## License [MIT](./LICENSE) diff --git a/src/utils.ts b/src/utils.ts index d5cda38..dfc1193 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -155,3 +155,25 @@ export function resolveURL (base: string, ...input: string[]): string { export function isSamePath (p1: string, p2: string) { return decode(withoutTrailingSlash(p1)) === decode(withoutTrailingSlash(p2)) } + +interface CompareURLOptions { + trailingSlash?: boolean + leadingSlash?: boolean + encoding?: boolean +} + +export function isEqual (a: string, b: string, opts: CompareURLOptions = {}) { + if (!opts.trailingSlash) { + a = withTrailingSlash(a) + b = withTrailingSlash(b) + } + if (!opts.leadingSlash) { + a = withLeadingSlash(a) + b = withLeadingSlash(b) + } + if (!opts.encoding) { + a = decode(a) + b = decode(b) + } + return a === b +} diff --git a/test/utilities.test.ts b/test/utilities.test.ts index 1cb1620..42df359 100644 --- a/test/utilities.test.ts +++ b/test/utilities.test.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from 'vitest' import { hasProtocol, + isEqual, isRelative, parsePath, stringifyParsedURL, @@ -139,3 +140,21 @@ describe('withoutProtocol', () => { }) } }) + +describe('isEqual', () => { + const tests: { input: [string, string, any?], out: boolean }[] = [ + { input: ['/foo', '/foo/'], out: true }, + { input: ['foo', '/foo'], out: true }, + { input: ['foo', '/foo/'], out: true }, + { input: ['/foo%20bar/', '/foo bar'], out: true }, + { input: ['foo', '/foo', { leadingSlash: true }], out: false }, + { input: ['foo', 'foo/', { trailingSlash: true }], out: false }, + { input: ['/foo%20bar/', '/foo bar', { encoding: true }], out: false } + ] + + for (const t of tests) { + test(`${t.input[0]} == ${t.input[1]} ${t.input[2] ? JSON.stringify(t.input[2]) : ''}`, () => { + expect(isEqual(t.input[0], t.input[1], t.input[2])).toBe(t.out) + }) + } +})