blob: 3d0589dd1c4ff392c7fa69548000a407aaf44fb9 [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.
#import <Foundation/Foundation.h>
#import <OCMock/OCMock.h>
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h"
#import "flutter/testing/testing.h"
#include "third_party/googletest/googletest/include/gtest/gtest.h"
@interface FlutterKeyboardManagerUnittestsObjC : NSObject
- (bool)nextResponderShouldThrowOnKeyUp;
- (bool)singlePrimaryResponder;
- (bool)doublePrimaryResponder;
- (bool)singleSecondaryResponder;
- (bool)emptyNextResponder;
@end
namespace flutter::testing {
namespace {
NSEvent* keyDownEvent(unsigned short keyCode) {
return [NSEvent keyEventWithType:NSEventTypeKeyDown
location:NSZeroPoint
modifierFlags:0x100
timestamp:0
windowNumber:0
context:nil
characters:@""
charactersIgnoringModifiers:@""
isARepeat:NO
keyCode:keyCode];
}
NSEvent* keyUpEvent(unsigned short keyCode) {
return [NSEvent keyEventWithType:NSEventTypeKeyUp
location:NSZeroPoint
modifierFlags:0x100
timestamp:0
windowNumber:0
context:nil
characters:@""
charactersIgnoringModifiers:@""
isARepeat:NO
keyCode:keyCode];
}
id checkKeyDownEvent(unsigned short keyCode) {
return [OCMArg checkWithBlock:^BOOL(id value) {
if (![value isKindOfClass:[NSEvent class]]) {
return NO;
}
NSEvent* event = value;
return event.keyCode == keyCode;
}];
}
NSResponder* mockOwnerWithDownOnlyNext() {
NSResponder* nextResponder = OCMStrictClassMock([NSResponder class]);
OCMStub([nextResponder keyDown:[OCMArg any]]).andDo(nil);
// The nextResponder is a strict mock and hasn't stubbed keyUp.
// An error will be thrown on keyUp.
NSResponder* owner = OCMStrictClassMock([NSResponder class]);
OCMStub([owner nextResponder]).andReturn(nextResponder);
return owner;
}
typedef void (^KeyCallbackSetter)(FlutterAsyncKeyCallback callback);
typedef BOOL (^BoolGetter)();
id<FlutterKeyPrimaryResponder> mockPrimaryResponder(KeyCallbackSetter callbackSetter) {
id<FlutterKeyPrimaryResponder> mock =
OCMStrictProtocolMock(@protocol(FlutterKeyPrimaryResponder));
OCMStub([mock handleEvent:[OCMArg any] callback:[OCMArg any]])
.andDo((^(NSInvocation* invocation) {
FlutterAsyncKeyCallback callback;
[invocation getArgument:&callback atIndex:3];
callbackSetter(callback);
}));
return mock;
}
id<FlutterKeySecondaryResponder> mockSecondaryResponder(BoolGetter resultGetter) {
id<FlutterKeySecondaryResponder> mock =
OCMStrictProtocolMock(@protocol(FlutterKeySecondaryResponder));
OCMStub([mock handleKeyEvent:[OCMArg any]]).andDo((^(NSInvocation* invocation) {
BOOL result = resultGetter();
[invocation setReturnValue:&result];
}));
return mock;
}
} // namespace
TEST(FlutterKeyboardManagerUnittests, NextResponderShouldThrowOnKeyUp) {
ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] nextResponderShouldThrowOnKeyUp]);
}
TEST(FlutterKeyboardManagerUnittests, SinglePrimaryResponder) {
ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] singlePrimaryResponder]);
}
TEST(FlutterKeyboardManagerUnittests, DoublePrimaryResponder) {
ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] doublePrimaryResponder]);
}
TEST(FlutterKeyboardManagerUnittests, SingleFinalResponder) {
ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] singleSecondaryResponder]);
}
TEST(FlutterKeyboardManagerUnittests, EmptyNextResponder) {
ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] emptyNextResponder]);
}
} // namespace flutter::testing
@implementation FlutterKeyboardManagerUnittestsObjC
// Verify that the nextResponder returned from mockOwnerWithDownOnlyNext()
// throws exception when keyUp is called.
- (bool)nextResponderShouldThrowOnKeyUp {
NSResponder* owner = flutter::testing::mockOwnerWithDownOnlyNext();
@try {
[owner.nextResponder keyUp:flutter::testing::keyUpEvent(0x50)];
return false;
} @catch (...) {
return true;
}
}
- (bool)singlePrimaryResponder {
NSResponder* owner = flutter::testing::mockOwnerWithDownOnlyNext();
FlutterKeyboardManager* manager = [[FlutterKeyboardManager alloc] initWithOwner:owner];
__block NSMutableArray<FlutterAsyncKeyCallback>* callbacks =
[NSMutableArray<FlutterAsyncKeyCallback> array];
[manager addPrimaryResponder:flutter::testing::mockPrimaryResponder(
^(FlutterAsyncKeyCallback callback) {
[callbacks addObject:callback];
})];
// Case: The responder reports FALSE
[manager handleEvent:flutter::testing::keyDownEvent(0x50)];
EXPECT_EQ([callbacks count], 1u);
callbacks[0](FALSE);
OCMVerify([owner.nextResponder keyDown:flutter::testing::checkKeyDownEvent(0x50)]);
[callbacks removeAllObjects];
// Case: The responder reports TRUE
[manager handleEvent:flutter::testing::keyUpEvent(0x50)];
EXPECT_EQ([callbacks count], 1u);
callbacks[0](TRUE);
// [owner.nextResponder keyUp:] should not be called, otherwise an error will be thrown.
return true;
}
- (bool)doublePrimaryResponder {
NSResponder* owner = flutter::testing::mockOwnerWithDownOnlyNext();
FlutterKeyboardManager* manager = [[FlutterKeyboardManager alloc] initWithOwner:owner];
__block NSMutableArray<FlutterAsyncKeyCallback>* callbacks1 =
[NSMutableArray<FlutterAsyncKeyCallback> array];
[manager addPrimaryResponder:flutter::testing::mockPrimaryResponder(
^(FlutterAsyncKeyCallback callback) {
[callbacks1 addObject:callback];
})];
__block NSMutableArray<FlutterAsyncKeyCallback>* callbacks2 =
[NSMutableArray<FlutterAsyncKeyCallback> array];
[manager addPrimaryResponder:flutter::testing::mockPrimaryResponder(
^(FlutterAsyncKeyCallback callback) {
[callbacks2 addObject:callback];
})];
// Case: Both responder report TRUE.
[manager handleEvent:flutter::testing::keyUpEvent(0x50)];
EXPECT_EQ([callbacks1 count], 1u);
EXPECT_EQ([callbacks2 count], 1u);
callbacks1[0](TRUE);
callbacks2[0](TRUE);
EXPECT_EQ([callbacks1 count], 1u);
EXPECT_EQ([callbacks2 count], 1u);
// [owner.nextResponder keyUp:] should not be called, otherwise an error will be thrown.
[callbacks1 removeAllObjects];
[callbacks2 removeAllObjects];
// Case: One responder reports TRUE.
[manager handleEvent:flutter::testing::keyUpEvent(0x50)];
EXPECT_EQ([callbacks1 count], 1u);
EXPECT_EQ([callbacks2 count], 1u);
callbacks1[0](FALSE);
callbacks2[0](TRUE);
EXPECT_EQ([callbacks1 count], 1u);
EXPECT_EQ([callbacks2 count], 1u);
// [owner.nextResponder keyUp:] should not be called, otherwise an error will be thrown.
[callbacks1 removeAllObjects];
[callbacks2 removeAllObjects];
// Case: Both responders report FALSE.
[manager handleEvent:flutter::testing::keyDownEvent(0x50)];
EXPECT_EQ([callbacks1 count], 1u);
EXPECT_EQ([callbacks2 count], 1u);
callbacks1[0](FALSE);
callbacks2[0](FALSE);
EXPECT_EQ([callbacks1 count], 1u);
EXPECT_EQ([callbacks2 count], 1u);
OCMVerify([owner.nextResponder keyDown:flutter::testing::checkKeyDownEvent(0x50)]);
[callbacks1 removeAllObjects];
[callbacks2 removeAllObjects];
return true;
}
- (bool)singleSecondaryResponder {
NSResponder* owner = flutter::testing::mockOwnerWithDownOnlyNext();
FlutterKeyboardManager* manager = [[FlutterKeyboardManager alloc] initWithOwner:owner];
__block NSMutableArray<FlutterAsyncKeyCallback>* callbacks =
[NSMutableArray<FlutterAsyncKeyCallback> array];
[manager addPrimaryResponder:flutter::testing::mockPrimaryResponder(
^(FlutterAsyncKeyCallback callback) {
[callbacks addObject:callback];
})];
__block BOOL nextResponse;
[manager addSecondaryResponder:flutter::testing::mockSecondaryResponder(^() {
return nextResponse;
})];
// Case: Primary responder responds TRUE. The event shouldn't be handled by
// the secondary responder.
nextResponse = FALSE;
[manager handleEvent:flutter::testing::keyUpEvent(0x50)];
EXPECT_EQ([callbacks count], 1u);
callbacks[0](TRUE);
// [owner.nextResponder keyUp:] should not be called, otherwise an error will be thrown.
[callbacks removeAllObjects];
// Case: Primary responder responds FALSE. The secondary responder returns
// TRUE.
nextResponse = TRUE;
[manager handleEvent:flutter::testing::keyUpEvent(0x50)];
EXPECT_EQ([callbacks count], 1u);
callbacks[0](FALSE);
// [owner.nextResponder keyUp:] should not be called, otherwise an error will be thrown.
[callbacks removeAllObjects];
// Case: Primary responder responds FALSE. The secondary responder returns FALSE.
nextResponse = FALSE;
[manager handleEvent:flutter::testing::keyDownEvent(0x50)];
EXPECT_EQ([callbacks count], 1u);
callbacks[0](FALSE);
OCMVerify([owner.nextResponder keyDown:flutter::testing::checkKeyDownEvent(0x50)]);
[callbacks removeAllObjects];
return true;
}
- (bool)emptyNextResponder {
NSResponder* owner = OCMStrictClassMock([NSResponder class]);
OCMStub([owner nextResponder]).andReturn(nil);
FlutterKeyboardManager* manager = [[FlutterKeyboardManager alloc] initWithOwner:owner];
[manager addPrimaryResponder:flutter::testing::mockPrimaryResponder(
^(FlutterAsyncKeyCallback callback) {
callback(FALSE);
})];
// Passes if no error is thrown.
return true;
}
@end