desktop-notifications.test.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. /**
  2. * @fileoverview Tests for desktop notification system
  3. * @author GrabZilla Development Team
  4. * @version 2.1.0
  5. */
  6. import { describe, it, expect, beforeEach, vi } from 'vitest'
  7. import { JSDOM } from 'jsdom'
  8. // Mock Electron API
  9. const mockElectronAPI = {
  10. showNotification: vi.fn(),
  11. showErrorDialog: vi.fn(),
  12. showInfoDialog: vi.fn(),
  13. selectSaveDirectory: vi.fn(),
  14. selectCookieFile: vi.fn(),
  15. checkBinaryDependencies: vi.fn()
  16. }
  17. // Mock Notification API
  18. const mockNotification = vi.fn()
  19. mockNotification.isSupported = vi.fn(() => true)
  20. describe('Desktop Notification System', () => {
  21. let dom
  22. let window
  23. let document
  24. let DesktopNotificationManager
  25. let notificationManager
  26. let NOTIFICATION_TYPES
  27. beforeEach(() => {
  28. // Set up DOM environment
  29. dom = new JSDOM(`
  30. <!DOCTYPE html>
  31. <html>
  32. <body>
  33. <div id="app"></div>
  34. </body>
  35. </html>
  36. `, {
  37. url: 'http://localhost',
  38. pretendToBeVisual: true,
  39. resources: 'usable'
  40. })
  41. window = dom.window
  42. document = window.document
  43. global.window = window
  44. global.document = document
  45. // Mock APIs - ensure electronAPI is properly detected as an object
  46. window.electronAPI = mockElectronAPI
  47. window.Notification = mockNotification
  48. // Ensure electronAPI is detected as available
  49. Object.defineProperty(window, 'electronAPI', {
  50. value: mockElectronAPI,
  51. writable: true,
  52. enumerable: true,
  53. configurable: true
  54. })
  55. // Load the notification manager
  56. const fs = require('fs')
  57. const path = require('path')
  58. const notificationScript = fs.readFileSync(
  59. path.join(__dirname, '../scripts/utils/desktop-notifications.js'),
  60. 'utf8'
  61. )
  62. // Execute the script in the window context
  63. const script = new window.Function(notificationScript)
  64. script.call(window)
  65. DesktopNotificationManager = window.DesktopNotificationManager
  66. notificationManager = window.notificationManager
  67. NOTIFICATION_TYPES = window.NOTIFICATION_TYPES
  68. })
  69. describe('DesktopNotificationManager', () => {
  70. it('should initialize with correct default values', () => {
  71. const manager = new DesktopNotificationManager()
  72. expect(manager.activeNotifications).toBeInstanceOf(Map)
  73. expect(manager.notificationQueue).toEqual([])
  74. expect(manager.maxActiveNotifications).toBe(5)
  75. })
  76. it('should detect Electron availability correctly', () => {
  77. // Create a new manager to test the detection logic
  78. const manager = new DesktopNotificationManager()
  79. expect(manager.isElectronAvailable).toBe(true)
  80. expect(typeof window.electronAPI).toBe('object')
  81. })
  82. it('should show success notification for downloads', async () => {
  83. mockElectronAPI.showNotification.mockResolvedValue({ success: true })
  84. const result = await notificationManager.showDownloadSuccess('test-video.mp4')
  85. expect(result.success).toBe(true)
  86. expect(mockElectronAPI.showNotification).toHaveBeenCalledWith(
  87. expect.objectContaining({
  88. title: 'Download Complete',
  89. message: 'Successfully downloaded: test-video.mp4'
  90. })
  91. )
  92. })
  93. it('should show error notification for failed downloads', async () => {
  94. mockElectronAPI.showNotification.mockResolvedValue({ success: true })
  95. const result = await notificationManager.showDownloadError(
  96. 'test-video.mp4',
  97. 'Network error'
  98. )
  99. expect(result.success).toBe(true)
  100. expect(mockElectronAPI.showNotification).toHaveBeenCalledWith(
  101. expect.objectContaining({
  102. title: 'Download Failed',
  103. message: 'Failed to download test-video.mp4: Network error'
  104. })
  105. )
  106. })
  107. it('should show progress notification', async () => {
  108. mockElectronAPI.showNotification.mockResolvedValue({ success: true })
  109. const result = await notificationManager.showDownloadProgress('test-video.mp4', 65)
  110. expect(result.success).toBe(true)
  111. expect(mockElectronAPI.showNotification).toHaveBeenCalledWith(
  112. expect.objectContaining({
  113. title: 'Downloading...',
  114. message: 'test-video.mp4 - 65% complete'
  115. })
  116. )
  117. })
  118. it('should fallback to in-app notifications when Electron fails', async () => {
  119. // Mock console.error to suppress expected error messages during testing
  120. const originalConsoleError = console.error
  121. console.error = vi.fn()
  122. mockElectronAPI.showNotification.mockRejectedValue(new Error('Electron error'))
  123. let eventFired = false
  124. document.addEventListener('app-notification', () => {
  125. eventFired = true
  126. })
  127. const result = await notificationManager.showNotification({
  128. title: 'Test',
  129. message: 'Test message'
  130. })
  131. expect(result.success).toBe(true)
  132. expect(result.method).toBe('in-app')
  133. expect(eventFired).toBe(true)
  134. // Restore console.error
  135. console.error = originalConsoleError
  136. })
  137. it('should track active notifications', async () => {
  138. mockElectronAPI.showNotification.mockResolvedValue({ success: true })
  139. await notificationManager.showNotification({
  140. id: 'test-notification',
  141. title: 'Test',
  142. message: 'Test message'
  143. })
  144. expect(notificationManager.activeNotifications.has('test-notification')).toBe(true)
  145. })
  146. it('should close specific notifications', async () => {
  147. mockElectronAPI.showNotification.mockResolvedValue({ success: true })
  148. await notificationManager.showNotification({
  149. id: 'test-notification',
  150. title: 'Test',
  151. message: 'Test message'
  152. })
  153. notificationManager.closeNotification('test-notification')
  154. expect(notificationManager.activeNotifications.has('test-notification')).toBe(false)
  155. })
  156. it('should generate unique notification IDs', () => {
  157. const id1 = notificationManager.generateNotificationId()
  158. const id2 = notificationManager.generateNotificationId()
  159. expect(id1).not.toBe(id2)
  160. expect(id1).toMatch(/^notification_\d+_[a-z0-9]+$/)
  161. })
  162. it('should provide notification statistics', () => {
  163. const stats = notificationManager.getStats()
  164. expect(stats).toHaveProperty('active')
  165. expect(stats).toHaveProperty('electronAvailable')
  166. expect(stats).toHaveProperty('browserSupported')
  167. expect(typeof stats.electronAvailable).toBe('boolean')
  168. expect(typeof stats.browserSupported).toBe('boolean')
  169. })
  170. })
  171. describe('Error Handling Integration', () => {
  172. it('should show dependency missing notifications', async () => {
  173. mockElectronAPI.showNotification.mockResolvedValue({ success: true })
  174. const result = await notificationManager.showDependencyMissing('yt-dlp')
  175. expect(result.success).toBe(true)
  176. expect(mockElectronAPI.showNotification).toHaveBeenCalledWith(
  177. expect.objectContaining({
  178. title: 'Missing Dependency',
  179. message: expect.stringContaining('yt-dlp')
  180. })
  181. )
  182. })
  183. it('should handle conversion progress notifications', async () => {
  184. mockElectronAPI.showNotification.mockResolvedValue({ success: true })
  185. const result = await notificationManager.showConversionProgress('test-video.mp4', 42)
  186. expect(result.success).toBe(true)
  187. expect(mockElectronAPI.showNotification).toHaveBeenCalledWith(
  188. expect.objectContaining({
  189. title: 'Converting...',
  190. message: 'test-video.mp4 - 42% converted'
  191. })
  192. )
  193. })
  194. })
  195. describe('Notification Types', () => {
  196. it('should have all required notification types', () => {
  197. expect(NOTIFICATION_TYPES).toHaveProperty('SUCCESS')
  198. expect(NOTIFICATION_TYPES).toHaveProperty('ERROR')
  199. expect(NOTIFICATION_TYPES).toHaveProperty('WARNING')
  200. expect(NOTIFICATION_TYPES).toHaveProperty('INFO')
  201. expect(NOTIFICATION_TYPES).toHaveProperty('PROGRESS')
  202. })
  203. it('should have correct configuration for each type', () => {
  204. expect(NOTIFICATION_TYPES.SUCCESS.color).toBe('#00a63e')
  205. expect(NOTIFICATION_TYPES.ERROR.color).toBe('#e7000b')
  206. expect(NOTIFICATION_TYPES.SUCCESS.sound).toBe(true)
  207. expect(NOTIFICATION_TYPES.PROGRESS.timeout).toBe(0)
  208. })
  209. })
  210. })