325 lines
9.8 KiB
TypeScript
325 lines
9.8 KiB
TypeScript
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<unknown>();
|
|
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<number>();
|
|
|
|
// 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);
|
|
});
|
|
});
|
|
});
|
|
|