blob: 5e58739fda5f818beb7b43bcc9f3fcee2d356d42 [file] [log] [blame]
// 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/windows/keyboard_key_handler.h"
#include <rapidjson/document.h>
#include <memory>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
namespace flutter {
namespace testing {
namespace {
static constexpr int kHandledScanCode = 20;
static constexpr int kHandledScanCode2 = 22;
static constexpr int kUnhandledScanCode = 21;
constexpr uint64_t kScanCodeShiftRight = 0x36;
constexpr uint64_t kScanCodeControl = 0x1D;
constexpr uint64_t kScanCodeAltLeft = 0x38;
typedef std::function<void(bool)> Callback;
typedef std::function<void(Callback&)> CallbackHandler;
void dont_respond(Callback& callback) {}
void respond_true(Callback& callback) {
callback(true);
}
void respond_false(Callback& callback) {
callback(false);
}
// A testing |KeyHandlerDelegate| that records all calls
// to |KeyboardHook| and can be customized with whether
// the hook is handled in async.
class MockKeyHandlerDelegate
: public KeyboardKeyHandler::KeyboardKeyHandlerDelegate {
public:
class KeyboardHookCall {
public:
int delegate_id;
int key;
int scancode;
int action;
char32_t character;
bool extended;
bool was_down;
std::function<void(bool)> callback;
};
// Create a |MockKeyHandlerDelegate|.
//
// The |delegate_id| is an arbitrary ID to tell between delegates
// that will be recorded in |KeyboardHookCall|.
//
// The |hook_history| will store every call to |KeyboardHookCall| that are
// handled asynchronously.
//
// The |is_async| is a function that the class calls upon every
// |KeyboardHookCall| to decide whether the call is handled asynchronously.
// Defaults to always returning true (async).
MockKeyHandlerDelegate(int delegate_id,
std::list<KeyboardHookCall>* hook_history)
: delegate_id(delegate_id),
hook_history(hook_history),
callback_handler(dont_respond) {}
virtual ~MockKeyHandlerDelegate() = default;
virtual void KeyboardHook(int key,
int scancode,
int action,
char32_t character,
bool extended,
bool was_down,
std::function<void(bool)> callback) {
hook_history->push_back(KeyboardHookCall{
.delegate_id = delegate_id,
.key = key,
.scancode = scancode,
.character = character,
.extended = extended,
.was_down = was_down,
.callback = std::move(callback),
});
callback_handler(hook_history->back().callback);
}
CallbackHandler callback_handler;
int delegate_id;
std::list<KeyboardHookCall>* hook_history;
};
enum KeyEventResponse {
kNoResponse,
kHandled,
kUnhandled,
};
static KeyEventResponse key_event_response = kNoResponse;
void OnKeyEventResult(bool handled) {
key_event_response = handled ? kHandled : kUnhandled;
}
} // namespace
TEST(KeyboardKeyHandlerTest, SingleDelegateWithAsyncResponds) {
std::list<MockKeyHandlerDelegate::KeyboardHookCall> hook_history;
KeyboardKeyHandler handler;
// Add one delegate
auto delegate = std::make_unique<MockKeyHandlerDelegate>(1, &hook_history);
handler.AddDelegate(std::move(delegate));
/// Test 1: One event that is handled by the framework
// Dispatch a key event
handler.KeyboardHook(64, kHandledScanCode, WM_KEYDOWN, L'a', false, true,
OnKeyEventResult);
EXPECT_EQ(key_event_response, kNoResponse);
EXPECT_EQ(hook_history.size(), 1);
EXPECT_EQ(hook_history.back().delegate_id, 1);
EXPECT_EQ(hook_history.back().scancode, kHandledScanCode);
EXPECT_EQ(hook_history.back().was_down, true);
EXPECT_EQ(key_event_response, kNoResponse);
hook_history.back().callback(true);
EXPECT_EQ(key_event_response, kHandled);
key_event_response = kNoResponse;
hook_history.clear();
/// Test 2: Two events that are unhandled by the framework
handler.KeyboardHook(64, kHandledScanCode, WM_KEYDOWN, L'a', false, false,
OnKeyEventResult);
EXPECT_EQ(key_event_response, kNoResponse);
EXPECT_EQ(hook_history.size(), 1);
EXPECT_EQ(hook_history.back().delegate_id, 1);
EXPECT_EQ(hook_history.back().scancode, kHandledScanCode);
EXPECT_EQ(hook_history.back().was_down, false);
// Dispatch another key event
handler.KeyboardHook(65, kHandledScanCode2, WM_KEYUP, L'b', false, true,
OnKeyEventResult);
EXPECT_EQ(key_event_response, kNoResponse);
EXPECT_EQ(hook_history.size(), 2);
EXPECT_EQ(hook_history.back().delegate_id, 1);
EXPECT_EQ(hook_history.back().scancode, kHandledScanCode2);
EXPECT_EQ(hook_history.back().was_down, true);
// Resolve the second event first to test out-of-order response
hook_history.back().callback(false);
EXPECT_EQ(key_event_response, kUnhandled);
key_event_response = kNoResponse;
// Resolve the first event then
hook_history.front().callback(false);
EXPECT_EQ(key_event_response, kUnhandled);
hook_history.clear();
key_event_response = kNoResponse;
}
TEST(KeyboardKeyHandlerTest, SingleDelegateWithSyncResponds) {
std::list<MockKeyHandlerDelegate::KeyboardHookCall> hook_history;
KeyboardKeyHandler handler;
// Add one delegate
auto delegate = std::make_unique<MockKeyHandlerDelegate>(1, &hook_history);
CallbackHandler& delegate_handler = delegate->callback_handler;
handler.AddDelegate(std::move(delegate));
/// Test 1: One event that is handled by the framework
// Dispatch a key event
delegate_handler = respond_true;
handler.KeyboardHook(64, kHandledScanCode, WM_KEYDOWN, L'a', false, false,
OnKeyEventResult);
EXPECT_EQ(key_event_response, kHandled);
EXPECT_EQ(hook_history.size(), 1);
EXPECT_EQ(hook_history.back().delegate_id, 1);
EXPECT_EQ(hook_history.back().scancode, kHandledScanCode);
EXPECT_EQ(hook_history.back().was_down, false);
hook_history.clear();
/// Test 2: An event unhandled by the framework
delegate_handler = respond_false;
handler.KeyboardHook(64, kHandledScanCode, WM_KEYDOWN, L'a', false, false,
OnKeyEventResult);
EXPECT_EQ(key_event_response, kUnhandled);
EXPECT_EQ(hook_history.size(), 1);
EXPECT_EQ(hook_history.back().delegate_id, 1);
EXPECT_EQ(hook_history.back().scancode, kHandledScanCode);
EXPECT_EQ(hook_history.back().was_down, false);
hook_history.clear();
key_event_response = kNoResponse;
}
} // namespace testing
} // namespace flutter