Skip to content

Commit 62b077c

Browse files
committed
[red] reproduce console expand with native keyboard ownership
Signed-off-by: Viwat Vchirawongkwin <viwat.v@chula.ac.th>
1 parent e219b74 commit 62b077c

2 files changed

Lines changed: 115 additions & 0 deletions

File tree

‎app/test/widget/app_shell_test.dart‎

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323

2424
import 'dart:typed_data';
2525

26+
import 'package:flutter/foundation.dart';
2627
import 'package:flutter/material.dart';
2728
import 'package:flutter/rendering.dart';
29+
import 'package:flutter/services.dart';
2830
import 'package:flutter_test/flutter_test.dart';
2931

3032
import 'package:pyble/app/app.dart';
@@ -93,6 +95,108 @@ Future<void> _focusLandscapeBlocks(WidgetTester tester) async {
9395

9496
void main() {
9597
group('landscape software keyboard', () {
98+
for (final TargetPlatform platform in <TargetPlatform>[
99+
TargetPlatform.android,
100+
TargetPlatform.iOS,
101+
]) {
102+
testWidgets('Expand dismisses a native Blocks keyboard on $platform', (
103+
WidgetTester tester,
104+
) async {
105+
debugDefaultTargetPlatformOverride = platform;
106+
addTearDown(() => debugDefaultTargetPlatformOverride = null);
107+
const MethodChannel keyboard = MethodChannel('dev.pyble/keyboard');
108+
final List<MethodCall> requests = <MethodCall>[];
109+
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
110+
keyboard,
111+
(MethodCall call) async => requests.add(call),
112+
);
113+
addTearDown(
114+
() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
115+
keyboard,
116+
null,
117+
),
118+
);
119+
await pumpShell(
120+
tester,
121+
connection: fakeConnection(initial: ConnState.ready),
122+
surface: androidTabletLandscape,
123+
);
124+
await _focusLandscapeBlocks(tester);
125+
final Element host = tester.element(
126+
find.byKey(_blocksWorkspaceHostKey),
127+
);
128+
expect(find.byType(EditableText), findsNothing);
129+
tester.view.viewInsets = const FakeViewPadding(bottom: 808);
130+
addTearDown(tester.view.resetViewInsets);
131+
await tester.pump();
132+
133+
await tester.tap(find.byKey(kConsoleStripToggleKey));
134+
await tester.pump();
135+
expect(requests.map((MethodCall call) => call.method), <String>[
136+
'dismissKeyboard',
137+
]);
138+
expect(requests.single.arguments, isNull);
139+
expect(
140+
tester
141+
.widget<ConsoleStripView>(find.byType(ConsoleStripView))
142+
.collapsed,
143+
isTrue,
144+
reason: 'acknowledging a hide request does not prove IME dismissal',
145+
);
146+
expect(tester.element(find.byKey(_blocksWorkspaceHostKey)), same(host));
147+
tester.view.resetViewInsets();
148+
await tester.pump();
149+
expect(
150+
tester
151+
.widget<ConsoleStripView>(find.byType(ConsoleStripView))
152+
.collapsed,
153+
isFalse,
154+
);
155+
expect(tester.element(find.byKey(_blocksWorkspaceHostKey)), same(host));
156+
expect(tester.takeException(), isNull);
157+
});
158+
}
159+
160+
testWidgets('Expand preserves a Flutter editor draft and selection', (
161+
WidgetTester tester,
162+
) async {
163+
const MethodChannel keyboard = MethodChannel('dev.pyble/keyboard');
164+
final List<String> requests = <String>[];
165+
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
166+
keyboard,
167+
(MethodCall call) async => requests.add(call.method),
168+
);
169+
addTearDown(
170+
() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
171+
keyboard,
172+
null,
173+
),
174+
);
175+
await pumpShell(
176+
tester,
177+
connection: fakeConnection(initial: ConnState.ready),
178+
surface: androidTabletLandscape,
179+
);
180+
final Finder editor = find.byType(EditableText).first;
181+
await tester.tap(editor);
182+
await tester.enterText(editor, '# retained draft');
183+
final EditableText before = tester.widget<EditableText>(editor);
184+
before.controller.selection = const TextSelection.collapsed(offset: 5);
185+
tester.view.viewInsets = const FakeViewPadding(bottom: 808);
186+
addTearDown(tester.view.resetViewInsets);
187+
await tester.pump();
188+
await tester.tap(find.byKey(kConsoleStripToggleKey));
189+
await tester.pump();
190+
expect(requests, <String>['dismissKeyboard']);
191+
expect(before.focusNode.hasFocus, isFalse);
192+
expect(before.controller.text, '# retained draft');
193+
expect(
194+
before.controller.selection,
195+
const TextSelection.collapsed(offset: 5),
196+
);
197+
expect(tester.takeException(), isNull);
198+
});
199+
96200
for (final double inset in <double>[404, 440]) {
97201
testWidgets('keeps editing and navigation usable with $inset dp inset', (
98202
WidgetTester tester,

‎docs/specifications/App/TDD.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2037,6 +2037,17 @@ Tablet-first, responsive (CON-5, FR-UI-5). The shell is **connection-gated** ([A
20372037
stays ≥ 720 dp and ≥ 60% may a 360–420 dp generated-Python inspector appear.
20382038
Host notices sit outside the canvas. The console is collapsed/on demand and
20392039
expands after Run or new output.
2040+
- **Landscape keyboard dismissal (FR-UI-8).** While a software-keyboard inset
2041+
is present, explicit console Expand first releases Flutter focus, then asks
2042+
the app-owned `dev.pyble/keyboard` channel to `dismissKeyboard`. Android hides
2043+
the IME using the current Activity window token; iOS ends editing in the
2044+
window of the registered Flutter view controller, including native WebView
2045+
fields. Flutter's `TextInput.hide` alone does not dismiss every embedded
2046+
native input owner. Other platforms use the ordinary Flutter hide request.
2047+
Native completion acknowledges a request, not keyboard visibility: the shell
2048+
stays collapsed until actual view insets clear. This action does not clear
2049+
a text client, replace the Blocks host, discard buffered output, or mutate
2050+
the document/selection.
20402051
- **Portrait / stacked tablet** layout uses a bottom bar to swap the full-width
20412052
Editor, Console, Files, and Blocks work surfaces (FR-UI-2,
20422053
[PRD §19.2](../prd.md)); editor remains the default primary surface. Blocks is

0 commit comments

Comments
 (0)