# iOS Test App Requirements
**Purpose:** What the iOS test app must provide so that the testing guide can be executed with parity vs Android
**Plugin Target:** DailyNotificationPlugin v3.x (iOS)
**Phase:** Phase 1 – Prefetch MVP
**Status:** 📋 **REQUIRED FOR PHASE 1**
**Date:** 2025-11-15
**Author:** Matthew Raymer
**Directive Reference:** `doc/directives/0003-iOS-Android-Parity-Directive.md`
**Note:** This app exists to support the prefetch testing scenarios in `doc/test-app-ios/IOS_PREFETCH_TESTING.md`.
**Android parity:** Behavior is aligned with `test-apps/android-test-app` where platform constraints allow. Timing and BGTask heuristics **will differ** from Android's exact alarms:
- **Android:** Exact alarms via AlarmManager / WorkManager
- **iOS:** Heuristic BGTaskScheduler (see glossary); no hard guarantee of 5-min prefetch
**Glossary:** See glossary in `IOS_PREFETCH_TESTING.md` for terminology.
---
## Overview
This document defines the requirements for the iOS test app (`test-apps/ios-test-app/`) that must be created as part of Phase 1 implementation. The iOS test app must provide UI parity with the Android test app (`test-apps/android-test-app/`) while respecting iOS-specific constraints and capabilities.
## Non-Goals (Phase 1)
**Out of scope for Phase 1:**
- ❌ No full UX polish (color, branding)
- ❌ No localization / accessibility guarantees (text only for internal QA)
- ❌ No production signing / App Store deployment
- ❌ No advanced UI features beyond basic functionality testing
## Security & Privacy Constraints
**Critical requirements for test app implementation:**
- Test app MUST use **non-production** endpoints and credentials
- JWT / API keys used here are **test-only** and may be rotated or revoked at any time
- Logs MUST NOT include real user PII (names, emails, phone numbers)
- Any screenshots or shared logs should be scrubbed of secrets before external sharing
- Test app should clearly indicate it's a development/testing build (not production)
## If You Only Have 30 Minutes
Quick setup checklist:
1. Copy HTML/JS from Android test app (`test-apps/android-test-app/app/src/main/assets/public/index.html`)
2. Wire plugin into Capacitor (`capacitor.config.json`)
3. Add Info.plist keys (BGTask identifiers, background modes, notification permissions)
4. Build/run (`./scripts/build-ios-test-app.sh --simulator` or Xcode)
5. Press buttons: Check Plugin Status → Request Permissions → Schedule Test Notification
6. See logs with prefixes `DNP-PLUGIN`, `DNP-FETCH`, `DNP-SCHEDULER`
---
## UI Parity Requirements
### HTML/JS UI
The iOS test app **MUST** use the same HTML/JS UI as the Android test app to ensure consistent testing experience across platforms.
**Source:** Copy from `test-apps/android-test-app/app/src/main/assets/public/index.html`
**Required UI Elements:**
- Plugin registration status indicator
- Permission status display (✅/❌ indicators)
- Test notification button
- Check permissions button
- Request permissions button
- Channel management buttons (Check Channel Status, Open Channel Settings)
- Status display area
- Log output area (optional, for debugging)
### UI Functionality
The test app UI must support:
1. **Plugin Status Check**
- Display plugin availability status
- Show "Plugin is loaded and ready!" when available
2. **Permission Management**
- Display current permission status
- Request permissions button
- Check permissions button
- Show ✅/❌ indicators for each permission
3. **Channel Management** (iOS parity with Android)
- Check channel status button (iOS: checks app-wide notification authorization)
- Open channel settings button (iOS: opens app Settings, not per-channel)
- Note: iOS doesn't have per-channel control like Android; these methods provide app-wide equivalents
4. **Notification Testing**
- Schedule test notification button
- Display scheduled time
- Show notification status
5. **Status Display**
- Show last notification time
- Show pending notification count
- Display error messages if any
### UI Elements to Plugin Methods Mapping
| UI Element / Button | Plugin Method / API Call | Notes |
|---------------------|-------------------------|-------|
| "Check Plugin Status" | `DailyNotification.configure()` or status call | Verify plugin load & config |
| "Check Permissions" | `checkPermissionStatus()` | Returns current notification permission status |
| "Request Permissions" | `requestNotificationPermissions()` | Requests notification permissions (shows system dialog) |
| "Schedule Test Notification" | `scheduleDailyNotification()` | Should schedule prefetch + notify |
| "Show Last Notification" | `getLastNotification()` | Uses deterministic path (Bucket A) |
| "Cancel All Notifications" | `cancelAllNotifications()` | Uses deterministic path (Bucket A) |
| "Get Notification Status" | `getNotificationStatus()` | Uses deterministic path (Bucket A) |
| "Check Channel Status" | `isChannelEnabled(channelId?)` | Checks if notifications enabled (iOS: app-wide) |
| "Open Channel Settings" | `openChannelSettings(channelId?)` | Opens notification settings (iOS: app Settings) |
**See `IOS_PREFETCH_TESTING.md` Behavior Classification for deterministic vs heuristic methods.**
---
## iOS Permissions Configuration
### Info.plist Requirements
The test app's `Info.plist` **MUST** include:
```xml
BGTaskSchedulerPermittedIdentifiers
com.timesafari.dailynotification.fetch
com.timesafari.dailynotification.notify
UIBackgroundModes
background-fetch
background-processing
remote-notification
NSUserNotificationsUsageDescription
This app uses notifications to deliver daily updates and reminders.
```
### Background App Refresh
- Background App Refresh must be enabled in Settings
- Test app should check and report Background App Refresh status
- User should be guided to enable Background App Refresh if disabled
---
## Build Options
### Xcode GUI Build
1. **Open Workspace:**
```bash
cd test-apps/ios-test-app
open App.xcworkspace # or App.xcodeproj
```
2. **Select Target:**
- Choose iOS Simulator (iPhone 15, iPhone 15 Pro, etc.)
- Or physical device (requires signing)
3. **Build and Run:**
- Press Cmd+R
- Or Product → Run
### Command-Line Build
Use the build script:
```bash
# From repo root
./scripts/build-ios-test-app.sh --simulator
# Or for device
./scripts/build-ios-test-app.sh --device
```
### Future CI Integration (Optional)
**Note for Phase 2+:** Consider adding `xcodebuild`-based CI job that:
- Builds `test-apps/ios-test-app` for simulator
- Runs a minimal UI test that:
- Launches app
- Calls `configure()` and `getNotificationStatus()`
- This ensures test app remains buildable as plugin evolves
**Phase 1:** Manual testing only; CI integration is out of scope.
### Build Requirements
**Required Tools:**
- **Xcode:** 15.0 or later
- **macOS:** 13.0 (Ventura) or later
- **iOS Deployment Target:** iOS 15.0 or later
- **CocoaPods:** >= 1.13 (must run `pod install` before first build)
- **Node.js:** 20.x (recommended)
- **npm:** Latest stable (comes with Node.js)
- **Xcode Command Line Tools:** Must run `xcode-select --install` if not already installed
**Note:** Mismatched versions are **out of scope** for Phase 1 support. Use recommended versions to avoid compatibility issues.
---
## Capacitor Configuration
### Plugin Registration
The test app **MUST** register the DailyNotification plugin:
**`capacitor.config.json` or `capacitor.config.ts`:**
```json
{
"plugins": {
"DailyNotification": {
"enabled": true
}
}
}
```
### Plugin Path
The plugin must be accessible from the test app:
- **Development:** Plugin source at `../../ios/Plugin/`
- **Production:** Plugin installed via npm/CocoaPods
### Sync Command
After making changes to plugin or web assets:
```bash
cd test-apps/ios-test-app
npx cap sync ios
```
---
## Debugging Strategy
**When executing the scenarios in `IOS_PREFETCH_TESTING.md`, use the following commands while looking for logs with prefixes `DNP-PLUGIN`, `DNP-FETCH`, `DNP-SCHEDULER`.**
### Xcode Debugger
**Check Pending Notifications:**
```swift
po UNUserNotificationCenter.current().pendingNotificationRequests()
```
**Check Permission Status:**
```swift
po await UNUserNotificationCenter.current().notificationSettings()
```
**Manually Trigger BGTask (Simulator Only):**
```swift
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]
```
### Console.app Logging
1. Open Console.app (Applications → Utilities)
2. Select device/simulator
3. Filter by: `DNP-` or `DailyNotification`
**Key Log Prefixes:**
- `DNP-PLUGIN:` - Main plugin operations
- `DNP-FETCH:` - Background fetch operations
- `DNP-SCHEDULER:` - Scheduling operations
- `DNP-STORAGE:` - Storage operations
### Common Debugging Scenarios
**Scenario: BGTask not running when expected → follow this checklist:**
1. **BGTask Not Running:**
- Check Info.plist has `BGTaskSchedulerPermittedIdentifiers`
- Verify identifiers match code exactly (case-sensitive)
- Verify task registered in AppDelegate before app finishes launching
- Check Background App Refresh enabled in Settings → [Your App]
- Verify app not force-quit (iOS won't run BGTask for force-quit apps)
- Use simulator-only LLDB command to manually trigger
- **See also:** `IOS_PREFETCH_TESTING.md – Log Checklist: Section 3`
2. **Notifications Not Delivering:**
- Check notification permissions: `UNUserNotificationCenter.current().getNotificationSettings()`
- Verify notification scheduled: `UNUserNotificationCenter.current().getPendingNotificationRequests()`
- Check notification category registered
- **See also:** `IOS_PREFETCH_TESTING.md – Log Checklist: Section 4`
3. **BGTaskScheduler Fails on Simulator:**
- **Expected Behavior:** BGTaskSchedulerErrorDomain Code=1 (notPermitted) is **normal on simulator**
- BGTaskScheduler doesn't work reliably on simulator - this is an iOS limitation, not a plugin bug
- Notification scheduling still works; prefetch won't run on simulator but will work on real devices
- Error handling logs clear message: "Background fetch scheduling failed (expected on simulator)"
- **See also:** `IOS_PREFETCH_TESTING.md – Known OS Limitations` for details
- **Testing:** Use Xcode → Debug → Simulate Background Fetch for simulator testing
4. **Plugin Not Discovered:**
- Check plugin class conforms to `CAPBridgedPlugin` protocol (required for Capacitor discovery)
- Verify `@objc extension DailyNotificationPlugin: CAPBridgedPlugin` exists in plugin code
- Check plugin framework is force-loaded in AppDelegate before Capacitor initializes
- Verify `pluginMethods` array includes all `@objc` methods
- **See also:** `doc/directives/0003-iOS-Android-Parity-Directive.md – Plugin Discovery Issue` for detailed troubleshooting
5. **Build Failures:**
- Run `pod install`
- Clean build folder (Cmd+Shift+K)
- Verify Capacitor plugin path
---
## Test App Implementation Checklist
### Setup
- [ ] Create `test-apps/ios-test-app/` directory
- [ ] Initialize Capacitor iOS project
- [ ] Copy HTML/JS UI from Android test app
- [ ] Configure Info.plist with BGTask identifiers
- [ ] Configure Info.plist with background modes
- [ ] Add notification permission description
### Plugin Integration
- [ ] Register DailyNotification plugin in Capacitor config
- [ ] Ensure plugin path is correct
- [ ] Run `npx cap sync ios`
- [ ] Verify plugin loads in test app
### UI Implementation
- [ ] Copy HTML/JS from Android test app
- [ ] Test plugin status display
- [ ] Test permission status display
- [ ] Test notification scheduling UI
- [ ] Test status display
### Build & Test
- [ ] Build script works (`./scripts/build-ios-test-app.sh`)
- [ ] App builds in Xcode
- [ ] App runs on simulator
- [ ] Plugin methods work from UI
- [ ] Notifications deliver correctly
- [ ] BGTask executes (with manual trigger in simulator)
---
## File Structure
```
test-apps/ios-test-app/
├── App.xcworkspace # Xcode workspace (if using CocoaPods)
├── App.xcodeproj # Xcode project
├── App/ # Main app directory
│ ├── App/
│ │ ├── AppDelegate.swift
│ │ ├── SceneDelegate.swift
│ │ ├── Info.plist # Must include BGTask identifiers
│ │ └── Assets.xcassets
│ └── Public/ # Web assets (HTML/JS)
│ └── index.html # Same as Android test app
├── Podfile # CocoaPods dependencies
├── capacitor.config.json # Capacitor configuration
└── package.json # npm dependencies (if any)
```
---
## Testing Scenarios
### Basic Functionality
**Happy Path Example:**
When everything is working correctly, your first run should produce logs like this:
```text
[DNP-PLUGIN] configure() called
[DNP-PLUGIN] status = ready
[DNP-PLUGIN] requestPermissions() → granted
[DNP-SCHEDULER] scheduleDailyNotification() → notificationTime=2025-11-15T05:53:00Z
[DNP-FETCH] Scheduling prefetch for notification at 2025-11-15T05:53:00Z
[DNP-FETCH] BGAppRefreshTask scheduled (earliestBeginDate=2025-11-15T05:48:00Z)
[DNP-SCHEDULER] Scheduling notification for 2025-11-15T05:53:00Z
[DNP-STORAGE] Persisted schedule to DB (id=..., type=DAILY, ...)
```
**If your first run doesn't look roughly like this, fix that before proceeding to BGTask tests.**
1. **Plugin Registration** `[P1-Core][SIM+DEV]`
- Launch app
- Verify plugin status shows "Plugin is loaded and ready!"
- **See also:** `IOS_PREFETCH_TESTING.md – Simulator Test Plan: Step 2`
2. **Permission Management** `[P1-Core][SIM+DEV]`
- Check permissions
- Request permissions
- Verify permissions granted
- **See also:** `IOS_PREFETCH_TESTING.md – Simulator Test Plan: Step 6 (Negative-Path Tests)`
3. **Notification Scheduling** `[P1-Prefetch][SIM+DEV]`
- Schedule test notification
- Verify notification scheduled
- Wait for notification to appear
- **See also:** `IOS_PREFETCH_TESTING.md – Simulator Test Plan: Steps 3-5`
### Background Tasks
**Sim vs Device Behavior:**
- `[SIM+DEV]` – can be fully tested on simulator and device (logic correctness)
- `[DEV-ONLY]` – timing / heuristic behavior, must be verified on real hardware
1. **BGTask Scheduling** `[P1-Prefetch][SIM+DEV]`
- Schedule notification with prefetch
- Verify BGTask scheduled 5 minutes before notification
- Manually trigger BGTask (simulator only)
- Verify content fetched
- **Note:** Logic and logs on sim; real timing on device
- **See also:** `IOS_PREFETCH_TESTING.md – Simulator Test Plan: Steps 3-4`
2. **BGTask Miss Detection** `[P1-Prefetch][DEV-ONLY]`
- Schedule notification
- Wait 15+ minutes
- Launch app
- Verify BGTask rescheduled
- **Note:** Only meaningful on device (heuristic timing)
- **See also:** `IOS_PREFETCH_TESTING.md – Real Device Test Plan: Step 4`
### Error Handling
1. **Permission Denied** `[P1-Core]`
- Deny notification permissions
- Try to schedule notification
- Verify error returned
- **See also:** `IOS_PREFETCH_TESTING.md – Simulator Test Plan: Step 6 (Negative-Path Tests)`
2. **Invalid Parameters** `[P1-Core]`
- Try to schedule with invalid time format
- Verify error returned
### Advanced Features `[P2-Advanced]`
1. **Rolling Window** `[P2-Advanced]`
- Schedule multiple notifications
- Verify rolling window maintenance
- Check notification limits (64 max on iOS)
2. **TTL Enforcement** `[P2-Advanced]`
- Schedule notification with prefetch
- Wait for TTL to expire
- Verify stale content discarded at delivery
---
## Developer/Test Harness Features (Optional but Recommended)
Since this test app is for internal testing, it can expose more tools:
### Dev-Only Toggles
1. **Force Schedule Notification N Minutes from Now**
- Bypass normal scheduling UI
- Directly call `scheduleDailyNotification()` with calculated time
- Useful for quick testing scenarios
2. **Force "Prefetch-Only" Task**
- Trigger BGTask without scheduling notification
- Useful for testing prefetch logic in isolation
- Display raw JSON returned from API (last fetched payload)
3. **Display Raw API Response**
- Show last fetched payload as JSON
- Useful for debugging API responses
- Can be referenced from `IOS_PREFETCH_TESTING.md` as shortcuts for specific scenarios
4. **Manual BGTask Trigger (Dev Build Only)**
- Button to manually trigger BGTask (simulator only)
- Wraps the LLDB command in UI for convenience
These features can then be referenced from `IOS_PREFETCH_TESTING.md` as shortcuts for specific test scenarios.
## Risks & Gotchas
**Common pitfalls when working with the test app:**
1. **Accidentally editing shared HTML/JS in iOS test app instead of Android canonical source**
- Always edit Android test app HTML/JS first, then copy to iOS
- Keep iOS test app HTML/JS as a copy, not the source of truth
2. **Forgetting `npx cap sync ios` after plugin or asset changes**
- Run `npx cap sync ios` after any plugin code changes
- Run `npx cap sync ios` after any web asset changes
- Check `capacitor.config.json` is up to date
3. **Running with stale Pods**
- Run `pod repo update` periodically
- Run `pod install` after dependency changes
- Clean build folder (Cmd+Shift+K) if Pods seem stale
4. **Changing BGTask identifiers in one place but not the other**
- Info.plist `BGTaskSchedulerPermittedIdentifiers` must match Swift code exactly (case-sensitive)
- Check both places when updating identifiers
- See `IOS_PREFETCH_TESTING.md` for identifier requirements
5. **Mismatched tooling versions**
- Use recommended versions (Node 20.x, CocoaPods >= 1.13, Xcode 15.0+)
- Mismatched versions are out of scope for Phase 1 support
## References
- **Directive:** `doc/directives/0003-iOS-Android-Parity-Directive.md`
- **Testing Guide:** `doc/test-app-ios/IOS_PREFETCH_TESTING.md` - Comprehensive prefetch testing procedures
- **Android Test App:** `test-apps/android-test-app/`
- **Build Script:** `scripts/build-ios-test-app.sh`
---
## Review & Sign-Off Checklist (Phase 1)
**Use this checklist to verify Phase 1 iOS test app is complete:**
- [ ] Test app builds on simulator with recommended toolchain (Node 20.x, CocoaPods >= 1.13, Xcode 15.0+)
- [ ] Test app builds on at least one real device
- [ ] All UI buttons map to plugin methods as per the **UI Elements to Plugin Methods Mapping** table
- [ ] Happy-path log sequence matches the example in **Testing Scenarios → Basic Functionality**
- [ ] BGTask identifiers are consistent (Info.plist ↔ Swift ↔ docs)
- [ ] Risks & Gotchas section has been read and acknowledged by the implementer
- [ ] Security & Privacy Constraints have been followed (non-production endpoints, no PII in logs)
---
## Changelog (high-level)
- 2025-11-15 — Initial Phase 1 version (prefetch MVP, Android parity)
---
**Status:** 📋 **REQUIRED FOR PHASE 1**
**Last Updated:** 2025-11-15