Files
honeyDueKMP/scripts/cleanup_orphan_goldens.sh
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

78 lines
2.6 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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."