Skip to content

Commit c5bdeca

Browse files
add a set of new utilities
1 parent 26a7ca0 commit c5bdeca

File tree

5 files changed

+148
-15
lines changed

5 files changed

+148
-15
lines changed

src/helpers/helpers.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
getShortcutLabel,
88
isCuid,
99
isTruthy,
10+
joinAsSentence,
1011
range,
1112
slugify,
1213
splitArrayChunks,
@@ -154,3 +155,21 @@ describe("splitArrayChunks", () => {
154155
expect(splitArrayChunks([], 3)).toEqual([])
155156
})
156157
})
158+
159+
describe("joinAsSentence", () => {
160+
it("joins an array of strings into a sentence", () => {
161+
expect(joinAsSentence(["apple"])).toEqual("apple")
162+
expect(joinAsSentence(["apple", "banana"])).toEqual("apple and banana")
163+
expect(joinAsSentence(["apple", "banana", "cherry"])).toEqual("apple, banana and cherry")
164+
})
165+
166+
it("joins an array of strings into a sentence with a custom last item", () => {
167+
expect(joinAsSentence(["apple", "banana", "cherry"], undefined, "or")).toEqual(
168+
"apple, banana or cherry",
169+
)
170+
})
171+
172+
it("joins an array with custom max items", () => {
173+
expect(joinAsSentence(["apple", "banana", "cherry"], 2)).toEqual("apple and banana")
174+
})
175+
})

src/helpers/helpers.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,17 @@ export const setInputValue = (
183183
// Trigger a change event if the value was changed
184184
triggerChange && input?.dispatchEvent(new Event("input", { bubbles: true }))
185185
}
186+
187+
/**
188+
* Joins an array of strings into a sentence, with a maximum of 3 items.
189+
*
190+
* @param items The array of strings to be joined.
191+
* @param maxItems The maximum number of items to include in the sentence.
192+
* @returns The joined sentence.
193+
*/
194+
export const joinAsSentence = (items: string[], maxItems = 3, lastItem = "and") => {
195+
return items
196+
.slice(0, maxItems)
197+
.join(", ")
198+
.replace(/, ([^,]*)$/, ` ${lastItem} $1`)
199+
}

src/http/http.test.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import { describe, expect, it } from "bun:test"
22

3-
import { addHttp, isExternalLink, removeHttp, removeTrailingSlash } from "./http"
3+
import {
4+
addHttp,
5+
getUrlHostname,
6+
isExternalLink,
7+
isValidUrl,
8+
removeHttp,
9+
removeTrailingSlash,
10+
stripURLSubpath,
11+
} from "./http"
412

