Merge branch 'master' into ios-implementation
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
package com.timesafari.dailynotification;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
/**
|
||||
* Instrumentation tests for Daily Notification Plugin
|
||||
*
|
||||
* Tests critical notification scheduling and delivery paths
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class NotificationInstrumentationTest {
|
||||
|
||||
private Context appContext;
|
||||
private AlarmManager alarmManager;
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule permissionRule = GrantPermissionRule.grant(
|
||||
android.Manifest.permission.POST_NOTIFICATIONS,
|
||||
android.Manifest.permission.SCHEDULE_EXACT_ALARM
|
||||
);
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
alarmManager = (AlarmManager) appContext.getSystemService(Context.ALARM_SERVICE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyReceiverRegistration() {
|
||||
// Verify NotifyReceiver is registered in AndroidManifest
|
||||
Intent intent = new Intent(appContext, NotifyReceiver.class);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(
|
||||
appContext,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE
|
||||
);
|
||||
|
||||
// If NotifyReceiver is registered, we can create a PendingIntent for it
|
||||
assertNotNull("NotifyReceiver should be registered in AndroidManifest", pendingIntent);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlarmScheduling() {
|
||||
// Test that alarms can be scheduled
|
||||
long triggerTime = System.currentTimeMillis() + 60000; // 1 minute from now
|
||||
|
||||
Intent intent = new Intent(appContext, NotifyReceiver.class);
|
||||
intent.putExtra("title", "Test Notification");
|
||||
intent.putExtra("body", "Test body");
|
||||
|
||||
int requestCode = NotifyReceiver.Companion.getRequestCode(triggerTime);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(
|
||||
appContext,
|
||||
requestCode,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
||||
);
|
||||
|
||||
try {
|
||||
// Use setAlarmClock for Android 5.0+
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(
|
||||
triggerTime,
|
||||
null
|
||||
);
|
||||
alarmManager.setAlarmClock(alarmClockInfo, pendingIntent);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
alarmManager.setExactAndAllowWhileIdle(
|
||||
AlarmManager.RTC_WAKEUP,
|
||||
triggerTime,
|
||||
pendingIntent
|
||||
);
|
||||
} else {
|
||||
alarmManager.setExact(
|
||||
AlarmManager.RTC_WAKEUP,
|
||||
triggerTime,
|
||||
pendingIntent
|
||||
);
|
||||
}
|
||||
|
||||
// Verify alarm is scheduled
|
||||
boolean isScheduled = NotifyReceiver.Companion.isAlarmScheduled(appContext, triggerTime);
|
||||
assertTrue("Alarm should be scheduled", isScheduled);
|
||||
|
||||
// Clean up
|
||||
alarmManager.cancel(pendingIntent);
|
||||
} catch (SecurityException e) {
|
||||
fail("Should have permission to schedule exact alarms: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUniqueRequestCodes() {
|
||||
// Test that different trigger times generate different request codes
|
||||
long triggerTime1 = System.currentTimeMillis() + 60000;
|
||||
long triggerTime2 = System.currentTimeMillis() + 120000;
|
||||
|
||||
int requestCode1 = NotifyReceiver.Companion.getRequestCode(triggerTime1);
|
||||
int requestCode2 = NotifyReceiver.Companion.getRequestCode(triggerTime2);
|
||||
|
||||
// Request codes should be different for different trigger times
|
||||
assertNotEquals("Different trigger times should generate different request codes",
|
||||
requestCode1, requestCode2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlarmStatusCheck() {
|
||||
// Test isAlarmScheduled method
|
||||
long triggerTime = System.currentTimeMillis() + 60000;
|
||||
|
||||
// Initially should not be scheduled
|
||||
boolean initiallyScheduled = NotifyReceiver.Companion.isAlarmScheduled(appContext, triggerTime);
|
||||
assertFalse("Alarm should not be scheduled initially", initiallyScheduled);
|
||||
|
||||
// Schedule alarm
|
||||
Intent intent = new Intent(appContext, NotifyReceiver.class);
|
||||
intent.putExtra("title", "Test");
|
||||
intent.putExtra("body", "Test");
|
||||
|
||||
int requestCode = NotifyReceiver.Companion.getRequestCode(triggerTime);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(
|
||||
appContext,
|
||||
requestCode,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
||||
);
|
||||
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(
|
||||
triggerTime,
|
||||
null
|
||||
);
|
||||
alarmManager.setAlarmClock(alarmClockInfo, pendingIntent);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
alarmManager.setExactAndAllowWhileIdle(
|
||||
AlarmManager.RTC_WAKEUP,
|
||||
triggerTime,
|
||||
pendingIntent
|
||||
);
|
||||
} else {
|
||||
alarmManager.setExact(
|
||||
AlarmManager.RTC_WAKEUP,
|
||||
triggerTime,
|
||||
pendingIntent
|
||||
);
|
||||
}
|
||||
|
||||
// Now should be scheduled
|
||||
boolean afterScheduling = NotifyReceiver.Companion.isAlarmScheduled(appContext, triggerTime);
|
||||
assertTrue("Alarm should be scheduled after calling setAlarmClock", afterScheduling);
|
||||
|
||||
// Clean up
|
||||
alarmManager.cancel(pendingIntent);
|
||||
} catch (SecurityException e) {
|
||||
fail("Should have permission to schedule exact alarms: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNextAlarmTime() {
|
||||
// Test getNextAlarmTime method (requires Android 5.0+)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
Long nextAlarmTime = NotifyReceiver.Companion.getNextAlarmTime(appContext);
|
||||
|
||||
// May be null if no alarm scheduled, which is valid
|
||||
if (nextAlarmTime != null) {
|
||||
assertTrue("Next alarm time should be in the future",
|
||||
nextAlarmTime > System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlarmCancellation() {
|
||||
// Test that alarms can be cancelled
|
||||
long triggerTime = System.currentTimeMillis() + 60000;
|
||||
|
||||
Intent intent = new Intent(appContext, NotifyReceiver.class);
|
||||
intent.putExtra("title", "Test");
|
||||
intent.putExtra("body", "Test");
|
||||
|
||||
int requestCode = NotifyReceiver.Companion.getRequestCode(triggerTime);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(
|
||||
appContext,
|
||||
requestCode,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
||||
);
|
||||
|
||||
try {
|
||||
// Schedule alarm
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(
|
||||
triggerTime,
|
||||
null
|
||||
);
|
||||
alarmManager.setAlarmClock(alarmClockInfo, pendingIntent);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
alarmManager.setExactAndAllowWhileIdle(
|
||||
AlarmManager.RTC_WAKEUP,
|
||||
triggerTime,
|
||||
pendingIntent
|
||||
);
|
||||
} else {
|
||||
alarmManager.setExact(
|
||||
AlarmManager.RTC_WAKEUP,
|
||||
triggerTime,
|
||||
pendingIntent
|
||||
);
|
||||
}
|
||||
|
||||
// Verify scheduled
|
||||
assertTrue("Alarm should be scheduled",
|
||||
NotifyReceiver.Companion.isAlarmScheduled(appContext, triggerTime));
|
||||
|
||||
// Cancel alarm
|
||||
NotifyReceiver.Companion.cancelNotification(appContext, triggerTime);
|
||||
|
||||
// Verify cancelled
|
||||
assertFalse("Alarm should be cancelled",
|
||||
NotifyReceiver.Companion.isAlarmScheduled(appContext, triggerTime));
|
||||
} catch (SecurityException e) {
|
||||
fail("Should have permission to schedule exact alarms: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPendingIntentUniqueness() {
|
||||
// Test that PendingIntents with different request codes don't conflict
|
||||
long triggerTime1 = System.currentTimeMillis() + 60000;
|
||||
long triggerTime2 = System.currentTimeMillis() + 120000;
|
||||
|
||||
Intent intent1 = new Intent(appContext, NotifyReceiver.class);
|
||||
intent1.putExtra("title", "Test 1");
|
||||
intent1.putExtra("body", "Test 1");
|
||||
|
||||
Intent intent2 = new Intent(appContext, NotifyReceiver.class);
|
||||
intent2.putExtra("title", "Test 2");
|
||||
intent2.putExtra("body", "Test 2");
|
||||
|
||||
int requestCode1 = NotifyReceiver.Companion.getRequestCode(triggerTime1);
|
||||
int requestCode2 = NotifyReceiver.Companion.getRequestCode(triggerTime2);
|
||||
|
||||
PendingIntent pendingIntent1 = PendingIntent.getBroadcast(
|
||||
appContext,
|
||||
requestCode1,
|
||||
intent1,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
||||
);
|
||||
|
||||
PendingIntent pendingIntent2 = PendingIntent.getBroadcast(
|
||||
appContext,
|
||||
requestCode2,
|
||||
intent2,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
||||
);
|
||||
|
||||
// Both PendingIntents should be created successfully
|
||||
assertNotNull("First PendingIntent should be created", pendingIntent1);
|
||||
assertNotNull("Second PendingIntent should be created", pendingIntent2);
|
||||
|
||||
// They should be different objects
|
||||
assertNotSame("PendingIntents should be different objects",
|
||||
pendingIntent1, pendingIntent2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,13 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- NotifyReceiver for AlarmManager-based notifications -->
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.NotifyReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.BootReceiver"
|
||||
android:enabled="true"
|
||||
|
||||
@@ -3,6 +3,4 @@ include ':capacitor-android'
|
||||
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
|
||||
|
||||
include ':timesafari-daily-notification-plugin'
|
||||
// NOTE: Plugin module is in android/plugin/ subdirectory, not android root
|
||||
// This file is auto-generated by Capacitor, but must be manually corrected for this plugin structure
|
||||
project(':timesafari-daily-notification-plugin').projectDir = new File('../node_modules/@timesafari/daily-notification-plugin/android/plugin')
|
||||
project(':timesafari-daily-notification-plugin').projectDir = new File('../node_modules/@timesafari/daily-notification-plugin/android')
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
*
|
||||
* Fixes:
|
||||
* 1. capacitor.plugins.json - Ensures DailyNotification plugin is registered
|
||||
* 2. capacitor.settings.gradle - Corrects plugin path from android/ to android/plugin/
|
||||
* 2. capacitor.settings.gradle - Verifies plugin path points to android/ (standard structure)
|
||||
*
|
||||
* This script should run automatically after 'npx cap sync android'
|
||||
* to fix issues with Capacitor's auto-generated files.
|
||||
* to verify Capacitor's auto-generated files are correct.
|
||||
*
|
||||
* @author Matthew Raymer
|
||||
*/
|
||||
@@ -60,10 +60,10 @@ function fixCapacitorPlugins() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix capacitor.settings.gradle to point to android/plugin/ instead of android/
|
||||
* Fix capacitor.settings.gradle to verify plugin path points to android/ (standard structure)
|
||||
*/
|
||||
function fixCapacitorSettingsGradle() {
|
||||
console.log('🔧 Fixing capacitor.settings.gradle...');
|
||||
console.log('🔧 Verifying capacitor.settings.gradle...');
|
||||
|
||||
if (!fs.existsSync(SETTINGS_GRADLE_PATH)) {
|
||||
console.log('ℹ️ capacitor.settings.gradle not found (may not be a test-app)');
|
||||
@@ -74,30 +74,31 @@ function fixCapacitorSettingsGradle() {
|
||||
let content = fs.readFileSync(SETTINGS_GRADLE_PATH, 'utf8');
|
||||
const originalContent = content;
|
||||
|
||||
// Check if the path already points to android/plugin
|
||||
if (content.includes('android/plugin')) {
|
||||
console.log('✅ capacitor.settings.gradle already has correct path (android/plugin)');
|
||||
return;
|
||||
}
|
||||
// Check if the path correctly points to android/ (standard structure)
|
||||
const correctPath = "project(':timesafari-daily-notification-plugin').projectDir = new File('../node_modules/@timesafari/daily-notification-plugin/android')";
|
||||
const oldPluginPath = "project(':timesafari-daily-notification-plugin').projectDir = new File('../node_modules/@timesafari/daily-notification-plugin/android/plugin')";
|
||||
|
||||
// Check if we need to fix the path (points to android but should be android/plugin)
|
||||
if (content.includes("project(':timesafari-daily-notification-plugin').projectDir = new File('../node_modules/@timesafari/daily-notification-plugin/android')")) {
|
||||
// Replace the path
|
||||
// Check if it's using the old android/plugin/ path (needs fixing)
|
||||
if (content.includes('android/plugin')) {
|
||||
console.log('⚠️ capacitor.settings.gradle uses old path (android/plugin/) - fixing to standard structure');
|
||||
content = content.replace(
|
||||
"project(':timesafari-daily-notification-plugin').projectDir = new File('../node_modules/@timesafari/daily-notification-plugin/android')",
|
||||
`// NOTE: Plugin module is in android/plugin/ subdirectory, not android root
|
||||
// This file is auto-generated by Capacitor, but must be manually corrected for this plugin structure
|
||||
project(':timesafari-daily-notification-plugin').projectDir = new File('../node_modules/@timesafari/daily-notification-plugin/android/plugin')`
|
||||
oldPluginPath,
|
||||
`// Plugin uses standard Capacitor structure: android/ (not android/plugin/)
|
||||
${correctPath}`
|
||||
);
|
||||
|
||||
fs.writeFileSync(SETTINGS_GRADLE_PATH, content);
|
||||
console.log('✅ Fixed plugin path in capacitor.settings.gradle (android -> android/plugin)');
|
||||
if (content !== originalContent) {
|
||||
fs.writeFileSync(SETTINGS_GRADLE_PATH, content);
|
||||
console.log('✅ Fixed plugin path in capacitor.settings.gradle (android/plugin -> android)');
|
||||
}
|
||||
} else if (content.includes(correctPath) || content.includes("android')")) {
|
||||
console.log('✅ capacitor.settings.gradle has correct path (android/)');
|
||||
} else {
|
||||
console.log('ℹ️ capacitor.settings.gradle doesn\'t reference the plugin or uses a different structure');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error fixing capacitor.settings.gradle:', error.message);
|
||||
console.error('❌ Error verifying capacitor.settings.gradle:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user