@yivan-lab/pretty-please 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -4
- package/bin/pls.tsx +153 -35
- package/dist/bin/pls.js +126 -23
- package/dist/package.json +10 -2
- package/dist/src/__integration__/command-generation.test.d.ts +5 -0
- package/dist/src/__integration__/command-generation.test.js +508 -0
- package/dist/src/__integration__/error-recovery.test.d.ts +5 -0
- package/dist/src/__integration__/error-recovery.test.js +511 -0
- package/dist/src/__integration__/shell-hook-workflow.test.d.ts +5 -0
- package/dist/src/__integration__/shell-hook-workflow.test.js +375 -0
- package/dist/src/__tests__/alias.test.d.ts +5 -0
- package/dist/src/__tests__/alias.test.js +421 -0
- package/dist/src/__tests__/chat-history.test.d.ts +5 -0
- package/dist/src/__tests__/chat-history.test.js +372 -0
- package/dist/src/__tests__/config.test.d.ts +5 -0
- package/dist/src/__tests__/config.test.js +822 -0
- package/dist/src/__tests__/history.test.d.ts +5 -0
- package/dist/src/__tests__/history.test.js +439 -0
- package/dist/src/__tests__/remote-history.test.d.ts +5 -0
- package/dist/src/__tests__/remote-history.test.js +641 -0
- package/dist/src/__tests__/remote.test.d.ts +5 -0
- package/dist/src/__tests__/remote.test.js +689 -0
- package/dist/src/__tests__/shell-hook-install.test.d.ts +5 -0
- package/dist/src/__tests__/shell-hook-install.test.js +413 -0
- package/dist/src/__tests__/shell-hook-remote.test.d.ts +5 -0
- package/dist/src/__tests__/shell-hook-remote.test.js +507 -0
- package/dist/src/__tests__/shell-hook.test.d.ts +5 -0
- package/dist/src/__tests__/shell-hook.test.js +440 -0
- package/dist/src/__tests__/sysinfo.test.d.ts +5 -0
- package/dist/src/__tests__/sysinfo.test.js +572 -0
- package/dist/src/__tests__/system-history.test.d.ts +5 -0
- package/dist/src/__tests__/system-history.test.js +457 -0
- package/dist/src/components/Chat.js +9 -28
- package/dist/src/config.d.ts +2 -0
- package/dist/src/config.js +30 -2
- package/dist/src/mastra-chat.js +6 -3
- package/dist/src/multi-step.js +6 -3
- package/dist/src/project-context.d.ts +22 -0
- package/dist/src/project-context.js +168 -0
- package/dist/src/prompts.d.ts +4 -4
- package/dist/src/prompts.js +23 -6
- package/dist/src/shell-hook.d.ts +13 -0
- package/dist/src/shell-hook.js +163 -33
- package/dist/src/sysinfo.d.ts +38 -9
- package/dist/src/sysinfo.js +245 -21
- package/dist/src/system-history.d.ts +5 -0
- package/dist/src/system-history.js +64 -18
- package/dist/src/ui/__tests__/theme.test.d.ts +5 -0
- package/dist/src/ui/__tests__/theme.test.js +688 -0
- package/dist/src/upgrade.js +3 -0
- package/dist/src/user-preferences.d.ts +44 -0
- package/dist/src/user-preferences.js +147 -0
- package/dist/src/utils/__tests__/platform-capabilities.test.d.ts +5 -0
- package/dist/src/utils/__tests__/platform-capabilities.test.js +214 -0
- package/dist/src/utils/__tests__/platform-exec.test.d.ts +5 -0
- package/dist/src/utils/__tests__/platform-exec.test.js +212 -0
- package/dist/src/utils/__tests__/platform-shell.test.d.ts +5 -0
- package/dist/src/utils/__tests__/platform-shell.test.js +300 -0
- package/dist/src/utils/__tests__/platform.test.d.ts +5 -0
- package/dist/src/utils/__tests__/platform.test.js +137 -0
- package/dist/src/utils/platform.d.ts +88 -0
- package/dist/src/utils/platform.js +331 -0
- package/package.json +10 -2
- package/src/__integration__/command-generation.test.ts +602 -0
- package/src/__integration__/error-recovery.test.ts +620 -0
- package/src/__integration__/shell-hook-workflow.test.ts +457 -0
- package/src/__tests__/alias.test.ts +545 -0
- package/src/__tests__/chat-history.test.ts +462 -0
- package/src/__tests__/config.test.ts +1043 -0
- package/src/__tests__/history.test.ts +538 -0
- package/src/__tests__/remote-history.test.ts +791 -0
- package/src/__tests__/remote.test.ts +866 -0
- package/src/__tests__/shell-hook-install.test.ts +510 -0
- package/src/__tests__/shell-hook-remote.test.ts +679 -0
- package/src/__tests__/shell-hook.test.ts +564 -0
- package/src/__tests__/sysinfo.test.ts +718 -0
- package/src/__tests__/system-history.test.ts +608 -0
- package/src/components/Chat.tsx +10 -37
- package/src/config.ts +29 -2
- package/src/mastra-chat.ts +8 -3
- package/src/multi-step.ts +7 -2
- package/src/project-context.ts +191 -0
- package/src/prompts.ts +26 -5
- package/src/shell-hook.ts +179 -33
- package/src/sysinfo.ts +326 -25
- package/src/system-history.ts +67 -14
- package/src/ui/__tests__/theme.test.ts +869 -0
- package/src/upgrade.ts +5 -0
- package/src/user-preferences.ts +178 -0
- package/src/utils/__tests__/platform-capabilities.test.ts +265 -0
- package/src/utils/__tests__/platform-exec.test.ts +278 -0
- package/src/utils/__tests__/platform-shell.test.ts +353 -0
- package/src/utils/__tests__/platform.test.ts +170 -0
- package/src/utils/platform.ts +431 -0
|
@@ -0,0 +1,869 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 主题系统测试
|
|
3
|
+
* 测试主题读取、验证、自定义主题加载等功能
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
|
7
|
+
|
|
8
|
+
// Mock fs 模块
|
|
9
|
+
vi.mock('fs', () => ({
|
|
10
|
+
default: {
|
|
11
|
+
existsSync: vi.fn(),
|
|
12
|
+
readFileSync: vi.fn(),
|
|
13
|
+
readdirSync: vi.fn(),
|
|
14
|
+
writeFileSync: vi.fn(),
|
|
15
|
+
mkdirSync: vi.fn(),
|
|
16
|
+
},
|
|
17
|
+
}))
|
|
18
|
+
|
|
19
|
+
// Mock os 模块
|
|
20
|
+
vi.mock('os', () => ({
|
|
21
|
+
default: {
|
|
22
|
+
homedir: vi.fn(() => '/home/testuser'),
|
|
23
|
+
userInfo: vi.fn(() => ({ username: 'testuser' })),
|
|
24
|
+
},
|
|
25
|
+
}))
|
|
26
|
+
|
|
27
|
+
import fs from 'fs'
|
|
28
|
+
import os from 'os'
|
|
29
|
+
|
|
30
|
+
const mockFs = vi.mocked(fs)
|
|
31
|
+
const mockOs = vi.mocked(os)
|
|
32
|
+
|
|
33
|
+
// 模块重置辅助函数
|
|
34
|
+
async function resetThemeModule() {
|
|
35
|
+
vi.resetModules()
|
|
36
|
+
return await import('../theme.js')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 有效的主题定义
|
|
40
|
+
const validThemeDefinition = {
|
|
41
|
+
metadata: {
|
|
42
|
+
name: 'custom-theme',
|
|
43
|
+
displayName: '自定义主题',
|
|
44
|
+
description: '测试用自定义主题',
|
|
45
|
+
category: 'dark' as const,
|
|
46
|
+
previewColor: '#FF0000',
|
|
47
|
+
author: 'testuser',
|
|
48
|
+
},
|
|
49
|
+
colors: {
|
|
50
|
+
primary: '#FF0000',
|
|
51
|
+
secondary: '#00FF00',
|
|
52
|
+
accent: '#0000FF',
|
|
53
|
+
success: '#00FF00',
|
|
54
|
+
error: '#FF0000',
|
|
55
|
+
warning: '#FFFF00',
|
|
56
|
+
info: '#00FFFF',
|
|
57
|
+
text: {
|
|
58
|
+
primary: '#FFFFFF',
|
|
59
|
+
secondary: '#CCCCCC',
|
|
60
|
+
muted: '#999999',
|
|
61
|
+
dim: '#666666',
|
|
62
|
+
},
|
|
63
|
+
border: '#333333',
|
|
64
|
+
divider: '#222222',
|
|
65
|
+
code: {
|
|
66
|
+
background: '#111111',
|
|
67
|
+
text: '#FFFFFF',
|
|
68
|
+
keyword: '#FF00FF',
|
|
69
|
+
string: '#00FF00',
|
|
70
|
+
function: '#00FFFF',
|
|
71
|
+
comment: '#666666',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 有效的配置
|
|
77
|
+
const validConfig = {
|
|
78
|
+
theme: 'dark',
|
|
79
|
+
apiKey: 'test-key',
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
vi.clearAllMocks()
|
|
84
|
+
mockOs.homedir.mockReturnValue('/home/testuser')
|
|
85
|
+
mockOs.userInfo.mockReturnValue({ username: 'testuser' } as any)
|
|
86
|
+
mockFs.existsSync.mockReturnValue(false)
|
|
87
|
+
mockFs.readFileSync.mockReturnValue('{}')
|
|
88
|
+
mockFs.readdirSync.mockReturnValue([])
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
afterEach(() => {
|
|
92
|
+
vi.restoreAllMocks()
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// themeDefinitions 和 themes 测试
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
describe('themeDefinitions', () => {
|
|
100
|
+
it('应该包含所有内置主题', async () => {
|
|
101
|
+
const { themeDefinitions } = await resetThemeModule()
|
|
102
|
+
|
|
103
|
+
expect(themeDefinitions).toHaveProperty('dark')
|
|
104
|
+
expect(themeDefinitions).toHaveProperty('light')
|
|
105
|
+
expect(themeDefinitions).toHaveProperty('nord')
|
|
106
|
+
expect(themeDefinitions).toHaveProperty('dracula')
|
|
107
|
+
expect(themeDefinitions).toHaveProperty('retro')
|
|
108
|
+
expect(themeDefinitions).toHaveProperty('contrast')
|
|
109
|
+
expect(themeDefinitions).toHaveProperty('monokai')
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('每个内置主题应该有完整的 metadata', async () => {
|
|
113
|
+
const { themeDefinitions } = await resetThemeModule()
|
|
114
|
+
|
|
115
|
+
for (const [name, def] of Object.entries(themeDefinitions)) {
|
|
116
|
+
expect(def.metadata.name).toBe(name)
|
|
117
|
+
expect(def.metadata.displayName).toBeTruthy()
|
|
118
|
+
expect(def.metadata.description).toBeTruthy()
|
|
119
|
+
expect(['dark', 'light']).toContain(def.metadata.category)
|
|
120
|
+
expect(def.metadata.previewColor).toMatch(/^#[0-9A-Fa-f]{6}$/)
|
|
121
|
+
expect(def.metadata.author).toBe('built-in')
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('每个内置主题应该有完整的 colors', async () => {
|
|
126
|
+
const { themeDefinitions } = await resetThemeModule()
|
|
127
|
+
|
|
128
|
+
for (const def of Object.values(themeDefinitions)) {
|
|
129
|
+
expect(def.colors.primary).toMatch(/^#[0-9A-Fa-f]{6}$/)
|
|
130
|
+
expect(def.colors.secondary).toMatch(/^#[0-9A-Fa-f]{6}$/)
|
|
131
|
+
expect(def.colors.accent).toMatch(/^#[0-9A-Fa-f]{6}$/)
|
|
132
|
+
expect(def.colors.success).toMatch(/^#[0-9A-Fa-f]{6}$/)
|
|
133
|
+
expect(def.colors.error).toMatch(/^#[0-9A-Fa-f]{6}$/)
|
|
134
|
+
expect(def.colors.warning).toMatch(/^#[0-9A-Fa-f]{6}$/)
|
|
135
|
+
expect(def.colors.info).toMatch(/^#[0-9A-Fa-f]{6}$/)
|
|
136
|
+
expect(def.colors.text.primary).toMatch(/^#[0-9A-Fa-f]{6}$/)
|
|
137
|
+
expect(def.colors.text.secondary).toMatch(/^#[0-9A-Fa-f]{6}$/)
|
|
138
|
+
expect(def.colors.text.muted).toMatch(/^#[0-9A-Fa-f]{6}$/)
|
|
139
|
+
expect(def.colors.text.dim).toMatch(/^#[0-9A-Fa-f]{6}$/)
|
|
140
|
+
expect(def.colors.border).toMatch(/^#[0-9A-Fa-f]{6}$/)
|
|
141
|
+
expect(def.colors.divider).toMatch(/^#[0-9A-Fa-f]{6}$/)
|
|
142
|
+
expect(def.colors.code.background).toMatch(/^#[0-9A-Fa-f]{6}$/)
|
|
143
|
+
expect(def.colors.code.text).toMatch(/^#[0-9A-Fa-f]{6}$/)
|
|
144
|
+
expect(def.colors.code.keyword).toMatch(/^#[0-9A-Fa-f]{6}$/)
|
|
145
|
+
expect(def.colors.code.string).toMatch(/^#[0-9A-Fa-f]{6}$/)
|
|
146
|
+
expect(def.colors.code.function).toMatch(/^#[0-9A-Fa-f]{6}$/)
|
|
147
|
+
expect(def.colors.code.comment).toMatch(/^#[0-9A-Fa-f]{6}$/)
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
describe('themes', () => {
|
|
153
|
+
it('应该是 themeDefinitions 的颜色映射', async () => {
|
|
154
|
+
const { themes, themeDefinitions } = await resetThemeModule()
|
|
155
|
+
|
|
156
|
+
expect(Object.keys(themes)).toEqual(Object.keys(themeDefinitions))
|
|
157
|
+
|
|
158
|
+
for (const [name, colors] of Object.entries(themes)) {
|
|
159
|
+
expect(colors).toEqual(themeDefinitions[name].colors)
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
// ============================================================================
|
|
165
|
+
// getCurrentTheme 测试
|
|
166
|
+
// ============================================================================
|
|
167
|
+
|
|
168
|
+
describe('getCurrentTheme', () => {
|
|
169
|
+
it('配置文件不存在时应该返回 dark 主题', async () => {
|
|
170
|
+
mockFs.existsSync.mockReturnValue(false)
|
|
171
|
+
|
|
172
|
+
const { getCurrentTheme, themeDefinitions } = await resetThemeModule()
|
|
173
|
+
const theme = getCurrentTheme()
|
|
174
|
+
|
|
175
|
+
expect(theme).toEqual(themeDefinitions.dark.colors)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it('配置为 dark 应该返回 dark 主题', async () => {
|
|
179
|
+
mockFs.existsSync.mockReturnValue(true)
|
|
180
|
+
mockFs.readFileSync.mockReturnValue(JSON.stringify({ theme: 'dark' }))
|
|
181
|
+
|
|
182
|
+
const { getCurrentTheme, themeDefinitions } = await resetThemeModule()
|
|
183
|
+
const theme = getCurrentTheme()
|
|
184
|
+
|
|
185
|
+
expect(theme).toEqual(themeDefinitions.dark.colors)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it('配置为 light 应该返回 light 主题', async () => {
|
|
189
|
+
mockFs.existsSync.mockReturnValue(true)
|
|
190
|
+
mockFs.readFileSync.mockReturnValue(JSON.stringify({ theme: 'light' }))
|
|
191
|
+
|
|
192
|
+
const { getCurrentTheme, themeDefinitions } = await resetThemeModule()
|
|
193
|
+
const theme = getCurrentTheme()
|
|
194
|
+
|
|
195
|
+
expect(theme).toEqual(themeDefinitions.light.colors)
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('配置为 nord 应该返回 nord 主题', async () => {
|
|
199
|
+
mockFs.existsSync.mockReturnValue(true)
|
|
200
|
+
mockFs.readFileSync.mockReturnValue(JSON.stringify({ theme: 'nord' }))
|
|
201
|
+
|
|
202
|
+
const { getCurrentTheme, themeDefinitions } = await resetThemeModule()
|
|
203
|
+
const theme = getCurrentTheme()
|
|
204
|
+
|
|
205
|
+
expect(theme).toEqual(themeDefinitions.nord.colors)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('配置为 dracula 应该返回 dracula 主题', async () => {
|
|
209
|
+
mockFs.existsSync.mockReturnValue(true)
|
|
210
|
+
mockFs.readFileSync.mockReturnValue(JSON.stringify({ theme: 'dracula' }))
|
|
211
|
+
|
|
212
|
+
const { getCurrentTheme, themeDefinitions } = await resetThemeModule()
|
|
213
|
+
const theme = getCurrentTheme()
|
|
214
|
+
|
|
215
|
+
expect(theme).toEqual(themeDefinitions.dracula.colors)
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it('配置 JSON 损坏应该返回 dark 主题', async () => {
|
|
219
|
+
mockFs.existsSync.mockReturnValue(true)
|
|
220
|
+
mockFs.readFileSync.mockReturnValue('{invalid json')
|
|
221
|
+
|
|
222
|
+
const { getCurrentTheme, themeDefinitions } = await resetThemeModule()
|
|
223
|
+
const theme = getCurrentTheme()
|
|
224
|
+
|
|
225
|
+
expect(theme).toEqual(themeDefinitions.dark.colors)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('配置没有 theme 字段应该返回 dark 主题', async () => {
|
|
229
|
+
mockFs.existsSync.mockReturnValue(true)
|
|
230
|
+
mockFs.readFileSync.mockReturnValue(JSON.stringify({ apiKey: 'test' }))
|
|
231
|
+
|
|
232
|
+
const { getCurrentTheme, themeDefinitions } = await resetThemeModule()
|
|
233
|
+
const theme = getCurrentTheme()
|
|
234
|
+
|
|
235
|
+
expect(theme).toEqual(themeDefinitions.dark.colors)
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('应该支持自定义主题', async () => {
|
|
239
|
+
mockFs.existsSync.mockImplementation((path: any) => {
|
|
240
|
+
if (path.includes('config.json')) return true
|
|
241
|
+
if (path.includes('custom-theme.json')) return true
|
|
242
|
+
return false
|
|
243
|
+
})
|
|
244
|
+
mockFs.readFileSync.mockImplementation((path: any) => {
|
|
245
|
+
if (path.toString().includes('config.json')) {
|
|
246
|
+
return JSON.stringify({ theme: 'custom-theme' })
|
|
247
|
+
}
|
|
248
|
+
if (path.toString().includes('custom-theme.json')) {
|
|
249
|
+
return JSON.stringify(validThemeDefinition)
|
|
250
|
+
}
|
|
251
|
+
return '{}'
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
const { getCurrentTheme } = await resetThemeModule()
|
|
255
|
+
const theme = getCurrentTheme()
|
|
256
|
+
|
|
257
|
+
expect(theme.primary).toBe('#FF0000')
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
it('自定义主题不存在应该回退到 dark', async () => {
|
|
261
|
+
// 模拟 console.warn
|
|
262
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
263
|
+
|
|
264
|
+
mockFs.existsSync.mockImplementation((path: any) => {
|
|
265
|
+
if (path.includes('config.json')) return true
|
|
266
|
+
return false
|
|
267
|
+
})
|
|
268
|
+
mockFs.readFileSync.mockReturnValue(JSON.stringify({ theme: 'nonexistent-theme' }))
|
|
269
|
+
|
|
270
|
+
const { getCurrentTheme, themeDefinitions } = await resetThemeModule()
|
|
271
|
+
const theme = getCurrentTheme()
|
|
272
|
+
|
|
273
|
+
expect(theme).toEqual(themeDefinitions.dark.colors)
|
|
274
|
+
expect(warnSpy).toHaveBeenCalled()
|
|
275
|
+
|
|
276
|
+
warnSpy.mockRestore()
|
|
277
|
+
})
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
// ============================================================================
|
|
281
|
+
// getThemeMetadata 测试
|
|
282
|
+
// ============================================================================
|
|
283
|
+
|
|
284
|
+
describe('getThemeMetadata', () => {
|
|
285
|
+
it('应该返回内置主题的元数据', async () => {
|
|
286
|
+
const { getThemeMetadata } = await resetThemeModule()
|
|
287
|
+
|
|
288
|
+
const darkMeta = getThemeMetadata('dark')
|
|
289
|
+
expect(darkMeta?.name).toBe('dark')
|
|
290
|
+
expect(darkMeta?.displayName).toBe('深色')
|
|
291
|
+
expect(darkMeta?.category).toBe('dark')
|
|
292
|
+
|
|
293
|
+
const lightMeta = getThemeMetadata('light')
|
|
294
|
+
expect(lightMeta?.name).toBe('light')
|
|
295
|
+
expect(lightMeta?.displayName).toBe('浅色')
|
|
296
|
+
expect(lightMeta?.category).toBe('light')
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
it('不存在的主题应该返回 undefined', async () => {
|
|
300
|
+
mockFs.existsSync.mockReturnValue(false)
|
|
301
|
+
|
|
302
|
+
const { getThemeMetadata } = await resetThemeModule()
|
|
303
|
+
const meta = getThemeMetadata('nonexistent')
|
|
304
|
+
|
|
305
|
+
expect(meta).toBeUndefined()
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
it('应该支持自定义主题', async () => {
|
|
309
|
+
mockFs.existsSync.mockReturnValue(true)
|
|
310
|
+
mockFs.readFileSync.mockReturnValue(JSON.stringify(validThemeDefinition))
|
|
311
|
+
|
|
312
|
+
const { getThemeMetadata } = await resetThemeModule()
|
|
313
|
+
const meta = getThemeMetadata('custom-theme')
|
|
314
|
+
|
|
315
|
+
expect(meta?.name).toBe('custom-theme')
|
|
316
|
+
expect(meta?.displayName).toBe('自定义主题')
|
|
317
|
+
expect(meta?.author).toBe('testuser')
|
|
318
|
+
})
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
// ============================================================================
|
|
322
|
+
// getAllThemeMetadata 测试
|
|
323
|
+
// ============================================================================
|
|
324
|
+
|
|
325
|
+
describe('getAllThemeMetadata', () => {
|
|
326
|
+
it('应该返回所有内置主题的元数据', async () => {
|
|
327
|
+
mockFs.existsSync.mockReturnValue(false)
|
|
328
|
+
|
|
329
|
+
const { getAllThemeMetadata } = await resetThemeModule()
|
|
330
|
+
const allMeta = getAllThemeMetadata()
|
|
331
|
+
|
|
332
|
+
expect(allMeta.length).toBeGreaterThanOrEqual(7) // 7 个内置主题
|
|
333
|
+
expect(allMeta.find((m) => m.name === 'dark')).toBeDefined()
|
|
334
|
+
expect(allMeta.find((m) => m.name === 'light')).toBeDefined()
|
|
335
|
+
expect(allMeta.find((m) => m.name === 'nord')).toBeDefined()
|
|
336
|
+
expect(allMeta.find((m) => m.name === 'dracula')).toBeDefined()
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
it('应该包含自定义主题', async () => {
|
|
340
|
+
mockFs.existsSync.mockReturnValue(true)
|
|
341
|
+
mockFs.readdirSync.mockReturnValue(['custom-theme.json'] as any)
|
|
342
|
+
mockFs.readFileSync.mockReturnValue(JSON.stringify(validThemeDefinition))
|
|
343
|
+
|
|
344
|
+
const { getAllThemeMetadata } = await resetThemeModule()
|
|
345
|
+
const allMeta = getAllThemeMetadata()
|
|
346
|
+
|
|
347
|
+
expect(allMeta.find((m) => m.name === 'custom-theme')).toBeDefined()
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
it('主题目录不存在时应该只返回内置主题', async () => {
|
|
351
|
+
mockFs.existsSync.mockReturnValue(false)
|
|
352
|
+
mockFs.readdirSync.mockImplementation(() => {
|
|
353
|
+
throw new Error('ENOENT')
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
const { getAllThemeMetadata } = await resetThemeModule()
|
|
357
|
+
const allMeta = getAllThemeMetadata()
|
|
358
|
+
|
|
359
|
+
expect(allMeta.length).toBe(7) // 只有内置主题
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
it('应该跳过无效的自定义主题文件', async () => {
|
|
363
|
+
mockFs.existsSync.mockReturnValue(true)
|
|
364
|
+
mockFs.readdirSync.mockReturnValue(['invalid.json', 'valid.json'] as any)
|
|
365
|
+
mockFs.readFileSync.mockImplementation((path: any) => {
|
|
366
|
+
if (path.toString().includes('invalid.json')) {
|
|
367
|
+
return '{invalid json'
|
|
368
|
+
}
|
|
369
|
+
return JSON.stringify({
|
|
370
|
+
...validThemeDefinition,
|
|
371
|
+
metadata: { ...validThemeDefinition.metadata, name: 'valid' },
|
|
372
|
+
})
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
const { getAllThemeMetadata } = await resetThemeModule()
|
|
376
|
+
const allMeta = getAllThemeMetadata()
|
|
377
|
+
|
|
378
|
+
// 应该有 7 个内置 + 1 个有效自定义
|
|
379
|
+
expect(allMeta.find((m) => m.name === 'valid')).toBeDefined()
|
|
380
|
+
})
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
// ============================================================================
|
|
384
|
+
// getThemeDefinition 测试
|
|
385
|
+
// ============================================================================
|
|
386
|
+
|
|
387
|
+
describe('getThemeDefinition', () => {
|
|
388
|
+
it('应该返回内置主题的完整定义', async () => {
|
|
389
|
+
const { getThemeDefinition } = await resetThemeModule()
|
|
390
|
+
|
|
391
|
+
const darkDef = getThemeDefinition('dark')
|
|
392
|
+
expect(darkDef?.metadata.name).toBe('dark')
|
|
393
|
+
expect(darkDef?.colors.primary).toBeDefined()
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
it('不存在的主题应该返回 undefined', async () => {
|
|
397
|
+
mockFs.existsSync.mockReturnValue(false)
|
|
398
|
+
|
|
399
|
+
const { getThemeDefinition } = await resetThemeModule()
|
|
400
|
+
const def = getThemeDefinition('nonexistent')
|
|
401
|
+
|
|
402
|
+
expect(def).toBeUndefined()
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
it('应该返回自定义主题的完整定义', async () => {
|
|
406
|
+
mockFs.existsSync.mockReturnValue(true)
|
|
407
|
+
mockFs.readFileSync.mockReturnValue(JSON.stringify(validThemeDefinition))
|
|
408
|
+
|
|
409
|
+
const { getThemeDefinition } = await resetThemeModule()
|
|
410
|
+
const def = getThemeDefinition('custom-theme')
|
|
411
|
+
|
|
412
|
+
expect(def?.metadata.name).toBe('custom-theme')
|
|
413
|
+
expect(def?.colors.primary).toBe('#FF0000')
|
|
414
|
+
})
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
// ============================================================================
|
|
418
|
+
// isValidTheme 测试
|
|
419
|
+
// ============================================================================
|
|
420
|
+
|
|
421
|
+
describe('isValidTheme', () => {
|
|
422
|
+
it('内置主题应该返回 true', async () => {
|
|
423
|
+
const { isValidTheme } = await resetThemeModule()
|
|
424
|
+
|
|
425
|
+
expect(isValidTheme('dark')).toBe(true)
|
|
426
|
+
expect(isValidTheme('light')).toBe(true)
|
|
427
|
+
expect(isValidTheme('nord')).toBe(true)
|
|
428
|
+
expect(isValidTheme('dracula')).toBe(true)
|
|
429
|
+
expect(isValidTheme('retro')).toBe(true)
|
|
430
|
+
expect(isValidTheme('contrast')).toBe(true)
|
|
431
|
+
expect(isValidTheme('monokai')).toBe(true)
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
it('不存在的主题应该返回 false', async () => {
|
|
435
|
+
mockFs.existsSync.mockReturnValue(false)
|
|
436
|
+
|
|
437
|
+
const { isValidTheme } = await resetThemeModule()
|
|
438
|
+
|
|
439
|
+
expect(isValidTheme('nonexistent')).toBe(false)
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
it('存在的自定义主题文件应该返回 true', async () => {
|
|
443
|
+
mockFs.existsSync.mockImplementation((path: any) => {
|
|
444
|
+
if (path.includes('custom-theme.json')) return true
|
|
445
|
+
return false
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
const { isValidTheme } = await resetThemeModule()
|
|
449
|
+
|
|
450
|
+
expect(isValidTheme('custom-theme')).toBe(true)
|
|
451
|
+
})
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
// ============================================================================
|
|
455
|
+
// isBuiltinTheme 测试
|
|
456
|
+
// ============================================================================
|
|
457
|
+
|
|
458
|
+
describe('isBuiltinTheme', () => {
|
|
459
|
+
it('内置主题应该返回 true', async () => {
|
|
460
|
+
const { isBuiltinTheme } = await resetThemeModule()
|
|
461
|
+
|
|
462
|
+
expect(isBuiltinTheme('dark')).toBe(true)
|
|
463
|
+
expect(isBuiltinTheme('light')).toBe(true)
|
|
464
|
+
expect(isBuiltinTheme('nord')).toBe(true)
|
|
465
|
+
expect(isBuiltinTheme('dracula')).toBe(true)
|
|
466
|
+
expect(isBuiltinTheme('retro')).toBe(true)
|
|
467
|
+
expect(isBuiltinTheme('contrast')).toBe(true)
|
|
468
|
+
expect(isBuiltinTheme('monokai')).toBe(true)
|
|
469
|
+
})
|
|
470
|
+
|
|
471
|
+
it('自定义主题应该返回 false', async () => {
|
|
472
|
+
const { isBuiltinTheme } = await resetThemeModule()
|
|
473
|
+
|
|
474
|
+
expect(isBuiltinTheme('custom-theme')).toBe(false)
|
|
475
|
+
expect(isBuiltinTheme('my-theme')).toBe(false)
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
it('不存在的主题应该返回 false', async () => {
|
|
479
|
+
const { isBuiltinTheme } = await resetThemeModule()
|
|
480
|
+
|
|
481
|
+
expect(isBuiltinTheme('nonexistent')).toBe(false)
|
|
482
|
+
})
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
// ============================================================================
|
|
486
|
+
// validateTheme 测试
|
|
487
|
+
// ============================================================================
|
|
488
|
+
|
|
489
|
+
describe('validateTheme', () => {
|
|
490
|
+
it('有效的主题定义应该返回 true', async () => {
|
|
491
|
+
const { validateTheme } = await resetThemeModule()
|
|
492
|
+
|
|
493
|
+
expect(validateTheme(validThemeDefinition)).toBe(true)
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
it('null 应该返回 false', async () => {
|
|
497
|
+
const { validateTheme } = await resetThemeModule()
|
|
498
|
+
|
|
499
|
+
expect(validateTheme(null)).toBe(false)
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
it('undefined 应该返回 false', async () => {
|
|
503
|
+
const { validateTheme } = await resetThemeModule()
|
|
504
|
+
|
|
505
|
+
expect(validateTheme(undefined)).toBe(false)
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
it('非对象应该返回 false', async () => {
|
|
509
|
+
const { validateTheme } = await resetThemeModule()
|
|
510
|
+
|
|
511
|
+
expect(validateTheme('string')).toBe(false)
|
|
512
|
+
expect(validateTheme(123)).toBe(false)
|
|
513
|
+
expect(validateTheme([])).toBe(false)
|
|
514
|
+
})
|
|
515
|
+
|
|
516
|
+
it('缺少 metadata 应该返回 false', async () => {
|
|
517
|
+
const { validateTheme } = await resetThemeModule()
|
|
518
|
+
|
|
519
|
+
expect(validateTheme({ colors: validThemeDefinition.colors })).toBe(false)
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
it('缺少 metadata.name 应该返回 false', async () => {
|
|
523
|
+
const { validateTheme } = await resetThemeModule()
|
|
524
|
+
|
|
525
|
+
const invalid = {
|
|
526
|
+
metadata: { displayName: 'Test', category: 'dark' },
|
|
527
|
+
colors: validThemeDefinition.colors,
|
|
528
|
+
}
|
|
529
|
+
expect(validateTheme(invalid)).toBe(false)
|
|
530
|
+
})
|
|
531
|
+
|
|
532
|
+
it('缺少 metadata.displayName 应该返回 false', async () => {
|
|
533
|
+
const { validateTheme } = await resetThemeModule()
|
|
534
|
+
|
|
535
|
+
const invalid = {
|
|
536
|
+
metadata: { name: 'test', category: 'dark' },
|
|
537
|
+
colors: validThemeDefinition.colors,
|
|
538
|
+
}
|
|
539
|
+
expect(validateTheme(invalid)).toBe(false)
|
|
540
|
+
})
|
|
541
|
+
|
|
542
|
+
it('缺少 metadata.category 应该返回 false', async () => {
|
|
543
|
+
const { validateTheme } = await resetThemeModule()
|
|
544
|
+
|
|
545
|
+
const invalid = {
|
|
546
|
+
metadata: { name: 'test', displayName: 'Test' },
|
|
547
|
+
colors: validThemeDefinition.colors,
|
|
548
|
+
}
|
|
549
|
+
expect(validateTheme(invalid)).toBe(false)
|
|
550
|
+
})
|
|
551
|
+
|
|
552
|
+
it('无效的 metadata.category 应该返回 false', async () => {
|
|
553
|
+
const { validateTheme } = await resetThemeModule()
|
|
554
|
+
|
|
555
|
+
const invalid = {
|
|
556
|
+
metadata: { name: 'test', displayName: 'Test', category: 'invalid' },
|
|
557
|
+
colors: validThemeDefinition.colors,
|
|
558
|
+
}
|
|
559
|
+
expect(validateTheme(invalid)).toBe(false)
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
it('缺少 colors 应该返回 false', async () => {
|
|
563
|
+
const { validateTheme } = await resetThemeModule()
|
|
564
|
+
|
|
565
|
+
expect(validateTheme({ metadata: validThemeDefinition.metadata })).toBe(false)
|
|
566
|
+
})
|
|
567
|
+
|
|
568
|
+
it('缺少 colors.primary 应该返回 false', async () => {
|
|
569
|
+
const { validateTheme } = await resetThemeModule()
|
|
570
|
+
|
|
571
|
+
const invalid = {
|
|
572
|
+
metadata: validThemeDefinition.metadata,
|
|
573
|
+
colors: { ...validThemeDefinition.colors, primary: undefined },
|
|
574
|
+
}
|
|
575
|
+
expect(validateTheme(invalid)).toBe(false)
|
|
576
|
+
})
|
|
577
|
+
|
|
578
|
+
it('缺少 colors.text.primary 应该返回 false', async () => {
|
|
579
|
+
const { validateTheme } = await resetThemeModule()
|
|
580
|
+
|
|
581
|
+
const invalid = {
|
|
582
|
+
metadata: validThemeDefinition.metadata,
|
|
583
|
+
colors: {
|
|
584
|
+
...validThemeDefinition.colors,
|
|
585
|
+
text: { ...validThemeDefinition.colors.text, primary: undefined },
|
|
586
|
+
},
|
|
587
|
+
}
|
|
588
|
+
expect(validateTheme(invalid)).toBe(false)
|
|
589
|
+
})
|
|
590
|
+
|
|
591
|
+
it('无效的颜色格式应该返回 false', async () => {
|
|
592
|
+
const { validateTheme } = await resetThemeModule()
|
|
593
|
+
|
|
594
|
+
const invalid = {
|
|
595
|
+
metadata: validThemeDefinition.metadata,
|
|
596
|
+
colors: { ...validThemeDefinition.colors, primary: 'red' }, // 不是 hex 格式
|
|
597
|
+
}
|
|
598
|
+
expect(validateTheme(invalid)).toBe(false)
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
it('3 位 hex 格式应该返回 false', async () => {
|
|
602
|
+
const { validateTheme } = await resetThemeModule()
|
|
603
|
+
|
|
604
|
+
const invalid = {
|
|
605
|
+
metadata: validThemeDefinition.metadata,
|
|
606
|
+
colors: { ...validThemeDefinition.colors, primary: '#F00' },
|
|
607
|
+
}
|
|
608
|
+
expect(validateTheme(invalid)).toBe(false)
|
|
609
|
+
})
|
|
610
|
+
|
|
611
|
+
it('8 位 hex 格式(含 alpha)应该返回 false', async () => {
|
|
612
|
+
const { validateTheme } = await resetThemeModule()
|
|
613
|
+
|
|
614
|
+
const invalid = {
|
|
615
|
+
metadata: validThemeDefinition.metadata,
|
|
616
|
+
colors: { ...validThemeDefinition.colors, primary: '#FF0000FF' },
|
|
617
|
+
}
|
|
618
|
+
expect(validateTheme(invalid)).toBe(false)
|
|
619
|
+
})
|
|
620
|
+
})
|
|
621
|
+
|
|
622
|
+
// ============================================================================
|
|
623
|
+
// validateThemeWithDetails 测试
|
|
624
|
+
// ============================================================================
|
|
625
|
+
|
|
626
|
+
describe('validateThemeWithDetails', () => {
|
|
627
|
+
it('有效的主题应该返回 valid: true 和空 errors', async () => {
|
|
628
|
+
const { validateThemeWithDetails } = await resetThemeModule()
|
|
629
|
+
|
|
630
|
+
const result = validateThemeWithDetails(validThemeDefinition)
|
|
631
|
+
expect(result.valid).toBe(true)
|
|
632
|
+
expect(result.errors).toEqual([])
|
|
633
|
+
})
|
|
634
|
+
|
|
635
|
+
it('null 应该返回详细错误', async () => {
|
|
636
|
+
const { validateThemeWithDetails } = await resetThemeModule()
|
|
637
|
+
|
|
638
|
+
const result = validateThemeWithDetails(null)
|
|
639
|
+
expect(result.valid).toBe(false)
|
|
640
|
+
expect(result.errors).toContain('主题必须是一个 JSON 对象')
|
|
641
|
+
})
|
|
642
|
+
|
|
643
|
+
it('缺少 metadata 应该报告错误', async () => {
|
|
644
|
+
const { validateThemeWithDetails } = await resetThemeModule()
|
|
645
|
+
|
|
646
|
+
const result = validateThemeWithDetails({ colors: validThemeDefinition.colors })
|
|
647
|
+
expect(result.valid).toBe(false)
|
|
648
|
+
expect(result.errors).toContain('缺少 metadata 字段')
|
|
649
|
+
})
|
|
650
|
+
|
|
651
|
+
it('缺少 metadata.name 应该报告错误', async () => {
|
|
652
|
+
const { validateThemeWithDetails } = await resetThemeModule()
|
|
653
|
+
|
|
654
|
+
const invalid = {
|
|
655
|
+
metadata: { displayName: 'Test', category: 'dark' },
|
|
656
|
+
colors: validThemeDefinition.colors,
|
|
657
|
+
}
|
|
658
|
+
const result = validateThemeWithDetails(invalid)
|
|
659
|
+
expect(result.valid).toBe(false)
|
|
660
|
+
expect(result.errors).toContain('缺少 metadata.name')
|
|
661
|
+
})
|
|
662
|
+
|
|
663
|
+
it('无效的 category 应该报告错误', async () => {
|
|
664
|
+
const { validateThemeWithDetails } = await resetThemeModule()
|
|
665
|
+
|
|
666
|
+
const invalid = {
|
|
667
|
+
metadata: { name: 'test', displayName: 'Test', category: 'invalid' },
|
|
668
|
+
colors: validThemeDefinition.colors,
|
|
669
|
+
}
|
|
670
|
+
const result = validateThemeWithDetails(invalid)
|
|
671
|
+
expect(result.valid).toBe(false)
|
|
672
|
+
expect(result.errors).toContain('metadata.category 必须是 "dark" 或 "light"')
|
|
673
|
+
})
|
|
674
|
+
|
|
675
|
+
it('缺少 colors 应该报告错误', async () => {
|
|
676
|
+
const { validateThemeWithDetails } = await resetThemeModule()
|
|
677
|
+
|
|
678
|
+
const result = validateThemeWithDetails({ metadata: validThemeDefinition.metadata })
|
|
679
|
+
expect(result.valid).toBe(false)
|
|
680
|
+
expect(result.errors).toContain('缺少 colors 字段')
|
|
681
|
+
})
|
|
682
|
+
|
|
683
|
+
it('缺少多个颜色字段应该报告所有错误', async () => {
|
|
684
|
+
const { validateThemeWithDetails } = await resetThemeModule()
|
|
685
|
+
|
|
686
|
+
const invalid = {
|
|
687
|
+
metadata: validThemeDefinition.metadata,
|
|
688
|
+
colors: {
|
|
689
|
+
primary: '#FF0000',
|
|
690
|
+
// 缺少其他必填字段
|
|
691
|
+
},
|
|
692
|
+
}
|
|
693
|
+
const result = validateThemeWithDetails(invalid)
|
|
694
|
+
expect(result.valid).toBe(false)
|
|
695
|
+
expect(result.errors.length).toBeGreaterThan(5)
|
|
696
|
+
expect(result.errors).toContain('缺少 colors.secondary')
|
|
697
|
+
expect(result.errors).toContain('缺少 colors.success')
|
|
698
|
+
})
|
|
699
|
+
|
|
700
|
+
it('无效的颜色格式应该报告详细错误', async () => {
|
|
701
|
+
const { validateThemeWithDetails } = await resetThemeModule()
|
|
702
|
+
|
|
703
|
+
const invalid = {
|
|
704
|
+
metadata: validThemeDefinition.metadata,
|
|
705
|
+
colors: {
|
|
706
|
+
...validThemeDefinition.colors,
|
|
707
|
+
primary: 'red',
|
|
708
|
+
secondary: '#GGG',
|
|
709
|
+
},
|
|
710
|
+
}
|
|
711
|
+
const result = validateThemeWithDetails(invalid)
|
|
712
|
+
expect(result.valid).toBe(false)
|
|
713
|
+
expect(result.errors).toContain('colors.primary 格式错误(应为 #RRGGBB 格式)')
|
|
714
|
+
expect(result.errors).toContain('colors.secondary 格式错误(应为 #RRGGBB 格式)')
|
|
715
|
+
})
|
|
716
|
+
|
|
717
|
+
it('缺少 colors.text 应该报告错误', async () => {
|
|
718
|
+
const { validateThemeWithDetails } = await resetThemeModule()
|
|
719
|
+
|
|
720
|
+
const invalid = {
|
|
721
|
+
metadata: validThemeDefinition.metadata,
|
|
722
|
+
colors: {
|
|
723
|
+
...validThemeDefinition.colors,
|
|
724
|
+
text: undefined,
|
|
725
|
+
},
|
|
726
|
+
}
|
|
727
|
+
const result = validateThemeWithDetails(invalid)
|
|
728
|
+
expect(result.valid).toBe(false)
|
|
729
|
+
expect(result.errors).toContain('缺少 colors.text')
|
|
730
|
+
})
|
|
731
|
+
|
|
732
|
+
it('缺少 colors.code 应该报告错误', async () => {
|
|
733
|
+
const { validateThemeWithDetails } = await resetThemeModule()
|
|
734
|
+
|
|
735
|
+
const invalid = {
|
|
736
|
+
metadata: validThemeDefinition.metadata,
|
|
737
|
+
colors: {
|
|
738
|
+
...validThemeDefinition.colors,
|
|
739
|
+
code: undefined,
|
|
740
|
+
},
|
|
741
|
+
}
|
|
742
|
+
const result = validateThemeWithDetails(invalid)
|
|
743
|
+
expect(result.valid).toBe(false)
|
|
744
|
+
expect(result.errors).toContain('缺少 colors.code')
|
|
745
|
+
})
|
|
746
|
+
})
|
|
747
|
+
|
|
748
|
+
// ============================================================================
|
|
749
|
+
// createThemeTemplate 测试
|
|
750
|
+
// ============================================================================
|
|
751
|
+
|
|
752
|
+
describe('createThemeTemplate', () => {
|
|
753
|
+
it('应该创建深色主题模板', async () => {
|
|
754
|
+
const { createThemeTemplate, themeDefinitions } = await resetThemeModule()
|
|
755
|
+
|
|
756
|
+
const template = createThemeTemplate('my-theme', '我的主题', 'dark')
|
|
757
|
+
|
|
758
|
+
expect(template.metadata.name).toBe('my-theme')
|
|
759
|
+
expect(template.metadata.displayName).toBe('我的主题')
|
|
760
|
+
expect(template.metadata.category).toBe('dark')
|
|
761
|
+
expect(template.metadata.description).toBe('自定义主题')
|
|
762
|
+
expect(template.metadata.author).toBe('testuser')
|
|
763
|
+
expect(template.colors).toEqual(themeDefinitions.dark.colors)
|
|
764
|
+
})
|
|
765
|
+
|
|
766
|
+
it('应该创建浅色主题模板', async () => {
|
|
767
|
+
const { createThemeTemplate, themeDefinitions } = await resetThemeModule()
|
|
768
|
+
|
|
769
|
+
const template = createThemeTemplate('light-theme', '浅色主题', 'light')
|
|
770
|
+
|
|
771
|
+
expect(template.metadata.name).toBe('light-theme')
|
|
772
|
+
expect(template.metadata.displayName).toBe('浅色主题')
|
|
773
|
+
expect(template.metadata.category).toBe('light')
|
|
774
|
+
expect(template.colors).toEqual(themeDefinitions.light.colors)
|
|
775
|
+
})
|
|
776
|
+
|
|
777
|
+
it('previewColor 应该与基础主题的 primary 颜色相同', async () => {
|
|
778
|
+
const { createThemeTemplate, themeDefinitions } = await resetThemeModule()
|
|
779
|
+
|
|
780
|
+
const darkTemplate = createThemeTemplate('test-dark', 'Test', 'dark')
|
|
781
|
+
expect(darkTemplate.metadata.previewColor).toBe(themeDefinitions.dark.colors.primary)
|
|
782
|
+
|
|
783
|
+
const lightTemplate = createThemeTemplate('test-light', 'Test', 'light')
|
|
784
|
+
expect(lightTemplate.metadata.previewColor).toBe(themeDefinitions.light.colors.primary)
|
|
785
|
+
})
|
|
786
|
+
|
|
787
|
+
it('author 应该使用当前用户名', async () => {
|
|
788
|
+
mockOs.userInfo.mockReturnValue({ username: 'customuser' } as any)
|
|
789
|
+
|
|
790
|
+
const { createThemeTemplate } = await resetThemeModule()
|
|
791
|
+
const template = createThemeTemplate('test', 'Test', 'dark')
|
|
792
|
+
|
|
793
|
+
expect(template.metadata.author).toBe('customuser')
|
|
794
|
+
})
|
|
795
|
+
|
|
796
|
+
it('userInfo 返回空用户名时应该使用 user', async () => {
|
|
797
|
+
mockOs.userInfo.mockReturnValue({ username: '' } as any)
|
|
798
|
+
|
|
799
|
+
const { createThemeTemplate } = await resetThemeModule()
|
|
800
|
+
const template = createThemeTemplate('test', 'Test', 'dark')
|
|
801
|
+
|
|
802
|
+
expect(template.metadata.author).toBe('user')
|
|
803
|
+
})
|
|
804
|
+
})
|
|
805
|
+
|
|
806
|
+
// ============================================================================
|
|
807
|
+
// 自定义主题加载测试
|
|
808
|
+
// ============================================================================
|
|
809
|
+
|
|
810
|
+
describe('自定义主题加载', () => {
|
|
811
|
+
it('自定义主题 JSON 损坏应该跳过并警告', async () => {
|
|
812
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
813
|
+
|
|
814
|
+
mockFs.existsSync.mockReturnValue(true)
|
|
815
|
+
mockFs.readFileSync.mockReturnValue('{invalid json')
|
|
816
|
+
|
|
817
|
+
const { getThemeMetadata } = await resetThemeModule()
|
|
818
|
+
const meta = getThemeMetadata('broken-theme')
|
|
819
|
+
|
|
820
|
+
expect(meta).toBeUndefined()
|
|
821
|
+
expect(warnSpy).toHaveBeenCalled()
|
|
822
|
+
|
|
823
|
+
warnSpy.mockRestore()
|
|
824
|
+
})
|
|
825
|
+
|
|
826
|
+
it('自定义主题格式不正确应该跳过并警告', async () => {
|
|
827
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
828
|
+
|
|
829
|
+
mockFs.existsSync.mockReturnValue(true)
|
|
830
|
+
mockFs.readFileSync.mockReturnValue(JSON.stringify({ invalid: 'structure' }))
|
|
831
|
+
|
|
832
|
+
const { getThemeMetadata } = await resetThemeModule()
|
|
833
|
+
const meta = getThemeMetadata('invalid-theme')
|
|
834
|
+
|
|
835
|
+
expect(meta).toBeUndefined()
|
|
836
|
+
expect(warnSpy).toHaveBeenCalled()
|
|
837
|
+
|
|
838
|
+
warnSpy.mockRestore()
|
|
839
|
+
})
|
|
840
|
+
|
|
841
|
+
it('读取主题文件失败应该返回 null', async () => {
|
|
842
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
843
|
+
|
|
844
|
+
mockFs.existsSync.mockReturnValue(true)
|
|
845
|
+
mockFs.readFileSync.mockImplementation(() => {
|
|
846
|
+
throw new Error('EACCES: permission denied')
|
|
847
|
+
})
|
|
848
|
+
|
|
849
|
+
const { getThemeDefinition } = await resetThemeModule()
|
|
850
|
+
const def = getThemeDefinition('permission-denied-theme')
|
|
851
|
+
|
|
852
|
+
expect(def).toBeUndefined()
|
|
853
|
+
expect(warnSpy).toHaveBeenCalled()
|
|
854
|
+
|
|
855
|
+
warnSpy.mockRestore()
|
|
856
|
+
})
|
|
857
|
+
})
|
|
858
|
+
|
|
859
|
+
// ============================================================================
|
|
860
|
+
// theme 导出测试
|
|
861
|
+
// ============================================================================
|
|
862
|
+
|
|
863
|
+
describe('theme 导出', () => {
|
|
864
|
+
it('应该导出 darkTheme 作为默认 theme', async () => {
|
|
865
|
+
const { theme, themeDefinitions } = await resetThemeModule()
|
|
866
|
+
|
|
867
|
+
expect(theme).toEqual(themeDefinitions.dark.colors)
|
|
868
|
+
})
|
|
869
|
+
})
|