test: improve rollover detection and UI auto-refresh

- Normalize alarm time seconds to :00 for consistent comparison
- Compare dates (YYYY-MM-DD) instead of full timestamps to detect rollover
- Expand logcat search patterns to catch all rollover logs (DN|RESCHEDULE, etc.)
- Add 5-second wait after notification fire to allow rollover processing
- UI: Normalize seconds display to :00 in all time displays
- UI: Add auto-refresh mechanism that detects nextNotificationTime changes
- UI: Poll every 3 seconds and force refresh when rollover detected
- UI: Initialize tracking variable on page load for change detection

Fixes issue where test passed but alarm time didn't actually change,
and UI wasn't updating to show rescheduled notification time after rollover.
This commit is contained in:
Matthew Raymer
2025-12-29 09:36:19 +00:00
parent 78cd72529d
commit b53042d679
2 changed files with 516 additions and 54 deletions

View File

@@ -65,16 +65,20 @@ check_permissions() {
print_info "Checking notification permissions..." print_info "Checking notification permissions..."
# Check if POST_NOTIFICATIONS permission is granted (Android 13+) # Check if POST_NOTIFICATIONS permission is granted (Android 13+)
PERM_CHECK=$($ADB_BIN shell dumpsys package "${APP_ID}" | grep -A 5 "granted=true" | grep -c "android.permission.POST_NOTIFICATIONS" || echo "0") # Strip whitespace/newlines to ensure we get a single integer
PERM_CHECK=$($ADB_BIN shell dumpsys package "${APP_ID}" | grep -A 5 "granted=true" | grep -c "android.permission.POST_NOTIFICATIONS" 2>/dev/null | tr -d '\n\r ' || echo "0")
# Also check via app's permission status if available # Also check via app's permission status if available
PERM_GRANTED=false PERM_GRANTED=false
if [ "${PERM_CHECK}" -gt "0" ]; then # Ensure PERM_CHECK is a valid integer (strip any remaining non-digits)
PERM_CHECK=$(echo "${PERM_CHECK}" | tr -d '\n\r ' | grep -oE '^[0-9]+$' || echo "0")
if [ "${PERM_CHECK}" -gt "0" ] 2>/dev/null; then
PERM_GRANTED=true PERM_GRANTED=true
else else
# Check if we're on Android 12 or below (permission not required) # Check if we're on Android 12 or below (permission not required)
SDK_VERSION=$($ADB_BIN shell getprop ro.build.version.sdk) SDK_VERSION=$($ADB_BIN shell getprop ro.build.version.sdk 2>/dev/null | tr -d '\n\r ' || echo "0")
if [ "${SDK_VERSION}" -lt "33" ]; then SDK_VERSION=$(echo "${SDK_VERSION}" | grep -oE '^[0-9]+$' || echo "0")
if [ "${SDK_VERSION}" -lt "33" ] 2>/dev/null; then
PERM_GRANTED=true # Pre-Android 13 doesn't need runtime permission PERM_GRANTED=true # Pre-Android 13 doesn't need runtime permission
fi fi
fi fi
@@ -88,6 +92,158 @@ check_permissions() {
fi fi
} }
# Comprehensive prerequisite verification (aligned with PHASE1_TEST0_GOLDEN.md)
# Verifies all 5 items specified in the golden run document:
# 1. Plugin Settings: Configured
# 2. Native Fetcher: Configured
# 3. Notifications: Granted
# 4. Exact Alarms: Granted
# 5. Channel: Enabled (High)
#
# Usage: verify_all_prerequisites "phase1" "phase1_test0" "p1_t0_s2"
# This will verify all prerequisites and emit step events for each check
verify_all_prerequisites() {
local phase_id="${1:-}"
local test_id="${2:-}"
local base_step_id="${3:-}"
info "Verifying all prerequisites (aligned with golden run specification)..."
local all_ok=true
local missing_items=()
# 1. Check Plugin Settings: Configured
local step_id="${base_step_id}_plugin"
set_test_context "${phase_id}" "${test_id}" "${step_id}"
step_start "${step_id}" "Verifying Plugin Settings: Configured"
if check_plugin_configured >/dev/null 2>&1; then
ok "Plugin Settings: ✅ Configured"
step_pass "${step_id}" "Plugin Settings configured"
else
warn "Plugin Settings: ❌ Not configured"
all_ok=false
missing_items+=("Plugin Settings")
step_fail "${step_id}" "Plugin Settings not configured"
fi
# 2. Check Native Fetcher: Configured
# (Plugin config check also verifies native fetcher via SharedPreferences)
# For explicit verification, we check logs for fetcher configuration
step_id="${base_step_id}_fetcher"
set_test_context "${phase_id}" "${test_id}" "${step_id}"
step_start "${step_id}" "Verifying Native Fetcher: Configured"
local fetcher_configured=false
local fetcher_logs=$($ADB_BIN logcat -d -t 100 | grep -E "Native fetcher.*registered|FETCHER.*CONFIGURE|configureNativeFetcher" | tail -5 || true)
if [ -n "${fetcher_logs}" ] || check_plugin_configured >/dev/null 2>&1; then
ok "Native Fetcher: ✅ Configured"
step_pass "${step_id}" "Native Fetcher configured"
fetcher_configured=true
else
warn "Native Fetcher: ⚠️ Status unclear (check UI)"
step_warn "${step_id}" "Native Fetcher status unclear - verify in UI"
# Don't fail on this - let UI verification catch it
fi
# 3. Check Notifications: Granted
step_id="${base_step_id}_notifications"
set_test_context "${phase_id}" "${test_id}" "${step_id}"
step_start "${step_id}" "Verifying Notifications: Granted"
if check_permissions >/dev/null 2>&1; then
ok "Notifications: ✅ Granted"
step_pass "${step_id}" "Notifications permission granted"
else
error "Notifications: ❌ Not granted"
all_ok=false
missing_items+=("Notifications permission")
step_fail "${step_id}" "Notifications permission not granted"
fi
# 4. Check Exact Alarms: Granted
# Best way: Check if app has alarms with exactAllowReason=policy_permission
# This indicates the permission is granted and working
step_id="${base_step_id}_exact_alarms"
set_test_context "${phase_id}" "${test_id}" "${step_id}"
step_start "${step_id}" "Verifying Exact Alarms: Granted"
local exact_alarm_granted=false
local sdk_version=$($ADB_BIN shell getprop ro.build.version.sdk 2>/dev/null | tr -d '\n\r ' | grep -oE '^[0-9]+$' || echo '0')
if [ "${sdk_version}" -ge "31" ] 2>/dev/null; then
# Android 12+ requires SCHEDULE_EXACT_ALARM permission
# Check if app has any alarms with exactAllowReason=policy_permission
# This is the most reliable indicator that exact alarms are working
local alarm_dump=$($ADB_BIN shell dumpsys alarm 2>/dev/null | grep -A 10 "${APP_ID}" | grep -c "exactAllowReason=policy_permission" || echo "0")
alarm_dump=$(echo "${alarm_dump}" | tr -d '\n\r ' | grep -oE '^[0-9]+$' || echo "0")
if [ "${alarm_dump}" -gt "0" ] 2>/dev/null; then
# Found alarms with policy_permission - exact alarms are working
exact_alarm_granted=true
else
# No alarms yet, but check if permission is declared in manifest
# (We can't easily check runtime permission status via ADB, so we'll rely on UI)
local has_perm=$($ADB_BIN shell dumpsys package "${APP_ID}" | grep -c "android.permission.SCHEDULE_EXACT_ALARM" 2>/dev/null | tr -d '\n\r ' || echo "0")
has_perm=$(echo "${has_perm}" | grep -oE '^[0-9]+$' || echo "0")
if [ "${has_perm}" -gt "0" ] 2>/dev/null; then
# Permission is declared, assume it's granted (will be verified in UI)
exact_alarm_granted=true
fi
fi
else
# Pre-Android 12 doesn't require this permission
exact_alarm_granted=true
fi
if [ "${exact_alarm_granted}" = true ]; then
ok "Exact Alarms: ✅ Granted"
step_pass "${step_id}" "Exact Alarms permission granted"
else
warn "Exact Alarms: ⚠️ May not be granted (check UI)"
step_warn "${step_id}" "Exact Alarms permission unclear - verify in UI"
# Don't fail - let UI verification catch it
fi
# 5. Check Channel: Enabled (High)
# Channel status is best verified via UI, but we can check logs
step_id="${base_step_id}_channel"
set_test_context "${phase_id}" "${test_id}" "${step_id}"
step_start "${step_id}" "Verifying Channel: Enabled (High)"
local channel_logs=$($ADB_BIN logcat -d -t 100 | grep -E "Channel.*enabled|channel.*importance|NotificationChannel" | tail -5 || true)
if [ -n "${channel_logs}" ]; then
ok "Channel: ✅ Status found in logs (verify 'Enabled (High)' in UI)"
step_pass "${step_id}" "Channel status verified"
else
info "Channel: ⚠️ Verify 'Enabled (High)' status in UI"
step_warn "${step_id}" "Channel status unclear - verify 'Enabled (High)' in UI"
# Don't fail - channel is created automatically
fi
# Final UI verification prompt (aligned with golden run step 4)
if [ "${MODE}" != "ci" ]; then
echo ""
info "=== Final UI Verification (Golden Run Step 4) ==="
info "Please confirm in the app UI that ALL of the following show ✅:"
echo " ⚙️ Plugin Settings: ✅ Configured"
echo " 🔌 Native Fetcher: ✅ Configured"
echo " 🔔 Notifications: ✅ Granted"
echo " ⏰ Exact Alarms: ✅ Granted"
echo " 📢 Channel: ✅ Enabled (High)"
echo ""
if [ "${all_ok}" = false ]; then
error "Some prerequisites are missing: ${missing_items[*]}"
ui_prompt "Please fix the missing prerequisites in the app UI, then press Enter to continue."
else
ui_prompt "If all 5 items show ✅ in the UI, press Enter to continue. Otherwise, fix any issues first."
fi
fi
if [ "${all_ok}" = false ] && [ "${MODE}" = "ci" ]; then
error "Prerequisites not met: ${missing_items[*]}"
return 1
fi
return 0
}
ensure_permissions() { ensure_permissions() {
if check_permissions; then if check_permissions; then
print_success "Permissions already granted" print_success "Permissions already granted"
@@ -280,38 +436,100 @@ run_setup() {
launch_app launch_app
sleep 2 sleep 2
# Check permissions # Check permissions and plugin configuration together
section "Permission Checks" section "Permissions & Plugin Configuration"
if check_permissions; then info "Checking both permissions and plugin/fetcher configuration..."
ok "Notification permissions already granted" echo ""
# Check both silently first
local perms_ok=false
local plugin_ok=false
if check_permissions >/dev/null 2>&1; then
perms_ok=true
fi
if check_plugin_configured >/dev/null 2>&1; then
plugin_ok=true
fi
# Show status
if [ "${perms_ok}" = "true" ]; then
ok "Notification permissions: ✅ Granted"
else else
if [ "${MODE}" = "ci" ]; then warn "Notification permissions: ❌ Not granted"
fi
if [ "${plugin_ok}" = "true" ]; then
ok "Plugin configuration: ✅ Configured"
else
warn "Plugin configuration: ❌ Not configured"
fi
# If either needs attention, show single combined prompt
if [ "${MODE}" = "ci" ]; then
if [ "${perms_ok}" != "true" ]; then
error "Permissions not granted - cannot proceed in CI mode" error "Permissions not granted - cannot proceed in CI mode"
exit 1 exit 1
fi fi
ensure_permissions if [ "${plugin_ok}" != "true" ]; then
fi
# Check plugin configuration
section "Plugin Configuration"
if check_plugin_configured; then
ok "Plugin appears to be configured"
if [ "${MODE}" != "ci" ]; then
ui_prompt "Please verify in the app UI that you see:
1) ⚙️ Plugin Settings: ✅ Configured
2) 🔌 Native Fetcher: ✅ Configured
3) 🔔 Notifications: ✅ Granted
If all show ✅, press Enter to continue.
If any show ❌, click 'Configure Plugin' first."
fi
else
if [ "${MODE}" = "ci" ]; then
error "Plugin not configured - cannot proceed in CI mode" error "Plugin not configured - cannot proceed in CI mode"
exit 1 exit 1
fi fi
ensure_plugin_configured else
# Show single combined prompt for both
if [ "${perms_ok}" != "true" ] || [ "${plugin_ok}" != "true" ]; then
ui_prompt "Please configure in the app UI:
REQUIRED STATUS (all must show ✅):
1) 🔔 Notifications: ✅ Granted
2) ⚙️ Plugin Settings: ✅ Configured
3) 🔌 Native Fetcher: ✅ Configured
ACTIONS:
- If Notifications shows ❌: Click 'Request Permissions' button
- If Plugin Settings or Native Fetcher show ❌: Click 'Configure Plugin' button
- Wait for all three to show ✅
Once all three show ✅, press Enter to continue."
# Re-check both after user action
sleep 2
echo ""
info "Re-checking after configuration..."
perms_ok=false
plugin_ok=false
if check_permissions >/dev/null 2>&1; then
perms_ok=true
ok "Notification permissions: ✅ Verified"
else
warn "Notification permissions: ⚠️ Still not granted"
fi
if check_plugin_configured >/dev/null 2>&1; then
plugin_ok=true
ok "Plugin configuration: ✅ Verified"
else
warn "Plugin configuration: ⚠️ Still not configured"
fi
echo ""
if [ "${perms_ok}" = "true" ] && [ "${plugin_ok}" = "true" ]; then
ok "All checks passed - ready to proceed"
else
warn "Some items may still need configuration - continuing anyway"
info "If tests fail, verify all three items show ✅ in the app UI"
fi
else
# Both are OK, but still prompt for final verification
ui_prompt "Please verify in the app UI that you see:
1) 🔔 Notifications: ✅ Granted
2) ⚙️ Plugin Settings: ✅ Configured
3) 🔌 Native Fetcher: ✅ Configured
If all show ✅, press Enter to continue.
If any show ❌, configure them first, then press Enter."
fi
fi fi
ok "Setup complete!" ok "Setup complete!"
@@ -429,6 +647,9 @@ main() {
if should_run_test "0" SELECTED_TESTS; then if should_run_test "0" SELECTED_TESTS; then
section "TEST 0: Daily Rollover Verification" section "TEST 0: Daily Rollover Verification"
# Set test context for event emission
set_test_context "phase1" "phase1_test0" ""
info "Purpose: Verify that after a notification fires, the next day's" info "Purpose: Verify that after a notification fires, the next day's"
info " schedule is correctly computed and only ONE alarm exists." info " schedule is correctly computed and only ONE alarm exists."
info "" info ""
@@ -441,12 +662,26 @@ main() {
fi fi
# Capture initial state # Capture initial state
set_test_context "phase1" "phase1_test0" "p1_t0_s1"
step_start "p1_t0_s1" "Capturing initial state"
capture_alarms "test0_initial" capture_alarms "test0_initial"
capture_logcat "test0_initial" "DNP" 50 capture_logcat "test0_initial" "DNP" 50
step_pass "p1_t0_s1" "Initial state captured"
substep "Step 1: Schedule a test notification" substep "Step 1: Verify setup and schedule a test notification"
set_test_context "phase1" "phase1_test0" "p1_t0_s2"
step_start "p1_t0_s2" "Verifying prerequisites and scheduling test notification"
launch_app launch_app
ensure_plugin_configured
# Comprehensive prerequisite verification (aligned with PHASE1_TEST0_GOLDEN.md step 4)
# Verifies all 5 items: Plugin Settings, Native Fetcher, Notifications, Exact Alarms, Channel
verify_all_prerequisites "phase1" "phase1_test0" "p1_t0_s2"
# If prerequisites failed and we're in CI mode, exit
if [ $? -ne 0 ] && [ "${MODE}" = "ci" ]; then
error "Prerequisites not met - cannot proceed with Test 0"
exit 1
fi
# Capture pre-schedule state # Capture pre-schedule state
capture_alarms "test0_before_schedule" capture_alarms "test0_before_schedule"
@@ -499,6 +734,7 @@ main() {
fi fi
# Show alarm details if we found exactly 1 alarm # Show alarm details if we found exactly 1 alarm
local initial_alarm_time=""
if [ "${initial_count}" -eq "1" ] 2>/dev/null; then if [ "${initial_count}" -eq "1" ] 2>/dev/null; then
ok "Single notification alarm scheduled (one per day)" ok "Single notification alarm scheduled (one per day)"
@@ -508,34 +744,53 @@ main() {
info "Notification alarm details:" info "Notification alarm details:"
echo "${alarm_details}" | head -5 echo "${alarm_details}" | head -5
echo "" echo ""
# Extract initial alarm time for rollover verification (normalize seconds to 00)
initial_alarm_time=$(echo "${alarm_details}" | grep -oE "origWhen=[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}" | head -1 | sed 's/origWhen=//' | sed 's/:[0-9][0-9]$/:00/')
if [ -n "${initial_alarm_time}" ]; then
info "Initial alarm time: ${initial_alarm_time} (normalized)"
fi
fi fi
elif [ "${initial_count}" -gt "1" ] 2>/dev/null; then elif [ "${initial_count}" -gt "1" ] 2>/dev/null; then
warn "Found ${initial_count} notification alarms (expected: 1) - DUPLICATES DETECTED!" warn "Found ${initial_count} notification alarms (expected: 1) - DUPLICATES DETECTED!"
fi fi
step_pass "p1_t0_s2" "Notification scheduled"
substep "Step 2: Wait for notification to fire" substep "Step 2: Wait for notification to fire"
info "To complete this test:" set_test_context "phase1" "phase1_test0" "p1_t0_s3"
echo " 1. Wait for notification to fire (or advance emulator clock)" step_start "p1_t0_s3" "Waiting for notification to fire"
echo " 2. Verify plugin computed next day's time (24 hours later)" info "The test will automatically verify rollover after the notification fires."
echo " 3. Verify exactly ONE notification alarm exists for tomorrow" echo ""
echo " 4. Verify no duplicate alarms were created" echo "What will be verified automatically:"
echo " • Alarm count (should be exactly 1)"
echo " • Alarm time changed (should be 24 hours later)"
echo " • Rollover logs present"
echo "" echo ""
if [ "${MODE}" != "ci" ]; then if [ "${MODE}" != "ci" ]; then
ui_prompt "2) After the notification fires (or you advance the clock), verify: ui_prompt "Please wait for the notification to fire (or advance the emulator clock past the scheduled time).
The test will automatically verify:
• Only ONE alarm exists (one per day) • Only ONE alarm exists (one per day)
• Alarm time is for tomorrow (24 hours later) • Alarm time advanced to tomorrow (24 hours later)
No duplicate alarms were created Rollover occurred (logs will be checked)
Press Enter when you've verified this (or to skip this test)." Press Enter after the notification has fired (or after advancing the clock)."
else else
info "CI mode: Skipping manual verification step" info "CI mode: Waiting for notification to fire..."
sleep 5 # Give time for rollover if automated sleep 5 # Give time for rollover if automated
fi fi
step_pass "p1_t0_s3" "Notification fired (or time advanced)"
substep "Step 3: Verify rollover state" substep "Step 3: Verify rollover state"
info "Polling for stable alarm count (allowing up to ~10 seconds for Android to settle)..." set_test_context "phase1" "phase1_test0" "p1_t0_s4"
step_start "p1_t0_s4" "Waiting for rollover to complete"
info "Waiting for rollover to complete (notification should auto-schedule next day's alarm)..."
info "Allowing up to ~15 seconds for Android to process rollover and update alarms..."
# Wait longer for rollover to complete (notification fires → rollover schedules next day)
sleep 5 # Give Android time to process the notification fire and rollover
local post_rollover_count system_final local post_rollover_count system_final
post_rollover_count="$(wait_for_stable_plugin_alarm_count 5 2)" post_rollover_count="$(wait_for_stable_plugin_alarm_count 5 2)"
@@ -543,22 +798,95 @@ main() {
# Capture post-rollover state # Capture post-rollover state
capture_alarms "test0_after_rollover" capture_alarms "test0_after_rollover"
capture_logcat "test0_after_rollover" "DNP-SCHEDULE|DNP-NOTIFY|ROLLOVER" 100 # Look for rollover logs: DN|RESCHEDULE, DN|DISPLAY, DN|RECEIVE, ROLLOVER_ON_FIRE, etc.
capture_logcat "test0_after_rollover" "DN|RESCHEDULE|DN|DISPLAY|DN|RECEIVE|ROLLOVER|DNP-SCHEDULE" 200
capture_screenshot "test0_after_rollover" capture_screenshot "test0_after_rollover"
step_pass "p1_t0_s4" "Post-rollover evidence captured"
set_test_context "phase1" "phase1_test0" "p1_t0_s6"
step_start "p1_t0_s6" "Verifying schedule state"
info "Notification alarms after rollover: ${post_rollover_count} (expected: 1)" info "Notification alarms after rollover: ${post_rollover_count} (expected: 1)"
info "System/other alarms: ${system_final} (for context)" info "System/other alarms: ${system_final} (for context)"
# Final verdict # Extract post-rollover alarm time and verify it changed
local post_rollover_alarm_time=""
local rollover_verified=false
if [ "${post_rollover_count}" -eq "1" ] 2>/dev/null; then if [ "${post_rollover_count}" -eq "1" ] 2>/dev/null; then
local post_alarm_details
post_alarm_details="$($ADB_BIN shell dumpsys alarm | grep -A 3 "com.timesafari.dailynotification" | grep -A 3 "com.timesafari.daily.NOTIFICATION" | head -10)"
if [ -n "${post_alarm_details}" ]; then
# Extract post-rollover alarm time (normalize seconds to 00 for comparison)
post_rollover_alarm_time=$(echo "${post_alarm_details}" | grep -oE "origWhen=[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}" | head -1 | sed 's/origWhen=//' | sed 's/:[0-9][0-9]$/:00/')
if [ -n "${post_rollover_alarm_time}" ]; then
info "Post-rollover alarm time: ${post_rollover_alarm_time} (normalized)"
# Verify alarm time changed (rollover occurred)
# Compare dates only (YYYY-MM-DD) to detect day change
if [ -n "${initial_alarm_time}" ] && [ -n "${post_rollover_alarm_time}" ]; then
local initial_date=$(echo "${initial_alarm_time}" | cut -d' ' -f1)
local post_date=$(echo "${post_rollover_alarm_time}" | cut -d' ' -f1)
if [ "${initial_date}" != "${post_date}" ]; then
ok "Alarm date changed: ${initial_alarm_time}${post_rollover_alarm_time}"
rollover_verified=true
else
warn "Alarm date did NOT change: ${post_rollover_alarm_time} (same date as initial: ${initial_date})"
warn "This indicates the notification did not fire and rollover did not occur"
rollover_verified=false
fi
fi
fi
fi
# Check for rollover logs (wider search including all possible rollover patterns)
# Look for: ROLLOVER_ON_FIRE source, RESCHEDULE logs, DNP-SCHEDULE with ROLLOVER, etc.
# Note: DN|RESCHEDULE uses pipe character, so we need to escape it properly
local rollover_logs=$($ADB_BIN logcat -d -t 1000 | grep -E "ROLLOVER_ON_FIRE|source=ROLLOVER|DNP-SCHEDULE.*ROLLOVER|DN\|RESCHEDULE|DN\|RESCHEDULE_OK|DN\|RESCHEDULE_START|Scheduling next.*notification|scheduleNextNotification|Next notification scheduled" | tail -15 || true)
if [ -n "${rollover_logs}" ]; then
ok "Rollover logs found - notification fired and rollover occurred"
info "Rollover log excerpt:"
echo "${rollover_logs}" | head -3 | sed 's/^/ /'
rollover_verified=true
else
warn "No rollover logs found - checking if notification fired at all..."
# Check for any notification display logs
local notification_logs=$($ADB_BIN logcat -d -t 500 | grep -E "DN\|DISPLAY_NOTIF|Notification.*displayed|com.timesafari.daily.NOTIFICATION" | tail -5 || true)
if [ -n "${notification_logs}" ]; then
warn "Notification display logs found, but no rollover logs - rollover may have failed"
info "Notification log excerpt:"
echo "${notification_logs}" | head -2 | sed 's/^/ /'
else
warn "No notification logs found - notification may not have fired"
fi
if [ "${rollover_verified}" = false ]; then
warn "⚠️ Rollover verification failed: alarm date unchanged AND no rollover logs"
fi
fi
fi
# Final verdict
if [ "${post_rollover_count}" -eq "1" ] 2>/dev/null && [ "${rollover_verified}" = true ]; then
step_pass "p1_t0_s6" "Schedule state verified - exactly 1 alarm, rollover confirmed"
set_test_context "phase1" "phase1_test0" "p1_t0_s7"
verdict_pass "test0_daily_rollover" "Daily rollover created exactly one NOTIFICATION alarm for tomorrow" verdict_pass "test0_daily_rollover" "Daily rollover created exactly one NOTIFICATION alarm for tomorrow"
elif [ "${post_rollover_count}" -eq "1" ] 2>/dev/null && [ "${rollover_verified}" = false ]; then
step_warn "p1_t0_s6" "Alarm count correct but rollover not verified"
warn "Found 1 alarm but time did not change - notification may not have fired"
set_test_context "phase1" "phase1_test0" "p1_t0_s7"
verdict_warn "test0_daily_rollover" "Alarm count correct (1) but rollover not verified - alarm time unchanged, no rollover logs"
elif [ "${post_rollover_count}" -gt "1" ] 2>/dev/null; then elif [ "${post_rollover_count}" -gt "1" ] 2>/dev/null; then
step_fail "p1_t0_s6" "Duplicate alarms detected"
error "Daily rollover created ${post_rollover_count} NOTIFICATION alarms (duplicates)" error "Daily rollover created ${post_rollover_count} NOTIFICATION alarms (duplicates)"
show_plugin_alarms_compact show_plugin_alarms_compact
set_test_context "phase1" "phase1_test0" "p1_t0_s7"
verdict_fail "test0_daily_rollover" "Duplicate alarms detected after rollover (expected: 1, got: ${post_rollover_count})" verdict_fail "test0_daily_rollover" "Duplicate alarms detected after rollover (expected: 1, got: ${post_rollover_count})"
else else
step_warn "p1_t0_s6" "No alarm found after rollover"
warn "No NOTIFICATION alarm found after rollover (expected: 1, got: 0)" warn "No NOTIFICATION alarm found after rollover (expected: 1, got: 0)"
show_plugin_alarms_compact show_plugin_alarms_compact
set_test_context "phase1" "phase1_test0" "p1_t0_s7"
verdict_warn "test0_daily_rollover" "Rollover may have failed - no alarm found (expected: 1, got: 0)" verdict_warn "test0_daily_rollover" "Rollover may have failed - no alarm found (expected: 1, got: 0)"
fi fi
@@ -575,6 +903,9 @@ main() {
if should_run_test "1" SELECTED_TESTS; then if should_run_test "1" SELECTED_TESTS; then
section "TEST 1: Force-Stop Recovery - Database Restoration" section "TEST 1: Force-Stop Recovery - Database Restoration"
# Set test context
set_test_context "phase1" "phase1_test1" ""
info "Purpose: Verify that after force-stop (which clears alarms), recovery" info "Purpose: Verify that after force-stop (which clears alarms), recovery"
info " uses the database to rebuild alarms on app relaunch." info " uses the database to rebuild alarms on app relaunch."
info "" info ""
@@ -587,10 +918,10 @@ main() {
fi fi
# Capture initial state # Capture initial state
set_test_context "phase1" "phase1_test1" "p1_t1_s1"
step_start "p1_t1_s1" "Clean start - verify no lingering alarms"
capture_alarms "test1_initial" capture_alarms "test1_initial"
capture_logcat "test1_initial" "DNP" 50 capture_logcat "test1_initial" "DNP" 50
substep "Step 1: Clean start - verify no lingering alarms"
local lingering_count system_count local lingering_count system_count
lingering_count="$(get_plugin_alarm_count)" lingering_count="$(get_plugin_alarm_count)"
system_count="$(get_system_alarm_count)" system_count="$(get_system_alarm_count)"
@@ -682,10 +1013,14 @@ main() {
ok "No lingering plugin alarms found (clean state)" ok "No lingering plugin alarms found (clean state)"
fi fi
step_pass "p1_t1_s1" "Clean state verified"
# Skip remaining TEST 1 steps if we couldn't achieve clean state # Skip remaining TEST 1 steps if we couldn't achieve clean state
if [ "${goto_test1_end}" != "true" ]; then if [ "${goto_test1_end}" != "true" ]; then
substep "Step 2: Schedule a known future alarm" substep "Step 2: Schedule a known future alarm"
set_test_context "phase1" "phase1_test1" "p1_t1_s2"
step_start "p1_t1_s2" "Scheduling notification"
launch_app launch_app
ensure_plugin_configured ensure_plugin_configured
@@ -707,7 +1042,11 @@ main() {
capture_alarms "test1_before_force_stop" capture_alarms "test1_before_force_stop"
capture_logcat "test1_before_force_stop" "DNP-SCHEDULE|DNP-NOTIFY" 50 capture_logcat "test1_before_force_stop" "DNP-SCHEDULE|DNP-NOTIFY" 50
step_pass "p1_t1_s2" "Notification scheduled"
substep "Step 3: Verify alarm exists in AlarmManager (BEFORE force-stop)" substep "Step 3: Verify alarm exists in AlarmManager (BEFORE force-stop)"
set_test_context "phase1" "phase1_test1" "p1_t1_s3"
step_start "p1_t1_s3" "Verifying alarm before force-stop"
local alarm_count_before system_count_before local alarm_count_before system_count_before
alarm_count_before="$(get_plugin_alarm_count)" alarm_count_before="$(get_plugin_alarm_count)"
system_count_before="$(get_system_alarm_count)" system_count_before="$(get_system_alarm_count)"
@@ -757,6 +1096,8 @@ main() {
fi fi
substep "Step 4: Force-stop the app (clears alarms)" substep "Step 4: Force-stop the app (clears alarms)"
set_test_context "phase1" "phase1_test1" "p1_t1_s3"
step_start "p1_t1_s3" "Force-stopping app"
warn "Force-stop will clear ALL alarms from AlarmManager" warn "Force-stop will clear ALL alarms from AlarmManager"
info "Executing: $ADB_BIN shell am force-stop ${APP_ID}" info "Executing: $ADB_BIN shell am force-stop ${APP_ID}"
force_stop_app force_stop_app
@@ -766,6 +1107,8 @@ main() {
capture_alarms "test1_after_force_stop" capture_alarms "test1_after_force_stop"
substep "Step 5: Verify alarms are MISSING (cleared by OS)" substep "Step 5: Verify alarms are MISSING (cleared by OS)"
set_test_context "phase1" "phase1_test1" "p1_t1_s4"
step_start "p1_t1_s4" "Verifying alarms cleared"
sleep 1 sleep 1
local alarm_count_after system_count_after local alarm_count_after system_count_after
alarm_count_after="$(get_plugin_alarm_count)" alarm_count_after="$(get_plugin_alarm_count)"
@@ -776,10 +1119,12 @@ main() {
if [ "${alarm_count_after}" -eq "0" ] 2>/dev/null; then if [ "${alarm_count_after}" -eq "0" ] 2>/dev/null; then
ok "Plugin alarms cleared by force-stop (count: ${alarm_count_after})" ok "Plugin alarms cleared by force-stop (count: ${alarm_count_after})"
info "This confirms: Force-stop cleared alarms from AlarmManager" info "This confirms: Force-stop cleared alarms from AlarmManager"
step_pass "p1_t1_s4" "Alarms cleared by force-stop"
else else
warn "Plugin alarms still present after force-stop (count: ${alarm_count_after})" warn "Plugin alarms still present after force-stop (count: ${alarm_count_after})"
info "Some devices/OS versions may not clear alarms on force-stop" info "Some devices/OS versions may not clear alarms on force-stop"
info "Continuing test anyway - recovery should still work" info "Continuing test anyway - recovery should still work"
step_warn "p1_t1_s4" "Alarms not cleared (device-specific behavior)"
fi fi
if [ "${MODE}" != "ci" ]; then if [ "${MODE}" != "ci" ]; then
@@ -787,11 +1132,15 @@ main() {
fi fi
substep "Step 6: Relaunch app (triggers recovery from database)" substep "Step 6: Relaunch app (triggers recovery from database)"
set_test_context "phase1" "phase1_test1" "p1_t1_s4"
step_start "p1_t1_s4" "Relaunching app to trigger recovery"
clear_logs clear_logs
launch_app launch_app
sleep 4 # Give recovery time to run sleep 4 # Give recovery time to run
substep "Step 7: Verify recovery rebuilt alarms from database" substep "Step 7: Verify recovery rebuilt alarms from database"
set_test_context "phase1" "phase1_test1" "p1_t1_s4"
step_start "p1_t1_s4" "Verifying recovery rebuilt alarms"
sleep 2 sleep 2
# Capture after recovery state # Capture after recovery state
@@ -842,8 +1191,12 @@ main() {
# Final verdict # Final verdict
if [ "${test1_passed}" = "true" ]; then if [ "${test1_passed}" = "true" ]; then
step_pass "p1_t1_s4" "Recovery successful"
set_test_context "phase1" "phase1_test1" "p1_t1_s5"
verdict_pass "test1_force_stop_recovery" "Recovery successfully rebuilt alarms from database (before: ${alarm_count_before}, after stop: ${alarm_count_after}, after recovery: ${alarm_count_recovered}, rescheduled: ${rescheduled_count})" verdict_pass "test1_force_stop_recovery" "Recovery successfully rebuilt alarms from database (before: ${alarm_count_before}, after stop: ${alarm_count_after}, after recovery: ${alarm_count_recovered}, rescheduled: ${rescheduled_count})"
else else
step_fail "p1_t1_s4" "Recovery failed"
set_test_context "phase1" "phase1_test1" "p1_t1_s5"
verdict_fail "test1_force_stop_recovery" "Recovery did not rebuild alarms correctly (before: ${alarm_count_before}, after stop: ${alarm_count_after}, after recovery: ${alarm_count_recovered})" verdict_fail "test1_force_stop_recovery" "Recovery did not rebuild alarms correctly (before: ${alarm_count_before}, after stop: ${alarm_count_after}, after recovery: ${alarm_count_recovered})"
fi fi
@@ -904,6 +1257,9 @@ main() {
if should_run_test "2" SELECTED_TESTS; then if should_run_test "2" SELECTED_TESTS; then
section "TEST 2: Schedule Update Verification" section "TEST 2: Schedule Update Verification"
# Set test context
set_test_context "phase1" "phase1_test2" ""
info "Purpose: Verify that updating the schedule time maintains 'one per day' semantics" info "Purpose: Verify that updating the schedule time maintains 'one per day' semantics"
info "" info ""
info "Expected time: 4-6 minutes" info "Expected time: 4-6 minutes"
@@ -915,6 +1271,8 @@ main() {
fi fi
# Capture initial state # Capture initial state
set_test_context "phase1" "phase1_test2" "p1_t2_s1"
step_start "p1_t2_s1" "Schedule initial notification"
capture_alarms "test2_initial" capture_alarms "test2_initial"
capture_logcat "test2_initial" "DNP" 50 capture_logcat "test2_initial" "DNP" 50
@@ -931,6 +1289,7 @@ main() {
if [ "${initial_alarm_count}" -eq "1" ] 2>/dev/null; then if [ "${initial_alarm_count}" -eq "1" ] 2>/dev/null; then
ok "Initial alarm confirmed (one per day)" ok "Initial alarm confirmed (one per day)"
step_pass "p1_t2_s1" "Initial notification scheduled"
elif [ "${initial_alarm_count}" -eq "0" ] 2>/dev/null; then elif [ "${initial_alarm_count}" -eq "0" ] 2>/dev/null; then
warn "No initial alarm found - scheduling one first..." warn "No initial alarm found - scheduling one first..."
if [ "${MODE}" != "ci" ]; then if [ "${MODE}" != "ci" ]; then
@@ -943,20 +1302,25 @@ main() {
initial_alarm_count="$(get_plugin_alarm_count)" initial_alarm_count="$(get_plugin_alarm_count)"
if [ "${initial_alarm_count}" -eq "1" ] 2>/dev/null; then if [ "${initial_alarm_count}" -eq "1" ] 2>/dev/null; then
ok "Alarm scheduled" ok "Alarm scheduled"
step_pass "p1_t2_s1" "Initial notification scheduled"
else else
error "Failed to schedule initial alarm" error "Failed to schedule initial alarm"
step_fail "p1_t2_s1" "Failed to schedule"
if [ "${MODE}" != "ci" ]; then if [ "${MODE}" != "ci" ]; then
pause pause
fi fi
fi fi
else else
warn "Found ${initial_alarm_count} plugin alarms (expected: 1) - continuing anyway" warn "Found ${initial_alarm_count} plugin alarms (expected: 1) - continuing anyway"
step_warn "p1_t2_s1" "Unexpected alarm count"
fi fi
# Capture before update state # Capture before update state
capture_alarms "test2_before_update" capture_alarms "test2_before_update"
substep "Step 2: Update schedule time" substep "Step 2: Update schedule time"
set_test_context "phase1" "phase1_test2" "p1_t2_s2"
step_start "p1_t2_s2" "Update schedule"
if [ "${MODE}" != "ci" ]; then if [ "${MODE}" != "ci" ]; then
ui_prompt "2) In the app UI, change the schedule time (e.g., from 06:50 to 07:10) ui_prompt "2) In the app UI, change the schedule time (e.g., from 06:50 to 07:10)
and apply the schedule. and apply the schedule.
@@ -982,16 +1346,21 @@ main() {
if [ "${updated_alarm_count}" -eq "1" ] 2>/dev/null; then if [ "${updated_alarm_count}" -eq "1" ] 2>/dev/null; then
ok "Single alarm confirmed after schedule update (one per day maintained)" ok "Single alarm confirmed after schedule update (one per day maintained)"
step_pass "p1_t2_s2" "Schedule updated"
elif [ "${updated_alarm_count}" -gt "1" ] 2>/dev/null; then elif [ "${updated_alarm_count}" -gt "1" ] 2>/dev/null; then
error "TEST 2 FAILED: Found ${updated_alarm_count} plugin alarms after update (expected: 1)" error "TEST 2 FAILED: Found ${updated_alarm_count} plugin alarms after update (expected: 1)"
error "Old alarm was NOT canceled - violates 'one per day' semantics" error "Old alarm was NOT canceled - violates 'one per day' semantics"
info "This indicates the plugin did not clean up existing schedules before creating new one" info "This indicates the plugin did not clean up existing schedules before creating new one"
test2_failed=true test2_failed=true
step_fail "p1_t2_s2" "Multiple alarms detected"
else else
warn "Found ${updated_alarm_count} plugin alarms (expected: 1) - no alarm scheduled after update" warn "Found ${updated_alarm_count} plugin alarms (expected: 1) - no alarm scheduled after update"
step_warn "p1_t2_s2" "No alarm after update"
fi fi
substep "Step 3: Kill app and relaunch (triggers recovery)" substep "Step 3: Kill app and relaunch (triggers recovery)"
set_test_context "phase1" "phase1_test2" "p1_t2_s3"
step_start "p1_t2_s3" "Verify one-per-day semantics"
kill_app kill_app
clear_logs clear_logs
launch_app launch_app
@@ -1025,9 +1394,15 @@ main() {
error "Multiple schedules in database caused recovery to reschedule duplicates" error "Multiple schedules in database caused recovery to reschedule duplicates"
error "This violates 'one per day' semantics - only one notification per day should exist" error "This violates 'one per day' semantics - only one notification per day should exist"
test2_failed=true test2_failed=true
step_fail "p1_t2_s3" "Multiple alarms after recovery"
elif [ "${alarm_count_after_recovery}" -eq "1" ] 2>/dev/null; then
step_pass "p1_t2_s3" "One-per-day semantics verified"
else
step_warn "p1_t2_s3" "Inconclusive alarm count"
fi fi
# Final verdict # Final verdict
set_test_context "phase1" "phase1_test2" "p1_t2_s4"
if [ "${test2_failed}" = "true" ]; then if [ "${test2_failed}" = "true" ]; then
verdict_fail "test2_schedule_update" "Multiple alarms detected - violates 'one per day' semantics (after update: ${updated_alarm_count}, after recovery: ${alarm_count_after_recovery})" verdict_fail "test2_schedule_update" "Multiple alarms detected - violates 'one per day' semantics (after update: ${updated_alarm_count}, after recovery: ${alarm_count_after_recovery})"
elif [ "${alarm_count_after_recovery}" -eq "1" ] 2>/dev/null; then elif [ "${alarm_count_after_recovery}" -eq "1" ] 2>/dev/null; then
@@ -1055,6 +1430,9 @@ main() {
if should_run_test "3" SELECTED_TESTS; then if should_run_test "3" SELECTED_TESTS; then
section "TEST 3: Recovery Timeout" section "TEST 3: Recovery Timeout"
# Set test context
set_test_context "phase1" "phase1_test3" ""
info "Purpose: Verify recovery times out gracefully" info "Purpose: Verify recovery times out gracefully"
info "" info ""
info "Expected time: 2-3 minutes" info "Expected time: 2-3 minutes"
@@ -1070,6 +1448,8 @@ main() {
capture_alarms "test3_initial" capture_alarms "test3_initial"
substep "Step 1: Check recovery timeout implementation" substep "Step 1: Check recovery timeout implementation"
set_test_context "phase1" "phase1_test3" "p1_t3_s1"
step_start "p1_t3_s1" "Check recovery timeout implementation"
local timeout_found timeout_protection_found local timeout_found timeout_protection_found
timeout_found=false timeout_found=false
timeout_protection_found=false timeout_protection_found=false
@@ -1091,7 +1471,14 @@ main() {
# Capture evidence # Capture evidence
capture_logcat "test3_timeout_check" "DNP-REACTIVATION" 50 capture_logcat "test3_timeout_check" "DNP-REACTIVATION" 50
if [ "${timeout_found}" = "true" ] && [ "${timeout_protection_found}" = "true" ]; then
step_pass "p1_t3_s1" "Timeout mechanism verified"
else
step_fail "p1_t3_s1" "Timeout mechanism not found"
fi
# Final verdict # Final verdict
set_test_context "phase1" "phase1_test3" "p1_t3_s2"
if [ "${timeout_found}" = "true" ] && [ "${timeout_protection_found}" = "true" ]; then if [ "${timeout_found}" = "true" ] && [ "${timeout_protection_found}" = "true" ]; then
verdict_pass "test3_recovery_timeout" "Timeout mechanism verified in code (2 seconds, withTimeout protection)" verdict_pass "test3_recovery_timeout" "Timeout mechanism verified in code (2 seconds, withTimeout protection)"
else else
@@ -1111,6 +1498,9 @@ main() {
if should_run_test "4" SELECTED_TESTS; then if should_run_test "4" SELECTED_TESTS; then
section "TEST 4: Invalid Data Handling" section "TEST 4: Invalid Data Handling"
# Set test context
set_test_context "phase1" "phase1_test4" ""
info "Purpose: Verify invalid data doesn't crash recovery" info "Purpose: Verify invalid data doesn't crash recovery"
info "" info ""
info "Expected time: 5-8 minutes" info "Expected time: 5-8 minutes"
@@ -1124,6 +1514,8 @@ main() {
fi fi
# Capture initial state # Capture initial state
set_test_context "phase1" "phase1_test4" "p1_t4_s1"
step_start "p1_t4_s1" "Inject invalid test data"
capture_alarms "test4_initial" capture_alarms "test4_initial"
capture_logcat "test4_initial" "DNP" 50 capture_logcat "test4_initial" "DNP" 50
@@ -1183,11 +1575,15 @@ main() {
if [ "${schedule_count}" -gt "0" ] 2>/dev/null; then if [ "${schedule_count}" -gt "0" ] 2>/dev/null; then
ok "Invalid test data injected successfully" ok "Invalid test data injected successfully"
step_pass "p1_t4_s1" "Invalid data injected"
else else
warn "No schedules found after injection - data may not have been inserted" warn "No schedules found after injection - data may not have been inserted"
step_warn "p1_t4_s1" "Data injection may have failed"
fi fi
substep "Step 3: Trigger recovery with invalid data" substep "Step 3: Trigger recovery with invalid data"
set_test_context "phase1" "phase1_test4" "p1_t4_s2"
step_start "p1_t4_s2" "Trigger recovery with invalid data"
$ADB_BIN shell am start -n "${APP_ID}/.MainActivity" > /dev/null 2>&1 $ADB_BIN shell am start -n "${APP_ID}/.MainActivity" > /dev/null 2>&1
sleep 3 # Give recovery time to run sleep 3 # Give recovery time to run
@@ -1198,8 +1594,11 @@ main() {
capture_alarms "test4_after_recovery" capture_alarms "test4_after_recovery"
capture_logcat "test4_after_recovery" "DNP-REACTIVATION|Skipping invalid" 100 capture_logcat "test4_after_recovery" "DNP-REACTIVATION|Skipping invalid" 100
capture_screenshot "test4_after_recovery" capture_screenshot "test4_after_recovery"
step_pass "p1_t4_s2" "Recovery triggered"
substep "Step 4: Check recovery logs for invalid data handling" substep "Step 4: Check recovery logs for invalid data handling"
set_test_context "phase1" "phase1_test4" "p1_t4_s3"
step_start "p1_t4_s3" "Check recovery logs"
# Check logs # Check logs
local recovery_logs local recovery_logs
@@ -1238,9 +1637,13 @@ main() {
if echo "${recovery_logs}" | grep -qiE "crash|fatal|exception.*recovery|Failed.*recovery"; then if echo "${recovery_logs}" | grep -qiE "crash|fatal|exception.*recovery|Failed.*recovery"; then
error "TEST 4 FAILED: Recovery crashed or threw fatal exception" error "TEST 4 FAILED: Recovery crashed or threw fatal exception"
test4_failed=true test4_failed=true
step_fail "p1_t4_s3" "Recovery crashed"
else
step_pass "p1_t4_s3" "Recovery handled invalid data gracefully"
fi fi
# Final verdict # Final verdict
set_test_context "phase1" "phase1_test4" "p1_t4_s4"
if [ "${test4_failed}" = "true" ]; then if [ "${test4_failed}" = "true" ]; then
verdict_fail "test4_invalid_data_handling" "Recovery crashed or threw fatal exception when handling invalid data" verdict_fail "test4_invalid_data_handling" "Recovery crashed or threw fatal exception when handling invalid data"
elif [ "${test4_passed}" = "true" ]; then elif [ "${test4_passed}" = "true" ]; then

View File

@@ -162,6 +162,25 @@
} }
} }
// Format date/time with seconds normalized to :00
function formatDateTimeNormalized(timestamp) {
if (!timestamp || timestamp === 0) return 'None scheduled';
const date = new Date(timestamp);
// Normalize seconds to :00
date.setSeconds(0, 0);
// Format as: MM/DD/YYYY, HH:MM:00 AM/PM
const options = {
month: '2-digit',
day: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: true
};
return date.toLocaleString('en-US', options);
}
function loadPluginStatus() { function loadPluginStatus() {
console.log('loadPluginStatus called'); console.log('loadPluginStatus called');
const pluginStatusContent = document.getElementById('pluginStatusContent'); const pluginStatusContent = document.getElementById('pluginStatusContent');
@@ -175,7 +194,7 @@
} }
window.DailyNotification.getNotificationStatus() window.DailyNotification.getNotificationStatus()
.then(result => { .then(result => {
const nextTime = result.nextNotificationTime ? new Date(result.nextNotificationTime).toLocaleString() : 'None scheduled'; const nextTime = formatDateTimeNormalized(result.nextNotificationTime);
const hasSchedules = result.isEnabled || (result.pending && result.pending > 0); const hasSchedules = result.isEnabled || (result.pending && result.pending > 0);
const statusIcon = hasSchedules ? '✅' : '⏸️'; const statusIcon = hasSchedules ? '✅' : '⏸️';
pluginStatusContent.innerHTML = `${statusIcon} Active Schedules: ${hasSchedules ? 'Yes' : 'No'}<br> pluginStatusContent.innerHTML = `${statusIcon} Active Schedules: ${hasSchedules ? 'Yes' : 'No'}<br>
@@ -233,8 +252,11 @@
priority: 'high' priority: 'high'
}) })
.then(() => { .then(() => {
const prefetchTimeReadable = prefetchTime.toLocaleTimeString(); // Normalize seconds to :00 for display
const notificationTimeReadable = notificationTime.toLocaleTimeString(); prefetchTime.setSeconds(0, 0);
notificationTime.setSeconds(0, 0);
const prefetchTimeReadable = prefetchTime.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true });
const notificationTimeReadable = notificationTime.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true });
status.innerHTML = '✅ Notification scheduled!<br>' + status.innerHTML = '✅ Notification scheduled!<br>' +
'📥 Prefetch: ' + prefetchTimeReadable + ' (' + prefetchTimeString + ')<br>' + '📥 Prefetch: ' + prefetchTimeReadable + ' (' + prefetchTimeString + ')<br>' +
'🔔 Notification: ' + notificationTimeReadable + ' (' + notificationTimeString + ')<br><br>' + '🔔 Notification: ' + notificationTimeReadable + ' (' + notificationTimeString + ')<br><br>' +
@@ -417,12 +439,16 @@
} }
} }
// Check for notification delivery periodically // Track last known nextNotificationTime to detect changes
let lastKnownNextNotificationTime = null;
// Check for notification delivery and status updates periodically
function checkNotificationDelivery() { function checkNotificationDelivery() {
if (!window.DailyNotification) return; if (!window.DailyNotification) return;
window.DailyNotification.getNotificationStatus() window.DailyNotification.getNotificationStatus()
.then(result => { .then(result => {
// Check for notification delivery
if (result.lastNotificationTime) { if (result.lastNotificationTime) {
const lastTime = new Date(result.lastNotificationTime); const lastTime = new Date(result.lastNotificationTime);
const now = new Date(); const now = new Date();
@@ -435,15 +461,37 @@
if (indicator && timeSpan) { if (indicator && timeSpan) {
indicator.style.display = 'block'; indicator.style.display = 'block';
timeSpan.textContent = `Received at ${lastTime.toLocaleTimeString()}`; // Normalize seconds to :00
lastTime.setSeconds(0, 0);
timeSpan.textContent = `Received at ${lastTime.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true })}`;
// Hide after 30 seconds // Hide after 30 seconds
setTimeout(() => { setTimeout(() => {
indicator.style.display = 'none'; indicator.style.display = 'none';
}, 30000); }, 30000);
// Force immediate refresh when notification is received (rollover may have occurred)
setTimeout(() => {
loadPluginStatus();
}, 1000); // Wait 1 second for rollover to complete
} }
} }
} }
// Detect if nextNotificationTime changed (rollover occurred)
const currentNextTime = result.nextNotificationTime;
if (currentNextTime && currentNextTime !== lastKnownNextNotificationTime) {
if (lastKnownNextNotificationTime !== null) {
console.log('Next notification time changed - rollover detected!');
// Force immediate refresh
loadPluginStatus();
}
lastKnownNextNotificationTime = currentNextTime;
}
// Auto-refresh plugin status periodically to show updated next notification time after rollover
// This ensures the UI updates when the plugin reschedules the notification
loadPluginStatus();
}) })
.catch(error => { .catch(error => {
// Silently fail - this is just for visual feedback // Silently fail - this is just for visual feedback
@@ -459,8 +507,19 @@
loadPermissionStatus(); loadPermissionStatus();
loadChannelStatus(); loadChannelStatus();
// Check for notification delivery every 5 seconds // Initialize last known next notification time
setInterval(checkNotificationDelivery, 5000); if (window.DailyNotification) {
window.DailyNotification.getNotificationStatus()
.then(result => {
lastKnownNextNotificationTime = result.nextNotificationTime;
console.log('Initialized nextNotificationTime:', lastKnownNextNotificationTime);
})
.catch(() => {});
}
// Check for notification delivery and status updates every 3 seconds (more frequent)
// This ensures UI updates quickly when rollover occurs
setInterval(checkNotificationDelivery, 3000);
}, 500); }, 500);
}); });