import { describe, expect, it } from 'vitest' import fc from 'fast-check' import { Keys } from '../../src/utils/keys.js' import { SortedMap } from '../../src/utils/sorted-map.js' import { Ulid } from '../../src/utils/ulid.js' import { DocumentQuery } from '../../src/utils/document-query.js' import { DocumentUpdate } from '../../src/utils/document-update.js' describe('Keys', () => { it('round-trips any segment and never emits a character at or below the separator', () => { fc.assert( fc.property(fc.string({ unit: 'binary', minLength: 1 }), (s) => { const escaped = Keys.escape(s) expect(Keys.unescape(escaped)).toBe(s) expect([...escaped].every((c) => c > '/')).toBe(true) expect(Keys.isReserved(escaped)).toBe(false) }), ) }) it('keeps every child subtree contiguous in key order', () => { fc.assert( fc.property( fc.uniqueArray(fc.string({ unit: fc.constantFrom('a', '-', '.', '/', '~', ' ', '0'), minLength: 1, maxLength: 3 }), { minLength: 2 }), (segments) => { const keys = segments.flatMap((s) => [Keys.escape(s), Keys.escape(s) + '/x']).sort() const order = keys.map((k) => k.split('/')[0]) // Each segment's keys appear as one consecutive run. expect(new Set(order).size).toBe(order.filter((s, i) => s !== order[i - 1]).length) }, ), ) }) }) describe('SortedMap', () => { it('matches a sorted reference, including iteration across mutations', () => { fc.assert( fc.property( fc.array(fc.tuple(fc.boolean(), fc.string({ unit: fc.constantFrom('a', 'b', 'c'), maxLength: 4 })), { maxLength: 200 }), fc.boolean(), (ops, reverse) => { const map = new SortedMap(4) const ref = new Set() for (const [add, key] of ops) add ? (map.set(key, 1), ref.add(key)) : (map.delete(key), ref.delete(key)) const sorted = [...ref].sort() expect([...map.keys(undefined, undefined, reverse)]).toEqual(reverse ? sorted.reverse() : sorted) // Deleting each key as we pass it must not skip the rest. const seen = [...map.keys()].length let visited = 0 for (const key of map.keys()) (visited++, map.delete(key)) expect(visited).toBe(seen) }, ), ) }) }) describe('Ulid', () => { it('is strictly increasing within a millisecond and sorts by time', () => { let t = 1_700_000_000_000 const ulid = new Ulid(() => t) const ids = Array.from({ length: 500 }, () => ulid.generate()) expect([...ids].sort()).toEqual(ids) t++ expect(ulid.generate() > ids.at(-1)!).toBe(true) }) }) describe('DocumentQuery', () => { const q = (filter: object, doc: object) => new DocumentQuery(filter as never).matches(doc as never) it('matches equality, comparisons, $in, dot paths, arrays and null-as-missing', () => { expect(q({ a: 1 }, { a: 1 })).toBe(true) expect(q({ 'a.b': 'x' }, { a: { b: 'x' } })).toBe(true) expect(q({ n: { $gte: 2, $lt: 5 } }, { n: 4 })).toBe(true) expect(q({ n: { $gt: 2 } }, { n: '9' })).toBe(false) expect(q({ n: { $in: [1, 2] } }, { n: 2 })).toBe(true) expect(q({ tags: 'x' }, { tags: ['y', 'x'] })).toBe(true) expect(q({ tags: ['x'] }, { tags: ['x'] })).toBe(true) expect(q({ a: null }, {})).toBe(true) expect(() => q({ a: { $regex: 'x' } }, {})).toThrow(/Unsupported/) }) }) describe('DocumentUpdate', () => { it('resolves operators to final values', () => { const state = { n: 5, tags: ['a', 'b'], nested: { x: 1 } } const change = DocumentUpdate.resolve(state, { $inc: { n: 1 }, $push: { tags: { $each: ['c'] } }, $unset: { 'nested.x': 1 }, $set: { 'nested.y': 2 } }) expect(change).toEqual({ op: 'patch', set: { n: 6, tags: ['a', 'b', 'c'], 'nested.y': 2 }, unset: ['nested.x'] }) expect(DocumentUpdate.apply(state, change)).toEqual({ n: 6, tags: ['a', 'b', 'c'], nested: { y: 2 } }) expect(DocumentUpdate.apply(state, DocumentUpdate.resolve(state, { $pull: { tags: 'a' }, $addToSet: { more: { $each: [1, 1] } } }))).toMatchObject({ tags: ['b'], more: [1], }) expect(() => DocumentUpdate.resolve(state, { $set: { n: 1 }, $inc: { n: 1 } })).toThrow(/twice/) }) })