XCUIElement+FBClassChain.m 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. /**
  2. * Copyright (c) 2015-present, Facebook, Inc.
  3. * All rights reserved.
  4. *
  5. * This source code is licensed under the BSD-style license found in the
  6. * LICENSE file in the root directory of this source tree. An additional grant
  7. * of patent rights can be found in the PATENTS file in the same directory.
  8. */
  9. #import "XCUIElement+FBClassChain.h"
  10. #import "FBClassChainQueryParser.h"
  11. #import "FBXCodeCompatibility.h"
  12. #import "FBExceptions.h"
  13. @implementation XCUIElement (FBClassChain)
  14. - (NSArray<XCUIElement *> *)fb_descendantsMatchingClassChain:(NSString *)classChainQuery shouldReturnAfterFirstMatch:(BOOL)shouldReturnAfterFirstMatch
  15. {
  16. NSError *error;
  17. FBClassChain *parsedChain = [FBClassChainQueryParser parseQuery:classChainQuery error:&error];
  18. if (nil == parsedChain) {
  19. @throw [NSException exceptionWithName:FBClassChainQueryParseException reason:error.localizedDescription userInfo:error.userInfo];
  20. return nil;
  21. }
  22. NSMutableArray<FBClassChainItem *> *lookupChain = parsedChain.elements.mutableCopy;
  23. FBClassChainItem *chainItem = lookupChain.firstObject;
  24. XCUIElement *currentRoot = self;
  25. XCUIElementQuery *query = [currentRoot fb_queryWithChainItem:chainItem query:nil];
  26. [lookupChain removeObjectAtIndex:0];
  27. while (lookupChain.count > 0) {
  28. BOOL isRootChanged = NO;
  29. if (nil != chainItem.position) {
  30. // It is necessary to resolve the query if intermediate element index is not zero or one,
  31. // because predicates don't support search by indexes
  32. NSArray<XCUIElement *> *currentRootMatch = [self.class fb_matchingElementsWithItem:chainItem
  33. query:query
  34. shouldReturnAfterFirstMatch:nil];
  35. if (0 == currentRootMatch.count) {
  36. return @[];
  37. }
  38. currentRoot = currentRootMatch.firstObject;
  39. isRootChanged = YES;
  40. }
  41. chainItem = [lookupChain firstObject];
  42. query = [currentRoot fb_queryWithChainItem:chainItem query:isRootChanged ? nil : query];
  43. [lookupChain removeObjectAtIndex:0];
  44. }
  45. return [self.class fb_matchingElementsWithItem:chainItem
  46. query:query
  47. shouldReturnAfterFirstMatch:@(shouldReturnAfterFirstMatch)];
  48. }
  49. - (XCUIElementQuery *)fb_queryWithChainItem:(FBClassChainItem *)item query:(nullable XCUIElementQuery *)query
  50. {
  51. if (item.isDescendant) {
  52. if (query) {
  53. query = [query descendantsMatchingType:item.type];
  54. } else {
  55. query = [self.fb_query descendantsMatchingType:item.type];
  56. }
  57. } else {
  58. if (query) {
  59. query = [query childrenMatchingType:item.type];
  60. } else {
  61. query = [self.fb_query childrenMatchingType:item.type];
  62. }
  63. }
  64. if (item.predicates) {
  65. for (FBAbstractPredicateItem *predicate in item.predicates) {
  66. if ([predicate isKindOfClass:FBSelfPredicateItem.class]) {
  67. query = [query matchingPredicate:predicate.value];
  68. } else if ([predicate isKindOfClass:FBDescendantPredicateItem.class]) {
  69. query = [query containingPredicate:predicate.value];
  70. }
  71. }
  72. }
  73. return query;
  74. }
  75. + (NSArray<XCUIElement *> *)fb_matchingElementsWithItem:(FBClassChainItem *)item query:(XCUIElementQuery *)query shouldReturnAfterFirstMatch:(nullable NSNumber *)shouldReturnAfterFirstMatch
  76. {
  77. if (1 == item.position.integerValue || (0 == item.position.integerValue && shouldReturnAfterFirstMatch.boolValue)) {
  78. XCUIElement *result = query.fb_firstMatch;
  79. return result ? @[result] : @[];
  80. }
  81. NSArray<XCUIElement *> *allMatches = query.fb_allMatches;
  82. if (0 == item.position.integerValue) {
  83. return allMatches;
  84. }
  85. if (allMatches.count >= (NSUInteger)ABS(item.position.integerValue)) {
  86. return item.position.integerValue > 0
  87. ? @[[allMatches objectAtIndex:item.position.integerValue - 1]]
  88. : @[[allMatches objectAtIndex:allMatches.count + item.position.integerValue]];
  89. }
  90. return @[];
  91. }
  92. @end