A calm place to write long-form, and publish it to the open social web. skypress.blog/
0

Configure Feed

Select the types of activity you want to include in your feed.

at trunk 5.1 kB View raw
1import { beforeAll, describe, expect, it } from 'vitest'; 2import { createBlock } from '@wordpress/blocks'; 3import { registerSkyPressBlocks, serializeBlocks } from './serialize'; 4import { renderBlocks, blocksToText } from './render'; 5 6/** Strip Gutenberg block-delimiter comments, leaving the frontend HTML. */ 7function stripDelimiters( markup: string ): string { 8 return markup.replace( /<!--\s*\/?wp:[\s\S]*?-->/g, '' ); 9} 10 11/** Collapse insignificant whitespace so structural comparisons are stable. */ 12function normalize( html: string ): string { 13 return html 14 .replace( />\s+</g, '><' ) 15 .replace( /\s+/g, ' ' ) 16 .trim(); 17} 18 19beforeAll( () => { 20 registerSkyPressBlocks(); 21} ); 22 23describe( 'renderBlocks — fidelity vs @wordpress/blocks.serialize()', () => { 24 it( 'reproduces the save-HTML of the curated sample blocks', () => { 25 // The dependency-free renderer must produce the SAME frontend HTML that 26 // Gutenberg's own save functions produce (the oracle). This locks the light 27 // reader renderer to the real packages without importing them at runtime. 28 const tree = [ 29 createBlock( 'core/heading', { 30 level: 1, 31 content: "The open sky meets the typesetter's bench", 32 } ), 33 createBlock( 'core/paragraph', { 34 content: 'You write in <strong>blocks</strong> &amp; own your words.', 35 } ), 36 createBlock( 'core/list', {}, [ 37 createBlock( 'core/list-item', { content: 'Your data lives on your PDS.' } ), 38 createBlock( 'core/list-item', { content: 'Rendered with the same packages.' } ), 39 ] ), 40 createBlock( 'core/quote', {}, [ 41 createBlock( 'core/paragraph', { content: 'A place to write things worth keeping.' } ), 42 ] ), 43 createBlock( 'core/code', { 44 content: 'agent.com.atproto.repo.createRecord( { collection, record } )', 45 } ), 46 createBlock( 'core/separator' ), 47 ]; 48 49 const expected = normalize( stripDelimiters( serializeBlocks( tree ) ) ); 50 const actual = normalize( renderBlocks( tree ) ); 51 52 expect( actual ).toBe( expected ); 53 } ); 54 55 it( 'reproduces a paragraph containing a skypress mention', async () => { 56 // Registering the mention format teaches @wordpress/blocks how to serialize the 57 // anchor (incl. data-did). Without it, serialize() would drop the attribute. 58 const { registerMentionFormat } = await import( '../editor/mention-format' ); 59 registerMentionFormat(); 60 61 const tree = [ 62 createBlock( 'core/paragraph', { 63 content: 64 'Thanks <a class="skypress-mention" href="https://bsky.app/profile/alice.bsky.social" data-did="did:plc:alice">@alice.bsky.social</a>!', 65 } ), 66 ]; 67 68 const expected = normalize( stripDelimiters( serializeBlocks( tree ) ) ); 69 const actual = normalize( renderBlocks( tree ) ); 70 71 expect( actual ).toBe( expected ); 72 } ); 73 74 it( 'renders an ordered list as <ol>', () => { 75 const tree = [ 76 createBlock( 'core/list', { ordered: true }, [ 77 createBlock( 'core/list-item', { content: 'First' } ), 78 ] ), 79 ]; 80 expect( renderBlocks( tree ) ).toContain( '<ol' ); 81 } ); 82} ); 83 84describe( 'blocksToText (textContent extraction)', () => { 85 it( 'yields clean plain text in document order, stripping inline markup', () => { 86 const blocks = [ 87 createBlock( 'core/heading', { level: 2, content: 'Welcome' } ), 88 createBlock( 'core/paragraph', { 89 content: 'Hello <strong>world</strong> &amp; friends', 90 } ), 91 ]; 92 expect( blocksToText( blocks ) ).toBe( 'Welcome\n\nHello world & friends' ); 93 } ); 94 95 it( 'recurses into inner blocks (list items)', () => { 96 const blocks = [ 97 createBlock( 'core/list', {}, [ 98 createBlock( 'core/list-item', { content: 'First' } ), 99 createBlock( 'core/list-item', { content: 'Second' } ), 100 ] ), 101 ]; 102 expect( blocksToText( blocks ) ).toBe( 'First\n\nSecond' ); 103 } ); 104 105 it( 'ignores blocks without text (separator) without leaving blank gaps', () => { 106 const blocks = [ 107 createBlock( 'core/paragraph', { content: 'Before' } ), 108 createBlock( 'core/separator' ), 109 createBlock( 'core/paragraph', { content: 'After' } ), 110 ]; 111 expect( blocksToText( blocks ) ).toBe( 'Before\n\nAfter' ); 112 } ); 113} ); 114 115describe( 'core/embed', () => { 116 it( 'renders the resolved card when a payload is attached', () => { 117 const html = renderBlocks( [ 118 { 119 name: 'core/embed', 120 attributes: { 121 url: 'https://bsky.app/profile/a/post/b', 122 _skypressEmbed: { 123 kind: 'atproto', 124 authorName: 'A', 125 handle: 'a.bsky.social', 126 text: 'hi', 127 images: [], 128 viewUrl: 'https://mu.social/profile/did:plc:a/post/b', 129 }, 130 }, 131 }, 132 ] ); 133 expect( html ).toContain( 'skypress-embed--atproto' ); 134 expect( html ).toContain( 'hi' ); 135 } ); 136 137 it( 'falls back to a plain link when there is no payload', () => { 138 const html = renderBlocks( [ 139 { name: 'core/embed', attributes: { url: 'https://example.com/x' } }, 140 ] ); 141 expect( html ).toContain( '<a' ); 142 expect( html ).toContain( 'https://example.com/x' ); 143 expect( html ).not.toContain( 'skypress-embed' ); 144 } ); 145 146 it( 'renders nothing for an embed with no url and no payload', () => { 147 expect( renderBlocks( [ { name: 'core/embed', attributes: {} } ] ) ).toBe( '' ); 148 } ); 149} );