Commit Graph

8 Commits

Author SHA1 Message Date
Trey T 0b6f26da99 fix(qlpreview): hide share-arrow in expired state (gitea#7 review)
Android UI Tests / ui-tests (pull_request) Has been cancelled
The down-chevron above the system Share button is a "tap here"
cue for the active flow. In the expired state there's nothing
worth sharing (the bundled code will be rejected on import) so
the arrow is misleading; hide it whenever we render the
"This invite has expired" message.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 15:21:57 -05:00
Trey T 83c3428b05 fix(qlpreview): expired-state copy + dedicated row text (gitea#7 review)
Android UI Tests / ui-tests (pull_request) Has been cancelled
When the share link's expiry is in the past, the preview now
swaps the "How to join" steps for a dead-end message ("This
invite has expired. Ask <sender> to send a new link.") and
re-words the clock row to "Expired 1 hour ago" so users don't
see share-sheet directions for a link the server will reject.

Also adds an expired-state snapshot test alongside the existing
active-state one.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 13:57:54 -05:00
Trey T f4c2780e34 fix(qlpreview): inline share icon instead of fixed position (gitea#7 review)
Android UI Tests / ui-tests (pull_request) Has been cancelled
The previous copy "1. Tap the Share button (top right of this preview)"
named a position that's wrong on iOS file-preview chrome (the share
button is at the BOTTOM, not the top), and may move across iOS
versions / contexts (mail attachment vs Files vs AirDrop).

Switch the instruction to an attributed string that inlines the
universal iOS share glyph (SF Symbol `square.and.arrow.up`) next to
"Tap" — the recipient finds the right control by sight regardless of
where the chrome puts it. New `PreviewViewController.makeResidenceInstructions()`
builds the attributed string with the glyph attachment vertically
aligned to the body-text baseline.

`Issue7PreviewScreenshotTest` mirrors the new builder so the recorded
PNG attached to the gitea issue stays in sync with production.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 13:46:59 -05:00
Trey T d26714f043 test(qlpreview): screenshot of the post-fix residence-invite preview (gitea#7)
Android UI Tests / ui-tests (pull_request) Has been cancelled
Adds a one-shot SnapshotTesting case that renders the new
`PreviewViewController.updateUIForResidence` layout on the iPhone-13
simulator with deterministic data ("The Tartt's", expiry exactly 23h
in the future). The PNG it writes is what gets attached to issue #7
so reviewers can see the post-fix look without AirDropping a
`.honeydue` file to a device.

`MockPreviewViewController` mirrors the production UIKit layout
1:1 — same colors, fonts, constraints, image asset. (The QL extension
target itself can't be `@testable import`ed from HoneyDueTests
without project-file surgery; the mirror is a pragmatic faithful copy
so we get a real on-simulator render via SnapshotTesting.)

The included PNG is the recorded golden.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 13:44:29 -05:00
Trey T 9fa58352c0 Parity gallery: unify around canonical manifest, fix populated-state rendering
Single source of truth: `com.tt.honeyDue.testing.GalleryScreens` lists
every user-reachable screen with its category (DataCarrying / DataFree)
and per-platform reachability. Both platforms' test harnesses are
CI-gated against it — `GalleryManifestParityTest` on each side fails
if the surface list drifts from the manifest.

Variant matrix by category: DataCarrying captures 4 PNGs
(empty/populated × light/dark), DataFree captures 2 (light/dark only).
Empty variants for DataCarrying use `FixtureDataManager.empty(seedLookups = false)`
so form screens that only read DM lookups can diff against populated.

Detail-screen rendering fixed on both platforms. Root cause: VM
`stateIn(Eagerly, initialValue = …)` closures evaluated
`_selectedX.value` before screen-side `LaunchedEffect` / `.onAppear`
could set the id, leaving populated captures byte-identical to empty.

  Kotlin: `ContractorViewModel` + `DocumentViewModel` accept
  `initialSelectedX: Int? = null` so the id is set in the primary
  constructor before `stateIn` computes its seed.

  Swift: `ContractorViewModel`, `DocumentViewModelWrapper`,
  `ResidenceViewModel`, `OnboardingTasksViewModel` gained pre-seed
  init params. `ContractorDetailView`, `DocumentDetailView`,
  `ResidenceDetailView`, `OnboardingFirstTaskContent` gained
  test/preview init overloads that accept the pre-seeded VM.
  Corresponding view bodies prefer cached success state over
  loading/error — avoids a spinner flashing over already-visible
  content during background refreshes (production benefit too).

Real production bug fixed along the way: `DataManager.clear()` was
missing `_contractorDetail`, `_documentDetail`, `_contractorsByResidence`,
`_taskCompletions`, `_notificationPreferences`. On logout these maps
leaked across user sessions; in the gallery they leaked the previous
surface's populated state into the next surface's empty capture.

`ImagePicker.android.kt` guards `rememberCameraPicker` with
`LocalInspectionMode` — `FileProvider.getUriForFile` can't resolve the
Robolectric test-cache path, so `add_document` / `edit_document`
previously failed the entire capture.

Honest reclassifications: `complete_task`, `manage_users`, and
`task_suggestions` moved to DataFree. Their first-paint visible state
is driven by static props or APILayer calls, not by anything on
`IDataManager` — populated would be byte-identical to empty without
a significant production rewire. The manifest comments call this out.

Manifest counts after all moves: 43 screens = 12 DataCarrying + 31
DataFree, 37 on both platforms + 3 Android-only (home, documents,
biometric_lock) + 3 iOS-only (documents_warranties, add_task,
profile_edit).

Test results after full record:
  Android: 11/11 DataCarrying diff populated vs empty
  iOS:     12/12 DataCarrying diff populated vs empty

Also in this change:
- `scripts/build_parity_gallery.py` parses the Kotlin manifest
  directly, renders rows in product-flow order, shows explicit
  `[missing — <platform>]` placeholders for expected-but-absent
  captures and muted `not on <platform>` placeholders for
  platform-specific screens. Docs regenerated.
- `scripts/cleanup_orphan_goldens.sh` safely removes PNGs from prior
  test configurations (theme-named, compare artifacts, legacy
  empty/populated pairs for what is now DataFree). Dry-run by default.
- `docs/parity-gallery.md` rewritten: canonical-manifest workflow,
  adding-a-screen guide, variant matrix explained.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 18:10:32 -05:00
Trey T 6c3c9d3e0c Coverage: iOS ViewModel DI seam + populated-state snapshots
6 user-facing ViewModels now accept optional `dataManager: DataManagerObservable = .shared`
init param — production call-sites unchanged; tests inject fixture-backed
observables. Refactored: ResidenceViewModel, TaskViewModel, ContractorViewModel,
DocumentViewModel, ProfileViewModel, LoginViewModel.

DataManagerObservable gains test-only init(observeSharedDataManager:) + convenience
init(kotlin: IDataManager).

SnapshotGalleryTests.setUp() resets .shared to FixtureDataManager.empty() per test;
populated tests call seedPopulated() to copy every StateFlow from
FixtureDataManager.populated() onto .shared synchronously. 15 populated surfaces ×
2 modes = 30 new PNGs.

iOS goldens: 58 → 88. 44 SnapshotGalleryTests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 01:45:04 -05:00
Trey T 3bac38449c P3.1: iOS goldens @2x + PNG optimizer + Makefile record/verify targets
- SnapshotGalleryTests rendered at displayScale: 2.0 (was native 3.0)
  → 49MB → 15MB (~69% reduction)
- Records via SNAPSHOT_TESTING_RECORD=1 env var (no code edits needed)
- scripts/optimize_goldens.sh runs zopflipng (or pngcrush fallback)
  over both iOS and Android golden dirs
- scripts/{record,verify}_snapshots.sh one-command wrappers
- Makefile targets: make {record,verify,optimize}-snapshots

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 23:45:02 -05:00
Trey T 6f2fb629c9 P3: iOS parity gallery (swift-snapshot-testing, 1.17.0+)
Records 58 baseline PNGs across 29 primary SwiftUI screens × {light, dark}
for the honeyDue iOS app. Covers auth, password reset, onboarding,
residences, tasks, contractors, documents, profile, and subscription
surfaces — everything that's instantiable without complex runtime context.

State coverage is empty-only for this first pass: views currently spin up
their own ViewModels which read DataManagerObservable.shared directly, and
the test host has no login → all flows render their empty states. A
follow-up PR adds an optional `dataManager:` init param to each
*ViewModel.swift so populated-state snapshots (backed by P1's
FixtureDataManager) can land.

Tolerance knobs: pixelPrecision 0.97 / perceptualPrecision 0.95 — tuned to
absorb animation-frame drift (gradient blobs, focus rings) while catching
structural regressions.

Tooling: swift-snapshot-testing SPM dep added to the HoneyDueTests target
only (not the app target) via scripts/add_snapshot_testing.rb, which is an
idempotent xcodeproj-gem script so the edit is reproducible rather than a
hand-crafted pbxproj diff. Pins resolve to 1.19.2 (up-to-next-major from
the 1.17.0 plan floor).

Blocks regressions at PR time via `xcodebuild test
-only-testing:HoneyDueTests/SnapshotGalleryTests`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 19:37:09 -05:00