返回 slidev
utils.test.ts
根目录 / test / utils.test.ts
1 import type { ResolvedFontOptions, SlideInfo } from '@slidev/types'
2 import { createHash } from 'node:crypto'
3 import { mkdtempSync, readFileSync, rmSync } from 'node:fs'
4 import { tmpdir } from 'node:os'
5 import { join, relative, resolve } from 'node:path'
6 import { fileURLToPath } from 'node:url'
7 import { slash } from '@antfu/utils'
8 import MarkdownExit from 'markdown-exit'
9 import { describe, expect, it } from 'vitest'
10 import YAML from 'yaml'
11 import { parseAspectRatio, parseRangeString } from '../packages/parser/src'
12 import { getRoots, toAtFS } from '../packages/slidev/node/resolver'
13 import { createMakeAbsoluteImportGlob, generateCoollabsFontsUrl, generateGoogleFontsUrl, stringifyMarkdownTokens, updateFrontmatterPatch } from '../packages/slidev/node/utils'
14
15 describe('utils', () => {
16 it('page-range', () => {
17 expect(parseRangeString(5)).toEqual([1, 2, 3, 4, 5])
18 expect(parseRangeString(5, 'all')).toEqual([1, 2, 3, 4, 5])
19 expect(parseRangeString(2, '*')).toEqual([1, 2])
20 expect(parseRangeString(5, '1')).toEqual([1])
21 expect(parseRangeString(10, '1,2-3,5')).toEqual([1, 2, 3, 5])
22 expect(parseRangeString(10, '1;2-3;5')).toEqual([1, 2, 3, 5])
23 expect(parseRangeString(10, '6-')).toEqual([6, 7, 8, 9, 10])
24 })
25
26 it('aspect-ratio', () => {
27 expect(parseAspectRatio(1)).toEqual(1)
28 expect(parseAspectRatio('16/9')).toEqual(16 / 9)
29 expect(parseAspectRatio('16 / 9 ')).toEqual(16 / 9)
30 expect(parseAspectRatio('3:4')).toEqual(3 / 4)
31 expect(parseAspectRatio('1x1')).toEqual(1)
32 expect(parseAspectRatio('1')).toEqual(1)
33
34 expect(() => parseAspectRatio('hello')).toThrow()
35 expect(() => parseAspectRatio('1/0')).toThrow()
36 })
37
38 it('stringify-markdown-tokens', () => {
39 const md = MarkdownExit({ html: true })
40 const stringify = (src: string) => stringifyMarkdownTokens(md.parseInline(src, {}))
41
42 expect(stringify('<span style="color:red">Composable</span> Vue')).toBe('Composable Vue')
43 expect(stringify('<b>Whatever</b>')).toBe('Whatever')
44 expect(stringify('Talk about `<details/>`')).toBe('Talk about <details/>')
45 expect(stringify('What is Readonly\\<T\\> in TypeScript')).toBe('What is Readonly<T> in TypeScript')
46 expect(stringify('Welcome to<br />*Slidev*')).toBe('Welcome to Slidev')
47 })
48
49 it('google-fonts', () => {
50 expect(
51 generateGoogleFontsUrl({
52 webfonts: ['Fira Code', 'PT Serif'],
53 weights: ['200', '400', '600'],
54 provider: 'google',
55 } as ResolvedFontOptions),
56 ).toMatchSnapshot()
57
58 expect(
59 generateGoogleFontsUrl({
60 webfonts: ['Fira Code', 'PT Serif'],
61 weights: ['200', '400', '600'],
62 italic: true,
63 provider: 'google',
64 } as ResolvedFontOptions),
65 ).toMatchSnapshot()
66 })
67
68 it('coollabs-fonts', () => {
69 expect(
70 generateCoollabsFontsUrl({
71 webfonts: ['Fira Code', 'PT Serif'],
72 weights: ['200', '400', '600'],
73 provider: 'google',
74 } as ResolvedFontOptions),
75 ).toMatchSnapshot()
76
77 expect(
78 generateCoollabsFontsUrl({
79 webfonts: ['Fira Code', 'PT Serif'],
80 weights: ['200', '400', '600'],
81 italic: true,
82 provider: 'google',
83 } as ResolvedFontOptions),
84 ).toMatchSnapshot()
85 })
86
87 it('roots', async () => {
88 const { cliRoot, clientRoot, userRoot, userWorkspaceRoot } = await getRoots(resolve('slides.md'))
89 const expectRelative = (v: string) => expect(slash(relative(__dirname, v)))
90 expectRelative(cliRoot).toMatchInlineSnapshot(`"../packages/slidev"`)
91 expectRelative(clientRoot).toMatchInlineSnapshot(`"../packages/client"`)
92 expectRelative(userRoot).toMatchInlineSnapshot(`".."`)
93 expectRelative(userWorkspaceRoot).toMatchInlineSnapshot(`".."`)
94 })
95
96 it('proxies slidev import globs through a real filesystem module', () => {
97 const root = mkdtempSync(join(tmpdir(), 'slidev-import-glob-'))
98 const makeAbsoluteImportGlob = createMakeAbsoluteImportGlob(root)
99
100 try {
101 const proxyUrl = makeAbsoluteImportGlob([
102 join(root, 'setup/main.ts'),
103 ], { import: 'default' })
104 const secondProxyUrl = makeAbsoluteImportGlob([
105 join(root, 'theme/setup/main.ts'),
106 ], { import: 'default' })
107
108 expect(proxyUrl).toMatch(/^\/@fs\/.*\/node_modules\/\.slidev\/virtual\/import-glob\.[a-f0-9]{10}\.ts$/)
109 expect(secondProxyUrl).toMatch(/^\/@fs\/.*\/node_modules\/\.slidev\/virtual\/import-glob\.[a-f0-9]{10}\.ts$/)
110 expect(secondProxyUrl).not.toBe(proxyUrl)
111 const content = readFileSync(fileURLToPath(`file://${proxyUrl.slice('/@fs'.length)}`), 'utf-8')
112 const secondContent = readFileSync(fileURLToPath(`file://${secondProxyUrl.slice('/@fs'.length)}`), 'utf-8')
113 expect(proxyUrl).toBe(toAtFS(join(root, `node_modules/.slidev/virtual/import-glob.${createHash('sha256').update(content).digest('hex').slice(0, 10)}.ts`)))
114 expect(secondProxyUrl).toBe(toAtFS(join(root, `node_modules/.slidev/virtual/import-glob.${createHash('sha256').update(secondContent).digest('hex').slice(0, 10)}.ts`)))
115 expect(content)
116 .toBe('export default import.meta.glob(["../../../setup/main.ts"], {"eager":true,"exhaustive":true,"import":"default"})\n')
117 expect(secondContent)
118 .toBe('export default import.meta.glob(["../../../theme/setup/main.ts"], {"eager":true,"exhaustive":true,"import":"default"})\n')
119 }
120 finally {
121 rmSync(root, { recursive: true, force: true })
122 }
123 })
124
125 it('update frontmatter patch', async () => {
126 const dragPos = {
127 foo: '1,2,3,4',
128 }
129 function createFakeSource(yaml: string) {
130 const doc = YAML.parseDocument(yaml)
131 return {
132 frontmatter: {},
133 source: {
134 frontmatter: doc.toJSON() || {},
135 frontmatterRaw: yaml,
136 frontmatterDoc: doc,
137 },
138 } as SlideInfo
139 }
140 function expectFrontmatter(slide: SlideInfo) {
141 return expect(slide.source.frontmatterDoc?.toString())
142 }
143
144 const slide1 = createFakeSource(``)
145 updateFrontmatterPatch(slide1.source, { dragPos })
146 expectFrontmatter(slide1).toMatchInlineSnapshot(`
147 "dragPos:
148 foo: 1,2,3,4
149 "
150 `)
151
152 const slide2 = createFakeSource(`
153 # comment
154 title: Hello # another comment
155 dragPos:
156 bar: 5,6,7,8
157 `)
158 updateFrontmatterPatch(slide2.source, { dragPos })
159 expectFrontmatter(slide2).toMatchInlineSnapshot(`
160 "# comment
161 title: Hello # another comment
162 dragPos:
163 foo: 1,2,3,4
164 "
165 `)
166
167 // remove a field
168 const slide3 = createFakeSource(`
169 # comment
170 title: Hello # another comment
171 dragPos:
172 bar: 5,6,7,8
173 `)
174 updateFrontmatterPatch(slide3.source, { title: null })
175 expectFrontmatter(slide3).toMatchInlineSnapshot(`
176 "dragPos:
177 bar: 5,6,7,8
178 "
179 `)
180 })
181 })
182
182 lines TYPESCRIPT