A calm place to write long-form, and publish it to the open social web.
skypress.blog/
1import { describe, expect, it, vi, beforeEach } from 'vitest';
2import { listAllReaderPublications } from './publications';
3import { listRecords } from './records';
4import { SITE_BASE } from '../publish/records';
5import { THEME_PRESETS } from '../publish/themes';
6
7vi.mock( './records', () => ( { listRecords: vi.fn() } ) );
8const mockedList = listRecords as unknown as ReturnType< typeof vi.fn >;
9
10const PDS = 'https://pds.example';
11const DID = 'did:plc:me';
12
13function rec( rkey: string, url: string, extra: Record< string, unknown > = {} ) {
14 return { uri: `at://${ DID }/site.standard.publication/${ rkey }`, cid: 'c', value: { url, name: `P${ rkey }`, ...extra } };
15}
16
17beforeEach( () => mockedList.mockReset() );
18
19describe( 'listAllReaderPublications', () => {
20 it( 'keeps only SkyPress-origin, slugged publications as owned', async () => {
21 mockedList.mockResolvedValue( [
22 rec( 'a', `${ SITE_BASE }/@me.bsky.social/blog`, { description: ' Hi ' } ),
23 rec( 'b', 'https://leaflet.pub/x/y' ),
24 rec( 'c', `${ SITE_BASE }/@me.bsky.social` ),
25 ] );
26 const { owned } = await listAllReaderPublications( PDS, DID );
27 expect( owned ).toHaveLength( 1 );
28 expect( owned[ 0 ] ).toMatchObject( {
29 uri: `at://${ DID }/site.standard.publication/a`,
30 slug: 'blog',
31 name: 'Pa',
32 description: 'Hi',
33 } );
34 } );
35
36 it( 'partitions owned SkyPress publications from foreign ones', async () => {
37 const icon = { ref: { $link: 'bafyicon' }, mimeType: 'image/png', size: 1 };
38 mockedList.mockResolvedValue( [
39 rec( 'a', `${ SITE_BASE }/@me.bsky.social/blog`, { description: ' Hi ' } ),
40 rec( 'b', 'https://leaflet.pub/lish/did:plc:me/xyz', { name: 'My Leaflet', icon } ),
41 rec( 'c', 'ipfs://not-a-web-url' ), // non-http(s) → excluded everywhere
42 rec( 'd', `${ SITE_BASE }/@me.bsky.social` ), // SkyPress, no slug → dropped
43 ] );
44 const { owned, foreign } = await listAllReaderPublications( PDS, DID );
45
46 expect( owned ).toHaveLength( 1 );
47 expect( owned[ 0 ] ).toMatchObject( { slug: 'blog', name: 'Pa' } );
48
49 expect( foreign ).toHaveLength( 1 );
50 expect( foreign[ 0 ] ).toEqual( {
51 uri: `at://${ DID }/site.standard.publication/b`,
52 name: 'My Leaflet',
53 hostname: 'leaflet.pub',
54 url: 'https://leaflet.pub/lish/did:plc:me/xyz',
55 icon,
56 provider: 'leaflet',
57 } );
58 } );
59
60 it( 'tags each foreign publication with its hostname and detected provider', async () => {
61 mockedList.mockResolvedValue( [
62 rec( 'a', 'https://jehervecom.leaflet.pub' ),
63 rec( 'b', 'https://my-domain.example', { theme: { $type: 'blog.pckt.theme' } } ),
64 rec( 'c', 'https://unknown.example/path' ),
65 ] );
66 const { foreign } = await listAllReaderPublications( PDS, DID );
67 const byRkey = Object.fromEntries(
68 foreign.map( ( p ) => [ p.uri.split( '/' ).pop(), { hostname: p.hostname, provider: p.provider } ] )
69 );
70 expect( byRkey ).toEqual( {
71 a: { hostname: 'jehervecom.leaflet.pub', provider: 'leaflet' },
72 b: { hostname: 'my-domain.example', provider: 'pckt' },
73 c: { hostname: 'unknown.example', provider: null },
74 } );
75 } );
76
77 it( 'falls back to the hostname when a foreign record has no name', async () => {
78 mockedList.mockResolvedValue( [
79 { uri: `at://${ DID }/site.standard.publication/n`, cid: 'c', value: { url: 'https://leaflet.pub/x' } },
80 ] );
81 const { foreign } = await listAllReaderPublications( PDS, DID );
82 expect( foreign[ 0 ] ).toMatchObject( { name: 'leaflet.pub', icon: null } );
83 } );
84} );
85
86describe( 'reader basicTheme', () => {
87 it( 'surfaces a valid stored basicTheme', async () => {
88 mockedList.mockResolvedValue( [
89 rec( 'a', `${ SITE_BASE }/@me.bsky.social/blog`, {
90 basicTheme: THEME_PRESETS[ 5 ].colors, // morning
91 } ),
92 ] );
93 const { owned } = await listAllReaderPublications( PDS, DID );
94 expect( owned[ 0 ].basicTheme ).toEqual( THEME_PRESETS[ 5 ].colors );
95 } );
96
97 it( 'sets basicTheme to null when absent or malformed', async () => {
98 mockedList.mockResolvedValue( [
99 rec( 'a', `${ SITE_BASE }/@me.bsky.social/plain` ),
100 rec( 'b', `${ SITE_BASE }/@me.bsky.social/broken`, { basicTheme: { accent: 'red' } } ),
101 ] );
102 const { owned } = await listAllReaderPublications( PDS, DID );
103 expect( owned.find( ( p ) => p.slug === 'plain' )?.basicTheme ).toBeNull();
104 expect( owned.find( ( p ) => p.slug === 'broken' )?.basicTheme ).toBeNull();
105 } );
106} );