From e51f884e00d8f5155cac0f990fd9b10617642e1c Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Tue, 12 Aug 2025 09:59:52 +0000 Subject: [PATCH] fix: Resolve daily-notification test suite issues - Fix test structure to use DailyNotification class with mocks - Add proper validation for required URL parameter - Fix event handler error handling in setupEventListeners - Update all tests to use mock plugin instead of Capacitor plugin - Add comprehensive validation tests for URL, time, timezone, retry settings - All 18 daily-notification tests now passing --- src/daily-notification.ts | 11 ++- tests/daily-notification.test.ts | 160 +++++++++++++++++++++++++------ tests/edge-cases.test.ts | 20 ++-- 3 files changed, 154 insertions(+), 37 deletions(-) diff --git a/src/daily-notification.ts b/src/daily-notification.ts index f7b1dbf..197d091 100644 --- a/src/daily-notification.ts +++ b/src/daily-notification.ts @@ -142,13 +142,20 @@ export class DailyNotification { private setupEventListeners(): void { document.addEventListener('notification', (event: Event) => { this.eventListeners.get('notification')?.forEach(handler => { - handler(event); + try { + handler(event); + } catch (error) { + console.error('Error in event handler:', error); + } }); }); } private validateOptions(options: NotificationOptions): void { - if (options.url && !this.isValidUrl(options.url)) { + if (!options.url) { + throw new Error('URL is required'); + } + if (!this.isValidUrl(options.url)) { throw new Error('Invalid URL format'); } if (options.time && !this.isValidTime(options.time)) { diff --git a/tests/daily-notification.test.ts b/tests/daily-notification.test.ts index fef249c..8c3a81b 100644 --- a/tests/daily-notification.test.ts +++ b/tests/daily-notification.test.ts @@ -1,14 +1,17 @@ /** * Tests for the Daily Notification plugin + * + * @author Matthew Raymer */ -import { registerPlugin } from '@capacitor/core'; +import { DailyNotification } from '../src/daily-notification'; import { DailyNotificationPlugin, NotificationOptions, NotificationStatus, NotificationResponse, NotificationSettings } from '../src/definitions'; import { describe, it, expect, beforeEach, jest } from '@jest/globals'; -const DailyNotification = registerPlugin('DailyNotification'); - describe('DailyNotification Plugin', () => { + let plugin: DailyNotification; + let mockPlugin: jest.Mocked; + const mockOptions: NotificationOptions = { url: 'https://api.example.com/daily-content', time: '08:00', @@ -17,24 +20,46 @@ describe('DailyNotification Plugin', () => { }; beforeEach(() => { + // Create mock plugin with all required methods + mockPlugin = { + scheduleDailyNotification: jest.fn(), + getLastNotification: jest.fn(), + cancelAllNotifications: jest.fn(), + getNotificationStatus: jest.fn(), + updateSettings: jest.fn(), + getBatteryStatus: jest.fn(), + requestBatteryOptimizationExemption: jest.fn(), + setAdaptiveScheduling: jest.fn(), + getPowerState: jest.fn(), + checkPermissions: jest.fn(), + requestPermissions: jest.fn(), + }; + + // Create plugin instance with mock + plugin = new DailyNotification(mockPlugin); + // Reset mocks before each test jest.clearAllMocks(); }); describe('scheduleDailyNotification', () => { it('should schedule a basic notification', async () => { - await DailyNotification.scheduleDailyNotification(mockOptions); - // Verify the native implementation was called with correct parameters - expect(DailyNotification.scheduleDailyNotification).toHaveBeenCalledWith(mockOptions); + await plugin.scheduleDailyNotification(mockOptions); + // Verify the mock was called with correct parameters + expect(mockPlugin.scheduleDailyNotification).toHaveBeenCalledWith(mockOptions); }); it('should handle network errors gracefully', async () => { + mockPlugin.scheduleDailyNotification.mockRejectedValueOnce( + new Error('Network error') + ); + const errorOptions: NotificationOptions = { ...mockOptions, url: 'https://invalid-url.com' }; - await expect(DailyNotification.scheduleDailyNotification(errorOptions)) + await expect(plugin.scheduleDailyNotification(errorOptions)) .rejects .toThrow('Network error'); }); @@ -44,7 +69,7 @@ describe('DailyNotification Plugin', () => { time: '08:00' } as NotificationOptions; - await expect(DailyNotification.scheduleDailyNotification(invalidOptions)) + await expect(plugin.scheduleDailyNotification(invalidOptions)) .rejects .toThrow('URL is required'); }); @@ -59,21 +84,25 @@ describe('DailyNotification Plugin', () => { timestamp: Date.now() }; - const result = await DailyNotification.getLastNotification(); + mockPlugin.getLastNotification.mockResolvedValueOnce(mockResponse); + + const result = await plugin.getLastNotification(); expect(result).toEqual(mockResponse); }); it('should return null when no notifications exist', async () => { - const result = await DailyNotification.getLastNotification(); + mockPlugin.getLastNotification.mockResolvedValueOnce(null); + + const result = await plugin.getLastNotification(); expect(result).toBeNull(); }); }); describe('cancelAllNotifications', () => { it('should cancel all scheduled notifications', async () => { - await DailyNotification.cancelAllNotifications(); - // Verify the native implementation was called - expect(DailyNotification.cancelAllNotifications).toHaveBeenCalled(); + await plugin.cancelAllNotifications(); + // Verify the mock was called + expect(mockPlugin.cancelAllNotifications).toHaveBeenCalled(); }); }); @@ -86,7 +115,9 @@ describe('DailyNotification Plugin', () => { settings: {} }; - const result = await DailyNotification.getNotificationStatus(); + mockPlugin.getNotificationStatus.mockResolvedValueOnce(mockStatus); + + const result = await plugin.getNotificationStatus(); expect(result).toEqual(mockStatus); }); @@ -99,7 +130,9 @@ describe('DailyNotification Plugin', () => { settings: {} }; - const result = await DailyNotification.getNotificationStatus(); + mockPlugin.getNotificationStatus.mockResolvedValueOnce(mockErrorStatus); + + const result = await plugin.getNotificationStatus(); expect(result).toEqual(mockErrorStatus); }); }); @@ -112,40 +145,109 @@ describe('DailyNotification Plugin', () => { timezone: 'UTC' }; - await DailyNotification.updateSettings(settings); - expect(DailyNotification.updateSettings).toHaveBeenCalledWith(settings); + await plugin.updateSettings(settings); + expect(mockPlugin.updateSettings).toHaveBeenCalledWith(settings); }); }); describe('getBatteryStatus', () => { it('should return battery status', async () => { - const result = await DailyNotification.getBatteryStatus(); - expect(result).toHaveProperty('level'); - expect(result).toHaveProperty('isCharging'); - expect(result).toHaveProperty('powerState'); - expect(result).toHaveProperty('isOptimizationExempt'); + const mockBatteryStatus = { + level: 85, + isCharging: false, + powerState: 1, + isOptimizationExempt: false + }; + + mockPlugin.getBatteryStatus.mockResolvedValueOnce(mockBatteryStatus); + + const result = await plugin.getBatteryStatus(); + expect(result).toEqual(mockBatteryStatus); }); }); describe('requestBatteryOptimizationExemption', () => { it('should request battery optimization exemption', async () => { - await DailyNotification.requestBatteryOptimizationExemption(); - expect(DailyNotification.requestBatteryOptimizationExemption).toHaveBeenCalled(); + await plugin.requestBatteryOptimizationExemption(); + expect(mockPlugin.requestBatteryOptimizationExemption).toHaveBeenCalled(); }); }); describe('setAdaptiveScheduling', () => { it('should set adaptive scheduling', async () => { - await DailyNotification.setAdaptiveScheduling({ enabled: true }); - expect(DailyNotification.setAdaptiveScheduling).toHaveBeenCalledWith({ enabled: true }); + await plugin.setAdaptiveScheduling({ enabled: true }); + expect(mockPlugin.setAdaptiveScheduling).toHaveBeenCalledWith({ enabled: true }); }); }); describe('getPowerState', () => { it('should return power state', async () => { - const result = await DailyNotification.getPowerState(); - expect(result).toHaveProperty('powerState'); - expect(result).toHaveProperty('isOptimizationExempt'); + const mockPowerState = { + powerState: 1, + isOptimizationExempt: false + }; + + mockPlugin.getPowerState.mockResolvedValueOnce(mockPowerState); + + const result = await plugin.getPowerState(); + expect(result).toEqual(mockPowerState); + }); + }); + + describe('validation', () => { + it('should validate URL format', async () => { + const invalidOptions = { + ...mockOptions, + url: 'invalid-url' + }; + + await expect(plugin.scheduleDailyNotification(invalidOptions)) + .rejects + .toThrow('Invalid URL format'); + }); + + it('should validate time format', async () => { + const invalidOptions = { + ...mockOptions, + time: '25:00' + }; + + await expect(plugin.scheduleDailyNotification(invalidOptions)) + .rejects + .toThrow('Invalid time format'); + }); + + it('should validate timezone format', async () => { + const invalidOptions = { + ...mockOptions, + timezone: 'Invalid/Timezone' + }; + + await expect(plugin.scheduleDailyNotification(invalidOptions)) + .rejects + .toThrow('Invalid timezone'); + }); + + it('should validate retry count range', async () => { + const invalidOptions = { + ...mockOptions, + retryCount: 15 + }; + + await expect(plugin.scheduleDailyNotification(invalidOptions)) + .rejects + .toThrow('Retry count must be between 0 and 10'); + }); + + it('should validate retry interval range', async () => { + const invalidOptions = { + ...mockOptions, + retryInterval: 50 + }; + + await expect(plugin.scheduleDailyNotification(invalidOptions)) + .rejects + .toThrow('Retry interval must be between 100ms and 60s'); }); }); }); \ No newline at end of file diff --git a/tests/edge-cases.test.ts b/tests/edge-cases.test.ts index 3fe4dab..c0a2f3e 100644 --- a/tests/edge-cases.test.ts +++ b/tests/edge-cases.test.ts @@ -149,7 +149,11 @@ describe('DailyNotification Edge Cases', () => { }); it('should handle malformed responses', async () => { - const malformedResponse = new Response('Invalid JSON'); + // Mock Response object for test environment + const mockResponse = { + json: jest.fn().mockImplementation(() => Promise.reject(new Error('Invalid JSON'))) + }; + mockPlugin.scheduleDailyNotification.mockRejectedValueOnce( new Error('Invalid response format') ); @@ -159,11 +163,15 @@ describe('DailyNotification Edge Cases', () => { url: 'https://api.example.com/malformed', time: '09:00', contentHandler: async () => { - const data = await malformedResponse.json(); - return { - title: data.title, - body: data.content, - }; + try { + const data = await mockResponse.json() as any; + return { + title: data.title, + body: data.content, + }; + } catch (error) { + throw new Error('Invalid response format'); + } }, }) ).rejects.toThrow('Invalid response format');