I have this widget containing a TextField
and a UndoHistoryController
:
class _MyHomePageState extends State<MyHomePage> {
final TextEditingController _controller = TextEditingController();
final FocusNode _focusNode = FocusNode();
final UndoHistoryController _undoController = UndoHistoryController();
TextStyle? get enabledStyle => Theme.of(context).textTheme.bodyMedium;
TextStyle? get disabledStyle => Theme.of(context).textTheme.bodyMedium?.copyWith(color: Colors.grey);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
maxLines: 4,
controller: _controller,
focusNode: _focusNode,
undoController: _undoController,
),
ValueListenableBuilder<UndoHistoryValue>(
valueListenable: _undoController,
builder: (BuildContext context, UndoHistoryValue value, Widget? child) {
return Row(
children: <Widget>[
TextButton(
child: Text('Undo', style: value.canUndo ? enabledStyle : disabledStyle),
onPressed: () {
_undoController.undo();
},
),
TextButton(
child: Text('Redo', style: value.canRedo ? enabledStyle : disabledStyle),
onPressed: () {
_undoController.redo();
},
),
],
);
},
),
],
),
),
);
}
}
I'm trying to write a test about it:
testWidgets('The undo history controller should undo and redo the history changes', (WidgetTester tester) async {
await tester.pumpWidget(
const example.UndoHistoryControllerExampleApp(),
);
expect(find.byType(TextField), findsOne);
expect(find.widgetWithText(TextButton, 'Undo'), findsOne);
expect(find.widgetWithText(TextButton, 'Redo'), findsOne);
await tester.enterText(find.byType(TextField), '1st change');
await tester.pump();
expect(find.text('1st change'), findsOne);
await tester.enterText(find.byType(TextField), '2nd change');
await tester.pump();
expect(find.text('2nd change'), findsOne);
await tester.tap(find.text('Undo'));
await tester.pump();
expect(find.text('2nd change'), findsOne);
await tester.tap(find.text('Redo'));
await tester.pump();
expect(find.text('2nd change'), findsOne);
});
which fails when at the expect(find.text('2nd change'), findsOne);
after the tap on the "Undo" button. It looks like _undoController.value.canUndo
is false
in the onPressed
of the "Undo" button.
I tried replacing the pump()
with pumpAndSettle()
or multiple pump()
s after each enterText
in the test, but it doesn't seem to trigger anything in the undo history controller.
How to make it work?
I had to wait for the _kThrottleDuration = Duration(milliseconds: 500)
. It looks like the undo history controller waits a bit before updating its state:
// This duration was chosen as a best fit for the behavior of Mac, Linux,
// and Windows undo/redo state save durations, but it is not perfect for any
// of them.
static const Duration _kThrottleDuration = Duration(milliseconds: 500);
The updated test is:
testWidgets('The undo history controller should undo and redo the history changes', (WidgetTester tester) async {
await tester.pumpWidget(
const example.UndoHistoryControllerExampleApp(),
);
expect(find.byType(TextField), findsOne);
expect(find.widgetWithText(TextButton, 'Undo'), findsOne);
expect(find.widgetWithText(TextButton, 'Redo'), findsOne);
await tester.enterText(find.byType(TextField), '1st change');
await tester.pump(const Duration(milliseconds: 500));
expect(find.text('1st change'), findsOne);
await tester.enterText(find.byType(TextField), '2nd change');
await tester.pump(const Duration(milliseconds: 500));
expect(find.text('2nd change'), findsOne);
await tester.tap(find.text('Undo'));
await tester.pump();
expect(find.text('2nd change'), findsOne);
await tester.tap(find.text('Redo'));
await tester.pump();
expect(find.text('2nd change'), findsOne);
});