refactor: improve build configuration and code organization
- Add build scripts for Android and iOS platforms - Remove duplicate web implementation (src/web.ts) - Add proper TypeScript configuration - Add module documentation to index.ts - Clean up package.json scripts This commit improves the project structure and build process by: 1. Adding dedicated build scripts for native platforms 2. Removing redundant web implementation 3. Adding proper TypeScript configuration with strict mode 4. Improving code documentation 5. Organizing package.json scripts The changes maintain backward compatibility while improving the development experience and code quality.
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
|
||||
import { DailyNotificationPlugin, NotificationEvent } from '../src/definitions';
|
||||
import { DailyNotification } from '../src/daily-notification';
|
||||
|
||||
describe('DailyNotification Advanced Scenarios', () => {
|
||||
let plugin: DailyNotification;
|
||||
let mockPlugin: jest.Mocked<DailyNotificationPlugin>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockPlugin = {
|
||||
scheduleDailyNotification: jest.fn(),
|
||||
getLastNotification: jest.fn(),
|
||||
cancelAllNotifications: jest.fn(),
|
||||
getNotificationStatus: jest.fn(),
|
||||
updateSettings: jest.fn(),
|
||||
checkPermissions: jest.fn(),
|
||||
requestPermissions: jest.fn(),
|
||||
};
|
||||
plugin = new DailyNotification(mockPlugin);
|
||||
});
|
||||
|
||||
describe('Multiple Schedules', () => {
|
||||
it('should handle multiple notification schedules', async () => {
|
||||
const schedules = [
|
||||
{ time: '09:00', title: 'Morning Update' },
|
||||
{ time: '12:00', title: 'Lunch Update' },
|
||||
{ time: '18:00', title: 'Evening Update' },
|
||||
];
|
||||
|
||||
for (const schedule of schedules) {
|
||||
await plugin.scheduleDailyNotification({
|
||||
url: 'https://api.example.com/updates',
|
||||
time: schedule.time,
|
||||
title: schedule.title,
|
||||
});
|
||||
}
|
||||
|
||||
expect(mockPlugin.scheduleDailyNotification).toHaveBeenCalledTimes(3);
|
||||
expect(mockPlugin.scheduleDailyNotification).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
time: '09:00',
|
||||
title: 'Morning Update',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle schedule conflicts', async () => {
|
||||
await plugin.scheduleDailyNotification({
|
||||
url: 'https://api.example.com/updates',
|
||||
time: '09:00',
|
||||
});
|
||||
|
||||
await expect(
|
||||
plugin.scheduleDailyNotification({
|
||||
url: 'https://api.example.com/updates',
|
||||
time: '09:00',
|
||||
})
|
||||
).rejects.toThrow('Notification already scheduled for this time');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Timezone Handling', () => {
|
||||
it('should handle timezone changes', async () => {
|
||||
const options = {
|
||||
url: 'https://api.example.com/updates',
|
||||
time: '09:00',
|
||||
timezone: 'America/New_York',
|
||||
};
|
||||
|
||||
await plugin.scheduleDailyNotification(options);
|
||||
expect(mockPlugin.scheduleDailyNotification).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
timezone: 'America/New_York',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle invalid timezone', async () => {
|
||||
await expect(
|
||||
plugin.scheduleDailyNotification({
|
||||
url: 'https://api.example.com/updates',
|
||||
time: '09:00',
|
||||
timezone: 'Invalid/Timezone',
|
||||
})
|
||||
).rejects.toThrow('Invalid timezone');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Offline Support', () => {
|
||||
it('should handle offline scenarios', async () => {
|
||||
const options = {
|
||||
url: 'https://api.example.com/updates',
|
||||
time: '09:00',
|
||||
offlineFallback: true,
|
||||
cacheDuration: 3600,
|
||||
};
|
||||
|
||||
await plugin.scheduleDailyNotification(options);
|
||||
expect(mockPlugin.scheduleDailyNotification).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
offlineFallback: true,
|
||||
cacheDuration: 3600,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle network errors gracefully', async () => {
|
||||
mockPlugin.getLastNotification.mockRejectedValueOnce(
|
||||
new Error('Network error')
|
||||
);
|
||||
|
||||
await expect(plugin.getLastNotification()).rejects.toThrow('Network error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Retry Logic', () => {
|
||||
it('should implement retry with exponential backoff', async () => {
|
||||
const options = {
|
||||
url: 'https://api.example.com/updates',
|
||||
time: '09:00',
|
||||
retryCount: 3,
|
||||
retryInterval: 1000,
|
||||
};
|
||||
|
||||
await plugin.scheduleDailyNotification(options);
|
||||
expect(mockPlugin.scheduleDailyNotification).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
retryCount: 3,
|
||||
retryInterval: 1000,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle max retries exceeded', async () => {
|
||||
mockPlugin.getLastNotification.mockRejectedValueOnce(
|
||||
new Error('Max retries exceeded')
|
||||
);
|
||||
|
||||
await expect(plugin.getLastNotification()).rejects.toThrow(
|
||||
'Max retries exceeded'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Event Handling', () => {
|
||||
it('should handle notification events', async () => {
|
||||
const event = new Event('notification') as NotificationEvent;
|
||||
event.detail = {
|
||||
id: 'test-id',
|
||||
action: 'click',
|
||||
data: { url: 'https://example.com' },
|
||||
};
|
||||
|
||||
const handler = jest.fn();
|
||||
plugin.on('notification', handler);
|
||||
document.dispatchEvent(event);
|
||||
|
||||
expect(handler).toHaveBeenCalledWith(event);
|
||||
});
|
||||
|
||||
it('should handle multiple event listeners', async () => {
|
||||
const event = new Event('notification') as NotificationEvent;
|
||||
event.detail = {
|
||||
id: 'test-id',
|
||||
action: 'click',
|
||||
};
|
||||
|
||||
const handler1 = jest.fn();
|
||||
const handler2 = jest.fn();
|
||||
plugin.on('notification', handler1);
|
||||
plugin.on('notification', handler2);
|
||||
document.dispatchEvent(event);
|
||||
|
||||
expect(handler1).toHaveBeenCalledWith(event);
|
||||
expect(handler2).toHaveBeenCalledWith(event);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Settings Management', () => {
|
||||
it('should handle settings updates', async () => {
|
||||
const settings = {
|
||||
time: '10:00',
|
||||
sound: true,
|
||||
priority: 'high' as const,
|
||||
timezone: 'America/New_York',
|
||||
};
|
||||
|
||||
await plugin.updateSettings(settings);
|
||||
expect(mockPlugin.updateSettings).toHaveBeenCalledWith(settings);
|
||||
});
|
||||
|
||||
it('should validate settings before update', async () => {
|
||||
await expect(
|
||||
plugin.updateSettings({
|
||||
time: 'invalid-time',
|
||||
sound: true,
|
||||
})
|
||||
).rejects.toThrow('Invalid time format');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* Tests for the Daily Notification plugin
|
||||
*/
|
||||
|
||||
import { registerPlugin } from '@capacitor/core';
|
||||
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', () => {
|
||||
const mockOptions: NotificationOptions = {
|
||||
url: 'https://api.example.com/daily-content',
|
||||
time: '08:00',
|
||||
title: 'Test Notification',
|
||||
body: 'This is a test notification'
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// 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);
|
||||
});
|
||||
|
||||
it('should handle network errors gracefully', async () => {
|
||||
const errorOptions: NotificationOptions = {
|
||||
...mockOptions,
|
||||
url: 'https://invalid-url.com'
|
||||
};
|
||||
|
||||
await expect(DailyNotification.scheduleDailyNotification(errorOptions))
|
||||
.rejects
|
||||
.toThrow('Network error');
|
||||
});
|
||||
|
||||
it('should validate required parameters', async () => {
|
||||
const invalidOptions = {
|
||||
time: '08:00'
|
||||
} as NotificationOptions;
|
||||
|
||||
await expect(DailyNotification.scheduleDailyNotification(invalidOptions))
|
||||
.rejects
|
||||
.toThrow('URL is required');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLastNotification', () => {
|
||||
it('should return the last notification', async () => {
|
||||
const mockResponse: NotificationResponse = {
|
||||
title: 'Last Notification',
|
||||
body: 'This was the last notification',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
const result = await DailyNotification.getLastNotification();
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
it('should return null when no notifications exist', async () => {
|
||||
const result = await DailyNotification.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();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getNotificationStatus', () => {
|
||||
it('should return current notification status', async () => {
|
||||
const mockStatus: NotificationStatus = {
|
||||
isScheduled: true,
|
||||
nextNotificationTime: '2024-03-20T08:00:00Z',
|
||||
lastNotificationTime: '2024-03-19T08:00:00Z'
|
||||
};
|
||||
|
||||
const result = await DailyNotification.getNotificationStatus();
|
||||
expect(result).toEqual(mockStatus);
|
||||
});
|
||||
|
||||
it('should handle error status', async () => {
|
||||
const mockErrorStatus: NotificationStatus = {
|
||||
isScheduled: false,
|
||||
error: 'Failed to schedule notification'
|
||||
};
|
||||
|
||||
const result = await DailyNotification.getNotificationStatus();
|
||||
expect(result).toEqual(mockErrorStatus);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateSettings', () => {
|
||||
it('should update notification settings', async () => {
|
||||
const settings: NotificationSettings = {
|
||||
time: '09:00',
|
||||
sound: false,
|
||||
priority: 'normal'
|
||||
};
|
||||
|
||||
await DailyNotification.updateSettings(settings);
|
||||
// Verify the native implementation was called with correct parameters
|
||||
expect(DailyNotification.updateSettings).toHaveBeenCalledWith(settings);
|
||||
});
|
||||
|
||||
it('should validate settings before updating', async () => {
|
||||
const invalidSettings = {
|
||||
time: 'invalid-time'
|
||||
};
|
||||
|
||||
await expect(DailyNotification.updateSettings(invalidSettings))
|
||||
.rejects
|
||||
.toThrow('Invalid time format');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Integration Tests', () => {
|
||||
it('should handle full notification lifecycle', async () => {
|
||||
// Schedule notification
|
||||
await DailyNotification.scheduleDailyNotification(mockOptions);
|
||||
|
||||
// Check status
|
||||
const status = await DailyNotification.getNotificationStatus();
|
||||
expect(status.isScheduled).toBe(true);
|
||||
|
||||
// Update settings
|
||||
const settings: NotificationSettings = {
|
||||
time: '09:00',
|
||||
priority: 'normal'
|
||||
};
|
||||
await DailyNotification.updateSettings(settings);
|
||||
|
||||
// Verify update
|
||||
const updatedStatus = await DailyNotification.getNotificationStatus();
|
||||
expect(updatedStatus.nextNotificationTime).toContain('09:00');
|
||||
|
||||
// Cancel notifications
|
||||
await DailyNotification.cancelAllNotifications();
|
||||
|
||||
// Verify cancellation
|
||||
const finalStatus = await DailyNotification.getNotificationStatus();
|
||||
expect(finalStatus.isScheduled).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,326 @@
|
||||
/**
|
||||
* Edge case and error scenario tests for the Daily Notification plugin
|
||||
* Tests unusual conditions and error handling
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
|
||||
import { DailyNotificationPlugin, NotificationEvent } from '../src/definitions';
|
||||
import { DailyNotification } from '../src/daily-notification';
|
||||
|
||||
describe('DailyNotification Edge Cases', () => {
|
||||
let plugin: DailyNotification;
|
||||
let mockPlugin: jest.Mocked<DailyNotificationPlugin>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockPlugin = {
|
||||
scheduleDailyNotification: jest.fn(),
|
||||
getLastNotification: jest.fn(),
|
||||
cancelAllNotifications: jest.fn(),
|
||||
getNotificationStatus: jest.fn(),
|
||||
updateSettings: jest.fn(),
|
||||
checkPermissions: jest.fn(),
|
||||
requestPermissions: jest.fn(),
|
||||
};
|
||||
plugin = new DailyNotification(mockPlugin);
|
||||
});
|
||||
|
||||
describe('Time Format Edge Cases', () => {
|
||||
it('should handle 24-hour time format edge cases', async () => {
|
||||
const edgeCases = [
|
||||
'00:00', // Midnight
|
||||
'23:59', // End of day
|
||||
'12:00', // Noon
|
||||
'13:00', // 1 PM
|
||||
];
|
||||
|
||||
for (const time of edgeCases) {
|
||||
await plugin.scheduleDailyNotification({
|
||||
url: 'https://api.example.com/time-test',
|
||||
time,
|
||||
});
|
||||
}
|
||||
|
||||
expect(mockPlugin.scheduleDailyNotification).toHaveBeenCalledTimes(4);
|
||||
expect(mockPlugin.scheduleDailyNotification).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
time: '00:00',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject invalid time formats', async () => {
|
||||
const invalidTimes = [
|
||||
'24:00', // Invalid hour
|
||||
'12:60', // Invalid minutes
|
||||
'9:00', // Missing leading zero
|
||||
'13:5', // Missing trailing zero
|
||||
'25:00', // Hour > 24
|
||||
'12:61', // Minutes > 60
|
||||
];
|
||||
|
||||
for (const time of invalidTimes) {
|
||||
await expect(
|
||||
plugin.scheduleDailyNotification({
|
||||
url: 'https://api.example.com/invalid-time',
|
||||
time,
|
||||
})
|
||||
).rejects.toThrow('Invalid time format');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Timezone Edge Cases', () => {
|
||||
it('should handle timezone transitions', async () => {
|
||||
const timezones = [
|
||||
'America/New_York', // DST transitions
|
||||
'Europe/London', // DST transitions
|
||||
'Asia/Kolkata', // No DST
|
||||
'Pacific/Auckland', // DST transitions
|
||||
];
|
||||
|
||||
for (const timezone of timezones) {
|
||||
await plugin.scheduleDailyNotification({
|
||||
url: 'https://api.example.com/timezone-test',
|
||||
time: '09:00',
|
||||
timezone,
|
||||
});
|
||||
}
|
||||
|
||||
expect(mockPlugin.scheduleDailyNotification).toHaveBeenCalledTimes(4);
|
||||
expect(mockPlugin.scheduleDailyNotification).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
timezone: 'America/New_York',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle invalid timezone formats', async () => {
|
||||
const invalidTimezones = [
|
||||
'Invalid/Timezone',
|
||||
'America/Invalid',
|
||||
'Europe/Invalid',
|
||||
'Asia/Invalid',
|
||||
];
|
||||
|
||||
for (const timezone of invalidTimezones) {
|
||||
await expect(
|
||||
plugin.scheduleDailyNotification({
|
||||
url: 'https://api.example.com/invalid-timezone',
|
||||
time: '09:00',
|
||||
timezone,
|
||||
})
|
||||
).rejects.toThrow('Invalid timezone');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Network Edge Cases', () => {
|
||||
it('should handle network timeouts', async () => {
|
||||
mockPlugin.scheduleDailyNotification.mockRejectedValueOnce(
|
||||
new Error('Network timeout')
|
||||
);
|
||||
|
||||
await expect(
|
||||
plugin.scheduleDailyNotification({
|
||||
url: 'https://api.example.com/timeout',
|
||||
time: '09:00',
|
||||
retryCount: 3,
|
||||
retryInterval: 1000,
|
||||
})
|
||||
).rejects.toThrow('Network timeout');
|
||||
});
|
||||
|
||||
it('should handle offline scenarios', async () => {
|
||||
mockPlugin.scheduleDailyNotification.mockRejectedValueOnce(
|
||||
new Error('No internet connection')
|
||||
);
|
||||
|
||||
await expect(
|
||||
plugin.scheduleDailyNotification({
|
||||
url: 'https://api.example.com/offline',
|
||||
time: '09:00',
|
||||
offlineFallback: true,
|
||||
})
|
||||
).rejects.toThrow('No internet connection');
|
||||
});
|
||||
|
||||
it('should handle malformed responses', async () => {
|
||||
const malformedResponse = new Response('Invalid JSON');
|
||||
mockPlugin.scheduleDailyNotification.mockRejectedValueOnce(
|
||||
new Error('Invalid response format')
|
||||
);
|
||||
|
||||
await expect(
|
||||
plugin.scheduleDailyNotification({
|
||||
url: 'https://api.example.com/malformed',
|
||||
time: '09:00',
|
||||
contentHandler: async () => {
|
||||
const data = await malformedResponse.json();
|
||||
return {
|
||||
title: data.title,
|
||||
body: data.content,
|
||||
};
|
||||
},
|
||||
})
|
||||
).rejects.toThrow('Invalid response format');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Content Handler Edge Cases', () => {
|
||||
it('should handle content handler timeouts', async () => {
|
||||
const slowHandler = async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
return {
|
||||
title: 'Slow Content',
|
||||
body: 'This took too long',
|
||||
};
|
||||
};
|
||||
|
||||
await expect(
|
||||
plugin.scheduleDailyNotification({
|
||||
url: 'https://api.example.com/slow',
|
||||
time: '09:00',
|
||||
contentHandler: slowHandler,
|
||||
})
|
||||
).rejects.toThrow('Content handler timeout');
|
||||
});
|
||||
|
||||
it('should handle invalid content handler responses', async () => {
|
||||
const invalidHandler = async () => {
|
||||
return {
|
||||
title: 'Invalid Content',
|
||||
body: 'Missing required data',
|
||||
// Missing required fields
|
||||
data: { timestamp: new Date().toISOString() },
|
||||
};
|
||||
};
|
||||
|
||||
await expect(
|
||||
plugin.scheduleDailyNotification({
|
||||
url: 'https://api.example.com/invalid-content',
|
||||
time: '09:00',
|
||||
contentHandler: invalidHandler,
|
||||
})
|
||||
).rejects.toThrow('Invalid content handler response');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Event Handling Edge Cases', () => {
|
||||
it('should handle multiple event listeners for same event', async () => {
|
||||
const event = new Event('notification') as NotificationEvent;
|
||||
event.detail = {
|
||||
id: 'test-id',
|
||||
action: 'delivered',
|
||||
};
|
||||
|
||||
const handlers = [jest.fn(), jest.fn(), jest.fn()];
|
||||
handlers.forEach(handler => plugin.on('notification', handler));
|
||||
document.dispatchEvent(event);
|
||||
|
||||
handlers.forEach(handler => {
|
||||
expect(handler).toHaveBeenCalledWith(event);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle event listener removal', async () => {
|
||||
const event = new Event('notification') as NotificationEvent;
|
||||
event.detail = {
|
||||
id: 'test-id',
|
||||
action: 'delivered',
|
||||
};
|
||||
|
||||
const handler = jest.fn();
|
||||
plugin.on('notification', handler);
|
||||
plugin.off('notification', handler);
|
||||
document.dispatchEvent(event);
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle event listener errors gracefully', async () => {
|
||||
const event = new Event('notification') as NotificationEvent;
|
||||
event.detail = {
|
||||
id: 'test-id',
|
||||
action: 'delivered',
|
||||
};
|
||||
|
||||
const errorHandler = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Handler error');
|
||||
});
|
||||
|
||||
plugin.on('notification', errorHandler);
|
||||
document.dispatchEvent(event);
|
||||
|
||||
expect(errorHandler).toHaveBeenCalledWith(event);
|
||||
// Error should be caught and logged, not thrown
|
||||
});
|
||||
});
|
||||
|
||||
describe('Resource Management Edge Cases', () => {
|
||||
it('should handle memory leaks from event listeners', async () => {
|
||||
const event = new Event('notification') as NotificationEvent;
|
||||
event.detail = {
|
||||
id: 'test-id',
|
||||
action: 'delivered',
|
||||
};
|
||||
|
||||
// Add many event listeners
|
||||
const handlers = Array(1000).fill(null).map(() => jest.fn());
|
||||
handlers.forEach(handler => plugin.on('notification', handler));
|
||||
|
||||
// Remove all listeners
|
||||
handlers.forEach(handler => plugin.off('notification', handler));
|
||||
|
||||
// Trigger event
|
||||
document.dispatchEvent(event);
|
||||
|
||||
// Verify no handlers were called
|
||||
handlers.forEach(handler => {
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle concurrent notification scheduling', async () => {
|
||||
const notifications = Array(10).fill(null).map((_, i) => ({
|
||||
url: `https://api.example.com/concurrent-${i}`,
|
||||
time: '09:00',
|
||||
}));
|
||||
|
||||
const promises = notifications.map(notification =>
|
||||
plugin.scheduleDailyNotification(notification)
|
||||
);
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
expect(mockPlugin.scheduleDailyNotification).toHaveBeenCalledTimes(10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Platform-Specific Edge Cases', () => {
|
||||
it('should handle platform-specific permission denials', async () => {
|
||||
mockPlugin.scheduleDailyNotification.mockRejectedValueOnce(
|
||||
new Error('Permission denied')
|
||||
);
|
||||
|
||||
await expect(
|
||||
plugin.scheduleDailyNotification({
|
||||
url: 'https://api.example.com/permission-denied',
|
||||
time: '09:00',
|
||||
})
|
||||
).rejects.toThrow('Permission denied');
|
||||
});
|
||||
|
||||
it('should handle platform-specific background restrictions', async () => {
|
||||
mockPlugin.scheduleDailyNotification.mockRejectedValueOnce(
|
||||
new Error('Background execution not allowed')
|
||||
);
|
||||
|
||||
await expect(
|
||||
plugin.scheduleDailyNotification({
|
||||
url: 'https://api.example.com/background-restricted',
|
||||
time: '09:00',
|
||||
})
|
||||
).rejects.toThrow('Background execution not allowed');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,288 @@
|
||||
/**
|
||||
* Enterprise-level test scenarios for the Daily Notification plugin
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
|
||||
import { DailyNotificationPlugin, NotificationEvent } from '../src/definitions';
|
||||
import { DailyNotification } from '../src/daily-notification';
|
||||
|
||||
describe('DailyNotification Enterprise Scenarios', () => {
|
||||
let plugin: DailyNotification;
|
||||
let mockPlugin: jest.Mocked<DailyNotificationPlugin>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockPlugin = {
|
||||
scheduleDailyNotification: jest.fn(),
|
||||
getLastNotification: jest.fn(),
|
||||
cancelAllNotifications: jest.fn(),
|
||||
getNotificationStatus: jest.fn(),
|
||||
updateSettings: jest.fn(),
|
||||
checkPermissions: jest.fn(),
|
||||
requestPermissions: jest.fn(),
|
||||
};
|
||||
plugin = new DailyNotification(mockPlugin);
|
||||
});
|
||||
|
||||
describe('Notification Queue System', () => {
|
||||
it('should process notifications in queue order', async () => {
|
||||
const notifications = [
|
||||
{ time: '09:00', title: 'First Update' },
|
||||
{ time: '10:00', title: 'Second Update' },
|
||||
{ time: '11:00', title: 'Third Update' },
|
||||
];
|
||||
|
||||
for (const notification of notifications) {
|
||||
await plugin.scheduleDailyNotification({
|
||||
url: 'https://api.example.com/queue',
|
||||
time: notification.time,
|
||||
title: notification.title,
|
||||
});
|
||||
}
|
||||
|
||||
expect(mockPlugin.scheduleDailyNotification).toHaveBeenCalledTimes(3);
|
||||
expect(mockPlugin.scheduleDailyNotification).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({
|
||||
time: '09:00',
|
||||
title: 'First Update',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle queue processing errors gracefully', async () => {
|
||||
mockPlugin.scheduleDailyNotification.mockRejectedValueOnce(
|
||||
new Error('Processing error')
|
||||
);
|
||||
|
||||
await expect(
|
||||
plugin.scheduleDailyNotification({
|
||||
url: 'https://api.example.com/error',
|
||||
time: '09:00',
|
||||
})
|
||||
).rejects.toThrow('Processing error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('A/B Testing', () => {
|
||||
it('should schedule different notification variants', async () => {
|
||||
const variants = [
|
||||
{ title: 'Variant A', priority: 'high' as const },
|
||||
{ title: 'Variant B', priority: 'normal' as const },
|
||||
];
|
||||
|
||||
for (const variant of variants) {
|
||||
await plugin.scheduleDailyNotification({
|
||||
url: 'https://api.example.com/ab-test',
|
||||
time: '09:00',
|
||||
...variant,
|
||||
});
|
||||
}
|
||||
|
||||
expect(mockPlugin.scheduleDailyNotification).toHaveBeenCalledTimes(2);
|
||||
expect(mockPlugin.scheduleDailyNotification).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
title: 'Variant A',
|
||||
priority: 'high',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should track variant data correctly', async () => {
|
||||
const variant = {
|
||||
title: 'Test Variant',
|
||||
data: { variant: 'A', timestamp: new Date().toISOString() },
|
||||
};
|
||||
|
||||
await plugin.scheduleDailyNotification({
|
||||
url: 'https://api.example.com/ab-test',
|
||||
time: '09:00',
|
||||
...variant,
|
||||
});
|
||||
|
||||
expect(mockPlugin.scheduleDailyNotification).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: variant.data,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Analytics Tracking', () => {
|
||||
it('should track notification delivery events', async () => {
|
||||
const event = new Event('notification') as NotificationEvent;
|
||||
event.detail = {
|
||||
id: 'test-id',
|
||||
action: 'delivered',
|
||||
data: { timestamp: new Date().toISOString() },
|
||||
};
|
||||
|
||||
const handler = jest.fn();
|
||||
plugin.on('notification', handler);
|
||||
document.dispatchEvent(event);
|
||||
|
||||
expect(handler).toHaveBeenCalledWith(event);
|
||||
});
|
||||
|
||||
it('should track notification interaction events', async () => {
|
||||
const event = new Event('notification_clicked') as NotificationEvent;
|
||||
event.detail = {
|
||||
id: 'test-id',
|
||||
action: 'clicked',
|
||||
data: { url: 'https://example.com' },
|
||||
};
|
||||
|
||||
const handler = jest.fn();
|
||||
document.addEventListener('notification_clicked', handler);
|
||||
document.dispatchEvent(event);
|
||||
|
||||
expect(handler).toHaveBeenCalledWith(event);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Preferences Management', () => {
|
||||
it('should update multiple notification settings', async () => {
|
||||
const settings = {
|
||||
time: '10:00',
|
||||
priority: 'high' as const,
|
||||
sound: true,
|
||||
timezone: 'America/New_York',
|
||||
};
|
||||
|
||||
await plugin.updateSettings(settings);
|
||||
expect(mockPlugin.updateSettings).toHaveBeenCalledWith(settings);
|
||||
});
|
||||
|
||||
it('should handle category-based notifications', async () => {
|
||||
const categories = ['news', 'weather', 'tasks'];
|
||||
|
||||
for (const category of categories) {
|
||||
await plugin.scheduleDailyNotification({
|
||||
url: `https://api.example.com/categories/${category}`,
|
||||
time: '09:00',
|
||||
title: `${category} Update`,
|
||||
});
|
||||
}
|
||||
|
||||
expect(mockPlugin.scheduleDailyNotification).toHaveBeenCalledTimes(3);
|
||||
expect(mockPlugin.scheduleDailyNotification).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
title: 'news Update',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Content Personalization', () => {
|
||||
it('should handle personalized notification content', async () => {
|
||||
const profile = {
|
||||
id: 'user123',
|
||||
name: 'John Doe',
|
||||
preferences: {
|
||||
language: 'en-US',
|
||||
timezone: 'America/New_York',
|
||||
categories: ['news'],
|
||||
},
|
||||
};
|
||||
|
||||
await plugin.scheduleDailyNotification({
|
||||
url: 'https://api.example.com/personalized',
|
||||
time: '09:00',
|
||||
title: `Good morning, ${profile.name}!`,
|
||||
timezone: profile.preferences.timezone,
|
||||
headers: {
|
||||
'X-User-ID': profile.id,
|
||||
'X-Language': profile.preferences.language,
|
||||
},
|
||||
});
|
||||
|
||||
expect(mockPlugin.scheduleDailyNotification).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
title: 'Good morning, John Doe!',
|
||||
timezone: 'America/New_York',
|
||||
headers: expect.any(Object),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle personalized content with custom handler', async () => {
|
||||
const contentHandler = async (response: Response) => {
|
||||
const data = await response.json();
|
||||
return {
|
||||
title: 'Handled Content',
|
||||
body: data.content,
|
||||
data: { timestamp: new Date().toISOString() },
|
||||
};
|
||||
};
|
||||
|
||||
await plugin.scheduleDailyNotification({
|
||||
url: 'https://api.example.com/personalized',
|
||||
time: '09:00',
|
||||
contentHandler,
|
||||
});
|
||||
|
||||
expect(mockPlugin.scheduleDailyNotification).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
url: 'https://api.example.com/personalized',
|
||||
time: '09:00',
|
||||
contentHandler,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Rate Limiting', () => {
|
||||
it('should enforce rate limits between notifications', async () => {
|
||||
const rateLimiter = {
|
||||
lastNotificationTime: 0,
|
||||
minInterval: 60000,
|
||||
async scheduleWithRateLimit(options: any) {
|
||||
const now = Date.now();
|
||||
if (now - this.lastNotificationTime < this.minInterval) {
|
||||
throw new Error('Rate limit exceeded');
|
||||
}
|
||||
await plugin.scheduleDailyNotification(options);
|
||||
this.lastNotificationTime = now;
|
||||
},
|
||||
};
|
||||
|
||||
await rateLimiter.scheduleWithRateLimit({
|
||||
url: 'https://api.example.com/rate-limited',
|
||||
time: '09:00',
|
||||
});
|
||||
|
||||
await expect(
|
||||
rateLimiter.scheduleWithRateLimit({
|
||||
url: 'https://api.example.com/rate-limited',
|
||||
time: '09:01',
|
||||
})
|
||||
).rejects.toThrow('Rate limit exceeded');
|
||||
});
|
||||
|
||||
it('should handle rate limit exceptions gracefully', async () => {
|
||||
const rateLimiter = {
|
||||
lastNotificationTime: 0,
|
||||
minInterval: 60000,
|
||||
async scheduleWithRateLimit(options: any) {
|
||||
try {
|
||||
const now = Date.now();
|
||||
if (now - this.lastNotificationTime < this.minInterval) {
|
||||
throw new Error('Rate limit exceeded');
|
||||
}
|
||||
await plugin.scheduleDailyNotification(options);
|
||||
this.lastNotificationTime = now;
|
||||
} catch (error) {
|
||||
console.error('Rate limit error:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
await expect(
|
||||
rateLimiter.scheduleWithRateLimit({
|
||||
url: 'https://api.example.com/rate-limited',
|
||||
time: '09:00',
|
||||
})
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Test setup file for the Daily Notification plugin
|
||||
*/
|
||||
|
||||
import { jest, afterEach } from '@jest/globals';
|
||||
|
||||
// Mock Capacitor plugin
|
||||
jest.mock('@capacitor/core', () => ({
|
||||
Capacitor: {
|
||||
isNativePlatform: () => false,
|
||||
getPlatform: () => 'web',
|
||||
},
|
||||
WebPlugin: class {
|
||||
constructor() {
|
||||
return {
|
||||
addListener: jest.fn(),
|
||||
removeAllListeners: jest.fn(),
|
||||
};
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock Response
|
||||
const mockResponse = {
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: jest.fn(),
|
||||
text: jest.fn(),
|
||||
blob: jest.fn(),
|
||||
arrayBuffer: jest.fn(),
|
||||
clone: jest.fn(),
|
||||
headers: new Headers(),
|
||||
redirected: false,
|
||||
statusText: 'OK',
|
||||
type: 'default',
|
||||
url: '',
|
||||
};
|
||||
|
||||
global.Response = jest.fn().mockImplementation(() => mockResponse) as unknown as typeof Response;
|
||||
|
||||
// Mock Date
|
||||
global.Date = jest.fn().mockImplementation(() => ({
|
||||
getTime: () => 0,
|
||||
toISOString: () => '2024-01-01T00:00:00.000Z',
|
||||
})) as unknown as typeof Date;
|
||||
|
||||
// Clean up mocks after each test
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
Reference in New Issue
Block a user