aboutsummaryrefslogtreecommitdiff
path: root/tests/unit/utils/equal.test.ts
diff options
context:
space:
mode:
authorBobby <[email protected]>2026-04-22 06:51:57 +0530
committerBobby <[email protected]>2026-04-22 06:51:57 +0530
commit4dfbf7a13120b3b64521fce6de7d5d3f76cd2084 (patch)
tree351d2ab064442e29e76a74c7b4fa53c1048fb66b /tests/unit/utils/equal.test.ts
parent4383213a23e27903e1fed270f1dbcc116644c7fc (diff)
downloadhollowdark-4dfbf7a13120b3b64521fce6de7d5d3f76cd2084.tar.xz
hollowdark-4dfbf7a13120b3b64521fce6de7d5d3f76cd2084.zip
Add shared utilities: Result, assert, deepEqual, common types, log
Foundational helpers referenced by most subsequent code. Small, pure, no dependencies on anything in the project. utils/result.ts Result<T, E> = Ok<T> | Err<E> for recoverable failures at system boundaries (save/load, manifest fetch, content validation). Internal pure logic still uses throw for programmer errors. ok / err / isOk / isErr / mapResult / mapErr / unwrap / unwrapOr. utils/assert.ts assert (with type-narrowing asserts), assertNever for exhaustive-switch termination, assertDefined for narrowing T | null | undefined → T. utils/equal.ts Structural deepEqual for tests comparing simulation snapshots. Handles plain objects, arrays, Map, Set, Date, primitives. Does not handle cyclic graphs (simulation state is a tree). utils/types.ts Common type aliases: NonEmptyArray, JsonValue, ElementOf, DeepReadonly, Brand<T, B> for nominal / branded types (PersonId vs RelationshipId). utils/log.ts Structured logging wrapping console. Developer- facing only; the game ships with no runtime telemetry. utils/index.ts Public re-exports. 40 unit tests in tests/unit/utils/ cover Result constructors and combinators, assert variants, and deepEqual's primitive / array / object / Map / Set / Date paths plus a realistic simulation-snapshot comparison.
Diffstat (limited to 'tests/unit/utils/equal.test.ts')
-rw-r--r--tests/unit/utils/equal.test.ts147
1 files changed, 147 insertions, 0 deletions
diff --git a/tests/unit/utils/equal.test.ts b/tests/unit/utils/equal.test.ts
new file mode 100644
index 0000000..8f07996
--- /dev/null
+++ b/tests/unit/utils/equal.test.ts
@@ -0,0 +1,147 @@
+import { describe, expect, test } from 'vitest'
+import { deepEqual } from 'utils/equal'
+
+describe('deepEqual — primitives', () => {
+ test('identical primitives', () => {
+ expect(deepEqual(1, 1)).toBe(true)
+ expect(deepEqual('a', 'a')).toBe(true)
+ expect(deepEqual(true, true)).toBe(true)
+ expect(deepEqual(null, null)).toBe(true)
+ expect(deepEqual(undefined, undefined)).toBe(true)
+ })
+
+ test('different primitives', () => {
+ expect(deepEqual(1, 2)).toBe(false)
+ expect(deepEqual('a', 'b')).toBe(false)
+ expect(deepEqual(null, undefined)).toBe(false)
+ })
+
+ test('NaN is treated as equal to NaN (Object.is semantics)', () => {
+ expect(deepEqual(NaN, NaN)).toBe(true)
+ })
+
+ test('+0 and -0 are distinct (Object.is semantics)', () => {
+ expect(deepEqual(0, -0)).toBe(false)
+ })
+})
+
+describe('deepEqual — arrays', () => {
+ test('same-length equal arrays', () => {
+ expect(deepEqual([1, 2, 3], [1, 2, 3])).toBe(true)
+ })
+
+ test('different-length arrays', () => {
+ expect(deepEqual([1, 2], [1, 2, 3])).toBe(false)
+ })
+
+ test('different elements', () => {
+ expect(deepEqual([1, 2, 3], [1, 2, 4])).toBe(false)
+ })
+
+ test('nested arrays', () => {
+ expect(
+ deepEqual(
+ [
+ [1, 2],
+ [3, 4]
+ ],
+ [
+ [1, 2],
+ [3, 4]
+ ]
+ )
+ ).toBe(true)
+ })
+
+ test('array vs non-array', () => {
+ expect(deepEqual([1], { 0: 1, length: 1 })).toBe(false)
+ })
+})
+
+describe('deepEqual — objects', () => {
+ test('same-shape objects', () => {
+ expect(deepEqual({ a: 1, b: 2 }, { a: 1, b: 2 })).toBe(true)
+ })
+
+ test('order of keys does not matter', () => {
+ expect(deepEqual({ a: 1, b: 2 }, { b: 2, a: 1 })).toBe(true)
+ })
+
+ test('missing key on one side', () => {
+ expect(deepEqual({ a: 1 }, { a: 1, b: 2 })).toBe(false)
+ })
+
+ test('nested objects', () => {
+ expect(deepEqual({ a: { b: { c: 1 } } }, { a: { b: { c: 1 } } })).toBe(true)
+ expect(deepEqual({ a: { b: { c: 1 } } }, { a: { b: { c: 2 } } })).toBe(false)
+ })
+})
+
+describe('deepEqual — Map', () => {
+ test('equal maps', () => {
+ expect(
+ deepEqual(
+ new Map([
+ ['a', 1],
+ ['b', 2]
+ ]),
+ new Map([
+ ['a', 1],
+ ['b', 2]
+ ])
+ )
+ ).toBe(true)
+ })
+
+ test('different sizes', () => {
+ expect(deepEqual(new Map([['a', 1]]), new Map([['a', 1], ['b', 2]]))).toBe(false)
+ })
+
+ test('different values', () => {
+ expect(deepEqual(new Map([['a', 1]]), new Map([['a', 2]]))).toBe(false)
+ })
+})
+
+describe('deepEqual — Set', () => {
+ test('equal sets', () => {
+ expect(deepEqual(new Set([1, 2, 3]), new Set([3, 2, 1]))).toBe(true)
+ })
+
+ test('different sets', () => {
+ expect(deepEqual(new Set([1, 2]), new Set([1, 2, 3]))).toBe(false)
+ })
+})
+
+describe('deepEqual — Date', () => {
+ test('same timestamp is equal', () => {
+ expect(deepEqual(new Date(1000), new Date(1000))).toBe(true)
+ })
+
+ test('different timestamps', () => {
+ expect(deepEqual(new Date(1000), new Date(2000))).toBe(false)
+ })
+
+ test('Date vs non-Date', () => {
+ expect(deepEqual(new Date(0), 0)).toBe(false)
+ })
+})
+
+describe('deepEqual — mixed', () => {
+ test('deep structure typical of simulation state', () => {
+ const a = {
+ id: 'p1',
+ name: { given: 'Meera', surname: 'Sarwath' },
+ traits: new Map([['openness', 72]]),
+ friends: new Set(['p2', 'p3']),
+ events: [{ t: 10, kind: 'birth' }]
+ }
+ const b = {
+ id: 'p1',
+ name: { given: 'Meera', surname: 'Sarwath' },
+ traits: new Map([['openness', 72]]),
+ friends: new Set(['p3', 'p2']),
+ events: [{ t: 10, kind: 'birth' }]
+ }
+ expect(deepEqual(a, b)).toBe(true)
+ })
+})