@astrojs/mdx 0.5.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.turbo/turbo-build.log +5 -5
  2. package/CHANGELOG.md +30 -0
  3. package/README.md +57 -76
  4. package/dist/astro-data-utils.d.ts +9 -0
  5. package/dist/astro-data-utils.js +47 -0
  6. package/dist/index.d.ts +0 -7
  7. package/dist/index.js +49 -44
  8. package/dist/utils.d.ts +2 -3
  9. package/dist/utils.js +4 -4
  10. package/package.json +4 -5
  11. package/src/astro-data-utils.ts +59 -0
  12. package/src/index.ts +63 -59
  13. package/src/utils.ts +2 -2
  14. package/test/fixtures/mdx-escape/src/components/Em.astro +7 -0
  15. package/test/fixtures/mdx-escape/src/components/P.astro +1 -0
  16. package/test/fixtures/mdx-escape/src/components/Title.astro +1 -0
  17. package/test/fixtures/mdx-escape/src/pages/html-tag.mdx +5 -0
  18. package/test/fixtures/mdx-escape/src/pages/index.mdx +13 -0
  19. package/test/fixtures/mdx-frontmatter/src/layouts/Base.astro +13 -3
  20. package/test/fixtures/mdx-frontmatter/src/pages/index.mdx +3 -0
  21. package/test/fixtures/mdx-frontmatter/src/pages/with-headings.mdx +7 -0
  22. package/test/fixtures/mdx-frontmatter-injection/astro.config.mjs +12 -0
  23. package/test/fixtures/mdx-frontmatter-injection/node_modules/.bin/astro +17 -0
  24. package/test/fixtures/mdx-frontmatter-injection/package.json +12 -0
  25. package/test/fixtures/mdx-frontmatter-injection/src/markdown-plugins.mjs +20 -0
  26. package/test/fixtures/mdx-frontmatter-injection/src/pages/glob.json.js +6 -0
  27. package/test/fixtures/mdx-frontmatter-injection/src/pages/page-1.mdx +3 -0
  28. package/test/fixtures/mdx-frontmatter-injection/src/pages/page-2.mdx +19 -0
  29. package/test/fixtures/mdx-frontmatter-injection/src/pages/with-overrides.mdx +7 -0
  30. package/test/fixtures/mdx-plus-react/astro.config.mjs +6 -0
  31. package/test/fixtures/mdx-plus-react/node_modules/.bin/astro +17 -0
  32. package/test/fixtures/mdx-plus-react/package.json +8 -0
  33. package/test/fixtures/mdx-plus-react/src/components/Component.jsx +5 -0
  34. package/test/fixtures/mdx-plus-react/src/pages/index.astro +11 -0
  35. package/test/fixtures/mdx-rehype-plugins/src/pages/reading-time.json.js +2 -2
  36. package/test/fixtures/mdx-remark-plugins/src/pages/headings-glob.json.js +6 -0
  37. package/test/mdx-escape.test.js +32 -0
  38. package/test/mdx-frontmatter-injection.test.js +44 -0
  39. package/test/mdx-frontmatter.test.js +20 -37
  40. package/test/mdx-plus-react.test.js +25 -0
  41. package/test/mdx-rehype-plugins.test.js +6 -17
  42. package/test/fixtures/mdx-custom-frontmatter-name/src/pages/glob.json.js +0 -9
  43. package/test/fixtures/mdx-custom-frontmatter-name/src/pages/index.mdx +0 -6
package/src/index.ts CHANGED
@@ -1,35 +1,30 @@
1
1
  import { compile as mdxCompile, nodeTypes } from '@mdx-js/mdx';
2
2
  import mdxPlugin, { Options as MdxRollupPluginOptions } from '@mdx-js/rollup';
3
- import type { AstroIntegration } from 'astro';
3
+ import type { AstroConfig, AstroIntegration } from 'astro';
4
4
  import { parse as parseESM } from 'es-module-lexer';
5
5
  import rehypeRaw from 'rehype-raw';
6
- import remarkFrontmatter from 'remark-frontmatter';
7
6
  import remarkGfm from 'remark-gfm';
