| // Copyright 2010-2015, Google Inc. |
| // All rights reserved. |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include "testing/base/public/googletest.h" |
| #include "testing/base/public/gunit.h" |
| |
| #include "session/commands.pb.h" |
| #include "win32/base/deleter.h" |
| #include "win32/base/input_state.h" |
| #include "win32/base/keyboard.h" |
| |
| namespace mozc { |
| namespace win32 { |
| |
| namespace { |
| |
| const BYTE kPressed = 0x80; |
| const uint64 kOutputId = 0x12345678; |
| |
| class KeyboardMock : public Win32KeyboardInterface { |
| public: |
| KeyboardMock() {} |
| |
| virtual bool IsKanaLocked(const KeyboardStatus &keyboard_state) { |
| return keyboard_state.IsPressed(VK_KANA); |
| } |
| |
| virtual bool SetKeyboardState(const KeyboardStatus &keyboard_state) { |
| key_state_ = keyboard_state; |
| return true; |
| } |
| |
| virtual bool GetKeyboardState(KeyboardStatus *keyboard_state) { |
| *keyboard_state = key_state_; |
| return true; |
| } |
| |
| virtual bool AsyncIsKeyPressed(int virtual_key) { |
| return async_key_state_.IsPressed(virtual_key); |
| } |
| |
| virtual int ToUnicode(UINT virtual_key, UINT scan_code, |
| const BYTE *key_state, LPWSTR unicode_buffer, |
| int unicode_buffer_num_elements, UINT flags) { |
| // We use a mock class in case the Japanese keyboard layout is not |
| // available on this system. This emulator class should work well in most |
| // cases. It returns an unicode character (if any) as if Japanese keyboard |
| // layout was currently active. |
| return JapaneseKeyboardLayoutEmulator::ToUnicode( |
| virtual_key, scan_code, key_state, unicode_buffer, |
| unicode_buffer_num_elements, flags); |
| } |
| |
| virtual UINT SendInput(const vector<INPUT> &inputs) { |
| last_send_input_data_ = inputs; |
| return inputs.size(); |
| } |
| |
| const KeyboardStatus &key_state() const { |
| return key_state_; |
| } |
| |
| void set_key_state(const KeyboardStatus &key_state) { |
| key_state_ = key_state; |
| } |
| |
| const KeyboardStatus &async_key_state() const { |
| return async_key_state_; |
| } |
| |
| void set_async_key_state(const KeyboardStatus &async_key_state) { |
| async_key_state_ = async_key_state; |
| } |
| |
| const vector<INPUT> &last_send_input_data() const { |
| return last_send_input_data_; |
| } |
| |
| void clear_last_send_input_data() { |
| last_send_input_data_.clear(); |
| } |
| |
| private: |
| KeyboardStatus key_state_; |
| KeyboardStatus async_key_state_; |
| vector<INPUT> last_send_input_data_; |
| DISALLOW_COPY_AND_ASSIGN(KeyboardMock); |
| }; |
| |
| } // namespace |
| |
| TEST(VKBackBasedDeleterTest, OnKeyEventTestWhenNoDeletionIsOngoing) { |
| KeyboardMock *keyboard_mock = new KeyboardMock(); |
| VKBackBasedDeleter deleter(keyboard_mock); |
| commands::Output output; |
| output.set_id(kOutputId); |
| |
| InputState ime_state; |
| |
| EXPECT_FALSE(deleter.IsDeletionOngoing()); |
| EXPECT_EQ(0, keyboard_mock->last_send_input_data().size()); |
| |
| // OnKeyEvent never crashes even when there is no ongoing session. |
| EXPECT_EQ(VKBackBasedDeleter::DO_DEFAULT_ACTION, |
| deleter.OnKeyEvent(VK_BACK, true, true)); |
| } |
| |
| TEST(VKBackBasedDeleterTest, BeginDeletionTest_DeletionCountZero) { |
| KeyboardMock *keyboard_mock = new KeyboardMock(); |
| VKBackBasedDeleter deleter(keyboard_mock); |
| commands::Output output; |
| output.set_id(kOutputId); |
| |
| InputState ime_state; |
| |
| // If the deletion count is zero, no deletion operation is started. |
| deleter.BeginDeletion(0, output, ime_state); |
| EXPECT_FALSE(deleter.IsDeletionOngoing()); |
| EXPECT_EQ(0, keyboard_mock->last_send_input_data().size()); |
| } |
| |
| TEST(VKBackBasedDeleterTest, NormalSequence) { |
| const UINT kLastKey = 'A'; |
| |
| KeyboardMock *keyboard_mock = new KeyboardMock(); |
| |
| // VKBackBasedDeleter must clear any modifier before calling SendInput. |
| // To check this functionality, set VK_CONTROL bit into the mock. |
| // See b/3419452 for detailed information. |
| { |
| KeyboardStatus keyboard_state; |
| keyboard_state.SetState(VK_CONTROL, kPressed); |
| keyboard_mock->set_key_state(keyboard_state); |
| } |
| { |
| KeyboardStatus async_keyboard_state; |
| async_keyboard_state.SetState(VK_CONTROL, kPressed); |
| keyboard_mock->set_async_key_state(async_keyboard_state); |
| } |
| |
| VKBackBasedDeleter deleter(keyboard_mock); |
| commands::Output output; |
| output.set_id(kOutputId); |
| |
| InputState ime_state; |
| ime_state.last_down_key = VirtualKey::FromVirtualKey(kLastKey); |
| |
| // Delete proceeding 3 characters. |
| deleter.BeginDeletion(3, output, ime_state); |
| EXPECT_FALSE(keyboard_mock->key_state().IsPressed(VK_CONTROL)); |
| EXPECT_TRUE(keyboard_mock->async_key_state().IsPressed(VK_CONTROL)); |
| EXPECT_TRUE(deleter.IsDeletionOngoing()); |
| |
| // Expect three pairs of VK_BACK [down/up] for deleting proceeding characters |
| // and one pair of VK_BACK [down/up] as a sentinel key event where pending |
| // output and ime state wiil be applied. |
| const vector<INPUT> inputs = keyboard_mock->last_send_input_data(); |
| EXPECT_EQ(8, inputs.size()); |
| EXPECT_EQ(VK_BACK, inputs[0].ki.wVk); |
| EXPECT_EQ(VK_BACK, inputs[1].ki.wVk); |
| EXPECT_EQ(VK_BACK, inputs[2].ki.wVk); |
| EXPECT_EQ(VK_BACK, inputs[3].ki.wVk); |
| EXPECT_EQ(VK_BACK, inputs[4].ki.wVk); |
| EXPECT_EQ(VK_BACK, inputs[5].ki.wVk); |
| EXPECT_EQ(VK_BACK, inputs[6].ki.wVk); |
| EXPECT_EQ(VK_BACK, inputs[7].ki.wVk); |
| EXPECT_EQ(0, inputs[0].ki.dwFlags); |
| EXPECT_EQ(KEYEVENTF_KEYUP, inputs[1].ki.dwFlags); |
| EXPECT_EQ(0, inputs[2].ki.dwFlags); |
| EXPECT_EQ(KEYEVENTF_KEYUP, inputs[3].ki.dwFlags); |
| EXPECT_EQ(0, inputs[4].ki.dwFlags); |
| EXPECT_EQ(KEYEVENTF_KEYUP, inputs[5].ki.dwFlags); |
| EXPECT_EQ(0, inputs[6].ki.dwFlags); |
| EXPECT_EQ(KEYEVENTF_KEYUP, inputs[7].ki.dwFlags); |
| |
| // Initially, the deleter is waiting for the first VK_BACK test-key-down. |
| EXPECT_EQ(VKBackBasedDeleter::DO_DEFAULT_ACTION, |
| deleter.OnKeyEvent(VK_TAB, true, true)); |
| EXPECT_EQ(VKBackBasedDeleter::DO_DEFAULT_ACTION, |
| deleter.OnKeyEvent(VK_TAB, false, true)); |
| EXPECT_EQ(VKBackBasedDeleter::DO_DEFAULT_ACTION, |
| deleter.OnKeyEvent('X', true, true)); |
| EXPECT_EQ(VKBackBasedDeleter::DO_DEFAULT_ACTION, |
| deleter.OnKeyEvent('X', false, true)); |
| |
| // The first pair of test-key-down/up. |
| EXPECT_EQ(VKBackBasedDeleter::SEND_KEY_TO_APPLICATION, |
| deleter.OnKeyEvent(VK_BACK, true, true)); |
| EXPECT_EQ(VKBackBasedDeleter::SEND_KEY_TO_APPLICATION, |
| deleter.OnKeyEvent(VK_BACK, false, true)); |
| |
| // The second pair of test-key-down/up. |
| EXPECT_EQ(VKBackBasedDeleter::SEND_KEY_TO_APPLICATION, |
| deleter.OnKeyEvent(VK_BACK, true, true)); |
| EXPECT_EQ(VKBackBasedDeleter::SEND_KEY_TO_APPLICATION, |
| deleter.OnKeyEvent(VK_BACK, false, true)); |
| |
| // The third pair of test-key-down/up. |
| EXPECT_EQ(VKBackBasedDeleter::SEND_KEY_TO_APPLICATION, |
| deleter.OnKeyEvent(VK_BACK, true, true)); |
| EXPECT_EQ(VKBackBasedDeleter::SEND_KEY_TO_APPLICATION, |
| deleter.OnKeyEvent(VK_BACK, false, true)); |
| |
| // The last key-down will not be sent to the application. |
| EXPECT_EQ(VKBackBasedDeleter::CONSUME_KEY_BUT_NEVER_SEND_TO_SERVER, |
| deleter.OnKeyEvent(VK_BACK, true, true)); |
| EXPECT_EQ(VKBackBasedDeleter::APPLY_PENDING_STATUS, |
| deleter.OnKeyEvent(VK_BACK, true, false)); |
| |
| // Check the pending output and state. |
| EXPECT_EQ(kOutputId, deleter.pending_output().id()); |
| EXPECT_EQ(kLastKey, deleter.pending_ime_state().last_down_key.virtual_key()); |
| |
| // The last key-up will not be sent to the application. |
| EXPECT_EQ(VKBackBasedDeleter::CONSUME_KEY_BUT_NEVER_SEND_TO_SERVER, |
| deleter.OnKeyEvent(VK_BACK, false, true)); |
| EXPECT_EQ(VKBackBasedDeleter::CALL_END_DELETION_BUT_NEVER_SEND_TO_SERVER, |
| deleter.OnKeyEvent(VK_BACK, false, false)); |
| |
| // Make sure the status of modifier keys have not changed. |
| EXPECT_FALSE(keyboard_mock->key_state().IsPressed(VK_CONTROL)); |
| EXPECT_TRUE(keyboard_mock->async_key_state().IsPressed(VK_CONTROL)); |
| |
| // The caller must call EndDeletion when |
| // CALL_END_DELETION_BUT_NEVER_SEND_TO_SERVER is returned. |
| deleter.EndDeletion(); |
| |
| // After EndDeletion, the modifier state should be restored based on the |
| // async key state. |
| EXPECT_TRUE(keyboard_mock->key_state().IsPressed(VK_CONTROL)); |
| EXPECT_TRUE(keyboard_mock->async_key_state().IsPressed(VK_CONTROL)); |
| |
| // Internal status must be reset by VKBackBasedDeleter::Reset(). |
| EXPECT_FALSE(deleter.IsDeletionOngoing()); |
| } |
| |
| TEST(VKBackBasedDeleterTest, BeginDeletion_InsuccessfulCase) { |
| const UINT kLastKey = 'A'; |
| |
| KeyboardMock *keyboard_mock = new KeyboardMock(); |
| |
| // VKBackBasedDeleter must clear any modifier before calling SendInput. |
| // To check this functionality, set VK_CONTROL bit into the mock. |
| // See b/3419452 for detailed information. |
| { |
| KeyboardStatus keyboard_state; |
| keyboard_state.SetState(VK_CONTROL, kPressed); |
| keyboard_mock->set_key_state(keyboard_state); |
| } |
| { |
| KeyboardStatus async_keyboard_state; |
| async_keyboard_state.SetState(VK_CONTROL, kPressed); |
| keyboard_mock->set_async_key_state(async_keyboard_state); |
| } |
| |
| VKBackBasedDeleter deleter(keyboard_mock); |
| |
| commands::Output output; |
| output.set_id(kOutputId); |
| |
| InputState ime_state; |
| ime_state.last_down_key = VirtualKey::FromVirtualKey(kLastKey); |
| |
| // Delete proceeding 3 characters. |
| deleter.BeginDeletion(3, output, ime_state); |
| EXPECT_FALSE(keyboard_mock->key_state().IsPressed(VK_CONTROL)); |
| EXPECT_TRUE(keyboard_mock->async_key_state().IsPressed(VK_CONTROL)); |
| |
| EXPECT_TRUE(deleter.IsDeletionOngoing()); |
| |
| // The first pair of test-key-down/up. |
| EXPECT_EQ(VKBackBasedDeleter::SEND_KEY_TO_APPLICATION, |
| deleter.OnKeyEvent(VK_BACK, true, true)); |
| EXPECT_EQ(VKBackBasedDeleter::SEND_KEY_TO_APPLICATION, |
| deleter.OnKeyEvent(VK_BACK, false, true)); |
| |
| // If an unexpected key is passed, the deletion sequence must be terminated. |
| EXPECT_EQ(VKBackBasedDeleter::CALL_END_DELETION_THEN_DO_DEFAULT_ACTION, |
| deleter.OnKeyEvent(VK_TAB, true, true)); |
| |
| // Make sure the status of modifier keys have not changed. |
| EXPECT_FALSE(keyboard_mock->key_state().IsPressed(VK_CONTROL)); |
| EXPECT_TRUE(keyboard_mock->async_key_state().IsPressed(VK_CONTROL)); |
| |
| // The caller must call EndDeletion when |
| // CALL_END_DELETION_THEN_DO_DEFAULT_ACTION is returned. |
| deleter.EndDeletion(); |
| |
| // After EndDeletion, the modifier state should be restored based on the |
| // async key state. |
| EXPECT_TRUE(keyboard_mock->key_state().IsPressed(VK_CONTROL)); |
| EXPECT_TRUE(keyboard_mock->async_key_state().IsPressed(VK_CONTROL)); |
| |
| // Internal status must be reset by VKBackBasedDeleter::Reset(). |
| EXPECT_FALSE(deleter.IsDeletionOngoing()); |
| } |
| |
| } // namespace win32 |
| } // namespace mozc |