diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index f5a8e92..2adef42 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -28,6 +28,13 @@ jobs: git remote add origin "http://x-access-token:${TOKEN}@forgejo:3000/${GITHUB_REPOSITORY}.git" git fetch -q --depth 1 origin "${GITHUB_SHA}" git checkout -q FETCH_HEAD + # private-settings*.json están en .gitignore (llevan DSN y claves), pero + # pubspec.yaml los declara como assets y `flutter analyze` los exige. + # Aquí basta con que existan: analyze no mira el contenido. + - name: Placeholders de private-settings + run: | + echo '{}' > assets/private-settings.json + echo '{}' > assets/private-settings-dev.json - run: flutter pub get - name: Generate code (json_serializable) run: dart run build_runner build --delete-conflicting-outputs @@ -47,6 +54,10 @@ jobs: git remote add origin "http://x-access-token:${TOKEN}@forgejo:3000/${GITHUB_REPOSITORY}.git" git fetch -q --depth 1 origin "${GITHUB_SHA}" git checkout -q FETCH_HEAD + - name: Placeholders de private-settings + run: | + echo '{}' > assets/private-settings.json + echo '{}' > assets/private-settings-dev.json - run: flutter pub get - name: Generate code + test run: | diff --git a/lib/file_utils.dart b/lib/file_utils.dart index dddadc6..1153de7 100644 --- a/lib/file_utils.dart +++ b/lib/file_utils.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:flutter/services.dart' show rootBundle; +import 'package:flutter/services.dart' show AssetBundle, rootBundle; final RegExp esRegExp = RegExp('^es-'); @@ -20,13 +20,15 @@ Future getFileNameOfLang( {required String dir, required String fileName, required String ext, - required String lang}) async { + required String lang, + // Inyectable para poder probarlo sin depender del bundle real. + AssetBundle? bundle}) async { final String base = '$dir/$fileName'; final String fallback = getFallbackLang(lang); String file = '$base-$lang.$ext'; - if (await assetNotExists(file)) { + if (await assetNotExists(file, bundle)) { file = '$base-$fallback.$ext'; - if (await assetNotExists(file)) { + if (await assetNotExists(file, bundle)) { file = '$base.$ext'; } } @@ -34,8 +36,8 @@ Future getFileNameOfLang( } // https://github.com/flutter/flutter/issues/15325 -Future assetNotExists(String asset) { - return rootBundle +Future assetNotExists(String asset, [AssetBundle? bundle]) { + return (bundle ?? rootBundle) .load(asset) .then((_) => false) .catchError((Object err, StackTrace stack) { diff --git a/pubspec.yaml b/pubspec.yaml index aaf3d51..1213681 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,9 @@ description: All Against Fire version: 1.10.0+10 environment: - sdk: ">=3.5.0 <4.0.0" + # 3.8 es lo que piden los generadores de código actuales (build_runner avisa + # "does not match the required range ^3.8.0" con una cota más baja). + sdk: ">=3.8.0 <4.0.0" dependencies: flutter: diff --git a/test/file_test.dart b/test/file_test.dart index d42e4f9..9dbd35c 100644 --- a/test/file_test.dart +++ b/test/file_test.dart @@ -1,5 +1,25 @@ +import 'dart:convert'; + import 'package:fires_flutter/file_utils.dart'; -import 'package:test/test.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +/// Bundle de mentira: solo "existen" los assets que se le pasan. Evita depender +/// del bundle real, que en un test unitario no responde. +class _FakeBundle extends CachingAssetBundle { + _FakeBundle(this.present); + + final Set present; + + @override + Future load(String key) async { + if (!present.contains(key)) { + throw FlutterError('Unable to load asset: $key'); + } + return ByteData.sublistView(Uint8List.fromList(utf8.encode('x'))); + } +} void main() { test('test es-ES fallback', () { @@ -24,7 +44,31 @@ void main() { test('test privacy English md page', () async { final String answer = await getFileNameOfLang( - dir: 'assets/pages', fileName: 'privacy', ext: 'md', lang: 'en'); + dir: 'assets/pages', + fileName: 'privacy', + ext: 'md', + lang: 'en', + bundle: _FakeBundle({'assets/pages/privacy-en.md'})); expect(answer, 'assets/pages/privacy-en.md'); }); + + test('idioma sin fichero propio cae al del idioma de respaldo', () async { + final String answer = await getFileNameOfLang( + dir: 'assets/pages', + fileName: 'privacy', + ext: 'md', + lang: 'gl', + bundle: _FakeBundle({'assets/pages/privacy-es.md'})); + expect(answer, 'assets/pages/privacy-es.md'); + }); + + test('sin traducciones cae al fichero base', () async { + final String answer = await getFileNameOfLang( + dir: 'assets/pages', + fileName: 'privacy', + ext: 'md', + lang: 'fr', + bundle: _FakeBundle({})); + expect(answer, 'assets/pages/privacy.md'); + }); }