Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit d24ae3f

Browse files
committed
WIP tests
1 parent c279243 commit d24ae3f

File tree

3 files changed

+288
-3
lines changed

3 files changed

+288
-3
lines changed

lib/web_ui/lib/src/engine/keyboard_binding.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ class KeyboardBinding {
153153
});
154154
_listeners.clear();
155155
_converter.dispose();
156+
// _rawKeyboard.dispose();
156157
}
157158
}
158159

lib/web_ui/lib/src/engine/raw_keyboard.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ class RawKeyboard {
1919
/// if no repeat events were received.
2020
final Map<String, Timer> _keydownTimers = <String, Timer>{};
2121

22-
/// Uninitializes the [RawKeyboard] singleton.
22+
/// Terminates unfinished tasks in this [RawKeyboard] instance.
2323
///
24-
/// After calling this method this object becomes unusable and [instance]
25-
/// becomes `null`. Call [initialize] again to initialize a new singleton.
24+
/// The [dispose] must be called when a [RawKeyboard] instance is no longer
25+
/// used.
2626
void dispose() {
2727
for (final String key in _keydownTimers.keys) {
2828
_keydownTimers[key]!.cancel();
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:typed_data';
6+
import 'package:meta/meta.dart' show isTest;
7+
import 'package:quiver/testing/async.dart';
8+
import 'package:test/bootstrap/browser.dart';
9+
import 'package:test/test.dart';
10+
import 'package:ui/src/engine.dart';
11+
import 'package:ui/ui.dart' as ui;
12+
13+
const int kLocationStandard = 0;
14+
const int kLocationLeft = 1;
15+
const int kLocationRight = 2;
16+
const int kLocationNumpad = 3;
17+
18+
final int kPhysicalKeyA = kWebToPhysicalKey['KeyA']!;
19+
final int kPhysicalKeyE = kWebToPhysicalKey['KeyE']!;
20+
final int kPhysicalKeyU = kWebToPhysicalKey['KeyU']!;
21+
final int kPhysicalDigit1 = kWebToPhysicalKey['Digit1']!;
22+
final int kPhysicalNumpad1 = kWebToPhysicalKey['Numpad1']!;
23+
final int kPhysicalShiftLeft = kWebToPhysicalKey['ShiftLeft']!;
24+
final int kPhysicalShiftRight = kWebToPhysicalKey['ShiftRight']!;
25+
final int kPhysicalMetaLeft = kWebToPhysicalKey['MetaLeft']!;
26+
final int kPhysicalTab = kWebToPhysicalKey['Tab']!;
27+
final int kPhysicalCapsLock = kWebToPhysicalKey['CapsLock']!;
28+
final int kPhysicalScrollLock = kWebToPhysicalKey['ScrollLock']!;
29+
// A web-specific physical key when code is empty.
30+
const int kPhysicalEmptyCode = 0x1700000000;
31+
32+
const int kLogicalKeyA = 0x00000000061;
33+
const int kLogicalKeyU = 0x00000000075;
34+
const int kLogicalDigit1 = 0x00000000031;
35+
final int kLogicalNumpad1 = kWebLogicalLocationMap['1']![kLocationNumpad]!;
36+
final int kLogicalShiftLeft = kWebLogicalLocationMap['Shift']![kLocationLeft]!;
37+
final int kLogicalShiftRight = kWebLogicalLocationMap['Shift']![kLocationRight]!;
38+
final int kLogicalCtrlLeft = kWebLogicalLocationMap['Control']![kLocationLeft]!;
39+
final int kLogicalAltLeft = kWebLogicalLocationMap['Alt']![kLocationLeft]!;
40+
final int kLogicalMetaLeft = kWebLogicalLocationMap['Meta']![kLocationLeft]!;
41+
const int kLogicalTab = 0x0000000009;
42+
final int kLogicalCapsLock = kWebToLogicalKey['CapsLock']!;
43+
final int kLogicalScrollLock = kWebToLogicalKey['ScrollLock']!;
44+
45+
typedef VoidCallback = void Function();
46+
47+
void main() {
48+
internalBootstrapBrowserTest(() => testMain);
49+
}
50+
51+
ui.PlatformMessageCallback storeChannelDataTo(List<Map<String, dynamic>> storage) {
52+
return (String channel, ByteData? data, ui.PlatformMessageResponseCallback? callback) {
53+
expect(channel, 'flutter/keyevent');
54+
final Map<String, dynamic>? dataReceived =
55+
const JSONMessageCodec().decodeMessage(data) as Map<String, dynamic>?;
56+
expect(dataReceived, isNotNull);
57+
storage.add(dataReceived!);
58+
};
59+
}
60+
61+
void testMain() {
62+
/// Used to save and restore [ui.window.onPlatformMessage] after each test.
63+
ui.PlatformMessageCallback? savedCallback;
64+
65+
setUp(() {
66+
savedCallback = ui.window.onPlatformMessage;
67+
});
68+
69+
tearDown(() {
70+
ui.window.onPlatformMessage = savedCallback;
71+
});
72+
73+
test('Single key press, repeat, and release', () {
74+
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
75+
final List<Map<String, dynamic>> channelData = <Map<String, dynamic>>[];
76+
ui.window.onPlatformMessage = storeChannelDataTo(channelData);
77+
final KeyboardBinding binding = KeyboardBinding.instance!;
78+
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
79+
keyDataList.add(key);
80+
// Only handle down events
81+
return key.type == ui.KeyEventType.down;
82+
});
83+
bool preventedDefault = false;
84+
void onPreventDefault() { preventedDefault = true; }
85+
86+
converter.handleEvent(keyDownEvent('KeyA', 'a')
87+
..timeStamp = 1
88+
..onPreventDefault = onPreventDefault
89+
);
90+
expectKeyData(keyDataList.last,
91+
timeStamp: const Duration(milliseconds: 1),
92+
type: ui.KeyEventType.down,
93+
physical: kPhysicalKeyA,
94+
logical: kLogicalKeyA,
95+
character: 'a',
96+
);
97+
expect(channelData, hasLength(1));
98+
expect(channelData.last, <String, dynamic>{
99+
'type': 'keyup',
100+
'keymap': 'web',
101+
'code': 'KeyA',
102+
'location': 0,
103+
'key': 'a',
104+
'metaState': 0x0,
105+
'keyCode': 1,
106+
});
107+
expect(preventedDefault, isTrue);
108+
preventedDefault = false;
109+
110+
converter.handleEvent(keyRepeatedDownEvent('KeyA', 'a')
111+
..timeStamp = 1.5
112+
..onPreventDefault = onPreventDefault
113+
);
114+
expectKeyData(keyDataList.last,
115+
timeStamp: const Duration(milliseconds: 1, microseconds: 500),
116+
type: ui.KeyEventType.repeat,
117+
physical: kPhysicalKeyA,
118+
logical: kLogicalKeyA,
119+
character: 'a',
120+
);
121+
expect(preventedDefault, isFalse);
122+
123+
converter.handleEvent(keyRepeatedDownEvent('KeyA', 'a')
124+
..timeStamp = 1500
125+
..onPreventDefault = onPreventDefault
126+
);
127+
expectKeyData(keyDataList.last,
128+
timeStamp: const Duration(seconds: 1, milliseconds: 500),
129+
type: ui.KeyEventType.repeat,
130+
physical: kPhysicalKeyA,
131+
logical: kLogicalKeyA,
132+
character: 'a',
133+
);
134+
expect(preventedDefault, isFalse);
135+
136+
converter.handleEvent(keyUpEvent('KeyA', 'a')
137+
..timeStamp = 2000.5
138+
..onPreventDefault = onPreventDefault
139+
);
140+
expectKeyData(keyDataList.last,
141+
timeStamp: const Duration(seconds: 2, microseconds: 500),
142+
type: ui.KeyEventType.up,
143+
physical: kPhysicalKeyA,
144+
logical: kLogicalKeyA,
145+
character: null,
146+
);
147+
expect(preventedDefault, isFalse);
148+
});
149+
}
150+
151+
class MockKeyboardEvent implements FlutterHtmlKeyboardEvent {
152+
MockKeyboardEvent({
153+
required this.type,
154+
required this.code,
155+
required this.key,
156+
this.timeStamp = 0,
157+
this.repeat = false,
158+
this.altKey = false,
159+
this.ctrlKey = false,
160+
this.shiftKey = false,
161+
this.metaKey = false,
162+
this.location = 0,
163+
this.onPreventDefault,
164+
});
165+
166+
@override
167+
String type;
168+
169+
@override
170+
String? code;
171+
172+
@override
173+
String? key;
174+
175+
@override
176+
bool? repeat;
177+
178+
@override
179+
num? timeStamp;
180+
181+
@override
182+
bool altKey;
183+
184+
@override
185+
bool ctrlKey;
186+
187+
@override
188+
bool shiftKey;
189+
190+
@override
191+
bool metaKey;
192+
193+
@override
194+
int? location;
195+
196+
@override
197+
bool getModifierState(String key) => modifierState.contains(key);
198+
final Set<String> modifierState = <String>{};
199+
200+
@override
201+
void preventDefault() { onPreventDefault?.call(); }
202+
VoidCallback? onPreventDefault;
203+
}
204+
205+
// Flags used for the `modifiers` argument of `key***Event` functions.
206+
const int kAlt = 0x1;
207+
const int kCtrl = 0x2;
208+
const int kShift = 0x4;
209+
const int kMeta = 0x8;
210+
211+
// Utility functions to make code more concise.
212+
//
213+
// To add timeStamp or onPreventDefault, use syntax like `..timeStamp = `.
214+
MockKeyboardEvent keyDownEvent(String code, String key, [int modifiers = 0, int location = 0]) {
215+
return MockKeyboardEvent(
216+
type: 'keydown',
217+
code: code,
218+
key: key,
219+
altKey: modifiers & kAlt != 0,
220+
ctrlKey: modifiers & kCtrl != 0,
221+
shiftKey: modifiers & kShift != 0,
222+
metaKey: modifiers & kMeta != 0,
223+
location: location,
224+
);
225+
}
226+
227+
MockKeyboardEvent keyUpEvent(String code, String key, [int modifiers = 0, int location = 0]) {
228+
return MockKeyboardEvent(
229+
type: 'keyup',
230+
code: code,
231+
key: key,
232+
altKey: modifiers & kAlt != 0,
233+
ctrlKey: modifiers & kCtrl != 0,
234+
shiftKey: modifiers & kShift != 0,
235+
metaKey: modifiers & kMeta != 0,
236+
location: location,
237+
);
238+
}
239+
240+
MockKeyboardEvent keyRepeatedDownEvent(String code, String key, [int modifiers = 0, int location = 0]) {
241+
return MockKeyboardEvent(
242+
type: 'keydown',
243+
code: code,
244+
key: key,
245+
altKey: modifiers & kAlt != 0,
246+
ctrlKey: modifiers & kCtrl != 0,
247+
shiftKey: modifiers & kShift != 0,
248+
metaKey: modifiers & kMeta != 0,
249+
repeat: true,
250+
location: location,
251+
);
252+
}
253+
254+
// Flags used for the `activeLocks` argument of expectKeyData.
255+
const int kCapsLock = 0x1;
256+
const int kNumlLock = 0x2;
257+
const int kScrollLock = 0x4;
258+
259+
void expectKeyData(
260+
ui.KeyData target, {
261+
required ui.KeyEventType type,
262+
required int physical,
263+
required int logical,
264+
required String? character,
265+
Duration? timeStamp,
266+
bool synthesized = false,
267+
}) {
268+
expect(target.type, type);
269+
expect(target.physical, physical);
270+
expect(target.logical, logical);
271+
expect(target.character, character);
272+
expect(target.synthesized, synthesized);
273+
if (timeStamp != null)
274+
expect(target.timeStamp, equals(timeStamp));
275+
}
276+
277+
typedef FakeAsyncTest = void Function(FakeAsync);
278+
279+
@isTest
280+
void testFakeAsync(String description, FakeAsyncTest fn) {
281+
test(description, () {
282+
FakeAsync().run(fn);
283+
});
284+
}

0 commit comments

Comments
 (0)