@heliosgraphics/utils 6.0.0-alpha.10 → 6.0.0-alpha.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/classnames.ts CHANGED
@@ -2,7 +2,7 @@ export const getClasses = (...args: Array<unknown>): string => {
2
2
  const classNames: Set<string> = new Set()
3
3
 
4
4
  for (const item of args) {
5
- const itemType = typeof item
5
+ const itemType: string = typeof item
6
6
  const isValidString: boolean = itemType === "string" && (item as string).length > 0
7
7
  const isValidObject: boolean = itemType === "object" && item !== null
8
8
 
@@ -17,5 +17,5 @@ export const getClasses = (...args: Array<unknown>): string => {
17
17
  }
18
18
  }
19
19
 
20
- return Array.from(classNames).join(" ")
20
+ return [...classNames].join(" ")
21
21
  }
package/clipboard.ts CHANGED
@@ -1,10 +1,14 @@
1
- export const copyValue = (text: string): void => {
1
+ export const copyValue = async (text: string): Promise<void> => {
2
+ if (navigator?.clipboard?.writeText) {
3
+ await navigator.clipboard.writeText(text)
4
+ return
5
+ }
6
+
2
7
  const input: HTMLTextAreaElement = document.createElement("textarea")
3
8
 
4
9
  document.body.appendChild(input)
5
10
  input.value = text
6
11
  input.select()
7
12
  document.execCommand("copy", false)
8
-
9
- return input.remove()
13
+ input.remove()
10
14
  }
package/colors.ts CHANGED
@@ -1,15 +1,20 @@
1
1
  type TypeRGB = [number, number, number]
2
2
 
3
- export const DEFAULT_PROFILE_RGB: TypeRGB = [199, 201, 209] as const
4
-
5
- export const hexToRgb = (hex?: string | null): TypeRGB => {
3
+ export const hexToRgb = (hex?: string | null): TypeRGB | null => {
6
4
  const isValid: boolean = !!hex && typeof hex === "string"
7
5
 
8
- if (!isValid || !hex) return DEFAULT_PROFILE_RGB
6
+ if (!isValid || !hex) return null
9
7
 
10
8
  hex = hex.replace(/^#/, "")
11
9
 
12
- const bigint = parseInt(hex, 16)
10
+ if (hex.length === 3) {
11
+ hex = hex
12
+ .split("")
13
+ .map((c: string) => c + c)
14
+ .join("")
15
+ }
16
+
17
+ const bigint: number = parseInt(hex, 16)
13
18
  const r: number = (bigint >> 16) & 255
14
19
  const g: number = (bigint >> 8) & 255
15
20
  const b: number = bigint & 255
@@ -19,12 +24,14 @@ export const hexToRgb = (hex?: string | null): TypeRGB => {
19
24
 
20
25
  export const rgbToHex = (r: number | string = 255, g: number | string = 255, b: number | string = 255): string => {
21
26
  const _toHex = (c: unknown): string => {
22
- const value: number = Number(c)
27
+ if (c === null || c === undefined) return "ff"
28
+
29
+ const value: number = Math.round(Number(c))
23
30
  const isInvalid: boolean = isNaN(value) || value < 0 || value > 255
24
31
 
25
32
  if (isInvalid) return "ff"
26
33
 
27
- const hex = value.toString(16)
34
+ const hex: string = value.toString(16)
28
35
 
29
36
  return hex.length === 1 ? `0${hex}` : hex
30
37
  }
package/debounce.ts CHANGED
@@ -1,15 +1,29 @@
1
+ interface DebouncedFunction<T extends (...args: Array<unknown>) => unknown> {
2
+ (...args: Parameters<T>): void
3
+ cancel: () => void
4
+ }
5
+
1
6
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
2
- export const debounce = <T extends (...args: Array<any>) => any>(
3
- callback: T,
4
- wait: number,
5
- ): ((...args: Parameters<T>) => void) => {
6
- let timeoutId: ReturnType<typeof setTimeout>
7
+ export const debounce = <T extends (...args: Array<any>) => any>(callback: T, wait: number): DebouncedFunction<T> => {
8
+ let timeoutId: ReturnType<typeof setTimeout> | null = null
7
9
 
8
- return (...args: Parameters<T>): void => {
9
- globalThis.clearTimeout(timeoutId)
10
+ const debounced: DebouncedFunction<T> = (...args: Parameters<T>): void => {
11
+ if (timeoutId !== null) {
12
+ globalThis.clearTimeout(timeoutId)
13
+ }
10
14
 
11
15
  timeoutId = globalThis.setTimeout(() => {
12
16
  callback.apply(null, args)
17
+ timeoutId = null
13
18
  }, wait)
14
19
  }
20
+
21
+ debounced.cancel = (): void => {
22
+ if (timeoutId !== null) {
23
+ globalThis.clearTimeout(timeoutId)
24
+ timeoutId = null
25
+ }
26
+ }
27
+
28
+ return debounced
15
29
  }
package/equals.ts CHANGED
@@ -1,19 +1,15 @@
1
1
  type FracturesPrimitive = string | number | boolean | null | undefined | bigint | symbol
2
2
  type FracturesComparable = FracturesPrimitive | object | Array<unknown> | Set<unknown> | Map<unknown, unknown>
3
3
 
4
- export const _getAreMapsEqual = <K, V>(
5
- mapA: Map<K, V>,
6
- mapB: Map<K, V>,
7
- seen = new WeakMap<object, object>(),
8
- ): boolean => {
4
+ const _getAreMapsEqual = <K, V>(mapA: Map<K, V>, mapB: Map<K, V>, seen = new WeakMap<object, object>()): boolean => {
9
5
  if (mapA.size !== mapB.size) return false
10
6
  if (mapA === mapB) return true
11
7
 
12
- const entriesA = Array.from(mapA.entries())
13
- const entriesB = new Map(mapB)
8
+ const entriesA: Array<[K, V]> = Array.from(mapA.entries())
9
+ const entriesB: Map<K, V> = new Map(mapB)
14
10
 
15
11
  return entriesA.every(
16
- ([key, value]) =>
12
+ ([key, value]: [K, V]) =>
17
13
  entriesB.has(key) && _getIsEqual(value as FracturesComparable, entriesB.get(key) as FracturesComparable, seen),
18
14
  )
19
15
  }
@@ -22,21 +18,21 @@ export const getAreDatesEqual = (a: Date, b: Date): boolean => a.getTime() === b
22
18
  export const getAreRegExpsEqual = (a: RegExp, b: RegExp): boolean => a.toString() === b.toString()
23
19
  export const getAreErrorsEqual = (a: Error, b: Error): boolean =>
24
20
  a.message === b.message && a.name === b.name && a.stack === b.stack
25
- export const getAreBuffersEqual = (a: Buffer, b: Buffer): boolean => a.length === b.length && Buffer.compare(a, b) === 0
21
+ export const getAreBuffersEqual = (a: Buffer, b: Buffer): boolean => Buffer.compare(a, b) === 0
26
22
 
27
23
  export const getAreSetsEqual = <T>(setA: Set<T>, setB: Set<T>): boolean => {
28
24
  if (setA.size !== setB.size) return false
29
25
  if (setA === setB) return true
30
26
  if (setA.size === 0) return true
31
27
 
32
- const arrA = Array.from(setA)
33
- const arrB = Array.from(setB)
34
- const matched = new Array(arrB.length).fill(false)
28
+ const arrA: Array<T> = Array.from(setA)
29
+ const arrB: Array<T> = Array.from(setB)
30
+ const matched: Array<boolean> = new Array(arrB.length).fill(false)
35
31
 
36
- for (let i = 0; i < arrA.length; i++) {
37
- let foundMatch = false
32
+ for (let i: number = 0; i < arrA.length; i++) {
33
+ let foundMatch: boolean = false
38
34
 
39
- for (let j = 0; j < arrB.length; j++) {
35
+ for (let j: number = 0; j < arrB.length; j++) {
40
36
  if (!matched[j] && _getIsEqual(arrA[i] as FracturesComparable, arrB[j] as FracturesComparable)) {
41
37
  matched[j] = true
42
38
  foundMatch = true
@@ -50,31 +46,15 @@ export const getAreSetsEqual = <T>(setA: Set<T>, setB: Set<T>): boolean => {
50
46
  return true
51
47
  }
52
48
 
53
- export const _getIsEqual = <T extends FracturesComparable>(
54
- a: T,
55
- b: T,
56
- seen = new WeakMap<object, object>(),
57
- ): boolean => {
49
+ const _getIsEqual = <T extends FracturesComparable>(a: T, b: T, seen = new WeakMap<object, object>()): boolean => {
58
50
  if (a === b) return true
59
- if (a === null || b === null) return a === b
51
+ if (a === null || b === null) return false
60
52
 
61
- if (typeof a !== "object" && typeof b !== "object") {
62
- return a === b
63
- }
64
-
65
- if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) {
66
- return false
67
- }
68
-
69
- if (Object.is(a, b)) return true
70
-
71
- const typeA = typeof a
72
- const typeB = typeof b
53
+ if (typeof a !== "object" || typeof b !== "object") return false
73
54
 
74
- if (typeA !== typeB) return false
75
- if (typeA !== "object") return false
55
+ if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) return false
76
56
 
77
- const seenA = seen.get(a as object)
57
+ const seenA: object | undefined = seen.get(a as object)
78
58
  if (seenA) return seenA === b
79
59
 
80
60
  seen.set(a as object, b as object)
@@ -82,7 +62,7 @@ export const _getIsEqual = <T extends FracturesComparable>(
82
62
  if (Array.isArray(a) && Array.isArray(b)) {
83
63
  if (a.length !== b.length) return false
84
64
 
85
- for (let i = 0; i < a.length; i++) {
65
+ for (let i: number = 0; i < a.length; i++) {
86
66
  if (!_getIsEqual(a[i], b[i], seen)) return false
87
67
  }
88
68
  return true
@@ -95,13 +75,13 @@ export const _getIsEqual = <T extends FracturesComparable>(
95
75
  if (a instanceof RegExp && b instanceof RegExp) return getAreRegExpsEqual(a, b)
96
76
  if (Buffer.isBuffer(a) && Buffer.isBuffer(b)) return getAreBuffersEqual(a, b)
97
77
 
98
- const keysA = [...Object.keys(a as object), ...Object.getOwnPropertySymbols(a as object)]
99
- const keysB = [...Object.keys(b as object), ...Object.getOwnPropertySymbols(b as object)]
78
+ const keysA: Array<string | symbol> = [...Object.keys(a as object), ...Object.getOwnPropertySymbols(a as object)]
79
+ const keysB: Array<string | symbol> = [...Object.keys(b as object), ...Object.getOwnPropertySymbols(b as object)]
100
80
 
101
81
  if (keysA.length !== keysB.length) return false
102
82
 
103
83
  return keysA.every(
104
- (key) =>
84
+ (key: string | symbol) =>
105
85
  Object.prototype.hasOwnProperty.call(b, key) &&
106
86
  _getIsEqual(
107
87
  (a as Record<string | symbol, unknown>)[key] as FracturesComparable,
@@ -115,6 +95,6 @@ export const getIsEqual = <T extends FracturesComparable>(a: T, b: T): boolean =
115
95
  return _getIsEqual(a, b)
116
96
  }
117
97
 
118
- export const getAreMapsEqual = <T extends FracturesComparable>(a: T, b: T): boolean => {
119
- return _getAreMapsEqual(a as Map<unknown, unknown>, b as Map<unknown, unknown>)
98
+ export const getAreMapsEqual = <K, V>(a: Map<K, V>, b: Map<K, V>): boolean => {
99
+ return _getAreMapsEqual(a, b)
120
100
  }
package/index.ts CHANGED
@@ -17,4 +17,4 @@ export { removeMarkdown, removeNewlines, ellipsis } from "./strings"
17
17
  export { sanitizeText } from "./sanitize"
18
18
  export { throttle } from "./throttle"
19
19
  export { isUUID, getUUID } from "./uuid"
20
- export { validateUrl, validateEmail } from "./validations"
20
+ export { validateHttpsUrl, validateEmail } from "./validations"
package/package.json CHANGED
@@ -1,14 +1,32 @@
1
1
  {
2
2
  "name": "@heliosgraphics/utils",
3
- "version": "6.0.0-alpha.10",
3
+ "version": "6.0.0-alpha.12",
4
4
  "type": "module",
5
+ "sideEffects": false,
5
6
  "author": "Chris Puska <chris@puska.org>",
6
7
  "license": "MIT",
7
8
  "private": false,
8
9
  "description": "Helios Utils",
9
10
  "main": "index.ts",
10
- "dependencies": {
11
- "@types/node": "^24"
11
+ "types": "index.ts",
12
+ "exports": {
13
+ ".": "./index.ts",
14
+ "./*": "./*.ts"
12
15
  },
13
- "devDependencies": {}
16
+ "files": [
17
+ "*.ts",
18
+ "!*.spec.ts"
19
+ ],
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/heliosgraphics/helios-ui.git",
23
+ "directory": "packages/utils"
24
+ },
25
+ "homepage": "https://github.com/heliosgraphics/helios-ui/tree/main/packages/utils",
26
+ "bugs": {
27
+ "url": "https://github.com/heliosgraphics/helios-ui/issues"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^25"
31
+ }
14
32
  }
package/sanitize.ts CHANGED
@@ -209,7 +209,7 @@ export const sanitizeText = (input: string = ""): string => {
209
209
  })
210
210
 
211
211
  // Additional targeted cleanup for specific dangerous contexts
212
- sanitized = sanitized.replace(/\balert\s*(?=[,\)])/gi, "")
212
+ sanitized = sanitized.replace(/\balert\s*(?=[,)])/gi, "")
213
213
  sanitized = sanitized.replace(/\bjavascript\s*(?=:)/gi, "")
214
214
 
215
215
  // Remove any remaining backticks
package/strings.ts CHANGED
@@ -33,7 +33,7 @@ export const removeNewlines = (text?: string | null, limit?: number): string =>
33
33
  export const ellipsis = (text: string, maxLength: number, position: "end" | "middle" = "end"): string => {
34
34
  if (!text || typeof text !== "string") return ""
35
35
  if (text.length <= maxLength) return text
36
- if (maxLength < 4) return ""
36
+ if (maxLength < 4) return text
37
37
 
38
38
  if (position === "middle") {
39
39
  const halfLength = Math.floor((maxLength - 3) / 2)
package/throttle.ts CHANGED
@@ -1,11 +1,13 @@
1
+ interface ThrottledFunction<T extends (...args: Array<unknown>) => unknown> {
2
+ (...args: Parameters<T>): void
3
+ cancel: () => void
4
+ }
5
+
1
6
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
2
- export const throttle = <T extends (...args: Array<any>) => any>(
3
- fn: T,
4
- delay: number,
5
- ): ((...args: Parameters<T>) => void) => {
7
+ export const throttle = <T extends (...args: Array<any>) => any>(fn: T, delay: number): ThrottledFunction<T> => {
6
8
  let timeoutId: ReturnType<typeof setTimeout> | null = null
7
9
 
8
- return (...args: Parameters<T>): void => {
10
+ const throttled: ThrottledFunction<T> = (...args: Parameters<T>): void => {
9
11
  if (!timeoutId) {
10
12
  fn(...args)
11
13
 
@@ -14,4 +16,13 @@ export const throttle = <T extends (...args: Array<any>) => any>(
14
16
  }, delay)
15
17
  }
16
18
  }
19
+
20
+ throttled.cancel = (): void => {
21
+ if (timeoutId !== null) {
22
+ clearTimeout(timeoutId)
23
+ timeoutId = null
24
+ }
25
+ }
26
+
27
+ return throttled
17
28
  }
package/uuid.ts CHANGED
@@ -1,14 +1,13 @@
1
- export const getUUID = (id?: unknown): string => {
2
- if (id) return id as string
1
+ export const getUUID = (id?: string): string => {
2
+ if (id !== undefined) return id
3
3
 
4
4
  return crypto.randomUUID()
5
5
  }
6
6
 
7
- export const isUUID = (uuid?: unknown): boolean => {
8
- const uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/
9
- const isValid = typeof uuid === "string"
7
+ const UUID_REGEX: RegExp = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/
10
8
 
11
- if (!isValid) return false
9
+ export const isUUID = (uuid?: unknown): uuid is string => {
10
+ if (typeof uuid !== "string") return false
12
11
 
13
- return uuidRegex.test(uuid)
12
+ return UUID_REGEX.test(uuid)
14
13
  }
package/validations.ts CHANGED
@@ -1,4 +1,4 @@
1
- export const validateUrl = (text?: string | null): boolean => {
1
+ export const validateHttpsUrl = (text?: string | null): boolean => {
2
2
  let url: URL
3
3
 
4
4
  try {
@@ -1,17 +0,0 @@
1
- import { it, describe, expect } from "vitest"
2
- import { getClasses } from "./classnames"
3
-
4
- describe("classnames", () => {
5
- describe("getClasses", () => {
6
- it("returns valid with string", () => expect(getClasses("burn")).toEqual("burn"))
7
- it("returns conditional object", () =>
8
- expect(getClasses("burn", { "burn--alternative": true })).toEqual("burn burn--alternative"))
9
- it("returns valid with invalid", () =>
10
- expect(
11
- getClasses("burn", null, "burn", undefined, {
12
- "burn--active": false,
13
- "burn--loading": true,
14
- }),
15
- ).toEqual("burn burn--loading"))
16
- })
17
- })
package/clipboard.spec.ts DELETED
@@ -1,40 +0,0 @@
1
- import { describe, it, expect, vi } from "vitest"
2
- import { copyValue } from "./clipboard"
3
-
4
- const mockDocument = {
5
- createElement: vi.fn(),
6
- execCommand: vi.fn(),
7
- body: {
8
- appendChild: vi.fn(),
9
- },
10
- } as unknown as Document
11
-
12
- global.document = mockDocument
13
-
14
- describe("copyValue", () => {
15
- const TEXT_STRING = "Test text" as const
16
-
17
- it("copies text to the clipboard", () => {
18
- const textAreaMock: {
19
- value: string
20
- select: () => void
21
- remove: () => void
22
- } = {
23
- value: "",
24
- select: vi.fn(),
25
- remove: vi.fn(),
26
- }
27
-
28
- document.createElement = vi.fn().mockReturnValue(textAreaMock)
29
- document.body.appendChild = vi.fn().mockReturnValue(textAreaMock)
30
-
31
- copyValue(TEXT_STRING)
32
-
33
- expect(document.createElement).toHaveBeenCalledWith("textarea")
34
- expect(document.body.appendChild).toHaveBeenCalled()
35
- expect(document.execCommand).toHaveBeenCalledWith("copy", false)
36
- expect(textAreaMock.value).toBe(TEXT_STRING)
37
- expect(textAreaMock.select).toHaveBeenCalled()
38
- expect(textAreaMock.remove).toHaveBeenCalled()
39
- })
40
- })
package/colors.spec.ts DELETED
@@ -1,40 +0,0 @@
1
- import { it, describe, expect } from "vitest"
2
- import { rgbToHex, hexToRgb, DEFAULT_PROFILE_RGB } from "./colors"
3
-
4
- describe("colors", () => {
5
- describe("hexToRgb", () => {
6
- it("converts hex to rgb", () => expect(hexToRgb("#0c2c78")).toEqual([12, 44, 120]))
7
- it("converts hex without #", () => expect(hexToRgb("0c2c78")).toEqual([12, 44, 120]))
8
- it("converts uppercase hex", () => expect(hexToRgb("#0C2C78")).toEqual([12, 44, 120]))
9
- it("converts short hex", () => expect(hexToRgb("#fff")).toEqual([0, 15, 255]))
10
- it("converts black", () => expect(hexToRgb("#000000")).toEqual([0, 0, 0]))
11
- it("converts white", () => expect(hexToRgb("#ffffff")).toEqual([255, 255, 255]))
12
- it("returns default for 0", () => expect(hexToRgb(0 as unknown as string)).toEqual(DEFAULT_PROFILE_RGB))
13
- it("returns default for undefined", () => expect(hexToRgb(undefined)).toEqual(DEFAULT_PROFILE_RGB))
14
- it("returns default for null", () => expect(hexToRgb(null)).toEqual(DEFAULT_PROFILE_RGB))
15
- it("returns default for empty string", () => expect(hexToRgb("")).toEqual(DEFAULT_PROFILE_RGB))
16
- it("returns default for invalid hex", () => expect(hexToRgb("gggggg")).toEqual([0, 0, 0]))
17
- it("returns default for non-string", () => expect(hexToRgb(123 as unknown as string)).toEqual(DEFAULT_PROFILE_RGB))
18
- })
19
-
20
- describe("rgbToHex", () => {
21
- it("converts rgb to hex", () => expect(rgbToHex(12, 44, 120)).toEqual("#0c2c78"))
22
- it("converts string to hex", () =>
23
- expect(rgbToHex("12" as unknown as number, "44" as unknown as number, "120" as unknown as number)).toEqual(
24
- "#0c2c78",
25
- ))
26
- it("converts null to hex", () => expect(rgbToHex(null as unknown as number, 44, 120)).toEqual("#002c78"))
27
- it("returns undefined to hex", () => expect(rgbToHex(12, undefined as unknown as number, 120)).toEqual("#0cff78"))
28
- it("returns FF for negative", () => expect(rgbToHex(-1, -12, 120)).toEqual("#ffff78"))
29
- it("converts zero values", () => expect(rgbToHex(0, 0, 0)).toEqual("#000000"))
30
- it("converts max values", () => expect(rgbToHex(255, 255, 255)).toEqual("#ffffff"))
31
- it("returns FF for over 255", () => expect(rgbToHex(300, 256, 120)).toEqual("#ffff78"))
32
- it("returns FF for invalid string", () => expect(rgbToHex("abc" as unknown as number, 44, 120)).toEqual("#ff2c78"))
33
- it("handles fractional numbers", () =>
34
- expect(rgbToHex(12.9, 44.1, 120.5)).toEqual("#c.e6666666666682c.19999999999a78.8"))
35
- it("uses defaults when no params", () => expect(rgbToHex()).toEqual("#ffffff"))
36
- it("handles mixed valid/invalid", () =>
37
- expect(rgbToHex(12, "invalid" as unknown as number, 120)).toEqual("#0cff78"))
38
- it("converts single digit to padded hex", () => expect(rgbToHex(1, 2, 3)).toEqual("#010203"))
39
- })
40
- })
package/debounce.spec.ts DELETED
@@ -1,101 +0,0 @@
1
- import { describe, it, expect, vi } from "vitest"
2
- import { debounce } from "./debounce"
3
- import { sleep } from "./sleep"
4
-
5
- describe("debounce", () => {
6
- it("calls the callback after the specified time", async () => {
7
- const callback = vi.fn()
8
- const debouncedFunction = debounce(callback, 50)
9
-
10
- debouncedFunction()
11
-
12
- await sleep(25)
13
- expect(callback).not.toHaveBeenCalled()
14
-
15
- await sleep(30)
16
- expect(callback).toHaveBeenCalledTimes(1)
17
- })
18
-
19
- it("does not call the callback if the function is called again within the wait time", async () => {
20
- const callback = vi.fn()
21
- const debouncedFunction = debounce(callback, 50)
22
-
23
- debouncedFunction()
24
- await sleep(25)
25
-
26
- debouncedFunction()
27
- await sleep(25)
28
-
29
- expect(callback).not.toHaveBeenCalled()
30
-
31
- await sleep(30)
32
- expect(callback).toHaveBeenCalledTimes(1)
33
- })
34
-
35
- it("preserves function arguments", async () => {
36
- const callback = vi.fn()
37
- const debouncedFunction = debounce(callback, 50)
38
-
39
- debouncedFunction("arg1", "arg2", 123)
40
-
41
- await sleep(60)
42
- expect(callback).toHaveBeenCalledWith("arg1", "arg2", 123)
43
- })
44
-
45
- it("uses the latest arguments when called multiple times", async () => {
46
- const callback = vi.fn()
47
- const debouncedFunction = debounce(callback, 50)
48
-
49
- debouncedFunction("first")
50
- await sleep(25)
51
-
52
- debouncedFunction("second")
53
- await sleep(60)
54
-
55
- expect(callback).toHaveBeenCalledTimes(1)
56
- expect(callback).toHaveBeenCalledWith("second")
57
- })
58
-
59
- it("works with functions that have specific parameter types", async () => {
60
- const callback = vi.fn((str: string, num: number) => str + num)
61
- const debouncedFunction = debounce(callback, 50)
62
-
63
- debouncedFunction("test", 42)
64
-
65
- await sleep(60)
66
- expect(callback).toHaveBeenCalledWith("test", 42)
67
- })
68
-
69
- it("works with functions that return values", async () => {
70
- const callback = vi.fn((x: number) => x * 2)
71
- const debouncedFunction = debounce(callback, 50)
72
-
73
- debouncedFunction(5)
74
-
75
- await sleep(60)
76
- expect(callback).toHaveBeenCalledWith(5)
77
- })
78
-
79
- it("works with zero wait time", async () => {
80
- const callback = vi.fn()
81
- const debouncedFunction = debounce(callback, 0)
82
-
83
- debouncedFunction()
84
-
85
- await sleep(1)
86
- expect(callback).toHaveBeenCalledTimes(1)
87
- })
88
-
89
- it("can be called after completion", async () => {
90
- const callback = vi.fn()
91
- const debouncedFunction = debounce(callback, 50)
92
-
93
- debouncedFunction()
94
- await sleep(60)
95
- expect(callback).toHaveBeenCalledTimes(1)
96
-
97
- debouncedFunction()
98
- await sleep(60)
99
- expect(callback).toHaveBeenCalledTimes(2)
100
- })
101
- })