You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
vitepress/__tests__/unit/node/markdown/plugins/link.test.ts

139 lines
4.6 KiB

import { slugify } from '@mdit-vue/shared'
import { MarkdownItAsync } from 'markdown-it-async'
import { linkPlugin } from 'node/markdown/plugins/link'
import type { MarkdownLink } from 'node/shared'
describe('node/markdown/plugins/link', () => {
const md = new MarkdownItAsync()
linkPlugin(md, {}, '/', slugify)
test('preserves text-fragment hashes on markdown links', async () => {
const html = await md.renderAsync(
'[58-61](/resources/server/user#:~:text=58*,time%20authentication%20token)',
{ cleanUrls: false }
)
expect(html).toContain(
'href="/resources/server/user.html#:~:text=58*,time%20authentication%20token"'
)
})
// https://web.dev/articles/text-fragments#mixing_element_and_text_fragments
test('preserves mixed element and text-fragment hashes', async () => {
const html = await md.renderAsync(
'[Section](/guide/getting-started#Hello%20World:~:text=Hello%20World)',
{ cleanUrls: false }
)
expect(html).toContain(
'href="/guide/getting-started.html#hello-world:~:text=Hello%20World"'
)
})
test('continues to normalize regular heading hashes', async () => {
const html = await md.renderAsync(
'[Section](/guide/getting-started#Hello%20World)',
{ cleanUrls: false }
)
expect(html).toContain('href="/guide/getting-started.html#hello-world"')
})
test('does not break encoding for text-fragments', async () => {
const html = await md.renderAsync(
'[Section](/foo?title=Cat&oldid=916388819#:~:text=Claws-,Like%20almost,the%20Felidae%2C,-cats)',
{ cleanUrls: false }
)
expect(html).toContain(
'href="/foo.html?title=Cat&oldid=916388819#:~:text=Claws-,Like%20almost,the%20Felidae%2C,-cats"'
)
})
test('collects links with their destination as authored', async () => {
const env: { cleanUrls: boolean; links?: MarkdownLink[] } = {
cleanUrls: false
}
await md.renderAsync('Intro\n\n[Missing](./missing.md)\n', env)
expect(env.links).toEqual([{ url: './missing.html', raw: './missing.md' }])
})
})
describe('node/markdown/plugins/link with a relative base', () => {
const md = new MarkdownItAsync()
linkPlugin(md, {}, './', slugify)
const render = (src: string, env: object = {}) =>
md.renderAsync(src, {
cleanUrls: false,
relativePath: 'guide/page.md',
relativizeUrls: true,
...env
})
test('site-absolute links become page-relative', async () => {
expect(await render('[x](/other/thing)')).toContain(
'href="../other/thing.html"'
)
expect(
await render('[x](/other/thing)', { relativePath: 'index.md' })
).toContain('href="./other/thing.html"')
expect(
await render('[x](/other/thing)', { relativePath: 'a/b/c.md' })
).toContain('href="../../other/thing.html"')
})
test('directory links point at index.html', async () => {
expect(await render('[home](/)')).toContain('href="../index.html"')
expect(await render('[dir](/guide/)')).toContain(
'href="../guide/index.html"'
)
})
test('non-page files get the prefix but no .html', async () => {
expect(await render('[zip](/file.zip)')).toContain('href="../file.zip"')
})
test('hash, external and relative links stay untouched', async () => {
expect(await render('[a](#section)')).toContain('href="#section"')
expect(await render('[a](https://example.com/x)')).toContain(
'href="https://example.com/x"'
)
expect(await render('[a](./sibling)')).toContain('href="./sibling.html"')
})
test('cleanUrls drops .html and the index suffix', async () => {
expect(await render('[x](/other/thing)', { cleanUrls: true })).toContain(
'href="../other/thing"'
)
expect(await render('[dir](/guide/)', { cleanUrls: true })).toContain(
'href="../guide/"'
)
})
test('content-loader renders keep absolute links site-absolute', async () => {
// content loaders set relativePath but not relativizeUrls — their html
// is embedded in other pages, so the source's depth must not apply
expect(
await render('[x](/other/thing)', { relativizeUrls: undefined })
).toContain('href="/other/thing.html"')
expect(
await render('[x](/other/thing)', { relativePath: undefined })
).toContain('href="/other/thing.html"')
})
})
describe('node/markdown/plugins/link with an absolute base', () => {
const md = new MarkdownItAsync()
linkPlugin(md, {}, '/docs/', slugify)
test('site-absolute links get the base and keep one slash', async () => {
const html = await md.renderAsync('[x](/guide/what)', {
cleanUrls: false,
relativePath: 'index.md'
})
expect(html).toContain('href="/docs/guide/what.html"')
})
})