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 3.4 kB View raw
1// @vitest-environment node 2// Pure module — no DOM needed. Node's global Blob exposes `.text()`, which the 3// repo's pinned jsdom Blob does not, so run this file under the node environment. 4import { describe, it, expect } from 'vitest'; 5import { 6 isDataUrl, 7 dataUrlToBlob, 8 splitAssets, 9 mergeAssets, 10} from './held-assets'; 11import type { BlockNode } from '../blocks/render'; 12 13const DATA_A = 'data:image/png;base64,QUFB'; // "AAA" 14const DATA_B = 'data:image/jpeg;base64,QkJC'; // "BBB" 15 16function tree(): BlockNode[] { 17 return [ 18 { name: 'core/paragraph', attributes: { content: 'hi' }, innerBlocks: [] }, 19 { name: 'core/image', attributes: { url: DATA_A }, innerBlocks: [] }, 20 { 21 name: 'core/columns', 22 attributes: {}, 23 innerBlocks: [ 24 { name: 'core/image', attributes: { url: DATA_B }, innerBlocks: [] }, 25 { name: 'core/image', attributes: { url: 'https://ext/img.png' }, innerBlocks: [] }, 26 ], 27 }, 28 ]; 29} 30 31describe( 'isDataUrl', () => { 32 it( 'matches data: URLs only', () => { 33 expect( isDataUrl( DATA_A ) ).toBe( true ); 34 expect( isDataUrl( 'https://x/y.png' ) ).toBe( false ); 35 expect( isDataUrl( undefined ) ).toBe( false ); 36 } ); 37} ); 38 39describe( 'dataUrlToBlob', () => { 40 it( 'reconstructs bytes + mime from a data: URL', async () => { 41 const blob = dataUrlToBlob( DATA_A ); 42 expect( blob.type ).toBe( 'image/png' ); 43 expect( await blob.text() ).toBe( 'AAA' ); 44 } ); 45} ); 46 47describe( 'splitAssets / mergeAssets', () => { 48 it( 'extracts every data: image URL (depth-first) + cover into tokens and round-trips', () => { 49 const { skeleton, assets } = splitAssets( tree(), DATA_A ); 50 51 // Body data URLs replaced by tokens; external + non-image untouched. 52 expect( skeleton.blocks[ 1 ].attributes!.url ).toBe( 'a0' ); 53 expect( skeleton.blocks[ 2 ].innerBlocks![ 0 ].attributes!.url ).toBe( 'a1' ); 54 expect( skeleton.blocks[ 2 ].innerBlocks![ 1 ].attributes!.url ).toBe( 'https://ext/img.png' ); 55 expect( skeleton.cover ).toBe( 'cover' ); 56 expect( assets ).toEqual( { a0: DATA_A, a1: DATA_B, cover: DATA_A } ); 57 58 const merged = mergeAssets( skeleton, assets ); 59 expect( merged.blocks ).toEqual( tree() ); 60 expect( merged.coverDataUrl ).toBe( DATA_A ); 61 } ); 62 63 it( 'drops a dangling token (held bytes missing) but keeps external image URLs', () => { 64 // localStorage skeleton survived but the IndexedDB bytes were evicted, so the 65 // `a0` token has no entry in `assets`. The merged image must not keep `a0` as a 66 // broken src; an external image alongside it stays untouched. 67 const skeleton = { 68 blocks: [ 69 { name: 'core/image', attributes: { url: 'a0', alt: 'gone' }, innerBlocks: [] }, 70 { name: 'core/image', attributes: { url: 'https://ext/x.png' }, innerBlocks: [] }, 71 ], 72 cover: 'cover', 73 }; 74 const merged = mergeAssets( skeleton, {} ); 75 expect( merged.blocks[ 0 ].attributes ).toEqual( { alt: 'gone' } ); 76 expect( merged.blocks[ 1 ].attributes!.url ).toBe( 'https://ext/x.png' ); 77 expect( merged.coverDataUrl ).toBe( null ); 78 } ); 79 80 it( 'leaves a null cover null and a tree with no data URLs unchanged', () => { 81 const plain: BlockNode[] = [ 82 { name: 'core/image', attributes: { url: 'https://ext/x.png' }, innerBlocks: [] }, 83 ]; 84 const { skeleton, assets } = splitAssets( plain, null ); 85 expect( assets ).toEqual( {} ); 86 expect( skeleton.cover ).toBe( null ); 87 expect( mergeAssets( skeleton, assets ) ).toEqual( { blocks: plain, coverDataUrl: null } ); 88 } ); 89} );