149 lines
5.0 KiB
Markdown
149 lines
5.0 KiB
Markdown
|
|
# Testing Proximity Without an iPhone
|
||
|
|
|
||
|
|
This document explains **how to run and test the entire app using only the
|
||
|
|
iOS Simulator** — no physical iPhone required for the core interaction loop.
|
||
|
|
|
||
|
|
## The strategy in one sentence
|
||
|
|
|
||
|
|
> **The interaction logic is fully testable in the simulator. Only the physical
|
||
|
|
> sensors (UWB, NFC) need real hardware.**
|
||
|
|
|
||
|
|
We achieved this by abstracting all hardware behind protocols
|
||
|
|
(`ProximityAdapterProtocols`) and injecting a `SimulatedAdapter` in DEBUG
|
||
|
|
builds. `PresenceEngine` talks to the protocols, never to hardware directly,
|
||
|
|
so it behaves identically whether it's sensing real UWB or receiving simulated
|
||
|
|
encounters.
|
||
|
|
|
||
|
|
## What you can test without any device
|
||
|
|
|
||
|
|
| Capability | Simulator | Real iPhone |
|
||
|
|
|---|---|---|
|
||
|
|
| SwiftUI UI, navigation, chat | ✅ | ✅ |
|
||
|
|
| Auth (Apple / Google) | ✅ (Apple needs bundle config) | ✅ |
|
||
|
|
| Backend + WebSocket live loop | ✅ | ✅ |
|
||
|
|
| Full reveal flow (wave → mutual → chat) | ✅ | ✅ |
|
||
|
|
| Simulated encounters / crowd | ✅ | ✅ |
|
||
|
|
| Core Location / iBeacon | ⚠️ simulated location | ✅ |
|
||
|
|
| BLE broadcast/discovery | ⚠️ limited | ✅ |
|
||
|
|
| **UWB (Nearby Interaction)** | ❌ | ✅ |
|
||
|
|
| **NFC tap** | ❌ | ✅ |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Quick start (simulator)
|
||
|
|
|
||
|
|
### 1. Run the reference backend
|
||
|
|
|
||
|
|
The Swift client in DEBUG mode points at a local server. Start it:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
cd server
|
||
|
|
npm install
|
||
|
|
npm run dev # runs on http://localhost:8080
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. Run the app in the Simulator
|
||
|
|
|
||
|
|
1. Open `Proximity.xcodeproj` in Xcode.
|
||
|
|
2. Select an iPhone simulator (any model).
|
||
|
|
3. Build & run (⌘R).
|
||
|
|
|
||
|
|
In DEBUG builds, `AppState` automatically:
|
||
|
|
- Points `APIClient` at `http://localhost:8080`
|
||
|
|
- Injects `SimulatedAdapter` into the `PresenceEngine`
|
||
|
|
|
||
|
|
### 3. Exercise the interaction loop
|
||
|
|
|
||
|
|
1. **Sign in** (Apple or Google — the reference server accepts any token).
|
||
|
|
2. Tap the big **"I'm Here"** toggle to go present.
|
||
|
|
3. Open the **debug menu** (hammer icon, top-left).
|
||
|
|
4. Tap **"Simulate someone walking by"** — a silhouette appears in the feed.
|
||
|
|
5. Tap **Wave** on the silhouette.
|
||
|
|
6. To complete the reveal, simulate the *other* side waving back — in real
|
||
|
|
life the server pushes this over the socket. For a fully local test, you
|
||
|
|
can drive the mutual wave via the debug tools or a second client.
|
||
|
|
|
||
|
|
### 4. Test the reveal moment
|
||
|
|
|
||
|
|
When both sides wave, `RevealView` presents full-screen — two avatars
|
||
|
|
converging. Tap **"Say hello"** to open the E2E chat.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Driving the simulation
|
||
|
|
|
||
|
|
The debug menu (`DebugMenu`) provides:
|
||
|
|
|
||
|
|
- **Simulate someone walking by** — one UWB encounter.
|
||
|
|
- **Simulate a crowd (20 people)** — a burst of silhouettes.
|
||
|
|
- **Auto-broadcast nearby users** — emits encounters on a timer (a "busy
|
||
|
|
street" simulator).
|
||
|
|
|
||
|
|
`SimulatedAdapter` also exposes programmatic methods if you want to write
|
||
|
|
UITests:
|
||
|
|
|
||
|
|
```swift
|
||
|
|
simulatedAdapter.simulateEncounter(remoteToken: "any-token", distance: 1.0)
|
||
|
|
simulatedAdapter.simulateCrowd(count: 20)
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Two-simulator / two-client testing
|
||
|
|
|
||
|
|
To test the **mutual** flow (both sides), run **two simulator instances**:
|
||
|
|
|
||
|
|
1. Boot two simulators (e.g. iPhone 15 + iPhone 15 Pro).
|
||
|
|
2. Run the app on both against the same local backend.
|
||
|
|
3. In each, go present and simulate an encounter (the debug menu gives each a
|
||
|
|
distinct token).
|
||
|
|
4. Wave from one — the server derives each sender's own token from its
|
||
|
|
session, records the directional edge, and when both directions exist it
|
||
|
|
pushes `mutual` to **both** sockets. The reveal triggers on both devices
|
||
|
|
in real time.
|
||
|
|
|
||
|
|
The reference server now implements the full session→token mapping, so the
|
||
|
|
mutual reveal and E2E key exchange work end-to-end across two clients.
|
||
|
|
|
||
|
|
## Running the UI tests
|
||
|
|
|
||
|
|
The `ProximityUITests` target drives the happy path automatically:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
xcodebuild test \
|
||
|
|
-project Proximity.xcodeproj \
|
||
|
|
-scheme Proximity \
|
||
|
|
-destination 'platform=iOS Simulator,name=iPhone 15' \
|
||
|
|
-only-testing:ProximityUITests/ProximityFlowUITests
|
||
|
|
```
|
||
|
|
|
||
|
|
Start the reference server first (`cd server && npm run dev`).
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Testing on real hardware (the sensors)
|
||
|
|
|
||
|
|
When you're ready to test UWB / BLE / NFC for real:
|
||
|
|
|
||
|
|
1. **Disable the simulated adapter.** Either build a non-DEBUG configuration,
|
||
|
|
or set `AppState.isSimulated = false`.
|
||
|
|
2. Use two physical iPhones (iPhone 11+ for UWB).
|
||
|
|
3. The real `UWBAdapter`, `BLEAdapter`, `BeaconAdapter` take over.
|
||
|
|
|
||
|
|
The rest of the app (auth, chat, reveal, backend) works identically — that's
|
||
|
|
the point of the abstraction.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## What the simulator can't do (and why it's OK)
|
||
|
|
|
||
|
|
- **UWB**: no support in the simulator. But UWB is only the *"this exact
|
||
|
|
person"* distance sensor — the interaction logic (handshake → reveal) is
|
||
|
|
fully testable via simulation.
|
||
|
|
- **NFC**: no support. NFC is a niche "tap to connect" feature, not core.
|
||
|
|
- **BLE**: very limited in simulator. The presence *logic* is testable; the
|
||
|
|
radio isn't.
|
||
|
|
|
||
|
|
Since the make-or-break mechanic — **the physical interaction moment** — is
|
||
|
|
driven by logic + UI, not by the radio, you can validate the entire product
|
||
|
|
experience in the simulator before ever touching real devices.
|