import { describe, it, expect, beforeEach } from "bun:test"; import { HashMap, NumericHashFunction } from "../src/index.ts"; import type { IHashFunction } from "../src/index.ts"; describe("HashMap", () => { let map: HashMap; beforeEach(() => { map = new HashMap(); }); describe("constructor", () => { it("should create an empty map with default capacity", () => { expect(map.size).toBe(0); expect(map.capacity).toBe(16); }); it("should create a map with custom initial capacity", () => { const customMap = new HashMap(32); expect(customMap.capacity).toBe(32); }); it("should throw error for invalid capacity", () => { expect(() => new HashMap(0)).toThrow(); expect(() => new HashMap(-1)).toThrow(); }); it("should throw error for invalid load factor", () => { expect(() => new HashMap(16, 0)).toThrow(); expect(() => new HashMap(16, 1.5)).toThrow(); }); }); describe("set and get", () => { it("should set and get a value", () => { map.set("key1", 100); expect(map.get("key1")).toBe(100); }); it("should update existing value", () => { map.set("key1", 100); map.set("key1", 200); expect(map.get("key1")).toBe(200); expect(map.size).toBe(1); }); it("should return undefined for non-existent key", () => { expect(map.get("nonexistent")).toBeUndefined(); }); it("should handle multiple key-value pairs", () => { map.set("a", 1); map.set("b", 2); map.set("c", 3); expect(map.get("a")).toBe(1); expect(map.get("b")).toBe(2); expect(map.get("c")).toBe(3); expect(map.size).toBe(3); }); }); describe("has", () => { it("should return true for existing key", () => { map.set("key1", 100); expect(map.has("key1")).toBe(true); }); it("should return false for non-existent key", () => { expect(map.has("nonexistent")).toBe(false); }); }); describe("delete", () => { it("should delete existing key", () => { map.set("key1", 100); expect(map.delete("key1")).toBe(true); expect(map.has("key1")).toBe(false); expect(map.size).toBe(0); }); it("should return false for non-existent key", () => { expect(map.delete("nonexistent")).toBe(false); }); it("should handle deletion from chain", () => { // Add multiple items that might collide map.set("a", 1); map.set("b", 2); map.set("c", 3); map.delete("b"); expect(map.has("b")).toBe(false); expect(map.has("a")).toBe(true); expect(map.has("c")).toBe(true); expect(map.size).toBe(2); }); }); describe("clear", () => { it("should remove all entries", () => { map.set("a", 1); map.set("b", 2); map.set("c", 3); map.clear(); expect(map.size).toBe(0); expect(map.has("a")).toBe(false); expect(map.has("b")).toBe(false); expect(map.has("c")).toBe(false); }); }); describe("size", () => { it("should track size correctly", () => { expect(map.size).toBe(0); map.set("a", 1); expect(map.size).toBe(1); map.set("b", 2); expect(map.size).toBe(2); map.delete("a"); expect(map.size).toBe(1); map.clear(); expect(map.size).toBe(0); }); }); describe("keys", () => { it("should iterate over all keys", () => { map.set("a", 1); map.set("b", 2); map.set("c", 3); const keys = Array.from(map.keys()); expect(keys).toHaveLength(3); expect(keys).toContain("a"); expect(keys).toContain("b"); expect(keys).toContain("c"); }); it("should return empty iterator for empty map", () => { const keys = Array.from(map.keys()); expect(keys).toHaveLength(0); }); }); describe("values", () => { it("should iterate over all values", () => { map.set("a", 1); map.set("b", 2); map.set("c", 3); const values = Array.from(map.values()); expect(values).toHaveLength(3); expect(values).toContain(1); expect(values).toContain(2); expect(values).toContain(3); }); }); describe("entries", () => { it("should iterate over all entries", () => { map.set("a", 1); map.set("b", 2); const entries = Array.from(map.entries()); expect(entries).toHaveLength(2); expect(entries).toContainEqual(["a", 1]); expect(entries).toContainEqual(["b", 2]); }); }); describe("forEach", () => { it("should iterate with forEach", () => { map.set("a", 1); map.set("b", 2); map.set("c", 3); const result: [string, number][] = []; map.forEach((value, key) => { result.push([key, value]); }); expect(result).toHaveLength(3); expect(result).toContainEqual(["a", 1]); expect(result).toContainEqual(["b", 2]); expect(result).toContainEqual(["c", 3]); }); }); describe("iterable", () => { it("should work with for...of", () => { map.set("a", 1); map.set("b", 2); const entries: [string, number][] = []; for (const entry of map) { entries.push(entry); } expect(entries).toHaveLength(2); expect(entries).toContainEqual(["a", 1]); expect(entries).toContainEqual(["b", 2]); }); }); describe("resizing", () => { it("should resize when load factor exceeds threshold", () => { const smallMap = new HashMap(4, 0.75); expect(smallMap.capacity).toBe(4); // Add enough items to trigger resize for (let i = 0; i < 10; i++) { smallMap.set(`key${i}`, i); } expect(smallMap.size).toBe(10); expect(smallMap.capacity).toBeGreaterThan(4); // Verify all items are still accessible for (let i = 0; i < 10; i++) { expect(smallMap.get(`key${i}`)).toBe(i); } }); it("should maintain all entries after resize", () => { const smallMap = new HashMap(2, 0.5); const entries = [ ["a", 1], ["b", 2], ["c", 3], ["d", 4], ["e", 5], ] as const; for (const [key, value] of entries) { smallMap.set(key, value); } for (const [key, value] of entries) { expect(smallMap.get(key)).toBe(value); } }); }); describe("custom hash function", () => { it("should work with NumericHashFunction", () => { const numMap = new HashMap( 16, 0.75, new NumericHashFunction(), ); numMap.set(123, "value1"); numMap.set(456, "value2"); expect(numMap.get(123)).toBe("value1"); expect(numMap.get(456)).toBe("value2"); }); it("should work with custom hash function", () => { class SimpleHashFunction implements IHashFunction { hash(key: string, capacity: number): number { return key.length % capacity; } } const customMap = new HashMap( 8, 0.75, new SimpleHashFunction(), ); customMap.set("hi", "short"); customMap.set("hello", "medium"); expect(customMap.get("hi")).toBe("short"); expect(customMap.get("hello")).toBe("medium"); }); }); describe("edge cases", () => { it("should handle null values", () => { const nullMap = new HashMap(); nullMap.set("key", null); expect(nullMap.get("key")).toBeNull(); expect(nullMap.has("key")).toBe(true); }); it("should handle undefined values", () => { const undefinedMap = new HashMap(); undefinedMap.set("key", undefined); expect(undefinedMap.has("key")).toBe(true); }); it("should handle empty string keys", () => { map.set("", 100); expect(map.get("")).toBe(100); }); it("should handle numeric keys", () => { const numMap = new HashMap(); numMap.set(0, "zero"); numMap.set(1, "one"); numMap.set(-1, "negative one"); expect(numMap.get(0)).toBe("zero"); expect(numMap.get(1)).toBe("one"); expect(numMap.get(-1)).toBe("negative one"); }); it("should handle large number of entries", () => { const largeMap = new HashMap(); const count = 1000; for (let i = 0; i < count; i++) { largeMap.set(i, i * 2); } expect(largeMap.size).toBe(count); for (let i = 0; i < count; i++) { expect(largeMap.get(i)).toBe(i * 2); } }); }); describe("collision handling", () => { it("should handle hash collisions correctly", () => { // Create a map with small capacity to increase collision probability const collisionMap = new HashMap(2, 0.99); collisionMap.set("a", 1); collisionMap.set("b", 2); collisionMap.set("c", 3); collisionMap.set("d", 4); expect(collisionMap.size).toBe(4); expect(collisionMap.get("a")).toBe(1); expect(collisionMap.get("b")).toBe(2); expect(collisionMap.get("c")).toBe(3); expect(collisionMap.get("d")).toBe(4); }); }); describe("toString", () => { it("should provide readable string representation", () => { map.set("a", 1); map.set("b", 2); const str = map.toString(); expect(str).toContain("HashMap"); expect(str).toContain("2"); // size }); }); });