Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 42 additions & 97 deletions packages/mdxld/src/render.test.ts
Original file line number Diff line number Diff line change
@@ -1,115 +1,60 @@
import { render } from './render'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import React from 'react'
import * as mdx from '@mdx-js/mdx'
import * as ReactDOMServer from 'react-dom/server'
import TurndownService from 'turndown'

vi.mock('@mdx-js/mdx', () => {
const mockCompile = vi.fn().mockResolvedValue('compiled-mdx-content')
const mockEvaluate = vi.fn().mockImplementation(() => {
return Promise.resolve({
default: () => React.createElement('div', null, 'Mocked MDX Component')
})
})

return {
compile: mockCompile,
evaluate: mockEvaluate
}
})

vi.mock('react-dom/server', () => {
return {
renderToString: vi.fn().mockReturnValue('<div>Mocked MDX Component</div>')
}
})

vi.mock('turndown', () => {
return {
default: vi.fn().mockImplementation(() => ({
turndown: vi.fn().mockReturnValue('Rendered markdown content')
}))
}
})

describe('render', () => {
beforeEach(() => {
vi.clearAllMocks()
})

it('should render MDX content to markdown', async () => {
const mdxContent = `---
title: Test Document
tags: ["mdx", "test"]
---

# Hello World

This is a test document.`
// Real compilation tests using actual @mdx-js/mdx

describe('render - real compilation', () => {
it('renders markdown and parses frontmatter', async () => {
const { render } = await import('./render')
const mdxContent = `---\ntitle: Real Test\n---\n\n# Hello World`
const result = await render(mdxContent)

expect(result.markdown).toBe('Rendered markdown content')
expect(result.frontmatter).toEqual({
title: 'Test Document',
tags: ['mdx', 'test'],
})
expect(mdx.compile).toHaveBeenCalled()
expect(mdx.evaluate).toHaveBeenCalled()
expect(ReactDOMServer.renderToString).toHaveBeenCalled()
expect(result.frontmatter).toEqual({ title: 'Real Test' })
expect(result.markdown).toContain('Hello World')
})

it('should handle MDX content without frontmatter', async () => {
const mdxContent = `# Hello World

This is a test document without frontmatter.`

it('handles content without frontmatter', async () => {
const { render } = await import('./render')
const mdxContent = `# Just Heading`
const result = await render(mdxContent)

expect(result.markdown).toBe('Rendered markdown content')
expect(result.frontmatter).toEqual({})
expect(mdx.compile).toHaveBeenCalled()
expect(result.markdown).toContain('Just Heading')
})
})

it('should pass components and scope to MDX rendering', async () => {
const mdxContent = `# Hello World

<CustomComponent />`

const customComponents = {
CustomComponent: () => React.createElement('div', null, 'Custom content'),
}

const customScope = {
testVar: 'test value',
}

const result = await render(mdxContent, {
components: customComponents,
scope: customScope,
describe('render - mocked mdx', () => {
beforeEach(() => {
vi.resetModules()
vi.doMock('@mdx-js/mdx', () => {
const mockEvaluate = vi.fn().mockResolvedValue({
default: () => React.createElement('div', null, 'Mocked MDX Component'),
})
return { evaluate: mockEvaluate }
})

expect(result.markdown).toBe('Rendered markdown content')
expect(mdx.evaluate).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({
...customScope
vi.doMock('react-dom/server', () => ({
renderToString: vi.fn().mockReturnValue('<div>Mocked MDX Component</div>'),
}))
vi.doMock('turndown', () => ({
default: vi.fn().mockImplementation(() => ({
turndown: vi.fn().mockReturnValue('Rendered markdown content'),
})),
}))
expect(ReactDOMServer.renderToString).toHaveBeenCalledWith(
expect.objectContaining({
props: expect.objectContaining({
components: customComponents
})
})
)
})

it('should throw an error when MDX compilation fails', async () => {
vi.mocked(mdx.compile).mockRejectedValueOnce(new Error('Compilation error'))

const mdxContent = `# Invalid MDX
afterEach(() => {
vi.clearAllMocks()
vi.resetModules()
})

<Component with syntax error />`
it('returns markdown using mocks', async () => {
const { render } = await import('./render')
const mdxContent = `# Test`
const result = await render(mdxContent)
expect(result.markdown).toBe('Rendered markdown content')
})

await expect(render(mdxContent)).rejects.toThrow('Failed to render MDX: Compilation error')
it('throws an error when evaluation fails', async () => {
vi.mocked((await import('@mdx-js/mdx')).evaluate).mockRejectedValueOnce(new Error('Compilation error'))
const { render } = await import('./render')
await expect(render('# Error')).rejects.toThrow('Failed to render MDX: Compilation error')
})
})
18 changes: 8 additions & 10 deletions packages/mdxld/src/render.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from 'react'
import { compile, evaluate } from '@mdx-js/mdx'
import { evaluate } from '@mdx-js/mdx'
import { VFile } from 'vfile'
import * as runtime from 'react/jsx-runtime'
import * as runtimeDev from 'react/jsx-dev-runtime'
import * as ReactDOMServer from 'react-dom/server'
import TurndownService from 'turndown'
import { parseFrontmatter } from './parser.js'
Expand Down Expand Up @@ -44,20 +45,17 @@ export async function render(mdxContent: string, options: RenderOptions = {}): P
const frontmatterRegex = /^\s*---\s*[\r\n]+([\s\S]*?)[\r\n]+---\s*[\r\n]+/
const contentWithoutFrontmatter = mdxContent.replace(frontmatterRegex, '')

const compiled = await compile(new VFile(contentWithoutFrontmatter), {
jsx: true,
jsxImportSource: 'react',
const dev = process.env.NODE_ENV !== 'production'
const runtimeLib = dev ? runtimeDev : runtime
const { default: Component } = await evaluate(new VFile(contentWithoutFrontmatter), {
...runtimeLib,
remarkPlugins: [remarkGfm],
rehypePlugins: [],
development: process.env.NODE_ENV !== 'production',
development: dev,
format: 'mdx',
recmaPlugins: [],
mdExtensions: ['.md', '.mdx'],
elementAttributeNameCase: 'html'
})

const { default: Component } = await evaluate(compiled, {
...runtime,
elementAttributeNameCase: 'html',
...options.scope,
})

Expand Down
Loading