test: improve unit test

This commit is contained in:
pengzhanbo 2026-02-14 20:03:44 +08:00
parent 5930c60462
commit fc9984b27c
18 changed files with 1408 additions and 1 deletions

View File

@ -0,0 +1,245 @@
import MarkdownIt from 'markdown-it'
import { describe, expect, it } from 'vitest'
import { abbrPlugin } from '../src/node/inline/abbr.js'
function createMarkdown(globalAbbreviations?: Record<string, string>) {
return MarkdownIt().use(abbrPlugin, globalAbbreviations)
}
describe('abbrPlugin', () => {
it('should parse abbreviation definition', () => {
const md = createMarkdown()
const code = `*[HTML]: HyperText Markup Language
This is HTML content.`
const result = md.render(code)
expect(result).toContain('Abbreviation')
expect(result).toContain('HTML')
expect(result).toContain('HyperText Markup Language')
})
it('should parse multiple abbreviation definitions', () => {
const md = createMarkdown()
const code = `*[HTML]: HyperText Markup Language
*[CSS]: Cascading Style Sheets
HTML and CSS are web technologies.`
const result = md.render(code)
expect(result).toContain('HTML')
expect(result).toContain('CSS')
})
it('should work with global abbreviations', () => {
const md = createMarkdown({ API: 'Application Programming Interface' })
const code = `This is an API documentation.`
const result = md.render(code)
expect(result).toContain('Abbreviation')
expect(result).toContain('API')
expect(result).toContain('Application Programming Interface')
})
it('should merge local and global abbreviations', () => {
const md = createMarkdown({ API: 'Application Programming Interface' })
const code = `*[SDK]: Software Development Kit
API and SDK are common terms.`
const result = md.render(code)
expect(result).toContain('API')
expect(result).toContain('SDK')
})
it('should handle abbreviation in middle of text', () => {
const md = createMarkdown()
const code = `*[TEST]: Test Abbreviation
This TEST is important.`
const result = md.render(code)
expect(result).toContain('TEST')
expect(result).toContain('Test Abbreviation')
})
it('should handle abbreviation at start of text', () => {
const md = createMarkdown()
const code = `*[HTML]: HyperText Markup Language
HTML is a markup language.`
const result = md.render(code)
expect(result).toContain('Abbreviation')
})
it('should handle abbreviation at end of text', () => {
const md = createMarkdown()
const code = `*[HTML]: HyperText Markup Language
Learn HTML`
const result = md.render(code)
expect(result).toContain('Abbreviation')
})
it('should handle multiple occurrences of same abbreviation', () => {
const md = createMarkdown()
const code = `*[HTML]: HyperText Markup Language
HTML, HTML, and more HTML.`
const result = md.render(code)
expect(result).toContain('HTML')
expect(result).toContain('HyperText Markup Language')
})
it('should not match partial words', () => {
const md = createMarkdown()
const code = `*[HTML]: HyperText Markup Language
This is HTML but not XHTML.`
const result = md.render(code)
expect(result).toContain('HTML')
})
it('should handle empty abbreviation definition gracefully', () => {
const md = createMarkdown()
const code = `*[]: Empty definition
Some text.`
const result = md.render(code)
expect(result).toBeDefined()
})
it('should handle case-sensitive abbreviations', () => {
const md = createMarkdown()
const code = `*[HTML]: HyperText Markup Language
html is different from HTML.`
const result = md.render(code)
expect(result).toContain('HTML')
})
it('should handle abbreviation with markdown in title', () => {
const md = createMarkdown()
const code = `*[MD]: **Markdown** text
MD is great.`
const result = md.render(code)
expect(result).toContain('MD')
})
it('should handle empty global abbreviations', () => {
const md = createMarkdown({})
const code = `*[TEST]: Test
TEST is defined.`
const result = md.render(code)
expect(result).toContain('TEST')
})
it('should handle escaped characters in label', () => {
const md = createMarkdown()
const code = `*[HTML\\[1\\]]: HTML Version 1
HTML[1] is old.`
const result = md.render(code)
expect(result).toContain('HTML[1]')
})
it('should handle abbreviation with special characters in title', () => {
const md = createMarkdown()
const code = `*[XML]: eXtensible Markup Language
This is XML.`
const result = md.render(code)
expect(result).toContain('XML')
})
it('should handle definition with empty title', () => {
const md = createMarkdown()
const code = `*[TEST]:
Some text.`
const result = md.render(code)
expect(result).not.toContain('Abbreviation')
})
it('should handle definition with only label', () => {
const md = createMarkdown()
const code = `*[TEST]:
Some text.`
const result = md.render(code)
expect(result).not.toContain('Abbreviation')
})
it('should handle unclosed bracket in definition', () => {
const md = createMarkdown()
const code = `*[TEST: Test
Some text.`
const result = md.render(code)
expect(result).toBeDefined()
})
it('should handle nested bracket in label', () => {
const md = createMarkdown()
const code = `*[TEST[INNER]]: Test Abbreviation
Some text.`
const result = md.render(code)
expect(result).toBeDefined()
})
it('should handle global abbreviations with colon prefix', () => {
const md = createMarkdown({ ':API': 'Application Programming Interface' })
const code = `This is an API call.`
const result = md.render(code)
expect(result).toContain('API')
})
it('should handle abbreviation adjacent to punctuation', () => {
const md = createMarkdown()
const code = `*[HTML]: HyperText Markup Language
Use HTML, CSS, and JS.`
const result = md.render(code)
expect(result).toContain('Abbreviation')
expect(result).toContain('HTML')
})
it('should handle abbreviation at sentence end', () => {
const md = createMarkdown()
const code = `*[HTML]: HyperText Markup Language
This is HTML.`
const result = md.render(code)
expect(result).toContain('Abbreviation')
})
it('should handle abbreviation in parentheses', () => {
const md = createMarkdown()
const code = `*[HTML]: HyperText Markup Language
See (HTML) for details.`
const result = md.render(code)
expect(result).toContain('Abbreviation')
})
})

