diff options
Diffstat (limited to 'platform/darwin/test')
33 files changed, 5170 insertions, 3231 deletions
diff --git a/platform/darwin/test/MGLAttributionInfoTests.m b/platform/darwin/test/MGLAttributionInfoTests.m index ed4927d44b..5961b61133 100644 --- a/platform/darwin/test/MGLAttributionInfoTests.m +++ b/platform/darwin/test/MGLAttributionInfoTests.m @@ -17,7 +17,7 @@ @"<a class=\"mapbox-improve-map\" href=\"https://www.mapbox.com/map-feedback/\" target=\"_blank\">Improve this map</a>", }; - NS_MUTABLE_ARRAY_OF(MGLAttributionInfo *) *infos = [NSMutableArray array]; + NSMutableArray<MGLAttributionInfo *> *infos = [NSMutableArray array]; for (NSUInteger i = 0; i < sizeof(htmlStrings) / sizeof(htmlStrings[0]); i++) { NSArray *subinfos = [MGLAttributionInfo attributionInfosFromHTMLString:htmlStrings[i] fontSize:0 @@ -51,12 +51,12 @@ XCTAssertEqualObjects([infos[3] feedbackURLAtCenterCoordinate:mapbox zoomLevel:14], [NSURL URLWithString:@"https://www.mapbox.com/feedback/?referrer=com.mapbox.sdk.ios#/77.63680/12.98108/14.00/0.0/0"]); XCTAssertEqualObjects([infos[3] feedbackURLForStyleURL:styleURL atCenterCoordinate:mapbox zoomLevel:3.14159 direction:90.9 pitch:12.5], - [NSURL URLWithString:@"https://www.mapbox.com/feedback/?referrer=com.mapbox.sdk.ios&owner=mapbox&id=satellite-streets-v99&access_token#/77.63680/12.98108/3.14/90.9/13"]); + [NSURL URLWithString:@"https://www.mapbox.com/feedback/?referrer=com.mapbox.sdk.ios&owner=mapbox&id=satellite-streets-v99&access_token&map_sdk_version=1.0.0#/77.63680/12.98108/3.14/90.9/13"]); #else XCTAssertEqualObjects([infos[3] feedbackURLAtCenterCoordinate:mapbox zoomLevel:14], [NSURL URLWithString:@"https://www.mapbox.com/feedback/?referrer=com.mapbox.MapboxGL#/77.63680/12.98108/14.00/0.0/0"]); XCTAssertEqualObjects([infos[3] feedbackURLForStyleURL:styleURL atCenterCoordinate:mapbox zoomLevel:3.14159 direction:90.9 pitch:12.5], - [NSURL URLWithString:@"https://www.mapbox.com/feedback/?referrer=com.mapbox.MapboxGL&owner=mapbox&id=satellite-streets-v99&access_token#/77.63680/12.98108/3.14/90.9/13"]); + [NSURL URLWithString:@"https://www.mapbox.com/feedback/?referrer=com.mapbox.MapboxGL&owner=mapbox&id=satellite-streets-v99&access_token&map_sdk_version=1.0.0#/77.63680/12.98108/3.14/90.9/13"]); #endif } @@ -67,7 +67,7 @@ CGFloat fontSize = 72; MGLColor *color = [MGLColor redColor]; - NS_MUTABLE_ARRAY_OF(MGLAttributionInfo *) *infos = [NSMutableArray array]; + NSMutableArray<MGLAttributionInfo *> *infos = [NSMutableArray array]; for (NSUInteger i = 0; i < sizeof(htmlStrings) / sizeof(htmlStrings[0]); i++) { NSArray *subinfos = [MGLAttributionInfo attributionInfosFromHTMLString:htmlStrings[i] fontSize:72 @@ -109,7 +109,7 @@ @"Hello World", }; - NS_MUTABLE_ARRAY_OF(MGLAttributionInfo *) *infos = [NSMutableArray array]; + NSMutableArray<MGLAttributionInfo *> *infos = [NSMutableArray array]; for (NSUInteger i = 0; i < sizeof(htmlStrings) / sizeof(htmlStrings[0]); i++) { NSArray *subinfos = [MGLAttributionInfo attributionInfosFromHTMLString:htmlStrings[i] fontSize:0 diff --git a/platform/darwin/test/MGLBackgroundStyleLayerTests.mm b/platform/darwin/test/MGLBackgroundStyleLayerTests.mm index c96a4fe7fa..de8080f425 100644 --- a/platform/darwin/test/MGLBackgroundStyleLayerTests.mm +++ b/platform/darwin/test/MGLBackgroundStyleLayerTests.mm @@ -31,39 +31,44 @@ { XCTAssertTrue(rawLayer->getBackgroundColor().isUndefined(), @"background-color should be unset initially."); - MGLStyleValue<MGLColor *> *defaultStyleValue = layer.backgroundColor; + NSExpression *defaultExpression = layer.backgroundColor; - MGLStyleValue<MGLColor *> *constantStyleValue = [MGLStyleValue<MGLColor *> valueWithRawValue:[MGLColor redColor]]; - layer.backgroundColor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.backgroundColor = constantExpression; mbgl::style::PropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } }; XCTAssertEqual(rawLayer->getBackgroundColor(), propertyValue, - @"Setting backgroundColor to a constant value should update background-color."); - XCTAssertEqualObjects(layer.backgroundColor, constantStyleValue, - @"backgroundColor should round-trip constant values."); - - MGLStyleValue<MGLColor *> * functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.backgroundColor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::Color> intervalStops = { {{18, { 1, 0, 0, 1 }}} }; + @"Setting backgroundColor to a constant value expression should update background-color."); + XCTAssertEqualObjects(layer.backgroundColor, constantExpression, + @"backgroundColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.backgroundColor = functionExpression; + + mbgl::style::IntervalStops<mbgl::Color> intervalStops = {{ + { -INFINITY, { 1, 0, 0, 1 } }, + { 18, { 1, 0, 0, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops }; XCTAssertEqual(rawLayer->getBackgroundColor(), propertyValue, - @"Setting backgroundColor to a camera function should update background-color."); - XCTAssertEqualObjects(layer.backgroundColor, functionStyleValue, - @"backgroundColor should round-trip camera functions."); + @"Setting backgroundColor to a camera expression should update background-color."); + XCTAssertEqualObjects(layer.backgroundColor, functionExpression, + @"backgroundColor should round-trip camera expressions."); layer.backgroundColor = nil; XCTAssertTrue(rawLayer->getBackgroundColor().isUndefined(), @"Unsetting backgroundColor should return background-color to the default value."); - XCTAssertEqualObjects(layer.backgroundColor, defaultStyleValue, + XCTAssertEqualObjects(layer.backgroundColor, defaultExpression, @"backgroundColor should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.backgroundColor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.backgroundColor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.backgroundColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLBackgroundLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.backgroundColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLBackgroundLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.backgroundColorTransition = transitionTest; auto toptions = rawLayer->getBackgroundColorTransition(); @@ -79,39 +84,44 @@ { XCTAssertTrue(rawLayer->getBackgroundOpacity().isUndefined(), @"background-opacity should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.backgroundOpacity; + NSExpression *defaultExpression = layer.backgroundOpacity; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.backgroundOpacity = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.backgroundOpacity = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getBackgroundOpacity(), propertyValue, - @"Setting backgroundOpacity to a constant value should update background-opacity."); - XCTAssertEqualObjects(layer.backgroundOpacity, constantStyleValue, - @"backgroundOpacity should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.backgroundOpacity = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting backgroundOpacity to a constant value expression should update background-opacity."); + XCTAssertEqualObjects(layer.backgroundOpacity, constantExpression, + @"backgroundOpacity should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.backgroundOpacity = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getBackgroundOpacity(), propertyValue, - @"Setting backgroundOpacity to a camera function should update background-opacity."); - XCTAssertEqualObjects(layer.backgroundOpacity, functionStyleValue, - @"backgroundOpacity should round-trip camera functions."); + @"Setting backgroundOpacity to a camera expression should update background-opacity."); + XCTAssertEqualObjects(layer.backgroundOpacity, functionExpression, + @"backgroundOpacity should round-trip camera expressions."); layer.backgroundOpacity = nil; XCTAssertTrue(rawLayer->getBackgroundOpacity().isUndefined(), @"Unsetting backgroundOpacity should return background-opacity to the default value."); - XCTAssertEqualObjects(layer.backgroundOpacity, defaultStyleValue, + XCTAssertEqualObjects(layer.backgroundOpacity, defaultExpression, @"backgroundOpacity should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.backgroundOpacity = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.backgroundOpacity = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.backgroundOpacity = functionExpression, NSException, NSInvalidArgumentException, @"MGLBackgroundLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.backgroundOpacity = functionExpression, NSException, NSInvalidArgumentException, @"MGLBackgroundLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.backgroundOpacityTransition = transitionTest; auto toptions = rawLayer->getBackgroundOpacityTransition(); @@ -127,39 +137,44 @@ { XCTAssertTrue(rawLayer->getBackgroundPattern().isUndefined(), @"background-pattern should be unset initially."); - MGLStyleValue<NSString *> *defaultStyleValue = layer.backgroundPattern; + NSExpression *defaultExpression = layer.backgroundPattern; - MGLStyleValue<NSString *> *constantStyleValue = [MGLStyleValue<NSString *> valueWithRawValue:@"Background Pattern"]; - layer.backgroundPattern = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'Background Pattern'"]; + layer.backgroundPattern = constantExpression; mbgl::style::PropertyValue<std::string> propertyValue = { "Background Pattern" }; XCTAssertEqual(rawLayer->getBackgroundPattern(), propertyValue, - @"Setting backgroundPattern to a constant value should update background-pattern."); - XCTAssertEqualObjects(layer.backgroundPattern, constantStyleValue, - @"backgroundPattern should round-trip constant values."); - - MGLStyleValue<NSString *> * functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.backgroundPattern = functionStyleValue; - - mbgl::style::IntervalStops<std::string> intervalStops = { {{18, "Background Pattern"}} }; + @"Setting backgroundPattern to a constant value expression should update background-pattern."); + XCTAssertEqualObjects(layer.backgroundPattern, constantExpression, + @"backgroundPattern should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'Background Pattern'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.backgroundPattern = functionExpression; + + mbgl::style::IntervalStops<std::string> intervalStops = {{ + { -INFINITY, "Background Pattern" }, + { 18, "Background Pattern" }, + }}; propertyValue = mbgl::style::CameraFunction<std::string> { intervalStops }; XCTAssertEqual(rawLayer->getBackgroundPattern(), propertyValue, - @"Setting backgroundPattern to a camera function should update background-pattern."); - XCTAssertEqualObjects(layer.backgroundPattern, functionStyleValue, - @"backgroundPattern should round-trip camera functions."); + @"Setting backgroundPattern to a camera expression should update background-pattern."); + XCTAssertEqualObjects(layer.backgroundPattern, functionExpression, + @"backgroundPattern should round-trip camera expressions."); layer.backgroundPattern = nil; XCTAssertTrue(rawLayer->getBackgroundPattern().isUndefined(), @"Unsetting backgroundPattern should return background-pattern to the default value."); - XCTAssertEqualObjects(layer.backgroundPattern, defaultStyleValue, + XCTAssertEqualObjects(layer.backgroundPattern, defaultExpression, @"backgroundPattern should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.backgroundPattern = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.backgroundPattern = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.backgroundPattern = functionExpression, NSException, NSInvalidArgumentException, @"MGLBackgroundLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.backgroundPattern = functionExpression, NSException, NSInvalidArgumentException, @"MGLBackgroundLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.backgroundPatternTransition = transitionTest; auto toptions = rawLayer->getBackgroundPatternTransition(); diff --git a/platform/darwin/test/MGLCircleStyleLayerTests.mm b/platform/darwin/test/MGLCircleStyleLayerTests.mm index c0c503153a..d7bf2a5afd 100644 --- a/platform/darwin/test/MGLCircleStyleLayerTests.mm +++ b/platform/darwin/test/MGLCircleStyleLayerTests.mm @@ -30,8 +30,8 @@ XCTAssertNil(layer.sourceLayerIdentifier); XCTAssertNil(layer.predicate); - layer.predicate = [NSPredicate predicateWithValue:NO]; - XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithValue:NO]); + layer.predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]; + XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]); layer.predicate = nil; XCTAssertNil(layer.predicate); } @@ -52,40 +52,45 @@ { XCTAssertTrue(rawLayer->getCircleBlur().isUndefined(), @"circle-blur should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.circleBlur; + NSExpression *defaultExpression = layer.circleBlur; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.circleBlur = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.circleBlur = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getCircleBlur(), propertyValue, - @"Setting circleBlur to a constant value should update circle-blur."); - XCTAssertEqualObjects(layer.circleBlur, constantStyleValue, - @"circleBlur should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.circleBlur = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting circleBlur to a constant value expression should update circle-blur."); + XCTAssertEqualObjects(layer.circleBlur, constantExpression, + @"circleBlur should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.circleBlur = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getCircleBlur(), propertyValue, - @"Setting circleBlur to a camera function should update circle-blur."); - XCTAssertEqualObjects(layer.circleBlur, functionStyleValue, - @"circleBlur should round-trip camera functions."); + @"Setting circleBlur to a camera expression should update circle-blur."); + XCTAssertEqualObjects(layer.circleBlur, functionExpression, + @"circleBlur should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.circleBlur = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.circleBlur = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getCircleBlur(), propertyValue, - @"Setting circleBlur to a source function should update circle-blur."); - XCTAssertEqualObjects(layer.circleBlur, functionStyleValue, - @"circleBlur should round-trip source functions."); + @"Setting circleBlur to a data expression should update circle-blur."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.circleBlur, pedanticFunctionExpression, + @"circleBlur should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.circleBlur = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.circleBlur = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -93,15 +98,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getCircleBlur(), propertyValue, - @"Setting circleBlur to a composite function should update circle-blur."); - XCTAssertEqualObjects(layer.circleBlur, functionStyleValue, - @"circleBlur should round-trip composite functions."); + @"Setting circleBlur to a camera-data expression should update circle-blur."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.circleBlur, pedanticFunctionExpression, + @"circleBlur should round-trip camera-data expressions."); layer.circleBlur = nil; XCTAssertTrue(rawLayer->getCircleBlur().isUndefined(), @"Unsetting circleBlur should return circle-blur to the default value."); - XCTAssertEqualObjects(layer.circleBlur, defaultStyleValue, + XCTAssertEqualObjects(layer.circleBlur, defaultExpression, @"circleBlur should return the default value after being unset."); // Transition property test layer.circleBlurTransition = transitionTest; @@ -118,40 +124,45 @@ { XCTAssertTrue(rawLayer->getCircleColor().isUndefined(), @"circle-color should be unset initially."); - MGLStyleValue<MGLColor *> *defaultStyleValue = layer.circleColor; + NSExpression *defaultExpression = layer.circleColor; - MGLStyleValue<MGLColor *> *constantStyleValue = [MGLStyleValue<MGLColor *> valueWithRawValue:[MGLColor redColor]]; - layer.circleColor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.circleColor = constantExpression; mbgl::style::DataDrivenPropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } }; XCTAssertEqual(rawLayer->getCircleColor(), propertyValue, - @"Setting circleColor to a constant value should update circle-color."); - XCTAssertEqualObjects(layer.circleColor, constantStyleValue, - @"circleColor should round-trip constant values."); - - MGLStyleValue<MGLColor *> * functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.circleColor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::Color> intervalStops = { {{18, { 1, 0, 0, 1 }}} }; + @"Setting circleColor to a constant value expression should update circle-color."); + XCTAssertEqualObjects(layer.circleColor, constantExpression, + @"circleColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.circleColor = functionExpression; + + mbgl::style::IntervalStops<mbgl::Color> intervalStops = {{ + { -INFINITY, { 1, 0, 0, 1 } }, + { 18, { 1, 0, 0, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops }; XCTAssertEqual(rawLayer->getCircleColor(), propertyValue, - @"Setting circleColor to a camera function should update circle-color."); - XCTAssertEqualObjects(layer.circleColor, functionStyleValue, - @"circleColor should round-trip camera functions."); + @"Setting circleColor to a camera expression should update circle-color."); + XCTAssertEqualObjects(layer.circleColor, functionExpression, + @"circleColor should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.circleColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.circleColor = functionExpression; mbgl::style::ExponentialStops<mbgl::Color> exponentialStops = { {{18, { 1, 0, 0, 1 }}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<mbgl::Color> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getCircleColor(), propertyValue, - @"Setting circleColor to a source function should update circle-color."); - XCTAssertEqualObjects(layer.circleColor, functionStyleValue, - @"circleColor should round-trip source functions."); + @"Setting circleColor to a data expression should update circle-color."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.circleColor, pedanticFunctionExpression, + @"circleColor should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.circleColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.circleColor = functionExpression; std::map<float, mbgl::Color> innerStops { {18, { 1, 0, 0, 1 }} }; mbgl::style::CompositeExponentialStops<mbgl::Color> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -159,15 +170,16 @@ propertyValue = mbgl::style::CompositeFunction<mbgl::Color> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getCircleColor(), propertyValue, - @"Setting circleColor to a composite function should update circle-color."); - XCTAssertEqualObjects(layer.circleColor, functionStyleValue, - @"circleColor should round-trip composite functions."); + @"Setting circleColor to a camera-data expression should update circle-color."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.circleColor, pedanticFunctionExpression, + @"circleColor should round-trip camera-data expressions."); layer.circleColor = nil; XCTAssertTrue(rawLayer->getCircleColor().isUndefined(), @"Unsetting circleColor should return circle-color to the default value."); - XCTAssertEqualObjects(layer.circleColor, defaultStyleValue, + XCTAssertEqualObjects(layer.circleColor, defaultExpression, @"circleColor should return the default value after being unset."); // Transition property test layer.circleColorTransition = transitionTest; @@ -184,40 +196,45 @@ { XCTAssertTrue(rawLayer->getCircleOpacity().isUndefined(), @"circle-opacity should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.circleOpacity; + NSExpression *defaultExpression = layer.circleOpacity; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.circleOpacity = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.circleOpacity = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getCircleOpacity(), propertyValue, - @"Setting circleOpacity to a constant value should update circle-opacity."); - XCTAssertEqualObjects(layer.circleOpacity, constantStyleValue, - @"circleOpacity should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.circleOpacity = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting circleOpacity to a constant value expression should update circle-opacity."); + XCTAssertEqualObjects(layer.circleOpacity, constantExpression, + @"circleOpacity should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.circleOpacity = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getCircleOpacity(), propertyValue, - @"Setting circleOpacity to a camera function should update circle-opacity."); - XCTAssertEqualObjects(layer.circleOpacity, functionStyleValue, - @"circleOpacity should round-trip camera functions."); + @"Setting circleOpacity to a camera expression should update circle-opacity."); + XCTAssertEqualObjects(layer.circleOpacity, functionExpression, + @"circleOpacity should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.circleOpacity = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.circleOpacity = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getCircleOpacity(), propertyValue, - @"Setting circleOpacity to a source function should update circle-opacity."); - XCTAssertEqualObjects(layer.circleOpacity, functionStyleValue, - @"circleOpacity should round-trip source functions."); + @"Setting circleOpacity to a data expression should update circle-opacity."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.circleOpacity, pedanticFunctionExpression, + @"circleOpacity should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.circleOpacity = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.circleOpacity = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -225,15 +242,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getCircleOpacity(), propertyValue, - @"Setting circleOpacity to a composite function should update circle-opacity."); - XCTAssertEqualObjects(layer.circleOpacity, functionStyleValue, - @"circleOpacity should round-trip composite functions."); + @"Setting circleOpacity to a camera-data expression should update circle-opacity."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.circleOpacity, pedanticFunctionExpression, + @"circleOpacity should round-trip camera-data expressions."); layer.circleOpacity = nil; XCTAssertTrue(rawLayer->getCircleOpacity().isUndefined(), @"Unsetting circleOpacity should return circle-opacity to the default value."); - XCTAssertEqualObjects(layer.circleOpacity, defaultStyleValue, + XCTAssertEqualObjects(layer.circleOpacity, defaultExpression, @"circleOpacity should return the default value after being unset."); // Transition property test layer.circleOpacityTransition = transitionTest; @@ -250,79 +268,89 @@ { XCTAssertTrue(rawLayer->getCirclePitchAlignment().isUndefined(), @"circle-pitch-alignment should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.circlePitchAlignment; + NSExpression *defaultExpression = layer.circlePitchAlignment; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLCirclePitchAlignment:MGLCirclePitchAlignmentViewport]]; - layer.circlePitchAlignment = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + layer.circlePitchAlignment = constantExpression; mbgl::style::PropertyValue<mbgl::style::AlignmentType> propertyValue = { mbgl::style::AlignmentType::Viewport }; XCTAssertEqual(rawLayer->getCirclePitchAlignment(), propertyValue, - @"Setting circlePitchAlignment to a constant value should update circle-pitch-alignment."); - XCTAssertEqualObjects(layer.circlePitchAlignment, constantStyleValue, - @"circlePitchAlignment should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.circlePitchAlignment = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::AlignmentType> intervalStops = { {{18, mbgl::style::AlignmentType::Viewport}} }; + @"Setting circlePitchAlignment to a constant value expression should update circle-pitch-alignment."); + XCTAssertEqualObjects(layer.circlePitchAlignment, constantExpression, + @"circlePitchAlignment should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.circlePitchAlignment = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::AlignmentType> intervalStops = {{ + { -INFINITY, mbgl::style::AlignmentType::Viewport }, + { 18, mbgl::style::AlignmentType::Viewport }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::AlignmentType> { intervalStops }; XCTAssertEqual(rawLayer->getCirclePitchAlignment(), propertyValue, - @"Setting circlePitchAlignment to a camera function should update circle-pitch-alignment."); - XCTAssertEqualObjects(layer.circlePitchAlignment, functionStyleValue, - @"circlePitchAlignment should round-trip camera functions."); + @"Setting circlePitchAlignment to a camera expression should update circle-pitch-alignment."); + XCTAssertEqualObjects(layer.circlePitchAlignment, functionExpression, + @"circlePitchAlignment should round-trip camera expressions."); layer.circlePitchAlignment = nil; XCTAssertTrue(rawLayer->getCirclePitchAlignment().isUndefined(), @"Unsetting circlePitchAlignment should return circle-pitch-alignment to the default value."); - XCTAssertEqualObjects(layer.circlePitchAlignment, defaultStyleValue, + XCTAssertEqualObjects(layer.circlePitchAlignment, defaultExpression, @"circlePitchAlignment should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.circlePitchAlignment = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.circlePitchAlignment = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.circlePitchAlignment = functionExpression, NSException, NSInvalidArgumentException, @"MGLCircleLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.circlePitchAlignment = functionExpression, NSException, NSInvalidArgumentException, @"MGLCircleLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // circle-radius { XCTAssertTrue(rawLayer->getCircleRadius().isUndefined(), @"circle-radius should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.circleRadius; + NSExpression *defaultExpression = layer.circleRadius; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.circleRadius = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.circleRadius = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getCircleRadius(), propertyValue, - @"Setting circleRadius to a constant value should update circle-radius."); - XCTAssertEqualObjects(layer.circleRadius, constantStyleValue, - @"circleRadius should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.circleRadius = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting circleRadius to a constant value expression should update circle-radius."); + XCTAssertEqualObjects(layer.circleRadius, constantExpression, + @"circleRadius should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.circleRadius = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getCircleRadius(), propertyValue, - @"Setting circleRadius to a camera function should update circle-radius."); - XCTAssertEqualObjects(layer.circleRadius, functionStyleValue, - @"circleRadius should round-trip camera functions."); + @"Setting circleRadius to a camera expression should update circle-radius."); + XCTAssertEqualObjects(layer.circleRadius, functionExpression, + @"circleRadius should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.circleRadius = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.circleRadius = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getCircleRadius(), propertyValue, - @"Setting circleRadius to a source function should update circle-radius."); - XCTAssertEqualObjects(layer.circleRadius, functionStyleValue, - @"circleRadius should round-trip source functions."); + @"Setting circleRadius to a data expression should update circle-radius."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.circleRadius, pedanticFunctionExpression, + @"circleRadius should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.circleRadius = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.circleRadius = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -330,15 +358,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getCircleRadius(), propertyValue, - @"Setting circleRadius to a composite function should update circle-radius."); - XCTAssertEqualObjects(layer.circleRadius, functionStyleValue, - @"circleRadius should round-trip composite functions."); + @"Setting circleRadius to a camera-data expression should update circle-radius."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.circleRadius, pedanticFunctionExpression, + @"circleRadius should round-trip camera-data expressions."); layer.circleRadius = nil; XCTAssertTrue(rawLayer->getCircleRadius().isUndefined(), @"Unsetting circleRadius should return circle-radius to the default value."); - XCTAssertEqualObjects(layer.circleRadius, defaultStyleValue, + XCTAssertEqualObjects(layer.circleRadius, defaultExpression, @"circleRadius should return the default value after being unset."); // Transition property test layer.circleRadiusTransition = transitionTest; @@ -355,79 +384,89 @@ { XCTAssertTrue(rawLayer->getCirclePitchScale().isUndefined(), @"circle-pitch-scale should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.circleScaleAlignment; + NSExpression *defaultExpression = layer.circleScaleAlignment; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLCircleScaleAlignment:MGLCircleScaleAlignmentViewport]]; - layer.circleScaleAlignment = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + layer.circleScaleAlignment = constantExpression; mbgl::style::PropertyValue<mbgl::style::CirclePitchScaleType> propertyValue = { mbgl::style::CirclePitchScaleType::Viewport }; XCTAssertEqual(rawLayer->getCirclePitchScale(), propertyValue, - @"Setting circleScaleAlignment to a constant value should update circle-pitch-scale."); - XCTAssertEqualObjects(layer.circleScaleAlignment, constantStyleValue, - @"circleScaleAlignment should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.circleScaleAlignment = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::CirclePitchScaleType> intervalStops = { {{18, mbgl::style::CirclePitchScaleType::Viewport}} }; + @"Setting circleScaleAlignment to a constant value expression should update circle-pitch-scale."); + XCTAssertEqualObjects(layer.circleScaleAlignment, constantExpression, + @"circleScaleAlignment should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.circleScaleAlignment = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::CirclePitchScaleType> intervalStops = {{ + { -INFINITY, mbgl::style::CirclePitchScaleType::Viewport }, + { 18, mbgl::style::CirclePitchScaleType::Viewport }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::CirclePitchScaleType> { intervalStops }; XCTAssertEqual(rawLayer->getCirclePitchScale(), propertyValue, - @"Setting circleScaleAlignment to a camera function should update circle-pitch-scale."); - XCTAssertEqualObjects(layer.circleScaleAlignment, functionStyleValue, - @"circleScaleAlignment should round-trip camera functions."); + @"Setting circleScaleAlignment to a camera expression should update circle-pitch-scale."); + XCTAssertEqualObjects(layer.circleScaleAlignment, functionExpression, + @"circleScaleAlignment should round-trip camera expressions."); layer.circleScaleAlignment = nil; XCTAssertTrue(rawLayer->getCirclePitchScale().isUndefined(), @"Unsetting circleScaleAlignment should return circle-pitch-scale to the default value."); - XCTAssertEqualObjects(layer.circleScaleAlignment, defaultStyleValue, + XCTAssertEqualObjects(layer.circleScaleAlignment, defaultExpression, @"circleScaleAlignment should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.circleScaleAlignment = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.circleScaleAlignment = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.circleScaleAlignment = functionExpression, NSException, NSInvalidArgumentException, @"MGLCircleLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.circleScaleAlignment = functionExpression, NSException, NSInvalidArgumentException, @"MGLCircleLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // circle-stroke-color { XCTAssertTrue(rawLayer->getCircleStrokeColor().isUndefined(), @"circle-stroke-color should be unset initially."); - MGLStyleValue<MGLColor *> *defaultStyleValue = layer.circleStrokeColor; + NSExpression *defaultExpression = layer.circleStrokeColor; - MGLStyleValue<MGLColor *> *constantStyleValue = [MGLStyleValue<MGLColor *> valueWithRawValue:[MGLColor redColor]]; - layer.circleStrokeColor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.circleStrokeColor = constantExpression; mbgl::style::DataDrivenPropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } }; XCTAssertEqual(rawLayer->getCircleStrokeColor(), propertyValue, - @"Setting circleStrokeColor to a constant value should update circle-stroke-color."); - XCTAssertEqualObjects(layer.circleStrokeColor, constantStyleValue, - @"circleStrokeColor should round-trip constant values."); - - MGLStyleValue<MGLColor *> * functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.circleStrokeColor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::Color> intervalStops = { {{18, { 1, 0, 0, 1 }}} }; + @"Setting circleStrokeColor to a constant value expression should update circle-stroke-color."); + XCTAssertEqualObjects(layer.circleStrokeColor, constantExpression, + @"circleStrokeColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.circleStrokeColor = functionExpression; + + mbgl::style::IntervalStops<mbgl::Color> intervalStops = {{ + { -INFINITY, { 1, 0, 0, 1 } }, + { 18, { 1, 0, 0, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops }; XCTAssertEqual(rawLayer->getCircleStrokeColor(), propertyValue, - @"Setting circleStrokeColor to a camera function should update circle-stroke-color."); - XCTAssertEqualObjects(layer.circleStrokeColor, functionStyleValue, - @"circleStrokeColor should round-trip camera functions."); + @"Setting circleStrokeColor to a camera expression should update circle-stroke-color."); + XCTAssertEqualObjects(layer.circleStrokeColor, functionExpression, + @"circleStrokeColor should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.circleStrokeColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.circleStrokeColor = functionExpression; mbgl::style::ExponentialStops<mbgl::Color> exponentialStops = { {{18, { 1, 0, 0, 1 }}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<mbgl::Color> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getCircleStrokeColor(), propertyValue, - @"Setting circleStrokeColor to a source function should update circle-stroke-color."); - XCTAssertEqualObjects(layer.circleStrokeColor, functionStyleValue, - @"circleStrokeColor should round-trip source functions."); + @"Setting circleStrokeColor to a data expression should update circle-stroke-color."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.circleStrokeColor, pedanticFunctionExpression, + @"circleStrokeColor should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.circleStrokeColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.circleStrokeColor = functionExpression; std::map<float, mbgl::Color> innerStops { {18, { 1, 0, 0, 1 }} }; mbgl::style::CompositeExponentialStops<mbgl::Color> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -435,15 +474,16 @@ propertyValue = mbgl::style::CompositeFunction<mbgl::Color> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getCircleStrokeColor(), propertyValue, - @"Setting circleStrokeColor to a composite function should update circle-stroke-color."); - XCTAssertEqualObjects(layer.circleStrokeColor, functionStyleValue, - @"circleStrokeColor should round-trip composite functions."); + @"Setting circleStrokeColor to a camera-data expression should update circle-stroke-color."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.circleStrokeColor, pedanticFunctionExpression, + @"circleStrokeColor should round-trip camera-data expressions."); layer.circleStrokeColor = nil; XCTAssertTrue(rawLayer->getCircleStrokeColor().isUndefined(), @"Unsetting circleStrokeColor should return circle-stroke-color to the default value."); - XCTAssertEqualObjects(layer.circleStrokeColor, defaultStyleValue, + XCTAssertEqualObjects(layer.circleStrokeColor, defaultExpression, @"circleStrokeColor should return the default value after being unset."); // Transition property test layer.circleStrokeColorTransition = transitionTest; @@ -460,40 +500,45 @@ { XCTAssertTrue(rawLayer->getCircleStrokeOpacity().isUndefined(), @"circle-stroke-opacity should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.circleStrokeOpacity; + NSExpression *defaultExpression = layer.circleStrokeOpacity; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.circleStrokeOpacity = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.circleStrokeOpacity = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getCircleStrokeOpacity(), propertyValue, - @"Setting circleStrokeOpacity to a constant value should update circle-stroke-opacity."); - XCTAssertEqualObjects(layer.circleStrokeOpacity, constantStyleValue, - @"circleStrokeOpacity should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.circleStrokeOpacity = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting circleStrokeOpacity to a constant value expression should update circle-stroke-opacity."); + XCTAssertEqualObjects(layer.circleStrokeOpacity, constantExpression, + @"circleStrokeOpacity should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.circleStrokeOpacity = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getCircleStrokeOpacity(), propertyValue, - @"Setting circleStrokeOpacity to a camera function should update circle-stroke-opacity."); - XCTAssertEqualObjects(layer.circleStrokeOpacity, functionStyleValue, - @"circleStrokeOpacity should round-trip camera functions."); + @"Setting circleStrokeOpacity to a camera expression should update circle-stroke-opacity."); + XCTAssertEqualObjects(layer.circleStrokeOpacity, functionExpression, + @"circleStrokeOpacity should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.circleStrokeOpacity = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.circleStrokeOpacity = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getCircleStrokeOpacity(), propertyValue, - @"Setting circleStrokeOpacity to a source function should update circle-stroke-opacity."); - XCTAssertEqualObjects(layer.circleStrokeOpacity, functionStyleValue, - @"circleStrokeOpacity should round-trip source functions."); + @"Setting circleStrokeOpacity to a data expression should update circle-stroke-opacity."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.circleStrokeOpacity, pedanticFunctionExpression, + @"circleStrokeOpacity should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.circleStrokeOpacity = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.circleStrokeOpacity = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -501,15 +546,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getCircleStrokeOpacity(), propertyValue, - @"Setting circleStrokeOpacity to a composite function should update circle-stroke-opacity."); - XCTAssertEqualObjects(layer.circleStrokeOpacity, functionStyleValue, - @"circleStrokeOpacity should round-trip composite functions."); + @"Setting circleStrokeOpacity to a camera-data expression should update circle-stroke-opacity."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.circleStrokeOpacity, pedanticFunctionExpression, + @"circleStrokeOpacity should round-trip camera-data expressions."); layer.circleStrokeOpacity = nil; XCTAssertTrue(rawLayer->getCircleStrokeOpacity().isUndefined(), @"Unsetting circleStrokeOpacity should return circle-stroke-opacity to the default value."); - XCTAssertEqualObjects(layer.circleStrokeOpacity, defaultStyleValue, + XCTAssertEqualObjects(layer.circleStrokeOpacity, defaultExpression, @"circleStrokeOpacity should return the default value after being unset."); // Transition property test layer.circleStrokeOpacityTransition = transitionTest; @@ -526,40 +572,45 @@ { XCTAssertTrue(rawLayer->getCircleStrokeWidth().isUndefined(), @"circle-stroke-width should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.circleStrokeWidth; + NSExpression *defaultExpression = layer.circleStrokeWidth; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.circleStrokeWidth = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.circleStrokeWidth = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getCircleStrokeWidth(), propertyValue, - @"Setting circleStrokeWidth to a constant value should update circle-stroke-width."); - XCTAssertEqualObjects(layer.circleStrokeWidth, constantStyleValue, - @"circleStrokeWidth should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.circleStrokeWidth = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting circleStrokeWidth to a constant value expression should update circle-stroke-width."); + XCTAssertEqualObjects(layer.circleStrokeWidth, constantExpression, + @"circleStrokeWidth should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.circleStrokeWidth = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getCircleStrokeWidth(), propertyValue, - @"Setting circleStrokeWidth to a camera function should update circle-stroke-width."); - XCTAssertEqualObjects(layer.circleStrokeWidth, functionStyleValue, - @"circleStrokeWidth should round-trip camera functions."); + @"Setting circleStrokeWidth to a camera expression should update circle-stroke-width."); + XCTAssertEqualObjects(layer.circleStrokeWidth, functionExpression, + @"circleStrokeWidth should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.circleStrokeWidth = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.circleStrokeWidth = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getCircleStrokeWidth(), propertyValue, - @"Setting circleStrokeWidth to a source function should update circle-stroke-width."); - XCTAssertEqualObjects(layer.circleStrokeWidth, functionStyleValue, - @"circleStrokeWidth should round-trip source functions."); + @"Setting circleStrokeWidth to a data expression should update circle-stroke-width."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.circleStrokeWidth, pedanticFunctionExpression, + @"circleStrokeWidth should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.circleStrokeWidth = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.circleStrokeWidth = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -567,15 +618,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getCircleStrokeWidth(), propertyValue, - @"Setting circleStrokeWidth to a composite function should update circle-stroke-width."); - XCTAssertEqualObjects(layer.circleStrokeWidth, functionStyleValue, - @"circleStrokeWidth should round-trip composite functions."); + @"Setting circleStrokeWidth to a camera-data expression should update circle-stroke-width."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.circleStrokeWidth, pedanticFunctionExpression, + @"circleStrokeWidth should round-trip camera-data expressions."); layer.circleStrokeWidth = nil; XCTAssertTrue(rawLayer->getCircleStrokeWidth().isUndefined(), @"Unsetting circleStrokeWidth should return circle-stroke-width to the default value."); - XCTAssertEqualObjects(layer.circleStrokeWidth, defaultStyleValue, + XCTAssertEqualObjects(layer.circleStrokeWidth, defaultExpression, @"circleStrokeWidth should return the default value after being unset."); // Transition property test layer.circleStrokeWidthTransition = transitionTest; @@ -592,84 +644,94 @@ { XCTAssertTrue(rawLayer->getCircleTranslate().isUndefined(), @"circle-translate should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.circleTranslation; + NSExpression *defaultExpression = layer.circleTranslation; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue: + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", #if TARGET_OS_IPHONE [NSValue valueWithCGVector:CGVectorMake(1, 1)] #else [NSValue valueWithMGLVector:CGVectorMake(1, -1)] #endif ]; - layer.circleTranslation = constantStyleValue; + layer.circleTranslation = constantExpression; mbgl::style::PropertyValue<std::array<float, 2>> propertyValue = { { 1, 1 } }; XCTAssertEqual(rawLayer->getCircleTranslate(), propertyValue, - @"Setting circleTranslation to a constant value should update circle-translate."); - XCTAssertEqualObjects(layer.circleTranslation, constantStyleValue, - @"circleTranslation should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.circleTranslation = functionStyleValue; - - mbgl::style::IntervalStops<std::array<float, 2>> intervalStops = { {{18, { 1, 1 }}} }; + @"Setting circleTranslation to a constant value expression should update circle-translate."); + XCTAssertEqualObjects(layer.circleTranslation, constantExpression, + @"circleTranslation should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"{1, 1}"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.circleTranslation = functionExpression; + + mbgl::style::IntervalStops<std::array<float, 2>> intervalStops = {{ + { -INFINITY, { 1, 1 } }, + { 18, { 1, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<std::array<float, 2>> { intervalStops }; XCTAssertEqual(rawLayer->getCircleTranslate(), propertyValue, - @"Setting circleTranslation to a camera function should update circle-translate."); - XCTAssertEqualObjects(layer.circleTranslation, functionStyleValue, - @"circleTranslation should round-trip camera functions."); + @"Setting circleTranslation to a camera expression should update circle-translate."); + XCTAssertEqualObjects(layer.circleTranslation, functionExpression, + @"circleTranslation should round-trip camera expressions."); layer.circleTranslation = nil; XCTAssertTrue(rawLayer->getCircleTranslate().isUndefined(), @"Unsetting circleTranslation should return circle-translate to the default value."); - XCTAssertEqualObjects(layer.circleTranslation, defaultStyleValue, + XCTAssertEqualObjects(layer.circleTranslation, defaultExpression, @"circleTranslation should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.circleTranslation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.circleTranslation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.circleTranslation = functionExpression, NSException, NSInvalidArgumentException, @"MGLCircleLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.circleTranslation = functionExpression, NSException, NSInvalidArgumentException, @"MGLCircleLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // circle-translate-anchor { XCTAssertTrue(rawLayer->getCircleTranslateAnchor().isUndefined(), @"circle-translate-anchor should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.circleTranslationAnchor; + NSExpression *defaultExpression = layer.circleTranslationAnchor; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLCircleTranslationAnchor:MGLCircleTranslationAnchorViewport]]; - layer.circleTranslationAnchor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + layer.circleTranslationAnchor = constantExpression; mbgl::style::PropertyValue<mbgl::style::TranslateAnchorType> propertyValue = { mbgl::style::TranslateAnchorType::Viewport }; XCTAssertEqual(rawLayer->getCircleTranslateAnchor(), propertyValue, - @"Setting circleTranslationAnchor to a constant value should update circle-translate-anchor."); - XCTAssertEqualObjects(layer.circleTranslationAnchor, constantStyleValue, - @"circleTranslationAnchor should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.circleTranslationAnchor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::TranslateAnchorType> intervalStops = { {{18, mbgl::style::TranslateAnchorType::Viewport}} }; + @"Setting circleTranslationAnchor to a constant value expression should update circle-translate-anchor."); + XCTAssertEqualObjects(layer.circleTranslationAnchor, constantExpression, + @"circleTranslationAnchor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.circleTranslationAnchor = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::TranslateAnchorType> intervalStops = {{ + { -INFINITY, mbgl::style::TranslateAnchorType::Viewport }, + { 18, mbgl::style::TranslateAnchorType::Viewport }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::TranslateAnchorType> { intervalStops }; XCTAssertEqual(rawLayer->getCircleTranslateAnchor(), propertyValue, - @"Setting circleTranslationAnchor to a camera function should update circle-translate-anchor."); - XCTAssertEqualObjects(layer.circleTranslationAnchor, functionStyleValue, - @"circleTranslationAnchor should round-trip camera functions."); + @"Setting circleTranslationAnchor to a camera expression should update circle-translate-anchor."); + XCTAssertEqualObjects(layer.circleTranslationAnchor, functionExpression, + @"circleTranslationAnchor should round-trip camera expressions."); layer.circleTranslationAnchor = nil; XCTAssertTrue(rawLayer->getCircleTranslateAnchor().isUndefined(), @"Unsetting circleTranslationAnchor should return circle-translate-anchor to the default value."); - XCTAssertEqualObjects(layer.circleTranslationAnchor, defaultStyleValue, + XCTAssertEqualObjects(layer.circleTranslationAnchor, defaultExpression, @"circleTranslationAnchor should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.circleTranslationAnchor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.circleTranslationAnchor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.circleTranslationAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLCircleLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.circleTranslationAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLCircleLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } } diff --git a/platform/darwin/test/MGLComputedShapeSourceTests.m b/platform/darwin/test/MGLComputedShapeSourceTests.m new file mode 100644 index 0000000000..6eb45913d6 --- /dev/null +++ b/platform/darwin/test/MGLComputedShapeSourceTests.m @@ -0,0 +1,24 @@ +#import <XCTest/XCTest.h> + +#import <Mapbox/Mapbox.h> + + +@interface MGLComputedShapeSourceTests : XCTestCase +@end + +@implementation MGLComputedShapeSourceTests + +- (void)testInitializer { + MGLComputedShapeSource *source = [[MGLComputedShapeSource alloc] initWithIdentifier:@"id" options:@{}]; + XCTAssertNotNil(source); + XCTAssertNotNil(source.requestQueue); + XCTAssertNil(source.dataSource); +} + +- (void)testNilOptions { + MGLComputedShapeSource *source = [[MGLComputedShapeSource alloc] initWithIdentifier:@"id" options:nil]; + XCTAssertNotNil(source); +} + + +@end diff --git a/platform/darwin/test/MGLDocumentationExampleTests.swift b/platform/darwin/test/MGLDocumentationExampleTests.swift index 668e5f57f8..9edb33a078 100644 --- a/platform/darwin/test/MGLDocumentationExampleTests.swift +++ b/platform/darwin/test/MGLDocumentationExampleTests.swift @@ -73,14 +73,14 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { XCTAssertNotNil(mapView.style?.source(withIdentifier: "lines")) } - func testMGLRasterSource() { + func testMGLRasterTileSource() { //#-example-code - let source = MGLRasterSource(identifier: "clouds", tileURLTemplates: ["https://example.com/raster-tiles/{z}/{x}/{y}.png"], options: [ + let source = MGLRasterTileSource(identifier: "clouds", tileURLTemplates: ["https://example.com/raster-tiles/{z}/{x}/{y}.png"], options: [ .minimumZoomLevel: 9, .maximumZoomLevel: 16, .tileSize: 512, .attributionInfos: [ - MGLAttributionInfo(title: NSAttributedString(string: "© Mapbox"), url: URL(string: "http://mapbox.com")) + MGLAttributionInfo(title: NSAttributedString(string: "© Mapbox"), url: URL(string: "https://mapbox.com")) ] ]) mapView.style?.addSource(source) @@ -88,14 +88,34 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { XCTAssertNotNil(mapView.style?.source(withIdentifier: "clouds")) } + + func testMGLRasterDEMSource() { + // We want to use mapbox.terrain-rgb in the example, but using a mapbox: + // URL requires setting an access token. So this identically named + // subclass of MGLRasterDEMSource swaps in a nonexistent URL. + class MGLRasterDEMSource: Mapbox.MGLRasterDEMSource { + override init(identifier: String, configurationURL: URL, tileSize: CGFloat = 256) { + let bogusURL = URL(string: "https://example.com/raster-rgb.json")! + super.init(identifier: identifier, configurationURL: bogusURL, tileSize: tileSize) + } + } + + //#-example-code + let terrainRGBURL = URL(string: "mapbox://mapbox.terrain-rgb")! + let source = MGLRasterDEMSource(identifier: "hills", configurationURL: terrainRGBURL) + mapView.style?.addSource(source) + //#-end-example-code + + XCTAssertNotNil(mapView.style?.source(withIdentifier: "hills")) + } - func testMGLVectorSource() { + func testMGLVectorTileSource() { //#-example-code - let source = MGLVectorSource(identifier: "pois", tileURLTemplates: ["https://example.com/vector-tiles/{z}/{x}/{y}.mvt"], options: [ + let source = MGLVectorTileSource(identifier: "pois", tileURLTemplates: ["https://example.com/vector-tiles/{z}/{x}/{y}.mvt"], options: [ .minimumZoomLevel: 9, .maximumZoomLevel: 16, .attributionInfos: [ - MGLAttributionInfo(title: NSAttributedString(string: "© Mapbox"), url: URL(string: "http://mapbox.com")) + MGLAttributionInfo(title: NSAttributedString(string: "© Mapbox"), url: URL(string: "https://mapbox.com")) ] ]) mapView.style?.addSource(source) @@ -131,18 +151,21 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { } func testMGLCircleStyleLayer() { - let population = MGLVectorSource(identifier: "population", configurationURL: URL(string: "https://example.com/style.json")!) + let population = MGLVectorTileSource(identifier: "population", configurationURL: URL(string: "https://example.com/style.json")!) mapView.style?.addSource(population) //#-example-code let layer = MGLCircleStyleLayer(identifier: "circles", source: population) layer.sourceLayerIdentifier = "population" - layer.circleColor = MGLStyleValue(rawValue: .green) - layer.circleRadius = MGLStyleValue(interpolationMode: .exponential, - cameraStops: [12: MGLStyleValue(rawValue: 2), - 22: MGLStyleValue(rawValue: 180)], - options: [.interpolationBase: 1.75]) - layer.circleOpacity = MGLStyleValue(rawValue: 0.7) + #if os(macOS) + layer.circleColor = NSExpression(forConstantValue: NSColor.green) + #else + layer.circleColor = NSExpression(forConstantValue: UIColor.green) + #endif + layer.circleRadius = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'exponential', 1.75, %@)", + [12: 2, + 22: 180]) + layer.circleOpacity = NSExpression(forConstantValue: 0.7) layer.predicate = NSPredicate(format: "%K == %@", "marital-status", "married") mapView.style?.addLayer(layer) //#-end-example-code @@ -151,18 +174,21 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { } func testMGLLineStyleLayer() { - let trails = MGLVectorSource(identifier: "trails", configurationURL: URL(string: "https://example.com/style.json")!) + let trails = MGLVectorTileSource(identifier: "trails", configurationURL: URL(string: "https://example.com/style.json")!) mapView.style?.addSource(trails) //#-example-code let layer = MGLLineStyleLayer(identifier: "trails-path", source: trails) layer.sourceLayerIdentifier = "trails" - layer.lineWidth = MGLStyleValue(interpolationMode: .exponential, - cameraStops: [14: MGLStyleValue(rawValue: 2), - 18: MGLStyleValue(rawValue: 20)], - options: [.interpolationBase: 1.5]) - layer.lineColor = MGLStyleValue(rawValue: .brown) - layer.lineCap = MGLStyleValue(rawValue: NSValue(mglLineCap: .round)) + layer.lineWidth = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'exponential', 1.5, %@)", + [14: 2, + 18: 20]) + #if os(macOS) + layer.lineColor = NSExpression(forConstantValue: NSColor.brown) + #else + layer.lineColor = NSExpression(forConstantValue: UIColor.brown) + #endif + layer.lineCap = NSExpression(forConstantValue: "round") layer.predicate = NSPredicate(format: "%K == %@", "trail-type", "mountain-biking") mapView.style?.addLayer(layer) //#-end-example-code @@ -171,13 +197,17 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { } func testMGLFillStyleLayer() { - let parks = MGLVectorSource(identifier: "parks", configurationURL: URL(string: "https://example.com/style.json")!) + let parks = MGLVectorTileSource(identifier: "parks", configurationURL: URL(string: "https://example.com/style.json")!) mapView.style?.addSource(parks) //#-example-code let layer = MGLFillStyleLayer(identifier: "parks", source: parks) layer.sourceLayerIdentifier = "parks" - layer.fillColor = MGLStyleValue(rawValue: .green) + #if os(macOS) + layer.fillColor = NSExpression(forConstantValue: NSColor.green) + #else + layer.fillColor = NSExpression(forConstantValue: UIColor.green) + #endif layer.predicate = NSPredicate(format: "type == %@", "national-park") mapView.style?.addLayer(layer) //#-end-example-code @@ -186,39 +216,57 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { } func testMGLFillExtrusionStyleLayer() { - let buildings = MGLVectorSource(identifier: "buildings", configurationURL: URL(string: "https://example.com/style.json")!) + let buildings = MGLVectorTileSource(identifier: "buildings", configurationURL: URL(string: "https://example.com/style.json")!) mapView.style?.addSource(buildings) //#-example-code let layer = MGLFillExtrusionStyleLayer(identifier: "buildings", source: buildings) layer.sourceLayerIdentifier = "building" - layer.fillExtrusionHeight = MGLStyleValue(interpolationMode: .identity, sourceStops: nil, attributeName: "height", options: nil) - layer.fillExtrusionBase = MGLStyleValue(interpolationMode: .identity, sourceStops: nil, attributeName: "min_height", options: nil) + layer.fillExtrusionHeight = NSExpression(forKeyPath: "height") + layer.fillExtrusionBase = NSExpression(forKeyPath: "min_height") layer.predicate = NSPredicate(format: "extrude == 'true'") mapView.style?.addLayer(layer) //#-end-example-code XCTAssertNotNil(mapView.style?.layer(withIdentifier: "buildings")) } + + func testMGLHeatmapStyleLayer() { + let earthquakes = MGLShapeSource(identifier: "earthquakes", url: URL(string: "https://example.com/earthquakes.json")!, options: [:]) + mapView.style?.addSource(earthquakes) + + //#-example-code + let layer = MGLHeatmapStyleLayer(identifier: "earthquake-heat", source: earthquakes) + layer.heatmapWeight = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:(magnitude, 'linear', nil, %@)", + [0: 0, + 6: 1]) + layer.heatmapIntensity = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", + [0: 1, + 9: 3]) + mapView.style?.addLayer(layer) + //#-end-example-code + + XCTAssertNotNil(mapView.style?.layer(withIdentifier: "earthquake-heat")) + } func testMGLSymbolStyleLayer() { - let pois = MGLVectorSource(identifier: "pois", configurationURL: URL(string: "https://example.com/style.json")!) + let pois = MGLVectorTileSource(identifier: "pois", configurationURL: URL(string: "https://example.com/style.json")!) mapView.style?.addSource(pois) //#-example-code let layer = MGLSymbolStyleLayer(identifier: "coffeeshops", source: pois) layer.sourceLayerIdentifier = "pois" - layer.iconImageName = MGLStyleValue(rawValue: "coffee") - layer.iconScale = MGLStyleValue(rawValue: 0.5) - layer.text = MGLStyleValue(rawValue: "{name}") + layer.iconImageName = NSExpression(forConstantValue: "coffee") + layer.iconScale = NSExpression(forConstantValue: 0.5) + layer.text = NSExpression(forKeyPath: "name") #if os(macOS) var vector = CGVector(dx: 10, dy: 0) - layer.textTranslation = MGLStyleValue(rawValue: NSValue(bytes: &vector, objCType: "{CGVector=dd}")) + layer.textTranslation = NSExpression(forConstantValue: NSValue(bytes: &vector, objCType: "{CGVector=dd}")) #else - layer.textTranslation = MGLStyleValue(rawValue: NSValue(cgVector: CGVector(dx: 10, dy: 0))) + layer.textTranslation = NSExpression(forConstantValue: NSValue(cgVector: CGVector(dx: 10, dy: 0))) #endif - layer.textJustification = MGLStyleValue(rawValue: NSValue(mglTextJustification: .left)) - layer.textAnchor = MGLStyleValue(rawValue: NSValue(mglTextAnchor: .left)) + layer.textJustification = NSExpression(forConstantValue: "left") + layer.textAnchor = NSExpression(forConstantValue: "left") layer.predicate = NSPredicate(format: "%K == %@", "venue-type", "coffee") mapView.style?.addLayer(layer) //#-end-example-code @@ -227,33 +275,60 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { } func testMGLRasterStyleLayer() { - let source = MGLRasterSource(identifier: "clouds", tileURLTemplates: ["https://example.com/raster-tiles/{z}/{x}/{y}.png"], options: [ + let source = MGLRasterTileSource(identifier: "clouds", tileURLTemplates: ["https://example.com/raster-tiles/{z}/{x}/{y}.png"], options: [ .minimumZoomLevel: 9, .maximumZoomLevel: 16, .tileSize: 512, .attributionInfos: [ - MGLAttributionInfo(title: NSAttributedString(string: "© Mapbox"), url: URL(string: "http://mapbox.com")) + MGLAttributionInfo(title: NSAttributedString(string: "© Mapbox"), url: URL(string: "https://mapbox.com")) ] ]) mapView.style?.addSource(source) //#-example-code let layer = MGLRasterStyleLayer(identifier: "clouds", source: source) - layer.rasterOpacity = MGLStyleValue(rawValue: 0.5) + layer.rasterOpacity = NSExpression(forConstantValue: 0.5) mapView.style?.addLayer(layer) //#-end-example-code XCTAssertNotNil(mapView.style?.layer(withIdentifier: "clouds")) } + func testMGLHillshadeStyleLayer() { + let source = MGLRasterDEMSource(identifier: "dem", tileURLTemplates: ["https://example.com/raster-rgb/{z}/{x}/{y}.png"], options: [ + .minimumZoomLevel: 9, + .maximumZoomLevel: 16, + .tileSize: 256, + .attributionInfos: [ + MGLAttributionInfo(title: NSAttributedString(string: "© Mapbox"), url: URL(string: "https://mapbox.com")) + ] + ]) + mapView.style?.addSource(source) + + let canals = MGLVectorTileSource(identifier: "canals", configurationURL: URL(string: "https://example.com/style.json")!) + mapView.style?.addSource(canals) + let canalShadowLayer = MGLLineStyleLayer(identifier: "waterway-river-canal-shadow", source: canals) + mapView.style?.addLayer(canalShadowLayer) + + //#-example-code + let layer = MGLHillshadeStyleLayer(identifier: "hills", source: source) + layer.hillshadeExaggeration = NSExpression(forConstantValue: 0.6) + if let canalShadowLayer = mapView.style?.layer(withIdentifier: "waterway-river-canal-shadow") { + mapView.style?.insertLayer(layer, below: canalShadowLayer) + } + //#-end-example-code + + XCTAssertNotNil(mapView.style?.layer(withIdentifier: "hills")) + } + func testMGLVectorStyleLayer$predicate() { - let terrain = MGLVectorSource(identifier: "terrain", configurationURL: URL(string: "https://example.com/style.json")!) + let terrain = MGLVectorTileSource(identifier: "terrain", configurationURL: URL(string: "https://example.com/style.json")!) mapView.style?.addSource(terrain) //#-example-code let layer = MGLLineStyleLayer(identifier: "contour", source: terrain) layer.sourceLayerIdentifier = "contours" - layer.predicate = NSPredicate(format: "(index == 5 || index == 10) && ele >= 1500.0") + layer.predicate = NSPredicate(format: "(index == 5 || index == 10) && CAST(ele, 'NSNumber') >= 1500.0") mapView.style?.addLayer(layer) //#-end-example-code @@ -295,7 +370,7 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { #endif class MGLStyle { - static func satelliteStreetsStyleURL() -> URL { + static var satelliteStreetsStyleURL: URL { return MGLDocumentationExampleTests.styleURL } } @@ -303,7 +378,7 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { //#-example-code let camera = MGLMapCamera(lookingAtCenter: CLLocationCoordinate2D(latitude: 37.7184, longitude: -122.4365), fromDistance: 100, pitch: 20, heading: 0) - let options = MGLMapSnapshotOptions(styleURL: MGLStyle.satelliteStreetsStyleURL(), camera: camera, size: CGSize(width: 320, height: 480)) + let options = MGLMapSnapshotOptions(styleURL: MGLStyle.satelliteStreetsStyleURL, camera: camera, size: CGSize(width: 320, height: 480)) options.zoomLevel = 10 let snapshotter = MGLMapSnapshotter(options: options) @@ -316,7 +391,7 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { } //#-end-example-code - wait(for: [expectation], timeout: 1) + wait(for: [expectation], timeout: 5) } // For testMGLMapView(). diff --git a/platform/darwin/test/MGLDocumentationGuideTests.swift b/platform/darwin/test/MGLDocumentationGuideTests.swift index c71f1b46c7..4de1d81aa9 100644 --- a/platform/darwin/test/MGLDocumentationGuideTests.swift +++ b/platform/darwin/test/MGLDocumentationGuideTests.swift @@ -1,3 +1,4 @@ +import XCTest import Foundation import Mapbox #if os(iOS) @@ -49,31 +50,32 @@ class MGLDocumentationGuideTests: XCTestCase, MGLMapViewDelegate { styleLoadingExpectation.fulfill() } - func testUsingStyleFunctionsAtRuntime$Stops() { + func testMigratingToExpressions$Stops() { //#-example-code #if os(macOS) - let stops = [ - 0: MGLStyleValue<NSColor>(rawValue: .yellow), - 2.5: MGLStyleValue(rawValue: .orange), - 5: MGLStyleValue(rawValue: .red), - 7.5: MGLStyleValue(rawValue: .blue), - 10: MGLStyleValue(rawValue: .white), + let stops: [NSNumber: NSColor] = [ + 0: .yellow, + 2.5: .orange, + 5: .red, + 7.5: .blue, + 10: .white, ] #else - let stops = [ - 0: MGLStyleValue<UIColor>(rawValue: .yellow), - 2.5: MGLStyleValue(rawValue: .orange), - 5: MGLStyleValue(rawValue: .red), - 7.5: MGLStyleValue(rawValue: .blue), - 10: MGLStyleValue(rawValue: .white), + let stops: [NSNumber: UIColor] = [ + 0: .yellow, + 2.5: .orange, + 5: .red, + 7.5: .blue, + 10: .white, ] #endif //#-end-example-code - let _ = MGLStyleValue(interpolationMode: .exponential, cameraStops: stops, options: nil) + let _ = NSExpression(format: "mgl_step:from:stops:(mag, %@, %@)", + stops[0]!, stops) } - func testUsingStyleFunctionsAtRuntime$Linear() { + func testMigratingToExpressions$Linear() { //#-example-code let url = URL(string: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_week.geojson")! let symbolSource = MGLSource(identifier: "source") @@ -83,132 +85,192 @@ class MGLDocumentationGuideTests: XCTestCase, MGLMapViewDelegate { mapView.style?.addSource(source) #if os(macOS) - let stops = [ - 0: MGLStyleValue<NSColor>(rawValue: .yellow), - 2.5: MGLStyleValue(rawValue: .orange), - 5: MGLStyleValue(rawValue: .red), - 7.5: MGLStyleValue(rawValue: .blue), - 10: MGLStyleValue(rawValue: .white), + let stops: [NSNumber: NSColor] = [ + 0: .yellow, + 2.5: .orange, + 5: .red, + 7.5: .blue, + 10: .white, ] #else - let stops = [ - 0: MGLStyleValue<UIColor>(rawValue: .yellow), - 2.5: MGLStyleValue(rawValue: .orange), - 5: MGLStyleValue(rawValue: .red), - 7.5: MGLStyleValue(rawValue: .blue), - 10: MGLStyleValue(rawValue: .white), + let stops: [NSNumber: UIColor] = [ + 0: .yellow, + 2.5: .orange, + 5: .red, + 7.5: .blue, + 10: .white, ] #endif let layer = MGLCircleStyleLayer(identifier: "circles", source: source) #if os(macOS) - layer.circleColor = MGLStyleValue(interpolationMode: .exponential, - sourceStops: stops, - attributeName: "mag", - options: [.defaultValue: MGLStyleValue<NSColor>(rawValue: .green)]) + layer.circleColor = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:(mag, 'linear', nil, %@)", + stops) #else - layer.circleColor = MGLStyleValue(interpolationMode: .exponential, - sourceStops: stops, - attributeName: "mag", - options: [.defaultValue: MGLStyleValue<UIColor>(rawValue: .green)]) + layer.circleColor = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:(mag, 'linear', nil, %@)", + stops) #endif - layer.circleRadius = MGLStyleValue(rawValue: 10) + layer.circleRadius = NSExpression(forConstantValue: 10) mapView.style?.insertLayer(layer, below: symbolLayer) //#-end-example-code } - func testUsingStyleFunctionsAtRuntime$Exponential() { + func testMigratingToExpressions$LinearConvenience() { + let source = MGLShapeSource(identifier: "circles", shape: nil, options: nil) + let layer = MGLCircleStyleLayer(identifier: "circles", source: source) + + #if os(macOS) + let stops: [NSNumber: NSColor] = [ + 0: .yellow, + 2.5: .orange, + 5: .red, + 7.5: .blue, + 10: .white, + ] + #else + let stops: [NSNumber: UIColor] = [ + 0: .yellow, + 2.5: .orange, + 5: .red, + 7.5: .blue, + 10: .white, + ] + #endif + + //#-example-code + layer.circleColor = NSExpression(forMGLInterpolating: NSExpression(forKeyPath: "mag"), curveType: .linear, parameters: nil, stops: NSExpression(forConstantValue: stops)) + //#-end-example-code + + layer.circleRadius = NSExpression(forConstantValue: 10) + mapView.style?.addLayer(layer) + + } + func testMigratingToExpressions$Exponential() { let source = MGLShapeSource(identifier: "circles", shape: nil, options: nil) let layer = MGLCircleStyleLayer(identifier: "circles", source: source) //#-example-code let stops = [ - 12: MGLStyleValue<NSNumber>(rawValue: 0.5), - 14: MGLStyleValue(rawValue: 2), - 18: MGLStyleValue(rawValue: 18), + 12: 0.5, + 14: 2, + 18: 18, ] - layer.circleRadius = MGLStyleValue(interpolationMode: .exponential, - cameraStops: stops, - options: [.interpolationBase: 1.5]) + layer.circleRadius = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'exponential', 1.5, %@)", + stops) //#-end-example-code } - func testUsingStyleFunctionsAtRuntime$Interval() { + func testMigratingToExpressions$ExponentialConvenience() { + let source = MGLShapeSource(identifier: "circles", shape: nil, options: nil) + let layer = MGLCircleStyleLayer(identifier: "circles", source: source) + + //#-example-code + let stops = [ + 12: 0.5, + 14: 2, + 18: 18, + ] + + layer.circleRadius = NSExpression(forMGLInterpolating: NSExpression.zoomLevelVariable, curveType: MGLExpressionInterpolationMode.exponential, parameters: NSExpression(forConstantValue: 1.5), stops: NSExpression(forConstantValue: stops)) + //#-end-example-code + } + func testMigratingToExpressions$Interval() { let source = MGLShapeSource(identifier: "circles", shape: nil, options: nil) let layer = MGLCircleStyleLayer(identifier: "circles", source: source) //#-example-code #if os(macOS) - let stops = [ - 0: MGLStyleValue<NSColor>(rawValue: .yellow), - 2.5: MGLStyleValue(rawValue: .orange), - 5: MGLStyleValue(rawValue: .red), - 7.5: MGLStyleValue(rawValue: .blue), - 10: MGLStyleValue(rawValue: .white), + let stops: [NSNumber: NSColor] = [ + 0: .yellow, + 2.5: .orange, + 5: .red, + 7.5: .blue, + 10: .white, ] - layer.circleColor = MGLStyleValue(interpolationMode: .interval, - sourceStops: stops, - attributeName: "mag", - options: [.defaultValue: MGLStyleValue<NSColor>(rawValue: .green)]) + layer.circleColor = NSExpression(format: "mgl_step:from:stops:(mag, %@, %@)", + NSColor.green, stops) #else - let stops = [ - 0: MGLStyleValue<UIColor>(rawValue: .yellow), - 2.5: MGLStyleValue(rawValue: .orange), - 5: MGLStyleValue(rawValue: .red), - 7.5: MGLStyleValue(rawValue: .blue), - 10: MGLStyleValue(rawValue: .white), + let stops: [NSNumber: UIColor] = [ + 0: .yellow, + 2.5: .orange, + 5: .red, + 7.5: .blue, + 10: .white, ] - layer.circleColor = MGLStyleValue(interpolationMode: .interval, - sourceStops: stops, - attributeName: "mag", - options: [.defaultValue: MGLStyleValue<UIColor>(rawValue: .green)]) + layer.circleColor = NSExpression(format: "mgl_step:from:stops:(mag, %@, %@)", + UIColor.green, stops) #endif //#-end-example-code } - func testUsingStyleFunctionsAtRuntime$Categorical() { + func testMigratingToExpressions$Categorical() { let source = MGLShapeSource(identifier: "circles", shape: nil, options: nil) let layer = MGLCircleStyleLayer(identifier: "circles", source: source) //#-example-code #if os(macOS) - let categoricalStops = [ - "earthquake": MGLStyleValue<NSColor>(rawValue: .orange), - "explosion": MGLStyleValue(rawValue: .red), - "quarry blast": MGLStyleValue(rawValue: .yellow), - ] - - layer.circleColor = MGLStyleValue(interpolationMode: .categorical, - sourceStops: categoricalStops, - attributeName: "type", - options: [.defaultValue: MGLStyleValue<NSColor>(rawValue: .blue)]) + let defaultColor = NSColor.blue + layer.circleColor = NSExpression( + format: "MGL_MATCH(type, 'earthquake', %@, 'explosion', %@, 'quarry blast', %@, %@)", + NSColor.orange, NSColor.red, NSColor.yellow, defaultColor) #else - let categoricalStops = [ - "earthquake": MGLStyleValue<UIColor>(rawValue: .orange), - "explosion": MGLStyleValue(rawValue: .red), - "quarry blast": MGLStyleValue(rawValue: .yellow), - ] - - layer.circleColor = MGLStyleValue(interpolationMode: .categorical, - sourceStops: categoricalStops, - attributeName: "type", - options: [.defaultValue: MGLStyleValue<UIColor>(rawValue: .blue)]) + let defaultColor = UIColor.blue + layer.circleColor = NSExpression(format: "MGL_MATCH(type, 'earthquake', %@, 'explosion', %@, 'quarry blast', %@, %@)", + UIColor.orange, UIColor.red, UIColor.yellow, defaultColor) + #endif + //#-end-example-code + } + + func testMigratingToExpressions$CategoricalValue() { + let source = MGLShapeSource(identifier: "circles", shape: nil, options: nil) + let layer = MGLCircleStyleLayer(identifier: "circles", source: source) + + //#-example-code + #if os(macOS) + let stops : [String : NSColor] = ["earthquake" : NSColor.orange, + "explosion" : NSColor.red, + "quarry blast" : NSColor.yellow] + layer.circleColor = NSExpression( + format: "FUNCTION(%@, 'valueForKeyPath:', type)", + stops) + #else + let stops : [String : UIColor] = ["earthquake" : UIColor.orange, + "explosion" : UIColor.red, + "quarry blast" : UIColor.yellow] + layer.circleColor = NSExpression( + format: "FUNCTION(%@, 'valueForKeyPath:', type)", + stops) #endif //#-end-example-code } + func testMigratingToExpressions$Identity() { + let source = MGLShapeSource(identifier: "circles", shape: nil, options: nil) + let layer = MGLCircleStyleLayer(identifier: "circles", source: source) + + //#-example-code + layer.circleRadius = NSExpression(forKeyPath: "mag") + //#-end-example-code + } - func testUsingStyleFunctionsAtRuntime$Identity() { + func testMigratingToExpressions$Multiply() { let source = MGLShapeSource(identifier: "circles", shape: nil, options: nil) let layer = MGLCircleStyleLayer(identifier: "circles", source: source) //#-example-code - layer.circleRadius = MGLStyleValue(interpolationMode: .identity, - sourceStops: nil, - attributeName: "mag", - options: [.defaultValue: MGLStyleValue<NSNumber>(rawValue: 0)]) + layer.circleRadius = NSExpression(forFunction: "multiply:by:", arguments: [NSExpression(forKeyPath: "mag"), 3]) + //#-end-example-code + } + + func testMigratingToExpressions$Cast() { + let source = MGLShapeSource(identifier: "circles", shape: nil, options: nil) + + //#-example-code + let magnitudeLayer = MGLSymbolStyleLayer(identifier: "mag-layer", source: source) + magnitudeLayer.text = NSExpression(format: "CAST(mag, 'NSString')") + mapView.style?.addLayer(magnitudeLayer) //#-end-example-code } } diff --git a/platform/darwin/test/MGLExpressionTests.mm b/platform/darwin/test/MGLExpressionTests.mm index ad0833a068..6d710fdcfe 100644 --- a/platform/darwin/test/MGLExpressionTests.mm +++ b/platform/darwin/test/MGLExpressionTests.mm @@ -1,9 +1,17 @@ #import <XCTest/XCTest.h> +#import "MGLStyleLayerTests.h" + #import <string> #import "MGLTypes.h" -#import "NSExpression+MGLAdditions.h" +#import "NSExpression+MGLPrivateAdditions.h" +#import "NSValue+MGLAdditions.h" +#if TARGET_OS_IPHONE +#import "UIColor+MGLAdditions.h" +#else +#import "NSColor+MGLAdditions.h" +#endif #define MGLAssertEqualValues(actual, expected, ...) \ XCTAssertTrue(actual.is<__typeof__(expected)>()); \ @@ -17,11 +25,14 @@ XCTAssertEqualWithAccuracy(actual.get<__typeof__(expected)>(), expected, accuracy, __VA_ARGS__); \ } +#define MGLConstantExpression(constant) \ + [NSExpression expressionForConstantValue:constant] + #define MGLAssertConstantEqualsValue(constant, value, ...) \ - MGLAssertEqualValues([NSExpression expressionForConstantValue:constant].mgl_constantMBGLValue, value, __VA_ARGS__); + MGLAssertEqualValues(MGLConstantExpression(constant).mgl_constantMBGLValue, value, __VA_ARGS__); #define MGLAssertConstantEqualsValueWithAccuracy(constant, value, accuracy, ...) \ - MGLAssertEqualValuesWithAccuracy([NSExpression expressionForConstantValue:constant].mgl_constantMBGLValue, value, accuracy, __VA_ARGS__); + MGLAssertEqualValuesWithAccuracy(MGLConstantExpression(constant).mgl_constantMBGLValue, value, accuracy, __VA_ARGS__); using namespace std::string_literals; @@ -140,4 +151,942 @@ using namespace std::string_literals; XCTAssertEqual([NSExpression expressionForConstantValue:nil].mgl_featureType, mbgl::FeatureType::Unknown); } +#pragma mark - JSON expression object tests + +- (void)testVariableExpressionObject { + { + NSExpression *expression = [NSExpression expressionForVariable:@"zoomLevel"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[@"zoom"]); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"$zoomLevel"].mgl_jsonExpressionObject, @[@"zoom"]); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:@[@"zoom"]], expression); + NSMutableDictionary *context = [@{@"zoomLevel": @16} mutableCopy]; + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:context], @16); + } + { + NSExpression *expression = [NSExpression expressionForVariable:@"heatmapDensity"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[@"heatmap-density"]); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"$heatmapDensity"].mgl_jsonExpressionObject, @[@"heatmap-density"]); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:@[@"heatmap-density"]], expression); + NSMutableDictionary *context = [@{@"heatmapDensity": @1} mutableCopy]; + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:context], @1); + } + { + NSExpression *expression = [NSExpression expressionForVariable:@"geometryType"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[@"geometry-type"]); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"$geometryType"].mgl_jsonExpressionObject, @[@"geometry-type"]); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:@[@"geometry-type"]], expression); + } + { + NSExpression *expression = [NSExpression expressionForVariable:@"featureIdentifier"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[@"id"]); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"$featureIdentifier"].mgl_jsonExpressionObject, @[@"id"]); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:@[@"id"]], expression); + } + { + NSExpression *expression = [NSExpression expressionForVariable:@"featureAttributes"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[@"properties"]); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"$featureAttributes"].mgl_jsonExpressionObject, @[@"properties"]); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:@[@"properties"]], expression); + } + { + NSExpression *expression = [NSExpression expressionForVariable:@"loremIpsum"]; + NSArray *jsonExpression = @[@"var", @"loremIpsum"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"$loremIpsum"].mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + NSMutableDictionary *context = [@{@"loremIpsum": @"Lorem ipsum dolor sit amet"} mutableCopy]; + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:context], @"Lorem ipsum dolor sit amet"); + } + { + NSDictionary *context = @{@"loremIpsum": MGLConstantExpression(@"Lorem ipsum dolor sit amet")}; + NSExpression *expression = [NSExpression expressionWithFormat:@"MGL_LET('loremIpsum', 'Lorem ipsum dolor sit amet', uppercase($loremIpsum))", context]; + NSExpression *compatibilityExpression = [NSExpression expressionWithFormat:@"FUNCTION(uppercase($loremIpsum), 'mgl_expressionWithContext:', %@)", context]; + NSArray *jsonExpression = @[@"let", @"loremIpsum", @"Lorem ipsum dolor sit amet", @[@"upcase", @[@"var", @"loremIpsum"]]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } +} + +- (void)testConstantValueExpressionObject { + { + NSExpression *expression = [NSExpression expressionForConstantValue:nil]; + XCTAssert(expression.mgl_jsonExpressionObject == [NSNull null]); + XCTAssert([NSExpression expressionWithFormat:@"nil"].mgl_jsonExpressionObject == [NSNull null]); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:[NSNull null]], expression); + XCTAssertNil([expression expressionValueWithObject:nil context:nil]); + } + { + NSExpression *expression = [NSExpression expressionForConstantValue:@1]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @1); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1"].mgl_jsonExpressionObject, @1); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:@1], expression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @1); + } + { + NSExpression *expression = [NSExpression expressionForConstantValue:@YES]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @YES); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"TRUE"].mgl_jsonExpressionObject, @YES); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:@YES], expression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @YES); + } + { + NSExpression *expression = [NSExpression expressionForConstantValue:nil]; + XCTAssert(expression.mgl_jsonExpressionObject == [NSNull null]); + XCTAssert([NSExpression expressionWithFormat:@"nil"].mgl_jsonExpressionObject == [NSNull null]); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:[NSNull null]], expression); + XCTAssertNil([expression expressionValueWithObject:nil context:nil]); + } + { + CGVector vector = CGVectorMake(1, 2); + NSExpression *expression = [NSExpression expressionForConstantValue:@(vector)]; +#if !TARGET_OS_IPHONE + NSArray *jsonExpression = @[@"literal", @[@1, @-2]]; +#else + NSArray *jsonExpression = @[@"literal", @[@1, @2]]; +#endif + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + // No way to distinguish offsets from ordinary arrays in expressions. + XCTAssertEqualObjects([[NSExpression expressionWithMGLJSONObject:jsonExpression].collection valueForKeyPath:@"constantValue"], jsonExpression.lastObject); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @(vector)); + } + { +#if !TARGET_OS_IPHONE + NSEdgeInsets padding = {1, 2, 3, 4}; + NSValue *value = [NSValue valueWithEdgeInsets:padding]; +#else + UIEdgeInsets padding = {1, 2, 3, 4}; + NSValue *value = [NSValue valueWithUIEdgeInsets:padding]; +#endif + NSExpression *expression = [NSExpression expressionForConstantValue:value]; + NSArray *jsonExpression = @[@"literal", @[@1, @4, @3, @2]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + // No way to distinguish offsets from ordinary arrays in expressions. + XCTAssertEqualObjects([[NSExpression expressionWithMGLJSONObject:jsonExpression].collection valueForKeyPath:@"constantValue"], jsonExpression.lastObject); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], value); + } + { + MGLColor *color = [MGLColor mgl_colorWithColor:{ 255.0/255, 239.0/255, 213.0/255, 1 }]; // papayawhip + NSExpression *expression = [NSExpression expressionForConstantValue:color]; + NSArray *jsonExpression = @[@"rgb", @255, @239, @213]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], color); + } + { + MGLColor *color = [MGLColor mgl_colorWithColor:{ 255.0/255, 239.0/255, 213.0/255, 0.5 }]; // papayawhip + NSExpression *expression = [NSExpression expressionForConstantValue:color]; + NSArray *jsonExpression = @[@"rgba", @255, @239, @213, @0.5]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], color); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"noindex(513)"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @513); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @513); + } +} + +- (void)testKeyPathExpressionObject { + { + NSExpression *expression = [NSExpression expressionForKeyPath:@"highway"]; + NSArray *jsonExpression = @[@"get", @"highway"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"highway"].mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"%@.population", @{@"population": MGLConstantExpression(@12000)}]; + NSArray *jsonExpression = @[@"get", @"population", @[@"literal", @{@"population": @12000}]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"%@.uppercase('population')", @{@"POPULATION": MGLConstantExpression(@12000)}]; + NSArray *jsonExpression = @[@"get", @[@"upcase", @"population"], @[@"literal", @{@"POPULATION": @12000}]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } +} + +- (void)testStatisticalExpressionObject { + { + NSExpression *expression = [NSExpression expressionWithFormat:@"average({1, 2, 2, 3, 4, 7, 9})"]; + NSArray *jsonExpression = @[@"/", @[@"+", @1, @2, @2, @3, @4, @7, @9], @[@"length", @[@"literal", @[@1, @2, @2, @3, @4, @7, @9]]]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @4); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"sum({1, 2, 2, 3, 4, 7, 9})"]; + NSArray *jsonExpression = @[@"+", @1, @2, @2, @3, @4, @7, @9]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @28); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"count({1, 2, 2, 3, 4, 7, 9})"]; + NSArray *jsonExpression = @[@"length", @[@"literal", @[@1, @2, @2, @3, @4, @7, @9]]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @7); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"min({1, 2, 2, 3, 4, 7, 9})"]; + NSArray *jsonExpression = @[@"min", @1, @2, @2, @3, @4, @7, @9]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @1); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"max({1, 2, 2, 3, 4, 7, 9})"]; + NSArray *jsonExpression = @[@"max", @1, @2, @2, @3, @4, @7, @9]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @9); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } +} + +- (void)testArithmeticExpressionObject { + NSArray *arguments = @[MGLConstantExpression(@1), MGLConstantExpression(@1)]; + { + NSExpression *expression = [NSExpression expressionForFunction:@"add:to:" arguments:arguments]; + NSArray *jsonExpression = @[@"+", @1, @1]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1 + 1"].mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSArray *arguments = @[MGLConstantExpression(@1), MGLConstantExpression(@1), MGLConstantExpression(@1)]; + NSExpression *expression = [NSExpression expressionForFunction:@"add:to:" arguments:arguments]; + NSArray *jsonExpression = @[@"+", @1, @1, @1]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + jsonExpression = @[@"+", @[@"+", @1, @1], @1]; + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1 + 1 + 1"].mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], [NSExpression expressionWithFormat:@"1 + 1 + 1"]); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"from:subtract:" arguments:arguments]; + NSArray *jsonExpression = @[@"-", @1, @1]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1 - 1"].mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"multiply:by:" arguments:arguments]; + NSArray *jsonExpression = @[@"*", @1, @1]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1 * 1"].mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"divide:by:" arguments:arguments]; + NSArray *jsonExpression = @[@"/", @1, @1]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1 / 1"].mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"modulus:by:" arguments:arguments]; + NSArray *jsonExpression = @[@"%", @1, @1]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + // NSExpression lacks a shorthand operator for modulus. + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"ceiling:" arguments:@[MGLConstantExpression(@1.5)]]; + NSArray *jsonExpression = @[@"ceil", @1.5]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @2); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"ceiling:" arguments:@[MGLConstantExpression(@-1.5)]]; + NSArray *jsonExpression = @[@"ceil", @-1.5]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-1); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"ceiling:" arguments:@[MGLConstantExpression(@2)]]; + NSArray *jsonExpression = @[@"ceil", @2]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @2); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"ceiling:" arguments:@[MGLConstantExpression(@-2)]]; + NSArray *jsonExpression = @[@"ceil", @-2]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-2); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"trunc:" arguments:@[MGLConstantExpression(@1.5)]]; + NSArray *jsonExpression = @[@"-", @1.5, @[@"%", @1.5, @1]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @1); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"trunc:" arguments:@[MGLConstantExpression(@-1.5)]]; + NSArray *jsonExpression = @[@"-", @-1.5, @[@"%", @-1.5, @1]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-1); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"abs:" arguments:@[MGLConstantExpression(@2)]]; + NSArray *jsonExpression = @[@"abs", @2]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @2); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"abs:" arguments:@[MGLConstantExpression(@-2)]]; + NSArray *jsonExpression = @[@"abs", @-2]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @2); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"floor:" arguments:@[MGLConstantExpression(@1.5)]]; + NSArray *jsonExpression = @[@"floor", @1.5]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @1); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"floor:" arguments:@[MGLConstantExpression(@-1.5)]]; + NSArray *jsonExpression = @[@"floor", @-1.5]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-2); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"floor:" arguments:@[MGLConstantExpression(@2)]]; + NSArray *jsonExpression = @[@"floor", @2]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @2); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"floor:" arguments:@[MGLConstantExpression(@-2)]]; + NSArray *jsonExpression = @[@"floor", @-2]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-2); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"mgl_round:" arguments:@[MGLConstantExpression(@1.5)]]; + NSArray *jsonExpression = @[@"round", @1.5]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @2); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"mgl_round:" arguments:@[MGLConstantExpression(@-1.5)]]; + NSArray *jsonExpression = @[@"round", @-1.5]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-2); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"mgl_round:" arguments:@[MGLConstantExpression(@2.5)]]; + NSArray *jsonExpression = @[@"round", @2.5]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @3); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"mgl_round:" arguments:@[MGLConstantExpression(@-2.5)]]; + NSArray *jsonExpression = @[@"round", @-2.5]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-3); + } +} + +- (void)testTrigonometricExpressionObject { + NSArray *arguments = @[MGLConstantExpression(@1), MGLConstantExpression(@1)]; + { + NSExpression *expression = [NSExpression expressionForFunction:@"sqrt:" arguments:arguments]; + NSArray *jsonExpression = @[@"sqrt", @1, @1]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"ln:" arguments:arguments]; + NSArray *jsonExpression = @[@"ln", @1, @1]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"mgl_log2:" arguments:@[MGLConstantExpression(@1024)]]; + NSArray *jsonExpression = @[@"log2", @1024]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @10); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"raise:toPower:" arguments:arguments]; + NSArray *jsonExpression = @[@"^", @1, @1]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1 ** 1"].mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"exp:" arguments:@[MGLConstantExpression(@0)]]; + NSArray *jsonExpression = @[@"^", @[@"e"], @0]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @1); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForConstantValue:@(M_E)]; + NSArray *jsonExpression = @[@"e"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @(M_E)); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForConstantValue:@(M_PI)]; + NSArray *jsonExpression = @[@"pi"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @(M_PI)); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"mgl_acos:" arguments:@[MGLConstantExpression(@1)]]; + NSArray *jsonExpression = @[@"acos", @1]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @0); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"mgl_cos:" arguments:@[MGLConstantExpression(@0)]]; + NSArray *jsonExpression = @[@"cos", @0]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @1); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"mgl_asin:" arguments:@[MGLConstantExpression(@0)]]; + NSArray *jsonExpression = @[@"asin", @0]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @0); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"mgl_sin:" arguments:@[MGLConstantExpression(@0)]]; + NSArray *jsonExpression = @[@"sin", @0]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @0); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"mgl_atan:" arguments:@[MGLConstantExpression(@20)]]; + NSArray *jsonExpression = @[@"atan", @20]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + NSNumber *value = [expression expressionValueWithObject:nil context:nil]; + XCTAssertEqualWithAccuracy(value.doubleValue, 1.52, 0.001); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"mgl_tan:" arguments:@[MGLConstantExpression(@0)]]; + NSArray *jsonExpression = @[@"tan", @0]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @0); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } +} + +- (void)testStringFormattingExpressionObject { + NSArray *arguments = @[MGLConstantExpression(@"MacDonald")]; + { + NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION('Old', 'stringByAppendingString:', 'MacDonald')"]; + NSExpression *aftermarketExpression = [NSExpression expressionWithFormat:@"mgl_join({'Old', 'MacDonald'})"]; + NSArray *jsonExpression = @[@"concat", @"Old", @"MacDonald"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects(aftermarketExpression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @"OldMacDonald"); + XCTAssertEqualObjects([aftermarketExpression expressionValueWithObject:nil context:nil], @"OldMacDonald"); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], aftermarketExpression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"uppercase:" arguments:arguments]; + NSArray *jsonExpression = @[@"upcase", @"MacDonald"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"lowercase:" arguments:arguments]; + NSArray *jsonExpression = @[@"downcase", @"MacDonald"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"length:" arguments:arguments]; + NSArray *jsonExpression = @[@"length", @"MacDonald"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } +} + +- (void)testTypeConversionExpressionObject { + { + NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(number, 'boolValue')"]; + NSArray *jsonExpression = @[@"to-boolean", @[@"get", @"number"]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + // NSExpression is unable to evaluate -[NSNumber boolValue] by itself + // because it returns a primitive instead of an object. + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'mgl_number')"]; + NSArray *jsonExpression = @[@"to-number", @[@"get", @"postalCode"]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'doubleValue')"].mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'floatValue')"].mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'decimalValue')"].mgl_jsonExpressionObject, jsonExpression); + // NSExpression is unable to evaluate NSNumber’s -floatValue, + // -doubleValue, or -decimalValue by themselves because they each return + // a primitive instead of an object. + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], + [NSExpression expressionWithFormat:@"CAST(postalCode, 'NSNumber')"]); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'mgl_numberWithFallbackValues:', zipCode)"]; + NSArray *jsonExpression = @[@"to-number", @[@"get", @"postalCode"], @[@"get", @"zipCode"]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'doubleValue', zipCode)"].mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'floatValue', zipCode)"].mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'decimalValue', zipCode)"].mgl_jsonExpressionObject, jsonExpression); + // NSExpression is unable to evaluate NSNumber’s -floatValue, + // -doubleValue, or -decimalValue by themselves because they each return + // a primitive instead of an object. + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"CAST(postalCode, 'NSNumber')"]; + NSExpression *compatibilityExpression = [NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'mgl_numberWithFallbackValues:')"]; + NSArray *jsonExpression = @[@"to-number", @[@"get", @"postalCode"]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:@{@"postalCode": @"02134"} context:nil], @02134); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"CAST(number, 'NSString')"]; + NSExpression *compatibilityExpression = [NSExpression expressionWithFormat:@"FUNCTION(number, 'stringValue')"]; + NSArray *jsonExpression = @[@"to-string", @[@"get", @"number"]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:@{@"number": @1.5} context:nil], @"1.5"); + XCTAssertEqualObjects([compatibilityExpression expressionValueWithObject:@{@"number": @1.5} context:nil], @"1.5"); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } +} + +- (void)testInterpolationExpressionObject { + { + NSDictionary *stops = @{@0: MGLConstantExpression(@100), @10: MGLConstantExpression(@200)}; + NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(x, 'linear', nil, %@)", stops]; + NSExpression *compatibilityExpression = [NSExpression expressionWithFormat:@"FUNCTION(x, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", stops]; + NSArray *jsonExpression = @[@"interpolate", @[@"linear"], @[@"get", @"x"], @0, @100, @10, @200]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSDictionary *stops = @{@1: MGLConstantExpression(@2), @3: MGLConstantExpression(@6)}; + NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(x, 'exponential', 2, %@)", stops]; + NSArray *jsonExpression = @[@"interpolate", @[@"exponential", @2], @[@"get", @"x"], @1, @2, @3, @6]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSDictionary *stops = @{@0: MGLConstantExpression(@0), @100: MGLConstantExpression(@100)}; + NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(x, 'cubic-bezier', { 0.42, 0, 0.58, 1 }, %@)", stops]; + NSArray *jsonExpression = @[@"interpolate", @[@"cubic-bezier", @0.42, @0, @0.58, @1], @[@"get", @"x"], @0, @0, @100, @100]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSDictionary *stops = @{@0: MGLConstantExpression(@111), @1: MGLConstantExpression(@1111)}; + NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(x, 11, %@)", stops]; + NSExpression *compatibilityExpression = [NSExpression expressionWithFormat:@"FUNCTION(x, 'mgl_stepWithMinimum:stops:', 11, %@)", stops]; + NSArray *jsonExpression = @[@"step", @[@"get", @"x"], @11, @0, @111, @1, @1111]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSDictionary *stops = @{@0: MGLConstantExpression(@111), @1: MGLConstantExpression(@1111)}; + NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, 11, %@)", stops]; + NSArray *jsonExpression = @[@"step", @[@"zoom"], @11, @0, @111, @1, @1111]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } +} + +- (void)testMatchExpressionObject { + { + NSExpression *expression = [NSExpression expressionWithFormat:@"MGL_MATCH(2 - 1, %@, %@, %@, %@, 'default')", MGLConstantExpression(@1), + MGLConstantExpression(@"one"), + MGLConstantExpression(@0), + MGLConstantExpression(@"zero")]; + NSExpression *predicate = [NSExpression expressionWithFormat:@"2 - 1"]; + NSExpression *compatibilityExpression = [NSExpression expressionWithFormat:@"FUNCTION(%@, 'mgl_match:', %@)", predicate, @[MGLConstantExpression(@1), + MGLConstantExpression(@"one"), + MGLConstantExpression(@0), + MGLConstantExpression(@"zero"), + MGLConstantExpression(@"default")]]; + NSArray *jsonExpression = @[@"match", @[@"-", @2, @1], @1, @"one", @0, @"zero", @"default"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"MGL_MATCH(2 * 1, %@, %@, 'default')", MGLConstantExpression(@1), MGLConstantExpression(@"one")]; + NSArray *jsonExpression = @[@"match", @[@"*", @2, @1], @1, @"one", @"default"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } +} + +- (void)testCoalesceExpressionObject { + { + NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_coalesce(%@)", + @[[NSExpression expressionForKeyPath:@"x"], + [NSExpression expressionForKeyPath:@"y"], + [NSExpression expressionForKeyPath:@"z"], + [NSExpression expressionForConstantValue:@0]]]; + NSExpression *compatibilityExpression = [NSExpression expressionWithFormat:@"FUNCTION(%@, 'mgl_coalesce')", @[[NSExpression expressionForKeyPath:@"x"], + [NSExpression expressionForKeyPath:@"y"], + [NSExpression expressionForKeyPath:@"z"], + [NSExpression expressionForConstantValue:@0]]]; + NSArray *jsonExpression = @[@"coalesce", @[@"get", @"x"], @[@"get", @"y"], @[@"get", @"z"], @0]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + +} + +- (void)testConditionalExpressionObject { + { + NSPredicate *conditional = [NSPredicate predicateWithFormat:@"1 = 2"]; + NSExpression *trueExpression = [NSExpression expressionForConstantValue:@YES]; + NSExpression *falseExpression = [NSExpression expressionForConstantValue:@NO]; + NSExpression *expression = [NSExpression expressionForConditional:conditional trueExpression:trueExpression falseExpression:falseExpression]; + NSArray *jsonExpression = @[@"case", @[@"==", @1, @2], @YES, @NO]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"TERNARY(1 = 2, TRUE, FALSE)"].mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @NO); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"TERNARY(0 = 1, TRUE, TERNARY(1 = 2, TRUE, FALSE))"]; + NSArray *jsonExpression = @[@"case", @[@"==", @0, @1], @YES, @[@"==", @1, @2], @YES, @NO]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @NO); + expression = [NSExpression expressionWithFormat:@"MGL_IF(%@, TRUE, %@, TRUE, FALSE)", + MGLConstantExpression([NSPredicate predicateWithFormat:@"0 = 1"]), + MGLConstantExpression([NSPredicate predicateWithFormat:@"1 = 2"])]; + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"MGL_IF(%@, %@, %@)", + [NSExpression expressionWithFormat:@"%@", [NSPredicate predicateWithFormat:@"1 = 2"]], + MGLConstantExpression(@YES), + MGLConstantExpression(@NO)]; + NSExpression *compatibilityExpression = [NSExpression expressionWithFormat:@"FUNCTION(%@, 'mgl_if:', %@)", [NSPredicate predicateWithFormat:@"1 = 2"], @[MGLConstantExpression(@YES), MGLConstantExpression(@NO)]]; + NSArray *jsonExpression = @[@"case", @[@"==", @1, @2], @YES, @NO]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); + expression = [NSExpression expressionWithFormat:@"TERNARY(1 = 2, YES, NO)"]; + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @NO); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"MGL_IF(%@, %@, %@, %@, %@)", + [NSExpression expressionWithFormat:@"%@", [NSPredicate predicateWithFormat:@"1 = 2"]], + MGLConstantExpression(@YES), + [NSExpression expressionWithFormat:@"%@", [NSPredicate predicateWithFormat:@"1 = 1"]], + MGLConstantExpression(@YES), + MGLConstantExpression(@NO)]; + NSArray *jsonExpression = @[@"case", @[@"==", @1, @2], @YES, @[@"==", @1, @1], @YES, @NO]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @YES); + } + { + NSArray *jsonExpression = @[ + @"case", + @[ + @"<", + @[@"get", @"area"], + @80000 + ], + @[@"get", @"abbr"], + @[@"get", @"name_en"] + ]; + NSExpression *expression = [NSExpression expressionWithFormat:@"TERNARY(area < 80000, abbr, name_en)"]; + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + } +} + +- (void)testLookupExpressionObject { + { + NSExpression *array = [NSExpression expressionForAggregate:@[MGLConstantExpression(@9), + MGLConstantExpression(@8), + MGLConstantExpression(@7)]]; + NSExpression *expression = [NSExpression expressionForFunction:@"objectFrom:withIndex:" + arguments:@[array, MGLConstantExpression(@1)]]; + NSArray *jsonExpression = @[@"at", @1, @[ @"literal", @[@9, @8, @7]]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *array = [NSExpression expressionForAggregate:@[MGLConstantExpression(@9), + MGLConstantExpression(@8), + MGLConstantExpression(@7)]]; + NSExpression *expression = [NSExpression expressionForFunction:@"objectFrom:withIndex:" + arguments:@[array, [NSExpression expressionForKeyPath:@"x"]]]; + NSArray *jsonExpression = @[@"at", @[@"get", @"x"], @[ @"literal", @[@9, @8, @7]]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"mgl_does:have:" + arguments:@[[NSExpression expressionForEvaluatedObject], + [NSExpression expressionForConstantValue:@"x"]]]; + NSExpression *compatibilityExpression = [NSExpression expressionWithFormat:@"FUNCTION(self, 'mgl_has:', 'x')"]; + NSArray *jsonExpression = @[@"has", @"x"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"mgl_does:have:" + arguments:@[MGLConstantExpression(@{@"x": MGLConstantExpression(@0)}), + MGLConstantExpression(@"x")]]; + NSArray *jsonExpression = @[@"has", @"x", @[@"literal", @{@"x": @0}]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"mgl_does:have:" + arguments:@[[NSExpression expressionForVariable:@"featureAttributes"], + [NSExpression expressionForConstantValue:@"x"]]]; + NSArray *jsonExpression = @[@"has", @"x", @[@"properties"]]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression; + expression = [NSExpression expressionWithFormat:@"TERNARY(key != nil, 1, 0)"]; + NSArray *jsonExpression = @[@"case", @[@"!=", @[@"get", @"key"], [NSNull null]], @1, @0]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + XCTAssertEqualObjects([expression expressionValueWithObject:@{} context:nil], @NO); + XCTAssertEqualObjects([expression expressionValueWithObject:@{@"key": @"🗝"} context:nil], @YES); + } + { + NSDictionary *dictionary = @{@"key": @"🔑"}; + NSExpression *expression; + expression = [NSExpression expressionWithFormat:@"TERNARY(%@.key != nil, 1, 0)", dictionary]; + NSArray *jsonExpression = @[@"case", @[@"!=", @[@"get", @"key", @[@"literal", dictionary]], [NSNull null]], @1, @0]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + // The dictionary isn’t equal enough. + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression].description, expression.description); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @YES); + } +} + +- (void)testGenericExpressionObject { + { + NSExpression *expression = [NSExpression expressionWithFormat:@"MGL_FUNCTION('random', 1, 2, 3, 4, 5)"]; + NSArray *jsonExpression = @[@"random", @1, @2, @3, @4, @5]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + expression = [NSExpression expressionWithFormat:@"MGL_FUNCTION('random', 1, 2, 3, 4)"]; + XCTAssertThrowsSpecificNamed([expression expressionValueWithObject:nil context:nil], NSException, NSInvalidArgumentException); + } +} + +#pragma mark - Localization tests + +- (void)testTokenReplacement { + { + NSExpression *original = MGLConstantExpression(@""); + NSExpression *expected = original; + XCTAssertEqualObjects(original.mgl_expressionByReplacingTokensWithKeyPaths, expected); + } + { + NSExpression *original = MGLConstantExpression(@"{"); + NSExpression *expected = original; + XCTAssertEqualObjects(original.mgl_expressionByReplacingTokensWithKeyPaths, expected); + } + { + NSExpression *original = MGLConstantExpression(@"{token"); + NSExpression *expected = original; + XCTAssertEqualObjects(original.mgl_expressionByReplacingTokensWithKeyPaths, expected); + } + { + NSExpression *original = MGLConstantExpression(@"{token}"); + NSExpression *expected = [NSExpression expressionForKeyPath:@"token"]; + XCTAssertEqualObjects(original.mgl_expressionByReplacingTokensWithKeyPaths, expected); + } + { + NSExpression *original = MGLConstantExpression(@"{token} {token}"); + NSExpression *expected = [NSExpression expressionWithFormat:@"mgl_join({token, ' ', token})"]; + XCTAssertEqualObjects(original.mgl_expressionByReplacingTokensWithKeyPaths, expected); + } + { + NSExpression *original = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, '{short}', %@)", @{ + @1: MGLConstantExpression(@"{short}"), + @2: @"…", + @3: @"{long}", + }]; + NSExpression *expected = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, short, %@)", @{ + @1: [NSExpression expressionForKeyPath:@"short"], + @2: @"…", + @3: [NSExpression expressionForKeyPath:@"long"], + }]; + XCTAssertEqualObjects(original.mgl_expressionByReplacingTokensWithKeyPaths, expected); + } +} + +- (void)testLocalization { + { + NSExpression *original = MGLConstantExpression(@""); + NSExpression *expected = original; + XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:nil], expected); + } + { + NSExpression *original = MGLConstantExpression(@"Old MacDonald"); + NSExpression *expected = original; + XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:nil], expected); + } + { + NSExpression *original = MGLConstantExpression(@"{name_en}"); + NSExpression *expected = original; + XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:nil], expected); + } + { + NSExpression *original = [NSExpression expressionForKeyPath:@"name_en"]; + NSExpression *expected = original; + XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:nil], expected); + } + { + NSExpression *original = [NSExpression expressionForKeyPath:@"name_en"]; + NSExpression *expected = [NSExpression expressionForKeyPath:@"name"]; + XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"mul"]], expected); + } + { + NSExpression *original = [NSExpression expressionForKeyPath:@"name_en"]; + NSExpression *expected = [NSExpression expressionForKeyPath:@"name_fr"]; + XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"fr-CA"]], expected); + } + { + NSExpression *original = [NSExpression expressionForKeyPath:@"name_en"]; + NSExpression *expected = [NSExpression expressionForKeyPath:@"name_zh-Hans"]; + XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"zh-Hans"]], expected); + } + { + NSExpression *original = [NSExpression expressionForKeyPath:@"name_en"]; + NSExpression *expected = [NSExpression expressionForKeyPath:@"name"]; + XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"tlh"]], expected); + } + { + NSExpression *original = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, short, %@)", @{ + @1: [NSExpression expressionForKeyPath:@"abbr"], + @2: @"…", + @3: [NSExpression expressionForKeyPath:@"name_fr"], + }]; + NSExpression *expected = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, short, %@)", @{ + @1: [NSExpression expressionForKeyPath:@"abbr"], + @2: @"…", + @3: [NSExpression expressionForKeyPath:@"name_es"], + }]; + XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"es-PR"]], expected); + } + { + NSArray *jsonExpression = @[ + @"step", + @[@"zoom"], + @[ + @"case", + @[ + @"<", + @[ + @"to-number", + @[@"get", @"area"] + ], + @80000 + ], + @[@"get", @"abbr"], + @[@"get", @"name_en"] + ], + @5, @[@"get", @"name_en"] + ]; + NSArray *localizedJSONExpression = @[ + @"step", + @[@"zoom"], + @[ + @"case", + @[ + @"<", + @[ + @"to-number", + @[@"get", @"area"] + ], + @80000 + ], + @[@"get", @"abbr"], + @[@"get", @"name"] + ], + @5, @[@"get", @"name"] + ]; + NSExpression *expression = [NSExpression expressionWithMGLJSONObject:jsonExpression]; + NSExpression *localizedExpression = [expression mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"mul"]]; + XCTAssertEqualObjects(localizedExpression.mgl_jsonExpressionObject, localizedJSONExpression); + } +} + +- (void)testConvenienceInitializers { + { + NSExpression *expression = [NSExpression mgl_expressionForConditional:[NSPredicate predicateWithFormat:@"1 = 2"] + trueExpression:MGLConstantExpression(@YES) + falseExpresssion:MGLConstantExpression(@NO)]; + + NSArray *jsonExpression = @[@"case", @[@"==", @1, @2], @YES, @NO]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @NO); + } + { + NSDictionary *stops = @{@0: MGLConstantExpression(@111), @1: MGLConstantExpression(@1111)}; + NSExpression *expression = [NSExpression mgl_expressionForSteppingExpression:[NSExpression expressionForKeyPath:@"x"] + fromExpression:[NSExpression expressionForConstantValue:@11] + stops:[NSExpression expressionForConstantValue:stops]]; + NSArray *jsonExpression = @[@"step", @[@"get", @"x"], @11, @0, @111, @1, @1111]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSDictionary *stops = @{@0: MGLConstantExpression(@100), @10: MGLConstantExpression(@200)}; + NSExpression *expression = [NSExpression mgl_expressionForInterpolatingExpression:[NSExpression expressionForKeyPath:@"x"] + withCurveType:MGLExpressionInterpolationModeLinear + parameters:nil + stops:[NSExpression expressionForConstantValue:stops]]; + NSArray *jsonExpression = @[@"interpolate", @[@"linear"], @[@"get", @"x"], @0, @100, @10, @200]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [[NSExpression expressionForConstantValue:@"Old"] mgl_expressionByAppendingExpression:[NSExpression expressionForConstantValue:@"MacDonald"]]; + + NSArray *jsonExpression = @[@"concat", @"Old", @"MacDonald"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @"OldMacDonald"); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } + { + NSDictionary *values = @{ MGLConstantExpression(@1): MGLConstantExpression(@"one") }; + NSExpression *expression = [NSExpression mgl_expressionForMatchingExpression:[NSExpression expressionWithFormat:@"2 * 1"] + inDictionary:values + defaultExpression:[NSExpression expressionForConstantValue:@"default"]]; + NSArray *jsonExpression = @[@"match", @[@"*", @2, @1], @1, @"one", @"default"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression); + } +} + + @end diff --git a/platform/darwin/test/MGLFeatureTests.mm b/platform/darwin/test/MGLFeatureTests.mm index 818ad8200e..d135b018f4 100644 --- a/platform/darwin/test/MGLFeatureTests.mm +++ b/platform/darwin/test/MGLFeatureTests.mm @@ -38,7 +38,7 @@ }; features.push_back(mbgl::Feature { polygon }); - NS_ARRAY_OF(MGLShape <MGLFeature> *) *shapes = MGLFeaturesFromMBGLFeatures(features); + NSArray<MGLShape <MGLFeature> *> *shapes = MGLFeaturesFromMBGLFeatures(features); XCTAssertEqual(shapes.count, 3, @"All features should be converted into shapes"); MGLPointFeature *pointShape = (MGLPointFeature *)shapes[0]; @@ -69,7 +69,7 @@ [NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(4, 4)]); XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:polygonCoordinates[3]], [NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(4, 1)]); - NS_ARRAY_OF(MGLPolygon *) *interiorPolygons = polygonShape.interiorPolygons; + NSArray<MGLPolygon *> *interiorPolygons = polygonShape.interiorPolygons; XCTAssertEqual(interiorPolygons.count, 1); MGLPolygon *interiorPolygon = interiorPolygons.firstObject; XCTAssertEqual(interiorPolygon.pointCount, 4); @@ -103,7 +103,7 @@ vector.push_back(true); features.push_back(pointFeature); - NS_ARRAY_OF(MGLShape <MGLFeature> *) *shapes = MGLFeaturesFromMBGLFeatures(features); + NSArray<MGLShape <MGLFeature> *> *shapes = MGLFeaturesFromMBGLFeatures(features); XCTAssertEqual(shapes.count, 1, @"All features should be converted into shapes"); MGLShape <MGLFeature> *shape = shapes.firstObject; @@ -298,29 +298,36 @@ } - (void)testShapeCollectionFeatureGeoJSONDictionary { - MGLPointAnnotation *pointFeature = [[MGLPointAnnotation alloc] init]; + MGLPointFeature *pointFeature = [[MGLPointFeature alloc] init]; CLLocationCoordinate2D pointCoordinate = { 10, 10 }; pointFeature.coordinate = pointCoordinate; CLLocationCoordinate2D coord1 = { 0, 0 }; CLLocationCoordinate2D coord2 = { 10, 10 }; CLLocationCoordinate2D coords[] = { coord1, coord2 }; - MGLPolyline *polyline = [MGLPolyline polylineWithCoordinates:coords count:2]; + MGLPolylineFeature *polylineFeature = [MGLPolylineFeature polylineWithCoordinates:coords count:2]; + + MGLShapeCollectionFeature *shapeCollectionFeature = [MGLShapeCollectionFeature shapeCollectionWithShapes:@[pointFeature, polylineFeature]]; - MGLShapeCollectionFeature *shapeCollectionFeature = [MGLShapeCollectionFeature shapeCollectionWithShapes:@[pointFeature, - polyline]]; // A GeoJSON feature NSDictionary *geoJSONFeature = [shapeCollectionFeature geoJSONDictionary]; // it has the correct geometry NSDictionary *expectedGeometry = @{@"type": @"GeometryCollection", @"geometries": @[ - @{@"type": @"Point", - @"coordinates": @[@(pointCoordinate.longitude), @(pointCoordinate.latitude)]}, - @{@"type": @"LineString", - @"coordinates": @[@[@(coord1.longitude), @(coord1.latitude)], - @[@(coord2.longitude), @(coord2.latitude)]]} - ]}; + @{ @"geometry": @{@"type": @"Point", + @"coordinates": @[@(pointCoordinate.longitude), @(pointCoordinate.latitude)]}, + @"properties": [NSNull null], + @"type": @"Feature", + }, + @{ @"geometry": @{@"type": @"LineString", + @"coordinates": @[@[@(coord1.longitude), @(coord1.latitude)], + @[@(coord2.longitude), @(coord2.latitude)]]}, + @"properties": [NSNull null], + @"type": @"Feature", + } + ] + }; XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry); // When the shape collection is created with an empty array of shapes diff --git a/platform/darwin/test/MGLFillExtrusionStyleLayerTests.mm b/platform/darwin/test/MGLFillExtrusionStyleLayerTests.mm index 5d99c815ea..6081d104e1 100644 --- a/platform/darwin/test/MGLFillExtrusionStyleLayerTests.mm +++ b/platform/darwin/test/MGLFillExtrusionStyleLayerTests.mm @@ -30,8 +30,8 @@ XCTAssertNil(layer.sourceLayerIdentifier); XCTAssertNil(layer.predicate); - layer.predicate = [NSPredicate predicateWithValue:NO]; - XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithValue:NO]); + layer.predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]; + XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]); layer.predicate = nil; XCTAssertNil(layer.predicate); } @@ -52,40 +52,45 @@ { XCTAssertTrue(rawLayer->getFillExtrusionBase().isUndefined(), @"fill-extrusion-base should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.fillExtrusionBase; + NSExpression *defaultExpression = layer.fillExtrusionBase; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.fillExtrusionBase = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.fillExtrusionBase = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getFillExtrusionBase(), propertyValue, - @"Setting fillExtrusionBase to a constant value should update fill-extrusion-base."); - XCTAssertEqualObjects(layer.fillExtrusionBase, constantStyleValue, - @"fillExtrusionBase should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillExtrusionBase = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting fillExtrusionBase to a constant value expression should update fill-extrusion-base."); + XCTAssertEqualObjects(layer.fillExtrusionBase, constantExpression, + @"fillExtrusionBase should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillExtrusionBase = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getFillExtrusionBase(), propertyValue, - @"Setting fillExtrusionBase to a camera function should update fill-extrusion-base."); - XCTAssertEqualObjects(layer.fillExtrusionBase, functionStyleValue, - @"fillExtrusionBase should round-trip camera functions."); + @"Setting fillExtrusionBase to a camera expression should update fill-extrusion-base."); + XCTAssertEqualObjects(layer.fillExtrusionBase, functionExpression, + @"fillExtrusionBase should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.fillExtrusionBase = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.fillExtrusionBase = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getFillExtrusionBase(), propertyValue, - @"Setting fillExtrusionBase to a source function should update fill-extrusion-base."); - XCTAssertEqualObjects(layer.fillExtrusionBase, functionStyleValue, - @"fillExtrusionBase should round-trip source functions."); + @"Setting fillExtrusionBase to a data expression should update fill-extrusion-base."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.fillExtrusionBase, pedanticFunctionExpression, + @"fillExtrusionBase should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.fillExtrusionBase = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.fillExtrusionBase = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -93,15 +98,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getFillExtrusionBase(), propertyValue, - @"Setting fillExtrusionBase to a composite function should update fill-extrusion-base."); - XCTAssertEqualObjects(layer.fillExtrusionBase, functionStyleValue, - @"fillExtrusionBase should round-trip composite functions."); + @"Setting fillExtrusionBase to a camera-data expression should update fill-extrusion-base."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.fillExtrusionBase, pedanticFunctionExpression, + @"fillExtrusionBase should round-trip camera-data expressions."); layer.fillExtrusionBase = nil; XCTAssertTrue(rawLayer->getFillExtrusionBase().isUndefined(), @"Unsetting fillExtrusionBase should return fill-extrusion-base to the default value."); - XCTAssertEqualObjects(layer.fillExtrusionBase, defaultStyleValue, + XCTAssertEqualObjects(layer.fillExtrusionBase, defaultExpression, @"fillExtrusionBase should return the default value after being unset."); // Transition property test layer.fillExtrusionBaseTransition = transitionTest; @@ -118,40 +124,45 @@ { XCTAssertTrue(rawLayer->getFillExtrusionColor().isUndefined(), @"fill-extrusion-color should be unset initially."); - MGLStyleValue<MGLColor *> *defaultStyleValue = layer.fillExtrusionColor; + NSExpression *defaultExpression = layer.fillExtrusionColor; - MGLStyleValue<MGLColor *> *constantStyleValue = [MGLStyleValue<MGLColor *> valueWithRawValue:[MGLColor redColor]]; - layer.fillExtrusionColor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.fillExtrusionColor = constantExpression; mbgl::style::DataDrivenPropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } }; XCTAssertEqual(rawLayer->getFillExtrusionColor(), propertyValue, - @"Setting fillExtrusionColor to a constant value should update fill-extrusion-color."); - XCTAssertEqualObjects(layer.fillExtrusionColor, constantStyleValue, - @"fillExtrusionColor should round-trip constant values."); - - MGLStyleValue<MGLColor *> * functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillExtrusionColor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::Color> intervalStops = { {{18, { 1, 0, 0, 1 }}} }; + @"Setting fillExtrusionColor to a constant value expression should update fill-extrusion-color."); + XCTAssertEqualObjects(layer.fillExtrusionColor, constantExpression, + @"fillExtrusionColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillExtrusionColor = functionExpression; + + mbgl::style::IntervalStops<mbgl::Color> intervalStops = {{ + { -INFINITY, { 1, 0, 0, 1 } }, + { 18, { 1, 0, 0, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops }; XCTAssertEqual(rawLayer->getFillExtrusionColor(), propertyValue, - @"Setting fillExtrusionColor to a camera function should update fill-extrusion-color."); - XCTAssertEqualObjects(layer.fillExtrusionColor, functionStyleValue, - @"fillExtrusionColor should round-trip camera functions."); + @"Setting fillExtrusionColor to a camera expression should update fill-extrusion-color."); + XCTAssertEqualObjects(layer.fillExtrusionColor, functionExpression, + @"fillExtrusionColor should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.fillExtrusionColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.fillExtrusionColor = functionExpression; mbgl::style::ExponentialStops<mbgl::Color> exponentialStops = { {{18, { 1, 0, 0, 1 }}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<mbgl::Color> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getFillExtrusionColor(), propertyValue, - @"Setting fillExtrusionColor to a source function should update fill-extrusion-color."); - XCTAssertEqualObjects(layer.fillExtrusionColor, functionStyleValue, - @"fillExtrusionColor should round-trip source functions."); + @"Setting fillExtrusionColor to a data expression should update fill-extrusion-color."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.fillExtrusionColor, pedanticFunctionExpression, + @"fillExtrusionColor should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.fillExtrusionColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.fillExtrusionColor = functionExpression; std::map<float, mbgl::Color> innerStops { {18, { 1, 0, 0, 1 }} }; mbgl::style::CompositeExponentialStops<mbgl::Color> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -159,15 +170,16 @@ propertyValue = mbgl::style::CompositeFunction<mbgl::Color> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getFillExtrusionColor(), propertyValue, - @"Setting fillExtrusionColor to a composite function should update fill-extrusion-color."); - XCTAssertEqualObjects(layer.fillExtrusionColor, functionStyleValue, - @"fillExtrusionColor should round-trip composite functions."); + @"Setting fillExtrusionColor to a camera-data expression should update fill-extrusion-color."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.fillExtrusionColor, pedanticFunctionExpression, + @"fillExtrusionColor should round-trip camera-data expressions."); layer.fillExtrusionColor = nil; XCTAssertTrue(rawLayer->getFillExtrusionColor().isUndefined(), @"Unsetting fillExtrusionColor should return fill-extrusion-color to the default value."); - XCTAssertEqualObjects(layer.fillExtrusionColor, defaultStyleValue, + XCTAssertEqualObjects(layer.fillExtrusionColor, defaultExpression, @"fillExtrusionColor should return the default value after being unset."); // Transition property test layer.fillExtrusionColorTransition = transitionTest; @@ -184,40 +196,45 @@ { XCTAssertTrue(rawLayer->getFillExtrusionHeight().isUndefined(), @"fill-extrusion-height should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.fillExtrusionHeight; + NSExpression *defaultExpression = layer.fillExtrusionHeight; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.fillExtrusionHeight = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.fillExtrusionHeight = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getFillExtrusionHeight(), propertyValue, - @"Setting fillExtrusionHeight to a constant value should update fill-extrusion-height."); - XCTAssertEqualObjects(layer.fillExtrusionHeight, constantStyleValue, - @"fillExtrusionHeight should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillExtrusionHeight = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting fillExtrusionHeight to a constant value expression should update fill-extrusion-height."); + XCTAssertEqualObjects(layer.fillExtrusionHeight, constantExpression, + @"fillExtrusionHeight should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillExtrusionHeight = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getFillExtrusionHeight(), propertyValue, - @"Setting fillExtrusionHeight to a camera function should update fill-extrusion-height."); - XCTAssertEqualObjects(layer.fillExtrusionHeight, functionStyleValue, - @"fillExtrusionHeight should round-trip camera functions."); + @"Setting fillExtrusionHeight to a camera expression should update fill-extrusion-height."); + XCTAssertEqualObjects(layer.fillExtrusionHeight, functionExpression, + @"fillExtrusionHeight should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.fillExtrusionHeight = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.fillExtrusionHeight = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getFillExtrusionHeight(), propertyValue, - @"Setting fillExtrusionHeight to a source function should update fill-extrusion-height."); - XCTAssertEqualObjects(layer.fillExtrusionHeight, functionStyleValue, - @"fillExtrusionHeight should round-trip source functions."); + @"Setting fillExtrusionHeight to a data expression should update fill-extrusion-height."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.fillExtrusionHeight, pedanticFunctionExpression, + @"fillExtrusionHeight should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.fillExtrusionHeight = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.fillExtrusionHeight = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -225,15 +242,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getFillExtrusionHeight(), propertyValue, - @"Setting fillExtrusionHeight to a composite function should update fill-extrusion-height."); - XCTAssertEqualObjects(layer.fillExtrusionHeight, functionStyleValue, - @"fillExtrusionHeight should round-trip composite functions."); + @"Setting fillExtrusionHeight to a camera-data expression should update fill-extrusion-height."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.fillExtrusionHeight, pedanticFunctionExpression, + @"fillExtrusionHeight should round-trip camera-data expressions."); layer.fillExtrusionHeight = nil; XCTAssertTrue(rawLayer->getFillExtrusionHeight().isUndefined(), @"Unsetting fillExtrusionHeight should return fill-extrusion-height to the default value."); - XCTAssertEqualObjects(layer.fillExtrusionHeight, defaultStyleValue, + XCTAssertEqualObjects(layer.fillExtrusionHeight, defaultExpression, @"fillExtrusionHeight should return the default value after being unset."); // Transition property test layer.fillExtrusionHeightTransition = transitionTest; @@ -250,39 +268,44 @@ { XCTAssertTrue(rawLayer->getFillExtrusionOpacity().isUndefined(), @"fill-extrusion-opacity should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.fillExtrusionOpacity; + NSExpression *defaultExpression = layer.fillExtrusionOpacity; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.fillExtrusionOpacity = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.fillExtrusionOpacity = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getFillExtrusionOpacity(), propertyValue, - @"Setting fillExtrusionOpacity to a constant value should update fill-extrusion-opacity."); - XCTAssertEqualObjects(layer.fillExtrusionOpacity, constantStyleValue, - @"fillExtrusionOpacity should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillExtrusionOpacity = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting fillExtrusionOpacity to a constant value expression should update fill-extrusion-opacity."); + XCTAssertEqualObjects(layer.fillExtrusionOpacity, constantExpression, + @"fillExtrusionOpacity should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillExtrusionOpacity = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getFillExtrusionOpacity(), propertyValue, - @"Setting fillExtrusionOpacity to a camera function should update fill-extrusion-opacity."); - XCTAssertEqualObjects(layer.fillExtrusionOpacity, functionStyleValue, - @"fillExtrusionOpacity should round-trip camera functions."); + @"Setting fillExtrusionOpacity to a camera expression should update fill-extrusion-opacity."); + XCTAssertEqualObjects(layer.fillExtrusionOpacity, functionExpression, + @"fillExtrusionOpacity should round-trip camera expressions."); layer.fillExtrusionOpacity = nil; XCTAssertTrue(rawLayer->getFillExtrusionOpacity().isUndefined(), @"Unsetting fillExtrusionOpacity should return fill-extrusion-opacity to the default value."); - XCTAssertEqualObjects(layer.fillExtrusionOpacity, defaultStyleValue, + XCTAssertEqualObjects(layer.fillExtrusionOpacity, defaultExpression, @"fillExtrusionOpacity should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillExtrusionOpacity = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillExtrusionOpacity = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.fillExtrusionOpacity = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillExtrusionLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.fillExtrusionOpacity = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillExtrusionLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.fillExtrusionOpacityTransition = transitionTest; auto toptions = rawLayer->getFillExtrusionOpacityTransition(); @@ -298,39 +321,44 @@ { XCTAssertTrue(rawLayer->getFillExtrusionPattern().isUndefined(), @"fill-extrusion-pattern should be unset initially."); - MGLStyleValue<NSString *> *defaultStyleValue = layer.fillExtrusionPattern; + NSExpression *defaultExpression = layer.fillExtrusionPattern; - MGLStyleValue<NSString *> *constantStyleValue = [MGLStyleValue<NSString *> valueWithRawValue:@"Fill Extrusion Pattern"]; - layer.fillExtrusionPattern = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'Fill Extrusion Pattern'"]; + layer.fillExtrusionPattern = constantExpression; mbgl::style::PropertyValue<std::string> propertyValue = { "Fill Extrusion Pattern" }; XCTAssertEqual(rawLayer->getFillExtrusionPattern(), propertyValue, - @"Setting fillExtrusionPattern to a constant value should update fill-extrusion-pattern."); - XCTAssertEqualObjects(layer.fillExtrusionPattern, constantStyleValue, - @"fillExtrusionPattern should round-trip constant values."); - - MGLStyleValue<NSString *> * functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillExtrusionPattern = functionStyleValue; - - mbgl::style::IntervalStops<std::string> intervalStops = { {{18, "Fill Extrusion Pattern"}} }; + @"Setting fillExtrusionPattern to a constant value expression should update fill-extrusion-pattern."); + XCTAssertEqualObjects(layer.fillExtrusionPattern, constantExpression, + @"fillExtrusionPattern should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'Fill Extrusion Pattern'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillExtrusionPattern = functionExpression; + + mbgl::style::IntervalStops<std::string> intervalStops = {{ + { -INFINITY, "Fill Extrusion Pattern" }, + { 18, "Fill Extrusion Pattern" }, + }}; propertyValue = mbgl::style::CameraFunction<std::string> { intervalStops }; XCTAssertEqual(rawLayer->getFillExtrusionPattern(), propertyValue, - @"Setting fillExtrusionPattern to a camera function should update fill-extrusion-pattern."); - XCTAssertEqualObjects(layer.fillExtrusionPattern, functionStyleValue, - @"fillExtrusionPattern should round-trip camera functions."); + @"Setting fillExtrusionPattern to a camera expression should update fill-extrusion-pattern."); + XCTAssertEqualObjects(layer.fillExtrusionPattern, functionExpression, + @"fillExtrusionPattern should round-trip camera expressions."); layer.fillExtrusionPattern = nil; XCTAssertTrue(rawLayer->getFillExtrusionPattern().isUndefined(), @"Unsetting fillExtrusionPattern should return fill-extrusion-pattern to the default value."); - XCTAssertEqualObjects(layer.fillExtrusionPattern, defaultStyleValue, + XCTAssertEqualObjects(layer.fillExtrusionPattern, defaultExpression, @"fillExtrusionPattern should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillExtrusionPattern = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillExtrusionPattern = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.fillExtrusionPattern = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillExtrusionLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.fillExtrusionPattern = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillExtrusionLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.fillExtrusionPatternTransition = transitionTest; auto toptions = rawLayer->getFillExtrusionPatternTransition(); @@ -346,84 +374,94 @@ { XCTAssertTrue(rawLayer->getFillExtrusionTranslate().isUndefined(), @"fill-extrusion-translate should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.fillExtrusionTranslation; + NSExpression *defaultExpression = layer.fillExtrusionTranslation; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue: + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", #if TARGET_OS_IPHONE [NSValue valueWithCGVector:CGVectorMake(1, 1)] #else [NSValue valueWithMGLVector:CGVectorMake(1, -1)] #endif ]; - layer.fillExtrusionTranslation = constantStyleValue; + layer.fillExtrusionTranslation = constantExpression; mbgl::style::PropertyValue<std::array<float, 2>> propertyValue = { { 1, 1 } }; XCTAssertEqual(rawLayer->getFillExtrusionTranslate(), propertyValue, - @"Setting fillExtrusionTranslation to a constant value should update fill-extrusion-translate."); - XCTAssertEqualObjects(layer.fillExtrusionTranslation, constantStyleValue, - @"fillExtrusionTranslation should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillExtrusionTranslation = functionStyleValue; - - mbgl::style::IntervalStops<std::array<float, 2>> intervalStops = { {{18, { 1, 1 }}} }; + @"Setting fillExtrusionTranslation to a constant value expression should update fill-extrusion-translate."); + XCTAssertEqualObjects(layer.fillExtrusionTranslation, constantExpression, + @"fillExtrusionTranslation should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"{1, 1}"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillExtrusionTranslation = functionExpression; + + mbgl::style::IntervalStops<std::array<float, 2>> intervalStops = {{ + { -INFINITY, { 1, 1 } }, + { 18, { 1, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<std::array<float, 2>> { intervalStops }; XCTAssertEqual(rawLayer->getFillExtrusionTranslate(), propertyValue, - @"Setting fillExtrusionTranslation to a camera function should update fill-extrusion-translate."); - XCTAssertEqualObjects(layer.fillExtrusionTranslation, functionStyleValue, - @"fillExtrusionTranslation should round-trip camera functions."); + @"Setting fillExtrusionTranslation to a camera expression should update fill-extrusion-translate."); + XCTAssertEqualObjects(layer.fillExtrusionTranslation, functionExpression, + @"fillExtrusionTranslation should round-trip camera expressions."); layer.fillExtrusionTranslation = nil; XCTAssertTrue(rawLayer->getFillExtrusionTranslate().isUndefined(), @"Unsetting fillExtrusionTranslation should return fill-extrusion-translate to the default value."); - XCTAssertEqualObjects(layer.fillExtrusionTranslation, defaultStyleValue, + XCTAssertEqualObjects(layer.fillExtrusionTranslation, defaultExpression, @"fillExtrusionTranslation should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillExtrusionTranslation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillExtrusionTranslation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.fillExtrusionTranslation = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillExtrusionLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.fillExtrusionTranslation = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillExtrusionLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // fill-extrusion-translate-anchor { XCTAssertTrue(rawLayer->getFillExtrusionTranslateAnchor().isUndefined(), @"fill-extrusion-translate-anchor should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.fillExtrusionTranslationAnchor; + NSExpression *defaultExpression = layer.fillExtrusionTranslationAnchor; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLFillExtrusionTranslationAnchor:MGLFillExtrusionTranslationAnchorViewport]]; - layer.fillExtrusionTranslationAnchor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + layer.fillExtrusionTranslationAnchor = constantExpression; mbgl::style::PropertyValue<mbgl::style::TranslateAnchorType> propertyValue = { mbgl::style::TranslateAnchorType::Viewport }; XCTAssertEqual(rawLayer->getFillExtrusionTranslateAnchor(), propertyValue, - @"Setting fillExtrusionTranslationAnchor to a constant value should update fill-extrusion-translate-anchor."); - XCTAssertEqualObjects(layer.fillExtrusionTranslationAnchor, constantStyleValue, - @"fillExtrusionTranslationAnchor should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillExtrusionTranslationAnchor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::TranslateAnchorType> intervalStops = { {{18, mbgl::style::TranslateAnchorType::Viewport}} }; + @"Setting fillExtrusionTranslationAnchor to a constant value expression should update fill-extrusion-translate-anchor."); + XCTAssertEqualObjects(layer.fillExtrusionTranslationAnchor, constantExpression, + @"fillExtrusionTranslationAnchor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillExtrusionTranslationAnchor = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::TranslateAnchorType> intervalStops = {{ + { -INFINITY, mbgl::style::TranslateAnchorType::Viewport }, + { 18, mbgl::style::TranslateAnchorType::Viewport }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::TranslateAnchorType> { intervalStops }; XCTAssertEqual(rawLayer->getFillExtrusionTranslateAnchor(), propertyValue, - @"Setting fillExtrusionTranslationAnchor to a camera function should update fill-extrusion-translate-anchor."); - XCTAssertEqualObjects(layer.fillExtrusionTranslationAnchor, functionStyleValue, - @"fillExtrusionTranslationAnchor should round-trip camera functions."); + @"Setting fillExtrusionTranslationAnchor to a camera expression should update fill-extrusion-translate-anchor."); + XCTAssertEqualObjects(layer.fillExtrusionTranslationAnchor, functionExpression, + @"fillExtrusionTranslationAnchor should round-trip camera expressions."); layer.fillExtrusionTranslationAnchor = nil; XCTAssertTrue(rawLayer->getFillExtrusionTranslateAnchor().isUndefined(), @"Unsetting fillExtrusionTranslationAnchor should return fill-extrusion-translate-anchor to the default value."); - XCTAssertEqualObjects(layer.fillExtrusionTranslationAnchor, defaultStyleValue, + XCTAssertEqualObjects(layer.fillExtrusionTranslationAnchor, defaultExpression, @"fillExtrusionTranslationAnchor should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillExtrusionTranslationAnchor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillExtrusionTranslationAnchor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.fillExtrusionTranslationAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillExtrusionLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.fillExtrusionTranslationAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillExtrusionLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } } diff --git a/platform/darwin/test/MGLFillStyleLayerTests.mm b/platform/darwin/test/MGLFillStyleLayerTests.mm index 85f0b24fa7..a5019f1032 100644 --- a/platform/darwin/test/MGLFillStyleLayerTests.mm +++ b/platform/darwin/test/MGLFillStyleLayerTests.mm @@ -30,8 +30,8 @@ XCTAssertNil(layer.sourceLayerIdentifier); XCTAssertNil(layer.predicate); - layer.predicate = [NSPredicate predicateWithValue:NO]; - XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithValue:NO]); + layer.predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]; + XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]); layer.predicate = nil; XCTAssertNil(layer.predicate); } @@ -52,79 +52,89 @@ { XCTAssertTrue(rawLayer->getFillAntialias().isUndefined(), @"fill-antialias should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.fillAntialiased; + NSExpression *defaultExpression = layer.fillAntialiased; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@NO]; - layer.fillAntialiased = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"false"]; + layer.fillAntialiased = constantExpression; mbgl::style::PropertyValue<bool> propertyValue = { false }; XCTAssertEqual(rawLayer->getFillAntialias(), propertyValue, - @"Setting fillAntialiased to a constant value should update fill-antialias."); - XCTAssertEqualObjects(layer.fillAntialiased, constantStyleValue, - @"fillAntialiased should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillAntialiased = functionStyleValue; - - mbgl::style::IntervalStops<bool> intervalStops = { {{18, false}} }; + @"Setting fillAntialiased to a constant value expression should update fill-antialias."); + XCTAssertEqualObjects(layer.fillAntialiased, constantExpression, + @"fillAntialiased should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"false"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillAntialiased = functionExpression; + + mbgl::style::IntervalStops<bool> intervalStops = {{ + { -INFINITY, false }, + { 18, false }, + }}; propertyValue = mbgl::style::CameraFunction<bool> { intervalStops }; XCTAssertEqual(rawLayer->getFillAntialias(), propertyValue, - @"Setting fillAntialiased to a camera function should update fill-antialias."); - XCTAssertEqualObjects(layer.fillAntialiased, functionStyleValue, - @"fillAntialiased should round-trip camera functions."); + @"Setting fillAntialiased to a camera expression should update fill-antialias."); + XCTAssertEqualObjects(layer.fillAntialiased, functionExpression, + @"fillAntialiased should round-trip camera expressions."); layer.fillAntialiased = nil; XCTAssertTrue(rawLayer->getFillAntialias().isUndefined(), @"Unsetting fillAntialiased should return fill-antialias to the default value."); - XCTAssertEqualObjects(layer.fillAntialiased, defaultStyleValue, + XCTAssertEqualObjects(layer.fillAntialiased, defaultExpression, @"fillAntialiased should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillAntialiased = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillAntialiased = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.fillAntialiased = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.fillAntialiased = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // fill-color { XCTAssertTrue(rawLayer->getFillColor().isUndefined(), @"fill-color should be unset initially."); - MGLStyleValue<MGLColor *> *defaultStyleValue = layer.fillColor; + NSExpression *defaultExpression = layer.fillColor; - MGLStyleValue<MGLColor *> *constantStyleValue = [MGLStyleValue<MGLColor *> valueWithRawValue:[MGLColor redColor]]; - layer.fillColor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.fillColor = constantExpression; mbgl::style::DataDrivenPropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } }; XCTAssertEqual(rawLayer->getFillColor(), propertyValue, - @"Setting fillColor to a constant value should update fill-color."); - XCTAssertEqualObjects(layer.fillColor, constantStyleValue, - @"fillColor should round-trip constant values."); - - MGLStyleValue<MGLColor *> * functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillColor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::Color> intervalStops = { {{18, { 1, 0, 0, 1 }}} }; + @"Setting fillColor to a constant value expression should update fill-color."); + XCTAssertEqualObjects(layer.fillColor, constantExpression, + @"fillColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillColor = functionExpression; + + mbgl::style::IntervalStops<mbgl::Color> intervalStops = {{ + { -INFINITY, { 1, 0, 0, 1 } }, + { 18, { 1, 0, 0, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops }; XCTAssertEqual(rawLayer->getFillColor(), propertyValue, - @"Setting fillColor to a camera function should update fill-color."); - XCTAssertEqualObjects(layer.fillColor, functionStyleValue, - @"fillColor should round-trip camera functions."); + @"Setting fillColor to a camera expression should update fill-color."); + XCTAssertEqualObjects(layer.fillColor, functionExpression, + @"fillColor should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.fillColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.fillColor = functionExpression; mbgl::style::ExponentialStops<mbgl::Color> exponentialStops = { {{18, { 1, 0, 0, 1 }}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<mbgl::Color> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getFillColor(), propertyValue, - @"Setting fillColor to a source function should update fill-color."); - XCTAssertEqualObjects(layer.fillColor, functionStyleValue, - @"fillColor should round-trip source functions."); + @"Setting fillColor to a data expression should update fill-color."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.fillColor, pedanticFunctionExpression, + @"fillColor should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.fillColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.fillColor = functionExpression; std::map<float, mbgl::Color> innerStops { {18, { 1, 0, 0, 1 }} }; mbgl::style::CompositeExponentialStops<mbgl::Color> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -132,15 +142,16 @@ propertyValue = mbgl::style::CompositeFunction<mbgl::Color> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getFillColor(), propertyValue, - @"Setting fillColor to a composite function should update fill-color."); - XCTAssertEqualObjects(layer.fillColor, functionStyleValue, - @"fillColor should round-trip composite functions."); + @"Setting fillColor to a camera-data expression should update fill-color."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.fillColor, pedanticFunctionExpression, + @"fillColor should round-trip camera-data expressions."); layer.fillColor = nil; XCTAssertTrue(rawLayer->getFillColor().isUndefined(), @"Unsetting fillColor should return fill-color to the default value."); - XCTAssertEqualObjects(layer.fillColor, defaultStyleValue, + XCTAssertEqualObjects(layer.fillColor, defaultExpression, @"fillColor should return the default value after being unset."); // Transition property test layer.fillColorTransition = transitionTest; @@ -157,40 +168,45 @@ { XCTAssertTrue(rawLayer->getFillOpacity().isUndefined(), @"fill-opacity should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.fillOpacity; + NSExpression *defaultExpression = layer.fillOpacity; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.fillOpacity = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.fillOpacity = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getFillOpacity(), propertyValue, - @"Setting fillOpacity to a constant value should update fill-opacity."); - XCTAssertEqualObjects(layer.fillOpacity, constantStyleValue, - @"fillOpacity should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillOpacity = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting fillOpacity to a constant value expression should update fill-opacity."); + XCTAssertEqualObjects(layer.fillOpacity, constantExpression, + @"fillOpacity should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillOpacity = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getFillOpacity(), propertyValue, - @"Setting fillOpacity to a camera function should update fill-opacity."); - XCTAssertEqualObjects(layer.fillOpacity, functionStyleValue, - @"fillOpacity should round-trip camera functions."); + @"Setting fillOpacity to a camera expression should update fill-opacity."); + XCTAssertEqualObjects(layer.fillOpacity, functionExpression, + @"fillOpacity should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.fillOpacity = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.fillOpacity = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getFillOpacity(), propertyValue, - @"Setting fillOpacity to a source function should update fill-opacity."); - XCTAssertEqualObjects(layer.fillOpacity, functionStyleValue, - @"fillOpacity should round-trip source functions."); + @"Setting fillOpacity to a data expression should update fill-opacity."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.fillOpacity, pedanticFunctionExpression, + @"fillOpacity should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.fillOpacity = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.fillOpacity = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -198,15 +214,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getFillOpacity(), propertyValue, - @"Setting fillOpacity to a composite function should update fill-opacity."); - XCTAssertEqualObjects(layer.fillOpacity, functionStyleValue, - @"fillOpacity should round-trip composite functions."); + @"Setting fillOpacity to a camera-data expression should update fill-opacity."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.fillOpacity, pedanticFunctionExpression, + @"fillOpacity should round-trip camera-data expressions."); layer.fillOpacity = nil; XCTAssertTrue(rawLayer->getFillOpacity().isUndefined(), @"Unsetting fillOpacity should return fill-opacity to the default value."); - XCTAssertEqualObjects(layer.fillOpacity, defaultStyleValue, + XCTAssertEqualObjects(layer.fillOpacity, defaultExpression, @"fillOpacity should return the default value after being unset."); // Transition property test layer.fillOpacityTransition = transitionTest; @@ -223,40 +240,45 @@ { XCTAssertTrue(rawLayer->getFillOutlineColor().isUndefined(), @"fill-outline-color should be unset initially."); - MGLStyleValue<MGLColor *> *defaultStyleValue = layer.fillOutlineColor; + NSExpression *defaultExpression = layer.fillOutlineColor; - MGLStyleValue<MGLColor *> *constantStyleValue = [MGLStyleValue<MGLColor *> valueWithRawValue:[MGLColor redColor]]; - layer.fillOutlineColor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.fillOutlineColor = constantExpression; mbgl::style::DataDrivenPropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } }; XCTAssertEqual(rawLayer->getFillOutlineColor(), propertyValue, - @"Setting fillOutlineColor to a constant value should update fill-outline-color."); - XCTAssertEqualObjects(layer.fillOutlineColor, constantStyleValue, - @"fillOutlineColor should round-trip constant values."); - - MGLStyleValue<MGLColor *> * functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillOutlineColor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::Color> intervalStops = { {{18, { 1, 0, 0, 1 }}} }; + @"Setting fillOutlineColor to a constant value expression should update fill-outline-color."); + XCTAssertEqualObjects(layer.fillOutlineColor, constantExpression, + @"fillOutlineColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillOutlineColor = functionExpression; + + mbgl::style::IntervalStops<mbgl::Color> intervalStops = {{ + { -INFINITY, { 1, 0, 0, 1 } }, + { 18, { 1, 0, 0, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops }; XCTAssertEqual(rawLayer->getFillOutlineColor(), propertyValue, - @"Setting fillOutlineColor to a camera function should update fill-outline-color."); - XCTAssertEqualObjects(layer.fillOutlineColor, functionStyleValue, - @"fillOutlineColor should round-trip camera functions."); + @"Setting fillOutlineColor to a camera expression should update fill-outline-color."); + XCTAssertEqualObjects(layer.fillOutlineColor, functionExpression, + @"fillOutlineColor should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.fillOutlineColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.fillOutlineColor = functionExpression; mbgl::style::ExponentialStops<mbgl::Color> exponentialStops = { {{18, { 1, 0, 0, 1 }}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<mbgl::Color> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getFillOutlineColor(), propertyValue, - @"Setting fillOutlineColor to a source function should update fill-outline-color."); - XCTAssertEqualObjects(layer.fillOutlineColor, functionStyleValue, - @"fillOutlineColor should round-trip source functions."); + @"Setting fillOutlineColor to a data expression should update fill-outline-color."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.fillOutlineColor, pedanticFunctionExpression, + @"fillOutlineColor should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.fillOutlineColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.fillOutlineColor = functionExpression; std::map<float, mbgl::Color> innerStops { {18, { 1, 0, 0, 1 }} }; mbgl::style::CompositeExponentialStops<mbgl::Color> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -264,15 +286,16 @@ propertyValue = mbgl::style::CompositeFunction<mbgl::Color> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getFillOutlineColor(), propertyValue, - @"Setting fillOutlineColor to a composite function should update fill-outline-color."); - XCTAssertEqualObjects(layer.fillOutlineColor, functionStyleValue, - @"fillOutlineColor should round-trip composite functions."); + @"Setting fillOutlineColor to a camera-data expression should update fill-outline-color."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.fillOutlineColor, pedanticFunctionExpression, + @"fillOutlineColor should round-trip camera-data expressions."); layer.fillOutlineColor = nil; XCTAssertTrue(rawLayer->getFillOutlineColor().isUndefined(), @"Unsetting fillOutlineColor should return fill-outline-color to the default value."); - XCTAssertEqualObjects(layer.fillOutlineColor, defaultStyleValue, + XCTAssertEqualObjects(layer.fillOutlineColor, defaultExpression, @"fillOutlineColor should return the default value after being unset."); // Transition property test layer.fillOutlineColorTransition = transitionTest; @@ -289,39 +312,44 @@ { XCTAssertTrue(rawLayer->getFillPattern().isUndefined(), @"fill-pattern should be unset initially."); - MGLStyleValue<NSString *> *defaultStyleValue = layer.fillPattern; + NSExpression *defaultExpression = layer.fillPattern; - MGLStyleValue<NSString *> *constantStyleValue = [MGLStyleValue<NSString *> valueWithRawValue:@"Fill Pattern"]; - layer.fillPattern = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'Fill Pattern'"]; + layer.fillPattern = constantExpression; mbgl::style::PropertyValue<std::string> propertyValue = { "Fill Pattern" }; XCTAssertEqual(rawLayer->getFillPattern(), propertyValue, - @"Setting fillPattern to a constant value should update fill-pattern."); - XCTAssertEqualObjects(layer.fillPattern, constantStyleValue, - @"fillPattern should round-trip constant values."); - - MGLStyleValue<NSString *> * functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillPattern = functionStyleValue; - - mbgl::style::IntervalStops<std::string> intervalStops = { {{18, "Fill Pattern"}} }; + @"Setting fillPattern to a constant value expression should update fill-pattern."); + XCTAssertEqualObjects(layer.fillPattern, constantExpression, + @"fillPattern should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'Fill Pattern'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillPattern = functionExpression; + + mbgl::style::IntervalStops<std::string> intervalStops = {{ + { -INFINITY, "Fill Pattern" }, + { 18, "Fill Pattern" }, + }}; propertyValue = mbgl::style::CameraFunction<std::string> { intervalStops }; XCTAssertEqual(rawLayer->getFillPattern(), propertyValue, - @"Setting fillPattern to a camera function should update fill-pattern."); - XCTAssertEqualObjects(layer.fillPattern, functionStyleValue, - @"fillPattern should round-trip camera functions."); + @"Setting fillPattern to a camera expression should update fill-pattern."); + XCTAssertEqualObjects(layer.fillPattern, functionExpression, + @"fillPattern should round-trip camera expressions."); layer.fillPattern = nil; XCTAssertTrue(rawLayer->getFillPattern().isUndefined(), @"Unsetting fillPattern should return fill-pattern to the default value."); - XCTAssertEqualObjects(layer.fillPattern, defaultStyleValue, + XCTAssertEqualObjects(layer.fillPattern, defaultExpression, @"fillPattern should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillPattern = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillPattern = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.fillPattern = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.fillPattern = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.fillPatternTransition = transitionTest; auto toptions = rawLayer->getFillPatternTransition(); @@ -337,84 +365,94 @@ { XCTAssertTrue(rawLayer->getFillTranslate().isUndefined(), @"fill-translate should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.fillTranslation; + NSExpression *defaultExpression = layer.fillTranslation; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue: + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", #if TARGET_OS_IPHONE [NSValue valueWithCGVector:CGVectorMake(1, 1)] #else [NSValue valueWithMGLVector:CGVectorMake(1, -1)] #endif ]; - layer.fillTranslation = constantStyleValue; + layer.fillTranslation = constantExpression; mbgl::style::PropertyValue<std::array<float, 2>> propertyValue = { { 1, 1 } }; XCTAssertEqual(rawLayer->getFillTranslate(), propertyValue, - @"Setting fillTranslation to a constant value should update fill-translate."); - XCTAssertEqualObjects(layer.fillTranslation, constantStyleValue, - @"fillTranslation should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillTranslation = functionStyleValue; - - mbgl::style::IntervalStops<std::array<float, 2>> intervalStops = { {{18, { 1, 1 }}} }; + @"Setting fillTranslation to a constant value expression should update fill-translate."); + XCTAssertEqualObjects(layer.fillTranslation, constantExpression, + @"fillTranslation should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"{1, 1}"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillTranslation = functionExpression; + + mbgl::style::IntervalStops<std::array<float, 2>> intervalStops = {{ + { -INFINITY, { 1, 1 } }, + { 18, { 1, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<std::array<float, 2>> { intervalStops }; XCTAssertEqual(rawLayer->getFillTranslate(), propertyValue, - @"Setting fillTranslation to a camera function should update fill-translate."); - XCTAssertEqualObjects(layer.fillTranslation, functionStyleValue, - @"fillTranslation should round-trip camera functions."); + @"Setting fillTranslation to a camera expression should update fill-translate."); + XCTAssertEqualObjects(layer.fillTranslation, functionExpression, + @"fillTranslation should round-trip camera expressions."); layer.fillTranslation = nil; XCTAssertTrue(rawLayer->getFillTranslate().isUndefined(), @"Unsetting fillTranslation should return fill-translate to the default value."); - XCTAssertEqualObjects(layer.fillTranslation, defaultStyleValue, + XCTAssertEqualObjects(layer.fillTranslation, defaultExpression, @"fillTranslation should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillTranslation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillTranslation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.fillTranslation = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.fillTranslation = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // fill-translate-anchor { XCTAssertTrue(rawLayer->getFillTranslateAnchor().isUndefined(), @"fill-translate-anchor should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.fillTranslationAnchor; + NSExpression *defaultExpression = layer.fillTranslationAnchor; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLFillTranslationAnchor:MGLFillTranslationAnchorViewport]]; - layer.fillTranslationAnchor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + layer.fillTranslationAnchor = constantExpression; mbgl::style::PropertyValue<mbgl::style::TranslateAnchorType> propertyValue = { mbgl::style::TranslateAnchorType::Viewport }; XCTAssertEqual(rawLayer->getFillTranslateAnchor(), propertyValue, - @"Setting fillTranslationAnchor to a constant value should update fill-translate-anchor."); - XCTAssertEqualObjects(layer.fillTranslationAnchor, constantStyleValue, - @"fillTranslationAnchor should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.fillTranslationAnchor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::TranslateAnchorType> intervalStops = { {{18, mbgl::style::TranslateAnchorType::Viewport}} }; + @"Setting fillTranslationAnchor to a constant value expression should update fill-translate-anchor."); + XCTAssertEqualObjects(layer.fillTranslationAnchor, constantExpression, + @"fillTranslationAnchor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.fillTranslationAnchor = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::TranslateAnchorType> intervalStops = {{ + { -INFINITY, mbgl::style::TranslateAnchorType::Viewport }, + { 18, mbgl::style::TranslateAnchorType::Viewport }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::TranslateAnchorType> { intervalStops }; XCTAssertEqual(rawLayer->getFillTranslateAnchor(), propertyValue, - @"Setting fillTranslationAnchor to a camera function should update fill-translate-anchor."); - XCTAssertEqualObjects(layer.fillTranslationAnchor, functionStyleValue, - @"fillTranslationAnchor should round-trip camera functions."); + @"Setting fillTranslationAnchor to a camera expression should update fill-translate-anchor."); + XCTAssertEqualObjects(layer.fillTranslationAnchor, functionExpression, + @"fillTranslationAnchor should round-trip camera expressions."); layer.fillTranslationAnchor = nil; XCTAssertTrue(rawLayer->getFillTranslateAnchor().isUndefined(), @"Unsetting fillTranslationAnchor should return fill-translate-anchor to the default value."); - XCTAssertEqualObjects(layer.fillTranslationAnchor, defaultStyleValue, + XCTAssertEqualObjects(layer.fillTranslationAnchor, defaultExpression, @"fillTranslationAnchor should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillTranslationAnchor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.fillTranslationAnchor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.fillTranslationAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.fillTranslationAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLFillLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } } diff --git a/platform/darwin/test/MGLGeometryTests.mm b/platform/darwin/test/MGLGeometryTests.mm index 1c85470188..a0ddecf77e 100644 --- a/platform/darwin/test/MGLGeometryTests.mm +++ b/platform/darwin/test/MGLGeometryTests.mm @@ -163,4 +163,13 @@ [NSValue valueWithMGLCoordinate:quad.bottomRight], @"Quad bottom right should be computed correctly."); } + +- (void)testMGLMapPoint { + MGLMapPoint point = MGLMapPointForCoordinate(CLLocationCoordinate2DMake(37.936, -80.425), 0.0); + + MGLMapPoint roundTrippedPoint = [NSValue valueWithMGLMapPoint:point].MGLMapPointValue; + XCTAssertEqual(point.x, roundTrippedPoint.x); + XCTAssertEqual(point.y, roundTrippedPoint.y); + XCTAssertEqual(point.zoomLevel, roundTrippedPoint.zoomLevel); +} @end diff --git a/platform/darwin/test/MGLHeatmapColorTests.mm b/platform/darwin/test/MGLHeatmapColorTests.mm new file mode 100644 index 0000000000..bed777ae05 --- /dev/null +++ b/platform/darwin/test/MGLHeatmapColorTests.mm @@ -0,0 +1,62 @@ +#import <Mapbox/Mapbox.h> +#import <XCTest/XCTest.h> + +#import "MGLStyleLayer_Private.h" + +#include <mbgl/style/layers/heatmap_layer.hpp> + +@interface MGLHeatmapColorTests : XCTestCase <MGLMapViewDelegate> +@end + +@implementation MGLHeatmapColorTests + +- (void)testProperties { + MGLPointFeature *feature = [[MGLPointFeature alloc] init]; + MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil]; + MGLHeatmapStyleLayer *layer = [[MGLHeatmapStyleLayer alloc] initWithIdentifier:@"layerID" source:source]; + + auto rawLayer = layer.rawLayer->as<mbgl::style::HeatmapLayer>(); + + XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(), + @"heatmap-color should be unset initially."); + NSExpression *defaultExpression = layer.heatmapColor; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.heatmapColor = constantExpression; + + + mbgl::style::PropertyValue<float> propertyValue = { 0xff }; + XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(0.0), mbgl::Color::red(), + @"Setting heatmapColor to a constant value expression should update heatmap-color."); + XCTAssertEqualObjects(layer.heatmapColor, constantExpression, + @"heatmapColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *constantExpression2 = [NSExpression expressionWithFormat:@"%@", [MGLColor blueColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($heatmapDensity, %@, %@)", constantExpression, @{@12: constantExpression2}]; + layer.heatmapColor = functionExpression; + + XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(11.0), mbgl::Color::red(), + @"Setting heatmapColor to an expression depending on $heatmapDensity should update heatmap-color."); + XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(12.0), mbgl::Color::blue(), + @"Setting heatmapColor to an expression depending on $heatmapDensity should update heatmap-color."); + XCTAssertEqualObjects(layer.heatmapColor, functionExpression, + @"heatmapColor should round-trip expressions depending on $heatmapDensity."); + + layer.heatmapColor = nil; + XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(), + @"Unsetting heatmapColor should return heatmap-color to the default value."); + // The contained colors aren’t object equal, even though their descriptions are. + XCTAssertEqualObjects(layer.heatmapColor.description, defaultExpression.description, + @"heatmapColor should return the default value after being unset."); + + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera expression is applied to heatmapColor."); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a data expression is applied to heatmapColor."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); +} + +@end diff --git a/platform/darwin/test/MGLHeatmapStyleLayerTests.mm b/platform/darwin/test/MGLHeatmapStyleLayerTests.mm new file mode 100644 index 0000000000..e4b1917257 --- /dev/null +++ b/platform/darwin/test/MGLHeatmapStyleLayerTests.mm @@ -0,0 +1,300 @@ +// This file is generated. +// Edit platform/darwin/scripts/generate-style-code.js, then run `make darwin-style-code`. + +#import "MGLStyleLayerTests.h" +#import "../../darwin/src/NSDate+MGLAdditions.h" + +#import "MGLStyleLayer_Private.h" + +#include <mbgl/style/layers/heatmap_layer.hpp> +#include <mbgl/style/transition_options.hpp> + +@interface MGLHeatmapLayerTests : MGLStyleLayerTests +@end + +@implementation MGLHeatmapLayerTests + ++ (NSString *)layerType { + return @"heatmap"; +} + +- (void)testPredicates { + MGLPointFeature *feature = [[MGLPointFeature alloc] init]; + MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil]; + MGLHeatmapStyleLayer *layer = [[MGLHeatmapStyleLayer alloc] initWithIdentifier:@"layerID" source:source]; + + XCTAssertNil(layer.sourceLayerIdentifier); + layer.sourceLayerIdentifier = @"layerID"; + XCTAssertEqualObjects(layer.sourceLayerIdentifier, @"layerID"); + layer.sourceLayerIdentifier = nil; + XCTAssertNil(layer.sourceLayerIdentifier); + + XCTAssertNil(layer.predicate); + layer.predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]; + XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]); + layer.predicate = nil; + XCTAssertNil(layer.predicate); +} + +- (void)testProperties { + MGLPointFeature *feature = [[MGLPointFeature alloc] init]; + MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil]; + + MGLHeatmapStyleLayer *layer = [[MGLHeatmapStyleLayer alloc] initWithIdentifier:@"layerID" source:source]; + XCTAssertNotEqual(layer.rawLayer, nullptr); + XCTAssertTrue(layer.rawLayer->is<mbgl::style::HeatmapLayer>()); + auto rawLayer = layer.rawLayer->as<mbgl::style::HeatmapLayer>(); + + MGLTransition transitionTest = MGLTransitionMake(5, 4); + + + // heatmap-intensity + { + XCTAssertTrue(rawLayer->getHeatmapIntensity().isUndefined(), + @"heatmap-intensity should be unset initially."); + NSExpression *defaultExpression = layer.heatmapIntensity; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.heatmapIntensity = constantExpression; + mbgl::style::PropertyValue<float> propertyValue = { 0xff }; + XCTAssertEqual(rawLayer->getHeatmapIntensity(), propertyValue, + @"Setting heatmapIntensity to a constant value expression should update heatmap-intensity."); + XCTAssertEqualObjects(layer.heatmapIntensity, constantExpression, + @"heatmapIntensity should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.heatmapIntensity = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; + propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; + + XCTAssertEqual(rawLayer->getHeatmapIntensity(), propertyValue, + @"Setting heatmapIntensity to a camera expression should update heatmap-intensity."); + XCTAssertEqualObjects(layer.heatmapIntensity, functionExpression, + @"heatmapIntensity should round-trip camera expressions."); + + + + layer.heatmapIntensity = nil; + XCTAssertTrue(rawLayer->getHeatmapIntensity().isUndefined(), + @"Unsetting heatmapIntensity should return heatmap-intensity to the default value."); + XCTAssertEqualObjects(layer.heatmapIntensity, defaultExpression, + @"heatmapIntensity should return the default value after being unset."); + + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.heatmapIntensity = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.heatmapIntensity = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + // Transition property test + layer.heatmapIntensityTransition = transitionTest; + auto toptions = rawLayer->getHeatmapIntensityTransition(); + XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay); + XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration); + + MGLTransition heatmapIntensityTransition = layer.heatmapIntensityTransition; + XCTAssertEqual(heatmapIntensityTransition.delay, transitionTest.delay); + XCTAssertEqual(heatmapIntensityTransition.duration, transitionTest.duration); + } + + // heatmap-opacity + { + XCTAssertTrue(rawLayer->getHeatmapOpacity().isUndefined(), + @"heatmap-opacity should be unset initially."); + NSExpression *defaultExpression = layer.heatmapOpacity; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.heatmapOpacity = constantExpression; + mbgl::style::PropertyValue<float> propertyValue = { 0xff }; + XCTAssertEqual(rawLayer->getHeatmapOpacity(), propertyValue, + @"Setting heatmapOpacity to a constant value expression should update heatmap-opacity."); + XCTAssertEqualObjects(layer.heatmapOpacity, constantExpression, + @"heatmapOpacity should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.heatmapOpacity = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; + propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; + + XCTAssertEqual(rawLayer->getHeatmapOpacity(), propertyValue, + @"Setting heatmapOpacity to a camera expression should update heatmap-opacity."); + XCTAssertEqualObjects(layer.heatmapOpacity, functionExpression, + @"heatmapOpacity should round-trip camera expressions."); + + + + layer.heatmapOpacity = nil; + XCTAssertTrue(rawLayer->getHeatmapOpacity().isUndefined(), + @"Unsetting heatmapOpacity should return heatmap-opacity to the default value."); + XCTAssertEqualObjects(layer.heatmapOpacity, defaultExpression, + @"heatmapOpacity should return the default value after being unset."); + + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.heatmapOpacity = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.heatmapOpacity = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + // Transition property test + layer.heatmapOpacityTransition = transitionTest; + auto toptions = rawLayer->getHeatmapOpacityTransition(); + XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay); + XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration); + + MGLTransition heatmapOpacityTransition = layer.heatmapOpacityTransition; + XCTAssertEqual(heatmapOpacityTransition.delay, transitionTest.delay); + XCTAssertEqual(heatmapOpacityTransition.duration, transitionTest.duration); + } + + // heatmap-radius + { + XCTAssertTrue(rawLayer->getHeatmapRadius().isUndefined(), + @"heatmap-radius should be unset initially."); + NSExpression *defaultExpression = layer.heatmapRadius; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.heatmapRadius = constantExpression; + mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; + XCTAssertEqual(rawLayer->getHeatmapRadius(), propertyValue, + @"Setting heatmapRadius to a constant value expression should update heatmap-radius."); + XCTAssertEqualObjects(layer.heatmapRadius, constantExpression, + @"heatmapRadius should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.heatmapRadius = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; + propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; + + XCTAssertEqual(rawLayer->getHeatmapRadius(), propertyValue, + @"Setting heatmapRadius to a camera expression should update heatmap-radius."); + XCTAssertEqualObjects(layer.heatmapRadius, functionExpression, + @"heatmapRadius should round-trip camera expressions."); + + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.heatmapRadius = functionExpression; + + mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; + propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; + + XCTAssertEqual(rawLayer->getHeatmapRadius(), propertyValue, + @"Setting heatmapRadius to a data expression should update heatmap-radius."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.heatmapRadius, pedanticFunctionExpression, + @"heatmapRadius should round-trip data expressions."); + + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.heatmapRadius = functionExpression; + + std::map<float, float> innerStops { {18, 0xff} }; + mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; + + propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; + + XCTAssertEqual(rawLayer->getHeatmapRadius(), propertyValue, + @"Setting heatmapRadius to a camera-data expression should update heatmap-radius."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.heatmapRadius, pedanticFunctionExpression, + @"heatmapRadius should round-trip camera-data expressions."); + + + layer.heatmapRadius = nil; + XCTAssertTrue(rawLayer->getHeatmapRadius().isUndefined(), + @"Unsetting heatmapRadius should return heatmap-radius to the default value."); + XCTAssertEqualObjects(layer.heatmapRadius, defaultExpression, + @"heatmapRadius should return the default value after being unset."); + // Transition property test + layer.heatmapRadiusTransition = transitionTest; + auto toptions = rawLayer->getHeatmapRadiusTransition(); + XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay); + XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration); + + MGLTransition heatmapRadiusTransition = layer.heatmapRadiusTransition; + XCTAssertEqual(heatmapRadiusTransition.delay, transitionTest.delay); + XCTAssertEqual(heatmapRadiusTransition.duration, transitionTest.duration); + } + + // heatmap-weight + { + XCTAssertTrue(rawLayer->getHeatmapWeight().isUndefined(), + @"heatmap-weight should be unset initially."); + NSExpression *defaultExpression = layer.heatmapWeight; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.heatmapWeight = constantExpression; + mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; + XCTAssertEqual(rawLayer->getHeatmapWeight(), propertyValue, + @"Setting heatmapWeight to a constant value expression should update heatmap-weight."); + XCTAssertEqualObjects(layer.heatmapWeight, constantExpression, + @"heatmapWeight should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.heatmapWeight = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; + propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; + + XCTAssertEqual(rawLayer->getHeatmapWeight(), propertyValue, + @"Setting heatmapWeight to a camera expression should update heatmap-weight."); + XCTAssertEqualObjects(layer.heatmapWeight, functionExpression, + @"heatmapWeight should round-trip camera expressions."); + + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.heatmapWeight = functionExpression; + + mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; + propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; + + XCTAssertEqual(rawLayer->getHeatmapWeight(), propertyValue, + @"Setting heatmapWeight to a data expression should update heatmap-weight."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.heatmapWeight, pedanticFunctionExpression, + @"heatmapWeight should round-trip data expressions."); + + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.heatmapWeight = functionExpression; + + std::map<float, float> innerStops { {18, 0xff} }; + mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; + + propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; + + XCTAssertEqual(rawLayer->getHeatmapWeight(), propertyValue, + @"Setting heatmapWeight to a camera-data expression should update heatmap-weight."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.heatmapWeight, pedanticFunctionExpression, + @"heatmapWeight should round-trip camera-data expressions."); + + + layer.heatmapWeight = nil; + XCTAssertTrue(rawLayer->getHeatmapWeight().isUndefined(), + @"Unsetting heatmapWeight should return heatmap-weight to the default value."); + XCTAssertEqualObjects(layer.heatmapWeight, defaultExpression, + @"heatmapWeight should return the default value after being unset."); + } +} + +- (void)testPropertyNames { + [self testPropertyName:@"heatmap-intensity" isBoolean:NO]; + [self testPropertyName:@"heatmap-opacity" isBoolean:NO]; + [self testPropertyName:@"heatmap-radius" isBoolean:NO]; + [self testPropertyName:@"heatmap-weight" isBoolean:NO]; +} + +@end diff --git a/platform/darwin/test/MGLHillshadeStyleLayerTests.mm b/platform/darwin/test/MGLHillshadeStyleLayerTests.mm new file mode 100644 index 0000000000..34937d1674 --- /dev/null +++ b/platform/darwin/test/MGLHillshadeStyleLayerTests.mm @@ -0,0 +1,348 @@ +// This file is generated. +// Edit platform/darwin/scripts/generate-style-code.js, then run `make darwin-style-code`. + +#import "MGLStyleLayerTests.h" +#import "../../darwin/src/NSDate+MGLAdditions.h" + +#import "MGLStyleLayer_Private.h" + +#include <mbgl/style/layers/hillshade_layer.hpp> +#include <mbgl/style/transition_options.hpp> + +@interface MGLHillshadeLayerTests : MGLStyleLayerTests +@end + +@implementation MGLHillshadeLayerTests + ++ (NSString *)layerType { + return @"hillshade"; +} + +- (void)testProperties { + MGLPointFeature *feature = [[MGLPointFeature alloc] init]; + MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil]; + + MGLHillshadeStyleLayer *layer = [[MGLHillshadeStyleLayer alloc] initWithIdentifier:@"layerID" source:source]; + XCTAssertNotEqual(layer.rawLayer, nullptr); + XCTAssertTrue(layer.rawLayer->is<mbgl::style::HillshadeLayer>()); + auto rawLayer = layer.rawLayer->as<mbgl::style::HillshadeLayer>(); + + MGLTransition transitionTest = MGLTransitionMake(5, 4); + + + // hillshade-accent-color + { + XCTAssertTrue(rawLayer->getHillshadeAccentColor().isUndefined(), + @"hillshade-accent-color should be unset initially."); + NSExpression *defaultExpression = layer.hillshadeAccentColor; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.hillshadeAccentColor = constantExpression; + mbgl::style::PropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } }; + XCTAssertEqual(rawLayer->getHillshadeAccentColor(), propertyValue, + @"Setting hillshadeAccentColor to a constant value expression should update hillshade-accent-color."); + XCTAssertEqualObjects(layer.hillshadeAccentColor, constantExpression, + @"hillshadeAccentColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.hillshadeAccentColor = functionExpression; + + mbgl::style::IntervalStops<mbgl::Color> intervalStops = {{ + { -INFINITY, { 1, 0, 0, 1 } }, + { 18, { 1, 0, 0, 1 } }, + }}; + propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops }; + + XCTAssertEqual(rawLayer->getHillshadeAccentColor(), propertyValue, + @"Setting hillshadeAccentColor to a camera expression should update hillshade-accent-color."); + XCTAssertEqualObjects(layer.hillshadeAccentColor, functionExpression, + @"hillshadeAccentColor should round-trip camera expressions."); + + + + layer.hillshadeAccentColor = nil; + XCTAssertTrue(rawLayer->getHillshadeAccentColor().isUndefined(), + @"Unsetting hillshadeAccentColor should return hillshade-accent-color to the default value."); + XCTAssertEqualObjects(layer.hillshadeAccentColor, defaultExpression, + @"hillshadeAccentColor should return the default value after being unset."); + + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.hillshadeAccentColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHillshadeLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.hillshadeAccentColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHillshadeLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + // Transition property test + layer.hillshadeAccentColorTransition = transitionTest; + auto toptions = rawLayer->getHillshadeAccentColorTransition(); + XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay); + XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration); + + MGLTransition hillshadeAccentColorTransition = layer.hillshadeAccentColorTransition; + XCTAssertEqual(hillshadeAccentColorTransition.delay, transitionTest.delay); + XCTAssertEqual(hillshadeAccentColorTransition.duration, transitionTest.duration); + } + + // hillshade-exaggeration + { + XCTAssertTrue(rawLayer->getHillshadeExaggeration().isUndefined(), + @"hillshade-exaggeration should be unset initially."); + NSExpression *defaultExpression = layer.hillshadeExaggeration; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.hillshadeExaggeration = constantExpression; + mbgl::style::PropertyValue<float> propertyValue = { 0xff }; + XCTAssertEqual(rawLayer->getHillshadeExaggeration(), propertyValue, + @"Setting hillshadeExaggeration to a constant value expression should update hillshade-exaggeration."); + XCTAssertEqualObjects(layer.hillshadeExaggeration, constantExpression, + @"hillshadeExaggeration should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.hillshadeExaggeration = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; + propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; + + XCTAssertEqual(rawLayer->getHillshadeExaggeration(), propertyValue, + @"Setting hillshadeExaggeration to a camera expression should update hillshade-exaggeration."); + XCTAssertEqualObjects(layer.hillshadeExaggeration, functionExpression, + @"hillshadeExaggeration should round-trip camera expressions."); + + + + layer.hillshadeExaggeration = nil; + XCTAssertTrue(rawLayer->getHillshadeExaggeration().isUndefined(), + @"Unsetting hillshadeExaggeration should return hillshade-exaggeration to the default value."); + XCTAssertEqualObjects(layer.hillshadeExaggeration, defaultExpression, + @"hillshadeExaggeration should return the default value after being unset."); + + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.hillshadeExaggeration = functionExpression, NSException, NSInvalidArgumentException, @"MGLHillshadeLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.hillshadeExaggeration = functionExpression, NSException, NSInvalidArgumentException, @"MGLHillshadeLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + // Transition property test + layer.hillshadeExaggerationTransition = transitionTest; + auto toptions = rawLayer->getHillshadeExaggerationTransition(); + XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay); + XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration); + + MGLTransition hillshadeExaggerationTransition = layer.hillshadeExaggerationTransition; + XCTAssertEqual(hillshadeExaggerationTransition.delay, transitionTest.delay); + XCTAssertEqual(hillshadeExaggerationTransition.duration, transitionTest.duration); + } + + // hillshade-highlight-color + { + XCTAssertTrue(rawLayer->getHillshadeHighlightColor().isUndefined(), + @"hillshade-highlight-color should be unset initially."); + NSExpression *defaultExpression = layer.hillshadeHighlightColor; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.hillshadeHighlightColor = constantExpression; + mbgl::style::PropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } }; + XCTAssertEqual(rawLayer->getHillshadeHighlightColor(), propertyValue, + @"Setting hillshadeHighlightColor to a constant value expression should update hillshade-highlight-color."); + XCTAssertEqualObjects(layer.hillshadeHighlightColor, constantExpression, + @"hillshadeHighlightColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.hillshadeHighlightColor = functionExpression; + + mbgl::style::IntervalStops<mbgl::Color> intervalStops = {{ + { -INFINITY, { 1, 0, 0, 1 } }, + { 18, { 1, 0, 0, 1 } }, + }}; + propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops }; + + XCTAssertEqual(rawLayer->getHillshadeHighlightColor(), propertyValue, + @"Setting hillshadeHighlightColor to a camera expression should update hillshade-highlight-color."); + XCTAssertEqualObjects(layer.hillshadeHighlightColor, functionExpression, + @"hillshadeHighlightColor should round-trip camera expressions."); + + + + layer.hillshadeHighlightColor = nil; + XCTAssertTrue(rawLayer->getHillshadeHighlightColor().isUndefined(), + @"Unsetting hillshadeHighlightColor should return hillshade-highlight-color to the default value."); + XCTAssertEqualObjects(layer.hillshadeHighlightColor, defaultExpression, + @"hillshadeHighlightColor should return the default value after being unset."); + + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.hillshadeHighlightColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHillshadeLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.hillshadeHighlightColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHillshadeLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + // Transition property test + layer.hillshadeHighlightColorTransition = transitionTest; + auto toptions = rawLayer->getHillshadeHighlightColorTransition(); + XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay); + XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration); + + MGLTransition hillshadeHighlightColorTransition = layer.hillshadeHighlightColorTransition; + XCTAssertEqual(hillshadeHighlightColorTransition.delay, transitionTest.delay); + XCTAssertEqual(hillshadeHighlightColorTransition.duration, transitionTest.duration); + } + + // hillshade-illumination-anchor + { + XCTAssertTrue(rawLayer->getHillshadeIlluminationAnchor().isUndefined(), + @"hillshade-illumination-anchor should be unset initially."); + NSExpression *defaultExpression = layer.hillshadeIlluminationAnchor; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + layer.hillshadeIlluminationAnchor = constantExpression; + mbgl::style::PropertyValue<mbgl::style::HillshadeIlluminationAnchorType> propertyValue = { mbgl::style::HillshadeIlluminationAnchorType::Viewport }; + XCTAssertEqual(rawLayer->getHillshadeIlluminationAnchor(), propertyValue, + @"Setting hillshadeIlluminationAnchor to a constant value expression should update hillshade-illumination-anchor."); + XCTAssertEqualObjects(layer.hillshadeIlluminationAnchor, constantExpression, + @"hillshadeIlluminationAnchor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.hillshadeIlluminationAnchor = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::HillshadeIlluminationAnchorType> intervalStops = {{ + { -INFINITY, mbgl::style::HillshadeIlluminationAnchorType::Viewport }, + { 18, mbgl::style::HillshadeIlluminationAnchorType::Viewport }, + }}; + propertyValue = mbgl::style::CameraFunction<mbgl::style::HillshadeIlluminationAnchorType> { intervalStops }; + + XCTAssertEqual(rawLayer->getHillshadeIlluminationAnchor(), propertyValue, + @"Setting hillshadeIlluminationAnchor to a camera expression should update hillshade-illumination-anchor."); + XCTAssertEqualObjects(layer.hillshadeIlluminationAnchor, functionExpression, + @"hillshadeIlluminationAnchor should round-trip camera expressions."); + + + + layer.hillshadeIlluminationAnchor = nil; + XCTAssertTrue(rawLayer->getHillshadeIlluminationAnchor().isUndefined(), + @"Unsetting hillshadeIlluminationAnchor should return hillshade-illumination-anchor to the default value."); + XCTAssertEqualObjects(layer.hillshadeIlluminationAnchor, defaultExpression, + @"hillshadeIlluminationAnchor should return the default value after being unset."); + + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.hillshadeIlluminationAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHillshadeLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.hillshadeIlluminationAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHillshadeLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + } + + // hillshade-illumination-direction + { + XCTAssertTrue(rawLayer->getHillshadeIlluminationDirection().isUndefined(), + @"hillshade-illumination-direction should be unset initially."); + NSExpression *defaultExpression = layer.hillshadeIlluminationDirection; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.hillshadeIlluminationDirection = constantExpression; + mbgl::style::PropertyValue<float> propertyValue = { 0xff }; + XCTAssertEqual(rawLayer->getHillshadeIlluminationDirection(), propertyValue, + @"Setting hillshadeIlluminationDirection to a constant value expression should update hillshade-illumination-direction."); + XCTAssertEqualObjects(layer.hillshadeIlluminationDirection, constantExpression, + @"hillshadeIlluminationDirection should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.hillshadeIlluminationDirection = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; + propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; + + XCTAssertEqual(rawLayer->getHillshadeIlluminationDirection(), propertyValue, + @"Setting hillshadeIlluminationDirection to a camera expression should update hillshade-illumination-direction."); + XCTAssertEqualObjects(layer.hillshadeIlluminationDirection, functionExpression, + @"hillshadeIlluminationDirection should round-trip camera expressions."); + + + + layer.hillshadeIlluminationDirection = nil; + XCTAssertTrue(rawLayer->getHillshadeIlluminationDirection().isUndefined(), + @"Unsetting hillshadeIlluminationDirection should return hillshade-illumination-direction to the default value."); + XCTAssertEqualObjects(layer.hillshadeIlluminationDirection, defaultExpression, + @"hillshadeIlluminationDirection should return the default value after being unset."); + + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.hillshadeIlluminationDirection = functionExpression, NSException, NSInvalidArgumentException, @"MGLHillshadeLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.hillshadeIlluminationDirection = functionExpression, NSException, NSInvalidArgumentException, @"MGLHillshadeLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + } + + // hillshade-shadow-color + { + XCTAssertTrue(rawLayer->getHillshadeShadowColor().isUndefined(), + @"hillshade-shadow-color should be unset initially."); + NSExpression *defaultExpression = layer.hillshadeShadowColor; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.hillshadeShadowColor = constantExpression; + mbgl::style::PropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } }; + XCTAssertEqual(rawLayer->getHillshadeShadowColor(), propertyValue, + @"Setting hillshadeShadowColor to a constant value expression should update hillshade-shadow-color."); + XCTAssertEqualObjects(layer.hillshadeShadowColor, constantExpression, + @"hillshadeShadowColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.hillshadeShadowColor = functionExpression; + + mbgl::style::IntervalStops<mbgl::Color> intervalStops = {{ + { -INFINITY, { 1, 0, 0, 1 } }, + { 18, { 1, 0, 0, 1 } }, + }}; + propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops }; + + XCTAssertEqual(rawLayer->getHillshadeShadowColor(), propertyValue, + @"Setting hillshadeShadowColor to a camera expression should update hillshade-shadow-color."); + XCTAssertEqualObjects(layer.hillshadeShadowColor, functionExpression, + @"hillshadeShadowColor should round-trip camera expressions."); + + + + layer.hillshadeShadowColor = nil; + XCTAssertTrue(rawLayer->getHillshadeShadowColor().isUndefined(), + @"Unsetting hillshadeShadowColor should return hillshade-shadow-color to the default value."); + XCTAssertEqualObjects(layer.hillshadeShadowColor, defaultExpression, + @"hillshadeShadowColor should return the default value after being unset."); + + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.hillshadeShadowColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHillshadeLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.hillshadeShadowColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHillshadeLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + // Transition property test + layer.hillshadeShadowColorTransition = transitionTest; + auto toptions = rawLayer->getHillshadeShadowColorTransition(); + XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay); + XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration); + + MGLTransition hillshadeShadowColorTransition = layer.hillshadeShadowColorTransition; + XCTAssertEqual(hillshadeShadowColorTransition.delay, transitionTest.delay); + XCTAssertEqual(hillshadeShadowColorTransition.duration, transitionTest.duration); + } +} + +- (void)testPropertyNames { + [self testPropertyName:@"hillshade-accent-color" isBoolean:NO]; + [self testPropertyName:@"hillshade-exaggeration" isBoolean:NO]; + [self testPropertyName:@"hillshade-highlight-color" isBoolean:NO]; + [self testPropertyName:@"hillshade-illumination-anchor" isBoolean:NO]; + [self testPropertyName:@"hillshade-illumination-direction" isBoolean:NO]; + [self testPropertyName:@"hillshade-shadow-color" isBoolean:NO]; +} + +- (void)testValueAdditions { + XCTAssertEqual([NSValue valueWithMGLHillshadeIlluminationAnchor:MGLHillshadeIlluminationAnchorMap].MGLHillshadeIlluminationAnchorValue, MGLHillshadeIlluminationAnchorMap); + XCTAssertEqual([NSValue valueWithMGLHillshadeIlluminationAnchor:MGLHillshadeIlluminationAnchorViewport].MGLHillshadeIlluminationAnchorValue, MGLHillshadeIlluminationAnchorViewport); +} + +@end diff --git a/platform/darwin/test/MGLLightTest.mm b/platform/darwin/test/MGLLightTest.mm index de64d57851..a51c59c725 100644 --- a/platform/darwin/test/MGLLightTest.mm +++ b/platform/darwin/test/MGLLightTest.mm @@ -27,29 +27,28 @@ { mbgl::style::Light light; MGLLight *mglLight = [[MGLLight alloc] initWithMBGLLight:&light]; - auto lightFromMGLlight = [mglLight mbglLight]; + auto lightFromMGLLight = mglLight.mbglLight; - XCTAssertEqual(light.getDefaultAnchor(), lightFromMGLlight.getAnchor()); - XCTAssert([mglLight.anchor isKindOfClass:[MGLConstantStyleValue class]], @"mglLight.anchor isn’t a MGLConstantStyleValue."); - NSValue *anchorValue = ((MGLConstantStyleValue *)mglLight.anchor).rawValue; - XCTAssertEqual(anchorValue.MGLLightAnchorValue, MGLLightAnchorViewport); + XCTAssertEqual(light.getDefaultAnchor(), lightFromMGLLight.getAnchor()); + XCTAssertEqual(mglLight.anchor.expressionType, NSConstantValueExpressionType, @"mglLight.anchor isn’t a constant value expression."); + XCTAssertEqualObjects(mglLight.anchor.constantValue, @"viewport"); mbgl::style::PropertyValue<mbgl::style::LightAnchorType> propertyValue = { mbgl::style::LightAnchorType::Viewport }; light.setAnchor(propertyValue); mglLight = [[MGLLight alloc] initWithMBGLLight:&light]; - lightFromMGLlight = [mglLight mbglLight]; + lightFromMGLLight = mglLight.mbglLight; - XCTAssertEqual(light.getAnchor(), lightFromMGLlight.getAnchor()); + XCTAssertEqual(light.getAnchor(), lightFromMGLLight.getAnchor()); } // position { mbgl::style::Light light; MGLLight *mglLight = [[MGLLight alloc] initWithMBGLLight:&light]; - auto lightFromMGLlight = [mglLight mbglLight]; + auto lightFromMGLLight = mglLight.mbglLight; - XCTAssertEqual(light.getDefaultPosition(), lightFromMGLlight.getPosition()); - auto positionTransition = lightFromMGLlight.getPositionTransition(); + XCTAssertEqual(light.getDefaultPosition(), lightFromMGLLight.getPosition()); + auto positionTransition = lightFromMGLLight.getPositionTransition(); XCTAssert(positionTransition.delay && MGLTimeIntervalFromDuration(*positionTransition.delay) == defaultTransition.delay); XCTAssert(positionTransition.duration && MGLTimeIntervalFromDuration(*positionTransition.duration) == defaultTransition.duration); @@ -60,10 +59,10 @@ light.setPositionTransition(transitionOptions); mglLight = [[MGLLight alloc] initWithMBGLLight:&light]; - lightFromMGLlight = [mglLight mbglLight]; + lightFromMGLLight = mglLight.mbglLight; - XCTAssertEqual(light.getPosition(), lightFromMGLlight.getPosition()); - positionTransition = lightFromMGLlight.getPositionTransition(); + XCTAssertEqual(light.getPosition(), lightFromMGLLight.getPosition()); + positionTransition = lightFromMGLLight.getPositionTransition(); XCTAssert(positionTransition.delay && MGLTimeIntervalFromDuration(*positionTransition.delay) == transition.delay); XCTAssert(positionTransition.duration && MGLTimeIntervalFromDuration(*positionTransition.duration) == transition.duration); @@ -73,10 +72,10 @@ { mbgl::style::Light light; MGLLight *mglLight = [[MGLLight alloc] initWithMBGLLight:&light]; - auto lightFromMGLlight = [mglLight mbglLight]; + auto lightFromMGLLight = mglLight.mbglLight; - XCTAssertEqual(light.getDefaultColor(), lightFromMGLlight.getColor()); - auto colorTransition = lightFromMGLlight.getColorTransition(); + XCTAssertEqual(light.getDefaultColor(), lightFromMGLLight.getColor()); + auto colorTransition = lightFromMGLLight.getColorTransition(); XCTAssert(colorTransition.delay && MGLTimeIntervalFromDuration(*colorTransition.delay) == defaultTransition.delay); XCTAssert(colorTransition.duration && MGLTimeIntervalFromDuration(*colorTransition.duration) == defaultTransition.duration); @@ -85,10 +84,10 @@ light.setColorTransition(transitionOptions); mglLight = [[MGLLight alloc] initWithMBGLLight:&light]; - lightFromMGLlight = [mglLight mbglLight]; + lightFromMGLLight = mglLight.mbglLight; - XCTAssertEqual(light.getColor(), lightFromMGLlight.getColor()); - colorTransition = lightFromMGLlight.getColorTransition(); + XCTAssertEqual(light.getColor(), lightFromMGLLight.getColor()); + colorTransition = lightFromMGLLight.getColorTransition(); XCTAssert(colorTransition.delay && MGLTimeIntervalFromDuration(*colorTransition.delay) == transition.delay); XCTAssert(colorTransition.duration && MGLTimeIntervalFromDuration(*colorTransition.duration) == transition.duration); @@ -98,10 +97,10 @@ { mbgl::style::Light light; MGLLight *mglLight = [[MGLLight alloc] initWithMBGLLight:&light]; - auto lightFromMGLlight = [mglLight mbglLight]; + auto lightFromMGLLight = mglLight.mbglLight; - XCTAssertEqual(light.getDefaultIntensity(), lightFromMGLlight.getIntensity()); - auto intensityTransition = lightFromMGLlight.getIntensityTransition(); + XCTAssertEqual(light.getDefaultIntensity(), lightFromMGLLight.getIntensity()); + auto intensityTransition = lightFromMGLLight.getIntensityTransition(); XCTAssert(intensityTransition.delay && MGLTimeIntervalFromDuration(*intensityTransition.delay) == defaultTransition.delay); XCTAssert(intensityTransition.duration && MGLTimeIntervalFromDuration(*intensityTransition.duration) == defaultTransition.duration); @@ -110,10 +109,10 @@ light.setIntensityTransition(transitionOptions); mglLight = [[MGLLight alloc] initWithMBGLLight:&light]; - lightFromMGLlight = [mglLight mbglLight]; + lightFromMGLLight = mglLight.mbglLight; - XCTAssertEqual(light.getIntensity(), lightFromMGLlight.getIntensity()); - intensityTransition = lightFromMGLlight.getIntensityTransition(); + XCTAssertEqual(light.getIntensity(), lightFromMGLLight.getIntensity()); + intensityTransition = lightFromMGLLight.getIntensityTransition(); XCTAssert(intensityTransition.delay && MGLTimeIntervalFromDuration(*intensityTransition.delay) == transition.delay); XCTAssert(intensityTransition.duration && MGLTimeIntervalFromDuration(*intensityTransition.duration) == transition.duration); diff --git a/platform/darwin/test/MGLLightTest.mm.ejs b/platform/darwin/test/MGLLightTest.mm.ejs index 5b1f27d8d1..35ff58b6d5 100644 --- a/platform/darwin/test/MGLLightTest.mm.ejs +++ b/platform/darwin/test/MGLLightTest.mm.ejs @@ -32,19 +32,18 @@ { mbgl::style::Light light; MGLLight *mglLight = [[MGLLight alloc] initWithMBGLLight:&light]; - auto lightFromMGLlight = [mglLight mbglLight]; + auto lightFromMGLLight = mglLight.mbglLight; - XCTAssertEqual(light.getDefault<%- camelize(property.name) -%>(), lightFromMGLlight.get<%- camelize(property.name) -%>()); + XCTAssertEqual(light.getDefault<%- camelize(property.name) -%>(), lightFromMGLLight.get<%- camelize(property.name) -%>()); <% if (property.transition) { -%> - auto <%- camelizeWithLeadingLowercase(property.name) -%>Transition = lightFromMGLlight.get<%- camelize(property.name) -%>Transition(); + auto <%- camelizeWithLeadingLowercase(property.name) -%>Transition = lightFromMGLLight.get<%- camelize(property.name) -%>Transition(); XCTAssert(<%- camelizeWithLeadingLowercase(property.name) -%>Transition.delay && MGLTimeIntervalFromDuration(*<%- camelizeWithLeadingLowercase(property.name) -%>Transition.delay) == defaultTransition.delay); XCTAssert(<%- camelizeWithLeadingLowercase(property.name) -%>Transition.duration && MGLTimeIntervalFromDuration(*<%- camelizeWithLeadingLowercase(property.name) -%>Transition.duration) == defaultTransition.duration); <% } -%> <% if (property.type == "enum" && property.default) { -%> - XCTAssert([mglLight.<%- camelizeWithLeadingLowercase(property.name) -%> isKindOfClass:[MGLConstantStyleValue class]], @"mglLight.<%- camelizeWithLeadingLowercase(property.name) -%> isn’t a MGLConstantStyleValue."); - NSValue *<%- camelizeWithLeadingLowercase(property.name) -%>Value = ((MGLConstantStyleValue *)mglLight.<%- camelizeWithLeadingLowercase(property.name) -%>).rawValue; - XCTAssertEqual(<%- camelizeWithLeadingLowercase(property.name) -%>Value.MGLLight<%- camelize(property.name) -%>Value, MGLLight<%- camelize(property.name) -%><%- camelize(property.default) -%>); + XCTAssertEqual(mglLight.<%- camelizeWithLeadingLowercase(property.name) -%>.expressionType, NSConstantValueExpressionType, @"mglLight.<%- camelizeWithLeadingLowercase(property.name) -%> isn’t a constant value expression."); + XCTAssertEqualObjects(mglLight.<%- camelizeWithLeadingLowercase(property.name) -%>.constantValue, @"<%- property.default -%>"); <% } -%> <% if (property.type == "array") { -%> @@ -60,11 +59,11 @@ <% } -%> mglLight = [[MGLLight alloc] initWithMBGLLight:&light]; - lightFromMGLlight = [mglLight mbglLight]; + lightFromMGLLight = mglLight.mbglLight; - XCTAssertEqual(light.get<%- camelize(property.name) -%>(), lightFromMGLlight.get<%- camelize(property.name) -%>()); + XCTAssertEqual(light.get<%- camelize(property.name) -%>(), lightFromMGLLight.get<%- camelize(property.name) -%>()); <% if (property.transition) { -%> - <%- camelizeWithLeadingLowercase(property.name) -%>Transition = lightFromMGLlight.get<%- camelize(property.name) -%>Transition(); + <%- camelizeWithLeadingLowercase(property.name) -%>Transition = lightFromMGLLight.get<%- camelize(property.name) -%>Transition(); XCTAssert(<%- camelizeWithLeadingLowercase(property.name) -%>Transition.delay && MGLTimeIntervalFromDuration(*<%- camelizeWithLeadingLowercase(property.name) -%>Transition.delay) == transition.delay); XCTAssert(<%- camelizeWithLeadingLowercase(property.name) -%>Transition.duration && MGLTimeIntervalFromDuration(*<%- camelizeWithLeadingLowercase(property.name) -%>Transition.duration) == transition.duration); diff --git a/platform/darwin/test/MGLLineStyleLayerTests.mm b/platform/darwin/test/MGLLineStyleLayerTests.mm index 7e7926e22e..5490278e98 100644 --- a/platform/darwin/test/MGLLineStyleLayerTests.mm +++ b/platform/darwin/test/MGLLineStyleLayerTests.mm @@ -30,8 +30,8 @@ XCTAssertNil(layer.sourceLayerIdentifier); XCTAssertNil(layer.predicate); - layer.predicate = [NSPredicate predicateWithValue:NO]; - XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithValue:NO]); + layer.predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]; + XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]); layer.predicate = nil; XCTAssertNil(layer.predicate); } @@ -52,72 +52,81 @@ { XCTAssertTrue(rawLayer->getLineCap().isUndefined(), @"line-cap should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.lineCap; + NSExpression *defaultExpression = layer.lineCap; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLLineCap:MGLLineCapSquare]]; - layer.lineCap = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'square'"]; + layer.lineCap = constantExpression; mbgl::style::PropertyValue<mbgl::style::LineCapType> propertyValue = { mbgl::style::LineCapType::Square }; XCTAssertEqual(rawLayer->getLineCap(), propertyValue, - @"Setting lineCap to a constant value should update line-cap."); - XCTAssertEqualObjects(layer.lineCap, constantStyleValue, - @"lineCap should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineCap = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::LineCapType> intervalStops = { {{18, mbgl::style::LineCapType::Square}} }; + @"Setting lineCap to a constant value expression should update line-cap."); + XCTAssertEqualObjects(layer.lineCap, constantExpression, + @"lineCap should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'square'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineCap = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::LineCapType> intervalStops = {{ + { -INFINITY, mbgl::style::LineCapType::Square }, + { 18, mbgl::style::LineCapType::Square }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::LineCapType> { intervalStops }; XCTAssertEqual(rawLayer->getLineCap(), propertyValue, - @"Setting lineCap to a camera function should update line-cap."); - XCTAssertEqualObjects(layer.lineCap, functionStyleValue, - @"lineCap should round-trip camera functions."); + @"Setting lineCap to a camera expression should update line-cap."); + XCTAssertEqualObjects(layer.lineCap, functionExpression, + @"lineCap should round-trip camera expressions."); layer.lineCap = nil; XCTAssertTrue(rawLayer->getLineCap().isUndefined(), @"Unsetting lineCap should return line-cap to the default value."); - XCTAssertEqualObjects(layer.lineCap, defaultStyleValue, + XCTAssertEqualObjects(layer.lineCap, defaultExpression, @"lineCap should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.lineCap = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.lineCap = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.lineCap = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.lineCap = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // line-join { XCTAssertTrue(rawLayer->getLineJoin().isUndefined(), @"line-join should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.lineJoin; + NSExpression *defaultExpression = layer.lineJoin; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLLineJoin:MGLLineJoinMiter]]; - layer.lineJoin = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'miter'"]; + layer.lineJoin = constantExpression; mbgl::style::DataDrivenPropertyValue<mbgl::style::LineJoinType> propertyValue = { mbgl::style::LineJoinType::Miter }; XCTAssertEqual(rawLayer->getLineJoin(), propertyValue, - @"Setting lineJoin to a constant value should update line-join."); - XCTAssertEqualObjects(layer.lineJoin, constantStyleValue, - @"lineJoin should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineJoin = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::LineJoinType> intervalStops = { {{18, mbgl::style::LineJoinType::Miter}} }; + @"Setting lineJoin to a constant value expression should update line-join."); + XCTAssertEqualObjects(layer.lineJoin, constantExpression, + @"lineJoin should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'miter'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineJoin = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::LineJoinType> intervalStops = {{ + { -INFINITY, mbgl::style::LineJoinType::Miter }, + { 18, mbgl::style::LineJoinType::Miter }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::LineJoinType> { intervalStops }; XCTAssertEqual(rawLayer->getLineJoin(), propertyValue, - @"Setting lineJoin to a camera function should update line-join."); - XCTAssertEqualObjects(layer.lineJoin, functionStyleValue, - @"lineJoin should round-trip camera functions."); + @"Setting lineJoin to a camera expression should update line-join."); + XCTAssertEqualObjects(layer.lineJoin, functionExpression, + @"lineJoin should round-trip camera expressions."); layer.lineJoin = nil; XCTAssertTrue(rawLayer->getLineJoin().isUndefined(), @"Unsetting lineJoin should return line-join to the default value."); - XCTAssertEqualObjects(layer.lineJoin, defaultStyleValue, + XCTAssertEqualObjects(layer.lineJoin, defaultExpression, @"lineJoin should return the default value after being unset."); } @@ -125,118 +134,133 @@ { XCTAssertTrue(rawLayer->getLineMiterLimit().isUndefined(), @"line-miter-limit should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.lineMiterLimit; + NSExpression *defaultExpression = layer.lineMiterLimit; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.lineMiterLimit = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.lineMiterLimit = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getLineMiterLimit(), propertyValue, - @"Setting lineMiterLimit to a constant value should update line-miter-limit."); - XCTAssertEqualObjects(layer.lineMiterLimit, constantStyleValue, - @"lineMiterLimit should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineMiterLimit = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting lineMiterLimit to a constant value expression should update line-miter-limit."); + XCTAssertEqualObjects(layer.lineMiterLimit, constantExpression, + @"lineMiterLimit should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineMiterLimit = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getLineMiterLimit(), propertyValue, - @"Setting lineMiterLimit to a camera function should update line-miter-limit."); - XCTAssertEqualObjects(layer.lineMiterLimit, functionStyleValue, - @"lineMiterLimit should round-trip camera functions."); + @"Setting lineMiterLimit to a camera expression should update line-miter-limit."); + XCTAssertEqualObjects(layer.lineMiterLimit, functionExpression, + @"lineMiterLimit should round-trip camera expressions."); layer.lineMiterLimit = nil; XCTAssertTrue(rawLayer->getLineMiterLimit().isUndefined(), @"Unsetting lineMiterLimit should return line-miter-limit to the default value."); - XCTAssertEqualObjects(layer.lineMiterLimit, defaultStyleValue, + XCTAssertEqualObjects(layer.lineMiterLimit, defaultExpression, @"lineMiterLimit should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.lineMiterLimit = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.lineMiterLimit = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.lineMiterLimit = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.lineMiterLimit = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // line-round-limit { XCTAssertTrue(rawLayer->getLineRoundLimit().isUndefined(), @"line-round-limit should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.lineRoundLimit; + NSExpression *defaultExpression = layer.lineRoundLimit; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.lineRoundLimit = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.lineRoundLimit = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getLineRoundLimit(), propertyValue, - @"Setting lineRoundLimit to a constant value should update line-round-limit."); - XCTAssertEqualObjects(layer.lineRoundLimit, constantStyleValue, - @"lineRoundLimit should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineRoundLimit = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting lineRoundLimit to a constant value expression should update line-round-limit."); + XCTAssertEqualObjects(layer.lineRoundLimit, constantExpression, + @"lineRoundLimit should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineRoundLimit = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getLineRoundLimit(), propertyValue, - @"Setting lineRoundLimit to a camera function should update line-round-limit."); - XCTAssertEqualObjects(layer.lineRoundLimit, functionStyleValue, - @"lineRoundLimit should round-trip camera functions."); + @"Setting lineRoundLimit to a camera expression should update line-round-limit."); + XCTAssertEqualObjects(layer.lineRoundLimit, functionExpression, + @"lineRoundLimit should round-trip camera expressions."); layer.lineRoundLimit = nil; XCTAssertTrue(rawLayer->getLineRoundLimit().isUndefined(), @"Unsetting lineRoundLimit should return line-round-limit to the default value."); - XCTAssertEqualObjects(layer.lineRoundLimit, defaultStyleValue, + XCTAssertEqualObjects(layer.lineRoundLimit, defaultExpression, @"lineRoundLimit should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.lineRoundLimit = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.lineRoundLimit = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.lineRoundLimit = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.lineRoundLimit = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // line-blur { XCTAssertTrue(rawLayer->getLineBlur().isUndefined(), @"line-blur should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.lineBlur; + NSExpression *defaultExpression = layer.lineBlur; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.lineBlur = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.lineBlur = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getLineBlur(), propertyValue, - @"Setting lineBlur to a constant value should update line-blur."); - XCTAssertEqualObjects(layer.lineBlur, constantStyleValue, - @"lineBlur should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineBlur = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting lineBlur to a constant value expression should update line-blur."); + XCTAssertEqualObjects(layer.lineBlur, constantExpression, + @"lineBlur should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineBlur = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getLineBlur(), propertyValue, - @"Setting lineBlur to a camera function should update line-blur."); - XCTAssertEqualObjects(layer.lineBlur, functionStyleValue, - @"lineBlur should round-trip camera functions."); + @"Setting lineBlur to a camera expression should update line-blur."); + XCTAssertEqualObjects(layer.lineBlur, functionExpression, + @"lineBlur should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.lineBlur = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.lineBlur = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getLineBlur(), propertyValue, - @"Setting lineBlur to a source function should update line-blur."); - XCTAssertEqualObjects(layer.lineBlur, functionStyleValue, - @"lineBlur should round-trip source functions."); + @"Setting lineBlur to a data expression should update line-blur."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.lineBlur, pedanticFunctionExpression, + @"lineBlur should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.lineBlur = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.lineBlur = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -244,15 +268,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getLineBlur(), propertyValue, - @"Setting lineBlur to a composite function should update line-blur."); - XCTAssertEqualObjects(layer.lineBlur, functionStyleValue, - @"lineBlur should round-trip composite functions."); + @"Setting lineBlur to a camera-data expression should update line-blur."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.lineBlur, pedanticFunctionExpression, + @"lineBlur should round-trip camera-data expressions."); layer.lineBlur = nil; XCTAssertTrue(rawLayer->getLineBlur().isUndefined(), @"Unsetting lineBlur should return line-blur to the default value."); - XCTAssertEqualObjects(layer.lineBlur, defaultStyleValue, + XCTAssertEqualObjects(layer.lineBlur, defaultExpression, @"lineBlur should return the default value after being unset."); // Transition property test layer.lineBlurTransition = transitionTest; @@ -269,40 +294,45 @@ { XCTAssertTrue(rawLayer->getLineColor().isUndefined(), @"line-color should be unset initially."); - MGLStyleValue<MGLColor *> *defaultStyleValue = layer.lineColor; + NSExpression *defaultExpression = layer.lineColor; - MGLStyleValue<MGLColor *> *constantStyleValue = [MGLStyleValue<MGLColor *> valueWithRawValue:[MGLColor redColor]]; - layer.lineColor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.lineColor = constantExpression; mbgl::style::DataDrivenPropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } }; XCTAssertEqual(rawLayer->getLineColor(), propertyValue, - @"Setting lineColor to a constant value should update line-color."); - XCTAssertEqualObjects(layer.lineColor, constantStyleValue, - @"lineColor should round-trip constant values."); - - MGLStyleValue<MGLColor *> * functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineColor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::Color> intervalStops = { {{18, { 1, 0, 0, 1 }}} }; + @"Setting lineColor to a constant value expression should update line-color."); + XCTAssertEqualObjects(layer.lineColor, constantExpression, + @"lineColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineColor = functionExpression; + + mbgl::style::IntervalStops<mbgl::Color> intervalStops = {{ + { -INFINITY, { 1, 0, 0, 1 } }, + { 18, { 1, 0, 0, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops }; XCTAssertEqual(rawLayer->getLineColor(), propertyValue, - @"Setting lineColor to a camera function should update line-color."); - XCTAssertEqualObjects(layer.lineColor, functionStyleValue, - @"lineColor should round-trip camera functions."); + @"Setting lineColor to a camera expression should update line-color."); + XCTAssertEqualObjects(layer.lineColor, functionExpression, + @"lineColor should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.lineColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.lineColor = functionExpression; mbgl::style::ExponentialStops<mbgl::Color> exponentialStops = { {{18, { 1, 0, 0, 1 }}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<mbgl::Color> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getLineColor(), propertyValue, - @"Setting lineColor to a source function should update line-color."); - XCTAssertEqualObjects(layer.lineColor, functionStyleValue, - @"lineColor should round-trip source functions."); + @"Setting lineColor to a data expression should update line-color."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.lineColor, pedanticFunctionExpression, + @"lineColor should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.lineColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.lineColor = functionExpression; std::map<float, mbgl::Color> innerStops { {18, { 1, 0, 0, 1 }} }; mbgl::style::CompositeExponentialStops<mbgl::Color> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -310,15 +340,16 @@ propertyValue = mbgl::style::CompositeFunction<mbgl::Color> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getLineColor(), propertyValue, - @"Setting lineColor to a composite function should update line-color."); - XCTAssertEqualObjects(layer.lineColor, functionStyleValue, - @"lineColor should round-trip composite functions."); + @"Setting lineColor to a camera-data expression should update line-color."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.lineColor, pedanticFunctionExpression, + @"lineColor should round-trip camera-data expressions."); layer.lineColor = nil; XCTAssertTrue(rawLayer->getLineColor().isUndefined(), @"Unsetting lineColor should return line-color to the default value."); - XCTAssertEqualObjects(layer.lineColor, defaultStyleValue, + XCTAssertEqualObjects(layer.lineColor, defaultExpression, @"lineColor should return the default value after being unset."); // Transition property test layer.lineColorTransition = transitionTest; @@ -335,79 +366,89 @@ { XCTAssertTrue(rawLayer->getLineDasharray().isUndefined(), @"line-dasharray should be unset initially."); - MGLStyleValue<NSArray<NSNumber *> *> *defaultStyleValue = layer.lineDashPattern; + NSExpression *defaultExpression = layer.lineDashPattern; - MGLStyleValue<NSArray<NSNumber *> *> *constantStyleValue = [MGLStyleValue<NSArray<NSNumber *> *> valueWithRawValue:@[@1, @2]]; - layer.lineDashPattern = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"{1, 2}"]; + layer.lineDashPattern = constantExpression; mbgl::style::PropertyValue<std::vector<float>> propertyValue = { {1, 2} }; XCTAssertEqual(rawLayer->getLineDasharray(), propertyValue, - @"Setting lineDashPattern to a constant value should update line-dasharray."); - XCTAssertEqualObjects(layer.lineDashPattern, constantStyleValue, - @"lineDashPattern should round-trip constant values."); - - MGLStyleValue<NSArray<NSNumber *> *> * functionStyleValue = [MGLStyleValue<NSArray<NSNumber *> *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineDashPattern = functionStyleValue; - - mbgl::style::IntervalStops<std::vector<float>> intervalStops = { {{18, {1, 2}}} }; + @"Setting lineDashPattern to a constant value expression should update line-dasharray."); + XCTAssertEqualObjects(layer.lineDashPattern, constantExpression, + @"lineDashPattern should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"{1, 2}"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineDashPattern = functionExpression; + + mbgl::style::IntervalStops<std::vector<float>> intervalStops = {{ + { -INFINITY, {1, 2} }, + { 18, {1, 2} }, + }}; propertyValue = mbgl::style::CameraFunction<std::vector<float>> { intervalStops }; XCTAssertEqual(rawLayer->getLineDasharray(), propertyValue, - @"Setting lineDashPattern to a camera function should update line-dasharray."); - XCTAssertEqualObjects(layer.lineDashPattern, functionStyleValue, - @"lineDashPattern should round-trip camera functions."); + @"Setting lineDashPattern to a camera expression should update line-dasharray."); + XCTAssertEqualObjects(layer.lineDashPattern, functionExpression, + @"lineDashPattern should round-trip camera expressions."); layer.lineDashPattern = nil; XCTAssertTrue(rawLayer->getLineDasharray().isUndefined(), @"Unsetting lineDashPattern should return line-dasharray to the default value."); - XCTAssertEqualObjects(layer.lineDashPattern, defaultStyleValue, + XCTAssertEqualObjects(layer.lineDashPattern, defaultExpression, @"lineDashPattern should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSArray<NSNumber *> *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.lineDashPattern = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSArray<NSNumber *> *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.lineDashPattern = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.lineDashPattern = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.lineDashPattern = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // line-gap-width { XCTAssertTrue(rawLayer->getLineGapWidth().isUndefined(), @"line-gap-width should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.lineGapWidth; + NSExpression *defaultExpression = layer.lineGapWidth; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.lineGapWidth = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.lineGapWidth = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getLineGapWidth(), propertyValue, - @"Setting lineGapWidth to a constant value should update line-gap-width."); - XCTAssertEqualObjects(layer.lineGapWidth, constantStyleValue, - @"lineGapWidth should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineGapWidth = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting lineGapWidth to a constant value expression should update line-gap-width."); + XCTAssertEqualObjects(layer.lineGapWidth, constantExpression, + @"lineGapWidth should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineGapWidth = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getLineGapWidth(), propertyValue, - @"Setting lineGapWidth to a camera function should update line-gap-width."); - XCTAssertEqualObjects(layer.lineGapWidth, functionStyleValue, - @"lineGapWidth should round-trip camera functions."); + @"Setting lineGapWidth to a camera expression should update line-gap-width."); + XCTAssertEqualObjects(layer.lineGapWidth, functionExpression, + @"lineGapWidth should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.lineGapWidth = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.lineGapWidth = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getLineGapWidth(), propertyValue, - @"Setting lineGapWidth to a source function should update line-gap-width."); - XCTAssertEqualObjects(layer.lineGapWidth, functionStyleValue, - @"lineGapWidth should round-trip source functions."); + @"Setting lineGapWidth to a data expression should update line-gap-width."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.lineGapWidth, pedanticFunctionExpression, + @"lineGapWidth should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.lineGapWidth = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.lineGapWidth = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -415,15 +456,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getLineGapWidth(), propertyValue, - @"Setting lineGapWidth to a composite function should update line-gap-width."); - XCTAssertEqualObjects(layer.lineGapWidth, functionStyleValue, - @"lineGapWidth should round-trip composite functions."); + @"Setting lineGapWidth to a camera-data expression should update line-gap-width."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.lineGapWidth, pedanticFunctionExpression, + @"lineGapWidth should round-trip camera-data expressions."); layer.lineGapWidth = nil; XCTAssertTrue(rawLayer->getLineGapWidth().isUndefined(), @"Unsetting lineGapWidth should return line-gap-width to the default value."); - XCTAssertEqualObjects(layer.lineGapWidth, defaultStyleValue, + XCTAssertEqualObjects(layer.lineGapWidth, defaultExpression, @"lineGapWidth should return the default value after being unset."); // Transition property test layer.lineGapWidthTransition = transitionTest; @@ -440,40 +482,45 @@ { XCTAssertTrue(rawLayer->getLineOffset().isUndefined(), @"line-offset should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.lineOffset; + NSExpression *defaultExpression = layer.lineOffset; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.lineOffset = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.lineOffset = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getLineOffset(), propertyValue, - @"Setting lineOffset to a constant value should update line-offset."); - XCTAssertEqualObjects(layer.lineOffset, constantStyleValue, - @"lineOffset should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineOffset = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting lineOffset to a constant value expression should update line-offset."); + XCTAssertEqualObjects(layer.lineOffset, constantExpression, + @"lineOffset should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineOffset = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getLineOffset(), propertyValue, - @"Setting lineOffset to a camera function should update line-offset."); - XCTAssertEqualObjects(layer.lineOffset, functionStyleValue, - @"lineOffset should round-trip camera functions."); + @"Setting lineOffset to a camera expression should update line-offset."); + XCTAssertEqualObjects(layer.lineOffset, functionExpression, + @"lineOffset should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.lineOffset = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.lineOffset = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getLineOffset(), propertyValue, - @"Setting lineOffset to a source function should update line-offset."); - XCTAssertEqualObjects(layer.lineOffset, functionStyleValue, - @"lineOffset should round-trip source functions."); + @"Setting lineOffset to a data expression should update line-offset."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.lineOffset, pedanticFunctionExpression, + @"lineOffset should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.lineOffset = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.lineOffset = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -481,15 +528,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getLineOffset(), propertyValue, - @"Setting lineOffset to a composite function should update line-offset."); - XCTAssertEqualObjects(layer.lineOffset, functionStyleValue, - @"lineOffset should round-trip composite functions."); + @"Setting lineOffset to a camera-data expression should update line-offset."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.lineOffset, pedanticFunctionExpression, + @"lineOffset should round-trip camera-data expressions."); layer.lineOffset = nil; XCTAssertTrue(rawLayer->getLineOffset().isUndefined(), @"Unsetting lineOffset should return line-offset to the default value."); - XCTAssertEqualObjects(layer.lineOffset, defaultStyleValue, + XCTAssertEqualObjects(layer.lineOffset, defaultExpression, @"lineOffset should return the default value after being unset."); // Transition property test layer.lineOffsetTransition = transitionTest; @@ -506,40 +554,45 @@ { XCTAssertTrue(rawLayer->getLineOpacity().isUndefined(), @"line-opacity should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.lineOpacity; + NSExpression *defaultExpression = layer.lineOpacity; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.lineOpacity = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.lineOpacity = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getLineOpacity(), propertyValue, - @"Setting lineOpacity to a constant value should update line-opacity."); - XCTAssertEqualObjects(layer.lineOpacity, constantStyleValue, - @"lineOpacity should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineOpacity = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting lineOpacity to a constant value expression should update line-opacity."); + XCTAssertEqualObjects(layer.lineOpacity, constantExpression, + @"lineOpacity should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineOpacity = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getLineOpacity(), propertyValue, - @"Setting lineOpacity to a camera function should update line-opacity."); - XCTAssertEqualObjects(layer.lineOpacity, functionStyleValue, - @"lineOpacity should round-trip camera functions."); + @"Setting lineOpacity to a camera expression should update line-opacity."); + XCTAssertEqualObjects(layer.lineOpacity, functionExpression, + @"lineOpacity should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.lineOpacity = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.lineOpacity = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getLineOpacity(), propertyValue, - @"Setting lineOpacity to a source function should update line-opacity."); - XCTAssertEqualObjects(layer.lineOpacity, functionStyleValue, - @"lineOpacity should round-trip source functions."); + @"Setting lineOpacity to a data expression should update line-opacity."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.lineOpacity, pedanticFunctionExpression, + @"lineOpacity should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.lineOpacity = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.lineOpacity = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -547,15 +600,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getLineOpacity(), propertyValue, - @"Setting lineOpacity to a composite function should update line-opacity."); - XCTAssertEqualObjects(layer.lineOpacity, functionStyleValue, - @"lineOpacity should round-trip composite functions."); + @"Setting lineOpacity to a camera-data expression should update line-opacity."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.lineOpacity, pedanticFunctionExpression, + @"lineOpacity should round-trip camera-data expressions."); layer.lineOpacity = nil; XCTAssertTrue(rawLayer->getLineOpacity().isUndefined(), @"Unsetting lineOpacity should return line-opacity to the default value."); - XCTAssertEqualObjects(layer.lineOpacity, defaultStyleValue, + XCTAssertEqualObjects(layer.lineOpacity, defaultExpression, @"lineOpacity should return the default value after being unset."); // Transition property test layer.lineOpacityTransition = transitionTest; @@ -572,39 +626,44 @@ { XCTAssertTrue(rawLayer->getLinePattern().isUndefined(), @"line-pattern should be unset initially."); - MGLStyleValue<NSString *> *defaultStyleValue = layer.linePattern; + NSExpression *defaultExpression = layer.linePattern; - MGLStyleValue<NSString *> *constantStyleValue = [MGLStyleValue<NSString *> valueWithRawValue:@"Line Pattern"]; - layer.linePattern = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'Line Pattern'"]; + layer.linePattern = constantExpression; mbgl::style::PropertyValue<std::string> propertyValue = { "Line Pattern" }; XCTAssertEqual(rawLayer->getLinePattern(), propertyValue, - @"Setting linePattern to a constant value should update line-pattern."); - XCTAssertEqualObjects(layer.linePattern, constantStyleValue, - @"linePattern should round-trip constant values."); - - MGLStyleValue<NSString *> * functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.linePattern = functionStyleValue; - - mbgl::style::IntervalStops<std::string> intervalStops = { {{18, "Line Pattern"}} }; + @"Setting linePattern to a constant value expression should update line-pattern."); + XCTAssertEqualObjects(layer.linePattern, constantExpression, + @"linePattern should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'Line Pattern'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.linePattern = functionExpression; + + mbgl::style::IntervalStops<std::string> intervalStops = {{ + { -INFINITY, "Line Pattern" }, + { 18, "Line Pattern" }, + }}; propertyValue = mbgl::style::CameraFunction<std::string> { intervalStops }; XCTAssertEqual(rawLayer->getLinePattern(), propertyValue, - @"Setting linePattern to a camera function should update line-pattern."); - XCTAssertEqualObjects(layer.linePattern, functionStyleValue, - @"linePattern should round-trip camera functions."); + @"Setting linePattern to a camera expression should update line-pattern."); + XCTAssertEqualObjects(layer.linePattern, functionExpression, + @"linePattern should round-trip camera expressions."); layer.linePattern = nil; XCTAssertTrue(rawLayer->getLinePattern().isUndefined(), @"Unsetting linePattern should return line-pattern to the default value."); - XCTAssertEqualObjects(layer.linePattern, defaultStyleValue, + XCTAssertEqualObjects(layer.linePattern, defaultExpression, @"linePattern should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.linePattern = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.linePattern = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.linePattern = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.linePattern = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.linePatternTransition = transitionTest; auto toptions = rawLayer->getLinePatternTransition(); @@ -620,124 +679,139 @@ { XCTAssertTrue(rawLayer->getLineTranslate().isUndefined(), @"line-translate should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.lineTranslation; + NSExpression *defaultExpression = layer.lineTranslation; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue: + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", #if TARGET_OS_IPHONE [NSValue valueWithCGVector:CGVectorMake(1, 1)] #else [NSValue valueWithMGLVector:CGVectorMake(1, -1)] #endif ]; - layer.lineTranslation = constantStyleValue; + layer.lineTranslation = constantExpression; mbgl::style::PropertyValue<std::array<float, 2>> propertyValue = { { 1, 1 } }; XCTAssertEqual(rawLayer->getLineTranslate(), propertyValue, - @"Setting lineTranslation to a constant value should update line-translate."); - XCTAssertEqualObjects(layer.lineTranslation, constantStyleValue, - @"lineTranslation should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineTranslation = functionStyleValue; - - mbgl::style::IntervalStops<std::array<float, 2>> intervalStops = { {{18, { 1, 1 }}} }; + @"Setting lineTranslation to a constant value expression should update line-translate."); + XCTAssertEqualObjects(layer.lineTranslation, constantExpression, + @"lineTranslation should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"{1, 1}"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineTranslation = functionExpression; + + mbgl::style::IntervalStops<std::array<float, 2>> intervalStops = {{ + { -INFINITY, { 1, 1 } }, + { 18, { 1, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<std::array<float, 2>> { intervalStops }; XCTAssertEqual(rawLayer->getLineTranslate(), propertyValue, - @"Setting lineTranslation to a camera function should update line-translate."); - XCTAssertEqualObjects(layer.lineTranslation, functionStyleValue, - @"lineTranslation should round-trip camera functions."); + @"Setting lineTranslation to a camera expression should update line-translate."); + XCTAssertEqualObjects(layer.lineTranslation, functionExpression, + @"lineTranslation should round-trip camera expressions."); layer.lineTranslation = nil; XCTAssertTrue(rawLayer->getLineTranslate().isUndefined(), @"Unsetting lineTranslation should return line-translate to the default value."); - XCTAssertEqualObjects(layer.lineTranslation, defaultStyleValue, + XCTAssertEqualObjects(layer.lineTranslation, defaultExpression, @"lineTranslation should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.lineTranslation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.lineTranslation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.lineTranslation = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.lineTranslation = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // line-translate-anchor { XCTAssertTrue(rawLayer->getLineTranslateAnchor().isUndefined(), @"line-translate-anchor should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.lineTranslationAnchor; + NSExpression *defaultExpression = layer.lineTranslationAnchor; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLLineTranslationAnchor:MGLLineTranslationAnchorViewport]]; - layer.lineTranslationAnchor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + layer.lineTranslationAnchor = constantExpression; mbgl::style::PropertyValue<mbgl::style::TranslateAnchorType> propertyValue = { mbgl::style::TranslateAnchorType::Viewport }; XCTAssertEqual(rawLayer->getLineTranslateAnchor(), propertyValue, - @"Setting lineTranslationAnchor to a constant value should update line-translate-anchor."); - XCTAssertEqualObjects(layer.lineTranslationAnchor, constantStyleValue, - @"lineTranslationAnchor should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineTranslationAnchor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::TranslateAnchorType> intervalStops = { {{18, mbgl::style::TranslateAnchorType::Viewport}} }; + @"Setting lineTranslationAnchor to a constant value expression should update line-translate-anchor."); + XCTAssertEqualObjects(layer.lineTranslationAnchor, constantExpression, + @"lineTranslationAnchor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineTranslationAnchor = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::TranslateAnchorType> intervalStops = {{ + { -INFINITY, mbgl::style::TranslateAnchorType::Viewport }, + { 18, mbgl::style::TranslateAnchorType::Viewport }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::TranslateAnchorType> { intervalStops }; XCTAssertEqual(rawLayer->getLineTranslateAnchor(), propertyValue, - @"Setting lineTranslationAnchor to a camera function should update line-translate-anchor."); - XCTAssertEqualObjects(layer.lineTranslationAnchor, functionStyleValue, - @"lineTranslationAnchor should round-trip camera functions."); + @"Setting lineTranslationAnchor to a camera expression should update line-translate-anchor."); + XCTAssertEqualObjects(layer.lineTranslationAnchor, functionExpression, + @"lineTranslationAnchor should round-trip camera expressions."); layer.lineTranslationAnchor = nil; XCTAssertTrue(rawLayer->getLineTranslateAnchor().isUndefined(), @"Unsetting lineTranslationAnchor should return line-translate-anchor to the default value."); - XCTAssertEqualObjects(layer.lineTranslationAnchor, defaultStyleValue, + XCTAssertEqualObjects(layer.lineTranslationAnchor, defaultExpression, @"lineTranslationAnchor should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.lineTranslationAnchor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.lineTranslationAnchor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.lineTranslationAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.lineTranslationAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLLineLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // line-width { XCTAssertTrue(rawLayer->getLineWidth().isUndefined(), @"line-width should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.lineWidth; + NSExpression *defaultExpression = layer.lineWidth; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.lineWidth = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.lineWidth = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getLineWidth(), propertyValue, - @"Setting lineWidth to a constant value should update line-width."); - XCTAssertEqualObjects(layer.lineWidth, constantStyleValue, - @"lineWidth should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.lineWidth = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting lineWidth to a constant value expression should update line-width."); + XCTAssertEqualObjects(layer.lineWidth, constantExpression, + @"lineWidth should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.lineWidth = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getLineWidth(), propertyValue, - @"Setting lineWidth to a camera function should update line-width."); - XCTAssertEqualObjects(layer.lineWidth, functionStyleValue, - @"lineWidth should round-trip camera functions."); + @"Setting lineWidth to a camera expression should update line-width."); + XCTAssertEqualObjects(layer.lineWidth, functionExpression, + @"lineWidth should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.lineWidth = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.lineWidth = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getLineWidth(), propertyValue, - @"Setting lineWidth to a source function should update line-width."); - XCTAssertEqualObjects(layer.lineWidth, functionStyleValue, - @"lineWidth should round-trip source functions."); + @"Setting lineWidth to a data expression should update line-width."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.lineWidth, pedanticFunctionExpression, + @"lineWidth should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.lineWidth = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.lineWidth = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -745,15 +819,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getLineWidth(), propertyValue, - @"Setting lineWidth to a composite function should update line-width."); - XCTAssertEqualObjects(layer.lineWidth, functionStyleValue, - @"lineWidth should round-trip composite functions."); + @"Setting lineWidth to a camera-data expression should update line-width."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.lineWidth, pedanticFunctionExpression, + @"lineWidth should round-trip camera-data expressions."); layer.lineWidth = nil; XCTAssertTrue(rawLayer->getLineWidth().isUndefined(), @"Unsetting lineWidth should return line-width to the default value."); - XCTAssertEqualObjects(layer.lineWidth, defaultStyleValue, + XCTAssertEqualObjects(layer.lineWidth, defaultExpression, @"lineWidth should return the default value after being unset."); // Transition property test layer.lineWidthTransition = transitionTest; diff --git a/platform/darwin/test/MGLNSStringAdditionsTests.m b/platform/darwin/test/MGLNSStringAdditionsTests.m index 03503b7f8a..a3ee7e3433 100644 --- a/platform/darwin/test/MGLNSStringAdditionsTests.m +++ b/platform/darwin/test/MGLNSStringAdditionsTests.m @@ -39,4 +39,30 @@ XCTAssertEqualObjects([@"Improve This iPhone" mgl_titleCasedStringWithLocale:locale], @"Improve This iPhone"); } +- (void)testTransliteratedString { + XCTAssertEqualObjects([@"Portland" mgl_stringByTransliteratingIntoScript:@"Latn"], @"Portland"); + XCTAssertEqualObjects([@"Portland" mgl_stringByTransliteratingIntoScript:@"Hans"], @"Portland"); + XCTAssertEqualObjects([@"Portland" mgl_stringByTransliteratingIntoScript:@"Cyrl"], @"Портланд"); + XCTAssertEqualObjects([@"Portland" mgl_stringByTransliteratingIntoScript:@"Arab"], @"پُرتلَند"); + XCTAssertEqualObjects([@"Portland" mgl_stringByTransliteratingIntoScript:@"Fake"], @"Portland"); + + XCTAssertEqualObjects([@"北京" mgl_stringByTransliteratingIntoScript:@"Latn"], @"běi jīng"); + XCTAssertEqualObjects([@"北京" mgl_stringByTransliteratingIntoScript:@"Hans"], @"北京"); + XCTAssertEqualObjects([@"北京" mgl_stringByTransliteratingIntoScript:@"Cyrl"], @"бе̌и йӣнг"); + XCTAssertEqualObjects([@"北京" mgl_stringByTransliteratingIntoScript:@"Arab"], @"بِِ̌ جِينگ"); + XCTAssertEqualObjects([@"北京" mgl_stringByTransliteratingIntoScript:@"Fake"], @"北京"); + + XCTAssertEqualObjects([@"Mосква" mgl_stringByTransliteratingIntoScript:@"Latn"], @"Moskva"); + XCTAssertEqualObjects([@"Mосква" mgl_stringByTransliteratingIntoScript:@"Hans"], @"Mосква"); + XCTAssertEqualObjects([@"Mосква" mgl_stringByTransliteratingIntoScript:@"Cyrl"], @"Москва"); + XCTAssertEqualObjects([@"Mосква" mgl_stringByTransliteratingIntoScript:@"Arab"], @"مُسكڤَ"); + XCTAssertEqualObjects([@"Mосква" mgl_stringByTransliteratingIntoScript:@"Fake"], @"Mосква"); + + XCTAssertEqualObjects([@"ロンドン" mgl_stringByTransliteratingIntoScript:@"Latn"], @"rondon"); + XCTAssertEqualObjects([@"ロンドン" mgl_stringByTransliteratingIntoScript:@"Hans"], @"ロンドン"); + XCTAssertEqualObjects([@"ロンドン" mgl_stringByTransliteratingIntoScript:@"Cyrl"], @"рондон"); + XCTAssertEqualObjects([@"ロンドン" mgl_stringByTransliteratingIntoScript:@"Arab"], @"رُندُن"); + XCTAssertEqualObjects([@"ロンドン" mgl_stringByTransliteratingIntoScript:@"Fake"], @"ロンドン"); +} + @end diff --git a/platform/darwin/test/MGLPredicateTests.mm b/platform/darwin/test/MGLPredicateTests.mm index 6e6951dcdd..ab4a7e2d88 100644 --- a/platform/darwin/test/MGLPredicateTests.mm +++ b/platform/darwin/test/MGLPredicateTests.mm @@ -23,295 +23,9 @@ namespace mbgl { @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:@"%K = 'Point'", @"$type"].mgl_filter; - mbgl::style::TypeEqualsFilter expected = { .value = mbgl::FeatureType::Point }; - MGLAssertEqualFilters(actual, expected); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"%K = 67086180", @"$id"].mgl_filter; - mbgl::style::IdentifierEqualsFilter expected = { .value = UINT64_C(67086180) }; - MGLAssertEqualFilters(actual, expected); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"%K = nil", @"$id"].mgl_filter; - mbgl::style::NotHasIdentifierFilter expected; - MGLAssertEqualFilters(actual, expected); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"a = nil"].mgl_filter; - mbgl::style::NotHasFilter expected = { .key = "a" }; - MGLAssertEqualFilters(actual, expected); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"%K != 'Point'", @"$type"].mgl_filter; - mbgl::style::TypeNotEqualsFilter expected = { .value = mbgl::FeatureType::Point }; - MGLAssertEqualFilters(actual, expected); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"%K != 67086180", @"$id"].mgl_filter; - mbgl::style::IdentifierNotEqualsFilter expected = { .value = UINT64_C(67086180) }; - MGLAssertEqualFilters(actual, expected); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"%K != nil", @"$id"].mgl_filter; - mbgl::style::HasIdentifierFilter expected; - 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); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"%K IN {'LineString', 'Polygon'}", @"$type"].mgl_filter; - mbgl::style::TypeInFilter expected = { .values = { mbgl::FeatureType::LineString, mbgl::FeatureType::Polygon } }; - MGLAssertEqualFilters(actual, expected); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"%K IN %@", @"$type", @[@"LineString", @"Polygon"]].mgl_filter; - mbgl::style::TypeInFilter expected = { .values = { mbgl::FeatureType::LineString, mbgl::FeatureType::Polygon } }; - MGLAssertEqualFilters(actual, expected); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"%K IN {67086180, 3709678893, 3352016856, 4189833989}", @"$id"].mgl_filter; - mbgl::style::IdentifierInFilter expected = { .values = { UINT64_C(67086180), UINT64_C(3709678893), UINT64_C(3352016856), UINT64_C(4189833989) } }; - MGLAssertEqualFilters(actual, expected); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"%K IN %@", @"$id", @[@67086180, @3709678893, @3352016856, @4189833989]].mgl_filter; - mbgl::style::IdentifierInFilter expected = { .values = { UINT64_C(67086180), UINT64_C(3709678893), UINT64_C(3352016856), UINT64_C(4189833989) } }; - 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); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"%@ CONTAINS %K", @[@"LineString", @"Polygon"], @"$type"].mgl_filter; - mbgl::style::TypeInFilter expected = { .values = { mbgl::FeatureType::LineString, mbgl::FeatureType::Polygon } }; - MGLAssertEqualFilters(actual, expected); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"{67086180, 3709678893, 3352016856, 4189833989} CONTAINS %K", @"$id"].mgl_filter; - mbgl::style::IdentifierInFilter expected = { .values = { UINT64_C(67086180), UINT64_C(3709678893), UINT64_C(3352016856), UINT64_C(4189833989) } }; - MGLAssertEqualFilters(actual, expected); - } - - { - auto actual = [NSPredicate predicateWithFormat:@"%@ CONTAINS %K", @[@67086180, @3709678893, @3352016856, @4189833989], @"$id"].mgl_filter; - mbgl::style::IdentifierInFilter expected = { .values = { UINT64_C(67086180), UINT64_C(3709678893), UINT64_C(3352016856), UINT64_C(4189833989) } }; - 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'"]); @@ -351,12 +65,12 @@ namespace mbgl { mbgl::style::TypeEqualsFilter filter = { .value = mbgl::FeatureType::Unknown }; XCTAssertThrowsSpecificNamed([NSPredicate mgl_predicateWithFilter:filter], NSException, NSInternalInconsistencyException); } - + { 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'"]); @@ -379,32 +93,32 @@ namespace mbgl { NSPredicate *expected = [NSPredicate predicateWithFormat:@"%K != nil", @"$id"]; XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], expected); } - + { 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 = { @@ -414,7 +128,7 @@ namespace mbgl { }; XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithFormat:@"a BETWEEN {'b', 'z'}"]); } - + { mbgl::style::AllFilter filter = { .filters = { @@ -424,7 +138,7 @@ namespace mbgl { }; 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); @@ -441,7 +155,7 @@ namespace mbgl { NSPredicate *expected = [NSPredicate predicateWithFormat:@"%K IN {67086180, 3709678893, 3352016856, 4189833989}", @"$id"]; XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], expected); } - + { 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); @@ -458,12 +172,12 @@ namespace mbgl { NSPredicate *expected = [NSPredicate predicateWithFormat:@"NOT %K IN {67086180, 3709678893, 3352016856, 4189833989}", @"$id"]; XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], expected); } - + { mbgl::style::AllFilter filter; XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:filter], [NSPredicate predicateWithValue:YES]); } - + { mbgl::style::AllFilter filter = { .filters = { @@ -473,12 +187,12 @@ namespace mbgl { }; 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 = { @@ -488,12 +202,12 @@ namespace mbgl { }; 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 = { @@ -505,69 +219,339 @@ namespace mbgl { } } -- (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:@"1 <= a && 2 >= a" mustRoundTrip:YES]; - [self testSymmetryWithPredicate:[NSPredicate predicateWithFormat:@"a BETWEEN %@", @[@1, @2]] - reversePredicate:[NSPredicate predicateWithFormat:@"1 <= a && 2 >= a"] - 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]; +- (void)testUnsupportedFilterPredicates { + XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"1 == 2"].mgl_filter, NSException, NSInvalidArgumentException); + XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"1 == 1"].mgl_filter, NSException, NSInvalidArgumentException); + XCTAssertThrowsSpecificNamed([NSPredicate predicateWithValue:YES].mgl_filter, NSException, NSInvalidArgumentException); + XCTAssertThrowsSpecificNamed([NSPredicate predicateWithValue:NO].mgl_filter, NSException, NSInvalidArgumentException); + 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); +} - // The reverse formats here are a bit backwards because we canonicalize - // a reverse CONTAINS to a forward IN. - [self testSymmetryWithFormat:@"{1, 2} CONTAINS a" reverseFormat:@"{1, 2} CONTAINS a" mustRoundTrip:NO]; - [self testSymmetryWithPredicate:[NSPredicate predicateWithFormat:@"%@ CONTAINS a", @[@1, @2]] - reversePredicate:[NSPredicate predicateWithFormat:@"%@ CONTAINS a", @[@1, @2]] - mustRoundTrip:NO]; +- (void)testComparisonPredicates { + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"x == YES"]; + NSArray *jsonExpression = @[@"==", @[@"get", @"x"], @YES]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') < 5"]; + NSArray *jsonExpression = @[@"<", @[@"to-number", @[@"get", @"x"]], @5]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') > 5"]; + NSArray *jsonExpression = @[@">", @[@"to-number", @[@"get", @"x"]], @5]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') <= 5"]; + NSArray *jsonExpression = @[@"<=", @[@"to-number", @[@"get", @"x"]], @5]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') >= 5"]; + NSArray *jsonExpression = @[@">=", @[@"to-number", @[@"get", @"x"]], @5]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSString') > 'value'"]; + NSArray *jsonExpression = @[@">", @[@"to-string", @[@"get", @"x"]], @"value"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a = 'b'"]; + NSArray *jsonExpression = @[@"==", @[@"get", @"a"], @"b"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$geometryType = 'Point'"]; + NSArray *jsonExpression = @[@"==", @[@"geometry-type"], @"Point"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = 67086180"]; + NSArray *jsonExpression = @[@"==", @[@"id"], @67086180]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = nil"]; + NSArray *jsonExpression = @[@"==", @[@"id"], [NSNull null]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a = nil"]; + NSArray *jsonExpression = @[@"==", @[@"get", @"a"], [NSNull null]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$geometryType != 'Point'"]; + NSArray *jsonExpression = @[@"!=", @[@"geometry-type"], @"Point"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier != 67086180"]; + NSArray *jsonExpression = @[@"!=", @[@"id"], @67086180]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier != nil"]; + NSArray *jsonExpression = @[@"!=", @[@"id"], [NSNull null]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a != 'b'"]; + NSArray *jsonExpression = @[@"!=", @[@"get", @"a"], @"b"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a != nil"]; + NSArray *jsonExpression = @[@"!=", @[@"get", @"a"], [NSNull null]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') < 'b'"]; + NSArray *jsonExpression = @[@"<", @[@"to-string", @[@"get", @"a"]], @"b"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') <= 'b'"]; + NSArray *jsonExpression = @[@"<=", @[@"to-string", @[@"get", @"a"]], @"b"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') > 'b'"]; + NSArray *jsonExpression = @[@">", @[@"to-string", @[@"get", @"a"]], @"b"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') >= 'b'"]; + NSArray *jsonExpression = @[@">=", @[@"to-string", @[@"get", @"a"]], @"b"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') BETWEEN {'b', 'z'}"]; + NSArray *jsonExpression =@[@"all", @[@"<=", @"b", @[@"to-string", @[@"get", @"a"]]], @[@"<=", @[@"to-string", @[@"get", @"a"]], @"z"]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSExpression *limits = [NSExpression expressionForAggregate:@[[NSExpression expressionForConstantValue:@10], [NSExpression expressionForConstantValue:@100]]]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') BETWEEN %@", limits]; + NSArray *jsonExpression = @[@"all", @[@">=", @[@"to-number", @[@"get", @"x"]], @10], @[@"<=", @[@"to-number", @[@"get", @"x"]], @100]]; + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSArray *expected = @[@"all", @[@"<=", @10, @[@"to-number", @[@"get", @"x"]]], @[@"<=", @[@"to-number", @[@"get", @"x"]], @100]]; + NSExpression *limits = [NSExpression expressionForAggregate:@[[NSExpression expressionForConstantValue:@10], [NSExpression expressionForConstantValue:@100]]]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') BETWEEN %@", limits]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:expected], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:expected] + mustRoundTrip:NO]; + } + { + NSArray *expected = @[@"all", @[@"<=", @10, @[@"to-number", @[@"get", @"x"]]], @[@">=", @100, @[@"to-number", @[@"get", @"x"]]]]; + NSExpression *limits = [NSExpression expressionForAggregate:@[[NSExpression expressionForConstantValue:@10], [NSExpression expressionForConstantValue:@100]]]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') BETWEEN %@", limits]; + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:expected], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:expected] + mustRoundTrip:NO]; + } + { + NSArray *expected = @[@"all", @[@">=", @[@"to-number", @[@"get", @"x"]], @10], @[@">=", @100, @[@"to-number", @[@"get", @"x"]]]]; + NSExpression *limits = [NSExpression expressionForAggregate:@[[NSExpression expressionForConstantValue:@10], [NSExpression expressionForConstantValue:@100]]]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') BETWEEN %@", limits]; + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:expected], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:expected] + mustRoundTrip:NO]; + } + { + NSArray *expected = @[@"match", @[@"id"], @6, @YES, @5, @YES, @4, @YES, @3, @YES, @NO]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier IN { 6, 5, 4, 3}"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"MGL_MATCH(CAST($featureIdentifier, 'NSNumber'), 3, YES, 4, YES, 5, YES, 6, YES, NO) == YES"]; + auto forwardFilter = [NSPredicate mgl_predicateWithJSONObject:expected].mgl_filter; + NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; + XCTAssertEqualObjects(predicateAfter, forwardPredicateAfter); + } + { + NSArray *expected = @[@"!", @[@"match", @[@"get", @"x"], @6, @YES, @5, @YES, @4, @YES, @3, @YES, @NO]]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT x IN { 6, 5, 4, 3}"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"NOT MGL_MATCH(CAST(x, 'NSNumber'), 3, YES, 4, YES, 5, YES, 6, YES, NO) == YES"]; + auto forwardFilter = [NSPredicate mgl_predicateWithJSONObject:expected].mgl_filter; + NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; + XCTAssertEqualObjects(predicateAfter, forwardPredicateAfter); + } + { + NSArray *expected = @[@"match", @[@"get", @"a"], @"b", @YES, @"c", @YES, @NO]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a IN { 'b', 'c' }"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"MGL_MATCH(CAST(a, 'NSString'), 'b', YES, 'c', YES, NO) == YES"]; + auto forwardFilter = [NSPredicate mgl_predicateWithJSONObject:expected].mgl_filter; + NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; + XCTAssertEqualObjects(predicateAfter, forwardPredicateAfter); + } + { + NSArray *expected = @[@"match", @[@"geometry-type"], @"LineString", @YES, @"Polygon", @YES, @NO]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%@ IN %@", [NSExpression expressionForVariable:@"geometryType"], @[@"LineString", @"Polygon"]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"MGL_MATCH($geometryType, 'LineString', YES, 'Polygon', YES, NO) == YES"]; + auto forwardFilter = [NSPredicate mgl_predicateWithJSONObject:expected].mgl_filter; + NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; + XCTAssertEqualObjects(predicateAfter, forwardPredicateAfter); + } + { + NSArray *expected = @[@"match", @[@"get", @"x"], @6, @YES, @5, @YES, @4, @YES, @3, @YES, @NO]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"{ 6, 5, 4, 3} CONTAINS x"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"MGL_MATCH(CAST(x, 'NSNumber'), 3, YES, 4, YES, 5, YES, 6, YES, NO) == YES"]; + auto forwardFilter = [NSPredicate mgl_predicateWithJSONObject:expected].mgl_filter; + NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; + XCTAssertEqualObjects(predicateAfter, forwardPredicateAfter); + } + { + NSArray *expected = @[@"match", @[@"geometry-type"], @"LineString", @YES, @"Polygon", @YES, @NO]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%@ CONTAINS %@", @[@"LineString", @"Polygon"], [NSExpression expressionForVariable:@"geometryType"]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"MGL_MATCH($geometryType, 'LineString', YES, 'Polygon', YES, NO) == YES"]; + auto forwardFilter = [NSPredicate mgl_predicateWithJSONObject:expected].mgl_filter; + NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; + XCTAssertEqualObjects(predicateAfter, forwardPredicateAfter); + } + { + NSArray *expected = @[@"match", @[@"id"], @6, @YES, @5, @YES, @4, @YES, @3, @YES, @NO]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"{ 6, 5, 4, 3} CONTAINS $featureIdentifier"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"MGL_MATCH(CAST($featureIdentifier, 'NSNumber'), 3, YES, 4, YES, 5, YES, 6, YES, NO) == YES"]; + auto forwardFilter = [NSPredicate mgl_predicateWithJSONObject:expected].mgl_filter; + NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; + XCTAssertEqualObjects(predicateAfter, forwardPredicateAfter); + } } -- (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)testCompoundPredicates { + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a == 'b' AND c == 'd'"]; + NSArray *jsonExpression = @[@"all", @[@"==", @[@"get", @"a"], @"b"], @[@"==", @[@"get", @"c"], @"d"]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a == 'b' OR c == 'd'"]; + NSArray *jsonExpression = @[@"any", @[@"==", @[@"get", @"a"], @"b"], @[@"==", @[@"get", @"c"], @"d"]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT(a == 'b' AND c == 'd')"]; + NSArray *jsonExpression = @[@"!", @[@"all", @[@"==", @[@"get", @"a"], @"b"], @[@"==", @[@"get", @"c"], @"d"]]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT(a == 'b' OR c == 'd')"]; + NSArray *jsonExpression = @[@"!", @[@"any", @[@"==", @[@"get", @"a"], @"b"], @[@"==", @[@"get", @"c"], @"d"]]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate mgl_predicateWithJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT a == nil"]; + NSArray *jsonExpression = @[@"!", @[@"==", @[@"get", @"a"], [NSNull null]]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT a != nil"]; + NSArray *jsonExpression = @[@"!", @[@"!=", @[@"get", @"a"], [NSNull null]]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate + mustRoundTrip:NO]; + } } -- (void)testSymmetryWithPredicate:(NSPredicate *)forwardPredicate reversePredicate:(NSPredicate *)reversePredicate mustRoundTrip:(BOOL)mustRoundTrip { +- (void)testSymmetryWithPredicate:(NSPredicate *)forwardPredicate mustRoundTrip:(BOOL)mustRoundTrip { auto forwardFilter = forwardPredicate.mgl_filter; NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; if (mustRoundTrip) { // A collection of ints may turn into an aggregate of longs, for // example, so compare formats instead of the predicates themselves. XCTAssertEqualObjects(forwardPredicate.predicateFormat, forwardPredicateAfter.predicateFormat); - } - - if (reversePredicate) { - auto reverseFilter = reversePredicate.mgl_filter; - NSPredicate *reversePredicateAfter = [NSPredicate mgl_predicateWithFilter:reverseFilter]; - XCTAssertNotEqualObjects(reversePredicate, reversePredicateAfter); - - XCTAssertEqualObjects(forwardPredicateAfter, reversePredicateAfter); + } else { + XCTAssertEqualObjects(forwardPredicate, forwardPredicateAfter); } } diff --git a/platform/darwin/test/MGLRasterStyleLayerTests.mm b/platform/darwin/test/MGLRasterStyleLayerTests.mm index 7b0757eeb6..c8e454743e 100644 --- a/platform/darwin/test/MGLRasterStyleLayerTests.mm +++ b/platform/darwin/test/MGLRasterStyleLayerTests.mm @@ -34,117 +34,132 @@ { XCTAssertTrue(rawLayer->getRasterBrightnessMax().isUndefined(), @"raster-brightness-max should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.maximumRasterBrightness; + NSExpression *defaultExpression = layer.maximumRasterBrightness; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.maximumRasterBrightness = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.maximumRasterBrightness = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getRasterBrightnessMax(), propertyValue, - @"Setting maximumRasterBrightness to a constant value should update raster-brightness-max."); - XCTAssertEqualObjects(layer.maximumRasterBrightness, constantStyleValue, - @"maximumRasterBrightness should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.maximumRasterBrightness = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting maximumRasterBrightness to a constant value expression should update raster-brightness-max."); + XCTAssertEqualObjects(layer.maximumRasterBrightness, constantExpression, + @"maximumRasterBrightness should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.maximumRasterBrightness = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getRasterBrightnessMax(), propertyValue, - @"Setting maximumRasterBrightness to a camera function should update raster-brightness-max."); - XCTAssertEqualObjects(layer.maximumRasterBrightness, functionStyleValue, - @"maximumRasterBrightness should round-trip camera functions."); + @"Setting maximumRasterBrightness to a camera expression should update raster-brightness-max."); + XCTAssertEqualObjects(layer.maximumRasterBrightness, functionExpression, + @"maximumRasterBrightness should round-trip camera expressions."); layer.maximumRasterBrightness = nil; XCTAssertTrue(rawLayer->getRasterBrightnessMax().isUndefined(), @"Unsetting maximumRasterBrightness should return raster-brightness-max to the default value."); - XCTAssertEqualObjects(layer.maximumRasterBrightness, defaultStyleValue, + XCTAssertEqualObjects(layer.maximumRasterBrightness, defaultExpression, @"maximumRasterBrightness should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.maximumRasterBrightness = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.maximumRasterBrightness = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.maximumRasterBrightness = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.maximumRasterBrightness = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // raster-brightness-min { XCTAssertTrue(rawLayer->getRasterBrightnessMin().isUndefined(), @"raster-brightness-min should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.minimumRasterBrightness; + NSExpression *defaultExpression = layer.minimumRasterBrightness; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.minimumRasterBrightness = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.minimumRasterBrightness = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getRasterBrightnessMin(), propertyValue, - @"Setting minimumRasterBrightness to a constant value should update raster-brightness-min."); - XCTAssertEqualObjects(layer.minimumRasterBrightness, constantStyleValue, - @"minimumRasterBrightness should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.minimumRasterBrightness = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting minimumRasterBrightness to a constant value expression should update raster-brightness-min."); + XCTAssertEqualObjects(layer.minimumRasterBrightness, constantExpression, + @"minimumRasterBrightness should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.minimumRasterBrightness = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getRasterBrightnessMin(), propertyValue, - @"Setting minimumRasterBrightness to a camera function should update raster-brightness-min."); - XCTAssertEqualObjects(layer.minimumRasterBrightness, functionStyleValue, - @"minimumRasterBrightness should round-trip camera functions."); + @"Setting minimumRasterBrightness to a camera expression should update raster-brightness-min."); + XCTAssertEqualObjects(layer.minimumRasterBrightness, functionExpression, + @"minimumRasterBrightness should round-trip camera expressions."); layer.minimumRasterBrightness = nil; XCTAssertTrue(rawLayer->getRasterBrightnessMin().isUndefined(), @"Unsetting minimumRasterBrightness should return raster-brightness-min to the default value."); - XCTAssertEqualObjects(layer.minimumRasterBrightness, defaultStyleValue, + XCTAssertEqualObjects(layer.minimumRasterBrightness, defaultExpression, @"minimumRasterBrightness should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.minimumRasterBrightness = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.minimumRasterBrightness = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.minimumRasterBrightness = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.minimumRasterBrightness = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // raster-contrast { XCTAssertTrue(rawLayer->getRasterContrast().isUndefined(), @"raster-contrast should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.rasterContrast; + NSExpression *defaultExpression = layer.rasterContrast; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.rasterContrast = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.rasterContrast = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getRasterContrast(), propertyValue, - @"Setting rasterContrast to a constant value should update raster-contrast."); - XCTAssertEqualObjects(layer.rasterContrast, constantStyleValue, - @"rasterContrast should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.rasterContrast = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting rasterContrast to a constant value expression should update raster-contrast."); + XCTAssertEqualObjects(layer.rasterContrast, constantExpression, + @"rasterContrast should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.rasterContrast = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getRasterContrast(), propertyValue, - @"Setting rasterContrast to a camera function should update raster-contrast."); - XCTAssertEqualObjects(layer.rasterContrast, functionStyleValue, - @"rasterContrast should round-trip camera functions."); + @"Setting rasterContrast to a camera expression should update raster-contrast."); + XCTAssertEqualObjects(layer.rasterContrast, functionExpression, + @"rasterContrast should round-trip camera expressions."); layer.rasterContrast = nil; XCTAssertTrue(rawLayer->getRasterContrast().isUndefined(), @"Unsetting rasterContrast should return raster-contrast to the default value."); - XCTAssertEqualObjects(layer.rasterContrast, defaultStyleValue, + XCTAssertEqualObjects(layer.rasterContrast, defaultExpression, @"rasterContrast should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.rasterContrast = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.rasterContrast = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.rasterContrast = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.rasterContrast = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.rasterContrastTransition = transitionTest; auto toptions = rawLayer->getRasterContrastTransition(); @@ -160,126 +175,132 @@ { XCTAssertTrue(rawLayer->getRasterFadeDuration().isUndefined(), @"raster-fade-duration should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.rasterFadeDuration; + NSExpression *defaultExpression = layer.rasterFadeDuration; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.rasterFadeDuration = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.rasterFadeDuration = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getRasterFadeDuration(), propertyValue, - @"Setting rasterFadeDuration to a constant value should update raster-fade-duration."); - XCTAssertEqualObjects(layer.rasterFadeDuration, constantStyleValue, - @"rasterFadeDuration should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.rasterFadeDuration = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting rasterFadeDuration to a constant value expression should update raster-fade-duration."); + XCTAssertEqualObjects(layer.rasterFadeDuration, constantExpression, + @"rasterFadeDuration should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.rasterFadeDuration = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getRasterFadeDuration(), propertyValue, - @"Setting rasterFadeDuration to a camera function should update raster-fade-duration."); - XCTAssertEqualObjects(layer.rasterFadeDuration, functionStyleValue, - @"rasterFadeDuration should round-trip camera functions."); + @"Setting rasterFadeDuration to a camera expression should update raster-fade-duration."); + XCTAssertEqualObjects(layer.rasterFadeDuration, functionExpression, + @"rasterFadeDuration should round-trip camera expressions."); layer.rasterFadeDuration = nil; XCTAssertTrue(rawLayer->getRasterFadeDuration().isUndefined(), @"Unsetting rasterFadeDuration should return raster-fade-duration to the default value."); - XCTAssertEqualObjects(layer.rasterFadeDuration, defaultStyleValue, + XCTAssertEqualObjects(layer.rasterFadeDuration, defaultExpression, @"rasterFadeDuration should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.rasterFadeDuration = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.rasterFadeDuration = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - // Transition property test - layer.rasterFadeDurationTransition = transitionTest; - auto toptions = rawLayer->getRasterFadeDurationTransition(); - XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay); - XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration); - - MGLTransition rasterFadeDurationTransition = layer.rasterFadeDurationTransition; - XCTAssertEqual(rasterFadeDurationTransition.delay, transitionTest.delay); - XCTAssertEqual(rasterFadeDurationTransition.duration, transitionTest.duration); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.rasterFadeDuration = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.rasterFadeDuration = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // raster-hue-rotate { XCTAssertTrue(rawLayer->getRasterHueRotate().isUndefined(), @"raster-hue-rotate should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.rasterHueRotation; + NSExpression *defaultExpression = layer.rasterHueRotation; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.rasterHueRotation = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.rasterHueRotation = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getRasterHueRotate(), propertyValue, - @"Setting rasterHueRotation to a constant value should update raster-hue-rotate."); - XCTAssertEqualObjects(layer.rasterHueRotation, constantStyleValue, - @"rasterHueRotation should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.rasterHueRotation = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting rasterHueRotation to a constant value expression should update raster-hue-rotate."); + XCTAssertEqualObjects(layer.rasterHueRotation, constantExpression, + @"rasterHueRotation should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.rasterHueRotation = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getRasterHueRotate(), propertyValue, - @"Setting rasterHueRotation to a camera function should update raster-hue-rotate."); - XCTAssertEqualObjects(layer.rasterHueRotation, functionStyleValue, - @"rasterHueRotation should round-trip camera functions."); + @"Setting rasterHueRotation to a camera expression should update raster-hue-rotate."); + XCTAssertEqualObjects(layer.rasterHueRotation, functionExpression, + @"rasterHueRotation should round-trip camera expressions."); layer.rasterHueRotation = nil; XCTAssertTrue(rawLayer->getRasterHueRotate().isUndefined(), @"Unsetting rasterHueRotation should return raster-hue-rotate to the default value."); - XCTAssertEqualObjects(layer.rasterHueRotation, defaultStyleValue, + XCTAssertEqualObjects(layer.rasterHueRotation, defaultExpression, @"rasterHueRotation should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.rasterHueRotation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.rasterHueRotation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.rasterHueRotation = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.rasterHueRotation = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // raster-opacity { XCTAssertTrue(rawLayer->getRasterOpacity().isUndefined(), @"raster-opacity should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.rasterOpacity; + NSExpression *defaultExpression = layer.rasterOpacity; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.rasterOpacity = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.rasterOpacity = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getRasterOpacity(), propertyValue, - @"Setting rasterOpacity to a constant value should update raster-opacity."); - XCTAssertEqualObjects(layer.rasterOpacity, constantStyleValue, - @"rasterOpacity should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.rasterOpacity = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting rasterOpacity to a constant value expression should update raster-opacity."); + XCTAssertEqualObjects(layer.rasterOpacity, constantExpression, + @"rasterOpacity should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.rasterOpacity = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getRasterOpacity(), propertyValue, - @"Setting rasterOpacity to a camera function should update raster-opacity."); - XCTAssertEqualObjects(layer.rasterOpacity, functionStyleValue, - @"rasterOpacity should round-trip camera functions."); + @"Setting rasterOpacity to a camera expression should update raster-opacity."); + XCTAssertEqualObjects(layer.rasterOpacity, functionExpression, + @"rasterOpacity should round-trip camera expressions."); layer.rasterOpacity = nil; XCTAssertTrue(rawLayer->getRasterOpacity().isUndefined(), @"Unsetting rasterOpacity should return raster-opacity to the default value."); - XCTAssertEqualObjects(layer.rasterOpacity, defaultStyleValue, + XCTAssertEqualObjects(layer.rasterOpacity, defaultExpression, @"rasterOpacity should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.rasterOpacity = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.rasterOpacity = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.rasterOpacity = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.rasterOpacity = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.rasterOpacityTransition = transitionTest; auto toptions = rawLayer->getRasterOpacityTransition(); @@ -295,39 +316,44 @@ { XCTAssertTrue(rawLayer->getRasterSaturation().isUndefined(), @"raster-saturation should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.rasterSaturation; + NSExpression *defaultExpression = layer.rasterSaturation; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.rasterSaturation = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.rasterSaturation = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getRasterSaturation(), propertyValue, - @"Setting rasterSaturation to a constant value should update raster-saturation."); - XCTAssertEqualObjects(layer.rasterSaturation, constantStyleValue, - @"rasterSaturation should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.rasterSaturation = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting rasterSaturation to a constant value expression should update raster-saturation."); + XCTAssertEqualObjects(layer.rasterSaturation, constantExpression, + @"rasterSaturation should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.rasterSaturation = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getRasterSaturation(), propertyValue, - @"Setting rasterSaturation to a camera function should update raster-saturation."); - XCTAssertEqualObjects(layer.rasterSaturation, functionStyleValue, - @"rasterSaturation should round-trip camera functions."); + @"Setting rasterSaturation to a camera expression should update raster-saturation."); + XCTAssertEqualObjects(layer.rasterSaturation, functionExpression, + @"rasterSaturation should round-trip camera expressions."); layer.rasterSaturation = nil; XCTAssertTrue(rawLayer->getRasterSaturation().isUndefined(), @"Unsetting rasterSaturation should return raster-saturation to the default value."); - XCTAssertEqualObjects(layer.rasterSaturation, defaultStyleValue, + XCTAssertEqualObjects(layer.rasterSaturation, defaultExpression, @"rasterSaturation should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.rasterSaturation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.rasterSaturation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.rasterSaturation = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.rasterSaturation = functionExpression, NSException, NSInvalidArgumentException, @"MGLRasterLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); // Transition property test layer.rasterSaturationTransition = transitionTest; auto toptions = rawLayer->getRasterSaturationTransition(); diff --git a/platform/darwin/test/MGLSDKTestHelpers.swift b/platform/darwin/test/MGLSDKTestHelpers.swift index f21041782e..727d8bf0c6 100644 --- a/platform/darwin/test/MGLSDKTestHelpers.swift +++ b/platform/darwin/test/MGLSDKTestHelpers.swift @@ -1,3 +1,4 @@ +import XCTest import Foundation class MGLSDKTestHelpers { @@ -22,9 +23,9 @@ extension MGLSDKTestHelpers { class func protocolMethodDescriptions(_ p: Protocol) -> Set<String> { var methods = Set<String>() var methodCount = UInt32() - let methodDescriptionList: UnsafeMutablePointer<objc_method_description>! = protocol_copyMethodDescriptionList(p, false, true, &methodCount) + let methodDescriptionList = protocol_copyMethodDescriptionList(p, false, true, &methodCount) for i in 0..<Int(methodCount) { - let description: objc_method_description = methodDescriptionList[i] + let description = methodDescriptionList![i] XCTAssertNotNil(description.name?.description) methods.insert(description.name!.description) } @@ -35,11 +36,16 @@ extension MGLSDKTestHelpers { class func classMethodDescriptions(_ cls: Swift.AnyClass) -> Set<String> { var methods = Set<String>() var methodCount = UInt32() - let methodList: UnsafeMutablePointer<Method?>! = class_copyMethodList(cls, &methodCount) + let methodList = class_copyMethodList(cls, &methodCount) for i in 0..<Int(methodCount) { - let method = methodList[i] - let selector : Selector = method_getName(method) - methods.insert(selector.description) + let method = methodList![i] + let selector = method_getName(method) + #if os(macOS) + methods.insert(selector.description) + #else + XCTAssertNotNil(selector) + methods.insert(selector!.description) + #endif } free(methodList) return methods diff --git a/platform/darwin/test/MGLShapeSourceTests.mm b/platform/darwin/test/MGLShapeSourceTests.mm index 561af7f3d0..d3f9a599e2 100644 --- a/platform/darwin/test/MGLShapeSourceTests.mm +++ b/platform/darwin/test/MGLShapeSourceTests.mm @@ -297,7 +297,10 @@ // when a shape is included in the features array MGLPolygon *polygon = [MGLPolygon polygonWithCoordinates:coordinates count:sizeof(coordinates)/sizeof(coordinates[0]) interiorPolygons:nil]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-literal-conversion" XCTAssertThrowsSpecificNamed([[MGLShapeSource alloc] initWithIdentifier:@"source-id-invalid" features:@[polygon] options:nil], NSException, NSInvalidArgumentException, @"Shape source should raise an exception if a shape is sent to the features initializer"); +#pragma clang diagnostic pop } - (void)testMGLShapeSourceWithShapesConvenienceInitializer { diff --git a/platform/darwin/test/MGLSourceQueryTests.m b/platform/darwin/test/MGLSourceQueryTests.m index d1ef180a52..b321da1ea4 100644 --- a/platform/darwin/test/MGLSourceQueryTests.m +++ b/platform/darwin/test/MGLSourceQueryTests.m @@ -7,8 +7,8 @@ @implementation MGLSourceQueryTests -- (void) testQueryVectorSource { - MGLVectorSource *source = [[MGLVectorSource alloc] initWithIdentifier:@"vector" tileURLTemplates:@[@"fake"] options:nil]; +- (void) testQueryVectorTileSource { + MGLVectorTileSource *source = [[MGLVectorTileSource alloc] initWithIdentifier:@"vector" tileURLTemplates:@[@"fake"] options:nil]; NSSet *sourceLayers = [NSSet setWithObjects:@"buildings", @"water", nil]; NSArray* features = [source featuresInSourceLayersWithIdentifiers:sourceLayers predicate:nil]; // Source not added yet, so features is 0 diff --git a/platform/darwin/test/MGLStyleLayerTests.h b/platform/darwin/test/MGLStyleLayerTests.h index f0b889f022..c7577819b8 100644 --- a/platform/darwin/test/MGLStyleLayerTests.h +++ b/platform/darwin/test/MGLStyleLayerTests.h @@ -1,6 +1,9 @@ #import <Mapbox/Mapbox.h> #import <XCTest/XCTest.h> +#define MGLConstantExpression(constant) \ + [NSExpression expressionForConstantValue:constant] + @interface MGLStyleLayerTests : XCTestCase <MGLMapViewDelegate> @property (nonatomic, copy, readonly, class) NSString *layerType; @@ -11,7 +14,7 @@ @interface NSString (MGLStyleLayerTestAdditions) -@property (nonatomic, readonly, copy) NS_ARRAY_OF(NSString *) *lexicalClasses; +@property (nonatomic, readonly, copy) NSArray<NSString *> *lexicalClasses; @property (nonatomic, readonly, copy) NSString *lemma; @end diff --git a/platform/darwin/test/MGLStyleLayerTests.m b/platform/darwin/test/MGLStyleLayerTests.m index b51fa02af4..52b36dba00 100644 --- a/platform/darwin/test/MGLStyleLayerTests.m +++ b/platform/darwin/test/MGLStyleLayerTests.m @@ -33,7 +33,7 @@ } - (void)testPropertyName:(NSString *)name isBoolean:(BOOL)isBoolean { - NS_MUTABLE_ARRAY_OF(NSString *) *components = [name componentsSeparatedByString:@"-"].mutableCopy; + NSMutableArray<NSString *> *components = [name componentsSeparatedByString:@"-"].mutableCopy; if (isBoolean) { if ([components.firstObject isEqualToString:@"is"]) { [components removeObjectAtIndex:0]; @@ -67,7 +67,7 @@ @implementation NSString (MGLStyleLayerTestAdditions) -- (NS_ARRAY_OF(NSString *) *)lexicalClasses { +- (NSArray<NSString *> *)lexicalClasses { NSOrthography *orthography = [NSOrthography orthographyWithDominantScript:@"Latn" languageMap:@{@"Latn": @[@"en"]}]; NSLinguisticTaggerOptions options = (NSLinguisticTaggerOmitPunctuation diff --git a/platform/darwin/test/MGLStyleLayerTests.mm.ejs b/platform/darwin/test/MGLStyleLayerTests.mm.ejs index 5fdfc3d44e..f70f0bba23 100644 --- a/platform/darwin/test/MGLStyleLayerTests.mm.ejs +++ b/platform/darwin/test/MGLStyleLayerTests.mm.ejs @@ -23,7 +23,7 @@ return @"<%- type %>"; } -<% if (type !== 'background' && type !== 'raster') { -%> +<% if (type !== 'background' && type !== 'raster' && type !== 'hillshade') { -%> - (void)testPredicates { MGLPointFeature *feature = [[MGLPointFeature alloc] init]; MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil]; @@ -36,8 +36,8 @@ XCTAssertNil(layer.sourceLayerIdentifier); XCTAssertNil(layer.predicate); - layer.predicate = [NSPredicate predicateWithValue:NO]; - XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithValue:NO]); + layer.predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]; + XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]); layer.predicate = nil; XCTAssertNil(layer.predicate); } @@ -59,50 +59,56 @@ MGLTransition transitionTest = MGLTransitionMake(5, 4); <% for (const property of properties) { -%> +<% if (property.name === 'heatmap-color') continue; -%> // <%- originalPropertyName(property) %> { XCTAssertTrue(rawLayer->get<%- camelize(originalPropertyName(property)) %>().isUndefined(), @"<%- originalPropertyName(property) %> should be unset initially."); - MGLStyleValue<<%- propertyType(property) %>> *defaultStyleValue = layer.<%- objCName(property) %>; + NSExpression *defaultExpression = layer.<%- objCName(property) %>; - MGLStyleValue<<%- propertyType(property) %>> *constantStyleValue = [MGLStyleValue<<%- propertyType(property) %>> valueWithRawValue:<%- objCTestValue(property, type, 3) %>]; - layer.<%- objCName(property) %> = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:<%- objCTestValue(property, type, true, 3) %>]; + layer.<%- objCName(property) %> = constantExpression; <% if (property["property-function"]) { -%> mbgl::style::DataDrivenPropertyValue<<%- mbglType(property) %>> propertyValue = { <%- mbglTestValue(property, type) %> }; <% } else { -%> mbgl::style::PropertyValue<<%- mbglType(property) %>> propertyValue = { <%- mbglTestValue(property, type) %> }; <% } -%> XCTAssertEqual(rawLayer->get<%- camelize(originalPropertyName(property)) %>(), propertyValue, - @"Setting <%- objCName(property) %> to a constant value should update <%- originalPropertyName(property) %>."); - XCTAssertEqualObjects(layer.<%- objCName(property) %>, constantStyleValue, - @"<%- objCName(property) %> should round-trip constant values."); - - MGLStyleValue<<%- propertyType(property) %>> * functionStyleValue = [MGLStyleValue<<%- propertyType(property) %>> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.<%- objCName(property) %> = functionStyleValue; - - mbgl::style::IntervalStops<<%- mbglType(property) %>> intervalStops = { {{18, <%- mbglTestValue(property, type) %>}} }; + @"Setting <%- objCName(property) %> to a constant value expression should update <%- originalPropertyName(property) %>."); + XCTAssertEqualObjects(layer.<%- objCName(property) %>, constantExpression, + @"<%- objCName(property) %> should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:<%- objCTestValue(property, type, false, 3) %>]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.<%- objCName(property) %> = functionExpression; + + mbgl::style::IntervalStops<<%- mbglType(property) %>> intervalStops = {{ + { -INFINITY, <%- mbglTestValue(property, type) %> }, + { 18, <%- mbglTestValue(property, type) %> }, + }}; propertyValue = mbgl::style::CameraFunction<<%- mbglType(property) %>> { intervalStops }; XCTAssertEqual(rawLayer->get<%- camelize(originalPropertyName(property)) %>(), propertyValue, - @"Setting <%- objCName(property) %> to a camera function should update <%- originalPropertyName(property) %>."); - XCTAssertEqualObjects(layer.<%- objCName(property) %>, functionStyleValue, - @"<%- objCName(property) %> should round-trip camera functions."); + @"Setting <%- objCName(property) %> to a camera expression should update <%- originalPropertyName(property) %>."); + XCTAssertEqualObjects(layer.<%- objCName(property) %>, functionExpression, + @"<%- objCName(property) %> should round-trip camera expressions."); <% if (property["property-function"] && isInterpolatable(property)) { -%> - functionStyleValue = [MGLStyleValue<<%- propertyType(property) %>> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.<%- objCName(property) %> = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.<%- objCName(property) %> = functionExpression; mbgl::style::ExponentialStops<<%- mbglType(property) %>> exponentialStops = { {{18, <%- mbglTestValue(property, type) %>}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<<%- mbglType(property) %>> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->get<%- camelize(originalPropertyName(property)) %>(), propertyValue, - @"Setting <%- objCName(property) %> to a source function should update <%- originalPropertyName(property) %>."); - XCTAssertEqualObjects(layer.<%- objCName(property) %>, functionStyleValue, - @"<%- objCName(property) %> should round-trip source functions."); + @"Setting <%- objCName(property) %> to a data expression should update <%- originalPropertyName(property) %>."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.<%- objCName(property) %>, pedanticFunctionExpression, + @"<%- objCName(property) %> should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<<%- propertyType(property) %>> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.<%- objCName(property) %> = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.<%- objCName(property) %> = functionExpression; std::map<float, <%- mbglType(property) %>> innerStops { {18, <%- mbglTestValue(property, type) %>} }; mbgl::style::CompositeExponentialStops<<%- mbglType(property) %>> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -110,24 +116,26 @@ propertyValue = mbgl::style::CompositeFunction<<%- mbglType(property) %>> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->get<%- camelize(originalPropertyName(property)) %>(), propertyValue, - @"Setting <%- objCName(property) %> to a composite function should update <%- originalPropertyName(property) %>."); - XCTAssertEqualObjects(layer.<%- objCName(property) %>, functionStyleValue, - @"<%- objCName(property) %> should round-trip composite functions."); + @"Setting <%- objCName(property) %> to a camera-data expression should update <%- originalPropertyName(property) %>."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.<%- objCName(property) %>, pedanticFunctionExpression, + @"<%- objCName(property) %> should round-trip camera-data expressions."); <% } -%> <% if (!property.required) { -%> layer.<%- objCName(property) %> = nil; XCTAssertTrue(rawLayer->get<%- camelize(originalPropertyName(property)) %>().isUndefined(), @"Unsetting <%- objCName(property) %> should return <%- originalPropertyName(property) %> to the default value."); - XCTAssertEqualObjects(layer.<%- objCName(property) %>, defaultStyleValue, + XCTAssertEqualObjects(layer.<%- objCName(property) %>, defaultExpression, @"<%- objCName(property) %> should return the default value after being unset."); <% } -%> <% if (!property["property-function"]) { -%> - functionStyleValue = [MGLStyleValue<<%- propertyType(property) %>> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.<%- objCName(property) %> = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<<%- propertyType(property) %>> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.<%- objCName(property) %> = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.<%- objCName(property) %> = functionExpression, NSException, NSInvalidArgumentException, @"MGL<%- camelize(type) %>Layer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.<%- objCName(property) %> = functionExpression, NSException, NSInvalidArgumentException, @"MGL<%- camelize(type) %>Layer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); <% } -%> <% if (property["transition"] && !property.original) { -%> // Transition property test @@ -146,6 +154,7 @@ - (void)testPropertyNames { <% for (const property of properties) { -%> +<% if (property.name === 'heatmap-color') continue; -%> [self testPropertyName:@"<%- property.getter || property.name %>" isBoolean:<%- property.type === 'boolean' ? 'YES' : 'NO' %>]; <% } -%> } diff --git a/platform/darwin/test/MGLStyleTests.mm b/platform/darwin/test/MGLStyleTests.mm index 8f610e338c..6048f39ea3 100644 --- a/platform/darwin/test/MGLStyleTests.mm +++ b/platform/darwin/test/MGLStyleTests.mm @@ -1,6 +1,7 @@ #import <Mapbox/Mapbox.h> #import "NSBundle+MGLAdditions.h" +#import "MGLVectorTileSource_Private.h" #import <mbgl/util/default_styles.hpp> @@ -64,12 +65,6 @@ XCTAssertEqualObjects([MGLStyle darkStyleURL].absoluteString, @(mbgl::util::default_styles::dark.url)); XCTAssertEqualObjects([MGLStyle satelliteStyleURL].absoluteString, @(mbgl::util::default_styles::satellite.url)); XCTAssertEqualObjects([MGLStyle satelliteStreetsStyleURL].absoluteString, @(mbgl::util::default_styles::satelliteStreets.url)); - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - XCTAssertEqualObjects([MGLStyle emeraldStyleURL].absoluteString, @"mapbox://styles/mapbox/emerald-v8"); - XCTAssertEqualObjects([MGLStyle hybridStyleURL].absoluteString, @"mapbox://styles/mapbox/satellite-hybrid-v8"); -#pragma clang diagnostic pop } - (void)testVersionedStyleURLs { @@ -99,19 +94,8 @@ @(mbgl::util::default_styles::satelliteStreets.url)); XCTAssertEqualObjects([MGLStyle satelliteStreetsStyleURLWithVersion:99].absoluteString, @"mapbox://styles/mapbox/satellite-streets-v99"); -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - XCTAssertEqualObjects([MGLStyle trafficDayStyleURLWithVersion:mbgl::util::default_styles::trafficDay.currentVersion].absoluteString, - @(mbgl::util::default_styles::trafficDay.url)); - XCTAssertEqualObjects([MGLStyle trafficDayStyleURLWithVersion:99].absoluteString, - @"mapbox://styles/mapbox/traffic-day-v99"); - XCTAssertEqualObjects([MGLStyle trafficNightStyleURLWithVersion:mbgl::util::default_styles::trafficNight.currentVersion].absoluteString, - @(mbgl::util::default_styles::trafficNight.url)); - XCTAssertEqualObjects([MGLStyle trafficNightStyleURLWithVersion:99].absoluteString, - @"mapbox://styles/mapbox/traffic-night-v99"); -#pragma clang diagnostic pop - - static_assert(8 == mbgl::util::default_styles::numOrderedStyles, + + static_assert(6 == mbgl::util::default_styles::numOrderedStyles, "MGLStyleTests isn’t testing all the styles in mbgl::util::default_styles."); } @@ -143,7 +127,7 @@ NSString *styleHeader = self.stringWithContentsOfStyleHeader; NSError *versionedMethodError; - NSString *versionedMethodExpressionString = @(R"RE(^\+\s*\(NSURL\s*\*\s*\)\s*\w+StyleURLWithVersion\s*:\s*\(\s*NSInteger\s*\)\s*version\s*\b)RE"); + NSString *versionedMethodExpressionString = @(R"RE(^\+\s*\(NSURL\s*\*\s*\)\s*(?!traffic)\w+StyleURLWithVersion\s*:\s*\(\s*NSInteger\s*\)\s*version\s*\b)RE"); NSRegularExpression *versionedMethodExpression = [NSRegularExpression regularExpressionWithPattern:versionedMethodExpressionString options:NSRegularExpressionAnchorsMatchLines error:&versionedMethodError]; XCTAssertNil(versionedMethodError, @"Error compiling regular expression to search for versioned methods."); NSUInteger numVersionedMethodDeclarations = [versionedMethodExpression numberOfMatchesInString:styleHeader options:0 range:NSMakeRange(0, styleHeader.length)]; @@ -174,89 +158,89 @@ [self.style addSource:shapeSource]; XCTAssertThrowsSpecificNamed([self.style addSource:shapeSource], NSException, @"MGLRedundantSourceException"); - MGLRasterSource *rasterSource = [[MGLRasterSource alloc] initWithIdentifier:@"rasterSource" configurationURL:[NSURL URLWithString:@".json"] tileSize:42]; - [self.style addSource:rasterSource]; - XCTAssertThrowsSpecificNamed([self.style addSource:rasterSource], NSException, @"MGLRedundantSourceException"); + MGLRasterTileSource *rasterTileSource = [[MGLRasterTileSource alloc] initWithIdentifier:@"rasterTileSource" configurationURL:[NSURL URLWithString:@".json"] tileSize:42]; + [self.style addSource:rasterTileSource]; + XCTAssertThrowsSpecificNamed([self.style addSource:rasterTileSource], NSException, @"MGLRedundantSourceException"); - MGLVectorSource *vectorSource = [[MGLVectorSource alloc] initWithIdentifier:@"vectorSource" configurationURL:[NSURL URLWithString:@".json"]]; - [self.style addSource:vectorSource]; - XCTAssertThrowsSpecificNamed([self.style addSource:vectorSource], NSException, @"MGLRedundantSourceException"); + MGLVectorTileSource *vectorTileSource = [[MGLVectorTileSource alloc] initWithIdentifier:@"vectorTileSource" configurationURL:[NSURL URLWithString:@".json"]]; + [self.style addSource:vectorTileSource]; + XCTAssertThrowsSpecificNamed([self.style addSource:vectorTileSource], NSException, @"MGLRedundantSourceException"); } - (void)testAddingSourcesWithDuplicateIdentifiers { - MGLVectorSource *source1 = [[MGLVectorSource alloc] initWithIdentifier:@"my-source" configurationURL:[NSURL URLWithString:@"mapbox://mapbox.mapbox-terrain-v2"]]; - MGLVectorSource *source2 = [[MGLVectorSource alloc] initWithIdentifier:@"my-source" configurationURL:[NSURL URLWithString:@"mapbox://mapbox.mapbox-terrain-v2"]]; + MGLVectorTileSource *source1 = [[MGLVectorTileSource alloc] initWithIdentifier:@"my-source" configurationURL:[NSURL URLWithString:@"mapbox://mapbox.mapbox-terrain-v2"]]; + MGLVectorTileSource *source2 = [[MGLVectorTileSource alloc] initWithIdentifier:@"my-source" configurationURL:[NSURL URLWithString:@"mapbox://mapbox.mapbox-terrain-v2"]]; [self.style addSource: source1]; XCTAssertThrowsSpecificNamed([self.style addSource: source2], NSException, @"MGLRedundantSourceIdentifierException"); } - (void)testRemovingSourcesBeforeAddingThem { - MGLRasterSource *rasterSource = [[MGLRasterSource alloc] initWithIdentifier:@"raster-source" tileURLTemplates:@[] options:nil]; - [self.style removeSource:rasterSource]; - [self.style addSource:rasterSource]; - XCTAssertNotNil([self.style sourceWithIdentifier:rasterSource.identifier]); + MGLRasterTileSource *rasterTileSource = [[MGLRasterTileSource alloc] initWithIdentifier:@"raster-tile-source" tileURLTemplates:@[] options:nil]; + [self.style removeSource:rasterTileSource]; + [self.style addSource:rasterTileSource]; + XCTAssertNotNil([self.style sourceWithIdentifier:rasterTileSource.identifier]); MGLShapeSource *shapeSource = [[MGLShapeSource alloc] initWithIdentifier:@"shape-source" shape:nil options:nil]; [self.style removeSource:shapeSource]; [self.style addSource:shapeSource]; XCTAssertNotNil([self.style sourceWithIdentifier:shapeSource.identifier]); - MGLVectorSource *vectorSource = [[MGLVectorSource alloc] initWithIdentifier:@"vector-source" tileURLTemplates:@[] options:nil]; - [self.style removeSource:vectorSource]; - [self.style addSource:vectorSource]; - XCTAssertNotNil([self.style sourceWithIdentifier:vectorSource.identifier]); + MGLVectorTileSource *vectorTileSource = [[MGLVectorTileSource alloc] initWithIdentifier:@"vector-tile-source" tileURLTemplates:@[] options:nil]; + [self.style removeSource:vectorTileSource]; + [self.style addSource:vectorTileSource]; + XCTAssertNotNil([self.style sourceWithIdentifier:vectorTileSource.identifier]); } - (void)testAddingSourceOfTypeABeforeSourceOfTypeBWithSameIdentifier { - // Add a raster source - MGLRasterSource *rasterSource = [[MGLRasterSource alloc] initWithIdentifier:@"some-identifier" tileURLTemplates:@[] options:nil]; - [self.style addSource:rasterSource]; + // Add a raster tile source + MGLRasterTileSource *rasterTileSource = [[MGLRasterTileSource alloc] initWithIdentifier:@"some-identifier" tileURLTemplates:@[] options:nil]; + [self.style addSource:rasterTileSource]; - // Attempt to remove an image source with the same identifier as the raster source + // Attempt to remove an image source with the same identifier as the raster tile source MGLImageSource *imageSource = [[MGLImageSource alloc] initWithIdentifier:@"some-identifier" coordinateQuad: { } URL:[NSURL URLWithString:@"http://host/image.png"]]; [self.style removeSource:imageSource]; - // The raster source should still be added - XCTAssertTrue([[self.style sourceWithIdentifier:rasterSource.identifier] isMemberOfClass:[MGLRasterSource class]]); + // The raster tile source should still be added + XCTAssertTrue([[self.style sourceWithIdentifier:rasterTileSource.identifier] isMemberOfClass:[MGLRasterTileSource class]]); - // Remove the raster source - [self.style removeSource:rasterSource]; + // Remove the raster tile source + [self.style removeSource:rasterTileSource]; // Add the shape source [self.style addSource:imageSource]; - // Attempt to remove a vector source with the same identifer as the shape source - MGLVectorSource *vectorSource = [[MGLVectorSource alloc] initWithIdentifier:@"some-identifier" tileURLTemplates:@[] options:nil]; - [self.style removeSource:vectorSource]; + // Attempt to remove a vector tile source with the same identifer as the shape source + MGLVectorTileSource *vectorTileSource = [[MGLVectorTileSource alloc] initWithIdentifier:@"some-identifier" tileURLTemplates:@[] options:nil]; + [self.style removeSource:vectorTileSource]; // The image source should still be added XCTAssertTrue([[self.style sourceWithIdentifier:imageSource.identifier] isMemberOfClass:[MGLImageSource class]]); // Remove the image source [self.style removeSource:imageSource]; - // Add the vector source - [self.style addSource:vectorSource]; + // Add the vector tile source + [self.style addSource:vectorTileSource]; - // Attempt to remove the previously created raster source that has the same identifer as the shape source - [self.style removeSource:rasterSource]; - // The vector source should still be added - XCTAssertTrue([[self.style sourceWithIdentifier:imageSource.identifier] isMemberOfClass:[MGLVectorSource class]]); + // Attempt to remove the previously created raster tile source that has the same identifer as the shape source + [self.style removeSource:rasterTileSource]; + // The vector tile source should still be added + XCTAssertTrue([[self.style sourceWithIdentifier:imageSource.identifier] isMemberOfClass:[MGLVectorTileSource class]]); } - (void)testRemovingSourceInUse { - // Add a raster source - MGLRasterSource *rasterSource = [[MGLRasterSource alloc] initWithIdentifier:@"some-identifier" tileURLTemplates:@[] options:nil]; - [self.style addSource:rasterSource]; + // Add a raster tile source + MGLRasterTileSource *rasterTileSource = [[MGLRasterTileSource alloc] initWithIdentifier:@"some-identifier" tileURLTemplates:@[] options:nil]; + [self.style addSource:rasterTileSource]; // Add a layer using it - MGLFillStyleLayer *fillLayer = [[MGLFillStyleLayer alloc] initWithIdentifier:@"fillLayer" source:rasterSource]; + MGLFillStyleLayer *fillLayer = [[MGLFillStyleLayer alloc] initWithIdentifier:@"fillLayer" source:rasterTileSource]; [self.style addLayer:fillLayer]; - // Attempt to remove the raster source - [self.style removeSource:rasterSource]; + // Attempt to remove the raster tile source + [self.style removeSource:rasterTileSource]; // Ensure it is still there - XCTAssertTrue([[self.style sourceWithIdentifier:rasterSource.identifier] isMemberOfClass:[MGLRasterSource class]]); + XCTAssertTrue([[self.style sourceWithIdentifier:rasterTileSource.identifier] isMemberOfClass:[MGLRasterTileSource class]]); } - (void)testLayers { @@ -306,7 +290,7 @@ - (void)testAddingLayersWithDuplicateIdentifiers { // Just some source - MGLVectorSource *source = [[MGLVectorSource alloc] initWithIdentifier:@"my-source" configurationURL:[NSURL URLWithString:@"mapbox://mapbox.mapbox-terrain-v2"]]; + MGLVectorTileSource *source = [[MGLVectorTileSource alloc] initWithIdentifier:@"my-source" configurationURL:[NSURL URLWithString:@"mapbox://mapbox.mapbox-terrain-v2"]]; [self.style addSource: source]; // Add initial layer @@ -383,13 +367,6 @@ return styleHeader; } -- (void)testClasses { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - XCTAssertEqual(self.style.styleClasses.count, 0); -#pragma clang diagnostic pop -} - - (void)testImages { NSString *imageName = @"TrackingLocationMask"; #if TARGET_OS_IPHONE @@ -443,4 +420,45 @@ XCTAssertEqualObjects(layers[startIndex++].identifier, layer4.identifier); } +#pragma mark Localization tests + +- (void)testLanguageMatching { + { + NSArray *preferences = @[@"en"]; + XCTAssertEqualObjects([MGLVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en"); + } + { + NSArray *preferences = @[@"en-US"]; + XCTAssertEqualObjects([MGLVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en"); + } + { + NSArray *preferences = @[@"fr"]; + XCTAssertEqualObjects([MGLVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"fr"); + } + { + NSArray *preferences = @[@"zh-Hans"]; + XCTAssertEqualObjects([MGLVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hans"); + } + { + NSArray *preferences = @[@"zh-Hans", @"en"]; + XCTAssertEqualObjects([MGLVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hans"); + } + { + NSArray *preferences = @[@"zh-Hant"]; + XCTAssertNil([MGLVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]); + } + { + NSArray *preferences = @[@"tlh"]; + XCTAssertNil([MGLVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]); + } + { + NSArray *preferences = @[@"tlh", @"en"]; + XCTAssertEqualObjects([MGLVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en"); + } + { + NSArray *preferences = @[@"mul"]; + XCTAssertNil([MGLVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]); + } +} + @end diff --git a/platform/darwin/test/MGLStyleValueTests.h b/platform/darwin/test/MGLStyleValueTests.h deleted file mode 100644 index a563de39f0..0000000000 --- a/platform/darwin/test/MGLStyleValueTests.h +++ /dev/null @@ -1,4 +0,0 @@ -#import <XCTest/XCTest.h> - -@interface MGLStyleValueTests : XCTestCase -@end diff --git a/platform/darwin/test/MGLStyleValueTests.m b/platform/darwin/test/MGLStyleValueTests.m deleted file mode 100644 index cd6eec8324..0000000000 --- a/platform/darwin/test/MGLStyleValueTests.m +++ /dev/null @@ -1,113 +0,0 @@ -#import <XCTest/XCTest.h> -#import <Mapbox/Mapbox.h> - -@interface MGLStyleValueTests : XCTestCase -@end - -@implementation MGLStyleValueTests - -- (void)testStoplessFunction { - XCTAssertThrowsSpecificNamed([MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential cameraStops:@{} options:nil], NSException, NSInvalidArgumentException, @"Stopless function should raise an exception"); -} - -- (void)testDeprecatedFunctions { - MGLShapeSource *shapeSource = [[MGLShapeSource alloc] initWithIdentifier:@"test" - shape:nil - options:nil]; - MGLSymbolStyleLayer *symbolStyleLayer = [[MGLSymbolStyleLayer alloc] initWithIdentifier:@"symbolLayer" - source:shapeSource]; - MGLCircleStyleLayer *circleStyleLayer = [[MGLCircleStyleLayer alloc] initWithIdentifier:@"circleLayer" - source:shapeSource]; - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - // deprecated function, stops with float values - NSDictionary<NSNumber *, MGLStyleValue<NSNumber *> *> *stops = @{ - @1: [MGLStyleValue<NSNumber *> valueWithRawValue:@0], - @2: [MGLStyleValue<NSNumber *> valueWithRawValue:@1], - @3: [MGLStyleValue<NSNumber *> valueWithRawValue:@2], - @4: [MGLStyleValue<NSNumber *> valueWithRawValue:@0], - }; - MGLStyleValue<NSNumber *> *iconHaloBlurStyleValue = - [MGLStyleValue<NSNumber *> valueWithInterpolationBase:1.0 stops:stops]; - symbolStyleLayer.iconHaloBlur = iconHaloBlurStyleValue; - XCTAssertEqualObjects(symbolStyleLayer.iconHaloBlur, iconHaloBlurStyleValue); - - // deprecated function, stops with boolean values - stops = @{ - @1: [MGLStyleValue<NSNumber *> valueWithRawValue:@YES], - @2: [MGLStyleValue<NSNumber *> valueWithRawValue:@NO], - @3: [MGLStyleValue<NSNumber *> valueWithRawValue:@YES], - @4: [MGLStyleValue<NSNumber *> valueWithRawValue:@NO], - }; - MGLStyleValue<NSNumber *> *iconAllowsOverlapStyleValue = - [MGLStyleValue<NSNumber *> valueWithInterpolationBase:1.0 stops:stops]; - symbolStyleLayer.iconAllowsOverlap = iconAllowsOverlapStyleValue; - // iconAllowsOverlap is boolean so mgl and mbgl conversions will coerce the developers stops into interval stops - MGLStyleValue<NSNumber *> *expectedIconAllowsOverlapStyleValue = - [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval - cameraStops:stops - options:nil]; - XCTAssertEqualObjects(symbolStyleLayer.iconAllowsOverlap, expectedIconAllowsOverlapStyleValue); - - /// - // creating and using MGLStyleFunctions directly - /// - - NSDictionary<NSNumber *, MGLStyleValue<NSNumber *> *> *circleRadiusStops = @{ - @0: [MGLStyleValue<NSNumber *> valueWithRawValue:@10], - @20: [MGLStyleValue<NSNumber *> valueWithRawValue:@5], - }; - MGLStyleFunction<NSNumber *> *circleRadiusFunction = - [MGLStyleFunction<NSNumber *> functionWithInterpolationBase:1.0 - stops:circleRadiusStops]; - circleStyleLayer.circleRadius = circleRadiusFunction; - MGLStyleValue<NSNumber *> *expectedCircleRadiusFunction = - [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential - cameraStops:circleRadiusStops - options:nil]; - // setting a data driven property to an MGLStyleFunction should return an exponential camera function - XCTAssertEqualObjects(circleStyleLayer.circleRadius, expectedCircleRadiusFunction); - - CGVector circleTranslationOne = CGVectorMake(100, 0); - CGVector circleTranslationTwo = CGVectorMake(0, 0); -#if TARGET_OS_IPHONE - NSValue *circleTranslationValueOne = [NSValue valueWithCGVector:circleTranslationOne]; - NSValue *circleTranslationValueTwo = [NSValue valueWithCGVector:circleTranslationTwo]; -#else - NSValue *circleTranslationValueOne = [NSValue value:&circleTranslationOne withObjCType:@encode(CGVector)]; - NSValue *circleTranslationValueTwo = [NSValue value:&circleTranslationTwo withObjCType:@encode(CGVector)]; -#endif - - NSDictionary<NSNumber *, MGLStyleValue<NSValue *> *> *circleTranslationStops = @{ - @0: [MGLStyleValue<NSValue *> valueWithRawValue:circleTranslationValueOne], - @10: [MGLStyleValue<NSValue *> valueWithRawValue:circleTranslationValueTwo], - }; - MGLStyleFunction<NSValue *> *circleTranslationFunction = - [MGLStyleFunction<NSValue *> functionWithInterpolationBase:1.0 - stops:circleTranslationStops]; - circleStyleLayer.circleTranslation = circleTranslationFunction; - MGLStyleValue<NSValue *> *expectedCircleTranslationFunction = - [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeExponential - cameraStops:circleTranslationStops - options:nil]; - // setting a non-data driven, interpolatable property to an MGLStyleFunction should return an exponential camera function - XCTAssertEqualObjects(circleStyleLayer.circleTranslation, expectedCircleTranslationFunction); - - NSDictionary<NSNumber *, MGLStyleValue<NSNumber *> *> *iconOptionalStops = @{ - @0: [MGLStyleValue<NSNumber *> valueWithRawValue:@NO], - @20: [MGLStyleValue<NSNumber *> valueWithRawValue:@YES], - }; - MGLStyleFunction<NSNumber *> *iconOptionalFunction = - [MGLStyleFunction<NSNumber *> valueWithInterpolationBase:1.0 - stops:iconOptionalStops]; - symbolStyleLayer.iconOptional = iconOptionalFunction; - MGLStyleValue<NSNumber *> *expectedIconOptionalFunction = - [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval - cameraStops:iconOptionalStops - options:nil]; - XCTAssertEqualObjects(symbolStyleLayer.iconOptional, expectedIconOptionalFunction); -#pragma clang diagnostic pop -} - -@end diff --git a/platform/darwin/test/MGLStyleValueTests.swift b/platform/darwin/test/MGLStyleValueTests.swift deleted file mode 100644 index c559037588..0000000000 --- a/platform/darwin/test/MGLStyleValueTests.swift +++ /dev/null @@ -1,362 +0,0 @@ -import XCTest -import Mapbox - -#if os(iOS) || os(watchOS) || os(tvOS) -typealias MGLColor = UIColor -#elseif os(macOS) -typealias MGLColor = NSColor -#endif - -#if swift(>=3.2) -#else -func XCTAssertEqual<T: FloatingPoint>(_ lhs: @autoclosure () throws -> T, _ rhs: @autoclosure () throws -> T, accuracy: T) { - XCTAssertEqualWithAccuracy(lhs, rhs, accuracy: accuracy) -} -#endif - -extension MGLStyleValueTests { - - struct Color { - var red: CGFloat = 0 - var green: CGFloat = 0 - var blue: CGFloat = 0 - var alpha: CGFloat = 0 - } - - func assertColorsEqualWithAccuracy(_ actual: MGLColor, _ expected: MGLColor, accuracy: Float = 1/255) { - var actualColor = Color() - var expectedColor = Color() - - actual.getRed(&actualColor.red, green: &actualColor.green, blue: &actualColor.blue, alpha: &actualColor.alpha) - expected.getRed(&expectedColor.red, green: &expectedColor.green, blue: &expectedColor.blue, alpha: &expectedColor.alpha) - - XCTAssertEqual(Float(actualColor.red), Float(expectedColor.red), accuracy: accuracy) - XCTAssertEqual(Float(actualColor.green), Float(expectedColor.green), accuracy: accuracy) - XCTAssertEqual(Float(actualColor.blue), Float(expectedColor.blue), accuracy: accuracy) - XCTAssertEqual(Float(actualColor.alpha), Float(expectedColor.alpha), accuracy: accuracy) - } - - func assertColorValuesEqual(_ actual: MGLStyleValue<MGLColor>, _ expected: MGLStyleValue<MGLColor>) { - guard type(of: actual) == type(of: expected) else { - XCTFail("Expected \(type(of: expected)), but found \(type(of: actual)) instead.") - return - } - - if let actualConstant = actual as? MGLConstantStyleValue<MGLColor> { - assertColorsEqualWithAccuracy(actualConstant.rawValue, (expected as! MGLConstantStyleValue<MGLColor>).rawValue) - } else if let actualFunction = actual as? MGLStyleFunction<MGLColor>, - let expectedFunction = expected as? MGLStyleFunction<MGLColor> { - - // unless we have stops, there's no need for a custom comparison - default to plain == assertion - guard let actualStops = actualFunction.stops, let expectedStops = expectedFunction.stops else { - XCTAssertEqual(actualFunction, expectedFunction) - return - } - - guard expectedStops is [String: Any] || expectedStops is [Float:Any] else { - XCTFail("Stop levels must be String or Float.") - return - } - - XCTAssertEqual(actualFunction.interpolationBase, expectedFunction.interpolationBase) - XCTAssertEqual(actualFunction.interpolationMode, expectedFunction.interpolationMode) - if let actualFunction = actualFunction as? MGLSourceStyleFunction<MGLColor>, - let expectedFunction = expectedFunction as? MGLSourceStyleFunction<MGLColor> { - XCTAssertEqual(actualFunction.defaultValue, expectedFunction.defaultValue) - } else if let actualFunction = actualFunction as? MGLCompositeStyleFunction<MGLColor>, - let expectedFunction = expectedFunction as? MGLCompositeStyleFunction<MGLColor> { - XCTAssertEqual(actualFunction.defaultValue, expectedFunction.defaultValue) - } - - func assertStopEqual (_ actualValue: Any?, _ expectedValue: Any?) { - guard type(of: actualValue) == type(of: expectedValue) else { - XCTFail("Expected stop value of type \(type(of: expectedValue)), but found \(type(of: actualValue)) instead.") - return - } - if let actualValue = actualValue as? MGLConstantStyleValue<MGLColor>, - let expectedValue = expectedValue as? MGLConstantStyleValue<MGLColor> { - assertColorsEqualWithAccuracy(actualValue.rawValue, expectedValue.rawValue) - } else if let actualValue = actualValue as? MGLConstantStyleValue<AnyObject>, - let expectedValue = expectedValue as? MGLConstantStyleValue<AnyObject> { - XCTAssertEqual(actualValue, expectedValue) - } else { - XCTFail("Unsupported stop value type \(type(of: actualValue)).") - } - } - - XCTAssertEqual(actualStops.count, expectedStops.count) - if let actualStops = actualStops as? [String:Any], let expectedStops = expectedStops as? [String: Any] { - for (key, value) in actualStops { - assertStopEqual(value, expectedStops[key]) - } - } else if let actualStops = actualStops as? [Float:Any], let expectedStops = expectedStops as? [Float:Any] { - for (key, value) in actualStops { - assertStopEqual(value, expectedStops[key]) - } - } else { - XCTFail("Expected stops of type \(type(of: Array(expectedStops.keys)).Index.self), but found \(type(of: Array(actualStops.keys)).Index.self) instead.") - return - } - } else { - XCTFail("MGLStyleValue<MGLColor> must be either a constant or a style function.") - } - } - - func testConstantValues() { - let shapeSource = MGLShapeSource(identifier: "source", shape: nil, options: nil) - let symbolStyleLayer = MGLSymbolStyleLayer(identifier: "symbolLayer", source: shapeSource) - let circleStyleLayer = MGLCircleStyleLayer(identifier: "circleLayer", source: shapeSource) - - // Boolean - symbolStyleLayer.iconAllowsOverlap = MGLConstantStyleValue(rawValue: true) - XCTAssertEqual((symbolStyleLayer.iconAllowsOverlap as! MGLConstantStyleValue<NSNumber>).rawValue, true) - - // Number - symbolStyleLayer.iconHaloWidth = MGLConstantStyleValue(rawValue: 3) - XCTAssertEqual((symbolStyleLayer.iconHaloWidth as! MGLConstantStyleValue<NSNumber>).rawValue, 3) - - // String - symbolStyleLayer.text = MGLConstantStyleValue(rawValue: "{name}") - XCTAssertEqual((symbolStyleLayer.text as! MGLConstantStyleValue<NSString>).rawValue, "{name}") - - var circleTranslationOne = CGVector(dx: 100, dy: 0) - let circleTranslationValueOne = NSValue(bytes: &circleTranslationOne, objCType: "{CGVector=dd}") - - // non-data-driven (interpolatable property value), set to constant style value - let expectedCircleTranslationValue = MGLStyleValue<NSValue>(rawValue: circleTranslationValueOne) - circleStyleLayer.circleTranslation = expectedCircleTranslationValue - XCTAssertEqual(circleStyleLayer.circleTranslation, expectedCircleTranslationValue) - - // non-data-driven (enumeration property value), set to constant style value - let expectedCircleScaleAlignmentValue = MGLStyleValue<NSValue>(rawValue: NSValue(mglCircleScaleAlignment: .map)) - circleStyleLayer.circleScaleAlignment = expectedCircleScaleAlignmentValue - XCTAssertEqual(circleStyleLayer.circleScaleAlignment, expectedCircleScaleAlignmentValue) - } - - func testFunctionsWithNonDataDrivenProperties() { - let shapeSource = MGLShapeSource(identifier: "test", shape: nil, options: nil) - let circleStyleLayer = MGLCircleStyleLayer(identifier: "circleLayer", source: shapeSource) - - var circleTranslationOne = CGVector(dx: 100, dy: 0) - let circleTranslationValueOne = NSValue(bytes: &circleTranslationOne, objCType: "{CGVector=dd}") - var circleTranslationTwo = CGVector(dx: 0, dy: 0) - let circleTranslationValueTwo = NSValue(bytes: &circleTranslationTwo, objCType: "{CGVector=dd}") - - let circleTranslationStops : [Float:MGLStyleValue<NSValue>] = [ - 0: MGLStyleValue<NSValue>(rawValue: circleTranslationValueOne), - 10: MGLStyleValue<NSValue>(rawValue: circleTranslationValueTwo) - ] - - // non-data-driven (interpolatable property value), camera function with CGVector (NSValue) stop values - let expectedCircleTranslationValue = MGLStyleValue<NSValue>( - interpolationMode: .interval, - cameraStops: circleTranslationStops, - options: nil - ) - circleStyleLayer.circleTranslation = expectedCircleTranslationValue - XCTAssertEqual(circleStyleLayer.circleTranslation, expectedCircleTranslationValue) - - // non-data-driven (enumeration property value), camera function with MGLCircleScaleAlignment enum (NSValue) stop values - let scaleAlignmentStops : [Float:MGLStyleValue<NSValue>] = [ - 0: MGLStyleValue(rawValue: NSValue(mglCircleScaleAlignment: .map)), - 10: MGLStyleValue(rawValue: NSValue(mglCircleScaleAlignment: .viewport)) - ] - let expectedCircleScaleAlignmentValue = MGLStyleValue<NSValue>( - interpolationMode: .interval, - cameraStops: scaleAlignmentStops, - options: nil - ) - circleStyleLayer.circleScaleAlignment = expectedCircleScaleAlignmentValue - XCTAssertEqual(circleStyleLayer.circleScaleAlignment, expectedCircleScaleAlignmentValue) - } - - func testFunctionsWithDataDrivenProperties() { - let shapeSource = MGLShapeSource(identifier: "test", shape: nil, options: nil) - let circleStyleLayer = MGLCircleStyleLayer(identifier: "circleLayer", source: shapeSource) - - // data-driven, camera function with exponential color stop values - let redGreenStops : [Float:MGLStyleValue<MGLColor>] = [ - 0: MGLStyleValue<MGLColor>(rawValue: .red), - 10: MGLStyleValue<MGLColor>(rawValue: .red), - 15: MGLStyleValue<MGLColor>(rawValue: .green) - ] - let expectedCircleColorValue = MGLStyleValue<MGLColor>( - interpolationMode: .exponential, - cameraStops: redGreenStops, - options: [.interpolationBase: 10.0] - ) - circleStyleLayer.circleColor = expectedCircleColorValue - assertColorValuesEqual(circleStyleLayer.circleColor as! MGLStyleFunction<MGLColor>, expectedCircleColorValue as! MGLStyleFunction<MGLColor>) - - // data-driven, source function with categorical color stop values with string attribute keys - let redOnlyStops = [ - "red": MGLStyleValue<MGLColor>(rawValue: .red) - ] - let expectedRedCategoricalValue = MGLStyleValue<MGLColor>( - interpolationMode: .categorical, - sourceStops: redOnlyStops, - attributeName: "red", - options: [.defaultValue: MGLStyleValue<MGLColor>(rawValue: .cyan)] - ) - circleStyleLayer.circleColor = expectedRedCategoricalValue - assertColorValuesEqual(circleStyleLayer.circleColor, expectedRedCategoricalValue) - - // data-driven, source function with categorical color stop values with integer attribute keys - let greenOrangeStops : [Float:MGLStyleValue<MGLColor>] = [ - 0: MGLStyleValue<MGLColor>(rawValue: .green), - 100: MGLStyleValue<MGLColor>(rawValue: .orange) - ] - let expectedGreenOrangeCategoricalValue = MGLStyleValue<MGLColor>( - interpolationMode: .categorical, - sourceStops: greenOrangeStops, - attributeName: "temp", - options: [.defaultValue: MGLStyleValue<MGLColor>(rawValue: .red)] - ) - circleStyleLayer.circleColor = expectedGreenOrangeCategoricalValue - assertColorValuesEqual(circleStyleLayer.circleColor, expectedGreenOrangeCategoricalValue) - - // data-driven, source function with exponential color stop values - let expectedRedGreenSourceExponentialValue = MGLStyleValue<MGLColor>( - interpolationMode: .exponential, - sourceStops: redGreenStops, - attributeName: "temp", - options: nil - ) - circleStyleLayer.circleColor = expectedRedGreenSourceExponentialValue - assertColorValuesEqual(circleStyleLayer.circleColor, expectedRedGreenSourceExponentialValue) - - // data-driven, identity source function - let expectedSourceIdentityValue = MGLStyleValue<MGLColor>( - interpolationMode: .identity, - sourceStops: nil, - attributeName: "size", - options: [.defaultValue: MGLStyleValue<MGLColor>(rawValue: .green)] - ) - circleStyleLayer.circleColor = expectedSourceIdentityValue - assertColorValuesEqual(circleStyleLayer.circleColor, expectedSourceIdentityValue) - - // data-driven, source function with categorical color stop values with boolean attribute keys - let booleanCategoricalStops = [ - false: MGLStyleValue<NSNumber>(rawValue: 0), - true: MGLStyleValue<NSNumber>(rawValue: 2) - ] - let expectedCircleBlurCategoricalValue = MGLStyleValue<NSNumber>( - interpolationMode: .categorical, - sourceStops: booleanCategoricalStops, - attributeName: "fuzzy", - options: [.defaultValue: MGLStyleValue<NSNumber>(rawValue: 42)] - ) - circleStyleLayer.circleBlur = expectedCircleBlurCategoricalValue - XCTAssertEqual(circleStyleLayer.circleBlur, expectedCircleBlurCategoricalValue) - - // data-driven, composite function with inner categorical color stop values with string attribute keys nested in outer camera stops - let smallRadius = MGLStyleValue<NSNumber>(rawValue: 5) - let mediumRadius = MGLStyleValue<NSNumber>(rawValue: 10) - let largeRadius = MGLStyleValue<NSNumber>(rawValue: 20) - let radiusCompositeCategoricalStops: [Float: [String: MGLStyleValue<NSNumber>]] = [ - 0: ["green": smallRadius], - 10: ["green": smallRadius], - 15: ["green": largeRadius], - 20: ["green": largeRadius] - ] - let defaultRadius = MGLStyleValue<NSNumber>(rawValue: 2) - let expectedCompositeCategoricalValue = MGLStyleValue<NSNumber>( - interpolationMode: .categorical, - compositeStops: radiusCompositeCategoricalStops, - attributeName: "color", - options: [.defaultValue: defaultRadius] - ) - circleStyleLayer.circleRadius = expectedCompositeCategoricalValue - - var compositeValue = circleStyleLayer.circleRadius as! MGLCompositeStyleFunction - var expectedCompositeValue = expectedCompositeCategoricalValue as! MGLCompositeStyleFunction - XCTAssertEqual(compositeValue.attributeName, expectedCompositeValue.attributeName) - XCTAssertEqual(compositeValue.stops as NSDictionary, radiusCompositeCategoricalStops as NSDictionary) - XCTAssertEqual(compositeValue.interpolationMode, expectedCompositeValue.interpolationMode) - XCTAssertEqual(compositeValue.defaultValue, expectedCompositeValue.defaultValue) - - // data-driven, composite function with inner exponential color stop values nested in outer camera stops - let radiusCompositeExponentialOrIntervalStops: [Float: [Float: MGLStyleValue<NSNumber>]] = [ - 0: [0: MGLStyleValue<NSNumber>(rawValue: 5)], - 10: [200: MGLStyleValue<NSNumber>(rawValue: 5)], - 20: [200: MGLStyleValue<NSNumber>(rawValue: 20)] - ] - - let expectedStops = [ - 0: [0: MGLStyleValue<NSNumber>(rawValue: 5)], - 10: [200: MGLStyleValue<NSNumber>(rawValue: 5)], - 20: [200: MGLStyleValue<NSNumber>(rawValue: 20)] - ] - circleStyleLayer.circleRadius = MGLStyleValue<NSNumber>( - interpolationMode: .exponential, - compositeStops: [ - 0: [0: MGLStyleValue<NSNumber>(rawValue: 5)], - 10: [200: MGLStyleValue<NSNumber>(rawValue: 5)], - 20: [200: MGLStyleValue<NSNumber>(rawValue: 20)] - ], - attributeName: "temp", - options: [.defaultValue: mediumRadius] - ) - - let expectedCompositeExponentialValue = MGLStyleValue<NSNumber>( - interpolationMode: .exponential, - compositeStops: radiusCompositeExponentialOrIntervalStops, - attributeName: "temp", - options: [.defaultValue: mediumRadius] - ) - - compositeValue = circleStyleLayer.circleRadius as! MGLCompositeStyleFunction - expectedCompositeValue = expectedCompositeExponentialValue as! MGLCompositeStyleFunction - XCTAssertEqual(compositeValue.attributeName, expectedCompositeValue.attributeName) - XCTAssertEqual(compositeValue.stops as NSDictionary, expectedStops as NSDictionary) - XCTAssertEqual(compositeValue.interpolationMode, expectedCompositeValue.interpolationMode) - XCTAssertEqual(compositeValue.defaultValue, expectedCompositeValue.defaultValue) - - // get a value back - if let returnedCircleRadius = circleStyleLayer.circleRadius as? MGLCompositeStyleFunction<NSNumber> { - if let returnedStops = returnedCircleRadius.stops as NSDictionary? as? [NSNumber: [NSNumber: MGLStyleValue<NSNumber>]] { - let lhs: MGLStyleValue<NSNumber> = returnedStops[0]!.values.first! - let rhs: MGLStyleValue<NSNumber> = radiusCompositeExponentialOrIntervalStops[0]!.values.first! - XCTAssertEqual(lhs, rhs) - } - } - - // get value back as base class - if let returnedCircleRadius = circleStyleLayer.circleRadius as? MGLStyleFunction<NSNumber> { - if let returnedStops = returnedCircleRadius.stops as NSDictionary? as? [NSNumber: [NSNumber: MGLStyleValue<NSNumber>]] { - let lhs: MGLStyleValue<NSNumber> = returnedStops[0]!.values.first! - let rhs: MGLStyleValue<NSNumber> = radiusCompositeExponentialOrIntervalStops[0]!.values.first! - XCTAssertEqual(lhs, rhs) - } - } - - // data-driven, composite function with inner interval color stop values nested in outer camera stops - let expectedCompositeIntervalValue = MGLStyleValue<NSNumber>( - interpolationMode: .interval, - compositeStops: [ - - 10: [200: MGLStyleValue<NSNumber>(rawValue: 5)], - 20: [200: MGLStyleValue<NSNumber>(rawValue: 20)] - ], - attributeName: "temp", - options: nil - ) - circleStyleLayer.circleRadius = MGLStyleValue<NSNumber>( - interpolationMode: .interval, - compositeStops: [ - 0: [0: MGLStyleValue<NSNumber>(rawValue: 5)], - 10: [200: MGLStyleValue<NSNumber>(rawValue: 5)], - 20: [200: MGLStyleValue<NSNumber>(rawValue: 20)] - ], - attributeName: "temp", - options: nil - ) - - compositeValue = circleStyleLayer.circleRadius as! MGLCompositeStyleFunction - expectedCompositeValue = expectedCompositeIntervalValue as! MGLCompositeStyleFunction - XCTAssertEqual(compositeValue.attributeName, expectedCompositeValue.attributeName) - XCTAssertEqual(compositeValue.stops as NSDictionary, expectedStops as NSDictionary) - XCTAssertEqual(compositeValue.interpolationMode, expectedCompositeValue.interpolationMode) - XCTAssertEqual(compositeValue.defaultValue, expectedCompositeValue.defaultValue) - } -} diff --git a/platform/darwin/test/MGLSymbolStyleLayerTests.mm b/platform/darwin/test/MGLSymbolStyleLayerTests.mm index 1ac86dd402..cf2f80125a 100644 --- a/platform/darwin/test/MGLSymbolStyleLayerTests.mm +++ b/platform/darwin/test/MGLSymbolStyleLayerTests.mm @@ -30,8 +30,8 @@ XCTAssertNil(layer.sourceLayerIdentifier); XCTAssertNil(layer.predicate); - layer.predicate = [NSPredicate predicateWithValue:NO]; - XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithValue:NO]); + layer.predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]; + XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithFormat:@"$featureIdentifier = 1"]); layer.predicate = nil; XCTAssertNil(layer.predicate); } @@ -52,72 +52,81 @@ { XCTAssertTrue(rawLayer->getIconAllowOverlap().isUndefined(), @"icon-allow-overlap should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.iconAllowsOverlap; + NSExpression *defaultExpression = layer.iconAllowsOverlap; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@YES]; - layer.iconAllowsOverlap = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"true"]; + layer.iconAllowsOverlap = constantExpression; mbgl::style::PropertyValue<bool> propertyValue = { true }; XCTAssertEqual(rawLayer->getIconAllowOverlap(), propertyValue, - @"Setting iconAllowsOverlap to a constant value should update icon-allow-overlap."); - XCTAssertEqualObjects(layer.iconAllowsOverlap, constantStyleValue, - @"iconAllowsOverlap should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.iconAllowsOverlap = functionStyleValue; - - mbgl::style::IntervalStops<bool> intervalStops = { {{18, true}} }; + @"Setting iconAllowsOverlap to a constant value expression should update icon-allow-overlap."); + XCTAssertEqualObjects(layer.iconAllowsOverlap, constantExpression, + @"iconAllowsOverlap should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"true"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.iconAllowsOverlap = functionExpression; + + mbgl::style::IntervalStops<bool> intervalStops = {{ + { -INFINITY, true }, + { 18, true }, + }}; propertyValue = mbgl::style::CameraFunction<bool> { intervalStops }; XCTAssertEqual(rawLayer->getIconAllowOverlap(), propertyValue, - @"Setting iconAllowsOverlap to a camera function should update icon-allow-overlap."); - XCTAssertEqualObjects(layer.iconAllowsOverlap, functionStyleValue, - @"iconAllowsOverlap should round-trip camera functions."); + @"Setting iconAllowsOverlap to a camera expression should update icon-allow-overlap."); + XCTAssertEqualObjects(layer.iconAllowsOverlap, functionExpression, + @"iconAllowsOverlap should round-trip camera expressions."); layer.iconAllowsOverlap = nil; XCTAssertTrue(rawLayer->getIconAllowOverlap().isUndefined(), @"Unsetting iconAllowsOverlap should return icon-allow-overlap to the default value."); - XCTAssertEqualObjects(layer.iconAllowsOverlap, defaultStyleValue, + XCTAssertEqualObjects(layer.iconAllowsOverlap, defaultExpression, @"iconAllowsOverlap should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.iconAllowsOverlap = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.iconAllowsOverlap = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.iconAllowsOverlap = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.iconAllowsOverlap = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // icon-anchor { XCTAssertTrue(rawLayer->getIconAnchor().isUndefined(), @"icon-anchor should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.iconAnchor; + NSExpression *defaultExpression = layer.iconAnchor; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLIconAnchor:MGLIconAnchorBottomRight]]; - layer.iconAnchor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'bottom-right'"]; + layer.iconAnchor = constantExpression; mbgl::style::DataDrivenPropertyValue<mbgl::style::SymbolAnchorType> propertyValue = { mbgl::style::SymbolAnchorType::BottomRight }; XCTAssertEqual(rawLayer->getIconAnchor(), propertyValue, - @"Setting iconAnchor to a constant value should update icon-anchor."); - XCTAssertEqualObjects(layer.iconAnchor, constantStyleValue, - @"iconAnchor should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.iconAnchor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::SymbolAnchorType> intervalStops = { {{18, mbgl::style::SymbolAnchorType::BottomRight}} }; + @"Setting iconAnchor to a constant value expression should update icon-anchor."); + XCTAssertEqualObjects(layer.iconAnchor, constantExpression, + @"iconAnchor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'bottom-right'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.iconAnchor = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::SymbolAnchorType> intervalStops = {{ + { -INFINITY, mbgl::style::SymbolAnchorType::BottomRight }, + { 18, mbgl::style::SymbolAnchorType::BottomRight }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::SymbolAnchorType> { intervalStops }; XCTAssertEqual(rawLayer->getIconAnchor(), propertyValue, - @"Setting iconAnchor to a camera function should update icon-anchor."); - XCTAssertEqualObjects(layer.iconAnchor, functionStyleValue, - @"iconAnchor should round-trip camera functions."); + @"Setting iconAnchor to a camera expression should update icon-anchor."); + XCTAssertEqualObjects(layer.iconAnchor, functionExpression, + @"iconAnchor should round-trip camera expressions."); layer.iconAnchor = nil; XCTAssertTrue(rawLayer->getIconAnchor().isUndefined(), @"Unsetting iconAnchor should return icon-anchor to the default value."); - XCTAssertEqualObjects(layer.iconAnchor, defaultStyleValue, + XCTAssertEqualObjects(layer.iconAnchor, defaultExpression, @"iconAnchor should return the default value after being unset."); } @@ -125,72 +134,81 @@ { XCTAssertTrue(rawLayer->getIconIgnorePlacement().isUndefined(), @"icon-ignore-placement should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.iconIgnoresPlacement; + NSExpression *defaultExpression = layer.iconIgnoresPlacement; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@YES]; - layer.iconIgnoresPlacement = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"true"]; + layer.iconIgnoresPlacement = constantExpression; mbgl::style::PropertyValue<bool> propertyValue = { true }; XCTAssertEqual(rawLayer->getIconIgnorePlacement(), propertyValue, - @"Setting iconIgnoresPlacement to a constant value should update icon-ignore-placement."); - XCTAssertEqualObjects(layer.iconIgnoresPlacement, constantStyleValue, - @"iconIgnoresPlacement should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.iconIgnoresPlacement = functionStyleValue; - - mbgl::style::IntervalStops<bool> intervalStops = { {{18, true}} }; + @"Setting iconIgnoresPlacement to a constant value expression should update icon-ignore-placement."); + XCTAssertEqualObjects(layer.iconIgnoresPlacement, constantExpression, + @"iconIgnoresPlacement should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"true"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.iconIgnoresPlacement = functionExpression; + + mbgl::style::IntervalStops<bool> intervalStops = {{ + { -INFINITY, true }, + { 18, true }, + }}; propertyValue = mbgl::style::CameraFunction<bool> { intervalStops }; XCTAssertEqual(rawLayer->getIconIgnorePlacement(), propertyValue, - @"Setting iconIgnoresPlacement to a camera function should update icon-ignore-placement."); - XCTAssertEqualObjects(layer.iconIgnoresPlacement, functionStyleValue, - @"iconIgnoresPlacement should round-trip camera functions."); + @"Setting iconIgnoresPlacement to a camera expression should update icon-ignore-placement."); + XCTAssertEqualObjects(layer.iconIgnoresPlacement, functionExpression, + @"iconIgnoresPlacement should round-trip camera expressions."); layer.iconIgnoresPlacement = nil; XCTAssertTrue(rawLayer->getIconIgnorePlacement().isUndefined(), @"Unsetting iconIgnoresPlacement should return icon-ignore-placement to the default value."); - XCTAssertEqualObjects(layer.iconIgnoresPlacement, defaultStyleValue, + XCTAssertEqualObjects(layer.iconIgnoresPlacement, defaultExpression, @"iconIgnoresPlacement should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.iconIgnoresPlacement = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.iconIgnoresPlacement = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.iconIgnoresPlacement = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.iconIgnoresPlacement = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // icon-image { XCTAssertTrue(rawLayer->getIconImage().isUndefined(), @"icon-image should be unset initially."); - MGLStyleValue<NSString *> *defaultStyleValue = layer.iconImageName; + NSExpression *defaultExpression = layer.iconImageName; - MGLStyleValue<NSString *> *constantStyleValue = [MGLStyleValue<NSString *> valueWithRawValue:@"Icon Image"]; - layer.iconImageName = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'Icon Image'"]; + layer.iconImageName = constantExpression; mbgl::style::DataDrivenPropertyValue<std::string> propertyValue = { "Icon Image" }; XCTAssertEqual(rawLayer->getIconImage(), propertyValue, - @"Setting iconImageName to a constant value should update icon-image."); - XCTAssertEqualObjects(layer.iconImageName, constantStyleValue, - @"iconImageName should round-trip constant values."); - - MGLStyleValue<NSString *> * functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.iconImageName = functionStyleValue; - - mbgl::style::IntervalStops<std::string> intervalStops = { {{18, "Icon Image"}} }; + @"Setting iconImageName to a constant value expression should update icon-image."); + XCTAssertEqualObjects(layer.iconImageName, constantExpression, + @"iconImageName should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'Icon Image'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.iconImageName = functionExpression; + + mbgl::style::IntervalStops<std::string> intervalStops = {{ + { -INFINITY, "Icon Image" }, + { 18, "Icon Image" }, + }}; propertyValue = mbgl::style::CameraFunction<std::string> { intervalStops }; XCTAssertEqual(rawLayer->getIconImage(), propertyValue, - @"Setting iconImageName to a camera function should update icon-image."); - XCTAssertEqualObjects(layer.iconImageName, functionStyleValue, - @"iconImageName should round-trip camera functions."); + @"Setting iconImageName to a camera expression should update icon-image."); + XCTAssertEqualObjects(layer.iconImageName, functionExpression, + @"iconImageName should round-trip camera expressions."); layer.iconImageName = nil; XCTAssertTrue(rawLayer->getIconImage().isUndefined(), @"Unsetting iconImageName should return icon-image to the default value."); - XCTAssertEqualObjects(layer.iconImageName, defaultStyleValue, + XCTAssertEqualObjects(layer.iconImageName, defaultExpression, @"iconImageName should return the default value after being unset."); } @@ -198,46 +216,51 @@ { XCTAssertTrue(rawLayer->getIconOffset().isUndefined(), @"icon-offset should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.iconOffset; + NSExpression *defaultExpression = layer.iconOffset; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue: + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", #if TARGET_OS_IPHONE [NSValue valueWithCGVector:CGVectorMake(1, 1)] #else [NSValue valueWithMGLVector:CGVectorMake(1, -1)] #endif ]; - layer.iconOffset = constantStyleValue; + layer.iconOffset = constantExpression; mbgl::style::DataDrivenPropertyValue<std::array<float, 2>> propertyValue = { { 1, 1 } }; XCTAssertEqual(rawLayer->getIconOffset(), propertyValue, - @"Setting iconOffset to a constant value should update icon-offset."); - XCTAssertEqualObjects(layer.iconOffset, constantStyleValue, - @"iconOffset should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.iconOffset = functionStyleValue; - - mbgl::style::IntervalStops<std::array<float, 2>> intervalStops = { {{18, { 1, 1 }}} }; + @"Setting iconOffset to a constant value expression should update icon-offset."); + XCTAssertEqualObjects(layer.iconOffset, constantExpression, + @"iconOffset should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"{1, 1}"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.iconOffset = functionExpression; + + mbgl::style::IntervalStops<std::array<float, 2>> intervalStops = {{ + { -INFINITY, { 1, 1 } }, + { 18, { 1, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<std::array<float, 2>> { intervalStops }; XCTAssertEqual(rawLayer->getIconOffset(), propertyValue, - @"Setting iconOffset to a camera function should update icon-offset."); - XCTAssertEqualObjects(layer.iconOffset, functionStyleValue, - @"iconOffset should round-trip camera functions."); + @"Setting iconOffset to a camera expression should update icon-offset."); + XCTAssertEqualObjects(layer.iconOffset, functionExpression, + @"iconOffset should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.iconOffset = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.iconOffset = functionExpression; mbgl::style::ExponentialStops<std::array<float, 2>> exponentialStops = { {{18, { 1, 1 }}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<std::array<float, 2>> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getIconOffset(), propertyValue, - @"Setting iconOffset to a source function should update icon-offset."); - XCTAssertEqualObjects(layer.iconOffset, functionStyleValue, - @"iconOffset should round-trip source functions."); + @"Setting iconOffset to a data expression should update icon-offset."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.iconOffset, pedanticFunctionExpression, + @"iconOffset should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.iconOffset = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.iconOffset = functionExpression; std::map<float, std::array<float, 2>> innerStops { {18, { 1, 1 }} }; mbgl::style::CompositeExponentialStops<std::array<float, 2>> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -245,15 +268,16 @@ propertyValue = mbgl::style::CompositeFunction<std::array<float, 2>> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getIconOffset(), propertyValue, - @"Setting iconOffset to a composite function should update icon-offset."); - XCTAssertEqualObjects(layer.iconOffset, functionStyleValue, - @"iconOffset should round-trip composite functions."); + @"Setting iconOffset to a camera-data expression should update icon-offset."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.iconOffset, pedanticFunctionExpression, + @"iconOffset should round-trip camera-data expressions."); layer.iconOffset = nil; XCTAssertTrue(rawLayer->getIconOffset().isUndefined(), @"Unsetting iconOffset should return icon-offset to the default value."); - XCTAssertEqualObjects(layer.iconOffset, defaultStyleValue, + XCTAssertEqualObjects(layer.iconOffset, defaultExpression, @"iconOffset should return the default value after being unset."); } @@ -261,157 +285,177 @@ { XCTAssertTrue(rawLayer->getIconOptional().isUndefined(), @"icon-optional should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.iconOptional; + NSExpression *defaultExpression = layer.iconOptional; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@YES]; - layer.iconOptional = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"true"]; + layer.iconOptional = constantExpression; mbgl::style::PropertyValue<bool> propertyValue = { true }; XCTAssertEqual(rawLayer->getIconOptional(), propertyValue, - @"Setting iconOptional to a constant value should update icon-optional."); - XCTAssertEqualObjects(layer.iconOptional, constantStyleValue, - @"iconOptional should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.iconOptional = functionStyleValue; - - mbgl::style::IntervalStops<bool> intervalStops = { {{18, true}} }; + @"Setting iconOptional to a constant value expression should update icon-optional."); + XCTAssertEqualObjects(layer.iconOptional, constantExpression, + @"iconOptional should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"true"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.iconOptional = functionExpression; + + mbgl::style::IntervalStops<bool> intervalStops = {{ + { -INFINITY, true }, + { 18, true }, + }}; propertyValue = mbgl::style::CameraFunction<bool> { intervalStops }; XCTAssertEqual(rawLayer->getIconOptional(), propertyValue, - @"Setting iconOptional to a camera function should update icon-optional."); - XCTAssertEqualObjects(layer.iconOptional, functionStyleValue, - @"iconOptional should round-trip camera functions."); + @"Setting iconOptional to a camera expression should update icon-optional."); + XCTAssertEqualObjects(layer.iconOptional, functionExpression, + @"iconOptional should round-trip camera expressions."); layer.iconOptional = nil; XCTAssertTrue(rawLayer->getIconOptional().isUndefined(), @"Unsetting iconOptional should return icon-optional to the default value."); - XCTAssertEqualObjects(layer.iconOptional, defaultStyleValue, + XCTAssertEqualObjects(layer.iconOptional, defaultExpression, @"iconOptional should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.iconOptional = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.iconOptional = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.iconOptional = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.iconOptional = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // icon-padding { XCTAssertTrue(rawLayer->getIconPadding().isUndefined(), @"icon-padding should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.iconPadding; + NSExpression *defaultExpression = layer.iconPadding; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.iconPadding = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.iconPadding = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getIconPadding(), propertyValue, - @"Setting iconPadding to a constant value should update icon-padding."); - XCTAssertEqualObjects(layer.iconPadding, constantStyleValue, - @"iconPadding should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.iconPadding = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting iconPadding to a constant value expression should update icon-padding."); + XCTAssertEqualObjects(layer.iconPadding, constantExpression, + @"iconPadding should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.iconPadding = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getIconPadding(), propertyValue, - @"Setting iconPadding to a camera function should update icon-padding."); - XCTAssertEqualObjects(layer.iconPadding, functionStyleValue, - @"iconPadding should round-trip camera functions."); + @"Setting iconPadding to a camera expression should update icon-padding."); + XCTAssertEqualObjects(layer.iconPadding, functionExpression, + @"iconPadding should round-trip camera expressions."); layer.iconPadding = nil; XCTAssertTrue(rawLayer->getIconPadding().isUndefined(), @"Unsetting iconPadding should return icon-padding to the default value."); - XCTAssertEqualObjects(layer.iconPadding, defaultStyleValue, + XCTAssertEqualObjects(layer.iconPadding, defaultExpression, @"iconPadding should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.iconPadding = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.iconPadding = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.iconPadding = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.iconPadding = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // icon-pitch-alignment { XCTAssertTrue(rawLayer->getIconPitchAlignment().isUndefined(), @"icon-pitch-alignment should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.iconPitchAlignment; + NSExpression *defaultExpression = layer.iconPitchAlignment; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLIconPitchAlignment:MGLIconPitchAlignmentAuto]]; - layer.iconPitchAlignment = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'auto'"]; + layer.iconPitchAlignment = constantExpression; mbgl::style::PropertyValue<mbgl::style::AlignmentType> propertyValue = { mbgl::style::AlignmentType::Auto }; XCTAssertEqual(rawLayer->getIconPitchAlignment(), propertyValue, - @"Setting iconPitchAlignment to a constant value should update icon-pitch-alignment."); - XCTAssertEqualObjects(layer.iconPitchAlignment, constantStyleValue, - @"iconPitchAlignment should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.iconPitchAlignment = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::AlignmentType> intervalStops = { {{18, mbgl::style::AlignmentType::Auto}} }; + @"Setting iconPitchAlignment to a constant value expression should update icon-pitch-alignment."); + XCTAssertEqualObjects(layer.iconPitchAlignment, constantExpression, + @"iconPitchAlignment should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'auto'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.iconPitchAlignment = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::AlignmentType> intervalStops = {{ + { -INFINITY, mbgl::style::AlignmentType::Auto }, + { 18, mbgl::style::AlignmentType::Auto }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::AlignmentType> { intervalStops }; XCTAssertEqual(rawLayer->getIconPitchAlignment(), propertyValue, - @"Setting iconPitchAlignment to a camera function should update icon-pitch-alignment."); - XCTAssertEqualObjects(layer.iconPitchAlignment, functionStyleValue, - @"iconPitchAlignment should round-trip camera functions."); + @"Setting iconPitchAlignment to a camera expression should update icon-pitch-alignment."); + XCTAssertEqualObjects(layer.iconPitchAlignment, functionExpression, + @"iconPitchAlignment should round-trip camera expressions."); layer.iconPitchAlignment = nil; XCTAssertTrue(rawLayer->getIconPitchAlignment().isUndefined(), @"Unsetting iconPitchAlignment should return icon-pitch-alignment to the default value."); - XCTAssertEqualObjects(layer.iconPitchAlignment, defaultStyleValue, + XCTAssertEqualObjects(layer.iconPitchAlignment, defaultExpression, @"iconPitchAlignment should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.iconPitchAlignment = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.iconPitchAlignment = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.iconPitchAlignment = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.iconPitchAlignment = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // icon-rotate { XCTAssertTrue(rawLayer->getIconRotate().isUndefined(), @"icon-rotate should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.iconRotation; + NSExpression *defaultExpression = layer.iconRotation; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.iconRotation = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.iconRotation = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getIconRotate(), propertyValue, - @"Setting iconRotation to a constant value should update icon-rotate."); - XCTAssertEqualObjects(layer.iconRotation, constantStyleValue, - @"iconRotation should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.iconRotation = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting iconRotation to a constant value expression should update icon-rotate."); + XCTAssertEqualObjects(layer.iconRotation, constantExpression, + @"iconRotation should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.iconRotation = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getIconRotate(), propertyValue, - @"Setting iconRotation to a camera function should update icon-rotate."); - XCTAssertEqualObjects(layer.iconRotation, functionStyleValue, - @"iconRotation should round-trip camera functions."); + @"Setting iconRotation to a camera expression should update icon-rotate."); + XCTAssertEqualObjects(layer.iconRotation, functionExpression, + @"iconRotation should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.iconRotation = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.iconRotation = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getIconRotate(), propertyValue, - @"Setting iconRotation to a source function should update icon-rotate."); - XCTAssertEqualObjects(layer.iconRotation, functionStyleValue, - @"iconRotation should round-trip source functions."); + @"Setting iconRotation to a data expression should update icon-rotate."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.iconRotation, pedanticFunctionExpression, + @"iconRotation should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.iconRotation = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.iconRotation = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -419,15 +463,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getIconRotate(), propertyValue, - @"Setting iconRotation to a composite function should update icon-rotate."); - XCTAssertEqualObjects(layer.iconRotation, functionStyleValue, - @"iconRotation should round-trip composite functions."); + @"Setting iconRotation to a camera-data expression should update icon-rotate."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.iconRotation, pedanticFunctionExpression, + @"iconRotation should round-trip camera-data expressions."); layer.iconRotation = nil; XCTAssertTrue(rawLayer->getIconRotate().isUndefined(), @"Unsetting iconRotation should return icon-rotate to the default value."); - XCTAssertEqualObjects(layer.iconRotation, defaultStyleValue, + XCTAssertEqualObjects(layer.iconRotation, defaultExpression, @"iconRotation should return the default value after being unset."); } @@ -435,79 +480,89 @@ { XCTAssertTrue(rawLayer->getIconRotationAlignment().isUndefined(), @"icon-rotation-alignment should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.iconRotationAlignment; + NSExpression *defaultExpression = layer.iconRotationAlignment; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLIconRotationAlignment:MGLIconRotationAlignmentAuto]]; - layer.iconRotationAlignment = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'auto'"]; + layer.iconRotationAlignment = constantExpression; mbgl::style::PropertyValue<mbgl::style::AlignmentType> propertyValue = { mbgl::style::AlignmentType::Auto }; XCTAssertEqual(rawLayer->getIconRotationAlignment(), propertyValue, - @"Setting iconRotationAlignment to a constant value should update icon-rotation-alignment."); - XCTAssertEqualObjects(layer.iconRotationAlignment, constantStyleValue, - @"iconRotationAlignment should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.iconRotationAlignment = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::AlignmentType> intervalStops = { {{18, mbgl::style::AlignmentType::Auto}} }; + @"Setting iconRotationAlignment to a constant value expression should update icon-rotation-alignment."); + XCTAssertEqualObjects(layer.iconRotationAlignment, constantExpression, + @"iconRotationAlignment should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'auto'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.iconRotationAlignment = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::AlignmentType> intervalStops = {{ + { -INFINITY, mbgl::style::AlignmentType::Auto }, + { 18, mbgl::style::AlignmentType::Auto }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::AlignmentType> { intervalStops }; XCTAssertEqual(rawLayer->getIconRotationAlignment(), propertyValue, - @"Setting iconRotationAlignment to a camera function should update icon-rotation-alignment."); - XCTAssertEqualObjects(layer.iconRotationAlignment, functionStyleValue, - @"iconRotationAlignment should round-trip camera functions."); + @"Setting iconRotationAlignment to a camera expression should update icon-rotation-alignment."); + XCTAssertEqualObjects(layer.iconRotationAlignment, functionExpression, + @"iconRotationAlignment should round-trip camera expressions."); layer.iconRotationAlignment = nil; XCTAssertTrue(rawLayer->getIconRotationAlignment().isUndefined(), @"Unsetting iconRotationAlignment should return icon-rotation-alignment to the default value."); - XCTAssertEqualObjects(layer.iconRotationAlignment, defaultStyleValue, + XCTAssertEqualObjects(layer.iconRotationAlignment, defaultExpression, @"iconRotationAlignment should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.iconRotationAlignment = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.iconRotationAlignment = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.iconRotationAlignment = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.iconRotationAlignment = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // icon-size { XCTAssertTrue(rawLayer->getIconSize().isUndefined(), @"icon-size should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.iconScale; + NSExpression *defaultExpression = layer.iconScale; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.iconScale = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.iconScale = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getIconSize(), propertyValue, - @"Setting iconScale to a constant value should update icon-size."); - XCTAssertEqualObjects(layer.iconScale, constantStyleValue, - @"iconScale should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.iconScale = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting iconScale to a constant value expression should update icon-size."); + XCTAssertEqualObjects(layer.iconScale, constantExpression, + @"iconScale should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.iconScale = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getIconSize(), propertyValue, - @"Setting iconScale to a camera function should update icon-size."); - XCTAssertEqualObjects(layer.iconScale, functionStyleValue, - @"iconScale should round-trip camera functions."); + @"Setting iconScale to a camera expression should update icon-size."); + XCTAssertEqualObjects(layer.iconScale, functionExpression, + @"iconScale should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.iconScale = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.iconScale = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getIconSize(), propertyValue, - @"Setting iconScale to a source function should update icon-size."); - XCTAssertEqualObjects(layer.iconScale, functionStyleValue, - @"iconScale should round-trip source functions."); + @"Setting iconScale to a data expression should update icon-size."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.iconScale, pedanticFunctionExpression, + @"iconScale should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.iconScale = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.iconScale = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -515,15 +570,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getIconSize(), propertyValue, - @"Setting iconScale to a composite function should update icon-size."); - XCTAssertEqualObjects(layer.iconScale, functionStyleValue, - @"iconScale should round-trip composite functions."); + @"Setting iconScale to a camera-data expression should update icon-size."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.iconScale, pedanticFunctionExpression, + @"iconScale should round-trip camera-data expressions."); layer.iconScale = nil; XCTAssertTrue(rawLayer->getIconSize().isUndefined(), @"Unsetting iconScale should return icon-size to the default value."); - XCTAssertEqualObjects(layer.iconScale, defaultStyleValue, + XCTAssertEqualObjects(layer.iconScale, defaultExpression, @"iconScale should return the default value after being unset."); } @@ -531,241 +587,271 @@ { XCTAssertTrue(rawLayer->getIconTextFit().isUndefined(), @"icon-text-fit should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.iconTextFit; + NSExpression *defaultExpression = layer.iconTextFit; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLIconTextFit:MGLIconTextFitBoth]]; - layer.iconTextFit = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'both'"]; + layer.iconTextFit = constantExpression; mbgl::style::PropertyValue<mbgl::style::IconTextFitType> propertyValue = { mbgl::style::IconTextFitType::Both }; XCTAssertEqual(rawLayer->getIconTextFit(), propertyValue, - @"Setting iconTextFit to a constant value should update icon-text-fit."); - XCTAssertEqualObjects(layer.iconTextFit, constantStyleValue, - @"iconTextFit should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.iconTextFit = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::IconTextFitType> intervalStops = { {{18, mbgl::style::IconTextFitType::Both}} }; + @"Setting iconTextFit to a constant value expression should update icon-text-fit."); + XCTAssertEqualObjects(layer.iconTextFit, constantExpression, + @"iconTextFit should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'both'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.iconTextFit = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::IconTextFitType> intervalStops = {{ + { -INFINITY, mbgl::style::IconTextFitType::Both }, + { 18, mbgl::style::IconTextFitType::Both }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::IconTextFitType> { intervalStops }; XCTAssertEqual(rawLayer->getIconTextFit(), propertyValue, - @"Setting iconTextFit to a camera function should update icon-text-fit."); - XCTAssertEqualObjects(layer.iconTextFit, functionStyleValue, - @"iconTextFit should round-trip camera functions."); + @"Setting iconTextFit to a camera expression should update icon-text-fit."); + XCTAssertEqualObjects(layer.iconTextFit, functionExpression, + @"iconTextFit should round-trip camera expressions."); layer.iconTextFit = nil; XCTAssertTrue(rawLayer->getIconTextFit().isUndefined(), @"Unsetting iconTextFit should return icon-text-fit to the default value."); - XCTAssertEqualObjects(layer.iconTextFit, defaultStyleValue, + XCTAssertEqualObjects(layer.iconTextFit, defaultExpression, @"iconTextFit should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.iconTextFit = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.iconTextFit = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.iconTextFit = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.iconTextFit = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // icon-text-fit-padding { XCTAssertTrue(rawLayer->getIconTextFitPadding().isUndefined(), @"icon-text-fit-padding should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.iconTextFitPadding; + NSExpression *defaultExpression = layer.iconTextFitPadding; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue: + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", #if TARGET_OS_IPHONE [NSValue valueWithUIEdgeInsets:UIEdgeInsetsMake(1, 1, 1, 1)] #else [NSValue valueWithEdgeInsets:NSEdgeInsetsMake(1, 1, 1, 1)] #endif ]; - layer.iconTextFitPadding = constantStyleValue; + layer.iconTextFitPadding = constantExpression; mbgl::style::PropertyValue<std::array<float, 4>> propertyValue = { { 1, 1, 1, 1 } }; XCTAssertEqual(rawLayer->getIconTextFitPadding(), propertyValue, - @"Setting iconTextFitPadding to a constant value should update icon-text-fit-padding."); - XCTAssertEqualObjects(layer.iconTextFitPadding, constantStyleValue, - @"iconTextFitPadding should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.iconTextFitPadding = functionStyleValue; - - mbgl::style::IntervalStops<std::array<float, 4>> intervalStops = { {{18, { 1, 1, 1, 1 }}} }; + @"Setting iconTextFitPadding to a constant value expression should update icon-text-fit-padding."); + XCTAssertEqualObjects(layer.iconTextFitPadding, constantExpression, + @"iconTextFitPadding should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"{1, 1, 1, 1}"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.iconTextFitPadding = functionExpression; + + mbgl::style::IntervalStops<std::array<float, 4>> intervalStops = {{ + { -INFINITY, { 1, 1, 1, 1 } }, + { 18, { 1, 1, 1, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<std::array<float, 4>> { intervalStops }; XCTAssertEqual(rawLayer->getIconTextFitPadding(), propertyValue, - @"Setting iconTextFitPadding to a camera function should update icon-text-fit-padding."); - XCTAssertEqualObjects(layer.iconTextFitPadding, functionStyleValue, - @"iconTextFitPadding should round-trip camera functions."); + @"Setting iconTextFitPadding to a camera expression should update icon-text-fit-padding."); + XCTAssertEqualObjects(layer.iconTextFitPadding, functionExpression, + @"iconTextFitPadding should round-trip camera expressions."); layer.iconTextFitPadding = nil; XCTAssertTrue(rawLayer->getIconTextFitPadding().isUndefined(), @"Unsetting iconTextFitPadding should return icon-text-fit-padding to the default value."); - XCTAssertEqualObjects(layer.iconTextFitPadding, defaultStyleValue, + XCTAssertEqualObjects(layer.iconTextFitPadding, defaultExpression, @"iconTextFitPadding should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.iconTextFitPadding = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.iconTextFitPadding = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.iconTextFitPadding = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.iconTextFitPadding = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // icon-keep-upright { XCTAssertTrue(rawLayer->getIconKeepUpright().isUndefined(), @"icon-keep-upright should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.keepsIconUpright; + NSExpression *defaultExpression = layer.keepsIconUpright; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@YES]; - layer.keepsIconUpright = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"true"]; + layer.keepsIconUpright = constantExpression; mbgl::style::PropertyValue<bool> propertyValue = { true }; XCTAssertEqual(rawLayer->getIconKeepUpright(), propertyValue, - @"Setting keepsIconUpright to a constant value should update icon-keep-upright."); - XCTAssertEqualObjects(layer.keepsIconUpright, constantStyleValue, - @"keepsIconUpright should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.keepsIconUpright = functionStyleValue; - - mbgl::style::IntervalStops<bool> intervalStops = { {{18, true}} }; + @"Setting keepsIconUpright to a constant value expression should update icon-keep-upright."); + XCTAssertEqualObjects(layer.keepsIconUpright, constantExpression, + @"keepsIconUpright should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"true"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.keepsIconUpright = functionExpression; + + mbgl::style::IntervalStops<bool> intervalStops = {{ + { -INFINITY, true }, + { 18, true }, + }}; propertyValue = mbgl::style::CameraFunction<bool> { intervalStops }; XCTAssertEqual(rawLayer->getIconKeepUpright(), propertyValue, - @"Setting keepsIconUpright to a camera function should update icon-keep-upright."); - XCTAssertEqualObjects(layer.keepsIconUpright, functionStyleValue, - @"keepsIconUpright should round-trip camera functions."); + @"Setting keepsIconUpright to a camera expression should update icon-keep-upright."); + XCTAssertEqualObjects(layer.keepsIconUpright, functionExpression, + @"keepsIconUpright should round-trip camera expressions."); layer.keepsIconUpright = nil; XCTAssertTrue(rawLayer->getIconKeepUpright().isUndefined(), @"Unsetting keepsIconUpright should return icon-keep-upright to the default value."); - XCTAssertEqualObjects(layer.keepsIconUpright, defaultStyleValue, + XCTAssertEqualObjects(layer.keepsIconUpright, defaultExpression, @"keepsIconUpright should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.keepsIconUpright = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.keepsIconUpright = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.keepsIconUpright = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.keepsIconUpright = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // text-keep-upright { XCTAssertTrue(rawLayer->getTextKeepUpright().isUndefined(), @"text-keep-upright should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.keepsTextUpright; + NSExpression *defaultExpression = layer.keepsTextUpright; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@NO]; - layer.keepsTextUpright = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"false"]; + layer.keepsTextUpright = constantExpression; mbgl::style::PropertyValue<bool> propertyValue = { false }; XCTAssertEqual(rawLayer->getTextKeepUpright(), propertyValue, - @"Setting keepsTextUpright to a constant value should update text-keep-upright."); - XCTAssertEqualObjects(layer.keepsTextUpright, constantStyleValue, - @"keepsTextUpright should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.keepsTextUpright = functionStyleValue; - - mbgl::style::IntervalStops<bool> intervalStops = { {{18, false}} }; + @"Setting keepsTextUpright to a constant value expression should update text-keep-upright."); + XCTAssertEqualObjects(layer.keepsTextUpright, constantExpression, + @"keepsTextUpright should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"false"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.keepsTextUpright = functionExpression; + + mbgl::style::IntervalStops<bool> intervalStops = {{ + { -INFINITY, false }, + { 18, false }, + }}; propertyValue = mbgl::style::CameraFunction<bool> { intervalStops }; XCTAssertEqual(rawLayer->getTextKeepUpright(), propertyValue, - @"Setting keepsTextUpright to a camera function should update text-keep-upright."); - XCTAssertEqualObjects(layer.keepsTextUpright, functionStyleValue, - @"keepsTextUpright should round-trip camera functions."); + @"Setting keepsTextUpright to a camera expression should update text-keep-upright."); + XCTAssertEqualObjects(layer.keepsTextUpright, functionExpression, + @"keepsTextUpright should round-trip camera expressions."); layer.keepsTextUpright = nil; XCTAssertTrue(rawLayer->getTextKeepUpright().isUndefined(), @"Unsetting keepsTextUpright should return text-keep-upright to the default value."); - XCTAssertEqualObjects(layer.keepsTextUpright, defaultStyleValue, + XCTAssertEqualObjects(layer.keepsTextUpright, defaultExpression, @"keepsTextUpright should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.keepsTextUpright = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.keepsTextUpright = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.keepsTextUpright = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.keepsTextUpright = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // text-max-angle { XCTAssertTrue(rawLayer->getTextMaxAngle().isUndefined(), @"text-max-angle should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.maximumTextAngle; + NSExpression *defaultExpression = layer.maximumTextAngle; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.maximumTextAngle = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.maximumTextAngle = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getTextMaxAngle(), propertyValue, - @"Setting maximumTextAngle to a constant value should update text-max-angle."); - XCTAssertEqualObjects(layer.maximumTextAngle, constantStyleValue, - @"maximumTextAngle should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.maximumTextAngle = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting maximumTextAngle to a constant value expression should update text-max-angle."); + XCTAssertEqualObjects(layer.maximumTextAngle, constantExpression, + @"maximumTextAngle should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.maximumTextAngle = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getTextMaxAngle(), propertyValue, - @"Setting maximumTextAngle to a camera function should update text-max-angle."); - XCTAssertEqualObjects(layer.maximumTextAngle, functionStyleValue, - @"maximumTextAngle should round-trip camera functions."); + @"Setting maximumTextAngle to a camera expression should update text-max-angle."); + XCTAssertEqualObjects(layer.maximumTextAngle, functionExpression, + @"maximumTextAngle should round-trip camera expressions."); layer.maximumTextAngle = nil; XCTAssertTrue(rawLayer->getTextMaxAngle().isUndefined(), @"Unsetting maximumTextAngle should return text-max-angle to the default value."); - XCTAssertEqualObjects(layer.maximumTextAngle, defaultStyleValue, + XCTAssertEqualObjects(layer.maximumTextAngle, defaultExpression, @"maximumTextAngle should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.maximumTextAngle = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.maximumTextAngle = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.maximumTextAngle = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.maximumTextAngle = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // text-max-width { XCTAssertTrue(rawLayer->getTextMaxWidth().isUndefined(), @"text-max-width should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.maximumTextWidth; + NSExpression *defaultExpression = layer.maximumTextWidth; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.maximumTextWidth = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.maximumTextWidth = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getTextMaxWidth(), propertyValue, - @"Setting maximumTextWidth to a constant value should update text-max-width."); - XCTAssertEqualObjects(layer.maximumTextWidth, constantStyleValue, - @"maximumTextWidth should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.maximumTextWidth = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting maximumTextWidth to a constant value expression should update text-max-width."); + XCTAssertEqualObjects(layer.maximumTextWidth, constantExpression, + @"maximumTextWidth should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.maximumTextWidth = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getTextMaxWidth(), propertyValue, - @"Setting maximumTextWidth to a camera function should update text-max-width."); - XCTAssertEqualObjects(layer.maximumTextWidth, functionStyleValue, - @"maximumTextWidth should round-trip camera functions."); + @"Setting maximumTextWidth to a camera expression should update text-max-width."); + XCTAssertEqualObjects(layer.maximumTextWidth, functionExpression, + @"maximumTextWidth should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.maximumTextWidth = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.maximumTextWidth = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getTextMaxWidth(), propertyValue, - @"Setting maximumTextWidth to a source function should update text-max-width."); - XCTAssertEqualObjects(layer.maximumTextWidth, functionStyleValue, - @"maximumTextWidth should round-trip source functions."); + @"Setting maximumTextWidth to a data expression should update text-max-width."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.maximumTextWidth, pedanticFunctionExpression, + @"maximumTextWidth should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.maximumTextWidth = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.maximumTextWidth = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -773,15 +859,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getTextMaxWidth(), propertyValue, - @"Setting maximumTextWidth to a composite function should update text-max-width."); - XCTAssertEqualObjects(layer.maximumTextWidth, functionStyleValue, - @"maximumTextWidth should round-trip composite functions."); + @"Setting maximumTextWidth to a camera-data expression should update text-max-width."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.maximumTextWidth, pedanticFunctionExpression, + @"maximumTextWidth should round-trip camera-data expressions."); layer.maximumTextWidth = nil; XCTAssertTrue(rawLayer->getTextMaxWidth().isUndefined(), @"Unsetting maximumTextWidth should return text-max-width to the default value."); - XCTAssertEqualObjects(layer.maximumTextWidth, defaultStyleValue, + XCTAssertEqualObjects(layer.maximumTextWidth, defaultExpression, @"maximumTextWidth should return the default value after being unset."); } @@ -789,150 +876,169 @@ { XCTAssertTrue(rawLayer->getSymbolAvoidEdges().isUndefined(), @"symbol-avoid-edges should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.symbolAvoidsEdges; + NSExpression *defaultExpression = layer.symbolAvoidsEdges; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@YES]; - layer.symbolAvoidsEdges = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"true"]; + layer.symbolAvoidsEdges = constantExpression; mbgl::style::PropertyValue<bool> propertyValue = { true }; XCTAssertEqual(rawLayer->getSymbolAvoidEdges(), propertyValue, - @"Setting symbolAvoidsEdges to a constant value should update symbol-avoid-edges."); - XCTAssertEqualObjects(layer.symbolAvoidsEdges, constantStyleValue, - @"symbolAvoidsEdges should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.symbolAvoidsEdges = functionStyleValue; - - mbgl::style::IntervalStops<bool> intervalStops = { {{18, true}} }; + @"Setting symbolAvoidsEdges to a constant value expression should update symbol-avoid-edges."); + XCTAssertEqualObjects(layer.symbolAvoidsEdges, constantExpression, + @"symbolAvoidsEdges should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"true"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.symbolAvoidsEdges = functionExpression; + + mbgl::style::IntervalStops<bool> intervalStops = {{ + { -INFINITY, true }, + { 18, true }, + }}; propertyValue = mbgl::style::CameraFunction<bool> { intervalStops }; XCTAssertEqual(rawLayer->getSymbolAvoidEdges(), propertyValue, - @"Setting symbolAvoidsEdges to a camera function should update symbol-avoid-edges."); - XCTAssertEqualObjects(layer.symbolAvoidsEdges, functionStyleValue, - @"symbolAvoidsEdges should round-trip camera functions."); + @"Setting symbolAvoidsEdges to a camera expression should update symbol-avoid-edges."); + XCTAssertEqualObjects(layer.symbolAvoidsEdges, functionExpression, + @"symbolAvoidsEdges should round-trip camera expressions."); layer.symbolAvoidsEdges = nil; XCTAssertTrue(rawLayer->getSymbolAvoidEdges().isUndefined(), @"Unsetting symbolAvoidsEdges should return symbol-avoid-edges to the default value."); - XCTAssertEqualObjects(layer.symbolAvoidsEdges, defaultStyleValue, + XCTAssertEqualObjects(layer.symbolAvoidsEdges, defaultExpression, @"symbolAvoidsEdges should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.symbolAvoidsEdges = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.symbolAvoidsEdges = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.symbolAvoidsEdges = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.symbolAvoidsEdges = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // symbol-placement { XCTAssertTrue(rawLayer->getSymbolPlacement().isUndefined(), @"symbol-placement should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.symbolPlacement; + NSExpression *defaultExpression = layer.symbolPlacement; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLSymbolPlacement:MGLSymbolPlacementLine]]; - layer.symbolPlacement = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'line'"]; + layer.symbolPlacement = constantExpression; mbgl::style::PropertyValue<mbgl::style::SymbolPlacementType> propertyValue = { mbgl::style::SymbolPlacementType::Line }; XCTAssertEqual(rawLayer->getSymbolPlacement(), propertyValue, - @"Setting symbolPlacement to a constant value should update symbol-placement."); - XCTAssertEqualObjects(layer.symbolPlacement, constantStyleValue, - @"symbolPlacement should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.symbolPlacement = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::SymbolPlacementType> intervalStops = { {{18, mbgl::style::SymbolPlacementType::Line}} }; + @"Setting symbolPlacement to a constant value expression should update symbol-placement."); + XCTAssertEqualObjects(layer.symbolPlacement, constantExpression, + @"symbolPlacement should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'line'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.symbolPlacement = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::SymbolPlacementType> intervalStops = {{ + { -INFINITY, mbgl::style::SymbolPlacementType::Line }, + { 18, mbgl::style::SymbolPlacementType::Line }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::SymbolPlacementType> { intervalStops }; XCTAssertEqual(rawLayer->getSymbolPlacement(), propertyValue, - @"Setting symbolPlacement to a camera function should update symbol-placement."); - XCTAssertEqualObjects(layer.symbolPlacement, functionStyleValue, - @"symbolPlacement should round-trip camera functions."); + @"Setting symbolPlacement to a camera expression should update symbol-placement."); + XCTAssertEqualObjects(layer.symbolPlacement, functionExpression, + @"symbolPlacement should round-trip camera expressions."); layer.symbolPlacement = nil; XCTAssertTrue(rawLayer->getSymbolPlacement().isUndefined(), @"Unsetting symbolPlacement should return symbol-placement to the default value."); - XCTAssertEqualObjects(layer.symbolPlacement, defaultStyleValue, + XCTAssertEqualObjects(layer.symbolPlacement, defaultExpression, @"symbolPlacement should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.symbolPlacement = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.symbolPlacement = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.symbolPlacement = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.symbolPlacement = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // symbol-spacing { XCTAssertTrue(rawLayer->getSymbolSpacing().isUndefined(), @"symbol-spacing should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.symbolSpacing; + NSExpression *defaultExpression = layer.symbolSpacing; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.symbolSpacing = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.symbolSpacing = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getSymbolSpacing(), propertyValue, - @"Setting symbolSpacing to a constant value should update symbol-spacing."); - XCTAssertEqualObjects(layer.symbolSpacing, constantStyleValue, - @"symbolSpacing should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.symbolSpacing = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting symbolSpacing to a constant value expression should update symbol-spacing."); + XCTAssertEqualObjects(layer.symbolSpacing, constantExpression, + @"symbolSpacing should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.symbolSpacing = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getSymbolSpacing(), propertyValue, - @"Setting symbolSpacing to a camera function should update symbol-spacing."); - XCTAssertEqualObjects(layer.symbolSpacing, functionStyleValue, - @"symbolSpacing should round-trip camera functions."); + @"Setting symbolSpacing to a camera expression should update symbol-spacing."); + XCTAssertEqualObjects(layer.symbolSpacing, functionExpression, + @"symbolSpacing should round-trip camera expressions."); layer.symbolSpacing = nil; XCTAssertTrue(rawLayer->getSymbolSpacing().isUndefined(), @"Unsetting symbolSpacing should return symbol-spacing to the default value."); - XCTAssertEqualObjects(layer.symbolSpacing, defaultStyleValue, + XCTAssertEqualObjects(layer.symbolSpacing, defaultExpression, @"symbolSpacing should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.symbolSpacing = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.symbolSpacing = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.symbolSpacing = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.symbolSpacing = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // text-field { XCTAssertTrue(rawLayer->getTextField().isUndefined(), @"text-field should be unset initially."); - MGLStyleValue<NSString *> *defaultStyleValue = layer.text; + NSExpression *defaultExpression = layer.text; - MGLStyleValue<NSString *> *constantStyleValue = [MGLStyleValue<NSString *> valueWithRawValue:@"Text Field"]; - layer.text = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'Text Field'"]; + layer.text = constantExpression; mbgl::style::DataDrivenPropertyValue<std::string> propertyValue = { "Text Field" }; XCTAssertEqual(rawLayer->getTextField(), propertyValue, - @"Setting text to a constant value should update text-field."); - XCTAssertEqualObjects(layer.text, constantStyleValue, - @"text should round-trip constant values."); - - MGLStyleValue<NSString *> * functionStyleValue = [MGLStyleValue<NSString *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.text = functionStyleValue; - - mbgl::style::IntervalStops<std::string> intervalStops = { {{18, "Text Field"}} }; + @"Setting text to a constant value expression should update text-field."); + XCTAssertEqualObjects(layer.text, constantExpression, + @"text should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'Text Field'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.text = functionExpression; + + mbgl::style::IntervalStops<std::string> intervalStops = {{ + { -INFINITY, "Text Field" }, + { 18, "Text Field" }, + }}; propertyValue = mbgl::style::CameraFunction<std::string> { intervalStops }; XCTAssertEqual(rawLayer->getTextField(), propertyValue, - @"Setting text to a camera function should update text-field."); - XCTAssertEqualObjects(layer.text, functionStyleValue, - @"text should round-trip camera functions."); + @"Setting text to a camera expression should update text-field."); + XCTAssertEqualObjects(layer.text, functionExpression, + @"text should round-trip camera expressions."); layer.text = nil; XCTAssertTrue(rawLayer->getTextField().isUndefined(), @"Unsetting text should return text-field to the default value."); - XCTAssertEqualObjects(layer.text, defaultStyleValue, + XCTAssertEqualObjects(layer.text, defaultExpression, @"text should return the default value after being unset."); } @@ -940,72 +1046,81 @@ { XCTAssertTrue(rawLayer->getTextAllowOverlap().isUndefined(), @"text-allow-overlap should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.textAllowsOverlap; + NSExpression *defaultExpression = layer.textAllowsOverlap; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@YES]; - layer.textAllowsOverlap = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"true"]; + layer.textAllowsOverlap = constantExpression; mbgl::style::PropertyValue<bool> propertyValue = { true }; XCTAssertEqual(rawLayer->getTextAllowOverlap(), propertyValue, - @"Setting textAllowsOverlap to a constant value should update text-allow-overlap."); - XCTAssertEqualObjects(layer.textAllowsOverlap, constantStyleValue, - @"textAllowsOverlap should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.textAllowsOverlap = functionStyleValue; - - mbgl::style::IntervalStops<bool> intervalStops = { {{18, true}} }; + @"Setting textAllowsOverlap to a constant value expression should update text-allow-overlap."); + XCTAssertEqualObjects(layer.textAllowsOverlap, constantExpression, + @"textAllowsOverlap should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"true"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.textAllowsOverlap = functionExpression; + + mbgl::style::IntervalStops<bool> intervalStops = {{ + { -INFINITY, true }, + { 18, true }, + }}; propertyValue = mbgl::style::CameraFunction<bool> { intervalStops }; XCTAssertEqual(rawLayer->getTextAllowOverlap(), propertyValue, - @"Setting textAllowsOverlap to a camera function should update text-allow-overlap."); - XCTAssertEqualObjects(layer.textAllowsOverlap, functionStyleValue, - @"textAllowsOverlap should round-trip camera functions."); + @"Setting textAllowsOverlap to a camera expression should update text-allow-overlap."); + XCTAssertEqualObjects(layer.textAllowsOverlap, functionExpression, + @"textAllowsOverlap should round-trip camera expressions."); layer.textAllowsOverlap = nil; XCTAssertTrue(rawLayer->getTextAllowOverlap().isUndefined(), @"Unsetting textAllowsOverlap should return text-allow-overlap to the default value."); - XCTAssertEqualObjects(layer.textAllowsOverlap, defaultStyleValue, + XCTAssertEqualObjects(layer.textAllowsOverlap, defaultExpression, @"textAllowsOverlap should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.textAllowsOverlap = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.textAllowsOverlap = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.textAllowsOverlap = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.textAllowsOverlap = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // text-anchor { XCTAssertTrue(rawLayer->getTextAnchor().isUndefined(), @"text-anchor should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.textAnchor; + NSExpression *defaultExpression = layer.textAnchor; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLTextAnchor:MGLTextAnchorBottomRight]]; - layer.textAnchor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'bottom-right'"]; + layer.textAnchor = constantExpression; mbgl::style::DataDrivenPropertyValue<mbgl::style::SymbolAnchorType> propertyValue = { mbgl::style::SymbolAnchorType::BottomRight }; XCTAssertEqual(rawLayer->getTextAnchor(), propertyValue, - @"Setting textAnchor to a constant value should update text-anchor."); - XCTAssertEqualObjects(layer.textAnchor, constantStyleValue, - @"textAnchor should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.textAnchor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::SymbolAnchorType> intervalStops = { {{18, mbgl::style::SymbolAnchorType::BottomRight}} }; + @"Setting textAnchor to a constant value expression should update text-anchor."); + XCTAssertEqualObjects(layer.textAnchor, constantExpression, + @"textAnchor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'bottom-right'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.textAnchor = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::SymbolAnchorType> intervalStops = {{ + { -INFINITY, mbgl::style::SymbolAnchorType::BottomRight }, + { 18, mbgl::style::SymbolAnchorType::BottomRight }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::SymbolAnchorType> { intervalStops }; XCTAssertEqual(rawLayer->getTextAnchor(), propertyValue, - @"Setting textAnchor to a camera function should update text-anchor."); - XCTAssertEqualObjects(layer.textAnchor, functionStyleValue, - @"textAnchor should round-trip camera functions."); + @"Setting textAnchor to a camera expression should update text-anchor."); + XCTAssertEqualObjects(layer.textAnchor, functionExpression, + @"textAnchor should round-trip camera expressions."); layer.textAnchor = nil; XCTAssertTrue(rawLayer->getTextAnchor().isUndefined(), @"Unsetting textAnchor should return text-anchor to the default value."); - XCTAssertEqualObjects(layer.textAnchor, defaultStyleValue, + XCTAssertEqualObjects(layer.textAnchor, defaultExpression, @"textAnchor should return the default value after being unset."); } @@ -1013,79 +1128,83 @@ { XCTAssertTrue(rawLayer->getTextFont().isUndefined(), @"text-font should be unset initially."); - MGLStyleValue<NSArray<NSString *> *> *defaultStyleValue = layer.textFontNames; + NSExpression *defaultExpression = layer.textFontNames; - MGLStyleValue<NSArray<NSString *> *> *constantStyleValue = [MGLStyleValue<NSArray<NSString *> *> valueWithRawValue:@[@"Text Font", @"Tnof Txet"]]; - layer.textFontNames = constantStyleValue; - mbgl::style::PropertyValue<std::vector<std::string>> propertyValue = { { "Text Font", "Tnof Txet" } }; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"{'Text Font', 'Tnof Txet'}"]; + layer.textFontNames = constantExpression; + mbgl::style::DataDrivenPropertyValue<std::vector<std::string>> propertyValue = { { "Text Font", "Tnof Txet" } }; XCTAssertEqual(rawLayer->getTextFont(), propertyValue, - @"Setting textFontNames to a constant value should update text-font."); - XCTAssertEqualObjects(layer.textFontNames, constantStyleValue, - @"textFontNames should round-trip constant values."); - - MGLStyleValue<NSArray<NSString *> *> * functionStyleValue = [MGLStyleValue<NSArray<NSString *> *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.textFontNames = functionStyleValue; - - mbgl::style::IntervalStops<std::vector<std::string>> intervalStops = { {{18, { "Text Font", "Tnof Txet" }}} }; + @"Setting textFontNames to a constant value expression should update text-font."); + XCTAssertEqualObjects(layer.textFontNames, constantExpression, + @"textFontNames should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"{'Text Font', 'Tnof Txet'}"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.textFontNames = functionExpression; + + mbgl::style::IntervalStops<std::vector<std::string>> intervalStops = {{ + { -INFINITY, { "Text Font", "Tnof Txet" } }, + { 18, { "Text Font", "Tnof Txet" } }, + }}; propertyValue = mbgl::style::CameraFunction<std::vector<std::string>> { intervalStops }; XCTAssertEqual(rawLayer->getTextFont(), propertyValue, - @"Setting textFontNames to a camera function should update text-font."); - XCTAssertEqualObjects(layer.textFontNames, functionStyleValue, - @"textFontNames should round-trip camera functions."); + @"Setting textFontNames to a camera expression should update text-font."); + XCTAssertEqualObjects(layer.textFontNames, functionExpression, + @"textFontNames should round-trip camera expressions."); layer.textFontNames = nil; XCTAssertTrue(rawLayer->getTextFont().isUndefined(), @"Unsetting textFontNames should return text-font to the default value."); - XCTAssertEqualObjects(layer.textFontNames, defaultStyleValue, + XCTAssertEqualObjects(layer.textFontNames, defaultExpression, @"textFontNames should return the default value after being unset."); - - functionStyleValue = [MGLStyleValue<NSArray<NSString *> *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.textFontNames = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSArray<NSString *> *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.textFontNames = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); } // text-size { XCTAssertTrue(rawLayer->getTextSize().isUndefined(), @"text-size should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.textFontSize; + NSExpression *defaultExpression = layer.textFontSize; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.textFontSize = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.textFontSize = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getTextSize(), propertyValue, - @"Setting textFontSize to a constant value should update text-size."); - XCTAssertEqualObjects(layer.textFontSize, constantStyleValue, - @"textFontSize should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.textFontSize = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting textFontSize to a constant value expression should update text-size."); + XCTAssertEqualObjects(layer.textFontSize, constantExpression, + @"textFontSize should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.textFontSize = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getTextSize(), propertyValue, - @"Setting textFontSize to a camera function should update text-size."); - XCTAssertEqualObjects(layer.textFontSize, functionStyleValue, - @"textFontSize should round-trip camera functions."); + @"Setting textFontSize to a camera expression should update text-size."); + XCTAssertEqualObjects(layer.textFontSize, functionExpression, + @"textFontSize should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.textFontSize = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.textFontSize = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getTextSize(), propertyValue, - @"Setting textFontSize to a source function should update text-size."); - XCTAssertEqualObjects(layer.textFontSize, functionStyleValue, - @"textFontSize should round-trip source functions."); + @"Setting textFontSize to a data expression should update text-size."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.textFontSize, pedanticFunctionExpression, + @"textFontSize should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.textFontSize = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.textFontSize = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -1093,15 +1212,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getTextSize(), propertyValue, - @"Setting textFontSize to a composite function should update text-size."); - XCTAssertEqualObjects(layer.textFontSize, functionStyleValue, - @"textFontSize should round-trip composite functions."); + @"Setting textFontSize to a camera-data expression should update text-size."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.textFontSize, pedanticFunctionExpression, + @"textFontSize should round-trip camera-data expressions."); layer.textFontSize = nil; XCTAssertTrue(rawLayer->getTextSize().isUndefined(), @"Unsetting textFontSize should return text-size to the default value."); - XCTAssertEqualObjects(layer.textFontSize, defaultStyleValue, + XCTAssertEqualObjects(layer.textFontSize, defaultExpression, @"textFontSize should return the default value after being unset."); } @@ -1109,72 +1229,81 @@ { XCTAssertTrue(rawLayer->getTextIgnorePlacement().isUndefined(), @"text-ignore-placement should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.textIgnoresPlacement; + NSExpression *defaultExpression = layer.textIgnoresPlacement; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@YES]; - layer.textIgnoresPlacement = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"true"]; + layer.textIgnoresPlacement = constantExpression; mbgl::style::PropertyValue<bool> propertyValue = { true }; XCTAssertEqual(rawLayer->getTextIgnorePlacement(), propertyValue, - @"Setting textIgnoresPlacement to a constant value should update text-ignore-placement."); - XCTAssertEqualObjects(layer.textIgnoresPlacement, constantStyleValue, - @"textIgnoresPlacement should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.textIgnoresPlacement = functionStyleValue; - - mbgl::style::IntervalStops<bool> intervalStops = { {{18, true}} }; + @"Setting textIgnoresPlacement to a constant value expression should update text-ignore-placement."); + XCTAssertEqualObjects(layer.textIgnoresPlacement, constantExpression, + @"textIgnoresPlacement should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"true"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.textIgnoresPlacement = functionExpression; + + mbgl::style::IntervalStops<bool> intervalStops = {{ + { -INFINITY, true }, + { 18, true }, + }}; propertyValue = mbgl::style::CameraFunction<bool> { intervalStops }; XCTAssertEqual(rawLayer->getTextIgnorePlacement(), propertyValue, - @"Setting textIgnoresPlacement to a camera function should update text-ignore-placement."); - XCTAssertEqualObjects(layer.textIgnoresPlacement, functionStyleValue, - @"textIgnoresPlacement should round-trip camera functions."); + @"Setting textIgnoresPlacement to a camera expression should update text-ignore-placement."); + XCTAssertEqualObjects(layer.textIgnoresPlacement, functionExpression, + @"textIgnoresPlacement should round-trip camera expressions."); layer.textIgnoresPlacement = nil; XCTAssertTrue(rawLayer->getTextIgnorePlacement().isUndefined(), @"Unsetting textIgnoresPlacement should return text-ignore-placement to the default value."); - XCTAssertEqualObjects(layer.textIgnoresPlacement, defaultStyleValue, + XCTAssertEqualObjects(layer.textIgnoresPlacement, defaultExpression, @"textIgnoresPlacement should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.textIgnoresPlacement = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.textIgnoresPlacement = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.textIgnoresPlacement = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.textIgnoresPlacement = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // text-justify { XCTAssertTrue(rawLayer->getTextJustify().isUndefined(), @"text-justify should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.textJustification; + NSExpression *defaultExpression = layer.textJustification; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLTextJustification:MGLTextJustificationRight]]; - layer.textJustification = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'right'"]; + layer.textJustification = constantExpression; mbgl::style::DataDrivenPropertyValue<mbgl::style::TextJustifyType> propertyValue = { mbgl::style::TextJustifyType::Right }; XCTAssertEqual(rawLayer->getTextJustify(), propertyValue, - @"Setting textJustification to a constant value should update text-justify."); - XCTAssertEqualObjects(layer.textJustification, constantStyleValue, - @"textJustification should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.textJustification = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::TextJustifyType> intervalStops = { {{18, mbgl::style::TextJustifyType::Right}} }; + @"Setting textJustification to a constant value expression should update text-justify."); + XCTAssertEqualObjects(layer.textJustification, constantExpression, + @"textJustification should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'right'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.textJustification = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::TextJustifyType> intervalStops = {{ + { -INFINITY, mbgl::style::TextJustifyType::Right }, + { 18, mbgl::style::TextJustifyType::Right }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::TextJustifyType> { intervalStops }; XCTAssertEqual(rawLayer->getTextJustify(), propertyValue, - @"Setting textJustification to a camera function should update text-justify."); - XCTAssertEqualObjects(layer.textJustification, functionStyleValue, - @"textJustification should round-trip camera functions."); + @"Setting textJustification to a camera expression should update text-justify."); + XCTAssertEqualObjects(layer.textJustification, functionExpression, + @"textJustification should round-trip camera expressions."); layer.textJustification = nil; XCTAssertTrue(rawLayer->getTextJustify().isUndefined(), @"Unsetting textJustification should return text-justify to the default value."); - XCTAssertEqualObjects(layer.textJustification, defaultStyleValue, + XCTAssertEqualObjects(layer.textJustification, defaultExpression, @"textJustification should return the default value after being unset."); } @@ -1182,40 +1311,45 @@ { XCTAssertTrue(rawLayer->getTextLetterSpacing().isUndefined(), @"text-letter-spacing should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.textLetterSpacing; + NSExpression *defaultExpression = layer.textLetterSpacing; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.textLetterSpacing = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.textLetterSpacing = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getTextLetterSpacing(), propertyValue, - @"Setting textLetterSpacing to a constant value should update text-letter-spacing."); - XCTAssertEqualObjects(layer.textLetterSpacing, constantStyleValue, - @"textLetterSpacing should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.textLetterSpacing = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting textLetterSpacing to a constant value expression should update text-letter-spacing."); + XCTAssertEqualObjects(layer.textLetterSpacing, constantExpression, + @"textLetterSpacing should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.textLetterSpacing = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getTextLetterSpacing(), propertyValue, - @"Setting textLetterSpacing to a camera function should update text-letter-spacing."); - XCTAssertEqualObjects(layer.textLetterSpacing, functionStyleValue, - @"textLetterSpacing should round-trip camera functions."); + @"Setting textLetterSpacing to a camera expression should update text-letter-spacing."); + XCTAssertEqualObjects(layer.textLetterSpacing, functionExpression, + @"textLetterSpacing should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.textLetterSpacing = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.textLetterSpacing = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getTextLetterSpacing(), propertyValue, - @"Setting textLetterSpacing to a source function should update text-letter-spacing."); - XCTAssertEqualObjects(layer.textLetterSpacing, functionStyleValue, - @"textLetterSpacing should round-trip source functions."); + @"Setting textLetterSpacing to a data expression should update text-letter-spacing."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.textLetterSpacing, pedanticFunctionExpression, + @"textLetterSpacing should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.textLetterSpacing = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.textLetterSpacing = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -1223,15 +1357,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getTextLetterSpacing(), propertyValue, - @"Setting textLetterSpacing to a composite function should update text-letter-spacing."); - XCTAssertEqualObjects(layer.textLetterSpacing, functionStyleValue, - @"textLetterSpacing should round-trip composite functions."); + @"Setting textLetterSpacing to a camera-data expression should update text-letter-spacing."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.textLetterSpacing, pedanticFunctionExpression, + @"textLetterSpacing should round-trip camera-data expressions."); layer.textLetterSpacing = nil; XCTAssertTrue(rawLayer->getTextLetterSpacing().isUndefined(), @"Unsetting textLetterSpacing should return text-letter-spacing to the default value."); - XCTAssertEqualObjects(layer.textLetterSpacing, defaultStyleValue, + XCTAssertEqualObjects(layer.textLetterSpacing, defaultExpression, @"textLetterSpacing should return the default value after being unset."); } @@ -1239,85 +1374,95 @@ { XCTAssertTrue(rawLayer->getTextLineHeight().isUndefined(), @"text-line-height should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.textLineHeight; + NSExpression *defaultExpression = layer.textLineHeight; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.textLineHeight = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.textLineHeight = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getTextLineHeight(), propertyValue, - @"Setting textLineHeight to a constant value should update text-line-height."); - XCTAssertEqualObjects(layer.textLineHeight, constantStyleValue, - @"textLineHeight should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.textLineHeight = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting textLineHeight to a constant value expression should update text-line-height."); + XCTAssertEqualObjects(layer.textLineHeight, constantExpression, + @"textLineHeight should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.textLineHeight = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getTextLineHeight(), propertyValue, - @"Setting textLineHeight to a camera function should update text-line-height."); - XCTAssertEqualObjects(layer.textLineHeight, functionStyleValue, - @"textLineHeight should round-trip camera functions."); + @"Setting textLineHeight to a camera expression should update text-line-height."); + XCTAssertEqualObjects(layer.textLineHeight, functionExpression, + @"textLineHeight should round-trip camera expressions."); layer.textLineHeight = nil; XCTAssertTrue(rawLayer->getTextLineHeight().isUndefined(), @"Unsetting textLineHeight should return text-line-height to the default value."); - XCTAssertEqualObjects(layer.textLineHeight, defaultStyleValue, + XCTAssertEqualObjects(layer.textLineHeight, defaultExpression, @"textLineHeight should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.textLineHeight = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.textLineHeight = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.textLineHeight = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.textLineHeight = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // text-offset { XCTAssertTrue(rawLayer->getTextOffset().isUndefined(), @"text-offset should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.textOffset; + NSExpression *defaultExpression = layer.textOffset; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue: + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", #if TARGET_OS_IPHONE [NSValue valueWithCGVector:CGVectorMake(1, 1)] #else [NSValue valueWithMGLVector:CGVectorMake(1, -1)] #endif ]; - layer.textOffset = constantStyleValue; + layer.textOffset = constantExpression; mbgl::style::DataDrivenPropertyValue<std::array<float, 2>> propertyValue = { { 1, 1 } }; XCTAssertEqual(rawLayer->getTextOffset(), propertyValue, - @"Setting textOffset to a constant value should update text-offset."); - XCTAssertEqualObjects(layer.textOffset, constantStyleValue, - @"textOffset should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.textOffset = functionStyleValue; - - mbgl::style::IntervalStops<std::array<float, 2>> intervalStops = { {{18, { 1, 1 }}} }; + @"Setting textOffset to a constant value expression should update text-offset."); + XCTAssertEqualObjects(layer.textOffset, constantExpression, + @"textOffset should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"{1, 1}"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.textOffset = functionExpression; + + mbgl::style::IntervalStops<std::array<float, 2>> intervalStops = {{ + { -INFINITY, { 1, 1 } }, + { 18, { 1, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<std::array<float, 2>> { intervalStops }; XCTAssertEqual(rawLayer->getTextOffset(), propertyValue, - @"Setting textOffset to a camera function should update text-offset."); - XCTAssertEqualObjects(layer.textOffset, functionStyleValue, - @"textOffset should round-trip camera functions."); + @"Setting textOffset to a camera expression should update text-offset."); + XCTAssertEqualObjects(layer.textOffset, functionExpression, + @"textOffset should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.textOffset = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.textOffset = functionExpression; mbgl::style::ExponentialStops<std::array<float, 2>> exponentialStops = { {{18, { 1, 1 }}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<std::array<float, 2>> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getTextOffset(), propertyValue, - @"Setting textOffset to a source function should update text-offset."); - XCTAssertEqualObjects(layer.textOffset, functionStyleValue, - @"textOffset should round-trip source functions."); + @"Setting textOffset to a data expression should update text-offset."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.textOffset, pedanticFunctionExpression, + @"textOffset should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.textOffset = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.textOffset = functionExpression; std::map<float, std::array<float, 2>> innerStops { {18, { 1, 1 }} }; mbgl::style::CompositeExponentialStops<std::array<float, 2>> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -1325,15 +1470,16 @@ propertyValue = mbgl::style::CompositeFunction<std::array<float, 2>> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getTextOffset(), propertyValue, - @"Setting textOffset to a composite function should update text-offset."); - XCTAssertEqualObjects(layer.textOffset, functionStyleValue, - @"textOffset should round-trip composite functions."); + @"Setting textOffset to a camera-data expression should update text-offset."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.textOffset, pedanticFunctionExpression, + @"textOffset should round-trip camera-data expressions."); layer.textOffset = nil; XCTAssertTrue(rawLayer->getTextOffset().isUndefined(), @"Unsetting textOffset should return text-offset to the default value."); - XCTAssertEqualObjects(layer.textOffset, defaultStyleValue, + XCTAssertEqualObjects(layer.textOffset, defaultExpression, @"textOffset should return the default value after being unset."); } @@ -1341,157 +1487,177 @@ { XCTAssertTrue(rawLayer->getTextOptional().isUndefined(), @"text-optional should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.textOptional; + NSExpression *defaultExpression = layer.textOptional; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@YES]; - layer.textOptional = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"true"]; + layer.textOptional = constantExpression; mbgl::style::PropertyValue<bool> propertyValue = { true }; XCTAssertEqual(rawLayer->getTextOptional(), propertyValue, - @"Setting textOptional to a constant value should update text-optional."); - XCTAssertEqualObjects(layer.textOptional, constantStyleValue, - @"textOptional should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.textOptional = functionStyleValue; - - mbgl::style::IntervalStops<bool> intervalStops = { {{18, true}} }; + @"Setting textOptional to a constant value expression should update text-optional."); + XCTAssertEqualObjects(layer.textOptional, constantExpression, + @"textOptional should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"true"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.textOptional = functionExpression; + + mbgl::style::IntervalStops<bool> intervalStops = {{ + { -INFINITY, true }, + { 18, true }, + }}; propertyValue = mbgl::style::CameraFunction<bool> { intervalStops }; XCTAssertEqual(rawLayer->getTextOptional(), propertyValue, - @"Setting textOptional to a camera function should update text-optional."); - XCTAssertEqualObjects(layer.textOptional, functionStyleValue, - @"textOptional should round-trip camera functions."); + @"Setting textOptional to a camera expression should update text-optional."); + XCTAssertEqualObjects(layer.textOptional, functionExpression, + @"textOptional should round-trip camera expressions."); layer.textOptional = nil; XCTAssertTrue(rawLayer->getTextOptional().isUndefined(), @"Unsetting textOptional should return text-optional to the default value."); - XCTAssertEqualObjects(layer.textOptional, defaultStyleValue, + XCTAssertEqualObjects(layer.textOptional, defaultExpression, @"textOptional should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.textOptional = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.textOptional = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.textOptional = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.textOptional = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // text-padding { XCTAssertTrue(rawLayer->getTextPadding().isUndefined(), @"text-padding should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.textPadding; + NSExpression *defaultExpression = layer.textPadding; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.textPadding = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.textPadding = constantExpression; mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getTextPadding(), propertyValue, - @"Setting textPadding to a constant value should update text-padding."); - XCTAssertEqualObjects(layer.textPadding, constantStyleValue, - @"textPadding should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.textPadding = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting textPadding to a constant value expression should update text-padding."); + XCTAssertEqualObjects(layer.textPadding, constantExpression, + @"textPadding should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.textPadding = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getTextPadding(), propertyValue, - @"Setting textPadding to a camera function should update text-padding."); - XCTAssertEqualObjects(layer.textPadding, functionStyleValue, - @"textPadding should round-trip camera functions."); + @"Setting textPadding to a camera expression should update text-padding."); + XCTAssertEqualObjects(layer.textPadding, functionExpression, + @"textPadding should round-trip camera expressions."); layer.textPadding = nil; XCTAssertTrue(rawLayer->getTextPadding().isUndefined(), @"Unsetting textPadding should return text-padding to the default value."); - XCTAssertEqualObjects(layer.textPadding, defaultStyleValue, + XCTAssertEqualObjects(layer.textPadding, defaultExpression, @"textPadding should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.textPadding = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.textPadding = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.textPadding = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.textPadding = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // text-pitch-alignment { XCTAssertTrue(rawLayer->getTextPitchAlignment().isUndefined(), @"text-pitch-alignment should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.textPitchAlignment; + NSExpression *defaultExpression = layer.textPitchAlignment; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLTextPitchAlignment:MGLTextPitchAlignmentAuto]]; - layer.textPitchAlignment = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'auto'"]; + layer.textPitchAlignment = constantExpression; mbgl::style::PropertyValue<mbgl::style::AlignmentType> propertyValue = { mbgl::style::AlignmentType::Auto }; XCTAssertEqual(rawLayer->getTextPitchAlignment(), propertyValue, - @"Setting textPitchAlignment to a constant value should update text-pitch-alignment."); - XCTAssertEqualObjects(layer.textPitchAlignment, constantStyleValue, - @"textPitchAlignment should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.textPitchAlignment = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::AlignmentType> intervalStops = { {{18, mbgl::style::AlignmentType::Auto}} }; + @"Setting textPitchAlignment to a constant value expression should update text-pitch-alignment."); + XCTAssertEqualObjects(layer.textPitchAlignment, constantExpression, + @"textPitchAlignment should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'auto'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.textPitchAlignment = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::AlignmentType> intervalStops = {{ + { -INFINITY, mbgl::style::AlignmentType::Auto }, + { 18, mbgl::style::AlignmentType::Auto }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::AlignmentType> { intervalStops }; XCTAssertEqual(rawLayer->getTextPitchAlignment(), propertyValue, - @"Setting textPitchAlignment to a camera function should update text-pitch-alignment."); - XCTAssertEqualObjects(layer.textPitchAlignment, functionStyleValue, - @"textPitchAlignment should round-trip camera functions."); + @"Setting textPitchAlignment to a camera expression should update text-pitch-alignment."); + XCTAssertEqualObjects(layer.textPitchAlignment, functionExpression, + @"textPitchAlignment should round-trip camera expressions."); layer.textPitchAlignment = nil; XCTAssertTrue(rawLayer->getTextPitchAlignment().isUndefined(), @"Unsetting textPitchAlignment should return text-pitch-alignment to the default value."); - XCTAssertEqualObjects(layer.textPitchAlignment, defaultStyleValue, + XCTAssertEqualObjects(layer.textPitchAlignment, defaultExpression, @"textPitchAlignment should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.textPitchAlignment = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.textPitchAlignment = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.textPitchAlignment = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.textPitchAlignment = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // text-rotate { XCTAssertTrue(rawLayer->getTextRotate().isUndefined(), @"text-rotate should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.textRotation; + NSExpression *defaultExpression = layer.textRotation; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.textRotation = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.textRotation = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getTextRotate(), propertyValue, - @"Setting textRotation to a constant value should update text-rotate."); - XCTAssertEqualObjects(layer.textRotation, constantStyleValue, - @"textRotation should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.textRotation = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting textRotation to a constant value expression should update text-rotate."); + XCTAssertEqualObjects(layer.textRotation, constantExpression, + @"textRotation should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.textRotation = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getTextRotate(), propertyValue, - @"Setting textRotation to a camera function should update text-rotate."); - XCTAssertEqualObjects(layer.textRotation, functionStyleValue, - @"textRotation should round-trip camera functions."); + @"Setting textRotation to a camera expression should update text-rotate."); + XCTAssertEqualObjects(layer.textRotation, functionExpression, + @"textRotation should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.textRotation = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.textRotation = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getTextRotate(), propertyValue, - @"Setting textRotation to a source function should update text-rotate."); - XCTAssertEqualObjects(layer.textRotation, functionStyleValue, - @"textRotation should round-trip source functions."); + @"Setting textRotation to a data expression should update text-rotate."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.textRotation, pedanticFunctionExpression, + @"textRotation should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.textRotation = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.textRotation = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -1499,15 +1665,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getTextRotate(), propertyValue, - @"Setting textRotation to a composite function should update text-rotate."); - XCTAssertEqualObjects(layer.textRotation, functionStyleValue, - @"textRotation should round-trip composite functions."); + @"Setting textRotation to a camera-data expression should update text-rotate."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.textRotation, pedanticFunctionExpression, + @"textRotation should round-trip camera-data expressions."); layer.textRotation = nil; XCTAssertTrue(rawLayer->getTextRotate().isUndefined(), @"Unsetting textRotation should return text-rotate to the default value."); - XCTAssertEqualObjects(layer.textRotation, defaultStyleValue, + XCTAssertEqualObjects(layer.textRotation, defaultExpression, @"textRotation should return the default value after being unset."); } @@ -1515,72 +1682,81 @@ { XCTAssertTrue(rawLayer->getTextRotationAlignment().isUndefined(), @"text-rotation-alignment should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.textRotationAlignment; + NSExpression *defaultExpression = layer.textRotationAlignment; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLTextRotationAlignment:MGLTextRotationAlignmentAuto]]; - layer.textRotationAlignment = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'auto'"]; + layer.textRotationAlignment = constantExpression; mbgl::style::PropertyValue<mbgl::style::AlignmentType> propertyValue = { mbgl::style::AlignmentType::Auto }; XCTAssertEqual(rawLayer->getTextRotationAlignment(), propertyValue, - @"Setting textRotationAlignment to a constant value should update text-rotation-alignment."); - XCTAssertEqualObjects(layer.textRotationAlignment, constantStyleValue, - @"textRotationAlignment should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.textRotationAlignment = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::AlignmentType> intervalStops = { {{18, mbgl::style::AlignmentType::Auto}} }; + @"Setting textRotationAlignment to a constant value expression should update text-rotation-alignment."); + XCTAssertEqualObjects(layer.textRotationAlignment, constantExpression, + @"textRotationAlignment should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'auto'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.textRotationAlignment = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::AlignmentType> intervalStops = {{ + { -INFINITY, mbgl::style::AlignmentType::Auto }, + { 18, mbgl::style::AlignmentType::Auto }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::AlignmentType> { intervalStops }; XCTAssertEqual(rawLayer->getTextRotationAlignment(), propertyValue, - @"Setting textRotationAlignment to a camera function should update text-rotation-alignment."); - XCTAssertEqualObjects(layer.textRotationAlignment, functionStyleValue, - @"textRotationAlignment should round-trip camera functions."); + @"Setting textRotationAlignment to a camera expression should update text-rotation-alignment."); + XCTAssertEqualObjects(layer.textRotationAlignment, functionExpression, + @"textRotationAlignment should round-trip camera expressions."); layer.textRotationAlignment = nil; XCTAssertTrue(rawLayer->getTextRotationAlignment().isUndefined(), @"Unsetting textRotationAlignment should return text-rotation-alignment to the default value."); - XCTAssertEqualObjects(layer.textRotationAlignment, defaultStyleValue, + XCTAssertEqualObjects(layer.textRotationAlignment, defaultExpression, @"textRotationAlignment should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.textRotationAlignment = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.textRotationAlignment = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.textRotationAlignment = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.textRotationAlignment = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // text-transform { XCTAssertTrue(rawLayer->getTextTransform().isUndefined(), @"text-transform should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.textTransform; + NSExpression *defaultExpression = layer.textTransform; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLTextTransform:MGLTextTransformLowercase]]; - layer.textTransform = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'lowercase'"]; + layer.textTransform = constantExpression; mbgl::style::DataDrivenPropertyValue<mbgl::style::TextTransformType> propertyValue = { mbgl::style::TextTransformType::Lowercase }; XCTAssertEqual(rawLayer->getTextTransform(), propertyValue, - @"Setting textTransform to a constant value should update text-transform."); - XCTAssertEqualObjects(layer.textTransform, constantStyleValue, - @"textTransform should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.textTransform = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::TextTransformType> intervalStops = { {{18, mbgl::style::TextTransformType::Lowercase}} }; + @"Setting textTransform to a constant value expression should update text-transform."); + XCTAssertEqualObjects(layer.textTransform, constantExpression, + @"textTransform should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'lowercase'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.textTransform = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::TextTransformType> intervalStops = {{ + { -INFINITY, mbgl::style::TextTransformType::Lowercase }, + { 18, mbgl::style::TextTransformType::Lowercase }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::TextTransformType> { intervalStops }; XCTAssertEqual(rawLayer->getTextTransform(), propertyValue, - @"Setting textTransform to a camera function should update text-transform."); - XCTAssertEqualObjects(layer.textTransform, functionStyleValue, - @"textTransform should round-trip camera functions."); + @"Setting textTransform to a camera expression should update text-transform."); + XCTAssertEqualObjects(layer.textTransform, functionExpression, + @"textTransform should round-trip camera expressions."); layer.textTransform = nil; XCTAssertTrue(rawLayer->getTextTransform().isUndefined(), @"Unsetting textTransform should return text-transform to the default value."); - XCTAssertEqualObjects(layer.textTransform, defaultStyleValue, + XCTAssertEqualObjects(layer.textTransform, defaultExpression, @"textTransform should return the default value after being unset."); } @@ -1588,40 +1764,45 @@ { XCTAssertTrue(rawLayer->getIconColor().isUndefined(), @"icon-color should be unset initially."); - MGLStyleValue<MGLColor *> *defaultStyleValue = layer.iconColor; + NSExpression *defaultExpression = layer.iconColor; - MGLStyleValue<MGLColor *> *constantStyleValue = [MGLStyleValue<MGLColor *> valueWithRawValue:[MGLColor redColor]]; - layer.iconColor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.iconColor = constantExpression; mbgl::style::DataDrivenPropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } }; XCTAssertEqual(rawLayer->getIconColor(), propertyValue, - @"Setting iconColor to a constant value should update icon-color."); - XCTAssertEqualObjects(layer.iconColor, constantStyleValue, - @"iconColor should round-trip constant values."); - - MGLStyleValue<MGLColor *> * functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.iconColor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::Color> intervalStops = { {{18, { 1, 0, 0, 1 }}} }; + @"Setting iconColor to a constant value expression should update icon-color."); + XCTAssertEqualObjects(layer.iconColor, constantExpression, + @"iconColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.iconColor = functionExpression; + + mbgl::style::IntervalStops<mbgl::Color> intervalStops = {{ + { -INFINITY, { 1, 0, 0, 1 } }, + { 18, { 1, 0, 0, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops }; XCTAssertEqual(rawLayer->getIconColor(), propertyValue, - @"Setting iconColor to a camera function should update icon-color."); - XCTAssertEqualObjects(layer.iconColor, functionStyleValue, - @"iconColor should round-trip camera functions."); + @"Setting iconColor to a camera expression should update icon-color."); + XCTAssertEqualObjects(layer.iconColor, functionExpression, + @"iconColor should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.iconColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.iconColor = functionExpression; mbgl::style::ExponentialStops<mbgl::Color> exponentialStops = { {{18, { 1, 0, 0, 1 }}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<mbgl::Color> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getIconColor(), propertyValue, - @"Setting iconColor to a source function should update icon-color."); - XCTAssertEqualObjects(layer.iconColor, functionStyleValue, - @"iconColor should round-trip source functions."); + @"Setting iconColor to a data expression should update icon-color."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.iconColor, pedanticFunctionExpression, + @"iconColor should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.iconColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.iconColor = functionExpression; std::map<float, mbgl::Color> innerStops { {18, { 1, 0, 0, 1 }} }; mbgl::style::CompositeExponentialStops<mbgl::Color> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -1629,15 +1810,16 @@ propertyValue = mbgl::style::CompositeFunction<mbgl::Color> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getIconColor(), propertyValue, - @"Setting iconColor to a composite function should update icon-color."); - XCTAssertEqualObjects(layer.iconColor, functionStyleValue, - @"iconColor should round-trip composite functions."); + @"Setting iconColor to a camera-data expression should update icon-color."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.iconColor, pedanticFunctionExpression, + @"iconColor should round-trip camera-data expressions."); layer.iconColor = nil; XCTAssertTrue(rawLayer->getIconColor().isUndefined(), @"Unsetting iconColor should return icon-color to the default value."); - XCTAssertEqualObjects(layer.iconColor, defaultStyleValue, + XCTAssertEqualObjects(layer.iconColor, defaultExpression, @"iconColor should return the default value after being unset."); // Transition property test layer.iconColorTransition = transitionTest; @@ -1654,40 +1836,45 @@ { XCTAssertTrue(rawLayer->getIconHaloBlur().isUndefined(), @"icon-halo-blur should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.iconHaloBlur; + NSExpression *defaultExpression = layer.iconHaloBlur; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.iconHaloBlur = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.iconHaloBlur = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getIconHaloBlur(), propertyValue, - @"Setting iconHaloBlur to a constant value should update icon-halo-blur."); - XCTAssertEqualObjects(layer.iconHaloBlur, constantStyleValue, - @"iconHaloBlur should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.iconHaloBlur = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting iconHaloBlur to a constant value expression should update icon-halo-blur."); + XCTAssertEqualObjects(layer.iconHaloBlur, constantExpression, + @"iconHaloBlur should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.iconHaloBlur = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getIconHaloBlur(), propertyValue, - @"Setting iconHaloBlur to a camera function should update icon-halo-blur."); - XCTAssertEqualObjects(layer.iconHaloBlur, functionStyleValue, - @"iconHaloBlur should round-trip camera functions."); + @"Setting iconHaloBlur to a camera expression should update icon-halo-blur."); + XCTAssertEqualObjects(layer.iconHaloBlur, functionExpression, + @"iconHaloBlur should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.iconHaloBlur = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.iconHaloBlur = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getIconHaloBlur(), propertyValue, - @"Setting iconHaloBlur to a source function should update icon-halo-blur."); - XCTAssertEqualObjects(layer.iconHaloBlur, functionStyleValue, - @"iconHaloBlur should round-trip source functions."); + @"Setting iconHaloBlur to a data expression should update icon-halo-blur."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.iconHaloBlur, pedanticFunctionExpression, + @"iconHaloBlur should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.iconHaloBlur = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.iconHaloBlur = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -1695,15 +1882,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getIconHaloBlur(), propertyValue, - @"Setting iconHaloBlur to a composite function should update icon-halo-blur."); - XCTAssertEqualObjects(layer.iconHaloBlur, functionStyleValue, - @"iconHaloBlur should round-trip composite functions."); + @"Setting iconHaloBlur to a camera-data expression should update icon-halo-blur."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.iconHaloBlur, pedanticFunctionExpression, + @"iconHaloBlur should round-trip camera-data expressions."); layer.iconHaloBlur = nil; XCTAssertTrue(rawLayer->getIconHaloBlur().isUndefined(), @"Unsetting iconHaloBlur should return icon-halo-blur to the default value."); - XCTAssertEqualObjects(layer.iconHaloBlur, defaultStyleValue, + XCTAssertEqualObjects(layer.iconHaloBlur, defaultExpression, @"iconHaloBlur should return the default value after being unset."); // Transition property test layer.iconHaloBlurTransition = transitionTest; @@ -1720,40 +1908,45 @@ { XCTAssertTrue(rawLayer->getIconHaloColor().isUndefined(), @"icon-halo-color should be unset initially."); - MGLStyleValue<MGLColor *> *defaultStyleValue = layer.iconHaloColor; + NSExpression *defaultExpression = layer.iconHaloColor; - MGLStyleValue<MGLColor *> *constantStyleValue = [MGLStyleValue<MGLColor *> valueWithRawValue:[MGLColor redColor]]; - layer.iconHaloColor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.iconHaloColor = constantExpression; mbgl::style::DataDrivenPropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } }; XCTAssertEqual(rawLayer->getIconHaloColor(), propertyValue, - @"Setting iconHaloColor to a constant value should update icon-halo-color."); - XCTAssertEqualObjects(layer.iconHaloColor, constantStyleValue, - @"iconHaloColor should round-trip constant values."); - - MGLStyleValue<MGLColor *> * functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.iconHaloColor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::Color> intervalStops = { {{18, { 1, 0, 0, 1 }}} }; + @"Setting iconHaloColor to a constant value expression should update icon-halo-color."); + XCTAssertEqualObjects(layer.iconHaloColor, constantExpression, + @"iconHaloColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.iconHaloColor = functionExpression; + + mbgl::style::IntervalStops<mbgl::Color> intervalStops = {{ + { -INFINITY, { 1, 0, 0, 1 } }, + { 18, { 1, 0, 0, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops }; XCTAssertEqual(rawLayer->getIconHaloColor(), propertyValue, - @"Setting iconHaloColor to a camera function should update icon-halo-color."); - XCTAssertEqualObjects(layer.iconHaloColor, functionStyleValue, - @"iconHaloColor should round-trip camera functions."); + @"Setting iconHaloColor to a camera expression should update icon-halo-color."); + XCTAssertEqualObjects(layer.iconHaloColor, functionExpression, + @"iconHaloColor should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.iconHaloColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.iconHaloColor = functionExpression; mbgl::style::ExponentialStops<mbgl::Color> exponentialStops = { {{18, { 1, 0, 0, 1 }}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<mbgl::Color> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getIconHaloColor(), propertyValue, - @"Setting iconHaloColor to a source function should update icon-halo-color."); - XCTAssertEqualObjects(layer.iconHaloColor, functionStyleValue, - @"iconHaloColor should round-trip source functions."); + @"Setting iconHaloColor to a data expression should update icon-halo-color."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.iconHaloColor, pedanticFunctionExpression, + @"iconHaloColor should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.iconHaloColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.iconHaloColor = functionExpression; std::map<float, mbgl::Color> innerStops { {18, { 1, 0, 0, 1 }} }; mbgl::style::CompositeExponentialStops<mbgl::Color> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -1761,15 +1954,16 @@ propertyValue = mbgl::style::CompositeFunction<mbgl::Color> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getIconHaloColor(), propertyValue, - @"Setting iconHaloColor to a composite function should update icon-halo-color."); - XCTAssertEqualObjects(layer.iconHaloColor, functionStyleValue, - @"iconHaloColor should round-trip composite functions."); + @"Setting iconHaloColor to a camera-data expression should update icon-halo-color."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.iconHaloColor, pedanticFunctionExpression, + @"iconHaloColor should round-trip camera-data expressions."); layer.iconHaloColor = nil; XCTAssertTrue(rawLayer->getIconHaloColor().isUndefined(), @"Unsetting iconHaloColor should return icon-halo-color to the default value."); - XCTAssertEqualObjects(layer.iconHaloColor, defaultStyleValue, + XCTAssertEqualObjects(layer.iconHaloColor, defaultExpression, @"iconHaloColor should return the default value after being unset."); // Transition property test layer.iconHaloColorTransition = transitionTest; @@ -1786,40 +1980,45 @@ { XCTAssertTrue(rawLayer->getIconHaloWidth().isUndefined(), @"icon-halo-width should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.iconHaloWidth; + NSExpression *defaultExpression = layer.iconHaloWidth; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.iconHaloWidth = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.iconHaloWidth = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getIconHaloWidth(), propertyValue, - @"Setting iconHaloWidth to a constant value should update icon-halo-width."); - XCTAssertEqualObjects(layer.iconHaloWidth, constantStyleValue, - @"iconHaloWidth should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.iconHaloWidth = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting iconHaloWidth to a constant value expression should update icon-halo-width."); + XCTAssertEqualObjects(layer.iconHaloWidth, constantExpression, + @"iconHaloWidth should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.iconHaloWidth = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getIconHaloWidth(), propertyValue, - @"Setting iconHaloWidth to a camera function should update icon-halo-width."); - XCTAssertEqualObjects(layer.iconHaloWidth, functionStyleValue, - @"iconHaloWidth should round-trip camera functions."); + @"Setting iconHaloWidth to a camera expression should update icon-halo-width."); + XCTAssertEqualObjects(layer.iconHaloWidth, functionExpression, + @"iconHaloWidth should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.iconHaloWidth = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.iconHaloWidth = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getIconHaloWidth(), propertyValue, - @"Setting iconHaloWidth to a source function should update icon-halo-width."); - XCTAssertEqualObjects(layer.iconHaloWidth, functionStyleValue, - @"iconHaloWidth should round-trip source functions."); + @"Setting iconHaloWidth to a data expression should update icon-halo-width."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.iconHaloWidth, pedanticFunctionExpression, + @"iconHaloWidth should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.iconHaloWidth = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.iconHaloWidth = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -1827,15 +2026,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getIconHaloWidth(), propertyValue, - @"Setting iconHaloWidth to a composite function should update icon-halo-width."); - XCTAssertEqualObjects(layer.iconHaloWidth, functionStyleValue, - @"iconHaloWidth should round-trip composite functions."); + @"Setting iconHaloWidth to a camera-data expression should update icon-halo-width."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.iconHaloWidth, pedanticFunctionExpression, + @"iconHaloWidth should round-trip camera-data expressions."); layer.iconHaloWidth = nil; XCTAssertTrue(rawLayer->getIconHaloWidth().isUndefined(), @"Unsetting iconHaloWidth should return icon-halo-width to the default value."); - XCTAssertEqualObjects(layer.iconHaloWidth, defaultStyleValue, + XCTAssertEqualObjects(layer.iconHaloWidth, defaultExpression, @"iconHaloWidth should return the default value after being unset."); // Transition property test layer.iconHaloWidthTransition = transitionTest; @@ -1852,40 +2052,45 @@ { XCTAssertTrue(rawLayer->getIconOpacity().isUndefined(), @"icon-opacity should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.iconOpacity; + NSExpression *defaultExpression = layer.iconOpacity; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.iconOpacity = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.iconOpacity = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getIconOpacity(), propertyValue, - @"Setting iconOpacity to a constant value should update icon-opacity."); - XCTAssertEqualObjects(layer.iconOpacity, constantStyleValue, - @"iconOpacity should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.iconOpacity = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting iconOpacity to a constant value expression should update icon-opacity."); + XCTAssertEqualObjects(layer.iconOpacity, constantExpression, + @"iconOpacity should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.iconOpacity = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getIconOpacity(), propertyValue, - @"Setting iconOpacity to a camera function should update icon-opacity."); - XCTAssertEqualObjects(layer.iconOpacity, functionStyleValue, - @"iconOpacity should round-trip camera functions."); + @"Setting iconOpacity to a camera expression should update icon-opacity."); + XCTAssertEqualObjects(layer.iconOpacity, functionExpression, + @"iconOpacity should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.iconOpacity = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.iconOpacity = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getIconOpacity(), propertyValue, - @"Setting iconOpacity to a source function should update icon-opacity."); - XCTAssertEqualObjects(layer.iconOpacity, functionStyleValue, - @"iconOpacity should round-trip source functions."); + @"Setting iconOpacity to a data expression should update icon-opacity."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.iconOpacity, pedanticFunctionExpression, + @"iconOpacity should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.iconOpacity = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.iconOpacity = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -1893,15 +2098,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getIconOpacity(), propertyValue, - @"Setting iconOpacity to a composite function should update icon-opacity."); - XCTAssertEqualObjects(layer.iconOpacity, functionStyleValue, - @"iconOpacity should round-trip composite functions."); + @"Setting iconOpacity to a camera-data expression should update icon-opacity."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.iconOpacity, pedanticFunctionExpression, + @"iconOpacity should round-trip camera-data expressions."); layer.iconOpacity = nil; XCTAssertTrue(rawLayer->getIconOpacity().isUndefined(), @"Unsetting iconOpacity should return icon-opacity to the default value."); - XCTAssertEqualObjects(layer.iconOpacity, defaultStyleValue, + XCTAssertEqualObjects(layer.iconOpacity, defaultExpression, @"iconOpacity should return the default value after being unset."); // Transition property test layer.iconOpacityTransition = transitionTest; @@ -1918,124 +2124,139 @@ { XCTAssertTrue(rawLayer->getIconTranslate().isUndefined(), @"icon-translate should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.iconTranslation; + NSExpression *defaultExpression = layer.iconTranslation; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue: + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", #if TARGET_OS_IPHONE [NSValue valueWithCGVector:CGVectorMake(1, 1)] #else [NSValue valueWithMGLVector:CGVectorMake(1, -1)] #endif ]; - layer.iconTranslation = constantStyleValue; + layer.iconTranslation = constantExpression; mbgl::style::PropertyValue<std::array<float, 2>> propertyValue = { { 1, 1 } }; XCTAssertEqual(rawLayer->getIconTranslate(), propertyValue, - @"Setting iconTranslation to a constant value should update icon-translate."); - XCTAssertEqualObjects(layer.iconTranslation, constantStyleValue, - @"iconTranslation should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.iconTranslation = functionStyleValue; - - mbgl::style::IntervalStops<std::array<float, 2>> intervalStops = { {{18, { 1, 1 }}} }; + @"Setting iconTranslation to a constant value expression should update icon-translate."); + XCTAssertEqualObjects(layer.iconTranslation, constantExpression, + @"iconTranslation should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"{1, 1}"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.iconTranslation = functionExpression; + + mbgl::style::IntervalStops<std::array<float, 2>> intervalStops = {{ + { -INFINITY, { 1, 1 } }, + { 18, { 1, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<std::array<float, 2>> { intervalStops }; XCTAssertEqual(rawLayer->getIconTranslate(), propertyValue, - @"Setting iconTranslation to a camera function should update icon-translate."); - XCTAssertEqualObjects(layer.iconTranslation, functionStyleValue, - @"iconTranslation should round-trip camera functions."); + @"Setting iconTranslation to a camera expression should update icon-translate."); + XCTAssertEqualObjects(layer.iconTranslation, functionExpression, + @"iconTranslation should round-trip camera expressions."); layer.iconTranslation = nil; XCTAssertTrue(rawLayer->getIconTranslate().isUndefined(), @"Unsetting iconTranslation should return icon-translate to the default value."); - XCTAssertEqualObjects(layer.iconTranslation, defaultStyleValue, + XCTAssertEqualObjects(layer.iconTranslation, defaultExpression, @"iconTranslation should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.iconTranslation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.iconTranslation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.iconTranslation = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.iconTranslation = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // icon-translate-anchor { XCTAssertTrue(rawLayer->getIconTranslateAnchor().isUndefined(), @"icon-translate-anchor should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.iconTranslationAnchor; + NSExpression *defaultExpression = layer.iconTranslationAnchor; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLIconTranslationAnchor:MGLIconTranslationAnchorViewport]]; - layer.iconTranslationAnchor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + layer.iconTranslationAnchor = constantExpression; mbgl::style::PropertyValue<mbgl::style::TranslateAnchorType> propertyValue = { mbgl::style::TranslateAnchorType::Viewport }; XCTAssertEqual(rawLayer->getIconTranslateAnchor(), propertyValue, - @"Setting iconTranslationAnchor to a constant value should update icon-translate-anchor."); - XCTAssertEqualObjects(layer.iconTranslationAnchor, constantStyleValue, - @"iconTranslationAnchor should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.iconTranslationAnchor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::TranslateAnchorType> intervalStops = { {{18, mbgl::style::TranslateAnchorType::Viewport}} }; + @"Setting iconTranslationAnchor to a constant value expression should update icon-translate-anchor."); + XCTAssertEqualObjects(layer.iconTranslationAnchor, constantExpression, + @"iconTranslationAnchor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.iconTranslationAnchor = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::TranslateAnchorType> intervalStops = {{ + { -INFINITY, mbgl::style::TranslateAnchorType::Viewport }, + { 18, mbgl::style::TranslateAnchorType::Viewport }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::TranslateAnchorType> { intervalStops }; XCTAssertEqual(rawLayer->getIconTranslateAnchor(), propertyValue, - @"Setting iconTranslationAnchor to a camera function should update icon-translate-anchor."); - XCTAssertEqualObjects(layer.iconTranslationAnchor, functionStyleValue, - @"iconTranslationAnchor should round-trip camera functions."); + @"Setting iconTranslationAnchor to a camera expression should update icon-translate-anchor."); + XCTAssertEqualObjects(layer.iconTranslationAnchor, functionExpression, + @"iconTranslationAnchor should round-trip camera expressions."); layer.iconTranslationAnchor = nil; XCTAssertTrue(rawLayer->getIconTranslateAnchor().isUndefined(), @"Unsetting iconTranslationAnchor should return icon-translate-anchor to the default value."); - XCTAssertEqualObjects(layer.iconTranslationAnchor, defaultStyleValue, + XCTAssertEqualObjects(layer.iconTranslationAnchor, defaultExpression, @"iconTranslationAnchor should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.iconTranslationAnchor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.iconTranslationAnchor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.iconTranslationAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.iconTranslationAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // text-color { XCTAssertTrue(rawLayer->getTextColor().isUndefined(), @"text-color should be unset initially."); - MGLStyleValue<MGLColor *> *defaultStyleValue = layer.textColor; + NSExpression *defaultExpression = layer.textColor; - MGLStyleValue<MGLColor *> *constantStyleValue = [MGLStyleValue<MGLColor *> valueWithRawValue:[MGLColor redColor]]; - layer.textColor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.textColor = constantExpression; mbgl::style::DataDrivenPropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } }; XCTAssertEqual(rawLayer->getTextColor(), propertyValue, - @"Setting textColor to a constant value should update text-color."); - XCTAssertEqualObjects(layer.textColor, constantStyleValue, - @"textColor should round-trip constant values."); - - MGLStyleValue<MGLColor *> * functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.textColor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::Color> intervalStops = { {{18, { 1, 0, 0, 1 }}} }; + @"Setting textColor to a constant value expression should update text-color."); + XCTAssertEqualObjects(layer.textColor, constantExpression, + @"textColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.textColor = functionExpression; + + mbgl::style::IntervalStops<mbgl::Color> intervalStops = {{ + { -INFINITY, { 1, 0, 0, 1 } }, + { 18, { 1, 0, 0, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops }; XCTAssertEqual(rawLayer->getTextColor(), propertyValue, - @"Setting textColor to a camera function should update text-color."); - XCTAssertEqualObjects(layer.textColor, functionStyleValue, - @"textColor should round-trip camera functions."); + @"Setting textColor to a camera expression should update text-color."); + XCTAssertEqualObjects(layer.textColor, functionExpression, + @"textColor should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.textColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.textColor = functionExpression; mbgl::style::ExponentialStops<mbgl::Color> exponentialStops = { {{18, { 1, 0, 0, 1 }}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<mbgl::Color> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getTextColor(), propertyValue, - @"Setting textColor to a source function should update text-color."); - XCTAssertEqualObjects(layer.textColor, functionStyleValue, - @"textColor should round-trip source functions."); + @"Setting textColor to a data expression should update text-color."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.textColor, pedanticFunctionExpression, + @"textColor should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.textColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.textColor = functionExpression; std::map<float, mbgl::Color> innerStops { {18, { 1, 0, 0, 1 }} }; mbgl::style::CompositeExponentialStops<mbgl::Color> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -2043,15 +2264,16 @@ propertyValue = mbgl::style::CompositeFunction<mbgl::Color> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getTextColor(), propertyValue, - @"Setting textColor to a composite function should update text-color."); - XCTAssertEqualObjects(layer.textColor, functionStyleValue, - @"textColor should round-trip composite functions."); + @"Setting textColor to a camera-data expression should update text-color."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.textColor, pedanticFunctionExpression, + @"textColor should round-trip camera-data expressions."); layer.textColor = nil; XCTAssertTrue(rawLayer->getTextColor().isUndefined(), @"Unsetting textColor should return text-color to the default value."); - XCTAssertEqualObjects(layer.textColor, defaultStyleValue, + XCTAssertEqualObjects(layer.textColor, defaultExpression, @"textColor should return the default value after being unset."); // Transition property test layer.textColorTransition = transitionTest; @@ -2068,40 +2290,45 @@ { XCTAssertTrue(rawLayer->getTextHaloBlur().isUndefined(), @"text-halo-blur should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.textHaloBlur; + NSExpression *defaultExpression = layer.textHaloBlur; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.textHaloBlur = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.textHaloBlur = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getTextHaloBlur(), propertyValue, - @"Setting textHaloBlur to a constant value should update text-halo-blur."); - XCTAssertEqualObjects(layer.textHaloBlur, constantStyleValue, - @"textHaloBlur should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.textHaloBlur = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting textHaloBlur to a constant value expression should update text-halo-blur."); + XCTAssertEqualObjects(layer.textHaloBlur, constantExpression, + @"textHaloBlur should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.textHaloBlur = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getTextHaloBlur(), propertyValue, - @"Setting textHaloBlur to a camera function should update text-halo-blur."); - XCTAssertEqualObjects(layer.textHaloBlur, functionStyleValue, - @"textHaloBlur should round-trip camera functions."); + @"Setting textHaloBlur to a camera expression should update text-halo-blur."); + XCTAssertEqualObjects(layer.textHaloBlur, functionExpression, + @"textHaloBlur should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.textHaloBlur = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.textHaloBlur = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getTextHaloBlur(), propertyValue, - @"Setting textHaloBlur to a source function should update text-halo-blur."); - XCTAssertEqualObjects(layer.textHaloBlur, functionStyleValue, - @"textHaloBlur should round-trip source functions."); + @"Setting textHaloBlur to a data expression should update text-halo-blur."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.textHaloBlur, pedanticFunctionExpression, + @"textHaloBlur should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.textHaloBlur = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.textHaloBlur = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -2109,15 +2336,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getTextHaloBlur(), propertyValue, - @"Setting textHaloBlur to a composite function should update text-halo-blur."); - XCTAssertEqualObjects(layer.textHaloBlur, functionStyleValue, - @"textHaloBlur should round-trip composite functions."); + @"Setting textHaloBlur to a camera-data expression should update text-halo-blur."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.textHaloBlur, pedanticFunctionExpression, + @"textHaloBlur should round-trip camera-data expressions."); layer.textHaloBlur = nil; XCTAssertTrue(rawLayer->getTextHaloBlur().isUndefined(), @"Unsetting textHaloBlur should return text-halo-blur to the default value."); - XCTAssertEqualObjects(layer.textHaloBlur, defaultStyleValue, + XCTAssertEqualObjects(layer.textHaloBlur, defaultExpression, @"textHaloBlur should return the default value after being unset."); // Transition property test layer.textHaloBlurTransition = transitionTest; @@ -2134,40 +2362,45 @@ { XCTAssertTrue(rawLayer->getTextHaloColor().isUndefined(), @"text-halo-color should be unset initially."); - MGLStyleValue<MGLColor *> *defaultStyleValue = layer.textHaloColor; + NSExpression *defaultExpression = layer.textHaloColor; - MGLStyleValue<MGLColor *> *constantStyleValue = [MGLStyleValue<MGLColor *> valueWithRawValue:[MGLColor redColor]]; - layer.textHaloColor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.textHaloColor = constantExpression; mbgl::style::DataDrivenPropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } }; XCTAssertEqual(rawLayer->getTextHaloColor(), propertyValue, - @"Setting textHaloColor to a constant value should update text-halo-color."); - XCTAssertEqualObjects(layer.textHaloColor, constantStyleValue, - @"textHaloColor should round-trip constant values."); - - MGLStyleValue<MGLColor *> * functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.textHaloColor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::Color> intervalStops = { {{18, { 1, 0, 0, 1 }}} }; + @"Setting textHaloColor to a constant value expression should update text-halo-color."); + XCTAssertEqualObjects(layer.textHaloColor, constantExpression, + @"textHaloColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.textHaloColor = functionExpression; + + mbgl::style::IntervalStops<mbgl::Color> intervalStops = {{ + { -INFINITY, { 1, 0, 0, 1 } }, + { 18, { 1, 0, 0, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops }; XCTAssertEqual(rawLayer->getTextHaloColor(), propertyValue, - @"Setting textHaloColor to a camera function should update text-halo-color."); - XCTAssertEqualObjects(layer.textHaloColor, functionStyleValue, - @"textHaloColor should round-trip camera functions."); + @"Setting textHaloColor to a camera expression should update text-halo-color."); + XCTAssertEqualObjects(layer.textHaloColor, functionExpression, + @"textHaloColor should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.textHaloColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.textHaloColor = functionExpression; mbgl::style::ExponentialStops<mbgl::Color> exponentialStops = { {{18, { 1, 0, 0, 1 }}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<mbgl::Color> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getTextHaloColor(), propertyValue, - @"Setting textHaloColor to a source function should update text-halo-color."); - XCTAssertEqualObjects(layer.textHaloColor, functionStyleValue, - @"textHaloColor should round-trip source functions."); + @"Setting textHaloColor to a data expression should update text-halo-color."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.textHaloColor, pedanticFunctionExpression, + @"textHaloColor should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.textHaloColor = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.textHaloColor = functionExpression; std::map<float, mbgl::Color> innerStops { {18, { 1, 0, 0, 1 }} }; mbgl::style::CompositeExponentialStops<mbgl::Color> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -2175,15 +2408,16 @@ propertyValue = mbgl::style::CompositeFunction<mbgl::Color> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getTextHaloColor(), propertyValue, - @"Setting textHaloColor to a composite function should update text-halo-color."); - XCTAssertEqualObjects(layer.textHaloColor, functionStyleValue, - @"textHaloColor should round-trip composite functions."); + @"Setting textHaloColor to a camera-data expression should update text-halo-color."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.textHaloColor, pedanticFunctionExpression, + @"textHaloColor should round-trip camera-data expressions."); layer.textHaloColor = nil; XCTAssertTrue(rawLayer->getTextHaloColor().isUndefined(), @"Unsetting textHaloColor should return text-halo-color to the default value."); - XCTAssertEqualObjects(layer.textHaloColor, defaultStyleValue, + XCTAssertEqualObjects(layer.textHaloColor, defaultExpression, @"textHaloColor should return the default value after being unset."); // Transition property test layer.textHaloColorTransition = transitionTest; @@ -2200,40 +2434,45 @@ { XCTAssertTrue(rawLayer->getTextHaloWidth().isUndefined(), @"text-halo-width should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.textHaloWidth; + NSExpression *defaultExpression = layer.textHaloWidth; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.textHaloWidth = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.textHaloWidth = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getTextHaloWidth(), propertyValue, - @"Setting textHaloWidth to a constant value should update text-halo-width."); - XCTAssertEqualObjects(layer.textHaloWidth, constantStyleValue, - @"textHaloWidth should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.textHaloWidth = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting textHaloWidth to a constant value expression should update text-halo-width."); + XCTAssertEqualObjects(layer.textHaloWidth, constantExpression, + @"textHaloWidth should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.textHaloWidth = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getTextHaloWidth(), propertyValue, - @"Setting textHaloWidth to a camera function should update text-halo-width."); - XCTAssertEqualObjects(layer.textHaloWidth, functionStyleValue, - @"textHaloWidth should round-trip camera functions."); + @"Setting textHaloWidth to a camera expression should update text-halo-width."); + XCTAssertEqualObjects(layer.textHaloWidth, functionExpression, + @"textHaloWidth should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.textHaloWidth = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.textHaloWidth = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getTextHaloWidth(), propertyValue, - @"Setting textHaloWidth to a source function should update text-halo-width."); - XCTAssertEqualObjects(layer.textHaloWidth, functionStyleValue, - @"textHaloWidth should round-trip source functions."); + @"Setting textHaloWidth to a data expression should update text-halo-width."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.textHaloWidth, pedanticFunctionExpression, + @"textHaloWidth should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.textHaloWidth = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.textHaloWidth = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -2241,15 +2480,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getTextHaloWidth(), propertyValue, - @"Setting textHaloWidth to a composite function should update text-halo-width."); - XCTAssertEqualObjects(layer.textHaloWidth, functionStyleValue, - @"textHaloWidth should round-trip composite functions."); + @"Setting textHaloWidth to a camera-data expression should update text-halo-width."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.textHaloWidth, pedanticFunctionExpression, + @"textHaloWidth should round-trip camera-data expressions."); layer.textHaloWidth = nil; XCTAssertTrue(rawLayer->getTextHaloWidth().isUndefined(), @"Unsetting textHaloWidth should return text-halo-width to the default value."); - XCTAssertEqualObjects(layer.textHaloWidth, defaultStyleValue, + XCTAssertEqualObjects(layer.textHaloWidth, defaultExpression, @"textHaloWidth should return the default value after being unset."); // Transition property test layer.textHaloWidthTransition = transitionTest; @@ -2266,40 +2506,45 @@ { XCTAssertTrue(rawLayer->getTextOpacity().isUndefined(), @"text-opacity should be unset initially."); - MGLStyleValue<NSNumber *> *defaultStyleValue = layer.textOpacity; + NSExpression *defaultExpression = layer.textOpacity; - MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; - layer.textOpacity = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.textOpacity = constantExpression; mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getTextOpacity(), propertyValue, - @"Setting textOpacity to a constant value should update text-opacity."); - XCTAssertEqualObjects(layer.textOpacity, constantStyleValue, - @"textOpacity should round-trip constant values."); - - MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.textOpacity = functionStyleValue; - - mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} }; + @"Setting textOpacity to a constant value expression should update text-opacity."); + XCTAssertEqualObjects(layer.textOpacity, constantExpression, + @"textOpacity should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.textOpacity = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; XCTAssertEqual(rawLayer->getTextOpacity(), propertyValue, - @"Setting textOpacity to a camera function should update text-opacity."); - XCTAssertEqualObjects(layer.textOpacity, functionStyleValue, - @"textOpacity should round-trip camera functions."); + @"Setting textOpacity to a camera expression should update text-opacity."); + XCTAssertEqualObjects(layer.textOpacity, functionExpression, + @"textOpacity should round-trip camera expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; - layer.textOpacity = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.textOpacity = functionExpression; mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; XCTAssertEqual(rawLayer->getTextOpacity(), propertyValue, - @"Setting textOpacity to a source function should update text-opacity."); - XCTAssertEqualObjects(layer.textOpacity, functionStyleValue, - @"textOpacity should round-trip source functions."); + @"Setting textOpacity to a data expression should update text-opacity."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.textOpacity, pedanticFunctionExpression, + @"textOpacity should round-trip data expressions."); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; - layer.textOpacity = functionStyleValue; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.textOpacity = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -2307,15 +2552,16 @@ propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; XCTAssertEqual(rawLayer->getTextOpacity(), propertyValue, - @"Setting textOpacity to a composite function should update text-opacity."); - XCTAssertEqualObjects(layer.textOpacity, functionStyleValue, - @"textOpacity should round-trip composite functions."); + @"Setting textOpacity to a camera-data expression should update text-opacity."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.textOpacity, pedanticFunctionExpression, + @"textOpacity should round-trip camera-data expressions."); layer.textOpacity = nil; XCTAssertTrue(rawLayer->getTextOpacity().isUndefined(), @"Unsetting textOpacity should return text-opacity to the default value."); - XCTAssertEqualObjects(layer.textOpacity, defaultStyleValue, + XCTAssertEqualObjects(layer.textOpacity, defaultExpression, @"textOpacity should return the default value after being unset."); // Transition property test layer.textOpacityTransition = transitionTest; @@ -2332,84 +2578,94 @@ { XCTAssertTrue(rawLayer->getTextTranslate().isUndefined(), @"text-translate should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.textTranslation; + NSExpression *defaultExpression = layer.textTranslation; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue: + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", #if TARGET_OS_IPHONE [NSValue valueWithCGVector:CGVectorMake(1, 1)] #else [NSValue valueWithMGLVector:CGVectorMake(1, -1)] #endif ]; - layer.textTranslation = constantStyleValue; + layer.textTranslation = constantExpression; mbgl::style::PropertyValue<std::array<float, 2>> propertyValue = { { 1, 1 } }; XCTAssertEqual(rawLayer->getTextTranslate(), propertyValue, - @"Setting textTranslation to a constant value should update text-translate."); - XCTAssertEqualObjects(layer.textTranslation, constantStyleValue, - @"textTranslation should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.textTranslation = functionStyleValue; - - mbgl::style::IntervalStops<std::array<float, 2>> intervalStops = { {{18, { 1, 1 }}} }; + @"Setting textTranslation to a constant value expression should update text-translate."); + XCTAssertEqualObjects(layer.textTranslation, constantExpression, + @"textTranslation should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"{1, 1}"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.textTranslation = functionExpression; + + mbgl::style::IntervalStops<std::array<float, 2>> intervalStops = {{ + { -INFINITY, { 1, 1 } }, + { 18, { 1, 1 } }, + }}; propertyValue = mbgl::style::CameraFunction<std::array<float, 2>> { intervalStops }; XCTAssertEqual(rawLayer->getTextTranslate(), propertyValue, - @"Setting textTranslation to a camera function should update text-translate."); - XCTAssertEqualObjects(layer.textTranslation, functionStyleValue, - @"textTranslation should round-trip camera functions."); + @"Setting textTranslation to a camera expression should update text-translate."); + XCTAssertEqualObjects(layer.textTranslation, functionExpression, + @"textTranslation should round-trip camera expressions."); layer.textTranslation = nil; XCTAssertTrue(rawLayer->getTextTranslate().isUndefined(), @"Unsetting textTranslation should return text-translate to the default value."); - XCTAssertEqualObjects(layer.textTranslation, defaultStyleValue, + XCTAssertEqualObjects(layer.textTranslation, defaultExpression, @"textTranslation should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.textTranslation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.textTranslation = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.textTranslation = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.textTranslation = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } // text-translate-anchor { XCTAssertTrue(rawLayer->getTextTranslateAnchor().isUndefined(), @"text-translate-anchor should be unset initially."); - MGLStyleValue<NSValue *> *defaultStyleValue = layer.textTranslationAnchor; + NSExpression *defaultExpression = layer.textTranslationAnchor; - MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLTextTranslationAnchor:MGLTextTranslationAnchorViewport]]; - layer.textTranslationAnchor = constantStyleValue; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + layer.textTranslationAnchor = constantExpression; mbgl::style::PropertyValue<mbgl::style::TranslateAnchorType> propertyValue = { mbgl::style::TranslateAnchorType::Viewport }; XCTAssertEqual(rawLayer->getTextTranslateAnchor(), propertyValue, - @"Setting textTranslationAnchor to a constant value should update text-translate-anchor."); - XCTAssertEqualObjects(layer.textTranslationAnchor, constantStyleValue, - @"textTranslationAnchor should round-trip constant values."); - - MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; - layer.textTranslationAnchor = functionStyleValue; - - mbgl::style::IntervalStops<mbgl::style::TranslateAnchorType> intervalStops = { {{18, mbgl::style::TranslateAnchorType::Viewport}} }; + @"Setting textTranslationAnchor to a constant value expression should update text-translate-anchor."); + XCTAssertEqualObjects(layer.textTranslationAnchor, constantExpression, + @"textTranslationAnchor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"'viewport'"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.textTranslationAnchor = functionExpression; + + mbgl::style::IntervalStops<mbgl::style::TranslateAnchorType> intervalStops = {{ + { -INFINITY, mbgl::style::TranslateAnchorType::Viewport }, + { 18, mbgl::style::TranslateAnchorType::Viewport }, + }}; propertyValue = mbgl::style::CameraFunction<mbgl::style::TranslateAnchorType> { intervalStops }; XCTAssertEqual(rawLayer->getTextTranslateAnchor(), propertyValue, - @"Setting textTranslationAnchor to a camera function should update text-translate-anchor."); - XCTAssertEqualObjects(layer.textTranslationAnchor, functionStyleValue, - @"textTranslationAnchor should round-trip camera functions."); + @"Setting textTranslationAnchor to a camera expression should update text-translate-anchor."); + XCTAssertEqualObjects(layer.textTranslationAnchor, functionExpression, + @"textTranslationAnchor should round-trip camera expressions."); layer.textTranslationAnchor = nil; XCTAssertTrue(rawLayer->getTextTranslateAnchor().isUndefined(), @"Unsetting textTranslationAnchor should return text-translate-anchor to the default value."); - XCTAssertEqualObjects(layer.textTranslationAnchor, defaultStyleValue, + XCTAssertEqualObjects(layer.textTranslationAnchor, defaultExpression, @"textTranslationAnchor should return the default value after being unset."); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.textTranslationAnchor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.textTranslationAnchor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.textTranslationAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.textTranslationAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } } diff --git a/platform/darwin/test/MGLTileSetTests.mm b/platform/darwin/test/MGLTileSetTests.mm index 4d5e1fcd05..2319f66447 100644 --- a/platform/darwin/test/MGLTileSetTests.mm +++ b/platform/darwin/test/MGLTileSetTests.mm @@ -66,8 +66,16 @@ // when the tile set has attribution infos MGLAttributionInfo *mapboxInfo = [[MGLAttributionInfo alloc] initWithTitle:[[NSAttributedString alloc] initWithString:@"Mapbox"] URL:[NSURL URLWithString:@"https://www.mapbox.com/"]]; +#if TARGET_OS_IPHONE + UIColor *redColor = [UIColor redColor]; +#else + // CSS uses the sRGB color space. In macOS 10.12 Sierra and below, + // -[NSColor redColor] is in the calibrated RGB space and has a slightly + // different sRGB value than on iOS and macOS 10.13 High Sierra. + NSColor *redColor = [NSColor colorWithSRGBRed:1 green:0 blue:0 alpha:1]; +#endif NSAttributedString *gl = [[NSAttributedString alloc] initWithString:@"GL" attributes:@{ - NSBackgroundColorAttributeName: [MGLColor redColor], + NSBackgroundColorAttributeName: redColor, }]; MGLAttributionInfo *glInfo = [[MGLAttributionInfo alloc] initWithTitle:gl URL:nil]; tileSet = MGLTileSetFromTileURLTemplates(tileURLTemplates, @{ @@ -82,7 +90,7 @@ #else NSString *html = (@"<font face=\"Helvetica\" size=\"3\" style=\"font: 12.0px Helvetica\">" @"<a href=\"https://www.mapbox.com/\">Mapbox</a> </font>" - @"<font face=\"Helvetica\" size=\"3\" style=\"font: 12.0px Helvetica; background-color: #ff2600\">GL</font>\n"); + @"<font face=\"Helvetica\" size=\"3\" style=\"font: 12.0px Helvetica; background-color: #ff0000\">GL</font>\n"); #endif XCTAssertEqualObjects(@(tileSet.attribution.c_str()), html); @@ -102,6 +110,23 @@ // the scheme is reflected by the mbgl tileset XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::TMS); + + // when the dem enciding is changed using an NSNumber + tileSet = MGLTileSetFromTileURLTemplates(tileURLTemplates, @{ + MGLTileSourceOptionDEMEncoding: @(MGLDEMEncodingTerrarium), + }); + + // the encoding is reflected by the mbgl tileset + XCTAssertEqual(tileSet.encoding, mbgl::Tileset::DEMEncoding::Terrarium); + + // when the dem enciding is changed using an NSValue + MGLDEMEncoding terrarium = MGLDEMEncodingTerrarium; + tileSet = MGLTileSetFromTileURLTemplates(tileURLTemplates, @{ + MGLTileSourceOptionDEMEncoding: [NSValue value:&terrarium withObjCType:@encode(MGLDEMEncoding)], + }); + + // the encoding is reflected by the mbgl tileset + XCTAssertEqual(tileSet.encoding, mbgl::Tileset::DEMEncoding::Terrarium); } - (void)testInvalidTileSet { diff --git a/platform/darwin/test/test-Bridging-Header.h b/platform/darwin/test/test-Bridging-Header.h index 5d23e9d6c5..1b2cb5d6d0 100644 --- a/platform/darwin/test/test-Bridging-Header.h +++ b/platform/darwin/test/test-Bridging-Header.h @@ -1,4 +1,4 @@ // // Use this file to import your target's public headers that you would like to expose to Swift. // -#import "MGLStyleValueTests.h" + |