import { describe, it, expect } from "bun:test"; import { DefaultHashFunction } from "../src/hash-functions/DefaultHashFunction.ts"; import { NumericHashFunction } from "../src/hash-functions/NumericHashFunction.ts"; describe("DefaultHashFunction", () => { const hashFn = new DefaultHashFunction(); const capacity = 16; describe("basic types", () => { it("should hash string keys", () => { const hash1 = hashFn.hash("hello", capacity); const hash2 = hashFn.hash("world", capacity); expect(hash1).toBeGreaterThanOrEqual(0); expect(hash1).toBeLessThan(capacity); expect(hash2).toBeGreaterThanOrEqual(0); expect(hash2).toBeLessThan(capacity); }); it("should hash number keys", () => { const hash1 = hashFn.hash(42, capacity); const hash2 = hashFn.hash(3.14, capacity); const hash3 = hashFn.hash(0, capacity); const hash4 = hashFn.hash(-10, capacity); expect(hash1).toBeGreaterThanOrEqual(0); expect(hash1).toBeLessThan(capacity); expect(hash2).toBeGreaterThanOrEqual(0); expect(hash3).toBeGreaterThanOrEqual(0); expect(hash4).toBeGreaterThanOrEqual(0); }); it("should hash boolean keys", () => { const hashTrue = hashFn.hash(true, capacity); const hashFalse = hashFn.hash(false, capacity); expect(hashTrue).toBeGreaterThanOrEqual(0); expect(hashTrue).toBeLessThan(capacity); expect(hashFalse).toBeGreaterThanOrEqual(0); expect(hashFalse).toBeLessThan(capacity); }); it("should hash null", () => { const hash = hashFn.hash(null, capacity); expect(hash).toBeGreaterThanOrEqual(0); expect(hash).toBeLessThan(capacity); }); it("should hash undefined", () => { const hash = hashFn.hash(undefined, capacity); expect(hash).toBeGreaterThanOrEqual(0); expect(hash).toBeLessThan(capacity); }); }); describe("object types", () => { it("should hash simple objects", () => { const obj = { name: "Alice", age: 30 }; const hash = hashFn.hash(obj, capacity); expect(hash).toBeGreaterThanOrEqual(0); expect(hash).toBeLessThan(capacity); }); it("should hash arrays", () => { const arr = [1, 2, 3, 4, 5]; const hash = hashFn.hash(arr, capacity); expect(hash).toBeGreaterThanOrEqual(0); expect(hash).toBeLessThan(capacity); }); it("should hash nested objects", () => { const nested = { user: { name: "Bob", address: { city: "NYC", zip: "10001", }, }, }; const hash = hashFn.hash(nested, capacity); expect(hash).toBeGreaterThanOrEqual(0); expect(hash).toBeLessThan(capacity); }); it("should handle circular references gracefully", () => { const circular: { name: string; self?: unknown } = { name: "test" }; circular.self = circular; // Create circular reference // Should not throw, should fall back to Object.prototype.toString const hash = hashFn.hash(circular, capacity); expect(hash).toBeGreaterThanOrEqual(0); expect(hash).toBeLessThan(capacity); }); it("should hash Date objects", () => { const date = new Date("2024-01-01"); const hash = hashFn.hash(date, capacity); expect(hash).toBeGreaterThanOrEqual(0); expect(hash).toBeLessThan(capacity); }); it("should hash RegExp objects", () => { const regex = /test/g; const hash = hashFn.hash(regex, capacity); expect(hash).toBeGreaterThanOrEqual(0); expect(hash).toBeLessThan(capacity); }); it("should hash Error objects", () => { const error = new Error("Test error"); const hash = hashFn.hash(error, capacity); expect(hash).toBeGreaterThanOrEqual(0); expect(hash).toBeLessThan(capacity); }); }); describe("special values", () => { it("should hash empty string", () => { const hash = hashFn.hash("", capacity); expect(hash).toBeGreaterThanOrEqual(0); expect(hash).toBeLessThan(capacity); }); it("should hash empty object", () => { const hash = hashFn.hash({}, capacity); expect(hash).toBeGreaterThanOrEqual(0); expect(hash).toBeLessThan(capacity); }); it("should hash empty array", () => { const hash = hashFn.hash([], capacity); expect(hash).toBeGreaterThanOrEqual(0); expect(hash).toBeLessThan(capacity); }); it("should hash symbols", () => { const sym = Symbol("test"); const hash = hashFn.hash(sym, capacity); expect(hash).toBeGreaterThanOrEqual(0); expect(hash).toBeLessThan(capacity); }); it("should hash bigint", () => { const bigInt = BigInt(12345678901234567890n); const hash = hashFn.hash(bigInt, capacity); expect(hash).toBeGreaterThanOrEqual(0); expect(hash).toBeLessThan(capacity); }); }); describe("consistency", () => { it("should return same hash for same key", () => { const key = "test-key"; const hash1 = hashFn.hash(key, capacity); const hash2 = hashFn.hash(key, capacity); expect(hash1).toBe(hash2); }); it("should return different hashes for different keys (usually)", () => { const hash1 = hashFn.hash("key1", capacity); const hash2 = hashFn.hash("key2", capacity); // Note: They COULD collide, but unlikely expect(hash1).toBeGreaterThanOrEqual(0); expect(hash2).toBeGreaterThanOrEqual(0); }); it("should handle different capacities", () => { const key = "test"; const hash8 = hashFn.hash(key, 8); const hash16 = hashFn.hash(key, 16); const hash32 = hashFn.hash(key, 32); expect(hash8).toBeLessThan(8); expect(hash16).toBeLessThan(16); expect(hash32).toBeLessThan(32); }); }); }); describe("NumericHashFunction", () => { const hashFn = new NumericHashFunction(); const capacity = 16; describe("normal numbers", () => { it("should hash positive integers", () => { const hash1 = hashFn.hash(42, capacity); const hash2 = hashFn.hash(100, capacity); expect(hash1).toBeGreaterThanOrEqual(0); expect(hash1).toBeLessThan(capacity); expect(hash2).toBeGreaterThanOrEqual(0); expect(hash2).toBeLessThan(capacity); }); it("should hash negative integers", () => { const hash = hashFn.hash(-42, capacity); expect(hash).toBeGreaterThanOrEqual(0); expect(hash).toBeLessThan(capacity); }); it("should hash zero", () => { const hash = hashFn.hash(0, capacity); expect(hash).toBeGreaterThanOrEqual(0); expect(hash).toBeLessThan(capacity); }); it("should hash floating point numbers", () => { const hash1 = hashFn.hash(3.14159, capacity); const hash2 = hashFn.hash(2.71828, capacity); expect(hash1).toBeGreaterThanOrEqual(0); expect(hash1).toBeLessThan(capacity); expect(hash2).toBeGreaterThanOrEqual(0); expect(hash2).toBeLessThan(capacity); }); it("should hash very large numbers", () => { const hash = hashFn.hash(Number.MAX_SAFE_INTEGER, capacity); expect(hash).toBeGreaterThanOrEqual(0); expect(hash).toBeLessThan(capacity); }); it("should hash very small numbers", () => { const hash = hashFn.hash(Number.MIN_VALUE, capacity); expect(hash).toBeGreaterThanOrEqual(0); expect(hash).toBeLessThan(capacity); }); }); describe("special numeric values", () => { it("should handle Infinity", () => { const hash = hashFn.hash(Infinity, capacity); // Should return 0 for non-finite numbers expect(hash).toBe(0); }); it("should handle negative Infinity", () => { const hash = hashFn.hash(-Infinity, capacity); // Should return 0 for non-finite numbers expect(hash).toBe(0); }); it("should handle NaN", () => { const hash = hashFn.hash(NaN, capacity); // Should return 0 for non-finite numbers expect(hash).toBe(0); }); }); describe("consistency", () => { it("should return same hash for same number", () => { const num = 42; const hash1 = hashFn.hash(num, capacity); const hash2 = hashFn.hash(num, capacity); expect(hash1).toBe(hash2); }); it("should handle different capacities", () => { const num = 42; const hash8 = hashFn.hash(num, 8); const hash16 = hashFn.hash(num, 16); const hash32 = hashFn.hash(num, 32); expect(hash8).toBeLessThan(8); expect(hash16).toBeLessThan(16); expect(hash32).toBeLessThan(32); }); it("should distribute numbers evenly", () => { const hashes = new Set(); // Hash 100 sequential numbers for (let i = 0; i < 100; i++) { hashes.add(hashFn.hash(i, capacity)); } // Should have good distribution (not all in one bucket) expect(hashes.size).toBeGreaterThan(1); }); }); describe("negative numbers", () => { it("should hash negative numbers correctly", () => { const hash1 = hashFn.hash(-1, capacity); const hash2 = hashFn.hash(-100, capacity); const hash3 = hashFn.hash(-3.14, capacity); expect(hash1).toBeGreaterThanOrEqual(0); expect(hash1).toBeLessThan(capacity); expect(hash2).toBeGreaterThanOrEqual(0); expect(hash2).toBeLessThan(capacity); expect(hash3).toBeGreaterThanOrEqual(0); expect(hash3).toBeLessThan(capacity); }); it("should handle same absolute value differently for positive and negative", () => { const hashPos = hashFn.hash(42, capacity); const hashNeg = hashFn.hash(-42, capacity); // They should hash to the same bucket (due to Math.abs in implementation) expect(hashPos).toBe(hashNeg); }); }); });