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>
78 lines
2.6 KiB
Bash
Executable File
78 lines
2.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
#
|
||
# Remove snapshot-gallery goldens left over from prior test configurations.
|
||
# Run from the MyCribKMM repo root after the manifest-driven refactor has
|
||
# landed on both platforms' test files, THEN regenerate via
|
||
# `make record-snapshots`. The regeneration fills in the canonical set.
|
||
#
|
||
# Orphan categories removed:
|
||
# 1. Theme-named variants (default/midnight/ocean × light/dark) — from
|
||
# an older per-theme capture scheme that predates the empty/populated
|
||
# matrix.
|
||
# 2. Roborazzi comparison artifacts (_actual.png, _compare.png) — leftover
|
||
# from verify-mode failures; regenerated on next record if needed.
|
||
# 3. Legacy empty/populated PNGs for DataFree surfaces — the new variant
|
||
# matrix captures these as `<name>_light.png` / `<name>_dark.png`
|
||
# without the empty/populated prefix, so the old files are obsolete.
|
||
#
|
||
# Safety: uses `git ls-files` to scope deletions to tracked files only,
|
||
# so no untracked work is touched. Dry-runs by default; pass `--execute`
|
||
# to actually delete.
|
||
|
||
set -euo pipefail
|
||
|
||
cd "$(dirname "$0")/.."
|
||
|
||
DRY_RUN=1
|
||
if [[ "${1:-}" == "--execute" ]]; then
|
||
DRY_RUN=0
|
||
fi
|
||
|
||
ANDROID_DIR="composeApp/src/androidUnitTest/roborazzi"
|
||
|
||
# DataFree surfaces from the canonical manifest — parsed from the Kotlin
|
||
# source so this script doesn't go stale when the manifest changes.
|
||
MANIFEST="composeApp/src/commonMain/kotlin/com/tt/honeyDue/testing/GalleryManifest.kt"
|
||
DATA_FREE=$(grep -oE 'GalleryScreen\("[a-z_]+", GalleryCategory\.DataFree' "$MANIFEST" \
|
||
| sed -E 's/GalleryScreen\("([a-z_]+)".*/\1/')
|
||
|
||
orphans=()
|
||
|
||
# 1. Theme-named legacy captures.
|
||
while IFS= read -r f; do
|
||
orphans+=("$f")
|
||
done < <(ls "$ANDROID_DIR"/*_default_*.png "$ANDROID_DIR"/*_midnight_*.png "$ANDROID_DIR"/*_ocean_*.png 2>/dev/null || true)
|
||
|
||
# 2. Roborazzi comparison artifacts.
|
||
while IFS= read -r f; do
|
||
orphans+=("$f")
|
||
done < <(ls "$ANDROID_DIR"/*_actual*.png "$ANDROID_DIR"/*_compare*.png 2>/dev/null || true)
|
||
|
||
# 3. Legacy empty/populated pairs for DataFree surfaces.
|
||
for surface in $DATA_FREE; do
|
||
for suffix in empty_light empty_dark populated_light populated_dark; do
|
||
f="$ANDROID_DIR/${surface}_${suffix}.png"
|
||
[[ -f "$f" ]] && orphans+=("$f")
|
||
done
|
||
done
|
||
|
||
count=${#orphans[@]}
|
||
echo "Found $count orphan Android goldens."
|
||
|
||
if [[ $count -eq 0 ]]; then
|
||
exit 0
|
||
fi
|
||
|
||
if [[ $DRY_RUN -eq 1 ]]; then
|
||
echo
|
||
echo "Dry run — pass --execute to delete. Files that would be removed:"
|
||
printf ' %s\n' "${orphans[@]}"
|
||
exit 0
|
||
fi
|
||
|
||
echo "Deleting $count files..."
|
||
for f in "${orphans[@]}"; do
|
||
git rm --quiet -f "$f" 2>/dev/null || rm -f "$f"
|
||
done
|
||
echo "Done. Commit the deletions in the same PR as the refactor so the review is one logical change."
|