Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qxbkp4tA7G5SCWjzWuxmE
74 lines
2.6 KiB
TypeScript
74 lines
2.6 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import fc from 'fast-check'
|
|
import { escapeSegment, joinPath, pathKeys, splitPath, succ, toSegment, unescapeSegment, validateSeparator } from '../../src/core/path.js'
|
|
import { InvalidKeyError } from '../../src/errors.js'
|
|
|
|
const separators = fc.constantFrom('/', ':', '.', '|', '#', '\\', '!')
|
|
|
|
describe('escapeSegment', () => {
|
|
it('escapes %, the separator and a leading ~', () => {
|
|
expect(escapeSegment('a/b')).toBe('a%2Fb')
|
|
expect(escapeSegment('50%')).toBe('50%25')
|
|
expect(escapeSegment('~x')).toBe('%7Ex')
|
|
expect(escapeSegment('x~')).toBe('x~')
|
|
expect(escapeSegment('a:b', ':')).toBe('a%3Ab')
|
|
expect(escapeSegment('a/b', ':')).toBe('a/b')
|
|
expect(escapeSegment('plain')).toBe('plain')
|
|
})
|
|
|
|
it('round-trips (property)', () => {
|
|
fc.assert(
|
|
fc.property(fc.string({ unit: 'binary' }), separators, (s, sep) => {
|
|
expect(unescapeSegment(escapeSegment(s, sep))).toBe(s)
|
|
}),
|
|
)
|
|
})
|
|
|
|
it('never emits a raw separator or a leading ~ (property)', () => {
|
|
fc.assert(
|
|
fc.property(fc.string({ unit: 'binary' }), separators, (s, sep) => {
|
|
const e = escapeSegment(s, sep)
|
|
expect(e.includes(sep)).toBe(false)
|
|
expect(e.startsWith('~')).toBe(false)
|
|
}),
|
|
)
|
|
})
|
|
|
|
it('joins and splits paths (property)', () => {
|
|
fc.assert(
|
|
fc.property(fc.array(fc.string({ minLength: 1 }), { maxLength: 6 }), separators, (path, sep) => {
|
|
expect(splitPath(joinPath(path, sep), sep)).toEqual(path)
|
|
}),
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('pathKeys', () => {
|
|
it('precomputes selfKey, prefix and subtree end', () => {
|
|
expect(pathKeys(['users', '123'], '/')).toMatchObject({ selfKey: 'users/123', prefix: 'users/123/', end: 'users/1230' })
|
|
expect(pathKeys([], '/')).toMatchObject({ selfKey: '', prefix: '', end: undefined })
|
|
expect(pathKeys(['a/b'], '/').selfKey).toBe('a%2Fb')
|
|
})
|
|
})
|
|
|
|
describe('misc', () => {
|
|
it('succ returns the next char', () => {
|
|
expect(succ('/')).toBe('0')
|
|
})
|
|
|
|
it('toSegment rejects undefined, null, empty and non-finite', () => {
|
|
expect(() => toSegment(undefined)).toThrow(InvalidKeyError)
|
|
expect(() => toSegment(null)).toThrow(TypeError)
|
|
expect(() => toSegment('')).toThrow(InvalidKeyError)
|
|
expect(() => toSegment(NaN)).toThrow(InvalidKeyError)
|
|
expect(toSegment(12)).toBe('12')
|
|
})
|
|
|
|
it('validates separators', () => {
|
|
expect(() => validateSeparator('//')).toThrow()
|
|
expect(() => validateSeparator('%')).toThrow()
|
|
expect(() => validateSeparator('~')).toThrow()
|
|
expect(() => validateSeparator(':')).not.toThrow()
|
|
})
|
|
})
|