Favoritos now renders non-empty custom groups as grupo:<id> sub-folders (hidden when empty) with ungrouped stations left as direct leaves, reusing the existing hijos() path so the zero-groups case stays byte-identical to today's flat list.
7.3 KiB
Apply Progress: Android Auto Favorite Groups
Change: android-auto-favorite-groups Mode: Strict TDD Batch: 1 of 1 (single delivery, no chaining — 291 changed lines, within 280-370 forecast)
Completed Tasks
All 22 tasks from tasks.md are complete (Phase 1 through Phase 5).
- 1.1-1.5 Foundation: snapshot seam + media-id scheme
- 2.1-2.10 Core Implementation: pure tree builders
- 3.1-3.3 Integration/Wiring
- 4.1-4.3 Testing/Verification
- 5.1 Cleanup (doc comments)
Files Changed
| File | Action | What Was Done |
|---|---|---|
lib/servicios/navegacion_auto.dart |
Modified | Added grupos() to FuenteEmisorasAuto abstract + grupos param on actualizarSnapshot; FuenteEmisorasAutoLocal.grupos() (snapshot-or-SQLite, try/catch→[], mirrors favoritos()); ConstructorArbolAuto: _prefijoGrupo, _maxGruposPorFavoritos, esCarpetaGrupo, itemGrupo, carpetasFavoritos, hijosGrupo. |
lib/servicios/servicio_audio.dart |
Modified | getChildren: two new thin branches (idFavoritos → carpetasFavoritos, esCarpetaGrupo → hijosGrupo); removed now-unreachable idFavoritos case from _listaParaCarpeta's switch. |
lib/estado/estado_radio.dart |
Modified | cargarGruposFavoritos() now pushes _fuenteAuto?.actualizarSnapshot(grupos: _gruposFavoritos), mirroring the existing favoritos: push. |
test/servicios/navegacion_auto_test.dart |
Modified | New groups: esCarpetaGrupo, itemGrupo, carpetasFavoritos (regression parity, custom groups, empty-group omission, >50 cap), hijosGrupo, resolver collision-safety; _FakeFuenteEmisorasAuto updated to implement grupos(). |
test/estado/estado_radio_test.dart |
Modified | _FuenteEmisorasAutoEspia updated to implement grupos() and capture ultimoGrupos; existing live-snapshot test extended with an assertion that cargarGruposFavoritos() pushes the group snapshot. |
TDD Cycle Evidence
| Task | Test File | Layer | Safety Net | RED | GREEN | TRIANGULATE | REFACTOR |
|---|---|---|---|---|---|---|---|
1.1-1.2 (esCarpetaGrupo) |
navegacion_auto_test.dart |
Unit | 26/26 baseline | Written (compile-fail, symbol absent) | Passed | 4 cases (grupo:/favoritos/emisora:/empty) | Clean |
| 1.3-1.5 (seam extension) | navegacion_auto_test.dart |
Unit | same run | Written (fake didn't compile) | Passed | N/A — structural interface extension | Clean |
2.1-2.2 (itemGrupo) |
navegacion_auto_test.dart |
Unit | same run | Written | Passed | Single scenario (spec defines one shape) | Clean |
2.3-2.7 (carpetasFavoritos) |
navegacion_auto_test.dart |
Unit | same run | Written (4 tests: regression parity, custom groups, empty omission, >50 cap) | Passed | 4 cases forced real filter/take/append logic (not fakeable with hardcoded return) | Clean |
2.8-2.9 (hijosGrupo) |
navegacion_auto_test.dart |
Unit | same run | Written (60-station cap+sort case, unknown-id case) | Passed | 2 cases (cap+sort vs. empty-for-unknown) | Clean |
2.10 (resolver collision) |
navegacion_auto_test.dart |
Unit | same run | Written | Passed | Single scenario (collision-free assertion) | Clean |
3.1-3.2 (servicio_audio.dart wiring) |
N/A (zero-coverage dispatch layer, confirmed by 2 prior archived verify-reports) | N/A | N/A | N/A — thin dispatch, no test file exists for this layer per design precedent | Manual static review | N/A | Manual review: no unused symbols, no leftover idFavoritos switch case, prefixes non-colliding |
3.3 (estado_radio.dart push) |
estado_radio_test.dart |
Unit | 21/21 baseline (pre-change: file needed a compile fix for the new interface member regardless) | Written (new assertion on existing test) | Passed | N/A — mirrors existing favoritos: push pattern 1:1 |
Clean |
Test Summary
- Total tests written: 8 new test groups / 10 new test cases in
navegacion_auto_test.dart+ 1 new assertion inestado_radio_test.dart - Total tests passing: 54/54 (33/33
navegacion_auto_test.dart, 21/21estado_radio_test.dart) — corrected during verify, apply agent's original count of 55/55 (34/34) was inaccurate - Layers used: Unit (all)
- Approval tests (refactoring): None — no refactoring-of-existing-behavior tasks; task 5.1 was pure doc-comment addition, verified via full re-run
- Pure functions created:
esCarpetaGrupo,itemGrupo,carpetasFavoritos,hijosGrupo(all pure, no side effects, fully unit-tested)
Deviations from Design
None — implementation matches design.md exactly, including:
grupo:<id>media-id scheme, collision-free againstemisora:<uuid>(verified via test)- Empty custom groups hidden from the car tree (Design Decision "Empty groups hidden")
sin_asignarstations stay as direct leaves atFavoritosroot, never their own folder (Design Decision "Ungrouped favorites stay as direct leaves")carpetasFavoritosreuses the existinghijos(idFavoritos, emisoras: ...)path forsin_asignarleaves — confirmed via the regression-parity test (2.3) which asserts byte-identical output tohijos()when no custom groups exist. This directly satisfies the orchestrator's flagged risk: the no-custom-groups case is not reimplemented, it delegates to the exact same code path._maxGruposPorFavoritos = 50kept as a separate constant from_maxItemsPorCarpeta, per design's tunability rationale- Phone order preserved (
orden ASC, nombre ASCfromobtenerGrupos(), no re-sort applied) - Handler dispatch (
servicio_audio.dart) kept to two thin branches; all real logic lives in pure, tested functions innavegacion_auto.dart
flutter build/flutter analyze/flutter gen-l10n were NOT run per environment constraint (hangs) — task 4.3 [DEVIATION], same precedent as openspec/changes/archive/2026-07-16-android-auto-media/tasks.md. Manual static review completed: no unused symbols, prefix collision-safety verified by test (2.10), no leftover references to the removed _listaParaCarpeta switch case.
Issues Found
One implementation detail not explicitly called out in tasks.md: test/estado/estado_radio_test.dart's _FuenteEmisorasAutoEspia fake implements FuenteEmisorasAuto and would have failed to compile once the interface gained grupos(). Fixed as part of task 4.2's "run any estado_radio/servicio_audio test files if they exist" — updated the fake and added a real assertion (not just a compile fix) proving cargarGruposFavoritos() pushes the snapshot correctly, consistent with the file's existing test style for the sibling favoritos:/misEmisoras:/todas: pushes.
MediaItem.playable is nullable (bool?) in the audio_service package — the >50 groups truncated test originally used !i.playable which failed to compile; fixed to i.playable != true.
Remaining Tasks
None. All 22 tasks complete.
Workload / PR Boundary
- Mode: single PR (Review Workload Forecast: Medium risk, no chaining recommended,
Decision needed before apply: No) - Current work unit: N/A — full change delivered in one batch
- Boundary: starts from zero prior apply-progress, ends with all 22 tasks complete and 54/54 targeted tests green
- Estimated review budget impact: 291 changed lines (5 files: 3 lib, 2 test) — within the 280-370 forecast, under the 400-line budget
Status
22/22 tasks complete. Ready for sdd-verify.