I am following this tutorial https://docs.flutter.dev/development/ui/interactive#the-parent-widget-manages-the-widgets-state and I have the following in main.dart
:
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
Widget statesSection = Container(
padding: const EdgeInsets.all(32),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [StatefulParentWidget()]));
return MaterialApp(
title: 'Flutter Layout',
home: Scaffold(
appBar: AppBar(title: const Text("Flutter Layout")),
body: ListView(children: [
statesSection
])));
}
It doesn't find anything at all in the following test code:
testWidgets('State management tests', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
expect(find.byType(StatefulParentWidget), findsOneWidget); // Fails!
expect(find.text("Inactive"), findsOneWidget); // Fails!
expect(find.text("Active"), findsNothing); // Fails!
});
Test error message:
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following TestFailure was thrown running a test:
Expected: exactly one matching node in the widget tree
Actual: _WidgetTypeFinder:<zero widgets with type "StatefulParentWidget" (ignoring offstage
widgets)>
Which: means none were found but one was expected
Any advice and insight is appreciated. https://github.com/khteh/flutter
CodePudding user response:
Finale
Change your code to:
// titleSection,
// buttonsSection,
// textSection,
statesSection
and the test will pass for
testWidgets('State management tests', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
expect(find.byType(Scaffold), findsOneWidget);
expect(find.byType(StatefulParentWidget), findsOneWidget);
expect(find.text("Inactive"), findsOneWidget);
expect(find.text("Active"), findsNothing);
//StatefulParentWidget statefulParentWidget = const StatefulParentWidget();
//tester.state(find.byWidget(statefulParentWidget));
//expect(find.byWidget(tapboxB), findsOneWidget);
});
So the test only can find widgets rendered already, and in your case, the widgets in statesSection
were once off the stage.
Former Discussion
1 With Scaffod
If you were facing the same exception message as follows:
flutter: (The following exception is now available via WidgetTester.takeException:)
flutter: ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
flutter: The following assertion was thrown building MainPage:
flutter: No MediaQuery widget ancestor found.
flutter: Scaffold widgets require a MediaQuery widget ancestor.
Change your test code to this, it should work
void main() {
testWidgets('State management tests', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MaterialApp(
home: const MainPage(),
));
expect(find.byType(ParentWidget), findsOneWidget); // Fails!
expect(find.text("Inactive"), findsOneWidget); // Fails!
expect(find.text("Active"), findsNothing); // Fails!
});
}
Full code is here:
void main() {
testWidgets('State management tests', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MaterialApp(home: const MainPage(),));
expect(find.byType(ParentWidget), findsOneWidget); // Fails!
expect(find.text("Inactive"), findsOneWidget); // Fails!
expect(find.text("Active"), findsNothing); // Fails!
});
}
class MainPage extends StatelessWidget {
const MainPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Hello"),
),
body: Container(
padding: const EdgeInsets.all(32),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [ParentWidget()])));
}
}
// ParentWidget manages the state for TapboxB.
//------------------------ ParentWidget --------------------------------
class ParentWidget extends StatefulWidget {
const ParentWidget({super.key});
@override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool _active = false;
void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
return SizedBox(
child: TapboxB(
active: _active,
onChanged: _handleTapboxChanged,
),
);
}
}
//------------------------- TapboxB ----------------------------------
class TapboxB extends StatelessWidget {
const TapboxB({
super.key,
this.active = false,
required this.onChanged,
});
final bool active;
final ValueChanged<bool> onChanged;
void _handleTap() {
onChanged(!active);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _handleTap,
child: Container(
child: Center(
child: Text(
active ? 'Active' : 'Inactive',
style: const TextStyle(fontSize: 32.0, color: Colors.white),
),
),
width: 200.0,
height: 200.0,
decoration: BoxDecoration(
color: active ? Colors.lightGreen[700] : Colors.grey[600],
),
),
);
}
}
2 Without Scaffod
If you want to test the widget solely, then you have to replace your Text widget with this:
child: Text(
active ? 'Active' : 'Inactive',
textDirection: TextDirection.ltr,
style: const TextStyle(fontSize: 32.0, color: Colors.white),
),
In my first example, the Scallfold
does the trick so you can test without telling textDirection
attribution. Please refer to TextDirection enum for further reading.
Full code is here:
void main() {
testWidgets('State management tests', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(statesSection);
expect(find.byType(ParentWidget), findsOneWidget); // Fails!
expect(find.text("Inactive"), findsOneWidget); // Fails!
expect(find.text("Active"), findsNothing); // Fails!
});
}
Widget statesSection = Container(
padding: const EdgeInsets.all(32),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [ParentWidget()]));
class MainPage extends StatelessWidget {
const MainPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Hello", textDirection: TextDirection.ltr),
),
body: Container(
padding: const EdgeInsets.all(32),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [ParentWidget()])));
}
}
// ParentWidget manages the state for TapboxB.
//------------------------ ParentWidget --------------------------------
class ParentWidget extends StatefulWidget {
const ParentWidget({super.key});
@override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool _active = false;
void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
return SizedBox(
child: TapboxB(
active: _active,
onChanged: _handleTapboxChanged,
),
);
}
}
//------------------------- TapboxB ----------------------------------
class TapboxB extends StatelessWidget {
const TapboxB({
super.key,
this.active = false,
required this.onChanged,
});
final bool active;
final ValueChanged<bool> onChanged;
void _handleTap() {
onChanged(!active);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _handleTap,
child: Container(
child: Center(
child: Text(
active ? 'Active' : 'Inactive',
textDirection: TextDirection.ltr,
style: const TextStyle(fontSize: 32.0, color: Colors.white),
),
),
width: 200.0,
height: 200.0,
decoration: BoxDecoration(
color: active ? Colors.lightGreen[700] : Colors.grey[600],
),
),
);
}
}