@astrojs/markdoc 0.0.5 → 0.1.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 (51) hide show
  1. package/.turbo/turbo-build.log +5 -5
  2. package/CHANGELOG.md +47 -0
  3. package/README.md +110 -146
  4. package/components/Renderer.astro +8 -11
  5. package/components/TreeNode.ts +6 -23
  6. package/dist/config.d.ts +2 -0
  7. package/dist/config.js +6 -0
  8. package/dist/default-config.d.ts +5 -0
  9. package/dist/default-config.js +13 -0
  10. package/dist/experimental-assets-config.d.ts +2 -0
  11. package/dist/experimental-assets-config.js +25 -0
  12. package/dist/index.d.ts +1 -2
  13. package/dist/index.js +43 -112
  14. package/dist/load-config.d.ts +14 -0
  15. package/dist/load-config.js +82 -0
  16. package/dist/utils.d.ts +0 -11
  17. package/dist/utils.js +1 -44
  18. package/package.json +8 -3
  19. package/src/config.ts +5 -0
  20. package/src/default-config.ts +18 -0
  21. package/src/experimental-assets-config.ts +29 -0
  22. package/src/index.ts +65 -143
  23. package/src/load-config.ts +102 -0
  24. package/src/utils.ts +0 -58
  25. package/template/content-module-types.d.ts +1 -3
  26. package/test/content-collections.test.js +24 -172
  27. package/test/fixtures/content-collections/package.json +0 -4
  28. package/test/fixtures/content-collections/src/content/blog/post-1.mdoc +7 -0
  29. package/test/fixtures/content-collections/src/content/blog/post-2.mdoc +7 -0
  30. package/test/fixtures/content-collections/src/content/blog/post-3.mdoc +7 -0
  31. package/test/fixtures/content-collections/src/pages/entry.json.js +1 -1
  32. package/test/fixtures/render-simple/astro.config.mjs +7 -0
  33. package/test/fixtures/render-simple/node_modules/.bin/astro +17 -0
  34. package/test/fixtures/render-simple/package.json +9 -0
  35. package/test/fixtures/{content-collections/src/pages/content-simple.astro → render-simple/src/pages/index.astro} +2 -1
  36. package/test/fixtures/render-with-components/astro.config.mjs +7 -0
  37. package/test/fixtures/render-with-components/markdoc.config.mjs +28 -0
  38. package/test/fixtures/render-with-components/node_modules/.bin/astro +17 -0
  39. package/test/fixtures/render-with-components/package.json +12 -0
  40. package/test/fixtures/{content-collections/src/pages/content-with-components.astro → render-with-components/src/pages/index.astro} +2 -6
  41. package/test/fixtures/render-with-config/astro.config.mjs +7 -0
  42. package/test/fixtures/render-with-config/markdoc.config.mjs +15 -0
  43. package/test/fixtures/render-with-config/node_modules/.bin/astro +17 -0
  44. package/test/fixtures/render-with-config/package.json +9 -0
  45. package/test/fixtures/{content-collections → render-with-config}/src/content/blog/with-config.mdoc +4 -0
  46. package/test/fixtures/{content-collections/src/pages/content-with-config.astro → render-with-config/src/pages/index.astro} +2 -2
  47. package/test/render.test.js +124 -0
  48. /package/test/fixtures/{content-collections → render-simple}/src/content/blog/simple.mdoc +0 -0
  49. /package/test/fixtures/{content-collections → render-with-components}/src/components/Code.astro +0 -0
  50. /package/test/fixtures/{content-collections → render-with-components}/src/components/CustomMarquee.astro +0 -0
  51. /package/test/fixtures/{content-collections → render-with-components}/src/content/blog/with-components.mdoc +0 -0
package/src/index.ts CHANGED
@@ -1,23 +1,15 @@
1
- import type {
2
- Config as ReadonlyMarkdocConfig,
3
- ConfigType as MarkdocConfig,
4
- Node,
5
- } from '@markdoc/markdoc';
1
+ import type { Node } from '@markdoc/markdoc';
6
2
  import Markdoc from '@markdoc/markdoc';
