Download desktop/src/lib/desktopNotifications.test.ts from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 16.3 kB
-
https://huggingface.co/chenbhao/codev/resolve/e40073cc959cd4729bf4009662bca374c5c4e266/desktop/src/lib/desktopNotifications.test.ts
- Command line
-
hf download hf://chenbhao/codev@e40073cc959cd4729bf4009662bca374c5c4e266/desktop/src/lib/desktopNotifications.test.ts
-
curl -L -o desktopNotifications.test.ts https://huggingface.co/chenbhao/codev/resolve/e40073cc959cd4729bf4009662bca374c5c4e266/desktop/src/lib/desktopNotifications.test.ts
16.3 kB
| import { beforeEach, describe, expect, it, vi } from 'vitest' | |
| const notificationPluginMock = vi.hoisted(() => ({ | |
| isPermissionGranted: vi.fn(), | |
| requestPermission: vi.fn(), | |
| sendNotification: vi.fn(), | |
| onAction: vi.fn(), | |
| })) | |
| const coreApiMock = vi.hoisted(() => ({ | |
| invoke: vi.fn(), | |
| })) | |
| const eventApiMock = vi.hoisted(() => ({ | |
| listen: vi.fn(), | |
| })) | |
| const shellApiMock = vi.hoisted(() => ({ | |
| open: vi.fn(), | |
| })) | |
| const requestUserAttentionMock = vi.hoisted(() => vi.fn()) | |
| const windowApiMock = vi.hoisted(() => ({ | |
| requestUserAttention: requestUserAttentionMock, | |
| getCurrentWindow: vi.fn(() => ({ | |
| requestUserAttention: requestUserAttentionMock, | |
| })), | |
| UserAttentionType: { | |
| Critical: 1, | |
| Informational: 2, | |
| }, | |
| })) | |
| vi.mock('@tauri-apps/plugin-notification', () => notificationPluginMock) | |
| vi.mock('@tauri-apps/api/core', () => coreApiMock) | |
| vi.mock('@tauri-apps/api/event', () => eventApiMock) | |
| vi.mock('@tauri-apps/api/window', () => windowApiMock) | |
| vi.mock('@tauri-apps/plugin-shell', () => shellApiMock) | |
| import { | |
| getDesktopNotificationPermission, | |
| installDesktopNotificationClickListener, | |
| notifyDesktop, | |
| openDesktopNotificationSettings, | |
| requestDesktopNotificationPermission, | |
| resetDesktopNotificationsForTests, | |
| setNativeNotificationSenderForTests, | |
| } from './desktopNotifications' | |
| import { useSettingsStore } from '../stores/settingsStore' | |
| describe('desktopNotifications', () => { | |
| beforeEach(() => { | |
| vi.useRealTimers() | |
| resetDesktopNotificationsForTests() | |
| coreApiMock.invoke.mockReset() | |
| eventApiMock.listen.mockReset() | |
| shellApiMock.open.mockReset() | |
| notificationPluginMock.isPermissionGranted.mockReset() | |
| notificationPluginMock.requestPermission.mockReset() | |
| notificationPluginMock.sendNotification.mockReset() | |
| notificationPluginMock.onAction.mockReset() | |
| windowApiMock.getCurrentWindow.mockClear() | |
| windowApiMock.requestUserAttention.mockReset() | |
| useSettingsStore.setState({ desktopNotificationsEnabled: true }) | |
| Object.defineProperty(navigator, 'platform', { | |
| configurable: true, | |
| value: 'Linux x86_64', | |
| }) | |
| }) | |
| it('sends through the Tauri plugin when native notification permission is already granted', async () => { | |
| notificationPluginMock.isPermissionGranted.mockResolvedValue(true) | |
| notifyDesktop({ | |
| dedupeKey: 'permission:1', | |
| title: 'Permission required', | |
| body: 'Approve command execution', | |
| }) | |
| await vi.waitFor(() => expect(notificationPluginMock.sendNotification).toHaveBeenCalledTimes(1)) | |
| expect(notificationPluginMock.isPermissionGranted).toHaveBeenCalledTimes(1) | |
| expect(notificationPluginMock.requestPermission).not.toHaveBeenCalled() | |
| expect(notificationPluginMock.sendNotification).toHaveBeenCalledWith({ | |
| title: 'Permission required', | |
| body: 'Approve command execution', | |
| }) | |
| }) | |
| it('passes notification targets through the Tauri plugin payload', async () => { | |
| notificationPluginMock.isPermissionGranted.mockResolvedValue(true) | |
| const target = { type: 'session' as const, sessionId: 'session-1', title: 'Build fix' } | |
| await expect(notifyDesktop({ | |
| dedupeKey: 'permission:targeted', | |
| title: 'Permission required', | |
| body: 'Approve command execution', | |
| target, | |
| })).resolves.toBe(true) | |
| expect(notificationPluginMock.sendNotification).toHaveBeenCalledWith(expect.objectContaining({ | |
| title: 'Permission required', | |
| body: 'Approve command execution', | |
| id: expect.any(Number), | |
| extra: { | |
| ccHahaTarget: JSON.stringify(target), | |
| }, | |
| })) | |
| }) | |
| it('does not request notification permission from a blocking permission prompt', async () => { | |
| const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) | |
| notificationPluginMock.isPermissionGranted.mockResolvedValue(false) | |
| notifyDesktop({ title: 'Permission required' }) | |
| await vi.waitFor(() => expect(warnSpy).toHaveBeenCalled()) | |
| expect(notificationPluginMock.sendNotification).not.toHaveBeenCalled() | |
| expect(warnSpy).toHaveBeenCalledWith( | |
| '[desktopNotifications] native notification permission was not granted', | |
| ) | |
| expect(notificationPluginMock.requestPermission).not.toHaveBeenCalled() | |
| warnSpy.mockRestore() | |
| }) | |
| it('uses the macOS native bridge for foreground-visible notifications', async () => { | |
| Object.defineProperty(navigator, 'platform', { | |
| configurable: true, | |
| value: 'MacIntel', | |
| }) | |
| coreApiMock.invoke.mockResolvedValueOnce(true) | |
| await expect(notifyDesktop({ | |
| title: 'Permission required', | |
| body: 'Approve command execution', | |
| })).resolves.toBe(true) | |
| expect(coreApiMock.invoke).toHaveBeenCalledWith('macos_send_notification', { | |
| title: 'Permission required', | |
| body: 'Approve command execution', | |
| }) | |
| expect(notificationPluginMock.sendNotification).not.toHaveBeenCalled() | |
| expect(notificationPluginMock.requestPermission).not.toHaveBeenCalled() | |
| }) | |
| it('passes notification targets through the macOS native bridge', async () => { | |
| Object.defineProperty(navigator, 'platform', { | |
| configurable: true, | |
| value: 'MacIntel', | |
| }) | |
| coreApiMock.invoke.mockResolvedValueOnce(true) | |
| const target = { type: 'session' as const, sessionId: 'session-1', title: 'Build fix' } | |
| await expect(notifyDesktop({ | |
| title: 'Permission required', | |
| body: 'Approve command execution', | |
| target, | |
| })).resolves.toBe(true) | |
| expect(coreApiMock.invoke).toHaveBeenCalledWith('macos_send_notification', { | |
| title: 'Permission required', | |
| body: 'Approve command execution', | |
| target: JSON.stringify(target), | |
| }) | |
| }) | |
| it('does not request macOS permission from a blocking permission prompt', async () => { | |
| const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) | |
| Object.defineProperty(navigator, 'platform', { | |
| configurable: true, | |
| value: 'MacIntel', | |
| }) | |
| coreApiMock.invoke.mockResolvedValueOnce(false) | |
| await expect(notifyDesktop({ title: 'Permission required' })).resolves.toBe(false) | |
| expect(coreApiMock.invoke).toHaveBeenCalledWith('macos_send_notification', { | |
| title: 'Permission required', | |
| body: undefined, | |
| }) | |
| expect(coreApiMock.invoke).not.toHaveBeenCalledWith('macos_request_notification_permission') | |
| expect(warnSpy).toHaveBeenCalledWith( | |
| '[desktopNotifications] native notification permission was not granted', | |
| ) | |
| warnSpy.mockRestore() | |
| }) | |
| it('does not fall back to the Tauri plugin when the macOS bridge fails', async () => { | |
| const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) | |
| Object.defineProperty(navigator, 'platform', { | |
| configurable: true, | |
| value: 'MacIntel', | |
| }) | |
| coreApiMock.invoke.mockRejectedValueOnce(new Error('bridge unavailable')) | |
| notificationPluginMock.isPermissionGranted.mockResolvedValue(true) | |
| await expect(notifyDesktop({ title: 'Permission required' })).resolves.toBe(false) | |
| expect(notificationPluginMock.sendNotification).not.toHaveBeenCalled() | |
| expect(notificationPluginMock.isPermissionGranted).not.toHaveBeenCalled() | |
| warnSpy.mockRestore() | |
| }) | |
| it('does not send or consume dedupe keys when desktop notifications are disabled', async () => { | |
| const sender = vi.fn(async () => true) | |
| setNativeNotificationSenderForTests(sender) | |
| useSettingsStore.setState({ desktopNotificationsEnabled: false }) | |
| notifyDesktop({ dedupeKey: 'permission:1', title: 'Permission required' }) | |
| await new Promise((resolve) => setTimeout(resolve, 0)) | |
| expect(sender).not.toHaveBeenCalled() | |
| useSettingsStore.setState({ desktopNotificationsEnabled: true }) | |
| notifyDesktop({ dedupeKey: 'permission:1', title: 'Permission required' }) | |
| await vi.waitFor(() => expect(sender).toHaveBeenCalledTimes(1)) | |
| }) | |
| it('does not consume dedupe keys when native notification delivery fails', async () => { | |
| const sender = vi.fn() | |
| .mockResolvedValueOnce(false) | |
| .mockResolvedValueOnce(true) | |
| const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) | |
| setNativeNotificationSenderForTests(sender) | |
| await expect(notifyDesktop({ dedupeKey: 'permission:retry', title: 'Permission required' })).resolves.toBe(false) | |
| await expect(notifyDesktop({ dedupeKey: 'permission:retry', title: 'Permission required' })).resolves.toBe(true) | |
| expect(sender).toHaveBeenCalledTimes(2) | |
| warnSpy.mockRestore() | |
| }) | |
| it('reports and requests native notification permission', async () => { | |
| notificationPluginMock.isPermissionGranted.mockResolvedValueOnce(false).mockResolvedValueOnce(false) | |
| notificationPluginMock.requestPermission.mockResolvedValue('granted') | |
| Object.defineProperty(window, 'Notification', { | |
| configurable: true, | |
| value: { permission: 'default' }, | |
| }) | |
| await expect(getDesktopNotificationPermission()).resolves.toBe('default') | |
| await expect(requestDesktopNotificationPermission()).resolves.toBe('granted') | |
| expect(notificationPluginMock.requestPermission).toHaveBeenCalledTimes(1) | |
| }) | |
| it('reads and requests Windows notification permission through the native plugin command', async () => { | |
| Object.defineProperty(navigator, 'platform', { | |
| configurable: true, | |
| value: 'Win32', | |
| }) | |
| coreApiMock.invoke | |
| .mockResolvedValueOnce(true) | |
| .mockResolvedValueOnce('granted') | |
| await expect(getDesktopNotificationPermission()).resolves.toBe('granted') | |
| await expect(requestDesktopNotificationPermission()).resolves.toBe('granted') | |
| expect(coreApiMock.invoke).toHaveBeenNthCalledWith(1, 'plugin:notification|is_permission_granted') | |
| expect(coreApiMock.invoke).toHaveBeenNthCalledWith(2, 'plugin:notification|request_permission') | |
| expect(notificationPluginMock.isPermissionGranted).not.toHaveBeenCalled() | |
| expect(notificationPluginMock.requestPermission).not.toHaveBeenCalled() | |
| }) | |
| it('sends Windows notifications when the native plugin reports permission granted', async () => { | |
| Object.defineProperty(navigator, 'platform', { | |
| configurable: true, | |
| value: 'Win32', | |
| }) | |
| coreApiMock.invoke.mockResolvedValueOnce(true) | |
| await expect(notifyDesktop({ | |
| title: 'Permission required', | |
| body: 'Approve command execution', | |
| })).resolves.toBe(true) | |
| expect(coreApiMock.invoke).toHaveBeenCalledWith('plugin:notification|is_permission_granted') | |
| expect(notificationPluginMock.isPermissionGranted).not.toHaveBeenCalled() | |
| expect(notificationPluginMock.sendNotification).toHaveBeenCalledWith({ | |
| title: 'Permission required', | |
| body: 'Approve command execution', | |
| }) | |
| }) | |
| it('opens Windows notification settings through the native command', async () => { | |
| Object.defineProperty(navigator, 'platform', { | |
| configurable: true, | |
| value: 'Win32', | |
| }) | |
| coreApiMock.invoke.mockResolvedValueOnce(true) | |
| await expect(openDesktopNotificationSettings()).resolves.toBe(true) | |
| expect(coreApiMock.invoke).toHaveBeenCalledWith('open_windows_notification_settings') | |
| expect(shellApiMock.open).not.toHaveBeenCalled() | |
| }) | |
| it('reports and requests macOS notification permission through the native bridge', async () => { | |
| Object.defineProperty(navigator, 'platform', { | |
| configurable: true, | |
| value: 'MacIntel', | |
| }) | |
| coreApiMock.invoke | |
| .mockResolvedValueOnce('default') | |
| .mockResolvedValueOnce('granted') | |
| await expect(getDesktopNotificationPermission()).resolves.toBe('default') | |
| await expect(requestDesktopNotificationPermission()).resolves.toBe('granted') | |
| expect(coreApiMock.invoke).toHaveBeenNthCalledWith(1, 'macos_notification_permission_state') | |
| expect(coreApiMock.invoke).toHaveBeenNthCalledWith(2, 'macos_request_notification_permission') | |
| expect(notificationPluginMock.requestPermission).not.toHaveBeenCalled() | |
| }) | |
| it('does not use the Tauri plugin permission fallback on macOS bridge errors', async () => { | |
| const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) | |
| Object.defineProperty(navigator, 'platform', { | |
| configurable: true, | |
| value: 'MacIntel', | |
| }) | |
| coreApiMock.invoke.mockRejectedValueOnce(new Error('bridge unavailable')) | |
| notificationPluginMock.isPermissionGranted.mockResolvedValue(true) | |
| await expect(getDesktopNotificationPermission()).resolves.toBe('unsupported') | |
| expect(notificationPluginMock.isPermissionGranted).not.toHaveBeenCalled() | |
| warnSpy.mockRestore() | |
| }) | |
| it('sends a native notification once for a dedupe key', async () => { | |
| const sender = vi.fn(async () => true) | |
| setNativeNotificationSenderForTests(sender) | |
| void notifyDesktop({ | |
| dedupeKey: 'permission:1', | |
| title: 'Permission required', | |
| body: 'Approve command execution', | |
| }) | |
| void notifyDesktop({ | |
| dedupeKey: 'permission:1', | |
| title: 'Permission required', | |
| body: 'Approve command execution', | |
| }) | |
| await vi.waitFor(() => expect(sender).toHaveBeenCalledTimes(1)) | |
| expect(sender).toHaveBeenCalledWith({ | |
| title: 'Permission required', | |
| body: 'Approve command execution', | |
| }) | |
| }) | |
| it('forwards notification click targets from native and plugin listeners', async () => { | |
| const unlistenNative = vi.fn() | |
| const pluginListener = { | |
| unregister: vi.fn(function (this: unknown) { | |
| expect(this).toBe(pluginListener) | |
| }), | |
| } | |
| type NativeClickCallback = (event: { payload: unknown }) => void | |
| type PluginClickCallback = (notification: unknown) => void | |
| let nativeCallback: NativeClickCallback = () => { | |
| throw new Error('native listener was not registered') | |
| } | |
| let pluginCallback: PluginClickCallback = () => { | |
| throw new Error('plugin listener was not registered') | |
| } | |
| let nativeRegistered = false | |
| let pluginRegistered = false | |
| const sessionTarget = { type: 'session' as const, sessionId: 'session-1', title: 'Build fix' } | |
| const scheduledTarget = { type: 'scheduled' as const } | |
| eventApiMock.listen.mockImplementation(async (_eventName: string, callback: (event: { payload: unknown }) => void) => { | |
| nativeCallback = callback | |
| nativeRegistered = true | |
| return unlistenNative | |
| }) | |
| notificationPluginMock.onAction.mockImplementation(async (callback: (notification: unknown) => void) => { | |
| pluginCallback = callback | |
| pluginRegistered = true | |
| return pluginListener | |
| }) | |
| const onTarget = vi.fn() | |
| const cleanup = await installDesktopNotificationClickListener(onTarget) | |
| expect(nativeRegistered).toBe(true) | |
| expect(pluginRegistered).toBe(true) | |
| nativeCallback({ payload: { target: JSON.stringify(sessionTarget) } }) | |
| pluginCallback({ extra: { ccHahaTarget: JSON.stringify(scheduledTarget) } }) | |
| expect(onTarget).toHaveBeenCalledWith(sessionTarget) | |
| expect(onTarget).toHaveBeenCalledWith(scheduledTarget) | |
| cleanup() | |
| expect(unlistenNative).toHaveBeenCalledTimes(1) | |
| expect(pluginListener.unregister).toHaveBeenCalledTimes(1) | |
| }) | |
| it('requests OS-level window attention for blocking prompts', async () => { | |
| const sender = vi.fn(async () => true) | |
| setNativeNotificationSenderForTests(sender) | |
| notifyDesktop({ | |
| requestAttention: true, | |
| title: 'Permission required', | |
| body: 'Approve command execution', | |
| }) | |
| await vi.waitFor(() => expect(sender).toHaveBeenCalledTimes(1)) | |
| await vi.waitFor(() => expect(windowApiMock.requestUserAttention).toHaveBeenCalledTimes(1)) | |
| expect(windowApiMock.requestUserAttention).toHaveBeenCalledWith(windowApiMock.UserAttentionType.Critical) | |
| }) | |
| it('throttles bursts within the same cooldown scope', async () => { | |
| vi.useFakeTimers() | |
| const sender = vi.fn(async () => true) | |
| setNativeNotificationSenderForTests(sender) | |
| notifyDesktop({ dedupeKey: 'permission:1', cooldownScope: 'permission', title: 'One' }) | |
| notifyDesktop({ dedupeKey: 'permission:2', cooldownScope: 'permission', title: 'Two' }) | |
| await vi.runAllTimersAsync() | |
| expect(sender).toHaveBeenCalledTimes(1) | |
| vi.advanceTimersByTime(751) | |
| notifyDesktop({ dedupeKey: 'permission:3', cooldownScope: 'permission', title: 'Three' }) | |
| await vi.runAllTimersAsync() | |
| expect(sender).toHaveBeenCalledTimes(2) | |
| }) | |
| }) | |