diff --git a/Igor.xcodeproj/project.pbxproj b/Igor.xcodeproj/project.pbxproj index 8a16a92..64d3b13 100644 --- a/Igor.xcodeproj/project.pbxproj +++ b/Igor.xcodeproj/project.pbxproj @@ -37,6 +37,15 @@ 2DCE893715193E8900B975EE /* DEChainParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DCE893515193E8900B975EE /* DEChainParser.m */; }; 2DCE893B15193F6100B975EE /* DEInstanceParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DCE893915193F6100B975EE /* DEInstanceParser.h */; }; 2DCE893C15193F6100B975EE /* DEInstanceParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DCE893A15193F6100B975EE /* DEInstanceParser.m */; }; + 54AC483717BB1E7B007B0BB4 /* DEPositionParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 54AC483517BB1E7B007B0BB4 /* DEPositionParser.h */; }; + 54AC483817BB1E7B007B0BB4 /* DEPositionParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 54AC483617BB1E7B007B0BB4 /* DEPositionParser.m */; }; + 54AC483B17BB1FE1007B0BB4 /* DEPositionMatcher.h in Headers */ = {isa = PBXBuildFile; fileRef = 54AC483917BB1FE1007B0BB4 /* DEPositionMatcher.h */; }; + 54AC483C17BB1FE1007B0BB4 /* DEPositionMatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 54AC483A17BB1FE1007B0BB4 /* DEPositionMatcher.m */; }; + 54AC483E17BB32B9007B0BB4 /* PositionMatcherTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 54AC483D17BB32B9007B0BB4 /* PositionMatcherTests.m */; }; + 54AC484017BB37A4007B0BB4 /* PositionPatternTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 54AC483F17BB37A4007B0BB4 /* PositionPatternTests.m */; }; + 54C9DBFD17BB227E00902BF2 /* PositionParserTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 54C9DBFB17BB227E00902BF2 /* PositionParserTests.m */; }; + 54C9DBFE17BB239D00902BF2 /* DEPositionParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 54AC483617BB1E7B007B0BB4 /* DEPositionParser.m */; }; + 54C9DBFF17BB23A200902BF2 /* DEPositionMatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 54AC483A17BB1FE1007B0BB4 /* DEPositionMatcher.m */; }; E621659DB794A5D612DCD443 /* DEQueryScanner.m in Sources */ = {isa = PBXBuildFile; fileRef = E621659DB794A5D612DCD442 /* DEQueryScanner.m */; }; E621659DB794A5D612DCD445 /* DEQueryScanner.h in Headers */ = {isa = PBXBuildFile; fileRef = E621659DB794A5D612DCD444 /* DEQueryScanner.h */; }; E621659DB794A5D612DCD449 /* DEChainMatcher.h in Headers */ = {isa = PBXBuildFile; fileRef = E621659DB794A5D612DCD448 /* DEChainMatcher.h */; }; @@ -165,6 +174,13 @@ 2DCE893915193F6100B975EE /* DEInstanceParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DEInstanceParser.h; sourceTree = ""; }; 2DCE893A15193F6100B975EE /* DEInstanceParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DEInstanceParser.m; sourceTree = ""; }; 2DCF9766147ED3FD003B0F38 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 54AC483517BB1E7B007B0BB4 /* DEPositionParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DEPositionParser.h; sourceTree = ""; }; + 54AC483617BB1E7B007B0BB4 /* DEPositionParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DEPositionParser.m; sourceTree = ""; }; + 54AC483917BB1FE1007B0BB4 /* DEPositionMatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DEPositionMatcher.h; sourceTree = ""; }; + 54AC483A17BB1FE1007B0BB4 /* DEPositionMatcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DEPositionMatcher.m; sourceTree = ""; }; + 54AC483D17BB32B9007B0BB4 /* PositionMatcherTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PositionMatcherTests.m; sourceTree = ""; }; + 54AC483F17BB37A4007B0BB4 /* PositionPatternTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PositionPatternTests.m; sourceTree = ""; }; + 54C9DBFB17BB227E00902BF2 /* PositionParserTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PositionParserTests.m; sourceTree = ""; }; E621659DB794A5D612DCD425 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; E621659DB794A5D612DCD437 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; }; E621659DB794A5D612DCD439 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; @@ -333,6 +349,7 @@ 2D695CCA147CD1D200AF4115 /* ClassPatternTests.m */, E621659DB794A5D612DCD500 /* ChildPatternTests.m */, E621659DB794A5D612DCD490 /* BranchPatternTests.m */, + 54AC483F17BB37A4007B0BB4 /* PositionPatternTests.m */, ); path = acceptance; sourceTree = ""; @@ -378,6 +395,7 @@ E621659DB794A5D612DCD4AB /* DescendantCombinatorTests.m */, E621659DB794A5D612DCD4DD /* ChildCombinatorTests.m */, E621659DB794A5D612DCD474 /* BranchMatcherTests.m */, + 54AC483D17BB32B9007B0BB4 /* PositionMatcherTests.m */, ); path = matchers; sourceTree = ""; @@ -392,6 +410,7 @@ E621659DB794A5D612DCD504 /* CombinatorParserTests.m */, E621659DB794A5D612DCD46C /* ClassParserTests.m */, E621659DB794A5D612DCD4B5 /* ChainParserTests.m */, + 54C9DBFB17BB227E00902BF2 /* PositionParserTests.m */, ); path = parser; sourceTree = ""; @@ -436,6 +455,8 @@ E621659DB794A5D612DCD448 /* DEChainMatcher.h */, E621659DB794A5D612DCD478 /* DEBranchMatcher.m */, E621659DB794A5D612DCD47A /* DEBranchMatcher.h */, + 54AC483A17BB1FE1007B0BB4 /* DEPositionMatcher.m */, + 54AC483917BB1FE1007B0BB4 /* DEPositionMatcher.h */, ); path = matchers; sourceTree = ""; @@ -464,6 +485,8 @@ 2DCE893415193E8900B975EE /* DEChainParser.h */, E621659DB794A5D612DCD499 /* DEBranchParser.m */, E621659DB794A5D612DCD49B /* DEBranchParser.h */, + 54AC483617BB1E7B007B0BB4 /* DEPositionParser.m */, + 54AC483517BB1E7B007B0BB4 /* DEPositionParser.h */, ); path = parser; sourceTree = ""; @@ -484,6 +507,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 54AC483717BB1E7B007B0BB4 /* DEPositionParser.h in Headers */, 2D9649E2147DA21400C40219 /* Igor-Prefix.pch in Headers */, 2D496EA51486A513007E1FDE /* DEPredicateMatcher.h in Headers */, 2DCE893615193E8900B975EE /* DEChainParser.h in Headers */, @@ -508,6 +532,7 @@ E621659DB794A5D612DCD4EC /* DEKindOfClassMatcher.h in Headers */, E621659DB794A5D612DCD4ED /* DEMatcher.h in Headers */, E621659DB794A5D612DCD4EE /* DEMemberOfClassMatcher.h in Headers */, + 54AC483B17BB1FE1007B0BB4 /* DEPositionMatcher.h in Headers */, E621659DB794A5D612DCD4EF /* DEIgor.h in Headers */, E621659DB794A5D612DCD4F7 /* DEIdentifierMatcher.h in Headers */, E621659DB794A5D612DCD4FB /* DEIdentifierParser.h in Headers */, @@ -604,6 +629,7 @@ 2D695CDA147CD1D200AF4115 /* KindOfClassMatcherTests.m in Sources */, 2D695CDB147CD1D200AF4115 /* MemberOfClassMatcherTests.m in Sources */, 2D695CDD147CD1D200AF4115 /* PredicateParserTests.m in Sources */, + 54AC483E17BB32B9007B0BB4 /* PositionMatcherTests.m in Sources */, 2D695CDE147CD1D200AF4115 /* PredicatePatternTests.m in Sources */, 2D3E1F3F1509B98200D62300 /* DescendantPatternTests.m in Sources */, 2D99DB351517E9C900244226 /* SubjectMarkerTests.m in Sources */, @@ -655,6 +681,10 @@ E621659DB794A5D612DCD53A /* DEQueryScanner.m in Sources */, E621659DB794A5D612DCD53B /* DEIgor.m in Sources */, E621659DB794A5D612DCD53C /* DECombinatorParser.m in Sources */, + 54C9DBFD17BB227E00902BF2 /* PositionParserTests.m in Sources */, + 54C9DBFE17BB239D00902BF2 /* DEPositionParser.m in Sources */, + 54C9DBFF17BB23A200902BF2 /* DEPositionMatcher.m in Sources */, + 54AC484017BB37A4007B0BB4 /* PositionPatternTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -670,9 +700,11 @@ 2D695CB7147CD1BA00AF4115 /* DEKindOfClassMatcher.m in Sources */, 2D695CBA147CD1BA00AF4115 /* DEMemberOfClassMatcher.m in Sources */, 2D695CC4147CD1BA00AF4115 /* DEPredicateParser.m in Sources */, + 54AC483C17BB1FE1007B0BB4 /* DEPositionMatcher.m in Sources */, 2D496EA61486A513007E1FDE /* DEPredicateMatcher.m in Sources */, 2DCE893715193E8900B975EE /* DEChainParser.m in Sources */, 2DCE893C15193F6100B975EE /* DEInstanceParser.m in Sources */, + 54AC483817BB1E7B007B0BB4 /* DEPositionParser.m in Sources */, E621659DB794A5D612DCD443 /* DEQueryScanner.m in Sources */, E621659DB794A5D612DCD45F /* DEUniversalMatcher.m in Sources */, E621659DB794A5D612DCD479 /* DEBranchMatcher.m in Sources */, diff --git a/Igor.xcodeproj/project.xcworkspace/xcshareddata/Igor.xccheckout b/Igor.xcodeproj/project.xcworkspace/xcshareddata/Igor.xccheckout new file mode 100644 index 0000000..f106dc5 --- /dev/null +++ b/Igor.xcodeproj/project.xcworkspace/xcshareddata/Igor.xccheckout @@ -0,0 +1,41 @@ + + + + + IDESourceControlProjectFavoriteDictionaryKey + + IDESourceControlProjectIdentifier + 774B6FC9-4E79-4024-BFE0-29F379B370EC + IDESourceControlProjectName + Igor + IDESourceControlProjectOriginsDictionary + + B3806047-E9A6-404E-91F7-D509622D3412 + ssh://github.com/siuying/igor.git + + IDESourceControlProjectPath + Igor.xcodeproj/project.xcworkspace + IDESourceControlProjectRelativeInstallPathDictionary + + B3806047-E9A6-404E-91F7-D509622D3412 + ../.. + + IDESourceControlProjectURL + ssh://github.com/siuying/igor.git + IDESourceControlProjectVersion + 110 + IDESourceControlProjectWCCIdentifier + B3806047-E9A6-404E-91F7-D509622D3412 + IDESourceControlProjectWCConfigurations + + + IDESourceControlRepositoryExtensionIdentifierKey + public.vcs.git + IDESourceControlWCCIdentifierKey + B3806047-E9A6-404E-91F7-D509622D3412 + IDESourceControlWCCName + igor + + + + diff --git a/Igor.xcodeproj/xcshareddata/xcschemes/Igor.xcscheme b/Igor.xcodeproj/xcshareddata/xcschemes/Igor.xcscheme index fd92805..ff6c4aa 100644 --- a/Igor.xcodeproj/xcshareddata/xcschemes/Igor.xcscheme +++ b/Igor.xcodeproj/xcshareddata/xcschemes/Igor.xcscheme @@ -14,7 +14,7 @@ diff --git a/igor/engine/DEIgor.m b/igor/engine/DEIgor.m index 02a4d5e..c7da66a 100644 --- a/igor/engine/DEIgor.m +++ b/igor/engine/DEIgor.m @@ -7,6 +7,7 @@ #import "DEPredicateParser.h" #import "DEBranchParser.h" #import "DEIdentifierParser.h" +#import "DEPositionParser.h" #import "DEDescendantCombinator.h" #import "DEMatcher.h" @@ -15,13 +16,13 @@ @implementation DEIgor { } - (NSArray *)findViewsThatMatchMatcher:(id )matcher inTree:(UIView *)tree { - NSMutableSet *matchingViews = [NSMutableSet set]; + NSMutableArray *matchingViews = [NSMutableArray array]; NSMutableArray *allViews = [NSMutableArray arrayWithObject:tree]; [allViews addObjectsFromArray:[[DEDescendantCombinator new] relativesOfView:tree]]; for (UIView *view in allViews) { if ([matcher matchesView:view]) [matchingViews addObject:view]; } - return [matchingViews allObjects]; + return [[NSOrderedSet orderedSetWithArray:matchingViews] array]; } - (NSArray *)findViewsThatMatchQuery:(NSString *)query inTree:(UIView *)tree { @@ -35,7 +36,8 @@ + (DEIgor *)igor { id classParser = [DEClassParser new]; id predicateParser = [DEPredicateParser new]; id identifierParser = [DEIdentifierParser new]; - NSArray *simpleParsers = [NSArray arrayWithObjects:identifierParser, predicateParser, nil]; + id positionParser = [DEPositionParser new]; + NSArray *simpleParsers = [NSArray arrayWithObjects:identifierParser, predicateParser, positionParser, nil]; id instanceParser = [DEInstanceParser parserWithClassParser:classParser simpleParsers:simpleParsers]; id combinatorParser = [DECombinatorParser new]; diff --git a/igor/matchers/DEPositionMatcher.h b/igor/matchers/DEPositionMatcher.h new file mode 100644 index 0000000..1b91fd6 --- /dev/null +++ b/igor/matchers/DEPositionMatcher.h @@ -0,0 +1,14 @@ +#import "DEChainMatcher.h" +#import "DEMatcher.h" + +@interface DEPositionMatcher : NSObject + +@property (nonatomic, copy) NSString* position; + +- (instancetype)initWithPosition:(NSString *)position; + +- (BOOL)matchesView:(UIView *)view; + ++ (DEPositionMatcher *)matcherForPosition:(NSString*)position; + +@end diff --git a/igor/matchers/DEPositionMatcher.m b/igor/matchers/DEPositionMatcher.m new file mode 100644 index 0000000..4964f9d --- /dev/null +++ b/igor/matchers/DEPositionMatcher.m @@ -0,0 +1,76 @@ +// +// DEPositionMatcher.m +// Igor +// +// Created by Chong Francis on 13年8月14日. +// +// + +#import "DEPositionMatcher.h" + +@interface DEPositionMatcher() +@end + +@implementation DEPositionMatcher + +- (NSString *)description { + return [NSString stringWithFormat:@":%@", self.position]; +} + +- (instancetype)initWithPosition:(NSString *)position { + self = [super init]; + if (self) { + _position = [position copy]; + } + return self; +} + +- (BOOL)matchesView:(UIView *)view { + if ([self.position isEqualToString:@"root"] && [self isRootView:view]) { + return YES; + } + + if ([self.position isEqualToString:@"empty"] && [self isEmptyView:view]) { + return YES; + } + + if ([self.position isEqualToString:@"first-child"] && [self isFirstChild:view]) { + return YES; + } + + if ([self.position isEqualToString:@"last-child"] && [self isLastChild:view]) { + return YES; + } + + if ([self.position isEqualToString:@"only-child"] && [self isOnlyChild:view]) { + return YES; + } + + return NO; +} + +- (BOOL) isRootView:(UIView *)view { + return view.superview == [UIApplication sharedApplication].keyWindow; +} + +- (BOOL) isEmptyView:(UIView *)view { + return [view.subviews count] == 0; +} + +- (BOOL) isFirstChild:(UIView *)view { + return view.superview && view.superview.subviews[0] == view; +} + +- (BOOL) isLastChild:(UIView *)view { + return view.superview && [view.superview.subviews lastObject] == view; +} + +- (BOOL) isOnlyChild:(UIView *)view { + return view.superview && view.superview.subviews.count == 1; +} + ++ (DEPositionMatcher *)matcherForPosition:(NSString*)position { + return [[DEPositionMatcher alloc] initWithPosition:position]; +} + +@end diff --git a/igor/parser/DEIgorParserException.h b/igor/parser/DEIgorParserException.h index 531c3c1..3b477d6 100644 --- a/igor/parser/DEIgorParserException.h +++ b/igor/parser/DEIgorParserException.h @@ -1,4 +1,4 @@ -@interface DEIgorParserException +@interface DEIgorParserException : NSException + (NSException *)exceptionWithReason:(NSString *)reason scanner:(NSScanner *)scanner; diff --git a/igor/parser/DEPositionParser.h b/igor/parser/DEPositionParser.h new file mode 100644 index 0000000..ad0ff54 --- /dev/null +++ b/igor/parser/DEPositionParser.h @@ -0,0 +1,5 @@ +#import +#import "DEPatternParser.h" + +@interface DEPositionParser : NSObject +@end diff --git a/igor/parser/DEPositionParser.m b/igor/parser/DEPositionParser.m new file mode 100644 index 0000000..6f3cac2 --- /dev/null +++ b/igor/parser/DEPositionParser.m @@ -0,0 +1,33 @@ +#import "DEPositionParser.h" +#import "DEQueryScanner.h" +#import "DEUniversalMatcher.h" +#import "DEPositionMatcher.h" + +static NSSet* _positionPatterns; + +@implementation DEPositionParser + ++(void) initialize { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _positionPatterns = [NSSet setWithArray:@[@"empty", @"first-child", @"last-child", @"only-child"]]; + }); +} + +- (id )parseMatcherFromScanner:(id )scanner { + if (![scanner skipString:@":"]) { + return nil; + } + + NSString *expression; + + if (![scanner scanPositionPatternIntoString:&expression]) + [scanner failBecause:@"Expected a position pattern after the :"]; + + if (![_positionPatterns containsObject:expression]) + [scanner failBecause:[NSString stringWithFormat:@"Expected to be one of valid position pattern %@, get: %@", [_positionPatterns allObjects], [scanner description]]]; + + return [DEPositionMatcher matcherForPosition:expression]; +} + +@end diff --git a/igor/parser/DEQueryScanner.h b/igor/parser/DEQueryScanner.h index 731e28f..c0945b7 100644 --- a/igor/parser/DEQueryScanner.h +++ b/igor/parser/DEQueryScanner.h @@ -6,6 +6,8 @@ - (BOOL)scanNameIntoString:(NSString **)destination; +- (BOOL)scanPositionPatternIntoString:(NSString **)destination; + - (BOOL)scanUpToString:(NSString *)string intoString:(NSString **)destination; - (BOOL)skipString:(NSString *)string; diff --git a/igor/parser/DEQueryScanner.m b/igor/parser/DEQueryScanner.m index 88683f9..d24be66 100644 --- a/igor/parser/DEQueryScanner.m +++ b/igor/parser/DEQueryScanner.m @@ -34,6 +34,12 @@ - (BOOL)scanNameIntoString:(NSString **)destination { return [self.scanner scanCharactersFromSet:nameCharacterSet intoString:destination]; } +- (BOOL)scanPositionPatternIntoString:(NSString **)destination { + NSMutableCharacterSet *positionCharacterSet = [NSMutableCharacterSet alphanumericCharacterSet]; + [positionCharacterSet addCharactersInString:@"-"]; + return [self.scanner scanCharactersFromSet:positionCharacterSet intoString:destination]; +} + + (id )scannerWithString:(NSString *)queryString { return [[self alloc] initWithString:queryString]; } diff --git a/test/acceptance/ClassPatternTests.m b/test/acceptance/ClassPatternTests.m index bfa7500..a2fcb79 100644 --- a/test/acceptance/ClassPatternTests.m +++ b/test/acceptance/ClassPatternTests.m @@ -81,4 +81,21 @@ - (void)testKindOfClassPattern { assertThat(matchingViews, isNot(hasItem(viewOfUnrelatedClass))); } +- (void)testMatchedSubviewsOrder { + NSString *query = @"UIButton"; + + UIView *root = [ViewFactory viewWithName:@"root"]; + UIButton *b1 = [ViewFactory button]; + UIButton *b2 = [ViewFactory button]; + UIButton *b3 = [ViewFactory button]; + + [root addSubview:b1]; + [root addSubview:b2]; + [root addSubview:b3]; + + NSArray *matchingViews = [igor findViewsThatMatchQuery:query inTree:root]; + assertThat(matchingViews, containsInAnyOrder(b1, b2, b3, nil)); + assertThat(matchingViews, equalTo(@[b1, b2, b3])); +} + @end diff --git a/test/acceptance/PositionPatternTests.m b/test/acceptance/PositionPatternTests.m new file mode 100644 index 0000000..462060a --- /dev/null +++ b/test/acceptance/PositionPatternTests.m @@ -0,0 +1,95 @@ +#import "DEIgor.h" +#import "ViewFactory.h" + +@interface PositionPatternTests : SenTestCase + +@end + +@implementation PositionPatternTests { + DEIgor *igor; + UIView *view; + UIView* header; + UIView* footer; + UIView* form; + UIButton* button; +} + +- (void)setUp +{ + [super setUp]; + + igor = [DEIgor igor]; + view = [ViewFactory viewWithName:@"root"]; + + header = [ViewFactory viewWithName:@"header"]; + [header addSubview:[ViewFactory viewWithName:@"subview11"]]; + [header addSubview:[ViewFactory viewWithName:@"subview12"]]; + [header addSubview:[ViewFactory viewWithName:@"subview13"]]; + [view addSubview:header]; + + form = [ViewFactory viewWithName:@"form"]; + [form addSubview:[ViewFactory viewWithName:@"subview21"]]; + [form addSubview:[ViewFactory viewWithName:@"subview22"]]; + [form addSubview:[ViewFactory viewWithName:@"subview23"]]; + [view addSubview:form]; + + footer = [ViewFactory viewWithName:@"footer"]; + [footer addSubview:[ViewFactory viewWithName:@"subview31"]]; + [view addSubview:footer]; + + button = [ViewFactory button]; + [view addSubview:button]; + +} + +- (void)testFirstChild +{ + NSArray *matchingViews = [igor findViewsThatMatchQuery:@"#header :first-child" inTree:view]; + assertThat(matchingViews, equalTo(@[header.subviews[0]])); + + matchingViews = [igor findViewsThatMatchQuery:@"#root > :first-child" inTree:view]; + assertThat(matchingViews, equalTo(@[header])); + + matchingViews = [igor findViewsThatMatchQuery:@"#root > UIView > :first-child" inTree:view]; + assertThat(matchingViews, equalTo(@[header.subviews[0], form.subviews[0], footer.subviews[0]])); +} + +- (void)testLastChild +{ + NSArray *matchingViews = [igor findViewsThatMatchQuery:@"#header :last-child" inTree:view]; + assertThat(matchingViews, equalTo(@[header.subviews[2]])); + + matchingViews = [igor findViewsThatMatchQuery:@"#root > :last-child" inTree:view]; + assertThat(matchingViews, equalTo(@[button])); + + matchingViews = [igor findViewsThatMatchQuery:@"#root > UIView > :last-child" inTree:view]; + assertThat(matchingViews, equalTo(@[header.subviews[2], form.subviews[2], footer.subviews[0]])); +} + +- (void)testEmpty +{ + NSArray *matchingViews = [igor findViewsThatMatchQuery:@"#root > :empty" inTree:view]; + assertThat(matchingViews, equalTo(@[button])); + + matchingViews = [igor findViewsThatMatchQuery:@"*:empty" inTree:view]; + assertThat(matchingViews, containsInAnyOrder(header.subviews[0], header.subviews[1], header.subviews[2], + form.subviews[0], form.subviews[1], form.subviews[2], + footer.subviews[0], button, nil)); + + matchingViews = [igor findViewsThatMatchQuery:@"UIView > :first-child > UIView:empty" inTree:view]; + assertThat(matchingViews, containsInAnyOrder(header.subviews[0], header.subviews[1], header.subviews[2], nil)); +} + +- (void)testOnlyChild +{ + NSArray *matchingViews = [igor findViewsThatMatchQuery:@"#header > :only-child" inTree:view]; + assertThat(matchingViews, empty()); + + matchingViews = [igor findViewsThatMatchQuery:@"#form > :only-child" inTree:view]; + assertThat(matchingViews, empty()); + + matchingViews = [igor findViewsThatMatchQuery:@"#footer > :only-child" inTree:view]; + assertThat(matchingViews, equalTo(@[footer.subviews[0]])); +} + +@end diff --git a/test/matchers/PositionMatcherTests.m b/test/matchers/PositionMatcherTests.m new file mode 100644 index 0000000..b173671 --- /dev/null +++ b/test/matchers/PositionMatcherTests.m @@ -0,0 +1,52 @@ +#import "ViewFactory.h" +#import "MatchesView.h" +#import "DEMatcher.h" +#import "DEIdentifierMatcher.h" +#import "DEPositionMatcher.h" + +@interface PositionMatcherTests : SenTestCase + +@end + +@implementation PositionMatcherTests + +- (void)setUp +{ + [super setUp]; +} + +- (void)tearDown +{ + [super tearDown]; +} + +- (void)testExample +{ + UIView *root = [ViewFactory viewWithName:@"root"]; + UIView *subview1 = [ViewFactory viewWithName:@"subview1"]; + UIView *subview2 = [ViewFactory viewWithName:@"subview2"]; + UIView *subview3 = [ViewFactory viewWithName:@"subview3"]; + UIView *subview31 = [ViewFactory viewWithName:@"subview31"]; + [root addSubview:subview1]; + [root addSubview:subview2]; + [root addSubview:subview3]; + [subview3 addSubview:subview31]; + + id emptyMatcher = [DEPositionMatcher matcherForPosition:@"empty"]; + assertThat(emptyMatcher, [MatchesView view:subview1]); + assertThat(emptyMatcher, isNot([MatchesView view:subview3])); + + id firstChildMatcher = [DEPositionMatcher matcherForPosition:@"first-child"]; + assertThat(firstChildMatcher, [MatchesView view:subview1]); + assertThat(firstChildMatcher, isNot([MatchesView view:subview3])); + + id lastChildMatcher = [DEPositionMatcher matcherForPosition:@"last-child"]; + assertThat(lastChildMatcher, [MatchesView view:subview3]); + assertThat(lastChildMatcher, isNot([MatchesView view:subview1])); + + id onlyChildMatcher = [DEPositionMatcher matcherForPosition:@"only-child"]; + assertThat(onlyChildMatcher, [MatchesView view:subview31]); + assertThat(onlyChildMatcher, isNot([MatchesView view:subview3])); +} + +@end diff --git a/test/parser/PositionParserTests.m b/test/parser/PositionParserTests.m new file mode 100644 index 0000000..765f1a8 --- /dev/null +++ b/test/parser/PositionParserTests.m @@ -0,0 +1,35 @@ +#import +#import "DEChainParser.h" +#import "DEUniversalMatcher.h" +#import "DEPositionParser.h" +#import "DEMatcher.h" +#import "DEPositionMatcher.h" +#import "DEQueryScanner.h" + +@interface PositionParserTests : SenTestCase +@end + +@implementation PositionParserTests { + DEQueryScanner* scanner; + DEPositionParser* parser; +} + +- (void)setUp { + parser = [DEPositionParser new]; +} + +- (void)testParsesPositionPatterns { + NSArray* patterns = @[@":empty", @":first-child", @":last-child", @":only-child"]; + NSArray* position = @[@"empty", @"first-child", @"last-child", @"only-child"]; + [patterns enumerateObjectsUsingBlock:^(NSString *expression, NSUInteger idx, BOOL *stop) { + scanner = [DEQueryScanner scannerWithString:expression]; + DEPositionMatcher* matcher = (DEPositionMatcher*) [parser parseMatcherFromScanner:scanner]; + assertThat(matcher, instanceOf([DEPositionMatcher class])); + assertThat(matcher.position, equalTo(position[idx])); + }]; + + scanner = [DEQueryScanner scannerWithString:@":invalid"]; + STAssertThrows([parser parseMatcherFromScanner:scanner], @"should failed with invalid pattern"); +} + +@end