import 'dart:ui' show Locale; import 'package:flutter_test/flutter_test.dart'; import 'package:pluriwave/l10n/gen/app_localizations.dart'; import 'package:pluriwave/modelos/pista_local.dart'; import 'package:pluriwave/servicios/musica_local_auto.dart'; import 'package:pluriwave/servicios/navegacion_auto.dart'; import 'package:pluriwave/servicios/servicio_audio.dart'; /// Every user-readable label of the Android Auto browse tree is translated. /// /// The owner's rule, after Play saw a Spanish-only car tree on a head unit /// in any of the 13 shipped locales: anything a user can read gets /// translated. The two ARB guards (`arb_parity_test`/`arb_anti_copy_test`) /// only ever see strings that already entered the ARB system, so neither /// could catch a label hardcoded in `navegacion_auto.dart` that never /// became a key. This file closes that hole from the CONSUMPTION side /// (the tree really renders the injected locale); /// `test/l10n/etiquetas_arbol_auto_test.dart` closes it from the SOURCE /// side (no new hardcoded label can be added at all). Future> _sinMetadatos(List ids) async => const {}; List _pistas(int cuantas, {String prefijo = 'cancion'}) => List.generate( cuantas, (i) => NodoLocal( documentId: 'doc-$prefijo-$i', nombre: '${prefijo}_${i.toString().padLeft(3, '0')}.mp3', esDirectorio: false, ), ); void main() { final ingles = lookupAppLocalizations(const Locale('en')); group('EtiquetasArbolAuto desde AppLocalizations', () { test('mapea cada etiqueta del árbol a su clave ARB del locale', () { final etiquetas = etiquetasArbolAutoDesde(ingles); expect(etiquetas.escuchar, ingles.autoCarpetaEscuchar); expect(etiquetas.favoritos, ingles.autoCarpetaFavoritos); expect(etiquetas.todasLasEmisoras, ingles.autoCarpetaTodas); expect(etiquetas.misEmisoras, ingles.autoCarpetaMisEmisoras); expect(etiquetas.musicaLocal, ingles.autoCarpetaMusicaLocal); expect( etiquetas.musicaLocalNoDisponible, ingles.autoMusicaLocalNoDisponible, ); expect(etiquetas.cargarMas, ingles.autoCargarMas); expect(etiquetas.ordenarPorCalidad, ingles.autoOrdenarPorCalidad); expect(etiquetas.reproducirCarpeta, ingles.autoReproducirCarpeta); expect(etiquetas.reproducirAleatorio, ingles.autoReproducirAleatorio); expect(etiquetas.pistaSinNombre, ingles.autoPistaSinNombre); }); test('ninguna etiqueta inglesa cae en el castellano de respaldo', () { final etiquetas = etiquetasArbolAutoDesde(ingles); const respaldo = EtiquetasArbolAuto.respaldo; expect(etiquetas.favoritos, isNot(respaldo.favoritos)); expect(etiquetas.todasLasEmisoras, isNot(respaldo.todasLasEmisoras)); expect(etiquetas.misEmisoras, isNot(respaldo.misEmisoras)); expect(etiquetas.musicaLocal, isNot(respaldo.musicaLocal)); expect( etiquetas.musicaLocalNoDisponible, isNot(respaldo.musicaLocalNoDisponible), ); expect(etiquetas.cargarMas, isNot(respaldo.cargarMas)); expect(etiquetas.ordenarPorCalidad, isNot(respaldo.ordenarPorCalidad)); expect(etiquetas.reproducirCarpeta, isNot(respaldo.reproducirCarpeta)); expect(etiquetas.reproducirAleatorio, isNot(respaldo.reproducirAleatorio)); expect(etiquetas.pistaSinNombre, isNot(respaldo.pistaSinNombre)); }); }); group('ConstructorArbolAuto rotula con las etiquetas inyectadas', () { final constructor = ConstructorArbolAuto( etiquetas: etiquetasArbolAutoDesde(ingles), ); test('la raíz premium rotula sus cuatro carpetas en el locale', () { final raiz = constructor.raiz(incluirMusicaLocal: true, premium: true); expect(raiz.map((i) => i.title).toList(), [ ingles.autoCarpetaFavoritos, ingles.autoCarpetaTodas, ingles.autoCarpetaMisEmisoras, ingles.autoCarpetaMusicaLocal, ]); }); test('la raíz gratuita sigue rotulando Escuchar en el locale', () { final raiz = constructor.raiz(incluirMusicaLocal: false, premium: false); expect(raiz.single.title, ingles.autoCarpetaEscuchar); }); test('el item de música local no disponible va en el locale', () { expect( constructor.itemLocalNoDisponible().title, ingles.autoMusicaLocalNoDisponible, ); }); test('las acciones de carpeta y la entrada de orden van en el ' 'locale', () async { final items = await constructor.itemsLocales( _pistas(3), documentIdPadre: 'padre', metadatosDe: _sinMetadatos, ); final titulos = items.map((i) => i.title).toList(); expect(titulos, contains(ingles.autoReproducirCarpeta)); expect(titulos, contains(ingles.autoReproducirAleatorio)); expect(titulos, contains(ingles.autoOrdenarPorCalidad)); }); test('el item "cargar más" de las tres vistas paginadas va en el ' 'locale', () async { final nodos = _pistas(60); final porNombre = await constructor.itemsLocales( nodos, documentIdPadre: 'padre', metadatosDe: _sinMetadatos, ); final porCalidad = await constructor.itemsLocalesOrdenCalidad( nodos, documentIdPadre: 'padre', metadatosDe: _sinMetadatos, ); final porBucket = await constructor.itemsLocalesBucket( _pistas(60, prefijo: 'apple'), documentIdPadre: 'padre', idxBucket: 0, metadatosDe: _sinMetadatos, ); expect(porNombre.last.title, ingles.autoCargarMas); expect(porCalidad.last.title, ingles.autoCargarMas); expect(porBucket.last.title, ingles.autoCargarMas); }); test('un nombre de fichero en blanco cae en la pista sin nombre del ' 'locale', () async { final items = await constructor.itemsLocales( const [ NodoLocal( documentId: 'doc-vacio', nombre: ' ', esDirectorio: false, ), ], documentIdPadre: 'padre', metadatosDe: _sinMetadatos, ); final pista = items.singleWhere((i) => i.id.startsWith('pista:')); expect(pista.title, ingles.autoPistaSinNombre); }); test('los rangos alfabéticos NO se traducen: son rangos de letras ' 'latinas, no prosa', () async { final items = await constructor.itemsLocales( _pistas(60), documentIdPadre: 'padre', metadatosDe: _sinMetadatos, ); expect(items.map((i) => i.title), containsAll(['A-F', 'G-M'])); }); }); group('hijosMusicaLocal propaga las etiquetas', () { test('la carpeta raíz local rotula sus acciones en el locale', () async { final items = await hijosMusicaLocal( ConstructorArbolAuto.idMusicaLocal, fuente: _FuenteLocalFalsa(), etiquetas: etiquetasArbolAutoDesde(ingles), ); expect( items!.map((i) => i.title), containsAll([ ingles.autoReproducirCarpeta, ingles.autoReproducirAleatorio, ]), ); }); }); } /// Minimal in-memory [FuenteMusicaLocalAuto]: one playable track at the /// tree root, no metadata, native channel available. class _FuenteLocalFalsa implements FuenteMusicaLocalAuto { @override Future estadoCarpeta() async => EstadoCarpetaLocal.configurada; @override Future> hijos(String documentId) async => documentId.isEmpty ? const [ NodoLocal( documentId: 'doc-0', nombre: 'cancion.mp3', esDirectorio: false, ), ] : const []; @override Future> metadatosDe(List documentIds) => _sinMetadatos(documentIds); @override Future uriContenidoDePista(String documentId) async => 'content://fake/$documentId'; }