mdx-plugins.test.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. import mdx from '@astrojs/mdx';
  2. import { describe, it, before } from 'node:test';
  3. import * as assert from 'node:assert/strict';
  4. import { parseHTML } from 'linkedom';
  5. import { loadFixture } from '../../../astro/test/test-utils.js';
  6. import remarkToc from 'remark-toc';
  7. import { visit as estreeVisit } from 'estree-util-visit';
  8. const FIXTURE_ROOT = new URL('./fixtures/mdx-plugins/', import.meta.url);
  9. const FILE = '/with-plugins/index.html';
  10. describe('MDX plugins', () => {
  11. it('supports custom remark plugins - TOC', async () => {
  12. const fixture = await buildFixture({
  13. integrations: [
  14. mdx({
  15. remarkPlugins: [remarkToc],
  16. }),
  17. ],
  18. });
  19. const html = await fixture.readFile(FILE);
  20. const { document } = parseHTML(html);
  21. assert.notEqual(selectTocLink(document), null);
  22. });
  23. it('Applies GFM by default', async () => {
  24. const fixture = await buildFixture({
  25. integrations: [mdx()],
  26. });
  27. const html = await fixture.readFile(FILE);
  28. const { document } = parseHTML(html);
  29. assert.notEqual(selectGfmLink(document), null);
  30. });
  31. it('Applies SmartyPants by default', async () => {
  32. const fixture = await buildFixture({
  33. integrations: [mdx()],
  34. });
  35. const html = await fixture.readFile(FILE);
  36. const { document } = parseHTML(html);
  37. const quote = selectSmartypantsQuote(document);
  38. assert.notEqual(quote, null);
  39. assert.equal(quote.textContent.includes('“Smartypants” is — awesome'), true);
  40. });
  41. it('supports custom rehype plugins', async () => {
  42. const fixture = await buildFixture({
  43. integrations: [
  44. mdx({
  45. rehypePlugins: [rehypeExamplePlugin],
  46. }),
  47. ],
  48. });
  49. const html = await fixture.readFile(FILE);
  50. const { document } = parseHTML(html);
  51. assert.notEqual(selectRehypeExample(document), null);
  52. });
  53. it('supports custom rehype plugins with namespaced attributes', async () => {
  54. const fixture = await buildFixture({
  55. integrations: [
  56. mdx({
  57. rehypePlugins: [rehypeSvgPlugin],
  58. }),
  59. ],
  60. });
  61. const html = await fixture.readFile(FILE);
  62. const { document } = parseHTML(html);
  63. assert.notEqual(selectRehypeSvg(document), null);
  64. });
  65. it('extends markdown config by default', async () => {
  66. const fixture = await buildFixture({
  67. markdown: {
  68. remarkPlugins: [remarkExamplePlugin],
  69. rehypePlugins: [rehypeExamplePlugin],
  70. },
  71. integrations: [mdx()],
  72. });
  73. const html = await fixture.readFile(FILE);
  74. const { document } = parseHTML(html);
  75. assert.notEqual(selectRemarkExample(document), null);
  76. assert.notEqual(selectRehypeExample(document), null);
  77. });
  78. it('ignores string-based plugins in markdown config', async () => {
  79. const fixture = await buildFixture({
  80. markdown: {
  81. remarkPlugins: [['remark-toc', {}]],
  82. },
  83. integrations: [mdx()],
  84. });
  85. const html = await fixture.readFile(FILE);
  86. const { document } = parseHTML(html);
  87. assert.equal(selectTocLink(document), null);
  88. });
  89. for (const extendMarkdownConfig of [true, false]) {
  90. describe(`extendMarkdownConfig = ${extendMarkdownConfig}`, () => {
  91. let fixture;
  92. before(async () => {
  93. fixture = await buildFixture({
  94. markdown: {
  95. remarkPlugins: [remarkToc],
  96. gfm: false,
  97. smartypants: false,
  98. },
  99. integrations: [
  100. mdx({
  101. extendMarkdownConfig,
  102. remarkPlugins: [remarkExamplePlugin],
  103. rehypePlugins: [rehypeExamplePlugin],
  104. }),
  105. ],
  106. });
  107. });
  108. it('Handles MDX plugins', async () => {
  109. const html = await fixture.readFile(FILE);
  110. const { document } = parseHTML(html);
  111. assert.notEqual(selectRemarkExample(document, 'MDX remark plugins not applied.'), null);
  112. assert.notEqual(selectRehypeExample(document, 'MDX rehype plugins not applied.'), null);
  113. });
  114. it('Handles Markdown plugins', async () => {
  115. const html = await fixture.readFile(FILE);
  116. const { document } = parseHTML(html);
  117. assert.equal(
  118. selectTocLink(
  119. document,
  120. '`remarkToc` plugin applied unexpectedly. Should override Markdown config.'
  121. ),
  122. null
  123. );
  124. });
  125. it('Handles gfm', async () => {
  126. const html = await fixture.readFile(FILE);
  127. const { document } = parseHTML(html);
  128. if (extendMarkdownConfig === true) {
  129. assert.equal(selectGfmLink(document), null, 'Does not respect `markdown.gfm` option.');
  130. } else {
  131. assert.notEqual(selectGfmLink(document), null, 'Respects `markdown.gfm` unexpectedly.');
  132. }
  133. });
  134. it('Handles smartypants', async () => {
  135. const html = await fixture.readFile(FILE);
  136. const { document } = parseHTML(html);
  137. const quote = selectSmartypantsQuote(document);
  138. if (extendMarkdownConfig === true) {
  139. assert.equal(
  140. quote.textContent.includes('"Smartypants" is -- awesome'),
  141. true,
  142. 'Does not respect `markdown.smartypants` option.'
  143. );
  144. } else {
  145. assert.equal(
  146. quote.textContent.includes('“Smartypants” is — awesome'),
  147. true,
  148. 'Respects `markdown.smartypants` unexpectedly.'
  149. );
  150. }
  151. });
  152. });
  153. }
  154. it('supports custom recma plugins', async () => {
  155. const fixture = await buildFixture({
  156. integrations: [
  157. mdx({
  158. recmaPlugins: [recmaExamplePlugin],
  159. }),
  160. ],
  161. });
  162. const html = await fixture.readFile(FILE);
  163. const { document } = parseHTML(html);
  164. assert.notEqual(selectRecmaExample(document), null);
  165. });
  166. });
  167. async function buildFixture(config) {
  168. const fixture = await loadFixture({
  169. root: FIXTURE_ROOT,
  170. ...config,
  171. });
  172. await fixture.build();
  173. return fixture;
  174. }
  175. function remarkExamplePlugin() {
  176. return (tree) => {
  177. tree.children.push({
  178. type: 'html',
  179. value: '<div data-remark-plugin-works="true"></div>',
  180. });
  181. };
  182. }
  183. function rehypeExamplePlugin() {
  184. return (tree) => {
  185. tree.children.push({
  186. type: 'element',
  187. tagName: 'div',
  188. properties: { 'data-rehype-plugin-works': 'true' },
  189. });
  190. };
  191. }
  192. function rehypeSvgPlugin() {
  193. return (tree) => {
  194. tree.children.push({
  195. type: 'element',
  196. tagName: 'svg',
  197. properties: { xmlns: 'http://www.w3.org/2000/svg' },
  198. children: [
  199. {
  200. type: 'element',
  201. tagName: 'use',
  202. properties: { xLinkHref: '#icon' },
  203. },
  204. ],
  205. });
  206. };
  207. }
  208. function recmaExamplePlugin() {
  209. return (tree) => {
  210. estreeVisit(tree, (node) => {
  211. if (
  212. node.type === 'VariableDeclarator' &&
  213. node.id.name === 'recmaPluginWorking' &&
  214. node.init?.type === 'Literal'
  215. ) {
  216. node.init = {
  217. ...(node.init ?? {}),
  218. value: true,
  219. raw: 'true',
  220. };
  221. }
  222. });
  223. };
  224. }
  225. function selectTocLink(document) {
  226. return document.querySelector('ul a[href="#section-1"]');
  227. }
  228. function selectGfmLink(document) {
  229. return document.querySelector('a[href="https://handle-me-gfm.com"]');
  230. }
  231. function selectSmartypantsQuote(document) {
  232. return document.querySelector('blockquote');
  233. }
  234. function selectRemarkExample(document) {
  235. return document.querySelector('div[data-remark-plugin-works]');
  236. }
  237. function selectRehypeExample(document) {
  238. return document.querySelector('div[data-rehype-plugin-works]');
  239. }
  240. function selectRehypeSvg(document) {
  241. return document.querySelector('svg > use[xlink\\:href]');
  242. }
  243. function selectRecmaExample(document) {
  244. return document.querySelector('div[data-recma-plugin-works]');
  245. }