8
- import type { RemarkMdxFrontmatterOptions } from 'remark-mdx-frontmatter';
9
- import remarkMdxFrontmatter from 'remark-mdx-frontmatter';
10
7
  import remarkShikiTwoslash from 'remark-shiki-twoslash';
11
8
  import remarkSmartypants from 'remark-smartypants';
12
9
  import { VFile } from 'vfile';
13
10
  import type { Plugin as VitePlugin } from 'vite';
11
+ import { rehypeApplyFrontmatterExport, remarkInitializeAstroData } from './astro-data-utils.js';
14
12
  import rehypeCollectHeadings from './rehype-collect-headings.js';
15
13
  import remarkPrism from './remark-prism.js';
16
- import { getFileInfo, getFrontmatter } from './utils.js';
14
+ import { getFileInfo, parseFrontmatter } from './utils.js';
17
15
 
18
16
  type WithExtends<T> = T | { extends: T };
19
17
 
20
18
  type MdxOptions = {
21
19
  remarkPlugins?: WithExtends<MdxRollupPluginOptions['remarkPlugins']>;
22
20
  rehypePlugins?: WithExtends<MdxRollupPluginOptions['rehypePlugins']>;
23
- /**
24
- * Configure the remark-mdx-frontmatter plugin
25
- * @see https://github.com/remcohaszing/remark-mdx-frontmatter#options for a full list of options
26
- * @default {{ name: 'frontmatter' }}
27
- */
28
- frontmatterOptions?: RemarkMdxFrontmatterOptions;
29
21
  };
30
22
 
31
- const DEFAULT_REMARK_PLUGINS = [remarkGfm, remarkSmartypants];
32
- const DEFAULT_REHYPE_PLUGINS = [rehypeCollectHeadings];
23
+ const DEFAULT_REMARK_PLUGINS: MdxRollupPluginOptions['remarkPlugins'] = [
24
+ remarkGfm,
25
+ remarkSmartypants,
26
+ ];
27
+ const DEFAULT_REHYPE_PLUGINS: MdxRollupPluginOptions['rehypePlugins'] = [];
33
28
 
