diff --git a/DeepLinkSDK.xcodeproj/project.pbxproj b/DeepLinkSDK.xcodeproj/project.pbxproj index b335b72..3867d4d 100644 --- a/DeepLinkSDK.xcodeproj/project.pbxproj +++ b/DeepLinkSDK.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 1252340A196B4771BE27D5FD /* libPods-ReceiverDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CFE63F7FEA51182807D98A78 /* libPods-ReceiverDemo.a */; }; 2F4988DE1AE71ABC0069EF2B /* DPLRouteHandlerIntegrationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F4988DD1AE71ABC0069EF2B /* DPLRouteHandlerIntegrationTest.m */; }; + 4D4F412B1B02A96400B710DB /* DPLRegularExpressionSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D4F412A1B02A96400B710DB /* DPLRegularExpressionSpec.m */; }; 6003F58E195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; }; 6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58F195388D20070C39A /* CoreGraphics.framework */; }; 6003F592195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; }; @@ -65,6 +66,11 @@ 19367BAF0FEDE5B798128F3D /* libPods-Tests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Tests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 2AE3E05821FBC0C05F248E61 /* Pods-Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Tests/Pods-Tests.debug.xcconfig"; sourceTree = ""; }; 2F4988DD1AE71ABC0069EF2B /* DPLRouteHandlerIntegrationTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DPLRouteHandlerIntegrationTest.m; path = IntegrationTests/DPLRouteHandlerIntegrationTest.m; sourceTree = ""; }; + 4D4F41231B0298FF00B710DB /* DPLRegularExpression.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DPLRegularExpression.h; sourceTree = ""; }; + 4D4F41241B0298FF00B710DB /* DPLRegularExpression.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DPLRegularExpression.m; sourceTree = ""; }; + 4D4F41251B029D9E00B710DB /* DPLMatchResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DPLMatchResult.h; sourceTree = ""; }; + 4D4F41261B029D9E00B710DB /* DPLMatchResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DPLMatchResult.m; sourceTree = ""; }; + 4D4F412A1B02A96400B710DB /* DPLRegularExpressionSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DPLRegularExpressionSpec.m; sourceTree = ""; }; 57D5F02E049D7887B4F4ACDF /* Pods-ReceiverDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReceiverDemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ReceiverDemo/Pods-ReceiverDemo.debug.xcconfig"; sourceTree = ""; }; 6003F58A195388D20070C39A /* ReceiverDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ReceiverDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; @@ -220,6 +226,25 @@ name = Pods; sourceTree = ""; }; + 4D4F41221B0298AA00B710DB /* Regex */ = { + isa = PBXGroup; + children = ( + 4D4F41231B0298FF00B710DB /* DPLRegularExpression.h */, + 4D4F41241B0298FF00B710DB /* DPLRegularExpression.m */, + 4D4F41251B029D9E00B710DB /* DPLMatchResult.h */, + 4D4F41261B029D9E00B710DB /* DPLMatchResult.m */, + ); + path = Regex; + sourceTree = ""; + }; + 4D4F41291B02A95800B710DB /* Regex */ = { + isa = PBXGroup; + children = ( + 4D4F412A1B02A96400B710DB /* DPLRegularExpressionSpec.m */, + ); + path = Regex; + sourceTree = ""; + }; 6003F581195388D10070C39A = { isa = PBXGroup; children = ( @@ -405,6 +430,7 @@ DE99EF6A1A3B6CDD00CE3449 /* Protocols */, DE3E61071A3B4485008D6DFC /* Categories */, DEB4EDBB1A49CEA400F31D14 /* Errors */, + 4D4F41221B0298AA00B710DB /* Regex */, DE16E91F1A42882F00077E18 /* Router */, DEAC406F1A5DA7B8004A9095 /* RouteHandler */, DE16E9331A4289D500077E18 /* RouteMatcher */, @@ -481,6 +507,7 @@ children = ( 2F4988DC1AE71A930069EF2B /* IntegrationTests */, DEEBD4A61AAB7928000BCA84 /* Fixtures */, + 4D4F41291B02A95800B710DB /* Regex */, DE16E9221A42883B00077E18 /* Router */, DE16E9361A4289DF00077E18 /* RouteMatcher */, DE058E0E1A3B485500147C04 /* DeepLink */, @@ -806,6 +833,7 @@ files = ( DE16E9281A42883B00077E18 /* DPLDeepLinkRouterSpec.m in Sources */, DE16E9381A4289DF00077E18 /* DPLRouteMatcherSpec.m in Sources */, + 4D4F412B1B02A96400B710DB /* DPLRegularExpressionSpec.m in Sources */, DECB32531A881CA10071C76E /* NSObject_DPLJSONObjectSpec.m in Sources */, DEEBD4A91AAB7946000BCA84 /* DPLSerializableObject.m in Sources */, DE058E101A3B485500147C04 /* DPLDeepLinkSpec.m in Sources */, diff --git a/DeepLinkSDK/Regex/DPLMatchResult.h b/DeepLinkSDK/Regex/DPLMatchResult.h new file mode 100644 index 0000000..c9f39ca --- /dev/null +++ b/DeepLinkSDK/Regex/DPLMatchResult.h @@ -0,0 +1,8 @@ +#import + +@interface DPLMatchResult : NSObject + +@property (nonatomic, assign, getter=isMatch) BOOL match; +@property (nonatomic, strong) NSDictionary *namedProperties; + +@end diff --git a/DeepLinkSDK/Regex/DPLMatchResult.m b/DeepLinkSDK/Regex/DPLMatchResult.m new file mode 100644 index 0000000..a80a8e9 --- /dev/null +++ b/DeepLinkSDK/Regex/DPLMatchResult.m @@ -0,0 +1,5 @@ +#import "DPLMatchResult.h" + +@implementation DPLMatchResult + +@end diff --git a/DeepLinkSDK/Regex/DPLRegularExpression.h b/DeepLinkSDK/Regex/DPLRegularExpression.h new file mode 100644 index 0000000..e2affc2 --- /dev/null +++ b/DeepLinkSDK/Regex/DPLRegularExpression.h @@ -0,0 +1,12 @@ +#import +#import "DPLMatchResult.h" + +@interface DPLRegularExpression : NSRegularExpression + +@property (nonatomic, strong) NSArray *groupNames; + ++ (instancetype)regularExpressionWithPattern:(NSString *)pattern; + +- (DPLMatchResult *)matchResultForString:(NSString *)str; + +@end diff --git a/DeepLinkSDK/Regex/DPLRegularExpression.m b/DeepLinkSDK/Regex/DPLRegularExpression.m new file mode 100644 index 0000000..809e9d2 --- /dev/null +++ b/DeepLinkSDK/Regex/DPLRegularExpression.m @@ -0,0 +1,135 @@ +#import "DPLRegularExpression.h" + +static NSString * const DPLNamedGroupComponentPattern = @":[a-zA-Z0-9-_][^/]+"; +static NSString * const DPLRouteParameterPattern = @":[a-zA-Z0-9-_]+"; +static NSString * const DPLURLParameterPattern = @"([^/]+)"; + +@implementation DPLRegularExpression + ++ (instancetype)regularExpressionWithPattern:(NSString *)pattern { + return [[self alloc] initWithPattern:pattern options:0 error:nil]; +} + + +- (instancetype)initWithPattern:(NSString *)pattern + options:(NSRegularExpressionOptions)options + error:(NSError *__autoreleasing *)error { + + NSString *cleanedPattern = [[self class] stringByRemovingNamedGroupsFromString:pattern]; + + self = [super initWithPattern:cleanedPattern options:options error:error]; + if (self) { + self.groupNames = [[self class] namedGroupsForString:pattern]; + } + return self; +} + + +- (DPLMatchResult *)matchResultForString:(NSString *)str { + NSArray *matches = [self matchesInString:str options:0 range:NSMakeRange(0, str.length)]; + DPLMatchResult *matchResult = [[DPLMatchResult alloc] init]; + + if (!matches.count) { + return matchResult; + } + + matchResult.match = YES; + + // Set route parameters in the routeParameters dictionary + NSMutableDictionary *routeParameters = [NSMutableDictionary dictionary]; + for (NSTextCheckingResult *result in matches) { + // Begin at 1 as first range is the whole match + for (NSInteger i = 1; i < result.numberOfRanges && i <= self.groupNames.count; i++) { + NSString *parameterName = self.groupNames[i - 1]; + NSString *parameterValue = [str substringWithRange:[result rangeAtIndex:i]]; + routeParameters[parameterName] = parameterValue; + } + } + + matchResult.namedProperties = routeParameters; + return matchResult; +} + + +#pragma mark - Named Group Helpers + ++ (NSArray *)namedGroupTokensForString:(NSString *)str { + NSRegularExpression *componentRegex = [NSRegularExpression regularExpressionWithPattern:DPLNamedGroupComponentPattern + options:0 + error:nil]; + NSArray *matches = [componentRegex matchesInString:str + options:0 + range:NSMakeRange(0, str.length)]; + + NSMutableArray *namedGroupTokens = [NSMutableArray array]; + for (NSTextCheckingResult *result in matches) { + NSString *namedGroupToken = [str substringWithRange:result.range]; + [namedGroupTokens addObject:namedGroupToken]; + } + return [NSArray arrayWithArray:namedGroupTokens]; +} + + ++ (NSString *)stringByRemovingNamedGroupsFromString:(NSString *)str { + NSString *modifiedStr = [str copy]; + + NSArray *namedGroupExpressions = [self namedGroupTokensForString:str]; + NSRegularExpression *parameterRegex = [NSRegularExpression regularExpressionWithPattern:DPLRouteParameterPattern + options:0 + error:nil]; + // For each of the named group expressions (including name & regex) + for (NSString *namedExpression in namedGroupExpressions) { + NSString *replacementExpression = [namedExpression copy]; + NSTextCheckingResult *foundGroupName = [[parameterRegex matchesInString:namedExpression + options:0 + range:NSMakeRange(0, namedExpression.length)] firstObject]; + // If it's a named group, remove the name + if (foundGroupName) { + NSString *stringToReplace = [namedExpression substringWithRange:foundGroupName.range]; + replacementExpression = [replacementExpression stringByReplacingOccurrencesOfString:stringToReplace + withString:@""]; + } + + // If it was a named group, without regex constraining it, put in default regex + if (replacementExpression.length == 0) { + replacementExpression = DPLURLParameterPattern; + } + + modifiedStr = [modifiedStr stringByReplacingOccurrencesOfString:namedExpression + withString:replacementExpression]; + } + + if (modifiedStr.length && !([modifiedStr characterAtIndex:0] == '/')) { + modifiedStr = [@"^" stringByAppendingString:modifiedStr]; + } + modifiedStr = [modifiedStr stringByAppendingString:@"$"]; + + return modifiedStr; +} + + ++ (NSArray *)namedGroupsForString:(NSString *)str { + NSMutableArray *groupNames = [NSMutableArray array]; + + NSArray *namedGroupExpressions = [self namedGroupTokensForString:str]; + NSRegularExpression *parameterRegex = [NSRegularExpression regularExpressionWithPattern:DPLRouteParameterPattern + options:0 + error:nil]; + + for (NSString *namedExpression in namedGroupExpressions) { + NSArray *componentMatches = [parameterRegex matchesInString:namedExpression + options:0 + range:NSMakeRange(0, namedExpression.length)]; + NSTextCheckingResult *foundGroupName = [componentMatches firstObject]; + if (foundGroupName) { + NSString *stringToReplace = [namedExpression substringWithRange:foundGroupName.range]; + NSString *variableName = [stringToReplace stringByReplacingOccurrencesOfString:@":" + withString:@""]; + + [groupNames addObject:variableName]; + } + } + return [NSArray arrayWithArray:groupNames]; +} + +@end diff --git a/DeepLinkSDK/RouteMatcher/DPLRouteMatcher.m b/DeepLinkSDK/RouteMatcher/DPLRouteMatcher.m index ac7026d..f325abe 100644 --- a/DeepLinkSDK/RouteMatcher/DPLRouteMatcher.m +++ b/DeepLinkSDK/RouteMatcher/DPLRouteMatcher.m @@ -1,15 +1,11 @@ #import "DPLRouteMatcher.h" #import "DPLDeepLink_Private.h" #import "NSString+DPLTrim.h" - -static NSString * const DPLRouteParameterPattern = @":[a-zA-Z0-9-_]+"; -static NSString * const DPLURLParameterPattern = @"([^/]+)"; +#import "DPLRegularExpression.h" @interface DPLRouteMatcher () -@property (nonatomic, copy) NSString *route; -@property (nonatomic, strong) NSRegularExpression *regex; -@property (nonatomic, strong) NSMutableArray *routeParamaterNames; +@property (nonatomic, strong) DPLRegularExpression *regexMatcher; @end @@ -27,70 +23,26 @@ - (instancetype)initWithRoute:(NSString *)route { self = [super init]; if (self) { - _route = route; + _regexMatcher = [DPLRegularExpression regularExpressionWithPattern:route]; } return self; } -- (NSRegularExpression *)regex { - if (!_regex) { - _routeParamaterNames = [NSMutableArray array]; - NSRegularExpression *parameterRegex = [NSRegularExpression regularExpressionWithPattern:DPLRouteParameterPattern - options:0 - error:nil]; - - __block NSString *modifiedRoute = [self.route copy]; - NSArray *matches = [parameterRegex matchesInString:self.route - options:0 - range:NSMakeRange(0, self.route.length)]; - - for (NSTextCheckingResult *result in matches) { - - NSString *stringToReplace = [self.route substringWithRange:result.range]; - NSString *variableName = [stringToReplace stringByReplacingOccurrencesOfString:@":" - withString:@""]; - [self.routeParamaterNames addObject:variableName]; - - modifiedRoute = [modifiedRoute stringByReplacingOccurrencesOfString:stringToReplace - withString:DPLURLParameterPattern]; - } - - modifiedRoute = [modifiedRoute stringByAppendingString:@"$"]; - _regex = [NSRegularExpression regularExpressionWithPattern:modifiedRoute - options:0 - error:nil]; - } - - return _regex; -} - - - (DPLDeepLink *)deepLinkWithURL:(NSURL *)url { DPLDeepLink *deepLink = [[DPLDeepLink alloc] initWithURL:url]; NSString *deepLinkString = [NSString stringWithFormat:@"%@%@", deepLink.URL.host, deepLink.URL.path]; - NSArray *matches = [self.regex matchesInString:deepLinkString - options:0 - range:NSMakeRange(0, deepLinkString.length)]; - if (!matches.count) { + + DPLMatchResult *matchResult = [self.regexMatcher matchResultForString:deepLinkString]; + if (!matchResult.isMatch) { return nil; } - // Set route parameters in the routeParameters dictionary - NSMutableDictionary *routeParameters = [NSMutableDictionary dictionary]; - for (NSTextCheckingResult *result in matches) { - // Begin at 1 as first range is the whole match - for (int i = 1; i < result.numberOfRanges && i <= self.routeParamaterNames.count; i++) { - NSString *parameterName = self.routeParamaterNames[i - 1]; - NSString *parameterValue = [deepLinkString substringWithRange:[result rangeAtIndex:i]]; - routeParameters[parameterName] = parameterValue; - } - } - deepLink.routeParameters = routeParameters; + deepLink.routeParameters = matchResult.namedProperties; return deepLink; } diff --git a/Rakefile b/Rakefile index c526852..e428350 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,6 @@ desc "Runs the specs" task :spec do - sh "xcodebuild test -workspace 'DeepLinkSDK.xcworkspace' -scheme 'DeepLinkSDK' -destination 'platform=iOS Simulator,name=iPhone 6,OS=8.1' | xcpretty -c" + sh "xcodebuild test -workspace 'DeepLinkSDK.xcworkspace' -scheme 'ReceiverDemo' -destination 'platform=iOS Simulator,name=iPhone 6,OS=8.1' | xcpretty -c" end desc "Synchronizes Xcode project folder with Xcode groups" diff --git a/Tests/IntegrationTests/DPLRouteHandlerIntegrationTest.m b/Tests/IntegrationTests/DPLRouteHandlerIntegrationTest.m index f2ec1b8..403299a 100644 --- a/Tests/IntegrationTests/DPLRouteHandlerIntegrationTest.m +++ b/Tests/IntegrationTests/DPLRouteHandlerIntegrationTest.m @@ -7,8 +7,7 @@ @interface DPLRouteHandlerIntegrationTest : XCTestCase @implementation DPLRouteHandlerIntegrationTest -- (void)testClassBasedRouteHandlerFlow -{ +- (void)testClassBasedRouteHandlerFlow { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"dpl://dpl.com/product/93598?"]]; [tester waitForViewWithAccessibilityLabel:@"Shiner Oktoberfest"]; [tester waitForViewWithAccessibilityLabel:@"8.99"]; diff --git a/Tests/Regex/DPLRegularExpressionSpec.m b/Tests/Regex/DPLRegularExpressionSpec.m new file mode 100644 index 0000000..f12ab76 --- /dev/null +++ b/Tests/Regex/DPLRegularExpressionSpec.m @@ -0,0 +1,47 @@ +#import "Specta.h" +#import "DPLRegularExpression.h" + +SpecBegin(DPLRegularExpression) + +describe(@"Matching named components", ^{ + + it(@"should match named components without regex", ^{ + DPLRegularExpression *expression = [DPLRegularExpression regularExpressionWithPattern:@"/hello/:do/:this/and/:that"]; + + DPLMatchResult *matchResult = [expression matchResultForString:@"/hello/dovalue/thisvalue/and/thatvalue"]; + expect(matchResult.match).to.beTruthy(); + expect(matchResult.namedProperties).to.equal(@{ @"do": @"dovalue", + @"this": @"thisvalue", + @"that": @"thatvalue" }); + }); + + it(@"should match named components with regex", ^{ + DPLRegularExpression *expression = [DPLRegularExpression regularExpressionWithPattern:@"/hello/:do([a-zA-Z]+)/:this([a-zA-Z]+)/and/:that([a-zA-Z]+)"]; + + DPLMatchResult *matchResult = [expression matchResultForString:@"/hello/dovalue/thisvalue/and/thatvalue"]; + expect(matchResult.match).to.beTruthy(); + expect(matchResult.namedProperties).to.equal(@{ @"do": @"dovalue", + @"this": @"thisvalue", + @"that": @"thatvalue" }); + }); + + it(@"should match a mixture of with and without regex", ^{ + DPLRegularExpression *expression = [DPLRegularExpression regularExpressionWithPattern:@"/hello/:do/:this([a-zA-Z]+)/and/:that([a-zA-Z]+)"]; + + DPLMatchResult *matchResult = [expression matchResultForString:@"/hello/dovalue/thisvalue/and/thatvalue"]; + expect(matchResult.match).to.beTruthy(); + expect(matchResult.namedProperties).to.equal(@{ @"do": @"dovalue", + @"this": @"thisvalue", + @"that": @"thatvalue" }); + }); + + it(@"should NOT match named components with regex that does not match", ^{ + DPLRegularExpression *expression = [DPLRegularExpression regularExpressionWithPattern:@"/hello/:do([a-zA-Z]+)/:this([0-9]+)/and/:that([a-zA-Z]+)"]; + + DPLMatchResult *matchResult = [expression matchResultForString:@"/hello/dovalue/thisvalue/and/thatvalue"]; + expect(matchResult.match).to.beFalsy(); + }); + +}); + +SpecEnd diff --git a/Tests/RouteMatcher/DPLRouteMatcherSpec.m b/Tests/RouteMatcher/DPLRouteMatcherSpec.m index a8ef623..9068f19 100644 --- a/Tests/RouteMatcher/DPLRouteMatcherSpec.m +++ b/Tests/RouteMatcher/DPLRouteMatcherSpec.m @@ -9,6 +9,18 @@ SpecBegin(DPLRouteMatcher) +describe(@"Initialization", ^{ + it(@"creates an instance with a route", ^{ + DPLRouteMatcher *routeMatcher = [DPLRouteMatcher matcherWithRoute:@"/thing/:another"]; + expect(routeMatcher).notTo.beNil(); + }); + + it(@"does not create an instance with no route", ^{ + DPLRouteMatcher *routeMatcher = [DPLRouteMatcher matcherWithRoute:@""]; + expect(routeMatcher).to.beNil(); + }); +}); + describe(@"Matching Routes", ^{ it(@"returns a deep link when a URL matches a route", ^{ @@ -16,6 +28,7 @@ NSURL *url = URLWithPath(@"/table/book"); DPLDeepLink *deepLink = [matcher deepLinkWithURL:url]; expect(deepLink).toNot.beNil(); + expect(deepLink.routeParameters).to.equal(@{}); }); it(@"returns a deep link when a URL matches a host", ^{ @@ -23,6 +36,7 @@ NSURL *url = URLWithPath(@""); DPLDeepLink *deepLink = [matcher deepLinkWithURL:url]; expect(deepLink).toNot.beNil(); + expect(deepLink.routeParameters).to.equal(@{}); }); it(@"does NOT return a deep link when a host does NOT match the URL host", ^{ @@ -108,9 +122,19 @@ NSURL *url2 = URLWithPath(@"/abc123"); DPLDeepLink *deepLink = [matcher deepLinkWithURL:url]; expect(deepLink).notTo.beNil(); + expect(deepLink.routeParameters).to.equal(@{}); DPLDeepLink *deepLink2 = [matcher deepLinkWithURL:url2]; expect(deepLink2).notTo.beNil(); + expect(deepLink2.routeParameters).to.equal(@{}); + }); + + it(@"matches a wildcard deeplink to route parameters", ^{ + DPLRouteMatcher *matcher = [DPLRouteMatcher matcherWithRoute:@"/table/:path(.*)"]; + NSURL *url = URLWithPath(@"/table/some/path/which/should/be/in/route/parameters"); + DPLDeepLink *deepLink = [matcher deepLinkWithURL:url]; + expect(deepLink).notTo.beNil(); + expect(deepLink.routeParameters).to.equal(@{@"path": @"some/path/which/should/be/in/route/parameters"}); }); it(@"matches URLs with commas", ^{ @@ -120,6 +144,46 @@ expect(deepLink.routeParameters[@"weird_comma_path_thing"]).to.equal(@"33.89,-84.46"); expect(deepLink).notTo.beNil(); }); + + it(@"returns a deep link with route parameters when a URL matches a parameterized regex route", ^{ + DPLRouteMatcher *matcher = [DPLRouteMatcher matcherWithRoute:@"/table/:table([a-zA-Z]+)/:id([0-9]+)"]; + NSURL *url = URLWithPath(@"/table/randomTableName/109"); + DPLDeepLink *deepLink = [matcher deepLinkWithURL:url]; + expect(deepLink).notTo.beNil(); + expect(deepLink.routeParameters).to.equal(@{@"table": @"randomTableName", + @"id": @"109" }); + }); + + it(@"allows some named groups to be expressed with regex and not others", ^{ + DPLRouteMatcher *matcher = [DPLRouteMatcher matcherWithRoute:@"/table/:table([a-zA-Z]+)/[a-z]+/:other([a-z]+)/:thing"]; + NSURL *url = URLWithPath(@"/table/anytable/anychair/another/anything"); + DPLDeepLink *deepLink = [matcher deepLinkWithURL:url]; + expect(deepLink).notTo.beNil(); + expect(deepLink.routeParameters).to.equal(@{@"table": @"anytable", + @"other": @"another", + @"thing": @"anything" }); + }); + + it(@"does NOT return a deep link when the URL path does not match regex table parameter", ^{ + DPLRouteMatcher *matcher = [DPLRouteMatcher matcherWithRoute:@"/table/:table([a-zA-Z]+)/:id([0-9])"]; + NSURL *url = URLWithPath(@"/table/table_name/109"); + DPLDeepLink *deepLink = [matcher deepLinkWithURL:url]; + expect(deepLink).to.beNil(); + }); + + it(@"does NOT return a deep link when the URL path does not match regex id parameter", ^{ + DPLRouteMatcher *matcher = [DPLRouteMatcher matcherWithRoute:@"/table/:table([a-zA-Z]+)/:id([0-9])"]; + NSURL *url = URLWithPath(@"/table/tableName/1a9"); + DPLDeepLink *deepLink = [matcher deepLinkWithURL:url]; + expect(deepLink).to.beNil(); + }); + + it(@"does NOT match partial strings", ^{ + DPLRouteMatcher *matcher = [DPLRouteMatcher matcherWithRoute:@"me"]; + NSURL *url = URLWithPath(@"home"); + DPLDeepLink *deepLink = [matcher deepLinkWithURL:url]; + expect(deepLink).to.beNil(); + }); }); SpecEnd diff --git a/Tests/Router/DPLDeepLinkRouterSpec.m b/Tests/Router/DPLDeepLinkRouterSpec.m index b19bfb4..cdc179f 100644 --- a/Tests/Router/DPLDeepLinkRouterSpec.m +++ b/Tests/Router/DPLDeepLinkRouterSpec.m @@ -36,7 +36,7 @@ }); it(@"does NOT register routes that are not strings", ^{ - router[@(0)] = [DPLRouteHandler class]; + router[(id)@(0)] = [DPLRouteHandler class]; expect(router[route]).to.beNil(); });