test.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. import { test } from 'uvu'
  2. import * as assert from 'uvu/assert'
  3. import { visit } from 'unist-util-visit'
  4. import { rehype } from 'rehype'
  5. import dedent from 'dedent'
  6. import rehypePrism from './index.js'
  7. /**
  8. * Mock meta in code block
  9. */
  10. const addMeta = (metastring) => {
  11. if (!metastring) return
  12. return (tree) => {
  13. visit(tree, 'element', (node, index, parent) => {
  14. if (node.tagName === 'code') {
  15. node.data = { meta: metastring }
  16. }
  17. })
  18. }
  19. }
  20. const processHtml = (html, options, metastring) => {
  21. return rehype()
  22. .data('settings', { fragment: true })
  23. .use(addMeta, metastring)
  24. .use(rehypePrism, options)
  25. .processSync(html)
  26. .toString()
  27. }
  28. test('copies the language- class to pre tag', () => {
  29. const result = processHtml(dedent`
  30. <pre><code class="language-py"></code></pre>
  31. `)
  32. const expected = dedent`<pre class="language-py"><code class="language-py"></code></pre>`
  33. assert.is(result, expected)
  34. })
  35. test('add div with class code line for each line', () => {
  36. const result = processHtml(dedent`
  37. <pre><code>x = 6</code></pre>
  38. `)
  39. const expected = dedent`<pre><code><div class="code-line">x = 6</div></code></pre>`
  40. assert.is(result, expected)
  41. })
  42. test('finds code and highlights', () => {
  43. const result = processHtml(dedent`
  44. <div>
  45. <p>foo</p>
  46. <pre><code class="language-py">x = 6</code></pre>
  47. </div>
  48. `).trim()
  49. const expected = dedent`
  50. <div>
  51. <p>foo</p>
  52. <pre class="language-py"><code class="language-py"><div class="code-line">x <span class="token operator">=</span> <span class="token number">6</span></div></code></pre>
  53. </div>
  54. `
  55. assert.is(result, expected)
  56. })
  57. test('respects line spacing', () => {
  58. const result = processHtml(dedent`
  59. <div>
  60. <pre><code class="language-py">x
  61. y
  62. </code></pre>
  63. </div>
  64. `).trim()
  65. const expected = dedent`
  66. <div>
  67. <pre class="language-py"><code class="language-py"><div class="code-line">x
  68. </div><div class="code-line">
  69. </div><div class="code-line">y
  70. </div></code></pre>
  71. </div>
  72. `
  73. assert.is(result, expected)
  74. })
  75. test('handles uppercase correctly', () => {
  76. const result = processHtml(dedent`
  77. <div>
  78. <p>foo</p>
  79. <pre><code class="language-PY">x = 6</code></pre>
  80. </div>
  81. `).trim()
  82. const expected = dedent`
  83. <div>
  84. <p>foo</p>
  85. <pre class="language-py"><code class="language-PY"><div class="code-line">x <span class="token operator">=</span> <span class="token number">6</span></div></code></pre>
  86. </div>
  87. `
  88. assert.is(result, expected)
  89. })
  90. test('each line of code should be a separate div', async () => {
  91. const result = processHtml(dedent`
  92. <div>
  93. <p>foo</p>
  94. <pre>
  95. <code class="language-py">x = 6
  96. y = 7
  97. </code>
  98. </pre>
  99. </div>
  100. `).trim()
  101. const codeLineCount = (result.match(/<div class="code-line">/g) || []).length
  102. assert.is(codeLineCount, 2)
  103. })
  104. test('should highlight line', async () => {
  105. const meta = '{1}'
  106. const result = processHtml(
  107. dedent`
  108. <div>
  109. <pre>
  110. <code class="language-py">x = 6
  111. y = 7
  112. </code>
  113. </pre>
  114. </div>
  115. `,
  116. {},
  117. meta
  118. ).trim()
  119. const codeHighlightCount = (result.match(/<div class="code-line highlight-line">/g) || []).length
  120. assert.is(codeHighlightCount, 1)
  121. })
  122. test('should highlight comma separated lines', async () => {
  123. const meta = '{1,3}'
  124. const result = processHtml(
  125. dedent`
  126. <div>
  127. <pre>
  128. <code class="language-py">x = 6
  129. y = 7
  130. z = 10
  131. </code>
  132. </pre>
  133. </div>
  134. `,
  135. {},
  136. meta
  137. ).trim()
  138. const codeHighlightCount = (result.match(/<div class="code-line highlight-line">/g) || []).length
  139. assert.is(codeHighlightCount, 2)
  140. })
  141. test('should should parse ranges with a space in between', async () => {
  142. const meta = '{1, 3}'
  143. const result = processHtml(
  144. dedent`
  145. <div>
  146. <pre>
  147. <code class="language-py">x = 6
  148. y = 7
  149. z = 10
  150. </code>
  151. </pre>
  152. </div>
  153. `,
  154. {},
  155. meta
  156. ).trim()
  157. const codeHighlightCount = (result.match(/<div class="code-line highlight-line">/g) || []).length
  158. assert.is(codeHighlightCount, 2)
  159. })
  160. test('should highlight range separated lines', async () => {
  161. const meta = '{1-3}'
  162. const result = processHtml(
  163. dedent`
  164. <div>
  165. <pre>
  166. <code class="language-py">x = 6
  167. y = 7
  168. z = 10
  169. </code>
  170. </pre>
  171. </div>
  172. `,
  173. {},
  174. meta
  175. ).trim()
  176. const codeHighlightCount = (result.match(/<div class="code-line highlight-line">/g) || []).length
  177. assert.is(codeHighlightCount, 3)
  178. })
  179. test('showLineNumbers option add line numbers', async () => {
  180. const result = processHtml(
  181. dedent`
  182. <div>
  183. <pre>
  184. <code class="language-py">x = 6
  185. y = 7
  186. </code>
  187. </pre>
  188. </div>
  189. `,
  190. { showLineNumbers: true }
  191. ).trim()
  192. assert.ok(result.match(/line="1"/g))
  193. assert.ok(result.match(/line="2"/g))
  194. assert.not(result.match(/line="3"/g))
  195. })
  196. test('showLineNumbers property works in meta field', async () => {
  197. const meta = 'showLineNumbers'
  198. const result = processHtml(
  199. dedent`
  200. <div>
  201. <pre>
  202. <code class="language-py">x = 6
  203. y = 7
  204. </code>
  205. </pre>
  206. </div>
  207. `,
  208. {},
  209. meta
  210. ).trim()
  211. assert.ok(result.match(/line="1"/g))
  212. assert.ok(result.match(/line="2"/g))
  213. assert.not(result.match(/line="3"/g))
  214. })
  215. test('should support both highlighting and add line number', async () => {
  216. const meta = '{1} showLineNumbers'
  217. const result = processHtml(
  218. dedent`
  219. <div>
  220. <pre>
  221. <code class="language-py">x = 6
  222. y = 7
  223. z = 10
  224. </code>
  225. </pre>
  226. </div>
  227. `,
  228. {},
  229. meta
  230. ).trim()
  231. const codeHighlightCount = (result.match(/highlight-line/g) || []).length
  232. assert.is(codeHighlightCount, 1)
  233. assert.ok(result.match(/line="1"/g))
  234. assert.ok(result.match(/line="2"/g))
  235. })
  236. test('throw error with fake language- class', () => {
  237. assert.throws(
  238. () =>
  239. processHtml(dedent`
  240. <pre><code class="language-thisisnotalanguage">x = 6</code></pre>
  241. `),
  242. /Unknown language/
  243. )
  244. })
  245. test('with options.ignoreMissing, does nothing to code block with fake language- class', () => {
  246. const result = processHtml(
  247. dedent`
  248. <pre><code class="language-thisisnotalanguage">x = 6</code></pre>
  249. `,
  250. { ignoreMissing: true }
  251. )
  252. const expected = dedent`<pre class="language-thisisnotalanguage"><code class="language-thisisnotalanguage"><div class="code-line">x = 6</div></code></pre>`
  253. assert.is(result, expected)
  254. })
  255. test('should work with multiline code / comments', () => {
  256. const result = processHtml(
  257. dedent`
  258. <pre><code class="language-js">
  259. /**
  260. * My comment
  261. */
  262. </code></pre>
  263. `,
  264. { ignoreMissing: true }
  265. )
  266. const expected = dedent`<pre class="language-js"><code class="language-js"><div class="code-line">
  267. </div><div class="code-line"><span class="token doc-comment comment">/**
  268. </span></div><div class="code-line"><span class="token doc-comment comment"> * My comment
  269. </span></div><div class="code-line"><span class="token doc-comment comment"> */</span>
  270. </div></code></pre>`
  271. assert.is(result, expected)
  272. })
  273. test.run()