@electric-sql/d2mini
Version:
D2Mini is a minimal implementation of Differential Dataflow for performing in-memory incremental view maintenance.
373 lines (302 loc) • 11.2 kB
text/typescript
import { describe, it, expect } from 'vitest'
import { DefaultMap, hash } from '../src/utils.js'
describe('DefaultMap', () => {
it('should return default value for missing keys', () => {
const map = new DefaultMap(() => 0)
expect(map.get('missing')).toBe(0)
})
it('should store and retrieve values', () => {
const map = new DefaultMap(() => 0)
map.set('key', 42)
expect(map.get('key')).toBe(42)
})
it('should accept initial entries', () => {
const map = new DefaultMap(() => 0, [['key', 1]])
expect(map.get('key')).toBe(1)
})
it('should update values using the update method', () => {
const map = new DefaultMap(() => 0)
map.update('key', (value) => value + 1)
expect(map.get('key')).toBe(1)
map.update('key', (value) => value * 2)
expect(map.get('key')).toBe(2)
})
})
describe('hash', () => {
describe('primitive types', () => {
it('should hash null', () => {
const result = hash(null)
expect(typeof result).toBe('string')
})
it('should hash undefined', () => {
const result = hash(undefined)
expect(typeof result).toBe('string')
})
it('should hash strings', () => {
const result1 = hash('hello')
const result2 = hash('')
const result3 = hash('test with spaces')
const result4 = hash('special\nchars\t"')
expect(typeof result1).toBe('string')
expect(typeof result2).toBe('string')
expect(typeof result3).toBe('string')
expect(typeof result4).toBe('string')
// Same strings should have same hash
expect(hash('hello')).toBe(result1)
})
it('should hash numbers', () => {
const result1 = hash(42)
const result2 = hash(0)
const result3 = hash(-1)
const result4 = hash(3.14159)
const result5 = hash(Infinity)
const result6 = hash(-Infinity)
const result7 = hash(NaN)
expect(typeof result1).toBe('string')
expect(typeof result2).toBe('string')
expect(typeof result3).toBe('string')
expect(typeof result4).toBe('string')
expect(typeof result5).toBe('string')
expect(typeof result6).toBe('string')
expect(typeof result7).toBe('string')
// Same numbers should have same hash
expect(hash(42)).toBe(result1)
})
it('should hash booleans', () => {
const result1 = hash(true)
const result2 = hash(false)
expect(typeof result1).toBe('string')
expect(typeof result2).toBe('string')
expect(result1).not.toBe(result2)
// Same booleans should have same hash
expect(hash(true)).toBe(result1)
expect(hash(false)).toBe(result2)
})
it('should hash bigint', () => {
const result1 = hash(123n)
const result2 = hash(456n)
const result3 = hash(123n)
expect(typeof result1).toBe('string')
expect(typeof result2).toBe('string')
expect(typeof result3).toBe('string')
expect(result1).toBe(result3) // Same bigint should have same hash
expect(result1).not.toBe(result2) // Different bigints should have different hash
})
it('should hash symbols', () => {
const sym1 = Symbol('test')
const sym2 = Symbol('test')
const sym3 = Symbol('different')
const result1 = hash(sym1)
const result2 = hash(sym2)
const result3 = hash(sym3)
expect(typeof result1).toBe('string')
expect(typeof result2).toBe('string')
expect(typeof result3).toBe('string')
// Note: Different symbol instances with same description have same string representation
expect(result1).toBe(result2)
expect(result1).not.toBe(result3)
})
})
describe('object types', () => {
it('should hash plain objects', () => {
const obj1 = { a: 1, b: 2 }
const obj2 = { b: 2, a: 1 } // Different key order
const hash1 = hash(obj1)
const hash2 = hash(obj2)
expect(typeof hash1).toBe('string')
expect(typeof hash2).toBe('string')
// Note: Different key orders might produce different hashes depending on JSON.stringify behavior
})
it('should hash arrays', () => {
const arr1 = [1, 2, 3]
const arr2 = [1, 2, 3]
const arr3 = [3, 2, 1]
const hash1 = hash(arr1)
const hash2 = hash(arr2)
const hash3 = hash(arr3)
expect(typeof hash1).toBe('string')
expect(hash1).toBe(hash2) // Same content should have same hash
expect(hash1).not.toBe(hash3) // Different content should have different hash
})
it('should hash Date objects', () => {
const date1 = new Date('2023-01-01')
const date2 = new Date('2023-01-01')
const date3 = new Date('2023-01-02')
const hash1 = hash(date1)
const hash2 = hash(date2)
const hash3 = hash(date3)
expect(typeof hash1).toBe('string')
expect(hash1).toBe(hash2) // Same date should have same hash
expect(hash1).not.toBe(hash3) // Different dates should have different hash
})
it('should hash RegExp objects', () => {
const regex1 = /test/g
const regex2 = /test/g
const regex3 = /different/i
const hash1 = hash(regex1)
const hash2 = hash(regex2)
const hash3 = hash(regex3)
expect(typeof hash1).toBe('string')
expect(hash1).toBe(hash2) // Same regex should have same hash
// Note: RegExp objects serialize to empty objects {}, so they all produce the same hash
expect(hash1).toBe(hash3) // All RegExp objects have the same hash
})
it('should hash nested objects', () => {
const nested1 = { a: { b: { c: 1 } } }
const nested2 = { a: { b: { c: 1 } } }
const nested3 = { a: { b: { c: 2 } } }
const hash1 = hash(nested1)
const hash2 = hash(nested2)
const hash3 = hash(nested3)
expect(typeof hash1).toBe('string')
expect(hash1).toBe(hash2)
expect(hash1).not.toBe(hash3)
})
it('should hash functions', () => {
const func1 = function test() {
return 1
}
const func2 = function test() {
return 1
}
const func3 = function different() {
return 2
}
const hash1 = hash(func1)
const hash2 = hash(func2)
const hash3 = hash(func3)
expect(typeof hash1).toBe('string')
expect(typeof hash2).toBe('string')
expect(typeof hash3).toBe('string')
expect(hash1).toBe(hash2) // Same function definition should have same hash
expect(hash1).not.toBe(hash3) // Different function should have different hash
})
it('should hash Set objects', () => {
const set1 = new Set([1, 2, 3])
const set2 = new Set([1, 2, 3])
const set3 = new Set([1, 2, 3, 4])
const hash1 = hash(set1)
const hash2 = hash(set2)
const hash3 = hash(set3)
expect(typeof hash1).toBe('string')
expect(hash1).toBe(hash2) // Same content should have same hash
expect(hash1).not.toBe(hash3) // Different content should have different hash
})
it('should hash Map objects', () => {
const map1 = new Map([
['a', 1],
['b', 2],
])
const map2 = new Map([
['a', 1],
['b', 2],
])
const map3 = new Map([
['a', 1],
['b', 2],
['c', 3],
])
const hash1 = hash(map1)
const hash2 = hash(map2)
const hash3 = hash(map3)
expect(typeof hash1).toBe('string')
expect(hash1).toBe(hash2) // Same content should have same hash
expect(hash1).not.toBe(hash3) // Different content should have different hash
})
it('should hash Maps and Sets with unsupported types', () => {
// Map with BigInt values
const mapWithBigInt1 = new Map([
['a', 123n],
['b', 456n],
])
const mapWithBigInt2 = new Map([
['a', 123n],
['b', 456n],
])
const mapWithBigInt3 = new Map([
['a', 123n],
['b', 789n],
])
const hash1 = hash(mapWithBigInt1)
const hash2 = hash(mapWithBigInt2)
const hash3 = hash(mapWithBigInt3)
expect(typeof hash1).toBe('string')
expect(hash1).toBe(hash2) // Same BigInt content should have same hash
expect(hash1).not.toBe(hash3) // Different BigInt content should have different hash
// Set with Symbol values
const sym1 = Symbol('test')
const sym2 = Symbol('different')
const setWithSymbols1 = new Set([sym1, sym2])
const setWithSymbols2 = new Set([sym1, sym2])
const setWithSymbols3 = new Set([sym1])
const hash4 = hash(setWithSymbols1)
const hash5 = hash(setWithSymbols2)
const hash6 = hash(setWithSymbols3)
expect(typeof hash4).toBe('string')
expect(hash4).toBe(hash5) // Same Symbol content should have same hash
expect(hash4).not.toBe(hash6) // Different Symbol content should have different hash
})
})
describe('caching behavior', () => {
it('should cache hash values for objects', () => {
const obj = { test: 'value' }
const hash1 = hash(obj)
const hash2 = hash(obj)
expect(hash1).toBe(hash2)
expect(typeof hash1).toBe('string')
})
it('should return cached values on subsequent calls', () => {
const obj = { complex: { nested: { data: [1, 2, 3] } } }
// First call should compute and cache
const hash1 = hash(obj)
// Second call should return cached value
const hash2 = hash(obj)
expect(hash1).toBe(hash2)
expect(typeof hash1).toBe('string')
})
it('should not cache primitive values', () => {
// Primitives should not be cached as they use JSON.stringify directly
const hash1 = hash('test')
const hash2 = hash('test')
expect(hash1).toBe(hash2)
expect(typeof hash1).toBe('string')
})
})
describe('edge cases', () => {
it('should handle empty objects and arrays', () => {
expect(typeof hash({})).toBe('string')
expect(typeof hash([])).toBe('string')
expect(hash({})).not.toBe(hash([]))
})
it('should handle objects with null and undefined values', () => {
const obj1 = { a: null, b: undefined }
const obj2 = { a: null, b: undefined }
const hash1 = hash(obj1)
const hash2 = hash(obj2)
expect(hash1).toBe(hash2)
expect(typeof hash1).toBe('string')
})
it('should handle mixed type arrays', () => {
const mixedArray = [1, 'string', true, null, { key: 'value' }]
const sameArray = [1, 'string', true, null, { key: 'value' }]
const hash1 = hash(mixedArray)
const hash2 = hash(sameArray)
expect(hash1).toBe(hash2)
expect(typeof hash1).toBe('string')
})
it('should produce consistent hashes for same content', () => {
const obj = {
string: 'test',
number: 42,
boolean: true,
array: [1, 2, 3],
nested: { inner: 'value' },
}
// Multiple calls should return the same hash
const hashes = Array.from({ length: 5 }, () => hash(obj))
const firstHash = hashes[0]
expect(hashes.every((h) => h === firstHash)).toBe(true)
expect(typeof firstHash).toBe('string')
})
})
})