| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // @dart = 2.6 |
| import 'dart:html' as html; |
| import 'dart:js_util' as js_util; |
| import 'dart:typed_data'; |
| |
| import 'package:ui/src/engine.dart'; |
| import 'package:ui/ui.dart' as ui; |
| |
| import 'package:test/test.dart'; |
| |
| void main() { |
| group('Keyboard', () { |
| /// Used to save and restore [ui.window.onPlatformMessage] after each test. |
| ui.PlatformMessageCallback savedCallback; |
| |
| setUp(() { |
| savedCallback = ui.window.onPlatformMessage; |
| }); |
| |
| tearDown(() { |
| ui.window.onPlatformMessage = savedCallback; |
| }); |
| |
| test('initializes and disposes', () { |
| expect(Keyboard.instance, isNull); |
| Keyboard.initialize(); |
| expect(Keyboard.instance, isA<Keyboard>()); |
| Keyboard.instance.dispose(); |
| expect(Keyboard.instance, isNull); |
| }); |
| |
| test('dispatches keyup to flutter/keyevent channel', () { |
| Keyboard.initialize(); |
| |
| String channelReceived; |
| Map<String, dynamic> dataReceived; |
| ui.window.onPlatformMessage = (String channel, ByteData data, |
| ui.PlatformMessageResponseCallback callback) { |
| channelReceived = channel; |
| dataReceived = const JSONMessageCodec().decodeMessage(data); |
| }; |
| |
| html.KeyboardEvent event; |
| |
| event = dispatchKeyboardEvent('keyup', key: 'SomeKey', code: 'SomeCode'); |
| |
| expect(event.defaultPrevented, isFalse); |
| expect(channelReceived, 'flutter/keyevent'); |
| expect(dataReceived, <String, dynamic>{ |
| 'type': 'keyup', |
| 'keymap': 'web', |
| 'code': 'SomeCode', |
| 'key': 'SomeKey', |
| 'metaState': 0x0, |
| }); |
| |
| Keyboard.instance.dispose(); |
| }, |
| // TODO(nurhan): https://github.com/flutter/flutter/issues/50815 |
| skip: browserEngine == BrowserEngine.edge); |
| |
| test('dispatches keydown to flutter/keyevent channel', () { |
| Keyboard.initialize(); |
| |
| String channelReceived; |
| Map<String, dynamic> dataReceived; |
| ui.window.onPlatformMessage = (String channel, ByteData data, |
| ui.PlatformMessageResponseCallback callback) { |
| channelReceived = channel; |
| dataReceived = const JSONMessageCodec().decodeMessage(data); |
| }; |
| |
| html.KeyboardEvent event; |
| |
| event = |
| dispatchKeyboardEvent('keydown', key: 'SomeKey', code: 'SomeCode'); |
| |
| expect(channelReceived, 'flutter/keyevent'); |
| expect(dataReceived, <String, dynamic>{ |
| 'type': 'keydown', |
| 'keymap': 'web', |
| 'code': 'SomeCode', |
| 'key': 'SomeKey', |
| 'metaState': 0x0, |
| }); |
| expect(event.defaultPrevented, isFalse); |
| |
| Keyboard.instance.dispose(); |
| }, |
| // TODO(nurhan): https://github.com/flutter/flutter/issues/50815 |
| skip: browserEngine == BrowserEngine.edge); |
| |
| test('dispatches correct meta state', () { |
| Keyboard.initialize(); |
| |
| Map<String, dynamic> dataReceived; |
| ui.window.onPlatformMessage = (String channel, ByteData data, |
| ui.PlatformMessageResponseCallback callback) { |
| dataReceived = const JSONMessageCodec().decodeMessage(data); |
| }; |
| |
| html.KeyboardEvent event; |
| |
| event = dispatchKeyboardEvent( |
| 'keydown', |
| key: 'SomeKey', |
| code: 'SomeCode', |
| isControlPressed: true, |
| ); |
| expect(event.defaultPrevented, isFalse); |
| expect(dataReceived, <String, dynamic>{ |
| 'type': 'keydown', |
| 'keymap': 'web', |
| 'code': 'SomeCode', |
| 'key': 'SomeKey', |
| // ctrl |
| 'metaState': 0x4, |
| }); |
| |
| event = dispatchKeyboardEvent( |
| 'keydown', |
| key: 'SomeKey', |
| code: 'SomeCode', |
| isShiftPressed: true, |
| isAltPressed: true, |
| isMetaPressed: true, |
| ); |
| expect(event.defaultPrevented, isFalse); |
| expect(dataReceived, <String, dynamic>{ |
| 'type': 'keydown', |
| 'keymap': 'web', |
| 'code': 'SomeCode', |
| 'key': 'SomeKey', |
| // shift alt meta |
| 'metaState': 0x1 | 0x2 | 0x8, |
| }); |
| |
| Keyboard.instance.dispose(); |
| }, |
| // TODO(nurhan): https://github.com/flutter/flutter/issues/50815 |
| skip: browserEngine == BrowserEngine.edge); |
| |
| test('dispatches repeat events', () { |
| Keyboard.initialize(); |
| |
| List<Map<String, dynamic>> messages = <Map<String, dynamic>>[]; |
| ui.window.onPlatformMessage = (String channel, ByteData data, |
| ui.PlatformMessageResponseCallback callback) { |
| messages.add(const JSONMessageCodec().decodeMessage(data)); |
| }; |
| |
| html.KeyboardEvent event; |
| |
| event = dispatchKeyboardEvent( |
| 'keydown', |
| key: 'SomeKey', |
| code: 'SomeCode', |
| repeat: true, |
| ); |
| expect(event.defaultPrevented, isFalse); |
| |
| event = dispatchKeyboardEvent( |
| 'keydown', |
| key: 'SomeKey', |
| code: 'SomeCode', |
| repeat: true, |
| ); |
| expect(event.defaultPrevented, isFalse); |
| |
| event = dispatchKeyboardEvent( |
| 'keydown', |
| key: 'SomeKey', |
| code: 'SomeCode', |
| repeat: true, |
| ); |
| expect(event.defaultPrevented, isFalse); |
| |
| final Map<String, dynamic> expectedMessage = <String, dynamic>{ |
| 'type': 'keydown', |
| 'keymap': 'web', |
| 'code': 'SomeCode', |
| 'key': 'SomeKey', |
| 'metaState': 0, |
| }; |
| expect(messages, <Map<String, dynamic>>[ |
| expectedMessage, |
| expectedMessage, |
| expectedMessage, |
| ]); |
| |
| Keyboard.instance.dispose(); |
| }, |
| // TODO(nurhan): https://github.com/flutter/flutter/issues/50815 |
| skip: browserEngine == BrowserEngine.edge); |
| |
| test('stops dispatching events after dispose', () { |
| Keyboard.initialize(); |
| |
| int count = 0; |
| ui.window.onPlatformMessage = (String channel, ByteData data, |
| ui.PlatformMessageResponseCallback callback) { |
| count += 1; |
| }; |
| |
| dispatchKeyboardEvent('keydown'); |
| expect(count, 1); |
| dispatchKeyboardEvent('keyup'); |
| expect(count, 2); |
| |
| Keyboard.instance.dispose(); |
| expect(Keyboard.instance, isNull); |
| |
| // No more event dispatching. |
| dispatchKeyboardEvent('keydown'); |
| expect(count, 2); |
| dispatchKeyboardEvent('keyup'); |
| expect(count, 2); |
| }); |
| |
| test('prevents default when "Tab" is pressed', () { |
| Keyboard.initialize(); |
| |
| int count = 0; |
| ui.window.onPlatformMessage = (String channel, ByteData data, |
| ui.PlatformMessageResponseCallback callback) { |
| count += 1; |
| }; |
| |
| final html.KeyboardEvent event = dispatchKeyboardEvent( |
| 'keydown', |
| key: 'Tab', |
| code: 'Tab', |
| ); |
| |
| expect(event.defaultPrevented, isTrue); |
| expect(count, 1); |
| |
| Keyboard.instance.dispose(); |
| }); |
| |
| test('ignores keyboard events triggered on text fields', () { |
| Keyboard.initialize(); |
| |
| int count = 0; |
| ui.window.onPlatformMessage = (String channel, ByteData data, |
| ui.PlatformMessageResponseCallback callback) { |
| count += 1; |
| }; |
| |
| useTextEditingElement((html.Element element) { |
| final html.KeyboardEvent event = dispatchKeyboardEvent( |
| 'keydown', |
| key: 'SomeKey', |
| code: 'SomeCode', |
| target: element, |
| ); |
| |
| expect(event.defaultPrevented, isFalse); |
| expect(count, 0); |
| }); |
| |
| Keyboard.instance.dispose(); |
| }); |
| |
| test('the "Tab" key should never be ignored', () { |
| Keyboard.initialize(); |
| |
| int count = 0; |
| ui.window.onPlatformMessage = (String channel, ByteData data, |
| ui.PlatformMessageResponseCallback callback) { |
| count += 1; |
| }; |
| |
| useTextEditingElement((html.Element element) { |
| final html.KeyboardEvent event = dispatchKeyboardEvent( |
| 'keydown', |
| key: 'Tab', |
| code: 'Tab', |
| target: element, |
| ); |
| |
| expect(event.defaultPrevented, isTrue); |
| expect(count, 1); |
| }); |
| |
| Keyboard.instance.dispose(); |
| }); |
| }); |
| } |
| |
| typedef ElementCallback = void Function(html.Element element); |
| |
| void useTextEditingElement(ElementCallback callback) { |
| final html.InputElement input = html.InputElement(); |
| input.classes.add(HybridTextEditing.textEditingClass); |
| |
| try { |
| html.document.body.append(input); |
| callback(input); |
| } finally { |
| input.remove(); |
| } |
| } |
| |
| html.KeyboardEvent dispatchKeyboardEvent( |
| String type, { |
| html.EventTarget target, |
| String key, |
| String code, |
| bool repeat = false, |
| bool isShiftPressed = false, |
| bool isAltPressed = false, |
| bool isControlPressed = false, |
| bool isMetaPressed = false, |
| }) { |
| target ??= html.window; |
| |
| final Function jsKeyboardEvent = |
| js_util.getProperty(html.window, 'KeyboardEvent'); |
| final List<dynamic> eventArgs = <dynamic>[ |
| type, |
| <String, dynamic>{ |
| 'key': key, |
| 'code': code, |
| 'repeat': repeat, |
| 'shiftKey': isShiftPressed, |
| 'altKey': isAltPressed, |
| 'ctrlKey': isControlPressed, |
| 'metaKey': isMetaPressed, |
| 'bubbles': true, |
| 'cancelable': true, |
| } |
| ]; |
| final html.KeyboardEvent event = |
| js_util.callConstructor(jsKeyboardEvent, js_util.jsify(eventArgs)); |
| target.dispatchEvent(event); |
| |
| return event; |
| } |