View File

@ -0,0 +1,269 @@
import MarkdownIt from 'markdown-it'
import { describe, expect, it } from 'vitest'
import { annotationPlugin } from '../src/node/inline/annotation.js'
function createMarkdown(globalAnnotations?: Record<string, string | string[]>) {
return MarkdownIt().use(annotationPlugin, globalAnnotations)
}
describe('annotationPlugin', () => {
it('should parse annotation definition and reference', () => {
const md = createMarkdown()
const code = `[+note]: This is a note annotation
This is a paragraph with a [+note] annotation.`
const result = md.render(code)
expect(result).toContain('Annotation')
expect(result).toContain('note')
expect(result).toContain('This is a note annotation')
})
it('should parse multiple annotation definitions', () => {
const md = createMarkdown()
const code = `[+note]: First note
[+tip]: Second tip
This has [+note] and [+tip].`
const result = md.render(code)
expect(result).toContain('note')
expect(result).toContain('tip')
})
it('should merge local and global annotations', () => {
const md = createMarkdown({ global: 'Global annotation' })
const code = `[+local]: Local annotation
This uses [+global] and [+local].`
const result = md.render(code)
expect(result).toContain('global')
expect(result).toContain('local')
})
it('should handle multiple references to same annotation', () => {
const md = createMarkdown()
const code = `[+note]: Same note
First [+note], second [+note], third [+note].`
const result = md.render(code)
expect(result).toContain('note')
expect(result).toContain('Same note')
})
it('should handle multi-line annotation', () => {
const md = createMarkdown()
const code = `[+note]: This is a
multi-line annotation
with multiple lines
This has [+note].`
const result = md.render(code)
expect(result).toContain('Annotation')
})
it('should handle annotation with markdown content', () => {
const md = createMarkdown()
const code = `[+note]: This is **bold** and *italic*
This has [+note].`
const result = md.render(code)
expect(result).toContain('Annotation')
})
it('should handle annotation with code', () => {
const md = createMarkdown()
const code = `[+code]: Use \`code\` syntax
This has [+code].`
const result = md.render(code)
expect(result).toContain('Annotation')
})
it('should handle annotation with links', () => {
const md = createMarkdown()
const code = `[+link]: See [documentation](https://example.com)
Check [+link].`
const result = md.render(code)
expect(result).toContain('Annotation')
})
it('should handle multiple annotations for same label', () => {
const md = createMarkdown()
const code = `[+multi]: First annotation
[+multi]: Second annotation
This has [+multi].`
const result = md.render(code)
expect(result).toContain('total="2"')
})
it('should not render undefined annotation reference', () => {
const md = createMarkdown()
const code = `This has [+undefined] annotation.`
const result = md.render(code)
expect(result).not.toContain('Annotation')
expect(result).toContain('[+undefined]')
})
it('should handle empty annotation label gracefully', () => {
const md = createMarkdown()
const code = `[+]: Empty label
Some text.`
const result = md.render(code)
expect(result).toBeDefined()
})
it('should handle annotation with special characters in label', () => {
const md = createMarkdown()
const code = `[+note-1]: Note with hyphen and number
Use [+note-1].`
const result = md.render(code)
expect(result).toContain('Annotation')
})
it('should handle annotation with unicode content', () => {
const md = createMarkdown()
const code = `[+中文]: 这是一个中文注释
使 [+]`
const result = md.render(code)
expect(result).toContain('Annotation')
})
it('should handle annotation in list', () => {
const md = createMarkdown()
const code = `[+item]: List item annotation
- Item 1 [+item]
- Item 2 [+item]`
const result = md.render(code)
expect(result).toContain('Annotation')
})
it('should handle annotation in blockquote', () => {
const md = createMarkdown()
const code = `[+quote]: Quote annotation
> This is a quote [+quote]`
const result = md.render(code)
expect(result).toContain('Annotation')
})
it('should handle empty global annotations', () => {
const md = createMarkdown({})
const code = `[+test]: Test annotation
Use [+test].`
const result = md.render(code)
expect(result).toContain('Annotation')
})
it('should handle space in annotation label', () => {
const md = createMarkdown()
const code = `[+ note]: Invalid label
Some text.`
const result = md.render(code)
expect(result).not.toContain('Annotation')
})
it('should handle space in annotation reference', () => {
const md = createMarkdown()
const code = `[+note]: Test annotation
Use [+ note].`
const result = md.render(code)
expect(result).toContain('[+ note]')
})
it('should handle newline in annotation reference', () => {
const md = createMarkdown()
const code = `[+note]: Test annotation
Use [+
note].`
const result = md.render(code)
expect(result).not.toContain('Annotation')
})
it('should handle unclosed annotation reference', () => {
const md = createMarkdown()
const code = `[+note]: Test annotation
Use [+note.`
const result = md.render(code)
expect(result).not.toContain('Annotation')
})
it('should handle global annotations with colon prefix', () => {
const md = createMarkdown({ ':test': 'Test annotation' })
const code = `[+test]: Local test
Use [+test].`
const result = md.render(code)
expect(result).toContain('Annotation')
})
it('should handle annotation reference at end of text', () => {
const md = createMarkdown()
const code = `[+note]: Test annotation
End with [+note]`
const result = md.render(code)
expect(result).toContain('Annotation')
})
it('should handle annotation reference at start of text', () => {
const md = createMarkdown()
const code = `[+note]: Test annotation
[+note] at start.`
const result = md.render(code)
expect(result).toContain('Annotation')
})
it('should handle definition without colon', () => {
const md = createMarkdown()
const code = `[+note] Test annotation
Some text.`
const result = md.render(code)
expect(result).not.toContain('Annotation')
})
it('should handle annotation reference in code block', () => {
const md = createMarkdown()
const code = `[+note]: Test annotation
\`[+note]\` is code.`
const result = md.render(code)
expect(result).toContain('[+note]')
})
})

