| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457 |
- import { test } from 'uvu'
- import * as assert from 'uvu/assert'
- import { visit } from 'unist-util-visit'
- import { rehype } from 'rehype'
- import { unified } from 'unified'
- import remarkParse from 'remark-parse'
- import remarkRehype from 'remark-rehype'
- import rehypeStringify from 'rehype-stringify'
- import dedent from 'dedent'
- import rehypePrism from './src/index.js'
- /**
- * Mock meta in code block
- */
- const addMeta = (metastring) => {
- if (!metastring) return
- return (tree) => {
- visit(tree, 'element', (node, index, parent) => {
- if (node.tagName === 'code') {
- node.data = { meta: metastring }
- }
- })
- }
- }
- const processHtml = (html, options, metastring) => {
- return rehype()
- .data('settings', { fragment: true })
- .use(addMeta, metastring)
- .use(rehypePrism, options)
- .processSync(html)
- .toString()
- }
- const processHtmlUnified = (html, options, metastring) => {
- return unified()
- .use(remarkParse)
- .use(remarkRehype, {})
- .use(addMeta, metastring)
- .use(rehypePrism, options)
- .use(rehypeStringify)
- .processSync(html)
- .toString()
- }
- test('adds a code-highlight class to the code and pre tag', () => {
- const result = processHtml(dedent`
- <pre><code class="language-py"></code></pre>
- `)
- const expected = dedent`<pre class="language-py"><code class="language-py code-highlight"></code></pre>`
- assert.is(result, expected)
- })
- test('add span with class code line for each line', () => {
- const result = processHtml(
- dedent`
- <pre><code>x = 6</code></pre>
- `
- )
- const expected = dedent`<pre><code class="code-highlight"><span class="code-line">x = 6</span></code></pre>`
- assert.is(result, expected)
- })
- test('finds code and highlights', () => {
- const result = processHtml(dedent`
- <div>
- <p>foo</p>
- <pre><code class="language-py">x = 6</code></pre>
- </div>
- `).trim()
- const expected = dedent`
- <div>
- <p>foo</p>
- <pre class="language-py"><code class="language-py code-highlight"><span class="code-line">x <span class="token operator">=</span> <span class="token number">6</span></span></code></pre>
- </div>
- `
- assert.is(result, expected)
- })
- test('respects line spacing', () => {
- const result = processHtml(dedent`
- <div>
- <pre><code class="language-py">x
- y
- </code></pre>
- </div>
- `).trim()
- const expected = dedent`
- <div>
- <pre class="language-py"><code class="language-py code-highlight"><span class="code-line">x
- </span><span class="code-line">
- </span><span class="code-line">y
- </span></code></pre>
- </div>
- `
- assert.is(result, expected)
- })
- test('handles uppercase correctly', () => {
- const result = processHtml(dedent`
- <div>
- <p>foo</p>
- <pre><code class="language-PY">x = 6</code></pre>
- </div>
- `).trim()
- const expected = dedent`
- <div>
- <p>foo</p>
- <pre class="language-py"><code class="language-PY code-highlight"><span class="code-line">x <span class="token operator">=</span> <span class="token number">6</span></span></code></pre>
- </div>
- `
- assert.is(result, expected)
- })
- test('each line of code should be a separate div', async () => {
- const result = processHtml(dedent`
- <div>
- <p>foo</p>
- <pre class="language-py">
- <code class="language-py code-highlight">x = 6
- y = 7
- </code>
- </pre>
- </div>
- `).trim()
- const codeLineCount = (result.match(/<span class="code-line">/g) || []).length
- assert.is(codeLineCount, 2)
- })
- test('should highlight line', async () => {
- const meta = '{1}'
- const result = processHtml(
- dedent`
- <div>
- <pre>
- <code class="language-py code-highlight">x = 6
- y = 7
- </code>
- </pre>
- </div>
- `,
- {},
- meta
- ).trim()
- const codeHighlightCount = (result.match(/<span class="code-line highlight-line">/g) || []).length
- assert.is(codeHighlightCount, 1)
- })
- test('should highlight comma separated lines', async () => {
- const meta = '{1,3}'
- const result = processHtml(
- dedent`
- <div>
- <pre>
- <code class="language-py code-highlight">x = 6
- y = 7
- z = 10
- </code>
- </pre>
- </div>
- `,
- {},
- meta
- ).trim()
- const codeHighlightCount = (result.match(/<span class="code-line highlight-line">/g) || []).length
- assert.is(codeHighlightCount, 2)
- })
- test('should should parse ranges with a space in between', async () => {
- const meta = '{1, 3}'
- const result = processHtml(
- dedent`
- <div>
- <pre>
- <code class="language-py code-highlight">x = 6
- y = 7
- z = 10
- </code>
- </pre>
- </div>
- `,
- {},
- meta
- ).trim()
- const codeHighlightCount = (result.match(/<span class="code-line highlight-line">/g) || []).length
- assert.is(codeHighlightCount, 2)
- })
- test('should highlight range separated lines', async () => {
- const meta = '{1-3}'
- const result = processHtml(
- dedent`
- <div>
- <pre>
- <code class="language-py code-highlight">x = 6
- y = 7
- z = 10
- </code>
- </pre>
- </div>
- `,
- {},
- meta
- ).trim()
- const codeHighlightCount = (result.match(/<span class="code-line highlight-line">/g) || []).length
- assert.is(codeHighlightCount, 3)
- })
- test('showLineNumbers option add line numbers', async () => {
- const result = processHtml(
- dedent`
- <div>
- <pre>
- <code class="language-py code-highlight">x = 6
- y = 7
- </code>
- </pre>
- </div>
- `,
- { showLineNumbers: true }
- ).trim()
- assert.ok(result.match(/line="1"/g))
- assert.ok(result.match(/line="2"/g))
- assert.not(result.match(/line="3"/g))
- })
- test('not show line number when showLineNumbers=false', async () => {
- const meta = 'showLineNumbers=false'
- const result = processHtml(
- dedent`
- <div>
- <pre>
- <code class="language-py code-highlight">x = 6
- y = 7
- </code>
- </pre>
- </div>
- `,
- { showLineNumbers: true },
- meta
- ).trim()
- assert.not(result.match(/line="1"/g))
- assert.not(result.match(/line="2"/g))
- })
- test('not show line number when showLineNumbers={false}', async () => {
- const meta = 'showLineNumbers={false}'
- const result = processHtml(
- dedent`
- <div>
- <pre>
- <code class="language-py code-highlight">x = 6
- y = 7
- </code>
- </pre>
- </div>
- `,
- { showLineNumbers: true },
- meta
- ).trim()
- assert.not(result.match(/line="1"/g))
- assert.not(result.match(/line="2"/g))
- })
- test('showLineNumbers property works in meta field', async () => {
- const meta = 'showLineNumbers'
- const result = processHtml(
- dedent`
- <div>
- <pre>
- <code class="language-py code-highlight">x = 6
- y = 7
- </code>
- </pre>
- </div>
- `,
- {},
- meta
- ).trim()
- assert.ok(result.match(/line="1"/g))
- assert.ok(result.match(/line="2"/g))
- assert.not(result.match(/line="3"/g))
- })
- test('showLineNumbers property with custom index works in meta field', async () => {
- const meta = 'showLineNumbers=5'
- const result = processHtml(
- dedent`
- <div>
- <pre>
- <code class="language-py code-highlight">x = 6
- y = 7
- </code>
- </pre>
- </div>
- `,
- {},
- meta
- ).trim()
- assert.ok(result.match(/line="5"/g))
- assert.ok(result.match(/line="6"/g))
- assert.not(result.match(/line="7"/g))
- })
- test('should support both highlighting and add line number', async () => {
- const meta = '{1} showLineNumbers'
- const result = processHtml(
- dedent`
- <div>
- <pre>
- <code class="language-py">x = 6
- y = 7
- z = 10
- </code>
- </pre>
- </div>
- `,
- {},
- meta
- ).trim()
- const codeHighlightCount = (result.match(/highlight-line/g) || []).length
- assert.is(codeHighlightCount, 1)
- assert.ok(result.match(/line="1"/g))
- assert.ok(result.match(/line="2"/g))
- })
- test('throw error with fake language- class', () => {
- assert.throws(
- () =>
- processHtml(dedent`
- <pre><code class="language-thisisnotalanguage">x = 6</code></pre>
- `),
- /Unknown language/
- )
- })
- test('with options.ignoreMissing, does nothing to code block with fake language- class', () => {
- const result = processHtml(
- dedent`
- <pre><code class="language-thisisnotalanguage">x = 6</code></pre>
- `,
- { ignoreMissing: true }
- )
- const expected = dedent`<pre><code class="language-thisisnotalanguage code-highlight"><span class="code-line">x = 6</span></code></pre>`
- assert.is(result, expected)
- })
- test('with options.defaultLanguage, it adds the correct language class tag', () => {
- const result = processHtml(
- dedent`
- <pre><code>x = 6</code></pre>
- `,
- { defaultLanguage: 'py' }
- )
- const expected = dedent`<pre class="language-py"><code class="language-py code-highlight"><span class="code-line">x <span class="token operator">=</span> <span class="token number">6</span></span></code></pre>`
- assert.is(result, expected)
- })
- test('defaultLanguage should produce the same syntax tree as if manually specified', () => {
- const resultDefaultLanguage = processHtml(
- dedent`
- <pre><code>x = 6</code></pre>
- `,
- { defaultLanguage: 'py' }
- )
- const resultManuallySpecified = processHtml(
- dedent`
- <pre><code class="language-py">x = 6</code></pre>
- `
- )
- assert.is(resultDefaultLanguage, resultManuallySpecified)
- })
- test('throws error if options.defaultLanguage is not registered with refractor', () => {
- assert.throws(
- () =>
- processHtml(
- dedent`
- <pre><code>x = 6</code></pre>
- `,
- { defaultLanguage: 'pyzqt' }
- ),
- /"pyzqt" is not registered with refractor/
- )
- })
- test('should work with multiline code / comments', () => {
- const result = processHtml(
- dedent`
- <pre><code class="language-js">
- /**
- * My comment
- */
- </code></pre>
- `,
- { ignoreMissing: true }
- )
- const expected = dedent`<pre class="language-js"><code class="language-js code-highlight"><span class="code-line">
- </span><span class="code-line"><span class="token doc-comment comment">/**
- </span></span><span class="code-line"><span class="token doc-comment comment"> * My comment
- </span></span><span class="code-line"><span class="token doc-comment comment"> */</span>
- </span></code></pre>`
- assert.is(result, expected)
- })
- test('adds inserted or deleted to code-line if lang=diff', async () => {
- const result = processHtml(
- dedent`
- <div>
- <pre>
- <code class="language-diff">+ x = 6
- - y = 7
- z = 10
- </code>
- </pre>
- </div>
- `
- ).trim()
- assert.ok(result.includes(`<span class="code-line inserted">`))
- assert.ok(result.includes(`<span class="code-line deleted">`))
- assert.ok(result.includes(`<span class="code-line">`))
- })
- test('works as a remarkjs / unifiedjs plugin', () => {
- const result = processHtmlUnified(
- dedent`
- ~~~jsx
- <Component/>
- ~~~
- `,
- { ignoreMissing: true }
- )
- const expected = dedent`<pre class="language-jsx"><code class="language-jsx code-highlight"><span class="code-line"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Component</span></span><span class="token punctuation">/></span></span>
- </span></code></pre>`
- assert.is(result, expected)
- })
- test('diff and code highlighting should work together', () => {
- const result = processHtml(
- dedent`
- <pre><code class="language-diff-css">
- .hello{
- - background:url('./urel.png');
- + background-image:url('./urel.png');
- }
- </code></pre>
- `,
- { ignoreMissing: true }
- )
- assert.ok(result.includes(`<pre class="language-css">`))
- assert.ok(result.includes(`<span class="code-line inserted">`))
- assert.ok(result.includes(`<span class="code-line deleted">`))
- assert.ok(result.includes(`<span class="code-line">`))
- })
- test.run()
|