Watch: snapshot tests render the complication and multi-select screenshots

ImageRenderer draws the real views with demo data, since the watch simulator
is not drivable here. README covers the widget target and how to add it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
gibandClaude Opus 5.5 committed 2026-11-01 11:06:38 -05:00
1 parent 0994fd7d5a
commit 67992dbf43
8 files changed
+241 -80

No files matched your search

+30 -2
View File
@@ -22,6 +22,7 @@ targets, the same pair Xcode's "Watch-only App" template makes:
| --- | --- | --- |
| `WristCodeContainer` | `org.gbrown.wristcode` | iOS stub that owns the App Store Connect record and embeds the watch app |
| `WristCode` | `org.gbrown.wristcode.watchkitapp` | The watch app, and the APNs topic |
| `WristCodeWidget` | `org.gbrown.wristcode.watchkitapp.widget` | The complication, embedded in the watch app |
```sh
scripts/testflight.sh # archive, export and upload to App Store Connect
@@ -46,7 +47,31 @@ xcrun simctl launch <udid> org.gbrown.wristcode.watchkitapp -demo -thread little
xcrun simctl launch <udid> org.gbrown.wristcode.watchkitapp -demo -screen new # new thread
```
`screenshots/` holds the screens captured this way.
`screenshots/` holds the screens captured this way. The complication and the multi-select question
are rendered by `SnapshotTests` instead, which only run when given a directory:
```sh
TEST_RUNNER_WC_SCREENSHOT_DIR=$PWD/screenshots xcodebuild -scheme WristCode \
-destination 'platform=watchOS Simulator,name=Apple Watch Series 12 (46mm)' test \
-only-testing:WristCodeTests/SnapshotTests
```
In demo mode the complication shows the demo counts too.
## Complication
`WristCodeWidget` shows how many threads wait on you, from `GET /v1/summary`, in the circular,
rectangular, inline and corner families. Tapping it opens the app, at the longest-waiting thread when
there is one (`wristcode://thread/<hostId>/<threadId>`). WidgetKit refreshes it about every 15 minutes;
the app reloads it sooner when its feed counts change, when a push arrives in the foreground, and
after a notification action.
The app and the extension share the App Group `group.org.gbrown.wristcode`. The device token and
gateway URL live in that group's Keychain access group, and the demo flag and last summary in its
defaults. Builds before the complication kept the token in the app's own Keychain group;
`DeviceStore.migrateToSharedGroup()` moves it on launch, so a paired watch stays paired.
To add it: long-press the watch face, Edit, swipe to Complications, pick a slot, then WristCode.
## Layout
@@ -56,7 +81,10 @@ xcrun simctl launch <udid> org.gbrown.wristcode.watchkitapp -demo -screen new
| `AppModel.swift` | Paired, demo or unpaired; navigation path; the feed; 401 sends you back to pairing |
| `API/GatewayClient.swift` | `GatewayAPI` protocol and the URLSession client |
| `API/Models.swift` | Codable mirror of the contract |
| `Auth/DeviceStore.swift` | Device token and gateway URL in the Keychain |
| `API/Route.swift` | Screens the app can open, and their `wristcode://` URLs |
| `Auth/DeviceStore.swift` | Device token and gateway URL in the shared Keychain group |
| `Shared/SharedState.swift` | Demo flag and last summary in the App Group, for the complication |
| `Demo/DemoGateway.swift` | In-memory gateway with fixtures |
| `Views/` | Pair, Feed, Thread, New thread, and the mock B theme |
| `Notifications/NotificationHandler.swift` | Categories `approval`, `finished`, `failed`, `input`, `host` and their actions |
| `../WristCodeWidget/` | The complication: timeline provider, and one view per family |