View File

@ -34,4 +34,57 @@ describe('cleanMarkdownEnv', () => {
annotations: 'annotations', annotations: 'annotations',
}) })
}) })
it('should handle empty env', () => {
const result = cleanMarkdownEnv({})
expect(result).toEqual({})
})
it('should handle env with only valid keys', () => {
const validEnv = {
base: '/base/',
filePath: '/path/to/file.md',
filePathRelative: 'path/to/file.md',
}
const result = cleanMarkdownEnv(validEnv)
expect(result).toEqual(validEnv)
})
it('should handle env with undefined values', () => {
const envWithUndefined = {
base: undefined,
filePath: 'file.md',
}
const result = cleanMarkdownEnv(envWithUndefined)
expect(result).toEqual({
base: undefined,
filePath: 'file.md',
})
})
it('should exclude all valid keys', () => {
const result = cleanMarkdownEnv(env, ['base', 'filePath', 'filePathRelative', 'references', 'abbreviations', 'annotations'])
expect(result).toEqual({})
})
it('should handle complex references', () => {
const envWithRefs: CleanMarkdownEnv = {
base: '/',
references: {
link1: { href: 'https://example.com', title: 'Example' },
},
abbreviations: { HTML: 'HyperText Markup Language' },
annotations: { note: { sources: ['Note 1'], rendered: [] } },
}
const result = cleanMarkdownEnv(envWithRefs)
expect(result.references).toEqual({ link1: { href: 'https://example.com', title: 'Example' } })
expect(result.abbreviations).toEqual({ HTML: 'HyperText Markup Language' })
expect(result.annotations).toEqual({ note: { sources: ['Note 1'], rendered: [] } })
})
it('should not mutate original env', () => {
const originalEnv = { ...env }
cleanMarkdownEnv(env)
expect(env).toEqual(originalEnv)
})
}) })

View File

@ -0,0 +1,163 @@
import { describe, expect, it } from 'vitest'
import { parseEmbedCode } from '../src/node/demo/normal.js'
describe('parseEmbedCode', () => {
it('should parse basic HTML code', () => {
const code = '<div>Hello World</div>'
const result = parseEmbedCode(code)
expect(result.html).toBe('<div>Hello World</div>')
expect(result.script).toBe('')
expect(result.css).toBe('')
expect(result.jsType).toBe('js')
expect(result.cssType).toBe('css')
})
it('should parse HTML with script', () => {
const code = `<div>Hello</div>
<script>
const message = 'Hello World'
</script>`
const result = parseEmbedCode(code)
expect(result.html?.trim()).toBe('<div>Hello</div>')
expect(result.script?.trim()).toBe('const message = \'Hello World\'')
expect(result.jsType).toBe('js')
})
it('should parse HTML with TypeScript script', () => {
const code = `<div>Hello</div>
<script lang="ts">
const message: string = 'Hello World'
</script>`
const result = parseEmbedCode(code)
expect(result.jsType).toBe('ts')
expect(result.script?.trim()).toBe('const message: string = \'Hello World\'')
})
it('should parse HTML with style', () => {
const code = `<div class="container">Hello</div>
<style>
.container { color: red; }
</style>`
const result = parseEmbedCode(code)
expect(result.html?.trim()).toBe('<div class="container">Hello</div>')
expect(result.css?.trim()).toBe('.container { color: red; }')
expect(result.cssType).toBe('css')
})
it('should parse HTML with SCSS style', () => {
const code = `<div>Hello</div>
<style lang="scss">
.container {
.inner { color: red; }
}
</style>`
const result = parseEmbedCode(code)
expect(result.cssType).toBe('scss')
})
it('should parse HTML with Less style', () => {
const code = `<div>Hello</div>
<style lang="less">
@color: red;
.container { color: @color; }
</style>`
const result = parseEmbedCode(code)
expect(result.cssType).toBe('less')
})
it('should parse HTML with Stylus style', () => {
const code = `<div>Hello</div>
<style lang="stylus">
color = red
.container
color color
</style>`
const result = parseEmbedCode(code)
expect(result.cssType).toBe('stylus')
})
it('should parse HTML with Stylus style (styl extension)', () => {
const code = `<div>Hello</div>
<style lang="styl">
color = red
.container
color color
</style>`
const result = parseEmbedCode(code)
expect(result.cssType).toBe('stylus')
})
it('should parse HTML with config', () => {
const code = `<div>Hello</div>
<script type="config">
{
"jsLib": ["https://cdn.example.com/lib.js"],
"cssLib": ["https://cdn.example.com/style.css"]
}
</script>`
const result = parseEmbedCode(code)
expect(result.imports?.trim()).toContain('jsLib')
expect(result.imports?.trim()).toContain('cssLib')
})
it('should parse complete code with all parts', () => {
const code = `<div id="app">{{ message }}</div>
<script type="config">
{ "jsLib": ["vue"] }
</script>
<script lang="ts">
import { ref } from 'vue'
const message = ref<string>('Hello')
</script>
<style lang="scss">
#app { color: blue; }
</style>`
const result = parseEmbedCode(code)
expect(result.html?.trim()).toBe('<div id="app">{{ message }}</div>')
expect(result.imports).toContain('vue')
expect(result.jsType).toBe('ts')
expect(result.cssType).toBe('scss')
})
it('should handle empty code', () => {
const result = parseEmbedCode('')
expect(result.html).toBe('')
expect(result.script).toBe('')
expect(result.css).toBe('')
})
it('should handle code with only whitespace', () => {
const result = parseEmbedCode(' \n \n ')
expect(result.html?.trim()).toBe('')
})
it('should handle unknown script lang as js', () => {
const code = `<script lang="unknown">
console.log('test')
</script>`
const result = parseEmbedCode(code)
expect(result.jsType).toBe('js')
})
it('should handle unknown style lang as css', () => {
const code = `<style lang="unknown">
.test { color: red }
</style>`
const result = parseEmbedCode(code)
expect(result.cssType).toBe('css')
})
})

