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.8 kB View raw
1import { describe, expect, it } from 'vitest'; 2import { sanitizeArticleHtml } from './sanitize'; 3 4describe( 'sanitizeArticleHtml', () => { 5 it( 'strips <script> and inline event handlers (untrusted PDS content)', () => { 6 const dirty = '<p>Hi</p><script>alert(1)</script><img src="x" onerror="alert(2)">'; 7 const clean = sanitizeArticleHtml( dirty ); 8 expect( clean ).toContain( '<p>Hi</p>' ); 9 expect( clean ).not.toContain( '<script' ); 10 expect( clean ).not.toContain( 'onerror' ); 11 expect( clean ).not.toContain( 'alert(2)' ); 12 } ); 13 14 it( 'drops javascript: URLs but keeps safe links + images', () => { 15 const dirty = 16 '<a href="javascript:alert(1)">x</a><a href="https://ok.example">ok</a><img src="https://cdn.example/a.png" alt="a">'; 17 const clean = sanitizeArticleHtml( dirty ); 18 expect( clean ).not.toContain( 'javascript:' ); 19 expect( clean ).toContain( 'href="https://ok.example"' ); 20 expect( clean ).toContain( '<img' ); 21 expect( clean ).toContain( 'src="https://cdn.example/a.png"' ); 22 } ); 23 24 it( 'preserves the curated blocks’ tags + classes', () => { 25 const html = 26 '<h2 class="wp-block-heading">T</h2><pre class="wp-block-code"><code>x</code></pre><blockquote class="wp-block-quote"><p>q</p></blockquote>'; 27 const clean = sanitizeArticleHtml( html ); 28 expect( clean ).toContain( '<h2 class="wp-block-heading">' ); 29 expect( clean ).toContain( '<pre class="wp-block-code">' ); 30 expect( clean ).toContain( '<blockquote class="wp-block-quote">' ); 31 } ); 32 33 it( 'hardens external links with rel + target', () => { 34 const clean = sanitizeArticleHtml( '<a href="https://x.example">x</a>' ); 35 expect( clean ).toContain( 'rel="noopener noreferrer nofollow ugc"' ); 36 expect( clean ).toContain( 'target="_blank"' ); 37 } ); 38 39 it( 'keeps a mention link (class + href) but strips its data-did', () => { 40 const dirty = 41 '<p>Hi <a class="skypress-mention" href="https://bsky.app/profile/alice.bsky.social" data-did="did:plc:alice">@alice.bsky.social</a></p>'; 42 const clean = sanitizeArticleHtml( dirty ); 43 expect( clean ).toContain( 'class="skypress-mention"' ); 44 expect( clean ).toContain( 'href="https://bsky.app/profile/alice.bsky.social"' ); 45 expect( clean ).not.toContain( 'data-did' ); 46 expect( clean ).toContain( '@alice.bsky.social' ); 47 } ); 48} ); 49 50describe( 'embed cards', () => { 51 it( 'keeps the atproto card structure', () => { 52 const html = sanitizeArticleHtml( 53 '<figure class="wp-block-embed skypress-embed skypress-embed--atproto"><a class="skypress-embed__link" href="https://mu.social/profile/x/post/y"><span class="skypress-embed__text">hi</span></a></figure>' 54 ); 55 expect( html ).toContain( 'skypress-embed--atproto' ); 56 expect( html ).toContain( 'href="https://mu.social/profile/x/post/y"' ); 57 } ); 58 59 it( 'keeps the video facade button + scoped data attributes', () => { 60 const html = sanitizeArticleHtml( 61 '<figure class="wp-block-embed skypress-embed skypress-embed--video"><button type="button" class="skypress-embed__play" data-embed-provider="youtube" data-embed-id="dQw4w9WgXcQ"><img class="skypress-embed__thumb" src="https://i/x.jpg" alt=""/></button></figure>' 62 ); 63 expect( html ).toContain( '<button' ); 64 expect( html ).toContain( 'data-embed-provider="youtube"' ); 65 expect( html ).toContain( 'data-embed-id="dQw4w9WgXcQ"' ); 66 } ); 67 68 it( 'still strips a document-injected iframe', () => { 69 const html = sanitizeArticleHtml( '<p>x</p><iframe src="https://evil.com"></iframe>' ); 70 expect( html ).not.toContain( '<iframe' ); 71 } ); 72 73 it( 'strips data attributes on non-button elements and onclick handlers', () => { 74 const html = sanitizeArticleHtml( 75 '<span data-embed-id="x" onclick="alert(1)">t</span>' 76 ); 77 expect( html ).not.toContain( 'data-embed-id' ); 78 expect( html ).not.toContain( 'onclick' ); 79 } ); 80} );