import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; import { uuid } from "./uuid.js"; // RFC 4122 v4 UUID pattern const UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; describe("uuid", () => { // --------------------------------------------------------------------------- // Shape and format // --------------------------------------------------------------------------- it("returns a string", () => { expect(typeof uuid()).toBe("string"); }); it("matches the RFC 4122 v4 UUID format", () => { expect(uuid()).toMatch(UUID_V4_REGEX); }); it("has the correct length (36 characters)", () => { expect(uuid()).toHaveLength(36); }); it("contains hyphens at positions 8, 13, 18, and 23", () => { const id = uuid(); expect(id[8]).toBe("-"); expect(id[13]).toBe("-"); expect(id[18]).toBe("-"); expect(id[23]).toBe("-"); }); it("has '4' as the version digit at position 14", () => { expect(uuid()[14]).toBe("4"); }); it("has a valid variant digit at position 19 (8, 9, a, or b)", () => { const variantChar = uuid()[19]!; expect(["8", "9", "a", "b"]).toContain(variantChar.toLowerCase()); }); // --------------------------------------------------------------------------- // Uniqueness // --------------------------------------------------------------------------- it("generates unique values on successive calls", () => { const ids = new Set(Array.from({ length: 1000 }, () => uuid())); expect(ids.size).toBe(1000); }); // --------------------------------------------------------------------------- // Fallback path (crypto.randomUUID not available) // --------------------------------------------------------------------------- describe("fallback when crypto.randomUUID is unavailable", () => { let originalCrypto: Crypto; beforeEach(() => { originalCrypto = globalThis.crypto; }); afterEach(() => { Object.defineProperty(globalThis, "crypto", { value: originalCrypto, configurable: true, writable: true, }); }); it("falls back to Math.random-based generation when randomUUID is missing", () => { // Stub crypto so that randomUUID is absent Object.defineProperty(globalThis, "crypto", { value: {}, configurable: true, writable: true, }); const id = uuid(); expect(id).toMatch(UUID_V4_REGEX); }); it("fallback still produces unique values", () => { Object.defineProperty(globalThis, "crypto", { value: {}, configurable: true, writable: true, }); const ids = new Set(Array.from({ length: 500 }, () => uuid())); expect(ids.size).toBe(500); }); it("falls back when crypto is undefined", () => { Object.defineProperty(globalThis, "crypto", { value: undefined, configurable: true, writable: true, }); const id = uuid(); expect(id).toMatch(UUID_V4_REGEX); }); }); // --------------------------------------------------------------------------- // Native path (crypto.randomUUID explicitly available) // --------------------------------------------------------------------------- describe("native crypto.randomUUID path", () => { it("delegates to crypto.randomUUID when it is available", () => { const spy = vi.spyOn(globalThis.crypto, "randomUUID"); uuid(); expect(spy).toHaveBeenCalledOnce(); spy.mockRestore(); }); }); });