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
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.
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],
),
),
);
}
}
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],
),
),
);
}
}