import { describe, expect, it } from 'vitest' import fc from 'fast-check' import { decodeIndexValue, encodeIndexValue, type IndexValue } from '../../src/core/encoding.js' const separators = fc.constantFrom('/', ':', '.', '|', '#', '!') const indexValue: fc.Arbitrary = fc.oneof( fc.constant(null), fc.boolean(), fc.double({ noNaN: true }), fc.integer(), fc.string({ unit: 'binary' }), fc.string({ unit: fc.constantFrom('/', ':', '0', '1', 'a', '%', '~', '\0', '!') }), ) const rank = (v: IndexValue) => (v === null ? 0 : v === false ? 1 : v === true ? 2 : typeof v === 'number' ? 3 : 4) function compare(a: IndexValue, b: IndexValue): number { const ra = rank(a) const rb = rank(b) if (ra !== rb) return ra - rb if (typeof a === 'number' && typeof b === 'number') return a === b ? 0 : a < b ? -1 : 1 if (typeof a === 'string' && typeof b === 'string') return a === b ? 0 : a < b ? -1 : 1 return 0 } const sign = (n: number) => (n < 0 ? -1 : n > 0 ? 1 : 0) const cmpStr = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0) describe('encodeIndexValue', () => { it('round-trips (property)', () => { fc.assert( fc.property(indexValue, separators, (v, sep) => { const decoded = decodeIndexValue(encodeIndexValue(v, sep), sep) if (typeof v === 'number' && v === 0) expect(decoded).toBe(0) // -0 normalised else expect(decoded).toEqual(v) }), ) }) it('emits only characters above the separator (property)', () => { fc.assert( fc.property(indexValue, separators, (v, sep) => { for (const c of encodeIndexValue(v, sep)) expect(c > sep).toBe(true) }), ) }) it('preserves order, including when followed by a separator and suffix (property)', () => { fc.assert( fc.property(indexValue, indexValue, fc.string(), fc.string(), separators, (a, b, sa, sb, sep) => { const ea = encodeIndexValue(a, sep) const eb = encodeIndexValue(b, sep) const expected = sign(compare(a, b)) expect(cmpStr(ea, eb)).toBe(expected) if (expected !== 0) expect(cmpStr(ea + sep + sa, eb + sep + sb)).toBe(expected) }), { numRuns: 2000 }, ) }) it('orders types null < false < true < numbers < strings', () => { const values: IndexValue[] = ['', 'a', -Infinity, -1, 0, 1e300, Infinity, true, false, null] const sorted = [...values].sort((a, b) => cmpStr(encodeIndexValue(a), encodeIndexValue(b))) expect(sorted).toEqual([null, false, true, -Infinity, -1, 0, 1e300, Infinity, '', 'a']) }) it('rejects NaN and non-index values', () => { expect(() => encodeIndexValue(NaN)).toThrow() expect(() => encodeIndexValue({} as never)).toThrow() }) })