---
name: verify-ui
description: After Compose UI changes, assess test coverage and write/amend Robolectric characterisation tests using the Robot pattern.
user_invocable: true
---

# Verify UI Changes

Assess uncommitted UI changes, determine if characterisation tests are needed, and write
them using this repo's Robot pattern. Prefer tests that exercise the real composable and
assert on user-visible text/nodes, faking only the ViewState/callbacks at the edges.

## Step 1: Identify What Changed

```bash
git diff --name-only HEAD
```

Classify each changed file:

| File location | Classification |
|---|---|
| Compose screens/composables under `android/app/src/main/.../ui/screens/` | **UI change** — likely needs a characterisation test |
| ViewModel, ViewState for a Compose screen | **Behavior change** — needs a characterisation test |
| `mediaprovider/`, `data/`, use cases | **Logic change** — unit test, not this skill |
| DI modules, navigation wiring | **Wiring change** — verify manually or via a Maestro flow |

If no Compose UI files changed, stop — say "No UI changes detected, characterisation tests
not needed."

## Step 2: Find Existing Coverage

For each affected screen, look for its existing test trio next to the screen's ViewModel:

```bash
find android/app/src/test -iname "*ScreenName*Test.kt" -o -iname "*ScreenName*Robot.kt"
```

Read the matching `*Test.kt` and `*Robot.kt`. Ask:
- Does an existing test already exercise the changed behavior?
- Would the existing test catch a regression if this change broke something?
- Does the robot need updating (new test tag, changed content description, new interaction)?

**If existing tests cover the change:** Update them to match new behavior. Don't add
redundant tests.

**If no existing test covers the change:** Write a new trio (Step 3).

## Step 3: Write the Test

Each screen's tests live in three files alongside each other, e.g.
`android/app/src/test/java/com/simplecityapps/shuttle/ui/screens/library/songs/`:

```
SongListTest.kt        # test cases
SongListRobot.kt       # selector/interaction encapsulation
SongListScenarios.kt   # ViewState factories
```

### Robot responsibilities — keep it thin

- `setContent(viewState)` — renders the composable with callback captures
- `assertTextDisplayed(text)` / `assertTextNotDisplayed(text)` — hides node selectors
- `openContextMenu()` — hides content description selectors
- `clickText(text)` / `clickMenuItem(text)` — interaction primitives
- Callback capture fields (`lastAddedToQueue`, `lastDeleted`, etc.) — avoid verbose lambda
  setup in tests
- The robot hides *selectors*, not *behaviour* — tests compose primitives to describe what
  they verify. No screen-specific compound assertions.

### Scenario factories

Top-level functions that construct `ViewState` with sensible defaults:

```kotlin
readySongList(songs = listOf(createSong(name = "My Song")))
scanningSongList(Progress(50, 200))
emptySongList()
loadingSongList  // val, not a function — Loading has no parameters
```

### Model factories

`createSong()`, `createGenre()`, `createPlaylist()` in
`android/app/src/test/java/com/simplecityapps/creationFunctions.kt`. All parameters have
defaults — override only what matters for the test.

### Example test

```kotlin
@Test
fun `context menu invokes onAddToQueue`() {
    val song = createSong(name = "Queue Me")
    robot.setContent(readySongList(songs = listOf(song)))
    robot.openContextMenu()
    robot.clickMenuItem("Add to Queue")
    robot.lastAddedToQueue shouldBe song
}
```

### Known Robolectric limitation: FastScroller + DropdownMenu

The `FastScroller` overlay causes `DropdownMenu` popups to be immediately dismissed under
Robolectric. Context menu tests that need dropdowns should render the list *item*
composable directly (e.g. `GenreListItem`) rather than the full list. The robot encapsulates
this — see `GenreListRobot.setItemContent()`.

## Step 4: Run and Verify

```bash
./gradlew :android:app:testDebugUnitTest --tests "com.simplecityapps.shuttle.ui.screens.library.songs.SongListTest"
```

If the test fails, fix the test or the implementation — diagnose which is wrong before
changing either.

## When NOT to Write a Test

- The change is a string/copy update and existing tests already assert on other elements of
  the same screen
- The change is a dependency version bump with no behavior change
- An existing characterisation test already covers the changed composable and would catch
  regressions
