import { describe, expect, it } from 'vitest'; import { accessLabel, appAccessLabel, bytesToHex, collectionsOf, fileSize, nsidDomain, policyLabel, rkeyTime, shortDid, spaceParts, until, } from '#/lib/format.js'; describe('spaceParts', () => { it('reads the three components a heading shows', () => { expect( spaceParts('at://did:plc:abcdef/space/com.example.forum/general'), ).toEqual({ spaceDid: 'did:plc:abcdef', spaceType: 'com.example.forum', skey: 'general', }); }); it('refuses a public AT-URI, which addresses a repo rather than a space', () => { expect(() => spaceParts('at://did:plc:abcdef/app.bsky.feed.post/3k2a'), ).toThrow(); }); }); describe('shortDid', () => { it('leaves a short DID whole', () => { expect(shortDid('did:web:example.com')).toBe('did:web:example.com'); }); it('keeps both ends, so two DIDs from one directory read apart', () => { const shortened = shortDid('did:plc:c4igoshkxhzw62eme3iltpdi'); expect(shortened.startsWith('did:plc:c4igosh')).toBe(true); expect(shortened.endsWith('ltpdi')).toBe(true); }); }); describe('rkeyTime', () => { it('reads the write time out of a TID', () => { const at = rkeyTime('3mtjzqmmgxsrb'); expect(at?.getUTCFullYear()).toBe(2026); }); it('answers null for a key that is not a TID', () => { expect(rkeyTime('self')).toBeNull(); }); }); describe('until', () => { // Each offset carries most of a unit in hand, because the clock moves between // building the argument and reading it, and the count is floored. it('counts down in the coarsest unit that still says something', () => { expect(until(Date.now() + 45_900)).toBe('in 45 seconds'); expect(until(Date.now() + 2 * 3_600_000 + 60_000)).toBe('in 2 hours'); }); // A delegation token lives sixty seconds, so a countdown that stopped at // minutes would show every one of them as nothing. it('has seconds as a unit', () => { expect(until(Date.now() + 1900)).toBe('in 1 second'); }); it('says a past time has expired', () => { expect(until(Date.now() - 1000)).toBe('expired'); }); it('answers empty for no time at all', () => { expect(until(null)).toBe(''); }); }); describe('fileSize', () => { it('names the unit the number is readable in', () => { expect(fileSize(70)).toBe('70 B'); expect(fileSize(2048)).toBe('2 KB'); expect(fileSize(3 * 1024 * 1024)).toBe('3.0 MB'); }); }); describe('bytesToHex', () => { it('renders a commit hash the way another implementation prints it', () => { expect(bytesToHex({ $bytes: 'AAECqv8' })).toBe('000102aaff'); }); it('answers empty for a field that carries no bytes', () => { expect(bytesToHex(null)).toBe(''); expect(bytesToHex(undefined)).toBe(''); }); }); describe('collectionsOf', () => { const records = [ { collection: 'b.example.note', rkey: '3mtjzqmmgxsrb' }, { collection: 'a.example.pin', rkey: '3mtjzqmmwlsrb' }, { collection: 'b.example.note', rkey: 'self' }, ]; it('counts each collection and sorts by name', () => { expect(collectionsOf(records).map((c) => [c.collection, c.count])).toEqual([ ['a.example.pin', 1], ['b.example.note', 2], ]); }); it('takes the latest write time from the TIDs, ignoring keys that carry none', () => { const notes = collectionsOf(records)[1]; expect(notes.lastWriteAt).toBe(rkeyTime('3mtjzqmmgxsrb')?.getTime()); }); it('leaves lastWriteAt null when no key is a TID', () => { expect( collectionsOf([{ collection: 'a.example.x', rkey: 'self' }])[0] .lastWriteAt, ).toBeNull(); }); }); describe('policyLabel', () => { it('names the policy', () => { expect( policyLabel({ policy: { $type: 'com.atproto.memberspace.policy' } }), ).toBe('Members on a list'); }); it('says so plainly for a policy it does not know', () => { expect(policyLabel({})).toBe('Unknown'); }); }); describe('accessLabel', () => { const members = { $type: 'com.atproto.memberspace.policy' }; it('reads public access off a memberspace', () => { const config = { policy: { ...members, publicRead: true } }; expect(accessLabel(config, 'read')).toBe('Anyone'); expect(accessLabel(config, 'write')).toBe('Members who may write'); }); it('names the policy for one with no flags', () => { expect( accessLabel( { policy: { $type: 'com.atproto.personalspace.policy' } }, 'read', ), ).toBe('The authority alone'); }); }); describe('appAccessLabel', () => { it('counts an allow list', () => { expect( appAccessLabel({ policy: { $type: 'com.atproto.memberspace.policy', allowedApps: ['https://one.example', 'https://two.example'], }, }), ).toBe('2 clients on an allow list'); }); it('treats an absent list as open, which is the documented default', () => { expect(appAccessLabel({})).toBe('Any client'); }); }); describe('nsidDomain', () => { // A space type is an NSID, and the domain it reverses to is the app the // space belongs to, which is what serves the icon. it('reverses the first two segments', () => { expect(nsidDomain('my.bulletin.board')).toBe('bulletin.my'); expect(nsidDomain('social.grain.gallery')).toBe('grain.social'); expect(nsidDomain('app.bulleted.space')).toBe('bulleted.app'); }); // Two segments, not the whole authority: a favicon answers at `bsky.app`, // never at `feed.bsky.app`. it('stops at the registrable domain however deep the NSID goes', () => { expect(nsidDomain('app.bsky.feed.post')).toBe('bsky.app'); expect(nsidDomain('dev.pdsjs.drive.space')).toBe('pdsjs.dev'); }); it('leaves something that is not an NSID alone', () => { expect(nsidDomain('board')).toBe('board'); }); });