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.

1import { describe, expect, it } from 'vitest'; 2import { highlightCodeBlocks, highlightJson, highlightSource } from './highlight'; 3 4const block = ( inner: string ) => 5 `<pre class="wp-block-code"><code>${ inner }</code></pre>`; 6 7describe( 'highlightCodeBlocks', () => { 8 it( 'tokenises a detected language into hljs spans', () => { 9 // A clearly-JavaScript snippet; auto-detect should produce tokens. 10 const out = highlightCodeBlocks( block( 'const answer = 42;' ) ); 11 expect( out ).toContain( 'class="hljs' ); 12 expect( out ).toContain( '<span class="hljs-' ); 13 // Still a single wp-block-code pre. 14 expect( out ).toContain( '<pre class="wp-block-code"><code class="hljs' ); 15 expect( out ).toContain( '</code></pre>' ); 16 } ); 17 18 it( 'keeps escaped angle brackets escaped (no raw HTML injected)', () => { 19 // render.ts stores code entity-escaped; markup must never be un-escaped into live tags. 20 const out = highlightCodeBlocks( block( '&lt;div class="x"&gt;hi&lt;/div&gt;' ) ); 21 expect( out ).toContain( '&lt;' ); 22 expect( out ).not.toContain( '<div class="x">' ); 23 } ); 24 25 it( 'leaves inline <code> (not in a wp-block-code pre) untouched', () => { 26 const html = '<p>Use <code>npm run dev</code> to start.</p>'; 27 expect( highlightCodeBlocks( html ) ).toBe( html ); 28 } ); 29 30 it( 'leaves non-code HTML untouched', () => { 31 const html = '<h2 class="wp-block-heading">Title</h2><p>Body.</p>'; 32 expect( highlightCodeBlocks( html ) ).toBe( html ); 33 } ); 34 35 it( 'falls back to a plain wp-block-code block when nothing is highlightable', () => { 36 // Empty content can yield zero-relevance auto-detect; must not throw or corrupt markup. 37 const out = highlightCodeBlocks( block( '' ) ); 38 expect( out ).toContain( '<pre class="wp-block-code">' ); 39 expect( out ).toContain( '</code></pre>' ); 40 expect( out ).not.toContain( 'hljs' ); 41 expect( out ).toContain( '<pre class="wp-block-code"><code></code></pre>' ); 42 } ); 43 44 it( 'highlights every block when there are several', () => { 45 const out = highlightCodeBlocks( 46 block( 'const a = 1;' ) + '<p>mid</p>' + block( 'let b = 2;' ) 47 ); 48 // One highlighted wrapper per code block (token spans also carry hljs-* classes, 49 // so count the <code> wrapper specifically, not every hljs class). 50 const wrappers = out.match( /<code class="hljs/g ) ?? []; 51 expect( wrappers.length ).toBe( 2 ); 52 expect( out ).toContain( '<p>mid</p>' ); 53 } ); 54 55 it( 'converts <br> line breaks in code to real newlines (never literal tag text)', () => { 56 // Some stored code blocks use <br /> as the line separator (render.ts passes it 57 // through; the sanitiser keeps it as a real break). Highlighting must tokenise 58 // clean multi-line source, not turn the breaks into literal "<br>" text. 59 const out = highlightCodeBlocks( 60 block( '{<br /> "name": "potato",<br /> "value": 42<br />}' ) 61 ); 62 expect( out ).not.toContain( '<br' ); // no raw <br> passed through to hljs 63 expect( out ).not.toContain( '&lt;' ); // and not escaped into literal "<br>" text 64 expect( out ).toContain( '\n' ); // line breaks preserved as real newlines 65 expect( out ).toContain( 'class="hljs' ); // still highlighted 66 } ); 67 68 it( 'keeps a literal <br> typed in a code sample as text, not a newline', () => { 69 // A literal `<br>` in a code sample is stored escaped (`&lt;br&gt;`), unlike the 70 // real break tags above. It must survive as escaped tag text after highlighting, 71 // not be collapsed into a newline along with the genuine separators. 72 const out = highlightCodeBlocks( block( 'const html = "&lt;br&gt;";' ) ); 73 expect( out ).toContain( '&lt;br&gt;' ); // literal example tag preserved as text 74 expect( out ).not.toContain( '\n' ); // not turned into a line break 75 expect( out ).toContain( 'class="hljs' ); // still highlighted 76 } ); 77} ); 78 79describe( 'highlightJson', () => { 80 it( 'tokenises JSON into hljs spans inside a language-json code element', () => { 81 const out = highlightJson( '{\n "answer": 42\n}' ); 82 expect( out ).toContain( '<code class="hljs language-json">' ); 83 expect( out ).toContain( '<span class="hljs-' ); 84 expect( out ).toContain( '</code>' ); 85 // No <pre> wrapper — the caller owns the chrome. 86 expect( out ).not.toContain( '<pre' ); 87 } ); 88 89 it( 'keeps HTML in string values escaped (no raw tags from untrusted records)', () => { 90 const out = highlightJson( '{\n "x": "</script><img src=x onerror=alert(1)>"\n}' ); 91 expect( out ).toContain( '&lt;' ); 92 expect( out ).not.toContain( '<img' ); 93 expect( out ).not.toContain( '</script>' ); 94 } ); 95 96 it( 'handles an empty object without throwing', () => { 97 const out = highlightJson( '{}' ); 98 expect( out ).toContain( '<code class="hljs language-json">' ); 99 expect( out ).toContain( '</code>' ); 100 } ); 101} ); 102 103describe( 'highlightSource', () => { 104 it( 'tokenises a known-language source string into hljs spans', () => { 105 // Unlike the reader blocks, static callers (e.g. the lexicon schema dump) know 106 // the language up front, so we highlight directly instead of auto-detecting. 107 const out = highlightSource( '{ "name": "potato", "value": 42 }', 'json' ); 108 expect( out ).toContain( '<span class="hljs-' ); 109 expect( out ).toContain( 'potato' ); 110 } ); 111 112 it( 'escapes angle brackets, never injecting raw markup', () => { 113 const out = highlightSource( '{ "tag": "<b>" }', 'json' ); 114 expect( out ).not.toContain( '<b>' ); 115 expect( out ).toContain( '&lt;b&gt;' ); 116 } ); 117 118 it( 'falls back to plain escaped text for an unregistered language', () => { 119 // An unknown grammar makes hljs throw; the source must still render as safe, 120 // entity-escaped text — never raw markup, never an error bubbling to the page. 121 const out = highlightSource( 'a < b && c > d', 'no-such-lang' ); 122 expect( out ).not.toContain( 'hljs-' ); 123 expect( out ).toContain( '&lt;' ); 124 expect( out ).toContain( '&amp;' ); 125 expect( out ).toContain( '&gt;' ); 126 } ); 127} );