Bonjour.

J'ai un test unitaire qui échoue
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following TestFailure was thrown running a test:
Expected: exactly one matching node in the widget tree
  Actual: _WidgetPredicateFinder:<zero widgets with widget matching predicate (Closure: (Widget) =>
bool) (ignoring offstage widgets)>
   Which: means none were found but one was expected
En utilisant debugDumpApp();, je me rends compte que l'application ressort le widget HeroControllerScope :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
AutomatedTestWidgetsFlutterBinding - DEBUG MODE
[root](renderObject: RenderView#ae39a)
└View-[GlobalObjectKey TestFlutterView#81a0a]
 └_ViewScope
  └_MediaQueryFromView(state: _MediaQueryFromViewState#6ab7a)
   └MediaQuery(...)
    └StreamBuilder<XFile>(state: _StreamBuilderBaseState<XFile, AsyncSnapshot<XFile>>#d6910)
     └BlocProvider<BlocXXX>(state: ProviderState#31496)
      └MaterialApp(state: _MaterialAppState#41775)
       └ScrollConfiguration(behavior: MaterialScrollBehavior)
        └HeroControllerScope
...
                                                     └AnimatedTheme(duration: 200ms, state: _AnimatedThemeState#ba34b(ticker inactive, ThemeDataTween(ThemeData#662fa → ThemeData#662fa)))
                                                      └Theme(ThemeData#662fa, dependencies: [DefaultSelectionStyle])
                                                       └_InheritedTheme
                                                        └CupertinoTheme(...)
                                                         └_InheritedCupertinoTheme
....
Je ne sais vraiment pas d'où ça sort, à aucun moment je n'ai utilisé HeroControllerScope, et il n'apparait lors d'un Ctrl+F que dans flutter\SDK\packages\flutter\lib\src\.

Le test en question :
Code dart : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  group("Bottom bar", () {
    late BlocXXX blocXXX;
    late StreamBuilder<XFile> testedStream;

    Widget snapshotBottomBar(BuildContext context, AsyncSnapshot<XFile> snapshot) {
      return BlocProvider<BlocXXX>(
          bloc: blocXXX,
          child: const MaterialApp(
            locale: Locale('fr', 'FR'),
            localizationsDelegates: [
              DefaultWidgetsLocalizations.delegate,
              DefaultMaterialLocalizations.delegate
            ],
            home: BottomBar("_title"))
      );
    }

    setUp(() {
      blocXXX = BlocXXX();
      testedStream = StreamBuilder(
          initialData: XFile("lalilulelo"),
          stream: blocXXX.stream,
          builder: snapshotBottomBar
      );
    });

    testWidgets('nominal', (WidgetTester tester) async {
      FlutterError.onError = ignoreOverflowErrors;
      await tester.pumpWidget(testedStream);

      debugDumpApp();

      expect(
          find.byWidgetPredicate((widget) => widget is FloatingActionButton &&
              widget.child == const Icon(Icons.camera)
          ),
          findsOneWidget);
    });
  });
}

Note : j'utilise l'architecture BLoC, qui complique sensiblement les tests... à mon grand désarroi. Mais ça fonctionne.
Note 2 : la partie ignoreOverflowErrors et TestWidgetsFlutterBinding.ensureInitialized(); proviennent de cette page (pour ignorer les erreurs de RenderFlex Overflow).

Le BottomBar utilise cette hiérarchie
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class BottomBar extends StatelessWidget {
  Directionality(
    SizedBox(
      Column(
        Container(
          Text(),
        ),
        Container(
          StreamBuilder<XFile>(
            Row(
              FloatingActionButton(),
              FloatingActionButton(),
              FloatingActionButton()
            )
          ),
        ),
      )
    ),
  )
}
Quant au bloc il est simplissime :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
class BlocXXX extends Bloc {
  XFile? selected;
  final StreamController<XFile> _imgCtrl = StreamController<XFile>();
  Stream<XFile> get stream => _imgCtrl.stream;
  Sink<XFile> get sink => _imgCtrl.sink;
 
  // D'autres fonctions
}
Je jette une bouteille à la mer. Est-ce que quelqu'un a déjà eu ce HeroController qui sort de nulle part ? Pourquoi je ne retrouve pas ma BottomBar lors du test ?

J'avoue que je sèche.