From 0ef6ce35b4a32abc17c57daf23b98c2927fd21e3 Mon Sep 17 00:00:00 2001 From: freetlab Date: Fri, 7 Aug 2026 13:05:59 +0200 Subject: [PATCH] fix(assets): declare the content subdirectories so onboarding ships Audit of the same failure family as the shrunk drawables: references by NAME that nothing validates at compile time. The whole onboarding and release-notes feature had never shipped. Reading the installed APK: ZERO entries under assets/content/, while assets/icons/alarmas/* was present. pubspec declared `assets/content/`, and Flutter does not recurse -- naming a directory includes the files sitting directly in it, never its subdirectories. Every content file lives in one (onboarding/, updates//), so none of them were packaged. On the device that surfaced on every single launch: Unable to load asset: "assets/content/onboarding/en.md" with the file plainly present on disk. That is why it never looked like a packaging problem. The tell was already in the pubspec: assets/icons/alarmas/ is listed explicitly, so the rule was known once and not applied here. All 14 content directories are now declared: onboarding/ plus updates/ for each of the 13 locales. The guard is a test that loads every file under assets/content/ through rootBundle, because that is the only thing that proves an asset is declared and will ship. A test asserting File.existsSync would have stayed green through all of this -- the files were never missing, only unpackaged. Run against the unfixed pubspec it fails 26 of 27; with the fix it passes. Tests: 1165 -> 1192. --- pubspec.yaml | 22 +++++++++ test/assets_contenido_declarados_test.dart | 55 ++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 test/assets_contenido_declarados_test.dart diff --git a/pubspec.yaml b/pubspec.yaml index e784e26..c2ae3fd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -75,4 +75,26 @@ flutter: - assets/audio/ - assets/mockups/ - assets/generated/ + # Flutter NO recurse: declarar 'assets/content/' incluye solo los + # ficheros sueltos de esa carpeta, nunca los de sus subcarpetas. Todo + # el contenido vive en subcarpetas, asi que NADA de esto viajaba en el + # APK -- verificado abriendo el binario instalado: cero entradas de + # assets/content. El onboarding reventaba en cada arranque con + # 'Unable to load asset: assets/content/onboarding/en.md' aunque el + # fichero existe en disco. Mismo fallo de familia que los drawables + # resueltos por nombre: referencia sin validacion en compilacion. - assets/content/ + - assets/content/onboarding/ + - assets/content/updates/ar/ + - assets/content/updates/bn/ + - assets/content/updates/de/ + - assets/content/updates/en/ + - assets/content/updates/es/ + - assets/content/updates/fr/ + - assets/content/updates/hi/ + - assets/content/updates/id/ + - assets/content/updates/it/ + - assets/content/updates/ja/ + - assets/content/updates/pt/ + - assets/content/updates/ru/ + - assets/content/updates/zh/ diff --git a/test/assets_contenido_declarados_test.dart b/test/assets_contenido_declarados_test.dart new file mode 100644 index 0000000..f844d23 --- /dev/null +++ b/test/assets_contenido_declarados_test.dart @@ -0,0 +1,55 @@ +import 'dart:io'; + +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +/// Every file under `assets/content/` must be loadable through `rootBundle`, +/// which is the only thing that proves it is DECLARED in pubspec.yaml and +/// therefore actually ships. +/// +/// Found by reading the installed APK: it contained ZERO entries under +/// `assets/content/`, while `assets/icons/alarmas/*` was present. pubspec +/// declared `assets/content/` — and Flutter does NOT recurse: naming a +/// directory includes the files sitting directly in it, never its +/// subdirectories. All of this content lives in subdirectories +/// (`onboarding/`, `updates//`), so the entire onboarding and +/// release-notes feature had never shipped in any build. On the device it +/// surfaced on every launch as: +/// +/// Unable to load asset: "assets/content/onboarding/en.md" +/// +/// with the file plainly present on disk. +/// +/// Same family as the drawables the resource shrinker deleted: a reference by +/// NAME that nothing validates at compile time, so it fails only on a device. +/// A test that merely checked `File(...).existsSync()` would have stayed green +/// throughout — the files were never missing. Loading through `rootBundle` is +/// what makes it a real guard, because that is the path the app itself takes. +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final directorio = Directory('assets/content'); + final ficheros = + directorio + .listSync(recursive: true) + .whereType() + .map((f) => f.path.replaceAll(r'\', '/')) + .toList() + ..sort(); + + test('hay contenido que comprobar (si no, este test sería vacuo)', () { + expect(ficheros, isNotEmpty); + }); + + for (final ruta in ficheros) { + test('$ruta está declarado y se puede cargar', () async { + await expectLater( + rootBundle.loadString(ruta), + completes, + reason: + 'existe en disco pero rootBundle no lo encuentra: falta declarar ' + 'su directorio en pubspec.yaml, y no viajará en el APK', + ); + }); + } +}