import { describe, expect, it } from 'vitest'; import { parseDictionary, SfToken, serializeBare, serializeDictionary, serializeInnerList, } from '../src/structured-fields.js'; describe('parseDictionary', () => { it('parses items, inner lists and parameters', () => { const dict = parseDictionary( 'a="x\\"y", b=12, c=:AQI=:, d=?1, e, f=tok/en;p=1, g=("h" "i";q);tag="t"', ); expect(dict.get('a')).toMatchObject({ kind: 'item', value: 'x"y' }); expect(dict.get('b')).toMatchObject({ kind: 'item', value: 12 }); const c = dict.get('c'); expect(c?.kind === 'item' && c.value).toEqual(new Uint8Array([1, 2])); expect(dict.get('d')).toMatchObject({ value: true }); expect(dict.get('e')).toMatchObject({ value: true }); const f = dict.get('f'); expect(f?.kind === 'item' && f.value).toBeInstanceOf(SfToken); expect(f?.params.get('p')).toBe(1); const g = dict.get('g'); if (g?.kind !== 'innerList') throw new Error('expected an inner list'); expect(g.items.map((i) => i.value)).toEqual(['h', 'i']); expect(g.items[1].params.get('q')).toBe(true); expect(g.params.get('tag')).toBe('t'); }); it('parses an empty field', () => { expect(parseDictionary('').size).toBe(0); }); it('refuses malformed input', () => { for (const bad of [ 'A=1', 'a=1,', 'a=1 b=2', 'a="unterminated', 'a="bad\\escape"', 'a=:AQI', 'a=:!!:', 'a=?2', 'a=("x"y)', 'a=@', 'a="é"', ]) { expect(() => parseDictionary(bad), bad).toThrow(); } }); }); describe('serialize', () => { it('writes canonical members', () => { expect( serializeDictionary([ ['type', 'atproto-space-repo'], ['ver', 1], ['mac', new Uint8Array([1, 2])], ['flag', true], ['off', false], ]), ).toBe('type="atproto-space-repo", ver=1, mac=:AQI=:, flag, off=?0'); expect(serializeBare('a"b\\c')).toBe('"a\\"b\\\\c"'); expect(serializeBare(new SfToken('tok'))).toBe('tok'); }); it('round-trips an inner list', () => { const input = '("authorization" "atproto-space-audience";x);tag="t";flag'; const g = parseDictionary(`s=${input}`).get('s'); if (g?.kind !== 'innerList') throw new Error('expected an inner list'); expect(serializeInnerList(g.items, g.params)).toBe(input); }); });