34
29
  function handleExtends<T>(config: WithExtends<T[] | undefined>, defaults: T[] = []): T[] {
35
30
  if (Array.isArray(config)) return config;
@@ -37,44 +32,54 @@ function handleExtends<T>(config: WithExtends<T[] | undefined>, defaults: T[] =
37
32
  return [...defaults, ...(config?.extends ?? [])];
38
33
  }
39
34
 
35
+ function getRemarkPlugins(
36
+ mdxOptions: MdxOptions,
37
+ config: AstroConfig
38
+ ): MdxRollupPluginOptions['remarkPlugins'] {
39
+ let remarkPlugins = [
40
+ // Initialize vfile.data.astroExports before all plugins are run
41
+ remarkInitializeAstroData,
42
+ ...handleExtends(mdxOptions.remarkPlugins, DEFAULT_REMARK_PLUGINS),
43
+ ];
44
+ if (config.markdown.syntaxHighlight === 'shiki') {
45
+ // Default export still requires ".default" chaining for some reason
46
+ // Workarounds tried:
47
+ // - "import * as remarkShikiTwoslash"
48
+ // - "import { default as remarkShikiTwoslash }"
49
+ const shikiTwoslash = (remarkShikiTwoslash as any).default ?? remarkShikiTwoslash;
50
+ remarkPlugins.push([shikiTwoslash, config.markdown.shikiConfig]);
51
+ }
52
+ if (config.markdown.syntaxHighlight === 'prism') {
53
+ remarkPlugins.push(remarkPrism);
54
+ }
55
+ return remarkPlugins;
56
+ }
57
+
58
+ function getRehypePlugins(
59
+ mdxOptions: MdxOptions,
60
+ config: AstroConfig
61
+ ): MdxRollupPluginOptions['rehypePlugins'] {
62
+ let rehypePlugins = handleExtends(mdxOptions.rehypePlugins, DEFAULT_REHYPE_PLUGINS);
63
+
64
+ if (config.markdown.syntaxHighlight === 'shiki' || config.markdown.syntaxHighlight === 'prism') {
65
+ rehypePlugins.push([rehypeRaw, { passThrough: nodeTypes }]);
66
+ }
67
+ // getHeadings() is guaranteed by TS, so we can't allow user to override
68
+ rehypePlugins.push(rehypeCollectHeadings);
69
+
70
+ return rehypePlugins;
71
+ }
72
+
40
73
  export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration {
41
74
  return {
42
75
  name: '@astrojs/mdx',
43
76
  hooks: {
44
77
  'astro:config:setup': ({ updateConfig, config, addPageExtension, command }: any) => {
45
78
  addPageExtension('.mdx');
46
- let remarkPlugins = handleExtends(mdxOptions.remarkPlugins, DEFAULT_REMARK_PLUGINS);
47
- let rehypePlugins = handleExtends(mdxOptions.rehypePlugins, DEFAULT_REHYPE_PLUGINS);
48
-
49
- if (config.markdown.syntaxHighlight === 'shiki') {
50
- remarkPlugins.push([
51
- // Default export still requires ".default" chaining for some reason
52
- // Workarounds tried:
53
- // - "import * as remarkShikiTwoslash"
54
- // - "import { default as remarkShikiTwoslash }"
55
- (remarkShikiTwoslash as any).default ?? remarkShikiTwoslash,
56
- config.markdown.shikiConfig,
57
- ]);
58
- rehypePlugins.push([rehypeRaw, { passThrough: nodeTypes }]);
59
- }
60
-
61
- if (config.markdown.syntaxHighlight === 'prism') {
62
- remarkPlugins.push(remarkPrism);
63
- rehypePlugins.push([rehypeRaw, { passThrough: nodeTypes }]);
64
- }
65
-
66
- remarkPlugins.push(remarkFrontmatter);
67
- remarkPlugins.push([
68
- remarkMdxFrontmatter,
69
- {
70
- name: 'frontmatter',
71
- ...mdxOptions.frontmatterOptions,
72
- },
73
- ]);
74
79
 
75
80
  const mdxPluginOpts: MdxRollupPluginOptions = {
76
- remarkPlugins,
77
- rehypePlugins,
81
+ remarkPlugins: getRemarkPlugins(mdxOptions, config),
82
+ rehypePlugins: getRehypePlugins(mdxOptions, config),
78
83
  jsx: true,
79
84
  jsxImportSource: 'astro',
80
85
  // Note: disable `.md` support
@@ -93,24 +98,23 @@ export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration {
93
98
  async transform(code, id) {
94
99
  if (!id.endsWith('mdx')) return;
95
100
 
96
- // If user overrides our default YAML parser,
97
- // do not attempt to parse the `layout` via gray-matter
98
- if (!mdxOptions.frontmatterOptions?.parsers) {
99
- const frontmatter = getFrontmatter(code, id);
100
- if (frontmatter.layout) {
101
- const { layout, ...content } = frontmatter;
102
- code += `\nexport default async function({ children }) {\nconst Layout = (await import(${JSON.stringify(
103
- frontmatter.layout
104
- )})).default;\nreturn <Layout content={${JSON.stringify(
105
- content
106
- )}}>{children}</Layout> }`;
107
- }
101
+ let { data: frontmatter, content: pageContent } = parseFrontmatter(code, id);
102
+ if (frontmatter.layout) {
103
+ const { layout, ...contentProp } = frontmatter;
104
+ pageContent += `\n\nexport default async function({ children }) {\nconst Layout = (await import(${JSON.stringify(
105
+ frontmatter.layout
106
+ )})).default;\nconst frontmatter=${JSON.stringify(
107
+ contentProp
108
+ )};\nreturn <Layout frontmatter={frontmatter} content={frontmatter} headings={getHeadings()}>{children}</Layout> }`;
108
109
  }
109
110
 
110
- const compiled = await mdxCompile(
111
- new VFile({ value: code, path: id }),
112
- mdxPluginOpts
113
- );
111
+ const compiled = await mdxCompile(new VFile({ value: pageContent, path: id }), {
112
+ ...mdxPluginOpts,
113
+ rehypePlugins: [
114
+ ...(mdxPluginOpts.rehypePlugins ?? []),
115
+ () => rehypeApplyFrontmatterExport(frontmatter),
116
+ ],
117
+ });
114
118
 
115
119
  return {
116
120
  code: String(compiled.value),
package/src/utils.ts CHANGED
@@ -47,9 +47,9 @@ export function getFileInfo(id: string, config: AstroConfig): FileInfo {
47
47
  * Match YAML exception handling from Astro core errors
48
48
  * @see 'astro/src/core/errors.ts'
49
49
  */
50
- export function getFrontmatter(code: string, id: string) {
50
+ export function parseFrontmatter(code: string, id: string) {
51
51
  try {
52
- return matter(code).data;
52
+ return matter(code);
53
53
  } catch (e: any) {
54
54
  if (e.name === 'YAMLException') {
55
55
  const err: SSRError = e;
@@ -0,0 +1,7 @@
1
+ <em><slot/></em>
2
+
3
+ <style>
4
+ em {
5
+ color: red;
6
+ }
7
+ </style>
@@ -0,0 +1 @@
1
+ <p><slot /></p>
@@ -0,0 +1 @@
1
+ <h1><slot/></h1>
@@ -0,0 +1,5 @@
1
+ import P from '../components/P.astro';
2
+ import Em from '../components/Em.astro';
3
+
4
+ <P>Render <Em>Me</Em></P>
5
+ <P><Em>Me</Em></P>
@@ -0,0 +1,13 @@
1
+ import P from '../components/P.astro';
2
+ import Em from '../components/Em.astro';
3
+ import Title from '../components/Title.astro';
4
+
5
+ export const components = { p: P, em: Em, h1: Title };
6
+
7
+ # Hello _there_
8
+
9
+ # _there_
10
+
11
+ Hello _there_
12
+
13
+ _there_
@@ -1,18 +1,28 @@
1
1
  ---
2
- const { content = { title: "Didn't work" } } = Astro.props;
2
+ const {
3
+ content = { title: "content didn't work" },
4
+ frontmatter = { title: "frontmatter didn't work" },
5
+ headings = [],
6
+ } = Astro.props;
3
7
  ---
4
8
 
5
9
  <!DOCTYPE html>
6
10
  <html lang="en">
11
+
7
12
  <head>
8
13
  <meta charset="UTF-8">
9
14
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
10
15
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
11
- <title>{content.title}</title>
12
16
  </head>
17
+
13
18
  <body>
14
- <h1>{content.title}</h1>
19
+ <p data-content-title>{content.title}</p>
20
+ <p data-frontmatter-title>{frontmatter.title}</p>
15
21
  <p data-layout-rendered>Layout rendered!</p>
22
+ <ul data-headings>
23
+ {headings.map(heading => <li>{heading.slug}</li>)}
24
+ </ul>
16
25
  <slot />
17
26
  </body>
27
+
18
28
  </html>
@@ -5,3 +5,6 @@ illThrowIfIDontExist: "Oh no, that's scary!"
5
5
  ---
6
6
 
7
7
  {frontmatter.illThrowIfIDontExist}
8
+
9
+ > Note: newline intentionally missing from the end of this file.
10
+ > Useful since that can be the source of bugs in our compile step.
@@ -0,0 +1,7 @@
1
+ ---
2
+ layout: '../layouts/Base.astro'
3
+ ---
4
+
5
+ ## Section 1
6
+
7
+ ## Section 2
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'astro/config';
2
+ import mdx from '@astrojs/mdx';
3
+ import { rehypeReadingTime, remarkTitle } from './src/markdown-plugins.mjs';
4
+
5
+ // https://astro.build/config
6
+ export default defineConfig({
7
+ site: 'https://astro.build/',
8
+ integrations: [mdx({
9
+ remarkPlugins: [remarkTitle],
10
+ rehypePlugins: [rehypeReadingTime],
11
+ })],
12
+ });
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6
+ esac
7
+
8
+ if [ -z "$NODE_PATH" ]; then
9
+ export NODE_PATH="/home/runner/work/astro/astro/node_modules/.pnpm/node_modules"
10
+ else
11
+ export NODE_PATH="$NODE_PATH:/home/runner/work/astro/astro/node_modules/.pnpm/node_modules"
12
+ fi
13
+ if [ -x "$basedir/node" ]; then
14
+ exec "$basedir/node" "$basedir/../../../../../../../astro/astro.js" "$@"
15
+ else
16
+ exec node "$basedir/../../../../../../../astro/astro.js" "$@"
17
+ fi
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "@test/mdx-frontmatter-injection",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "dependencies": {
6
+ "astro": "workspace:*",
7
+ "@astrojs/mdx": "workspace:*",
8
+ "mdast-util-to-string": "^3.1.0",
9
+ "reading-time": "^1.5.0",
10
+ "unist-util-visit": "^4.1.0"
11
+ }
12
+ }
@@ -0,0 +1,20 @@
1
+ import getReadingTime from 'reading-time';
2
+ import { toString } from 'mdast-util-to-string';
3
+ import { visit } from 'unist-util-visit';
4
+
5
+ export function rehypeReadingTime() {
6
+ return function (tree, { data }) {
7
+ const readingTime = getReadingTime(toString(tree));
8
+ data.astro.frontmatter.injectedReadingTime = readingTime;
9
+ };
10
+ }
11
+
12
+ export function remarkTitle() {
13
+ return function (tree, { data }) {
14
+ visit(tree, ['heading'], (node) => {
15
+ if (node.depth === 1) {
16
+ data.astro.frontmatter.title = toString(node.children);
17
+ }
18
+ });
19
+ };
20
+ }
@@ -0,0 +1,6 @@
1
+ export async function get() {
2
+ const docs = await import.meta.glob('./*.mdx', { eager: true });
3
+ return {
4
+ body: JSON.stringify(Object.values(docs).map(doc => doc.frontmatter)),
5
+ }
6
+ }
@@ -0,0 +1,3 @@
1
+ # Page 1
2
+
3
+ Look at that!
@@ -0,0 +1,19 @@
1
+ # Page 2
2
+
3
+ ## Table of contents
4
+
5
+ ## Section 1
6
+
7
+ Some text!
8
+
9
+ ### Subsection 1
10
+
11
+ Some subsection test!
12
+
13
+ ### Subsection 2
14
+
15
+ Oh cool, more text!
16
+
17
+ ## Section 2
18
+
19
+ More content
@@ -0,0 +1,7 @@
1
+ ---
2
+ title: 'Overridden title'
3
+ injectedReadingTime:
4
+ text: '1000 min read'
5
+ ---
6
+
7
+ # Working!
@@ -0,0 +1,6 @@
1
+ import mdx from '@astrojs/mdx';
2
+ import react from '@astrojs/react';
3
+
4
+ export default {
5
+ integrations: [react(), mdx()]
6
+ }
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6
+ esac
7
+
8
+ if [ -z "$NODE_PATH" ]; then
9
+ export NODE_PATH="/home/runner/work/astro/astro/node_modules/.pnpm/node_modules"
10
+ else
11
+ export NODE_PATH="$NODE_PATH:/home/runner/work/astro/astro/node_modules/.pnpm/node_modules"
12
+ fi
13
+ if [ -x "$basedir/node" ]; then
14
+ exec "$basedir/node" "$basedir/../../../../../../../astro/astro.js" "$@"
15
+ else
16
+ exec node "$basedir/../../../../../../../astro/astro.js" "$@"
17
+ fi
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "@test/mdx-plus-react",
3
+ "dependencies": {
4
+ "astro": "workspace:*",
5
+ "@astrojs/mdx": "workspace:*",
6
+ "@astrojs/react": "workspace:*"
7
+ }
8
+ }
@@ -0,0 +1,5 @@
1
+ const Component = () => {
2
+ return <p>Hello world</p>;
3
+ };
4
+
5
+ export default Component;
@@ -0,0 +1,11 @@
1
+ ---
2
+ import Component from "../components/Component.jsx";
3
+ ---
4
+ <html>
5
+ <head>
6
+ <title>Testing</title>
7
+ </head>
8
+ <body>
9
+ <Component />
10
+ </body>
11
+ </html>
@@ -1,7 +1,7 @@
1
- import { readingTime } from './space-ipsum.mdx';
1
+ import * as exps from './space-ipsum.mdx';
2
2
 
3
3
  export function get() {
4
4
  return {
5
- body: JSON.stringify(readingTime),
5
+ body: JSON.stringify(exps),
6
6
  }
7
7
  }
@@ -0,0 +1,6 @@
1
+ export async function get() {
2
+ const docs = await import.meta.glob('./*.mdx', { eager: true });
3
+ return {
4
+ body: JSON.stringify(Object.values(docs).map(doc => doc.frontmatter)),
5
+ }
6
+ }
@@ -0,0 +1,32 @@
1
+ import mdx from '@astrojs/mdx';
2
+
3
+ import { expect } from 'chai';
4
+ import { parseHTML } from 'linkedom';
5
+ import { loadFixture } from '../../../astro/test/test-utils.js';
6
+
7
+ const FIXTURE_ROOT = new URL('./fixtures/mdx-escape/', import.meta.url);
8
+
9
+ describe('MDX frontmatter', () => {
10
+ let fixture;
11
+ before(async () => {
12
+ fixture = await loadFixture({
13
+ root: FIXTURE_ROOT,
14
+ integrations: [mdx()],
15
+ });
16
+ await fixture.build();
17
+ });
18
+
19
+ it('does not have unescaped HTML at top-level', async () => {
20
+ const html = await fixture.readFile('/index.html');
21
+ const { document } = parseHTML(html);
22
+
23
+ expect(document.body.textContent).to.not.include('<em');
24
+ });
25
+
26
+ it('does not have unescaped HTML inside html tags', async () => {
27
+ const html = await fixture.readFile('/html-tag/index.html');
28
+ const { document } = parseHTML(html);
29
+
30
+ expect(document.body.textContent).to.not.include('<em');
31
+ });
32
+ });
@@ -0,0 +1,44 @@
1
+ import { expect } from 'chai';
2
+ import { loadFixture } from '../../../astro/test/test-utils.js';
3
+
4
+ const FIXTURE_ROOT = new URL('./fixtures/mdx-frontmatter-injection/', import.meta.url);
5
+
6
+ describe('MDX frontmatter injection', () => {
7
+ let fixture;
8
+
9
+ before(async () => {
10
+ fixture = await loadFixture({
11
+ root: FIXTURE_ROOT,
12
+ });
13
+ await fixture.build();
14
+ });
15
+
16
+ it('remark supports custom vfile data - get title', async () => {
17
+ const frontmatterByPage = JSON.parse(await fixture.readFile('/glob.json'));
18
+ const titles = frontmatterByPage.map((frontmatter = {}) => frontmatter.title);
19
+ expect(titles).to.contain('Page 1');
20
+ expect(titles).to.contain('Page 2');
21
+ });
22
+
23
+ it('rehype supports custom vfile data - reading time', async () => {
24
+ const frontmatterByPage = JSON.parse(await fixture.readFile('/glob.json'));
25
+ const readingTimes = frontmatterByPage.map(
26
+ (frontmatter = {}) => frontmatter.injectedReadingTime
27
+ );
28
+ expect(readingTimes.length).to.be.greaterThan(0);
29
+ for (let readingTime of readingTimes) {
30
+ expect(readingTime).to.not.be.null;
31
+ expect(readingTime.text).match(/^\d+ min read/);
32
+ }
33
+ });
34
+
35
+ it('overrides injected frontmatter with user frontmatter', async () => {
36
+ const frontmatterByPage = JSON.parse(await fixture.readFile('/glob.json'));
37
+ const readingTimes = frontmatterByPage.map(
38
+ (frontmatter = {}) => frontmatter.injectedReadingTime?.text
39
+ );
40
+ const titles = frontmatterByPage.map((frontmatter = {}) => frontmatter.title);
41
+ expect(titles).to.contain('Overridden title');
42
+ expect(readingTimes).to.contain('1000 min read');
43
+ });
44
+ });
@@ -7,33 +7,24 @@ import { loadFixture } from '../../../astro/test/test-utils.js';
7
7
  const FIXTURE_ROOT = new URL('./fixtures/mdx-frontmatter/', import.meta.url);
8
8
 
9
9
  describe('MDX frontmatter', () => {
10
- it('builds when "frontmatter.property" is in JSX expression', async () => {
11
- const fixture = await loadFixture({
10
+ let fixture;
11
+ before(async () => {
12
+ fixture = await loadFixture({
12
13
  root: FIXTURE_ROOT,
13
14
  integrations: [mdx()],
14
15
  });
15
16
  await fixture.build();
17
+ });
18
+ it('builds when "frontmatter.property" is in JSX expression', async () => {
16
19
  expect(true).to.equal(true);
17
20
  });
18
21
 
19
22
  it('extracts frontmatter to "frontmatter" export', async () => {
20
- const fixture = await loadFixture({
21
- root: FIXTURE_ROOT,
22
- integrations: [mdx()],
23
- });
24
- await fixture.build();
25
-
26
23
  const { titles } = JSON.parse(await fixture.readFile('/glob.json'));
27
24
  expect(titles).to.include('Using YAML frontmatter');
28
25
  });
29
26
 
30
27
  it('renders layout from "layout" frontmatter property', async () => {
31
- const fixture = await loadFixture({
32
- root: FIXTURE_ROOT,
33
- integrations: [mdx()],
34
- });
35
- await fixture.build();
36
-
37
28
  const html = await fixture.readFile('/index.html');
38
29
  const { document } = parseHTML(html);
39
30
 
@@ -42,35 +33,27 @@ describe('MDX frontmatter', () => {
42
33
  expect(layoutParagraph).to.not.be.null;
43
34
  });
44
35
 
45
- it('passes frontmatter to layout via "content" prop', async () => {
46
- const fixture = await loadFixture({
47
- root: FIXTURE_ROOT,
48
- integrations: [mdx()],
49
- });
50
- await fixture.build();
51
-
36
+ it('passes frontmatter to layout via "content" and "frontmatter" props', async () => {
52
37
  const html = await fixture.readFile('/index.html');
53
38
  const { document } = parseHTML(html);
54
39
 
55
- const h1 = document.querySelector('h1');
40
+ const contentTitle = document.querySelector('[data-content-title]');
41
+ const frontmatterTitle = document.querySelector('[data-frontmatter-title]');
56
42
 
57
- expect(h1.textContent).to.equal('Using YAML frontmatter');
43
+ expect(contentTitle.textContent).to.equal('Using YAML frontmatter');
44
+ expect(frontmatterTitle.textContent).to.equal('Using YAML frontmatter');
58
45
  });
59
46
 
60
- it('extracts frontmatter to "customFrontmatter" export when configured', async () => {
61
- const fixture = await loadFixture({
62
- root: new URL('./fixtures/mdx-custom-frontmatter-name/', import.meta.url),
63
- integrations: [
64
- mdx({
65
- frontmatterOptions: {
66
- name: 'customFrontmatter',
67
- },
68
- }),
69
- ],
70
- });
71
- await fixture.build();
47
+ it('passes headings to layout via "headings" prop', async () => {
48
+ const html = await fixture.readFile('/with-headings/index.html');
49
+ const { document } = parseHTML(html);
72
50
 
73
- const { titles } = JSON.parse(await fixture.readFile('/glob.json'));
74
- expect(titles).to.include('Using YAML frontmatter');
51
+ const headingSlugs = [...document.querySelectorAll('[data-headings] > li')].map(
52
+ (el) => el.textContent
53
+ );
54
+
55
+ expect(headingSlugs.length).to.be.greaterThan(0);
56
+ expect(headingSlugs).to.contain('section-1');
57
+ expect(headingSlugs).to.contain('section-2');
75
58
  });
76
59
  });
@@ -0,0 +1,25 @@
1
+ import mdx from '@astrojs/mdx';
2
+
3
+ import { expect } from 'chai';
4
+ import { parseHTML } from 'linkedom';
5
+ import { loadFixture } from '../../../astro/test/test-utils.js';
6
+
7
+ describe('MDX and React', () => {
8
+ let fixture;
9
+
10
+ before(async () => {
11
+ fixture = await loadFixture({
12
+ root: new URL('./fixtures/mdx-plus-react/', import.meta.url),
13
+ });
14
+ await fixture.build();
15
+ });
16
+
17
+ it('can be used in the same project', async () => {
18
+ const html = await fixture.readFile('/index.html');
19
+ const { document } = parseHTML(html);
20
+
21
+ const p = document.querySelector('p');
22
+
23
+ expect(p.textContent).to.equal('Hello world');
24
+ });
25
+ });