Files
2026-07-03 14:27:57 +09:00

182 lines
6.7 KiB
TypeScript

import type { ResolvedFontOptions, SlideInfo } from '@slidev/types'
import { createHash } from 'node:crypto'
import { mkdtempSync, readFileSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join, relative, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { slash } from '@antfu/utils'
import MarkdownExit from 'markdown-exit'
import { describe, expect, it } from 'vitest'
import YAML from 'yaml'
import { parseAspectRatio, parseRangeString } from '../packages/parser/src'
import { getRoots, toAtFS } from '../packages/slidev/node/resolver'
import { createMakeAbsoluteImportGlob, generateCoollabsFontsUrl, generateGoogleFontsUrl, stringifyMarkdownTokens, updateFrontmatterPatch } from '../packages/slidev/node/utils'
describe('utils', () => {
it('page-range', () => {
expect(parseRangeString(5)).toEqual([1, 2, 3, 4, 5])
expect(parseRangeString(5, 'all')).toEqual([1, 2, 3, 4, 5])
expect(parseRangeString(2, '*')).toEqual([1, 2])
expect(parseRangeString(5, '1')).toEqual([1])
expect(parseRangeString(10, '1,2-3,5')).toEqual([1, 2, 3, 5])
expect(parseRangeString(10, '1;2-3;5')).toEqual([1, 2, 3, 5])
expect(parseRangeString(10, '6-')).toEqual([6, 7, 8, 9, 10])
})
it('aspect-ratio', () => {
expect(parseAspectRatio(1)).toEqual(1)
expect(parseAspectRatio('16/9')).toEqual(16 / 9)
expect(parseAspectRatio('16 / 9 ')).toEqual(16 / 9)
expect(parseAspectRatio('3:4')).toEqual(3 / 4)
expect(parseAspectRatio('1x1')).toEqual(1)
expect(parseAspectRatio('1')).toEqual(1)
expect(() => parseAspectRatio('hello')).toThrow()
expect(() => parseAspectRatio('1/0')).toThrow()
})
it('stringify-markdown-tokens', () => {
const md = MarkdownExit({ html: true })
const stringify = (src: string) => stringifyMarkdownTokens(md.parseInline(src, {}))
expect(stringify('<span style="color:red">Composable</span> Vue')).toBe('Composable Vue')
expect(stringify('<b>Whatever</b>')).toBe('Whatever')
expect(stringify('Talk about `<details/>`')).toBe('Talk about <details/>')
expect(stringify('What is Readonly\\<T\\> in TypeScript')).toBe('What is Readonly<T> in TypeScript')
expect(stringify('Welcome to<br />*Slidev*')).toBe('Welcome to Slidev')
})
it('google-fonts', () => {
expect(
generateGoogleFontsUrl({
webfonts: ['Fira Code', 'PT Serif'],
weights: ['200', '400', '600'],
provider: 'google',
} as ResolvedFontOptions),
).toMatchSnapshot()
expect(
generateGoogleFontsUrl({
webfonts: ['Fira Code', 'PT Serif'],
weights: ['200', '400', '600'],
italic: true,
provider: 'google',
} as ResolvedFontOptions),
).toMatchSnapshot()
})
it('coollabs-fonts', () => {
expect(
generateCoollabsFontsUrl({
webfonts: ['Fira Code', 'PT Serif'],
weights: ['200', '400', '600'],
provider: 'google',
} as ResolvedFontOptions),
).toMatchSnapshot()
expect(
generateCoollabsFontsUrl({
webfonts: ['Fira Code', 'PT Serif'],
weights: ['200', '400', '600'],
italic: true,
provider: 'google',
} as ResolvedFontOptions),
).toMatchSnapshot()
})
it('roots', async () => {
const { cliRoot, clientRoot, userRoot, userWorkspaceRoot } = await getRoots(resolve('slides.md'))
const expectRelative = (v: string) => expect(slash(relative(__dirname, v)))
expectRelative(cliRoot).toMatchInlineSnapshot(`"../packages/slidev"`)
expectRelative(clientRoot).toMatchInlineSnapshot(`"../packages/client"`)
expectRelative(userRoot).toMatchInlineSnapshot(`".."`)
expectRelative(userWorkspaceRoot).toMatchInlineSnapshot(`".."`)
})
it('proxies slidev import globs through a real filesystem module', () => {
const root = mkdtempSync(join(tmpdir(), 'slidev-import-glob-'))
const makeAbsoluteImportGlob = createMakeAbsoluteImportGlob(root)
try {
const proxyUrl = makeAbsoluteImportGlob([
join(root, 'setup/main.ts'),
], { import: 'default' })
const secondProxyUrl = makeAbsoluteImportGlob([
join(root, 'theme/setup/main.ts'),
], { import: 'default' })
expect(proxyUrl).toMatch(/^\/@fs\/.*\/node_modules\/\.slidev\/virtual\/import-glob\.[a-f0-9]{10}\.ts$/)
expect(secondProxyUrl).toMatch(/^\/@fs\/.*\/node_modules\/\.slidev\/virtual\/import-glob\.[a-f0-9]{10}\.ts$/)
expect(secondProxyUrl).not.toBe(proxyUrl)
const content = readFileSync(fileURLToPath(`file://${proxyUrl.slice('/@fs'.length)}`), 'utf-8')
const secondContent = readFileSync(fileURLToPath(`file://${secondProxyUrl.slice('/@fs'.length)}`), 'utf-8')
expect(proxyUrl).toBe(toAtFS(join(root, `node_modules/.slidev/virtual/import-glob.${createHash('sha256').update(content).digest('hex').slice(0, 10)}.ts`)))
expect(secondProxyUrl).toBe(toAtFS(join(root, `node_modules/.slidev/virtual/import-glob.${createHash('sha256').update(secondContent).digest('hex').slice(0, 10)}.ts`)))
expect(content)
.toBe('export default import.meta.glob(["../../../setup/main.ts"], {"eager":true,"exhaustive":true,"import":"default"})\n')
expect(secondContent)
.toBe('export default import.meta.glob(["../../../theme/setup/main.ts"], {"eager":true,"exhaustive":true,"import":"default"})\n')
}
finally {
rmSync(root, { recursive: true, force: true })
}
})
it('update frontmatter patch', async () => {
const dragPos = {
foo: '1,2,3,4',
}
function createFakeSource(yaml: string) {
const doc = YAML.parseDocument(yaml)
return {
frontmatter: {},
source: {
frontmatter: doc.toJSON() || {},
frontmatterRaw: yaml,
frontmatterDoc: doc,
},
} as SlideInfo
}
function expectFrontmatter(slide: SlideInfo) {
return expect(slide.source.frontmatterDoc?.toString())
}
const slide1 = createFakeSource(``)
updateFrontmatterPatch(slide1.source, { dragPos })
expectFrontmatter(slide1).toMatchInlineSnapshot(`
"dragPos:
foo: 1,2,3,4
"
`)
const slide2 = createFakeSource(`
# comment
title: Hello # another comment
dragPos:
bar: 5,6,7,8
`)
updateFrontmatterPatch(slide2.source, { dragPos })
expectFrontmatter(slide2).toMatchInlineSnapshot(`
"# comment
title: Hello # another comment
dragPos:
foo: 1,2,3,4
"
`)
// remove a field
const slide3 = createFakeSource(`
# comment
title: Hello # another comment
dragPos:
bar: 5,6,7,8
`)
updateFrontmatterPatch(slide3.source, { title: null })
expectFrontmatter(slide3).toMatchInlineSnapshot(`
"dragPos:
bar: 5,6,7,8
"
`)
})
})