View File

@ -0,0 +1,103 @@
import { describe, expect, it } from 'vitest'
import { encryptContent } from '../src/node/utils/encryptContent.js'
describe('encryptContent', () => {
it('should encrypt content with valid options', async () => {
const content = 'Hello, World!'
const password = 'test-password'
const iv = new Uint8Array(16)
const salt = new Uint8Array(16)
const encrypted = await encryptContent(content, { password, iv, salt })
expect(typeof encrypted).toBe('string')
expect(encrypted.length).toBeGreaterThan(0)
expect(encrypted).not.toBe(content)
})
it('should produce different encrypted content for different passwords', async () => {
const content = 'Same content'
const iv = new Uint8Array(16)
const salt = new Uint8Array(16)
const encrypted1 = await encryptContent(content, { password: 'password1', iv, salt })
const encrypted2 = await encryptContent(content, { password: 'password2', iv, salt })
expect(encrypted1).not.toBe(encrypted2)
})
it('should produce different encrypted content for different IVs', async () => {
const content = 'Same content'
const password = 'same-password'
const salt = new Uint8Array(16)
const iv1 = new Uint8Array(16).fill(1)
const iv2 = new Uint8Array(16).fill(2)
const encrypted1 = await encryptContent(content, { password, iv: iv1, salt })
const encrypted2 = await encryptContent(content, { password, iv: iv2, salt })
expect(encrypted1).not.toBe(encrypted2)
})
it('should produce different encrypted content for different salts', async () => {
const content = 'Same content'
const password = 'same-password'
const iv = new Uint8Array(16)
const salt1 = new Uint8Array(16).fill(1)
const salt2 = new Uint8Array(16).fill(2)
const encrypted1 = await encryptContent(content, { password, iv, salt: salt1 })
const encrypted2 = await encryptContent(content, { password, iv, salt: salt2 })
expect(encrypted1).not.toBe(encrypted2)
})
it('should encrypt empty string', async () => {
const content = ''
const password = 'test-password'
const iv = new Uint8Array(16)
const salt = new Uint8Array(16)
const encrypted = await encryptContent(content, { password, iv, salt })
expect(typeof encrypted).toBe('string')
})
it('should encrypt unicode content', async () => {
const content = '你好,世界!🌍🎉'
const password = 'test-password'
const iv = new Uint8Array(16)
const salt = new Uint8Array(16)
const encrypted = await encryptContent(content, { password, iv, salt })
expect(typeof encrypted).toBe('string')
expect(encrypted.length).toBeGreaterThan(0)
})
it('should encrypt long content', async () => {
const content = 'A'.repeat(10000)
const password = 'test-password'
const iv = new Uint8Array(16)
const salt = new Uint8Array(16)
const encrypted = await encryptContent(content, { password, iv, salt })
expect(typeof encrypted).toBe('string')
expect(encrypted.length).toBeGreaterThan(0)
})
it('should encrypt content with special characters', async () => {
const content = '<script>alert("xss")</script>\n\t\r'
const password = 'test-password'
const iv = new Uint8Array(16)
const salt = new Uint8Array(16)
const encrypted = await encryptContent(content, { password, iv, salt })
expect(typeof encrypted).toBe('string')
expect(encrypted.length).toBeGreaterThan(0)
})
})

View File

