diff options
-rw-r--r-- | platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm | 212 | ||||
-rw-r--r-- | platform/darwin/src/NSCompoundPredicate+MGLAdditions.mm | 53 | ||||
-rw-r--r-- | platform/darwin/src/NSExpression+MGLAdditions.mm | 25 | ||||
-rw-r--r-- | platform/darwin/src/NSPredicate+MGLAdditions.mm | 75 | ||||
-rw-r--r-- | platform/darwin/test/MGLFilterTests.mm | 194 | ||||
-rw-r--r-- | platform/darwin/test/MGLPredicateTests.mm | 416 | ||||
-rw-r--r-- | platform/ios/ios.xcodeproj/project.pbxproj | 15 | ||||
-rw-r--r-- | platform/macos/macos.xcodeproj/project.pbxproj | 12 |
8 files changed, 696 insertions, 306 deletions
diff --git a/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm b/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm index 19c264aa40..58390b0b81 100644 --- a/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm +++ b/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm @@ -7,95 +7,189 @@ - (mbgl::style::Filter)mgl_filter { + NSExpression *leftExpression = self.leftExpression; + NSExpression *rightExpression = self.rightExpression; + NSExpressionType leftType = leftExpression.expressionType; + NSExpressionType rightType = rightExpression.expressionType; + BOOL isReversed = ((leftType == NSConstantValueExpressionType || leftType == NSAggregateExpressionType) + && rightType == NSKeyPathExpressionType); switch (self.predicateOperatorType) { case NSEqualToPredicateOperatorType: { - if (self.rightExpression.constantValue) - { - auto filter = mbgl::style::EqualsFilter(); - filter.key = self.leftExpression.keyPath.UTF8String; - filter.value = self.rightExpression.mgl_filterValue; - return filter; - } - else - { - auto filter = mbgl::style::NotHasFilter(); - filter.key = self.leftExpression.keyPath.UTF8String; - return filter; + mbgl::style::EqualsFilter eqFilter; + eqFilter.key = self.mgl_keyPath.UTF8String; + eqFilter.value = self.mgl_constantValue; + + // Convert == nil to NotHasFilter. + if (eqFilter.value.is<mbgl::NullValue>()) { + mbgl::style::NotHasFilter notHasFilter; + notHasFilter.key = eqFilter.key; + return notHasFilter; } + + return eqFilter; } case NSNotEqualToPredicateOperatorType: { - if (self.rightExpression.constantValue) - { - auto filter = mbgl::style::NotEqualsFilter(); - filter.key = self.leftExpression.keyPath.UTF8String; - filter.value = self.rightExpression.mgl_filterValue; - return filter; - } - else - { - auto filter = mbgl::style::HasFilter(); - filter.key = self.leftExpression.keyPath.UTF8String; - return filter; + mbgl::style::NotEqualsFilter neFilter; + neFilter.key = self.mgl_keyPath.UTF8String; + neFilter.value = self.mgl_constantValue; + + // Convert != nil to HasFilter. + if (neFilter.value.is<mbgl::NullValue>()) { + mbgl::style::HasFilter hasFilter; + hasFilter.key = neFilter.key; + return hasFilter; } + + return neFilter; } case NSGreaterThanPredicateOperatorType: { - auto filter = mbgl::style::GreaterThanFilter(); - filter.key = self.leftExpression.keyPath.UTF8String; - filter.value = self.rightExpression.mgl_filterValue; - return filter; + if (isReversed) { + mbgl::style::LessThanFilter ltFilter; + ltFilter.key = self.mgl_keyPath.UTF8String; + ltFilter.value = self.mgl_constantValue; + return ltFilter; + } else { + mbgl::style::GreaterThanFilter gtFilter; + gtFilter.key = self.mgl_keyPath.UTF8String; + gtFilter.value = self.mgl_constantValue; + return gtFilter; + } } case NSGreaterThanOrEqualToPredicateOperatorType: { - auto filter = mbgl::style::GreaterThanEqualsFilter(); - filter.key = self.leftExpression.keyPath.UTF8String; - filter.value = self.rightExpression.mgl_filterValue; - return filter; + if (isReversed) { + mbgl::style::LessThanEqualsFilter lteFilter; + lteFilter.key = self.mgl_keyPath.UTF8String; + lteFilter.value = self.mgl_constantValue; + return lteFilter; + } else { + mbgl::style::GreaterThanEqualsFilter gteFilter; + gteFilter.key = self.mgl_keyPath.UTF8String; + gteFilter.value = self.mgl_constantValue; + return gteFilter; + } } case NSLessThanPredicateOperatorType: { - auto filter = mbgl::style::LessThanFilter(); - filter.key = self.leftExpression.keyPath.UTF8String; - filter.value = self.rightExpression.mgl_filterValue; - return filter; + if (isReversed) { + mbgl::style::GreaterThanFilter gtFilter; + gtFilter.key = self.mgl_keyPath.UTF8String; + gtFilter.value = self.mgl_constantValue; + return gtFilter; + } else { + mbgl::style::LessThanFilter ltFilter; + ltFilter.key = self.mgl_keyPath.UTF8String; + ltFilter.value = self.mgl_constantValue; + return ltFilter; + } } case NSLessThanOrEqualToPredicateOperatorType: { - auto filter = mbgl::style::LessThanEqualsFilter(); - filter.key = self.leftExpression.keyPath.UTF8String; - filter.value = self.rightExpression.mgl_filterValue; - return filter; + if (isReversed) { + mbgl::style::GreaterThanEqualsFilter gteFilter; + gteFilter.key = self.mgl_keyPath.UTF8String; + gteFilter.value = self.mgl_constantValue; + return gteFilter; + } else { + mbgl::style::LessThanEqualsFilter lteFilter; + lteFilter.key = self.mgl_keyPath.UTF8String; + lteFilter.value = self.mgl_constantValue; + return lteFilter; + } } case NSInPredicateOperatorType: { - auto filter = mbgl::style::InFilter(); - filter.key = self.leftExpression.keyPath.UTF8String; - filter.values = self.rightExpression.mgl_filterValues; - return filter; + if (isReversed) { + if (leftType == NSConstantValueExpressionType && [leftExpression.constantValue isKindOfClass:[NSString class]]) { + [NSException raise:NSInvalidArgumentException + format:@"CONTAINS not supported for string comparison."]; + } + [NSException raise:NSInvalidArgumentException + format:@"Predicate cannot compare values IN attribute."]; + } + mbgl::style::InFilter inFilter; + inFilter.key = leftExpression.keyPath.UTF8String; + inFilter.values = rightExpression.mgl_filterValues; + return inFilter; } case NSContainsPredicateOperatorType: { - auto filter = mbgl::style::InFilter(); - filter.key = [self.rightExpression.constantValue UTF8String]; - filter.values = self.leftExpression.mgl_filterValues; - return filter; + if (!isReversed) { + if (rightType == NSConstantValueExpressionType && [rightExpression.constantValue isKindOfClass:[NSString class]]) { + [NSException raise:NSInvalidArgumentException + format:@"IN not supported for string comparison."]; + } + [NSException raise:NSInvalidArgumentException + format:@"Predicate cannot compare attribute CONTAINS values."]; + } + mbgl::style::InFilter inFilter; + inFilter.key = rightExpression.keyPath.UTF8String; + inFilter.values = leftExpression.mgl_filterValues; + return inFilter; } case NSBetweenPredicateOperatorType: { - auto filter = mbgl::style::AllFilter(); - auto gteFilter = mbgl::style::GreaterThanEqualsFilter(); - gteFilter.key = self.leftExpression.keyPath.UTF8String; - gteFilter.value = self.rightExpression.mgl_filterValues[0]; - filter.filters.push_back(gteFilter); - auto lteFilter = mbgl::style::LessThanEqualsFilter(); - lteFilter.key = self.leftExpression.keyPath.UTF8String; - lteFilter.value = self.rightExpression.mgl_filterValues[1]; - filter.filters.push_back(lteFilter); - return filter; + if (isReversed) { + [NSException raise:NSInvalidArgumentException + format:@"Predicate cannot compare bounds BETWEEN attribute."]; + } + if (![rightExpression.constantValue isKindOfClass:[NSArray class]]) { + [NSException raise:NSInvalidArgumentException + format:@"Right side of BETWEEN predicate must be an array."]; // not NSSet + } + auto values = rightExpression.mgl_filterValues; + if (values.size() != 2) { + [NSException raise:NSInvalidArgumentException + format:@"Right side of BETWEEN predicate must have two items."]; + } + mbgl::style::AllFilter allFilter; + mbgl::style::GreaterThanEqualsFilter gteFilter; + gteFilter.key = leftExpression.keyPath.UTF8String; + gteFilter.value = values[0]; + allFilter.filters.push_back(gteFilter); + mbgl::style::LessThanEqualsFilter lteFilter; + lteFilter.key = leftExpression.keyPath.UTF8String; + lteFilter.value = values[1]; + allFilter.filters.push_back(lteFilter); + return allFilter; } case NSMatchesPredicateOperatorType: case NSLikePredicateOperatorType: case NSBeginsWithPredicateOperatorType: case NSEndsWithPredicateOperatorType: case NSCustomSelectorPredicateOperatorType: - [NSException raise:@"Unsupported operator type" + [NSException raise:NSInvalidArgumentException format:@"NSPredicateOperatorType:%lu is not supported.", (unsigned long)self.predicateOperatorType]; } return {}; } +- (NSString *)mgl_keyPath { + NSExpression *leftExpression = self.leftExpression; + NSExpression *rightExpression = self.rightExpression; + NSExpressionType leftType = leftExpression.expressionType; + NSExpressionType rightType = rightExpression.expressionType; + if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) { + return leftExpression.keyPath; + } else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) { + return rightExpression.keyPath; + } + + [NSException raise:NSInvalidArgumentException + format:@"Comparison predicate must compare an attribute (as a key path) to a constant or vice versa."]; + return nil; +} + +- (mbgl::Value)mgl_constantValue { + NSExpression *leftExpression = self.leftExpression; + NSExpression *rightExpression = self.rightExpression; + NSExpressionType leftType = leftExpression.expressionType; + NSExpressionType rightType = rightExpression.expressionType; + mbgl::Value value; + if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) { + value = rightExpression.mgl_filterValue; + } else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) { + value = leftExpression.mgl_filterValue; + } else { + [NSException raise:NSInvalidArgumentException + format:@"Comparison predicate must compare an attribute (as a key path) to a constant or vice versa."]; + } + return value; +} + @end diff --git a/platform/darwin/src/NSCompoundPredicate+MGLAdditions.mm b/platform/darwin/src/NSCompoundPredicate+MGLAdditions.mm index 07114308c9..2697467198 100644 --- a/platform/darwin/src/NSCompoundPredicate+MGLAdditions.mm +++ b/platform/darwin/src/NSCompoundPredicate+MGLAdditions.mm @@ -18,32 +18,49 @@ { switch (self.compoundPredicateType) { case NSNotPredicateType: { + NSAssert(self.subpredicates.count <= 1, @"NOT predicate cannot have multiple subpredicates."); + NSPredicate *subpredicate = self.subpredicates.firstObject; + mbgl::style::Filter subfilter = subpredicate.mgl_filter; - // Translate a nested NSComparisonPredicate with operator type NSInPredicateOperatorType into a flat mbgl::NotIn filter. - NSArray<NSComparisonPredicate *> *comparisonPredicates = [self.subpredicates filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"class == %@", [NSComparisonPredicate class]]]; - NSArray<NSComparisonPredicate *> *notInPredicates = [comparisonPredicates filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSComparisonPredicate *_Nonnull predicate, NSDictionary<NSString *,id> * _Nullable bindings) { - return predicate.predicateOperatorType == NSInPredicateOperatorType; - }]]; + // Convert NOT(!= nil) to NotHasFilter. + if (subfilter.is<mbgl::style::HasFilter>()) { + auto hasFilter = subfilter.get<mbgl::style::HasFilter>(); + return mbgl::style::NotHasFilter { .key = hasFilter.key }; + } + + // Convert NOT(== nil) to HasFilter. + if (subfilter.is<mbgl::style::NotHasFilter>()) { + auto hasFilter = subfilter.get<mbgl::style::NotHasFilter>(); + return mbgl::style::HasFilter { .key = hasFilter.key }; + } + + // Convert NOT(IN) or NOT(CONTAINS) to NotInFilter. + if (subfilter.is<mbgl::style::InFilter>()) { + auto inFilter = subfilter.get<mbgl::style::InFilter>(); + mbgl::style::NotInFilter notInFilter; + notInFilter.key = inFilter.key; + notInFilter.values = inFilter.values; + return notInFilter; + } - if (notInPredicates.count) { - auto filter = mbgl::style::NotInFilter(); - filter.key = notInPredicates.firstObject.leftExpression.keyPath.UTF8String; - filter.values = notInPredicates.firstObject.rightExpression.mgl_filterValues; - return filter; - } else { - auto filter = mbgl::style::NoneFilter(); - filter.filters = [self mgl_subfilters]; - return filter; + // Convert NOT(), NOT(AND), NOT(NOT), NOT(==), etc. into NoneFilter. + mbgl::style::NoneFilter noneFilter; + if (subfilter.is<mbgl::style::AnyFilter>()) { + // Flatten NOT(OR). + noneFilter.filters = subfilter.get<mbgl::style::AnyFilter>().filters; + } else if (subpredicate) { + noneFilter.filters = { subfilter }; } + return noneFilter; } case NSAndPredicateType: { - auto filter = mbgl::style::AllFilter(); - filter.filters = [self mgl_subfilters]; + mbgl::style::AllFilter filter; + filter.filters = self.mgl_subfilters; return filter; } case NSOrPredicateType: { - auto filter = mbgl::style::AnyFilter(); - filter.filters = [self mgl_subfilters]; + mbgl::style::AnyFilter filter; + filter.filters = self.mgl_subfilters; return filter; } } diff --git a/platform/darwin/src/NSExpression+MGLAdditions.mm b/platform/darwin/src/NSExpression+MGLAdditions.mm index 6af069487e..97f3e11dba 100644 --- a/platform/darwin/src/NSExpression+MGLAdditions.mm +++ b/platform/darwin/src/NSExpression+MGLAdditions.mm @@ -4,15 +4,19 @@ - (std::vector<mbgl::Value>)mgl_filterValues { - if ([self.constantValue isKindOfClass:NSArray.class]) { - NSArray *values = self.constantValue; - std::vector<mbgl::Value>convertedValues; - for (id value in values) { - convertedValues.push_back([self mgl_convertedValueWithValue:value]); + if ([self.constantValue isKindOfClass:[NSArray class]] || [self.constantValue isKindOfClass:[NSSet class]]) { + std::vector<mbgl::Value> convertedValues; + for (id item in self.constantValue) { + id constantValue = item; + if ([item isKindOfClass:[NSExpression class]]) { + constantValue = [constantValue constantValue]; + } + convertedValues.push_back([self mgl_convertedValueWithValue:constantValue]); } return convertedValues; } - [NSException raise:@"Values not handled" format:@""]; + [NSException raise:NSInvalidArgumentException + format:@"Constant value expression must contain an array or set."]; return { }; } @@ -42,7 +46,10 @@ // We still do this conversion in order to provide a valid value. static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - NSLog(@"One-time warning: Float values are converted to double and can introduce imprecision; please use double values explicitly in predicate arguments."); + NSLog(@"Float value in expression will be converted to a double; some imprecision may result. " + @"Use double values explicitly when specifying constant expression values and " + @"when specifying arguments to predicate and expression format strings. " + @"This will be logged only once."); }); return { (double)number.doubleValue }; } else if ([number compare:@(0)] == NSOrderedDescending || @@ -55,8 +62,8 @@ // We use long long here to avoid any truncation. return { (int64_t)number.longLongValue }; } - } else if (value != [NSNull null]) { - [NSException raise:@"Value not handled" + } else if (value && value != [NSNull null]) { + [NSException raise:NSInvalidArgumentException format:@"Can’t convert %s:%@ to mbgl::Value", [value objCType], value]; } return { }; diff --git a/platform/darwin/src/NSPredicate+MGLAdditions.mm b/platform/darwin/src/NSPredicate+MGLAdditions.mm index 64ad277e4d..f999df96b0 100644 --- a/platform/darwin/src/NSPredicate+MGLAdditions.mm +++ b/platform/darwin/src/NSPredicate+MGLAdditions.mm @@ -16,7 +16,8 @@ public: NSArray* getValues(std::vector<mbgl::Value> values) { NSMutableArray *array = [NSMutableArray arrayWithCapacity:values.size()]; for (auto value : values) { - [array addObject:mbgl::Value::visit(value, ValueEvaluator())]; + id constantValue = mbgl::Value::visit(value, ValueEvaluator()); + [array addObject:[NSExpression expressionForConstantValue:constantValue]]; } return array; } @@ -58,20 +59,64 @@ public: } NSPredicate* operator()(mbgl::style::AnyFilter filter) { - return [NSCompoundPredicate orPredicateWithSubpredicates:getPredicates(filter.filters)]; + NSArray *subpredicates = getPredicates(filter.filters); + if (subpredicates.count) { + return [NSCompoundPredicate orPredicateWithSubpredicates:subpredicates]; + } + return [NSPredicate predicateWithValue:NO]; } NSPredicate* operator()(mbgl::style::AllFilter filter) { - return [NSCompoundPredicate andPredicateWithSubpredicates:getPredicates(filter.filters)]; + // Convert [all, [>=, key, lower], [<=, key, upper]] to key BETWEEN {lower, upper} + if (filter.filters.size() == 2) { + auto leftFilter = filter.filters[0]; + auto rightFilter = filter.filters[1]; + + std::string lowerKey; + std::string upperKey; + mbgl::Value lowerBound; + mbgl::Value upperBound; + if (leftFilter.is<mbgl::style::GreaterThanEqualsFilter>()) { + lowerKey = leftFilter.get<mbgl::style::GreaterThanEqualsFilter>().key; + lowerBound = leftFilter.get<mbgl::style::GreaterThanEqualsFilter>().value; + } else if (rightFilter.is<mbgl::style::GreaterThanEqualsFilter>()) { + lowerKey = rightFilter.get<mbgl::style::GreaterThanEqualsFilter>().key; + lowerBound = rightFilter.get<mbgl::style::GreaterThanEqualsFilter>().value; + } + + if (leftFilter.is<mbgl::style::LessThanEqualsFilter>()) { + upperKey = leftFilter.get<mbgl::style::LessThanEqualsFilter>().key; + upperBound = leftFilter.get<mbgl::style::LessThanEqualsFilter>().value; + } else if (rightFilter.is<mbgl::style::LessThanEqualsFilter>()) { + upperKey = rightFilter.get<mbgl::style::LessThanEqualsFilter>().key; + upperBound = rightFilter.get<mbgl::style::LessThanEqualsFilter>().value; + } + + if (!lowerBound.is<mbgl::NullValue>() && !upperBound.is<mbgl::NullValue>() + && lowerKey == upperKey) { + return [NSPredicate predicateWithFormat:@"%K BETWEEN {%@, %@}", + @(lowerKey.c_str()), + mbgl::Value::visit(lowerBound, ValueEvaluator()), + mbgl::Value::visit(upperBound, ValueEvaluator())]; + } + } + + NSArray *subpredicates = getPredicates(filter.filters); + if (subpredicates.count) { + return [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]; + } + return [NSPredicate predicateWithValue:YES]; } NSPredicate* operator()(mbgl::style::NoneFilter filter) { - NSArray *predicates = getPredicates(filter.filters); - if (predicates.count > 1) { - NSCompoundPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:predicates]; + NSArray *subpredicates = getPredicates(filter.filters); + if (subpredicates.count > 1) { + NSCompoundPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subpredicates]; return [NSCompoundPredicate notPredicateWithSubpredicate:predicate]; + } else if (subpredicates.count) { + return [NSCompoundPredicate notPredicateWithSubpredicate:subpredicates.firstObject]; } else { - return [NSCompoundPredicate notPredicateWithSubpredicate:predicates.firstObject]; + return [NSPredicate predicateWithValue:YES]; } } @@ -91,18 +136,22 @@ public: { if ([self isEqual:[NSPredicate predicateWithValue:YES]]) { - auto filter = mbgl::style::AllFilter(); - return filter; + return mbgl::style::AllFilter(); } if ([self isEqual:[NSPredicate predicateWithValue:NO]]) { - auto filter = mbgl::style::AnyFilter(); - return filter; + return mbgl::style::AnyFilter(); + } + + if ([self.predicateFormat hasPrefix:@"BLOCKPREDICATE("]) + { + [NSException raise:NSInvalidArgumentException + format:@"Block-based predicates are not supported."]; } - [NSException raise:@"Not supported" - format:@"Try with NSComparisonPredicate or NSCompoundPredicate instead."]; + [NSException raise:NSInvalidArgumentException + format:@"Unrecognized predicate type."]; return {}; } diff --git a/platform/darwin/test/MGLFilterTests.mm b/platform/darwin/test/MGLFilterTests.mm deleted file mode 100644 index e688d50583..0000000000 --- a/platform/darwin/test/MGLFilterTests.mm +++ /dev/null @@ -1,194 +0,0 @@ -#import "MGLStyleLayerTests.h" - -#import "NSPredicate+MGLAdditions.h" -#import "MGLValueEvaluator.h" - - -@interface MGLFilterTests : MGLStyleLayerTests { - MGLShapeSource *source; - MGLLineStyleLayer *layer; -} -@end - -@implementation MGLFilterTests - -- (void)setUp -{ - [super setUp]; - NSString *filePath = [[NSBundle bundleForClass:self.class] pathForResource:@"amsterdam" ofType:@"geojson"]; - NSURL *url = [NSURL fileURLWithPath:filePath]; - NSData *geoJSONData = [NSData dataWithContentsOfURL:url]; - NSError *error; - MGLShape *shape = [MGLShape shapeWithData:geoJSONData encoding:NSUTF8StringEncoding error:&error]; - XCTAssertNil(error); - XCTAssertNotNil(shape); - source = [[MGLShapeSource alloc] initWithIdentifier:@"test-source" shape:shape options:nil]; - [self.mapView.style addSource:source]; - layer = [[MGLLineStyleLayer alloc] initWithIdentifier:@"test-layer" source:source]; -} - -- (void)tearDown -{ - [self.mapView.style removeLayer:layer]; - [self.mapView.style removeSource:source]; -} - -- (NSArray<NSPredicate *> *)predicates -{ - NSPredicate *equalPredicate = [NSPredicate predicateWithFormat:@"type == 'neighbourhood'"]; - NSPredicate *notEqualPredicate = [NSPredicate predicateWithFormat:@"type != 'park'"]; - NSPredicate *greaterThanPredicate = [NSPredicate predicateWithFormat:@"%K > %@", @"stroke-width", @2.1]; - NSPredicate *greaterThanOrEqualToPredicate = [NSPredicate predicateWithFormat:@"%K >= %@", @"stroke-width", @2.1]; - NSPredicate *lessThanOrEqualToPredicate = [NSPredicate predicateWithFormat:@"%K <= %@", @"stroke-width", @2.1]; - NSPredicate *lessThanPredicate = [NSPredicate predicateWithFormat:@"%K < %@", @"stroke-width", @2.1]; - NSPredicate *inPredicate = [NSPredicate predicateWithFormat:@"type IN %@", @[@"park", @"neighbourhood"]]; - NSPredicate *notInPredicate = [NSPredicate predicateWithFormat:@"NOT (type IN %@)", @[@"park", @"neighbourhood"]]; - NSPredicate *inNotInPredicate = [NSPredicate predicateWithFormat:@"type IN %@ AND NOT (type IN %@)", @[@"park"], @[@"neighbourhood", @"test"]]; - NSPredicate *typePredicate = [NSPredicate predicateWithFormat:@"%K == %@", @"$type", @"Feature"]; - NSPredicate *idPredicate = [NSPredicate predicateWithFormat:@"%K == %@", @"$id", @"1234123"]; - NSPredicate *specialCharsPredicate = [NSPredicate predicateWithFormat:@"%K == %@", @"ty-’pè", @"sŒm-ethįng"]; - NSPredicate *booleanPredicate = [NSPredicate predicateWithFormat:@"cluster != YES"]; - NSPredicate *nilEqualsPredicate = [NSPredicate predicateWithFormat:@"type == %@", nil]; - NSPredicate *nilNotEqualsPredicate = [NSPredicate predicateWithFormat:@"type != %@", nil]; - return @[ - equalPredicate, - notEqualPredicate, - greaterThanPredicate, - greaterThanOrEqualToPredicate, - lessThanOrEqualToPredicate, - lessThanPredicate, - inPredicate, - notInPredicate, - inNotInPredicate, - typePredicate, - idPredicate, - specialCharsPredicate, - booleanPredicate, - nilEqualsPredicate, - nilNotEqualsPredicate - ]; -} - -- (void)testAllPredicates -{ - for (NSPredicate *predicate in self.predicates) { - layer.predicate = predicate; - XCTAssertEqualObjects(layer.predicate, predicate); - } - [self.mapView.style addLayer:layer]; -} - -- (void)testContainsPredicate -{ - // core does not have a "contains" filter but we can achieve the equivalent by creating an `mbgl::style::InFilter` - // and searching the value for the key - NSPredicate *expectedPredicate = [NSPredicate predicateWithFormat:@"park IN %@", @[@"park", @"neighbourhood"]]; - NSPredicate *containsPredicate = [NSPredicate predicateWithFormat:@"%@ CONTAINS %@", @[@"park", @"neighbourhood"], @"park"]; - - layer.predicate = containsPredicate; - XCTAssertEqualObjects(layer.predicate, expectedPredicate); - [self.mapView.style addLayer:layer]; -} - -- (void)testBetweenPredicate -{ - // core does not have a "between" filter but we can achieve the equivalent by creating a set of greater than or equal / less than or equal - // filters for the lower and upper bounds (inclusive) - NSPredicate *expectedPredicate = [NSCompoundPredicate predicateWithFormat:@"%K >= 2 AND %K <= 3", @"stroke-width", @"stroke-width"]; - NSPredicate *betweenPredicate = [NSPredicate predicateWithFormat:@"%K BETWEEN %@", @"stroke-width", @[@2.0, @3.0]]; - - layer.predicate = betweenPredicate; - XCTAssertEqualObjects(layer.predicate, expectedPredicate); - [self.mapView.style addLayer:layer]; -} - -- (void)testTruePredicate -{ - // This comes out of the class cluster as an NSTruePredicate and it is equal to `[NSPredicate predicateWithValue:YES]` - NSPredicate *truePredicate = [NSPredicate predicateWithFormat:@"TRUEPREDICATE"]; - - layer.predicate = truePredicate; - XCTAssertEqualObjects(layer.predicate.description, truePredicate.description); - [self.mapView.style addLayer:layer]; -} - -- (void)testFalsePredicate -{ - // This comes out of the class cluster as an NSFalsePredicate and it is equal to `[NSPredicate predicateWithValue:NO]` - NSPredicate *falsePredicate = [NSPredicate predicateWithFormat:@"FALSEPREDICATE"]; - - layer.predicate = falsePredicate; - XCTAssertEqualObjects(layer.predicate.description, falsePredicate.description); - [self.mapView.style addLayer:layer]; -} - -- (void)testIntermittentEncoding -{ - NSPredicate *specialCharsPredicate = [NSPredicate predicateWithFormat:@"%K == %@", @"ty-’pè", @"sŒm-ethįng"]; - layer.predicate = specialCharsPredicate; - - NSComparisonPredicate *getPredicate = (NSComparisonPredicate *)layer.predicate; - mbgl::style::EqualsFilter filter = layer.predicate.mgl_filter.get<mbgl::style::EqualsFilter>(); - - id objcKey = getPredicate.leftExpression.keyPath; - id cppKey = @(filter.key.c_str()); - id objcValue = mbgl::Value::visit(getPredicate.rightExpression.mgl_filterValue, ValueEvaluator()); - id cppValue = mbgl::Value::visit(filter.value, ValueEvaluator()); - - XCTAssertEqualObjects(objcKey, cppKey); - XCTAssertEqualObjects(objcValue, cppValue); - - [self.mapView.style addLayer:layer]; -} - -- (void)testNestedFilters -{ - NSPredicate *equalPredicate = [NSPredicate predicateWithFormat:@"type == 'neighbourhood'"]; - NSPredicate *notEqualPredicate = [NSPredicate predicateWithFormat:@"type != 'park'"]; - - NSPredicate *allPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:@[equalPredicate, notEqualPredicate]]; - NSPredicate *anyPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:@[equalPredicate, notEqualPredicate]]; - - layer.predicate = allPredicate; - XCTAssertEqualObjects(layer.predicate, allPredicate); - layer.predicate = anyPredicate; - XCTAssertEqualObjects(layer.predicate, anyPredicate); - - [self.mapView.style addLayer:layer]; -} - -- (void)testAndPredicates -{ - NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:self.predicates]; - layer.predicate = predicate; - XCTAssertEqualObjects(predicate, layer.predicate); - [self.mapView.style addLayer:layer]; -} - -- (void)testOrPredicates -{ - NSPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:self.predicates]; - layer.predicate = predicate; - XCTAssertEqualObjects(predicate, layer.predicate); - [self.mapView.style addLayer:layer]; -} - -- (void)testNotAndPredicates -{ - NSPredicate *predicates = [NSCompoundPredicate andPredicateWithSubpredicates:self.predicates]; - NSCompoundPredicate *predicate = [NSCompoundPredicate notPredicateWithSubpredicate:predicates]; - layer.predicate = predicate; - XCTAssertEqualObjects(predicate, layer.predicate); - [self.mapView.style addLayer:layer]; -} - -- (void)testNotOrPredicates -{ - NSPredicate *predicates = [NSCompoundPredicate orPredicateWithSubpredicates:self.predicates]; - NSCompoundPredicate *predicate = [NSCompoundPredicate notPredicateWithSubpredicate:predicates]; - layer.predicate = predicate; - XCTAssertEqualObjects(predicate, layer.predicate); - [self.mapView.style addLayer:layer]; -} - -@end diff --git a/platform/darwin/test/MGLPredicateTests.mm b/platform/darwin/test/MGLPredicateTests.mm new file mode 100644 index 0000000000..07466dfd13 --- /dev/null +++ b/platform/darwin/test/MGLPredicateTests.mm @@ -0,0 +1,416 @@ +#import <XCTest/XCTest.h> +#import <Mapbox/Mapbox.h> + +#import "NSPredicate+MGLAdditions.h" +#import "MGLValueEvaluator.h" + +namespace mbgl { + namespace style { + bool operator!=(const Filter &a, const Filter &b) { + return !(a == b); + } + } +} + +#define MGLAssertEqualFilters(actual, expected, ...) \ + XCTAssertTrue(actual.is<__typeof__(expected)>()); \ + if (actual.is<__typeof__(expected)>()) { \ + XCTAssertEqual(actual.get<__typeof__(expected)>(), expected, __VA_ARGS__); \ + } + +@interface MGLPredicateTests : XCTestCase +@end + +@implementation MGLPredicateTests + +- (void)testFilterization { + { + auto actual = [NSPredicate predicateWithValue:YES].mgl_filter; + mbgl::style::AllFilter expected; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithValue:NO].mgl_filter; + mbgl::style::AnyFilter expected; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"a = 'b'"].mgl_filter; + mbgl::style::EqualsFilter expected = { .key = "a", .value = std::string("b") }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"a = nil"].mgl_filter; + mbgl::style::NotHasFilter expected = { .key = "a" }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"a != 'b'"].mgl_filter; + mbgl::style::NotEqualsFilter expected = { .key = "a", .value = std::string("b") }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"a != nil"].mgl_filter; + mbgl::style::HasFilter expected = { .key = "a" }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"a < 'b'"].mgl_filter; + mbgl::style::LessThanFilter expected = { .key = "a", .value = std::string("b") }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"a <= 'b'"].mgl_filter; + mbgl::style::LessThanEqualsFilter expected = { .key = "a", .value = std::string("b") }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"a > 'b'"].mgl_filter; + mbgl::style::GreaterThanFilter expected = { .key = "a", .value = std::string("b") }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"a >= 'b'"].mgl_filter; + mbgl::style::GreaterThanEqualsFilter expected = { .key = "a", .value = std::string("b") }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"a BETWEEN {'b', 'z'}"].mgl_filter; + mbgl::style::AllFilter expected = { + .filters = { + mbgl::style::GreaterThanEqualsFilter { .key = "a", .value = std::string("b") }, + mbgl::style::LessThanEqualsFilter { .key = "a", .value = std::string("z") }, + }, + }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"a BETWEEN %@", @[@"b", @"z"]].mgl_filter; + mbgl::style::AllFilter expected = { + .filters = { + mbgl::style::GreaterThanEqualsFilter { .key = "a", .value = std::string("b") }, + mbgl::style::LessThanEqualsFilter { .key = "a", .value = std::string("z") }, + }, + }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"a IN {'b', 'c'}"].mgl_filter; + mbgl::style::InFilter expected = { .key = "a", .values = { std::string("b"), std::string("c") } }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"a IN %@", @[@"b", @"c"]].mgl_filter; + mbgl::style::InFilter expected = { .key = "a", .values = { std::string("b"), std::string("c") } }; + MGLAssertEqualFilters(actual, expected); + } + + XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"'Mapbox' IN a"].mgl_filter, NSException, NSInvalidArgumentException); + + { + auto actual = [NSPredicate predicateWithFormat:@"{'b', 'c'} CONTAINS a"].mgl_filter; + mbgl::style::InFilter expected = { .key = "a", .values = { std::string("b"), std::string("c") } }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"%@ CONTAINS a", @[@"b", @"c"]].mgl_filter; + mbgl::style::InFilter expected = { .key = "a", .values = { std::string("b"), std::string("c") } }; + MGLAssertEqualFilters(actual, expected); + } + + XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a CONTAINS 'Mapbox'"].mgl_filter, NSException, NSInvalidArgumentException); + + { + auto actual = [NSPredicate predicateWithFormat:@"a == 'b' AND c == 'd'"].mgl_filter; + mbgl::style::AllFilter expected = { + .filters = { + mbgl::style::EqualsFilter { .key = "a", .value = std::string("b") }, + mbgl::style::EqualsFilter { .key = "c", .value = std::string("d") }, + }, + }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"a == 'b' OR c == 'd'"].mgl_filter; + mbgl::style::AnyFilter expected = { + .filters = { + mbgl::style::EqualsFilter { .key = "a", .value = std::string("b") }, + mbgl::style::EqualsFilter { .key = "c", .value = std::string("d") }, + }, + }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"NOT(a == 'b' AND c == 'd')"].mgl_filter; + mbgl::style::NoneFilter expected = { + .filters = { + mbgl::style::AllFilter { + .filters = { + mbgl::style::EqualsFilter { .key = "a", .value = std::string("b") }, + mbgl::style::EqualsFilter { .key = "c", .value = std::string("d") }, + }, + }, + }, + }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"NOT(a == 'b' OR c == 'd')"].mgl_filter; + mbgl::style::NoneFilter expected = { + .filters = { + mbgl::style::EqualsFilter { .key = "a", .value = std::string("b") }, + mbgl::style::EqualsFilter { .key = "c", .value = std::string("d") }, + }, + }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"NOT a == nil"].mgl_filter; + mbgl::style::HasFilter expected = { .key = "a" }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"NOT a != nil"].mgl_filter; + mbgl::style::NotHasFilter expected = { .key = "a" }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"NOT a IN {'b', 'c'}"].mgl_filter; + mbgl::style::NotInFilter expected = { .key = "a", .values = { std::string("b"), std::string("c") } }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"NOT a IN %@", @[@"b", @"c"]].mgl_filter; + mbgl::style::NotInFilter expected = { .key = "a", .values = { std::string("b"), std::string("c") } }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"NOT {'b', 'c'} CONTAINS a"].mgl_filter; + mbgl::style::NotInFilter expected = { .key = "a", .values = { std::string("b"), std::string("c") } }; + MGLAssertEqualFilters(actual, expected); + } + + { + auto actual = [NSPredicate predicateWithFormat:@"NOT %@ CONTAINS a", @[@"b", @"c"]].mgl_filter; + mbgl::style::NotInFilter expected = { .key = "a", .values = { std::string("b"), std::string("c") } }; + MGLAssertEqualFilters(actual, expected); + } + + XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a BEGINSWITH 'L'"].mgl_filter, NSException, NSInvalidArgumentException); + XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a ENDSWITH 'itude'"].mgl_filter, NSException, NSInvalidArgumentException); + XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a LIKE 'glob?trotter'"].mgl_filter, NSException, NSInvalidArgumentException); + XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a MATCHES 'i\\w{18}n'"].mgl_filter, NSException, NSInvalidArgumentException); + NSPredicate *selectorPredicate = [NSPredicate predicateWithFormat:@"(SELF isKindOfClass: %@)", [MGLPolyline class]]; + XCTAssertThrowsSpecificNamed(selectorPredicate.mgl_filter, NSException, NSInvalidArgumentException); + + XCTAssertThrowsSpecificNamed([NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary<NSString *, id> * _Nullable bindings) { + XCTAssertTrue(NO, @"Predicate block should not be evaluated."); + return NO; + }].mgl_filter, NSException, NSInvalidArgumentException); +} + +- (void)testPredication { + XCTAssertNil([NSPredicate mgl_predicateWithFilter:mbgl::style::NullFilter()]); + + { + mbgl::style::EqualsFilter filter = { .key = "a", .value = std::string("b") }; + XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a = 'b'"]); + } + + { + mbgl::style::NotHasFilter filter = { .key = "a" }; + XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a = nil"]); + } + + { + mbgl::style::NotEqualsFilter filter = { .key = "a", .value = std::string("b") }; + XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a != 'b'"]); + } + + { + mbgl::style::HasFilter filter = { .key = "a" }; + XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a != nil"]); + } + + { + mbgl::style::LessThanFilter filter = { .key = "a", .value = std::string("b") }; + XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a < 'b'"]); + } + + { + mbgl::style::LessThanEqualsFilter filter = { .key = "a", .value = std::string("b") }; + XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a <= 'b'"]); + } + + { + mbgl::style::GreaterThanFilter filter = { .key = "a", .value = std::string("b") }; + XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a > 'b'"]); + } + + { + mbgl::style::GreaterThanEqualsFilter filter = { .key = "a", .value = std::string("b") }; + XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a >= 'b'"]); + } + + { + mbgl::style::AllFilter filter = { + .filters = { + mbgl::style::GreaterThanEqualsFilter { .key = "a", .value = std::string("b") }, + mbgl::style::LessThanEqualsFilter { .key = "a", .value = std::string("z") }, + }, + }; + XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a BETWEEN {'b', 'z'}"]); + } + + { + mbgl::style::AllFilter filter = { + .filters = { + mbgl::style::LessThanEqualsFilter { .key = "a", .value = std::string("z") }, + mbgl::style::GreaterThanEqualsFilter { .key = "a", .value = std::string("b") }, + }, + }; + XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a BETWEEN {'b', 'z'}"]); + } + + { + mbgl::style::InFilter filter = { .key = "a", .values = { std::string("b"), std::string("c") } }; + XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter].predicateFormat, [NSPredicate predicateWithFormat:@"a IN {'b', 'c'}"].predicateFormat); + } + + { + mbgl::style::NotInFilter filter = { .key = "a", .values = { std::string("b"), std::string("c") } }; + XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter].predicateFormat, [NSPredicate predicateWithFormat:@"NOT a IN {'b', 'c'}"].predicateFormat); + } + + { + mbgl::style::AllFilter filter; + XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithValue:YES]); + } + + { + mbgl::style::AllFilter filter = { + .filters = { + mbgl::style::EqualsFilter { .key = "a", .value = std::string("b") }, + mbgl::style::EqualsFilter { .key = "c", .value = std::string("d") }, + }, + }; + XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a == 'b' AND c == 'd'"]); + } + + { + mbgl::style::AnyFilter filter; + XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithValue:NO]); + } + + { + mbgl::style::AnyFilter filter = { + .filters = { + mbgl::style::EqualsFilter { .key = "a", .value = std::string("b") }, + mbgl::style::EqualsFilter { .key = "c", .value = std::string("d") }, + }, + }; + XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a == 'b' OR c == 'd'"]); + } + + { + mbgl::style::NoneFilter filter; + XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithValue:YES]); + } + + { + mbgl::style::NoneFilter filter = { + .filters = { + mbgl::style::EqualsFilter { .key = "a", .value = std::string("b") }, + mbgl::style::EqualsFilter { .key = "c", .value = std::string("d") }, + }, + }; + XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"NOT(a == 'b' OR c == 'd')"]); + } +} + +- (void)testSymmetry { + [self testSymmetryWithFormat:@"a = 1" reverseFormat:@"1 = a" mustRoundTrip:YES]; + [self testSymmetryWithFormat:@"a != 1" reverseFormat:@"1 != a" mustRoundTrip:YES]; + XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a = b"].mgl_filter, NSException, NSInvalidArgumentException); + XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"1 = 1"].mgl_filter, NSException, NSInvalidArgumentException); + + // In the predicate format language, $ is a special character denoting a + // variable. Use %K to escape the special feature attribute $id. + XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"$id == 670861802"].mgl_filter, NSException, NSInvalidArgumentException); + XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a = $id"].mgl_filter, NSException, NSInvalidArgumentException); + + [self testSymmetryWithFormat:@"a = nil" reverseFormat:@"nil = a" mustRoundTrip:YES]; + [self testSymmetryWithFormat:@"a != nil" reverseFormat:@"nil != a" mustRoundTrip:YES]; + + [self testSymmetryWithFormat:@"a < 1" reverseFormat:@"1 > a" mustRoundTrip:YES]; + [self testSymmetryWithFormat:@"a <= 1" reverseFormat:@"1 >= a" mustRoundTrip:YES]; + [self testSymmetryWithFormat:@"a > 1" reverseFormat:@"1 < a" mustRoundTrip:YES]; + [self testSymmetryWithFormat:@"a >= 1" reverseFormat:@"1 <= a" mustRoundTrip:YES]; + + [self testSymmetryWithFormat:@"a BETWEEN {1, 2}" reverseFormat:nil mustRoundTrip:YES]; + [self testSymmetryWithPredicate:[NSPredicate predicateWithFormat:@"a BETWEEN %@", @[@1, @2]] + reversePredicate:nil + mustRoundTrip:YES]; + XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"{1, 2} BETWEEN a"].mgl_filter, NSException, NSInvalidArgumentException); + NSPredicate *betweenSetPredicate = [NSPredicate predicateWithFormat:@"a BETWEEN %@", [NSSet setWithObjects:@1, @2, nil]]; + XCTAssertThrowsSpecificNamed(betweenSetPredicate.mgl_filter, NSException, NSInvalidArgumentException); + XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a BETWEEN {1}"].mgl_filter, NSException, NSInvalidArgumentException); + XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a BETWEEN {1, 2, 3}"].mgl_filter, NSException, NSInvalidArgumentException); + + [self testSymmetryWithFormat:@"a IN {1, 2}" reverseFormat:@"{1, 2} CONTAINS a" mustRoundTrip:NO]; + [self testSymmetryWithPredicate:[NSPredicate predicateWithFormat:@"a IN %@", @[@1, @2]] + reversePredicate:[NSPredicate predicateWithFormat:@"%@ CONTAINS a", @[@1, @2]] + mustRoundTrip:YES]; + + [self testSymmetryWithFormat:@"{1, 2} CONTAINS a" reverseFormat:@"a IN {1, 2}" mustRoundTrip:NO]; + [self testSymmetryWithPredicate:[NSPredicate predicateWithFormat:@"%@ CONTAINS a", @[@1, @2]] + reversePredicate:[NSPredicate predicateWithFormat:@"a IN %@", @[@1, @2]] + mustRoundTrip:NO]; +} + +- (void)testSymmetryWithFormat:(NSString *)forwardFormat reverseFormat:(NSString *)reverseFormat mustRoundTrip:(BOOL)mustRoundTrip { + NSPredicate *forwardPredicate = [NSPredicate predicateWithFormat:forwardFormat]; + NSPredicate *reversePredicate = reverseFormat ? [NSPredicate predicateWithFormat:reverseFormat] : nil; + [self testSymmetryWithPredicate:forwardPredicate reversePredicate:reversePredicate mustRoundTrip:mustRoundTrip]; +} + +- (void)testSymmetryWithPredicate:(NSPredicate *)forwardPredicate reversePredicate:(NSPredicate *)reversePredicate mustRoundTrip:(BOOL)mustRoundTrip { + auto forwardFilter = forwardPredicate.mgl_filter; + NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; + if (mustRoundTrip) { + // Aggregates should round-trip, but for some reason only their format strings do. + XCTAssertEqualObjects(forwardPredicate.predicateFormat, forwardPredicateAfter.predicateFormat); + } + + if (reversePredicate) { + auto reverseFilter = reversePredicate.mgl_filter; + NSPredicate *reversePredicateAfter = [NSPredicate mgl_predicateWithFilter:reverseFilter]; + XCTAssertNotEqualObjects(reversePredicate, reversePredicateAfter); + + XCTAssertEqualObjects(forwardPredicateAfter, reversePredicateAfter); + } +} + +@end diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index 139e2b014b..ebcf3a55cb 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -103,7 +103,7 @@ 35B82BF91D6C5F8400B1B721 /* NSPredicate+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 35B82BF61D6C5F8400B1B721 /* NSPredicate+MGLAdditions.h */; }; 35B82BFA1D6C5F8400B1B721 /* NSPredicate+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35B82BF71D6C5F8400B1B721 /* NSPredicate+MGLAdditions.mm */; }; 35B82BFB1D6C5F8400B1B721 /* NSPredicate+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35B82BF71D6C5F8400B1B721 /* NSPredicate+MGLAdditions.mm */; }; - 35B8E08C1D6C8B5100E768D2 /* MGLFilterTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35B8E08B1D6C8B5100E768D2 /* MGLFilterTests.mm */; }; + 35B8E08C1D6C8B5100E768D2 /* MGLPredicateTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35B8E08B1D6C8B5100E768D2 /* MGLPredicateTests.mm */; }; 35CE61821D4165D9004F2359 /* UIColor+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 35CE61801D4165D9004F2359 /* UIColor+MGLAdditions.h */; }; 35CE61831D4165D9004F2359 /* UIColor+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 35CE61801D4165D9004F2359 /* UIColor+MGLAdditions.h */; }; 35CE61841D4165D9004F2359 /* UIColor+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35CE61811D4165D9004F2359 /* UIColor+MGLAdditions.mm */; }; @@ -570,7 +570,7 @@ 359F57451D2FDBD5005217F1 /* MGLUserLocationAnnotationView_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLUserLocationAnnotationView_Private.h; sourceTree = "<group>"; }; 35B82BF61D6C5F8400B1B721 /* NSPredicate+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSPredicate+MGLAdditions.h"; sourceTree = "<group>"; }; 35B82BF71D6C5F8400B1B721 /* NSPredicate+MGLAdditions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSPredicate+MGLAdditions.mm"; sourceTree = "<group>"; }; - 35B8E08B1D6C8B5100E768D2 /* MGLFilterTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLFilterTests.mm; path = ../../darwin/test/MGLFilterTests.mm; sourceTree = "<group>"; }; + 35B8E08B1D6C8B5100E768D2 /* MGLPredicateTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLPredicateTests.mm; path = ../../darwin/test/MGLPredicateTests.mm; sourceTree = "<group>"; }; 35CE61801D4165D9004F2359 /* UIColor+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIColor+MGLAdditions.h"; sourceTree = "<group>"; }; 35CE61811D4165D9004F2359 /* UIColor+MGLAdditions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "UIColor+MGLAdditions.mm"; sourceTree = "<group>"; }; 35D13AB51D3D15E300AFB4E0 /* MGLStyleLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLStyleLayer.h; sourceTree = "<group>"; }; @@ -937,11 +937,10 @@ isa = PBXGroup; children = ( 3575798F1D513EF1000B822E /* Layers */, - 35B8E08B1D6C8B5100E768D2 /* MGLFilterTests.mm */, + 40CFA64E1D78754A008103BD /* Sources */, 357F09091DF84F3800941873 /* MGLStyleValueTests.h */, 3599A3E51DF708BC00E77FB2 /* MGLStyleValueTests.m */, DA2207BE1DC0805F0002F84D /* MGLStyleValueTests.swift */, - 40CFA64E1D78754A008103BD /* Sources */, ); name = Styling; sourceTree = "<group>"; @@ -1094,9 +1093,11 @@ DAEDC4331D603417000224FF /* MGLAttributionInfoTests.m */, 353D23951D0B0DFE002BE09D /* MGLAnnotationViewTests.m */, DA35A2C31CCA9F8300E826B2 /* MGLClockDirectionFormatterTests.m */, + 35D9DDE11DA25EEC00DAAD69 /* MGLCodingTests.m */, DA35A2C41CCA9F8300E826B2 /* MGLCompassDirectionFormatterTests.m */, DA35A2A91CCA058D00E826B2 /* MGLCoordinateFormatterTests.m */, 6407D66F1E0085FD00F6A9C3 /* MGLDocumentationExampleTests.swift */, + DD58A4C51D822BD000E1F038 /* MGLExpressionTests.mm */, DA0CD58F1CF56F6A00A5F5A5 /* MGLFeatureTests.mm */, DA2E885C1CC0382C00F24E7B /* MGLGeometryTests.mm */, DA2DBBCC1D51E80400D38FF9 /* MGLStyleLayerTests.h */, @@ -1105,9 +1106,8 @@ DA2E885D1CC0382C00F24E7B /* MGLOfflinePackTests.m */, DA2E885E1CC0382C00F24E7B /* MGLOfflineRegionTests.m */, DA2E885F1CC0382C00F24E7B /* MGLOfflineStorageTests.m */, - 35D9DDE11DA25EEC00DAAD69 /* MGLCodingTests.m */, + 35B8E08B1D6C8B5100E768D2 /* MGLPredicateTests.mm */, DA2E88601CC0382C00F24E7B /* MGLStyleTests.mm */, - DD58A4C51D822BD000E1F038 /* MGLExpressionTests.mm */, DA2E88551CC036F400F24E7B /* Info.plist */, DA2784FB1DF02FF4001D5B8D /* Media.xcassets */, ); @@ -1785,6 +1785,7 @@ TargetAttributes = { DA1DC9491CB6C1C2006E619F = { CreatedOnToolsVersion = 7.3; + DevelopmentTeam = GJZR2MEM28; LastSwiftMigration = 0820; }; DA25D5B81CCD9EDE00607828 = { @@ -2002,7 +2003,7 @@ DA2207BF1DC0805F0002F84D /* MGLStyleValueTests.swift in Sources */, 40CFA6511D7875BB008103BD /* MGLShapeSourceTests.mm in Sources */, DA35A2C51CCA9F8300E826B2 /* MGLClockDirectionFormatterTests.m in Sources */, - 35B8E08C1D6C8B5100E768D2 /* MGLFilterTests.mm in Sources */, + 35B8E08C1D6C8B5100E768D2 /* MGLPredicateTests.mm in Sources */, DD58A4C61D822BD000E1F038 /* MGLExpressionTests.mm in Sources */, 3575798B1D502B0C000B822E /* MGLBackgroundStyleLayerTests.m in Sources */, DA2E88621CC0382C00F24E7B /* MGLOfflinePackTests.m in Sources */, diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj index 951ae1bd37..56872a1b19 100644 --- a/platform/macos/macos.xcodeproj/project.pbxproj +++ b/platform/macos/macos.xcodeproj/project.pbxproj @@ -99,7 +99,7 @@ DA87A9981DC9D88400810D09 /* MGLShapeSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA87A9961DC9D88400810D09 /* MGLShapeSourceTests.mm */; }; DA87A9991DC9D88400810D09 /* MGLTileSetTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA87A9971DC9D88400810D09 /* MGLTileSetTests.mm */; }; DA87A99C1DC9D8DD00810D09 /* MGLShapeSource_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DA87A99B1DC9D8DD00810D09 /* MGLShapeSource_Private.h */; }; - DA87A99E1DC9DC2100810D09 /* MGLFilterTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35C5D84B1D6DD75B00E95907 /* MGLFilterTests.mm */; }; + DA87A99E1DC9DC2100810D09 /* MGLPredicateTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35C5D84B1D6DD75B00E95907 /* MGLPredicateTests.mm */; }; DA87A9A01DC9DC6200810D09 /* MGLValueEvaluator.h in Headers */ = {isa = PBXBuildFile; fileRef = DA87A99F1DC9DC6200810D09 /* MGLValueEvaluator.h */; }; DA87A9A11DC9DCB400810D09 /* MGLRuntimeStylingHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = DA8F257B1D51C5F40010E6B5 /* MGLRuntimeStylingHelper.m */; }; DA87A9A21DC9DCF100810D09 /* MGLFillStyleLayerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DA8F25741D51C5F40010E6B5 /* MGLFillStyleLayerTests.m */; }; @@ -287,7 +287,7 @@ 35C5D8441D6DD66D00E95907 /* NSComparisonPredicate+MGLAdditions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSComparisonPredicate+MGLAdditions.mm"; sourceTree = "<group>"; }; 35C5D8451D6DD66D00E95907 /* NSCompoundPredicate+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSCompoundPredicate+MGLAdditions.h"; sourceTree = "<group>"; }; 35C5D8461D6DD66D00E95907 /* NSCompoundPredicate+MGLAdditions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSCompoundPredicate+MGLAdditions.mm"; sourceTree = "<group>"; }; - 35C5D84B1D6DD75B00E95907 /* MGLFilterTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLFilterTests.mm; sourceTree = "<group>"; }; + 35C5D84B1D6DD75B00E95907 /* MGLPredicateTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLPredicateTests.mm; path = ../../darwin/test/MGLPredicateTests.mm; sourceTree = "<group>"; }; 35D65C581D65AD5500722C23 /* NSDate+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDate+MGLAdditions.h"; sourceTree = "<group>"; }; 35D65C591D65AD5500722C23 /* NSDate+MGLAdditions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSDate+MGLAdditions.mm"; sourceTree = "<group>"; }; 4032C5B91DE1EEBA0062E8BD /* NSValue+MGLStyleEnumAttributeAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSValue+MGLStyleEnumAttributeAdditions.h"; sourceTree = "<group>"; }; @@ -714,7 +714,6 @@ children = ( DA8F257C1D51C5F40010E6B5 /* Layers */, DA87A99A1DC9D88800810D09 /* Sources */, - 35C5D84B1D6DD75B00E95907 /* MGLFilterTests.mm */, 353722EB1DF850ED004D2F3F /* MGLStyleValueTests.h */, 3599A3E71DF70E2000E77FB2 /* MGLStyleValueTests.m */, DA2207BB1DC076940002F84D /* MGLStyleValueTests.swift */, @@ -883,17 +882,18 @@ DA8F257D1D51C5F40010E6B5 /* Styling */, DAEDC4311D6033F1000224FF /* MGLAttributionInfoTests.m */, DAEDC4361D606291000224FF /* MGLAttributionButtonTests.m */, - 3526EABC1DF9B19800006B43 /* MGLCodingTests.m */, DA35A2C11CCA9F4A00E826B2 /* MGLClockDirectionFormatterTests.m */, + 3526EABC1DF9B19800006B43 /* MGLCodingTests.m */, DA35A2B51CCA14D700E826B2 /* MGLCompassDirectionFormatterTests.m */, DA35A2A71CC9F41600E826B2 /* MGLCoordinateFormatterTests.m */, + DD58A4C71D822C6200E1F038 /* MGLExpressionTests.mm */, DA0CD58D1CF56F5800A5F5A5 /* MGLFeatureTests.mm */, DAE6C3C81CC34BD800DB3429 /* MGLGeometryTests.mm */, DAE6C3C91CC34BD800DB3429 /* MGLOfflinePackTests.m */, DAE6C3CA1CC34BD800DB3429 /* MGLOfflineRegionTests.m */, DAE6C3CB1CC34BD800DB3429 /* MGLOfflineStorageTests.m */, + 35C5D84B1D6DD75B00E95907 /* MGLPredicateTests.mm */, DAE6C3CC1CC34BD800DB3429 /* MGLStyleTests.mm */, - DD58A4C71D822C6200E1F038 /* MGLExpressionTests.mm */, DAE6C33A1CC30DB200DB3429 /* Info.plist */, DA2784FD1DF03060001D5B8D /* Media.xcassets */, ); @@ -1323,7 +1323,7 @@ DAE6C3D51CC34C9900DB3429 /* MGLOfflineStorageTests.m in Sources */, 40E1601D1DF217D6005EA6D9 /* MGLStyleLayerTests.m in Sources */, DA87A9A61DCACC5000810D09 /* MGLCircleStyleLayerTests.m in Sources */, - DA87A99E1DC9DC2100810D09 /* MGLFilterTests.mm in Sources */, + DA87A99E1DC9DC2100810D09 /* MGLPredicateTests.mm in Sources */, DD58A4C91D822C6700E1F038 /* MGLExpressionTests.mm in Sources */, DA87A9A71DCACC5000810D09 /* MGLBackgroundStyleLayerTests.m in Sources */, DAE6C3D31CC34C9900DB3429 /* MGLOfflinePackTests.m in Sources */, |