7
3
  import type { AstroConfig, AstroIntegration, ContentEntryType, HookParameters } from 'astro';
8
4
  import fs from 'node:fs';
9
5
  import { fileURLToPath } from 'node:url';
10
- import type * as rollup from 'rollup';
11
- import {
12
- getAstroConfigPath,
13
- isValidUrl,
14
- MarkdocError,
15
- parseFrontmatter,
16
- prependForwardSlash,
17
- } from './utils.js';
6
+ import { isValidUrl, MarkdocError, parseFrontmatter, prependForwardSlash } from './utils.js';
18
7
  // @ts-expect-error Cannot find module 'astro/assets' or its corresponding type declarations.
19
8
  import { emitESMImage } from 'astro/assets';
20
- import type { Plugin as VitePlugin } from 'vite';
9
+ import { bold, red } from 'kleur/colors';
10
+ import type * as rollup from 'rollup';
11
+ import { applyDefaultConfig } from './default-config.js';
12
+ import { loadMarkdocConfig } from './load-config.js';
21
13
 
22
14
  type SetupHookParams = HookParameters<'astro:config:setup'> & {
23
15
  // `contentEntryType` is not a public API
@@ -25,24 +17,24 @@ type SetupHookParams = HookParameters<'astro:config:setup'> & {
25
17
  addContentEntryType: (contentEntryType: ContentEntryType) => void;
26
18
  };
27
19
 
28
- export default function markdocIntegration(
29
- userMarkdocConfig: ReadonlyMarkdocConfig = {}
30
- ): AstroIntegration {
20
+ export default function markdocIntegration(legacyConfig: any): AstroIntegration {
21
+ if (legacyConfig) {
22
+ // eslint-disable-next-line no-console
23
+ console.log(
24
+ `${red(
25
+ bold('[Markdoc]')
26
+ )} Passing Markdoc config from your \`astro.config\` is no longer supported. Configuration should be exported from a \`markdoc.config.mjs\` file. See the configuration docs for more: https://docs.astro.build/en/guides/integrations-guide/markdoc/#configuration`
27
+ );
28
+ process.exit(0);
29
+ }
31
30
  return {
32
31
  name: '@astrojs/markdoc',
33
32
  hooks: {
34
33
  'astro:config:setup': async (params) => {
35
- const {
36
- updateConfig,
37
- config: astroConfig,
38
- addContentEntryType,
39
- } = params as SetupHookParams;
34
+ const { config: astroConfig, addContentEntryType } = params as SetupHookParams;
40
35
 
41
- updateConfig({
42
- vite: {
43
- plugins: [safeAssetsVirtualModulePlugin({ astroConfig })],
44
- },
45
- });
36
+ const configLoadResult = await loadMarkdocConfig(astroConfig);
37
+ const userMarkdocConfig = configLoadResult?.config ?? {};
46
38
 
47
39
  function getEntryInfo({ fileUrl, contents }: { fileUrl: URL; contents: string }) {
48
40
  const parsed = parseFrontmatter(contents, fileURLToPath(fileUrl));
@@ -56,49 +48,63 @@ export default function markdocIntegration(
56
48
  addContentEntryType({
57
49
  extensions: ['.mdoc'],
58
50
  getEntryInfo,
59
- async getRenderModule({ entry }) {
60
- validateRenderProperties(userMarkdocConfig, astroConfig);
51
+ async getRenderModule({ entry, viteId }) {
61
52
  const ast = Markdoc.parse(entry.body);
62
53
  const pluginContext = this;
63
- const markdocConfig: MarkdocConfig = {
64
- ...userMarkdocConfig,
65
- variables: {
66
- ...userMarkdocConfig.variables,
67
- entry,
68
- },
69
- };
54
+ const markdocConfig = applyDefaultConfig(userMarkdocConfig, { entry });
55
+
56
+ const validationErrors = Markdoc.validate(ast, markdocConfig).filter((e) => {
57
+ // Ignore `variable-undefined` errors.
58
+ // Variables can be configured at runtime,
59
+ // so we cannot validate them at build time.
60
+ return e.error.id !== 'variable-undefined';
61
+ });
62
+ if (validationErrors.length) {
63
+ throw new MarkdocError({
64
+ message: [
65
+ `**${String(entry.collection)} → ${String(entry.id)}** failed to validate:`,
66
+ ...validationErrors.map((e) => e.error.id),
67
+ ].join('\n'),
68
+ });
69
+ }
70
70
 
71
- if (astroConfig.experimental?.assets) {
71
+ if (astroConfig.experimental.assets) {
72
72
  await emitOptimizedImages(ast.children, {
73
73
  astroConfig,
74
74
  pluginContext,
75
75
  filePath: entry._internal.filePath,
76
76
  });
77
-
78
- markdocConfig.nodes ??= {};
79
- markdocConfig.nodes.image = {
80
- ...Markdoc.nodes.image,
81
- transform(node, config) {
82
- const attributes = node.transformAttributes(config);
83
- const children = node.transformChildren(config);
84
-
85
- if (node.type === 'image' && '__optimizedSrc' in node.attributes) {
86
- const { __optimizedSrc, ...rest } = node.attributes;
87
- return new Markdoc.Tag('Image', { ...rest, src: __optimizedSrc }, children);
88
- } else {
89
- return new Markdoc.Tag('img', attributes, children);
90
- }
91
- },
92
- };
93
77
  }
94
78
 
95
- const content = Markdoc.transform(ast, markdocConfig);
96
-
97
- return {
98
- code: `import { jsx as h } from 'astro/jsx-runtime';\nimport { Renderer } from '@astrojs/markdoc/components';\nconst transformedContent = ${JSON.stringify(
99
- content
100
- )};\nexport async function Content ({ components }) { return h(Renderer, { content: transformedContent, components }); }\nContent[Symbol.for('astro.needsHeadRendering')] = true;`,
79
+ const code = {
80
+ code: `import { jsx as h } from 'astro/jsx-runtime';
81
+ import { applyDefaultConfig } from '@astrojs/markdoc/default-config';
82
+ import { Renderer } from '@astrojs/markdoc/components';
83
+ import * as entry from ${JSON.stringify(viteId + '?astroContent')};${
84
+ configLoadResult
85
+ ? `\nimport userConfig from ${JSON.stringify(configLoadResult.fileUrl.pathname)};`
86
+ : ''
87
+ }${
88
+ astroConfig.experimental.assets
89
+ ? `\nimport { experimentalAssetsConfig } from '@astrojs/markdoc/experimental-assets-config';`
90
+ : ''
91
+ }
92
+ const stringifiedAst = ${JSON.stringify(
93
+ /* Double stringify to encode *as* stringified JSON */ JSON.stringify(ast)
94
+ )};
95
+ export async function Content (props) {
96
+ const config = applyDefaultConfig(${
97
+ configLoadResult
98
+ ? '{ ...userConfig, variables: { ...userConfig.variables, ...props } }'
99
+ : '{ variables: props }'
100
+ }, { entry });${
101
+ astroConfig.experimental.assets
102
+ ? `\nconfig.nodes = { ...experimentalAssetsConfig.nodes, ...config.nodes };`
103
+ : ''
104
+ }
105
+ return h(Renderer, { stringifiedAst, config }); };`,
101
106
  };
107
+ return code;
102
108
  },
103
109
  contentModuleTypes: await fs.promises.readFile(
104
110
  new URL('../template/content-module-types.d.ts', import.meta.url),
@@ -156,87 +162,3 @@ function shouldOptimizeImage(src: string) {
156
162
  // Optimize anything that is NOT external or an absolute path to `public/`
157
163
  return !isValidUrl(src) && !src.startsWith('/');
158
164
  }
159
-
160
- function validateRenderProperties(markdocConfig: ReadonlyMarkdocConfig, astroConfig: AstroConfig) {
161
- const tags = markdocConfig.tags ?? {};
162
- const nodes = markdocConfig.nodes ?? {};
163
-
164
- for (const [name, config] of Object.entries(tags)) {
165
- validateRenderProperty({ type: 'tag', name, config, astroConfig });
166
- }
167
- for (const [name, config] of Object.entries(nodes)) {
168
- validateRenderProperty({ type: 'node', name, config, astroConfig });
169
- }
170
- }
171
-
172
- function validateRenderProperty({
173
- name,
174
- config,
175
- type,
176
- astroConfig,
177
- }: {
178
- name: string;
179
- config: { render?: string };
180
- type: 'node' | 'tag';
181
- astroConfig: Pick<AstroConfig, 'root'>;
182
- }) {
183
- if (typeof config.render === 'string' && config.render.length === 0) {
184
- throw new Error(
185
- `Invalid ${type} configuration: ${JSON.stringify(
186
- name
187
- )}. The "render" property cannot be an empty string.`
188
- );
189
- }
190
- if (typeof config.render === 'string' && !isCapitalized(config.render)) {
191
- const astroConfigPath = getAstroConfigPath(fs, fileURLToPath(astroConfig.root));
192
- throw new MarkdocError({
193
- message: `Invalid ${type} configuration: ${JSON.stringify(
194
- name
195
- )}. The "render" property must reference a capitalized component name.`,
196
- hint: 'If you want to render to an HTML element, see our docs on rendering Markdoc manually: https://docs.astro.build/en/guides/integrations-guide/markdoc/#render-markdoc-nodes--html-elements-as-astro-components',
197
- location: astroConfigPath
198
- ? {
199
- file: astroConfigPath,
200
- }
201
- : undefined,
202
- });
203
- }
204
- }
205
-
206
- function isCapitalized(str: string) {
207
- return str.length > 0 && str[0] === str[0].toUpperCase();
208
- }
209
-
210
- /**
211
- * TODO: remove when `experimental.assets` is baselined.
212
- *
213
- * `astro:assets` will fail to resolve if the `experimental.assets` flag is not enabled.
214
- * This ensures a fallback for the Markdoc renderer to safely import at the top level.
215
- * @see ../components/TreeNode.ts
216
- */
217
- function safeAssetsVirtualModulePlugin({
218
- astroConfig,
219
- }: {
220
- astroConfig: Pick<AstroConfig, 'experimental'>;
221
- }): VitePlugin {
222
- const virtualModuleId = 'astro:markdoc-assets';
223
- const resolvedVirtualModuleId = '\0' + virtualModuleId;
224
-
225
- return {
226
- name: 'astro:markdoc-safe-assets-virtual-module',
227
- resolveId(id) {
228
- if (id === virtualModuleId) {
229
- return resolvedVirtualModuleId;
230
- }
231
- },
232
- load(id) {
233
- if (id !== resolvedVirtualModuleId) return;
234
-
235
- if (astroConfig.experimental?.assets) {
236
- return `export { Image } from 'astro:assets';`;
237
- } else {
238
- return `export const Image = () => { throw new Error('Cannot use the Image component without the \`experimental.assets\` flag.'); }`;
239
- }
240
- },
241
- };
242
- }
@@ -0,0 +1,102 @@
1
+ import type { Config as MarkdocConfig } from '@markdoc/markdoc';
2
+ import type { AstroConfig } from 'astro';
3
+ import { build as esbuild } from 'esbuild';
4
+ import * as fs from 'node:fs';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const SUPPORTED_MARKDOC_CONFIG_FILES = [
8
+ 'markdoc.config.js',
9
+ 'markdoc.config.mjs',
10
+ 'markdoc.config.mts',
11
+ 'markdoc.config.ts',
12
+ ];
13
+
14
+ export async function loadMarkdocConfig(astroConfig: Pick<AstroConfig, 'root'>) {
15
+ let markdocConfigUrl: URL | undefined;
16
+ for (const filename of SUPPORTED_MARKDOC_CONFIG_FILES) {
17
+ const filePath = new URL(filename, astroConfig.root);
18
+ if (!fs.existsSync(filePath)) continue;
19
+
20
+ markdocConfigUrl = filePath;
21
+ break;
22
+ }
23
+ if (!markdocConfigUrl) return;
24
+
25
+ const { code, dependencies } = await bundleConfigFile({
26
+ markdocConfigUrl,
27
+ astroConfig,
28
+ });
29
+ const config: MarkdocConfig = await loadConfigFromBundledFile(astroConfig.root, code);
30
+
31
+ return {
32
+ config,
33
+ fileUrl: markdocConfigUrl,
34
+ };
35
+ }
36
+
37
+ /**
38
+ * Forked from Vite's `bundleConfigFile` function
39
+ * with added handling for `.astro` imports,
40
+ * and removed unused Deno patches.
41
+ * @see https://github.com/vitejs/vite/blob/main/packages/vite/src/node/config.ts#L961
42
+ */
43
+ async function bundleConfigFile({
44
+ markdocConfigUrl,
45
+ astroConfig,
46
+ }: {
47
+ markdocConfigUrl: URL;
48
+ astroConfig: Pick<AstroConfig, 'root'>;
49
+ }): Promise<{ code: string; dependencies: string[] }> {
50
+ const result = await esbuild({
51
+ absWorkingDir: fileURLToPath(astroConfig.root),
52
+ entryPoints: [fileURLToPath(markdocConfigUrl)],
53
+ outfile: 'out.js',
54
+ write: false,
55
+ target: ['node16'],
56
+ platform: 'node',
57
+ packages: 'external',
58
+ bundle: true,
59
+ format: 'esm',
60
+ sourcemap: 'inline',
61
+ metafile: true,
62
+ plugins: [
63
+ {
64
+ name: 'stub-astro-imports',
65
+ setup(build) {
66
+ build.onResolve({ filter: /.*\.astro$/ }, () => {
67
+ return {
68
+ // Stub with an unused default export
69
+ path: 'data:text/javascript,export default true',
70
+ external: true,
71
+ };
72
+ });
73
+ },
74
+ },
75
+ ],
76
+ });
77
+ const { text } = result.outputFiles[0];
78
+ return {
79
+ code: text,
80
+ dependencies: result.metafile ? Object.keys(result.metafile.inputs) : [],
81
+ };
82
+ }
83
+
84
+ /**
85
+ * Forked from Vite config loader, replacing CJS-based path concat
86
+ * with ESM only
87
+ * @see https://github.com/vitejs/vite/blob/main/packages/vite/src/node/config.ts#L1074
88
+ */
89
+ async function loadConfigFromBundledFile(root: URL, code: string): Promise<MarkdocConfig> {
90
+ // Write it to disk, load it with native Node ESM, then delete the file.
91
+ const tmpFileUrl = new URL(`markdoc.config.timestamp-${Date.now()}.mjs`, root);
92
+ fs.writeFileSync(tmpFileUrl, code);
93
+ try {
94
+ return (await import(tmpFileUrl.pathname)).default;
95
+ } finally {
96
+ try {
97
+ fs.unlinkSync(tmpFileUrl);
98
+ } catch {
99
+ // already removed if this function is called twice simultaneously
100
+ }
101
+ }
102
+ }
package/src/utils.ts CHANGED
@@ -1,8 +1,4 @@
1
- import type { AstroInstance } from 'astro';
2
- import z from 'astro/zod';
3
1
  import matter from 'gray-matter';
4
- import type fsMod from 'node:fs';
5
- import path from 'node:path';
6
2
  import type { ErrorPayload as ViteErrorPayload } from 'vite';
7
3
 
8
4
  /**
@@ -85,28 +81,6 @@ interface ErrorProperties {
85
81
  frame?: string;
86
82
  }
87
83
 
88
- /**
89
- * Matches `search` function used for resolving `astro.config` files.
90
- * Used by Markdoc for error handling.
91
- * @see 'astro/src/core/config/config.ts'
92
- */
93
- export function getAstroConfigPath(fs: typeof fsMod, root: string): string | undefined {
94
- const paths = [
95
- 'astro.config.mjs',
96
- 'astro.config.js',
97
- 'astro.config.ts',
98
- 'astro.config.mts',
99
- 'astro.config.cjs',
100
- 'astro.config.cts',
101
- ].map((p) => path.join(root, p));
102
-
103
- for (const file of paths) {
104
- if (fs.existsSync(file)) {
105
- return file;
106
- }
107
- }
108
- }
109
-
110
84
  /**
111
85
  * @see 'astro/src/core/path.ts'
112
86
  */
@@ -114,38 +88,6 @@ export function prependForwardSlash(str: string) {
114
88
  return str[0] === '/' ? str : '/' + str;
115
89
  }
116
90
 
117
- export function validateComponentsProp(components: Record<string, AstroInstance['default']>) {
118
- try {
119
- componentsPropValidator.parse(components);
120
- } catch (e) {
121
- throw new MarkdocError({
122
- message:
123
- e instanceof z.ZodError
124
- ? e.issues[0].message
125
- : 'Invalid `components` prop. Ensure you are passing an object of components to <Content />',
126
- });
127
- }
128
- }
129
-
130
- const componentsPropValidator = z.record(
131
- z
132
- .string()
133
- .min(1, 'Invalid `components` prop. Component names cannot be empty!')
134
- .refine(
135
- (value) => isCapitalized(value),
136
- (value) => ({
137
- message: `Invalid \`components\` prop: ${JSON.stringify(
138
- value
139
- )}. Component name must be capitalized. If you want to render HTML elements as components, try using a Markdoc node (https://docs.astro.build/en/guides/integrations-guide/markdoc/#render-markdoc-nodes--html-elements-as-astro-components)`,
140
- })
141
- ),
142
- z.any()
143
- );
144
-
145
- export function isCapitalized(str: string) {
146
- return str.length > 0 && str[0] === str[0].toUpperCase();
147
- }
148
-
149
91
  export function isValidUrl(str: string): boolean {
150
92
  try {
151
93
  new URL(str);
@@ -1,9 +1,7 @@
1
1
  declare module 'astro:content' {
2
2
  interface Render {
3
3
  '.mdoc': Promise<{
4
- Content(props: {
5
- components?: Record<string, import('astro').AstroInstance['default']>;
6
- }): import('astro').MarkdownInstance<{}>['Content'];
4
+ Content(props: Record<string, any>): import('astro').MarkdownInstance<{}>['Content'];
7
5
  }>;
8
6
  }
9
7
  }
@@ -1,4 +1,3 @@
1
- import { parseHTML } from 'linkedom';
2
1
  import { parse as parseDevalue } from 'devalue';
3
2
  import { expect } from 'chai';
4
3
  import { loadFixture, fixLineEndings } from '../../../astro/test/test-utils.js';
@@ -37,70 +36,20 @@ describe('Markdoc - Content Collections', () => {
37
36
  it('loads entry', async () => {
38
37
  const res = await baseFixture.fetch('/entry.json');
39
38
  const post = parseDevalue(await res.text());
40
- expect(formatPost(post)).to.deep.equal(simplePostEntry);
39
+ expect(formatPost(post)).to.deep.equal(post1Entry);
41
40
  });
42
41
 
43
42
  it('loads collection', async () => {
44
43
  const res = await baseFixture.fetch('/collection.json');
45
44
  const posts = parseDevalue(await res.text());
46
45
  expect(posts).to.not.be.null;
46
+
47
47
  expect(posts.sort().map((post) => formatPost(post))).to.deep.equal([
48
- simplePostEntry,
49
- withComponentsEntry,
50
- withConfigEntry,
48
+ post1Entry,
49
+ post2Entry,
50
+ post3Entry,
51
51
  ]);
52
52
  });
53
-
54
- it('renders content - simple', async () => {
55
- const res = await baseFixture.fetch('/content-simple');
56
- const html = await res.text();
57
- const { document } = parseHTML(html);
58
- const h2 = document.querySelector('h2');
59
- expect(h2.textContent).to.equal('Simple post');
60
- const p = document.querySelector('p');
61
- expect(p.textContent).to.equal('This is a simple Markdoc post.');
62
- });
63
-
64
- it('renders content - with config', async () => {
65
- const fixture = await getFixtureWithConfig();
66
- const server = await fixture.startDevServer();
67
-
68
- const res = await fixture.fetch('/content-with-config');
69
- const html = await res.text();
70
- const { document } = parseHTML(html);
71
- const h2 = document.querySelector('h2');
72
- expect(h2.textContent).to.equal('Post with config');
73
- const textContent = html;
74
-
75
- expect(textContent).to.not.include('Hello');
76
- expect(textContent).to.include('Hola');
77
- expect(textContent).to.include(`Konnichiwa`);
78
-
79
- await server.stop();
80
- });
81
-
82
- it('renders content - with components', async () => {
83
- const fixture = await getFixtureWithComponents();
84
- const server = await fixture.startDevServer();
85
-
86
- const res = await fixture.fetch('/content-with-components');
87
- const html = await res.text();
88
- const { document } = parseHTML(html);
89
- const h2 = document.querySelector('h2');
90
- expect(h2.textContent).to.equal('Post with components');
91
-
92
- // Renders custom shortcode component
93
- const marquee = document.querySelector('marquee');
94
- expect(marquee).to.not.be.null;
95
- expect(marquee.hasAttribute('data-custom-marquee')).to.equal(true);
96
-
97
- // Renders Astro Code component
98
- const pre = document.querySelector('pre');
99
- expect(pre).to.not.be.null;
100
- expect(pre.className).to.equal('astro-code');
101
-
102
- await server.stop();
103
- });
104
53
  });
105
54
 
106
55
  describe('build', () => {
@@ -111,7 +60,7 @@ describe('Markdoc - Content Collections', () => {
111
60
  it('loads entry', async () => {
112
61
  const res = await baseFixture.readFile('/entry.json');
113
62
  const post = parseDevalue(res);
114
- expect(formatPost(post)).to.deep.equal(simplePostEntry);
63
+ expect(formatPost(post)).to.deep.equal(post1Entry);
115
64
  });
116
65
 
117
66
  it('loads collection', async () => {
@@ -119,140 +68,43 @@ describe('Markdoc - Content Collections', () => {
119
68
  const posts = parseDevalue(res);
120
69
  expect(posts).to.not.be.null;
121
70
  expect(posts.sort().map((post) => formatPost(post))).to.deep.equal([
122
- simplePostEntry,
123
- withComponentsEntry,
124
- withConfigEntry,
71
+ post1Entry,
72
+ post2Entry,
73
+ post3Entry,
125
74
  ]);
126
75
  });
127
-
128
- it('renders content - simple', async () => {
129
- const html = await baseFixture.readFile('/content-simple/index.html');
130
- const { document } = parseHTML(html);
131
- const h2 = document.querySelector('h2');
132
- expect(h2.textContent).to.equal('Simple post');
133
- const p = document.querySelector('p');
134
- expect(p.textContent).to.equal('This is a simple Markdoc post.');
135
- });
136
-
137
- it('renders content - with config', async () => {
138
- const fixture = await getFixtureWithConfig();
139
- await fixture.build();
140
-
141
- const html = await fixture.readFile('/content-with-config/index.html');
142
- const { document } = parseHTML(html);
143
- const h2 = document.querySelector('h2');
144
- expect(h2.textContent).to.equal('Post with config');
145
- const textContent = html;
146
-
147
- expect(textContent).to.not.include('Hello');
148
- expect(textContent).to.include('Hola');
149
- expect(textContent).to.include(`Konnichiwa`);
150
- });
151
-
152
- it('renders content - with components', async () => {
153
- const fixture = await getFixtureWithComponents();
154
- await fixture.build();
155
-
156
- const html = await fixture.readFile('/content-with-components/index.html');
157
- const { document } = parseHTML(html);
158
- const h2 = document.querySelector('h2');
159
- expect(h2.textContent).to.equal('Post with components');
160
-
161
- // Renders custom shortcode component
162
- const marquee = document.querySelector('marquee');
163
- expect(marquee).to.not.be.null;
164
- expect(marquee.hasAttribute('data-custom-marquee')).to.equal(true);
165
-
166
- // Renders Astro Code component
167
- const pre = document.querySelector('pre');
168
- expect(pre).to.not.be.null;
169
- expect(pre.className).to.equal('astro-code');
170
- });
171
76
  });
172
77
  });
173
78
 
174
- function getFixtureWithConfig() {
175
- return loadFixture({
176
- root,
177
- integrations: [
178
- markdoc({
179
- variables: {
180
- countries: ['ES', 'JP'],
181
- },
182
- functions: {
183
- includes: {
184
- transform(parameters) {
185
- const [array, value] = Object.values(parameters);
186
- return Array.isArray(array) ? array.includes(value) : false;
187
- },
188
- },
189
- },
190
- }),
191
- ],
192
- });
193
- }
194
-
195
- function getFixtureWithComponents() {
196
- return loadFixture({
197
- root,
198
- integrations: [
199
- markdoc({
200
- nodes: {
201
- fence: {
202
- render: 'Code',
203
- attributes: {
204
- language: { type: String },
205
- content: { type: String },
206
- },
207
- },
208
- },
209
- tags: {
210
- mq: {
211
- render: 'CustomMarquee',
212
- attributes: {
213
- direction: {
214
- type: String,
215
- default: 'left',
216
- matches: ['left', 'right', 'up', 'down'],
217
- errorLevel: 'critical',
218
- },
219
- },
220
- },
221
- },
222
- }),
223
- ],
224
- });
225
- }
226
-
227
- const simplePostEntry = {
228
- id: 'simple.mdoc',
229
- slug: 'simple',
79
+ const post1Entry = {
80
+ id: 'post-1.mdoc',
81
+ slug: 'post-1',
230
82
  collection: 'blog',
231
83
  data: {
232
84
  schemaWorks: true,
233
- title: 'Simple post',
85
+ title: 'Post 1',
234
86
  },
235
- body: '\n## Simple post\n\nThis is a simple Markdoc post.\n',
87
+ body: '\n## Post 1\n\nThis is the contents of post 1.\n',
236
88
  };
237
89
 
238
- const withComponentsEntry = {
239
- id: 'with-components.mdoc',
240
- slug: 'with-components',
90
+ const post2Entry = {
91
+ id: 'post-2.mdoc',
92
+ slug: 'post-2',
241
93
  collection: 'blog',
242
94
  data: {
243
95
  schemaWorks: true,
244
- title: 'Post with components',
96
+ title: 'Post 2',
245
97
  },
246
- body: '\n## Post with components\n\nThis uses a custom marquee component with a shortcode:\n\n{% mq direction="right" %}\nI\'m a marquee too!\n{% /mq %}\n\nAnd a code component for code blocks:\n\n```js\nconst isRenderedWithShiki = true;\n```\n',
98
+ body: '\n## Post 2\n\nThis is the contents of post 2.\n',
247
99
  };
248
100
 
249
- const withConfigEntry = {
250
- id: 'with-config.mdoc',
251
- slug: 'with-config',
101
+ const post3Entry = {
102
+ id: 'post-3.mdoc',
103
+ slug: 'post-3',
252
104
  collection: 'blog',
253
105
  data: {
254
106
  schemaWorks: true,
255
- title: 'Post with config',
107
+ title: 'Post 3',
256
108
  },
257
- body: '\n## Post with config\n\n{% if includes($countries, "EN") %} Hello {% /if %}\n{% if includes($countries, "ES") %} Hola {% /if %}\n{% if includes($countries, "JP") %} Konnichiwa {% /if %}\n',
109
+ body: '\n## Post 3\n\nThis is the contents of post 3.\n',
258
110
  };