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:
@@ -65,16 +65,20 @@ check_permissions() {
|
||||
print_info "Checking notification permissions..."
|
||||
|
||||
# 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
|
||||
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
|
||||
else
|
||||
# Check if we're on Android 12 or below (permission not required)
|
||||
SDK_VERSION=$($ADB_BIN shell getprop ro.build.version.sdk)
|
||||
if [ "${SDK_VERSION}" -lt "33" ]; then
|
||||
SDK_VERSION=$($ADB_BIN shell getprop ro.build.version.sdk 2>/dev/null | tr -d '\n\r ' || echo "0")
|
||||
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
|
||||
fi
|
||||
fi
|
||||
@@ -88,6 +92,158 @@ check_permissions() {
|
||||
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() {
|
||||
if check_permissions; then
|
||||
print_success "Permissions already granted"
|
||||
@@ -280,38 +436,100 @@ run_setup() {
|
||||
launch_app
|
||||
sleep 2
|
||||
|
||||
# Check permissions
|
||||
section "Permission Checks"
|
||||
if check_permissions; then
|
||||
ok "Notification permissions already granted"
|
||||
# Check permissions and plugin configuration together
|
||||
section "Permissions & Plugin Configuration"
|
||||
info "Checking both permissions and plugin/fetcher configuration..."
|
||||
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
|
||||
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"
|
||||
exit 1
|
||||
fi
|
||||
ensure_permissions
|
||||
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
|
||||
if [ "${plugin_ok}" != "true" ]; then
|
||||
error "Plugin not configured - cannot proceed in CI mode"
|
||||
exit 1
|
||||
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
|
||||
|
||||
ok "Setup complete!"
|
||||
@@ -429,6 +647,9 @@ main() {
|
||||
if should_run_test "0" SELECTED_TESTS; then
|
||||
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 " schedule is correctly computed and only ONE alarm exists."
|
||||
info ""
|
||||
@@ -441,12 +662,26 @@ main() {
|
||||
fi
|
||||
|
||||
# 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_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
|
||||
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_alarms "test0_before_schedule"
|
||||
@@ -499,6 +734,7 @@ main() {
|
||||
fi
|
||||
|
||||
# Show alarm details if we found exactly 1 alarm
|
||||
local initial_alarm_time=""
|
||||
if [ "${initial_count}" -eq "1" ] 2>/dev/null; then
|
||||
ok "Single notification alarm scheduled (one per day)"
|
||||
|
||||
@@ -508,34 +744,53 @@ main() {
|
||||
info "Notification alarm details:"
|
||||
echo "${alarm_details}" | head -5
|
||||
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
|
||||
elif [ "${initial_count}" -gt "1" ] 2>/dev/null; then
|
||||
warn "Found ${initial_count} notification alarms (expected: 1) - DUPLICATES DETECTED!"
|
||||
fi
|
||||
|
||||
step_pass "p1_t0_s2" "Notification scheduled"
|
||||
|
||||
substep "Step 2: Wait for notification to fire"
|
||||
info "To complete this test:"
|
||||
echo " 1. Wait for notification to fire (or advance emulator clock)"
|
||||
echo " 2. Verify plugin computed next day's time (24 hours later)"
|
||||
echo " 3. Verify exactly ONE notification alarm exists for tomorrow"
|
||||
echo " 4. Verify no duplicate alarms were created"
|
||||
set_test_context "phase1" "phase1_test0" "p1_t0_s3"
|
||||
step_start "p1_t0_s3" "Waiting for notification to fire"
|
||||
info "The test will automatically verify rollover after the notification fires."
|
||||
echo ""
|
||||
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 ""
|
||||
|
||||
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)
|
||||
• Alarm time is for tomorrow (24 hours later)
|
||||
• No duplicate alarms were created
|
||||
• Alarm time advanced to tomorrow (24 hours later)
|
||||
• 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
|
||||
info "CI mode: Skipping manual verification step"
|
||||
info "CI mode: Waiting for notification to fire..."
|
||||
sleep 5 # Give time for rollover if automated
|
||||
fi
|
||||
|
||||
step_pass "p1_t0_s3" "Notification fired (or time advanced)"
|
||||
|
||||
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
|
||||
post_rollover_count="$(wait_for_stable_plugin_alarm_count 5 2)"
|
||||
@@ -543,22 +798,95 @@ main() {
|
||||
|
||||
# Capture post-rollover state
|
||||
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"
|
||||
|
||||
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 "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
|
||||
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"
|
||||
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
|
||||
step_fail "p1_t0_s6" "Duplicate alarms detected"
|
||||
error "Daily rollover created ${post_rollover_count} NOTIFICATION alarms (duplicates)"
|
||||
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})"
|
||||
else
|
||||
step_warn "p1_t0_s6" "No alarm found after rollover"
|
||||
warn "No NOTIFICATION alarm found after rollover (expected: 1, got: 0)"
|
||||
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)"
|
||||
fi
|
||||
|
||||
@@ -575,6 +903,9 @@ main() {
|
||||
if should_run_test "1" SELECTED_TESTS; then
|
||||
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 " uses the database to rebuild alarms on app relaunch."
|
||||
info ""
|
||||
@@ -587,10 +918,10 @@ main() {
|
||||
fi
|
||||
|
||||
# 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_logcat "test1_initial" "DNP" 50
|
||||
|
||||
substep "Step 1: Clean start - verify no lingering alarms"
|
||||
local lingering_count system_count
|
||||
lingering_count="$(get_plugin_alarm_count)"
|
||||
system_count="$(get_system_alarm_count)"
|
||||
@@ -682,10 +1013,14 @@ main() {
|
||||
ok "No lingering plugin alarms found (clean state)"
|
||||
fi
|
||||
|
||||
step_pass "p1_t1_s1" "Clean state verified"
|
||||
|
||||
# Skip remaining TEST 1 steps if we couldn't achieve clean state
|
||||
if [ "${goto_test1_end}" != "true" ]; then
|
||||
|
||||
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
|
||||
ensure_plugin_configured
|
||||
|
||||
@@ -707,7 +1042,11 @@ main() {
|
||||
capture_alarms "test1_before_force_stop"
|
||||
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)"
|
||||
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
|
||||
alarm_count_before="$(get_plugin_alarm_count)"
|
||||
system_count_before="$(get_system_alarm_count)"
|
||||
@@ -757,6 +1096,8 @@ main() {
|
||||
fi
|
||||
|
||||
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"
|
||||
info "Executing: $ADB_BIN shell am force-stop ${APP_ID}"
|
||||
force_stop_app
|
||||
@@ -766,6 +1107,8 @@ main() {
|
||||
capture_alarms "test1_after_force_stop"
|
||||
|
||||
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
|
||||
local alarm_count_after system_count_after
|
||||
alarm_count_after="$(get_plugin_alarm_count)"
|
||||
@@ -776,10 +1119,12 @@ main() {
|
||||
if [ "${alarm_count_after}" -eq "0" ] 2>/dev/null; then
|
||||
ok "Plugin alarms cleared by force-stop (count: ${alarm_count_after})"
|
||||
info "This confirms: Force-stop cleared alarms from AlarmManager"
|
||||
step_pass "p1_t1_s4" "Alarms cleared by force-stop"
|
||||
else
|
||||
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 "Continuing test anyway - recovery should still work"
|
||||
step_warn "p1_t1_s4" "Alarms not cleared (device-specific behavior)"
|
||||
fi
|
||||
|
||||
if [ "${MODE}" != "ci" ]; then
|
||||
@@ -787,11 +1132,15 @@ main() {
|
||||
fi
|
||||
|
||||
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
|
||||
launch_app
|
||||
sleep 4 # Give recovery time to run
|
||||
|
||||
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
|
||||
|
||||
# Capture after recovery state
|
||||
@@ -842,8 +1191,12 @@ main() {
|
||||
|
||||
# Final verdict
|
||||
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})"
|
||||
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})"
|
||||
fi
|
||||
|
||||
@@ -904,6 +1257,9 @@ main() {
|
||||
if should_run_test "2" SELECTED_TESTS; then
|
||||
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 ""
|
||||
info "Expected time: 4-6 minutes"
|
||||
@@ -915,6 +1271,8 @@ main() {
|
||||
fi
|
||||
|
||||
# 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_logcat "test2_initial" "DNP" 50
|
||||
|
||||
@@ -931,6 +1289,7 @@ main() {
|
||||
|
||||
if [ "${initial_alarm_count}" -eq "1" ] 2>/dev/null; then
|
||||
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
|
||||
warn "No initial alarm found - scheduling one first..."
|
||||
if [ "${MODE}" != "ci" ]; then
|
||||
@@ -943,20 +1302,25 @@ main() {
|
||||
initial_alarm_count="$(get_plugin_alarm_count)"
|
||||
if [ "${initial_alarm_count}" -eq "1" ] 2>/dev/null; then
|
||||
ok "Alarm scheduled"
|
||||
step_pass "p1_t2_s1" "Initial notification scheduled"
|
||||
else
|
||||
error "Failed to schedule initial alarm"
|
||||
step_fail "p1_t2_s1" "Failed to schedule"
|
||||
if [ "${MODE}" != "ci" ]; then
|
||||
pause
|
||||
fi
|
||||
fi
|
||||
else
|
||||
warn "Found ${initial_alarm_count} plugin alarms (expected: 1) - continuing anyway"
|
||||
step_warn "p1_t2_s1" "Unexpected alarm count"
|
||||
fi
|
||||
|
||||
# Capture before update state
|
||||
capture_alarms "test2_before_update"
|
||||
|
||||
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
|
||||
ui_prompt "2) In the app UI, change the schedule time (e.g., from 06:50 to 07:10)
|
||||
and apply the schedule.
|
||||
@@ -982,16 +1346,21 @@ main() {
|
||||
|
||||
if [ "${updated_alarm_count}" -eq "1" ] 2>/dev/null; then
|
||||
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
|
||||
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"
|
||||
info "This indicates the plugin did not clean up existing schedules before creating new one"
|
||||
test2_failed=true
|
||||
step_fail "p1_t2_s2" "Multiple alarms detected"
|
||||
else
|
||||
warn "Found ${updated_alarm_count} plugin alarms (expected: 1) - no alarm scheduled after update"
|
||||
step_warn "p1_t2_s2" "No alarm after update"
|
||||
fi
|
||||
|
||||
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
|
||||
clear_logs
|
||||
launch_app
|
||||
@@ -1025,9 +1394,15 @@ main() {
|
||||
error "Multiple schedules in database caused recovery to reschedule duplicates"
|
||||
error "This violates 'one per day' semantics - only one notification per day should exist"
|
||||
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
|
||||
|
||||
# Final verdict
|
||||
set_test_context "phase1" "phase1_test2" "p1_t2_s4"
|
||||
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})"
|
||||
elif [ "${alarm_count_after_recovery}" -eq "1" ] 2>/dev/null; then
|
||||
@@ -1055,6 +1430,9 @@ main() {
|
||||
if should_run_test "3" SELECTED_TESTS; then
|
||||
section "TEST 3: Recovery Timeout"
|
||||
|
||||
# Set test context
|
||||
set_test_context "phase1" "phase1_test3" ""
|
||||
|
||||
info "Purpose: Verify recovery times out gracefully"
|
||||
info ""
|
||||
info "Expected time: 2-3 minutes"
|
||||
@@ -1070,6 +1448,8 @@ main() {
|
||||
capture_alarms "test3_initial"
|
||||
|
||||
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
|
||||
timeout_found=false
|
||||
timeout_protection_found=false
|
||||
@@ -1091,7 +1471,14 @@ main() {
|
||||
# Capture evidence
|
||||
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
|
||||
set_test_context "phase1" "phase1_test3" "p1_t3_s2"
|
||||
if [ "${timeout_found}" = "true" ] && [ "${timeout_protection_found}" = "true" ]; then
|
||||
verdict_pass "test3_recovery_timeout" "Timeout mechanism verified in code (2 seconds, withTimeout protection)"
|
||||
else
|
||||
@@ -1111,6 +1498,9 @@ main() {
|
||||
if should_run_test "4" SELECTED_TESTS; then
|
||||
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 ""
|
||||
info "Expected time: 5-8 minutes"
|
||||
@@ -1124,6 +1514,8 @@ main() {
|
||||
fi
|
||||
|
||||
# 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_logcat "test4_initial" "DNP" 50
|
||||
|
||||
@@ -1183,11 +1575,15 @@ main() {
|
||||
|
||||
if [ "${schedule_count}" -gt "0" ] 2>/dev/null; then
|
||||
ok "Invalid test data injected successfully"
|
||||
step_pass "p1_t4_s1" "Invalid data injected"
|
||||
else
|
||||
warn "No schedules found after injection - data may not have been inserted"
|
||||
step_warn "p1_t4_s1" "Data injection may have failed"
|
||||
fi
|
||||
|
||||
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
|
||||
sleep 3 # Give recovery time to run
|
||||
|
||||
@@ -1198,8 +1594,11 @@ main() {
|
||||
capture_alarms "test4_after_recovery"
|
||||
capture_logcat "test4_after_recovery" "DNP-REACTIVATION|Skipping invalid" 100
|
||||
capture_screenshot "test4_after_recovery"
|
||||
step_pass "p1_t4_s2" "Recovery triggered"
|
||||
|
||||
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
|
||||
local recovery_logs
|
||||
@@ -1238,9 +1637,13 @@ main() {
|
||||
if echo "${recovery_logs}" | grep -qiE "crash|fatal|exception.*recovery|Failed.*recovery"; then
|
||||
error "TEST 4 FAILED: Recovery crashed or threw fatal exception"
|
||||
test4_failed=true
|
||||
step_fail "p1_t4_s3" "Recovery crashed"
|
||||
else
|
||||
step_pass "p1_t4_s3" "Recovery handled invalid data gracefully"
|
||||
fi
|
||||
|
||||
# Final verdict
|
||||
set_test_context "phase1" "phase1_test4" "p1_t4_s4"
|
||||
if [ "${test4_failed}" = "true" ]; then
|
||||
verdict_fail "test4_invalid_data_handling" "Recovery crashed or threw fatal exception when handling invalid data"
|
||||
elif [ "${test4_passed}" = "true" ]; then
|
||||
|
||||
@@ -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() {
|
||||
console.log('loadPluginStatus called');
|
||||
const pluginStatusContent = document.getElementById('pluginStatusContent');
|
||||
@@ -175,7 +194,7 @@
|
||||
}
|
||||
window.DailyNotification.getNotificationStatus()
|
||||
.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 statusIcon = hasSchedules ? '✅' : '⏸️';
|
||||
pluginStatusContent.innerHTML = `${statusIcon} Active Schedules: ${hasSchedules ? 'Yes' : 'No'}<br>
|
||||
@@ -233,8 +252,11 @@
|
||||
priority: 'high'
|
||||
})
|
||||
.then(() => {
|
||||
const prefetchTimeReadable = prefetchTime.toLocaleTimeString();
|
||||
const notificationTimeReadable = notificationTime.toLocaleTimeString();
|
||||
// Normalize seconds to :00 for display
|
||||
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>' +
|
||||
'📥 Prefetch: ' + prefetchTimeReadable + ' (' + prefetchTimeString + ')<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() {
|
||||
if (!window.DailyNotification) return;
|
||||
|
||||
window.DailyNotification.getNotificationStatus()
|
||||
.then(result => {
|
||||
// Check for notification delivery
|
||||
if (result.lastNotificationTime) {
|
||||
const lastTime = new Date(result.lastNotificationTime);
|
||||
const now = new Date();
|
||||
@@ -435,15 +461,37 @@
|
||||
|
||||
if (indicator && timeSpan) {
|
||||
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
|
||||
setTimeout(() => {
|
||||
indicator.style.display = 'none';
|
||||
}, 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 => {
|
||||
// Silently fail - this is just for visual feedback
|
||||
@@ -459,8 +507,19 @@
|
||||
loadPermissionStatus();
|
||||
loadChannelStatus();
|
||||
|
||||
// Check for notification delivery every 5 seconds
|
||||
setInterval(checkNotificationDelivery, 5000);
|
||||
// Initialize last known next notification time
|
||||
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);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user