Added unit tests for RCTModuleMethod parsing, and fixed some edge cases
This commit is contained in:
parent
e964e741ef
commit
b812b0ee2e
|
@ -21,6 +21,7 @@
|
|||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
|
||||
13DB03481B5D2ED500C27245 /* RCTJSONTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13DB03471B5D2ED500C27245 /* RCTJSONTests.m */; };
|
||||
13DF61B61B67A45000EDB188 /* RCTModuleMethodTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13DF61B51B67A45000EDB188 /* RCTModuleMethodTests.m */; };
|
||||
141FC1211B222EBB004D5FFB /* IntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 141FC1201B222EBB004D5FFB /* IntegrationTests.m */; };
|
||||
143BC5A11B21E45C00462512 /* UIExplorerSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 143BC5A01B21E45C00462512 /* UIExplorerSnapshotTests.m */; };
|
||||
144D21241B2204C5006DB32B /* RCTClipRectTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 144D21231B2204C5006DB32B /* RCTClipRectTests.m */; };
|
||||
|
@ -176,6 +177,7 @@
|
|||
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = UIExplorer/main.m; sourceTree = "<group>"; };
|
||||
13CC9D481AEED2B90020D1C2 /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = ../../Libraries/Settings/RCTSettings.xcodeproj; sourceTree = "<group>"; };
|
||||
13DB03471B5D2ED500C27245 /* RCTJSONTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJSONTests.m; sourceTree = "<group>"; };
|
||||
13DF61B51B67A45000EDB188 /* RCTModuleMethodTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleMethodTests.m; sourceTree = "<group>"; };
|
||||
141FC1201B222EBB004D5FFB /* IntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IntegrationTests.m; sourceTree = "<group>"; };
|
||||
143BC57E1B21E18100462512 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
143BC5811B21E18100462512 /* testLayoutExampleSnapshot_1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testLayoutExampleSnapshot_1@2x.png"; sourceTree = "<group>"; };
|
||||
|
@ -360,6 +362,7 @@
|
|||
1497CFA91B21F5E400C1F8F2 /* RCTEventDispatcherTests.m */,
|
||||
1300627E1B59179B0043FE5A /* RCTGzipTests.m */,
|
||||
13DB03471B5D2ED500C27245 /* RCTJSONTests.m */,
|
||||
13DF61B51B67A45000EDB188 /* RCTModuleMethodTests.m */,
|
||||
138D6A161B53CD440074A87E /* RCTShadowViewTests.m */,
|
||||
1497CFAA1B21F5E400C1F8F2 /* RCTSparseArrayTests.m */,
|
||||
1497CFAB1B21F5E400C1F8F2 /* RCTUIManagerTests.m */,
|
||||
|
@ -798,6 +801,7 @@
|
|||
138D6A171B53CD440074A87E /* RCTCacheTests.m in Sources */,
|
||||
13DB03481B5D2ED500C27245 /* RCTJSONTests.m in Sources */,
|
||||
1497CFAC1B21F5E400C1F8F2 /* RCTAllocationTests.m in Sources */,
|
||||
13DF61B61B67A45000EDB188 /* RCTModuleMethodTests.m in Sources */,
|
||||
138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
//
|
||||
// RCTModuleMethodTests.m
|
||||
// UIExplorer
|
||||
//
|
||||
// Created by Nick Lockwood on 28/07/2015.
|
||||
// Copyright (c) 2015 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "RCTModuleMethod.h"
|
||||
|
||||
@interface RCTModuleMethodTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTModuleMethodTests
|
||||
|
||||
extern void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes);
|
||||
|
||||
- (void)testOneArgument
|
||||
{
|
||||
NSArray *argTypes;
|
||||
NSString *methodName = @"foo:(NSInteger)foo";
|
||||
RCTParseObjCMethodName(&methodName, &argTypes);
|
||||
XCTAssertEqualObjects(methodName, @"foo:");
|
||||
XCTAssertEqual(argTypes.count, (NSUInteger)1);
|
||||
XCTAssertEqualObjects(argTypes[0], @"NSInteger");
|
||||
}
|
||||
|
||||
- (void)testTwoArguments
|
||||
{
|
||||
NSArray *argTypes;
|
||||
NSString *methodName = @"foo:(NSInteger)foo bar:(BOOL)bar";
|
||||
RCTParseObjCMethodName(&methodName, &argTypes);
|
||||
XCTAssertEqualObjects(methodName, @"foo:bar:");
|
||||
XCTAssertEqual(argTypes.count, (NSUInteger)2);
|
||||
XCTAssertEqualObjects(argTypes[0], @"NSInteger");
|
||||
XCTAssertEqualObjects(argTypes[1], @"BOOL");
|
||||
}
|
||||
|
||||
- (void)testSpaces
|
||||
{
|
||||
NSArray *argTypes;
|
||||
NSString *methodName = @"foo : (NSInteger)foo bar : (BOOL) bar";
|
||||
RCTParseObjCMethodName(&methodName, &argTypes);
|
||||
XCTAssertEqualObjects(methodName, @"foo:bar:");
|
||||
XCTAssertEqual(argTypes.count, (NSUInteger)2);
|
||||
XCTAssertEqualObjects(argTypes[0], @"NSInteger");
|
||||
XCTAssertEqualObjects(argTypes[1], @"BOOL");
|
||||
}
|
||||
|
||||
- (void)testNewlines
|
||||
{
|
||||
NSArray *argTypes;
|
||||
NSString *methodName = @"foo : (NSInteger)foo\nbar : (BOOL) bar";
|
||||
RCTParseObjCMethodName(&methodName, &argTypes);
|
||||
XCTAssertEqualObjects(methodName, @"foo:bar:");
|
||||
XCTAssertEqual(argTypes.count, (NSUInteger)2);
|
||||
XCTAssertEqualObjects(argTypes[0], @"NSInteger");
|
||||
XCTAssertEqualObjects(argTypes[1], @"BOOL");
|
||||
}
|
||||
|
||||
- (void)testUnnamedArgs
|
||||
{
|
||||
NSArray *argTypes;
|
||||
NSString *methodName = @"foo:(NSInteger)foo:(BOOL)bar";
|
||||
RCTParseObjCMethodName(&methodName, &argTypes);
|
||||
XCTAssertEqualObjects(methodName, @"foo::");
|
||||
XCTAssertEqual(argTypes.count, (NSUInteger)2);
|
||||
XCTAssertEqualObjects(argTypes[0], @"NSInteger");
|
||||
XCTAssertEqualObjects(argTypes[1], @"BOOL");
|
||||
}
|
||||
|
||||
- (void)testUntypedUnnamedArgs
|
||||
{
|
||||
NSArray *argTypes;
|
||||
NSString *methodName = @"foo:foo:bar:bar";
|
||||
RCTParseObjCMethodName(&methodName, &argTypes);
|
||||
XCTAssertEqualObjects(methodName, @"foo:::");
|
||||
XCTAssertEqual(argTypes.count, (NSUInteger)3);
|
||||
XCTAssertEqualObjects(argTypes[0], @"id");
|
||||
XCTAssertEqualObjects(argTypes[1], @"id");
|
||||
XCTAssertEqualObjects(argTypes[2], @"id");
|
||||
}
|
||||
|
||||
- (void)testAttributes
|
||||
{
|
||||
NSArray *argTypes;
|
||||
NSString *methodName = @"foo:(__attribute__((nonnull)) NSString *)foo bar:(__unused BOOL)bar";
|
||||
RCTParseObjCMethodName(&methodName, &argTypes);
|
||||
XCTAssertEqualObjects(methodName, @"foo:bar:");
|
||||
XCTAssertEqual(argTypes.count, (NSUInteger)2);
|
||||
XCTAssertEqualObjects(argTypes[0], @"NSString");
|
||||
XCTAssertEqualObjects(argTypes[1], @"BOOL");
|
||||
}
|
||||
|
||||
- (void)testSemicolonStripping
|
||||
{
|
||||
NSArray *argTypes;
|
||||
NSString *methodName = @"foo:(NSString *)foo bar:(BOOL)bar;";
|
||||
RCTParseObjCMethodName(&methodName, &argTypes);
|
||||
XCTAssertEqualObjects(methodName, @"foo:bar:");
|
||||
XCTAssertEqual(argTypes.count, (NSUInteger)2);
|
||||
XCTAssertEqualObjects(argTypes[0], @"NSString");
|
||||
XCTAssertEqualObjects(argTypes[1], @"BOOL");
|
||||
}
|
||||
|
||||
@end
|
|
@ -670,7 +670,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL
|
|||
}
|
||||
|
||||
RCTProfileEndEvent(@"Invoke callback", @"objc_call", @{
|
||||
@"module": method.moduleClassName,
|
||||
@"module": NSStringFromClass(method.moduleClass),
|
||||
@"method": method.JSMethodName,
|
||||
@"selector": NSStringFromSelector(method.selector),
|
||||
@"args": RCTJSONStringify(RCTNullIfNil(params), NULL),
|
||||
|
|
|
@ -18,8 +18,8 @@ typedef NS_ENUM(NSUInteger, RCTJavaScriptFunctionKind) {
|
|||
|
||||
@interface RCTModuleMethod : NSObject
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *moduleClassName;
|
||||
@property (nonatomic, copy, readonly) NSString *JSMethodName;
|
||||
@property (nonatomic, strong, readonly) Class moduleClass;
|
||||
@property (nonatomic, assign, readonly) SEL selector;
|
||||
@property (nonatomic, assign, readonly) RCTJavaScriptFunctionKind functionKind;
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#import <objc/message.h>
|
||||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTLog.h"
|
||||
|
@ -34,52 +35,73 @@
|
|||
|
||||
RCT_NOT_IMPLEMENTED(-init)
|
||||
|
||||
void RCTParseObjCMethodName(NSString **, NSArray **);
|
||||
void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes)
|
||||
{
|
||||
static NSRegularExpression *typeNameRegex;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSString *unusedPattern = @"(?:__unused|__attribute__\\(\\(unused\\)\\))";
|
||||
NSString *constPattern = @"(?:const)";
|
||||
NSString *nullabilityPattern = @"(?:__nullable|__nonnull|nullable|nonnull|__attribute__\\(\\(nonnull\\)\\)|__attribute__\\(\\(nullable\\)\\))";
|
||||
NSString *annotationPattern = [NSString stringWithFormat:@"(?:(?:%@|%@|%@)\\s*)",
|
||||
unusedPattern, constPattern, nullabilityPattern];
|
||||
NSString *pattern = [NSString stringWithFormat:@"(?<=:)(\\s*\\(%1$@?(\\w+?)(?:\\s*\\*)?%1$@?\\))?\\s*\\w+",
|
||||
annotationPattern];
|
||||
typeNameRegex = [[NSRegularExpression alloc] initWithPattern:pattern options:0 error:NULL];
|
||||
});
|
||||
|
||||
// Extract argument types
|
||||
NSString *methodName = *objCMethodName;
|
||||
NSRange methodRange = {0, methodName.length};
|
||||
NSMutableArray *arguments = [NSMutableArray array];
|
||||
[typeNameRegex enumerateMatchesInString:methodName options:0 range:methodRange usingBlock:^(NSTextCheckingResult *result, __unused NSMatchingFlags flags, __unused BOOL *stop) {
|
||||
NSRange range = [result rangeAtIndex:2];
|
||||
[arguments addObject:range.length ? [methodName substringWithRange:range] : @"id"];
|
||||
}];
|
||||
*argTypes = [arguments copy];
|
||||
|
||||
// Remove the parameter types and names
|
||||
methodName = [typeNameRegex stringByReplacingMatchesInString:methodName options:0
|
||||
range:methodRange
|
||||
withTemplate:@""];
|
||||
|
||||
// Remove whitespace
|
||||
methodName = [methodName stringByReplacingOccurrencesOfString:@"\n" withString:@""];
|
||||
methodName = [methodName stringByReplacingOccurrencesOfString:@" " withString:@""];
|
||||
|
||||
// Strip trailing semicolon
|
||||
if ([methodName hasSuffix:@";"]) {
|
||||
methodName = [methodName substringToIndex:methodName.length - 1];
|
||||
}
|
||||
|
||||
*objCMethodName = methodName;
|
||||
}
|
||||
|
||||
- (instancetype)initWithObjCMethodName:(NSString *)objCMethodName
|
||||
JSMethodName:(NSString *)JSMethodName
|
||||
moduleClass:(Class)moduleClass
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
static NSRegularExpression *typeRegex;
|
||||
static NSRegularExpression *selectorRegex;
|
||||
if (!typeRegex) {
|
||||
NSString *unusedPattern = @"(?:__unused|__attribute__\\(\\(unused\\)\\))";
|
||||
NSString *constPattern = @"(?:const)";
|
||||
NSString *nullabilityPattern = @"(?:__nullable|__nonnull|nullable|nonnull)";
|
||||
NSString *annotationPattern = [NSString stringWithFormat:@"(?:(?:%@|%@|%@)\\s*)",
|
||||
unusedPattern, constPattern, nullabilityPattern];
|
||||
NSString *pattern = [NSString stringWithFormat:@"\\(%1$@?(\\w+?)(?:\\s*\\*)?%1$@?\\)", annotationPattern];
|
||||
typeRegex = [[NSRegularExpression alloc] initWithPattern:pattern options:0 error:NULL];
|
||||
|
||||
selectorRegex = [[NSRegularExpression alloc] initWithPattern:@"(?<=:).*?(?=[a-zA-Z_]+:|$)" options:0 error:NULL];
|
||||
}
|
||||
|
||||
NSMutableArray *argumentNames = [NSMutableArray array];
|
||||
[typeRegex enumerateMatchesInString:objCMethodName options:0 range:NSMakeRange(0, objCMethodName.length) usingBlock:^(NSTextCheckingResult *result, __unused NSMatchingFlags flags, __unused BOOL *stop) {
|
||||
NSString *argumentName = [objCMethodName substringWithRange:[result rangeAtIndex:1]];
|
||||
[argumentNames addObject:argumentName];
|
||||
}];
|
||||
|
||||
// Remove the parameters' type and name
|
||||
objCMethodName = [selectorRegex stringByReplacingMatchesInString:objCMethodName
|
||||
options:0
|
||||
range:NSMakeRange(0, objCMethodName.length)
|
||||
withTemplate:@""];
|
||||
// Remove any spaces since `selector : (Type)name` is a valid syntax
|
||||
objCMethodName = [objCMethodName stringByReplacingOccurrencesOfString:@" " withString:@""];
|
||||
NSArray *argTypes;
|
||||
RCTParseObjCMethodName(&objCMethodName, &argTypes);
|
||||
|
||||
_moduleClass = moduleClass;
|
||||
_moduleClassName = NSStringFromClass(_moduleClass);
|
||||
_selector = NSSelectorFromString(objCMethodName);
|
||||
RCTAssert(_selector, @"%@ is not a valid selector", objCMethodName);
|
||||
|
||||
_JSMethodName = JSMethodName.length > 0 ? JSMethodName : ({
|
||||
NSString *methodName = NSStringFromSelector(_selector);
|
||||
NSString *methodName = objCMethodName;
|
||||
NSRange colonRange = [methodName rangeOfString:@":"];
|
||||
if (colonRange.length) {
|
||||
if (colonRange.location != NSNotFound) {
|
||||
methodName = [methodName substringToIndex:colonRange.location];
|
||||
}
|
||||
RCTAssert(methodName.length, @"%@ is not a valid JS function name, please"
|
||||
" supply an alternative using RCT_REMAP_METHOD()", objCMethodName);
|
||||
methodName;
|
||||
});
|
||||
|
||||
|
||||
// Get method signature
|
||||
_methodSignature = [_moduleClass instanceMethodSignatureForSelector:_selector];
|
||||
|
||||
|
@ -123,8 +145,8 @@ RCT_NOT_IMPLEMENTED(-init)
|
|||
for (NSUInteger i = 2; i < numberOfArguments; i++) {
|
||||
const char *argumentType = [_methodSignature getArgumentTypeAtIndex:i];
|
||||
|
||||
NSString *argumentName = argumentNames[i - 2];
|
||||
SEL selector = NSSelectorFromString([argumentName stringByAppendingString:@":"]);
|
||||
NSString *selName = argTypes[i - 2];
|
||||
SEL selector = NSSelectorFromString([selName stringByAppendingString:@":"]);
|
||||
if ([RCTConvert respondsToSelector:selector]) {
|
||||
switch (argumentType[0]) {
|
||||
|
||||
|
@ -174,9 +196,9 @@ case _value: { \
|
|||
default:
|
||||
defaultCase(argumentType);
|
||||
}
|
||||
} else if ([argumentName isEqualToString:@"RCTResponseSenderBlock"]) {
|
||||
} else if ([selName isEqualToString:@"RCTResponseSenderBlock"]) {
|
||||
addBlockArgument();
|
||||
} else if ([argumentName isEqualToString:@"RCTResponseErrorBlock"]) {
|
||||
} else if ([selName isEqualToString:@"RCTResponseErrorBlock"]) {
|
||||
RCT_ARG_BLOCK(
|
||||
|
||||
if (RCT_DEBUG && json && ![json isKindOfClass:[NSNumber class]]) {
|
||||
|
@ -192,10 +214,10 @@ case _value: { \
|
|||
arguments:@[json, @[RCTJSErrorFromNSError(error)]]];
|
||||
} : ^(__unused NSError *error) {});
|
||||
)
|
||||
} else if ([argumentName isEqualToString:@"RCTPromiseResolveBlock"]) {
|
||||
} else if ([selName isEqualToString:@"RCTPromiseResolveBlock"]) {
|
||||
RCTAssert(i == numberOfArguments - 2,
|
||||
@"The RCTPromiseResolveBlock must be the second to last parameter in -[%@ %@]",
|
||||
_moduleClassName, objCMethodName);
|
||||
_moduleClass, objCMethodName);
|
||||
RCT_ARG_BLOCK(
|
||||
if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
|
||||
RCTLogError(@"Argument %tu (%@) of %@.%@ must be a promise resolver ID", index,
|
||||
|
@ -212,10 +234,10 @@ case _value: { \
|
|||
});
|
||||
)
|
||||
_functionKind = RCTJavaScriptFunctionKindAsync;
|
||||
} else if ([argumentName isEqualToString:@"RCTPromiseRejectBlock"]) {
|
||||
} else if ([selName isEqualToString:@"RCTPromiseRejectBlock"]) {
|
||||
RCTAssert(i == numberOfArguments - 1,
|
||||
@"The RCTPromiseRejectBlock must be the last parameter in -[%@ %@]",
|
||||
_moduleClassName, objCMethodName);
|
||||
_moduleClass, objCMethodName);
|
||||
RCT_ARG_BLOCK(
|
||||
if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
|
||||
RCTLogError(@"Argument %tu (%@) of %@.%@ must be a promise rejecter ID", index,
|
||||
|
@ -236,7 +258,7 @@ case _value: { \
|
|||
|
||||
// Unknown argument type
|
||||
RCTLogError(@"Unknown argument type '%@' in method %@. Extend RCTConvert"
|
||||
" to support this type.", argumentName, [self methodName]);
|
||||
" to support this type.", selName, [self methodName]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue