feat: add minimal Capacitor test apps for all platforms

- Add Android test app with exact alarm permission testing
- Add iOS test app with rolling window and BGTaskScheduler testing
- Add Electron test app with mock implementations and IPC
- Include automated setup scripts for each platform
- Provide comprehensive testing checklist and troubleshooting guide
- Follow best practices for Capacitor plugin testing

Test apps include:
- Plugin configuration and scheduling validation
- Platform-specific feature testing (Android exact alarms, iOS rolling window)
- Performance monitoring and debug information
- Error handling and edge case testing
- Cross-platform API consistency validation

Setup: Run ./setup-*.sh scripts for automated platform setup
Testing: Each app provides interactive UI for comprehensive plugin validation

Files: 25+ new files across test-apps/ directory
This commit is contained in:
Matthew Raymer
2025-09-09 05:24:27 +00:00
parent 0ccf071f5c
commit 956abff320
29 changed files with 2167 additions and 39 deletions

114
test-apps/electron-test/.gitignore vendored Normal file
View File

@@ -0,0 +1,114 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Build outputs
dist/
build/
*.tsbuildinfo
# Electron
out/
app/
packages/
# IDE and editor files
.vscode/
.idea/
*.swp
*.swo
*~
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
*.lcov
# nyc test coverage
.nyc_output
# Dependency directories
jspm_packages/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
.env.local
.env.development.local
.env.test.local
.env.production.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Electron specific
*.app
*.dmg
*.exe
*.deb
*.rpm
*.AppImage
*.snap

View File

@@ -0,0 +1,117 @@
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
// Mock plugin for Electron development
const DailyNotification = {
async configure(options) {
console.log('Electron Configure called:', options);
return Promise.resolve();
},
async scheduleDailyNotification(options) {
console.log('Electron Schedule called:', options);
return Promise.resolve();
},
async getDebugInfo() {
return Promise.resolve({
status: 'Electron Mock Mode',
configuration: {
storage: 'mock',
platform: 'electron',
version: '1.0.0'
},
recentErrors: [],
performance: {
overallScore: 95,
memoryUsage: 15.2,
cpuUsage: 1.2
}
});
},
async getPerformanceMetrics() {
return Promise.resolve({
overallScore: 95,
databasePerformance: 100,
memoryEfficiency: 95,
batteryEfficiency: 100,
objectPoolEfficiency: 100,
totalDatabaseQueries: 0,
averageMemoryUsage: 15.2,
objectPoolHits: 0,
backgroundCpuUsage: 0.5,
totalNetworkRequests: 0,
recommendations: ['Electron mock mode - no optimizations needed']
});
}
};
// IPC handlers for Electron
ipcMain.handle('configure-plugin', async (event, options) => {
try {
await DailyNotification.configure(options);
return { success: true, message: 'Configuration successful' };
} catch (error) {
return { success: false, error: error.message };
}
});
ipcMain.handle('schedule-notification', async (event, options) => {
try {
await DailyNotification.scheduleDailyNotification(options);
return { success: true, message: 'Notification scheduled' };
} catch (error) {
return { success: false, error: error.message };
}
});
ipcMain.handle('get-debug-info', async () => {
try {
const info = await DailyNotification.getDebugInfo();
return { success: true, data: info };
} catch (error) {
return { success: false, error: error.message };
}
});
ipcMain.handle('get-performance-metrics', async () => {
try {
const metrics = await DailyNotification.getPerformanceMetrics();
return { success: true, data: metrics };
} catch (error) {
return { success: false, error: error.message };
}
});
function createWindow() {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
},
title: 'Daily Notification - Electron Test'
});
// Load the web app
mainWindow.loadFile('dist/index.html');
// Open DevTools in development
if (process.argv.includes('--dev')) {
mainWindow.webContents.openDevTools();
}
}
app.whenReady().then(createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});

View File

@@ -0,0 +1,28 @@
{
"name": "daily-notification-electron-test",
"version": "1.0.0",
"description": "Minimal Electron test app for Daily Notification Plugin",
"main": "main.js",
"scripts": {
"start": "electron .",
"dev": "electron . --dev",
"build": "webpack --mode=production",
"build-web": "webpack --mode=production",
"electron": "npm run build-web && electron ."
},
"keywords": ["capacitor", "electron", "notifications", "test"],
"author": "Matthew Raymer",
"license": "MIT",
"dependencies": {
"@capacitor/core": "^5.0.0",
"@capacitor/cli": "^5.0.0",
"electron": "^25.0.0"
},
"devDependencies": {
"webpack": "^5.88.0",
"webpack-cli": "^5.1.0",
"html-webpack-plugin": "^5.5.0",
"typescript": "^5.0.0",
"ts-loader": "^9.4.0"
}
}

View File

@@ -0,0 +1,10 @@
const { contextBridge, ipcRenderer } = require('electron');
// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld('electronAPI', {
configurePlugin: (options) => ipcRenderer.invoke('configure-plugin', options),
scheduleNotification: (options) => ipcRenderer.invoke('schedule-notification', options),
getDebugInfo: () => ipcRenderer.invoke('get-debug-info'),
getPerformanceMetrics: () => ipcRenderer.invoke('get-performance-metrics')
});

View File

@@ -0,0 +1,107 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Daily Notification - Electron Test</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 600px;
margin: 0 auto;
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
margin-bottom: 30px;
}
.status {
background: #fff3e0;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
text-align: center;
font-weight: bold;
color: #f57c00;
}
.button-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 20px;
}
button {
background: #f57c00;
color: white;
border: none;
padding: 12px 16px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s;
}
button:hover {
background: #ef6c00;
}
button:disabled {
background: #ccc;
cursor: not-allowed;
}
.log-container {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 6px;
padding: 15px;
height: 300px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 12px;
}
.timestamp {
color: #666;
font-weight: bold;
}
pre {
background: #e9ecef;
padding: 8px;
border-radius: 4px;
margin: 5px 0;
overflow-x: auto;
}
.clear-button {
background: #dc3545;
margin-top: 10px;
width: 100%;
}
.clear-button:hover {
background: #c82333;
}
</style>
</head>
<body>
<div class="container">
<h1>⚡ Daily Notification Plugin - Electron Test</h1>
<div class="status" id="status">Ready</div>
<div class="button-grid">
<button id="configure">Configure Plugin</button>
<button id="schedule">Schedule Notification</button>
<button id="debug-info">Debug Info</button>
<button id="performance">Performance Metrics</button>
</div>
<div class="log-container" id="log"></div>
<button class="clear-button" id="clear-log">Clear Log</button>
</div>
</body>
</html>

View File

@@ -0,0 +1,121 @@
// Electron test interface
class TestApp {
private statusElement: HTMLElement;
private logElement: HTMLElement;
constructor() {
this.statusElement = document.getElementById('status')!;
this.logElement = document.getElementById('log')!;
this.setupEventListeners();
this.log('Electron Test app initialized');
}
private setupEventListeners() {
document.getElementById('configure')?.addEventListener('click', () => this.testConfigure());
document.getElementById('schedule')?.addEventListener('click', () => this.testSchedule());
document.getElementById('debug-info')?.addEventListener('click', () => this.testDebugInfo());
document.getElementById('performance')?.addEventListener('click', () => this.testPerformance());
document.getElementById('clear-log')?.addEventListener('click', () => this.clearLog());
}
private async testConfigure() {
try {
this.log('Testing Electron configuration...');
const result = await (window as any).electronAPI.configurePlugin({
storage: 'mock',
ttlSeconds: 1800,
prefetchLeadMinutes: 15,
enableETagSupport: true,
enableErrorHandling: true,
enablePerformanceOptimization: true
});
if (result.success) {
this.log('✅ Electron Configuration successful');
this.updateStatus('Configured');
} else {
this.log(`❌ Configuration failed: ${result.error}`);
}
} catch (error) {
this.log(`❌ Configuration error: ${error}`);
}
}
private async testSchedule() {
try {
this.log('Testing Electron notification scheduling...');
const result = await (window as any).electronAPI.scheduleNotification({
url: 'https://api.example.com/daily-content',
time: '09:00',
title: 'Daily Electron Test Notification',
body: 'This is a test notification from the Electron test app'
});
if (result.success) {
this.log('✅ Electron Notification scheduled successfully');
this.updateStatus('Scheduled');
} else {
this.log(`❌ Scheduling failed: ${result.error}`);
}
} catch (error) {
this.log(`❌ Scheduling error: ${error}`);
}
}
private async testDebugInfo() {
try {
this.log('Testing Electron debug info...');
const result = await (window as any).electronAPI.getDebugInfo();
if (result.success) {
this.log('🔍 Electron Debug Info:', result.data);
this.updateStatus(`Debug: ${result.data.status}`);
} else {
this.log(`❌ Debug info failed: ${result.error}`);
}
} catch (error) {
this.log(`❌ Debug info error: ${error}`);
}
}
private async testPerformance() {
try {
this.log('Testing Electron performance metrics...');
const result = await (window as any).electronAPI.getPerformanceMetrics();
if (result.success) {
this.log('📊 Electron Performance Metrics:', result.data);
this.updateStatus(`Performance: ${result.data.overallScore}/100`);
} else {
this.log(`❌ Performance check failed: ${result.error}`);
}
} catch (error) {
this.log(`❌ Performance error: ${error}`);
}
}
private log(message: string, data?: any) {
const timestamp = new Date().toLocaleTimeString();
const logEntry = document.createElement('div');
logEntry.innerHTML = `<span class="timestamp">[${timestamp}]</span> ${message}`;
if (data) {
logEntry.innerHTML += `<pre>${JSON.stringify(data, null, 2)}</pre>`;
}
this.logElement.appendChild(logEntry);
this.logElement.scrollTop = this.logElement.scrollHeight;
}
private clearLog() {
this.logElement.innerHTML = '';
this.log('Log cleared');
}
private updateStatus(status: string) {
this.statusElement.textContent = status;
}
}
// Initialize app when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
new TestApp();
});

View File

@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,28 @@
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.ts',
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.ts', '.js'],
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
}),
],
};