import { describe, it } from "@effect/vitest" import { assertFalse, assertNone, assertSome, assertTrue, deepStrictEqual, strictEqual } from "@effect/vitest/utils" import { Equal, Hash, MutableHashMap as HM, Option, pipe } from "effect" class Key implements Equal.Equal { constructor(readonly a: number, readonly b: number) {} [Hash.symbol]() { return Hash.hash(`${this.a}-${this.b}`) } [Equal.symbol](that: unknown): boolean { return that instanceof Key && this.a === that.a && this.b === that.b } } class Value implements Equal.Equal { constructor(readonly c: number, readonly d: number) {} [Hash.symbol]() { return Hash.hash(`${this.c}-${this.d}`) } [Equal.symbol](that: unknown): boolean { return that instanceof Value && this.c === that.c && this.d === that.d } } function key(a: number, b: number): Key { return new Key(a, b) } function value(c: number, d: number): Value { return new Value(c, d) } describe("MutableHashMap", () => { it("isMutableHashMap", () => { assertTrue(HM.isMutableHashMap(HM.make([0, "a"], [1, "b"]))) assertFalse(HM.isMutableHashMap(new Map([[0, "a"]]))) }) it("toString", () => { const map = HM.make( [0, "a"], [1, "b"] ) strictEqual( String(map), `MutableHashMap([[0,"a"],[1,"b"]])` ) }) it("toJSON", () => { const map = HM.make( [0, "a"], [1, "b"] ) deepStrictEqual(map.toJSON(), { _id: "MutableHashMap", values: [[0, "a"], [1, "b"]] }) }) it("inspect", () => { if (typeof window !== "undefined") { return } // oxlint-disable-next-line @typescript-eslint/no-require-imports const { inspect } = require("node:util") const map = HM.make( [0, "a"], [1, "b"] ) deepStrictEqual(inspect(map), inspect({ _id: "MutableHashMap", values: [[0, "a"], [1, "b"]] })) }) it("make", () => { const map = HM.make( [key(0, 0), value(0, 0)], [key(1, 1), value(1, 1)] ) strictEqual(HM.size(map), 2) assertTrue(pipe(map, HM.has(key(0, 0)))) assertTrue(pipe(map, HM.has(key(1, 1)))) }) it("fromIterable", () => { const map = HM.fromIterable([ [key(0, 0), value(0, 0)], [key(1, 1), value(1, 1)] ]) strictEqual(HM.size(map), 2) assertTrue(pipe(map, HM.has(key(0, 0)))) assertTrue(pipe(map, HM.has(key(1, 1)))) }) it("iterate", () => { class Hello { [Hash.symbol]() { return 0 } [Equal.symbol](that: unknown) { return this === that } } const a = new Hello() const b = new Hello() const map = HM.make( [a, 0], [b, 0] ) strictEqual(Array.from(map).length, 2) }) it("get returns the latest value for an equal key", () => { const map = pipe( HM.empty(), HM.set(key(0, 0), value(0, 0)), HM.set(key(0, 0), value(1, 1)) ) const result = pipe( map, HM.get(key(0, 0)) ) assertSome(result, value(1, 1)) }) it("has", () => { const map = HM.make( [key(0, 0), value(0, 0)], [key(0, 0), value(1, 1)], [key(1, 1), value(2, 2)], [key(1, 1), value(3, 3)], [key(0, 0), value(4, 4)] ) pipe( map, HM.has(key(0, 0)), assertTrue ) pipe( map, HM.has(key(1, 1)), assertTrue ) pipe( map, HM.has(key(4, 4)), assertFalse ) }) it("keys", () => { const map = pipe( HM.empty(), HM.set(key(0, 0), value(0, 0)), HM.set(key(1, 1), value(1, 1)) ) deepStrictEqual(Array.from(HM.keys(map)), [ key(0, 0), key(1, 1) ]) }) it("values", () => { const map = pipe( HM.empty(), HM.set(key(0, 0), value(0, 0)), HM.set(key(1, 1), value(1, 1)) ) pipe( map, HM.modifyAt( key(0, 0), () => Option.some(value(0, 1)) ) ) strictEqual(HM.size(map), 2) assertSome(pipe(map, HM.get(key(0, 0))), value(0, 1)) pipe( map, HM.modifyAt( key(2, 2), Option.match({ onNone: () => Option.some(value(2, 2)), onSome: Option.some }) ) ) strictEqual(HM.size(map), 3) assertSome(pipe(map, HM.get(key(2, 2))), value(2, 2)) pipe( map, HM.modifyAt( key(2, 2), () => Option.none() ) ) strictEqual(HM.size(map), 2) }) it("remove deletes an equal key in place", () => { const map = pipe( HM.empty(), HM.set(key(0, 0), value(0, 0)), HM.set(key(1, 1), value(1, 1)) ) strictEqual(HM.size(map), 2) pipe( map, HM.has(key(1, 1)), assertTrue ) pipe( map, HM.remove(key(1, 1)) ) strictEqual(HM.size(map), 1) pipe( map, HM.has(key(1, 1)), assertFalse ) }) it("set overwrites equal keys without changing size", () => { const map = pipe( HM.empty(), HM.set(key(0, 0), value(0, 0)), HM.set(key(0, 0), value(1, 1)), HM.set(key(1, 1), value(2, 2)), HM.set(key(1, 1), value(3, 3)), HM.set(key(0, 0), value(4, 4)) ) deepStrictEqual(Array.from(map), [ [key(0, 0), value(4, 4)], [key(1, 1), value(3, 3)] ]) }) it("size", () => { const map = pipe( HM.empty(), HM.set(key(0, 0), value(0, 0)), HM.set(key(0, 0), value(1, 1)), HM.set(key(1, 1), value(2, 2)), HM.set(key(1, 1), value(3, 3)), HM.set(key(0, 0), value(4, 4)) ) strictEqual(HM.size(map), 2) }) it("modify updates existing keys and ignores missing keys", () => { const map = pipe( HM.empty(), HM.set(key(0, 0), value(0, 0)), HM.set(key(1, 1), value(1, 1)) ) pipe( map, HM.modify(key(0, 0), (v) => value(v.c + 1, v.d + 1)) ) assertSome(pipe(map, HM.get(key(0, 0))), value(1, 1)) pipe( map, HM.modify(key(1, 1), (v) => value(v.c + 1, v.d + 1)) ) assertNone(pipe( map, HM.remove(key(0, 0)), HM.get(key(0, 0)) )) }) it("pipe()", () => { deepStrictEqual(HM.empty().pipe(HM.set("key", "value")), HM.make(["key", "value"])) }) })