diff --git a/apps/web/src/lib/utils.ts b/apps/web/src/lib/utils.ts index 365058ce..3f826293 100644 --- a/apps/web/src/lib/utils.ts +++ b/apps/web/src/lib/utils.ts @@ -4,3 +4,12 @@ import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } + +export function generateId(): string { + const bytes = new Uint8Array(16); + crypto.getRandomValues(bytes); + bytes[6] = (bytes[6] & 0x0f) | 0x40; + bytes[8] = (bytes[8] & 0x3f) | 0x80; + const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join(""); + return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`; +} diff --git a/tests/unit/web/utils.test.ts b/tests/unit/web/utils.test.ts new file mode 100644 index 00000000..d85185c6 --- /dev/null +++ b/tests/unit/web/utils.test.ts @@ -0,0 +1,15 @@ +// @vitest-environment jsdom +import { describe, expect, it } from "vitest"; +import { generateId } from "../../../apps/web/src/lib/utils"; + +describe("generateId", () => { + it("returns a valid UUID v4 string", () => { + const id = generateId(); + expect(id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/); + }); + + it("returns unique values on successive calls", () => { + const ids = new Set(Array.from({ length: 100 }, () => generateId())); + expect(ids.size).toBe(100); + }); +});