| // 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 <array> | |
| #include "flutter/shell/platform/windows/testing/mock_direct_manipulation.h" | |
| #include "flutter/shell/platform/windows/testing/mock_text_input_manager.h" | |
| #include "flutter/shell/platform/windows/testing/mock_window.h" | |
| #include "flutter/shell/platform/windows/testing/mock_windows_proc_table.h" | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| using testing::_; | |
| using testing::Eq; | |
| using testing::InSequence; | |
| using testing::Invoke; | |
| using testing::Return; | |
| namespace flutter { | |
| namespace testing { | |
| TEST(MockWindow, CreateDestroy) { | |
| MockWindow window; | |
| ASSERT_TRUE(TRUE); | |
| } | |
| TEST(MockWindow, GetDpiAfterCreate) { | |
| MockWindow window; | |
| ASSERT_TRUE(window.GetDpi() > 0); | |
| } | |
| TEST(MockWindow, VerticalScroll) { | |
| MockWindow window; | |
| const int scroll_amount = 10; | |
| // Vertical scroll should be passed along, adjusted for scroll tick size | |
| // and direction. | |
| EXPECT_CALL(window, OnScroll(0, -scroll_amount / 120.0, | |
| kFlutterPointerDeviceKindMouse, 0)) | |
| .Times(1); | |
| window.InjectWindowMessage(WM_MOUSEWHEEL, MAKEWPARAM(0, scroll_amount), 0); | |
| } | |
| TEST(MockWindow, OnImeCompositionCompose) { | |
| auto windows_proc_table = std::make_unique<MockWindowsProcTable>(); | |
| auto* text_input_manager = new MockTextInputManager(); | |
| std::unique_ptr<TextInputManager> text_input_manager_ptr(text_input_manager); | |
| MockWindow window(std::move(windows_proc_table), | |
| std::move(text_input_manager_ptr)); | |
| EXPECT_CALL(*text_input_manager, GetComposingString()) | |
| .WillRepeatedly( | |
| Return(std::optional<std::u16string>(std::u16string(u"nihao")))); | |
| EXPECT_CALL(*text_input_manager, GetResultString()) | |
| .WillRepeatedly( | |
| Return(std::optional<std::u16string>(std::u16string(u"`}")))); | |
| EXPECT_CALL(*text_input_manager, GetComposingCursorPosition()) | |
| .WillRepeatedly(Return((int)0)); | |
| EXPECT_CALL(window, OnComposeChange(std::u16string(u"nihao"), 0)).Times(1); | |
| EXPECT_CALL(window, OnComposeChange(std::u16string(u"`}"), 0)).Times(0); | |
| EXPECT_CALL(window, OnComposeCommit()).Times(0); | |
| ON_CALL(window, OnImeComposition) | |
| .WillByDefault(Invoke(&window, &MockWindow::CallOnImeComposition)); | |
| EXPECT_CALL(window, OnImeComposition(_, _, _)).Times(1); | |
| // Send an IME_COMPOSITION event that contains just the composition string. | |
| window.InjectWindowMessage(WM_IME_COMPOSITION, 0, GCS_COMPSTR); | |
| } | |
| TEST(MockWindow, OnImeCompositionResult) { | |
| auto windows_proc_table = std::make_unique<MockWindowsProcTable>(); | |
| auto* text_input_manager = new MockTextInputManager(); | |
| std::unique_ptr<TextInputManager> text_input_manager_ptr(text_input_manager); | |
| MockWindow window(std::move(windows_proc_table), | |
| std::move(text_input_manager_ptr)); | |
| EXPECT_CALL(*text_input_manager, GetComposingString()) | |
| .WillRepeatedly( | |
| Return(std::optional<std::u16string>(std::u16string(u"nihao")))); | |
| EXPECT_CALL(*text_input_manager, GetResultString()) | |
| .WillRepeatedly( | |
| Return(std::optional<std::u16string>(std::u16string(u"`}")))); | |
| EXPECT_CALL(*text_input_manager, GetComposingCursorPosition()) | |
| .WillRepeatedly(Return((int)0)); | |
| EXPECT_CALL(window, OnComposeChange(std::u16string(u"nihao"), 0)).Times(0); | |
| EXPECT_CALL(window, OnComposeChange(std::u16string(u"`}"), 0)).Times(1); | |
| EXPECT_CALL(window, OnComposeCommit()).Times(1); | |
| ON_CALL(window, OnImeComposition) | |
| .WillByDefault(Invoke(&window, &MockWindow::CallOnImeComposition)); | |
| EXPECT_CALL(window, OnImeComposition(_, _, _)).Times(1); | |
| // Send an IME_COMPOSITION event that contains just the result string. | |
| window.InjectWindowMessage(WM_IME_COMPOSITION, 0, GCS_RESULTSTR); | |
| } | |
| TEST(MockWindow, OnImeCompositionResultAndCompose) { | |
| auto windows_proc_table = std::make_unique<MockWindowsProcTable>(); | |
| auto* text_input_manager = new MockTextInputManager(); | |
| std::unique_ptr<TextInputManager> text_input_manager_ptr(text_input_manager); | |
| MockWindow window(std::move(windows_proc_table), | |
| std::move(text_input_manager_ptr)); | |
| // This situation is that Google Japanese Input finished composing "今日" in | |
| // "今日は" but is still composing "は". | |
| { | |
| InSequence dummy; | |
| EXPECT_CALL(*text_input_manager, GetResultString()) | |
| .WillRepeatedly( | |
| Return(std::optional<std::u16string>(std::u16string(u"今日")))); | |
| EXPECT_CALL(*text_input_manager, GetComposingString()) | |
| .WillRepeatedly( | |
| Return(std::optional<std::u16string>(std::u16string(u"は")))); | |
| } | |
| { | |
| InSequence dummy; | |
| EXPECT_CALL(window, OnComposeChange(std::u16string(u"今日"), 0)).Times(1); | |
| EXPECT_CALL(window, OnComposeCommit()).Times(1); | |
| EXPECT_CALL(window, OnComposeChange(std::u16string(u"は"), 0)).Times(1); | |
| } | |
| EXPECT_CALL(*text_input_manager, GetComposingCursorPosition()) | |
| .WillRepeatedly(Return((int)0)); | |
| ON_CALL(window, OnImeComposition) | |
| .WillByDefault(Invoke(&window, &MockWindow::CallOnImeComposition)); | |
| EXPECT_CALL(window, OnImeComposition(_, _, _)).Times(1); | |
| // send an IME_COMPOSITION event that contains both the result string and the | |
| // composition string. | |
| window.InjectWindowMessage(WM_IME_COMPOSITION, 0, | |
| GCS_COMPSTR | GCS_RESULTSTR); | |
| } | |
| TEST(MockWindow, OnImeCompositionClearChange) { | |
| auto windows_proc_table = std::make_unique<MockWindowsProcTable>(); | |
| auto* text_input_manager = new MockTextInputManager(); | |
| std::unique_ptr<TextInputManager> text_input_manager_ptr(text_input_manager); | |
| MockWindow window(std::move(windows_proc_table), | |
| std::move(text_input_manager_ptr)); | |
| EXPECT_CALL(window, OnComposeChange(std::u16string(u""), 0)).Times(1); | |
| EXPECT_CALL(window, OnComposeCommit()).Times(1); | |
| ON_CALL(window, OnImeComposition) | |
| .WillByDefault(Invoke(&window, &MockWindow::CallOnImeComposition)); | |
| EXPECT_CALL(window, OnImeComposition(_, _, _)).Times(1); | |
| // send an IME_COMPOSITION event that contains both the result string and the | |
| // composition string. | |
| window.InjectWindowMessage(WM_IME_COMPOSITION, 0, 0); | |
| } | |
| TEST(MockWindow, HorizontalScroll) { | |
| MockWindow window; | |
| const int scroll_amount = 10; | |
| // Vertical scroll should be passed along, adjusted for scroll tick size. | |
| EXPECT_CALL(window, OnScroll(scroll_amount / 120.0, 0, | |
| kFlutterPointerDeviceKindMouse, 0)) | |
| .Times(1); | |
| window.InjectWindowMessage(WM_MOUSEHWHEEL, MAKEWPARAM(0, scroll_amount), 0); | |
| } | |
| TEST(MockWindow, MouseLeave) { | |
| MockWindow window; | |
| const double mouse_x = 10.0; | |
| const double mouse_y = 20.0; | |
| EXPECT_CALL(window, OnPointerMove(mouse_x, mouse_y, | |
| kFlutterPointerDeviceKindMouse, 0, 0)) | |
| .Times(1); | |
| EXPECT_CALL(window, OnPointerLeave(mouse_x, mouse_y, | |
| kFlutterPointerDeviceKindMouse, 0)) | |
| .Times(1); | |
| window.InjectWindowMessage(WM_MOUSEMOVE, 0, MAKELPARAM(mouse_x, mouse_y)); | |
| window.InjectWindowMessage(WM_MOUSELEAVE, 0, 0); | |
| } | |
| TEST(MockWindow, KeyDown) { | |
| MockWindow window; | |
| EXPECT_CALL(window, OnKey(_, _, _, _, _, _, _)).Times(1); | |
| LPARAM lparam = CreateKeyEventLparam(42, false, false); | |
| // send a "Shift" key down event. | |
| window.InjectWindowMessage(WM_KEYDOWN, 16, lparam); | |
| } | |
| TEST(MockWindow, KeyUp) { | |
| MockWindow window; | |
| EXPECT_CALL(window, OnKey(_, _, _, _, _, _, _)).Times(1); | |
| LPARAM lparam = CreateKeyEventLparam(42, false, true); | |
| // send a "Shift" key up event. | |
| window.InjectWindowMessage(WM_KEYUP, 16, lparam); | |
| } | |
| TEST(MockWindow, SysKeyDown) { | |
| MockWindow window; | |
| EXPECT_CALL(window, OnKey(_, _, _, _, _, _, _)).Times(1); | |
| LPARAM lparam = CreateKeyEventLparam(42, false, false); | |
| // send a "Shift" key down event. | |
| window.InjectWindowMessage(WM_SYSKEYDOWN, 16, lparam); | |
| } | |
| TEST(MockWindow, SysKeyUp) { | |
| MockWindow window; | |
| EXPECT_CALL(window, OnKey(_, _, _, _, _, _, _)).Times(1); | |
| LPARAM lparam = CreateKeyEventLparam(42, false, true); | |
| // send a "Shift" key up event. | |
| window.InjectWindowMessage(WM_SYSKEYUP, 16, lparam); | |
| } | |
| TEST(MockWindow, KeyDownPrintable) { | |
| MockWindow window; | |
| LPARAM lparam = CreateKeyEventLparam(30, false, false); | |
| auto respond_false = [](int key, int scancode, int action, char32_t character, | |
| bool extended, bool was_down, | |
| std::function<void(bool)> callback) { | |
| callback(false); | |
| }; | |
| EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 0, false, false, _)) | |
| .Times(1) | |
| .WillOnce(respond_false); | |
| EXPECT_CALL(window, OnText(_)).Times(1); | |
| std::array<Win32Message, 2> messages = { | |
| Win32Message{WM_KEYDOWN, 65, lparam, kWmResultDontCheck}, | |
| Win32Message{WM_CHAR, 65, lparam, kWmResultDontCheck}}; | |
| window.InjectMessageList(2, messages.data()); | |
| } | |
| TEST(MockWindow, KeyDownWithCtrl) { | |
| MockWindow window; | |
| // Simulate CONTROL pressed | |
| std::array<BYTE, 256> keyboard_state; | |
| keyboard_state[VK_CONTROL] = -1; | |
| SetKeyboardState(keyboard_state.data()); | |
| LPARAM lparam = CreateKeyEventLparam(30, false, false); | |
| // Expect OnKey, but not OnText, because Control + Key is not followed by | |
| // WM_CHAR | |
| EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 0, false, false, _)).Times(1); | |
| EXPECT_CALL(window, OnText(_)).Times(0); | |
| window.InjectWindowMessage(WM_KEYDOWN, 65, lparam); | |
| keyboard_state.fill(0); | |
| SetKeyboardState(keyboard_state.data()); | |
| } | |
| TEST(MockWindow, KeyDownWithCtrlToggled) { | |
| MockWindow window; | |
| auto respond_false = [](int key, int scancode, int action, char32_t character, | |
| bool extended, bool was_down, | |
| std::function<void(bool)> callback) { | |
| callback(false); | |
| }; | |
| // Simulate CONTROL toggled | |
| std::array<BYTE, 256> keyboard_state; | |
| keyboard_state[VK_CONTROL] = 1; | |
| SetKeyboardState(keyboard_state.data()); | |
| LPARAM lparam = CreateKeyEventLparam(30, false, false); | |
| EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 0, false, false, _)) | |
| .Times(1) | |
| .WillOnce(respond_false); | |
| EXPECT_CALL(window, OnText(_)).Times(1); | |
| // send a "A" key down event. | |
| Win32Message messages[] = {{WM_KEYDOWN, 65, lparam, kWmResultDontCheck}, | |
| {WM_CHAR, 65, lparam, kWmResultDontCheck}}; | |
| window.InjectMessageList(2, messages); | |
| keyboard_state.fill(0); | |
| SetKeyboardState(keyboard_state.data()); | |
| } | |
| TEST(MockWindow, Paint) { | |
| MockWindow window; | |
| EXPECT_CALL(window, OnPaint()).Times(1); | |
| window.InjectWindowMessage(WM_PAINT, 0, 0); | |
| } | |
| // Verify direct manipulation isn't notified of pointer hit tests. | |
| TEST(MockWindow, PointerHitTest) { | |
| UINT32 pointer_id = 123; | |
| auto windows_proc_table = std::make_unique<MockWindowsProcTable>(); | |
| auto text_input_manager = std::make_unique<MockTextInputManager>(); | |
| EXPECT_CALL(*windows_proc_table, GetPointerType(Eq(pointer_id), _)) | |
| .Times(1) | |
| .WillOnce([](UINT32 pointer_id, POINTER_INPUT_TYPE* type) { | |
| *type = PT_POINTER; | |
| return TRUE; | |
| }); | |
| MockWindow window(std::move(windows_proc_table), | |
| std::move(text_input_manager)); | |
| auto direct_manipulation = | |
| std::make_unique<MockDirectManipulationOwner>(&window); | |
| EXPECT_CALL(*direct_manipulation, SetContact).Times(0); | |
| window.SetDirectManipulationOwner(std::move(direct_manipulation)); | |
| window.InjectWindowMessage(DM_POINTERHITTEST, MAKEWPARAM(pointer_id, 0), 0); | |
| } | |
| // Verify direct manipulation is notified of touchpad hit tests. | |
| TEST(MockWindow, TouchPadHitTest) { | |
| UINT32 pointer_id = 123; | |
| auto windows_proc_table = std::make_unique<MockWindowsProcTable>(); | |
| auto text_input_manager = std::make_unique<MockTextInputManager>(); | |
| EXPECT_CALL(*windows_proc_table, GetPointerType(Eq(pointer_id), _)) | |
| .Times(1) | |
| .WillOnce([](UINT32 pointer_id, POINTER_INPUT_TYPE* type) { | |
| *type = PT_TOUCHPAD; | |
| return TRUE; | |
| }); | |
| MockWindow window(std::move(windows_proc_table), | |
| std::move(text_input_manager)); | |
| auto direct_manipulation = | |
| std::make_unique<MockDirectManipulationOwner>(&window); | |
| EXPECT_CALL(*direct_manipulation, SetContact(Eq(pointer_id))).Times(1); | |
| window.SetDirectManipulationOwner(std::move(direct_manipulation)); | |
| window.InjectWindowMessage(DM_POINTERHITTEST, MAKEWPARAM(pointer_id, 0), 0); | |
| } | |
| // Verify direct manipulation isn't notified of unknown hit tests. | |
| // This can happen if determining the pointer type fails, for example, | |
| // if GetPointerType is unsupported by the current Windows version. | |
| // See: https://github.com/flutter/flutter/issues/109412 | |
| TEST(MockWindow, UnknownPointerTypeSkipsDirectManipulation) { | |
| UINT32 pointer_id = 123; | |
| auto windows_proc_table = std::make_unique<MockWindowsProcTable>(); | |
| auto text_input_manager = std::make_unique<MockTextInputManager>(); | |
| EXPECT_CALL(*windows_proc_table, GetPointerType(Eq(pointer_id), _)) | |
| .Times(1) | |
| .WillOnce( | |
| [](UINT32 pointer_id, POINTER_INPUT_TYPE* type) { return FALSE; }); | |
| MockWindow window(std::move(windows_proc_table), | |
| std::move(text_input_manager)); | |
| auto direct_manipulation = | |
| std::make_unique<MockDirectManipulationOwner>(&window); | |
| EXPECT_CALL(*direct_manipulation, SetContact).Times(0); | |
| window.SetDirectManipulationOwner(std::move(direct_manipulation)); | |
| window.InjectWindowMessage(DM_POINTERHITTEST, MAKEWPARAM(pointer_id, 0), 0); | |
| } | |
| // Test that the root UIA object is queried by WM_GETOBJECT. | |
| TEST(MockWindow, DISABLED_GetObjectUia) { | |
| MockWindow window; | |
| bool uia_called = false; | |
| ON_CALL(window, OnGetObject) | |
| .WillByDefault(Invoke([&uia_called](UINT msg, WPARAM wpar, LPARAM lpar) { | |
| #ifdef FLUTTER_ENGINE_USE_UIA | |
| uia_called = true; | |
| #endif // FLUTTER_ENGINE_USE_UIA | |
| return static_cast<LRESULT>(0); | |
| })); | |
| EXPECT_CALL(window, OnGetObject).Times(1); | |
| window.InjectWindowMessage(WM_GETOBJECT, 0, UiaRootObjectId); | |
| EXPECT_TRUE(uia_called); | |
| } | |
| } // namespace testing | |
| } // namespace flutter |