Files
ZenEcho b48e639490 feat: 引入共享类型与工具层,重构代码结构并优化用户体验
- 新增 `shared/` 目录,统一管理前后端共享的类型定义与工具函数,消除重复代码
- 重构 IPC 数据传输,使用 `toIpcSafeSnapshot` 确保序列化安全
- 新增系统托盘独立窗口 (`/tray` 路由) 与主窗口关闭行为设置,提升用户体验
- 优化 Sing-box 配置校验错误提示,增强可读性
- 修复订阅更新无新节点时的提示逻辑
- 更新项目依赖与构建配置,支持共享层路径别名 (`@shared`)
- 补充单元测试,完善国际化词条
2026-03-01 16:50:40 +08:00

114 lines
3.0 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from 'vitest'
import { createPinia, setActivePinia } from 'pinia'
import { effectScope, nextTick } from 'vue'
import { useTheme } from '../../src/composables/useTheme'
const localStorageMock = (() => {
let store: Record<string, string> = {}
return {
getItem: vi.fn((key: string) => store[key] || null),
setItem: vi.fn((key: string, value: string) => {
store[key] = value.toString()
}),
removeItem: vi.fn((key: string) => {
delete store[key]
}),
clear: vi.fn(() => {
store = {}
})
}
})()
Object.defineProperty(window, 'localStorage', {
value: localStorageMock
})
describe('useTheme', () => {
let prefersDark = false
beforeEach(() => {
setActivePinia(createPinia())
prefersDark = false
localStorageMock.clear()
localStorageMock.getItem.mockClear()
localStorageMock.setItem.mockClear()
localStorageMock.removeItem.mockClear()
document.documentElement.className = ''
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation((query: string) => ({
get matches() {
return prefersDark
},
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn()
}))
})
})
it('should initialize with default theme', () => {
const scope = effectScope()
const api = scope.run(() => useTheme())!
expect(api.theme.value).toBe('auto')
expect(api.themeColor.value).toBe('green')
scope.stop()
})
it('should set theme and apply dark class', async () => {
const scope = effectScope()
const api = scope.run(() => useTheme())!
api.setTheme('dark')
await nextTick()
expect(api.theme.value).toBe('dark')
expect(document.documentElement.classList.contains('dark')).toBe(true)
expect(localStorageMock.setItem).toHaveBeenCalled()
scope.stop()
})
it('should toggle theme between dark and light', async () => {
const scope = effectScope()
const api = scope.run(() => useTheme())!
api.setTheme('dark')
await nextTick()
expect(api.resolvedTheme.value).toBe('dark')
api.toggleTheme()
await nextTick()
expect(api.theme.value).toBe('light')
expect(api.resolvedTheme.value).toBe('light')
api.toggleTheme()
await nextTick()
expect(api.theme.value).toBe('dark')
expect(api.resolvedTheme.value).toBe('dark')
scope.stop()
})
it('should follow system preference when theme is auto', async () => {
const scope = effectScope()
const api = scope.run(() => useTheme())!
prefersDark = false
api.setTheme('auto')
await nextTick()
expect(document.documentElement.classList.contains('dark')).toBe(false)
prefersDark = true
api.setTheme('light')
await nextTick()
api.setTheme('auto')
await nextTick()
expect(document.documentElement.classList.contains('dark')).toBe(true)
scope.stop()
})
})