@ -45,4 +45,39 @@ describe('getFileIcon(filename, type)', () => {
expect(getFileIcon('abc.', 'folder')).toBe('vscode-icons:default-folder') expect(getFileIcon('abc.', 'folder')).toBe('vscode-icons:default-folder')
expect(getFileIcon('')).toBe('vscode-icons:default-file') expect(getFileIcon('')).toBe('vscode-icons:default-file')
}) })
it('should handle common file types', () => {
expect(getFileIcon('README.md')).toBeDefined()
expect(getFileIcon('package.json')).toBeDefined()
expect(getFileIcon('style.css')).toBeDefined()
expect(getFileIcon('index.html')).toBeDefined()
})
it('should handle config files', () => {
expect(getFileIcon('.gitignore')).toBeDefined()
expect(getFileIcon('.eslintrc')).toBeDefined()
expect(getFileIcon('tsconfig.json')).toBeDefined()
})
it('should handle image files', () => {
expect(getFileIcon('image.png')).toBeDefined()
expect(getFileIcon('image.jpg')).toBeDefined()
expect(getFileIcon('image.svg')).toBeDefined()
expect(getFileIcon('image.gif')).toBeDefined()
})
it('should handle files with path', () => {
expect(getFileIcon('src/components/Button.vue')).toBe('vscode-icons:file-type-vue')
expect(getFileIcon('/absolute/path/to/file.ts')).toBe('vscode-icons:file-type-typescript')
})
it('should handle files without extension', () => {
expect(getFileIcon('Makefile')).toBeDefined()
expect(getFileIcon('Dockerfile')).toBeDefined()
})
it('should handle hidden files', () => {
expect(getFileIcon('.env')).toBeDefined()
expect(getFileIcon('.npmrc')).toBeDefined()
})
}) })

View File

@ -0,0 +1,68 @@
import type { MDPowerLocaleData } from '../src/shared/index.js'
import { describe, expect, it } from 'vitest'
import { findLocales } from '../src/node/utils/findLocales.js'
describe('findLocales', () => {
interface TestLocaleData extends MDPowerLocaleData {
hint?: string
label?: string
}
it('should find locales for a key', () => {
const locales = {
'/': { hint: 'Hint', label: 'Label' },
'/zh/': { hint: '提示', label: '标签' },
}
expect(findLocales<TestLocaleData, 'hint'>(locales, 'hint')).toEqual({
'/': 'Hint',
'/zh/': '提示',
})
})
it('should return empty object for missing key', () => {
const locales = {
'/': { hint: 'Hint' },
'/zh/': { hint: '提示' },
}
expect(findLocales<TestLocaleData, 'label'>(locales, 'label')).toEqual({
'/': {},
'/zh/': {},
})
})
it('should handle empty locales', () => {
expect(findLocales({}, 'hint' as any)).toEqual({})
})
it('should handle partial locale data', () => {
const locales = {
'/': { hint: 'Hint', label: 'Label' },
'/zh/': { hint: '提示' },
'/en/': {},
}
expect(findLocales<TestLocaleData, 'label'>(locales, 'label')).toEqual({
'/': 'Label',
'/zh/': {},
'/en/': {},
})
})
it('should handle nested locale paths', () => {
const locales = {
'/': { hint: 'Root' },
'/zh/': { hint: 'Chinese' },
'/zh-tw/': { hint: 'Traditional Chinese' },
'/en-US/': { hint: 'US English' },
}
expect(findLocales<TestLocaleData, 'hint'>(locales, 'hint')).toEqual({
'/': 'Root',
'/zh/': 'Chinese',
'/zh-tw/': 'Traditional Chinese',
'/en-US/': 'US English',
})
})
})

View File

@ -0,0 +1,29 @@
import { describe, expect, it } from 'vitest'
import { nanoid } from '../src/node/utils/nanoid.js'
describe('nanoid', () => {
it('should generate id with default length', () => {
const id = nanoid()
expect(id).toHaveLength(5)
expect(id).toMatch(/^[a-z]+$/)
})
it('should generate id with custom length', () => {
expect(nanoid(10)).toHaveLength(10)
expect(nanoid(1)).toHaveLength(1)
expect(nanoid(20)).toHaveLength(20)
})
it('should generate unique ids', () => {
const ids = new Set<string>()
for (let i = 0; i < 100; i++) {
ids.add(nanoid())
}
expect(ids.size).toBe(100)
})
it('should only contain lowercase letters', () => {
const id = nanoid(100)
expect(id).toMatch(/^[a-z]+$/)
})
})

View File

@ -0,0 +1,93 @@
import { describe, expect, it } from 'vitest'
import { interopDefault } from '../src/node/utils/package.js'
describe('interopDefault', () => {
it('should return default export if exists', async () => {
const module = { default: { name: 'test' }, other: 'value' }
const result = await interopDefault(Promise.resolve(module))
expect(result).toEqual({ name: 'test' })
})
it('should return module itself if no default export', async () => {
const module = { name: 'test', value: 123 }
const result = await interopDefault(Promise.resolve(module))
expect(result).toEqual({ name: 'test', value: 123 })
})
it('should handle non-object default', async () => {
const module = { default: 'string value' }
const result = await interopDefault(Promise.resolve(module))
expect(result).toBe('string value')
})
it('should handle function default', async () => {
const fn = () => 'function result'
const module = { default: fn }
const result = await interopDefault(Promise.resolve(module))
expect(result).toBe(fn)
expect(result()).toBe('function result')
})
it('should handle primitive values', async () => {
const result = await interopDefault(Promise.resolve(42))
expect(result).toBe(42)
})
it('should handle string values', async () => {
const result = await interopDefault(Promise.resolve('hello'))
expect(result).toBe('hello')
})
it('should handle array values', async () => {
const arr = [1, 2, 3]
const result = await interopDefault(Promise.resolve(arr))
expect(result).toEqual([1, 2, 3])
})
it('should handle nested object default', async () => {
const module = {
default: {
nested: {
deep: 'value',
},
},
}
const result = await interopDefault(Promise.resolve(module))
expect(result).toEqual({ nested: { deep: 'value' } })
})
it('should handle class as default', async () => {
class TestClass {
value = 'test'
}
const module = { default: TestClass }
const result = await interopDefault(Promise.resolve(module))
expect(result).toBe(TestClass)
// eslint-disable-next-line new-cap
expect(new result().value).toBe('test')
})
it('should return module for falsy default values', async () => {
const module = { default: 0 }
const result = await interopDefault(Promise.resolve(module))
expect(result).toEqual({ default: 0 })
})
it('should return module for empty string default', async () => {
const module = { default: '' }
const result = await interopDefault(Promise.resolve(module))
expect(result).toEqual({ default: '' })
})
it('should return module for false as default', async () => {
const module = { default: false }
const result = await interopDefault(Promise.resolve(module))
expect(result).toEqual({ default: false })
})
it('should return module for null as default', async () => {
const module = { default: null }
const result = await interopDefault(Promise.resolve(module))
expect(result).toEqual({ default: null })
})
})

