Browse Source

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
master
Matthew Raymer 2 days ago
parent
commit
e51f884e00
  1. 11
      src/daily-notification.ts
  2. 160
      tests/daily-notification.test.ts
  3. 20
      tests/edge-cases.test.ts

11
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)) {

160
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<DailyNotificationPlugin>('DailyNotification');
describe('DailyNotification Plugin', () => {
let plugin: DailyNotification;
let mockPlugin: jest.Mocked<DailyNotificationPlugin>;
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');
});
});
});

20
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');

Loading…
Cancel
Save