diff --git a/plugins/plugin-md-power/__test__/abbrPlugin.spec.ts b/plugins/plugin-md-power/__test__/abbrPlugin.spec.ts new file mode 100644 index 00000000..eb5d4bf1 --- /dev/null +++ b/plugins/plugin-md-power/__test__/abbrPlugin.spec.ts @@ -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) { + 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') + }) +}) diff --git a/plugins/plugin-md-power/__test__/annotationPlugin.spec.ts b/plugins/plugin-md-power/__test__/annotationPlugin.spec.ts new file mode 100644 index 00000000..cfa42f62 --- /dev/null +++ b/plugins/plugin-md-power/__test__/annotationPlugin.spec.ts @@ -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) { + 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]') + }) +}) diff --git a/plugins/plugin-md-power/__test__/cleanMarkdownEnv.spec.ts b/plugins/plugin-md-power/__test__/cleanMarkdownEnv.spec.ts index 7ccfed08..51c3b1af 100644 --- a/plugins/plugin-md-power/__test__/cleanMarkdownEnv.spec.ts +++ b/plugins/plugin-md-power/__test__/cleanMarkdownEnv.spec.ts @@ -34,4 +34,57 @@ describe('cleanMarkdownEnv', () => { 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) + }) }) diff --git a/plugins/plugin-md-power/__test__/demoNormal.spec.ts b/plugins/plugin-md-power/__test__/demoNormal.spec.ts new file mode 100644 index 00000000..309388da --- /dev/null +++ b/plugins/plugin-md-power/__test__/demoNormal.spec.ts @@ -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 = '
Hello World
' + const result = parseEmbedCode(code) + + expect(result.html).toBe('
Hello World
') + 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 = `
Hello
+` + const result = parseEmbedCode(code) + + expect(result.html?.trim()).toBe('
Hello
') + expect(result.script?.trim()).toBe('const message = \'Hello World\'') + expect(result.jsType).toBe('js') + }) + + it('should parse HTML with TypeScript script', () => { + const code = `
Hello
+` + 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 = `
Hello
+` + const result = parseEmbedCode(code) + + expect(result.html?.trim()).toBe('
Hello
') + expect(result.css?.trim()).toBe('.container { color: red; }') + expect(result.cssType).toBe('css') + }) + + it('should parse HTML with SCSS style', () => { + const code = `
Hello
+` + const result = parseEmbedCode(code) + + expect(result.cssType).toBe('scss') + }) + + it('should parse HTML with Less style', () => { + const code = `
Hello
+` + const result = parseEmbedCode(code) + + expect(result.cssType).toBe('less') + }) + + it('should parse HTML with Stylus style', () => { + const code = `
Hello
+` + const result = parseEmbedCode(code) + + expect(result.cssType).toBe('stylus') + }) + + it('should parse HTML with Stylus style (styl extension)', () => { + const code = `
Hello
+` + const result = parseEmbedCode(code) + + expect(result.cssType).toBe('stylus') + }) + + it('should parse HTML with config', () => { + const code = `
Hello
+` + 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 = `
{{ message }}
+ + +` + const result = parseEmbedCode(code) + + expect(result.html?.trim()).toBe('
{{ message }}
') + 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 = `` + const result = parseEmbedCode(code) + + expect(result.jsType).toBe('js') + }) + + it('should handle unknown style lang as css', () => { + const code = `` + const result = parseEmbedCode(code) + + expect(result.cssType).toBe('css') + }) +}) diff --git a/plugins/plugin-md-power/__test__/encryptContent.spec.ts b/plugins/plugin-md-power/__test__/encryptContent.spec.ts new file mode 100644 index 00000000..ecff45b1 --- /dev/null +++ b/plugins/plugin-md-power/__test__/encryptContent.spec.ts @@ -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 = '\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) + }) +}) diff --git a/plugins/plugin-md-power/__test__/findIcon.spec.ts b/plugins/plugin-md-power/__test__/findIcon.spec.ts index 3053b7a9..d3334df9 100644 --- a/plugins/plugin-md-power/__test__/findIcon.spec.ts +++ b/plugins/plugin-md-power/__test__/findIcon.spec.ts @@ -45,4 +45,39 @@ describe('getFileIcon(filename, type)', () => { expect(getFileIcon('abc.', 'folder')).toBe('vscode-icons:default-folder') 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() + }) }) diff --git a/plugins/plugin-md-power/__test__/findLocales.spec.ts b/plugins/plugin-md-power/__test__/findLocales.spec.ts new file mode 100644 index 00000000..2bebb391 --- /dev/null +++ b/plugins/plugin-md-power/__test__/findLocales.spec.ts @@ -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(locales, 'hint')).toEqual({ + '/': 'Hint', + '/zh/': '提示', + }) + }) + + it('should return empty object for missing key', () => { + const locales = { + '/': { hint: 'Hint' }, + '/zh/': { hint: '提示' }, + } + + expect(findLocales(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(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(locales, 'hint')).toEqual({ + '/': 'Root', + '/zh/': 'Chinese', + '/zh-tw/': 'Traditional Chinese', + '/en-US/': 'US English', + }) + }) +}) diff --git a/plugins/plugin-md-power/__test__/nanoid.spec.ts b/plugins/plugin-md-power/__test__/nanoid.spec.ts new file mode 100644 index 00000000..0a06927b --- /dev/null +++ b/plugins/plugin-md-power/__test__/nanoid.spec.ts @@ -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() + 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]+$/) + }) +}) diff --git a/plugins/plugin-md-power/__test__/package.spec.ts b/plugins/plugin-md-power/__test__/package.spec.ts new file mode 100644 index 00000000..4fa1c942 --- /dev/null +++ b/plugins/plugin-md-power/__test__/package.spec.ts @@ -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 }) + }) +}) diff --git a/plugins/plugin-md-power/__test__/parseRect.spec.ts b/plugins/plugin-md-power/__test__/parseRect.spec.ts index 5399005a..6ef75e06 100644 --- a/plugins/plugin-md-power/__test__/parseRect.spec.ts +++ b/plugins/plugin-md-power/__test__/parseRect.spec.ts @@ -17,4 +17,45 @@ describe('parseRect(str)', () => { expect(parseRect('1%', 'px')).toBe('1%') 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)') + }) }) diff --git a/plugins/plugin-md-power/__test__/resolveAttrs.spec.ts b/plugins/plugin-md-power/__test__/resolveAttrs.spec.ts index b51909b8..0bd192aa 100644 --- a/plugins/plugin-md-power/__test__/resolveAttrs.spec.ts +++ b/plugins/plugin-md-power/__test__/resolveAttrs.spec.ts @@ -42,6 +42,62 @@ describe('resolveAttrs(info)', () => { 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="' })).toBe(' data-value=""') + }) + + 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"') + }) }) diff --git a/plugins/plugin-md-power/__test__/stringifyProp.spec.ts b/plugins/plugin-md-power/__test__/stringifyProp.spec.ts new file mode 100644 index 00000000..1465b105 --- /dev/null +++ b/plugins/plugin-md-power/__test__/stringifyProp.spec.ts @@ -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's"') + expect(stringifyProp('\'hello\'')).toBe('"'hello'"') + expect(stringifyProp('a\'b\'c')).toBe('"a'b'c"') + }) + + 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"') + }) +}) diff --git a/plugins/plugin-md-power/__test__/timeToSeconds.spec.ts b/plugins/plugin-md-power/__test__/timeToSeconds.spec.ts index de06c58d..f21b1fd7 100644 --- a/plugins/plugin-md-power/__test__/timeToSeconds.spec.ts +++ b/plugins/plugin-md-power/__test__/timeToSeconds.spec.ts @@ -23,4 +23,35 @@ describe('timeToSeconds(timeLike)', () => { expect(timeToSeconds('a:b')).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) + }) }) diff --git a/plugins/plugin-md-power/src/node/inline/abbr.ts b/plugins/plugin-md-power/src/node/inline/abbr.ts index 021982e5..3e2aa6b7 100644 --- a/plugins/plugin-md-power/src/node/inline/abbr.ts +++ b/plugins/plugin-md-power/src/node/inline/abbr.ts @@ -127,6 +127,7 @@ export const abbrPlugin: PluginWithOptions> = (md, global if (labelEnd < 0 || state.src.charAt(labelEnd + 1) !== ':') return false + /* istanbul ignore if -- @preserve */ if (silent) return true @@ -219,6 +220,7 @@ export const abbrPlugin: PluginWithOptions> = (md, global pos = regExp.lastIndex } + /* istanbul ignore if -- @preserve */ if (!nodes.length) continue diff --git a/plugins/plugin-md-power/src/node/inline/annotation.ts b/plugins/plugin-md-power/src/node/inline/annotation.ts index 8758c801..736f5802 100644 --- a/plugins/plugin-md-power/src/node/inline/annotation.ts +++ b/plugins/plugin-md-power/src/node/inline/annotation.ts @@ -142,7 +142,7 @@ const annotationDef: RuleBlock = ( ) { return false } - + /* istanbul ignore if -- @preserve */ if (silent) return true @@ -228,6 +228,7 @@ const annotationRef: RuleInline = ( if (annotations.length === 0) return false + /* istanbul ignore if -- @preserve */ if (!silent) { const refToken = state.push('annotation_ref', '', 0) @@ -272,6 +273,7 @@ export const annotationPlugin: PluginWithOptions { const label = tokens[idx].meta.label + /* istanbul ignore next -- @preserve */ const data = env.annotations[`:${label}`] || annotations[`:${label}`] return `${ diff --git a/vitest.config.ts b/vitest.config.ts index 2dfdf7cb..27df0254 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -12,6 +12,14 @@ export default defineConfig({ enabled: true, provider: 'v8', reporter: ['text', 'clover', 'json'], + exclude: [ + '**/node_modules/**', + '**/dist/**', + '**/lib/**', + '**/demo/**', + '**/demo/supports/**', + '**/fileIcons/index.ts', + ], }, }, })