View File

@ -17,4 +17,45 @@ describe('parseRect(str)', () => {
expect(parseRect('1%', 'px')).toBe('1%') expect(parseRect('1%', 'px')).toBe('1%')
expect(parseRect('1em', 'px')).toBe('1em') expect(parseRect('1em', 'px')).toBe('1em')
}) })
it('should handle zero value', () => {
expect(parseRect('0')).toBe('0px')
expect(parseRect('0px')).toBe('0px')
expect(parseRect('0%')).toBe('0%')
})
it('should handle decimal values', () => {
expect(parseRect('1.5')).toBe('1.5px')
expect(parseRect('0.5em')).toBe('0.5em')
expect(parseRect('50.5%')).toBe('50.5%')
})
it('should handle negative values', () => {
expect(parseRect('-1')).toBe('-1px')
expect(parseRect('-10px')).toBe('-10px')
expect(parseRect('-5%')).toBe('-5%')
})
it('should handle large values', () => {
expect(parseRect('10000')).toBe('10000px')
expect(parseRect('9999px')).toBe('9999px')
})
it('should handle various units', () => {
expect(parseRect('1rem')).toBe('1rem')
expect(parseRect('1vh')).toBe('1vh')
expect(parseRect('1vw')).toBe('1vw')
expect(parseRect('1pt')).toBe('1pt')
expect(parseRect('1mm')).toBe('1mm')
expect(parseRect('1in')).toBe('1in')
})
it('should handle non-numeric values', () => {
expect(parseRect('auto')).toBe('auto')
expect(parseRect('abc')).toBe('abc')
})
it('should handle calc expressions', () => {
expect(parseRect('calc(100% - 20px)')).toBe('calc(100% - 20px)')
})
}) })

View File

@ -42,6 +42,62 @@ describe('resolveAttrs(info)', () => {
attrs: { fooBar: '1', fizzBuzz: true }, attrs: { fooBar: '1', fizzBuzz: true },
}) })
}) })
it('should handle single quotes', () => {
expect(resolveAttrs('a=\'1\'')).toEqual({
rawAttrs: 'a=\'1\'',
attrs: { a: '1' },
})
})
it('should handle mixed quotes', () => {
expect(resolveAttrs('a="1" b=\'2\'')).toEqual({
rawAttrs: 'a="1" b=\'2\'',
attrs: { a: '1', b: '2' },
})
})
it('should handle values with spaces in quotes', () => {
expect(resolveAttrs('title="hello world"')).toEqual({
rawAttrs: 'title="hello world"',
attrs: { title: 'hello world' },
})
})
it('should handle special characters in values', () => {
expect(resolveAttrs('data-value="<script>"')).toEqual({
rawAttrs: 'data-value="<script>"',
attrs: { dataValue: '<script>' },
})
})
it('should handle unicode values', () => {
expect(resolveAttrs('title="你好世界"')).toEqual({
rawAttrs: 'title="你好世界"',
attrs: { title: '你好世界' },
})
})
it('should handle numeric string values', () => {
expect(resolveAttrs('width="100" height="200"')).toEqual({
rawAttrs: 'width="100" height="200"',
attrs: { width: '100', height: '200' },
})
})
it('should handle empty value with quotes', () => {
expect(resolveAttrs('a=""')).toEqual({
rawAttrs: 'a=""',
attrs: { a: '' },
})
})
it('should handle multiple spaces between attrs', () => {
expect(resolveAttrs('a="1" b="2"')).toEqual({
rawAttrs: 'a="1" b="2"',
attrs: { a: '1', b: '2' },
})
})
}) })
describe('resolveAttr(info, key)', () => { describe('resolveAttr(info, key)', () => {
@ -52,4 +108,13 @@ describe('resolveAttr(info, key)', () => {
expect(resolveAttr('a=\'1\'', 'a')).toEqual('1') expect(resolveAttr('a=\'1\'', 'a')).toEqual('1')
expect(resolveAttr('a', 'a')).toEqual(undefined) expect(resolveAttr('a', 'a')).toEqual(undefined)
}) })
it('should return undefined for missing key', () => {
expect(resolveAttr('', 'a')).toEqual(undefined)
expect(resolveAttr('b="1"', 'a')).toEqual(undefined)
})
it('should return first match for duplicate keys', () => {
expect(resolveAttr('a="1" a="2"', 'a')).toEqual('1')
})
}) })

View File

