import { createMarkdownRenderer, disposeMdItInstance, type MarkdownOptions } from 'node/markdown/markdown' async function render(src: string, options: MarkdownOptions = {}) { disposeMdItInstance() const md = await createMarkdownRenderer('.', { highlight: (code) => code, ...options }) return md.renderAsync(src) } describe('node/markdown/plugins/containers', () => { test('renders built-in containers with default titles', async () => { const src = [ 'tip', 'info', 'warning', 'danger', 'note', 'important', 'caution' ] .map((t) => `::: ${t}\ncontent of ${t}\n:::`) .join('\n\n') expect(await render(src)).toMatchInlineSnapshot(` "

TIP

content of tip

INFO

content of info

WARNING

content of warning

DANGER

content of danger

NOTE

content of note

IMPORTANT

content of important

CAUTION

content of caution

" `) }) test('renders details as a disclosure with summary', async () => { expect(await render('::: details\nhidden content\n:::')) .toMatchInlineSnapshot(` "
Details

hidden content

" `) }) test('renders custom titles, including inline markdown', async () => { const src = [ '::: danger STOP', 'Danger zone, do not proceed', ':::', '', '::: tip A **bold** _title_ with `code`', 'content', ':::', '', '::: details Click me to toggle the code', '```js', "console.log('hi')", '```', ':::' ].join('\n') expect(await render(src)).toMatchInlineSnapshot(` "

STOP

Danger zone, do not proceed

A bold title with code

content

Click me to toggle the code
js
console.log('hi')
      
" `) }) test('resolves reference links in titles', async () => { const src = [ '::: tip See [the guide][guide]', 'content', ':::', '', '[guide]: /guide/' ].join('\n') expect(await render(src)).toMatchInlineSnapshot(` "

See the guide

content

" `) }) test('respects custom labels from container options', async () => { const src = '::: tip\n提示内容\n:::\n\n::: details\n详情内容\n:::' expect( await render(src, { container: { tipLabel: '提示', detailsLabel: '详细信息' } }) ).toMatchInlineSnapshot(` "

提示

提示内容

详细信息

详情内容

" `) }) test('supports attrs on the fence line', async () => { const src = [ '::: details Click me {open}', 'content', ':::', '', '::: tip Custom {.extra-class #custom-id}', 'content', ':::' ].join('\n') expect(await render(src)).toMatchInlineSnapshot(` "
Click me

content

Custom

content

" `) }) test('supports quoted and bare attr values on the fence line', async () => { expect(await render('::: tip Custom {data-a="b c" data-d=e}\ncontent\n:::')) .toMatchInlineSnapshot(` "

Custom

content

" `) }) test('skips the title element with a no-title attr', async () => { const src = [ '::: tip {no-title}', 'content', ':::', '', '::: warning Discarded {no-title .extra-class}', 'content', ':::', '', '::: details {no-title}', 'still needs its summary', ':::' ].join('\n') expect(await render(src)).toMatchInlineSnapshot(` "

content

content

Details

still needs its summary

" `) }) test('keeps fence line braces verbatim when attrs are disabled', async () => { expect( await render('::: details Click me {open}\ncontent\n:::', { attrs: false }) ).toMatchInlineSnapshot(` "
Click me {open}

content

" `) }) test('renders v-pre and raw containers as plain wrappers', async () => { const src = [ '::: v-pre', '{{ this will be displayed as-is }}', ':::', '', '::: raw', 'Wraps in a `
`', ':::' ].join('\n') expect(await render(src)).toMatchInlineSnapshot(` "

{{ this will be displayed as-is }}

Wraps in a <div class="vp-raw">

" `) }) test('renders code groups with tabs', async () => { const src = [ '::: code-group', '', '```js [config.js]', 'const a = 1', '```', '', '```ts [config.ts]', 'const a: number = 1', '```', '', ':::' ].join('\n') expect(await render(src)).toMatchInlineSnapshot(` "
js
const a = 1
      
ts
const a: number = 1
      
" `) }) test('supports nesting via longer fences', async () => { const src = [ ':::: info Outer', 'outer content', '', '::: details Inner', 'inner content', ':::', '::::' ].join('\n') expect(await render(src)).toMatchInlineSnapshot(` "

Outer

outer content

Inner

inner content

" `) }) test('auto-closes unclosed containers', async () => { expect(await render('::: warning\nno closing fence')) .toMatchInlineSnapshot(` "

WARNING

no closing fence

" `) }) test('parses fences without a space before the name', async () => { expect(await render(':::tip\ncontent\n:::')).toMatchInlineSnapshot(` "

TIP

content

" `) }) test('leaves non-container fence lines alone', async () => { expect(await render('::: unknown\ncontent\n:::')).toMatchInlineSnapshot(` "

::: unknown content :::

" `) }) }) describe('node/markdown/plugins/containers (github alerts)', () => { test('renders github alerts like containers', async () => { const src = [ '> [!NOTE]', '> note content', '', '> [!TIP]', '> tip content', '', '> [!IMPORTANT]', '> important content', '', '> [!WARNING]', '> warning content', '', '> [!CAUTION]', '> caution content' ].join('\n') expect(await render(src)).toMatchInlineSnapshot(` "

NOTE

note content

TIP

tip content

IMPORTANT

important content

WARNING

warning content

CAUTION

caution content

" `) }) test('matches markers case-insensitively', async () => { expect(await render('> [!tip]\n> content')).toMatchInlineSnapshot(` "

TIP

content

" `) }) test('supports custom titles after the marker', async () => { expect(await render('> [!WARNING] Custom Title\n> content')) .toMatchInlineSnapshot(` "

Custom Title

content

" `) }) test('respects custom labels from container options', async () => { expect( await render('> [!TIP]\n> content', { container: { tipLabel: '提示' } }) ).toMatchInlineSnapshot(` "

提示

content

" `) }) test('supports block content and lazy continuation', async () => { const src = [ '> [!NOTE]', '> first paragraph', 'lazy continuation', '>', '> - list item', '>', '> ```js', '> const a = 1', '> ```' ].join('\n') expect(await render(src)).toMatchInlineSnapshot(` "

NOTE

first paragraph lazy continuation

js
const a = 1
      
" `) }) test('converts markers without content', async () => { expect(await render('> [!NOTE]')).toMatchInlineSnapshot(` "

NOTE

" `) }) test('leaves regular blockquotes and unknown markers alone', async () => { const src = [ '> just a quote', '', '> [!FOO]', '> not an alert', '', 'paragraph [!NOTE] not at blockquote start' ].join('\n') expect(await render(src)).toMatchInlineSnapshot(` "

just a quote

[!FOO] not an alert

paragraph [!NOTE] not at blockquote start

" `) }) test('can be disabled via gfmAlerts: false', async () => { expect(await render('> [!NOTE]\n> content', { gfmAlerts: false })) .toMatchInlineSnapshot(` "

[!NOTE] content

" `) }) })