diff options
Diffstat (limited to 'platform/darwin/test/MGLSymbolStyleLayerTests.mm')
-rw-r--r-- | platform/darwin/test/MGLSymbolStyleLayerTests.mm | 2285 |
1 files changed, 1255 insertions, 1030 deletions
diff --git a/platform/darwin/test/MGLSymbolStyleLayerTests.mm b/platform/darwin/test/MGLSymbolStyleLayerTests.mm index 60d674f2ed..05e091b5c8 100644 --- a/platform/darwin/test/MGLSymbolStyleLayerTests.mm +++ b/platform/darwin/test/MGLSymbolStyleLayerTests.mm @@ -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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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,50 @@ { 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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', '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."); + XCTAssertEqualObjects(layer.iconOffset, functionExpression, + @"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:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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 +267,15 @@ 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."); + XCTAssertEqualObjects(layer.iconOffset, functionExpression, + @"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 +283,176 @@ { 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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', '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."); + XCTAssertEqualObjects(layer.iconRotation, functionExpression, + @"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:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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 +460,15 @@ 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."); + XCTAssertEqualObjects(layer.iconRotation, functionExpression, + @"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 +476,88 @@ { 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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', '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."); + XCTAssertEqualObjects(layer.iconScale, functionExpression, + @"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:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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 +565,15 @@ 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."); + XCTAssertEqualObjects(layer.iconScale, functionExpression, + @"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 +581,270 @@ { 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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', '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."); + XCTAssertEqualObjects(layer.maximumTextWidth, functionExpression, + @"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:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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 +852,15 @@ 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."); + XCTAssertEqualObjects(layer.maximumTextWidth, functionExpression, + @"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 +868,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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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 +1038,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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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,33 +1120,37 @@ { 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; + 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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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."); } @@ -1047,40 +1158,44 @@ { 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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', '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."); + XCTAssertEqualObjects(layer.textFontSize, functionExpression, + @"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:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.textFontSize = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -1088,15 +1203,15 @@ 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."); + XCTAssertEqualObjects(layer.textFontSize, functionExpression, + @"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."); } @@ -1104,72 +1219,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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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."); } @@ -1177,40 +1301,44 @@ { 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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', '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."); + XCTAssertEqualObjects(layer.textLetterSpacing, functionExpression, + @"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:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.textLetterSpacing = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -1218,15 +1346,15 @@ 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."); + XCTAssertEqualObjects(layer.textLetterSpacing, functionExpression, + @"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."); } @@ -1234,85 +1362,94 @@ { 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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', '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."); + XCTAssertEqualObjects(layer.textOffset, functionExpression, + @"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:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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 }; @@ -1320,15 +1457,15 @@ 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."); + XCTAssertEqualObjects(layer.textOffset, functionExpression, + @"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."); } @@ -1336,157 +1473,176 @@ { 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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', '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."); + XCTAssertEqualObjects(layer.textRotation, functionExpression, + @"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:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.textRotation = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -1494,15 +1650,15 @@ 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."); + XCTAssertEqualObjects(layer.textRotation, functionExpression, + @"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."); } @@ -1510,72 +1666,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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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."); } @@ -1583,40 +1748,44 @@ { 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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', '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."); + XCTAssertEqualObjects(layer.iconColor, functionExpression, + @"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:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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 }; @@ -1624,15 +1793,15 @@ 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."); + XCTAssertEqualObjects(layer.iconColor, functionExpression, + @"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; @@ -1649,40 +1818,44 @@ { 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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', '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."); + XCTAssertEqualObjects(layer.iconHaloBlur, functionExpression, + @"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:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.iconHaloBlur = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -1690,15 +1863,15 @@ 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."); + XCTAssertEqualObjects(layer.iconHaloBlur, functionExpression, + @"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; @@ -1715,40 +1888,44 @@ { 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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', '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."); + XCTAssertEqualObjects(layer.iconHaloColor, functionExpression, + @"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:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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 }; @@ -1756,15 +1933,15 @@ 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."); + XCTAssertEqualObjects(layer.iconHaloColor, functionExpression, + @"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; @@ -1781,40 +1958,44 @@ { 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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', '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."); + XCTAssertEqualObjects(layer.iconHaloWidth, functionExpression, + @"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:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.iconHaloWidth = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -1822,15 +2003,15 @@ 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."); + XCTAssertEqualObjects(layer.iconHaloWidth, functionExpression, + @"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; @@ -1847,40 +2028,44 @@ { 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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', '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."); + XCTAssertEqualObjects(layer.iconOpacity, functionExpression, + @"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:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.iconOpacity = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -1888,15 +2073,15 @@ 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."); + XCTAssertEqualObjects(layer.iconOpacity, functionExpression, + @"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; @@ -1913,124 +2098,138 @@ { 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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', '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."); + XCTAssertEqualObjects(layer.textColor, functionExpression, + @"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:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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 }; @@ -2038,15 +2237,15 @@ 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."); + XCTAssertEqualObjects(layer.textColor, functionExpression, + @"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; @@ -2063,40 +2262,44 @@ { 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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', '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."); + XCTAssertEqualObjects(layer.textHaloBlur, functionExpression, + @"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:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.textHaloBlur = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -2104,15 +2307,15 @@ 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."); + XCTAssertEqualObjects(layer.textHaloBlur, functionExpression, + @"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; @@ -2129,40 +2332,44 @@ { 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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', '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."); + XCTAssertEqualObjects(layer.textHaloColor, functionExpression, + @"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:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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 }; @@ -2170,15 +2377,15 @@ 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."); + XCTAssertEqualObjects(layer.textHaloColor, functionExpression, + @"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; @@ -2195,40 +2402,44 @@ { 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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', '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."); + XCTAssertEqualObjects(layer.textHaloWidth, functionExpression, + @"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:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.textHaloWidth = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -2236,15 +2447,15 @@ 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."); + XCTAssertEqualObjects(layer.textHaloWidth, functionExpression, + @"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; @@ -2261,40 +2472,44 @@ { 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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', '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."); + XCTAssertEqualObjects(layer.textOpacity, functionExpression, + @"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:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.textOpacity = functionExpression; std::map<float, float> innerStops { {18, 0xff} }; mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; @@ -2302,15 +2517,15 @@ 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."); + XCTAssertEqualObjects(layer.textOpacity, functionExpression, + @"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; @@ -2327,84 +2542,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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", 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:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', '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."); } } |