| // 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. |
| |
| #include "flutter/shell/platform/common/json_message_codec.h" |
| #include "flutter/shell/platform/embedder/embedder.h" |
| #include "flutter/shell/platform/embedder/test_utils/key_codes.h" |
| #include "flutter/shell/platform/windows/flutter_windows_engine.h" |
| #include "flutter/shell/platform/windows/flutter_windows_view.h" |
| #include "flutter/shell/platform/windows/keyboard_key_channel_handler.h" |
| #include "flutter/shell/platform/windows/keyboard_key_embedder_handler.h" |
| #include "flutter/shell/platform/windows/keyboard_key_handler.h" |
| #include "flutter/shell/platform/windows/keyboard_manager_win32.h" |
| #include "flutter/shell/platform/windows/testing/engine_modifier.h" |
| #include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h" |
| #include "flutter/shell/platform/windows/testing/test_keyboard.h" |
| |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include "rapidjson/stringbuffer.h" |
| #include "rapidjson/writer.h" |
| |
| #include <functional> |
| #include <vector> |
| |
| using testing::_; |
| using testing::Invoke; |
| using testing::Return; |
| using namespace ::flutter::testing::keycodes; |
| |
| namespace flutter { |
| namespace testing { |
| |
| namespace { |
| |
| constexpr SHORT kStateMaskToggled = 0x01; |
| constexpr SHORT kStateMaskPressed = 0x80; |
| |
| static LPARAM CreateKeyEventLparam(USHORT scancode, |
| bool extended, |
| bool was_down, |
| USHORT repeat_count = 1, |
| bool context_code = 0, |
| bool transition_state = 0) { |
| return ((LPARAM(transition_state) << 31) | (LPARAM(was_down) << 30) | |
| (LPARAM(context_code) << 29) | (LPARAM(extended ? 0x1 : 0x0) << 24) | |
| (LPARAM(scancode) << 16) | LPARAM(repeat_count)); |
| } |
| |
| typedef uint32_t (*MapVkToCharHandler)(uint32_t virtual_key); |
| |
| uint32_t LayoutDefault(uint32_t virtual_key) { |
| return MapVirtualKey(virtual_key, MAPVK_VK_TO_CHAR); |
| } |
| |
| uint32_t LayoutFrench(uint32_t virtual_key) { |
| switch (virtual_key) { |
| case 0xDD: |
| return 0x8000005E; |
| default: |
| return MapVirtualKey(virtual_key, MAPVK_VK_TO_CHAR); |
| } |
| } |
| |
| class TestKeyboardManagerWin32 : public KeyboardManagerWin32 { |
| public: |
| explicit TestKeyboardManagerWin32(WindowDelegate* delegate) |
| : KeyboardManagerWin32(delegate) {} |
| |
| bool DuringRedispatch() { return during_redispatch_; } |
| |
| protected: |
| void RedispatchEvent(std::unique_ptr<PendingEvent> event) override { |
| assert(!during_redispatch_); |
| during_redispatch_ = true; |
| KeyboardManagerWin32::RedispatchEvent(std::move(event)); |
| during_redispatch_ = false; |
| } |
| |
| private: |
| bool during_redispatch_ = false; |
| }; |
| |
| class MockKeyboardManagerWin32Delegate |
| : public KeyboardManagerWin32::WindowDelegate, |
| public MockMessageQueue { |
| public: |
| MockKeyboardManagerWin32Delegate(WindowBindingHandlerDelegate* view) |
| : view_(view), map_vk_to_char_(LayoutDefault) { |
| keyboard_manager_ = std::make_unique<TestKeyboardManagerWin32>(this); |
| } |
| virtual ~MockKeyboardManagerWin32Delegate() {} |
| |
| // |KeyboardManagerWin32::WindowDelegate| |
| void OnKey(int key, |
| int scancode, |
| int action, |
| char32_t character, |
| bool extended, |
| bool was_down, |
| KeyEventCallback callback) override { |
| view_->OnKey(key, scancode, action, character, extended, was_down, |
| callback); |
| } |
| |
| // |KeyboardManagerWin32::WindowDelegate| |
| void OnText(const std::u16string& text) override { view_->OnText(text); } |
| |
| void SetLayout(MapVkToCharHandler map_vk_to_char) { |
| map_vk_to_char_ = |
| map_vk_to_char == nullptr ? LayoutDefault : map_vk_to_char; |
| } |
| |
| protected: |
| BOOL Win32PeekMessage(LPMSG lpMsg, |
| UINT wMsgFilterMin, |
| UINT wMsgFilterMax, |
| UINT wRemoveMsg) override { |
| return MockMessageQueue::Win32PeekMessage(lpMsg, wMsgFilterMin, |
| wMsgFilterMax, wRemoveMsg); |
| } |
| |
| uint32_t Win32MapVkToChar(uint32_t virtual_key) override { |
| return map_vk_to_char_(virtual_key); |
| } |
| |
| // This method is called for each message injected by test cases with |
| // `tester.InjectMessages`. |
| LRESULT Win32SendMessage(UINT const message, |
| WPARAM const wparam, |
| LPARAM const lparam) override { |
| return keyboard_manager_->HandleMessage(message, wparam, lparam) |
| ? 0 |
| : kWmResultDefault; |
| } |
| |
| // This method is called when the keyboard manager redispatches messages |
| // or dispatches CtrlLeft up for AltGr. |
| UINT Win32DispatchMessage(UINT Msg, WPARAM wParam, LPARAM lParam) override { |
| bool handled = keyboard_manager_->HandleMessage(Msg, wParam, lParam); |
| if (keyboard_manager_->DuringRedispatch()) { |
| EXPECT_FALSE(handled); |
| } |
| return 0; |
| } |
| |
| private: |
| WindowBindingHandlerDelegate* view_; |
| std::unique_ptr<TestKeyboardManagerWin32> keyboard_manager_; |
| MapVkToCharHandler map_vk_to_char_; |
| }; |
| |
| class TestKeystate { |
| public: |
| void Set(uint32_t virtual_key, bool pressed, bool toggled_on = false) { |
| state_[virtual_key] = (pressed ? kStateMaskPressed : 0) | |
| (toggled_on ? kStateMaskToggled : 0); |
| } |
| |
| SHORT Get(uint32_t virtual_key) { return state_[virtual_key]; } |
| |
| KeyboardKeyEmbedderHandler::GetKeyStateHandler Getter() { |
| return [this](uint32_t virtual_key) { return Get(virtual_key); }; |
| } |
| |
| private: |
| std::map<uint32_t, SHORT> state_; |
| }; |
| |
| // A FlutterWindowsView that overrides the RegisterKeyboardHandlers function |
| // to register the keyboard hook handlers that can be spied upon. |
| class TestFlutterWindowsView : public FlutterWindowsView { |
| public: |
| typedef std::function<void(const std::u16string& text)> U16StringHandler; |
| |
| TestFlutterWindowsView(U16StringHandler on_text) |
| // The WindowBindingHandler is used for window size and such, and doesn't |
| // affect keyboard. |
| : FlutterWindowsView( |
| std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>()), |
| on_text_(std::move(on_text)) {} |
| |
| void OnText(const std::u16string& text) override { on_text_(text); } |
| |
| void SetKeyState(uint32_t key, bool pressed, bool toggled_on) { |
| key_state_.Set(key, pressed, toggled_on); |
| } |
| |
| void HandleMessage(const char* channel, |
| const char* method, |
| const char* args) { |
| rapidjson::Document args_doc; |
| args_doc.Parse(args); |
| assert(!args_doc.HasParseError()); |
| |
| rapidjson::Document message_doc(rapidjson::kObjectType); |
| auto& allocator = message_doc.GetAllocator(); |
| message_doc.AddMember("method", rapidjson::Value(method, allocator), |
| allocator); |
| message_doc.AddMember("args", args_doc, allocator); |
| |
| rapidjson::StringBuffer buffer; |
| rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); |
| message_doc.Accept(writer); |
| |
| std::unique_ptr<std::vector<uint8_t>> data = |
| JsonMessageCodec::GetInstance().EncodeMessage(message_doc); |
| FlutterPlatformMessageResponseHandle response_handle; |
| const FlutterPlatformMessage message = { |
| sizeof(FlutterPlatformMessage), // struct_size |
| channel, // channel |
| data->data(), // message |
| data->size(), // message_size |
| &response_handle, // response_handle |
| }; |
| GetEngine()->HandlePlatformMessage(&message); |
| } |
| |
| protected: |
| std::unique_ptr<KeyboardHandlerBase> CreateKeyboardKeyHandler( |
| BinaryMessenger* messenger, |
| KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state) override { |
| return FlutterWindowsView::CreateKeyboardKeyHandler(messenger, |
| key_state_.Getter()); |
| } |
| |
| private: |
| U16StringHandler on_text_; |
| TestKeystate key_state_; |
| }; |
| |
| typedef enum { |
| kKeyCallOnKey, |
| kKeyCallOnText, |
| kKeyCallTextMethodCall, |
| } KeyCallType; |
| |
| typedef struct { |
| KeyCallType type; |
| |
| // Only one of the following fields should be assigned. |
| FlutterKeyEvent key_event; // For kKeyCallOnKey |
| std::u16string text; // For kKeyCallOnText |
| std::string text_method_call; // For kKeyCallTextMethodCall |
| } KeyCall; |
| |
| static std::vector<KeyCall> key_calls; |
| |
| void clear_key_calls() { |
| for (KeyCall& key_call : key_calls) { |
| if (key_call.type == kKeyCallOnKey && |
| key_call.key_event.character != nullptr) { |
| delete[] key_call.key_event.character; |
| } |
| } |
| key_calls.clear(); |
| } |
| |
| class KeyboardTester { |
| public: |
| using ResponseHandler = |
| std::function<void(MockKeyResponseController::ResponseCallback)>; |
| |
| explicit KeyboardTester() : callback_handler_(RespondValue(false)) { |
| view_ = std::make_unique<TestFlutterWindowsView>( |
| [](const std::u16string& text) { |
| key_calls.push_back(KeyCall{ |
| .type = kKeyCallOnText, |
| .text = text, |
| }); |
| }); |
| view_->SetEngine(GetTestEngine( |
| [&callback_handler = callback_handler_]( |
| const FlutterKeyEvent* event, |
| MockKeyResponseController::ResponseCallback callback) { |
| FlutterKeyEvent clone_event = *event; |
| clone_event.character = event->character == nullptr |
| ? nullptr |
| : clone_string(event->character); |
| key_calls.push_back(KeyCall{ |
| .type = kKeyCallOnKey, |
| .key_event = clone_event, |
| }); |
| callback_handler(event, callback); |
| })); |
| window_ = std::make_unique<MockKeyboardManagerWin32Delegate>(view_.get()); |
| } |
| |
| TestFlutterWindowsView& GetView() { return *view_; } |
| |
| void SetKeyState(uint32_t key, bool pressed, bool toggled_on) { |
| view_->SetKeyState(key, pressed, toggled_on); |
| } |
| |
| void Responding(bool response) { callback_handler_ = RespondValue(response); } |
| |
| // Manually handle event callback of the onKeyData embedder API. |
| // |
| // On every onKeyData call, the |handler| will be invoked with the target |
| // key data and the result callback. Immediately calling the callback with |
| // a boolean is equivalent to setting |Responding| with the boolean. However, |
| // |LateResponding| allows storing the callback to call later. |
| void LateResponding( |
| MockKeyResponseController::EmbedderCallbackHandler handler) { |
| callback_handler_ = std::move(handler); |
| } |
| |
| void SetLayout(MapVkToCharHandler layout) { window_->SetLayout(layout); } |
| |
| void InjectMessages(int count, Win32Message message1, ...) { |
| Win32Message messages[count]; |
| messages[0] = message1; |
| va_list args; |
| va_start(args, message1); |
| for (int i = 1; i < count; i += 1) { |
| messages[i] = va_arg(args, Win32Message); |
| } |
| va_end(args); |
| window_->InjectMessageList(count, messages); |
| } |
| |
| private: |
| std::unique_ptr<TestFlutterWindowsView> view_; |
| std::unique_ptr<MockKeyboardManagerWin32Delegate> window_; |
| MockKeyResponseController::EmbedderCallbackHandler callback_handler_; |
| |
| // Returns an engine instance configured with dummy project path values, and |
| // overridden methods for sending platform messages, so that the engine can |
| // respond as if the framework were connected. |
| static std::unique_ptr<FlutterWindowsEngine> GetTestEngine( |
| MockKeyResponseController::EmbedderCallbackHandler |
| embedder_callback_handler) { |
| FlutterDesktopEngineProperties properties = {}; |
| properties.assets_path = L"C:\\foo\\flutter_assets"; |
| properties.icu_data_path = L"C:\\foo\\icudtl.dat"; |
| properties.aot_library_path = L"C:\\foo\\aot.so"; |
| FlutterProjectBundle project(properties); |
| auto engine = std::make_unique<FlutterWindowsEngine>(project); |
| |
| EngineModifier modifier(engine.get()); |
| |
| auto key_response_controller = |
| std::make_shared<MockKeyResponseController>(); |
| key_response_controller->SetEmbedderResponse( |
| std::move(embedder_callback_handler)); |
| key_response_controller->SetTextInputResponse( |
| [](std::unique_ptr<rapidjson::Document> document) { |
| rapidjson::StringBuffer buffer; |
| rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); |
| document->Accept(writer); |
| key_calls.push_back(KeyCall{ |
| .type = kKeyCallTextMethodCall, |
| .text_method_call = buffer.GetString(), |
| }); |
| }); |
| |
| MockEmbedderApiForKeyboard(modifier, key_response_controller); |
| |
| engine->RunWithEntrypoint(nullptr); |
| return engine; |
| } |
| |
| static MockKeyResponseController::EmbedderCallbackHandler RespondValue( |
| bool value) { |
| return [value](const FlutterKeyEvent* event, |
| MockKeyResponseController::ResponseCallback callback) { |
| callback(value); |
| }; |
| } |
| }; |
| |
| constexpr uint64_t kScanCodeBackquote = 0x29; |
| constexpr uint64_t kScanCodeKeyA = 0x1e; |
| constexpr uint64_t kScanCodeKeyB = 0x30; |
| constexpr uint64_t kScanCodeKeyE = 0x12; |
| constexpr uint64_t kScanCodeKeyQ = 0x10; |
| constexpr uint64_t kScanCodeKeyW = 0x11; |
| constexpr uint64_t kScanCodeDigit1 = 0x02; |
| constexpr uint64_t kScanCodeDigit6 = 0x07; |
| // constexpr uint64_t kScanCodeNumpad1 = 0x4f; |
| // constexpr uint64_t kScanCodeNumLock = 0x45; |
| constexpr uint64_t kScanCodeControl = 0x1d; |
| constexpr uint64_t kScanCodeMetaLeft = 0x5b; |
| constexpr uint64_t kScanCodeMetaRight = 0x5c; |
| constexpr uint64_t kScanCodeAlt = 0x38; |
| constexpr uint64_t kScanCodeShiftLeft = 0x2a; |
| constexpr uint64_t kScanCodeShiftRight = 0x36; |
| constexpr uint64_t kScanCodeBracketLeft = 0x1a; |
| constexpr uint64_t kScanCodeArrowLeft = 0x4b; |
| constexpr uint64_t kScanCodeEnter = 0x1c; |
| |
| constexpr uint64_t kVirtualDigit1 = 0x31; |
| constexpr uint64_t kVirtualKeyA = 0x41; |
| constexpr uint64_t kVirtualKeyB = 0x42; |
| constexpr uint64_t kVirtualKeyE = 0x45; |
| constexpr uint64_t kVirtualKeyQ = 0x51; |
| constexpr uint64_t kVirtualKeyW = 0x57; |
| |
| constexpr bool kSynthesized = true; |
| constexpr bool kNotSynthesized = false; |
| |
| } // namespace |
| |
| // Define compound `expect` in macros. If they're defined in functions, the |
| // stacktrace wouldn't print where the function is called in the unit tests. |
| |
| #define EXPECT_CALL_IS_EVENT(_key_call, ...) \ |
| EXPECT_EQ(_key_call.type, kKeyCallOnKey); \ |
| EXPECT_EVENT_EQUALS(_key_call.key_event, __VA_ARGS__); |
| |
| #define EXPECT_CALL_IS_TEXT(_key_call, u16_string) \ |
| EXPECT_EQ(_key_call.type, kKeyCallOnText); \ |
| EXPECT_EQ(_key_call.text, u16_string); |
| |
| #define EXPECT_CALL_IS_TEXT_METHOD_CALL(_key_call, json_string) \ |
| EXPECT_EQ(_key_call.type, kKeyCallTextMethodCall); \ |
| EXPECT_STREQ(_key_call.text_method_call.c_str(), json_string); |
| |
| TEST(KeyboardTest, LowerCaseAHandled) { |
| KeyboardTester tester; |
| tester.Responding(true); |
| |
| // US Keyboard layout |
| |
| // Press A |
| tester.InjectMessages( |
| 2, |
| WmKeyDownInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmCharInfo{'a', kScanCodeKeyA, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyA, |
| kLogicalKeyA, "a", kNotSynthesized); |
| clear_key_calls(); |
| |
| // Release A |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyA, |
| kLogicalKeyA, "", kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| TEST(KeyboardTest, LowerCaseAUnhandled) { |
| KeyboardTester tester; |
| tester.Responding(false); |
| |
| // US Keyboard layout |
| |
| // Press A |
| tester.InjectMessages( |
| 2, |
| WmKeyDownInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmCharInfo{'a', kScanCodeKeyA, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 2); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyA, |
| kLogicalKeyA, "a", kNotSynthesized); |
| EXPECT_CALL_IS_TEXT(key_calls[1], u"a"); |
| clear_key_calls(); |
| |
| // Release A |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyA, |
| kLogicalKeyA, "", kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| TEST(KeyboardTest, ArrowLeftHandled) { |
| KeyboardTester tester; |
| tester.Responding(true); |
| |
| // US Keyboard layout |
| |
| // Press ArrowLeft |
| tester.InjectMessages( |
| 1, WmKeyDownInfo{VK_LEFT, kScanCodeArrowLeft, kExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, |
| kPhysicalArrowLeft, kLogicalArrowLeft, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| |
| // Release ArrowLeft |
| tester.InjectMessages( |
| 1, |
| WmKeyUpInfo{VK_LEFT, kScanCodeArrowLeft, kExtended}.Build(kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalArrowLeft, |
| kLogicalArrowLeft, "", kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| TEST(KeyboardTest, ArrowLeftUnhandled) { |
| KeyboardTester tester; |
| tester.Responding(false); |
| |
| // US Keyboard layout |
| |
| // Press ArrowLeft |
| tester.InjectMessages( |
| 1, WmKeyDownInfo{VK_LEFT, kScanCodeArrowLeft, kExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, |
| kPhysicalArrowLeft, kLogicalArrowLeft, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| |
| // Release ArrowLeft |
| tester.InjectMessages( |
| 1, |
| WmKeyUpInfo{VK_LEFT, kScanCodeArrowLeft, kExtended}.Build(kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalArrowLeft, |
| kLogicalArrowLeft, "", kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| TEST(KeyboardTest, ShiftLeftUnhandled) { |
| KeyboardTester tester; |
| tester.Responding(false); |
| |
| // US Keyboard layout |
| |
| // Press ShiftLeft |
| tester.SetKeyState(VK_LSHIFT, true, false); |
| tester.InjectMessages( |
| 1, |
| WmKeyDownInfo{VK_SHIFT, kScanCodeShiftLeft, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, |
| kPhysicalShiftLeft, kLogicalShiftLeft, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| |
| // Release ShiftLeft |
| tester.SetKeyState(VK_LSHIFT, false, true); |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{VK_SHIFT, kScanCodeShiftLeft, kNotExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalShiftLeft, |
| kLogicalShiftLeft, "", kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| TEST(KeyboardTest, ShiftRightUnhandled) { |
| KeyboardTester tester; |
| tester.Responding(false); |
| |
| // US Keyboard layout |
| |
| // Press ShiftRight |
| tester.SetKeyState(VK_RSHIFT, true, false); |
| tester.InjectMessages( |
| 1, |
| WmKeyDownInfo{VK_SHIFT, kScanCodeShiftRight, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, |
| kPhysicalShiftRight, kLogicalShiftRight, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| |
| // Release ShiftRight |
| tester.SetKeyState(VK_RSHIFT, false, true); |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{VK_SHIFT, kScanCodeShiftRight, kNotExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, |
| kPhysicalShiftRight, kLogicalShiftRight, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| TEST(KeyboardTest, CtrlLeftUnhandled) { |
| KeyboardTester tester; |
| tester.Responding(false); |
| |
| // US Keyboard layout |
| |
| // Press CtrlLeft |
| tester.SetKeyState(VK_LCONTROL, true, false); |
| tester.InjectMessages( |
| 1, |
| WmKeyDownInfo{VK_CONTROL, kScanCodeControl, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, |
| kPhysicalControlLeft, kLogicalControlLeft, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| |
| // Release CtrlLeft |
| tester.SetKeyState(VK_LCONTROL, false, true); |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{VK_SHIFT, kScanCodeControl, kNotExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, |
| kPhysicalControlLeft, kLogicalControlLeft, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| TEST(KeyboardTest, CtrlRightUnhandled) { |
| KeyboardTester tester; |
| tester.Responding(false); |
| |
| // US Keyboard layout |
| |
| // Press CtrlRight |
| tester.SetKeyState(VK_RCONTROL, true, false); |
| tester.InjectMessages( |
| 1, WmKeyDownInfo{VK_CONTROL, kScanCodeControl, kExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, |
| kPhysicalControlRight, kLogicalControlRight, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| |
| // Release CtrlRight |
| tester.SetKeyState(VK_RCONTROL, false, true); |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{VK_CONTROL, kScanCodeControl, kExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, |
| kPhysicalControlRight, kLogicalControlRight, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| TEST(KeyboardTest, AltLeftUnhandled) { |
| KeyboardTester tester; |
| tester.Responding(false); |
| |
| // US Keyboard layout |
| |
| // Press AltLeft. AltLeft is a SysKeyDown event. |
| tester.SetKeyState(VK_LMENU, true, false); |
| tester.InjectMessages( |
| 1, WmSysKeyDownInfo{VK_MENU, kScanCodeAlt, kNotExtended, kWasUp}.Build( |
| kWmResultDefault)); // Always pass to the default WndProc. |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalAltLeft, |
| kLogicalAltLeft, "", kNotSynthesized); |
| clear_key_calls(); |
| |
| // Release AltLeft. AltLeft is a SysKeyUp event. |
| tester.SetKeyState(VK_LMENU, false, true); |
| tester.InjectMessages( |
| 1, WmSysKeyUpInfo{VK_MENU, kScanCodeAlt, kNotExtended}.Build( |
| kWmResultDefault)); // Always pass to the default WndProc. |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalAltLeft, |
| kLogicalAltLeft, "", kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| TEST(KeyboardTest, AltRightUnhandled) { |
| KeyboardTester tester; |
| tester.Responding(false); |
| |
| // US Keyboard layout |
| |
| // Press AltRight. AltRight is a SysKeyDown event. |
| tester.SetKeyState(VK_RMENU, true, false); |
| tester.InjectMessages( |
| 1, WmSysKeyDownInfo{VK_MENU, kScanCodeAlt, kExtended, kWasUp}.Build( |
| kWmResultDefault)); // Always pass to the default WndProc. |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, |
| kPhysicalAltRight, kLogicalAltRight, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| |
| // Release AltRight. AltRight is a SysKeyUp event. |
| tester.SetKeyState(VK_RMENU, false, true); |
| tester.InjectMessages( |
| 1, WmSysKeyUpInfo{VK_MENU, kScanCodeAlt, kExtended}.Build( |
| kWmResultDefault)); // Always pass to the default WndProc. |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalAltRight, |
| kLogicalAltRight, "", kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| TEST(KeyboardTest, MetaLeftUnhandled) { |
| KeyboardTester tester; |
| tester.Responding(false); |
| |
| // US Keyboard layout |
| |
| // Press MetaLeft |
| tester.SetKeyState(VK_LWIN, true, false); |
| tester.InjectMessages( |
| 1, WmKeyDownInfo{VK_LWIN, kScanCodeMetaLeft, kExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, |
| kPhysicalMetaLeft, kLogicalMetaLeft, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| |
| // Release MetaLeft |
| tester.SetKeyState(VK_LWIN, false, true); |
| tester.InjectMessages( |
| 1, |
| WmKeyUpInfo{VK_LWIN, kScanCodeMetaLeft, kExtended}.Build(kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalMetaLeft, |
| kLogicalMetaLeft, "", kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| TEST(KeyboardTest, MetaRightUnhandled) { |
| KeyboardTester tester; |
| tester.Responding(false); |
| |
| // US Keyboard layout |
| |
| // Press MetaRight |
| tester.SetKeyState(VK_RWIN, true, false); |
| tester.InjectMessages( |
| 1, WmKeyDownInfo{VK_RWIN, kScanCodeMetaRight, kExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, |
| kPhysicalMetaRight, kLogicalMetaRight, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| |
| // Release MetaRight |
| tester.SetKeyState(VK_RWIN, false, true); |
| tester.InjectMessages( |
| 1, |
| WmKeyUpInfo{VK_RWIN, kScanCodeMetaRight, kExtended}.Build(kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalMetaRight, |
| kLogicalMetaRight, "", kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| // Press Shift-A. This is special because Win32 gives 'A' as character for the |
| // KeyA press. |
| TEST(KeyboardTest, ShiftLeftKeyA) { |
| KeyboardTester tester; |
| tester.Responding(false); |
| |
| // US Keyboard layout |
| |
| // Press ShiftLeft |
| tester.SetKeyState(VK_LSHIFT, true, true); |
| tester.InjectMessages( |
| 1, |
| WmKeyDownInfo{VK_SHIFT, kScanCodeShiftLeft, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, |
| kPhysicalShiftLeft, kLogicalShiftLeft, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| |
| // Press A |
| tester.InjectMessages( |
| 2, |
| WmKeyDownInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmCharInfo{'A', kScanCodeKeyA, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 2); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyA, |
| kLogicalKeyA, "A", kNotSynthesized); |
| EXPECT_CALL_IS_TEXT(key_calls[1], u"A"); |
| clear_key_calls(); |
| |
| // Release ShiftLeft |
| tester.SetKeyState(VK_LSHIFT, false, true); |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{VK_SHIFT, kScanCodeShiftLeft, kNotExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalShiftLeft, |
| kLogicalShiftLeft, "", kNotSynthesized); |
| clear_key_calls(); |
| |
| // Release A |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyA, |
| kLogicalKeyA, "", kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| // Press Ctrl-A. This is special because Win32 gives 0x01 as character for the |
| // KeyA press. |
| TEST(KeyboardTest, CtrlLeftKeyA) { |
| KeyboardTester tester; |
| tester.Responding(false); |
| |
| // US Keyboard layout |
| |
| // Press ControlLeft |
| tester.SetKeyState(VK_LCONTROL, true, true); |
| tester.InjectMessages( |
| 1, |
| WmKeyDownInfo{VK_CONTROL, kScanCodeControl, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, |
| kPhysicalControlLeft, kLogicalControlLeft, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| |
| // Press A |
| tester.InjectMessages( |
| 2, |
| WmKeyDownInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmCharInfo{0x01, kScanCodeKeyA, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyA, |
| kLogicalKeyA, "", kNotSynthesized); |
| clear_key_calls(); |
| |
| // Release A |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyA, |
| kLogicalKeyA, "", kNotSynthesized); |
| clear_key_calls(); |
| |
| // Release ControlLeft |
| tester.SetKeyState(VK_LCONTROL, false, true); |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{VK_CONTROL, kScanCodeControl, kNotExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, |
| kPhysicalControlLeft, kLogicalControlLeft, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| // Press Ctrl-1. This is special because it yields no WM_CHAR for the 1. |
| TEST(KeyboardTest, CtrlLeftDigit1) { |
| KeyboardTester tester; |
| tester.Responding(false); |
| |
| // US Keyboard layout |
| |
| // Press ControlLeft |
| tester.SetKeyState(VK_LCONTROL, true, true); |
| tester.InjectMessages( |
| 1, |
| WmKeyDownInfo{VK_CONTROL, kScanCodeControl, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, |
| kPhysicalControlLeft, kLogicalControlLeft, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| |
| // Press 1 |
| tester.InjectMessages( |
| 1, WmKeyDownInfo{kVirtualDigit1, kScanCodeDigit1, kNotExtended, kWasUp} |
| .Build(kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalDigit1, |
| kLogicalDigit1, "", kNotSynthesized); |
| clear_key_calls(); |
| |
| // Release 1 |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{kVirtualDigit1, kScanCodeDigit1, kNotExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalDigit1, |
| kLogicalDigit1, "", kNotSynthesized); |
| clear_key_calls(); |
| |
| // Release ControlLeft |
| tester.SetKeyState(VK_LCONTROL, false, true); |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{VK_CONTROL, kScanCodeControl, kNotExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, |
| kPhysicalControlLeft, kLogicalControlLeft, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| // Press 1 on a French keyboard. This is special because it yields WM_CHAR |
| // with char_code '&'. |
| TEST(KeyboardTest, Digit1OnFrenchLayout) { |
| KeyboardTester tester; |
| tester.Responding(false); |
| |
| tester.SetLayout(LayoutFrench); |
| |
| // Press 1 |
| tester.InjectMessages( |
| 2, |
| WmKeyDownInfo{kVirtualDigit1, kScanCodeDigit1, kNotExtended, kWasUp} |
| .Build(kWmResultZero), |
| WmCharInfo{'&', kScanCodeDigit1, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 2); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalDigit1, |
| kLogicalDigit1, "&", kNotSynthesized); |
| EXPECT_CALL_IS_TEXT(key_calls[1], u"&"); |
| clear_key_calls(); |
| |
| // Release 1 |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{kVirtualDigit1, kScanCodeDigit1, kNotExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalDigit1, |
| kLogicalDigit1, "", kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| // This tests AltGr-Q on a German keyboard, which should print '@'. |
| TEST(KeyboardTest, AltGrModifiedKey) { |
| KeyboardTester tester; |
| tester.Responding(false); |
| |
| // German Keyboard layout |
| |
| // Press AltGr, which Win32 precedes with a ContrlLeft down. |
| tester.SetKeyState(VK_LCONTROL, true, true); |
| tester.InjectMessages( |
| 2, |
| WmKeyDownInfo{VK_LCONTROL, kScanCodeControl, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmKeyDownInfo{VK_MENU, kScanCodeAlt, kExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 2); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, |
| kPhysicalControlLeft, kLogicalControlLeft, "", |
| kNotSynthesized); |
| EXPECT_CALL_IS_EVENT(key_calls[1], kFlutterKeyEventTypeDown, |
| kPhysicalAltRight, kLogicalAltRight, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| |
| // Press Q |
| tester.InjectMessages( |
| 2, |
| WmKeyDownInfo{kVirtualKeyQ, kScanCodeKeyQ, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmCharInfo{'@', kScanCodeKeyQ, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 2); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyQ, |
| kLogicalKeyQ, "@", kNotSynthesized); |
| EXPECT_CALL_IS_TEXT(key_calls[1], u"@"); |
| clear_key_calls(); |
| |
| // Release Q |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{kVirtualKeyQ, kScanCodeKeyQ, kNotExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyQ, |
| kLogicalKeyQ, "", kNotSynthesized); |
| clear_key_calls(); |
| |
| // Release AltGr. Win32 doesn't dispatch ControlLeft up. Instead Flutter will |
| // dispatch one. The AltGr is a system key, therefore will be handled by |
| // Win32's default WndProc. |
| tester.SetKeyState(VK_LCONTROL, false, true); |
| tester.InjectMessages( |
| 1, |
| WmSysKeyUpInfo{VK_MENU, kScanCodeAlt, kExtended}.Build(kWmResultDefault)); |
| |
| EXPECT_EQ(key_calls.size(), 2); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, |
| kPhysicalControlLeft, kLogicalControlLeft, "", |
| kNotSynthesized); |
| EXPECT_CALL_IS_EVENT(key_calls[1], kFlutterKeyEventTypeUp, kPhysicalAltRight, |
| kLogicalAltRight, "", kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| // Test the following two key sequences at the same time: |
| // |
| // 1. Tap AltGr, then tap AltGr. |
| // 2. Tap AltGr, hold CtrlLeft, tap AltGr, release CtrlLeft. |
| // |
| // The two sequences are indistinguishable until the very end when a CtrlLeft |
| // up event might or might not follow. |
| // |
| // Sequence 1: CtrlLeft down, AltRight down, AltRight up |
| // Sequence 2: CtrlLeft down, AltRight down, AltRight up, CtrlLeft up |
| // |
| // This is because pressing AltGr alone causes Win32 to send a fake "CtrlLeft |
| // down" event first (see |IsKeyDownAltRight| for detailed explanation). |
| TEST(KeyboardTest, AltGrTwice) { |
| KeyboardTester tester; |
| tester.Responding(false); |
| |
| // 1. AltGr down. |
| |
| // The key down event causes a ControlLeft down and a AltRight (extended |
| // AltLeft) down. |
| tester.SetKeyState(VK_LCONTROL, true, true); |
| tester.InjectMessages( |
| 2, |
| WmKeyDownInfo{VK_LCONTROL, kScanCodeControl, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmKeyDownInfo{VK_MENU, kScanCodeAlt, kExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 2); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, |
| kPhysicalControlLeft, kLogicalControlLeft, "", |
| kNotSynthesized); |
| EXPECT_CALL_IS_EVENT(key_calls[1], kFlutterKeyEventTypeDown, |
| kPhysicalAltRight, kLogicalAltRight, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| |
| // 2. AltGr up. |
| |
| // The key up event only causes a AltRight (extended AltLeft) up. |
| tester.SetKeyState(VK_RMENU, false, true); |
| tester.SetKeyState(VK_LCONTROL, false, true); |
| tester.InjectMessages( |
| 1, |
| WmSysKeyUpInfo{VK_MENU, kScanCodeAlt, kExtended}.Build(kWmResultDefault)); |
| EXPECT_EQ(key_calls.size(), 2); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, |
| kPhysicalControlLeft, kLogicalControlLeft, "", |
| kNotSynthesized); |
| EXPECT_CALL_IS_EVENT(key_calls[1], kFlutterKeyEventTypeUp, kPhysicalAltRight, |
| kLogicalAltRight, "", kNotSynthesized); |
| clear_key_calls(); |
| |
| // 3. AltGr down (or: ControlLeft down then AltRight down.) |
| |
| tester.SetKeyState(VK_LCONTROL, true, false); |
| tester.InjectMessages( |
| 2, |
| WmKeyDownInfo{VK_LCONTROL, kScanCodeControl, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmKeyDownInfo{VK_MENU, kScanCodeAlt, kExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 2); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, |
| kPhysicalControlLeft, kLogicalControlLeft, "", |
| kNotSynthesized); |
| EXPECT_CALL_IS_EVENT(key_calls[1], kFlutterKeyEventTypeDown, |
| kPhysicalAltRight, kLogicalAltRight, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| |
| // 4. AltGr up. |
| |
| // The key up event only causes a AltRight (extended AltLeft) up. |
| tester.SetKeyState(VK_RMENU, false, false); |
| tester.SetKeyState(VK_LCONTROL, false, false); |
| tester.InjectMessages( |
| 1, |
| WmSysKeyUpInfo{VK_MENU, kScanCodeAlt, kExtended}.Build(kWmResultDefault)); |
| EXPECT_EQ(key_calls.size(), 2); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, |
| kPhysicalControlLeft, kLogicalControlLeft, "", |
| kNotSynthesized); |
| EXPECT_CALL_IS_EVENT(key_calls[1], kFlutterKeyEventTypeUp, kPhysicalAltRight, |
| kLogicalAltRight, "", kNotSynthesized); |
| clear_key_calls(); |
| |
| // 5. For key sequence 2: a real ControlLeft up. |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{VK_LCONTROL, kScanCodeControl, kNotExtended}.Build( |
| kWmResultZero)); |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, 0, 0, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| // This tests dead key ^ then E on a French keyboard, which should be combined |
| // into ê. |
| TEST(KeyboardTest, DeadKeyThatCombines) { |
| KeyboardTester tester; |
| tester.Responding(false); |
| |
| tester.SetLayout(LayoutFrench); |
| |
| // Press ^¨ (US: Left bracket) |
| tester.InjectMessages( |
| 2, |
| WmKeyDownInfo{0xDD, kScanCodeBracketLeft, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmDeadCharInfo{'^', kScanCodeBracketLeft, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, |
| kPhysicalBracketLeft, kLogicalBracketRight, "^", |
| kNotSynthesized); |
| clear_key_calls(); |
| |
| // Release ^¨ |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{0xDD, kScanCodeBracketLeft, kNotExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, |
| kPhysicalBracketLeft, kLogicalBracketRight, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| |
| // Press E |
| tester.InjectMessages( |
| 2, |
| WmKeyDownInfo{kVirtualKeyE, kScanCodeKeyE, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmCharInfo{0xEA, kScanCodeKeyE, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 2); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyE, |
| kLogicalKeyE, "ê", kNotSynthesized); |
| EXPECT_CALL_IS_TEXT(key_calls[1], u"ê"); |
| clear_key_calls(); |
| |
| // Release E |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{kVirtualKeyE, kScanCodeKeyE, kNotExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyE, |
| kLogicalKeyE, "", kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| // This tests dead key ^ then E on a US INTL keyboard, which should be combined |
| // into ê. |
| // |
| // It is different from French AZERTY because the character that the ^ key is |
| // mapped to does not contain the dead key character somehow. |
| TEST(KeyboardTest, DeadKeyWithoutDeadMaskThatCombines) { |
| KeyboardTester tester; |
| tester.Responding(false); |
| |
| // Press ShiftLeft |
| tester.SetKeyState(VK_LSHIFT, true, true); |
| tester.InjectMessages( |
| 1, |
| WmKeyDownInfo{VK_SHIFT, kScanCodeShiftLeft, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, |
| kPhysicalShiftLeft, kLogicalShiftLeft, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| |
| // Press 6^ |
| tester.InjectMessages( |
| 2, |
| WmKeyDownInfo{'6', kScanCodeDigit6, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmDeadCharInfo{'^', kScanCodeDigit6, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalDigit6, |
| kLogicalDigit6, "6", kNotSynthesized); |
| clear_key_calls(); |
| |
| // Release 6^ |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{'6', kScanCodeDigit6, kNotExtended}.Build(kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalDigit6, |
| kLogicalDigit6, "", kNotSynthesized); |
| clear_key_calls(); |
| |
| // Release ShiftLeft |
| tester.SetKeyState(VK_LSHIFT, false, true); |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{VK_SHIFT, kScanCodeShiftLeft, kNotExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalShiftLeft, |
| kLogicalShiftLeft, "", kNotSynthesized); |
| clear_key_calls(); |
| |
| // Press E |
| tester.InjectMessages( |
| 2, |
| WmKeyDownInfo{kVirtualKeyE, kScanCodeKeyE, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmCharInfo{0xEA, kScanCodeKeyE, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 2); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyE, |
| kLogicalKeyE, "ê", kNotSynthesized); |
| EXPECT_CALL_IS_TEXT(key_calls[1], u"ê"); |
| clear_key_calls(); |
| |
| // Release E |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{kVirtualKeyE, kScanCodeKeyE, kNotExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyE, |
| kLogicalKeyE, "", kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| // This tests dead key ^ then & (US: 1) on a French keyboard, which do not |
| // combine and should output "^&". |
| TEST(KeyboardTest, DeadKeyThatDoesNotCombine) { |
| KeyboardTester tester; |
| tester.Responding(false); |
| |
| tester.SetLayout(LayoutFrench); |
| |
| // Press ^¨ (US: Left bracket) |
| tester.InjectMessages( |
| 2, |
| WmKeyDownInfo{0xDD, kScanCodeBracketLeft, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmDeadCharInfo{'^', kScanCodeBracketLeft, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, |
| kPhysicalBracketLeft, kLogicalBracketRight, "^", |
| kNotSynthesized); |
| clear_key_calls(); |
| |
| // Release ^¨ |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{0xDD, kScanCodeBracketLeft, kNotExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, |
| kPhysicalBracketLeft, kLogicalBracketRight, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| |
| // Press 1 |
| tester.InjectMessages( |
| 3, |
| WmKeyDownInfo{kVirtualDigit1, kScanCodeDigit1, kNotExtended, kWasUp} |
| .Build(kWmResultZero), |
| WmCharInfo{'^', kScanCodeDigit1, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmCharInfo{'&', kScanCodeDigit1, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 3); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalDigit1, |
| kLogicalDigit1, "^", kNotSynthesized); |
| EXPECT_CALL_IS_TEXT(key_calls[1], u"^"); |
| EXPECT_CALL_IS_TEXT(key_calls[2], u"&"); |
| clear_key_calls(); |
| |
| // Release 1 |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{kVirtualDigit1, kScanCodeDigit1, kNotExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalDigit1, |
| kLogicalDigit1, "", kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| // This tests dead key `, then dead key `, then e. |
| // |
| // It should output ``e, instead of `è. |
| TEST(KeyboardTest, DeadKeyTwiceThenLetter) { |
| KeyboardTester tester; |
| tester.Responding(false); |
| |
| // US INTL layout. |
| |
| // Press ` |
| tester.InjectMessages( |
| 2, |
| WmKeyDownInfo{0xC0, kScanCodeBackquote, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmDeadCharInfo{'`', kScanCodeBackquote, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, |
| kPhysicalBackquote, kLogicalBackquote, "`", |
| kNotSynthesized); |
| clear_key_calls(); |
| |
| // Release ` |
| tester.InjectMessages( |
| 1, |
| WmKeyUpInfo{0xC0, kScanCodeBackquote, kNotExtended}.Build(kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalBackquote, |
| kLogicalBackquote, "", kNotSynthesized); |
| clear_key_calls(); |
| |
| // Press ` again. |
| // The response should be slow. |
| std::vector<MockKeyResponseController::ResponseCallback> recorded_callbacks; |
| tester.LateResponding( |
| [&recorded_callbacks]( |
| const FlutterKeyEvent* event, |
| MockKeyResponseController::ResponseCallback callback) { |
| recorded_callbacks.push_back(callback); |
| }); |
| |
| tester.InjectMessages( |
| 3, |
| WmKeyDownInfo{0xC0, kScanCodeBackquote, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmCharInfo{'`', kScanCodeBackquote, kNotExtended, kWasUp, kBeingReleased, |
| kNoContext, 1, /*bit25*/ true} |
| .Build(kWmResultZero), |
| WmCharInfo{'`', kScanCodeBackquote, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(recorded_callbacks.size(), 1); |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, |
| kPhysicalBackquote, kLogicalBackquote, "`", |
| kNotSynthesized); |
| clear_key_calls(); |
| // Key down event responded with false. |
| recorded_callbacks.front()(false); |
| EXPECT_EQ(key_calls.size(), 2); |
| EXPECT_CALL_IS_TEXT(key_calls[0], u"`"); |
| EXPECT_CALL_IS_TEXT(key_calls[1], u"`"); |
| clear_key_calls(); |
| |
| tester.Responding(false); |
| |
| // Release ` |
| tester.InjectMessages( |
| 1, |
| WmKeyUpInfo{0xC0, kScanCodeBackquote, kNotExtended}.Build(kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalBackquote, |
| kLogicalBackquote, "", kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| // This tests when the resulting character needs to be combined with surrogates. |
| TEST(KeyboardTest, MultibyteCharacter) { |
| KeyboardTester tester; |
| tester.Responding(false); |
| |
| // Gothic Keyboard layout. (We need a layout that yields non-BMP characters |
| // without IME, which is actually very rare.) |
| |
| // Press key W of a US keyboard, which should yield character '𐍅'. |
| tester.InjectMessages( |
| 3, |
| WmKeyDownInfo{kVirtualKeyW, kScanCodeKeyW, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmCharInfo{0xd800, kScanCodeKeyW, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmCharInfo{0xdf45, kScanCodeKeyW, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| const char* st = key_calls[0].key_event.character; |
| |
| EXPECT_EQ(key_calls.size(), 2); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyW, |
| kLogicalKeyW, "𐍅", kNotSynthesized); |
| EXPECT_CALL_IS_TEXT(key_calls[1], u"𐍅"); |
| clear_key_calls(); |
| |
| // Release W |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{kVirtualKeyW, kScanCodeKeyW, kNotExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyW, |
| kLogicalKeyW, "", kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| // A key down event for shift right must not be redispatched even if |
| // the framework returns unhandled. |
| // |
| // The reason for this test is documented in |IsKeyDownShiftRight|. |
| TEST(KeyboardTest, NeverRedispatchShiftRightKeyDown) { |
| KeyboardTester tester; |
| tester.Responding(false); |
| |
| // Press ShiftRight and the delegate responds false. |
| tester.SetKeyState(VK_RSHIFT, true, true); |
| tester.InjectMessages( |
| 1, |
| WmKeyDownInfo{VK_SHIFT, kScanCodeShiftRight, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| clear_key_calls(); |
| } |
| |
| // Pressing modifiers during IME events should work properly by not sending any |
| // events. |
| // |
| // Regression test for https://github.com/flutter/flutter/issues/95888 . |
| TEST(KeyboardTest, ImeModifierEventsAreIgnored) { |
| KeyboardTester tester; |
| tester.Responding(false); |
| |
| // US Keyboard layout. |
| |
| // To make the keyboard into IME mode, there should have been events like |
| // letter key down with VK_PROCESSKEY. Omit them in this test since they don't |
| // seem significant. |
| |
| // Press CtrlRight in IME mode. |
| tester.SetKeyState(VK_RCONTROL, true, false); |
| tester.InjectMessages( |
| 1, |
| WmKeyDownInfo{VK_PROCESSKEY, kScanCodeControl, kExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, 0, 0, "", |
| kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| TEST(KeyboardTest, DisorderlyRespondedEvents) { |
| KeyboardTester tester; |
| |
| // Store callbacks to manually call them. |
| std::vector<MockKeyResponseController::ResponseCallback> recorded_callbacks; |
| tester.LateResponding( |
| [&recorded_callbacks]( |
| const FlutterKeyEvent* event, |
| MockKeyResponseController::ResponseCallback callback) { |
| recorded_callbacks.push_back(callback); |
| }); |
| |
| // Press A |
| tester.InjectMessages( |
| 2, |
| WmKeyDownInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmCharInfo{'a', kScanCodeKeyA, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| // Press B |
| tester.InjectMessages( |
| 2, |
| WmKeyDownInfo{kVirtualKeyB, kScanCodeKeyB, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmCharInfo{'b', kScanCodeKeyB, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 2); |
| EXPECT_EQ(recorded_callbacks.size(), 2); |
| clear_key_calls(); |
| |
| // Resolve the second event first to test disordered responses. |
| recorded_callbacks.back()(false); |
| |
| EXPECT_EQ(key_calls.size(), 0); |
| clear_key_calls(); |
| |
| // Resolve the first event. |
| recorded_callbacks.front()(false); |
| |
| EXPECT_EQ(key_calls.size(), 2); |
| EXPECT_CALL_IS_TEXT(key_calls[0], u"a"); |
| EXPECT_CALL_IS_TEXT(key_calls[1], u"b"); |
| clear_key_calls(); |
| } |
| |
| // Regression test for a crash in an earlier implementation. |
| // |
| // In real life, the framework responds slowly. The next real event might |
| // arrive earlier than the framework response, and if the 2nd event has an |
| // identical hash as the one waiting for response, an earlier implementation |
| // will crash upon the response. |
| TEST(KeyboardTest, SlowFrameworkResponse) { |
| KeyboardTester tester; |
| |
| std::vector<MockKeyResponseController::ResponseCallback> recorded_callbacks; |
| |
| // Store callbacks to manually call them. |
| tester.LateResponding( |
| [&recorded_callbacks]( |
| const FlutterKeyEvent* event, |
| MockKeyResponseController::ResponseCallback callback) { |
| recorded_callbacks.push_back(callback); |
| }); |
| |
| // Press A |
| tester.InjectMessages( |
| 2, |
| WmKeyDownInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmCharInfo{'a', kScanCodeKeyA, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| // Hold A |
| tester.InjectMessages( |
| 2, |
| WmKeyDownInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended, kWasDown}.Build( |
| kWmResultZero), |
| WmCharInfo{'a', kScanCodeKeyA, kNotExtended, kWasDown}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 2); |
| EXPECT_EQ(recorded_callbacks.size(), 2); |
| clear_key_calls(); |
| |
| // The first response. |
| recorded_callbacks.front()(false); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_TEXT(key_calls[0], u"a"); |
| clear_key_calls(); |
| |
| // The second response. |
| recorded_callbacks.back()(false); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_TEXT(key_calls[0], u"a"); |
| clear_key_calls(); |
| } |
| |
| // Regression test for https://github.com/flutter/flutter/issues/84210. |
| // |
| // When the framework response is slow during a sequence of identical messages, |
| // make sure the real messages are not mistaken as redispatched messages, |
| // in order to not mess up the order of events. |
| // |
| // In this test we use: |
| // |
| // KeyA down, KeyA up, (down event responded with false), KeyA down, KeyA up, |
| // |
| // The code must not take the 2nd real key down events as a redispatched event. |
| TEST(KeyboardTest, SlowFrameworkResponseForIdenticalEvents) { |
| KeyboardTester tester; |
| |
| std::vector<MockKeyResponseController::ResponseCallback> recorded_callbacks; |
| |
| // Store callbacks to manually call them. |
| tester.LateResponding( |
| [&recorded_callbacks]( |
| const FlutterKeyEvent* event, |
| MockKeyResponseController::ResponseCallback callback) { |
| recorded_callbacks.push_back(callback); |
| }); |
| |
| // Press A |
| tester.InjectMessages( |
| 2, |
| WmKeyDownInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmCharInfo{'a', kScanCodeKeyA, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyA, |
| kLogicalKeyA, "a", kNotSynthesized); |
| clear_key_calls(); |
| |
| // Release A |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyA, |
| kLogicalKeyA, "", kNotSynthesized); |
| clear_key_calls(); |
| |
| // The first down event responded with false. |
| EXPECT_EQ(recorded_callbacks.size(), 2); |
| recorded_callbacks.front()(false); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_TEXT(key_calls[0], u"a"); |
| clear_key_calls(); |
| |
| // Press A again |
| tester.InjectMessages( |
| 2, |
| WmKeyDownInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmCharInfo{'a', kScanCodeKeyA, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyA, |
| kLogicalKeyA, "a", kNotSynthesized); |
| clear_key_calls(); |
| |
| // Release A again |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyA, |
| kLogicalKeyA, "", kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| TEST(KeyboardTest, TextInputSubmit) { |
| KeyboardTester tester; |
| tester.Responding(false); |
| |
| // US Keyboard layout |
| |
| tester.GetView().HandleMessage( |
| "flutter/textinput", "TextInput.setClient", |
| R"|([108, {"inputAction": "TextInputAction.none"}])|"); |
| |
| // Press Enter |
| tester.InjectMessages( |
| 2, |
| WmKeyDownInfo{VK_RETURN, kScanCodeEnter, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmCharInfo{'\n', kScanCodeEnter, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 2); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalEnter, |
| kLogicalEnter, "", kNotSynthesized); |
| EXPECT_CALL_IS_TEXT_METHOD_CALL( |
| key_calls[1], |
| "{" |
| R"|("method":"TextInputClient.performAction",)|" |
| R"|("args":[108,"TextInputAction.none"])|" |
| "}"); |
| clear_key_calls(); |
| |
| // Release Enter |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{VK_RETURN, kScanCodeEnter, kNotExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalEnter, |
| kLogicalEnter, "", kNotSynthesized); |
| clear_key_calls(); |
| |
| // Make sure OnText is not obstructed after pressing Enter. |
| // |
| // Regression test for https://github.com/flutter/flutter/issues/97706. |
| |
| // Press A |
| tester.InjectMessages( |
| 2, |
| WmKeyDownInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended, kWasUp}.Build( |
| kWmResultZero), |
| WmCharInfo{'a', kScanCodeKeyA, kNotExtended, kWasUp}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 2); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyA, |
| kLogicalKeyA, "a", kNotSynthesized); |
| EXPECT_CALL_IS_TEXT(key_calls[1], u"a"); |
| clear_key_calls(); |
| |
| // Release A |
| tester.InjectMessages( |
| 1, WmKeyUpInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended}.Build( |
| kWmResultZero)); |
| |
| EXPECT_EQ(key_calls.size(), 1); |
| EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyA, |
| kLogicalKeyA, "", kNotSynthesized); |
| clear_key_calls(); |
| } |
| |
| } // namespace testing |
| } // namespace flutter |