feat: refine terminal input controls
This commit is contained in:
parent
e071f73490
commit
2dd5113044
@ -41,30 +41,53 @@ class TerminalPage extends ConsumerStatefulWidget {
|
||||
class _TerminalPageState extends ConsumerState<TerminalPage>
|
||||
with WidgetsBindingObserver {
|
||||
static const Duration _historySeedDelay = Duration(milliseconds: 120);
|
||||
static const List<_QuickTerminalKey> _symbolTerminalKeys = [
|
||||
_QuickTerminalKey(keyId: 'symbol_at', label: '@', input: '@'),
|
||||
_QuickTerminalKey(keyId: 'symbol_slash', label: '/', input: '/'),
|
||||
_QuickTerminalKey(keyId: 'symbol_dash', label: '-', input: '-'),
|
||||
_QuickTerminalKey(keyId: 'symbol_underscore', label: '_', input: '_'),
|
||||
_QuickTerminalKey(keyId: 'symbol_dot', label: '.', input: '.'),
|
||||
_QuickTerminalKey(keyId: 'symbol_colon', label: ':', input: ':'),
|
||||
_QuickTerminalKey(keyId: 'symbol_tilde', label: '~', input: '~'),
|
||||
_QuickTerminalKey(keyId: 'symbol_backslash', label: r'\', input: r'\'),
|
||||
_QuickTerminalKey(keyId: 'symbol_pipe', label: '|', input: '|'),
|
||||
_QuickTerminalKey(keyId: 'symbol_dollar', label: r'$', input: r'$'),
|
||||
];
|
||||
static const List<_QuickTerminalKey> _controlTerminalKeys = [
|
||||
static const List<_QuickTerminalKey> _editingControlKeys = [
|
||||
_QuickTerminalKey(keyId: 'esc', label: 'Esc', input: '\u001b'),
|
||||
_QuickTerminalKey(keyId: 'tab', label: 'Tab', input: '\t'),
|
||||
_QuickTerminalKey(keyId: 'enter', label: 'Enter', input: '\r'),
|
||||
_QuickTerminalKey(keyId: 'ctrl_c', label: 'Ctrl+C', input: '\u0003'),
|
||||
_QuickTerminalKey(keyId: 'ctrl_d', label: 'Ctrl+D', input: '\u0004'),
|
||||
_QuickTerminalKey(keyId: 'ctrl_l', label: 'Ctrl+L', input: '\u000c'),
|
||||
_QuickTerminalKey(keyId: 'ctrl_u', label: 'Ctrl+U', input: '\u0015'),
|
||||
_QuickTerminalKey(keyId: 'ctrl_z', label: 'Ctrl+Z', input: '\u001a'),
|
||||
_QuickTerminalKey(
|
||||
keyId: 'backspace',
|
||||
label: 'Backspace',
|
||||
input: '\x7f',
|
||||
repeatable: true,
|
||||
),
|
||||
_QuickTerminalKey(
|
||||
keyId: 'delete',
|
||||
label: 'Del',
|
||||
input: '\u001b[3~',
|
||||
repeatable: true,
|
||||
),
|
||||
];
|
||||
static const List<_QuickTerminalKey> _navigationKeys = [
|
||||
_QuickTerminalKey(
|
||||
keyId: 'home',
|
||||
label: 'Home',
|
||||
input: '\u001b[H',
|
||||
repeatable: true,
|
||||
),
|
||||
_QuickTerminalKey(
|
||||
keyId: 'end',
|
||||
label: 'End',
|
||||
input: '\u001b[F',
|
||||
repeatable: true,
|
||||
),
|
||||
_QuickTerminalKey(
|
||||
keyId: 'page_up',
|
||||
label: 'PgUp',
|
||||
input: '\u001b[5~',
|
||||
repeatable: true,
|
||||
),
|
||||
_QuickTerminalKey(
|
||||
keyId: 'page_down',
|
||||
label: 'PgDn',
|
||||
input: '\u001b[6~',
|
||||
repeatable: true,
|
||||
),
|
||||
_QuickTerminalKey(
|
||||
keyId: 'up',
|
||||
label: 'Up',
|
||||
@ -90,6 +113,18 @@ class _TerminalPageState extends ConsumerState<TerminalPage>
|
||||
repeatable: true,
|
||||
),
|
||||
];
|
||||
static const List<_QuickTerminalKey> _symbolTerminalKeys = [
|
||||
_QuickTerminalKey(keyId: 'symbol_at', label: '@', input: '@'),
|
||||
_QuickTerminalKey(keyId: 'symbol_slash', label: '/', input: '/'),
|
||||
_QuickTerminalKey(keyId: 'symbol_dash', label: '-', input: '-'),
|
||||
_QuickTerminalKey(keyId: 'symbol_underscore', label: '_', input: '_'),
|
||||
_QuickTerminalKey(keyId: 'symbol_dot', label: '.', input: '.'),
|
||||
_QuickTerminalKey(keyId: 'symbol_colon', label: ':', input: ':'),
|
||||
_QuickTerminalKey(keyId: 'symbol_tilde', label: '~', input: '~'),
|
||||
_QuickTerminalKey(keyId: 'symbol_backslash', label: r'\', input: r'\'),
|
||||
_QuickTerminalKey(keyId: 'symbol_pipe', label: '|', input: '|'),
|
||||
_QuickTerminalKey(keyId: 'symbol_dollar', label: r'$', input: r'$'),
|
||||
];
|
||||
|
||||
final Terminal terminal = Terminal(maxLines: 1000);
|
||||
final TerminalInteractionController controller =
|
||||
@ -107,6 +142,7 @@ class _TerminalPageState extends ConsumerState<TerminalPage>
|
||||
bool _awaitingAttachReplayFrame = true;
|
||||
bool _awaitingReconnectRestore = false;
|
||||
bool _shouldReconnectOnResume = false;
|
||||
_TerminalInputMode _inputMode = _TerminalInputMode.read;
|
||||
TerminalConnectionState? _lastConnectionState;
|
||||
|
||||
@override
|
||||
@ -138,6 +174,7 @@ class _TerminalPageState extends ConsumerState<TerminalPage>
|
||||
_diagnosticLog.add('ui.terminal.key', normalizedInput);
|
||||
_coordinator.sendInput(normalizedInput);
|
||||
};
|
||||
_applyInputMode();
|
||||
unawaited(_coordinator.start());
|
||||
}
|
||||
|
||||
@ -236,14 +273,6 @@ class _TerminalPageState extends ConsumerState<TerminalPage>
|
||||
);
|
||||
}
|
||||
|
||||
void _sendEnter() {
|
||||
if (!_canSendInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
_sendTerminalInput('\r', diagnosticEvent: 'ui.input.send', detail: r'\r');
|
||||
}
|
||||
|
||||
void _sendQuickKey(_QuickTerminalKey quickKey) {
|
||||
_sendTerminalInput(
|
||||
quickKey.input,
|
||||
@ -263,11 +292,44 @@ class _TerminalPageState extends ConsumerState<TerminalPage>
|
||||
|
||||
_QuickTerminalKey _quickKey(String keyId) {
|
||||
return <_QuickTerminalKey>[
|
||||
..._editingControlKeys,
|
||||
..._navigationKeys,
|
||||
..._symbolTerminalKeys,
|
||||
..._controlTerminalKeys,
|
||||
].singleWhere((key) => key.keyId == keyId);
|
||||
}
|
||||
|
||||
void _setInputMode(_TerminalInputMode mode) {
|
||||
if (_inputMode == mode) {
|
||||
if (mode == _TerminalInputMode.edit) {
|
||||
_terminalFocusNode.requestFocus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_inputMode = mode;
|
||||
});
|
||||
_applyInputMode();
|
||||
}
|
||||
|
||||
void _applyInputMode() {
|
||||
_terminalFocusNode.canRequestFocus = _inputMode == _TerminalInputMode.edit;
|
||||
if (_inputMode == _TerminalInputMode.edit) {
|
||||
_terminalFocusNode.requestFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
_terminalFocusNode.unfocus();
|
||||
}
|
||||
|
||||
void _handleTerminalSurfaceTap() {
|
||||
if (_inputMode != _TerminalInputMode.edit) {
|
||||
return;
|
||||
}
|
||||
|
||||
_terminalFocusNode.requestFocus();
|
||||
}
|
||||
|
||||
void _handleTerminalFrame(String frame) {
|
||||
if (_awaitingAttachReplayFrame) {
|
||||
_awaitingAttachReplayFrame = false;
|
||||
@ -585,13 +647,6 @@ class _TerminalPageState extends ConsumerState<TerminalPage>
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Quick keys',
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildQuickKeysSection(),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Presets',
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
@ -748,13 +803,17 @@ class _TerminalPageState extends ConsumerState<TerminalPage>
|
||||
horizontal: 10,
|
||||
vertical: 8,
|
||||
),
|
||||
child: TerminalView(
|
||||
terminal,
|
||||
focusNode: _terminalFocusNode,
|
||||
autofocus: false,
|
||||
keyboardType: TextInputType.multiline,
|
||||
deleteDetection: true,
|
||||
scrollController: _terminalScrollController,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: _handleTerminalSurfaceTap,
|
||||
child: TerminalView(
|
||||
terminal,
|
||||
focusNode: _terminalFocusNode,
|
||||
autofocus: false,
|
||||
keyboardType: TextInputType.multiline,
|
||||
deleteDetection: true,
|
||||
scrollController: _terminalScrollController,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -1000,19 +1059,43 @@ class _TerminalPageState extends ConsumerState<TerminalPage>
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildCommandDeckAction(
|
||||
FilledButton.icon(
|
||||
key: const Key('terminal_send_button'),
|
||||
onPressed: _canSendInput ? _sendEnter : null,
|
||||
style: FilledButton.styleFrom(
|
||||
minimumSize: const Size(0, 42),
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
icon: const Icon(Icons.keyboard_return),
|
||||
label: Text(isCompact ? 'Enter' : 'Send Enter'),
|
||||
Container(
|
||||
key: const Key('terminal_mode_button'),
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Input mode',
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
),
|
||||
_buildModeButton(
|
||||
key: const Key('terminal_mode_read_button'),
|
||||
label: 'Read',
|
||||
icon: Icons.menu_book_outlined,
|
||||
selected: _inputMode == _TerminalInputMode.read,
|
||||
onPressed: () => _setInputMode(_TerminalInputMode.read),
|
||||
),
|
||||
_buildModeButton(
|
||||
key: const Key('terminal_mode_edit_button'),
|
||||
label: 'Edit',
|
||||
icon: Icons.keyboard_outlined,
|
||||
selected: _inputMode == _TerminalInputMode.edit,
|
||||
onPressed: () => _setInputMode(_TerminalInputMode.edit),
|
||||
),
|
||||
Text(
|
||||
_inputMode == _TerminalInputMode.read
|
||||
? 'Read mode prevents the terminal from taking focus.'
|
||||
: 'Edit mode keeps the terminal ready for typing.',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
_buildQuickKeysSection(),
|
||||
],
|
||||
);
|
||||
},
|
||||
@ -1023,7 +1106,9 @@ class _TerminalPageState extends ConsumerState<TerminalPage>
|
||||
Widget _buildQuickKeysSection() {
|
||||
return Column(
|
||||
children: [
|
||||
_buildKeyTrayRow(_controlTerminalKeys),
|
||||
_buildKeyTrayRow(_editingControlKeys),
|
||||
const SizedBox(height: 8),
|
||||
_buildKeyTrayRow(_navigationKeys),
|
||||
const SizedBox(height: 8),
|
||||
_buildKeyTrayRow(_symbolTerminalKeys),
|
||||
],
|
||||
@ -1054,6 +1139,32 @@ class _TerminalPageState extends ConsumerState<TerminalPage>
|
||||
return ExcludeFocus(child: child);
|
||||
}
|
||||
|
||||
Widget _buildModeButton({
|
||||
required Key key,
|
||||
required String label,
|
||||
required IconData icon,
|
||||
required bool selected,
|
||||
required VoidCallback onPressed,
|
||||
}) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
return OutlinedButton.icon(
|
||||
key: key,
|
||||
onPressed: onPressed,
|
||||
style: OutlinedButton.styleFrom(
|
||||
backgroundColor: selected
|
||||
? colorScheme.primary.withValues(alpha: 0.18)
|
||||
: Colors.transparent,
|
||||
side: BorderSide(
|
||||
color: selected ? colorScheme.primary : const Color(0xFF4A3E31),
|
||||
),
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
icon: Icon(icon, size: 16),
|
||||
label: Text(label),
|
||||
);
|
||||
}
|
||||
|
||||
String get _statusLabel => switch (_connectionState) {
|
||||
TerminalConnectionState.connecting => 'Connecting',
|
||||
TerminalConnectionState.connected => 'Connected',
|
||||
@ -1068,7 +1179,8 @@ class _TerminalPageState extends ConsumerState<TerminalPage>
|
||||
TerminalConnectionState.disconnected => 'Off',
|
||||
};
|
||||
|
||||
bool get _canSendInput => controller.canSendInput;
|
||||
bool get _canSendInput =>
|
||||
controller.canSendInput && _inputMode == _TerminalInputMode.edit;
|
||||
|
||||
IconData get _statusIcon => switch (_connectionState) {
|
||||
TerminalConnectionState.connecting => Icons.sync,
|
||||
@ -1104,3 +1216,5 @@ class _QuickTerminalKey {
|
||||
final String input;
|
||||
final bool repeatable;
|
||||
}
|
||||
|
||||
enum _TerminalInputMode { read, edit }
|
||||
|
||||
@ -348,7 +348,7 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'terminal page keeps tools hidden until the user opens the tools sheet',
|
||||
'terminal page shows mode controls and inline quick keys in the command deck',
|
||||
(tester) async {
|
||||
await _pumpApp(
|
||||
tester,
|
||||
@ -359,31 +359,19 @@ void main() {
|
||||
await _openProjectTerminal(tester);
|
||||
|
||||
expect(find.byKey(const Key('terminal_action_bar')), findsOneWidget);
|
||||
expect(find.byKey(const Key('terminal_send_button')), findsOneWidget);
|
||||
expect(find.byKey(const Key('terminal_send_button')), findsNothing);
|
||||
expect(find.byKey(const Key('terminal_command_deck')), findsOneWidget);
|
||||
expect(find.byType(TextField), findsNothing);
|
||||
expect(find.byKey(const Key('terminal_actions_sheet')), findsNothing);
|
||||
expect(find.byKey(const Key('terminal_mode_read_button')), findsOneWidget);
|
||||
expect(find.byKey(const Key('terminal_mode_edit_button')), findsOneWidget);
|
||||
expect(find.byKey(const Key('terminal_quick_key_ctrl_c')), findsOneWidget);
|
||||
expect(find.byKey(const Key('terminal_quick_key_up')), findsOneWidget);
|
||||
expect(find.byKey(const Key('terminal_quick_key_enter')), findsOneWidget);
|
||||
expect(find.byKey(const Key('terminal_actions_button')), findsOneWidget);
|
||||
expect(
|
||||
find.byKey(const Key('terminal_toggle_actions_button')),
|
||||
findsNothing,
|
||||
);
|
||||
|
||||
await tester.tap(find.byKey(const Key('terminal_actions_button')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byKey(const Key('terminal_actions_sheet')), findsOneWidget);
|
||||
expect(find.text('Reconnect'), findsOneWidget);
|
||||
expect(find.text('Latest'), findsOneWidget);
|
||||
expect(
|
||||
find.byKey(const Key('terminal_quick_key_ctrl_c')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.text('ssh prod'), findsNothing);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('terminal tools expose quick terminal keys', (tester) async {
|
||||
testWidgets('terminal command deck exposes expanded quick terminal keys', (tester) async {
|
||||
final transportFactory = _QueuedTerminalSocketTransportFactory();
|
||||
|
||||
await _pumpApp(
|
||||
@ -397,19 +385,24 @@ void main() {
|
||||
|
||||
await _openProjectTerminal(tester);
|
||||
|
||||
await tester.tap(find.byKey(const Key('terminal_actions_button')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byKey(const Key('terminal_quick_key_esc')), findsOneWidget);
|
||||
expect(find.byKey(const Key('terminal_quick_key_tab')), findsOneWidget);
|
||||
expect(find.byKey(const Key('terminal_quick_key_ctrl_c')), findsOneWidget);
|
||||
expect(find.byKey(const Key('terminal_quick_key_ctrl_d')), findsOneWidget);
|
||||
expect(find.byKey(const Key('terminal_quick_key_ctrl_l')), findsOneWidget);
|
||||
expect(find.byKey(const Key('terminal_quick_key_ctrl_z')), findsOneWidget);
|
||||
expect(find.byKey(const Key('terminal_quick_key_delete')), findsOneWidget);
|
||||
expect(find.byKey(const Key('terminal_quick_key_home')), findsOneWidget);
|
||||
expect(find.byKey(const Key('terminal_quick_key_end')), findsOneWidget);
|
||||
expect(find.byKey(const Key('terminal_quick_key_page_up')), findsOneWidget);
|
||||
expect(find.byKey(const Key('terminal_quick_key_page_down')), findsOneWidget);
|
||||
expect(find.byKey(const Key('terminal_quick_key_up')), findsOneWidget);
|
||||
expect(find.byKey(const Key('terminal_quick_key_down')), findsOneWidget);
|
||||
expect(find.byKey(const Key('terminal_quick_key_left')), findsOneWidget);
|
||||
expect(find.byKey(const Key('terminal_quick_key_right')), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byKey(const Key('terminal_mode_edit_button')));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byKey(const Key('terminal_quick_key_esc')));
|
||||
await tester.pump();
|
||||
|
||||
@ -419,7 +412,7 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('terminal page no longer exposes input mode switching', (
|
||||
testWidgets('terminal page exposes reading and editing mode switching', (
|
||||
tester,
|
||||
) async {
|
||||
await _pumpApp(
|
||||
@ -431,9 +424,45 @@ void main() {
|
||||
await _openProjectTerminal(tester);
|
||||
|
||||
expect(find.byType(TextField), findsNothing);
|
||||
await tester.tap(find.byKey(const Key('terminal_actions_button')));
|
||||
expect(find.byKey(const Key('terminal_mode_button')), findsOneWidget);
|
||||
expect(find.text('Read'), findsOneWidget);
|
||||
expect(find.text('Edit'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('terminal quick keys stay disabled until edit mode is selected', (
|
||||
tester,
|
||||
) async {
|
||||
final transportFactory = _QueuedTerminalSocketTransportFactory();
|
||||
|
||||
await _pumpApp(
|
||||
tester,
|
||||
projectRepository: _FakeProjectRepository(),
|
||||
sessionRepository: _FakeSessionRepository(),
|
||||
socketFactory: TerminalSocketSessionFactory(
|
||||
transportFactory: transportFactory.create,
|
||||
),
|
||||
);
|
||||
|
||||
await _openProjectTerminal(tester);
|
||||
|
||||
await tester.tap(find.byKey(const Key('terminal_quick_key_ctrl_l')));
|
||||
await tester.pump();
|
||||
|
||||
expect(
|
||||
transportFactory.createdTransports.single.sentMessages,
|
||||
isNot(contains(contains('"input":"\\f"'))),
|
||||
);
|
||||
|
||||
await tester.tap(find.byKey(const Key('terminal_mode_edit_button')));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byKey(const Key('terminal_mode_button')), findsNothing);
|
||||
|
||||
await tester.tap(find.byKey(const Key('terminal_quick_key_ctrl_l')));
|
||||
await tester.pump();
|
||||
|
||||
expect(
|
||||
transportFactory.createdTransports.single.sentMessages.last,
|
||||
contains('"input":"\\f"'),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
@ -472,21 +501,20 @@ void main() {
|
||||
);
|
||||
|
||||
await _openProjectTerminal(tester);
|
||||
await tester.tap(find.byKey(const Key('terminal_send_button')));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byKey(const Key('terminal_actions_button')));
|
||||
await tester.tap(find.byKey(const Key('terminal_mode_edit_button')));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byKey(const Key('terminal_quick_key_ctrl_l')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
transportFactory.createdTransports.single.emit('command-output');
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byKey(const Key('terminal_actions_button')));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byKey(const Key('terminal_diagnostics_button')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('ui.input.quick | Ctrl+L'), findsOneWidget);
|
||||
expect(find.textContaining('socket.input.tx | '), findsWidgets);
|
||||
expect(find.textContaining(r'socket.input.tx | \r'), findsOneWidget);
|
||||
expect(find.textContaining('socket.input.tx |'), findsOneWidget);
|
||||
expect(
|
||||
find.textContaining('socket.frame.rx | command-output'),
|
||||
findsOneWidget,
|
||||
@ -494,7 +522,7 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('terminal send button writes carriage return', (tester) async {
|
||||
testWidgets('terminal enter quick key writes carriage return', (tester) async {
|
||||
final transportFactory = _QueuedTerminalSocketTransportFactory();
|
||||
|
||||
await _pumpApp(
|
||||
@ -507,7 +535,9 @@ void main() {
|
||||
);
|
||||
|
||||
await _openProjectTerminal(tester);
|
||||
await tester.tap(find.byKey(const Key('terminal_send_button')));
|
||||
await tester.tap(find.byKey(const Key('terminal_mode_edit_button')));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byKey(const Key('terminal_quick_key_enter')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
@ -695,6 +725,8 @@ void main() {
|
||||
transportFactory: transportFactory.create,
|
||||
),
|
||||
);
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final terminal = tester
|
||||
.widget<TerminalView>(find.byType(TerminalView))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user