mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-17 13:13:35 +08:00
Add native system notifications as a desktop-wide attention channel for permission prompts and scheduled task completion. The implementation keeps notification presentation owned by the OS, adds a user-facing enable switch with permission handling, and lets scheduled tasks choose desktop notifications without routing that channel through IM adapters. Constraint: Notifications must use OS-native APIs without custom sound playback. Constraint: Desktop channel is local-only and must not be sent through IM adapter delivery. Rejected: Browser Notification API | not reliable inside the packaged Tauri desktop runtime. Rejected: Treat desktop as an IM channel | would leak a local-only channel into server-side adapter sending. Confidence: high Scope-risk: moderate Directive: Keep notification styling at the OS layer; business code should only provide title, body, dedupe, and routing decisions. Tested: bun run check:desktop Tested: bun run quality:pr Tested: Computer Use macOS debug app verification for settings toggle, permission prompt, scheduled task desktop channel, and task-run polling dedupe Not-tested: Windows and Linux native runtime smoke tests on physical hosts
80 lines
2.3 KiB
TypeScript
80 lines
2.3 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
describe('settingsStore locale defaults', () => {
|
|
beforeEach(() => {
|
|
vi.resetModules()
|
|
window.localStorage.clear()
|
|
})
|
|
|
|
it('defaults to Chinese when no locale is stored', async () => {
|
|
const { useSettingsStore } = await import('./settingsStore')
|
|
|
|
expect(useSettingsStore.getState().locale).toBe('zh')
|
|
})
|
|
|
|
it('keeps a stored locale override', async () => {
|
|
window.localStorage.setItem('cc-haha-locale', 'en')
|
|
|
|
const { useSettingsStore } = await import('./settingsStore')
|
|
|
|
expect(useSettingsStore.getState().locale).toBe('en')
|
|
})
|
|
})
|
|
|
|
describe('settingsStore desktop notification persistence', () => {
|
|
beforeEach(() => {
|
|
vi.resetModules()
|
|
vi.clearAllMocks()
|
|
window.localStorage.clear()
|
|
})
|
|
|
|
it('persists the latest desktop notification toggle when saves overlap', async () => {
|
|
const pendingSaves: Array<() => void> = []
|
|
const updateUser = vi.fn(
|
|
() =>
|
|
new Promise<{ ok: true }>((resolve) => {
|
|
pendingSaves.push(() => resolve({ ok: true }))
|
|
}),
|
|
)
|
|
|
|
vi.doMock('../api/settings', () => ({
|
|
settingsApi: {
|
|
getUser: vi.fn(),
|
|
updateUser,
|
|
getPermissionMode: vi.fn(),
|
|
setPermissionMode: vi.fn(),
|
|
getCliLauncherStatus: vi.fn(),
|
|
},
|
|
}))
|
|
vi.doMock('../api/models', () => ({
|
|
modelsApi: {
|
|
list: vi.fn(),
|
|
getCurrent: vi.fn(),
|
|
setCurrent: vi.fn(),
|
|
getEffort: vi.fn(),
|
|
setEffort: vi.fn(),
|
|
},
|
|
}))
|
|
|
|
const { useSettingsStore } = await import('./settingsStore')
|
|
|
|
const firstSave = useSettingsStore.getState().setDesktopNotificationsEnabled(false)
|
|
await vi.waitFor(() => {
|
|
expect(updateUser).toHaveBeenCalledWith({ desktopNotificationsEnabled: false })
|
|
})
|
|
|
|
const secondSave = useSettingsStore.getState().setDesktopNotificationsEnabled(true)
|
|
expect(useSettingsStore.getState().desktopNotificationsEnabled).toBe(true)
|
|
|
|
pendingSaves.shift()?.()
|
|
await vi.waitFor(() => {
|
|
expect(updateUser).toHaveBeenCalledWith({ desktopNotificationsEnabled: true })
|
|
})
|
|
pendingSaves.shift()?.()
|
|
await Promise.all([firstSave, secondSave])
|
|
|
|
expect(updateUser).toHaveBeenLastCalledWith({ desktopNotificationsEnabled: true })
|
|
expect(useSettingsStore.getState().desktopNotificationsEnabled).toBe(true)
|
|
})
|
|
})
|