diff --git a/.gitattributes b/.gitattributes index ef352538..074fccf4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1 @@ -app/src/test/screenshots/** filter=lfs diff=lfs merge=lfs -text app/schemas/** linguist-generated -app/src/test/screenshots/** linguist-generated diff --git a/.gitignore b/.gitignore index ea465f2e..fc2b6f41 100644 --- a/.gitignore +++ b/.gitignore @@ -15,5 +15,6 @@ captures/ .direnv/ .preview/ .integration/ +app/src/test/screenshots/ .DS_Store diff --git a/AGENTS.md b/AGENTS.md index 8ba03494..a9bb1727 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,7 +22,7 @@ Everything comes from the Nix flake devshell; do not install toolchains by hand. - Lint: `gradle :app:ktlintCheck :app:lintDebug` - Format: `gradle :app:ktlintFormat` - Integration tests: `./tools/integration-test.nu` (starts navidrome and gonic, then runs `:app:integrationTest`) -- Screenshots: `gradle :app:recordRoborazziDebug` to record, `gradle :app:verifyRoborazziDebug` to check +- Screenshots: `gradle :app:recordRoborazziDebug` renders the `*Screen` composables to `app/src/test/screenshots/` (gitignored) plus an HTML report in `app/build/reports/roborazzi/`, for local review. No golden images are committed and `verifyRoborazziDebug` is not part of CI. ## Testing layout @@ -49,7 +49,7 @@ Everything comes from the Nix flake devshell; do not install toolchains by hand. ## Gotchas -- Robolectric runs at SDK 35 while `targetSdk` is 37; raising it means re-recording the Roborazzi goldens. +- Robolectric runs at SDK 35 while `targetSdk` is 37; raising it changes the on-demand Roborazzi renders (there are no committed goldens to re-record). - Room 3 does not map `PagingSource` automatically: a DAO that returns it needs `@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class)`. - Material icons are not pulled in by `material3`; use the BOM-managed `material-icons-core`/`material-icons-extended` (frozen at 1.7.8). Extended is large in debug builds but R8 strips unused icons from release. - Robolectric creates a fresh `Application` per test in one JVM, so `SubtracksApp.onCreate` stops any running Koin before `startKoin`. diff --git a/docs/architecture.md b/docs/architecture.md index d5ece519..a4871d1d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -122,9 +122,9 @@ No analytics or third-party telemetry. Credentials are stored locally in app dat ## Testing -- Unit tests use Robolectric, Roborazzi (screenshot goldens) and MockWebServer (client), plus an in-memory Room database on the bundled SQLite JVM driver (Robolectric's SQLite has no FTS5). -- Screenshot goldens exercise the stateless `*Screen` composables with fake data; paging screens are fed `PagingData.from(...)`. Record with `gradle :app:recordRoborazziDebug`, check with `:app:verifyRoborazziDebug`. -- Cover art in goldens is generated deterministically in the test and served through Coil's `FakeImageLoaderEngine` (synchronous, and no third-party images are committed); a real `ImageLoader` is installed for the run and reset afterwards. Each image is two or three flat colours in a pattern that identifies its type — stripes for albums, dots for playlists, rings for artists. +- Unit tests use Robolectric, Roborazzi (screenshots) and MockWebServer (client), plus an in-memory Room database on the bundled SQLite JVM driver (Robolectric's SQLite has no FTS5). +- Roborazzi screenshots exercise the stateless `*Screen` composables with fake data; paging screens are fed `PagingData.from(...)`. They are rendered on demand with `gradle :app:recordRoborazziDebug` (into the gitignored `app/src/test/screenshots/`) for local review; no images are committed and nothing verifies them in CI. +- Cover art in the screenshots is generated deterministically in the test and served through Coil's `FakeImageLoaderEngine` (synchronous, and no third-party images are committed); a real `ImageLoader` is installed for the run and reset afterwards. Each image is two or three flat colours in a pattern that identifies its type — stripes for albums, dots for playlists, rings for artists. - Integration tests run `SubsonicSourceIntegrationTest` against real navidrome and gonic instances, driven by `tools/integration-test.nu`. ## Build and CI diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md index ee7dd403..7f24851c 100644 --- a/docs/implementation-plan.md +++ b/docs/implementation-plan.md @@ -24,7 +24,7 @@ Goal: a usable app for browsing a server, end to end. - App shell: a root gate that shows the add-server flow until a server exists, then the tabbed library screen matching the Flutter app (icon-only tabs for albums, artists, songs and playlists, with sync and settings actions), plus album and playlist detail screens and a monochrome theme. - Source setup: name/address/username/password with token-auth toggle, a connection test, save-and-sync, plus server switching and removal in settings. - Library browsing: albums (covers-only grid), artists, songs and playlists paged from Room (`PagingSource`) and rendered with `LazyPagingItems`, with Coil cover art served from stable media URLs. -- Roborazzi screenshot goldens for the eight new screens. +- Roborazzi screenshots for the eight new screens, rendered on demand for review (not committed). Not done yet in this slice (deliberately): artist detail, cover-art tonal colour extraction, long-press menus, editing an existing server, and per-section sort/filter controls (only album sort is wired). @@ -54,5 +54,5 @@ Not done yet in this slice (deliberately): artist detail, cover-art tonal colour ## Deferred decisions - Whether to drop the plaintext-auth fallback or keep it behind an "insecure server" toggle. -- Robolectric runs at SDK 35 while `targetSdk` is 37; aligning them means re-recording the Roborazzi goldens. +- Robolectric runs at SDK 35 while `targetSdk` is 37; aligning them changes the on-demand Roborazzi renders (nothing committed to re-record). - Whether to extract `:core:data` and friends once the UI has grown.