@ -0,0 +1,121 @@
import type { IconOptions } from '../src/shared/index.js'
import { describe, expect, it } from 'vitest'
import { resolveIcon } from '../src/node/icon/resolveIcon.js'
describe('resolveIcon', () => {
const defaultOptions: IconOptions = {
provider: 'iconify',
}
it('should resolve basic icon name', () => {
const result = resolveIcon('mdi:home', defaultOptions)
expect(result).toEqual({
provider: 'iconify',
name: 'mdi:home',
})
})
it('should resolve icon with size', () => {
const result = resolveIcon('mdi:home =24', defaultOptions)
expect(result).toEqual({
provider: 'iconify',
name: 'mdi:home',
size: '24',
})
})
it('should resolve icon with color', () => {
const result = resolveIcon('mdi:home /red', defaultOptions)
expect(result).toEqual({
provider: 'iconify',
name: 'mdi:home',
color: 'red',
})
})
it('should resolve icon with size and color', () => {
const result = resolveIcon('mdi:home =24 /blue', defaultOptions)
expect(result).toEqual({
provider: 'iconify',
name: 'mdi:home',
size: '24',
color: 'blue',
})
})
it('should resolve icon with different providers', () => {
expect(resolveIcon('iconfont icon-home', defaultOptions)).toEqual({
provider: 'iconfont',
name: 'icon-home',
})
expect(resolveIcon('fontawesome fa-home', defaultOptions)).toEqual({
provider: 'fontawesome',
name: 'fa-home',
})
expect(resolveIcon('iconify mdi:home', defaultOptions)).toEqual({
provider: 'iconify',
name: 'mdi:home',
})
})
it('should use options provider as default', () => {
const result = resolveIcon('mdi:home', { provider: 'iconfont' })
expect(result.provider).toBe('iconfont')
})
it('should use options size as default', () => {
const result = resolveIcon('mdi:home', { provider: 'iconify', size: '32' })
expect(result.size).toBe('32')
})
it('should override options size with inline size', () => {
const result = resolveIcon('mdi:home =24', { provider: 'iconify', size: '32' })
expect(result.size).toBe('24')
})
it('should use options color as default', () => {
const result = resolveIcon('mdi:home', { provider: 'iconify', color: 'red' })
expect(result.color).toBe('red')
})
it('should override options color with inline color', () => {
const result = resolveIcon('mdi:home /blue', { provider: 'iconify', color: 'red' })
expect(result.color).toBe('blue')
})
it('should resolve icon with extra attributes', () => {
const result = resolveIcon('mdi:home class="icon" id="home-icon"', defaultOptions)
expect(result.name).toBe('mdi:home')
expect(result).toHaveProperty('class', 'icon')
expect(result).toHaveProperty('id', 'home-icon')
})
it('should resolve icon with boolean extra attributes', () => {
const result = resolveIcon('mdi:home spin', defaultOptions)
expect(result.name).toBe('mdi:home')
expect(result.extra).toBe('spin')
})
it('should resolve icon with hex color', () => {
const result = resolveIcon('mdi:home /#ff0000', defaultOptions)
expect(result.color).toBe('#ff0000')
})
it('should resolve icon with rgb color', () => {
const result = resolveIcon('mdi:home /rgb(255,0,0)', defaultOptions)
expect(result.color).toBe('rgb(255,0,0)')
})
it('should handle empty content', () => {
const result = resolveIcon('', defaultOptions)
expect(result.name).toBe('')
})
it('should handle complex size values', () => {
expect(resolveIcon('mdi:home =2rem', defaultOptions).size).toBe('2rem')
expect(resolveIcon('mdi:home =1.5em', defaultOptions).size).toBe('1.5em')
expect(resolveIcon('mdi:home =100%', defaultOptions).size).toBe('100%')
})
})

View File

@ -52,4 +52,29 @@ describe('stringifyAttrs', () => {
it('should handle kebabCase keys', () => { it('should handle kebabCase keys', () => {
expect(stringifyAttrs({ 'data-foo': 'bar', 'data-baz': 1, 'fooBaz': 'bar' })).toBe(' data-foo="bar" :data-baz="1" foo-baz="bar"') expect(stringifyAttrs({ 'data-foo': 'bar', 'data-baz': 1, 'fooBaz': 'bar' })).toBe(' data-foo="bar" :data-baz="1" foo-baz="bar"')
}) })
it('should handle empty string values', () => {
expect(stringifyAttrs({ id: '' })).toBe(' id=""')
})
it('should handle special characters in values', () => {
expect(stringifyAttrs({ 'data-value': '<script>alert(1)</script>' })).toBe(' data-value="<script>alert(1)</script>"')
})
it('should handle unicode values', () => {
expect(stringifyAttrs({ title: '你好世界' })).toBe(' title="你好世界"')
expect(stringifyAttrs({ 'data-emoji': '🎉🎊' })).toBe(' data-emoji="🎉🎊"')
})
it('should handle zero values', () => {
expect(stringifyAttrs({ width: 0, height: 0 })).toBe(' :width="0" :height="0"')
})
it('should handle negative numbers', () => {
expect(stringifyAttrs({ offset: -1 })).toBe(' :offset="-1"')
})
it('should handle float numbers', () => {
expect(stringifyAttrs({ ratio: 1.5 })).toBe(' :ratio="1.5"')
})
}) })

View File

