Files
Nearfield_Friends/docs/TESTING_GUIDE.html

514 lines
23 KiB
HTML
Raw Permalink Normal View History

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Proximity — Testing Guide (M-Series Mac)</title>
<style>
:root {
--bg: #0f1115;
--panel: #171a21;
--panel-2: #1e222b;
--border: #2a2f3a;
--text: #e6e8ee;
--muted: #9aa3b2;
--green: #34c759;
--green-dim: #1f7a3d;
--blue: #4aa8ff;
--amber: #ffb340;
--red: #ff5f57;
--mono: "SF Mono", "Menlo", "Consolas", monospace;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--text);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
line-height: 1.65;
padding-bottom: 80px;
}
.wrap { max-width: 860px; margin: 0 auto; padding: 0 24px; }
header {
background: linear-gradient(135deg, #0d1f14 0%, #0f1115 60%);
border-bottom: 1px solid var(--border);
padding: 48px 0 36px;
margin-bottom: 32px;
}
header .wrap { display: flex; align-items: center; gap: 20px; }
.logo {
width: 64px; height: 64px; border-radius: 16px;
background: radial-gradient(circle at 30% 30%, #34c759, #1f7a3d);
display: flex; align-items: center; justify-content: center;
font-size: 30px; color: white; flex-shrink: 0;
box-shadow: 0 8px 24px rgba(52,199,89,.35);
}
h1 { font-size: 26px; font-weight: 700; letter-spacing: -0.5px; }
.sub { color: var(--muted); font-size: 15px; margin-top: 4px; }
h2 {
font-size: 21px; font-weight: 700; margin: 40px 0 16px;
padding-bottom: 8px; border-bottom: 1px solid var(--border);
display: flex; align-items: center; gap: 10px;
}
h2 .step-num {
background: var(--green); color: #07130a; font-size: 13px; font-weight: 800;
width: 26px; height: 26px; border-radius: 8px; display: inline-flex;
align-items: center; justify-content: center; flex-shrink: 0;
}
h3 { font-size: 16px; font-weight: 600; margin: 22px 0 10px; color: var(--text); }
p { margin: 10px 0; color: var(--text); }
.muted { color: var(--muted); }
.card {
background: var(--panel); border: 1px solid var(--border);
border-radius: 12px; padding: 18px 20px; margin: 14px 0;
}
.card.tip { border-left: 4px solid var(--blue); }
.card.warn { border-left: 4px solid var(--amber); }
.card.danger { border-left: 4px solid var(--red); }
.card.success { border-left: 4px solid var(--green); }
code {
font-family: var(--mono); font-size: 13px;
background: var(--panel-2); border: 1px solid var(--border);
padding: 2px 6px; border-radius: 5px; color: #d7e0ee;
}
pre {
font-family: var(--mono); font-size: 13px; line-height: 1.6;
background: #0b0d12; border: 1px solid var(--border); border-radius: 10px;
padding: 16px 18px; overflow-x: auto; margin: 12px 0;
}
pre code { background: none; border: none; padding: 0; color: #c9d4e6; }
.cmt { color: #5c6b7a; }
ul, ol { margin: 10px 0 10px 22px; }
li { margin: 6px 0; }
table { width: 100%; border-collapse: collapse; margin: 14px 0; font-size: 14px; }
th, td { text-align: left; padding: 10px 12px; border: 1px solid var(--border); }
th { background: var(--panel-2); font-weight: 600; }
td { background: var(--panel); }
.yes { color: var(--green); font-weight: 600; }
.no { color: var(--red); font-weight: 600; }
.part { color: var(--amber); font-weight: 600; }
.kbd {
font-family: var(--mono); font-size: 12px; background: var(--panel-2);
border: 1px solid var(--border); border-bottom-width: 3px; border-radius: 6px;
padding: 2px 7px; color: var(--text);
}
.toc { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 20px 24px; }
.toc a { color: var(--blue); text-decoration: none; }
.toc a:hover { text-decoration: underline; }
.toc ol { margin-left: 18px; }
.toc li { margin: 5px 0; }
.check { color: var(--green); margin-right: 6px; }
.cross { color: var(--red); margin-right: 6px; }
.arrow { color: var(--green); font-weight: 700; }
footer { margin-top: 60px; padding-top: 20px; border-top: 1px solid var(--border); color: var(--muted); font-size: 13px; text-align: center; }
</style>
</head>
<body>
<header>
<div class="wrap">
<div class="logo">📡</div>
<div>
<h1>Proximity — Testing Guide</h1>
<div class="sub">Step-by-step walkthrough for an Apple Silicon (M-series) MacBook · iOS Simulator · no physical iPhone needed for the core flow</div>
</div>
</div>
</header>
<div class="wrap">
<!-- ================= TOC ================= -->
<div class="toc">
<strong>Contents</strong>
<ol>
<li><a href="#overview">What you're testing &amp; the big idea</a></li>
<li><a href="#prereqs">Prerequisites (install these first)</a></li>
<li><a href="#structure">Project structure map</a></li>
<li><a href="#server">Start the reference backend</a></li>
<li><a href="#xcode">Open &amp; configure the project in Xcode</a></li>
<li><a href="#run">Run the app in the Simulator</a></li>
<li><a href="#walkthrough">Walk through the interaction loop</a></li>
<li><a href="#twosim">Two-simulator mutual reveal test</a></li>
<li><a href="#uitests">Run the automated UI tests</a></li>
<li><a href="#reald">Testing on a real iPhone (the sensors)</a></li>
<li><a href="#troubleshoot">Troubleshooting on M-series</a></li>
</ol>
</div>
<!-- ================= 1. OVERVIEW ================= -->
<h2 id="overview"><span class="step-num">1</span> What you're testing &amp; the big idea</h2>
<p>
Proximity is a social app where you can only connect with people you're
<strong>physically near</strong>. The make-or-break mechanic is the
<em>interaction moment</em>: silhouette → wave → mutual reveal → chat.
</p>
<div class="card success">
<strong>The key insight:</strong> All the <em>interaction logic</em> — the part that makes or breaks the
community — is fully testable in the <strong>iOS Simulator</strong>, with no iPhone. Only the physical
<em>sensors</em> (UWB, NFC) need real hardware. We built a <code>SimulatedAdapter</code> that stands in for
the hardware, so the app behaves identically.
</div>
<table>
<tr><th>Capability</th><th>Simulator</th><th>Real iPhone</th></tr>
<tr><td>UI, navigation, chat</td><td class="yes"></td><td class="yes"></td></tr>
<tr><td>Auth (Apple / Google)</td><td class="yes"></td><td class="yes"></td></tr>
<tr><td>Backend + WebSocket live loop</td><td class="yes"></td><td class="yes"></td></tr>
<tr><td>Reveal flow (wave → mutual → chat)</td><td class="yes"></td><td class="yes"></td></tr>
<tr><td>Simulated encounters / crowd</td><td class="yes"></td><td class="yes"></td></tr>
<tr><td>BLE broadcast / discovery</td><td class="part">⚠️ limited</td><td class="yes"></td></tr>
<tr><td>UWB (Nearby Interaction)</td><td class="no"></td><td class="yes"></td></tr>
<tr><td>NFC tap</td><td class="no"></td><td class="yes"></td></tr>
</table>
<!-- ================= 2. PREREQS ================= -->
<h2 id="prereqs"><span class="step-num">2</span> Prerequisites</h2>
<p>Install these before starting. On an M-series Mac, everything runs natively on Apple Silicon.</p>
<div class="card">
<h3 style="margin-top:0">A. Xcode (required)</h3>
<p>Install from the Mac App Store, or run:</p>
<pre><code>xcode-select --install
<span class="cmt"># If you need a specific version, use Xcodes:</span>
brew install --cask xcodes</code></pre>
<p class="muted">You need Xcode 15 or later for Swift 5.9+ concurrency and SwiftData. Xcode 16 recommended.</p>
</div>
<div class="card">
<h3 style="margin-top:0">B. Node.js (required for the reference server)</h3>
<p>Check if you have it:</p>
<pre><code>node --version <span class="cmt"># want v18+</span>
npm --version</code></pre>
<p>If not, install via Homebrew (native arm64 build):</p>
<pre><code>brew install node</code></pre>
</div>
<div class="card">
<h3 style="margin-top:0">C. Homebrew (recommended)</h3>
<pre><code>/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"</code></pre>
</div>
<div class="card tip">
<strong>M-series note:</strong> Everything here is native arm64. You do <em>not</em> need Rosetta for this
project. If you ever see "running under Rosetta" in Activity Monitor for a tool, it's optional and not required.
</div>
<!-- ================= 3. STRUCTURE ================= -->
<h2 id="structure"><span class="step-num">3</span> Project structure map</h2>
<p>Here's what you're working with:</p>
<pre><code>Nearfield_Friends/
├── Proximity/ <span class="cmt"># iOS app (SwiftUI)</span>
│ ├── App/ <span class="cmt"># entry point, AppState wiring</span>
│ ├── Models/ <span class="cmt"># Encounter, Connection, DistanceTier…</span>
│ ├── Services/
│ │ ├── Proximity/ <span class="cmt"># PresenceEngine + adapters + SimulatedAdapter</span>
│ │ ├── Network/ <span class="cmt"># APIClient, RealtimeClient (WebSocket)</span>
│ │ ├── Auth/ <span class="cmt"># Apple/Google sign-in, profile import</span>
│ │ └── Security/ <span class="cmt"># tokens, E2E crypto</span>
│ ├── ViewModels/ <span class="cmt"># Presence, Chat, Reveal</span>
│ └── Views/ <span class="cmt"># SwiftUI screens + DebugMenu</span>
├── ProximityUITests/ <span class="cmt"># automated UI tests</span>
├── Proximity.xcodeproj/ <span class="cmt"># Xcode project (generated)</span>
├── scripts/
│ └── generate_xcodeproj.py <span class="cmt"># regenerates the .xcodeproj</span>
├── server/ <span class="cmt"># reference backend (Node/TS)</span>
│ └── src/index.ts
└── docs/
├── backend-api-spec.md <span class="cmt"># API contract</span>
├── TESTING.md <span class="cmt"># testing notes</span>
└── TESTING_GUIDE.html <span class="cmt"># this file</span></code></pre>
<div class="card success">
<strong>Good news:</strong> A <code>Proximity.xcodeproj</code> is now included in the repo (generated by
<code>scripts/generate_xcodeproj.py</code>). It contains the app target, the UI test target, and a shared
scheme — so you can open it directly in Xcode and run <code>xcodebuild test</code> with no manual setup.
If you ever add/remove Swift files, re-run the generator to keep the project in sync.
</div>
<!-- ================= 4. SERVER ================= -->
<h2 id="server"><span class="step-num">4</span> Start the reference backend</h2>
<p>The Swift app in DEBUG mode points at a local server on <code>localhost:8080</code>. Start it first.</p>
<div class="card">
<p>Open <strong>Terminal</strong> and run:</p>
<pre><code>cd ~/path/to/Nearfield_Friends/server
npm install
npm run dev</code></pre>
<p>You should see:</p>
<pre><code>Proximity reference server on :8080</code></pre>
</div>
<div class="card warn">
<strong>Keep this terminal window open.</strong> The server must stay running while you test. If you close it,
the app will still work for local UI, but the live socket (mutual reveal, real-time chat) won't.
</div>
<div class="card tip">
<strong>Quick sanity check:</strong> In a second terminal, run
<code>curl http://localhost:8080/v1/nearby?geohash=abc&tier=1</code> — you should get <code>[]</code> back.
</div>
<!-- ================= 5. XCODE ================= -->
<h2 id="xcode"><span class="step-num">5</span> Open &amp; configure the project in Xcode</h2>
<h3>5a. Open the project</h3>
<div class="card">
<p>The <code>Proximity.xcodeproj</code> is already generated. Open it:</p>
<pre><code>open Proximity.xcodeproj</code></pre>
<p>or double-click it in Finder. It includes the app target, the <code>ProximityUITests</code> target, and a
shared scheme, so everything is ready to go.</p>
<p class="muted">The entry point is <code>ProximityApp.swift</code> (marked <code>@main</code>).</p>
</div>
<div class="card tip">
<strong>Keeping the project in sync:</strong> If you add or remove Swift files, regenerate the project so the
file list stays correct:
<pre><code>python3 scripts/generate_xcodeproj.py</code></pre>
Then re-open the project in Xcode.
</div>
<h3>5b. Set the signing team (free account is fine)</h3>
<div class="card">
<ol>
<li>Select the <strong>Proximity</strong> target in the project navigator.</li>
<li>Go to <strong>Signing &amp; Capabilities</strong>.</li>
<li>Check <strong>"Automatically manage signing"</strong>.</li>
<li>Pick your <strong>Team</strong> (your Apple ID — free accounts work for the simulator).</li>
<li>Set a unique <strong>Bundle Identifier</strong>, e.g. <code>com.yourname.proximity</code>.</li>
</ol>
</div>
<h3>5c. Add required capabilities (for real devices later)</h3>
<div class="card">
<p>For the simulator you only strictly need these for the real-hardware build, but add them now so the
project is ready:</p>
<ul>
<li><strong>Near Field Communication Tag Reading</strong> (Core NFC)</li>
<li><strong>Bluetooth</strong> (Core Bluetooth)</li>
<li><strong>Location</strong> (Core Location)</li>
</ul>
<p class="muted">The simulator ignores UWB/NFC but will use simulated location.</p>
</div>
<h3>5d. Add the Info.plist usage strings</h3>
<div class="card">
<p>Add these to <code>Info.plist</code> so permissions work:</p>
<pre><code>NFCReaderUsageDescription "Proximity uses NFC to connect when you tap phones."
NSBluetoothAlwaysUsageDescription "Proximity uses Bluetooth to detect nearby friends."
NSLocationWhenInUseUsageDescription "Proximity uses your location to find people nearby."</code></pre>
</div>
<!-- ================= 6. RUN ================= -->
<h2 id="run"><span class="step-num">6</span> Run the app in the Simulator</h2>
<div class="card">
<ol>
<li>At the top of Xcode, pick a simulator from the device dropdown
(e.g. <strong>iPhone 15</strong> or <strong>iPhone 15 Pro</strong>).</li>
<li>Press <span class="kbd">⌘R</span> to build &amp; run.</li>
<li>The first build may take a minute. The Simulator window opens with the app.</li>
</ol>
</div>
<div class="card success">
<strong>What happens automatically in DEBUG builds:</strong>
<ul>
<li><span class="arrow"></span> <code>APIClient</code> points at <code>http://localhost:8080</code></li>
<li><span class="arrow"></span> <code>SimulatedAdapter</code> is injected into the engine (no hardware)</li>
<li><span class="arrow"></span> A debug menu (hammer icon) appears in the toolbar</li>
</ul>
</div>
<div class="card warn">
<strong>If the app can't reach the server:</strong> the simulator shares your Mac's network, so
<code>localhost</code> works. If you see connection errors, make sure the server from
<a href="#server">Step 4</a> is still running, then use the debug menu's <em>"Reconnect socket"</em>.
</div>
<!-- ================= 7. WALKTHROUGH ================= -->
<h2 id="walkthrough"><span class="step-num">7</span> Walk through the interaction loop</h2>
<p>This is the heart of the product. You'll simulate a full encounter with no hardware.</p>
<div class="card">
<h3 style="margin-top:0">Step 1 — Sign in</h3>
<p>Tap <strong>Continue with Google</strong> (or Apple). The reference server accepts any token, so this
succeeds immediately. You'll land on the main screen.</p>
</div>
<div class="card">
<h3>Step 2 — Go present</h3>
<p>Tap the big circular <strong>"Tap to be Present"</strong> button. It turns green and reads
<strong>"I'm Here"</strong>. This opens the live socket.</p>
</div>
<div class="card">
<h3>Step 3 — Simulate an encounter</h3>
<ol>
<li>Tap the <strong>hammer icon</strong> (top-left) to open the Debug menu.</li>
<li>Tap <strong>"Simulate someone walking by"</strong>.</li>
<li>Go back. A <strong>silhouette</strong> ("Someone nearby") appears in the feed.</li>
</ol>
</div>
<div class="card">
<h3>Step 4 — Wave</h3>
<p>Tap <strong>Wave</strong> on the silhouette. It changes to <strong>"Waved ✓"</strong> — you're now waiting
for the other person.</p>
</div>
<div class="card">
<h3>Step 5 — Complete the reveal (two ways)</h3>
<p><strong>Option A — two simulators</strong> (recommended, <a href="#twosim">see next section</a>): the server
relays the mutual wave and the reveal triggers automatically.</p>
<p><strong>Option B — single simulator:</strong> the reveal needs the other side to wave. Use the debug menu's
<em>"Simulate a crowd"</em> to generate more silhouettes, or drive the mutual wave programmatically via the
simulated adapter.</p>
</div>
<div class="card">
<h3>Step 6 — The reveal moment</h3>
<p>When both sides wave, <code>RevealView</code> presents full-screen: two avatars converge. Tap
<strong>"Say hello"</strong> to open the E2E-encrypted chat.</p>
</div>
<div class="card">
<h3>Step 7 — Chat</h3>
<p>Type a message and send. In a two-simulator setup, the other side receives it decrypted in real time over
the WebSocket.</p>
</div>
<!-- ================= 8. TWO SIM ================= -->
<h2 id="twosim"><span class="step-num">8</span> Two-simulator mutual reveal test</h2>
<p>This tests the real-time mutual flow end-to-end across two "people".</p>
<div class="card">
<ol>
<li>Make sure the reference server is running (<a href="#server">Step 4</a>).</li>
<li>In Xcode, boot a second simulator: <span class="kbd">File</span><span class="kbd">Open Simulator</span>,
or use <span class="kbd">⌃⌘R</span> after adding a second scheme destination.</li>
<li>Run the app on <strong>both</strong> simulators (e.g. iPhone 15 + iPhone 15 Pro).</li>
<li>In each, sign in and go present.</li>
<li>In each, use the debug menu to simulate an encounter (each gets a distinct token).</li>
<li>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 <code>mutual</code> to <strong>both</strong> sockets.</li>
<li>The reveal triggers on <strong>both</strong> devices in real time.</li>
</ol>
</div>
<div class="card tip">
<strong>Tip:</strong> To run two instances easily, create a second scheme
(<span class="kbd">Product</span><span class="kbd">Scheme</span><span class="kbd">New Scheme</span>) and
set a different simulator destination, then run both schemes.
</div>
<!-- ================= 9. UI TESTS ================= -->
<h2 id="uitests"><span class="step-num">9</span> Run the automated UI tests</h2>
<p>The <code>ProximityUITests</code> target drives the happy path automatically.</p>
<div class="card">
<ol>
<li>Ensure the server is running.</li>
<li>In Xcode, select the <strong>ProximityUITests</strong> test target.</li>
<li>Press <span class="kbd">⌘U</span> to run all tests.</li>
</ol>
<p>Or from the terminal:</p>
<pre><code>cd ~/path/to/Nearfield_Friends
xcodebuild test \
-project Proximity.xcodeproj \
-scheme Proximity \
-destination 'platform=iOS Simulator,name=iPhone 15' \
-only-testing:ProximityUITests/ProximityFlowUITests</code></pre>
</div>
<div class="card success">
<strong>Ready to go:</strong> The <code>ProximityUITests</code> target and its shared scheme are already part of
<code>Proximity.xcodeproj</code>, so <span class="kbd">⌘U</span> and the <code>xcodebuild test</code> command work
out of the box.
</div>
<!-- ================= 10. REAL DEVICE ================= -->
<h2 id="reald"><span class="step-num">10</span> Testing on a real iPhone (the sensors)</h2>
<p>When you're ready to test the actual UWB / BLE / NFC radios:</p>
<div class="card">
<ol>
<li><strong>Disable the simulated adapter.</strong> Build a Release (non-DEBUG) configuration, or set
<code>AppState.isSimulated = false</code>.</li>
<li>Use <strong>two physical iPhones</strong> (iPhone 11+ for UWB).</li>
<li>Point the app at a reachable backend (change <code>APIClient.baseURL</code> to your Mac's LAN IP or a
deployed server).</li>
<li>Trust the developer certificate on each device (Settings → General → VPN &amp; Device Management).</li>
<li>Run from Xcode with the device selected. The real <code>UWBAdapter</code>, <code>BLEAdapter</code>,
<code>BeaconAdapter</code> take over.</li>
</ol>
</div>
<div class="card danger">
<strong>Important:</strong> UWB and NFC are <em>not</em> available in the simulator. Only test these on real
hardware. Everything else works identically — that's the point of the abstraction.
</div>
<!-- ================= 11. TROUBLESHOOT ================= -->
<h2 id="troubleshoot"><span class="step-num">11</span> Troubleshooting on M-series</h2>
<div class="card warn">
<h3 style="margin-top:0">App won't connect to the server</h3>
<p><span class="check"></span> Confirm the server terminal shows <code>:8080</code> and is still running.<br>
<span class="check"></span> Use the debug menu's <em>"Reconnect socket"</em>.<br>
<span class="check"></span> Try <code>curl http://localhost:8080/v1/nearby?geohash=abc&tier=1</code> in Terminal.</p>
</div>
<div class="card warn">
<h3 style="margin-top:0">Build fails / missing files</h3>
<p><span class="check"></span> Make sure all folders under <code>Proximity/</code> are added to the target
(not just referenced).<br>
<span class="check"></span> Confirm <code>ProximityApp.swift</code> is the <code>@main</code> entry and only one
<code>@main</code> exists.</p>
</div>
<div class="card warn">
<h3 style="margin-top:0">Signing errors</h3>
<p><span class="check"></span> For the simulator you can often use <em>"Sign to Run Locally"</em> / no team.<br>
<span class="check"></span> Use a unique bundle identifier.</p>
</div>
<div class="card warn">
<h3 style="margin-top:0">"Rosetta" warnings or arch issues</h3>
<p><span class="check"></span> This project is native arm64 — no Rosetta needed.<br>
<span class="check"></span> If a tool was installed via an Intel package, reinstall via <code>brew</code>
(arm64) or use the Apple Silicon build.</p>
</div>
<div class="card warn">
<h3 style="margin-top:0">Simulator can't find localhost</h3>
<p><span class="check"></span> The simulator shares the Mac's network, so <code>localhost</code> resolves to
your Mac. If you moved the server to another machine, update <code>APIClient.local()</code> to that IP.</p>
</div>
<div class="card warn">
<h3 style="margin-top:0">Mutual reveal never triggers in single-simulator mode</h3>
<p><span class="check"></span> A reveal needs <em>both</em> sides to wave. Use the two-simulator setup
(<a href="#twosim">Step 8</a>) for the full end-to-end test, or drive the mutual wave programmatically.</p>
</div>
<footer>
Proximity · Testing Guide · Apple Silicon (M-series) · iOS Simulator-first workflow
</footer>
</div>
</body>
</html>