Skip to content

Commit 6e5b599

Browse files
Update "src/utils/url"
1 parent 79dff6b commit 6e5b599

File tree

2 files changed

+86
-95
lines changed

2 files changed

+86
-95
lines changed
Lines changed: 48 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,75 @@
1-
import { highlightText } from '../../../tests/highlight-text';
21
import { hasParams, isEqualPath, removeParams, isExternalLink, removeLastSlash } from './url';
32

43
// ----------------------------------------------------------------------
54

6-
describe('hasParams()', () => {
7-
it(`1. Should return ${highlightText.val('true')} if URL has query parameters`, () => {
8-
expect(hasParams('https://example.com?page=1')).toBe(true);
9-
});
5+
const mapWithIndex = <T extends readonly any[][]>(cases: T) =>
6+
cases.map((testCase, index) => [index + 1, ...testCase] as const);
107

11-
it(`2. Should return ${highlightText.val('false')} if URL does not have query parameters`, () => {
12-
expect(hasParams('https://example.com')).toBe(false);
13-
});
8+
describe('hasParams()', () => {
9+
const cases = [
10+
['Should return true with single param', '/dashboard?page=1', true],
11+
['Should return true with multiple params', '/dashboard?x=1&y=2', true],
12+
['Should return false with no params', '/dashboard', false],
13+
['Should return false with trailing "?"', '/dashboard?', false],
14+
['Should return false for empty string', '', false],
15+
];
1416

15-
it(`3. Should return ${highlightText.val('false')} if URL has empty query parameters`, () => {
16-
expect(hasParams('https://example.com?')).toBe(false);
17+
test.each(mapWithIndex(cases))('%i. %s', (_i, _desc, input, expected) => {
18+
expect(hasParams(input)).toBe(expected);
1719
});
1820
});
1921

2022
describe('removeLastSlash()', () => {
21-
it(`1. Should remove trailing slash from pathname`, () => {
22-
expect(removeLastSlash('/dashboard/calendar/')).toBe('/dashboard/calendar');
23-
});
23+
const cases = [
24+
['Removes trailing slash', '/dashboard/', '/dashboard'],
25+
['Keeps path without slash', '/dashboard', '/dashboard'],
26+
['Removes slash from nested path', '/dashboard/user/', '/dashboard/user'],
27+
['Preserves root slash', '/', '/'],
28+
['Removes extra trailing slashes', '/test//', '/test/'],
29+
];
2430

25-
it(`2. Should return the same pathname if there is no trailing slash`, () => {
26-
expect(removeLastSlash('/dashboard/calendar')).toBe('/dashboard/calendar');
27-
});
28-
29-
it(`3. Should not remove the slash if pathname is just "/"`, () => {
30-
expect(removeLastSlash('/')).toBe('/');
31+
test.each(mapWithIndex(cases))('%i. %s', (_i, _desc, input, expected) => {
32+
expect(removeLastSlash(input)).toBe(expected);
3133
});
3234
});
3335

3436
describe('isEqualPath()', () => {
35-
it(`1. Should return true if both paths are equal after removing trailing slashes`, () => {
36-
expect(isEqualPath('/dashboard/calendar/', '/dashboard/calendar/')).toBe(true);
37-
});
37+
const cases = [
38+
['Exact match', '/dashboard', '/dashboard', true],
39+
['Match with trailing slash', '/dashboard/', '/dashboard', true],
40+
['Both paths have trailing slashes', '/dashboard/', '/dashboard/', true],
41+
['Different paths', '/dashboard', '/settings', false],
42+
];
3843

39-
it(`2. Should return true if both paths are equal after removing trailing slashes`, () => {
40-
expect(isEqualPath('/dashboard/calendar', '/dashboard/calendar/')).toBe(true);
44+
test.each(mapWithIndex(cases))('%i. %s', (_i, _desc, a, b, expected) => {
45+
expect(isEqualPath(a, b)).toBe(expected);
4146
});
4247
});
4348

4449
describe('removeParams()', () => {
45-
it(`1. Should remove query parameters from URL`, () => {
46-
expect(removeParams('https://example.com/page?param=value')).toBe('/page');
47-
});
50+
const cases = [
51+
['Removes simple query', '/dashboard?page=1', '/dashboard'],
52+
['Removes complex query', '/dashboard/user?id=123&filter=active', '/dashboard/user'],
53+
['Ignores when no query', '/dashboard/user', '/dashboard/user'],
54+
['Removes query with trailing slash', '/dashboard/user/?id=1', '/dashboard/user'],
55+
['Removes query from full URL', 'https://example.com/page?id=1', '/page'],
56+
];
4857

49-
it(`2. Should return the same URL if there are no query parameters`, () => {
50-
expect(removeParams('https://example.com/page')).toBe('/page');
51-
});
52-
53-
it(`3. Should return the same URL if there are no query parameters`, () => {
54-
expect(removeParams('https://example.com/')).toBe('/');
55-
});
56-
57-
it(`4. Should return the same URL if there are no query parameters`, () => {
58-
expect(removeParams('https://example.com')).toBe('/');
59-
});
60-
61-
it(`5. Should return the same URL if it is invalid`, () => {
62-
expect(removeParams('invalid-url')).toBe('/invalid-url');
58+
test.each(mapWithIndex(cases))('%i. %s', (_i, _desc, input, expected) => {
59+
expect(removeParams(input)).toBe(expected);
6360
});
6461
});
6562

6663
describe('isExternalLink()', () => {
67-
it(`1. Should return ${highlightText.val('true')} if URL is an external link`, () => {
68-
expect(isExternalLink('https://example.com')).toBe(true);
69-
});
70-
71-
it(`2. Should return ${highlightText.val('false')} if URL is not an external link`, () => {
72-
expect(isExternalLink('/internal/page')).toBe(false);
73-
});
74-
75-
it(`3. Should return ${highlightText.val('true')} if URL starts with http`, () => {
76-
expect(isExternalLink('http://example.com')).toBe(true);
64+
const cases = [
65+
['Detects http link', 'http://example.com', true],
66+
['Detects https link', 'https://example.com', true],
67+
['Returns false for local path', '/dashboard', false],
68+
['Returns false for anchor', '#section', false],
69+
['Does not false-positive on "http123"', 'http123', false],
70+
];
71+
72+
test.each(mapWithIndex(cases))('%i. %s', (_i, _desc, input, expected) => {
73+
expect(isExternalLink(input)).toBe(expected);
7774
});
7875
});
Lines changed: 38 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,52 @@
11
/**
22
* Checks if a URL has query parameters.
33
*
4-
* @param {string} url - The URL to check.
5-
* @returns {boolean} - True if the URL has query parameters, false otherwise.
4+
* @param url - The URL to check.
5+
* @returns True if the URL has query parameters, false otherwise.
66
*
77
* @example
8-
* const hasQueryParams = hasParams('https://example.com?page=1');
9-
* console.log(hasQueryParams); // true
8+
* hasParams('https://example.com?page=1'); // true
9+
* hasParams('https://example.com'); // false
1010
*/
11-
12-
export const hasParams = (url: string): boolean => {
13-
const queryString = url.split('?')[1];
14-
return queryString ? new URLSearchParams(queryString).toString().length > 0 : false;
15-
};
11+
export function hasParams(url: string): boolean {
12+
try {
13+
const urlObj = new URL(url, window.location.origin);
14+
return Array.from(urlObj.searchParams.keys()).length > 0;
15+
} catch {
16+
return false;
17+
}
18+
}
1619

1720
// ----------------------------------------------------------------------
1821

1922
/**
20-
* Removes the trailing slash from a pathname if it exists.
23+
* Removes the trailing slash from a pathname if present.
2124
*
22-
* @param {string} pathname - The pathname to process.
23-
* @returns {string} - The pathname without the trailing slash.
25+
* @param pathname - The pathname to process.
26+
* @returns The pathname without the trailing slash.
2427
*
2528
* @example
26-
* const cleanPathname = removeLastSlash('/dashboard/calendar/');
27-
* console.log(cleanPathname); // '/dashboard/calendar'
29+
* removeLastSlash('/dashboard/'); // '/dashboard'
30+
* removeLastSlash('/dashboard'); // '/dashboard'
2831
*/
2932
export function removeLastSlash(pathname: string): string {
30-
/**
31-
* Remove last slash
32-
* [1]
33-
* @input = '/dashboard/calendar/'
34-
* @output = '/dashboard/calendar'
35-
* [2]
36-
* @input = '/dashboard/calendar'
37-
* @output = '/dashboard/calendar'
38-
*/
39-
if (pathname !== '/' && pathname.endsWith('/')) {
40-
return pathname.slice(0, -1);
41-
}
33+
const isValid = pathname !== '/' && pathname.endsWith('/');
4234

43-
return pathname;
35+
return isValid ? pathname.slice(0, -1) : pathname;
4436
}
4537

4638
// ----------------------------------------------------------------------
4739

4840
/**
49-
* Checks if two URLs have the same path.
41+
* Checks if two paths are equal after removing trailing slashes.
42+
*
43+
* @param targetUrl - The target URL to compare.
44+
* @param pathname - The pathname to compare.
45+
* @returns True if the paths are equal, false otherwise.
5046
*
51-
* @param {string} targetUrl - The target URL to compare.
52-
* @param {string} pathname - The pathname to compare.
53-
* @returns {boolean} - True if the paths are equal, false otherwise.
47+
* @example
48+
* isEqualPath('/dashboard/', '/dashboard'); // true
49+
* isEqualPath('/home', '/dashboard'); // false
5450
*/
5551
export function isEqualPath(targetUrl: string, pathname: string): boolean {
5652
return removeLastSlash(targetUrl) === removeLastSlash(pathname);
@@ -59,16 +55,15 @@ export function isEqualPath(targetUrl: string, pathname: string): boolean {
5955
// ----------------------------------------------------------------------
6056

6157
/**
62-
* Removes query parameters from a URL.
58+
* Removes query parameters from a URL and returns only the cleaned pathname.
6359
*
64-
* @param {string} url - The URL to process.
65-
* @returns {string} - The URL without query parameters.
60+
* @param url - The URL to process.
61+
* @returns The pathname without query parameters.
6662
*
6763
* @example
68-
* const cleanUrl = removeParams('https://example.com/page?param=value');
69-
* console.log(cleanUrl); // 'https://example.com/page'
64+
* removeParams('https://example.com/dashboard/user?id=123'); // '/dashboard/user'
65+
* removeParams('/dashboard/user?id=123'); // '/dashboard/user'
7066
*/
71-
7267
export function removeParams(url: string): string {
7368
try {
7469
const urlObj = new URL(url, window.location.origin);
@@ -82,16 +77,15 @@ export function removeParams(url: string): string {
8277
// ----------------------------------------------------------------------
8378

8479
/**
85-
* Checks if a URL is an external link.
80+
* Determines whether a given URL is external (i.e., starts with "http").
8681
*
87-
* @param {string} url - The URL to check.
88-
* @returns {boolean} - True if the URL is an external link, false otherwise.
82+
* @param url - The URL to check.
83+
* @returns True if the URL is external, false otherwise.
8984
*
9085
* @example
91-
* const isExternal = isExternalLink('https://example.com');
92-
* console.log(isExternal); // true
86+
* isExternalLink('https://example.com'); // true
87+
* isExternalLink('/internal'); // false
9388
*/
94-
9589
export function isExternalLink(url: string): boolean {
96-
return url.startsWith('http');
90+
return /^https?:\/\//i.test(url);
9791
}

0 commit comments

Comments
 (0)