@ -0,0 +1,54 @@
import { describe, expect, it } from 'vitest'
import { stringifyProp } from '../src/node/utils/stringifyProp.js'
describe('stringifyProp', () => {
it('should stringify string', () => {
expect(stringifyProp('hello')).toBe('"hello"')
expect(stringifyProp('')).toBe('""')
})
it('should stringify number', () => {
expect(stringifyProp(123)).toBe('123')
expect(stringifyProp(0)).toBe('0')
expect(stringifyProp(-1)).toBe('-1')
expect(stringifyProp(3.14)).toBe('3.14')
})
it('should stringify boolean', () => {
expect(stringifyProp(true)).toBe('true')
expect(stringifyProp(false)).toBe('false')
})
it('should stringify null', () => {
expect(stringifyProp(null)).toBe('null')
})
it('should stringify array', () => {
expect(stringifyProp([1, 2, 3])).toBe('[1,2,3]')
expect(stringifyProp(['a', 'b'])).toBe('["a","b"]')
expect(stringifyProp([])).toBe('[]')
})
it('should stringify object', () => {
expect(stringifyProp({ a: 1 })).toBe('{"a":1}')
expect(stringifyProp({ a: 'b' })).toBe('{"a":"b"}')
expect(stringifyProp({})).toBe('{}')
})
it('should escape single quotes', () => {
expect(stringifyProp('it\'s')).toBe('"it&#39s"')
expect(stringifyProp('\'hello\'')).toBe('"&#39hello&#39"')
expect(stringifyProp('a\'b\'c')).toBe('"a&#39b&#39c"')
})
it('should handle nested objects', () => {
expect(stringifyProp({ a: { b: 1 } })).toBe('{"a":{"b":1}}')
expect(stringifyProp({ arr: [1, 2] })).toBe('{"arr":[1,2]}')
})
it('should handle special characters', () => {
expect(stringifyProp('hello\nworld')).toBe('"hello\\nworld"')
expect(stringifyProp('hello\tworld')).toBe('"hello\\tworld"')
expect(stringifyProp('hello"world')).toBe('"hello\\"world"')
})
})

View File

@ -23,4 +23,35 @@ describe('timeToSeconds(timeLike)', () => {
expect(timeToSeconds('a:b')).toBe(0) expect(timeToSeconds('a:b')).toBe(0)
expect(timeToSeconds('a : b : c')).toBe(0) expect(timeToSeconds('a : b : c')).toBe(0)
}) })
it('should handle zero values', () => {
expect(timeToSeconds('0')).toBe(0)
expect(timeToSeconds('0:0')).toBe(0)
expect(timeToSeconds('0:0:0')).toBe(0)
})
it('should handle large values', () => {
expect(timeToSeconds('100:00:00')).toBe(360000)
expect(timeToSeconds('99:59:59')).toBe(359999)
})
it('should handle decimal seconds', () => {
expect(timeToSeconds('1.5')).toBe(1.5)
expect(timeToSeconds('1:30.5')).toBe(90.5)
expect(timeToSeconds('1:1:1.999')).toBe(3661.999)
})
it('should handle leading zeros', () => {
expect(timeToSeconds('01:02:03')).toBe(3723)
expect(timeToSeconds('001:002:003')).toBe(3723)
})
it('should handle trailing zeros', () => {
expect(timeToSeconds('10:20:30')).toBe(37230)
})
it('should handle whitespace', () => {
expect(timeToSeconds(' 1:2:3 ')).toBe(3723)
expect(timeToSeconds('1 : 2 : 3')).toBe(3723)
})
}) })

View File

@ -127,6 +127,7 @@ export const abbrPlugin: PluginWithOptions<Record<string, string>> = (md, global
if (labelEnd < 0 || state.src.charAt(labelEnd + 1) !== ':') if (labelEnd < 0 || state.src.charAt(labelEnd + 1) !== ':')
return false return false
/* istanbul ignore if -- @preserve */
if (silent) if (silent)
return true return true
@ -219,6 +220,7 @@ export const abbrPlugin: PluginWithOptions<Record<string, string>> = (md, global
pos = regExp.lastIndex pos = regExp.lastIndex
} }
/* istanbul ignore if -- @preserve */
if (!nodes.length) if (!nodes.length)
continue continue

View File

@ -142,7 +142,7 @@ const annotationDef: RuleBlock = (
) { ) {
return false return false
} }
/* istanbul ignore if -- @preserve */
if (silent) if (silent)
return true return true
@ -228,6 +228,7 @@ const annotationRef: RuleInline = (
if (annotations.length === 0) if (annotations.length === 0)
return false return false
/* istanbul ignore if -- @preserve */
if (!silent) { if (!silent) {
const refToken = state.push('annotation_ref', '', 0) const refToken = state.push('annotation_ref', '', 0)
@ -272,6 +273,7 @@ export const annotationPlugin: PluginWithOptions<Record<string, string | string[
env: AnnotationEnv, env: AnnotationEnv,
) => { ) => {
const label = tokens[idx].meta.label const label = tokens[idx].meta.label
/* istanbul ignore next -- @preserve */
const data = env.annotations[`:${label}`] || annotations[`:${label}`] const data = env.annotations[`:${label}`] || annotations[`:${label}`]
return `<Annotation label="${label}" :total="${data.sources.length}">${ return `<Annotation label="${label}" :total="${data.sources.length}">${

View File

@ -12,6 +12,14 @@ export default defineConfig({
enabled: true, enabled: true,
provider: 'v8', provider: 'v8',
reporter: ['text', 'clover', 'json'], reporter: ['text', 'clover', 'json'],
exclude: [
'**/node_modules/**',
'**/dist/**',
'**/lib/**',
'**/demo/**',
'**/demo/supports/**',
'**/fileIcons/index.ts',
],
}, },
}, },
}) })