Files
pluriwave/test/servicios/servicio_alarmas_pre_notice_template_test.dart
FreeTLab 1e33a79724
Build & Deploy PluriWave / Análisis de código (push) Successful in 25s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m47s
fix(recordings): open the recordings folder from the system file manager
The recordings live in app-private storage (<data>/app_flutter/grabaciones),
which the Android sandbox forbids any other app from reading, so no
ACTION_VIEW on a file:// or FileProvider URI could ever open it. On top of
that, viewDirectory built an EMPTY candidate list for that path:
directoryDocumentUri returned null (path outside external storage) and
FileProvider.getUriForFile threw because pluriwave_file_paths.xml never
covered app_flutter. The loop never ran, so both entry points -- the radio
recorder and Settings -- always showed "could not open the folder".

Publish the folder as a browsable storage root via
RecordingsDocumentsProvider instead. The files never leave private storage;
the document framework asks us for them one document at a time, and the user
can browse, copy out, rename and delete straight from the file manager. The
root follows a user-configured path and falls back to the default recordings
directory. Its title reuses the already-translated recordingsFolderTitle, so
no new literal is introduced in any of the 13 locales.

Also fixes "open last recording", broken by the same missing FileProvider
root, and replaces Intent.createChooser with a bare startActivity in the
candidate loop: a chooser never throws when nothing can handle the intent, so
the first candidate always "succeeded" and the fallback chain never ran.

Device QA pending -- the provider is driven entirely by the platform's
document framework, so no unit test covers it. Each candidate logs its own
name under file_actions.viewDirectory for logcat triage.
2026-07-25 15:07:10 +02:00

110 lines
3.5 KiB
Dart

import 'dart:ui' show Locale;
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pluriwave/l10n/gen/app_localizations.dart';
import 'package:pluriwave/modelos/alarma_musical.dart';
import 'package:pluriwave/servicios/servicio_alarmas_android.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
const channel = MethodChannel('pluriwave/alarm_scheduler');
late List<MethodCall> llamadas;
setUp(() {
llamadas = [];
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, (call) async {
llamadas.add(call);
if (call.method == 'scheduleAlarm') return true;
return null;
});
});
tearDown(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, null);
});
test(
'configurarLocalizaciones pushes localized notification strings with {minutes} templates',
() async {
final servicio = ServicioAlarmasAndroid(channel: channel);
final l10n = lookupAppLocalizations(const Locale('es'));
servicio.configurarLocalizaciones(l10n);
// configurarLocalizaciones fires setNotificationStrings as unawaited; let
// the microtask/event queue drain so the MethodChannel call is recorded.
await Future<void>.delayed(Duration.zero);
final llamada = llamadas.singleWhere(
(c) => c.method == 'setNotificationStrings',
);
final args = llamada.arguments as Map<Object?, Object?>;
expect(
args['preNoticeTemplate'],
contains('{minutes}'),
reason: 'pre-notice template must keep the {minutes} placeholder',
);
expect(
args['snoozeCountdownTemplate'],
contains('{minutes}'),
reason: 'snooze countdown template must keep the {minutes} placeholder',
);
expect(
args['missedTemplate'],
contains('{name}'),
reason: 'missed template must keep the {name} placeholder',
);
// Every notification/channel/chooser string must be present and non-empty
// so the native side never falls back to English for a configured locale.
const claves = [
'ringTitle',
'snoozeLabel',
'stopLabel',
'skipLabel',
'snoozeAgainLabel',
'fireChannelName',
'fireChannelDescription',
'preNoticeChannelName',
'preNoticeChannelDescription',
'openFolderTitle',
'openRecordingTitle',
'recordingsRootTitle',
'missedTitle',
];
for (final clave in claves) {
expect(args[clave], isA<String>(), reason: '$clave missing');
expect(
(args[clave] as String).isNotEmpty,
isTrue,
reason: '$clave is empty',
);
}
},
);
test('scheduleAlarm no longer carries notification string templates', () async {
final servicio = ServicioAlarmasAndroid(channel: channel);
final alarma = AlarmaMusical(
id: 'no-template',
nombre: 'Alarm',
hora: 8,
minuto: 30,
tipoProgramacion: TipoProgramacionAlarma.diaria,
diasSemana: const [],
proximaEjecucion: DateTime(2099, 1, 2, 8, 30),
);
await servicio.programar(alarma);
final llamada = llamadas.singleWhere((c) => c.method == 'scheduleAlarm');
final args = llamada.arguments as Map<Object?, Object?>;
expect(args.containsKey('preNoticeTemplate'), isFalse);
expect(args.containsKey('snoozeCountdownTemplate'), isFalse);
});
}