A calm place to write long-form, and publish it to the open social web.
skypress.blog/
1import { describe, expect, it, vi } from 'vitest';
2import type { Agent } from '@atproto/api';
3import { fetchViewerProfile, displayNameFor, authorPath, accountMenuItems } from './profile';
4
5/** Minimal Agent stub: only getProfile is exercised. */
6function agentReturning( data: Record< string, unknown > ): Agent {
7 return { getProfile: vi.fn().mockResolvedValue( { data } ) } as unknown as Agent;
8}
9function agentThrowing(): Agent {
10 return { getProfile: vi.fn().mockRejectedValue( new Error( 'appview down' ) ) } as unknown as Agent;
11}
12
13describe( 'fetchViewerProfile', () => {
14 it( 'maps did, handle, displayName and avatar from getProfile', async () => {
15 const agent = agentReturning( {
16 did: 'did:plc:ignored',
17 handle: 'jane.bsky.social',
18 displayName: 'Jane Rivera',
19 avatar: 'https://cdn.example/av.jpg',
20 } );
21 const profile = await fetchViewerProfile( agent, 'did:plc:abc' );
22 expect( profile ).toEqual( {
23 did: 'did:plc:abc',
24 handle: 'jane.bsky.social',
25 displayName: 'Jane Rivera',
26 avatar: 'https://cdn.example/av.jpg',
27 } );
28 } );
29
30 it( 'falls back to nulls but keeps the did when getProfile throws', async () => {
31 const profile = await fetchViewerProfile( agentThrowing(), 'did:plc:abc' );
32 expect( profile ).toEqual( {
33 did: 'did:plc:abc',
34 handle: null,
35 displayName: null,
36 avatar: null,
37 } );
38 } );
39
40 it( 'coerces missing optional fields to null', async () => {
41 const agent = agentReturning( { did: 'did:plc:abc', handle: 'jane.bsky.social' } );
42 const profile = await fetchViewerProfile( agent, 'did:plc:abc' );
43 expect( profile.displayName ).toBeNull();
44 expect( profile.avatar ).toBeNull();
45 expect( profile.handle ).toBe( 'jane.bsky.social' );
46 } );
47} );
48
49describe( 'displayNameFor', () => {
50 const base = { did: 'did:plc:abc', handle: 'jane.bsky.social', avatar: null };
51 it( 'prefers displayName', () => {
52 expect( displayNameFor( { ...base, displayName: 'Jane Rivera' } ) ).toBe( 'Jane Rivera' );
53 } );
54 it( 'falls back to handle when displayName is null', () => {
55 expect( displayNameFor( { ...base, displayName: null } ) ).toBe( 'jane.bsky.social' );
56 } );
57 it( 'falls back to the did when handle and displayName are null', () => {
58 expect(
59 displayNameFor( { did: 'did:plc:abc', handle: null, displayName: null, avatar: null } )
60 ).toBe( 'did:plc:abc' );
61 } );
62} );
63
64describe( 'authorPath', () => {
65 it( 'builds the public page path for a handle', () => {
66 expect( authorPath( 'jane.bsky.social' ) ).toBe( '/@jane.bsky.social' );
67 } );
68 it( 'returns null when there is no handle', () => {
69 expect( authorPath( null ) ).toBeNull();
70 } );
71} );
72
73describe( 'accountMenuItems', () => {
74 const base = { did: 'did:plc:abc', displayName: 'Jane Rivera', avatar: null };
75
76 it( 'returns Dashboard, Write and Profile in order for a profile with a handle', () => {
77 expect( accountMenuItems( { ...base, handle: 'jane.bsky.social' } ) ).toEqual( [
78 { label: 'Dashboard', href: '/dashboard' },
79 { label: 'Write', href: '/write' },
80 { label: 'Profile', href: '/@jane.bsky.social' },
81 ] );
82 } );
83
84 it( 'omits the Profile item when no handle is known', () => {
85 expect( accountMenuItems( { ...base, handle: null } ) ).toEqual( [
86 { label: 'Dashboard', href: '/dashboard' },
87 { label: 'Write', href: '/write' },
88 ] );
89 } );
90} );