513
describe("addHttp", () => {
614
it("adds https protocol to url", () => {
@@ -77,3 +85,48 @@ describe("isExternalLink", () => {
7785
expect(isExternalLink()).toBe(false)
7886
})
7987
})
88+
89+
describe("stripURLSubpath", () => {
90+
it("returns the original URL if it's not a valid URL", () => {
91+
const url = "not a URL"
92+
expect(stripURLSubpath(url)).toBe(url)
93+
})
94+
95+
it("returns stripped URL", () => {
96+
const url = "https://example.com/path/to/resource"
97+
expect(stripURLSubpath(url)).toBe("https://example.com")
98+
})
99+
})
100+
101+
describe("getUrlHostname", () => {
102+
it("returns the hostname for a valid URL", () => {
103+
const url = "https://example.com"
104+
expect(getUrlHostname(url)).toBe("example.com")
105+
})
106+
107+
it("returns the original URL if it's not a valid URL", () => {
108+
const url = "not a URL"
109+
expect(getUrlHostname(url)).toBe(url)
110+
})
111+
112+
it("returns the original URL if it's not a valid URL", () => {
113+
const url = "not a URL"
114+
expect(getUrlHostname(url)).toBe(url)
115+
})
116+
})
117+
118+
describe("isValidUrl", () => {
119+
it("returns true for valid URL", () => {
120+
const url = "https://example.com"
121+
expect(isValidUrl(url)).toBe(true)
122+
})
123+
124+
it("returns false for invalid URL", () => {
125+
const url = "not a URL"
126+
expect(isValidUrl(url)).toBe(false)
127+
})
128+
129+
it("returns false for empty input", () => {
130+
expect(isValidUrl()).toBe(false)
131+
})
132+
})

src/http/http.ts

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,83 @@
55
/**
66
* Adds the appropriate protocol (http or https) to the given URL
77
* based on whether the URL contains the string 'localhost'.
8-
* @param {string} url - The URL to modify.
9-
* @returns {string} A new URL string with the appropriate protocol added.
8+
* @param url - The URL to modify.
9+
* @returns A new URL string with the appropriate protocol added.
1010
*/
1111
export const addHttp = (url?: string): string => {
1212
return url ? `${url.includes("localhost") ? "http" : "https"}://${url}` : ""
1313
}
1414

1515
/**
1616
* Removes the protocol from the given URL string.
17-
* @param {string} url - The URL string to modify.
18-
* @returns {string} A new URL string with the protocol removed.
17+
* @param url - The URL string to modify.
18+
* @returns A new URL string with the protocol removed.
1919
*/
2020
export const removeHttp = (url?: string): string => {
2121
return url ? url.replace(/^https?:\/\//, "") : ""
2222
}
2323

2424
/**
2525
* Removes the trailing slash from the given URL string.
26-
* @param {string} url - The URL string to modify.
27-
* @returns {string} A new URL string with the trailing slash removed.
26+
* @param url - The URL string to modify.
27+
* @returns A new URL string with the trailing slash removed.
2828
*/
2929
export const removeTrailingSlash = (url?: string): string => {
3030
return url ? url.replace(/\/$/, "") : ""
3131
}
3232

3333
/**
3434
* Checks if the given URL is an external link.
35-
* @param {string} url - The URL to check.
36-
* @returns {boolean} A boolean indicating whether the URL is an external link.
35+
* @param url - The URL to check.
36+
* @returns A boolean indicating whether the URL is an external link.
3737
*/
3838
export const isExternalLink = (url?: string): boolean => {
3939
return url ? url.includes("http") : false
4040
}
41+
42+
/**
43+
* Strips the subpath from a URL, returning only the protocol and host.
44+
*
45+
* @param url The URL to be stripped.
46+
* @returns The URL with the subpath removed.
47+
*/
48+
export const stripURLSubpath = (url?: string) => {
49+
if (!url) return url
50+
51+
try {
52+
const parsedUrl = new URL(url)
53+
return `${parsedUrl.protocol}//${parsedUrl.host}`
54+
} catch (error) {
55+
// If the URL is invalid, return the original string
56+
return url
57+
}
58+
}
59+
60+
/**
61+
* Returns the hostname of the given URL.
62+
* @param url - The URL to get the hostname from.
63+
* @returns The hostname of the URL.
64+
*/
65+
export const getUrlHostname = (url: string) => {
66+
if (isValidUrl(url)) {
67+
return new URL(url).hostname
68+
}
69+
70+
return url
71+
}
72+
73+
/**
74+
* Checks if the given URL is valid.
75+
* @param url - The URL to check.
76+
* @returns A boolean indicating whether the URL is valid.
77+
*/
78+
export const isValidUrl = (url?: string) => {
79+
if (!url) return false
80+
81+
try {
82+
new URL(url)
83+
return true
84+
} catch (e) {
85+
return false
86+
}
87+
}

src/params/params.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ export type GetPageParams<T> = T & {
1313

1414
/**
1515
* Returns an object containing the search parameters from a request URL.
16-
* @param request - The request object.
16+
* @param url - The request url.
1717
* @returns An object containing the search parameters.
1818
*/
19-
export const getSearchParams = (request: Request) => {
20-
return Object.fromEntries(new URL(request.url).searchParams)
19+
export const getSearchParams = (url: string) => {
20+
return Object.fromEntries(new URL(url).searchParams)
2121
}
2222

2323
/**
@@ -32,12 +32,12 @@ export const getCurrentPage = (page?: string | null) => {
3232
/**
3333
* Returns an object containing the parameters for a paginated query.
3434
* @template T - The type of the query parameters.
35-
* @param request - The request object.
35+
* @param url - The URL to get the page parameters from.
3636
* @param take - The number of items to take per page.
3737
* @returns An object containing the parameters for a paginated query.
3838
*/
39-
export const getPageParams = <T extends object>(request: Request, take: number) => {
40-
const { page, ...params } = getSearchParams(request)
39+
export const getPageParams = <T extends object>(url: string, take: number) => {
40+
const { page, ...params } = getSearchParams(url)
4141

4242
const currentPage = getCurrentPage(page)
4343
const skip = (currentPage - 1) * take

0 commit comments

Comments
 (0)